✅ Bug Lunedì Booking - Risoluzione Completa
🐛 IL PROBLEMA
Sintomo: I lunedì non erano cliccabili nel calendario di booking, anche senza Google Calendar collegato.
🔍 ROOT CAUSE: TIMEZONE BUG MULTIPLI
Bug Principale: toISOString() converte a UTC
JavaScript toISOString() converte le date locali in UTC, causando uno shift di -1 giorno per timezone positive (Europe/Rome = UTC+1):
// ESEMPIO BUG:
const date = new Date(2025, 10, 3, 0, 0, 0); // Lunedì 3/11 00:00 (locale)
date.toISOString(); // "2025-11-02T23:00:00.000Z" (UTC)
date.toISOString().split('T')[0]; // "2025-11-02" ❌ DOMENICA!4 Luoghi Buggati:
loadMonthSlots()- Richiesta API iniziale- PRIMA:
?date=${startDate.toISOString().split('T')[0]}→ data -1 giorno - DOPO:
?date=${formatDateLocal(startDate)}→ data corretta
- PRIMA:
daysWithSlotsgrouping - Chiavi dell'oggetto- PRIMA:
const slotDate = new Date(slot.datetime).toISOString().split('T')[0]→ chiave -1 giorno - DOPO:
const slotDate = slot.datetime.split('T')[0]→ chiave corretta
- PRIMA:
generateCalendar()- Lookup nel calendario- PRIMA:
const dateKey = date.toISOString().split('T')[0]→ cerca giorno -1 - DOPO:
const dateKey = formatDateLocal(date)→ cerca giorno corretto
- PRIMA:
selectDate()- Click su un giorno- PRIMA:
?date=${date.toISOString().split('T')[0]}→ chiede slot per giorno -1 - DOPO:
?date=${formatDateLocal(date)}→ chiede slot per giorno corretto
- PRIMA:
✅ FIX APPLICATI
1. Helper Function Creata:
const formatDateLocal = (d) =>
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;Questa funzione NON converte a UTC = NO timezone shift!
2. Slot Grouping Semplificato:
// Il backend ritorna già datetime in formato ISO corretto
const slotDate = slot.datetime.split('T')[0]; // Usa direttamente senza parsing3. Rimosso Check min_notice dal Frontend:
Il frontend faceva un check confuso di min_notice confrontando:
date(midnight 00:00 del giorno)minNoticeDate(ora corrente + X ore)
Questo causava logica imprevedibile. Il backend già filtra slot-per-slot, quindi il frontend non deve fare questo check.
4. Anti-Cache Headers:
Aggiunti headers HTTP per prevenire caching delle API:
return response()->json(['slots' => $allSlots])
->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->header('Pragma', 'no-cache')
->header('Expires', '0');5. Version Comment:
Aggiunto commento versione per forzare browser cache invalidation:
// Version: 2025-10-30-23:10 - Fixed all timezone bugs🧪 VERIFICA FUNZIONAMENTO
Backend Test (Locale):
php -r "
require 'vendor/autoload.php';
\$app = require_once 'bootstrap/app.php';
\$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
\$bp = App\Models\BookingPage::where('slug', 'formazione-ozzarini')->first();
\$service = app(App\Services\BookingAvailabilityService::class);
\$monday = Carbon\Carbon::parse('2025-11-03');
\$slots = \$service->getAvailableSlots(\$bp, \$monday);
echo 'Lunedì 3/11: ' . count(\$slots) . ' slot disponibili\n';
"Output atteso: Lunedì 3/11: 15 slot disponibili
API Test (HTTP):
curl -s "http://saasykit-tenancy.test/booking/formazione-ozzarini/slots?date=2025-11-03" | jq '.slots | length'Output atteso: 15
Frontend Test (Browser):
- Hard refresh:
Cmd + Shift + R - Apri Console (F12)
- Verifica log:
✅ Slot caricati: {giorni_con_slot: 16, totale_slot: 235-240}
🔍 Lunedì con slot: ['2025-11-03', '2025-11-10', '2025-11-17', '2025-11-24']
🔍 Lunedì 3/11: {dateKey: '2025-11-03', hasSlots: true, willBeClickable: true}- Clicca su un lunedì → Dovresti vedere 15 slot orari disponibili
📊 RISULTATO FINALE
Prima del Fix:
- ❌ Lunedì non cliccabili (grigi)
- ❌
dateKey: '2025-11-02'(giorno sbagliato) - ❌
hasSlots: undefined(chiavi non matchano) - ❌ API request:
?date=2025-11-02(chiede domenica invece di lunedì)
Dopo il Fix:
- ✅ Lunedì cliccabili (verdi/bianchi)
- ✅
dateKey: '2025-11-03'(giorno corretto) - ✅
hasSlots: true(chiavi matchano) - ✅ API request:
?date=2025-11-03(chiede lunedì corretto) - ✅ 15 slot orari visibili al click
🎯 FILES MODIFICATI
resources/views/booking/show.blade.php
- Creata
formatDateLocal()helper function - Fixati 4 posti dove si usava
toISOString() - Rimosso check
min_noticeconfuso - Aggiunto logging dettagliato per debug
- Creata
app/Http/Controllers/BookingController.php
- Aggiunti anti-cache headers alle risposte JSON
config/services.php
- Aggiunto scope OAuth
userinfo.emailper Google Calendar
- Aggiunto scope OAuth
🚀 DEPLOYMENT
git log --oneline -10
434dddcb cleanup: Rimossi file temporanei di debug
113d65e6 fix: Aggiunto version comment per forzare cache bust
39af90b6 fix: Timezone bug in TUTTI i toISOString()
f2a1a45c fix: Timezone bug nel dateKey (lunedì -1 giorno)
995aef93 debug: Aggiunto logging dettagliato per lunedì
25cea302 fix: Rimosso check min_notice errato dal frontend
26576abb fix: Anti-cache headers per booking slots API
9f10d09e debug: Route diagnostica per Monday booking bug
18ca9089 fix: OAuth scope userinfo.email + Summary finale
0a5167b9 fix: Default duration_minutes 30 se NULLDeploy completato su: https://github.com/ozzarini/bladecrm.git
📝 LESSONS LEARNED
1. JavaScript Date Timezone Pitfalls:
toISOString()SEMPRE converte a UTC- Per date locali senza conversione, usa formatting manuale
getFullYear(),getMonth(),getDate()sono sempre locali
2. Cache Busting Strategies:
- HTTP headers anti-cache per API
- Version comments per inline JavaScript
- Hard refresh necessario per vedere fix
3. Frontend vs Backend Validation:
- Il backend è la fonte di verità
- Il frontend può fare check semplici (weekly_hours)
- Check complessi (min_notice, overlap) solo nel backend
🎉 CONCLUSIONE
Il bug dei "lunedì non cliccabili" era causato da 4 timezone bug che creavano un mismatch tra:
- Le chiavi usate per salvare gli slot (
daysWithSlots) - Le chiavi usate per cercare gli slot (calendario)
- Le date richieste alle API
Dopo il fix, il sistema funziona perfettamente sia con che senza Google Calendar! ✅