import { Check, X } from "lucide-react"; import type { FilterSpecification } from "maplibre-gl"; import { useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Layer, Source, type MapLayerMouseEvent, type MapRef, } from "react-map-gl/maplibre"; import { useNavigate } from "react-router"; import { useApp } from "~/AppContext"; import { StopSummarySheet, type StopSheetProps, } from "~/components/map/StopSummarySheet"; import { PlannerOverlay } from "~/components/PlannerOverlay"; import { AppMap } from "~/components/shared/AppMap"; import { usePageTitle } from "~/contexts/PageTitleContext"; import { reverseGeocode } from "~/data/PlannerApi"; import { usePlanner } from "~/hooks/usePlanner"; import StopDataProvider from "../data/StopDataProvider"; import "../tailwind-full.css"; import "./map.css"; // Componente principal del mapa export default function StopMap() { const { t } = useTranslation(); const { showBusStops: showCitybusStops, showCoachStops: showIntercityBusStops, showTrainStops, } = useApp(); const navigate = useNavigate(); usePageTitle(t("navbar.map", "Mapa")); const [selectedStop, setSelectedStop] = useState< StopSheetProps["stop"] | null >(null); const [isSheetOpen, setIsSheetOpen] = useState(false); const mapRef = useRef(null); const { searchRoute, pickingMode, setPickingMode, setOrigin, setDestination, addRecentPlace, } = usePlanner({ autoLoad: false }); const [isConfirming, setIsConfirming] = useState(false); const handleConfirmPick = async () => { if (!mapRef.current || !pickingMode) return; const center = mapRef.current.getCenter(); setIsConfirming(true); try { const result = await reverseGeocode(center.lat, center.lng); const finalResult = { name: result?.name || `${center.lat.toFixed(5)}, ${center.lng.toFixed(5)}`, label: result?.label || "Map location", lat: center.lat, lon: center.lng, layer: "map-pick", }; if (pickingMode === "origin") { setOrigin(finalResult); } else { setDestination(finalResult); } addRecentPlace(finalResult); setPickingMode(null); } catch (err) { console.error("Failed to reverse geocode:", err); } finally { setIsConfirming(false); } }; const onMapInteraction = () => { if (!pickingMode) { window.dispatchEvent(new CustomEvent("plannerOverlay:collapse")); } }; const favouriteIds = useMemo(() => StopDataProvider.getFavouriteIds(), []); const favouriteFilter = useMemo(() => { if (favouriteIds.length === 0) return ["boolean", false]; return ["match", ["get", "id"], favouriteIds, true, false]; }, [favouriteIds]); // Handle click events on clusters and individual stops const onMapClick = (e: MapLayerMouseEvent) => { const features = e.features; if (!features || features.length === 0) { console.debug( "No features found on map click. Position:", e.lngLat, "Point:", e.point ); return; } const feature = features[0]; handlePointClick(feature); }; const stopLayerFilter = useMemo(() => { const filter: any[] = ["any"]; if (showCitybusStops) { filter.push(["==", ["get", "transitKind"], "bus"]); } if (showIntercityBusStops) { filter.push(["==", ["get", "transitKind"], "coach"]); } if (showTrainStops) { filter.push(["==", ["get", "transitKind"], "train"]); } return filter as FilterSpecification; }, [showCitybusStops, showIntercityBusStops, showTrainStops]); 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: { id: string; code: string; name: string; routes: string; } = feature.properties; // TODO: Move ID to constant, improve type checking if (!props || feature.layer.id.startsWith("stops") === false) { console.warn("Invalid feature properties:", props); return; } const stopId = props.id; const routes: { shortName: string; colour: string; textColour: string; }[] = JSON.parse(props.routes || "[]"); setSelectedStop({ stopId: props.id, stopCode: props.code, name: props.name || "Unknown Stop", lines: routes.map((route) => { return { line: route.shortName, colour: route.colour, textColour: route.textColour, }; }), }); setIsSheetOpen(true); }; return (
{!pickingMode && ( searchRoute(o, d, time, arriveBy)} onNavigateToPlanner={() => navigate("/planner")} clearPickerOnOpen={true} showLastDestinationWhenCollapsed={false} cardBackground="bg-white/95 dark:bg-slate-900/90" autoLoad={false} /> )} {pickingMode && (

{pickingMode === "origin" ? t("planner.pick_origin", "Select origin") : t("planner.pick_destination", "Select destination")}

{t( "planner.pick_instruction", "Move the map to place the target on the desired location" )}

)} {pickingMode && (
{/* Modern discrete target */}
)} {!pickingMode && ( )} {selectedStop && ( setIsSheetOpen(false)} stop={selectedStop} /> )}
); }