From 1b54ef6a7da4b2d356a2a33abf98cbb7bf39df2f Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Mon, 12 May 2025 17:57:33 +0200 Subject: Fix bugs, add new setting, make app great again --- src/AppContext.tsx | 50 +++++++++++++++++++++++++++++++++++++++- src/components/GroupedTable.tsx | 2 +- src/pages/Settings.tsx | 23 +++++++++++-------- src/pages/StopList.tsx | 51 +++++++++++++++++++++++++++-------------- 4 files changed, 98 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/AppContext.tsx b/src/AppContext.tsx index a9af208..8b4ffe2 100644 --- a/src/AppContext.tsx +++ b/src/AppContext.tsx @@ -4,6 +4,7 @@ import { LatLngTuple } from 'leaflet'; type Theme = 'light' | 'dark'; type TableStyle = 'regular'|'grouped'; +type MapPositionMode = 'gps' | 'last'; interface MapState { center: LatLngTuple; @@ -27,6 +28,9 @@ interface AppContextProps { setUserLocation: (location: LatLngTuple | null) => void; setLocationPermission: (hasPermission: boolean) => void; updateMapState: (center: LatLngTuple, zoom: number) => void; + + mapPositionMode: MapPositionMode; + setMapPositionMode: (mode: MapPositionMode) => void; } // Coordenadas por defecto centradas en Vigo @@ -74,6 +78,17 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { }, [tableStyle]); //#endregion + //#region Map Position Mode + const [mapPositionMode, setMapPositionMode] = useState(() => { + const saved = localStorage.getItem('mapPositionMode'); + return saved === 'last' ? 'last' : 'gps'; + }); + + useEffect(() => { + localStorage.setItem('mapPositionMode', mapPositionMode); + }, [mapPositionMode]); + //#endregion + //#region Map State const [mapState, setMapState] = useState(() => { const savedMapState = localStorage.getItem('mapState'); @@ -98,6 +113,37 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { }; }); + // Helper: check if coordinates are within Vigo bounds + function isWithinVigo([lat, lng]: LatLngTuple): boolean { + // Rough bounding box for Vigo + return lat >= 42.18 && lat <= 42.30 && lng >= -8.78 && lng <= -8.65; + } + + // On app load, if mapPositionMode is 'gps', try to get GPS and set map center + useEffect(() => { + if (mapPositionMode === 'gps') { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + (position) => { + const { latitude, longitude } = position.coords; + const coords: LatLngTuple = [latitude, longitude]; + if (isWithinVigo(coords)) { + setMapState(prev => { + const newState = { ...prev, center: coords, zoom: 16, userLocation: coords }; + localStorage.setItem('mapState', JSON.stringify(newState)); + return newState; + }); + } + }, + () => { + // Ignore error, fallback to last + } + ); + } + } + // If 'last', do nothing (already loaded from localStorage) + }, [mapPositionMode]); + const setMapCenter = (center: LatLngTuple) => { setMapState(prev => { const newState = { ...prev, center }; @@ -170,7 +216,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { setMapZoom, setUserLocation, setLocationPermission, - updateMapState + updateMapState, + mapPositionMode, + setMapPositionMode }}> {children} diff --git a/src/components/GroupedTable.tsx b/src/components/GroupedTable.tsx index b4f30a7..58bb5ed 100644 --- a/src/components/GroupedTable.tsx +++ b/src/components/GroupedTable.tsx @@ -46,7 +46,7 @@ export const GroupedTable: React.FC = ({ data, dataDate }) => { groupedEstimates[line].map((estimate, idx) => ( {idx === 0 && ( - + )} diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index e4a1a31..1ad15ab 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -2,7 +2,7 @@ import { useApp } from "../AppContext"; import "../styles/Settings.css"; export function Settings() { - const { theme, setTheme, tableStyle, setTableStyle } = useApp(); + const { theme, setTheme, tableStyle, setTableStyle, mapPositionMode, setMapPositionMode } = useApp(); return (
@@ -15,20 +15,25 @@ export function Settings() {

Ajustes

- setTheme(e.target.value as "light" | "dark")}>
- setTableStyle(e.target.value as "regular" | "grouped")}>
+
+ + +
¿Qué significa esto?

@@ -44,16 +49,16 @@ export function Settings() {

Créditos

- + Código en GitHub - - Desarrollado por + Desarrollado por Ariel Costas

- Datos obtenidos de datos.vigo.org bajo - licencia Open Data Commons Attribution License + Datos obtenidos de datos.vigo.org bajo + licencia Open Data Commons Attribution License

) diff --git a/src/pages/StopList.tsx b/src/pages/StopList.tsx index a2269ec..b965456 100644 --- a/src/pages/StopList.tsx +++ b/src/pages/StopList.tsx @@ -1,35 +1,54 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import StopDataProvider, { Stop } from "../data/StopDataProvider"; import StopItem from "../components/StopItem"; import Fuse from "fuse.js"; const placeholders = [ - "Urzaiz", - "Gran Vía", - "Castelao", - "García Barbón", - "Valladares", - "Florida", - "Pizarro", - "Estrada Madrid", - "Sanjurjo Badía" + "Urzaiz", + "Gran Vía", + "Castelao", + "García Barbón", + "Valladares", + "Florida", + "Pizarro", + "Estrada Madrid", + "Sanjurjo Badía" ]; export function StopList() { const [data, setData] = useState(null) const [searchResults, setSearchResults] = useState(null); + const searchTimeout = useRef(null); + + const randomPlaceholder = useMemo(() => placeholders[Math.floor(Math.random() * placeholders.length)], []); + const fuse = useMemo(() => new Fuse(data || [], { threshold: 0.3, keys: ['name.original'] }), [data]); useEffect(() => { StopDataProvider.getStops().then((stops: Stop[]) => setData(stops)) }, []); const handleStopSearch = (event: React.ChangeEvent) => { - const stopName = event.target.value; - if (data) { - const fuse = new Fuse(data, { keys: ['name'], threshold: 0.3 }); - const results = fuse.search(stopName).map(result => result.item); - setSearchResults(results); + const stopName = event.target.value || ""; + + if (searchTimeout.current) { + clearTimeout(searchTimeout.current); } + + searchTimeout.current = setTimeout(() => { + if (stopName.length === 0) { + setSearchResults(null); + return; + } + + if (!data) { + console.error("No data available for search"); + return; + } + + const results = fuse.search(stopName); + const items = results.map(result => result.item); + setSearchResults(items); + }, 300); } const favouritedStops = useMemo(() => { @@ -50,8 +69,6 @@ export function StopList() { if (data === null) return

Loading...

- const randomPlaceholder = placeholders[Math.floor(Math.random() * placeholders.length)]; - return (

UrbanoVigo Web

-- cgit v1.3