Update station type handling and current time logic for quai display
This commit is contained in:
1 parent
e42f6c01ec
commit
503a70bd49
5 files changed
+368
-40
No files matched your search
@@ -1,3 +1,2 @@
|
|||||||
|
REACT_APP_SUPABASE_URL=https://supabase-ferrovia.mr-patator.fr
|
||||||
REACT_APP_SUPABASE_URL=https://snkpkkewlpetcqhfxotm.supabase.co
|
REACT_APP_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzc1OTUxMjAxLCJleHAiOjE5MzM2MzEyMDF9.ZFuzZSHVOMX-poU1fhWTjZ2sn7LFz9NYATTwJknlSr0
|
||||||
REACT_APP_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNua3Bra2V3bHBldGNxaGZ4b3RtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAxMzYyMDEsImV4cCI6MjA4NTcxMjIwMX0.zLyNKXHSM2Kz58DFwTPySnhLjnSY_T3LtdYbXM2nVVY
|
|
||||||
@@ -86,6 +86,15 @@ export default function ClassiquesArrivees(){
|
|||||||
|
|
||||||
// État pour alterner l'affichage toutes les 3 secondes
|
// État pour alterner l'affichage toutes les 3 secondes
|
||||||
const [showStatus, setShowStatus] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
const id = setInterval(() => setShowStatus(s => !s), 3000);
|
const id = setInterval(() => setShowStatus(s => !s), 3000);
|
||||||
return () => clearInterval(id);
|
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
|
// retourne le threshold (en minutes) pour afficher le quai selon le type de gare
|
||||||
const thresholdForStationType = useCallback((type) => {
|
const thresholdForStationType = useCallback((type) => {
|
||||||
if (!type) return 0;
|
if (!type) return 0;
|
||||||
const t = String(type).toLowerCase();
|
const t = String(type).trim().toLowerCase();
|
||||||
if (t.includes('interurb') || t.includes('interurbaine')) return 360; // 6 heures
|
if (t === 'interurbaine' || t.includes('interurb')) return 360; // 6 heures
|
||||||
if (t.includes('ville')) return 30; // 30 minutes
|
if (t === 'ville' || t.includes('ville')) return 30; // 30 minutes
|
||||||
return 0;
|
return 0;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -537,7 +546,41 @@ export default function ClassiquesArrivees(){
|
|||||||
const i = weekdayIndex(d);
|
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; }
|
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) => {
|
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);
|
const col = weekdayNameForColumn(d);
|
||||||
if (!col) return true;
|
if (!col) return true;
|
||||||
const v = h[col];
|
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
|
// 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);
|
let showPlatform = !(status && status.label === 'supprimé') && Boolean(platform);
|
||||||
if (showPlatform) {
|
if (showPlatform) {
|
||||||
const now = new Date();
|
const nowHM = currentTime.getHours()*60 + currentTime.getMinutes(); // Use currentTime
|
||||||
const nowHM = now.getHours()*60 + now.getMinutes();
|
|
||||||
const sched = (typeof horaire._arrHM === 'number') ? horaire._arrHM : parseTimeHM(horaire.heure_arrivee || horaire.arrival_time || horaire.time || horaire._rawArrTime || '');
|
const sched = (typeof horaire._arrHM === 'number') ? horaire._arrHM : parseTimeHM(horaire.heure_arrivee || horaire.arrival_time || horaire.time || horaire._rawArrTime || '');
|
||||||
const threshold = thresholdForStationType(stationType);
|
const threshold = thresholdForStationType(stationType);
|
||||||
if (typeof sched === 'number' && typeof threshold === 'number' && threshold > 0) {
|
if (typeof sched === 'number' && typeof threshold === 'number' && threshold > 0) {
|
||||||
@@ -742,5 +784,3 @@ export default function ClassiquesArrivees(){
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import './ClassiquesDeparts.css';
|
import './ClassiquesDeparts.css';
|
||||||
import supabase from '../../../lib/supabase';
|
import supabase from '../../../lib/supabase';
|
||||||
@@ -101,6 +102,14 @@ export default function ClassiquesDeparts(){
|
|||||||
|
|
||||||
// État pour alterner l'affichage toutes les 3 secondes
|
// État pour alterner l'affichage toutes les 3 secondes
|
||||||
const [showStatus, setShowStatus] = useState(false);
|
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
|
// Données depuis Supabase
|
||||||
const [horaires, setHoraires] = useState([]);
|
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
|
// retourne le threshold (en minutes) pour afficher le quai selon le type de gare
|
||||||
const thresholdForStationType = useCallback((type) => {
|
const thresholdForStationType = useCallback((type) => {
|
||||||
if (!type) return 0;
|
if (!type) return 0;
|
||||||
const t = String(type).toLowerCase();
|
const t = String(type).trim().toLowerCase();
|
||||||
if (t.includes('interurb') || t.includes('interurbaine')) return 360; // 6 heures
|
if (t === 'interurbaine' || t.includes('interurb')) return 360; // 6 heures
|
||||||
if (t.includes('ville')) return 30; // 30 minutes
|
if (t === 'ville' || t.includes('ville')) return 30; // 30 minutes
|
||||||
return 0;
|
return 0;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -456,7 +465,6 @@ export default function ClassiquesDeparts(){
|
|||||||
if (typeof base === 'number') displayTime = formatTimeFromHM(base + displayStatus.delayMinutes);
|
if (typeof base === 'number') displayTime = formatTimeFromHM(base + displayStatus.delayMinutes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
horaire._stopsStatus = stopsStatus;
|
horaire._stopsStatus = stopsStatus;
|
||||||
// prefer platform from stop data (quai) if available
|
// prefer platform from stop data (quai) if available
|
||||||
const stopForStation = (stops || []).find(s => {
|
const stopForStation = (stops || []).find(s => {
|
||||||
@@ -565,7 +573,9 @@ export default function ClassiquesDeparts(){
|
|||||||
} catch (err) { /* ignore */ }
|
} 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 */ }
|
} catch (e) { /* ignore errors reading gares table */ }
|
||||||
// Filtrer par gare : garder uniquement les horaires qui DESSERVENT la gare ciblée
|
// 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`)
|
// (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 isPublicHoliday = (d) => {
|
||||||
const col = weekdayNameForColumn(d);
|
const y = d.getFullYear();
|
||||||
if (!col) return true;
|
const m = d.getMonth() + 1;
|
||||||
const v = h[col];
|
const day = d.getDate();
|
||||||
if (typeof v === 'boolean') return v;
|
const s = `${y}-${String(m).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||||
if (typeof v === 'number') return v === 1;
|
const holidays = [
|
||||||
if (typeof v === 'string') {
|
`${y}-01-01`, // Jour de l'an
|
||||||
const s = v.trim().toLowerCase();
|
`${y}-05-01`, // Fête du Travail
|
||||||
return s === 'true' || s === '1' || s === 't' || s === 'y' || s === 'yes';
|
`${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 holidays.includes(s);
|
||||||
return (typeof v === 'undefined' || v === null) ? true : Boolean(v);
|
};
|
||||||
|
|
||||||
|
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_*
|
// 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
|
// 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));
|
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 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) {
|
if (status && status.label === 'supprimé' && showStatus) {
|
||||||
time = '';
|
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
|
// 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);
|
let showPlatform = !(status && status.label === 'supprimé') && Boolean(platform);
|
||||||
if (showPlatform) {
|
if (showPlatform) {
|
||||||
const now = new Date();
|
const nowHM = currentTime.getHours()*60 + currentTime.getMinutes(); // Use currentTime
|
||||||
const nowHM = now.getHours()*60 + now.getMinutes();
|
|
||||||
const sched = (typeof horaire._depHM === 'number') ? horaire._depHM : parseTimeHM(horaire.heure_depart || horaire.time || horaire.heure || horaire._rawTime || '');
|
const sched = (typeof horaire._depHM === 'number') ? horaire._depHM : parseTimeHM(horaire.heure_depart || horaire.time || horaire.heure || horaire._rawTime || '');
|
||||||
const threshold = thresholdForStationType(stationType);
|
const threshold = thresholdForStationType(stationType);
|
||||||
if (typeof sched === 'number' && typeof threshold === 'number' && threshold > 0) {
|
if (typeof sched === 'number' && typeof threshold === 'number' && threshold > 0) {
|
||||||
@@ -1034,5 +1093,3 @@ export default function ClassiquesDeparts(){
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -123,11 +123,82 @@ export default function ClassiquesArriveesQuais(){
|
|||||||
const [loadingHoraire, setLoadingHoraire] = useState(false);
|
const [loadingHoraire, setLoadingHoraire] = useState(false);
|
||||||
const [horaireError, setHoraireError] = useState(null);
|
const [horaireError, setHoraireError] = useState(null);
|
||||||
const [candidatesCount, setCandidatesCount] = useState(0);
|
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)
|
// perturbations/travaux (récupérées depuis Supabase)
|
||||||
const [perturbations, setPerturbations] = useState([]);
|
const [perturbations, setPerturbations] = useState([]);
|
||||||
const [, setTravaux] = useState([]);
|
const [, setTravaux] = useState([]);
|
||||||
|
|
||||||
// helpers locaux (simples) — similaires à ceux du composant principal
|
// 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) => {
|
const normalizeName = useCallback((s) => {
|
||||||
if (!s) return '';
|
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(); }
|
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}`;
|
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
|
// lecture des query params pour gare & quai
|
||||||
const paramsForThis = new URLSearchParams(window.location.search || '');
|
const paramsForThis = new URLSearchParams(window.location.search || '');
|
||||||
const gareParam = paramsForThis.get('gare');
|
const gareParam = paramsForThis.get('gare');
|
||||||
@@ -454,6 +534,19 @@ export default function ClassiquesArriveesQuais(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
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, *');
|
const { data: all, error } = await supabase.from('horaires').select('id, *');
|
||||||
if (error) {
|
if (error) {
|
||||||
if (mounted) setHoraireError(error.message || String(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 targetQuai = quaiParam ? String(quaiParam).toLowerCase().replace(/\s+/g,'') : null;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
const tomorrow = new Date(now);
|
||||||
|
tomorrow.setDate(now.getDate() + 1);
|
||||||
const nowHM = now.getHours()*60 + now.getMinutes();
|
const nowHM = now.getHours()*60 + now.getMinutes();
|
||||||
|
|
||||||
const candidates = [];
|
const candidates = [];
|
||||||
|
|
||||||
for (const h of all) {
|
for (const h of all) {
|
||||||
if (!h) continue;
|
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)
|
// build stops list (support multiple field names)
|
||||||
const stops = getStops(h);
|
const stops = getStops(h);
|
||||||
|
|
||||||
@@ -547,7 +648,7 @@ export default function ClassiquesArriveesQuais(){
|
|||||||
let minutesUntil = depHM - nowHM;
|
let minutesUntil = depHM - nowHM;
|
||||||
if (minutesUntil < 0) minutesUntil = depHM + 24*60 - 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) {
|
if (!candidates.length) {
|
||||||
@@ -557,9 +658,9 @@ export default function ClassiquesArriveesQuais(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// split into today (depHM > nowHM) and tomorrow (depHM <= nowHM)
|
// split into today (circulatesToday AND depHM > nowHM) and tomorrow (circulatesTomorrow AND (depHM <= nowHM OR not circulatesToday))
|
||||||
const todayCandidates = candidates.filter(c => c.depHM > nowHM).sort((a,b) => a.depHM - b.depHM);
|
const todayCandidates = candidates.filter(c => c.circulatesToday && 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);
|
const tomorrowCandidates = candidates.filter(c => c.circulatesTomorrow && (c.depHM <= nowHM || !c.circulatesToday)).sort((a,b) => a.depHM - b.depHM);
|
||||||
|
|
||||||
let chosen = null;
|
let chosen = null;
|
||||||
if (todayCandidates.length > 0) chosen = todayCandidates[0];
|
if (todayCandidates.length > 0) chosen = todayCandidates[0];
|
||||||
@@ -579,7 +680,7 @@ export default function ClassiquesArriveesQuais(){
|
|||||||
|
|
||||||
loadNextForQuai();
|
loadNextForQuai();
|
||||||
return () => { mounted = false; };
|
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)
|
// toggle flag to alternate time/status when needed (observe selectedHoraire.status when present)
|
||||||
const [showTime, setShowTime] = useState(true);
|
const [showTime, setShowTime] = useState(true);
|
||||||
@@ -700,6 +801,21 @@ export default function ClassiquesArriveesQuais(){
|
|||||||
const s = (canonicalStatus || statusStrFromField || '').toLowerCase();
|
const s = (canonicalStatus || statusStrFromField || '').toLowerCase();
|
||||||
const isAlert = s.includes('retard') || s.includes('supprim');
|
const isAlert = s.includes('retard') || s.includes('supprim');
|
||||||
const label = canonicalStatus || statusStrFromField || '';
|
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 (
|
||||||
|
<span className="aff-quais__time-status-wrapper" aria-live="polite">
|
||||||
|
<span className="aff-quais__time aff-quais__time--strict visible">--h--</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Always render both time and status inside a fixed-size wrapper to avoid layout shift
|
// Always render both time and status inside a fixed-size wrapper to avoid layout shift
|
||||||
return (
|
return (
|
||||||
<span className="aff-quais__time-status-wrapper" aria-live="polite">
|
<span className="aff-quais__time-status-wrapper" aria-live="polite">
|
||||||
|
|||||||
@@ -106,11 +106,82 @@ export default function ClassiquesDepartsQuais(){
|
|||||||
const [loadingHoraire, setLoadingHoraire] = useState(false);
|
const [loadingHoraire, setLoadingHoraire] = useState(false);
|
||||||
const [horaireError, setHoraireError] = useState(null);
|
const [horaireError, setHoraireError] = useState(null);
|
||||||
const [candidatesCount, setCandidatesCount] = useState(0);
|
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)
|
// perturbations/travaux (récupérées depuis Supabase)
|
||||||
const [perturbations, setPerturbations] = useState([]);
|
const [perturbations, setPerturbations] = useState([]);
|
||||||
const [, setTravaux] = useState([]);
|
const [, setTravaux] = useState([]);
|
||||||
|
|
||||||
// helpers locaux (simples) — similaires à ceux du composant principal
|
// 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) => {
|
const normalizeName = useCallback((s) => {
|
||||||
if (!s) return '';
|
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(); }
|
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}`;
|
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
|
// lecture des query params pour gare & quai
|
||||||
const paramsForThis = new URLSearchParams(window.location.search || '');
|
const paramsForThis = new URLSearchParams(window.location.search || '');
|
||||||
const gareParam = paramsForThis.get('gare');
|
const gareParam = paramsForThis.get('gare');
|
||||||
@@ -437,6 +517,19 @@ export default function ClassiquesDepartsQuais(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
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, *');
|
const { data: all, error } = await supabase.from('horaires').select('id, *');
|
||||||
if (error) {
|
if (error) {
|
||||||
if (mounted) setHoraireError(error.message || String(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 targetQuai = quaiParam ? String(quaiParam).toLowerCase().replace(/\s+/g,'') : null;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
const tomorrow = new Date(now);
|
||||||
|
tomorrow.setDate(now.getDate() + 1);
|
||||||
const nowHM = now.getHours()*60 + now.getMinutes();
|
const nowHM = now.getHours()*60 + now.getMinutes();
|
||||||
|
|
||||||
const candidates = [];
|
const candidates = [];
|
||||||
|
|
||||||
for (const h of all) {
|
for (const h of all) {
|
||||||
if (!h) continue;
|
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)
|
// build stops list (support multiple field names)
|
||||||
const stops = getStops(h);
|
const stops = getStops(h);
|
||||||
|
|
||||||
@@ -577,7 +678,7 @@ export default function ClassiquesDepartsQuais(){
|
|||||||
let minutesUntil = depHM - nowHM;
|
let minutesUntil = depHM - nowHM;
|
||||||
if (minutesUntil < 0) minutesUntil = depHM + 24*60 - 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) {
|
if (!candidates.length) {
|
||||||
@@ -587,9 +688,9 @@ export default function ClassiquesDepartsQuais(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// split into today (depHM > nowHM) and tomorrow (depHM <= nowHM)
|
// split into today (circulatesToday AND depHM > nowHM) and tomorrow (circulatesTomorrow AND (depHM <= nowHM OR not circulatesToday))
|
||||||
const todayCandidates = candidates.filter(c => c.depHM > nowHM).sort((a,b) => a.depHM - b.depHM);
|
const todayCandidates = candidates.filter(c => c.circulatesToday && 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);
|
const tomorrowCandidates = candidates.filter(c => c.circulatesTomorrow && (c.depHM <= nowHM || !c.circulatesToday)).sort((a,b) => a.depHM - b.depHM);
|
||||||
|
|
||||||
let chosen = null;
|
let chosen = null;
|
||||||
if (todayCandidates.length > 0) chosen = todayCandidates[0];
|
if (todayCandidates.length > 0) chosen = todayCandidates[0];
|
||||||
@@ -609,7 +710,7 @@ export default function ClassiquesDepartsQuais(){
|
|||||||
|
|
||||||
loadNextForQuai();
|
loadNextForQuai();
|
||||||
return () => { mounted = false; };
|
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)
|
// toggle flag to alternate time/status when needed (observe selectedHoraire.status when present)
|
||||||
const [showTime, setShowTime] = useState(true);
|
const [showTime, setShowTime] = useState(true);
|
||||||
@@ -730,6 +831,21 @@ export default function ClassiquesDepartsQuais(){
|
|||||||
const s = (canonicalStatus || statusStrFromField || '').toLowerCase();
|
const s = (canonicalStatus || statusStrFromField || '').toLowerCase();
|
||||||
const isAlert = s.includes('retard') || s.includes('supprim');
|
const isAlert = s.includes('retard') || s.includes('supprim');
|
||||||
const label = canonicalStatus || statusStrFromField || '';
|
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 (
|
||||||
|
<span className="aff-quais__time-status-wrapper" aria-live="polite">
|
||||||
|
<span className="aff-quais__time aff-quais__time--strict visible">--h--</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Always render both time and status inside a fixed-size wrapper to avoid layout shift
|
// Always render both time and status inside a fixed-size wrapper to avoid layout shift
|
||||||
return (
|
return (
|
||||||
<span className="aff-quais__time-status-wrapper" aria-live="polite">
|
<span className="aff-quais__time-status-wrapper" aria-live="polite">
|
||||||
|
|||||||
Reference in new issue
Block a user