Add GPS coordinate lookup and display for site locations
Integrate OpenStreetMap Nominatim API for geocoding addresses to latitude and longitude, enabling GPS coordinate storage and display for sites. Update User-Agent for Nominatim requests. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e5565357-90e1-419f-b9a8-6ee8394636df Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e5565357-90e1-419f-b9a8-6ee8394636df/TFybNy5
This commit is contained in:
parent
db860125fc
commit
0c702f4dbf
@ -28,6 +28,8 @@ export default function Sites() {
|
||||
const { toast } = useToast();
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [editingSite, setEditingSite] = useState<Site | null>(null);
|
||||
const [isGeocoding, setIsGeocoding] = useState(false);
|
||||
const [isGeocodingEdit, setIsGeocodingEdit] = useState(false);
|
||||
|
||||
const { data: sites, isLoading } = useQuery<Site[]>({
|
||||
queryKey: ["/api/sites"],
|
||||
@ -57,6 +59,8 @@ export default function Sites() {
|
||||
contractEndDate: undefined,
|
||||
serviceStartTime: "",
|
||||
serviceEndTime: "",
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
@ -77,6 +81,8 @@ export default function Sites() {
|
||||
contractEndDate: undefined,
|
||||
serviceStartTime: "",
|
||||
serviceEndTime: "",
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
@ -125,6 +131,80 @@ export default function Sites() {
|
||||
},
|
||||
});
|
||||
|
||||
const handleGeocode = async () => {
|
||||
const address = form.getValues("address");
|
||||
if (!address) {
|
||||
toast({
|
||||
title: "Indirizzo mancante",
|
||||
description: "Inserisci un indirizzo prima di cercare le coordinate",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGeocoding(true);
|
||||
try {
|
||||
const result: any = await apiRequest(
|
||||
"POST",
|
||||
"/api/geocode",
|
||||
{ address }
|
||||
);
|
||||
|
||||
form.setValue("latitude", result.latitude);
|
||||
form.setValue("longitude", result.longitude);
|
||||
|
||||
toast({
|
||||
title: "Coordinate trovate",
|
||||
description: `Indirizzo: ${result.displayName}`,
|
||||
});
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: "Errore geocodifica",
|
||||
description: error.message || "Impossibile trovare le coordinate per questo indirizzo",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setIsGeocoding(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGeocodeEdit = async () => {
|
||||
const address = editForm.getValues("address");
|
||||
if (!address) {
|
||||
toast({
|
||||
title: "Indirizzo mancante",
|
||||
description: "Inserisci un indirizzo prima di cercare le coordinate",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGeocodingEdit(true);
|
||||
try {
|
||||
const result: any = await apiRequest(
|
||||
"POST",
|
||||
"/api/geocode",
|
||||
{ address }
|
||||
);
|
||||
|
||||
editForm.setValue("latitude", result.latitude);
|
||||
editForm.setValue("longitude", result.longitude);
|
||||
|
||||
toast({
|
||||
title: "Coordinate trovate",
|
||||
description: `Indirizzo: ${result.displayName}`,
|
||||
});
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: "Errore geocodifica",
|
||||
description: error.message || "Impossibile trovare le coordinate per questo indirizzo",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setIsGeocodingEdit(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = (data: InsertSite) => {
|
||||
createMutation.mutate(data);
|
||||
};
|
||||
@ -151,6 +231,8 @@ export default function Sites() {
|
||||
contractEndDate: site.contractEndDate || undefined,
|
||||
serviceStartTime: site.serviceStartTime || "",
|
||||
serviceEndTime: site.serviceEndTime || "",
|
||||
latitude: site.latitude || undefined,
|
||||
longitude: site.longitude || undefined,
|
||||
isActive: site.isActive,
|
||||
});
|
||||
};
|
||||
@ -234,6 +316,69 @@ export default function Sites() {
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="border rounded-lg p-4 space-y-4 bg-muted/50">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-medium flex items-center gap-2">
|
||||
<MapPin className="h-4 w-4" />
|
||||
Coordinate GPS (per mappa)
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleGeocode}
|
||||
disabled={isGeocoding || !form.watch("address")}
|
||||
data-testid="button-geocode"
|
||||
>
|
||||
{isGeocoding ? "Ricerca in corso..." : "📍 Trova Coordinate"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="latitude"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Latitudine</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="41.9028"
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
data-testid="input-latitude"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="longitude"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Longitudine</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="12.4964"
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
data-testid="input-longitude"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Le coordinate GPS permettono di visualizzare il sito sulla mappa in Planning Mobile
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerId"
|
||||
@ -521,6 +666,69 @@ export default function Sites() {
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="border rounded-lg p-4 space-y-4 bg-muted/50">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-medium flex items-center gap-2">
|
||||
<MapPin className="h-4 w-4" />
|
||||
Coordinate GPS (per mappa)
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleGeocodeEdit}
|
||||
disabled={isGeocodingEdit || !editForm.watch("address")}
|
||||
data-testid="button-geocode-edit"
|
||||
>
|
||||
{isGeocodingEdit ? "Ricerca in corso..." : "📍 Trova Coordinate"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={editForm.control}
|
||||
name="latitude"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Latitudine</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="41.9028"
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
data-testid="input-edit-latitude"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={editForm.control}
|
||||
name="longitude"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Longitudine</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="12.4964"
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
data-testid="input-edit-longitude"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Le coordinate GPS permettono di visualizzare il sito sulla mappa in Planning Mobile
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={editForm.control}
|
||||
name="customerId"
|
||||
|
||||
@ -3332,9 +3332,11 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
nominatimUrl.searchParams.set("limit", "1");
|
||||
nominatimUrl.searchParams.set("addressdetails", "1");
|
||||
|
||||
// Nominatim Usage Policy richiede User-Agent con contatto email
|
||||
// Ref: https://operations.osmfoundation.org/policies/nominatim/
|
||||
const response = await fetch(nominatimUrl.toString(), {
|
||||
headers: {
|
||||
"User-Agent": "VigilanzaTurni/1.0 (Security Shift Management System)",
|
||||
"User-Agent": "VigilanzaTurni/1.0 (Security Shift Management System; contact: support@vigilanzaturni.it)",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user