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