Improve planning accuracy and real-time updates for guard shifts
Fixes an issue where dates were incorrectly shifted due to timezone handling and ensures the guard assignment dialog updates in real-time after modifications. Additionally, refactors the calculation of needed guards to accurately reflect site service hours and minimum guard requirements. 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/TWQ52cO
This commit is contained in:
parent
d8a6ec9c49
commit
3b7c55b55b
BIN
attached_assets/immagine_1761123543970.png
Normal file
BIN
attached_assets/immagine_1761123543970.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
@ -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",
|
||||
|
||||
@ -1026,32 +1026,33 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
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<Server> {
|
||||
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<Server> {
|
||||
}
|
||||
}
|
||||
|
||||
// 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<Server> {
|
||||
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({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user