From 073c7174490ed3d8ae34c3f8c8f1b91bce711f6f Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Fri, 30 Jan 2026 19:59:47 +0100 Subject: feat: Add date parameter to GetRouteDetails and update fetchRouteDetails to support date queries feat: Enhance localization with new date-related strings in English, Spanish, and Galician feat: Improve RouteDetailsPage with layout options and date selection for better user experience --- src/frontend/app/routes/routes-$id.tsx | 202 ++++++++++++++++++++++++++------- 1 file changed, 161 insertions(+), 41 deletions(-) (limited to 'src/frontend/app/routes') diff --git a/src/frontend/app/routes/routes-$id.tsx b/src/frontend/app/routes/routes-$id.tsx index 62de642..7de16eb 100644 --- a/src/frontend/app/routes/routes-$id.tsx +++ b/src/frontend/app/routes/routes-$id.tsx @@ -1,4 +1,5 @@ import { useQuery } from "@tanstack/react-query"; +import { LayoutGrid, List, Map as MapIcon } from "lucide-react"; import { useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { @@ -19,17 +20,35 @@ import "../tailwind-full.css"; export default function RouteDetailsPage() { const { id } = useParams(); - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const [selectedPatternId, setSelectedPatternId] = useState( null ); const [selectedStopId, setSelectedStopId] = useState(null); + const [layoutMode, setLayoutMode] = useState<"balanced" | "map" | "list">( + "balanced" + ); + const [selectedWeekDate, setSelectedWeekDate] = useState( + () => new Date() + ); const mapRef = useRef(null); const stopRefs = useRef>({}); + const formatDateKey = (value: Date) => { + const year = value.getFullYear(); + const month = String(value.getMonth() + 1).padStart(2, "0"); + const day = String(value.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; + }; + + const selectedDateKey = useMemo( + () => formatDateKey(selectedWeekDate), + [selectedWeekDate] + ); + const { data: route, isLoading } = useQuery({ - queryKey: ["route", id], - queryFn: () => fetchRouteDetails(id!), + queryKey: ["route", id, selectedDateKey], + queryFn: () => fetchRouteDetails(id!, selectedDateKey), enabled: !!id, }); @@ -71,6 +90,35 @@ export default function RouteDetailsPage() { useBackButton({ to: "/routes" }); + const weekDays = useMemo(() => { + const base = new Date(); + return [-2, -1, 0, 1, 2, 3, 4].map((offset) => { + const date = new Date(base); + date.setDate(base.getDate() + offset); + + let label: string; + if (offset === -1) { + label = t("routes.day_yesterday", "Ayer"); + } else if (offset === 0) { + label = t("routes.day_today", "Hoy"); + } else if (offset === 1) { + label = t("routes.day_tomorrow", "MaƱana"); + } else { + label = date.toLocaleDateString(i18n.language || "es-ES", { + weekday: "short", + day: "numeric", + month: "short", + }); + } + + return { + key: formatDateKey(date), + date, + label, + }; + }); + }, [i18n.language, t]); + if (isLoading) { return (
@@ -100,6 +148,31 @@ export default function RouteDetailsPage() { const selectedPattern = activePatterns.find((p) => p.id === selectedPatternId) || activePatterns[0]; + const mapHeightClass = + layoutMode === "map" + ? "h-[75%] md:h-[75%]" + : layoutMode === "list" + ? "h-[25%] md:h-[25%]" + : "h-[50%] md:h-[50%]"; + + const layoutOptions = [ + { + id: "balanced", + label: t("routes.layout_balanced", "Equilibrada"), + icon: LayoutGrid, + }, + { + id: "map", + label: t("routes.layout_map", "Mapa"), + icon: MapIcon, + }, + { + id: "list", + label: t("routes.layout_list", "Paradas"), + icon: List, + }, + ] as const; + const handleStopClick = ( stopId: string, lat: number, @@ -109,7 +182,7 @@ export default function RouteDetailsPage() { setSelectedStopId(stopId); mapRef.current?.flyTo({ center: [lon, lat], - zoom: 16, + zoom: 15, duration: 1000, }); @@ -160,7 +233,7 @@ export default function RouteDetailsPage() {
-
+
+ +
+ {layoutOptions.map((option) => { + const Icon = option.icon; + const isActive = layoutMode === option.id; + return ( + + ); + })} +
- { + setSelectedPatternId(e.target.value); + setSelectedStopId(null); + }} > - {patterns.map((pattern) => ( - + {patterns.map((pattern) => ( + + ))} + + ))} + + + + +
+
-
-

+
+

{t("routes.stops", "Paradas")}

-
+
{selectedPattern?.stops.map((stop, idx) => (
handleStopClick(stop.id, stop.lat, stop.lon, false) } - className={`flex items-start gap-4 p-3 rounded-lg border transition-colors cursor-pointer ${ + className={`flex items-start gap-3 p-2.5 rounded-lg border transition-colors cursor-pointer ${ selectedStopId === stop.id ? "bg-primary/5 border-primary" : "bg-surface border-border hover:border-primary/50" @@ -268,17 +388,17 @@ export default function RouteDetailsPage() { >
{idx < selectedPattern.stops.length - 1 && ( -
+
)}
-

+

{stop.name} {stop.code && ( - + {stop.code} )} -- cgit v1.3