From dd84ddb35bef7cc0812fb4bc77734b79fef3d1b8 Mon Sep 17 00:00:00 2001 From: marco370 <48531002-marco370@users.noreply.replit.com> Date: Sat, 25 Oct 2025 08:59:53 +0000 Subject: [PATCH] Add route optimization and display results in mobile planning Integrate a new API endpoint for route optimization, update the UI to include Sparkles icon, and implement state management for optimization results including total distance and estimated time. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e0b5b11c-5b75-4389-8ea9-5f3cd9332f88 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e0b5b11c-5b75-4389-8ea9-5f3cd9332f88/N0pfy8w --- .replit | 4 - client/src/pages/planning-mobile.tsx | 159 ++++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 8 deletions(-) diff --git a/.replit b/.replit index cf77b29..6b5c189 100644 --- a/.replit +++ b/.replit @@ -19,10 +19,6 @@ externalPort = 80 localPort = 33035 externalPort = 3001 -[[ports]] -localPort = 35023 -externalPort = 6000 - [[ports]] localPort = 41295 externalPort = 5173 diff --git a/client/src/pages/planning-mobile.tsx b/client/src/pages/planning-mobile.tsx index 94e4069..46ce4dc 100644 --- a/client/src/pages/planning-mobile.tsx +++ b/client/src/pages/planning-mobile.tsx @@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Calendar, MapPin, User, Car, Clock, Navigation, ListOrdered, Copy, GripVertical } from "lucide-react"; +import { Calendar, MapPin, User, Car, Clock, Navigation, ListOrdered, Copy, GripVertical, Sparkles } from "lucide-react"; import { format, parseISO, isValid, addDays } from "date-fns"; import { DndContext, @@ -169,6 +169,17 @@ export default function PlanningMobile() { selectedDuplicateGuardId: "", }); + // State per dialog risultati ottimizzazione + const [optimizationResults, setOptimizationResults] = useState<{ + isOpen: boolean; + totalDistanceKm: string; + estimatedTime: string; + }>({ + isOpen: false, + totalDistanceKm: "", + estimatedTime: "", + }); + // Ref per scroll alla sezione sequenze pattuglia const patrolSequencesRef = useRef(null); @@ -471,6 +482,36 @@ export default function PlanningMobile() { }); }; + // Mutation per ottimizzare percorso + const optimizeRouteMutation = useMutation({ + mutationFn: async (coordinates: { lat: string; lon: string; id: string; name: string }[]) => { + const response = await apiRequest("POST", "/api/optimize-route", { coordinates }); + return response.json(); + }, + onSuccess: (data: any) => { + // Riordina le tappe secondo l'ordine ottimizzato + const optimizedStops = data.optimizedRoute.map((coord: any) => + patrolRoute.find(site => site.id === coord.id) + ).filter((site: any) => site !== undefined) as MobileSite[]; + + setPatrolRoute(optimizedStops); + + // Mostra dialog con risultati + setOptimizationResults({ + isOpen: true, + totalDistanceKm: data.totalDistanceKm, + estimatedTime: data.estimatedTimeFormatted, + }); + }, + onError: (error: any) => { + toast({ + title: "Errore ottimizzazione", + description: error.message || "Impossibile ottimizzare il percorso", + variant: "destructive", + }); + }, + }); + // Mutation per salvare patrol route const savePatrolRouteMutation = useMutation({ mutationFn: async ({ data, existingRouteId }: { data: any; existingRouteId?: string }) => { @@ -501,6 +542,45 @@ export default function PlanningMobile() { }, }); + // Funzione per ottimizzare il percorso + const handleOptimizeRoute = () => { + // Verifica che ci siano almeno 2 tappe + if (patrolRoute.length < 2) { + toast({ + title: "Tappe insufficienti", + description: "Servono almeno 2 tappe per ottimizzare il percorso", + variant: "destructive", + }); + return; + } + + // Verifica che tutte le tappe abbiano coordinate GPS + const sitesWithCoords = patrolRoute.filter(site => + site.latitude && site.longitude && + !isNaN(parseFloat(site.latitude)) && + !isNaN(parseFloat(site.longitude)) + ); + + if (sitesWithCoords.length !== patrolRoute.length) { + toast({ + title: "Coordinate GPS mancanti", + description: `${patrolRoute.length - sitesWithCoords.length} tappe non hanno coordinate GPS valide`, + variant: "destructive", + }); + return; + } + + // Prepara dati per ottimizzazione + const coordinates = sitesWithCoords.map(site => ({ + lat: site.latitude!, + lon: site.longitude!, + id: site.id, + name: site.name, + })); + + optimizeRouteMutation.mutate(coordinates); + }; + // Funzione per salvare il turno pattuglia const handleSavePatrolRoute = () => { if (!selectedGuard) { @@ -645,7 +725,7 @@ export default function PlanningMobile() { {patrolRoute.length} tappe programmate per il turno del {format(parseISO(selectedDate), "dd MMMM yyyy", { locale: it })}
- 💡 Trascina le tappe per riordinarle + Trascina le tappe per riordinarle
@@ -670,12 +750,22 @@ export default function PlanningMobile() { -
+
+ + + +
); }