Auto-trigger WALK + BICYCLE distance calculation on POI creation

After creating a POI, automatically trigger WALK and BICYCLE distance
calculations (cheap OSRM batch API). TRANSIT is excluded since it uses
the expensive OTP backend — users trigger it manually via the calculator
button. Failure is non-fatal: the POI is still created and calculation
can be retried manually.
This commit is contained in:
Viktor Barzin 2026-02-08 14:51:34 +00:00
parent 8a5d1b3787
commit 2fdafdcb64
No known key found for this signature in database
GPG key ID: 0EB088298288D958

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { MapPin, Plus, Trash2, Calculator, Loader2 } from 'lucide-react'; import { MapPin, Plus, Trash2, Calculator, Loader2, Crosshair } from 'lucide-react';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { Input } from './ui/input'; import { Input } from './ui/input';
import type { AuthUser } from '@/auth/types'; import type { AuthUser } from '@/auth/types';
@ -10,9 +10,11 @@ interface POIManagerProps {
user: AuthUser; user: AuthUser;
listingType: 'RENT' | 'BUY'; listingType: 'RENT' | 'BUY';
onTaskCreated?: (taskId: string) => void; onTaskCreated?: (taskId: string) => void;
pickedLocation?: { lat: number; lng: number } | null;
onStartPicking?: () => void;
} }
export function POIManager({ user, listingType, onTaskCreated }: POIManagerProps) { export function POIManager({ user, listingType, onTaskCreated, pickedLocation, onStartPicking }: POIManagerProps) {
const [pois, setPois] = useState<POI[]>([]); const [pois, setPois] = useState<POI[]>([]);
const [isAdding, setIsAdding] = useState(false); const [isAdding, setIsAdding] = useState(false);
const [name, setName] = useState(''); const [name, setName] = useState('');
@ -26,6 +28,14 @@ export function POIManager({ user, listingType, onTaskCreated }: POIManagerProps
fetchUserPOIs(user).then(setPois).catch(() => {}); fetchUserPOIs(user).then(setPois).catch(() => {});
}, [user]); }, [user]);
// When a location is picked from the map, populate lat/lng
useEffect(() => {
if (pickedLocation) {
setLat(String(pickedLocation.lat));
setLng(String(pickedLocation.lng));
}
}, [pickedLocation]);
const handleCreate = async () => { const handleCreate = async () => {
if (!name || !lat || !lng) return; if (!name || !lat || !lng) return;
try { try {
@ -41,6 +51,16 @@ export function POIManager({ user, listingType, onTaskCreated }: POIManagerProps
setAddress(''); setAddress('');
setLat(''); setLat('');
setLng(''); setLng('');
// Auto-trigger WALK + BICYCLE distance calculation (cheap OSRM).
// TRANSIT (expensive OTP) is excluded — user triggers manually.
try {
const result = await triggerPOICalculation(
user, poi.id, ['WALK', 'BICYCLE'], listingType
);
onTaskCreated?.(result.task_id);
} catch {
// Non-fatal: POI created successfully, calculation can be retried manually.
}
} catch { } catch {
// silently fail // silently fail
} }
@ -83,6 +103,7 @@ export function POIManager({ user, listingType, onTaskCreated }: POIManagerProps
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
type="button"
className="h-6 w-6 p-0" className="h-6 w-6 p-0"
onClick={() => handleCalculate(poi.id)} onClick={() => handleCalculate(poi.id)}
disabled={calculating === poi.id} disabled={calculating === poi.id}
@ -96,6 +117,7 @@ export function POIManager({ user, listingType, onTaskCreated }: POIManagerProps
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
type="button"
className="h-6 w-6 p-0 text-destructive hover:text-destructive" className="h-6 w-6 p-0 text-destructive hover:text-destructive"
onClick={() => handleDelete(poi.id)} onClick={() => handleDelete(poi.id)}
> >
@ -139,33 +161,42 @@ export function POIManager({ user, listingType, onTaskCreated }: POIManagerProps
onChange={e => setAddress(e.target.value)} onChange={e => setAddress(e.target.value)}
className="h-7 text-xs" className="h-7 text-xs"
/> />
<div className="grid grid-cols-2 gap-2"> {lat && lng ? (
<Input <div className="flex items-center gap-2 p-1.5 bg-muted rounded text-xs text-muted-foreground">
type="number" <MapPin className="h-3 w-3 shrink-0" />
step="any" <span className="truncate">{parseFloat(lat).toFixed(5)}, {parseFloat(lng).toFixed(5)}</span>
placeholder="Latitude" <Button
value={lat} variant="ghost"
onChange={e => setLat(e.target.value)} size="sm"
className="h-7 text-xs" type="button"
/> className="h-5 px-1.5 text-xs ml-auto"
<Input onClick={() => { onStartPicking?.(); }}
type="number" >
step="any" Change
placeholder="Longitude" </Button>
value={lng} </div>
onChange={e => setLng(e.target.value)} ) : (
className="h-7 text-xs" <Button
/> variant="outline"
</div> size="sm"
type="button"
className="w-full h-7 text-xs"
onClick={() => onStartPicking?.()}
>
<Crosshair className="h-3.5 w-3.5 mr-1" />
Pick on Map
</Button>
)}
<div className="flex gap-2"> <div className="flex gap-2">
<Button size="sm" className="h-7 text-xs flex-1" onClick={handleCreate}> <Button size="sm" type="button" className="h-7 text-xs flex-1" onClick={handleCreate} disabled={!lat || !lng}>
Save Save
</Button> </Button>
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
type="button"
className="h-7 text-xs" className="h-7 text-xs"
onClick={() => setIsAdding(false)} onClick={() => { setIsAdding(false); setLat(''); setLng(''); }}
> >
Cancel Cancel
</Button> </Button>
@ -175,6 +206,7 @@ export function POIManager({ user, listingType, onTaskCreated }: POIManagerProps
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
type="button"
className="w-full h-7 text-xs" className="w-full h-7 text-xs"
onClick={() => setIsAdding(true)} onClick={() => setIsAdding(true)}
> >