diff options
Diffstat (limited to 'src/frontend/app/routes')
| -rw-r--r-- | src/frontend/app/routes/estimates-$id.tsx | 54 | ||||
| -rw-r--r-- | src/frontend/app/routes/map.tsx | 8 | ||||
| -rw-r--r-- | src/frontend/app/routes/settings.tsx | 22 | ||||
| -rw-r--r-- | src/frontend/app/routes/stoplist.tsx | 16 | ||||
| -rw-r--r-- | src/frontend/app/routes/timetable-$id.tsx | 35 |
5 files changed, 99 insertions, 36 deletions
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<StopDetails> => { - const resp = await fetch(`/api/GetStopEstimates?id=${stopId}`, { +const loadData = async (region: RegionId, stopId: string): Promise<StopDetails> => { + 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<StopDetails> => { return await resp.json(); }; -const loadTimetableData = async (stopId: string): Promise<TimetableEntry[]> => { +const loadTimetableData = async (region: RegionId, stopId: string): Promise<TimetableEntry[]> => { + 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" ? ( - <GroupedTable data={data} dataDate={dataDate} /> + <GroupedTable data={data} dataDate={dataDate} regionConfig={regionConfig} /> ) : ( - <RegularTable data={data} dataDate={dataDate} /> + <RegularTable data={data} dataDate={dataDate} regionConfig={regionConfig} /> ) ) : null} </div> 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<MapRef>(null); const [mapStyleKey, setMapStyleKey] = useState<string>("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,8 +13,12 @@ export default function Settings() { setTableStyle, mapPositionMode, setMapPositionMode, + region, + setRegion, } = useApp(); + const regions = getAvailableRegions(); + return ( <div className="page-container"> <h1 className="page-title">{t("about.title")}</h1> @@ -21,6 +26,23 @@ export default function Settings() { <section className="settings-section"> <h2>{t("about.settings")}</h2> <div className="settings-content-inline"> + <label htmlFor="region" className="form-label-inline"> + Región: + </label> + <select + id="region" + className="form-select-inline" + value={region} + onChange={(e) => setRegion(e.target.value as any)} + > + {regions.map((r) => ( + <option key={r.id} value={r.id}> + {r.name} + </option> + ))} + </select> + </div> + <div className="settings-content-inline"> <label htmlFor="theme" className="form-label-inline"> {t("about.theme")} </label> diff --git a/src/frontend/app/routes/stoplist.tsx b/src/frontend/app/routes/stoplist.tsx index 8b0ebe2..13d3584 100644 --- a/src/frontend/app/routes/stoplist.tsx +++ b/src/frontend/app/routes/stoplist.tsx @@ -5,9 +5,11 @@ import StopItemSkeleton from "../components/StopItemSkeleton"; import Fuse from "fuse.js"; import "./stoplist.css"; import { useTranslation } from "react-i18next"; +import { useApp } from "../AppContext"; export default function StopList() { const { t } = useTranslation(); + const { region } = useApp(); const [data, setData] = useState<Stop[] | null>(null); const [loading, setLoading] = useState(true); const [searchResults, setSearchResults] = useState<Stop[] | null>(null); @@ -29,19 +31,19 @@ export default function StopList() { // Load favourite and recent IDs immediately from localStorage useEffect(() => { - setFavouriteIds(StopDataProvider.getFavouriteIds()); - setRecentIds(StopDataProvider.getRecent()); - }, []); + setFavouriteIds(StopDataProvider.getFavouriteIds(region)); + setRecentIds(StopDataProvider.getRecent(region)); + }, [region]); // Load stops from network const loadStops = useCallback(async () => { try { setLoading(true); - const stops = await StopDataProvider.loadStopsFromNetwork(); + const stops = await StopDataProvider.loadStopsFromNetwork(region); // Add favourite flags to stops - const favouriteStopsIds = StopDataProvider.getFavouriteIds(); + const favouriteStopsIds = StopDataProvider.getFavouriteIds(region); const stopsWithFavourites = stops.map(stop => ({ ...stop, favourite: favouriteStopsIds.includes(stop.stopId) @@ -55,7 +57,7 @@ export default function StopList() { ); setFavouriteStops(favStops); - const recIds = StopDataProvider.getRecent(); + const recIds = StopDataProvider.getRecent(region); const recStops = recIds .map(id => stopsWithFavourites.find(stop => stop.stopId === id)) .filter(Boolean) as Stop[]; @@ -66,7 +68,7 @@ export default function StopList() { } finally { setLoading(false); } - }, []); + }, [region]); useEffect(() => { loadStops(); diff --git a/src/frontend/app/routes/timetable-$id.tsx b/src/frontend/app/routes/timetable-$id.tsx index cb55f53..1942ce8 100644 --- a/src/frontend/app/routes/timetable-$id.tsx +++ b/src/frontend/app/routes/timetable-$id.tsx @@ -7,6 +7,8 @@ import { TimetableSkeleton } from "../components/TimetableSkeleton"; import { ErrorDisplay } from "../components/ErrorDisplay"; import LineIcon from "../components/LineIcon"; import { useTranslation } from "react-i18next"; +import { type RegionId, getRegionConfig } from "../data/RegionConfig"; +import { useApp } from "../AppContext"; import "./timetable-$id.css"; interface ErrorInfo { @@ -15,12 +17,19 @@ interface ErrorInfo { message?: string; } -const loadTimetableData = async (stopId: string): Promise<TimetableEntry[]> => { +const loadTimetableData = async (region: RegionId, stopId: string): Promise<TimetableEntry[]> => { + const regionConfig = getRegionConfig(region); + + // Check if timetable is available for this region + if (!regionConfig.timetableEndpoint) { + throw new Error("Timetable not available for this region"); + } + // Add delay to see skeletons in action (remove in production) await new Promise(resolve => setTimeout(resolve, 1000)); 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", }, @@ -99,6 +108,7 @@ const parseServiceId = (serviceId: string): string => { export default function Timetable() { const { t } = useTranslation(); + const { region } = useApp(); const params = useParams(); const stopIdNum = parseInt(params.id ?? ""); const [timetableData, setTimetableData] = useState<TimetableEntry[]>([]); @@ -107,6 +117,7 @@ export default function Timetable() { const [error, setError] = useState<ErrorInfo | null>(null); const [showPastEntries, setShowPastEntries] = useState(false); const nextEntryRef = useRef<HTMLDivElement>(null); + const regionConfig = getRegionConfig(region); const currentTime = new Date().toTimeString().slice(0, 8); // HH:MM:SS const filteredData = filterTimetableData(timetableData, currentTime, showPastEntries); @@ -130,11 +141,22 @@ export default function Timetable() { }; const loadData = async () => { + // Check if timetable is available for this region + if (!regionConfig.timetableEndpoint) { + setError({ + type: 'server', + status: 501, + message: 'Timetable not available for this region' + }); + setLoading(false); + return; + } + try { setLoading(true); setError(null); - const timetableBody = await loadTimetableData(params.id!); + const timetableBody = await loadTimetableData(region, params.id!); setTimetableData(timetableBody); if (timetableBody.length > 0) { @@ -168,8 +190,8 @@ export default function Timetable() { useEffect(() => { loadData(); - setCustomName(StopDataProvider.getCustomName(stopIdNum)); - }, [params.id]); + setCustomName(StopDataProvider.getCustomName(region, stopIdNum)); + }, [params.id, region]); if (loading) { return ( @@ -266,6 +288,7 @@ const TimetableTableWithScroll: React.FC<{ nextEntryRef: React.RefObject<HTMLDivElement | null>; }> = ({ data, showAll, currentTime, nextEntryRef }) => { const { t } = useTranslation(); + const { region } = useApp(); const nowMinutes = timeToMinutes(currentTime); return ( @@ -295,7 +318,7 @@ const TimetableTableWithScroll: React.FC<{ > <div className="card-header"> <div className="line-info"> - <LineIcon line={entry.line.name} /> + <LineIcon line={entry.line.name} region={region} /> </div> <div className="destination-info"> |
