diff --git a/client/src/pages/services.tsx b/client/src/pages/services.tsx index 4bb6068..184fcd7 100644 --- a/client/src/pages/services.tsx +++ b/client/src/pages/services.tsx @@ -166,6 +166,7 @@ export default function Services() { description: "", icon: "Building2", color: "blue", + classification: "fisso", // ✅ NUOVO: Discriminante Planning Fissi/Mobile isActive: true, }, }); @@ -178,6 +179,7 @@ export default function Services() { description: "", icon: "Building2", color: "blue", + classification: "fisso", // ✅ NUOVO: Discriminante Planning Fissi/Mobile isActive: true, }, }); @@ -235,10 +237,7 @@ export default function Services() { description: type.description, icon: type.icon, color: type.color, - fixedPostHours: type.fixedPostHours || null, - patrolPassages: type.patrolPassages || null, - inspectionFrequency: type.inspectionFrequency || null, - responseTimeMinutes: type.responseTimeMinutes || null, + classification: type.classification, // ✅ NUOVO: includi classification isActive: type.isActive, }); setEditTypeDialogOpen(true); @@ -1071,91 +1070,28 @@ export default function Services() { /> -
-

Parametri Specifici

-
- ( - - Ore Presidio Fisso - - field.onChange(e.target.value ? parseInt(e.target.value) : null)} - placeholder="es: 8, 12" - data-testid="input-fixed-post-hours" - /> - - - - )} - /> - ( - - Passaggi Pattugliamento - - field.onChange(e.target.value ? parseInt(e.target.value) : null)} - placeholder="es: 3, 5" - data-testid="input-patrol-passages" - /> - - - - )} - /> - ( - - Frequenza Ispezioni (min) - - field.onChange(e.target.value ? parseInt(e.target.value) : null)} - placeholder="es: 60, 120" - data-testid="input-inspection-frequency" - /> - - - - )} - /> - ( - - Tempo Risposta (min) - - field.onChange(e.target.value ? parseInt(e.target.value) : null)} - placeholder="es: 15, 30" - data-testid="input-response-time" - /> - - - - )} - /> -
-
+ {/* ✅ NUOVO: Classification (Fisso/Mobile) */} + ( + + Tipo Pianificazione* + + + + )} + /> -
+ {/* ✅ NUOVO: Classification (Fisso/Mobile) */} + ( + + Tipo Pianificazione* + + + + )} + /> + +

Parametri Specifici

{ const rawWeekStart = req.query.weekStart as string || format(new Date(), "yyyy-MM-dd"); const normalizedWeekStart = rawWeekStart.split("/")[0]; - // Valida la data - const parsedWeekStart = parseISO(normalizedWeekStart); - if (!isValid(parsedWeekStart)) { + // ✅ CORRETTO: Valida date con regex, NON parseISO + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(normalizedWeekStart)) { return res.status(400).json({ message: "Invalid date format. Use yyyy-MM-dd" }); } - const weekStartDate = format(parsedWeekStart, "yyyy-MM-dd"); + // ✅ CORRETTO: Costruisci Date da componenti per evitare timezone shift + const [year, month, day] = normalizedWeekStart.split("-").map(Number); + const parsedWeekStart = new Date(year, month - 1, day, 0, 0, 0, 0); + + const weekStartDate = normalizedWeekStart; // Ottieni location dalla query (default: roccapiemonte) const location = req.query.location as string || "roccapiemonte"; - // Calcola fine settimana (weekStart + 6 giorni) - const weekEndDate = format(addDays(parsedWeekStart, 6), "yyyy-MM-dd"); + // Calcola fine settimana (weekStart + 6 giorni) usando componenti + const tempWeekEnd = new Date(year, month - 1, day + 6, 23, 59, 59, 999); + const weekEndYear = tempWeekEnd.getFullYear(); + const weekEndMonth = tempWeekEnd.getMonth() + 1; + const weekEndDay = tempWeekEnd.getDate(); + const weekEndDate = `${weekEndYear}-${String(weekEndMonth).padStart(2, '0')}-${String(weekEndDay).padStart(2, '0')}`; - // Timestamp per filtro contratti - const weekStartTimestampForContract = new Date(weekStartDate); - const weekEndTimestampForContract = new Date(weekEndDate); + // ✅ CORRETTO: Timestamp da componenti per query database + const weekStartTimestampForContract = new Date(year, month - 1, day, 0, 0, 0, 0); + const weekEndTimestampForContract = new Date(weekEndYear, weekEndMonth - 1, weekEndDay, 23, 59, 59, 999); // Ottieni tutti i siti attivi della sede con contratto valido nelle date della settimana const activeSites = await db @@ -917,11 +925,9 @@ export async function registerRoutes(app: Express): Promise { ); // Ottieni tutti i turni della settimana per la sede - const weekStartTimestamp = new Date(weekStartDate); - weekStartTimestamp.setHours(0, 0, 0, 0); - - const weekEndTimestamp = new Date(weekEndDate); - weekEndTimestamp.setHours(23, 59, 59, 999); + // ✅ CORRETTO: Usa timestamp già creati correttamente sopra + const weekStartTimestamp = weekStartTimestampForContract; + const weekEndTimestamp = weekEndTimestampForContract; const weekShifts = await db .select({ @@ -971,14 +977,15 @@ export async function registerRoutes(app: Express): Promise { const weekData = []; for (let dayOffset = 0; dayOffset < 7; dayOffset++) { - const currentDay = addDays(parsedWeekStart, dayOffset); - const dayStr = format(currentDay, "yyyy-MM-dd"); + // ✅ CORRETTO: Calcola date usando componenti per evitare timezone shift + const currentDayTimestamp = new Date(year, month - 1, day + dayOffset, 0, 0, 0, 0); + const currentYear = currentDayTimestamp.getFullYear(); + const currentMonth = currentDayTimestamp.getMonth() + 1; + const currentDay_num = currentDayTimestamp.getDate(); + const dayStr = `${currentYear}-${String(currentMonth).padStart(2, '0')}-${String(currentDay_num).padStart(2, '0')}`; - const dayStartTimestamp = new Date(dayStr); - dayStartTimestamp.setHours(0, 0, 0, 0); - - const dayEndTimestamp = new Date(dayStr); - dayEndTimestamp.setHours(23, 59, 59, 999); + const dayStartTimestamp = new Date(currentYear, currentMonth - 1, currentDay_num, 0, 0, 0, 0); + const dayEndTimestamp = new Date(currentYear, currentMonth - 1, currentDay_num, 23, 59, 59, 999); const sitesData = activeSites.map(({ sites: site, service_types: serviceType }: any) => { // Trova turni del giorno per questo sito diff --git a/shared/schema.ts b/shared/schema.ts index 7ec9919..9b9bbd2 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -91,6 +91,11 @@ export const locationEnum = pgEnum("location", [ "roma", // Sede Roma ]); +export const serviceClassificationEnum = pgEnum("service_classification", [ + "fisso", // Presidio fisso - Planning Fissi + "mobile", // Pattuglie/ronde/interventi - Planning Mobile +]); + // ============= SESSION & AUTH TABLES (Replit Auth) ============= // Session storage table - mandatory for Replit Auth @@ -189,6 +194,9 @@ export const serviceTypes = pgTable("service_types", { icon: varchar("icon").notNull().default("Building2"), // Nome icona Lucide color: varchar("color").notNull().default("blue"), // blue, green, purple, orange + // ✅ NUOVO: Classificazione servizio - determina quale planning usare + classification: serviceClassificationEnum("classification").notNull().default("fisso"), + // Parametri specifici per tipo servizio fixedPostHours: integer("fixed_post_hours"), // Ore presidio fisso (es. 8, 12) patrolPassages: integer("patrol_passages"), // Numero passaggi pattugliamento (es. 3, 5)