diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-23 12:59:52 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-23 13:00:16 +0100 |
| commit | 87417c313b455ba0dee19708528cc8d0b830a276 (patch) | |
| tree | 34b7a2d6bb97157a1d35f57be85b8ff6532865d2 /src/frontend/app/components | |
| parent | bed48c3d7e49b1736d50ce42d92bb6c18cf02504 (diff) | |
Reimplement real time for Vitrasa
Diffstat (limited to 'src/frontend/app/components')
| -rw-r--r-- | src/frontend/app/components/arrivals/ArrivalCard.tsx | 136 | ||||
| -rw-r--r-- | src/frontend/app/components/arrivals/ArrivalList.tsx | 4 | ||||
| -rw-r--r-- | src/frontend/app/components/map/StopSummarySheet.tsx | 2 |
3 files changed, 122 insertions, 20 deletions
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<ArrivalCardProps> = ({ - arrival, - reduced, -}) => { +export const ReducedArrivalCard: React.FC<ArrivalCardProps> = ({ 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<ArrivalCardProps> = ({ } }, [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 ( - <div - className={` - flex-none flex items-center gap-2.5 min-h-12 - bg-(--message-background-color) border border-(--border-color) - rounded-xl px-3 py-2.5 transition-all - ${reduced ? "reduced" : ""} - `.trim()} - > + <div className="flex-none flex items-center gap-2.5 min-h-12 rounded px-3 py-2.5 transition-all bg-slate-50 dark:bg-slate-800 shadow-sm"> <div className="shrink-0 min-w-[7ch]"> <LineIcon line={route.shortName} @@ -50,11 +105,58 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({ /> </div> <div className="flex-1 min-w-0 flex flex-col gap-1"> - <strong - className={`text-base overflow-hidden text-ellipsis line-clamp-2 leading-tight ${estimate.precision == "past" ? "line-through" : ""}`} + <span + className={`text-base font-medium overflow-hidden text-ellipsis line-clamp-2 leading-tight ${estimate.precision == "past" ? "line-through" : ""}`} > {headsign.destination} - </strong> + </span> + {metaChips.length > 0 && ( + <div className="flex items-center gap-1 flex-wrap"> + {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 ( + <span + key={`${chip.label}-${idx}`} + className={`text-xs px-2.5 py-0.5 rounded-full flex items-center justify-center gap-1 shrink-0 font-medium tracking-wide ${chipColourClasses}`} + > + {chip.kind === "gps" && ( + <LocateIcon className="w-3 h-3 my-0.5 inline-block" /> + )} + {chip.kind === "warning" && ( + <AlertTriangle className="w-3 h-3 my-0.5 inline-block" /> + )} + {chip.label} + </span> + ); + })} + </div> + )} </div> <div className={` diff --git a/src/frontend/app/components/arrivals/ArrivalList.tsx b/src/frontend/app/components/arrivals/ArrivalList.tsx index a1210d5..b2394fb 100644 --- a/src/frontend/app/components/arrivals/ArrivalList.tsx +++ b/src/frontend/app/components/arrivals/ArrivalList.tsx @@ -1,6 +1,6 @@ import React from "react"; import { type Arrival } from "../../api/schema"; -import { ArrivalCard } from "./ArrivalCard"; +import { ReducedArrivalCard } from "./ArrivalCard"; interface ArrivalListProps { arrivals: Arrival[]; @@ -14,7 +14,7 @@ export const ArrivalList: React.FC<ArrivalListProps> = ({ return ( <div className="flex flex-col gap-3"> {arrivals.map((arrival, index) => ( - <ArrivalCard + <ReducedArrivalCard key={`${arrival.route.shortName}-${index}`} arrival={arrival} reduced={reduced} diff --git a/src/frontend/app/components/map/StopSummarySheet.tsx b/src/frontend/app/components/map/StopSummarySheet.tsx index e318bee..56e80a4 100644 --- a/src/frontend/app/components/map/StopSummarySheet.tsx +++ b/src/frontend/app/components/map/StopSummarySheet.tsx @@ -56,7 +56,7 @@ export const StopSheet: React.FC<StopSheetProps> = ({ <span className="stop-sheet-id">({stop.stopCode})</span> </div> - <div className={`d-flex flex-wrap flex-row gap-2`}> + <div className={`flex flex-wrap flex-row gap-2`}> {stop.lines.map((lineObj) => ( <LineIcon key={lineObj.line} |
