diff options
| author | Copilot <198982749+Copilot@users.noreply.github.com> | 2026-03-05 18:24:51 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-05 18:24:51 +0100 |
| commit | 49ef6f4af837d4f3f4f367fa831f1ff176036c27 (patch) | |
| tree | 157c5f7fbbfb4da181a3dce9fb8e4700d82b5231 | |
| parent | 6e2d9ffe812eb1ca8fe5d04d3df2aa322e9e5760 (diff) | |
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 <ariel@costas.dev>
| -rw-r--r-- | src/frontend/app/api/schema.ts | 1 | ||||
| -rw-r--r-- | src/frontend/app/data/PlannerApi.ts | 2 | ||||
| -rw-r--r-- | src/frontend/app/i18n/locales/en-GB.json | 5 | ||||
| -rw-r--r-- | src/frontend/app/i18n/locales/es-ES.json | 5 | ||||
| -rw-r--r-- | src/frontend/app/i18n/locales/gl-ES.json | 16 | ||||
| -rw-r--r-- | src/frontend/app/routes/planner.tsx | 42 | ||||
| -rw-r--r-- | src/frontend/package-lock.json | 19 |
7 files changed, 63 insertions, 27 deletions
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<string, string> = { + "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 = ({ </ul> </details> )} + {(() => { + const municipality = getUrbanMunicipalityWarning(leg); + if (!municipality) return null; + return ( + <div className="mt-2 flex items-start gap-2 rounded-md bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-300 dark:border-yellow-700 px-3 py-2 text-xs text-yellow-800 dark:text-yellow-200"> + <AlertTriangle className="w-4 h-4 shrink-0 mt-0.5 text-yellow-600 dark:text-yellow-400" /> + <div> + <div className="font-semibold"> + {t("planner.urban_traffic_warning")} + </div> + <div> + {t("planner.urban_traffic_warning_desc", { + municipality, + })} + </div> + </div> + </div> + ); + })()} </> )} </div> @@ -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, diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 05ea86e..76fedf5 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -91,7 +91,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1479,7 +1478,6 @@ "integrity": "sha512-vh5lr41rioXLz/zNLTYo0zq4yh97AkgEkJK7bhPeXnNbLNtI36WCZ2AeBtSJ4sdx4gx5LZvcjP8zoWFfSbNupA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@mjackson/node-fetch-server": "^0.2.0", "@react-router/express": "7.13.1", @@ -2151,7 +2149,6 @@ "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -2162,7 +2159,6 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2232,7 +2228,6 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -2564,7 +2559,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2782,7 +2776,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -3354,7 +3347,6 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4363,8 +4355,7 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/leaflet.markercluster": { "version": "1.5.3", @@ -4702,7 +4693,6 @@ "integrity": "sha512-REhYUN8gNP3HlcIZS6QU2uy8iovl31cXsrNDkCcqWSQbCkcpdYLczqDz5PVIwNH42UQNyvukjes/RoHPDrOUmQ==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", @@ -5175,7 +5165,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5358,7 +5347,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5368,7 +5356,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5510,7 +5497,6 @@ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", "license": "MIT", - "peer": true, "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -6153,7 +6139,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6326,7 +6311,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -6485,7 +6469,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } |
