summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCopilot <198982749+Copilot@users.noreply.github.com>2026-03-05 18:24:51 +0100
committerGitHub <noreply@github.com>2026-03-05 18:24:51 +0100
commit49ef6f4af837d4f3f4f367fa831f1ff176036c27 (patch)
tree157c5f7fbbfb4da181a3dce9fb8e4700d82b5231
parent6e2d9ffe812eb1ca8fe5d04d3df2aa322e9e5760 (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.ts1
-rw-r--r--src/frontend/app/data/PlannerApi.ts2
-rw-r--r--src/frontend/app/i18n/locales/en-GB.json5
-rw-r--r--src/frontend/app/i18n/locales/es-ES.json5
-rw-r--r--src/frontend/app/i18n/locales/gl-ES.json16
-rw-r--r--src/frontend/app/routes/planner.tsx42
-rw-r--r--src/frontend/package-lock.json19
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"
}