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") ) )