diff options
Diffstat (limited to 'src/frontend/app/routes/map.tsx')
| -rw-r--r-- | src/frontend/app/routes/map.tsx | 284 |
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> + ); } |
