From 107295575e3a7c37911ae192baf426b0003975a4 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Mon, 8 Dec 2025 01:37:10 +0100 Subject: Refactor code structure for improved readability and maintainability; removed redundant code blocks and optimized functions. --- src/frontend/app/AppContext.tsx | 13 +-- src/frontend/app/components/LineIcon.css | 10 +++ .../Stops/ConsolidatedCirculationCard.css | 1 - .../Stops/ConsolidatedCirculationCard.tsx | 96 +++++++++++++++++----- .../Stops/ConsolidatedCirculationList.tsx | 3 + src/frontend/app/config/RegionConfig.ts | 25 +++++- src/frontend/app/routes/home.tsx | 10 +-- src/frontend/app/routes/stops-$id.tsx | 5 +- 8 files changed, 127 insertions(+), 36 deletions(-) (limited to 'src/frontend') diff --git a/src/frontend/app/AppContext.tsx b/src/frontend/app/AppContext.tsx index 59f2724..12a54da 100644 --- a/src/frontend/app/AppContext.tsx +++ b/src/frontend/app/AppContext.tsx @@ -3,11 +3,11 @@ import { type ReactNode } from "react"; import { type RegionId } from "./config/RegionConfig"; import { MapProvider, useMap } from "./contexts/MapContext"; import { - SettingsProvider, - useSettings, - type MapPositionMode, - type TableStyle, - type Theme, + SettingsProvider, + useSettings, + type MapPositionMode, + type TableStyle, + type Theme, } from "./contexts/SettingsContext"; // Re-export types for compatibility @@ -21,6 +21,9 @@ export const useApp = () => { return { ...settings, ...map, + // Mock region support for now since we only have one region + region: "vigo" as RegionId, + setRegion: (region: RegionId) => { console.log("Set region", region); }, }; }; diff --git a/src/frontend/app/components/LineIcon.css b/src/frontend/app/components/LineIcon.css index 6363c85..6492d39 100644 --- a/src/frontend/app/components/LineIcon.css +++ b/src/frontend/app/components/LineIcon.css @@ -87,6 +87,16 @@ --line-gol: hsl(208, 68%, 66%); --line-gol-text: hsl(0, 0%, 100%); + --line-md: hsl(316, 99%, 27%); + --line-md-text: hsl(0, 0%, 100%); + --line-ave: hsl(316, 99%, 27%); + --line-ave-text: hsl(0, 0%, 100%); + --line-alvia: hsl(316, 99%, 27%); + --line-alvia-text: hsl(0, 0%, 100%); + --line-trencelta: hsl(135, 58%, 25%); + --line-trencelta-text: hsl(0, 0%, 100%); + --line-regional: hsl(316, 99%, 27%); + --line-regional-text: hsl(0, 0%, 100%); } .line-icon-default { diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css index 57d30c8..9922b03 100644 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css +++ b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css @@ -61,7 +61,6 @@ } .consolidated-circulation-card .route-info strong { - font-size: 1rem; color: var(--text-color); overflow: hidden; text-overflow: ellipsis; diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx index 635c0ce..70a9355 100644 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx +++ b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import Marquee from 'react-fast-marquee'; import { useTranslation } from "react-i18next"; import LineIcon from "~components/LineIcon"; @@ -12,6 +12,7 @@ interface ConsolidatedCirculationCardProps { onMapClick?: () => void; readonly?: boolean; reduced?: boolean; + driver?: string; } // Utility function to parse service ID and get the turn number @@ -71,9 +72,52 @@ const parseServiceId = (serviceId: string): string => { 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 }) => { +> = ({ estimate, onMapClick, readonly, reduced, driver }) => { const { t } = useTranslation(); const formatDistance = (meters: number) => { @@ -157,7 +201,7 @@ export const ConsolidatedCirculationCard: React.FC< chips.push(delayChip); } - if (estimate.schedule) { + if (estimate.schedule && driver !== 'renfe') { chips.push({ label: `${parseServiceId(estimate.schedule.serviceId)} · ${getTripIdDisplay( estimate.schedule.tripId @@ -199,14 +243,17 @@ export const ConsolidatedCirculationCard: React.FC< // 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: onMapClick, + onClick: isClickable ? onMapClick : undefined, type: "button" as const, - disabled: !hasGpsPosition, + disabled: !isClickable, }; if (reduced) { @@ -217,12 +264,14 @@ export const ConsolidatedCirculationCard: React.FC< bg-(--message-background-color) border border-(--border-color) rounded-xl px-3 py-2.5 transition-all ${readonly - ? !hasGpsPosition + ? looksDisabled ? "opacity-70 cursor-not-allowed" : "" - : hasGpsPosition + : isClickable ? "cursor-pointer hover:shadow-[0_4px_14px_rgba(0,0,0,0.08)] hover:border-(--button-background-color) hover:bg-[color-mix(in_oklab,var(--button-background-color)_5%,var(--message-background-color))] active:scale-[0.98]" - : "opacity-70 cursor-not-allowed" + : looksDisabled + ? "opacity-70 cursor-not-allowed" + : "" } `.trim()} {...interactiveProps} @@ -232,6 +281,9 @@ export const ConsolidatedCirculationCard: React.FC<
+ {driver === 'renfe' && estimate.schedule?.tripId && ( + {estimate.schedule.tripId} + )} {estimate.route} {metaChips.length > 0 && ( @@ -295,12 +347,14 @@ export const ConsolidatedCirculationCard: React.FC< return ( @@ -310,17 +364,15 @@ export const ConsolidatedCirculationCard: React.FC<
- {estimate.route} - {estimate.nextStreets && estimate.nextStreets.length > 0 && (() => { - const text = estimate.nextStreets.join(" — "); - return ( - 30}> -
- {text} -
-
- ); - })()} + + {driver === 'renfe' && estimate.schedule?.tripId && ( + {estimate.schedule.tripId} + )} + {estimate.route} + + {estimate.nextStreets && estimate.nextStreets.length > 0 && ( + + )}
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx index 088f978..ec79f1c 100644 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx +++ b/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx @@ -9,12 +9,14 @@ interface ConsolidatedCirculationListProps { data: ConsolidatedCirculation[]; onCirculationClick?: (estimate: ConsolidatedCirculation, index: number) => void; reduced?: boolean; + driver?: string; } export const ConsolidatedCirculationList: React.FC = ({ data, onCirculationClick, reduced, + driver, }) => { const { t } = useTranslation(); @@ -43,6 +45,7 @@ export const ConsolidatedCirculationList: React.FC ( onCirculationClick?.(estimate, idx)} diff --git a/src/frontend/app/config/RegionConfig.ts b/src/frontend/app/config/RegionConfig.ts index 4677509..43fe70a 100644 --- a/src/frontend/app/config/RegionConfig.ts +++ b/src/frontend/app/config/RegionConfig.ts @@ -1,6 +1,26 @@ import type { LngLatLike } from "maplibre-gl"; -export const REGION_DATA = { +export type RegionId = "vigo"; + +export interface RegionData { + id: RegionId; + name: string; + stopsEndpoint: string; + estimatesEndpoint: string; + consolidatedCirculationsEndpoint: string; + timetableEndpoint: string; + shapeEndpoint: string; + defaultCenter: LngLatLike; + bounds: { + sw: LngLatLike; + ne: LngLatLike; + }; + textColour: string; + defaultZoom: number; + showMeters: boolean; +} + +export const REGION_DATA: RegionData = { id: "vigo", name: "Vigo", stopsEndpoint: "/stops/vigo.json", @@ -20,3 +40,6 @@ export const REGION_DATA = { defaultZoom: 14, showMeters: true, }; + +export const getAvailableRegions = (): RegionData[] => [REGION_DATA]; + diff --git a/src/frontend/app/routes/home.tsx b/src/frontend/app/routes/home.tsx index 5d56b48..f97fdf7 100644 --- a/src/frontend/app/routes/home.tsx +++ b/src/frontend/app/routes/home.tsx @@ -104,7 +104,7 @@ export default function StopList() { } if (!userLocation) { - return [...data].sort((a, b) => a.stopId - b.stopId); + return [...data].sort((a, b) => a.stopId.localeCompare(b.stopId)); } const toRadians = (value: number) => (value * Math.PI) / 180; @@ -147,7 +147,7 @@ export default function StopList() { }) .sort((a, b) => { if (a.distance === b.distance) { - return a.stop.stopId - b.stop.stopId; + return a.stop.stopId.localeCompare(b.stop.stopId); } return a.distance - b.distance; }) @@ -216,8 +216,8 @@ export default function StopList() { let items: Stop[]; if (isNumericSearch) { // Direct match for stop codes - const stopId = parseInt(searchQuery.trim(), 10); - const exactMatch = data.filter((stop) => stop.stopId === stopId); + const stopId = searchQuery.trim(); + const exactMatch = data.filter((stop) => stop.stopId === stopId || stop.stopId.endsWith(`:${stopId}`)); if (exactMatch.length > 0) { items = exactMatch; } else { @@ -281,7 +281,7 @@ export default function StopList() { {/* Favourites Gallery */} {!loading && ( a.stopId - b.stopId)} + stops={favouriteStops.sort((a, b) => a.stopId.localeCompare(b.stopId))} title={t("stoplist.favourites")} emptyMessage={t("stoplist.no_favourites")} /> diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx index 5260c32..553b8e7 100644 --- a/src/frontend/app/routes/stops-$id.tsx +++ b/src/frontend/app/routes/stops-$id.tsx @@ -247,8 +247,8 @@ export default function Estimates() {
@@ -273,6 +273,7 @@ export default function Estimates() { { setSelectedCirculationId(getCirculationId(estimate)); setIsMapModalOpen(true); -- cgit v1.3