From 121206a492a281efa9476783f96da7a25dc11232 Mon Sep 17 00:00:00 2001 From: marco370 <48531002-marco370@users.noreply.replit.com> Date: Fri, 17 Oct 2025 16:36:09 +0000 Subject: [PATCH] Improve operational planning by fixing date handling and selection logic Fixes issues with date validation and event propagation in operational planning, and updates resource query keys for better data fetching. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e5565357-90e1-419f-b9a8-6ee8394636df Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e5565357-90e1-419f-b9a8-6ee8394636df/09WwRvv --- client/src/pages/operational-planning.tsx | 22 ++++++++----- replit.md | 5 +++ server/routes.ts | 40 +++++++++++++++++------ 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/client/src/pages/operational-planning.tsx b/client/src/pages/operational-planning.tsx index b13c141..7934707 100644 --- a/client/src/pages/operational-planning.tsx +++ b/client/src/pages/operational-planning.tsx @@ -102,7 +102,7 @@ export default function OperationalPlanning() { // Query per risorse (veicoli e guardie) - solo quando c'è un sito selezionato const { data: resourcesData, isLoading: isLoadingResources } = useQuery({ - queryKey: [`/api/operational-planning/availability?date=${selectedDate}`, selectedDate], + queryKey: [`/api/operational-planning/availability?date=${selectedDate}`, selectedDate, selectedSite?.id], enabled: !!selectedDate && !!selectedSite, }); @@ -423,10 +423,12 @@ export default function OperationalPlanning() { {vehicle.brand} {vehicle.model}

- setSelectedVehicle(vehicle.id)} - /> +
e.stopPropagation()}> + setSelectedVehicle(vehicle.id)} + /> +
@@ -481,10 +483,12 @@ export default function OperationalPlanning() { Ore sett.: {guard.availability.weeklyHours}h | Rimaste: {guard.availability.remainingWeeklyHours}h

- toggleGuardSelection(guard.id)} - /> +
e.stopPropagation()}> + toggleGuardSelection(guard.id)} + /> +
diff --git a/replit.md b/replit.md index dfe6231..665e637 100644 --- a/replit.md +++ b/replit.md @@ -36,6 +36,11 @@ The database includes core tables for `users`, `guards`, `certifications`, `site - **Contract Management**: Sites now include contract fields: `contractReference` (codice contratto), `contractStartDate`, `contractEndDate` (date validità contratto in formato YYYY-MM-DD) - Sites now reference service types via `serviceTypeId` foreign key; `shiftType` is optional and can be derived from service type +**Recent Bug Fixes (October 17, 2025)**: +- **Operational Planning Date Handling**: Fixed date sanitization in `/api/operational-planning/uncovered-sites` and `/api/operational-planning/availability` endpoints to handle malformed date inputs (e.g., "2025-10-17/2025-10-17"). Both endpoints now validate dates using `parseISO`/`isValid` and return 400 for invalid formats. +- **Checkbox Event Propagation**: Fixed double-toggle bug in operational planning resource selection by wrapping vehicle and guard checkboxes in `
e.stopPropagation()}>` to prevent Card onClick from firing when clicking checkboxes. +- **Resource Query Key**: Added `selectedSite?.id` to TanStack Query queryKey for availability endpoint to ensure resources re-fetch when operator selects a different site. + ### API Endpoints Comprehensive RESTful API endpoints are provided for Authentication, Users, Guards, Sites, Shifts, and Notifications, supporting full CRUD operations with role-based access control. diff --git a/server/routes.ts b/server/routes.ts index 1dac112..c5c43df 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -6,7 +6,7 @@ import { setupLocalAuth, isAuthenticated as isAuthenticatedLocal } from "./local import { db } from "./db"; import { guards, certifications, sites, shifts, shiftAssignments, users, insertShiftSchema, contractParameters } from "@shared/schema"; import { eq, and, gte, lte, desc, asc, ne, sql } from "drizzle-orm"; -import { differenceInDays, differenceInHours, differenceInMinutes, startOfWeek, endOfWeek, startOfMonth, endOfMonth, isWithinInterval, startOfDay, isSameDay, parseISO, format } from "date-fns"; +import { differenceInDays, differenceInHours, differenceInMinutes, startOfWeek, endOfWeek, startOfMonth, endOfMonth, isWithinInterval, startOfDay, isSameDay, parseISO, format, isValid } from "date-fns"; // Determina quale sistema auth usare basandosi sull'ambiente const USE_LOCAL_AUTH = process.env.DOMAIN === "vt.alfacom.it" || !process.env.REPLIT_DOMAINS; @@ -538,8 +538,18 @@ export async function registerRoutes(app: Express): Promise { app.get("/api/operational-planning/availability", isAuthenticated, async (req, res) => { try { const { getGuardAvailabilityReport } = await import("./ccnlRules"); - const dateStr = req.query.date as string || format(new Date(), "yyyy-MM-dd"); - const date = new Date(dateStr + "T00:00:00.000Z"); + + // Sanitizza input: gestisce sia "2025-10-17" che "2025-10-17/2025-10-17" + const rawDateStr = req.query.date as string || format(new Date(), "yyyy-MM-dd"); + const normalizedDateStr = rawDateStr.split("/")[0]; // Prende solo la prima parte se c'è uno slash + + // Valida la data + const parsedDate = parseISO(normalizedDateStr); + if (!isValid(parsedDate)) { + return res.status(400).json({ message: "Invalid date format. Use yyyy-MM-dd" }); + } + + const dateStr = format(parsedDate, "yyyy-MM-dd"); // Imposta inizio e fine giornata in UTC const startOfDay = new Date(dateStr + "T00:00:00.000Z"); @@ -657,7 +667,17 @@ export async function registerRoutes(app: Express): Promise { // Endpoint per ottenere siti non completamente coperti per una data app.get("/api/operational-planning/uncovered-sites", isAuthenticated, async (req, res) => { try { - const dateStr = req.query.date as string || format(new Date(), "yyyy-MM-dd"); + // Sanitizza input: gestisce sia "2025-10-17" che "2025-10-17/2025-10-17" + const rawDateStr = req.query.date as string || format(new Date(), "yyyy-MM-dd"); + const normalizedDateStr = rawDateStr.split("/")[0]; // Prende solo la prima parte se c'è uno slash + + // Valida la data + const parsedDate = parseISO(normalizedDateStr); + if (!isValid(parsedDate)) { + return res.status(400).json({ message: "Invalid date format. Use yyyy-MM-dd" }); + } + + const dateStr = format(parsedDate, "yyyy-MM-dd"); // Ottieni tutti i siti attivi const allSites = await db @@ -687,11 +707,11 @@ export async function registerRoutes(app: Express): Promise { }); // Ottieni turni del giorno con assegnazioni - const startOfDay = new Date(dateStr); - startOfDay.setHours(0, 0, 0, 0); + const startOfDayDate = new Date(dateStr); + startOfDayDate.setHours(0, 0, 0, 0); - const endOfDay = new Date(dateStr); - endOfDay.setHours(23, 59, 59, 999); + const endOfDayDate = new Date(dateStr); + endOfDayDate.setHours(23, 59, 59, 999); const dayShifts = await db .select({ @@ -702,8 +722,8 @@ export async function registerRoutes(app: Express): Promise { .leftJoin(shiftAssignments, eq(shifts.id, shiftAssignments.shiftId)) .where( and( - gte(shifts.startTime, startOfDay), - lte(shifts.startTime, endOfDay), + gte(shifts.startTime, startOfDayDate), + lte(shifts.startTime, endOfDayDate), ne(shifts.status, "cancelled") ) )