From ba0bd4d36f1d3dc22f1bd23f7f278443939f09c9 Mon Sep 17 00:00:00 2001 From: marco370 <48531002-marco370@users.noreply.replit.com> Date: Thu, 23 Oct 2025 07:46:11 +0000 Subject: [PATCH] Add confirmation dialog for guard assignments exceeding limits Update general planning to include AlertDialog for CCNL_VIOLATION errors, allowing forced guard assignments and displaying service details. 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/2w7P7NW --- .replit | 4 -- client/src/pages/general-planning.tsx | 97 +++++++++++++++++++++++++-- server/routes.ts | 3 + 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/.replit b/.replit index 1577d4d..90b1d94 100644 --- a/.replit +++ b/.replit @@ -19,10 +19,6 @@ externalPort = 80 localPort = 33035 externalPort = 3001 -[[ports]] -localPort = 40311 -externalPort = 5173 - [[ports]] localPort = 41343 externalPort = 3000 diff --git a/client/src/pages/general-planning.tsx b/client/src/pages/general-planning.tsx index 0abf7e3..29567bc 100644 --- a/client/src/pages/general-planning.tsx +++ b/client/src/pages/general-planning.tsx @@ -19,6 +19,16 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; import { queryClient, apiRequest } from "@/lib/queryClient"; import { useToast } from "@/hooks/use-toast"; import type { GuardAvailability, Vehicle as VehicleDb } from "@shared/schema"; @@ -44,6 +54,9 @@ interface SiteData { siteId: string; siteName: string; serviceType: string; + serviceStartTime: string; + serviceEndTime: string; + serviceHours: number; minGuards: number; guards: GuardWithHours[]; vehicles: Vehicle[]; @@ -103,6 +116,7 @@ export default function GeneralPlanning() { const [durationHours, setDurationHours] = useState(8); const [consecutiveDays, setConsecutiveDays] = useState(1); const [showOvertimeGuards, setShowOvertimeGuards] = useState(false); + const [ccnlConfirmation, setCcnlConfirmation] = useState<{ message: string; data: any } | null>(null); // Query per dati planning settimanale const { data: planningData, isLoading } = useQuery({ @@ -200,7 +214,7 @@ export default function GeneralPlanning() { // 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; vehicleId?: string }) => { + mutationFn: async (data: { siteId: string; date: string; guardId: string; startTime: string; durationHours: number; consecutiveDays: number; vehicleId?: string; force?: boolean }) => { return apiRequest("POST", "/api/general-planning/assign-guard", data); }, onSuccess: async () => { @@ -220,19 +234,33 @@ export default function GeneralPlanning() { // Reset form (NON chiudere dialog per vedere lista aggiornata) setSelectedGuardId(""); setSelectedVehicleId(""); + setCcnlConfirmation(null); // Reset dialog conferma se aperto }, - onError: (error: any) => { + onError: (error: any, variables) => { // Parse error message from API response let errorMessage = "Impossibile assegnare la guardia"; + let errorType = ""; + if (error.message) { // Error format from apiRequest: "STATUS_CODE: {json_body}" - const match = error.message.match(/^\d+:\s*(.+)$/); + const match = error.message.match(/^(\d+):\s*(.+)$/); if (match) { + const statusCode = match[1]; try { - const parsed = JSON.parse(match[1]); + const parsed = JSON.parse(match[2]); errorMessage = parsed.message || errorMessage; + errorType = parsed.type || ""; + + // Se è un errore CCNL (409 con tipo CCNL_VIOLATION), mostra dialog conferma + if (statusCode === "409" && errorType === "CCNL_VIOLATION") { + setCcnlConfirmation({ + message: errorMessage, + data: variables + }); + return; // Non mostrare toast, mostra dialog + } } catch { - errorMessage = match[1]; + errorMessage = match[2]; } } else { errorMessage = error.message; @@ -834,7 +862,25 @@ export default function GeneralPlanning() { {/* Info turni esistenti */}
-

Situazione Attuale

+

Informazioni Servizio

+ + {/* Tipo servizio e orario */} + {currentCellData && ( +
+
+ Tipo Servizio + {currentCellData.serviceType} +
+
+ Orario Servizio + {currentCellData.serviceStartTime} - {currentCellData.serviceEndTime} +
+
+ Ore Richieste + {currentCellData.serviceHours}h +
+
+ )}
@@ -842,7 +888,7 @@ export default function GeneralPlanning() {

{currentCellData?.shiftsCount || 0}

-

Ore Totali

+

Ore Assegnate

{currentCellData?.totalShiftHours || 0}h

@@ -905,6 +951,43 @@ export default function GeneralPlanning() { + + {/* Dialog conferma forzatura CCNL */} + setCcnlConfirmation(null)}> + + + + + Superamento Limite CCNL + + +

+ {ccnlConfirmation?.message} +

+

+ Vuoi forzare comunque l'assegnazione? L'operazione verrà registrata e potrai consultarla nei report. +

+
+
+ + Annulla + { + if (ccnlConfirmation) { + assignGuardMutation.mutate({ + ...ccnlConfirmation.data, + force: true + }); + } + }} + data-testid="button-confirm-force" + className="bg-yellow-600 hover:bg-yellow-700" + > + Forza Assegnazione + + +
+
); } diff --git a/server/routes.ts b/server/routes.ts index d871a00..fb79ca9 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1058,6 +1058,9 @@ export async function registerRoutes(app: Express): Promise { siteId: site.id, siteName: site.name, serviceType: serviceType?.label || "N/A", + serviceStartTime: serviceStart, + serviceEndTime: serviceEnd, + serviceHours: Math.round(serviceHours * 10) / 10, // Arrotonda a 1 decimale minGuards: site.minGuards, guards: guardsWithHours, vehicles: dayVehicles,