Refonte complète et fidèle des afficheurs Classiques Départs et Arrivées selon les images

This commit is contained in:
mathis committed 2026-08-30 22:45:03 +02:00
1 parent 62f268e677
commit 326e3e7c8b
4 files changed
+408 -765

No files matched your search

@@ -1,7 +1,6 @@
import React, { useEffect, useState, useCallback } from 'react';
import './ClassiquesDeparts.css';
import supabase from '../../../lib/supabase';
import Logo from '../../../components/Logo';
import MarqueeText from '../../../components/MarqueeText';
import useAnnonceSonoreRealtime from '../../../hooks/useAnnonceSonoreRealtime';
import useAutoAnnonce from '../../../hooks/useAutoAnnonce';
@@ -49,8 +48,71 @@ const isSubstitution = (h) => {
return Boolean(subFlag || subStr || typeMatch || commentMatch);
};
// (La fonction TrainType a été retirée car la colonne affiche désormais directement les
// informations via .train-meta dans la colonne .col-train-type)
const formatStationName = (name) => {
if (!name) return '';
let str = String(name).trim();
str = str.replace(/^Gare de\s+/i, '').replace(/^Gare d'\s*/i, '');
str = str.replace(/([a-zA-ZÀ-ÿ])-(sur|en|les|sous|au|aux)-([a-zA-ZÀ-ÿ])/gi, '$1-$2-$3');
str = str.replace(/-(Viotte|Bercy|Part[- ]Dieu|Perrache|Gare[- ]de[- ]Lyon|Saint[- ]Lazare|Austerlitz|Montparnasse|Est|Nord|Ville|Centre|Rive[- ]Droite|Rive[- ]Gauche|Chantiers|Matabiau|Saint[- ]Jean|Saint[- ]Charles|Flandres|Europe|TGV|Alésia|Saint[- ]Gervais)/gi, ' $1');
str = str.replace(/Lyon-Part-Dieu/gi, 'Lyon Part Dieu');
str = str.replace(/Paris-Gare-de-Lyon/gi, 'Paris Gare de Lyon');
str = str.replace(/Paris-Bercy/gi, 'Paris Bercy');
str = str.replace(/Besançon-Viotte/gi, 'Besançon Viotte');
str = str.replace(/Besancon-Viotte/gi, 'Besançon Viotte');
str = str.replace(/Les-Laumes-Alésia/gi, 'Les Laumes Alésia');
str = str.replace(/Les-Laumes-Alesia/gi, 'Les Laumes Alésia');
str = str.replace(/Auxerre-Saint-Gervais/gi, 'Auxerre Saint-Gervais');
str = str.replace(/Lyon-Perrache/gi, 'Lyon Perrache');
return str;
};
const getTrainMeta = (horaire) => {
const opRaw = String(horaire.type_train || horaire.op || horaire.mode || '').trim();
const numTrain = String(horaire.numero_train || horaire.numero || horaire.number || horaire.trip_short_name || '').trim();
const opUpper = opRaw.toUpperCase();
let logoSrc = '/type/logo-sncf.svg';
let typeLabel = 'TER';
if (opUpper.includes('INOUI') || opUpper.includes('TGV')) {
logoSrc = '/type/logo-inoui.svg';
typeLabel = 'TGV inOUI';
} else if (opUpper.includes('OUIGO')) {
logoSrc = '/type/logo-ouigo.svg';
typeLabel = 'OUIGO';
} else if (opUpper.includes('MOBIGO')) {
logoSrc = '/type/logo-sncf.svg';
typeLabel = 'MOBIGO';
} else if (opUpper.includes('ICE')) {
logoSrc = '/type/logo-ice.svg';
typeLabel = 'ICE';
} else if (opUpper.includes('LYRIA')) {
logoSrc = '/type/logo-lyria.svg';
typeLabel = 'TGV Lyria';
} else if (opUpper.includes('INTERCIT') || opUpper.includes('IC')) {
logoSrc = '/type/logo-sncf.svg';
typeLabel = 'INTERCITÉS';
} else if (opUpper.includes('CAR') || opUpper.includes('AUTOCAR') || opUpper.includes('BUS')) {
logoSrc = '/type/logo-sncf.svg';
typeLabel = 'AUTOCAR';
} else if (opRaw) {
logoSrc = '/type/logo-sncf.svg';
typeLabel = opRaw;
}
// Si c'est un numéro typique MOBIGO en région BFC (séries 89xxxx, 84xxxx)
if (typeLabel === 'TER' && (numTrain.startsWith('89') || numTrain.startsWith('84'))) {
typeLabel = 'MOBIGO';
}
return { logoSrc, typeLabel, numTrain };
};
const formatPlatform = (p) => {
if (!p) return '';
return String(p).toUpperCase().replace(/VOIE|QUAI|\s+/gi, '').trim();
};
function Clock(){
const [now, setNow] = useState(new Date());
@@ -1145,67 +1207,35 @@ export default function ClassiquesDeparts(){
const renderRow = (h, idx) => {
if (h._isEmpty) {
return <li className="aff-classiques-departs__row" key={`empty-${idx}`} style={{ background: 'transparent' }}></li>;
return <li className="aff-classiques-departs__row aff-classiques-departs__row--empty" key={`empty-${idx}`} style={{ opacity: 0 }}></li>;
}
const horaire = h || {};
const op = horaire.type_train || horaire.op || '';
const isCarTER = (op === 'Car TER' || op === 'Autocar');
const time = horaire._displayTime || formatTimeNoSeconds(horaire.heure_depart || horaire.time || horaire.heure || '');
const destination = horaire.gare_arrivee_nom || '';
const platform = (horaire._displayPlatform || '').toString().trim();
const { logoSrc, typeLabel, numTrain } = getTrainMeta(horaire);
const time = horaire._displayTime || formatTimeNoSeconds(horaire.heure_depart || horaire.departure_time || horaire.time || horaire.heure || '');
const destination = horaire.gare_arrivee_nom || horaire.gare_arrivee || horaire.destination || '';
const formattedDestination = formatStationName(destination);
const rawPlatform = (horaire._displayPlatform || horaire.voie || horaire.quai || '').toString().trim();
const cleanPlatform = formatPlatform(rawPlatform);
const stationId = horaire.gare_depart_id || horaire.gare_id || horaire.origin_id || (gareIdParam || null);
const stationName = gareDisplay || horaire.gare_depart_nom || horaire.gare_depart || horaire.origin_name || 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é');
let showPlatform = !isSuppressed && Boolean(platform);
const isSub = isSubstitution(horaire);
const infoText = isSub
? (horaire.motif_substitution && horaire.motif_substitution !== 'TRAVAUX' && horaire.motif_substitution !== 'Travaux.' ? horaire.motif_substitution : 'Autocar cour de la gare Plateforme 14')
: (status && status.causeDetails ? status.causeDetails : '');
const trainNumber = horaire.numero_train || horaire.numero || horaire.number || '';
const showPlatform = !isSuppressed && Boolean(cleanPlatform);
return (
<li className="aff-classiques-departs__row" key={idx}>
{/* COL 1 : Logo opérateur */}
<div className="aff-classiques-departs__col-logo">
{isCarTER ? (
<div className="aff-classiques-departs__logo-box">
<img src="/type/sncf-voyageurs-logo.png" alt="SNCF Voyageurs" className="aff-classiques-departs__op-logo" />
</div>
) : (
<Logo
op={op}
classNameBox="aff-classiques-departs__logo-box"
classNameImg="aff-classiques-departs__op-logo"
classNameText="aff-classiques-departs__logo-text"
/>
)}
<img src={logoSrc} alt={typeLabel} className="aff-classiques-departs__op-logo" />
</div>
{/* COL 2 : Type + Numéro (4s) OU Statut (4s) */}
{/* COL 2 : Type + Numéro */}
<div className="aff-classiques-departs__col-meta">
{showStatus ? (
isSuppressed ? (
<div className="aff-classiques-departs__status-suppressed">supprimé</div>
) : isDelayed ? (
<div className="aff-classiques-departs__status-delayed">
<div className="aff-classiques-departs__status-label">retard</div>
<div className="aff-classiques-departs__status-value">{status.delayMinutes} min.</div>
</div>
) : (
<div className="aff-classiques-departs__status-ontime">à l'heure</div>
)
) : (
<>
<div className="aff-classiques-departs__train-type">{op}</div>
<div className="aff-classiques-departs__train-number">{trainNumber}</div>
</>
)}
<div className="aff-classiques-departs__train-type">{typeLabel}</div>
<div className="aff-classiques-departs__train-number">{numTrain}</div>
</div>
{/* COL 3 : Heure (grande, jaune) */}
@@ -1213,26 +1243,16 @@ export default function ClassiquesDeparts(){
<div className="aff-classiques-departs__time">{time}</div>
</div>
{/* COL 4 : Destination + Info cause (slide-in 5s/5s) */}
{/* COL 4 : Destination */}
<div className="aff-classiques-departs__col-destination">
<MarqueeText text={destination} className="aff-classiques-departs__destination" />
{showInfoCause && infoText ? (
<div className="aff-classiques-departs__info-wrapper">
<div className="aff-classiques-departs__info-inner">
<div className="aff-classiques-departs__separator">|</div>
<div className="aff-classiques-departs__info-text">{infoText}</div>
</div>
</div>
) : null}
<MarqueeText text={formattedDestination} className="aff-classiques-departs__destination" />
</div>
{/* COL 5 : Quai OU icône bus */}
{/* COL 5 : Quai / Voie */}
<div className="aff-classiques-departs__col-platform">
{isSub || isCarTER ? (
<img src="/type/track-car.svg" alt="Car" style={{ width: '70px', height: '70px' }} />
) : showPlatform ? (
{showPlatform ? (
<div className="aff-classiques-departs__platform-box">
<div className="aff-classiques-departs__platform-text">{platform}</div>
<div className="aff-classiques-departs__platform-text">{cleanPlatform}</div>
</div>
) : null}
</div>
@@ -1241,20 +1261,25 @@ export default function ClassiquesDeparts(){
);
};
const displayList = [...horaires.slice(0, 10)];
while (displayList.length < 10) {
displayList.push({ _isEmpty: true });
}
return (
<div className="aff-classiques-departs__container">
{/* Watermark "départs" — diagonal droite */}
<div className="aff-classiques-departs__watermark">départs</div>
{/* Watermark vertical "départs" sur la droite */}
<div className="aff-classiques-departs__sidebar" aria-hidden>
<div className="aff-classiques-departs__sidebar-text">départs</div>
</div>
{defilementMode ? (
/* ── MODE DÉFILEMENT : marquee vertical infini ── */
<div className="aff-classiques-departs__scroll-container">
{horaires.length === 0 ? (
<ul className="aff-classiques-departs__list">
<li className="aff-classiques-departs__row aff-classiques-departs__row--empty">
<div>Aucun horaire disponible.</div>
</li>
{displayList.map((h, idx) => renderRow(h, idx))}
</ul>
) : (
<ul
@@ -1267,26 +1292,10 @@ export default function ClassiquesDeparts(){
) : (
/* ── MODE NORMAL : 10 lignes statiques ── */
<ul className="aff-classiques-departs__list">
{horaires.length === 0 ? (
<li className="aff-classiques-departs__row aff-classiques-departs__row--empty" key="empty">
<div>Aucun horaire disponible.</div>
<div style={{fontSize:'0.8rem', opacity:0.8}}>lignes brutes renvoyées : {rawDebug.count}</div>
{rawDebug.count > 0 && rawDebug.sample && rawDebug.sample.length > 0 ? (
<pre style={{maxHeight:160, overflow:'auto', fontSize:'0.75rem', marginTop:8, background:'#111', color:'#fff', padding:8}}>
{JSON.stringify(rawDebug.sample.slice(0,2), null, 2).slice(0,1000)}
{JSON.stringify(rawDebug.sample.slice(0,2), null, 2).length > 1000 ? '…(truncated)' : ''}
</pre>
) : null}
</li>
) : horaires.map((h, idx) => renderRow(h, idx))}
{displayList.map((h, idx) => renderRow(h, idx))}
</ul>
)}
{/* Horloge — superposée en bas à droite */}
<div className="aff-classiques-departs__footer" aria-hidden>
<Clock />
</div>
</div>
)
}