import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { type RegionConfig } from "~/config/RegionConfig"; import LineIcon from "~components/LineIcon"; import { type ConsolidatedCirculation } from "~routes/stops-$id"; import "./ConsolidatedCirculationList.css"; interface ConsolidatedCirculationCardProps { estimate: ConsolidatedCirculation; regionConfig: RegionConfig; } // Utility function to parse service ID and get the turn number const parseServiceId = (serviceId: string): string => { const parts = serviceId.split("_"); if (parts.length === 0) return ""; const lastPart = parts[parts.length - 1]; if (lastPart.length < 6) return ""; const last6 = lastPart.slice(-6); const lineCode = last6.slice(0, 3); const turnCode = last6.slice(-3); // Remove leading zeros from turn const turnNumber = parseInt(turnCode, 10).toString(); // Parse line number with special cases const lineNumber = parseInt(lineCode, 10); let displayLine: string; switch (lineNumber) { case 1: displayLine = "C1"; break; case 3: displayLine = "C3"; break; case 30: displayLine = "N1"; break; case 33: displayLine = "N4"; break; case 8: displayLine = "A"; break; case 101: displayLine = "H"; break; case 150: displayLine = "REF"; break; case 500: displayLine = "TUR"; break; case 201: displayLine = "U1"; break; case 202: displayLine = "U2"; break; default: displayLine = `L${lineNumber}`; } return `${displayLine}-${turnNumber}`; }; export const ConsolidatedCirculationCard: React.FC< ConsolidatedCirculationCardProps > = ({ estimate, regionConfig }) => { const { t } = useTranslation(); const absoluteArrivalTime = (minutes: number) => { const now = new Date(); const arrival = new Date(now.getTime() + minutes * 60000); return Intl.DateTimeFormat( typeof navigator !== "undefined" ? navigator.language : "en", { hour: "2-digit", minute: "2-digit", } ).format(arrival); }; const formatDistance = (meters: number) => { if (meters > 1024) { return `${(meters / 1000).toFixed(1)} km`; } return `${meters} ${t("estimates.meters", "m")}`; }; const getTripIdDisplay = (tripId: string): string => { const parts = tripId.split("_"); return parts.length > 1 ? parts[1] : tripId; }; const etaMinutes = estimate.realTime?.minutes ?? estimate.schedule?.minutes ?? null; const etaValue = etaMinutes === null ? "--" : Math.max(0, Math.round(etaMinutes)).toString(); const etaUnit = t("estimates.minutes", "min"); const timeClass = useMemo(() => { if (estimate.realTime && estimate.schedule?.running) { return "time-running"; } if (estimate.realTime && !estimate.schedule) { return "time-running"; } if (estimate.realTime && !estimate.schedule?.running) { return "time-delayed"; } return "time-scheduled"; }, [estimate.realTime, estimate.schedule]); const delayChip = useMemo(() => { if (!estimate.schedule || !estimate.realTime) { return null; } const delta = Math.round(estimate.realTime.minutes - estimate.schedule.minutes); const absDelta = Math.abs(delta); if (delta === 0) { return { label: t("estimates.delay_on_time", "En hora (0 min)"), tone: "delay-ok", } as const; } if (delta > 0) { const tone = delta <= 2 ? "delay-ok" : delta <= 10 ? "delay-warn" : "delay-critical"; return { label: t("estimates.delay_positive", "Retraso de {{minutes}} min", { minutes: delta, }), tone, } as const; } const tone = absDelta <= 2 ? "delay-ok" : "delay-early"; return { label: t("estimates.delay_negative", "Adelanto de {{minutes}} min", { minutes: absDelta, }), tone, } as const; }, [estimate.schedule, estimate.realTime, t]); const metaChips = useMemo(() => { const chips: Array<{ label: string; tone?: string }> = []; if (delayChip) { chips.push(delayChip); } if (estimate.schedule) { chips.push({ label: `${parseServiceId(estimate.schedule.serviceId)} ยท ${getTripIdDisplay( estimate.schedule.tripId )}`, }); } if (estimate.realTime && estimate.realTime.distance >= 0) { chips.push({ label: formatDistance(estimate.realTime.distance) }); } return chips; }, [delayChip, estimate.schedule, estimate.realTime]); return (