From a68ba30716062b265f85c4be078a736c7135d7bc Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Sun, 30 Nov 2025 20:49:48 +0100 Subject: Refactor StopMap and Settings components; replace region config usage with REGION_DATA, update StopDataProvider calls, and improve UI elements. Remove unused timetable files and add Tailwind CSS support. --- src/frontend/app/routes/estimates-$id.tsx | 374 ------------------------------ 1 file changed, 374 deletions(-) delete mode 100644 src/frontend/app/routes/estimates-$id.tsx (limited to 'src/frontend/app/routes/estimates-$id.tsx') diff --git a/src/frontend/app/routes/estimates-$id.tsx b/src/frontend/app/routes/estimates-$id.tsx deleted file mode 100644 index afeb3d2..0000000 --- a/src/frontend/app/routes/estimates-$id.tsx +++ /dev/null @@ -1,374 +0,0 @@ -import { Edit2, ExternalLink, RefreshCw, Star } from "lucide-react"; -import { useCallback, useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Link, Navigate, useParams } from "react-router"; -import { ErrorDisplay } from "~/components/ErrorDisplay"; -import LineIcon from "~/components/LineIcon"; -import { PullToRefresh } from "~/components/PullToRefresh"; -import { - type ScheduledTable, - SchedulesTable, -} from "~/components/SchedulesTable"; -import { - EstimatesGroupedSkeleton, - SchedulesTableSkeleton, -} from "~/components/SchedulesTableSkeleton"; -import { StopAlert } from "~/components/StopAlert"; -import { TimetableSkeleton } from "~/components/TimetableSkeleton"; -import { type RegionId, getRegionConfig } from "~/config/RegionConfig"; -import { useAutoRefresh } from "~/hooks/useAutoRefresh"; -import { useApp } from "../AppContext"; -import { GroupedTable } from "../components/GroupedTable"; -import { RegularTable } from "../components/RegularTable"; -import StopDataProvider, { type Stop } from "../data/StopDataProvider"; -import "./estimates-$id.css"; - -export interface Estimate { - line: string; - route: string; - minutes: number; - meters: number; -} - -interface ErrorInfo { - type: "network" | "server" | "unknown"; - status?: number; - message?: string; -} - -const loadData = async ( - region: RegionId, - stopId: string -): Promise => { - const regionConfig = getRegionConfig(region); - const resp = await fetch(`${regionConfig.estimatesEndpoint}?id=${stopId}`, { - headers: { - Accept: "application/json", - }, - }); - - if (!resp.ok) { - throw new Error(`HTTP ${resp.status}: ${resp.statusText}`); - } - - return await resp.json(); -}; - -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"); - } - - // Use "today" to let server determine date based on Europe/Madrid timezone - const resp = await fetch( - `${regionConfig.timetableEndpoint}?date=today&stopId=${stopId}`, - { - headers: { - Accept: "application/json", - }, - } - ); - - if (!resp.ok) { - throw new Error(`HTTP ${resp.status}: ${resp.statusText}`); - } - - return await resp.json(); -}; - -export default function Estimates() { - const { t } = useTranslation(); - const params = useParams(); - const stopIdNum = parseInt(params.id ?? ""); - const [customName, setCustomName] = useState(undefined); - const [stopData, setStopData] = useState(undefined); - - // Estimates data state - const [data, setData] = useState(null); - const [dataDate, setDataDate] = useState(null); - const [estimatesLoading, setEstimatesLoading] = useState(true); - const [estimatesError, setEstimatesError] = useState(null); - - // Timetable data state - const [timetableData, setTimetableData] = useState([]); - const [timetableLoading, setTimetableLoading] = useState(true); - const [timetableError, setTimetableError] = useState(null); - - const [favourited, setFavourited] = useState(false); - const [isManualRefreshing, setIsManualRefreshing] = useState(false); - const { tableStyle, region } = useApp(); - const regionConfig = getRegionConfig(region); - - // Redirect to /stops/$id if table style is experimental_consolidated - if (tableStyle === "experimental_consolidated") { - return ; - } - - const parseError = (error: any): ErrorInfo => { - if (!navigator.onLine) { - return { type: "network", message: "No internet connection" }; - } - - if ( - error.message?.includes("Failed to fetch") || - error.message?.includes("NetworkError") - ) { - return { type: "network" }; - } - - if (error.message?.includes("HTTP")) { - const statusMatch = error.message.match(/HTTP (\d+):/); - const status = statusMatch ? parseInt(statusMatch[1]) : undefined; - return { type: "server", status }; - } - - return { type: "unknown", message: error.message }; - }; - - const loadEstimatesData = useCallback(async () => { - try { - setEstimatesLoading(true); - setEstimatesError(null); - - const body = await loadData(region, params.id!); - setData(body); - setDataDate(new Date()); - - // Load stop data from StopDataProvider - const stop = await StopDataProvider.getStopById(region, stopIdNum); - setStopData(stop); - setCustomName(StopDataProvider.getCustomName(region, stopIdNum)); - } catch (error) { - console.error("Error loading estimates data:", error); - setEstimatesError(parseError(error)); - setData(null); - setDataDate(null); - } finally { - setEstimatesLoading(false); - } - }, [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(region, params.id!); - setTimetableData(timetableBody); - } catch (error) { - console.error("Error loading timetable data:", error); - setTimetableError(parseError(error)); - setTimetableData([]); - } finally { - setTimetableLoading(false); - } - }, [params.id, region, regionConfig.timetableEndpoint]); - - // Manual refresh function for pull-to-refresh and button - const handleManualRefresh = useCallback(async () => { - try { - setIsManualRefreshing(true); - // Only reload real-time estimates data, not timetable - await loadEstimatesData(); - } finally { - setIsManualRefreshing(false); - } - }, [loadEstimatesData]); - - // Auto-refresh estimates data every 30 seconds (only if not in error state) - useAutoRefresh({ - onRefresh: loadEstimatesData, - interval: 30000, - enabled: !estimatesError, - }); - - useEffect(() => { - // Initial load - 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(region, stopIdNum); - setFavourited(false); - } else { - StopDataProvider.addFavourite(region, stopIdNum); - setFavourited(true); - } - }; - - // Helper function to get the display name for the stop - const getStopDisplayName = () => { - if (customName) return customName; - if (stopData?.name.intersect) return stopData.name.intersect; - if (stopData?.name.original) return stopData.name.original; - return `Parada ${stopIdNum}`; - }; - - const handleRename = () => { - const current = getStopDisplayName(); - const input = window.prompt("Custom name for this stop:", current); - if (input === null) return; // cancelled - const trimmed = input.trim(); - if (trimmed === "") { - StopDataProvider.removeCustomName(region, stopIdNum); - setCustomName(undefined); - } else { - StopDataProvider.setCustomName(region, stopIdNum, trimmed); - setCustomName(trimmed); - } - }; - - // Show loading skeleton while initial data is loading - if (estimatesLoading && !data) { - return ( - -
-
-

- - - {t("common.loading")}... -

-
- -
- {tableStyle === "grouped" ? ( - - ) : ( - - )} -
- -
- -
-
-
- ); - } - - return ( - -
-
-

- - - {getStopDisplayName()}{" "} - ({stopIdNum}) -

- - -
- - {stopData && stopData.lines && stopData.lines.length > 0 && ( -
- {stopData.lines.map((line) => ( -
- -
- ))} -
- )} - - {stopData && } - -
- {estimatesLoading ? ( - tableStyle === "grouped" ? ( - - ) : ( - - ) - ) : estimatesError ? ( - - ) : data ? ( - tableStyle === "grouped" ? ( - - ) : ( - - ) - ) : null} -
- -
- {timetableLoading ? ( - - ) : timetableError ? ( - - ) : timetableData.length > 0 ? ( - <> - -
- - - {t("timetable.viewAll", "Ver todos los horarios")} - -
- - ) : null} -
-
-
- ); -} -- cgit v1.3