From 503a70bd497ba2e687f01e8613b4bfa8ea59580d Mon Sep 17 00:00:00 2001 From: Ferrovia Date: Fri, 1 May 2026 21:47:27 +0200 Subject: [PATCH] Update station type handling and current time logic for quai display --- .env | 5 +- .../classiques/ClassiquesArrivees.js | 54 +++++++- .../classiques/ClassiquesDeparts.js | 97 +++++++++++--- .../quais/ClassiquesArriveesQuais.js | 126 +++++++++++++++++- .../quais/ClassiquesDepartsQuais.js | 126 +++++++++++++++++- 5 files changed, 368 insertions(+), 40 deletions(-) diff --git a/.env b/.env index 0b018df..a103574 100644 --- a/.env +++ b/.env @@ -1,3 +1,2 @@ - -REACT_APP_SUPABASE_URL=https://snkpkkewlpetcqhfxotm.supabase.co -REACT_APP_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNua3Bra2V3bHBldGNxaGZ4b3RtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAxMzYyMDEsImV4cCI6MjA4NTcxMjIwMX0.zLyNKXHSM2Kz58DFwTPySnhLjnSY_T3LtdYbXM2nVVY +REACT_APP_SUPABASE_URL=https://supabase-ferrovia.mr-patator.fr +REACT_APP_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzc1OTUxMjAxLCJleHAiOjE5MzM2MzEyMDF9.ZFuzZSHVOMX-poU1fhWTjZ2sn7LFz9NYATTwJknlSr0 diff --git a/src/pages/afficheurs/classiques/ClassiquesArrivees.js b/src/pages/afficheurs/classiques/ClassiquesArrivees.js index 89a8e56..9190aac 100644 --- a/src/pages/afficheurs/classiques/ClassiquesArrivees.js +++ b/src/pages/afficheurs/classiques/ClassiquesArrivees.js @@ -86,6 +86,15 @@ export default function ClassiquesArrivees(){ // État pour alterner l'affichage toutes les 3 secondes const [showStatus, setShowStatus] = useState(false); + // État pour l'heure actuelle, mise à jour chaque seconde pour la logique d'affichage du quai + const [currentTime, setCurrentTime] = useState(new Date()); + + // Met à jour l'heure actuelle chaque seconde + useEffect(() => { + const id = setInterval(() => setCurrentTime(new Date()), 1000); + return () => clearInterval(id); + }, []); + useEffect(() => { const id = setInterval(() => setShowStatus(s => !s), 3000); return () => clearInterval(id); @@ -186,9 +195,9 @@ export default function ClassiquesArrivees(){ // retourne le threshold (en minutes) pour afficher le quai selon le type de gare const thresholdForStationType = useCallback((type) => { if (!type) return 0; - const t = String(type).toLowerCase(); - if (t.includes('interurb') || t.includes('interurbaine')) return 360; // 6 heures - if (t.includes('ville')) return 30; // 30 minutes + const t = String(type).trim().toLowerCase(); + if (t === 'interurbaine' || t.includes('interurb')) return 360; // 6 heures + if (t === 'ville' || t.includes('ville')) return 30; // 30 minutes return 0; }, []); @@ -537,7 +546,41 @@ export default function ClassiquesArrivees(){ const i = weekdayIndex(d); switch(i) { case 1: return 'circule_lundi'; case 2: return 'circule_mardi'; case 3: return 'circule_mercredi'; case 4: return 'circule_jeudi'; case 5: return 'circule_vendredi'; case 6: return 'circule_samedi'; case 0: return 'circule_dimanche'; default: return null; } }; + const isPublicHoliday = (d) => { + const y = d.getFullYear(); + const m = d.getMonth() + 1; + const day = d.getDate(); + const s = `${y}-${String(m).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + const holidays = [ + `${y}-01-01`, // Jour de l'an + `${y}-05-01`, // Fête du Travail + `${y}-05-08`, // Victoire 1945 + `${y}-07-14`, // Fête nationale + `${y}-08-15`, // Assomption + `${y}-11-01`, // Toussaint + `${y}-11-11`, // Armistice 1918 + `${y}-12-25`, // Noël + ]; + // Dates variables pour 2026 + if (y === 2026) { + holidays.push('2026-04-06'); // Lundi de Pâques + holidays.push('2026-05-14'); // Ascension + holidays.push('2026-05-25'); // Lundi de Pentecôte + } + return holidays.includes(s); + }; + const circulatesOn = (h, d) => { + if (isPublicHoliday(d)) { + const v = h['circule_jours_feries']; + if (typeof v === 'boolean') return v; + if (typeof v === 'number') return v === 1; + if (typeof v === 'string') { + const s = v.trim().toLowerCase(); + return s === 'true' || s === '1' || s === 't' || s === 'y' || s === 'yes'; + } + return (typeof v === 'undefined' || v === null) ? false : Boolean(v); + } const col = weekdayNameForColumn(d); if (!col) return true; const v = h[col]; @@ -701,8 +744,7 @@ export default function ClassiquesArrivees(){ // Ne pas afficher le quai si le train est supprimé ou si aucun quai n'est défini let showPlatform = !(status && status.label === 'supprimé') && Boolean(platform); if (showPlatform) { - const now = new Date(); - const nowHM = now.getHours()*60 + now.getMinutes(); + const nowHM = currentTime.getHours()*60 + currentTime.getMinutes(); // Use currentTime const sched = (typeof horaire._arrHM === 'number') ? horaire._arrHM : parseTimeHM(horaire.heure_arrivee || horaire.arrival_time || horaire.time || horaire._rawArrTime || ''); const threshold = thresholdForStationType(stationType); if (typeof sched === 'number' && typeof threshold === 'number' && threshold > 0) { @@ -742,5 +784,3 @@ export default function ClassiquesArrivees(){ ); } - - diff --git a/src/pages/afficheurs/classiques/ClassiquesDeparts.js b/src/pages/afficheurs/classiques/ClassiquesDeparts.js index 0b586da..757ed55 100644 --- a/src/pages/afficheurs/classiques/ClassiquesDeparts.js +++ b/src/pages/afficheurs/classiques/ClassiquesDeparts.js @@ -1,3 +1,4 @@ + import React, { useEffect, useState, useCallback } from 'react'; import './ClassiquesDeparts.css'; import supabase from '../../../lib/supabase'; @@ -101,6 +102,14 @@ export default function ClassiquesDeparts(){ // État pour alterner l'affichage toutes les 3 secondes const [showStatus, setShowStatus] = useState(false); + // État pour l'heure actuelle, mise à jour chaque seconde pour la logique d'affichage du quai + const [currentTime, setCurrentTime] = useState(new Date()); + + // Met à jour l'heure actuelle chaque seconde + useEffect(() => { + const id = setInterval(() => setCurrentTime(new Date()), 1000); + return () => clearInterval(id); + }, []); // Données depuis Supabase const [horaires, setHoraires] = useState([]); @@ -245,9 +254,9 @@ export default function ClassiquesDeparts(){ // retourne le threshold (en minutes) pour afficher le quai selon le type de gare const thresholdForStationType = useCallback((type) => { if (!type) return 0; - const t = String(type).toLowerCase(); - if (t.includes('interurb') || t.includes('interurbaine')) return 360; // 6 heures - if (t.includes('ville')) return 30; // 30 minutes + const t = String(type).trim().toLowerCase(); + if (t === 'interurbaine' || t.includes('interurb')) return 360; // 6 heures + if (t === 'ville' || t.includes('ville')) return 30; // 30 minutes return 0; }, []); @@ -456,7 +465,6 @@ export default function ClassiquesDeparts(){ if (typeof base === 'number') displayTime = formatTimeFromHM(base + displayStatus.delayMinutes); } } - horaire._stopsStatus = stopsStatus; // prefer platform from stop data (quai) if available const stopForStation = (stops || []).find(s => { @@ -565,7 +573,9 @@ export default function ClassiquesDeparts(){ } catch (err) { /* ignore */ } } } - if (mounted) setStationType(fetchedType ? String(fetchedType).toLowerCase() : null); + if (mounted) { + setStationType(fetchedType ? String(fetchedType).toLowerCase() : null); + } } catch (e) { /* ignore errors reading gares table */ } // Filtrer par gare : garder uniquement les horaires qui DESSERVENT la gare ciblée // (soit comme gare_depart, soit comme un arrêt intermédiaire dans `stops` / `gares_desservies`) @@ -704,18 +714,65 @@ export default function ClassiquesDeparts(){ } }; - const circulatesOn = (h, d) => { - const col = weekdayNameForColumn(d); - if (!col) return true; - const v = h[col]; - if (typeof v === 'boolean') return v; - if (typeof v === 'number') return v === 1; - if (typeof v === 'string') { - const s = v.trim().toLowerCase(); - return s === 'true' || s === '1' || s === 't' || s === 'y' || s === 'yes'; + const isPublicHoliday = (d) => { + const y = d.getFullYear(); + const m = d.getMonth() + 1; + const day = d.getDate(); + const s = `${y}-${String(m).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + const holidays = [ + `${y}-01-01`, // Jour de l'an + `${y}-05-01`, // Fête du Travail + `${y}-05-08`, // Victoire 1945 + `${y}-07-14`, // Fête nationale + `${y}-08-15`, // Assomption + `${y}-11-01`, // Toussaint + `${y}-11-11`, // Armistice 1918 + `${y}-12-25`, // Noël + ]; + // Dates variables pour 2026 + if (y === 2026) { + holidays.push('2026-04-06'); // Lundi de Pâques + holidays.push('2026-05-14'); // Ascension + holidays.push('2026-05-25'); // Lundi de Pentecôte } - // si la colonne est undefined ou null, on considère que ça circule (fallback permissif) - return (typeof v === 'undefined' || v === null) ? true : Boolean(v); + return holidays.includes(s); + }; + + const parseCirculationFlag = (value, defaultValue) => { + if (value === null || typeof value === 'undefined') { + return defaultValue; + } + if (typeof value === 'boolean') { + return value; + } + if (typeof value === 'number') { + return value === 1; + } + if (typeof value === 'string') { + const s = value.trim().toLowerCase(); + if (['true', '1', 't', 'y', 'yes', 'oui'].includes(s)) { + return true; + } + if (['false', '0', 'f', 'n', 'no', 'non'].includes(s)) { + return false; + } + } + return defaultValue; + }; + + const circulatesOn = (h, d) => { + if (isPublicHoliday(d)) { + // On public holidays, circulation is determined SOLELY by `circule_jours_feries`. + // If the flag is true, it runs. Otherwise, it doesn't. + return parseCirculationFlag(h['circule_jours_feries'], false); + } + + // On a regular day, check the corresponding weekday column. + const col = weekdayNameForColumn(d); + if (!col) return true; // Should not happen, but permissive. + + // For regular weekdays, the default is to run unless specified otherwise. + return parseCirculationFlag(h[col], true); }; // Construire listes pour aujourd'hui et demain en tenant compte des colonnes circule_* @@ -952,6 +1009,9 @@ export default function ClassiquesDeparts(){ // prefer precomputed status and display time (synchronisé depuis la BDD) — sinon calculer à la volée const status = horaire._status || computeStatusForHoraireAtStop(horaire, stationId, stationName, buildPerturbMap(perturbations)); + if (status && status.label === 'supprimé' && showStatus) { + time = ''; + } // if the status is 'supprimé' and the component is currently showing the status (showStatus toggles every 3s), hide the time if (status && status.label === 'supprimé' && showStatus) { time = ''; @@ -978,8 +1038,7 @@ export default function ClassiquesDeparts(){ // Ne pas afficher le quai si le train est supprimé ou si aucun quai n'est défini let showPlatform = !(status && status.label === 'supprimé') && Boolean(platform); if (showPlatform) { - const now = new Date(); - const nowHM = now.getHours()*60 + now.getMinutes(); + const nowHM = currentTime.getHours()*60 + currentTime.getMinutes(); // Use currentTime const sched = (typeof horaire._depHM === 'number') ? horaire._depHM : parseTimeHM(horaire.heure_depart || horaire.time || horaire.heure || horaire._rawTime || ''); const threshold = thresholdForStationType(stationType); if (typeof sched === 'number' && typeof threshold === 'number' && threshold > 0) { @@ -1034,5 +1093,3 @@ export default function ClassiquesDeparts(){ ) } - - diff --git a/src/pages/afficheurs/classiques/quais/ClassiquesArriveesQuais.js b/src/pages/afficheurs/classiques/quais/ClassiquesArriveesQuais.js index 2922c33..24806bd 100644 --- a/src/pages/afficheurs/classiques/quais/ClassiquesArriveesQuais.js +++ b/src/pages/afficheurs/classiques/quais/ClassiquesArriveesQuais.js @@ -123,11 +123,82 @@ export default function ClassiquesArriveesQuais(){ const [loadingHoraire, setLoadingHoraire] = useState(false); const [horaireError, setHoraireError] = useState(null); const [candidatesCount, setCandidatesCount] = useState(0); + // type de la gare (récupéré depuis la table gares) + const [stationType, setStationType] = useState(null); + // Heure actuelle mise à jour chaque seconde pour la logique de seuil + const [currentTime, setCurrentTime] = useState(new Date()); + + useEffect(() => { + const id = setInterval(() => setCurrentTime(new Date()), 1000); + return () => clearInterval(id); + }, []); + // perturbations/travaux (récupérées depuis Supabase) const [perturbations, setPerturbations] = useState([]); const [, setTravaux] = useState([]); // helpers locaux (simples) — similaires à ceux du composant principal + const weekdayNameForColumn = useCallback((d) => { + const i = d.getDay(); + switch(i) { + case 1: return 'circule_lundi'; + case 2: return 'circule_mardi'; + case 3: return 'circule_mercredi'; + case 4: return 'circule_jeudi'; + case 5: return 'circule_vendredi'; + case 6: return 'circule_samedi'; + case 0: return 'circule_dimanche'; + default: return null; + } + }, []); + + const isPublicHoliday = useCallback((d) => { + const y = d.getFullYear(); + const m = d.getMonth() + 1; + const day = d.getDate(); + const s = `${y}-${String(m).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + const holidays = [ + `${y}-01-01`, // Jour de l'an + `${y}-05-01`, // Fête du Travail + `${y}-05-08`, // Victoire 1945 + `${y}-07-14`, // Fête nationale + `${y}-08-15`, // Assomption + `${y}-11-01`, // Toussaint + `${y}-11-11`, // Armistice 1918 + `${y}-12-25`, // Noël + ]; + // Dates variables pour 2026 + if (y === 2026) { + holidays.push('2026-04-06'); // Lundi de Pâques + holidays.push('2026-05-14'); // Ascension + holidays.push('2026-05-25'); // Lundi de Pentecôte + } + return holidays.includes(s); + }, []); + + const circulatesOn = useCallback((h, d) => { + if (isPublicHoliday(d)) { + const v = h['circule_jours_feries']; + if (typeof v === 'boolean') return v; + if (typeof v === 'number') return v === 1; + if (typeof v === 'string') { + const s = v.trim().toLowerCase(); + return s === 'true' || s === '1' || s === 't' || s === 'y' || s === 'yes'; + } + return (typeof v === 'undefined' || v === null) ? false : Boolean(v); + } + const col = weekdayNameForColumn(d); + if (!col) return true; + const v = h[col]; + if (typeof v === 'boolean') return v; + if (typeof v === 'number') return v === 1; + if (typeof v === 'string') { + const s = v.trim().toLowerCase(); + return s === 'true' || s === '1' || s === 't' || s === 'y' || s === 'yes'; + } + return (typeof v === 'undefined' || v === null) ? true : Boolean(v); + }, [isPublicHoliday, weekdayNameForColumn]); + const normalizeName = useCallback((s) => { if (!s) return ''; try { return String(s).normalize('NFD').replace(/\p{M}/gu,'').toLowerCase().trim().replace(/[-_]+/g,' ').replace(/\s+/g,' '); } catch (e) { return String(s).toLowerCase().trim(); } @@ -171,6 +242,15 @@ export default function ClassiquesArriveesQuais(){ return `${hh}h${mm}`; }, []); + // retourne le threshold (en minutes) pour afficher le quai selon le type de gare + const thresholdForStationType = useCallback((type) => { + if (!type) return 0; + const t = String(type).trim().toLowerCase(); + if (t === 'interurbaine' || t.includes('interurb')) return 360; // 6 heures + if (t === 'ville' || t.includes('ville')) return 30; // 30 minutes + return 0; + }, []); + // lecture des query params pour gare & quai const paramsForThis = new URLSearchParams(window.location.search || ''); const gareParam = paramsForThis.get('gare'); @@ -454,6 +534,19 @@ export default function ClassiquesArriveesQuais(){ return; } try { + // Récupérer le type de la gare + let fetchedType = null; + try { + if (gareIdParam) { + const { data: g } = await supabase.from('gares').select('type').eq('gare_id', gareIdParam).single(); + if (g) fetchedType = g.type; + } else if (gareDisplayParam) { + const { data: g } = await supabase.from('gares').select('type').ilike('nom', `%${gareDisplayParam}%`).limit(1).single(); + if (g) fetchedType = g.type; + } + } catch (e) { /* ignore */ } + if (mounted) setStationType(fetchedType); + const { data: all, error } = await supabase.from('horaires').select('id, *'); if (error) { if (mounted) setHoraireError(error.message || String(error)); @@ -470,12 +563,20 @@ export default function ClassiquesArriveesQuais(){ const targetQuai = quaiParam ? String(quaiParam).toLowerCase().replace(/\s+/g,'') : null; const now = new Date(); + const tomorrow = new Date(now); + tomorrow.setDate(now.getDate() + 1); const nowHM = now.getHours()*60 + now.getMinutes(); const candidates = []; for (const h of all) { if (!h) continue; + + // Filtrage par jour de circulation + const circulatesToday = circulatesOn(h, now); + const circulatesTomorrow = circulatesOn(h, tomorrow); + if (!circulatesToday && !circulatesTomorrow) continue; + // build stops list (support multiple field names) const stops = getStops(h); @@ -547,7 +648,7 @@ export default function ClassiquesArriveesQuais(){ let minutesUntil = depHM - nowHM; if (minutesUntil < 0) minutesUntil = depHM + 24*60 - nowHM; - candidates.push({ horaire: h, stop: stopMatch, depHM, minutesUntil, rawTime }); + candidates.push({ horaire: h, stop: stopMatch, depHM, minutesUntil, rawTime, circulatesToday, circulatesTomorrow }); } if (!candidates.length) { @@ -557,9 +658,9 @@ export default function ClassiquesArriveesQuais(){ return; } - // split into today (depHM > nowHM) and tomorrow (depHM <= nowHM) - const todayCandidates = candidates.filter(c => c.depHM > nowHM).sort((a,b) => a.depHM - b.depHM); - const tomorrowCandidates = candidates.filter(c => c.depHM <= nowHM).sort((a,b) => a.depHM - b.depHM); + // split into today (circulatesToday AND depHM > nowHM) and tomorrow (circulatesTomorrow AND (depHM <= nowHM OR not circulatesToday)) + const todayCandidates = candidates.filter(c => c.circulatesToday && c.depHM > nowHM).sort((a,b) => a.depHM - b.depHM); + const tomorrowCandidates = candidates.filter(c => c.circulatesTomorrow && (c.depHM <= nowHM || !c.circulatesToday)).sort((a,b) => a.depHM - b.depHM); let chosen = null; if (todayCandidates.length > 0) chosen = todayCandidates[0]; @@ -579,7 +680,7 @@ export default function ClassiquesArriveesQuais(){ loadNextForQuai(); return () => { mounted = false; }; - }, [gareDisplayParam, gareIdParam, quaiParam, parseTimeHM, normalizeName]); + }, [gareDisplayParam, gareIdParam, quaiParam, parseTimeHM, normalizeName, circulatesOn]); // toggle flag to alternate time/status when needed (observe selectedHoraire.status when present) const [showTime, setShowTime] = useState(true); @@ -700,6 +801,21 @@ export default function ClassiquesArriveesQuais(){ const s = (canonicalStatus || statusStrFromField || '').toLowerCase(); const isAlert = s.includes('retard') || s.includes('supprim'); const label = canonicalStatus || statusStrFromField || ''; + + // Logique de seuil d'affichage du quai + const nowHM = currentTime.getHours() * 60 + currentTime.getMinutes(); + const sched = selectedHoraire && selectedHoraire.depHM ? selectedHoraire.depHM : null; + const threshold = thresholdForStationType(stationType); + const showPlatform = (sched !== null) ? (nowHM >= (sched - threshold)) : true; + + if (!showPlatform) { + return ( + + --h-- + + ); + } + // Always render both time and status inside a fixed-size wrapper to avoid layout shift return ( diff --git a/src/pages/afficheurs/classiques/quais/ClassiquesDepartsQuais.js b/src/pages/afficheurs/classiques/quais/ClassiquesDepartsQuais.js index 6190754..f996a5f 100644 --- a/src/pages/afficheurs/classiques/quais/ClassiquesDepartsQuais.js +++ b/src/pages/afficheurs/classiques/quais/ClassiquesDepartsQuais.js @@ -106,11 +106,82 @@ export default function ClassiquesDepartsQuais(){ const [loadingHoraire, setLoadingHoraire] = useState(false); const [horaireError, setHoraireError] = useState(null); const [candidatesCount, setCandidatesCount] = useState(0); + // type de la gare (récupéré depuis la table gares) + const [stationType, setStationType] = useState(null); + // Heure actuelle mise à jour chaque seconde pour la logique de seuil + const [currentTime, setCurrentTime] = useState(new Date()); + + useEffect(() => { + const id = setInterval(() => setCurrentTime(new Date()), 1000); + return () => clearInterval(id); + }, []); + // perturbations/travaux (récupérées depuis Supabase) const [perturbations, setPerturbations] = useState([]); const [, setTravaux] = useState([]); // helpers locaux (simples) — similaires à ceux du composant principal + const weekdayNameForColumn = useCallback((d) => { + const i = d.getDay(); + switch(i) { + case 1: return 'circule_lundi'; + case 2: return 'circule_mardi'; + case 3: return 'circule_mercredi'; + case 4: return 'circule_jeudi'; + case 5: return 'circule_vendredi'; + case 6: return 'circule_samedi'; + case 0: return 'circule_dimanche'; + default: return null; + } + }, []); + + const isPublicHoliday = useCallback((d) => { + const y = d.getFullYear(); + const m = d.getMonth() + 1; + const day = d.getDate(); + const s = `${y}-${String(m).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + const holidays = [ + `${y}-01-01`, // Jour de l'an + `${y}-05-01`, // Fête du Travail + `${y}-05-08`, // Victoire 1945 + `${y}-07-14`, // Fête nationale + `${y}-08-15`, // Assomption + `${y}-11-01`, // Toussaint + `${y}-11-11`, // Armistice 1918 + `${y}-12-25`, // Noël + ]; + // Dates variables pour 2026 + if (y === 2026) { + holidays.push('2026-04-06'); // Lundi de Pâques + holidays.push('2026-05-14'); // Ascension + holidays.push('2026-05-25'); // Lundi de Pentecôte + } + return holidays.includes(s); + }, []); + + const circulatesOn = useCallback((h, d) => { + if (isPublicHoliday(d)) { + const v = h['circule_jours_feries']; + if (typeof v === 'boolean') return v; + if (typeof v === 'number') return v === 1; + if (typeof v === 'string') { + const s = v.trim().toLowerCase(); + return s === 'true' || s === '1' || s === 't' || s === 'y' || s === 'yes'; + } + return (typeof v === 'undefined' || v === null) ? false : Boolean(v); + } + const col = weekdayNameForColumn(d); + if (!col) return true; + const v = h[col]; + if (typeof v === 'boolean') return v; + if (typeof v === 'number') return v === 1; + if (typeof v === 'string') { + const s = v.trim().toLowerCase(); + return s === 'true' || s === '1' || s === 't' || s === 'y' || s === 'yes'; + } + return (typeof v === 'undefined' || v === null) ? true : Boolean(v); + }, [isPublicHoliday, weekdayNameForColumn]); + const normalizeName = useCallback((s) => { if (!s) return ''; try { return String(s).normalize('NFD').replace(/\p{M}/gu,'').toLowerCase().trim().replace(/[-_]+/g,' ').replace(/\s+/g,' '); } catch (e) { return String(s).toLowerCase().trim(); } @@ -154,6 +225,15 @@ export default function ClassiquesDepartsQuais(){ return `${hh}h${mm}`; }, []); + // retourne le threshold (en minutes) pour afficher le quai selon le type de gare + const thresholdForStationType = useCallback((type) => { + if (!type) return 0; + const t = String(type).trim().toLowerCase(); + if (t === 'interurbaine' || t.includes('interurb')) return 360; // 6 heures + if (t === 'ville' || t.includes('ville')) return 30; // 30 minutes + return 0; + }, []); + // lecture des query params pour gare & quai const paramsForThis = new URLSearchParams(window.location.search || ''); const gareParam = paramsForThis.get('gare'); @@ -437,6 +517,19 @@ export default function ClassiquesDepartsQuais(){ return; } try { + // Récupérer le type de la gare + let fetchedType = null; + try { + if (gareIdParam) { + const { data: g } = await supabase.from('gares').select('type').eq('gare_id', gareIdParam).single(); + if (g) fetchedType = g.type; + } else if (gareDisplayParam) { + const { data: g } = await supabase.from('gares').select('type').ilike('nom', `%${gareDisplayParam}%`).limit(1).single(); + if (g) fetchedType = g.type; + } + } catch (e) { /* ignore */ } + if (mounted) setStationType(fetchedType); + const { data: all, error } = await supabase.from('horaires').select('id, *'); if (error) { if (mounted) setHoraireError(error.message || String(error)); @@ -453,12 +546,20 @@ export default function ClassiquesDepartsQuais(){ const targetQuai = quaiParam ? String(quaiParam).toLowerCase().replace(/\s+/g,'') : null; const now = new Date(); + const tomorrow = new Date(now); + tomorrow.setDate(now.getDate() + 1); const nowHM = now.getHours()*60 + now.getMinutes(); const candidates = []; for (const h of all) { if (!h) continue; + + // Filtrage par jour de circulation + const circulatesToday = circulatesOn(h, now); + const circulatesTomorrow = circulatesOn(h, tomorrow); + if (!circulatesToday && !circulatesTomorrow) continue; + // build stops list (support multiple field names) const stops = getStops(h); @@ -577,7 +678,7 @@ export default function ClassiquesDepartsQuais(){ let minutesUntil = depHM - nowHM; if (minutesUntil < 0) minutesUntil = depHM + 24*60 - nowHM; - candidates.push({ horaire: h, stop: stopMatch, depHM, minutesUntil, rawTime }); + candidates.push({ horaire: h, stop: stopMatch, depHM, minutesUntil, rawTime, circulatesToday, circulatesTomorrow }); } if (!candidates.length) { @@ -587,9 +688,9 @@ export default function ClassiquesDepartsQuais(){ return; } - // split into today (depHM > nowHM) and tomorrow (depHM <= nowHM) - const todayCandidates = candidates.filter(c => c.depHM > nowHM).sort((a,b) => a.depHM - b.depHM); - const tomorrowCandidates = candidates.filter(c => c.depHM <= nowHM).sort((a,b) => a.depHM - b.depHM); + // split into today (circulatesToday AND depHM > nowHM) and tomorrow (circulatesTomorrow AND (depHM <= nowHM OR not circulatesToday)) + const todayCandidates = candidates.filter(c => c.circulatesToday && c.depHM > nowHM).sort((a,b) => a.depHM - b.depHM); + const tomorrowCandidates = candidates.filter(c => c.circulatesTomorrow && (c.depHM <= nowHM || !c.circulatesToday)).sort((a,b) => a.depHM - b.depHM); let chosen = null; if (todayCandidates.length > 0) chosen = todayCandidates[0]; @@ -609,7 +710,7 @@ export default function ClassiquesDepartsQuais(){ loadNextForQuai(); return () => { mounted = false; }; - }, [gareDisplayParam, gareIdParam, quaiParam, parseTimeHM, normalizeName]); + }, [gareDisplayParam, gareIdParam, quaiParam, parseTimeHM, normalizeName, circulatesOn]); // toggle flag to alternate time/status when needed (observe selectedHoraire.status when present) const [showTime, setShowTime] = useState(true); @@ -730,6 +831,21 @@ export default function ClassiquesDepartsQuais(){ const s = (canonicalStatus || statusStrFromField || '').toLowerCase(); const isAlert = s.includes('retard') || s.includes('supprim'); const label = canonicalStatus || statusStrFromField || ''; + + // Logique de seuil d'affichage du quai + const nowHM = currentTime.getHours() * 60 + currentTime.getMinutes(); + const sched = selectedHoraire && selectedHoraire.depHM ? selectedHoraire.depHM : null; + const threshold = thresholdForStationType(stationType); + const showPlatform = (sched !== null) ? (nowHM >= (sched - threshold)) : true; + + if (!showPlatform) { + return ( + + --h-- + + ); + } + // Always render both time and status inside a fixed-size wrapper to avoid layout shift return (