import { useRef, useCallback, useEffect, useState } from 'react' import { useStore } from '../store' import SearchBar from './SearchBar' import StopList from './StopList' import ModeSelector from './ModeSelector' import ManeuverList from './ManeuverList' import { requestOptimizedRoute } from '../api' export default function Panel({ onManeuverClick }) { const stops = useStore((s) => s.stops) const mode = useStore((s) => s.mode) const route = useStore((s) => s.route) const routeLoading = useStore((s) => s.routeLoading) const routeError = useStore((s) => s.routeError) const setStops = useStore((s) => s.setStops) const setRoute = useStore((s) => s.setRoute) const setRouteError = useStore((s) => s.setRouteError) const setRouteLoading = useStore((s) => s.setRouteLoading) const sheetState = useStore((s) => s.sheetState) const setSheetState = useStore((s) => s.setSheetState) const [isMobile, setIsMobile] = useState(false) const [optimizing, setOptimizing] = useState(false) const sheetRef = useRef(null) const dragStartY = useRef(0) const dragStartState = useRef('half') // Responsive detection useEffect(() => { const check = () => setIsMobile(window.innerWidth < 768) check() window.addEventListener('resize', check) return () => window.removeEventListener('resize', check) }, []) // Optimize stops const handleOptimize = useCallback(async () => { if (stops.length < 3 || optimizing) return setOptimizing(true) try { const locations = stops.map((s) => ({ lat: s.lat, lon: s.lon })) const data = await requestOptimizedRoute(locations, mode) if (data.trip) { // Reorder stops based on optimized waypoint order const wpOrder = data.trip.locations if (wpOrder && wpOrder.length === stops.length) { // Match optimized locations back to original stops by proximity const reordered = wpOrder.map((wp) => { let closest = stops[0] let minDist = Infinity for (const s of stops) { const d = Math.abs(s.lat - wp.lat) + Math.abs(s.lon - wp.lon) if (d < minDist) { minDist = d closest = s } } return closest }) // Deduplicate (in case of matching issues) const seen = new Set() const unique = reordered.filter((s) => { if (seen.has(s.id)) return false seen.add(s.id) return true }) if (unique.length === stops.length) { setStops(unique) } } setRoute(data.trip) } } catch (e) { setRouteError(e.message) } finally { setOptimizing(false) } }, [stops, mode, optimizing, setStops, setRoute, setRouteError]) // Mobile sheet drag handling const handleTouchStart = useCallback((e) => { dragStartY.current = e.touches[0].clientY dragStartState.current = sheetState }, [sheetState]) const handleTouchEnd = useCallback((e) => { const deltaY = e.changedTouches[0].clientY - dragStartY.current if (Math.abs(deltaY) < 30) return if (deltaY < 0) { // Swipe up if (dragStartState.current === 'collapsed') setSheetState('half') else if (dragStartState.current === 'half') setSheetState('full') } else { // Swipe down if (dragStartState.current === 'full') setSheetState('half') else if (dragStartState.current === 'half') setSheetState('collapsed') } }, [setSheetState]) const showOptimize = stops.length >= 3 const content = ( <> {/* Stop list */}
{/* Mode selector + optimize */} {stops.length >= 1 && (
{showOptimize && ( )}
)} {/* Maneuver list */} {(route || routeLoading || routeError) && (
)} {/* TODO: Recents / saved places placeholder */} {stops.length === 0 && !route && (
{/* TODO: Wire recents + favorites in a later phase */}

Recent places will appear here

)} ) // Desktop: side panel if (!isMobile) { return (

Navi

{content}
) } // Mobile: bottom sheet const sheetHeights = { collapsed: 'h-12', half: 'h-[45vh]', full: 'h-[85vh]', } return (
{/* Drag handle */}
{ if (sheetState === 'collapsed') setSheetState('half') else if (sheetState === 'half') setSheetState('full') else setSheetState('half') }} >
{sheetState !== 'collapsed' && (
{content}
)}
) }