aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2025-11-14 18:24:43 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2025-11-14 18:24:53 +0100
commitd285093900ff6f8e3d5dba394999bb413f5d00f3 (patch)
tree88b9127d031177b8e3787d4e263ae7cb7d9a461f
parent80f6263516e307bcc6d887f6f91757bc73ae63f2 (diff)
Enhance stop map functionality with new styles and components for better user experience
-rw-r--r--src/frontend/app/components/StopMapSheet.css17
-rw-r--r--src/frontend/app/components/StopMapSheet.tsx218
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationList.css14
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx45
-rw-r--r--src/frontend/app/data/LineColors.ts100
-rw-r--r--src/frontend/app/routes/estimates-$id.css53
-rw-r--r--src/frontend/app/routes/stops-$id.tsx79
-rw-r--r--src/stop_downloader/vigo/overrides/navidad-2025.yaml19
8 files changed, 479 insertions, 66 deletions
diff --git a/src/frontend/app/components/StopMapSheet.css b/src/frontend/app/components/StopMapSheet.css
new file mode 100644
index 0000000..8ad784d
--- /dev/null
+++ b/src/frontend/app/components/StopMapSheet.css
@@ -0,0 +1,17 @@
+/* Stop map container */
+.stop-map-container {
+ width: 100%;
+ height: 300px;
+ border-radius: 8px;
+ overflow: hidden;
+ border: 1px solid var(--border-color);
+ margin-block-start: 0;
+ margin-block-end: 1rem;
+ flex-shrink: 0;
+}
+
+@media (max-width: 640px) {
+ .stop-map-container {
+ height: 250px;
+ }
+}
diff --git a/src/frontend/app/components/StopMapSheet.tsx b/src/frontend/app/components/StopMapSheet.tsx
new file mode 100644
index 0000000..a0d30f4
--- /dev/null
+++ b/src/frontend/app/components/StopMapSheet.tsx
@@ -0,0 +1,218 @@
+import maplibregl from "maplibre-gl";
+import React, { useEffect, useMemo, useRef, useState } from "react";
+import Map, { Marker, NavigationControl, type MapRef } from "react-map-gl/maplibre";
+import { useApp } from "~/AppContext";
+import { getLineColor } from "~/data/LineColors";
+import type { RegionId } from "~/data/RegionConfig";
+import type { Stop } from "~/data/StopDataProvider";
+import { loadStyle } from "~/maps/styleloader";
+import "./StopMapSheet.css";
+
+export interface Position {
+ latitude: number;
+ longitude: number;
+ orientationDegrees: number;
+}
+
+export interface ConsolidatedCirculationForMap {
+ line: string;
+ route: string;
+ currentPosition?: Position;
+}
+
+interface StopMapProps {
+ stop: Stop;
+ circulations: ConsolidatedCirculationForMap[];
+ region: RegionId;
+}
+
+export const StopMap: React.FC<StopMapProps> = ({
+ stop,
+ circulations,
+ region,
+}) => {
+ const { theme } = useApp();
+ const [styleSpec, setStyleSpec] = useState<any | null>(null);
+ const mapRef = useRef<MapRef | null>(null);
+ const hasFitBounds = useRef(false);
+
+ useEffect(() => {
+ let mounted = true;
+ loadStyle("openfreemap", theme)
+ .then((style) => {
+ if (mounted) setStyleSpec(style);
+ })
+ .catch((err) => console.error("Failed to load map style", err));
+ return () => {
+ mounted = false;
+ };
+ }, [theme]);
+
+ const center = useMemo(() => {
+ if (stop.latitude && stop.longitude) {
+ return { latitude: stop.latitude, longitude: stop.longitude };
+ }
+ // fallback to first available bus position
+ const pos = circulations.find((c) => c.currentPosition)?.currentPosition;
+ return pos
+ ? { latitude: pos.latitude, longitude: pos.longitude }
+ : { latitude: 42.2406, longitude: -8.7207 }; // Vigo approx fallback
+ }, [stop.latitude, stop.longitude, circulations]);
+
+ const busPositions = useMemo(
+ () => circulations.filter((c) => !!c.currentPosition),
+ [circulations],
+ );
+
+ // Fit bounds to stop + buses, with ~1km padding each side, with a modest animation
+ // Only fit bounds on the first load, not on subsequent updates
+ useEffect(() => {
+ if (!styleSpec || !mapRef.current || hasFitBounds.current) return;
+
+ const points: { lat: number; lon: number }[] = [];
+ if (stop.latitude && stop.longitude) {
+ points.push({ lat: stop.latitude, lon: stop.longitude });
+ }
+ for (const c of busPositions) {
+ if (c.currentPosition) {
+ points.push({
+ lat: c.currentPosition.latitude,
+ lon: c.currentPosition.longitude,
+ });
+ }
+ }
+ if (points.length === 0) return;
+
+ let minLat = points[0].lat,
+ maxLat = points[0].lat,
+ minLon = points[0].lon,
+ maxLon = points[0].lon;
+ for (const p of points) {
+ if (p.lat < minLat) minLat = p.lat;
+ if (p.lat > maxLat) maxLat = p.lat;
+ if (p.lon < minLon) minLon = p.lon;
+ if (p.lon > maxLon) maxLon = p.lon;
+ }
+
+ // ~1km in degrees
+ const kmToDegLat = 1.0 / 111.32; // ≈0.008983
+ const centerLat = (minLat + maxLat) / 2;
+ const kmToDegLon = kmToDegLat / Math.max(Math.cos((centerLat * Math.PI) / 180), 0.1);
+ const padLat = kmToDegLat;
+ const padLon = kmToDegLon;
+
+ const sw = [minLon - padLon, minLat - padLat] as [number, number];
+ const ne = [maxLon + padLon, maxLat + padLat] as [number, number];
+ const bounds = new maplibregl.LngLatBounds(sw, ne);
+
+ try {
+ mapRef.current.fitBounds(bounds, {
+ padding: 32,
+ duration: 700,
+ maxZoom: 17,
+ } as any);
+ hasFitBounds.current = true;
+ } catch {}
+ }, [styleSpec, stop.latitude, stop.longitude, busPositions]);
+
+ return (
+ <div className="stop-map-container">
+ {styleSpec && (
+ <Map
+ mapLib={maplibregl as any}
+ initialViewState={{
+ latitude: center.latitude,
+ longitude: center.longitude,
+ zoom: 16,
+ }}
+ style={{ width: "100%", height: "100%" }}
+ mapStyle={styleSpec}
+ attributionControl={false}
+ ref={mapRef}
+ >
+ <NavigationControl position="top-left" />
+
+ {/* Stop marker (center) */}
+ {stop.latitude && stop.longitude && (
+ <Marker
+ longitude={stop.longitude}
+ latitude={stop.latitude}
+ anchor="bottom"
+ >
+ <div
+ style={{
+ width: 14,
+ height: 14,
+ background: "#1976d2",
+ border: "2px solid white",
+ borderRadius: "50%",
+ boxShadow: "0 0 0 2px rgba(0,0,0,0.2)",
+ }}
+ title={`Stop ${stop.stopId}`}
+ />
+ </Marker>
+ )}
+
+ {/* Bus markers with heading */}
+ {busPositions.map((c, idx) => {
+ const p = c.currentPosition!;
+ const lineColor = getLineColor(region, c.line);
+ return (
+ <Marker
+ key={idx}
+ longitude={p.longitude}
+ latitude={p.latitude}
+ anchor="center"
+ >
+ <div
+ title={`${c.line} → ${c.route}`}
+ style={{
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ gap: 2,
+ transform: `rotate(${p.orientationDegrees}deg)`,
+ transformOrigin: "center center",
+ }}
+ >
+ {/* Line number above */}
+ <div
+ style={{
+ background: lineColor.background,
+ color: lineColor.text,
+ padding: "2px 4px",
+ borderRadius: 4,
+ fontSize: 10,
+ fontWeight: 700,
+ lineHeight: 1,
+ border: "1px solid #fff",
+ boxShadow: "0 1px 2px rgba(0,0,0,0.3)",
+ }}
+ >
+ {c.line}
+ </div>
+ {/* Arrow pointing direction */}
+ <svg
+ width="20"
+ height="20"
+ viewBox="0 0 24 24"
+ style={{
+ filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.3))",
+ }}
+ >
+ <path
+ d="M12 2 L20 22 L12 18 L4 22 Z"
+ fill={lineColor.background}
+ stroke="#fff"
+ strokeWidth="1.5"
+ />
+ </svg>
+ </div>
+ </Marker>
+ );
+ })}
+ </Map>
+ )}
+ </div>
+ );
+};
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
index 65e897b..3705ec3 100644
--- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
+++ b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
@@ -104,12 +104,22 @@
color: #09106e;
}
+/* Scheduled-only: dark blue in light mode, softer blue in dark mode */
.consolidated-circulation-card .arrival-time.time-scheduled {
- color: var(--text-color);
+ color: #0b3d91; /* dark blue */
}
.consolidated-circulation-card .arrival-time.time-scheduled svg {
- color: var(--subtitle-color);
+ color: #0b3d91;
+}
+
+@media (prefers-color-scheme: dark) {
+ .consolidated-circulation-card .arrival-time.time-scheduled {
+ color: #8fb4ff; /* lighten for dark backgrounds */
+ }
+ .consolidated-circulation-card .arrival-time.time-scheduled svg {
+ color: #8fb4ff;
+ }
}
.consolidated-circulation-card .distance-info {
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx
index 1ba460b..37f6a47 100644
--- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx
+++ b/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx
@@ -1,8 +1,8 @@
-import { useTranslation } from "react-i18next";
import { Clock } from "lucide-react";
-import { type ConsolidatedCirculation } from "~routes/stops-$id";
+import { useTranslation } from "react-i18next";
import LineIcon from "~components/LineIcon";
import { type RegionConfig } from "~data/RegionConfig";
+import { type ConsolidatedCirculation } from "~routes/stops-$id";
import "./ConsolidatedCirculationList.css";
@@ -104,15 +104,11 @@ export const ConsolidatedCirculationList: React.FC<RegularTableProps> = ({
const delay = estimate.realTime.minutes - estimate.schedule.minutes;
if (delay >= -1 && delay <= 2) {
- return t("estimates.on_time", "on time");
+ return "OK"
} else if (delay > 2) {
- return t("estimates.minutes_late", "{{minutes}} minutes late", {
- minutes: delay,
- });
+ return "R" + delay;
} else {
- return t("estimates.minutes_early", "{{minutes}} minutes early", {
- minutes: Math.abs(delay),
- });
+ return "A" + Math.abs(delay);
}
};
@@ -179,15 +175,32 @@ export const ConsolidatedCirculationList: React.FC<RegularTableProps> = ({
? `${displayMinutes} ${t("estimates.minutes", "min")}`
: absoluteArrivalTime(displayMinutes)}
</div>
- {estimate.realTime && estimate.realTime.distance >= 0 && (
- <div className="distance-info">
- {formatDistance(estimate.realTime.distance)}
- </div>
- )}
+ <div className="distance-info">
+ {estimate.schedule && (
+ <>
+ {parseServiceId(estimate.schedule.serviceId)} v{getTripIdDisplay(estimate.schedule.tripId)} {" "}
+ </>
+ )}
+
+ {estimate.schedule &&
+ estimate.realTime &&
+ estimate.realTime.distance >= 0 && <> &middot; </>}
+
+ {estimate.realTime && estimate.realTime.distance >= 0 && (
+ <>{formatDistance(estimate.realTime.distance)}</>
+ )}
+
+ {estimate.schedule &&
+ estimate.realTime &&
+ estimate.realTime.distance >= 0 && <> &middot; </>}
+
+ {delayText}
+
+ </div>
</div>
</div>
- <div className="card-footer">
+ {/*<div className="card-footer">
<span className="status-text">
{delayText && (
<>
@@ -213,7 +226,7 @@ export const ConsolidatedCirculationList: React.FC<RegularTableProps> = ({
</>
)}
</span>
- </div>
+ </div>*/}
</div>
);
})}
diff --git a/src/frontend/app/data/LineColors.ts b/src/frontend/app/data/LineColors.ts
new file mode 100644
index 0000000..878fd8f
--- /dev/null
+++ b/src/frontend/app/data/LineColors.ts
@@ -0,0 +1,100 @@
+import type { RegionId } from "./RegionConfig";
+
+interface LineColorInfo {
+ background: string;
+ text: string;
+}
+
+const vigoLineColors: Record<string, LineColorInfo> = {
+ c1: { background: "rgb(237, 71, 19)", text: "#ffffff" },
+ c3d: { background: "rgb(255, 204, 0)", text: "#000000" },
+ c3i: { background: "rgb(255, 204, 0)", text: "#000000" },
+ l4a: { background: "rgb(0, 153, 0)", text: "#ffffff" },
+ l4c: { background: "rgb(0, 153, 0)", text: "#ffffff" },
+ l5a: { background: "rgb(0, 176, 240)", text: "#000000" },
+ l5b: { background: "rgb(0, 176, 240)", text: "#000000" },
+ l6: { background: "rgb(204, 51, 153)", text: "#ffffff" },
+ l7: { background: "rgb(150, 220, 153)", text: "#000000" },
+ l9b: { background: "rgb(244, 202, 140)", text: "#000000" },
+ l10: { background: "rgb(153, 51, 0)", text: "#ffffff" },
+ l11: { background: "rgb(226, 0, 38)", text: "#ffffff" },
+ l12a: { background: "rgb(106, 150, 190)", text: "#000000" },
+ l12b: { background: "rgb(106, 150, 190)", text: "#000000" },
+ l13: { background: "rgb(0, 176, 240)", text: "#000000" },
+ l14: { background: "rgb(129, 142, 126)", text: "#ffffff" },
+ l15a: { background: "rgb(216, 168, 206)", text: "#000000" },
+ l15b: { background: "rgb(216, 168, 206)", text: "#000000" },
+ l15c: { background: "rgb(216, 168, 168)", text: "#000000" },
+ l16: { background: "rgb(129, 142, 126)", text: "#ffffff" },
+ l17: { background: "rgb(214, 245, 31)", text: "#000000" },
+ l18a: { background: "rgb(212, 80, 168)", text: "#ffffff" },
+ l18b: { background: "rgb(212, 80, 168)", text: "#ffffff" },
+ l18h: { background: "rgb(212, 80, 168)", text: "#ffffff" },
+ l23: { background: "rgb(0, 70, 210)", text: "#ffffff" },
+ l24: { background: "rgb(191, 191, 191)", text: "#000000" },
+ l25: { background: "rgb(172, 100, 4)", text: "#ffffff" },
+ l27: { background: "rgb(112, 74, 42)", text: "#ffffff" },
+ l28: { background: "rgb(176, 189, 254)", text: "#000000" },
+ l29: { background: "rgb(248, 184, 90)", text: "#000000" },
+ l31: { background: "rgb(255, 255, 0)", text: "#000000" },
+ a: { background: "rgb(119, 41, 143)", text: "#ffffff" },
+ h: { background: "rgb(0, 96, 168)", text: "#ffffff" },
+ h1: { background: "rgb(0, 96, 168)", text: "#ffffff" },
+ h2: { background: "rgb(0, 96, 168)", text: "#ffffff" },
+ h3: { background: "rgb(0, 96, 168)", text: "#ffffff" },
+ lzd: { background: "rgb(61, 78, 167)", text: "#ffffff" },
+ n1: { background: "rgb(191, 191, 191)", text: "#000000" },
+ n4: { background: "rgb(102, 51, 102)", text: "#ffffff" },
+ psa1: { background: "rgb(0, 153, 0)", text: "#ffffff" },
+ psa4: { background: "rgb(0, 153, 0)", text: "#ffffff" },
+ ptl: { background: "rgb(150, 220, 153)", text: "#000000" },
+ turistico: { background: "rgb(102, 51, 102)", text: "#ffffff" },
+ u1: { background: "rgb(172, 100, 4)", text: "#ffffff" },
+ u2: { background: "rgb(172, 100, 4)", text: "#ffffff" },
+};
+
+const santiagoLineColors: Record<string, LineColorInfo> = {
+ l1: { background: "#f32621", text: "#ffffff" },
+ l4: { background: "#ffcc33", text: "#000000" },
+ l5: { background: "#fa8405", text: "#ffffff" },
+ l6: { background: "#d73983", text: "#ffffff" },
+ l6a: { background: "#d73983", text: "#ffffff" },
+ l7: { background: "#488bc1", text: "#ffffff" },
+ l8: { background: "#6aaf48", text: "#ffffff" },
+ l9: { background: "#46b8bb", text: "#ffffff" },
+ c11: { background: "#aec741", text: "#000000" },
+ l12: { background: "#842e14", text: "#ffffff" },
+ l13: { background: "#336600", text: "#ffffff" },
+ l15: { background: "#7a4b2a", text: "#ffffff" },
+ c2: { background: "#283a87", text: "#ffffff" },
+ c4: { background: "#283a87", text: "#ffffff" },
+ c5: { background: "#999999", text: "#000000" },
+ c6: { background: "#006666", text: "#ffffff" },
+ p1: { background: "#537eb3", text: "#ffffff" },
+ p2: { background: "#d23354", text: "#ffffff" },
+ p3: { background: "#75bd96", text: "#000000" },
+ p4: { background: "#f1c54f", text: "#000000" },
+ p6: { background: "#999999", text: "#000000" },
+ p7: { background: "#d2438c", text: "#ffffff" },
+ p8: { background: "#e28c3a", text: "#ffffff" },
+};
+
+const defaultLineColor: LineColorInfo = {
+ background: "#d32f2f",
+ text: "#ffffff",
+};
+
+export function getLineColor(
+ region: RegionId,
+ line: string,
+): LineColorInfo {
+ const normalizedLine = line.toLowerCase().trim();
+
+ if (region === "vigo") {
+ return vigoLineColors[normalizedLine] ?? defaultLineColor;
+ } else if (region === "santiago") {
+ return santiagoLineColors[normalizedLine] ?? defaultLineColor;
+ }
+
+ return defaultLineColor;
+}
diff --git a/src/frontend/app/routes/estimates-$id.css b/src/frontend/app/routes/estimates-$id.css
index 81fba1d..ff09f82 100644
--- a/src/frontend/app/routes/estimates-$id.css
+++ b/src/frontend/app/routes/estimates-$id.css
@@ -1,6 +1,20 @@
+.estimates-content-wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ min-height: 0;
+ flex: 1;
+}
+
+.estimates-list-container {
+ overflow-y: auto;
+ flex: 1;
+ min-height: 0;
+ border-radius: 0.5rem;
+}
+
.table-responsive {
overflow-x: auto;
- margin-bottom: 1.5rem;
}
.table {
@@ -29,12 +43,20 @@
}
/* Estimates page specific styles */
+.estimates-page {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ overflow: hidden;
+}
+
.estimates-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
gap: 1rem;
+ flex-shrink: 0;
}
.manual-refresh-button {
@@ -63,6 +85,32 @@
transform: none;
}
+.toggle-map-button {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.5rem 0.75rem;
+ background: var(--primary-color);
+ border: none;
+ border-radius: 0.375rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ min-width: max-content;
+}
+
+.toggle-map-button:hover:not(:disabled) {
+ background: var(--primary-color-hover);
+ transform: translateY(-1px);
+}
+
+.toggle-map-button:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none;
+}
+
.refresh-icon {
width: 1.5rem;
height: 1.5rem;
@@ -215,6 +263,7 @@
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 1rem;
+ flex-shrink: 0;
}
.estimates-lines-container.scrollable {
@@ -245,6 +294,7 @@
padding: 1rem;
margin-bottom: 1rem;
color: #856404;
+ flex-shrink: 0;
}
.experimental-notice strong {
@@ -282,6 +332,7 @@
color: var(--primary-color);
font-size: 0.9rem;
font-weight: 500;
+ flex-shrink: 0;
}
.refresh-status .refresh-icon {
diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx
index 86e74d9..efe2867 100644
--- a/src/frontend/app/routes/stops-$id.tsx
+++ b/src/frontend/app/routes/stops-$id.tsx
@@ -1,18 +1,19 @@
-import { useEffect, useState, useCallback } from "react";
-import { useParams, Link } from "react-router";
-import StopDataProvider, { type Stop } from "../data/StopDataProvider";
-import { Star, Edit2, ExternalLink, RefreshCw } from "lucide-react";
-import "./estimates-$id.css";
-import { useApp } from "../AppContext";
+import { Edit2, RefreshCw, Star } from "lucide-react";
+import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
+import { useParams } from "react-router";
+import { ErrorDisplay } from "~/components/ErrorDisplay";
+import LineIcon from "~/components/LineIcon";
import { PullToRefresh } from "~/components/PullToRefresh";
-import { useAutoRefresh } from "~/hooks/useAutoRefresh";
-import { type RegionId, getRegionConfig } from "~/data/RegionConfig";
import { StopAlert } from "~/components/StopAlert";
-import LineIcon from "~/components/LineIcon";
+import { StopMap } from "~/components/StopMapSheet";
import { ConsolidatedCirculationList } from "~/components/Stops/ConsolidatedCirculationList";
import { ConsolidatedCirculationListSkeleton } from "~/components/Stops/ConsolidatedCirculationListSkeleton";
-import { ErrorDisplay } from "~/components/ErrorDisplay";
+import { type RegionId, getRegionConfig } from "~/data/RegionConfig";
+import { useAutoRefresh } from "~/hooks/useAutoRefresh";
+import { useApp } from "../AppContext";
+import StopDataProvider, { type Stop } from "../data/StopDataProvider";
+import "./estimates-$id.css";
export interface ConsolidatedCirculation {
line: string;
@@ -27,6 +28,11 @@ export interface ConsolidatedCirculation {
minutes: number;
distance: number;
};
+ currentPosition?: {
+ latitude: number;
+ longitude: number;
+ orientationDegrees: number;
+ };
}
interface ErrorInfo {
@@ -266,25 +272,42 @@ export default function Estimates() {
{stopData && <StopAlert stop={stopData} />}
- <div className="table-responsive">
- {dataLoading ? (
- <ConsolidatedCirculationListSkeleton />
- ) : dataError ? (
- <ErrorDisplay
- error={dataError}
- onRetry={loadData}
- title={t(
- "errors.estimates_title",
- "Error al cargar estimaciones",
- )}
- />
- ) : data ? (
- <ConsolidatedCirculationList
- data={data}
- dataDate={dataDate}
- regionConfig={regionConfig}
+ <div className="estimates-content-wrapper">
+ <div className="estimates-list-container">
+ <div className="table-responsive">
+ {dataLoading ? (
+ <ConsolidatedCirculationListSkeleton />
+ ) : dataError ? (
+ <ErrorDisplay
+ error={dataError}
+ onRetry={loadData}
+ title={t(
+ "errors.estimates_title",
+ "Error al cargar estimaciones",
+ )}
+ />
+ ) : data ? (
+ <ConsolidatedCirculationList
+ data={data}
+ dataDate={dataDate}
+ regionConfig={regionConfig}
+ />
+ ) : null}
+ </div>
+ </div>
+
+ {/* Map showing stop and bus positions */}
+ {stopData && (
+ <StopMap
+ stop={stopData}
+ region={region}
+ circulations={(data ?? []).map((c) => ({
+ line: c.line,
+ route: c.route,
+ currentPosition: c.currentPosition,
+ }))}
/>
- ) : null}
+ )}
</div>
</div>
</PullToRefresh>
diff --git a/src/stop_downloader/vigo/overrides/navidad-2025.yaml b/src/stop_downloader/vigo/overrides/navidad-2025.yaml
index cb20222..cb7f1d7 100644
--- a/src/stop_downloader/vigo/overrides/navidad-2025.yaml
+++ b/src/stop_downloader/vigo/overrides/navidad-2025.yaml
@@ -1,22 +1,3 @@
-20208:
- new: true
- name: "Colón 12"
- title: "Parada provisional sin datos"
- message: "Parada provisional donde paran las líneas de Policarpo Sanz 40 *exceptuando 9B*"
- location:
- latitude: 42.23805815883466
- longitude: -8.72057889828808
- lines:
- - "C1"
- - "A"
- - "5A"
- - "9B"
- - "15B"
- - "15C"
- - "24"
- - "28"
- - "N4"
-
20194: # Cánovas del Castillo 28
cancelled: true
alert: "error"