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>
2026-04-20 16:50:53 +00:00
|
|
|
import { useRef, useCallback, useEffect, useState } from 'react'
|
2026-04-20 20:59:18 +00:00
|
|
|
import { Sun, Moon } from 'lucide-react'
|
2026-04-26 21:14:39 +00:00
|
|
|
import { useStore, usePanelState } from '../store'
|
Add contacts/phone book UI with search integration
New components:
- ContactModal.jsx: Save/edit overlay with form fields and soft delete
- ContactList.jsx: Contacts tab with filter, create, and tap-to-navigate
Modified:
- store.js: Add contacts slice (contacts, activeTab, editingContact)
- api.js: Add contacts API functions (fetch, create, update, delete, nearby)
- config.js: Add has_contacts fallback flag
- Panel.jsx: Routes/Contacts tab bar (only when has_contacts enabled)
- PlaceDetail.jsx: Save button opens ContactModal, proximity annotation
- SearchBar.jsx: Prepend matching contacts before Photon results
- App.jsx: Render ContactModal at top level
- index.css: Modal overlay, tab bar, contact list item styles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 05:30:19 +00:00
|
|
|
import { hasFeature } from '../config'
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
import SearchBar from './SearchBar'
|
|
|
|
|
import StopList from './StopList'
|
|
|
|
|
import ModeSelector from './ModeSelector'
|
|
|
|
|
import ManeuverList from './ManeuverList'
|
Add contacts/phone book UI with search integration
New components:
- ContactModal.jsx: Save/edit overlay with form fields and soft delete
- ContactList.jsx: Contacts tab with filter, create, and tap-to-navigate
Modified:
- store.js: Add contacts slice (contacts, activeTab, editingContact)
- api.js: Add contacts API functions (fetch, create, update, delete, nearby)
- config.js: Add has_contacts fallback flag
- Panel.jsx: Routes/Contacts tab bar (only when has_contacts enabled)
- PlaceDetail.jsx: Save button opens ContactModal, proximity annotation
- SearchBar.jsx: Prepend matching contacts before Photon results
- App.jsx: Render ContactModal at top level
- index.css: Modal overlay, tab bar, contact list item styles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 05:30:19 +00:00
|
|
|
import ContactList from './ContactList'
|
2026-04-26 21:14:39 +00:00
|
|
|
import { PlaceCard } from './PlaceCard'
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
import { requestOptimizedRoute } from '../api'
|
|
|
|
|
|
|
|
|
|
export default function Panel({ onManeuverClick }) {
|
2026-04-26 21:14:39 +00:00
|
|
|
const selectedPlace = useStore((s) => s.selectedPlace)
|
|
|
|
|
const clearSelectedPlace = useStore((s) => s.clearSelectedPlace)
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
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)
|
2026-04-20 20:59:18 +00:00
|
|
|
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)
|
Add contacts/phone book UI with search integration
New components:
- ContactModal.jsx: Save/edit overlay with form fields and soft delete
- ContactList.jsx: Contacts tab with filter, create, and tap-to-navigate
Modified:
- store.js: Add contacts slice (contacts, activeTab, editingContact)
- api.js: Add contacts API functions (fetch, create, update, delete, nearby)
- config.js: Add has_contacts fallback flag
- Panel.jsx: Routes/Contacts tab bar (only when has_contacts enabled)
- PlaceDetail.jsx: Save button opens ContactModal, proximity annotation
- SearchBar.jsx: Prepend matching contacts before Photon results
- App.jsx: Render ContactModal at top level
- index.css: Modal overlay, tab bar, contact list item styles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 05:30:19 +00:00
|
|
|
const activeTab = useStore((s) => s.activeTab)
|
|
|
|
|
const setActiveTab = useStore((s) => s.setActiveTab)
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
|
2026-04-26 23:05:51 +00:00
|
|
|
const panelState = usePanelState()
|
2026-04-26 21:14:39 +00: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>
2026-04-20 16:50:53 +00:00
|
|
|
const [isMobile, setIsMobile] = useState(false)
|
|
|
|
|
const [optimizing, setOptimizing] = useState(false)
|
|
|
|
|
const sheetRef = useRef(null)
|
|
|
|
|
const dragStartY = useRef(0)
|
|
|
|
|
const dragStartState = useRef('half')
|
|
|
|
|
|
Add contacts/phone book UI with search integration
New components:
- ContactModal.jsx: Save/edit overlay with form fields and soft delete
- ContactList.jsx: Contacts tab with filter, create, and tap-to-navigate
Modified:
- store.js: Add contacts slice (contacts, activeTab, editingContact)
- api.js: Add contacts API functions (fetch, create, update, delete, nearby)
- config.js: Add has_contacts fallback flag
- Panel.jsx: Routes/Contacts tab bar (only when has_contacts enabled)
- PlaceDetail.jsx: Save button opens ContactModal, proximity annotation
- SearchBar.jsx: Prepend matching contacts before Photon results
- App.jsx: Render ContactModal at top level
- index.css: Modal overlay, tab bar, contact list item styles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 05:30:19 +00:00
|
|
|
const showContacts = hasFeature('has_contacts')
|
|
|
|
|
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
// Responsive detection
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const check = () => setIsMobile(window.innerWidth < 768)
|
|
|
|
|
check()
|
|
|
|
|
window.addEventListener('resize', check)
|
|
|
|
|
return () => window.removeEventListener('resize', check)
|
|
|
|
|
}, [])
|
|
|
|
|
|
2026-04-20 20:59:18 +00:00
|
|
|
// Theme toggle
|
|
|
|
|
const toggleTheme = () => {
|
|
|
|
|
const next = theme === 'dark' ? 'light' : 'dark'
|
|
|
|
|
setThemeOverride(next)
|
|
|
|
|
}
|
|
|
|
|
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
// Optimize stops
|
2026-04-20 20:59:18 +00:00
|
|
|
const hasGpsOrigin = gpsOrigin && geoPermission === 'granted'
|
|
|
|
|
const effectiveCount = stops.length + (hasGpsOrigin ? 1 : 0)
|
|
|
|
|
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
const handleOptimize = useCallback(async () => {
|
2026-04-20 20:59:18 +00:00
|
|
|
if (effectiveCount < 3 || optimizing) return
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
setOptimizing(true)
|
|
|
|
|
try {
|
2026-04-20 20:59:18 +00:00
|
|
|
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]
|
|
|
|
|
}
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
const data = await requestOptimizedRoute(locations, mode)
|
|
|
|
|
if (data.trip) {
|
2026-04-20 20:59:18 +00:00
|
|
|
const wpOrder = hasGpsOrigin && userLocation
|
|
|
|
|
? (data.trip.locations || []).slice(1)
|
|
|
|
|
: data.trip.locations
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
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)
|
|
|
|
|
}
|
2026-04-20 20:59:18 +00:00
|
|
|
}, [stops, mode, optimizing, effectiveCount, hasGpsOrigin, setStops, setRoute, setRouteError])
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
|
|
|
|
|
// 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])
|
|
|
|
|
|
2026-04-20 20:59:18 +00:00
|
|
|
const showOptimize = effectiveCount >= 3
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
|
2026-04-26 23:05:51 +00:00
|
|
|
// Determine what to show based on panel state
|
|
|
|
|
const showPreviewCard = panelState.startsWith('PREVIEW')
|
|
|
|
|
const showRouteSection = ['ROUTING', 'ROUTE_CALCULATED', 'PREVIEW_ROUTING', 'PREVIEW_CALCULATED'].includes(panelState)
|
|
|
|
|
const showManeuvers = panelState === 'ROUTE_CALCULATED' || panelState === 'PREVIEW_CALCULATED'
|
|
|
|
|
const showEmptyState = panelState === 'IDLE'
|
2026-04-26 21:14:39 +00:00
|
|
|
|
|
|
|
|
// Routes tab content - now state-driven
|
Add contacts/phone book UI with search integration
New components:
- ContactModal.jsx: Save/edit overlay with form fields and soft delete
- ContactList.jsx: Contacts tab with filter, create, and tap-to-navigate
Modified:
- store.js: Add contacts slice (contacts, activeTab, editingContact)
- api.js: Add contacts API functions (fetch, create, update, delete, nearby)
- config.js: Add has_contacts fallback flag
- Panel.jsx: Routes/Contacts tab bar (only when has_contacts enabled)
- PlaceDetail.jsx: Save button opens ContactModal, proximity annotation
- SearchBar.jsx: Prepend matching contacts before Photon results
- App.jsx: Render ContactModal at top level
- index.css: Modal overlay, tab bar, contact list item styles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 05:30:19 +00:00
|
|
|
const routesContent = (
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
<>
|
|
|
|
|
<SearchBar />
|
|
|
|
|
|
2026-04-26 21:14:39 +00:00
|
|
|
{/* Preview card when place is selected */}
|
|
|
|
|
{showPreviewCard && selectedPlace && (
|
|
|
|
|
<div className="mt-3">
|
|
|
|
|
<PlaceCard
|
|
|
|
|
place={selectedPlace}
|
|
|
|
|
variant="preview"
|
|
|
|
|
expanded={true}
|
|
|
|
|
onClose={clearSelectedPlace}
|
|
|
|
|
/>
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-04-26 21:14:39 +00:00
|
|
|
{/* Route section with stops */}
|
|
|
|
|
{showRouteSection && (
|
|
|
|
|
<>
|
|
|
|
|
<div className="mt-3">
|
|
|
|
|
<StopList />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mt-3 flex flex-col gap-2">
|
|
|
|
|
<ModeSelector />
|
|
|
|
|
{showOptimize && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleOptimize}
|
|
|
|
|
disabled={optimizing || routeLoading}
|
|
|
|
|
className="navi-btn-secondary w-full"
|
|
|
|
|
>
|
|
|
|
|
{optimizing ? 'Optimizing...' : 'Optimize stop order'}
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Maneuvers when route is calculated */}
|
|
|
|
|
{showManeuvers && (route || routeLoading || routeError) && (
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
<div className="mt-3">
|
|
|
|
|
<ManeuverList onManeuverClick={onManeuverClick} />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-04-26 21:14:39 +00:00
|
|
|
{/* Empty state */}
|
|
|
|
|
{showEmptyState && (
|
2026-04-20 20:59:18 +00:00
|
|
|
<div className="mt-6 text-center text-xs" style={{ color: 'var(--text-tertiary)' }}>
|
2026-04-26 21:14:39 +00:00
|
|
|
<p>Search or tap the map to explore</p>
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
|
Add contacts/phone book UI with search integration
New components:
- ContactModal.jsx: Save/edit overlay with form fields and soft delete
- ContactList.jsx: Contacts tab with filter, create, and tap-to-navigate
Modified:
- store.js: Add contacts slice (contacts, activeTab, editingContact)
- api.js: Add contacts API functions (fetch, create, update, delete, nearby)
- config.js: Add has_contacts fallback flag
- Panel.jsx: Routes/Contacts tab bar (only when has_contacts enabled)
- PlaceDetail.jsx: Save button opens ContactModal, proximity annotation
- SearchBar.jsx: Prepend matching contacts before Photon results
- App.jsx: Render ContactModal at top level
- index.css: Modal overlay, tab bar, contact list item styles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 05:30:19 +00:00
|
|
|
const content = (
|
|
|
|
|
<>
|
|
|
|
|
{showContacts && (
|
|
|
|
|
<div className="navi-tab-bar mb-3">
|
|
|
|
|
<button
|
|
|
|
|
className={`navi-tab ${activeTab === 'routes' ? 'navi-tab-active' : ''}`}
|
|
|
|
|
onClick={() => setActiveTab('routes')}
|
|
|
|
|
>
|
|
|
|
|
Routes
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
className={`navi-tab ${activeTab === 'contacts' ? 'navi-tab-active' : ''}`}
|
|
|
|
|
onClick={() => setActiveTab('contacts')}
|
|
|
|
|
>
|
|
|
|
|
Contacts
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{(!showContacts || activeTab === 'routes') ? routesContent : <ContactList />}
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-20 20:59:18 +00:00
|
|
|
const header = (
|
|
|
|
|
<div className="flex items-center justify-between mb-3">
|
|
|
|
|
<h1 className="text-md font-semibold" style={{ color: 'var(--accent)' }}>Navi</h1>
|
|
|
|
|
<button
|
|
|
|
|
onClick={toggleTheme}
|
|
|
|
|
className="p-1.5 rounded"
|
|
|
|
|
style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
|
|
|
|
|
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
|
|
|
|
|
>
|
|
|
|
|
{theme === 'dark' ? <Sun size={16} /> : <Moon size={16} />}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-26 21:14:39 +00:00
|
|
|
// Desktop: side panel (now 360px to accommodate PlaceCard)
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
if (!isMobile) {
|
|
|
|
|
return (
|
2026-04-20 20:59:18 +00:00
|
|
|
<div
|
2026-04-26 21:14:39 +00:00
|
|
|
className="absolute top-0 left-0 z-10 h-full overflow-y-auto p-4 flex flex-col"
|
2026-04-20 20:59:18 +00:00
|
|
|
style={{
|
2026-04-26 21:14:39 +00:00
|
|
|
width: '400px',
|
2026-04-20 20:59:18 +00:00
|
|
|
background: 'var(--bg-raised)',
|
|
|
|
|
borderRight: '1px solid var(--border)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{header}
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
{content}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mobile: bottom sheet
|
|
|
|
|
const sheetHeights = {
|
|
|
|
|
collapsed: 'h-12',
|
|
|
|
|
half: 'h-[45vh]',
|
|
|
|
|
full: 'h-[85vh]',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
ref={sheetRef}
|
2026-04-20 20:59:18 +00:00
|
|
|
className={`absolute bottom-0 left-0 right-0 z-10 rounded-t-2xl transition-all duration-200 ${sheetHeights[sheetState]}`}
|
|
|
|
|
style={{
|
|
|
|
|
background: 'var(--bg-raised)',
|
|
|
|
|
borderTop: '1px solid var(--border)',
|
|
|
|
|
}}
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
>
|
|
|
|
|
{/* Drag handle */}
|
|
|
|
|
<div
|
|
|
|
|
className="flex justify-center py-2 cursor-grab"
|
|
|
|
|
onTouchStart={handleTouchStart}
|
|
|
|
|
onTouchEnd={handleTouchEnd}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
if (sheetState === 'collapsed') setSheetState('half')
|
|
|
|
|
else if (sheetState === 'half') setSheetState('full')
|
|
|
|
|
else setSheetState('half')
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-04-20 20:59:18 +00:00
|
|
|
<div className="w-10 h-1 rounded-full" style={{ background: 'var(--border)' }} />
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{sheetState !== 'collapsed' && (
|
2026-04-22 03:27:21 +00:00
|
|
|
<div className="px-4 pb-4 overflow-y-auto overflow-x-hidden h-[calc(100%-2rem)]" style={{ paddingBottom: 'max(1rem, env(safe-area-inset-bottom))' }}>
|
2026-04-20 20:59:18 +00:00
|
|
|
{header}
|
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>
2026-04-20 16:50:53 +00:00
|
|
|
{content}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|