aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/routes
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/app/routes')
-rw-r--r--src/frontend/app/routes/map.tsx96
-rw-r--r--src/frontend/app/routes/planner.tsx20
-rw-r--r--src/frontend/app/routes/settings.tsx42
-rw-r--r--src/frontend/app/routes/stops-$id.tsx7
4 files changed, 57 insertions, 108 deletions
diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx
index 517549b..45dd935 100644
--- a/src/frontend/app/routes/map.tsx
+++ b/src/frontend/app/routes/map.tsx
@@ -1,20 +1,17 @@
import StopDataProvider from "../data/StopDataProvider";
import "./map.css";
-import { DEFAULT_STYLE, loadStyle } from "app/maps/styleloader";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
-import Map, {
- GeolocateControl,
+import {
Layer,
- NavigationControl,
Source,
type MapLayerMouseEvent,
type MapRef,
- type StyleSpecification,
} from "react-map-gl/maplibre";
import { useNavigate } from "react-router";
import { PlannerOverlay } from "~/components/PlannerOverlay";
+import { AppMap } from "~/components/shared/AppMap";
import {
StopSummarySheet,
type StopSheetProps,
@@ -34,14 +31,10 @@ export default function StopMap() {
StopSheetProps["stop"] | null
>(null);
const [isSheetOpen, setIsSheetOpen] = useState(false);
- const { mapState, updateMapState, theme } = useApp();
const mapRef = useRef<MapRef>(null);
const { searchRoute } = usePlanner();
- // Style state for Map component
- const [mapStyle, setMapStyle] = useState<StyleSpecification>(DEFAULT_STYLE);
-
// Handle click events on clusters and individual stops
const onMapClick = (e: MapLayerMouseEvent) => {
const features = e.features;
@@ -59,63 +52,6 @@ export default function StopMap() {
handlePointClick(feature);
};
- useEffect(() => {
- //const styleName = "carto";
- const styleName = "openfreemap";
- loadStyle(styleName, theme)
- .then((style) => setMapStyle(style))
- .catch((error) => console.error("Failed to load map style:", error));
- }, [theme]);
-
- useEffect(() => {
- const handleMapChange = () => {
- if (!mapRef.current) return;
- const map = mapRef.current.getMap();
- if (!map) return;
- const center = map.getCenter();
- const zoom = map.getZoom();
- updateMapState([center.lat, center.lng], zoom);
- };
-
- const handleStyleImageMissing = (e: any) => {
- // Suppress warnings for missing sprite images from base style
- // This prevents console noise from OpenFreeMap's missing icons
- if (!mapRef.current) return;
- const map = mapRef.current.getMap();
- if (!map || map.hasImage(e.id)) return;
-
- // Log warning for our own icons if they are missing
- if (e.id.startsWith("stop-")) {
- console.warn(`Missing icon image: ${e.id}`);
- }
-
- // Add a transparent 1x1 placeholder to prevent repeated warnings
- map.addImage(e.id, {
- width: 1,
- height: 1,
- data: new Uint8Array(4),
- });
- };
-
- if (mapRef.current) {
- const map = mapRef.current.getMap();
- if (map) {
- map.on("moveend", handleMapChange);
- map.on("styleimagemissing", handleStyleImageMissing);
- }
- }
-
- return () => {
- if (mapRef.current) {
- const map = mapRef.current.getMap();
- if (map) {
- map.off("moveend", handleMapChange);
- map.off("styleimagemissing", handleStyleImageMissing);
- }
- }
- };
- }, [mapRef.current]);
-
const getLatitude = (center: any) =>
Array.isArray(center) ? center[0] : center.lat;
const getLongitude = (center: any) =>
@@ -166,31 +102,15 @@ export default function StopMap() {
cardBackground="bg-white/95 dark:bg-slate-900/90"
/>
- <Map
- mapStyle={mapStyle}
- style={{ width: "100%", height: "100%" }}
+ <AppMap
+ ref={mapRef}
+ syncState={true}
+ showNavigation={true}
+ showGeolocate={true}
interactiveLayerIds={["stops", "stops-label"]}
onClick={onMapClick}
- minZoom={5}
- scrollZoom
- pitch={0}
- roll={0}
- ref={mapRef}
- initialViewState={{
- latitude: getLatitude(mapState.center),
- longitude: getLongitude(mapState.center),
- zoom: mapState.zoom,
- }}
attributionControl={{ compact: false }}
- maxBounds={[APP_CONSTANTS.bounds.sw, APP_CONSTANTS.bounds.ne]}
>
- <NavigationControl position="bottom-right" />
- <GeolocateControl
- position="bottom-right"
- trackUserLocation={true}
- positionOptions={{ enableHighAccuracy: false }}
- />
-
<Source
id="stops-source"
type="vector"
@@ -284,7 +204,7 @@ export default function StopMap() {
stop={selectedStop}
/>
)}
- </Map>
+ </AppMap>
</div>
);
}
diff --git a/src/frontend/app/routes/planner.tsx b/src/frontend/app/routes/planner.tsx
index 3d0f703..e99cb03 100644
--- a/src/frontend/app/routes/planner.tsx
+++ b/src/frontend/app/routes/planner.tsx
@@ -3,17 +3,17 @@ import maplibregl, { type StyleSpecification } from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
-import Map, { Layer, Source, type MapRef } from "react-map-gl/maplibre";
+import { Layer, Source, type MapRef } from "react-map-gl/maplibre";
import { useLocation } from "react-router";
import { useApp } from "~/AppContext";
import LineIcon from "~/components/LineIcon";
import { PlannerOverlay } from "~/components/PlannerOverlay";
+import { AppMap } from "~/components/shared/AppMap";
import { APP_CONSTANTS } from "~/config/constants";
import { usePageTitle } from "~/contexts/PageTitleContext";
import { type Itinerary } from "~/data/PlannerApi";
import { usePlanner } from "~/hooks/usePlanner";
-import { DEFAULT_STYLE, loadStyle } from "~/maps/styleloader";
import "../tailwind-full.css";
export interface ConsolidatedCirculation {
@@ -371,16 +371,6 @@ const ItineraryDetail = ({
return () => clearTimeout(timer);
}, [mapRef.current, itinerary]);
- const { theme } = useApp();
- const [mapStyle, setMapStyle] = useState<StyleSpecification>(DEFAULT_STYLE);
-
- useEffect(() => {
- const styleName = "openfreemap";
- loadStyle(styleName, theme, { includeTraffic: false })
- .then((style) => setMapStyle(style))
- .catch((error) => console.error("Failed to load map style:", error));
- }, [theme]);
-
// Fetch next arrivals for bus legs
useEffect(() => {
const fetchArrivals = async () => {
@@ -420,7 +410,7 @@ const ItineraryDetail = ({
<div className="flex flex-col md:flex-row h-full">
{/* Map Section */}
<div className="relative h-2/3 md:h-full md:flex-1">
- <Map
+ <AppMap
ref={mapRef}
initialViewState={{
longitude:
@@ -431,7 +421,7 @@ const ItineraryDetail = ({
(APP_CONSTANTS.defaultCenter as [number, number])[1],
zoom: 13,
}}
- mapStyle={mapStyle}
+ showTraffic={false}
attributionControl={false}
>
<Source id="route" type="geojson" data={routeGeoJson as any}>
@@ -565,7 +555,7 @@ const ItineraryDetail = ({
}}
/>
</Source>
- </Map>
+ </AppMap>
<button
onClick={onClose}
diff --git a/src/frontend/app/routes/settings.tsx b/src/frontend/app/routes/settings.tsx
index c615844..f51b2e9 100644
--- a/src/frontend/app/routes/settings.tsx
+++ b/src/frontend/app/routes/settings.tsx
@@ -7,7 +7,16 @@ import "../tailwind-full.css";
export default function Settings() {
const { t, i18n } = useTranslation();
usePageTitle(t("navbar.settings", "Ajustes"));
- const { theme, setTheme, mapPositionMode, setMapPositionMode } = useApp();
+ const {
+ theme,
+ setTheme,
+ mapPositionMode,
+ setMapPositionMode,
+ showTraffic,
+ setShowTraffic,
+ showCameras,
+ setShowCameras,
+ } = useApp();
const THEMES = [
{
@@ -83,6 +92,37 @@ export default function Settings() {
</select>
</section>
+ {/* Map Layers */}
+ <section className="mb-8">
+ <h2 className="text-xl font-semibold mb-4 text-text">
+ {t("about.map_layers", "Capas del mapa")}
+ </h2>
+ <div className="space-y-4">
+ <label className="flex items-center justify-between p-4 rounded-lg border border-border bg-surface cursor-pointer hover:bg-surface/50 transition-colors">
+ <span className="text-text font-medium">
+ {t("about.show_traffic", "Mostrar tráfico")}
+ </span>
+ <input
+ type="checkbox"
+ checked={showTraffic}
+ onChange={(e) => setShowTraffic(e.target.checked)}
+ className="w-5 h-5 rounded border-border text-primary focus:ring-primary/50"
+ />
+ </label>
+ <label className="flex items-center justify-between p-4 rounded-lg border border-border bg-surface cursor-pointer hover:bg-surface/50 transition-colors">
+ <span className="text-text font-medium">
+ {t("about.show_cameras", "Mostrar cámaras")}
+ </span>
+ <input
+ type="checkbox"
+ checked={showCameras}
+ onChange={(e) => setShowCameras(e.target.checked)}
+ className="w-5 h-5 rounded border-border text-primary focus:ring-primary/50"
+ />
+ </label>
+ </div>
+ </section>
+
{/* Language Selection */}
<section>
<label
diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx
index 9147302..7aba9f2 100644
--- a/src/frontend/app/routes/stops-$id.tsx
+++ b/src/frontend/app/routes/stops-$id.tsx
@@ -8,9 +8,8 @@ import { ArrivalList } from "~/components/arrivals/ArrivalList";
import { ErrorDisplay } from "~/components/ErrorDisplay";
import LineIcon from "~/components/LineIcon";
import { PullToRefresh } from "~/components/PullToRefresh";
-import { StopHelpModal } from "~/components/StopHelpModal";
-import { StopMapModal } from "~/components/StopMapModal";
-import { ConsolidatedCirculationListSkeleton } from "~/components/Stops/ConsolidatedCirculationListSkeleton";
+import { StopHelpModal } from "~/components/stop/StopHelpModal";
+import { StopMapModal } from "~/components/stop/StopMapModal";
import { usePageTitle } from "~/contexts/PageTitleContext";
import { useAutoRefresh } from "~/hooks/useAutoRefresh";
import StopDataProvider from "../data/StopDataProvider";
@@ -163,7 +162,7 @@ export default function Estimates() {
<div className="estimates-list-container">
{dataLoading ? (
- <ConsolidatedCirculationListSkeleton />
+ <>{/*TODO: New loading skeleton*/}</>
) : dataError ? (
<ErrorDisplay
error={dataError}