diff --git a/attached_assets/immagine_1761123543970.png b/attached_assets/immagine_1761123543970.png new file mode 100644 index 0000000..fb5f343 Binary files /dev/null and b/attached_assets/immagine_1761123543970.png differ diff --git a/client/src/pages/general-planning.tsx b/client/src/pages/general-planning.tsx index bf71929..a251823 100644 --- a/client/src/pages/general-planning.tsx +++ b/client/src/pages/general-planning.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { format, startOfWeek, addWeeks } from "date-fns"; import { it } from "date-fns/locale"; @@ -161,14 +161,35 @@ export default function GeneralPlanning() { staleTime: 0, }); + // Effect per aggiornare selectedCell quando planningData cambia (per real-time update del dialog) + useEffect(() => { + if (selectedCell && planningData) { + // Trova la cella aggiornata nei nuovi dati + const day = planningData.days.find(d => d.date === selectedCell.date); + if (day) { + const updatedSite = day.sites.find(s => s.siteId === selectedCell.siteId); + if (updatedSite) { + setSelectedCell({ + siteId: selectedCell.siteId, + siteName: selectedCell.siteName, + date: selectedCell.date, + data: updatedSite, + }); + } + } + } + }, [planningData]); + // 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"] }); + onSuccess: async () => { + // Invalida e refetch planning generale per aggiornare dialog + await queryClient.invalidateQueries({ queryKey: ["/api/general-planning"] }); + await queryClient.invalidateQueries({ queryKey: ["/api/guards/availability"] }); + await queryClient.refetchQueries({ queryKey: ["/api/general-planning"] }); toast({ title: "Guardia rimossa", diff --git a/server/routes.ts b/server/routes.ts index 6628d50..cb7c541 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1026,32 +1026,33 @@ export async function registerRoutes(app: Express): Promise { const maxOreGuardia = 9; // Max ore per guardia const minGuardie = site.minGuards || 1; - // Somma ore totali dei turni del giorno + // Calcola ore servizio del sito (per calcolo corretto anche senza turni) + const serviceStart = site.serviceStartTime || "00:00"; + const serviceEnd = site.serviceEndTime || "23:59"; + const [startH, startM] = serviceStart.split(":").map(Number); + const [endH, endM] = serviceEnd.split(":").map(Number); + let serviceHours = (endH + endM/60) - (startH + startM/60); + if (serviceHours <= 0) serviceHours += 24; // Servizio notturno (es. 22:00-06:00) + + // Somma ore totali dei turni del giorno (se esistono) const totalShiftHours = dayShifts.reduce((sum: number, ds: any) => { const start = new Date(ds.shift.startTime); const end = new Date(ds.shift.endTime); return sum + differenceInHours(end, start); }, 0); + // Usa ore servizio o ore turni (se già creati) + const effectiveHours = totalShiftHours > 0 ? totalShiftHours : serviceHours; + // Guardie uniche assegnate (conta ogni guardia una volta anche se ha più turni) const uniqueGuardsAssigned = new Set(guardsWithHours.map((g: any) => g.guardId)).size; - // Calcolo guardie necessarie e mancanti - let totalGuardsNeeded: number; - let missingGuards: number; - - if (totalShiftHours > 0) { - // Se ci sono turni: calcola basandosi sulle ore - // Slot necessari per coprire le ore totali - const slotsNeeded = Math.ceil(totalShiftHours / maxOreGuardia); - // Guardie totali necessarie (slot × min guardie contemporanee) - totalGuardsNeeded = slotsNeeded * minGuardie; - missingGuards = Math.max(0, totalGuardsNeeded - uniqueGuardsAssigned); - } else { - // Se NON ci sono turni: serve almeno la copertura minima - totalGuardsNeeded = minGuardie; - missingGuards = minGuardie; // Tutte mancanti perché non ci sono turni - } + // Calcolo guardie necessarie basato su ore servizio + // Slot necessari per coprire le ore (ogni guardia max 9h) + const slotsNeeded = Math.ceil(effectiveHours / maxOreGuardia); + // Guardie totali necessarie (slot × min guardie contemporanee) + const totalGuardsNeeded = slotsNeeded * minGuardie; + const missingGuards = Math.max(0, totalGuardsNeeded - uniqueGuardsAssigned); return { siteId: site.id, @@ -1287,8 +1288,8 @@ export async function registerRoutes(app: Express): Promise { 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)); + // Build dates in LOCAL timezone to match user's selection + const shiftDate = new Date(actualYear, actualMonth, actualDay, 0, 0, 0, 0); // Check contract validity for this date if (site.contractStartDate && site.contractEndDate) { @@ -1301,13 +1302,13 @@ export async function registerRoutes(app: Express): Promise { } } - // 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)); + // Calculate planned start and end times in LOCAL timezone + const plannedStart = new Date(actualYear, actualMonth, actualDay, hours, minutes, 0, 0); + const plannedEnd = new Date(actualYear, actualMonth, actualDay, hours + durationHours, minutes, 0, 0); - // 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)); + // Find or create shift for this site/date (full day boundaries in LOCAL timezone) + const dayStart = new Date(actualYear, actualMonth, actualDay, 0, 0, 0, 0); + const dayEnd = new Date(actualYear, actualMonth, actualDay, 23, 59, 59, 999); let existingShifts = await tx .select() @@ -1331,12 +1332,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(Date.UTC(actualYear, actualMonth, actualDay, startHour, startMin, 0, 0)); - let shiftEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay, endHour, endMin, 0, 0)); + const shiftStart = new Date(actualYear, actualMonth, actualDay, startHour, startMin, 0, 0); + let shiftEnd = new Date(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 = new Date(Date.UTC(actualYear, actualMonth, actualDay + 1, endHour, endMin, 0, 0)); + shiftEnd = new Date(actualYear, actualMonth, actualDay + 1, endHour, endMin, 0, 0); } [shift] = await tx.insert(shifts).values({