diff --git a/.replit b/.replit index 90b1d94..97e4596 100644 --- a/.replit +++ b/.replit @@ -19,6 +19,10 @@ externalPort = 80 localPort = 33035 externalPort = 3001 +[[ports]] +localPort = 36359 +externalPort = 5173 + [[ports]] localPort = 41343 externalPort = 3000 diff --git a/client/src/pages/general-planning.tsx b/client/src/pages/general-planning.tsx index 68941fe..6a9d585 100644 --- a/client/src/pages/general-planning.tsx +++ b/client/src/pages/general-planning.tsx @@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { ChevronLeft, ChevronRight, Calendar, MapPin, Users, AlertTriangle, Car, Edit, CheckCircle2, Plus } from "lucide-react"; +import { ChevronLeft, ChevronRight, Calendar, MapPin, Users, AlertTriangle, Car, Edit, CheckCircle2, Plus, Trash2, Clock } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { @@ -24,10 +24,13 @@ import { useToast } from "@/hooks/use-toast"; import type { GuardAvailability } from "@shared/schema"; interface GuardWithHours { + assignmentId: string; guardId: string; guardName: string; badgeNumber: string; hours: number; + plannedStartTime: string; + plannedEndTime: string; } interface Vehicle { @@ -68,6 +71,18 @@ interface GeneralPlanningResponse { }; } +// Helper per formattare orario in formato italiano 24h (HH:MM) +const formatTime = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleTimeString("it-IT", { hour: "2-digit", minute: "2-digit", hour12: false }); +}; + +// Helper per formattare data in formato italiano (gg/mm/aaaa) +const formatDateIT = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString("it-IT"); +}; + export default function GeneralPlanning() { const [, navigate] = useLocation(); const { toast } = useToast(); @@ -124,6 +139,29 @@ export default function GeneralPlanning() { enabled: !!selectedCell, // Query attiva solo se dialog è aperto }); + // Mutation per eliminare assegnazione guardia + const deleteAssignmentMutation = useMutation({ + mutationFn: async (assignmentId: string) => { + return apiRequest("DELETE", `/api/shift-assignments/${assignmentId}`, undefined); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/general-planning"] }); + queryClient.invalidateQueries({ queryKey: ["/api/guards/availability"] }); + + toast({ + title: "Guardia rimossa", + description: "L'assegnazione è stata eliminata con successo", + }); + }, + onError: (error: any) => { + toast({ + title: "Errore", + description: "Impossibile eliminare l'assegnazione", + variant: "destructive", + }); + }, + }); + // Mutation per assegnare guardia con orari (anche multi-giorno) const assignGuardMutation = useMutation({ mutationFn: async (data: { siteId: string; date: string; guardId: string; startTime: string; durationHours: number; consecutiveDays: number }) => { @@ -599,11 +637,34 @@ export default function GeneralPlanning() {

Guardie già assegnate per questa data:

-
+
{selectedCell.data.guards.map((guard, idx) => ( -
- {guard.guardName} #{guard.badgeNumber} - {guard.hours}h +
+
+
+ {guard.guardName} + #{guard.badgeNumber} +
+
+ + {formatTime(guard.plannedStartTime)} - {formatTime(guard.plannedEndTime)} + ({guard.hours}h) +
+
+
))}
diff --git a/server/routes.ts b/server/routes.ts index debfd18..bcc19b1 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -963,17 +963,20 @@ export async function registerRoutes(app: Express): Promise { dayShifts.some((ds: any) => ds.shift.id === a.shift.id) ); - // Calcola ore per ogni guardia + // Calcola ore per ogni guardia con orari e assignmentId const guardsWithHours = dayAssignments.map((a: any) => { - const shiftStart = new Date(a.shift.startTime); - const shiftEnd = new Date(a.shift.endTime); - const hours = differenceInHours(shiftEnd, shiftStart); + const plannedStart = new Date(a.assignment.plannedStartTime); + const plannedEnd = new Date(a.assignment.plannedEndTime); + const hours = differenceInHours(plannedEnd, plannedStart); return { + assignmentId: a.assignment.id, guardId: a.guard.id, guardName: a.guard.fullName, badgeNumber: a.guard.badgeNumber, hours, + plannedStartTime: a.assignment.plannedStartTime, + plannedEndTime: a.assignment.plannedEndTime, }; }); @@ -1177,6 +1180,35 @@ export async function registerRoutes(app: Express): Promise { } }); + // Delete a shift assignment + app.delete("/api/shift-assignments/:assignmentId", isAuthenticated, async (req, res) => { + try { + const { assignmentId } = req.params; + + if (!assignmentId) { + return res.status(400).json({ message: "Assignment ID is required" }); + } + + // Delete the assignment + const deleted = await db + .delete(shiftAssignments) + .where(eq(shiftAssignments.id, assignmentId)) + .returning(); + + if (deleted.length === 0) { + return res.status(404).json({ message: "Assignment not found" }); + } + + res.json({ + message: "Assignment deleted successfully", + assignment: deleted[0] + }); + } catch (error: any) { + console.error("Error deleting assignment:", error); + res.status(500).json({ message: "Failed to delete assignment", error: String(error) }); + } + }); + // Assign guard to site/date with specific time slot (supports multi-day assignments) app.post("/api/general-planning/assign-guard", isAuthenticated, async (req, res) => { try { @@ -1204,7 +1236,7 @@ export async function registerRoutes(app: Express): Promise { return res.status(404).json({ message: "Guard not found" }); } - // Parse start date WITHOUT timezone conversion (stay in local time) + // Parse start date components const [year, month, day] = date.split("-").map(Number); if (!year || !month || !day || month < 1 || month > 12 || day < 1 || day > 31) { return res.status(400).json({ message: "Invalid date format. Expected YYYY-MM-DD" }); @@ -1217,10 +1249,18 @@ export async function registerRoutes(app: Express): Promise { // Loop through each consecutive day for (let dayOffset = 0; dayOffset < consecutiveDays; dayOffset++) { - // Calculate date for this iteration - const currentDate = new Date(year, month - 1, day + dayOffset); - const shiftDate = new Date(currentDate); - shiftDate.setHours(0, 0, 0, 0); + // Calculate date components for this iteration (avoid timezone issues) + const targetDay = day + dayOffset; + const baseDate = new Date(year, month - 1, 1); // First day of month + baseDate.setDate(targetDay); // Set to target day (handles month overflow) + + // Extract actual date components after overflow handling + const actualYear = baseDate.getFullYear(); + const actualMonth = baseDate.getMonth(); + const actualDay = baseDate.getDate(); + + // Build dates in UTC to avoid timezone shifts + const shiftDate = new Date(Date.UTC(actualYear, actualMonth, actualDay, 0, 0, 0, 0)); // Check contract validity for this date if (site.contractStartDate && site.contractEndDate) { @@ -1233,17 +1273,13 @@ export async function registerRoutes(app: Express): Promise { } } - // Calculate planned start and end times for this day - const plannedStart = new Date(currentDate); - plannedStart.setHours(hours, minutes, 0, 0); - const plannedEnd = new Date(currentDate); - plannedEnd.setHours(hours + durationHours, minutes, 0, 0); + // Calculate planned start and end times in UTC + const plannedStart = new Date(Date.UTC(actualYear, actualMonth, actualDay, hours, minutes, 0, 0)); + const plannedEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay, hours + durationHours, minutes, 0, 0)); - // Find or create shift for this site/date - const dayStart = new Date(shiftDate); - dayStart.setHours(0, 0, 0, 0); - const dayEnd = new Date(shiftDate); - dayEnd.setHours(23, 59, 59, 999); + // Find or create shift for this site/date (full day boundaries in UTC) + const dayStart = new Date(Date.UTC(actualYear, actualMonth, actualDay, 0, 0, 0, 0)); + const dayEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay, 23, 59, 59, 999)); let existingShifts = await tx .select() @@ -1267,14 +1303,12 @@ export async function registerRoutes(app: Express): Promise { const [startHour, startMin] = serviceStart.split(":").map(Number); const [endHour, endMin] = serviceEnd.split(":").map(Number); - const shiftStart = new Date(shiftDate); - shiftStart.setHours(startHour, startMin, 0, 0); - - const shiftEnd = new Date(shiftDate); - shiftEnd.setHours(endHour, endMin, 0, 0); + const shiftStart = new Date(Date.UTC(actualYear, actualMonth, actualDay, startHour, startMin, 0, 0)); + let shiftEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay, endHour, endMin, 0, 0)); + // If end time is before/equal to start time, shift extends to next day if (shiftEnd <= shiftStart) { - shiftEnd.setDate(shiftEnd.getDate() + 1); + shiftEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay + 1, endHour, endMin, 0, 0)); } [shift] = await tx.insert(shifts).values({