RELEASE V5.6.0 : Voici les modifications apportées :
-> REFONTE TOTALE DE TOUS LES AFFICHEURS CLASSIQUES + QUAIS + OPTION TGV FONCTIONNALITÉS EN COURS DE DÉVELOPPEMENT : -> ADAPTABILITÉ EN FONCTION DES SITUATIONS
This commit is contained in:
1 parent
9c6ac798f1
commit
d8f8ceaa9f
18 files changed
+2324
-533
No files matched your search
@@ -6,9 +6,23 @@ import useAnnonceSonoreRealtime from '../../../hooks/useAnnonceSonoreRealtime';
|
||||
import useAutoAnnonce from '../../../hooks/useAutoAnnonce';
|
||||
import { fetchAllRows } from '../../../lib/fetchAllRows';
|
||||
import { getSourceFromPath } from '../../../services/railwayService';
|
||||
import { getSncfTimetable } from '../../../services/sncfApi';
|
||||
import { getSncfTimetable, resolveNominalTrack } from '../../../services/sncfApi';
|
||||
import { matchStationNameStrict } from '../../../services/gtfsStations';
|
||||
import { getTrainLogoAndMeta } from '../../../utils/trainLogoHelper';
|
||||
|
||||
// Pictogramme Autocar SNCF / MOBIGO
|
||||
const AutocarIcon = ({ size = '50px', color = '#ffffff', style = {} }) => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width={size}
|
||||
height={size}
|
||||
fill={color}
|
||||
style={{ flexShrink: 0, ...style }}
|
||||
>
|
||||
<path d="M4 16c0 .88.39 1.67 1 2.22V20c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h8v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1.78c.61-.55 1-1.34 1-2.22V6c0-3.5-3.58-4-8-4s-8 .5-8 4v10zm3.5 1c-.83 0-1.5-.67-1.5-1.5S6.67 14 7.5 14s1.5.67 1.5 1.5S8.33 17 7.5 17zm9 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm1.5-6H6V6h12v5z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Helper utilitaire : format HHhMM sans secondes (ex: 06h44)
|
||||
const formatTimeNoSeconds = (v) => {
|
||||
if (v == null) return '';
|
||||
@@ -212,12 +226,8 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
const flexNeedle = `%${String(gareDisplay).replace(/[-_+\s]+/g, '%')}%`;
|
||||
const { data: gdata, error: gerr } = await supabase.from('gares').select('id,type,nom').ilike('nom', flexNeedle);
|
||||
if (!gerr && gdata && gdata.length) {
|
||||
const exactMatch = gdata.find(g => {
|
||||
const n1 = String(g.nom || '').normalize('NFD').replace(/\p{M}/gu, '').toLowerCase().replace(/[-_+\s]+/g, ' ').trim();
|
||||
const n2 = String(gareDisplay || '').normalize('NFD').replace(/\p{M}/gu, '').toLowerCase().replace(/[-_+\s]+/g, ' ').trim();
|
||||
return n1 === n2;
|
||||
});
|
||||
const chosen = exactMatch || gdata[0];
|
||||
const exactMatch = gdata.find(g => matchStationNameStrict(g.nom, gareDisplay));
|
||||
const chosen = exactMatch || (gdata.length === 1 ? gdata[0] : null);
|
||||
fetchedType = chosen ? chosen.type : null;
|
||||
fetchedId = chosen ? chosen.id : null;
|
||||
}
|
||||
@@ -650,21 +660,20 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
const stopForStation = (stops || []).find(s => {
|
||||
const sid = s.gare_id || s.gareId || s.id || s.stop_id || s.stopId || null;
|
||||
const nm = s.gare_nom || s.nom || s.name || s.stop_name || '';
|
||||
const ns = normalizeName(nm || '');
|
||||
const target = normalizeName(targetStationName || '');
|
||||
return (targetStationId && String(sid) === String(targetStationId)) || (targetStationName && ns === target);
|
||||
return (targetStationId && String(sid) === String(targetStationId)) || (targetStationName && matchStationNameStrict(nm, targetStationName));
|
||||
}) || null;
|
||||
horaire._status = displayStatus;
|
||||
horaire._displayTime = displayTime;
|
||||
// utiliser quai en priorité
|
||||
horaire._displayPlatform = (stopForStation && (stopForStation.quai || stopForStation.quai_reel || stopForStation.platform || stopForStation.quay)) || horaire.quai || horaire.quai_reel || horaire.platform || '';
|
||||
let platformVal = (stopForStation && (stopForStation.quai || stopForStation.quai_reel || stopForStation.platform || stopForStation.quay || stopForStation.voie)) || horaire.quai || horaire.quai_reel || horaire.platform || horaire.voie || '';
|
||||
if (!platformVal) {
|
||||
platformVal = resolveNominalTrack(targetStationName || gareDisplay, horaire.numero_train || horaire.num, targetStationId || gareIdParam);
|
||||
}
|
||||
horaire._displayPlatform = String(platformVal || '').replace(/^(?:voie|quai|track|platform)\s*/i, '').trim();
|
||||
return horaire;
|
||||
});
|
||||
}, [buildPerturbMap, computeStatusForHoraireAtStop, gareDisplay, gareIdParam, getStops, normalizeName]);
|
||||
}, [buildPerturbMap, computeStatusForHoraireAtStop, gareDisplay, gareIdParam, gareParam, getStops, normalizeName]);
|
||||
|
||||
useEffect(() => {
|
||||
// Alternance toutes les 4 secondes (type+numéro ↔ statut)
|
||||
const id = setInterval(() => setShowStatus(s => !s), 4000);
|
||||
// Alternance toutes les 2 secondes (type+numéro ↔ statut / suppression)
|
||||
const id = setInterval(() => setShowStatus(s => !s), 2000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
@@ -705,20 +714,26 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
if (sncfErr) {
|
||||
setError(sncfErr);
|
||||
} else {
|
||||
const formatted = (sncfData || []).map(t => ({
|
||||
...t,
|
||||
_displayTime: formatTimeNoSeconds(t.heure_depart),
|
||||
_displayPlatform: t.quai || t.voie || '',
|
||||
_depHM: (typeof t._depHM === 'number') ? t._depHM : parseTimeHM(t.heure_depart),
|
||||
_status: t._status || {
|
||||
label: t.etat === 'supprime' ? 'supprimé' : (t.retard_minutes > 0 ? `retardé +${t.retard_minutes}min.` : "à l'heure"),
|
||||
detail: '',
|
||||
delayMinutes: t.retard_minutes || null,
|
||||
causeDetails: t.cause_retard || null
|
||||
}
|
||||
}));
|
||||
setHoraires(formatted);
|
||||
setAllHoraires(formatted);
|
||||
const formatted = (sncfData || []).map(t => {
|
||||
const depHM = (typeof t._depHM === 'number') ? t._depHM : parseTimeHM(t.heure_depart);
|
||||
const plat = String(t._displayPlatform || t.quai_reel || t.quai || t.voie || '').replace(/^(?:voie|quai|track|platform)\s*/i, '').trim();
|
||||
return {
|
||||
...t,
|
||||
_displayTime: formatTimeNoSeconds(t.heure_depart),
|
||||
_displayPlatform: plat,
|
||||
_depHM: depHM,
|
||||
_status: t._status || {
|
||||
label: t.etat === 'supprime' ? 'supprimé' : (t.retard_minutes > 0 ? `retardé +${t.retard_minutes}min.` : "à l'heure"),
|
||||
detail: '',
|
||||
delayMinutes: t.retard_minutes || null,
|
||||
causeDetails: t.cause_retard || null
|
||||
}
|
||||
};
|
||||
});
|
||||
const maxCount = defilementMode ? 20 : 10;
|
||||
const limited = (formatted || []).slice(0, maxCount);
|
||||
setHoraires(limited);
|
||||
setAllHoraires(limited);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) setError(e.message || String(e));
|
||||
@@ -788,15 +803,14 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
// Exclure les horaires pour lesquels la gare ciblée est la gare d'arrivée finale (terminus)
|
||||
let displayHoraires = parsedHoraires.slice();
|
||||
if (gareDisplay) {
|
||||
const normalized = normalizeName(String(gareDisplay).replace(/-/g, ' '));
|
||||
displayHoraires = displayHoraires.filter(h => {
|
||||
// Exclure si la gare ciblée est la gare d'arrivée finale
|
||||
const destName = normalizeName((h.gare_arrivee_nom || h.gare_arrivee || '').toString());
|
||||
if (destName === normalized) return false;
|
||||
const destName = (h.gare_arrivee_nom || h.gare_arrivee || '').toString();
|
||||
if (matchStationNameStrict(destName, gareDisplay)) return false;
|
||||
|
||||
// Inclure si gare de départ correspond
|
||||
const departName = normalizeName((h.gare_depart_nom || h.gare_depart_name || h.gare_depart || '').toString());
|
||||
if (departName === normalized) return true;
|
||||
const departName = (h.gare_depart_nom || h.gare_depart_name || h.gare_depart || '').toString();
|
||||
if (matchStationNameStrict(departName, gareDisplay)) return true;
|
||||
|
||||
// Chercher dans les arrêts desservis ; ignorer si l'arrêt est le terminus (isLast ou index==max)
|
||||
const stopsList = getStops(h);
|
||||
@@ -809,11 +823,11 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
}
|
||||
}
|
||||
for (const s of stopsList) {
|
||||
const stopName = normalizeName((s.gare_nom || s.nom || s.name || s.stop_name || '').toString());
|
||||
const stopName = (s.gare_nom || s.nom || s.name || s.stop_name || '').toString();
|
||||
if (!stopName) continue;
|
||||
const isTerminus = Boolean(s.isLast || s.is_last || (typeof s.index !== 'undefined' && maxIdx != null && Number(s.index) === maxIdx));
|
||||
if (isTerminus) continue;
|
||||
if (stopName === normalized) return true;
|
||||
if (matchStationNameStrict(stopName, gareDisplay)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -886,7 +900,7 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
const stopsListForH = getStops(h);
|
||||
if ((gareIdParam || gareDisplay) && Array.isArray(stopsListForH)) {
|
||||
const byId = gareIdParam ? stopsListForH.find(s => String(s.gare_id || s.gareId || s.id) === String(gareIdParam)) : null;
|
||||
const byName = gareDisplay ? stopsListForH.find(s => normalizeName((s.gare_nom || s.nom || s.name || '').toString()) === normalizeName(gareDisplay)) : null;
|
||||
const byName = gareDisplay ? stopsListForH.find(s => matchStationNameStrict(s.gare_nom || s.nom || s.name || '', gareDisplay)) : null;
|
||||
const stop = byId || byName;
|
||||
if (stop && (stop.heure_depart || stop.time || stop.heure)) raw = stop.heure_depart || stop.time || stop.heure;
|
||||
}
|
||||
@@ -1162,10 +1176,13 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
// Création des lignes vides pour créer l'espace
|
||||
const emptyRows = Array.from({ length: 10 }).map((_, i) => ({ _isEmpty: true, id: `empty-${i}` }));
|
||||
|
||||
const maxRowsCount = defilementMode ? 20 : 10;
|
||||
const limitedHoraires = (horaires || []).slice(0, maxRowsCount);
|
||||
|
||||
// Liste de lignes à rendre (horaires + espace + horaires + espace en mode défilement pour boucle infinie)
|
||||
const rowsToRender = defilementMode
|
||||
? [...horaires, ...emptyRows, ...horaires, ...emptyRows]
|
||||
: horaires;
|
||||
? [...limitedHoraires, ...emptyRows, ...limitedHoraires, ...emptyRows]
|
||||
: limitedHoraires;
|
||||
|
||||
// Calcul dynamique de l'animation CSS
|
||||
useEffect(() => {
|
||||
@@ -1256,7 +1273,21 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
const status = horaire._status || computeStatusForHoraireAtStop(horaire, stationId, stationName, buildPerturbMap(perturbations));
|
||||
const isSuppressed = status && status.label === 'supprimé';
|
||||
const isDelayed = status && status.label && String(status.label).startsWith('retardé');
|
||||
const showPlatform = !isSuppressed && Boolean(cleanPlatform);
|
||||
const isCar = (
|
||||
(horaire.type_train && (horaire.type_train.toUpperCase().includes('AUTOCAR') || horaire.type_train.toUpperCase().includes('CAR TER') || horaire.type_train.toUpperCase() === 'BUS')) ||
|
||||
(horaire.op && (horaire.op.toUpperCase().includes('AUTOCAR') || horaire.op.toUpperCase().includes('CAR TER') || horaire.op.toUpperCase() === 'BUS')) ||
|
||||
(horaire.type && (horaire.type.toUpperCase().includes('AUTOCAR') || horaire.type.toUpperCase().includes('CAR TER') || horaire.type.toUpperCase() === 'BUS')) ||
|
||||
Boolean(horaire.is_substitution) ||
|
||||
(numTrain && String(numTrain).startsWith('3') && String(numTrain).length >= 5) ||
|
||||
typeLabel === 'AUTOCAR' ||
|
||||
rawPlatform === 'AUTOCAR' ||
|
||||
rawPlatform === 'CAR' ||
|
||||
rawPlatform === 'BUS' ||
|
||||
cleanPlatform === 'AUTOCAR' ||
|
||||
cleanPlatform === 'CAR' ||
|
||||
cleanPlatform === 'BUS'
|
||||
);
|
||||
const showPlatform = !isSuppressed && (isCar || Boolean(cleanPlatform));
|
||||
|
||||
const stopsList = isExtended ? getStopsListForDeparts(horaire, gareDisplay || gareParam, normalizeName) : [];
|
||||
|
||||
@@ -1277,7 +1308,10 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
isSuppressed ? (
|
||||
<div className="aff-classiques-departs__status-suppressed">supprimé</div>
|
||||
) : isDelayed ? (
|
||||
<div className="aff-classiques-departs__status-delayed">retard {status.delayMinutes} min.</div>
|
||||
<div className="aff-classiques-departs__status-delayed">
|
||||
<div className="aff-classiques-departs__status-delayed-label">retardé</div>
|
||||
<div className="aff-classiques-departs__status-delayed-time">+{status.delayMinutes || horaire.retard_minutes || 0} min.</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="aff-classiques-departs__status-ontime">à l'heure</div>
|
||||
)
|
||||
@@ -1290,7 +1324,12 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
</div>
|
||||
|
||||
<div className="aff-classiques-departs__col-time">
|
||||
<div className="aff-classiques-departs__time">{time}</div>
|
||||
<div
|
||||
className="aff-classiques-departs__time"
|
||||
style={{ visibility: isSuppressed && showStatus ? 'hidden' : 'visible' }}
|
||||
>
|
||||
{time}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="aff-classiques-departs__col-destination">
|
||||
@@ -1300,7 +1339,11 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
<div className="aff-classiques-departs__col-platform">
|
||||
{showPlatform ? (
|
||||
<div className="aff-classiques-departs__platform-box">
|
||||
<div className="aff-classiques-departs__platform-text">{cleanPlatform}</div>
|
||||
{isCar ? (
|
||||
<AutocarIcon size="48px" />
|
||||
) : (
|
||||
<div className="aff-classiques-departs__platform-text">{cleanPlatform}</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1343,7 +1386,10 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
isSuppressed ? (
|
||||
<div className="aff-classiques-departs__status-suppressed">supprimé</div>
|
||||
) : isDelayed ? (
|
||||
<div className="aff-classiques-departs__status-delayed">retard {status.delayMinutes} min.</div>
|
||||
<div className="aff-classiques-departs__status-delayed">
|
||||
<div className="aff-classiques-departs__status-delayed-label">retardé</div>
|
||||
<div className="aff-classiques-departs__status-delayed-time">+{status.delayMinutes || horaire.retard_minutes || 0} min.</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="aff-classiques-departs__status-ontime">à l'heure</div>
|
||||
)
|
||||
@@ -1357,7 +1403,12 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
|
||||
{/* COL 3 : Heure (grande, jaune) */}
|
||||
<div className="aff-classiques-departs__col-time">
|
||||
<div className="aff-classiques-departs__time">{time}</div>
|
||||
<div
|
||||
className="aff-classiques-departs__time"
|
||||
style={{ visibility: isSuppressed && showStatus ? 'hidden' : 'visible' }}
|
||||
>
|
||||
{time}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* COL 4 : Destination */}
|
||||
@@ -1369,7 +1420,11 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
<div className="aff-classiques-departs__col-platform">
|
||||
{showPlatform ? (
|
||||
<div className="aff-classiques-departs__platform-box">
|
||||
<div className="aff-classiques-departs__platform-text">{cleanPlatform}</div>
|
||||
{isCar ? (
|
||||
<AutocarIcon size="48px" />
|
||||
) : (
|
||||
<div className="aff-classiques-departs__platform-text">{cleanPlatform}</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user