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
This commit is contained in:
marco370 2025-10-17 16:36:09 +00:00
parent 1edc335ca6
commit 121206a492
3 changed files with 48 additions and 19 deletions

View File

@ -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<ResourcesData>({
queryKey: [`/api/operational-planning/availability?date=${selectedDate}`, selectedDate],
queryKey: [`/api/operational-planning/availability?date=${selectedDate}`, selectedDate, selectedSite?.id],
enabled: !!selectedDate && !!selectedSite,
});
@ -423,11 +423,13 @@ export default function OperationalPlanning() {
{vehicle.brand} {vehicle.model}
</p>
</div>
<div onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={selectedVehicle === vehicle.id}
onCheckedChange={() => setSelectedVehicle(vehicle.id)}
/>
</div>
</div>
</CardContent>
</Card>
))}
@ -481,11 +483,13 @@ export default function OperationalPlanning() {
Ore sett.: {guard.availability.weeklyHours}h | Rimaste: {guard.availability.remainingWeeklyHours}h
</p>
</div>
<div onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={selectedGuards.includes(guard.id)}
onCheckedChange={() => toggleGuardSelection(guard.id)}
/>
</div>
</div>
</CardContent>
</Card>
))}

View File

@ -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 `<div onClick={e => 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.

View File

@ -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<Server> {
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<Server> {
// 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<Server> {
});
// 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<Server> {
.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")
)
)