import maplibregl, { type StyleSpecification } from "maplibre-gl"; import "maplibre-gl/dist/maplibre-gl.css"; import React, { useEffect, useRef, useState } from "react"; import Map, { Layer, Source, type MapRef } from "react-map-gl/maplibre"; import { Sheet } from "react-modal-sheet"; import { useApp } from "~/AppContext"; import { REGION_DATA } from "~/config/RegionConfig"; import { searchPlaces, type Itinerary, type PlannerSearchResult, } from "~/data/PlannerApi"; import { usePlanner } from "~/hooks/usePlanner"; import { DEFAULT_STYLE, loadStyle } from "~/maps/styleloader"; import "../tailwind-full.css"; // --- Components --- const AutocompleteInput = ({ label, value, onChange, placeholder, }: { label: string; value: PlannerSearchResult | null; onChange: (val: PlannerSearchResult | null) => void; placeholder: string; }) => { const [query, setQuery] = useState(value?.name || ""); const [results, setResults] = useState([]); const [showResults, setShowResults] = useState(false); useEffect(() => { if (value) setQuery(value.name || ""); }, [value]); useEffect(() => { const timer = setTimeout(async () => { if (query.length > 2 && query !== value?.name) { const res = await searchPlaces(query); setResults(res); setShowResults(true); } else { setResults([]); } }, 500); return () => clearTimeout(timer); }, [query, value]); return (
{ setQuery(e.target.value); if (!e.target.value) onChange(null); }} placeholder={placeholder} onFocus={() => setShowResults(true)} /> {value && ( )}
{showResults && results.length > 0 && ( )}
); }; const ItinerarySummary = ({ itinerary, onClick, }: { itinerary: Itinerary; onClick: () => void; }) => { const durationMinutes = Math.round(itinerary.durationSeconds / 60); const startTime = new Date(itinerary.startTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", }); const endTime = new Date(itinerary.endTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", }); return (
{startTime} - {endTime}
{durationMinutes} min
{itinerary.legs.map((leg, idx) => ( {idx > 0 && }
{leg.mode === "WALK" ? "Walk" : leg.routeShortName || leg.mode}
))}
Walk: {Math.round(itinerary.walkDistanceMeters)}m
); }; const ItineraryDetail = ({ itinerary, onClose, }: { itinerary: Itinerary; onClose: () => void; }) => { const mapRef = useRef(null); const [sheetOpen, setSheetOpen] = useState(true); // Prepare GeoJSON for the route const routeGeoJson = { type: "FeatureCollection", features: itinerary.legs.map((leg) => ({ type: "Feature", geometry: { type: "LineString", coordinates: leg.geometry?.coordinates || [], }, properties: { mode: leg.mode, color: leg.mode === "WALK" ? "#9ca3af" : "#2563eb", // Gray for walk, Blue for transit }, })), }; // Fit bounds on mount useEffect(() => { if (mapRef.current && itinerary.legs.length > 0) { const bounds = new maplibregl.LngLatBounds(); itinerary.legs.forEach((leg) => { leg.geometry?.coordinates.forEach((coord) => { bounds.extend([coord[0], coord[1]]); }); }); mapRef.current.fitBounds(bounds, { padding: 50 }); } }, [itinerary]); const { theme } = useApp(); const [mapStyle, setMapStyle] = useState(DEFAULT_STYLE); 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]); return (
{/* Markers for start/end/transfers could be added here */}
setSheetOpen(false)} detent="content" initialSnap={0} >

Itinerary Details

{itinerary.legs.map((leg, idx) => (
{leg.mode === "WALK" ? "🚶" : "🚌"}
{idx < itinerary.legs.length - 1 && (
)}
{leg.mode === "WALK" ? "Walk" : `${leg.routeShortName} ${leg.headsign}`}
{new Date(leg.startTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })} {" - "} {new Date(leg.endTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })}
{leg.mode === "WALK" ? ( Walk {Math.round(leg.distanceMeters)}m to{" "} {leg.to?.name} ) : ( From {leg.from?.name} to {leg.to?.name} )}
))}
setSheetOpen(false)} />
); }; // --- Main Page --- export default function PlannerPage() { const { origin, setOrigin, destination, setDestination, plan, loading, error, searchRoute, clearRoute, } = usePlanner(); const [selectedItinerary, setSelectedItinerary] = useState( null ); const handleSearch = () => { if (origin && destination) { searchRoute(origin, destination); } }; if (selectedItinerary) { return ( setSelectedItinerary(null)} /> ); } return (

Route Planner

{/* Form */}
{error && (
{error}
)}
{/* Results */} {plan && (

Results

{plan.itineraries.length === 0 ? (
😕

No routes found

We couldn't find a route for your trip. Try changing the time or locations.

) : (
{plan.itineraries.map((itinerary, idx) => ( setSelectedItinerary(itinerary)} /> ))}
)}
)}
); }