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 type { Stop } from "~/data/StopDataProvider"; import { useApp } from "../AppContext"; import { type RegionId, getRegionConfig } from "../data/RegionConfig"; import { type ConsolidatedCirculation } from "../routes/stops-$id"; import { ErrorDisplay } from "./ErrorDisplay"; import LineIcon from "./LineIcon"; import { StopAlert } from "./StopAlert"; import { ConsolidatedCirculationCard } from "./Stops/ConsolidatedCirculationCard"; import "./StopSheet.css"; import { StopSheetSkeleton } from "./StopSheetSkeleton"; interface StopSheetProps { isOpen: boolean; onClose: () => void; stop: Stop; } interface ErrorInfo { type: "network" | "server" | "unknown"; status?: number; message?: string; } const loadConsolidatedData = async ( region: RegionId, stopId: number ): Promise => { const regionConfig = getRegionConfig(region); const resp = await fetch( `${regionConfig.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 { region } = useApp(); const regionConfig = getRegionConfig(region); 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(region, 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, region]); // 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")}
) : (
{limitedEstimates.map((estimate, idx) => ( ))}
)}
) : 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")}
); };