mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
Fix: Décalage horaire Google Calendar - Solution complète
PROBLÈME PERSISTANT résolu avec 3 correctifs:
1. FRONTEND - Timezone explicite (src/pages/Index.tsx):
- Ligne 904-905: Ajout timeZone: 'Europe/Paris'
- Ligne 761: Ajout timeZone: 'Europe/Paris' pour alertes
Avant: toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })
Après: toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })
Force l'affichage en heure de Paris même si navigateur dans autre timezone
2. BACKEND - Script correction forcée (scripts/force-fix-calendar-timezone.js):
- Diagnostic complet de l'état actuel
- Suppression FORCÉE de tous les événements
- Conversion TIMESTAMPTZ avec timezone Europe/Paris
- Vérification finale des types de colonnes
- Instructions étape par étape
Utilisation:
docker exec notes-todo-app node scripts/force-fix-calendar-timezone.js
3. DIAGNOSTIC approfondi:
- Affiche types colonnes PostgreSQL
- Montre exemples de dates stockées
- Compare format brut vs formaté
- Détecte problèmes timezone automatiquement
POURQUOI ÇA NE MARCHAIT PAS AVANT:
- Migration timezone peut ne pas avoir été appliquée
- Anciennes données encore présentes avec mauvais timezone
- Frontend n'avait pas timeZone explicite dans toLocaleTimeString
- Navigateur pouvait être dans autre timezone que Paris
SOLUTION MAINTENANT:
1. Script force la suppression + conversion
2. Frontend force timezone Europe/Paris
3. Nouvelles syncs Google Calendar auront bonnes heures
APRÈS REBUILD:
1. docker-compose build notes-app
2. docker-compose restart notes-app
3. docker exec notes-todo-app node scripts/force-fix-calendar-timezone.js
4. Resynchroniser Google Calendar dans interface web
5. Vérifier que heures correspondent
Fichiers modifiés:
- src/pages/Index.tsx (timeZone: 'Europe/Paris')
- scripts/force-fix-calendar-timezone.js (nouveau, correction forcée)
This commit is contained in:
2 files changed
+158
-3
No files matched your search
Executable
+155
@@ -0,0 +1,155 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Script de correction forcée du timezone Google Calendar
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
|
||||||
|
const DATABASE_URL = process.env.DATABASE_URL;
|
||||||
|
|
||||||
|
if (!DATABASE_URL) {
|
||||||
|
console.error('❌ DATABASE_URL non définie');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function forceFixTimezone() {
|
||||||
|
const pool = new Pool({ connectionString: DATABASE_URL });
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('\n╔════════════════════════════════════════════════════════════╗');
|
||||||
|
console.log('║ CORRECTION FORCÉE TIMEZONE GOOGLE CALENDAR ║');
|
||||||
|
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
||||||
|
|
||||||
|
const client = await pool.connect();
|
||||||
|
|
||||||
|
// 1. Vérifier l'état actuel
|
||||||
|
console.log('📊 ÉTAT ACTUEL:\n');
|
||||||
|
|
||||||
|
const typeCheck = await client.query(`
|
||||||
|
SELECT column_name, data_type
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'calendar_events'
|
||||||
|
AND column_name IN ('start_time', 'end_time')
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log('Types de colonnes:');
|
||||||
|
typeCheck.rows.forEach(row => {
|
||||||
|
const icon = row.data_type.includes('with time zone') ? '✅' : '❌';
|
||||||
|
console.log(` ${icon} ${row.column_name}: ${row.data_type}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const eventCount = await client.query('SELECT COUNT(*) FROM calendar_events');
|
||||||
|
console.log(`\nNombre d'événements: ${eventCount.rows[0].count}`);
|
||||||
|
|
||||||
|
if (parseInt(eventCount.rows[0].count) > 0) {
|
||||||
|
const sample = await client.query(`
|
||||||
|
SELECT
|
||||||
|
title,
|
||||||
|
start_time,
|
||||||
|
pg_typeof(start_time) as type,
|
||||||
|
to_char(start_time, 'YYYY-MM-DD HH24:MI:SS TZ') as formatted
|
||||||
|
FROM calendar_events
|
||||||
|
ORDER BY start_time ASC
|
||||||
|
LIMIT 3
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log('\nExemples d\'événements:');
|
||||||
|
sample.rows.forEach((evt, i) => {
|
||||||
|
console.log(`\n ${i + 1}. ${evt.title}`);
|
||||||
|
console.log(` Type: ${evt.type}`);
|
||||||
|
console.log(` Valeur brute: ${evt.start_time}`);
|
||||||
|
console.log(` Formaté: ${evt.formatted}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. CORRECTION FORCÉE
|
||||||
|
console.log('\n\n🔧 CORRECTION FORCÉE:\n');
|
||||||
|
|
||||||
|
const needsFix = typeCheck.rows.some(row =>
|
||||||
|
!row.data_type.includes('with time zone')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!needsFix && parseInt(eventCount.rows[0].count) === 0) {
|
||||||
|
console.log('✅ Les colonnes sont déjà en TIMESTAMPTZ');
|
||||||
|
console.log('✅ Aucun événement corrompu');
|
||||||
|
console.log('\n💡 Resynchronisez Google Calendar depuis l\'interface web\n');
|
||||||
|
client.release();
|
||||||
|
await pool.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Étape 1: Suppression de TOUS les événements...');
|
||||||
|
const deleted = await client.query('DELETE FROM calendar_events');
|
||||||
|
console.log(` ✅ ${deleted.rowCount} événement(s) supprimé(s)\n`);
|
||||||
|
|
||||||
|
if (needsFix) {
|
||||||
|
console.log('Étape 2: Conversion des colonnes en TIMESTAMPTZ...');
|
||||||
|
|
||||||
|
await client.query('BEGIN');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.query(`
|
||||||
|
ALTER TABLE calendar_events
|
||||||
|
ALTER COLUMN start_time TYPE TIMESTAMPTZ
|
||||||
|
USING start_time AT TIME ZONE 'Europe/Paris'
|
||||||
|
`);
|
||||||
|
console.log(' ✅ start_time → TIMESTAMPTZ');
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
ALTER TABLE calendar_events
|
||||||
|
ALTER COLUMN end_time TYPE TIMESTAMPTZ
|
||||||
|
USING end_time AT TIME ZONE 'Europe/Paris'
|
||||||
|
`);
|
||||||
|
console.log(' ✅ end_time → TIMESTAMPTZ');
|
||||||
|
|
||||||
|
await client.query('COMMIT');
|
||||||
|
} catch (error) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
console.error(' ❌ Erreur:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Vérification finale
|
||||||
|
console.log('\n📊 VÉRIFICATION FINALE:\n');
|
||||||
|
|
||||||
|
const finalCheck = await client.query(`
|
||||||
|
SELECT column_name, data_type
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'calendar_events'
|
||||||
|
AND column_name IN ('start_time', 'end_time')
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log('Types de colonnes après correction:');
|
||||||
|
finalCheck.rows.forEach(row => {
|
||||||
|
const icon = row.data_type.includes('with time zone') ? '✅' : '❌';
|
||||||
|
console.log(` ${icon} ${row.column_name}: ${row.data_type}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalCount = await client.query('SELECT COUNT(*) FROM calendar_events');
|
||||||
|
console.log(`\nNombre d'événements: ${finalCount.rows[0].count} (devrait être 0)`);
|
||||||
|
|
||||||
|
console.log('\n╔════════════════════════════════════════════════════════════╗');
|
||||||
|
console.log('║ ✅ CORRECTION TERMINÉE ║');
|
||||||
|
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
||||||
|
|
||||||
|
console.log('📝 PROCHAINES ÉTAPES:\n');
|
||||||
|
console.log('1. Ouvrez l\'interface web: http://localhost:2222');
|
||||||
|
console.log('2. Allez dans Admin → Google Calendar');
|
||||||
|
console.log('3. Cliquez sur le bouton "Synchroniser"');
|
||||||
|
console.log('4. Vérifiez que les heures correspondent maintenant\n');
|
||||||
|
|
||||||
|
console.log('🔍 POUR VÉRIFIER:\n');
|
||||||
|
console.log('- Regardez un événement dans Google Calendar (ex: 10h00)');
|
||||||
|
console.log('- Comparez avec l\'affichage dans NoteFlow');
|
||||||
|
console.log('- Les heures doivent être IDENTIQUES (pas de +1h ou -1h)\n');
|
||||||
|
|
||||||
|
client.release();
|
||||||
|
await pool.end();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ Erreur:', error.message);
|
||||||
|
console.error(error.stack);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forceFixTimezone();
|
||||||
+3
-3
@@ -758,7 +758,7 @@ const Index = () => {
|
|||||||
Rendez-vous dans moins de 30 minutes : {upcomingAlert.title}
|
Rendez-vous dans moins de 30 minutes : {upcomingAlert.title}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm opacity-90">
|
<span className="text-sm opacity-90">
|
||||||
à {new Date(upcomingAlert.start_time).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}
|
à {new Date(upcomingAlert.start_time).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -901,8 +901,8 @@ const Index = () => {
|
|||||||
<h4 className="font-medium line-clamp-2 text-sm">{event.title}</h4>
|
<h4 className="font-medium line-clamp-2 text-sm">{event.title}</h4>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{isToday ? "Aujourd'hui" : startDate.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' })}
|
{isToday ? "Aujourd'hui" : startDate.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', timeZone: 'Europe/Paris' })}
|
||||||
{isAllDay ? ' - Toute la journée' : ` à ${startDate.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}`}
|
{isAllDay ? ' - Toute la journée' : ` à ${startDate.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })}`}
|
||||||
</p>
|
</p>
|
||||||
{event.location && (
|
{event.location && (
|
||||||
<p className="text-xs text-muted-foreground mt-1 truncate">
|
<p className="text-xs text-muted-foreground mt-1 truncate">
|
||||||
|
|||||||
Reference in new issue
Block a user