import { useRef, useCallback, useEffect, useState } from 'react' import { Sun, Moon } from 'lucide-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 theme = useStore((s) => s.theme) const themeOverride = useStore((s) => s.themeOverride) const setThemeOverride = useStore((s) => s.setThemeOverride) const gpsOrigin = useStore((s) => s.gpsOrigin) const geoPermission = useStore((s) => s.geoPermission) 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) }, []) // Theme toggle const toggleTheme = () => { const next = theme === 'dark' ? 'light' : 'dark' setThemeOverride(next) } // Optimize stops const hasGpsOrigin = gpsOrigin && geoPermission === 'granted' const effectiveCount = stops.length + (hasGpsOrigin ? 1 : 0) const handleOptimize = useCallback(async () => { if (effectiveCount < 3 || optimizing) return setOptimizing(true) try { const { userLocation } = useStore.getState() let locations = stops.map((s) => ({ lat: s.lat, lon: s.lon })) if (hasGpsOrigin && userLocation) { locations = [{ lat: userLocation.lat, lon: userLocation.lon }, ...locations] } const data = await requestOptimizedRoute(locations, mode) if (data.trip) { // If GPS origin was prepended, skip it from the result waypoints const wpOrder = hasGpsOrigin && userLocation ? (data.trip.locations || []).slice(1) : data.trip.locations if (wpOrder && wpOrder.length === stops.length) { 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 }) 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, effectiveCount, hasGpsOrigin, 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) { if (dragStartState.current === 'collapsed') setSheetState('half') else if (dragStartState.current === 'half') setSheetState('full') } else { if (dragStartState.current === 'full') setSheetState('half') else if (dragStartState.current === 'half') setSheetState('collapsed') } }, [setSheetState]) const showOptimize = effectiveCount >= 3 const content = ( <>
{stops.length >= 1 && (
{showOptimize && ( )}
)} {(route || routeLoading || routeError) && (
)} {stops.length === 0 && !route && (

Search and add stops to build your route

)} ) const header = (

Navi

) // Desktop: side panel if (!isMobile) { return (
{header} {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' && (
{header} {content}
)}
) }