Skip to content

✅ 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):

javascript
// 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:

  1. loadMonthSlots() - Richiesta API iniziale

    • PRIMA: ?date=${startDate.toISOString().split('T')[0]} → data -1 giorno
    • DOPO: ?date=${formatDateLocal(startDate)} → data corretta
  2. daysWithSlots grouping - 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
  3. generateCalendar() - Lookup nel calendario

    • PRIMA: const dateKey = date.toISOString().split('T')[0] → cerca giorno -1
    • DOPO: const dateKey = formatDateLocal(date) → cerca giorno corretto
  4. 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

✅ FIX APPLICATI

1. Helper Function Creata:

javascript
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:

javascript
// Il backend ritorna già datetime in formato ISO corretto
const slotDate = slot.datetime.split('T')[0]; // Usa direttamente senza parsing

3. 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:

php
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:

javascript
// Version: 2025-10-30-23:10 - Fixed all timezone bugs

🧪 VERIFICA FUNZIONAMENTO

Backend Test (Locale):

bash
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):

bash
curl -s "http://saasykit-tenancy.test/booking/formazione-ozzarini/slots?date=2025-11-03" | jq '.slots | length'

Output atteso: 15

Frontend Test (Browser):

  1. Hard refresh: Cmd + Shift + R
  2. Apri Console (F12)
  3. Verifica log:
javascript
✅ 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}
  1. 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

  1. resources/views/booking/show.blade.php

    • Creata formatDateLocal() helper function
    • Fixati 4 posti dove si usava toISOString()
    • Rimosso check min_notice confuso
    • Aggiunto logging dettagliato per debug
  2. app/Http/Controllers/BookingController.php

    • Aggiunti anti-cache headers alle risposte JSON
  3. config/services.php

    • Aggiunto scope OAuth userinfo.email per Google Calendar

🚀 DEPLOYMENT

bash
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 NULL

Deploy 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! ✅

Realizzato con ❤️ da DScom