mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-05-20 14:44:51 +02:00
feat: search, multi-stop routing, and route display
Full navigation UI with: - Search bar with 150ms debounced autocomplete from /api/geocode - Keyboard navigation (arrow keys, Enter, Escape) - Exact match badge for verified address results - Multi-stop list with drag-to-reorder (dnd-kit) - 10-stop cap with disabled state - Mode selector (drive/walk/bike) - Valhalla route display with per-leg color polyline - Maneuver list with instructions, distance, time remaining - Click maneuver to fly map to that point - Optimize stops button (3+ stops, uses /optimized_route) - Responsive: side panel (desktop ≥768px), bottom sheet (mobile) - Stop pins: green origin, red destination, blue intermediate - Pin popup with remove button - Geolocation permission requested on first route, not on load - Error handling for unroutable pairs - nginx proxy for /api/ and /valhalla/ endpoints Dependencies added: zustand, @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ce32014896
commit
e7b08a7dc9
16 changed files with 1364 additions and 44 deletions
65
src/store.js
Normal file
65
src/store.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { create } from 'zustand'
|
||||
|
||||
export const useStore = create((set, get) => ({
|
||||
// ── Search state ──
|
||||
query: '',
|
||||
results: [],
|
||||
searchLoading: false,
|
||||
abortController: null,
|
||||
|
||||
setQuery: (query) => set({ query }),
|
||||
setResults: (results) => set({ results }),
|
||||
setSearchLoading: (loading) => set({ searchLoading: loading }),
|
||||
setAbortController: (ctrl) => set({ abortController: ctrl }),
|
||||
|
||||
// ── Stop list ──
|
||||
stops: [],
|
||||
// Each stop: { id, lat, lon, name, source, matchCode, isOrigin }
|
||||
|
||||
addStop: (stop) => {
|
||||
const { stops } = get()
|
||||
if (stops.length >= 10) return false
|
||||
set({ stops: [...stops, { ...stop, id: crypto.randomUUID() }] })
|
||||
return true
|
||||
},
|
||||
|
||||
removeStop: (id) => {
|
||||
set({ stops: get().stops.filter((s) => s.id !== id) })
|
||||
},
|
||||
|
||||
reorderStops: (newStops) => set({ stops: newStops }),
|
||||
|
||||
clearStops: () => set({ stops: [] }),
|
||||
|
||||
setStops: (stops) => set({ stops }),
|
||||
|
||||
// ── Geolocation ──
|
||||
userLocation: null, // { lat, lon }
|
||||
geoPermission: 'prompt', // 'prompt' | 'granted' | 'denied'
|
||||
|
||||
setUserLocation: (loc) => set({ userLocation: loc }),
|
||||
setGeoPermission: (p) => set({ geoPermission: p }),
|
||||
|
||||
// ── Mode ──
|
||||
mode: 'auto', // 'auto' | 'pedestrian' | 'bicycle'
|
||||
setMode: (mode) => set({ mode }),
|
||||
|
||||
// ── Route ──
|
||||
route: null, // Valhalla response (trip object)
|
||||
routeLoading: false,
|
||||
routeError: null,
|
||||
|
||||
setRoute: (route) => set({ route, routeError: null }),
|
||||
setRouteLoading: (loading) => set({ routeLoading: loading }),
|
||||
setRouteError: (err) => set({ routeError: err, route: null }),
|
||||
clearRoute: () => set({ route: null, routeError: null }),
|
||||
|
||||
// ── UI state ──
|
||||
sheetState: 'half', // 'collapsed' | 'half' | 'full'
|
||||
panelOpen: true,
|
||||
autocompleteOpen: false,
|
||||
|
||||
setSheetState: (s) => set({ sheetState: s }),
|
||||
setPanelOpen: (open) => set({ panelOpen: open }),
|
||||
setAutocompleteOpen: (open) => set({ autocompleteOpen: open }),
|
||||
}))
|
||||
Loading…
Add table
Add a link
Reference in a new issue