From 12ecc97b07093f3cac6567c70ff75d57b429c674 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Tue, 21 Oct 2025 17:38:01 +0200 Subject: Implement new Santiago region (WIP) --- src/frontend/app/routes/estimates-$id.tsx | 54 ++++++++++++++++++++----------- src/frontend/app/routes/map.tsx | 8 ++--- src/frontend/app/routes/settings.tsx | 22 +++++++++++++ src/frontend/app/routes/stoplist.tsx | 16 +++++---- src/frontend/app/routes/timetable-$id.tsx | 35 ++++++++++++++++---- 5 files changed, 99 insertions(+), 36 deletions(-) (limited to 'src/frontend/app/routes') diff --git a/src/frontend/app/routes/estimates-$id.tsx b/src/frontend/app/routes/estimates-$id.tsx index dc45198..c48932c 100644 --- a/src/frontend/app/routes/estimates-$id.tsx +++ b/src/frontend/app/routes/estimates-$id.tsx @@ -13,6 +13,7 @@ import { TimetableSkeleton } from "../components/TimetableSkeleton"; import { ErrorDisplay } from "../components/ErrorDisplay"; import { PullToRefresh } from "../components/PullToRefresh"; import { useAutoRefresh } from "../hooks/useAutoRefresh"; +import { type RegionId, getRegionConfig } from "../data/RegionConfig"; export interface StopDetails { stop: { @@ -35,8 +36,9 @@ interface ErrorInfo { message?: string; } -const loadData = async (stopId: string): Promise => { - const resp = await fetch(`/api/GetStopEstimates?id=${stopId}`, { +const loadData = async (region: RegionId, stopId: string): Promise => { + const regionConfig = getRegionConfig(region); + const resp = await fetch(`${regionConfig.estimatesEndpoint}?id=${stopId}`, { headers: { Accept: "application/json", }, @@ -49,9 +51,16 @@ const loadData = async (stopId: string): Promise => { return await resp.json(); }; -const loadTimetableData = async (stopId: string): Promise => { +const loadTimetableData = async (region: RegionId, stopId: string): Promise => { + const regionConfig = getRegionConfig(region); + + // Check if timetable is available for this region + if (!regionConfig.timetableEndpoint) { + throw new Error("Timetable not available for this region"); + } + const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format - const resp = await fetch(`/api/GetStopTimetable?date=${today}&stopId=${stopId}`, { + const resp = await fetch(`${regionConfig.timetableEndpoint}?date=${today}&stopId=${stopId}`, { headers: { Accept: "application/json", }, @@ -83,7 +92,8 @@ export default function Estimates() { const [favourited, setFavourited] = useState(false); const [isManualRefreshing, setIsManualRefreshing] = useState(false); - const { tableStyle } = useApp(); + const { tableStyle, region } = useApp(); + const regionConfig = getRegionConfig(region); const parseError = (error: any): ErrorInfo => { if (!navigator.onLine) { @@ -108,10 +118,10 @@ export default function Estimates() { setEstimatesLoading(true); setEstimatesError(null); - const body = await loadData(params.id!); + const body = await loadData(region, params.id!); setData(body); setDataDate(new Date()); - setCustomName(StopDataProvider.getCustomName(stopIdNum)); + setCustomName(StopDataProvider.getCustomName(region, stopIdNum)); } catch (error) { console.error('Error loading estimates data:', error); setEstimatesError(parseError(error)); @@ -120,14 +130,20 @@ export default function Estimates() { } finally { setEstimatesLoading(false); } - }, [params.id, stopIdNum]); + }, [params.id, stopIdNum, region]); const loadTimetableDataAsync = useCallback(async () => { + // Skip loading timetable if not available for this region + if (!regionConfig.timetableEndpoint) { + setTimetableLoading(false); + return; + } + try { setTimetableLoading(true); setTimetableError(null); - const timetableBody = await loadTimetableData(params.id!); + const timetableBody = await loadTimetableData(region, params.id!); setTimetableData(timetableBody); } catch (error) { console.error('Error loading timetable data:', error); @@ -136,7 +152,7 @@ export default function Estimates() { } finally { setTimetableLoading(false); } - }, [params.id]); + }, [params.id, region, regionConfig.timetableEndpoint]); const refreshData = useCallback(async () => { await Promise.all([ @@ -168,16 +184,16 @@ export default function Estimates() { loadEstimatesData(); loadTimetableDataAsync(); - StopDataProvider.pushRecent(parseInt(params.id ?? "")); - setFavourited(StopDataProvider.isFavourite(parseInt(params.id ?? ""))); - }, [params.id, loadEstimatesData, loadTimetableDataAsync]); + StopDataProvider.pushRecent(region, parseInt(params.id ?? "")); + setFavourited(StopDataProvider.isFavourite(region, parseInt(params.id ?? ""))); + }, [params.id, region, loadEstimatesData, loadTimetableDataAsync]); const toggleFavourite = () => { if (favourited) { - StopDataProvider.removeFavourite(stopIdNum); + StopDataProvider.removeFavourite(region, stopIdNum); setFavourited(false); } else { - StopDataProvider.addFavourite(stopIdNum); + StopDataProvider.addFavourite(region, stopIdNum); setFavourited(true); } }; @@ -188,10 +204,10 @@ export default function Estimates() { if (input === null) return; // cancelled const trimmed = input.trim(); if (trimmed === "") { - StopDataProvider.removeCustomName(stopIdNum); + StopDataProvider.removeCustomName(region, stopIdNum); setCustomName(undefined); } else { - StopDataProvider.setCustomName(stopIdNum, trimmed); + StopDataProvider.setCustomName(region, stopIdNum, trimmed); setCustomName(trimmed); } }; @@ -270,9 +286,9 @@ export default function Estimates() { /> ) : data ? ( tableStyle === "grouped" ? ( - + ) : ( - + ) ) : null} diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx index 56a9c79..c3a1308 100644 --- a/src/frontend/app/routes/map.tsx +++ b/src/frontend/app/routes/map.tsx @@ -38,7 +38,7 @@ export default function StopMap() { name: string; } | null>(null); const [isSheetOpen, setIsSheetOpen] = useState(false); - const { mapState, updateMapState, theme } = useApp(); + const { mapState, updateMapState, theme, region } = useApp(); const mapRef = useRef(null); const [mapStyleKey, setMapStyleKey] = useState("light"); @@ -56,7 +56,7 @@ export default function StopMap() { }; useEffect(() => { - StopDataProvider.getStops().then((data) => { + StopDataProvider.getStops(region).then((data) => { const features: GeoJsonFeature< Point, { stopId: number; name: string; lines: string[] } @@ -70,7 +70,7 @@ export default function StopMap() { })); setStops(features); }); - }, []); + }, [region]); useEffect(() => { //const styleName = "carto"; @@ -115,7 +115,7 @@ export default function StopMap() { const handlePointClick = (feature: any) => { const props: any = feature.properties; // fetch full stop to get lines array - StopDataProvider.getStopById(props.stopId).then((stop) => { + StopDataProvider.getStopById(region, props.stopId).then((stop) => { if (!stop) return; setSelectedStop({ stopId: stop.stopId, diff --git a/src/frontend/app/routes/settings.tsx b/src/frontend/app/routes/settings.tsx index bcda311..eae6ad8 100644 --- a/src/frontend/app/routes/settings.tsx +++ b/src/frontend/app/routes/settings.tsx @@ -2,6 +2,7 @@ import { type Theme, useApp } from "../AppContext"; import "./settings.css"; import { useTranslation } from "react-i18next"; import { useState } from "react"; +import { getAvailableRegions } from "../data/RegionConfig"; export default function Settings() { const { t, i18n } = useTranslation(); @@ -12,14 +13,35 @@ export default function Settings() { setTableStyle, mapPositionMode, setMapPositionMode, + region, + setRegion, } = useApp(); + const regions = getAvailableRegions(); + return (

{t("about.title")}

{t("about.description")}

{t("about.settings")}

+
+ + +