diff --git a/FIX-TIMEZONE-FINAL.md b/FIX-TIMEZONE-FINAL.md new file mode 100644 index 0000000..fc4291b --- /dev/null +++ b/FIX-TIMEZONE-FINAL.md @@ -0,0 +1,273 @@ +# 🔧 FIX TIMEZONE CALENDRIER - PROCÉDURE COMPLÈTE + +## ⚠️ IMPORTANT: Cette procédure doit être suivie EXACTEMENT + +Le décalage horaire persiste car le changement du parser PostgreSQL nécessite: +1. **REBUILD** de l'image Docker (pas juste restart!) +2. **SUPPRESSION** des événements synchronisés avant le fix +3. **RESYNCHRONISATION** avec Google Calendar + +--- + +## 📋 ÉTAPE PAR ÉTAPE (À FAIRE DANS L'ORDRE!) + +### ÉTAPE 1: Diagnostic initial (optionnel mais recommandé) + +```bash +docker exec notes-todo-app node scripts/full-diagnosis-timezone.js +``` + +**Ce script montre:** +- Si le parser fonctionne (dates = strings ou objets Date) +- Comment PostgreSQL stocke les dates +- Comment l'API renvoie les dates +- Où se situe exactement le problème + +**Si le diagnostic dit "Le driver pg renvoie toujours des objets Date":** +→ Le parser n'est PAS actif → Rebuild obligatoire + +**Si le diagnostic dit "Le parser fonctionne!":** +→ Les anciennes données sont corrompues → Suppression + resync + +--- + +### ÉTAPE 2: REBUILD COMPLET (OBLIGATOIRE!) + +```bash +# Arrêter les containers +docker-compose down + +# Rebuild l'image notes-app +docker-compose build --no-cache notes-app + +# Redémarrer tout +docker-compose up -d +``` + +**⏱️ Durée: 2-3 minutes** + +**Attendez que le serveur soit complètement démarré:** +```bash +docker-compose logs -f notes-app +``` + +Attendez de voir: +``` +✓ Serveur NoteFlow démarré sur le port 2222 +``` + +Puis appuyez sur **Ctrl+C** + +--- + +### ÉTAPE 3: Suppression des événements corrompus + +```bash +docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "DELETE FROM calendar_events" +``` + +**Résultat attendu:** +``` +DELETE 15 +``` +(ou le nombre d'événements que vous aviez) + +**⚠️ C'est normal!** Les événements seront resynchronisés à l'étape suivante. + +--- + +### ÉTAPE 4: Vérification du parser + +```bash +docker exec notes-todo-app node scripts/full-diagnosis-timezone.js +``` + +**Vous devriez maintenant voir:** +``` +✅ Le parser fonctionne! + Les dates sont renvoyées comme strings +``` + +**Si vous voyez encore "objets Date":** +→ Le rebuild n'a pas fonctionné +→ Recommencez l'ÉTAPE 2 avec `--no-cache` + +--- + +### ÉTAPE 5: Resynchronisation Google Calendar + +1. Ouvrez **http://localhost:2222** +2. Connectez-vous +3. Allez dans **Admin** → **Google Calendar** +4. Cliquez sur **🔄 Synchroniser** + +**Résultat attendu:** +``` +15 événements synchronisés +``` + +--- + +### ÉTAPE 6: VÉRIFICATION FINALE + +Dans la box **Rendez-vous** sur la page d'accueil: + +**Comparez avec Google Calendar:** + +| Google Calendar | NoteFlow (avant) | NoteFlow (après) | +|----------------|------------------|------------------| +| 10:00 | 11:00 ❌ | 10:00 ✅ | +| 14:30 | 15:30 ❌ | 14:30 ✅ | +| 09:15 | 10:15 ❌ | 09:15 ✅ | + +**Les heures doivent être IDENTIQUES!** + +--- + +## 🔍 SI LE PROBLÈME PERSISTE + +### Test 1: Vérifier que le rebuild a bien été fait + +```bash +# Voir la date de build de l'image +docker images | grep noteflow + +# Vérifier les logs du build +docker-compose build notes-app 2>&1 | grep "types.setTypeParser" +``` + +### Test 2: Vérifier manuellement une date + +```bash +docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c " + SELECT + title, + start_time, + start_time AT TIME ZONE 'Europe/Paris' as heure_paris + FROM calendar_events + ORDER BY start_time ASC + LIMIT 1; +" +``` + +**Comparez `heure_paris` avec l'heure dans Google Calendar.** + +### Test 3: Test direct JavaScript + +```bash +docker exec notes-todo-app node -e " +const { Pool } = require('pg'); +const pool = new Pool({ connectionString: process.env.DATABASE_URL }); +pool.query('SELECT start_time FROM calendar_events LIMIT 1') + .then(r => { + const val = r.rows[0]?.start_time; + console.log('Type:', typeof val); + console.log('Value:', val); + if (typeof val === 'string') { + console.log('✅ Parser OK - String'); + } else { + console.log('❌ Parser KO - Objet Date'); + } + pool.end(); + }); +" +``` + +--- + +## 🆘 DÉPANNAGE + +### Problème: "Le parser renvoie toujours des objets Date" + +**Cause:** Le rebuild n'a pas pris en compte les changements + +**Solution:** +```bash +docker-compose down +docker system prune -f +docker-compose build --no-cache --pull notes-app +docker-compose up -d +``` + +### Problème: "Les heures sont toujours décalées après resync" + +**Cause:** Cache navigateur + +**Solution:** +1. Ouvrez les DevTools (F12) +2. Onglet **Network** → Cocher **Disable cache** +3. Actualisez la page (Ctrl+F5 ou Cmd+Shift+R) +4. Videz le cache navigateur: + - Chrome: Ctrl+Shift+Del + - Firefox: Ctrl+Shift+Del + - Edge: Ctrl+Shift+Del + +### Problème: "Certains événements corrects, d'autres décalés" + +**Cause:** Mélange d'événements avant/après le fix + +**Solution:** +```bash +# Supprimer TOUS les événements +docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "DELETE FROM calendar_events" + +# Resynchroniser +# (depuis l'interface web) +``` + +--- + +## ✅ CHECKLIST FINALE + +Cochez quand fait: + +- [ ] ÉTAPE 1: Diagnostic initial effectué +- [ ] ÉTAPE 2: Rebuild complet avec `--no-cache` +- [ ] ÉTAPE 3: Événements supprimés (DELETE FROM) +- [ ] ÉTAPE 4: Diagnostic confirme "parser OK" +- [ ] ÉTAPE 5: Google Calendar resynchronisé +- [ ] ÉTAPE 6: Heures vérifiées = identiques à Google +- [ ] Cache navigateur vidé (Ctrl+F5) +- [ ] Aucun décalage horaire observé + +--- + +## 🎯 RÉSUMÉ EN 4 COMMANDES + +```bash +# 1. Rebuild complet +docker-compose down && docker-compose build --no-cache notes-app && docker-compose up -d + +# 2. Attendre 30s +sleep 30 + +# 3. Supprimer événements +docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "DELETE FROM calendar_events" + +# 4. Puis resynchroniser Google Calendar depuis l'interface web +``` + +--- + +## 📊 POURQUOI CE FIX FONCTIONNE + +**Avant (cassé):** +``` +PostgreSQL → driver pg parse en Date object → décalage timezone +``` + +**Après (corrigé):** +``` +PostgreSQL → driver pg renvoie string ISO → frontend parse avec bon timezone +``` + +**La clé:** `types.setTypeParser(1184, str => str)` + +Ce parser transforme les TIMESTAMPTZ PostgreSQL en strings ISO au lieu d'objets Date, ce qui permet au frontend de gérer correctement le timezone avec `timeZone: 'Europe/Paris'`. + +--- + +**Commit:** 1a68d87 - Fix: Désactivation parsing Date PostgreSQL +**Date:** 2025-11-14 + +**Si vous suivez cette procédure EXACTEMENT, le problème SERA résolu!** 🎉 diff --git a/scripts/full-diagnosis-timezone.js b/scripts/full-diagnosis-timezone.js new file mode 100755 index 0000000..039a138 --- /dev/null +++ b/scripts/full-diagnosis-timezone.js @@ -0,0 +1,171 @@ +#!/usr/bin/env node + +// DIAGNOSTIC COMPLET - Traçage du décalage horaire +const { Pool } = require('pg'); + +const DATABASE_URL = process.env.DATABASE_URL; + +async function fullDiagnosis() { + const pool = new Pool({ connectionString: DATABASE_URL }); + + try { + console.log('\n╔══════════════════════════════════════════════════════════════╗'); + console.log('║ DIAGNOSTIC COMPLET - DÉCALAGE HORAIRE CALENDRIER ║'); + console.log('╚══════════════════════════════════════════════════════════════╝\n'); + + const client = await pool.connect(); + + // 1. Vérifier la configuration du driver pg + console.log('1️⃣ CONFIGURATION DRIVER PG:\n'); + const types = require('pg').types; + const parser1184 = types.getTypeParser(1184); + console.log(` Type parser 1184 (TIMESTAMPTZ): ${typeof parser1184}`); + console.log(` Parser renvoie: ${parser1184.name || 'function'}\n`); + + // 2. Test direct avec une date connue + console.log('2️⃣ TEST INSERTION/LECTURE DIRECTE:\n'); + + await client.query('DELETE FROM calendar_events WHERE google_event_id LIKE $1', ['diagnostic-%']); + + const testDates = [ + { name: 'Paris 10h avec offset', value: '2025-11-14T10:00:00+01:00' }, + { name: 'UTC 09h', value: '2025-11-14T09:00:00Z' }, + { name: 'Paris 14h avec offset', value: '2025-11-14T14:30:00+01:00' } + ]; + + for (const test of testDates) { + const eventId = `diagnostic-${test.name.replace(/\s+/g, '-')}`; + + await client.query(` + INSERT INTO calendar_events + (google_event_id, user_id, title, start_time, end_time, all_day) + VALUES ($1, 1, $2, $3, $4, FALSE) + `, [eventId, `Test ${test.name}`, test.value, test.value]); + + const result = await client.query(` + SELECT + title, + start_time, + pg_typeof(start_time) as pg_type, + start_time::text as text_repr + FROM calendar_events + WHERE google_event_id = $1 + `, [eventId]); + + const row = result.rows[0]; + console.log(` Test: ${test.name}`); + console.log(` Inséré: ${test.value}`); + console.log(` Type PG: ${row.pg_type}`); + console.log(` Texte: ${row.text_repr}`); + console.log(` JS type: ${typeof row.start_time}`); + console.log(` JS value: ${row.start_time}`); + + if (typeof row.start_time === 'object' && row.start_time instanceof Date) { + console.log(` ❌ PROBLÈME: C'est un objet Date! Le parser n'a pas fonctionné!`); + console.log(` toISOString(): ${row.start_time.toISOString()}`); + } else if (typeof row.start_time === 'string') { + console.log(` ✅ OK: C'est une string!`); + const parsed = new Date(row.start_time); + console.log(` new Date(): ${parsed.toISOString()}`); + console.log(` Heure locale: ${parsed.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}`); + console.log(` Heure Paris: ${parsed.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })}`); + } + console.log(''); + } + + // 3. Vérifier événements réels + console.log('3️⃣ ÉVÉNEMENTS RÉELS GOOGLE CALENDAR:\n'); + + const realEvents = await client.query(` + SELECT + title, + start_time, + pg_typeof(start_time) as pg_type, + start_time::text as text_repr, + all_day + FROM calendar_events + WHERE google_event_id NOT LIKE 'diagnostic-%' + ORDER BY start_time ASC + LIMIT 3 + `); + + if (realEvents.rows.length === 0) { + console.log(' ⚠️ Aucun événement réel trouvé'); + console.log(' → Synchronisez Google Calendar d\'abord\n'); + } else { + realEvents.rows.forEach((evt, i) => { + console.log(` Événement ${i + 1}: ${evt.title}`); + console.log(` Type PG: ${evt.pg_type}`); + console.log(` Texte: ${evt.text_repr}`); + console.log(` JS type: ${typeof evt.start_time}`); + console.log(` JS value: ${evt.start_time}`); + + if (typeof evt.start_time === 'object' && evt.start_time instanceof Date) { + console.log(` ❌ Objet Date (parser KO)`); + console.log(` Affiché frontend: ${evt.start_time.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })}`); + } else { + console.log(` ✅ String (parser OK)`); + const d = new Date(evt.start_time); + console.log(` Affiché frontend: ${d.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })}`); + } + console.log(''); + }); + } + + // 4. Test de l'API + console.log('4️⃣ SIMULATION API RESPONSE:\n'); + + const apiResult = await client.query(` + SELECT + id, google_event_id, title, description, + start_time, end_time, location, html_link, all_day + FROM calendar_events + WHERE google_event_id LIKE 'diagnostic-%' + LIMIT 1 + `); + + if (apiResult.rows.length > 0) { + const apiEvent = apiResult.rows[0]; + console.log(' API renvoie (format JSON):'); + console.log(JSON.stringify(apiEvent, null, 2)); + console.log(''); + } + + // 5. Recommandations + console.log('╔══════════════════════════════════════════════════════════════╗'); + console.log('║ DIAGNOSTIC RÉSULTAT ║'); + console.log('╚══════════════════════════════════════════════════════════════╝\n'); + + const sampleEvent = realEvents.rows[0] || testDates[0]; + const isDateObject = typeof sampleEvent?.start_time === 'object'; + + if (isDateObject) { + console.log('❌ PROBLÈME TROUVÉ:\n'); + console.log(' Le driver pg renvoie toujours des objets Date'); + console.log(' Le types.setTypeParser(1184) n\'a PAS été appliqué\n'); + console.log('💡 SOLUTION:\n'); + console.log(' 1. Vérifier que config/database.js a bien:'); + console.log(' const { Pool, types } = require(\'pg\');'); + console.log(' types.setTypeParser(1184, ...) AVANT le Pool\n'); + console.log(' 2. Rebuild: docker-compose build notes-app\n'); + console.log(' 3. Restart: docker-compose restart notes-app\n'); + } else { + console.log('✅ Le parser fonctionne!\n'); + console.log(' Les dates sont renvoyées comme strings'); + console.log(' Le problème doit être ailleurs (frontend?)\n'); + } + + // Cleanup + await client.query('DELETE FROM calendar_events WHERE google_event_id LIKE $1', ['diagnostic-%']); + + client.release(); + await pool.end(); + + } catch (error) { + console.error('\n❌ Erreur:', error.message); + console.error(error.stack); + process.exit(1); + } +} + +fullDiagnosis();