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:
parent
1edc335ca6
commit
121206a492
@ -102,7 +102,7 @@ export default function OperationalPlanning() {
|
|||||||
|
|
||||||
// Query per risorse (veicoli e guardie) - solo quando c'è un sito selezionato
|
// Query per risorse (veicoli e guardie) - solo quando c'è un sito selezionato
|
||||||
const { data: resourcesData, isLoading: isLoadingResources } = useQuery<ResourcesData>({
|
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,
|
enabled: !!selectedDate && !!selectedSite,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -423,11 +423,13 @@ export default function OperationalPlanning() {
|
|||||||
{vehicle.brand} {vehicle.model}
|
{vehicle.brand} {vehicle.model}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedVehicle === vehicle.id}
|
checked={selectedVehicle === vehicle.id}
|
||||||
onCheckedChange={() => setSelectedVehicle(vehicle.id)}
|
onCheckedChange={() => setSelectedVehicle(vehicle.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
@ -481,11 +483,13 @@ export default function OperationalPlanning() {
|
|||||||
Ore sett.: {guard.availability.weeklyHours}h | Rimaste: {guard.availability.remainingWeeklyHours}h
|
Ore sett.: {guard.availability.weeklyHours}h | Rimaste: {guard.availability.remainingWeeklyHours}h
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedGuards.includes(guard.id)}
|
checked={selectedGuards.includes(guard.id)}
|
||||||
onCheckedChange={() => toggleGuardSelection(guard.id)}
|
onCheckedChange={() => toggleGuardSelection(guard.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -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)
|
- **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
|
- 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
|
### 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.
|
Comprehensive RESTful API endpoints are provided for Authentication, Users, Guards, Sites, Shifts, and Notifications, supporting full CRUD operations with role-based access control.
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { setupLocalAuth, isAuthenticated as isAuthenticatedLocal } from "./local
|
|||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { guards, certifications, sites, shifts, shiftAssignments, users, insertShiftSchema, contractParameters } from "@shared/schema";
|
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 { 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
|
// Determina quale sistema auth usare basandosi sull'ambiente
|
||||||
const USE_LOCAL_AUTH = process.env.DOMAIN === "vt.alfacom.it" || !process.env.REPLIT_DOMAINS;
|
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) => {
|
app.get("/api/operational-planning/availability", isAuthenticated, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { getGuardAvailabilityReport } = await import("./ccnlRules");
|
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
|
// Imposta inizio e fine giornata in UTC
|
||||||
const startOfDay = new Date(dateStr + "T00:00:00.000Z");
|
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
|
// Endpoint per ottenere siti non completamente coperti per una data
|
||||||
app.get("/api/operational-planning/uncovered-sites", isAuthenticated, async (req, res) => {
|
app.get("/api/operational-planning/uncovered-sites", isAuthenticated, async (req, res) => {
|
||||||
try {
|
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
|
// Ottieni tutti i siti attivi
|
||||||
const allSites = await db
|
const allSites = await db
|
||||||
@ -687,11 +707,11 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Ottieni turni del giorno con assegnazioni
|
// Ottieni turni del giorno con assegnazioni
|
||||||
const startOfDay = new Date(dateStr);
|
const startOfDayDate = new Date(dateStr);
|
||||||
startOfDay.setHours(0, 0, 0, 0);
|
startOfDayDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
const endOfDay = new Date(dateStr);
|
const endOfDayDate = new Date(dateStr);
|
||||||
endOfDay.setHours(23, 59, 59, 999);
|
endOfDayDate.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
const dayShifts = await db
|
const dayShifts = await db
|
||||||
.select({
|
.select({
|
||||||
@ -702,8 +722,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
.leftJoin(shiftAssignments, eq(shifts.id, shiftAssignments.shiftId))
|
.leftJoin(shiftAssignments, eq(shifts.id, shiftAssignments.shiftId))
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
gte(shifts.startTime, startOfDay),
|
gte(shifts.startTime, startOfDayDate),
|
||||||
lte(shifts.startTime, endOfDay),
|
lte(shifts.startTime, endOfDayDate),
|
||||||
ne(shifts.status, "cancelled")
|
ne(shifts.status, "cancelled")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user