diff --git a/.replit b/.replit index da955e9..3b4f4d4 100644 --- a/.replit +++ b/.replit @@ -19,6 +19,10 @@ externalPort = 80 localPort = 33035 externalPort = 3001 +[[ports]] +localPort = 34027 +externalPort = 6000 + [[ports]] localPort = 41295 externalPort = 5173 @@ -47,10 +51,6 @@ externalPort = 5000 localPort = 43267 externalPort = 3003 -[[ports]] -localPort = 44369 -externalPort = 6000 - [env] PORT = "5000" diff --git a/client/src/pages/general-planning.tsx b/client/src/pages/general-planning.tsx index 2a1f4d2..f4d35f2 100644 --- a/client/src/pages/general-planning.tsx +++ b/client/src/pages/general-planning.tsx @@ -85,14 +85,14 @@ interface GeneralPlanningResponse { } // Helper per formattare orario in formato italiano 24h (HH:MM) -// IMPORTANTE: usa timeZone UTC per evitare shift di +2 ore +// IMPORTANTE: Gli orari nel DB sono UTC, visualizzali in timezone Europe/Rome const formatTime = (dateString: string) => { const date = new Date(dateString); return date.toLocaleTimeString("it-IT", { hour: "2-digit", minute: "2-digit", hour12: false, - timeZone: "UTC" // Evita conversione timezone locale (+2h in Italia) + timeZone: "Europe/Rome" // Converti da UTC a Italy time }); }; diff --git a/client/src/pages/my-shifts-fixed.tsx b/client/src/pages/my-shifts-fixed.tsx index a55758a..13a8b52 100644 --- a/client/src/pages/my-shifts-fixed.tsx +++ b/client/src/pages/my-shifts-fixed.tsx @@ -159,21 +159,31 @@ export default function MyShiftsFixed() { ) : ( dayShifts.map((shift) => { - // Parsing sicuro orari + // Parsing sicuro orari (DB in UTC → visualizza in Europe/Rome) let startTime = "N/A"; let endTime = "N/A"; if (shift.plannedStartTime) { - const parsedStart = parseISO(shift.plannedStartTime); + const parsedStart = new Date(shift.plannedStartTime); if (isValid(parsedStart)) { - startTime = format(parsedStart, "HH:mm"); + startTime = parsedStart.toLocaleTimeString("it-IT", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "Europe/Rome" + }); } } if (shift.plannedEndTime) { - const parsedEnd = parseISO(shift.plannedEndTime); + const parsedEnd = new Date(shift.plannedEndTime); if (isValid(parsedEnd)) { - endTime = format(parsedEnd, "HH:mm"); + endTime = parsedEnd.toLocaleTimeString("it-IT", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "Europe/Rome" + }); } } diff --git a/client/src/pages/site-planning-view.tsx b/client/src/pages/site-planning-view.tsx index d93ba15..4f728f5 100644 --- a/client/src/pages/site-planning-view.tsx +++ b/client/src/pages/site-planning-view.tsx @@ -201,21 +201,31 @@ export default function SitePlanningView() { ) : ( dayGuards.map((guard, index) => { - // Parsing sicuro orari + // Parsing sicuro orari (DB in UTC → visualizza in Europe/Rome) let startTime = "N/A"; let endTime = "N/A"; if (guard.plannedStartTime) { - const parsedStart = parseISO(guard.plannedStartTime); + const parsedStart = new Date(guard.plannedStartTime); if (isValid(parsedStart)) { - startTime = format(parsedStart, "HH:mm"); + startTime = parsedStart.toLocaleTimeString("it-IT", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "Europe/Rome" + }); } } if (guard.plannedEndTime) { - const parsedEnd = parseISO(guard.plannedEndTime); + const parsedEnd = new Date(guard.plannedEndTime); if (isValid(parsedEnd)) { - endTime = format(parsedEnd, "HH:mm"); + endTime = parsedEnd.toLocaleTimeString("it-IT", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "Europe/Rome" + }); } } diff --git a/server/routes.ts b/server/routes.ts index 039af21..eea961b 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -11,28 +11,62 @@ import { z } from "zod"; import { fromZodError } from "zod-validation-error"; /** - * Calcola l'offset di Europe/Rome rispetto a UTC per una data specifica. + * Calcola l'offset di Europe/Rome rispetto a UTC per una specifica data/ora. + * Gestisce correttamente orari serali e transizioni DST. * Italy: UTC+1 (ora solare inverno) / UTC+2 (ora legale estate) - * L'ora legale va dall'ultima domenica di marzo all'ultima domenica di ottobre + * @param year, month (0-11), day, hour, minute * @returns offset in ore (1 o 2) */ -function getItalyTimezoneOffsetHours(date: Date): number { - const year = date.getFullYear(); +function getItalyTimezoneOffsetHours(year: number, month: number, day: number, hour: number, minute: number = 0): number { + // Crea timestamp UTC esatto per l'input Italy time + // Useremo formatToParts per ottenere tutti i componenti date/time + const utcTimestamp = Date.UTC(year, month, day, hour, minute, 0); + const utcDate = new Date(utcTimestamp); - // Calcola ultima domenica di marzo (inizio ora legale) - const marchLastSunday = new Date(year, 2, 31); // 31 marzo - marchLastSunday.setDate(31 - marchLastSunday.getDay()); // torna indietro alla domenica + // Ottieni tutti i componenti in Europe/Rome timezone + const formatter = new Intl.DateTimeFormat('en-US', { + timeZone: 'Europe/Rome', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false + }); - // Calcola ultima domenica di ottobre (fine ora legale) - const octoberLastSunday = new Date(year, 9, 31); // 31 ottobre - octoberLastSunday.setDate(31 - octoberLastSunday.getDay()); // torna indietro alla domenica + const parts = formatter.formatToParts(utcDate); + const italyYear = parseInt(parts.find(p => p.type === 'year')?.value || '0'); + const italyMonth = parseInt(parts.find(p => p.type === 'month')?.value || '0') - 1; + const italyDay = parseInt(parts.find(p => p.type === 'day')?.value || '0'); + let italyHour = parseInt(parts.find(p => p.type === 'hour')?.value || '0'); + const italyMinute = parseInt(parts.find(p => p.type === 'minute')?.value || '0'); - // L'ora legale inizia alle 02:00 UTC dell'ultima domenica di marzo - // e termina alle 03:00 UTC dell'ultima domenica di ottobre - const isDST = date >= marchLastSunday && date < octoberLastSunday; + // formatToParts restituisce hour=24 per mezzanotte (00:00 del giorno successivo) + // Il giorno è già stato incrementato automaticamente da formatToParts + // Normalizziamo solo l'ora: 24:00 → 00:00 + const normalizedHour = italyHour === 24 ? 0 : italyHour; - // UTC+1 (inverno) o UTC+2 (estate) - return isDST ? 2 : 1; + // Crea timestamp Italy come se fosse UTC (per calcolare differenza) + const italyAsUtcTimestamp = Date.UTC(italyYear, italyMonth, italyDay, normalizedHour, italyMinute, 0); + + // Calcola differenza in millisecondi e converti in ore + const offsetMs = italyAsUtcTimestamp - utcTimestamp; + const offsetHours = Math.round(offsetMs / (1000 * 60 * 60)); + + console.log("🕐 Offset calculation:", { + input: `${year}-${String(month+1).padStart(2,'0')}-${String(day).padStart(2,'0')} ${String(hour).padStart(2,'0')}:${String(minute).padStart(2,'0')}`, + utcTimestamp: utcDate.toISOString(), + italyComponents: { year: italyYear, month: italyMonth+1, day: italyDay, hour: italyHour, minute: italyMinute }, + offsetHours + }); + + // Italy è sempre UTC+1 o UTC+2 + if (offsetHours !== 1 && offsetHours !== 2) { + console.error("⚠️ Unexpected offset:", offsetHours, "- defaulting to UTC+1"); + return 1; + } + + return offsetHours; } // Determina quale sistema auth usare basandosi sull'ambiente @@ -1348,7 +1382,7 @@ export async function registerRoutes(app: Express): Promise { // CRITICAL: Gli orari dal frontend sono in fuso orario Europe/Rome (UTC+1 o UTC+2) // Calcola offset corretto per convertire a UTC - const italyOffsetHours = getItalyTimezoneOffsetHours(new Date(year, month - 1, day)); + const italyOffsetHours = getItalyTimezoneOffsetHours(year, month - 1, day, hours, minutes); console.log("🕐 DEBUG Timezone setup:", { inputDate: date, inputTime: `${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}`,