From 3227c7bc6bd233c92b1cf54bec689f0582dca547 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Mon, 1 Dec 2025 22:25:56 +0100 Subject: refactor: replace StopSheet with StopSummarySheet and update related components - Deleted StopSheet and StopSheetSkeleton components. - Introduced StopSummarySheet and StopSummarySheetSkeleton components. - Updated ConsolidatedCirculationCard to support a reduced view. - Modified ConsolidatedCirculationList to accept a reduced prop. - Adjusted map route to use StopSummarySheet. - Cleaned up CSS styles related to the stop sheet components. - Enhanced error handling and loading states in the new summary sheet. - Updated stop report logic to filter out empty next streets. --- src/frontend/app/components/StopSummarySheet.tsx | 208 +++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 src/frontend/app/components/StopSummarySheet.tsx (limited to 'src/frontend/app/components/StopSummarySheet.tsx') diff --git a/src/frontend/app/components/StopSummarySheet.tsx b/src/frontend/app/components/StopSummarySheet.tsx new file mode 100644 index 0000000..17c0afd --- /dev/null +++ b/src/frontend/app/components/StopSummarySheet.tsx @@ -0,0 +1,208 @@ +import { RefreshCw } from "lucide-react"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Sheet } from "react-modal-sheet"; +import { Link } from "react-router"; +import { ConsolidatedCirculationList } from "~/components/Stops/ConsolidatedCirculationList"; +import { REGION_DATA } from "~/config/RegionConfig"; +import type { Stop } from "~/data/StopDataProvider"; +import { type ConsolidatedCirculation } from "../routes/stops-$id"; +import { ErrorDisplay } from "./ErrorDisplay"; +import LineIcon from "./LineIcon"; +import { StopAlert } from "./StopAlert"; +import "./StopSummarySheet.css"; +import { StopSummarySheetSkeleton } from "./StopSummarySheetSkeleton"; + +interface StopSheetProps { + isOpen: boolean; + onClose: () => void; + stop: Stop; +} + +interface ErrorInfo { + type: "network" | "server" | "unknown"; + status?: number; + message?: string; +} + +const loadConsolidatedData = async ( + stopId: number +): Promise => { + const resp = await fetch( + `${REGION_DATA.consolidatedCirculationsEndpoint}?stopId=${stopId}`, + { + headers: { + Accept: "application/json", + }, + } + ); + + if (!resp.ok) { + throw new Error(`HTTP ${resp.status}: ${resp.statusText}`); + } + + return await resp.json(); +}; + +export const StopSheet: React.FC = ({ + isOpen, + onClose, + stop, +}) => { + const { t } = useTranslation(); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [lastUpdated, setLastUpdated] = useState(null); + + 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 loadData = async () => { + try { + setLoading(true); + setError(null); + setData(null); + + const stopData = await loadConsolidatedData(stop.stopId); + setData(stopData); + setLastUpdated(new Date()); + } catch (err) { + console.error("Failed to load stop data:", err); + setError(parseError(err)); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (isOpen && stop.stopId) { + loadData(); + } + }, [isOpen, stop.stopId]); + + // Show only the next 4 arrivals + const sortedData = data + ? [...data].sort( + (a, b) => + (a.realTime?.minutes ?? a.schedule?.minutes ?? 999) - + (b.realTime?.minutes ?? b.schedule?.minutes ?? 999) + ) + : []; + const limitedEstimates = sortedData.slice(0, 4); + + return ( + + + + +
+
+

{stop.name.original}

+ ({stop.stopId}) +
+ +
= 10 ? "scrollable" : ""}`} + > + {stop.lines.map((line) => ( +
+ +
+ ))} +
+ + + + {loading ? ( + + ) : error ? ( + + ) : data ? ( + <> +
+

+ {t("estimates.next_arrivals", "Next arrivals")} +

+ + {limitedEstimates.length === 0 ? ( +
+ {t("estimates.none", "No hay estimaciones disponibles")} +
+ ) : ( + + )} +
+ + ) : null} +
+
+ +
+ {lastUpdated && ( +
+ {t("estimates.last_updated", "Actualizado a las")}{" "} + {lastUpdated.toLocaleTimeString(undefined, { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + })} +
+ )} + +
+ + + + {t("map.view_all_estimates", "Ver todas las estimaciones")} + +
+
+
+ +
+ ); +}; -- cgit v1.3