From 9ed46bea58dbb81ceada2a957fd1db653fb21e52 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Wed, 24 Dec 2025 17:53:32 +0100 Subject: Implement new UI styles --- .../Stops/ConsolidatedCirculationCard.tsx | 467 --------------------- 1 file changed, 467 deletions(-) delete mode 100644 src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx (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 deleted file mode 100644 index 679345f..0000000 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx +++ /dev/null @@ -1,467 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import Marquee from "react-fast-marquee"; -import { useTranslation } from "react-i18next"; -import LineIcon from "~components/LineIcon"; -import { type ConsolidatedCirculation } from "~routes/stops-$id"; - -import { AlertTriangle, LocateIcon } from "lucide-react"; -import "./ConsolidatedCirculationCard.css"; - -interface ConsolidatedCirculationCardProps { - estimate: ConsolidatedCirculation; - onMapClick?: () => void; - readonly?: boolean; - reduced?: boolean; - driver?: string; -} - -// 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}`; -}; - -const AutoMarquee = ({ text }: { text: string }) => { - const containerRef = useRef(null); - const [shouldScroll, setShouldScroll] = useState(false); - - useEffect(() => { - const el = containerRef.current; - if (!el) return; - - const checkScroll = () => { - // 9px per char for text-sm font-mono is a safe upper bound estimate - // (14px * 0.6 = 8.4px) - const charWidth = 9; - const availableWidth = el.offsetWidth; - const textWidth = text.length * charWidth; - - setShouldScroll(textWidth > availableWidth); - }; - - checkScroll(); - - const observer = new ResizeObserver(checkScroll); - observer.observe(el); - - return () => observer.disconnect(); - }, [text]); - - if (shouldScroll) { - return ( -
- -
{text}
-
-
- ); - } - - return ( -
- {text} -
- ); -}; - -export const ConsolidatedCirculationCard: React.FC< - ConsolidatedCirculationCardProps -> = ({ estimate, onMapClick, readonly, reduced, driver }) => { - const { t } = useTranslation(); - - 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; - - let etaValue: string; - let etaUnit: string; - - if (etaMinutes === null) { - etaValue = "--"; - etaUnit = t("estimates.minutes", "min"); - } else { - const isRenfe = driver === "renfe"; - const isLongWait = etaMinutes > 60; - - if (isRenfe || isLongWait) { - const now = new Date(); - const arrivalTime = new Date(now.getTime() + etaMinutes * 60 * 1000); - etaValue = arrivalTime.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); - etaUnit = ""; - } else { - etaValue = Math.max(0, Math.round(etaMinutes)).toString(); - 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); - - // On time - if (delta === 0) { - return { - label: reduced ? "OK" : t("estimates.delay_on_time"), - tone: "delay-ok", - kind: "delay", - } as const; - } - - // Delayed - if (delta > 0) { - const tone = - delta <= 2 ? "delay-ok" : delta <= 10 ? "delay-warn" : "delay-critical"; - return { - label: reduced - ? `R${delta}` - : t("estimates.delay_positive", { - minutes: delta, - }), - tone, - kind: "delay", - } as const; - } - - // Early - const tone = absDelta <= 2 ? "delay-ok" : "delay-early"; - return { - label: reduced - ? `A${absDelta}` - : t("estimates.delay_negative", { - minutes: absDelta, - }), - tone, - kind: "delay", - } as const; - }, [estimate.schedule, estimate.realTime, t, reduced]); - - const metaChips = useMemo(() => { - const chips: Array<{ - label: string; - tone?: string; - kind?: "regular" | "gps" | "delay" | "warning"; - }> = []; - - if (delayChip) { - chips.push(delayChip); - } - - if (estimate.schedule && driver !== "renfe") { - chips.push({ - label: `${parseServiceId(estimate.schedule.serviceId)} · ${getTripIdDisplay( - estimate.schedule.tripId - )}`, - kind: "regular", - }); - } - - if (estimate.realTime && estimate.realTime.distance >= 0) { - chips.push({ - label: formatDistance(estimate.realTime.distance), - kind: "regular", - }); - } - - if (!reduced) { - if (estimate.currentPosition) { - if (estimate.isPreviousTrip) { - chips.push({ label: t("estimates.previous_trip"), kind: "gps" }); - } else { - chips.push({ label: t("estimates.bus_gps_position"), kind: "gps" }); - } - } - - if (driver !== "renfe") { - if (timeClass === "time-delayed") { - chips.push({ - label: reduced ? "!" : t("estimates.low_accuracy"), - tone: "warning", - kind: "warning", - }); - } - - if (timeClass === "time-scheduled") { - chips.push({ - label: reduced ? "⧗" : t("estimates.no_realtime"), - tone: "warning", - kind: "warning", - }); - } - } - } - - return chips; - }, [delayChip, estimate.schedule, estimate.realTime, timeClass, t, reduced]); - - // Check if bus has GPS position (live tracking) - const hasGpsPosition = !!estimate.currentPosition; - const isRenfe = driver === "renfe"; - const isClickable = hasGpsPosition; - const looksDisabled = !isClickable && !isRenfe; - - const Tag = readonly ? "div" : "button"; - const interactiveProps = readonly - ? {} - : { - onClick: isClickable ? onMapClick : undefined, - type: "button" as const, - disabled: !isClickable, - }; - - if (reduced) { - return ( - -
- -
-
- - {driver === "renfe" && estimate.schedule?.tripId && ( - - {estimate.schedule.tripId} - - )} - {driver === "renfe" ? estimate.route.toUpperCase() : estimate.route} - - {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-[var(--text-color)]"; - } - - return ( - - {chip.kind === "gps" && ( - - )} - {chip.kind === "warning" && ( - - )} - {chip.label} - - ); - })} -
- )} -
-
-
- {etaValue} - - {etaUnit} - -
-
-
- ); - } - - return ( - - <> -
-
- -
-
- - {driver === "renfe" && estimate.schedule?.tripId && ( - - {estimate.schedule.tripId} - - )} - {driver === "renfe" - ? estimate.route.toUpperCase() - : estimate.route} - - {estimate.nextStreets && estimate.nextStreets.length > 0 && ( - - )} -
-
-
- {etaValue} - {etaUnit} -
-
-
- - {metaChips.length > 0 && ( -
- {metaChips.map((chip, idx) => ( - - {chip.kind === "gps" && ( - - )} - {chip.kind === "warning" && ( - - )} - {chip.label} - - ))} -
- )} - -
- ); -}; -- cgit v1.3