aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/routes/map.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/app/routes/map.tsx')
-rw-r--r--src/frontend/app/routes/map.tsx284
1 files changed, 146 insertions, 138 deletions
diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx
index ca095e2..5887b9c 100644
--- a/src/frontend/app/routes/map.tsx
+++ b/src/frontend/app/routes/map.tsx
@@ -1,13 +1,21 @@
import StopDataProvider from "../data/StopDataProvider";
-import './map.css';
+import "./map.css";
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState } from "react";
import { useApp } from "../AppContext";
-import Map, { AttributionControl, GeolocateControl, Layer, NavigationControl, Popup, Source, type MapRef, type MapLayerMouseEvent, type StyleSpecification } from "react-map-gl/maplibre";
+import Map, {
+ AttributionControl,
+ GeolocateControl,
+ Layer,
+ NavigationControl,
+ Source,
+ type MapRef,
+ type MapLayerMouseEvent,
+ type StyleSpecification,
+} from "react-map-gl/maplibre";
import { loadStyle } from "app/maps/styleloader";
-import type { Feature as GeoJsonFeature, Point } from 'geojson';
-import LineIcon from "~/components/LineIcon";
-import { Link } from "react-router";
+import type { Feature as GeoJsonFeature, Point } from "geojson";
+import { StopSheet } from "~/components/StopSheet";
import { useTranslation } from "react-i18next";
// Default minimal fallback style before dynamic loading
@@ -16,154 +24,154 @@ const defaultStyle: StyleSpecification = {
glyphs: `${window.location.origin}/maps/fonts/{fontstack}/{range}.pbf`,
sprite: `${window.location.origin}/maps/spritesheet/sprite`,
sources: {},
- layers: []
+ layers: [],
};
// Componente principal del mapa
export default function StopMap() {
- const { t } = useTranslation();
- const [stops, setStops] = useState<GeoJsonFeature<Point, { stopId: number; name: string; lines: string[] }>[]>([]);
- const [popupInfo, setPopupInfo] = useState<any>(null);
- const { mapState, updateMapState, theme } = useApp();
- const mapRef = useRef<MapRef>(null);
- const [mapStyleKey, setMapStyleKey] = useState<string>("light");
+ const { t } = useTranslation();
+ const [stops, setStops] = useState<
+ GeoJsonFeature<Point, { stopId: number; name: string; lines: string[] }>[]
+ >([]);
+ const [selectedStop, setSelectedStop] = useState<{
+ stopId: number;
+ name: string;
+ } | null>(null);
+ const [isSheetOpen, setIsSheetOpen] = useState(false);
+ const { mapState, updateMapState, theme } = useApp();
+ const mapRef = useRef<MapRef>(null);
+ const [mapStyleKey, setMapStyleKey] = useState<string>("light");
- // Style state for Map component
- const [mapStyle, setMapStyle] = useState<StyleSpecification>(defaultStyle);
+ // Style state for Map component
+ const [mapStyle, setMapStyle] = useState<StyleSpecification>(defaultStyle);
- // Handle click events on clusters and individual stops
- const onMapClick = (e: MapLayerMouseEvent) => {
- const features = e.features;
- if (!features || features.length === 0) return;
- const feature = features[0];
- const props: any = feature.properties;
+ // Handle click events on clusters and individual stops
+ const onMapClick = (e: MapLayerMouseEvent) => {
+ const features = e.features;
+ if (!features || features.length === 0) return;
+ const feature = features[0];
+ const props: any = feature.properties;
- handlePointClick(feature);
- };
+ handlePointClick(feature);
+ };
- useEffect(() => {
- StopDataProvider.getStops().then(data => {
- const features: GeoJsonFeature<Point, { stopId: number; name: string; lines: string[] }>[] = data.map(s => ({
- type: "Feature",
- geometry: { type: "Point", coordinates: [s.longitude as number, s.latitude as number] },
- properties: { stopId: s.stopId, name: s.name.original, lines: s.lines }
- }));
- setStops(features);
- });
- }, []);
+ useEffect(() => {
+ StopDataProvider.getStops().then((data) => {
+ const features: GeoJsonFeature<
+ Point,
+ { stopId: number; name: string; lines: string[] }
+ >[] = data.map((s) => ({
+ type: "Feature",
+ geometry: {
+ type: "Point",
+ coordinates: [s.longitude as number, s.latitude as number],
+ },
+ properties: { stopId: s.stopId, name: s.name.original, lines: s.lines },
+ }));
+ setStops(features);
+ });
+ }, []);
- useEffect(() => {
- const styleName = "carto";
- loadStyle(styleName, theme)
- .then(style => setMapStyle(style))
- .catch(error => console.error("Failed to load map style:", error));
- }, [mapStyleKey, theme]);
+ useEffect(() => {
+ const styleName = "carto";
+ loadStyle(styleName, theme)
+ .then((style) => setMapStyle(style))
+ .catch((error) => console.error("Failed to load map style:", error));
+ }, [mapStyleKey, 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);
- };
+ 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);
+ };
- if (mapRef.current) {
- const map = mapRef.current.getMap();
- if (map) {
- map.on('moveend', handleMapChange);
- }
- }
+ if (mapRef.current) {
+ const map = mapRef.current.getMap();
+ if (map) {
+ map.on("moveend", handleMapChange);
+ }
+ }
- return () => {
- if (mapRef.current) {
- const map = mapRef.current.getMap();
- if (map) {
- map.off('moveend', handleMapChange);
- }
- }
- };
- }, [mapRef.current]);
+ return () => {
+ if (mapRef.current) {
+ const map = mapRef.current.getMap();
+ if (map) {
+ map.off("moveend", handleMapChange);
+ }
+ }
+ };
+ }, [mapRef.current]);
- const getLatitude = (center: any) => Array.isArray(center) ? center[0] : center.lat;
- const getLongitude = (center: any) => Array.isArray(center) ? center[1] : center.lng;
+ const getLatitude = (center: any) =>
+ Array.isArray(center) ? center[0] : center.lat;
+ const getLongitude = (center: any) =>
+ Array.isArray(center) ? center[1] : center.lng;
- const handlePointClick = (feature: any) => {
- const props: any = feature.properties;
- // fetch full stop to get lines array
- StopDataProvider.getStopById(props.stopId).then(stop => {
- if (!stop) return;
- setPopupInfo({
- geometry: feature.geometry,
- properties: {
- stopId: stop.stopId,
- name: stop.name.original,
- lines: stop.lines
- }
- });
- });
- };
+ const handlePointClick = (feature: any) => {
+ const props: any = feature.properties;
+ // fetch full stop to get lines array
+ StopDataProvider.getStopById(props.stopId).then((stop) => {
+ if (!stop) return;
+ setSelectedStop({
+ stopId: stop.stopId,
+ name: stop.name.original,
+ });
+ setIsSheetOpen(true);
+ });
+ };
- return (
- <Map
- mapStyle={mapStyle}
- style={{ width: '100%', height: '100%' }}
- interactiveLayerIds={["stops"]}
- onClick={onMapClick}
- minZoom={11}
- scrollZoom
- pitch={0}
- roll={0}
- ref={mapRef}
- initialViewState={{
- latitude: getLatitude(mapState.center),
- longitude: getLongitude(mapState.center),
- zoom: mapState.zoom,
- }}
- attributionControl={false}
- >
- <NavigationControl position="top-right" />
- <GeolocateControl position="top-right" trackUserLocation={true} />
- <AttributionControl position="bottom-right" compact={false} />
+ return (
+ <Map
+ mapStyle={mapStyle}
+ style={{ width: "100%", height: "100%" }}
+ interactiveLayerIds={["stops"]}
+ onClick={onMapClick}
+ minZoom={11}
+ scrollZoom
+ pitch={0}
+ roll={0}
+ ref={mapRef}
+ initialViewState={{
+ latitude: getLatitude(mapState.center),
+ longitude: getLongitude(mapState.center),
+ zoom: mapState.zoom,
+ }}
+ attributionControl={false}
+ >
+ <NavigationControl position="top-right" />
+ <GeolocateControl position="top-right" trackUserLocation={true} />
+ <AttributionControl position="bottom-right" compact={false} />
- <Source
- id="stops-source"
- type="geojson"
- data={{ type: "FeatureCollection", features: stops }}
- />
+ <Source
+ id="stops-source"
+ type="geojson"
+ data={{ type: "FeatureCollection", features: stops }}
+ />
- <Layer
- id="stops"
- type="symbol"
- source="stops-source"
- layout={{
- "icon-image": "stop",
- "icon-size": ["interpolate", ["linear"], ["zoom"], 11, 0.4, 16, 0.8],
- "icon-allow-overlap": true,
- "icon-ignore-placement": true,
- }}
- />
+ <Layer
+ id="stops"
+ type="symbol"
+ source="stops-source"
+ layout={{
+ "icon-image": "stop",
+ "icon-size": ["interpolate", ["linear"], ["zoom"], 11, 0.4, 16, 0.8],
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true,
+ }}
+ />
- {popupInfo && (
- <Popup
- latitude={popupInfo.geometry.coordinates[1]}
- longitude={popupInfo.geometry.coordinates[0]}
- onClose={() => setPopupInfo(null)}
- >
- <div>
- <h3>{popupInfo.properties.name}</h3>
- <div>
- {popupInfo.properties.lines.map((line: string) => (
- <LineIcon line={line} key={line} />
- ))}
- </div>
- <Link to={`/estimates/${popupInfo.properties.stopId}`} className="popup-link">
- Ver parada
- </Link>
- </div>
- </Popup>
- )}
- </Map>
- );
+ {selectedStop && (
+ <StopSheet
+ isOpen={isSheetOpen}
+ onClose={() => setIsSheetOpen(false)}
+ stopId={selectedStop.stopId}
+ stopName={selectedStop.name}
+ />
+ )}
+ </Map>
+ );
}