From efa056dd98c11d8862f254333fab67b11f237bff Mon Sep 17 00:00:00 2001 From: marco370 <48531002-marco370@users.noreply.replit.com> Date: Sat, 25 Oct 2025 08:52:36 +0000 Subject: [PATCH] Allow users to reorder planned stops using drag-and-drop Integrate @dnd-kit for implementing drag-and-drop functionality to reorder stops within the mobile planning view. 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 | 128 ++++++++++++++++++++++----- package-lock.json | 56 ++++++++++++ package.json | 3 + 4 files changed, 168 insertions(+), 23 deletions(-) diff --git a/.replit b/.replit index 6b5c189..cf77b29 100644 --- a/.replit +++ b/.replit @@ -19,6 +19,10 @@ 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 0e3e8e3..94e4069 100644 --- a/client/src/pages/planning-mobile.tsx +++ b/client/src/pages/planning-mobile.tsx @@ -6,8 +6,25 @@ 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 } from "lucide-react"; +import { Calendar, MapPin, User, Car, Clock, Navigation, ListOrdered, Copy, GripVertical } from "lucide-react"; import { format, parseISO, isValid, addDays } from "date-fns"; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + DragEndEvent, +} from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, + useSortable, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; import { Dialog, DialogContent, @@ -61,6 +78,50 @@ type MobileSite = { longitude: string | null; }; +// Componente per tappa draggable +function SortableStop({ site, index, onRemove }: { site: MobileSite; index: number; onRemove: () => void }) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: site.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + }; + + return ( +
+
+ +
+ + {index + 1} + + {site.name} + +
+ ); +} + type AvailableGuard = { id: string; firstName: string; @@ -111,6 +172,27 @@ export default function PlanningMobile() { // Ref per scroll alla sezione sequenze pattuglia const patrolSequencesRef = useRef(null); + // Sensors per drag-and-drop + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + + // Handler per riordinare le tappe con drag-and-drop + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + if (over && active.id !== over.id) { + setPatrolRoute((items) => { + const oldIndex = items.findIndex(item => item.id === active.id); + const newIndex = items.findIndex(item => item.id === over.id); + return arrayMove(items, oldIndex, newIndex); + }); + } + }; + // Query siti mobile per location const { data: mobileSites, isLoading: sitesLoading } = useQuery({ queryKey: ["/api/planning-mobile/sites", selectedLocation], @@ -562,32 +644,32 @@ 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
-
- {patrolRoute.map((site, index) => ( -
- - {index + 1} - - {site.name} - + + site.id)} + strategy={verticalListSortingStrategy} + > +
+ {patrolRoute.map((site, index) => ( + handleRemoveFromRoute(site.id)} + /> + ))}
- ))} -
+ +