From 87417c313b455ba0dee19708528cc8d0b830a276 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Tue, 23 Dec 2025 12:59:52 +0100 Subject: Reimplement real time for Vitrasa --- .../app/components/arrivals/ArrivalCard.tsx | 136 ++++++++++++++++++--- .../app/components/arrivals/ArrivalList.tsx | 4 +- .../app/components/map/StopSummarySheet.tsx | 2 +- 3 files changed, 122 insertions(+), 20 deletions(-) (limited to 'src/frontend/app/components') diff --git a/src/frontend/app/components/arrivals/ArrivalCard.tsx b/src/frontend/app/components/arrivals/ArrivalCard.tsx index de4fcc7..5cfbaa3 100644 --- a/src/frontend/app/components/arrivals/ArrivalCard.tsx +++ b/src/frontend/app/components/arrivals/ArrivalCard.tsx @@ -1,3 +1,4 @@ +import { AlertTriangle, LocateIcon } from "lucide-react"; import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; import LineIcon from "~/components/LineIcon"; @@ -6,15 +7,11 @@ import "./ArrivalCard.css"; interface ArrivalCardProps { arrival: Arrival; - reduced?: boolean; } -export const ArrivalCard: React.FC = ({ - arrival, - reduced, -}) => { +export const ReducedArrivalCard: React.FC = ({ arrival }) => { const { t } = useTranslation(); - const { route, headsign, estimate } = arrival; + const { route, headsign, estimate, delay, shift } = arrival; const etaValue = estimate.minutes.toString(); const etaUnit = t("estimates.minutes", "min"); @@ -32,15 +29,73 @@ export const ArrivalCard: React.FC = ({ } }, [estimate.precision]); + const metaChips = useMemo(() => { + const chips: Array<{ + label: string; + tone?: string; + kind?: "regular" | "gps" | "delay" | "warning"; + }> = []; + + // Delay chip + if (delay) { + const delta = Math.round(delay.minutes); + const absDelta = Math.abs(delta); + + if (delta === 0) { + chips.push({ + label: "OK", + tone: "delay-ok", + kind: "delay", + }); + } else if (delta > 0) { + const tone = + delta <= 2 + ? "delay-ok" + : delta <= 10 + ? "delay-warn" + : "delay-critical"; + chips.push({ + label: `R${delta}`, + tone, + kind: "delay", + }); + } else { + const tone = absDelta <= 2 ? "delay-ok" : "delay-early"; + chips.push({ + label: `A${absDelta}`, + tone, + kind: "delay", + }); + } + } + + // Shift chip + if (shift) { + chips.push({ + label: `${shift.shiftName} ยท ${shift.shiftTrip}`, + kind: "regular", + }); + } + + // Precision chips + if (estimate.precision === "unsure") { + chips.push({ + label: "!", + tone: "warning", + kind: "warning", + }); + } else if (estimate.precision === "confident") { + chips.push({ + label: "", // Just the icon for reduced + kind: "gps", + }); + } + + return chips; + }, [delay, shift, estimate.precision]); + return ( -
+
= ({ />
- {headsign.destination} - + + {metaChips.length > 0 && ( +
+ {metaChips.map((chip, idx) => { + let chipColourClasses = ""; + switch (chip.tone) { + case "delay-ok": + chipColourClasses = + "bg-green-600/20 dark:bg-green-600/30 text-green-700 dark:text-green-300"; + break; + case "delay-warn": + chipColourClasses = + "bg-amber-600/20 dark:bg-yellow-600/30 text-amber-700 dark:text-yellow-300"; + break; + case "delay-critical": + chipColourClasses = + "bg-red-400/20 dark:bg-red-600/30 text-red-600 dark:text-red-300"; + break; + case "delay-early": + chipColourClasses = + "bg-blue-400/20 dark:bg-blue-600/30 text-blue-700 dark:text-blue-300"; + break; + case "warning": + chipColourClasses = + "bg-orange-400/20 dark:bg-orange-600/30 text-orange-700 dark:text-orange-300"; + break; + default: + chipColourClasses = + "bg-black/[0.06] dark:bg-white/[0.12] text-slate-600 dark:text-slate-400"; + } + + return ( + + {chip.kind === "gps" && ( + + )} + {chip.kind === "warning" && ( + + )} + {chip.label} + + ); + })} +
+ )}
= ({ return (
{arrivals.map((arrival, index) => ( - = ({ ({stop.stopCode})
-
+
{stop.lines.map((lineObj) => (