mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-05-20 22:54:42 +02:00
fix: show pendingDestination as visible card when Get Directions clicked
When GPS is denied and user clicks Get Directions, StopList now shows: - Empty origin slot (A) with dashed border and "Search for a starting point" prompt - Destination slot (B) showing the place name from pendingDestination Previously only showed small grey text, making it feel like the UI showed "nothing" after the place card disappeared.
This commit is contained in:
parent
ed81b4e186
commit
616d01623d
1 changed files with 158 additions and 117 deletions
|
|
@ -1,117 +1,158 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
closestCenter,
|
closestCenter,
|
||||||
KeyboardSensor,
|
KeyboardSensor,
|
||||||
PointerSensor,
|
PointerSensor,
|
||||||
useSensor,
|
useSensor,
|
||||||
useSensors,
|
useSensors,
|
||||||
} from '@dnd-kit/core'
|
} from '@dnd-kit/core'
|
||||||
import {
|
import {
|
||||||
arrayMove,
|
arrayMove,
|
||||||
SortableContext,
|
SortableContext,
|
||||||
sortableKeyboardCoordinates,
|
sortableKeyboardCoordinates,
|
||||||
verticalListSortingStrategy,
|
verticalListSortingStrategy,
|
||||||
useSortable,
|
useSortable,
|
||||||
} from '@dnd-kit/sortable'
|
} from '@dnd-kit/sortable'
|
||||||
import { CSS } from '@dnd-kit/utilities'
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
import { useStore } from '../store'
|
import { useStore } from '../store'
|
||||||
import { PlaceCard } from './PlaceCard'
|
import { PlaceCard } from './PlaceCard'
|
||||||
import GpsOriginItem from './GpsOriginItem'
|
import GpsOriginItem from './GpsOriginItem'
|
||||||
|
|
||||||
// Wrapper to make PlaceCard sortable
|
// Wrapper to make PlaceCard sortable
|
||||||
function SortableStopCard({ stop, index, indexOffset }) {
|
function SortableStopCard({ stop, index, indexOffset }) {
|
||||||
const removeStop = useStore((s) => s.removeStop)
|
const removeStop = useStore((s) => s.removeStop)
|
||||||
const [expanded, setExpanded] = useState(false)
|
const [expanded, setExpanded] = useState(false)
|
||||||
|
|
||||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
|
||||||
useSortable({ id: stop.id })
|
useSortable({ id: stop.id })
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
transform: CSS.Transform.toString(transform),
|
transform: CSS.Transform.toString(transform),
|
||||||
transition,
|
transition,
|
||||||
opacity: isDragging ? 0.5 : 1,
|
opacity: isDragging ? 0.5 : 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert stop to place format for PlaceCard
|
// Convert stop to place format for PlaceCard
|
||||||
const place = {
|
const place = {
|
||||||
lat: stop.lat,
|
lat: stop.lat,
|
||||||
lon: stop.lon,
|
lon: stop.lon,
|
||||||
name: stop.name,
|
name: stop.name,
|
||||||
source: stop.source,
|
source: stop.source,
|
||||||
matchCode: stop.matchCode,
|
matchCode: stop.matchCode,
|
||||||
type: stop.type || null,
|
type: stop.type || null,
|
||||||
raw: stop.raw || null,
|
raw: stop.raw || null,
|
||||||
wikidata: stop.wikidata || null,
|
wikidata: stop.wikidata || null,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={setNodeRef} style={style}>
|
<div ref={setNodeRef} style={style}>
|
||||||
<PlaceCard
|
<PlaceCard
|
||||||
place={place}
|
place={place}
|
||||||
variant="stop"
|
variant="stop"
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
onToggleExpand={() => setExpanded(!expanded)}
|
onToggleExpand={() => setExpanded(!expanded)}
|
||||||
onRemove={() => removeStop(stop.id)}
|
onRemove={() => removeStop(stop.id)}
|
||||||
stopIndex={index + indexOffset}
|
stopIndex={index + indexOffset}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
dragHandleProps={{ ...attributes, ...listeners }}
|
dragHandleProps={{ ...attributes, ...listeners }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StopList() {
|
export default function StopList() {
|
||||||
const stops = useStore((s) => s.stops)
|
const stops = useStore((s) => s.stops)
|
||||||
const reorderStops = useStore((s) => s.reorderStops)
|
const reorderStops = useStore((s) => s.reorderStops)
|
||||||
const geoPermission = useStore((s) => s.geoPermission)
|
const geoPermission = useStore((s) => s.geoPermission)
|
||||||
const gpsOrigin = useStore((s) => s.gpsOrigin)
|
const gpsOrigin = useStore((s) => s.gpsOrigin)
|
||||||
const pendingDestination = useStore((s) => s.pendingDestination)
|
const pendingDestination = useStore((s) => s.pendingDestination)
|
||||||
|
|
||||||
const hasGpsOrigin = gpsOrigin && geoPermission === 'granted'
|
const hasGpsOrigin = gpsOrigin && geoPermission === 'granted'
|
||||||
const indexOffset = hasGpsOrigin ? 1 : 0
|
const indexOffset = hasGpsOrigin ? 1 : 0
|
||||||
|
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
||||||
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
||||||
)
|
)
|
||||||
|
|
||||||
function handleDragEnd(event) {
|
function handleDragEnd(event) {
|
||||||
const { active, over } = event
|
const { active, over } = event
|
||||||
if (!over || active.id === over.id) return
|
if (!over || active.id === over.id) return
|
||||||
|
|
||||||
const oldIndex = stops.findIndex((s) => s.id === active.id)
|
const oldIndex = stops.findIndex((s) => s.id === active.id)
|
||||||
const newIndex = stops.findIndex((s) => s.id === over.id)
|
const newIndex = stops.findIndex((s) => s.id === over.id)
|
||||||
reorderStops(arrayMove(stops, oldIndex, newIndex))
|
reorderStops(arrayMove(stops, oldIndex, newIndex))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stops.length === 0 && !hasGpsOrigin) {
|
// When pendingDestination is set, show it as the destination with origin prompt
|
||||||
return (
|
if (stops.length === 0 && !hasGpsOrigin && pendingDestination) {
|
||||||
<div className="text-xs px-2 py-3 text-center" style={{ color: 'var(--text-tertiary)' }}>
|
return (
|
||||||
{pendingDestination
|
<div className="flex flex-col gap-2">
|
||||||
? 'Search for a starting point above'
|
{/* Origin slot - empty, prompting user to search */}
|
||||||
: geoPermission === 'denied'
|
<div
|
||||||
? 'Add a starting point and destination above'
|
className="flex items-center gap-2 px-3 py-2.5 rounded-lg text-xs"
|
||||||
: 'Search and add stops to build your route'}
|
style={{
|
||||||
</div>
|
background: 'var(--bg-muted)',
|
||||||
)
|
border: '1px dashed var(--border)',
|
||||||
}
|
color: 'var(--text-secondary)'
|
||||||
|
}}
|
||||||
return (
|
>
|
||||||
<div className="flex flex-col gap-2">
|
<span
|
||||||
{hasGpsOrigin && <GpsOriginItem />}
|
className="flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold"
|
||||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
style={{ background: 'var(--accent-muted)', color: 'var(--accent)' }}
|
||||||
<SortableContext items={stops.map((s) => s.id)} strategy={verticalListSortingStrategy}>
|
>
|
||||||
{stops.map((stop, i) => (
|
A
|
||||||
<SortableStopCard
|
</span>
|
||||||
key={stop.id}
|
<span className="flex-1">Search for a starting point above</span>
|
||||||
stop={stop}
|
</div>
|
||||||
index={i}
|
{/* Destination - show pendingDestination as read-only card */}
|
||||||
indexOffset={indexOffset}
|
<div
|
||||||
/>
|
className="flex items-center gap-2 px-3 py-2.5 rounded-lg text-xs"
|
||||||
))}
|
style={{
|
||||||
</SortableContext>
|
background: 'var(--bg-raised)',
|
||||||
</DndContext>
|
border: '1px solid var(--border)'
|
||||||
</div>
|
}}
|
||||||
)
|
>
|
||||||
}
|
<span
|
||||||
|
className="flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold"
|
||||||
|
style={{ background: 'var(--accent)', color: 'var(--text-inverse)' }}
|
||||||
|
>
|
||||||
|
B
|
||||||
|
</span>
|
||||||
|
<span className="flex-1 truncate" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
{pendingDestination.name || 'Destination'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stops.length === 0 && !hasGpsOrigin) {
|
||||||
|
return (
|
||||||
|
<div className="text-xs px-2 py-3 text-center" style={{ color: 'var(--text-tertiary)' }}>
|
||||||
|
{geoPermission === 'denied'
|
||||||
|
? 'Add a starting point and destination above'
|
||||||
|
: 'Search and add stops to build your route'}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{hasGpsOrigin && <GpsOriginItem />}
|
||||||
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||||
|
<SortableContext items={stops.map((s) => s.id)} strategy={verticalListSortingStrategy}>
|
||||||
|
{stops.map((stop, i) => (
|
||||||
|
<SortableStopCard
|
||||||
|
key={stop.id}
|
||||||
|
stop={stop}
|
||||||
|
index={i}
|
||||||
|
indexOffset={indexOffset}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue