From 49ef6f4af837d4f3f4f367fa831f1ff176036c27 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:24:51 +0100 Subject: Show probable traffic restriction warning on Xunta legs within urban municipalities (#141) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: arielcostas <94913521+arielcostas@users.noreply.github.com> Co-authored-by: Ariel Costas Guerrero --- src/frontend/app/api/schema.ts | 1 + src/frontend/app/data/PlannerApi.ts | 2 ++ src/frontend/app/i18n/locales/en-GB.json | 5 +++- src/frontend/app/i18n/locales/es-ES.json | 5 +++- src/frontend/app/i18n/locales/gl-ES.json | 16 ++++++++---- src/frontend/app/routes/planner.tsx | 42 ++++++++++++++++++++++++++++++-- 6 files changed, 62 insertions(+), 9 deletions(-) (limited to 'src/frontend/app') diff --git a/src/frontend/app/api/schema.ts b/src/frontend/app/api/schema.ts index 57f34b1..bc47116 100644 --- a/src/frontend/app/api/schema.ts +++ b/src/frontend/app/api/schema.ts @@ -181,6 +181,7 @@ export const PlannerPlaceSchema = z.object({ lon: z.number(), stopId: z.string().optional().nullable(), stopCode: z.string().optional().nullable(), + zoneId: z.string().optional().nullable(), }); export const PlannerGeometrySchema = z.object({ diff --git a/src/frontend/app/data/PlannerApi.ts b/src/frontend/app/data/PlannerApi.ts index 8d51ceb..43c8ae1 100644 --- a/src/frontend/app/data/PlannerApi.ts +++ b/src/frontend/app/data/PlannerApi.ts @@ -30,6 +30,7 @@ export interface Itinerary { export interface Leg { mode?: string; + feedId?: string; routeName?: string; routeShortName?: string; routeLongName?: string; @@ -53,6 +54,7 @@ export interface PlannerPlace { lon: number; stopId?: string; stopCode?: string; + zoneId?: string; } export interface PlannerGeometry { diff --git a/src/frontend/app/i18n/locales/en-GB.json b/src/frontend/app/i18n/locales/en-GB.json index aed0066..1987d28 100644 --- a/src/frontend/app/i18n/locales/en-GB.json +++ b/src/frontend/app/i18n/locales/en-GB.json @@ -147,7 +147,9 @@ "operator": "Operator", "back": "← Back", "fare": "€{{amount}}", - "free": "Free" + "free": "Free", + "urban_traffic_warning": "Possible transit restriction", + "urban_traffic_warning_desc": "Both stops on this leg are within {{municipality}}, which has its own urban transport. It's likely you are not allowed to do this trip with Xunta services." }, "common": { "loading": "Loading...", @@ -160,6 +162,7 @@ "stops": "Stops", "planner": "Planner", "routes": "Routes", + "settings": "Settings", "favourites": "Favourites" }, "routes": { diff --git a/src/frontend/app/i18n/locales/es-ES.json b/src/frontend/app/i18n/locales/es-ES.json index 1c805b3..ac02026 100644 --- a/src/frontend/app/i18n/locales/es-ES.json +++ b/src/frontend/app/i18n/locales/es-ES.json @@ -147,7 +147,9 @@ "operator": "Operador", "back": "← Atrás", "fare": "{{amount}} €", - "free": "Gratuito" + "free": "Gratuito", + "urban_traffic_warning": "Posible restricción de tráfico", + "urban_traffic_warning_desc": "Las dos paradas de este tramo están en {{municipality}}, que dispone de transporte urbano propio. Es probable que no puedas utilizar los servicios de la Xunta en este trayecto." }, "common": { "loading": "Cargando...", @@ -160,6 +162,7 @@ "stops": "Paradas", "planner": "Planificador", "routes": "Rutas", + "settings": "Ajustes", "favourites": "Favoritos" }, "routes": { diff --git a/src/frontend/app/i18n/locales/gl-ES.json b/src/frontend/app/i18n/locales/gl-ES.json index 1af3b56..2c874d8 100644 --- a/src/frontend/app/i18n/locales/gl-ES.json +++ b/src/frontend/app/i18n/locales/gl-ES.json @@ -7,10 +7,6 @@ "data_gtfs": "Horarios programados", "data_gtfs_source": "Feed GTFS oficial (datos abertos municipais)", "data_realtime": "Datos en tempo real", - "day_yesterday": "Onte", - "day_today": "Hoxe", - "day_tomorrow": "Mañá", - "week_date": "Data", "data_realtime_source": "API da cidade", "data_traffic": "Estado do tráfico", "data_traffic_source": "Datos abertos municipais", @@ -131,6 +127,13 @@ "searching_ellipsis": "Buscando…", "results": "Resultados", "close": "Pechar", + "collapse": "Contraer", + "pick_on_map": "Seleccionar en mapa", + "pick_on_map_desc": "Selecciona un punto visualmente", + "pick_origin": "Seleccionar orixe", + "pick_destination": "Seleccionar destino", + "pick_instruction": "Move o mapa para colocar o destino na ubicación desexada", + "confirm_location": "Confirmar ubicación", "results_title": "Resultados", "clear": "Limpar", "recent_routes": "Rutas recentes", @@ -144,7 +147,9 @@ "operator": "Operador", "back": "← Atrás", "fare": "{{amount}} €", - "free": "Gratuíto" + "free": "Gratuíto", + "urban_traffic_warning": "Posible restrición de tráfico", + "urban_traffic_warning_desc": "As dúas paradas deste tramo están en {{municipality}}, que dispón de transporte urbano propio. É probable que non poidas utilizar os servizos da Xunta neste traxecto." }, "common": { "loading": "Cargando...", @@ -162,6 +167,7 @@ "choose_trip": "Escolle un traxecto", "close": "Pechar", "trip": "Traxecto", + "settings": "Axustes", "view_stop": "Ver parada" }, "routes": { diff --git a/src/frontend/app/routes/planner.tsx b/src/frontend/app/routes/planner.tsx index 1f64590..5fd0ce7 100644 --- a/src/frontend/app/routes/planner.tsx +++ b/src/frontend/app/routes/planner.tsx @@ -1,4 +1,4 @@ -import { Coins, CreditCard, Footprints } from "lucide-react"; +import { AlertTriangle, Coins, CreditCard, Footprints } from "lucide-react"; import maplibregl from "maplibre-gl"; import "maplibre-gl/dist/maplibre-gl.css"; import React, { useEffect, useMemo, useRef, useState } from "react"; @@ -72,6 +72,25 @@ const sumWalkMetrics = (legs: Itinerary["legs"]) => { return { meters, minutes: Math.max(0, Math.round(minutes)) }; }; +const URBAN_MUNICIPALITIES: Record = { + "15030": "A Coruña", + "27028": "Lugo", + "32054": "Ourense", + "15078": "Santiago de Compostela", + "36057": "Vigo", +}; + +const getUrbanMunicipalityWarning = ( + leg: Itinerary["legs"][number] +): string | null => { + if (leg.feedId !== "xunta") return null; + const fromMunicipality = leg.from?.zoneId?.substring(0, 5); + const toMunicipality = leg.to?.zoneId?.substring(0, 5); + if (!fromMunicipality || !toMunicipality) return null; + if (fromMunicipality !== toMunicipality) return null; + return URBAN_MUNICIPALITIES[fromMunicipality] ?? null; +}; + const ItinerarySummary = ({ itinerary, onClick, @@ -653,6 +672,25 @@ const ItineraryDetail = ({ )} + {(() => { + const municipality = getUrbanMunicipalityWarning(leg); + if (!municipality) return null; + return ( +
+ +
+
+ {t("planner.urban_traffic_warning")} +
+
+ {t("planner.urban_traffic_warning_desc", { + municipality, + })} +
+
+
+ ); + })()} )} @@ -668,7 +706,7 @@ const ItineraryDetail = ({ export default function PlannerPage() { const { t } = useTranslation(); - usePageTitle(t("navbar.planner", "Planificador")); + () => usePageTitle(t("navbar.planner", "Planificador")); const location = useLocation(); const { plan, -- cgit v1.3