From 3ebb062e99dbd8a63d5642d67ba4be753e61a34d Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Wed, 19 Nov 2025 22:58:40 +0100 Subject: feat: Enhance map attribution feature; improve styles and add toggle functionality in StopMapSheet component --- .../Stops/ConsolidatedCirculationCard.tsx | 141 ++++++++++++--------- 1 file changed, 81 insertions(+), 60 deletions(-) (limited to 'src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx') diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx index 9733d89..f725b8c 100644 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx +++ b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx @@ -1,4 +1,4 @@ -import { Clock } from "lucide-react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { type RegionConfig } from "~/config/RegionConfig"; import LineIcon from "~components/LineIcon"; @@ -88,25 +88,8 @@ export const ConsolidatedCirculationCard: React.FC< const formatDistance = (meters: number) => { if (meters > 1024) { return `${(meters / 1000).toFixed(1)} km`; - } else { - return `${meters} ${t("estimates.meters", "m")}`; - } - }; - - const getDelayText = (estimate: ConsolidatedCirculation): string | null => { - if (!estimate.schedule || !estimate.realTime) { - return null; - } - - const delay = estimate.realTime.minutes - estimate.schedule.minutes; - - if (delay >= -1 && delay <= 2) { - return "OK"; - } else if (delay > 2) { - return "R" + delay; - } else { - return "A" + Math.abs(delay); } + return `${meters} ${t("estimates.meters", "m")}`; }; const getTripIdDisplay = (tripId: string): string => { @@ -114,67 +97,105 @@ export const ConsolidatedCirculationCard: React.FC< return parts.length > 1 ? parts[1] : tripId; }; - const getTimeClass = (estimate: ConsolidatedCirculation): string => { + 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"; - } else if (estimate.realTime && !estimate.schedule?.running) { + } + if (estimate.realTime && !estimate.schedule?.running) { return "time-delayed"; } - return "time-scheduled"; - }; + }, [estimate.realTime, estimate.schedule]); - const displayMinutes = - estimate.realTime?.minutes ?? estimate.schedule?.minutes ?? 0; - const timeClass = getTimeClass(estimate); - const delayText = getDelayText(estimate); + 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 (
-
+
- +
-
{estimate.route}
- -
-
- - {estimate.realTime - ? `${displayMinutes} ${t("estimates.minutes", "min")}` - : absoluteArrivalTime(displayMinutes)} -
-
- {estimate.schedule && ( - <> - {parseServiceId(estimate.schedule.serviceId)} ( - {getTripIdDisplay(estimate.schedule.tripId)}) - - )} - - {estimate.schedule && - estimate.realTime && - estimate.realTime.distance >= 0 && <> · } - - {estimate.realTime && estimate.realTime.distance >= 0 && ( - <>{formatDistance(estimate.realTime.distance)} - )} - - {estimate.schedule && - estimate.realTime && - estimate.realTime.distance >= 0 && <> · } - - {delayText} +
+
+ {etaValue} + {etaUnit}
+ {metaChips.length > 0 && ( +
+ {metaChips.map((chip, idx) => ( + + {chip.label} + + ))} +
+ )}
); }; -- cgit v1.3