V1.1.1 //
-> Patch du bug du logo de l'horaire pas affiché. Corrigé.
This commit is contained in:
1 parent
eb198cc727
commit
23828a54ef
10 files changed
+662
-428
No files matched your search
@@ -1,48 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
function patchFile(file) {
|
|
||||||
let content = fs.readFileSync(file, 'utf8');
|
|
||||||
|
|
||||||
// Change destructuring to include isCar as isCarMeta, etc.
|
|
||||||
content = content.replace(
|
|
||||||
/const { logoSrc, typeLabel, numTrain } = getTrainLogoAndMeta\(horaire\);/g,
|
|
||||||
"const { logoSrc, typeLabel, numTrain, isCar: metaIsCar, isSubstitution: metaIsSubstitution, brand: metaBrand } = getTrainLogoAndMeta(horaire);"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Patch the isCar definition
|
|
||||||
content = content.replace(
|
|
||||||
/const isCar = \([\s\S]*?\s*Boolean\(horaire\.is_substitution\) \|\|[\s\S]*?\s*isSubstitution\(horaire\)\n\t\);/,
|
|
||||||
"const isCar = metaIsCar || Boolean(horaire.is_substitution) || isSubstitution(horaire);\n\tconst isSub = metaIsSubstitution || Boolean(horaire.is_substitution) || isSubstitution(horaire);"
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the type column is rendered, we can apply a class based on metaBrand
|
|
||||||
// Example: <div className="aff-classiques-departs__type">
|
|
||||||
// We can add a data-brand={metaBrand} or just use style/class. Let's just modify the typeLabel styling slightly if needed.
|
|
||||||
// Actually, we also need to append "CAR DE REMPLACEMENT" to status if isSub is true.
|
|
||||||
|
|
||||||
// Let's replace `const status = horaire._status || computeStatusForHoraireAtStop(...)`
|
|
||||||
// No, let's just append to `showStatus` or similar where the marquee displays the cause.
|
|
||||||
|
|
||||||
// Let's find `<div className="aff-classiques-departs__type">` or similar
|
|
||||||
content = content.replace(
|
|
||||||
/<div className="aff-classiques-departs__type">/g,
|
|
||||||
`<div className={\`aff-classiques-departs__type \${metaBrand ? 'brand-' + metaBrand.toLowerCase().replace(/[^a-z]/g, '') : ''}\`}>`
|
|
||||||
);
|
|
||||||
|
|
||||||
content = content.replace(
|
|
||||||
/<div className="aff-classiques-arrivees__type">/g,
|
|
||||||
`<div className={\`aff-classiques-arrivees__type \${metaBrand ? 'brand-' + metaBrand.toLowerCase().replace(/[^a-z]/g, '') : ''}\`}>`
|
|
||||||
);
|
|
||||||
|
|
||||||
// To handle the marquee for "CAR DE REMPLACEMENT"
|
|
||||||
// find: `let infoText = infoCause || cause;`
|
|
||||||
content = content.replace(
|
|
||||||
/let infoText = infoCause \|\| cause;/g,
|
|
||||||
"let infoText = infoCause || cause;\n\t\tif (isSub) infoText = infoText ? 'CAR DE REMPLACEMENT - ' + infoText : 'CAR DE REMPLACEMENT';"
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.writeFileSync(file, content, 'utf8');
|
|
||||||
}
|
|
||||||
|
|
||||||
patchFile('src/pages/afficheurs/classiques/ClassiquesDeparts.js');
|
|
||||||
patchFile('src/pages/afficheurs/classiques/ClassiquesArrivees.js');
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
let content = fs.readFileSync('src/utils/trainLogoHelper.js', 'utf8');
|
|
||||||
|
|
||||||
if (!content.includes('import { resolveRegionByNumber }')) {
|
|
||||||
content = "import { resolveRegionByNumber } from './gtfsRegionalRanges';\n" + content;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetStart = " // Application du plan de num";
|
|
||||||
const startIndex = content.indexOf(targetStart);
|
|
||||||
if (startIndex !== -1) {
|
|
||||||
const newLogic = ` let isCar = opNorm.includes('car') || opNorm.includes('autocar') || opNorm.includes('bus');
|
|
||||||
let isSubstitution = false;
|
|
||||||
let brand = typeLabel;
|
|
||||||
|
|
||||||
// 4. Application du plan de numérotation déterministe depuis GTFS (nouveau moteur)
|
|
||||||
if ((typeLabel === 'TER' || typeLabel === 'AUTOCAR' || typeLabel === 'Car TER' || typeLabel === 'Car' || typeLabel === 'Autocar' || opRaw.toUpperCase().includes('CAR')) && numTrain) {
|
|
||||||
const regionMatch = resolveRegionByNumber(numTrain);
|
|
||||||
if (regionMatch) {
|
|
||||||
if (regionMatch.isCar) {
|
|
||||||
isCar = true;
|
|
||||||
typeLabel = 'Car TER'; // Forcer l'affichage CAR
|
|
||||||
}
|
|
||||||
if (regionMatch.isSubstitution) {
|
|
||||||
isSubstitution = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
brand = regionMatch.brand;
|
|
||||||
|
|
||||||
// Affiner le typeLabel selon la marque si train
|
|
||||||
if (!isCar) {
|
|
||||||
switch (brand) {
|
|
||||||
case 'FLUO': typeLabel = 'FLUO'; break;
|
|
||||||
case 'TER HDF': typeLabel = 'TER HDF'; break;
|
|
||||||
case 'NOMAD': typeLabel = 'NOMAD'; break;
|
|
||||||
case 'BREIZHGO': typeLabel = 'BREIZHGO'; break;
|
|
||||||
case 'ALEOP': typeLabel = 'ALÉOP'; break;
|
|
||||||
case 'REMI': typeLabel = 'RÉMI'; break;
|
|
||||||
case 'TER NA': typeLabel = 'TER NA'; break;
|
|
||||||
case 'LIO': typeLabel = 'LiO'; break;
|
|
||||||
case 'ZOU': typeLabel = 'ZOU !'; break;
|
|
||||||
case 'MOBIGO': typeLabel = 'MOBIGO'; break;
|
|
||||||
case 'TER AURA': typeLabel = 'TER AURA'; break;
|
|
||||||
default: typeLabel = 'TER';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tenter de charger le logo SVG de la marque si ce n'est pas un car
|
|
||||||
if (!isCar) {
|
|
||||||
const slugMatch = logoLookup.get(brand.toLowerCase().replace(/[^a-z]/g, ''));
|
|
||||||
if (slugMatch) {
|
|
||||||
matchedLogo = slugMatch;
|
|
||||||
} else if (brand === 'TER AURA') {
|
|
||||||
matchedLogo = logoLookup.get('terrhonealpes');
|
|
||||||
} else if (brand === 'TER NA') {
|
|
||||||
matchedLogo = logoLookup.get('teraquitaine');
|
|
||||||
} else if (brand === 'TER HDF') {
|
|
||||||
matchedLogo = logoLookup.get('hautsdefrance');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Pour les cars régionaux, on associe l'icône de car
|
|
||||||
matchedLogo = logoLookup.get('trackcar') || { file: 'track-car.svg', name: 'Car TER' };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const logoFile = (matchedLogo && matchedLogo.file) || (isCar ? 'track-car.svg' : 'sncf-voyageurs-logo.png');
|
|
||||||
const logoSrc = \`/type/\${logoFile}\`;
|
|
||||||
|
|
||||||
return { logoSrc, typeLabel, numTrain, isCar, isSubstitution, brand };
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
content = content.substring(0, startIndex) + newLogic;
|
|
||||||
fs.writeFileSync('src/utils/trainLogoHelper.js', content, 'utf8');
|
|
||||||
} else {
|
|
||||||
console.log('Target block not found');
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
"name": "Aleop"
|
"name": "Aleop"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "sncf-voyageurs-logo.png",
|
"file": "logo-auvergerhonealpes.svg",
|
||||||
"path": "/sncf-voyageurs-logo.png",
|
"path": "/logo-auvergerhonealpes.svg",
|
||||||
"slug": "auvergerhonealpes",
|
"slug": "auvergerhonealpes",
|
||||||
"name": "TER AURA"
|
"name": "TER AURA"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -435,14 +435,3 @@
|
|||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-lio { background-color: #e3001b !important; color: white !important; font-weight: bold; }
|
|
||||||
.brand-fluo { background-color: #f78bb8 !important; color: white !important; }
|
|
||||||
.brand-mobigo { background-color: #cfd82c !important; color: black !important; }
|
|
||||||
.brand-remi { background-color: #00874e !important; color: white !important; }
|
|
||||||
.brand-nomad { background-color: #6bb07a !important; color: white !important; }
|
|
||||||
.brand-breizhgo { background-color: #00c6d3 !important; color: black !important; }
|
|
||||||
.brand-aleop { background-color: #00b0ca !important; color: white !important; }
|
|
||||||
.brand-terna { background-color: #ea5b0c !important; color: white !important; }
|
|
||||||
.brand-teraura { background-color: #0077c8 !important; color: white !important; }
|
|
||||||
.brand-zou { background-color: #0055a4 !important; color: white !important; }
|
|
||||||
.brand-terhdf { background-color: #c9e000 !important; color: black !important; }
|
|
||||||
@@ -696,12 +696,17 @@ export default function ClassiquesArrivees({ defilement = null } = {}){
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
let inFlight = false; // empêche les fetchs concurrents
|
let inFlight = false; // empêche les fetchs concurrents
|
||||||
|
let pendingRefresh = false;
|
||||||
const source = getSourceFromPath(window.location.pathname);
|
const source = getSourceFromPath(window.location.pathname);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (inFlight) return;
|
if (inFlight) {
|
||||||
|
pendingRefresh = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
inFlight = true;
|
inFlight = true;
|
||||||
|
pendingRefresh = false;
|
||||||
|
|
||||||
if (source === 'sncf') {
|
if (source === 'sncf') {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -742,6 +747,10 @@ export default function ClassiquesArrivees({ defilement = null } = {}){
|
|||||||
} finally {
|
} finally {
|
||||||
if (mounted) setLoading(false);
|
if (mounted) setLoading(false);
|
||||||
inFlight = false;
|
inFlight = false;
|
||||||
|
if (pendingRefresh && mounted) {
|
||||||
|
pendingRefresh = false;
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1170,7 +1179,18 @@ export default function ClassiquesArrivees({ defilement = null } = {}){
|
|||||||
const intervalMs = 15000; // 15 secondes par défaut
|
const intervalMs = 15000; // 15 secondes par défaut
|
||||||
const intervalId = setInterval(fetchData, intervalMs);
|
const intervalId = setInterval(fetchData, intervalMs);
|
||||||
|
|
||||||
return () => { mounted = false; clearInterval(intervalId); };
|
const onRtUpdate = () => { if (mounted) fetchData(); };
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('sncf-realtime-updated', onRtUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
clearInterval(intervalId);
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.removeEventListener('sncf-realtime-updated', onRtUpdate);
|
||||||
|
}
|
||||||
|
};
|
||||||
}, [gareDisplay, gareParam, gareIdParam, parseTimeHM, buildPerturbMap, computeStatusForHoraireAtStop, findPerturbsForHoraireAndStop, normalizeName, formatTimeFromHM, applyPerturbationsToHoraires, debug, getStops, getPerturbationJours, defilementMode]);
|
}, [gareDisplay, gareParam, gareIdParam, parseTimeHM, buildPerturbMap, computeStatusForHoraireAtStop, findPerturbsForHoraireAndStop, normalizeName, formatTimeFromHM, applyPerturbationsToHoraires, debug, getStops, getPerturbationJours, defilementMode]);
|
||||||
|
|
||||||
// Helpers pour perturbations (memoized plus haut)
|
// Helpers pour perturbations (memoized plus haut)
|
||||||
@@ -1254,7 +1274,7 @@ export default function ClassiquesArrivees({ defilement = null } = {}){
|
|||||||
}
|
}
|
||||||
|
|
||||||
const horaire = h || {};
|
const horaire = h || {};
|
||||||
const { logoSrc, typeLabel, numTrain, isCar: metaIsCar, isSubstitution: metaIsSubstitution, brand: metaBrand } = getTrainLogoAndMeta(horaire);
|
const { logoSrc, typeLabel, numTrain, isCar: metaIsCar, isSubstitution: metaIsSubstitution, brand: metaBrand, brandClass } = getTrainLogoAndMeta(horaire, gareDisplay || gareParam);
|
||||||
const time = horaire._displayTime || formatTimeNoSeconds(horaire.heure_arrivee || horaire.arrival_time || horaire.time || horaire.heure || '');
|
const time = horaire._displayTime || formatTimeNoSeconds(horaire.heure_arrivee || horaire.arrival_time || horaire.time || horaire.heure || '');
|
||||||
const origin = horaire.gare_depart_nom || horaire.gare_depart || horaire.origine || '';
|
const origin = horaire.gare_depart_nom || horaire.gare_depart || horaire.origine || '';
|
||||||
const formattedOrigin = formatStationName(origin);
|
const formattedOrigin = formatStationName(origin);
|
||||||
@@ -1275,7 +1295,8 @@ export default function ClassiquesArrivees({ defilement = null } = {}){
|
|||||||
const status = horaire._status || computeStatusForHoraireAtStop(horaire, stationId, stationName, buildPerturbMap(perturbations));
|
const status = horaire._status || computeStatusForHoraireAtStop(horaire, stationId, stationName, buildPerturbMap(perturbations));
|
||||||
const isSuppressed = status && status.label === 'supprimé';
|
const isSuppressed = status && status.label === 'supprimé';
|
||||||
const isDelayed = status && status.label && String(status.label).startsWith('retardé');
|
const isDelayed = status && status.label && String(status.label).startsWith('retardé');
|
||||||
const isCar = (
|
const isSub = metaIsSubstitution || Boolean(horaire.is_substitution);
|
||||||
|
const isCar = metaIsCar || (
|
||||||
(horaire.type_train && (horaire.type_train.toUpperCase().includes('AUTOCAR') || horaire.type_train.toUpperCase().includes('CAR TER') || horaire.type_train.toUpperCase() === 'BUS')) ||
|
(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.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')) ||
|
(horaire.type && (horaire.type.toUpperCase().includes('AUTOCAR') || horaire.type.toUpperCase().includes('CAR TER') || horaire.type.toUpperCase() === 'BUS')) ||
|
||||||
@@ -1335,7 +1356,7 @@ export default function ClassiquesArrivees({ defilement = null } = {}){
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="aff-classiques-arrivees__col-destination">
|
<div className="aff-classiques-arrivees__col-destination">
|
||||||
<MarqueeText text={formattedOrigin} className="aff-classiques-arrivees__destination" />
|
<MarqueeText text={isSub ? `[CAR DE REMPLACEMENT] ${formattedOrigin}` : formattedOrigin} className="aff-classiques-arrivees__destination" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="aff-classiques-arrivees__col-platform">
|
<div className="aff-classiques-arrivees__col-platform">
|
||||||
@@ -1359,7 +1380,12 @@ export default function ClassiquesArrivees({ defilement = null } = {}){
|
|||||||
speed={50}
|
speed={50}
|
||||||
text={
|
text={
|
||||||
<span className="aff-classiques-arrivees__stops-chain">
|
<span className="aff-classiques-arrivees__stops-chain">
|
||||||
{stopsList.map((stopName, sIdx) => (
|
{isSub && (
|
||||||
|
<span className="aff-classiques-arrivees__stop-item" style={{ color: '#ffd200', fontWeight: 'bold' }}>
|
||||||
|
CAR DE REMPLACEMENT
|
||||||
|
<span className="aff-classiques-arrivees__stop-bullet">•</span>
|
||||||
|
</span>
|
||||||
|
)}{stopsList.map((stopName, sIdx) => (
|
||||||
<span key={sIdx} className="aff-classiques-arrivees__stop-item">
|
<span key={sIdx} className="aff-classiques-arrivees__stop-item">
|
||||||
{sIdx > 0 && <span className="aff-classiques-arrivees__stop-bullet">•</span>}
|
{sIdx > 0 && <span className="aff-classiques-arrivees__stop-bullet">•</span>}
|
||||||
<span className="aff-classiques-arrivees__stop-name">{stopName}</span>
|
<span className="aff-classiques-arrivees__stop-name">{stopName}</span>
|
||||||
|
|||||||
@@ -435,14 +435,3 @@
|
|||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-lio { background-color: #e3001b !important; color: white !important; font-weight: bold; }
|
|
||||||
.brand-fluo { background-color: #f78bb8 !important; color: white !important; }
|
|
||||||
.brand-mobigo { background-color: #cfd82c !important; color: black !important; }
|
|
||||||
.brand-remi { background-color: #00874e !important; color: white !important; }
|
|
||||||
.brand-nomad { background-color: #6bb07a !important; color: white !important; }
|
|
||||||
.brand-breizhgo { background-color: #00c6d3 !important; color: black !important; }
|
|
||||||
.brand-aleop { background-color: #00b0ca !important; color: white !important; }
|
|
||||||
.brand-terna { background-color: #ea5b0c !important; color: white !important; }
|
|
||||||
.brand-teraura { background-color: #0077c8 !important; color: white !important; }
|
|
||||||
.brand-zou { background-color: #0055a4 !important; color: white !important; }
|
|
||||||
.brand-terhdf { background-color: #c9e000 !important; color: black !important; }
|
|
||||||
@@ -696,12 +696,17 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
let inFlight = false; // empêche les fetchs concurrents
|
let inFlight = false; // empêche les fetchs concurrents
|
||||||
|
let pendingRefresh = false;
|
||||||
const source = getSourceFromPath(window.location.pathname);
|
const source = getSourceFromPath(window.location.pathname);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (inFlight) return;
|
if (inFlight) {
|
||||||
|
pendingRefresh = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
inFlight = true;
|
inFlight = true;
|
||||||
|
pendingRefresh = false;
|
||||||
|
|
||||||
if (source === 'sncf') {
|
if (source === 'sncf') {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -741,6 +746,10 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
|||||||
} finally {
|
} finally {
|
||||||
if (mounted) setLoading(false);
|
if (mounted) setLoading(false);
|
||||||
inFlight = false;
|
inFlight = false;
|
||||||
|
if (pendingRefresh && mounted) {
|
||||||
|
pendingRefresh = false;
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1169,7 +1178,18 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
|||||||
const intervalMs = 15000; // 15 secondes par défaut
|
const intervalMs = 15000; // 15 secondes par défaut
|
||||||
const intervalId = setInterval(fetchData, intervalMs);
|
const intervalId = setInterval(fetchData, intervalMs);
|
||||||
|
|
||||||
return () => { mounted = false; clearInterval(intervalId); };
|
const onRtUpdate = () => { if (mounted) fetchData(); };
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('sncf-realtime-updated', onRtUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
clearInterval(intervalId);
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.removeEventListener('sncf-realtime-updated', onRtUpdate);
|
||||||
|
}
|
||||||
|
};
|
||||||
}, [gareDisplay, gareParam, gareIdParam, parseTimeHM, buildPerturbMap, computeStatusForHoraireAtStop, findPerturbsForHoraireAndStop, normalizeName, formatTimeFromHM, applyPerturbationsToHoraires, debug, getStops, getPerturbationJours, defilementMode]);
|
}, [gareDisplay, gareParam, gareIdParam, parseTimeHM, buildPerturbMap, computeStatusForHoraireAtStop, findPerturbsForHoraireAndStop, normalizeName, formatTimeFromHM, applyPerturbationsToHoraires, debug, getStops, getPerturbationJours, defilementMode]);
|
||||||
|
|
||||||
// Helpers pour perturbations (memoized plus haut)
|
// Helpers pour perturbations (memoized plus haut)
|
||||||
@@ -1253,7 +1273,7 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
|||||||
}
|
}
|
||||||
|
|
||||||
const horaire = h || {};
|
const horaire = h || {};
|
||||||
const { logoSrc, typeLabel, numTrain, isCar: metaIsCar, isSubstitution: metaIsSubstitution, brand: metaBrand } = getTrainLogoAndMeta(horaire);
|
const { logoSrc, typeLabel, numTrain, isCar: metaIsCar, isSubstitution: metaIsSubstitution, brand: metaBrand, brandClass } = getTrainLogoAndMeta(horaire, gareDisplay || gareParam);
|
||||||
const time = horaire._displayTime || formatTimeNoSeconds(horaire.heure_depart || horaire.departure_time || horaire.time || horaire.heure || '');
|
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 destination = horaire.gare_arrivee_nom || horaire.gare_arrivee || horaire.destination || '';
|
||||||
const formattedDestination = formatStationName(destination);
|
const formattedDestination = formatStationName(destination);
|
||||||
@@ -1274,7 +1294,7 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
|||||||
const status = horaire._status || computeStatusForHoraireAtStop(horaire, stationId, stationName, buildPerturbMap(perturbations));
|
const status = horaire._status || computeStatusForHoraireAtStop(horaire, stationId, stationName, buildPerturbMap(perturbations));
|
||||||
const isSuppressed = status && status.label === 'supprimé';
|
const isSuppressed = status && status.label === 'supprimé';
|
||||||
const isDelayed = status && status.label && String(status.label).startsWith('retardé');
|
const isDelayed = status && status.label && String(status.label).startsWith('retardé');
|
||||||
const isCar = (
|
const isCar = metaIsCar || (
|
||||||
(horaire.type_train && (horaire.type_train.toUpperCase().includes('AUTOCAR') || horaire.type_train.toUpperCase().includes('CAR TER') || horaire.type_train.toUpperCase() === 'BUS')) ||
|
(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.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')) ||
|
(horaire.type && (horaire.type.toUpperCase().includes('AUTOCAR') || horaire.type.toUpperCase().includes('CAR TER') || horaire.type.toUpperCase() === 'BUS')) ||
|
||||||
@@ -1288,6 +1308,7 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
|||||||
cleanPlatform === 'CAR' ||
|
cleanPlatform === 'CAR' ||
|
||||||
cleanPlatform === 'BUS'
|
cleanPlatform === 'BUS'
|
||||||
);
|
);
|
||||||
|
const isSub = metaIsSubstitution || Boolean(horaire.is_substitution);
|
||||||
const showPlatform = !isSuppressed && (isCar || Boolean(cleanPlatform));
|
const showPlatform = !isSuppressed && (isCar || Boolean(cleanPlatform));
|
||||||
|
|
||||||
const stopsList = isExtended ? getStopsListForDeparts(horaire, gareDisplay || gareParam, normalizeName) : [];
|
const stopsList = isExtended ? getStopsListForDeparts(horaire, gareDisplay || gareParam, normalizeName) : [];
|
||||||
@@ -1334,7 +1355,7 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="aff-classiques-departs__col-destination">
|
<div className="aff-classiques-departs__col-destination">
|
||||||
<MarqueeText text={formattedDestination} className="aff-classiques-departs__destination" />
|
<MarqueeText text={isSub ? `[CAR DE REMPLACEMENT] ${formattedDestination}` : formattedDestination} className="aff-classiques-departs__destination" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="aff-classiques-departs__col-platform">
|
<div className="aff-classiques-departs__col-platform">
|
||||||
@@ -1358,7 +1379,12 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
|||||||
speed={50}
|
speed={50}
|
||||||
text={
|
text={
|
||||||
<span className="aff-classiques-departs__stops-chain">
|
<span className="aff-classiques-departs__stops-chain">
|
||||||
{stopsList.map((stopName, sIdx) => (
|
{isSub && (
|
||||||
|
<span className="aff-classiques-departs__stop-item" style={{ color: '#ffd200', fontWeight: 'bold' }}>
|
||||||
|
CAR DE REMPLACEMENT
|
||||||
|
<span className="aff-classiques-departs__stop-bullet">•</span>
|
||||||
|
</span>
|
||||||
|
)}{stopsList.map((stopName, sIdx) => (
|
||||||
<span key={sIdx} className="aff-classiques-departs__stop-item">
|
<span key={sIdx} className="aff-classiques-departs__stop-item">
|
||||||
{sIdx > 0 && <span className="aff-classiques-departs__stop-bullet">•</span>}
|
{sIdx > 0 && <span className="aff-classiques-departs__stop-bullet">•</span>}
|
||||||
<span className="aff-classiques-departs__stop-name">{stopName}</span>
|
<span className="aff-classiques-departs__stop-name">{stopName}</span>
|
||||||
|
|||||||
+150
-54
@@ -1,3 +1,62 @@
|
|||||||
|
|
||||||
|
let isRealtimeRefreshing = false;
|
||||||
|
|
||||||
|
// Fonctions de persistance du cache temps réel dans sessionStorage
|
||||||
|
function saveRealtimeCacheToStorage() {
|
||||||
|
if (typeof window === 'undefined' || !window.sessionStorage) return;
|
||||||
|
try {
|
||||||
|
if (siriLiteCache.platforms && siriLiteCache.platforms.size > 0) {
|
||||||
|
const arr = Array.from(siriLiteCache.platforms.entries()).slice(0, 4000);
|
||||||
|
window.sessionStorage.setItem('sncf_siri_platforms', JSON.stringify({
|
||||||
|
ts: siriLiteCache.timestamp,
|
||||||
|
data: arr
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreRealtimeCacheFromStorage() {
|
||||||
|
if (typeof window === 'undefined' || !window.sessionStorage) return;
|
||||||
|
try {
|
||||||
|
const raw = window.sessionStorage.getItem('sncf_siri_platforms');
|
||||||
|
if (raw) {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
if (parsed && Array.isArray(parsed.data) && (Date.now() - parsed.ts < 900000)) {
|
||||||
|
siriLiteCache.timestamp = parsed.ts;
|
||||||
|
siriLiteCache.platforms = new Map(parsed.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function triggerRealtimeBackgroundRefresh() {
|
||||||
|
if (isRealtimeRefreshing) return;
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
const needRt = (now - gtfsRtCache.timestamp > 30000) || gtfsRtCache.updates.size === 0;
|
||||||
|
const needSiri = (now - siriLiteCache.timestamp > 60000) || siriLiteCache.platforms.size === 0;
|
||||||
|
const needSx = (now - situationExchangeCache.timestamp > 60000) || situationExchangeCache.platforms.size === 0;
|
||||||
|
|
||||||
|
if (!needRt && !needSiri && !needSx) return;
|
||||||
|
|
||||||
|
isRealtimeRefreshing = true;
|
||||||
|
try {
|
||||||
|
const promises = [];
|
||||||
|
if (needRt) promises.push(fetchGtfsRealtimeData());
|
||||||
|
if (needSiri) promises.push(fetchSiriLiteData());
|
||||||
|
if (needSx) promises.push(fetchSituationExchangeData());
|
||||||
|
|
||||||
|
await Promise.allSettled(promises);
|
||||||
|
} catch (_) {
|
||||||
|
// ignore
|
||||||
|
} finally {
|
||||||
|
isRealtimeRefreshing = false;
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.dispatchEvent(new CustomEvent('sncf-realtime-updated'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// Service d'accès aux données SNCF (GTFS / GTFS-RT / SIRI Lite / Open Data SNCF)
|
// Service d'accès aux données SNCF (GTFS / GTFS-RT / SIRI Lite / Open Data SNCF)
|
||||||
import { transit_realtime } from 'gtfs-realtime-bindings';
|
import { transit_realtime } from 'gtfs-realtime-bindings';
|
||||||
@@ -9,8 +68,15 @@ const SNCF_SIRI_LITE_TIMETABLE = 'https://proxy.transport.data.gouv.fr/resource/
|
|||||||
const SNCF_SIRI_LITE_SITUATION_EXCHANGE = 'https://proxy.transport.data.gouv.fr/resource/sncf-siri-lite-situation-exchange';
|
const SNCF_SIRI_LITE_SITUATION_EXCHANGE = 'https://proxy.transport.data.gouv.fr/resource/sncf-siri-lite-situation-exchange';
|
||||||
const NAVITIA_TOKEN = (typeof process !== 'undefined' && process.env && process.env.REACT_APP_NAVITIA_TOKEN) || 'bf06df97-2ff1-4560-b74c-f127cb70764d';
|
const NAVITIA_TOKEN = (typeof process !== 'undefined' && process.env && process.env.REACT_APP_NAVITIA_TOKEN) || 'bf06df97-2ff1-4560-b74c-f127cb70764d';
|
||||||
|
|
||||||
// TTL de rafraîchissement des flux temps réel SNCF : 2 minutes (120 secondes)
|
// Détection du mode développement pour accélération maximale
|
||||||
const REALTIME_CACHE_TTL = 120000;
|
const isDev = (typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'development') ||
|
||||||
|
(typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'));
|
||||||
|
|
||||||
|
// TTL de rafraîchissement des flux temps réel SNCF : 5 minutes en dev, 2 minutes en prod
|
||||||
|
const REALTIME_CACHE_TTL = isDev ? 300000 : 120000;
|
||||||
|
|
||||||
|
// Cache mémoire des données statiques des gares (chargement instantané en dev)
|
||||||
|
const stationJsonCache = new Map();
|
||||||
|
|
||||||
// Cache mémoire pour le flux GTFS-RT (TTL = 2 min)
|
// Cache mémoire pour le flux GTFS-RT (TTL = 2 min)
|
||||||
let gtfsRtCache = {
|
let gtfsRtCache = {
|
||||||
@@ -35,7 +101,7 @@ let situationExchangeCache = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Cache mémoire pour l'API OpenData SNCF (TTL = 2 min)
|
// Cache mémoire pour l'API OpenData SNCF (TTL = 2 min)
|
||||||
const openDataPlatformsCache = new Map();
|
restoreRealtimeCacheFromStorage();
|
||||||
|
|
||||||
const MAJOR_HUBS = [
|
const MAJOR_HUBS = [
|
||||||
'paris', 'lyon', 'dijon', 'marseille', 'lille', 'strasbourg', 'bordeaux',
|
'paris', 'lyon', 'dijon', 'marseille', 'lille', 'strasbourg', 'bordeaux',
|
||||||
@@ -79,7 +145,7 @@ export function resolveNominalTrack(stationName, trainNum, uic = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper fetch avec timeout pour ne jamais bloquer l'affichage en cas de réseau lent ou de blocage
|
// Helper fetch avec timeout pour ne jamais bloquer l'affichage en cas de réseau lent ou de blocage
|
||||||
async function fetchWithTimeout(url, options = {}, timeoutMs = 25000) {
|
async function fetchWithTimeout(url, options = {}, timeoutMs = 15000) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const id = setTimeout(() => controller.abort(), timeoutMs);
|
const id = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
try {
|
try {
|
||||||
@@ -156,7 +222,7 @@ async function fetchSiriLiteData() {
|
|||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/xml, text/xml, */*'
|
'Accept': 'application/xml, text/xml, */*'
|
||||||
}
|
}
|
||||||
}, 25000);
|
}, 35000);
|
||||||
|
|
||||||
if (res && res.ok) {
|
if (res && res.ok) {
|
||||||
const xml = await res.text();
|
const xml = await res.text();
|
||||||
@@ -263,11 +329,14 @@ async function fetchSiriLiteData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
siriLiteCache = {
|
if (platformsMap.size > 0 || updatesMap.size > 0) {
|
||||||
timestamp: now,
|
siriLiteCache = {
|
||||||
platforms: platformsMap,
|
timestamp: now,
|
||||||
updates: updatesMap
|
platforms: platformsMap,
|
||||||
};
|
updates: updatesMap
|
||||||
|
};
|
||||||
|
saveRealtimeCacheToStorage();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[sncfApi] Notice: SIRI-Lite Estimated Timetable refresh error/timeout:', err);
|
console.warn('[sncfApi] Notice: SIRI-Lite Estimated Timetable refresh error/timeout:', err);
|
||||||
}
|
}
|
||||||
@@ -294,7 +363,7 @@ async function fetchSituationExchangeData() {
|
|||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/xml, text/xml, */*'
|
'Accept': 'application/xml, text/xml, */*'
|
||||||
}
|
}
|
||||||
}, 12000);
|
}, 20000);
|
||||||
|
|
||||||
if (res && res.ok) {
|
if (res && res.ok) {
|
||||||
const xml = await res.text();
|
const xml = await res.text();
|
||||||
@@ -388,12 +457,14 @@ async function fetchSituationExchangeData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
situationExchangeCache = {
|
if (platformsMap.size > 0 || updatesMap.size > 0 || alertsList.length > 0) {
|
||||||
timestamp: now,
|
situationExchangeCache = {
|
||||||
platforms: platformsMap,
|
timestamp: now,
|
||||||
updates: updatesMap,
|
platforms: platformsMap,
|
||||||
alerts: alertsList
|
updates: updatesMap,
|
||||||
};
|
alerts: alertsList
|
||||||
|
};
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[sncfApi] Notice: SIRI-Lite Situation Exchange error/timeout:', err);
|
console.warn('[sncfApi] Notice: SIRI-Lite Situation Exchange error/timeout:', err);
|
||||||
}
|
}
|
||||||
@@ -424,8 +495,8 @@ async function fetchGtfsRealtimeData() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const [rtRes, alertsRes] = await Promise.allSettled([
|
const [rtRes, alertsRes] = await Promise.allSettled([
|
||||||
fetchWithTimeout(SNCF_GTFS_RT_TRIP_UPDATES, {}, 6000),
|
fetchWithTimeout(SNCF_GTFS_RT_TRIP_UPDATES, {}, 15000),
|
||||||
fetchWithTimeout(SNCF_GTFS_RT_SERVICE_ALERTS, {}, 6000)
|
fetchWithTimeout(SNCF_GTFS_RT_SERVICE_ALERTS, {}, 15000)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 1. Décodage Trip Updates (Retards, Suppressions)
|
// 1. Décodage Trip Updates (Retards, Suppressions)
|
||||||
@@ -462,8 +533,26 @@ async function fetchGtfsRealtimeData() {
|
|||||||
stopUpdates: stopUpdates
|
stopUpdates: stopUpdates
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cTrainNo = cleanNum(trainNo);
|
||||||
if (tid) updatesMap.set(tid, updateObj);
|
if (tid) updatesMap.set(tid, updateObj);
|
||||||
if (trainNo) updatesMap.set(trainNo, updateObj);
|
if (trainNo) updatesMap.set(trainNo, updateObj);
|
||||||
|
if (cTrainNo) updatesMap.set(cTrainNo, updateObj);
|
||||||
|
|
||||||
|
stopUpdates.forEach(stu => {
|
||||||
|
const stopId = String(stu.stopId || '').replace(/\D/g, '');
|
||||||
|
if (stopId) {
|
||||||
|
const stopDelay = stu.departure && typeof stu.departure.delay === 'number'
|
||||||
|
? stu.departure.delay
|
||||||
|
: (stu.arrival && typeof stu.arrival.delay === 'number' ? stu.arrival.delay : maxDelaySec);
|
||||||
|
const stopDelayMin = Math.max(0, Math.round(stopDelay / 60));
|
||||||
|
const stopUpdateObj = {
|
||||||
|
...updateObj,
|
||||||
|
delayMinutes: stopDelayMin
|
||||||
|
};
|
||||||
|
if (trainNo) updatesMap.set(`${trainNo}_${stopId}`, stopUpdateObj);
|
||||||
|
if (cTrainNo) updatesMap.set(`${cTrainNo}_${stopId}`, stopUpdateObj);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -489,11 +578,13 @@ async function fetchGtfsRealtimeData() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
gtfsRtCache = {
|
if (updatesMap.size > 0 || alertsList.length > 0) {
|
||||||
timestamp: now,
|
gtfsRtCache = {
|
||||||
updates: updatesMap,
|
timestamp: now,
|
||||||
alerts: alertsList
|
updates: updatesMap,
|
||||||
};
|
alerts: alertsList
|
||||||
|
};
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[sncfApi] Notice: GTFS-RT stream refresh error:', err);
|
console.warn('[sncfApi] Notice: GTFS-RT stream refresh error:', err);
|
||||||
}
|
}
|
||||||
@@ -578,36 +669,35 @@ async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
|||||||
const publicUrl = (typeof process !== 'undefined' && process.env && process.env.PUBLIC_URL) ? process.env.PUBLIC_URL : '';
|
const publicUrl = (typeof process !== 'undefined' && process.env && process.env.PUBLIC_URL) ? process.env.PUBLIC_URL : '';
|
||||||
const stationUrl = `${publicUrl}/data/stations/${uic}.json`;
|
const stationUrl = `${publicUrl}/data/stations/${uic}.json`;
|
||||||
|
|
||||||
// Chargement parallèle tolérant aux pannes réseau / CORS (OpenData SNCF, SIRI-Lite Estimated Timetable, SIRI-Lite Situation Exchange, GTFS-RT)
|
// Récupération avec cache mémoire immédiat pour affichage instantané en dev
|
||||||
const [jsonRes, rtData, siriData, sxData, openDataPlatforms] = await Promise.all([
|
let data = stationJsonCache.get(String(uic));
|
||||||
fetch(stationUrl).catch(err => {
|
if (!data) {
|
||||||
|
const jsonRes = await fetch(stationUrl).catch(err => {
|
||||||
console.warn(`[sncfApi] Notice: fetch station ${uic} error:`, err);
|
console.warn(`[sncfApi] Notice: fetch station ${uic} error:`, err);
|
||||||
return null;
|
return null;
|
||||||
}),
|
});
|
||||||
fetchGtfsRealtimeData().catch(err => {
|
if (jsonRes && jsonRes.ok) {
|
||||||
console.warn('[sncfApi] Notice: GTFS-RT error:', err);
|
data = await jsonRes.json();
|
||||||
return { updates: new Map(), alerts: [] };
|
stationJsonCache.set(String(uic), data);
|
||||||
}),
|
}
|
||||||
fetchSiriLiteData().catch(err => {
|
}
|
||||||
console.warn('[sncfApi] Notice: SIRI-Lite Timetable error:', err);
|
|
||||||
return { platforms: new Map(), updates: new Map() };
|
|
||||||
}),
|
|
||||||
fetchSituationExchangeData().catch(err => {
|
|
||||||
console.warn('[sncfApi] Notice: SIRI-Lite Situation Exchange error:', err);
|
|
||||||
return { platforms: new Map(), updates: new Map(), alerts: [] };
|
|
||||||
}),
|
|
||||||
fetchOpenDataSncfPlatforms(uic, mode).catch(err => {
|
|
||||||
console.warn('[sncfApi] Notice: OpenData SNCF platform error:', err);
|
|
||||||
return new Map();
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!jsonRes || !jsonRes.ok) {
|
if (!data) {
|
||||||
console.warn(`[sncfApi] Fichier de gare introuvable : ${stationUrl} (statut : ${jsonRes ? jsonRes.status : 'réseau bloqué'})`);
|
console.warn(`[sncfApi] Fichier de gare introuvable : ${stationUrl}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await jsonRes.json();
|
// Utilisation immédiate du cache temps réel & déclenchement non-bloquant en tâche de fond
|
||||||
|
const rtPromise = triggerRealtimeBackgroundRefresh();
|
||||||
|
if (gtfsRtCache.updates.size === 0 && siriLiteCache.platforms.size === 0) {
|
||||||
|
await Promise.race([
|
||||||
|
rtPromise,
|
||||||
|
new Promise(resolve => setTimeout(resolve, 800))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
const rtData = gtfsRtCache || { updates: new Map(), alerts: [] };
|
||||||
|
const siriData = siriLiteCache || { platforms: new Map(), updates: new Map() };
|
||||||
|
const sxData = situationExchangeCache || { platforms: new Map(), updates: new Map(), alerts: [] };
|
||||||
const gareRef = findGtfsGare(uic);
|
const gareRef = findGtfsGare(uic);
|
||||||
const targetStationNom = (gareRef && gareRef.nom) || (data && data.nom) || '';
|
const targetStationNom = (gareRef && gareRef.nom) || (data && data.nom) || '';
|
||||||
const rawList = mode === 'arrivees' ? data.arrivees : data.departs;
|
const rawList = mode === 'arrivees' ? data.arrivees : data.departs;
|
||||||
@@ -699,11 +789,6 @@ async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
|||||||
(uicClean && cNum && sxData.platforms.get(`${uicClean}_${cNum}`)) ||
|
(uicClean && cNum && sxData.platforms.get(`${uicClean}_${cNum}`)) ||
|
||||||
(cNum && cName && sxData.platforms.get(`${cNum}_${cName}`)) ||
|
(cNum && cName && sxData.platforms.get(`${cNum}_${cName}`)) ||
|
||||||
(rawNum && cName && sxData.platforms.get(`${rawNum}_${cName}`)) ||
|
(rawNum && cName && sxData.platforms.get(`${rawNum}_${cName}`)) ||
|
||||||
// 3. OpenData SNCF direct
|
|
||||||
(cNum && uicClean && openDataPlatforms.get(`${cNum}_${uicClean}`)) ||
|
|
||||||
(rawNum && uicClean && openDataPlatforms.get(`${rawNum}_${uicClean}`)) ||
|
|
||||||
(cNum && cName && openDataPlatforms.get(`${cNum}_${cName}`)) ||
|
|
||||||
(cNum && cDest && openDataPlatforms.get(`${cNum}_${cDest}`)) ||
|
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -713,8 +798,19 @@ async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
|||||||
const rawTime = mode === 'arrivees' ? (t.arr || t.dep) : (t.dep || t.arr);
|
const rawTime = mode === 'arrivees' ? (t.arr || t.dep) : (t.dep || t.arr);
|
||||||
const timeHM = parseTimeHM(rawTime) || 0;
|
const timeHM = parseTimeHM(rawTime) || 0;
|
||||||
|
|
||||||
|
const stopUicClean = String(uic || '').replace(/\D/g, '');
|
||||||
|
const stopUic7 = (stopUicClean.length === 8 && stopUicClean.startsWith('87')) ? stopUicClean.slice(2) : '';
|
||||||
|
const cTrainNum = cleanNum(t.num);
|
||||||
|
|
||||||
// Recherche de mise à jour GTFS-RT, SIRI-Lite Timetable ou SIRI-Lite Situation Exchange
|
// Recherche de mise à jour GTFS-RT, SIRI-Lite Timetable ou SIRI-Lite Situation Exchange
|
||||||
const rt = !isTomorrow ? (rtData.updates.get(t.trip_id) || (t.num ? rtData.updates.get(t.num) : null) || (cleanNum(t.num) ? rtData.updates.get(cleanNum(t.num)) : null)) : null;
|
const rt = !isTomorrow ? (
|
||||||
|
(stopUicClean && cTrainNum && rtData.updates.get(`${cTrainNum}_${stopUicClean}`)) ||
|
||||||
|
(stopUicClean && t.num && rtData.updates.get(`${t.num}_${stopUicClean}`)) ||
|
||||||
|
(stopUic7 && cTrainNum && rtData.updates.get(`${cTrainNum}_${stopUic7}`)) ||
|
||||||
|
rtData.updates.get(t.trip_id) ||
|
||||||
|
(t.num ? rtData.updates.get(t.num) : null) ||
|
||||||
|
(cTrainNum ? rtData.updates.get(cTrainNum) : null)
|
||||||
|
) : null;
|
||||||
const siri = !isTomorrow ? (siriData.updates.get(t.num) || (cleanNum(t.num) ? siriData.updates.get(cleanNum(t.num)) : null) || (t.trip_id ? siriData.updates.get(t.trip_id) : null)) : null;
|
const siri = !isTomorrow ? (siriData.updates.get(t.num) || (cleanNum(t.num) ? siriData.updates.get(cleanNum(t.num)) : null) || (t.trip_id ? siriData.updates.get(t.trip_id) : null)) : null;
|
||||||
const sx = !isTomorrow ? (sxData.updates.get(t.num) || (cleanNum(t.num) ? sxData.updates.get(cleanNum(t.num)) : null) || (t.trip_id ? sxData.updates.get(t.trip_id) : null)) : null;
|
const sx = !isTomorrow ? (sxData.updates.get(t.num) || (cleanNum(t.num) ? sxData.updates.get(cleanNum(t.num)) : null) || (t.trip_id ? sxData.updates.get(t.trip_id) : null)) : null;
|
||||||
|
|
||||||
|
|||||||
+264
-52
@@ -1,62 +1,274 @@
|
|||||||
export const REGIONAL_RANGES = [
|
/**
|
||||||
// --- CARS DE SUBSTITUTION & CARS REGULIERS (Route type 2 & 3) ---
|
* Base de données exhaustive des plages de numérotation GTFS SNCF
|
||||||
{ min: 404000, max: 412999, regionCode: 'AURA', brand: 'TER', isCar: true, isSubstitution: true },
|
* Source : GTFS SNCF Open Data (feed valide 02/10/2026 au 31/03/2027)
|
||||||
{ min: 415035, max: 424403, regionCode: 'BFC', brand: 'MOBIGO', isCar: true, isSubstitution: true },
|
*
|
||||||
{ min: 421001, max: 422112, regionCode: 'BRE', brand: 'BREIZHGO', isCar: true, isSubstitution: true },
|
* 1. Cars TER Réguliers (route_type = 3)
|
||||||
{ min: 424020, max: 426502, regionCode: 'CVL', brand: 'REMI', isCar: true, isSubstitution: true },
|
* 2. Cars de Substitution Travaux (route_type = 2)
|
||||||
{ min: 427250, max: 435837, regionCode: 'GE', brand: 'FLUO', isCar: true, isSubstitution: true },
|
* 3. Trains TER (route_type = 2)
|
||||||
{ min: 436010, max: 439537, regionCode: 'HDF', brand: 'TER', isCar: true, isSubstitution: true },
|
*/
|
||||||
{ min: 442108, max: 447248, regionCode: 'NOR', brand: 'NOMAD', isCar: true, isSubstitution: true },
|
|
||||||
{ min: 448014, max: 455684, regionCode: 'NA', brand: 'TER', isCar: true, isSubstitution: true },
|
|
||||||
{ min: 412062, max: 412494, regionCode: 'OCC', brand: 'LIO', isCar: true, isSubstitution: true },
|
|
||||||
{ min: 455005, max: 462221, regionCode: 'OCC', brand: 'LIO', isCar: true, isSubstitution: true },
|
|
||||||
{ min: 464040, max: 464140, regionCode: 'PDL', brand: 'ALEOP', isCar: true, isSubstitution: true },
|
|
||||||
{ min: 466752, max: 468300, regionCode: 'PACA', brand: 'ZOU', isCar: true, isSubstitution: true },
|
|
||||||
|
|
||||||
// Cars TER réguliers 5 chiffres
|
export const REGIONS_META = {
|
||||||
{ min: 30131, max: 31478, regionCode: 'AURA', brand: 'TER', isCar: true, isSubstitution: false },
|
AURA: { code: 'AURA', name: 'Auvergne-Rhône-Alpes', brand: 'TER AURA', logo: 'logo-auvergerhonealpes.svg', color: '#0077c8', cssClass: 'brand-teraura' },
|
||||||
{ min: 33155, max: 35072, regionCode: 'AURA', brand: 'TER', isCar: true, isSubstitution: false },
|
BFC: { code: 'BFC', name: 'Bourgogne-Franche-Comté', brand: 'MOBIGO', logo: 'logo-mobigo.svg', color: '#cfd82c', cssClass: 'brand-mobigo' },
|
||||||
{ min: 35000, max: 37529, regionCode: 'BFC', brand: 'MOBIGO', isCar: true, isSubstitution: false },
|
BRE: { code: 'BRE', name: 'Bretagne', brand: 'BREIZHGO', logo: 'logo-breizhgo.svg', color: '#00c6d3', cssClass: 'brand-breizhgo' },
|
||||||
{ min: 43001, max: 45141, regionCode: 'GE', brand: 'FLUO', isCar: true, isSubstitution: false },
|
CVL: { code: 'CVL', name: 'Centre-Val de Loire', brand: 'RÉMI', logo: 'logo-remi.svg', color: '#00874e', cssClass: 'brand-remi' },
|
||||||
{ min: 68130, max: 68283, regionCode: 'GE', brand: 'FLUO', isCar: true, isSubstitution: false },
|
GE: { code: 'GE', name: 'Grand Est', brand: 'FLUO', logo: 'logo-fluo.svg', color: '#f78bb8', cssClass: 'brand-fluo' },
|
||||||
|
HDF: { code: 'HDF', name: 'Hauts-de-France', brand: 'TER HDF', logo: 'logo-hautsdefrance.svg', color: '#c9e000', cssClass: 'brand-terhdf' },
|
||||||
|
NOR: { code: 'NOR', name: 'Normandie', brand: 'NOMAD', logo: 'logo-nomad.svg', color: '#6bb07a', cssClass: 'brand-nomad' },
|
||||||
|
NA: { code: 'NA', name: 'Nouvelle-Aquitaine', brand: 'TER NA', logo: 'logo-teraquitaine.svg', color: '#ea5b0c', cssClass: 'brand-terna' },
|
||||||
|
OCC: { code: 'OCC', name: 'Occitanie', brand: 'liO', logo: 'logo-lio.svg', color: '#e3001b', cssClass: 'brand-lio' },
|
||||||
|
PDL: { code: 'PDL', name: 'Pays de la Loire', brand: 'ALÉOP', logo: 'logo-aleop.svg', color: '#00b0ca', cssClass: 'brand-aleop' },
|
||||||
|
PACA: { code: 'PACA', name: "Provence-Alpes-Côte d'Azur", brand: 'ZOU !', logo: 'logo-zou.svg', color: '#0055a4', cssClass: 'brand-zou' },
|
||||||
|
IDF: { code: 'IDF', name: 'Île-de-France', brand: 'TRANSILIEN', logo: 'logo-sncf.svg', color: '#003b80', cssClass: 'brand-idf' }
|
||||||
|
};
|
||||||
|
|
||||||
// --- TRAINS TER (Séries 6 chiffres) ---
|
// 1. CARS TER RÉGULIERS (route_type = 3)
|
||||||
{ min: 830102, max: 840877, regionCode: 'GE', brand: 'FLUO', isCar: false, isSubstitution: false },
|
export const CARS_REGULIERS_RANGES = [
|
||||||
{ min: 841200, max: 849966, regionCode: 'HDF', brand: 'TER', isCar: false, isSubstitution: false },
|
{ min: 30131, max: 31478, region: 'AURA' },
|
||||||
{ min: 850016, max: 853728, regionCode: 'NOR', brand: 'NOMAD', isCar: false, isSubstitution: false },
|
{ min: 33155, max: 35072, region: 'AURA' },
|
||||||
{ min: 854007, max: 858397, regionCode: 'BRE', brand: 'BREIZHGO', isCar: false, isSubstitution: false },
|
{ min: 405036, max: 405562, region: 'AURA' },
|
||||||
{ min: 857000, max: 862589, regionCode: 'PDL', brand: 'ALEOP', isCar: false, isSubstitution: false }, // Chevauchement partiel, géré par l'ordre
|
{ min: 35000, max: 35065, region: 'BFC' },
|
||||||
{ min: 860001, max: 863714, regionCode: 'CVL', brand: 'REMI', isCar: false, isSubstitution: false },
|
{ min: 36518, max: 37529, region: 'BFC' },
|
||||||
{ min: 863031, max: 869129, regionCode: 'NA', brand: 'TER', isCar: false, isSubstitution: false },
|
{ min: 48022, max: 48027, region: 'BRE' },
|
||||||
{ min: 870000, max: 878991, regionCode: 'OCC', brand: 'LIO', isCar: false, isSubstitution: false },
|
{ min: 424300, max: 424336, region: 'CVL' },
|
||||||
{ min: 879102, max: 881699, regionCode: 'PACA', brand: 'ZOU', isCar: false, isSubstitution: false },
|
{ min: 426210, max: 426223, region: 'CVL' },
|
||||||
{ min: 882103, max: 890392, regionCode: 'AURA', brand: 'TER', isCar: false, isSubstitution: false },
|
{ min: 43001, max: 45141, region: 'GE' },
|
||||||
{ min: 891000, max: 895975, regionCode: 'BFC', brand: 'MOBIGO', isCar: false, isSubstitution: false },
|
{ min: 68130, max: 68283, region: 'GE' },
|
||||||
|
{ min: 428429, max: 429499, region: 'GE' },
|
||||||
|
{ min: 45530, max: 45569, region: 'HDF' },
|
||||||
|
{ min: 69400, max: 69830, region: 'HDF' },
|
||||||
|
{ min: 48000, max: 48033, region: 'NOR' },
|
||||||
|
{ min: 87002, max: 88061, region: 'NOR' },
|
||||||
|
{ min: 49957, max: 49974, region: 'NA' },
|
||||||
|
{ min: 70926, max: 70944, region: 'NA' },
|
||||||
|
{ min: 94000, max: 94032, region: 'OCC' },
|
||||||
|
{ min: 95201, max: 95477, region: 'OCC' },
|
||||||
|
{ min: 457156, max: 457268, region: 'OCC' },
|
||||||
|
{ min: 461900, max: 462402, region: 'OCC' },
|
||||||
|
{ min: 49981, max: 49986, region: 'PDL' },
|
||||||
|
{ min: 75000, max: 75039, region: 'PDL' }
|
||||||
|
];
|
||||||
|
|
||||||
// --- TRAINS TER (Séries 5 chiffres non ambiguës) ---
|
// 2. CARS DE SUBSTITUTION TRAVAUX (route_type = 2)
|
||||||
{ min: 96500, max: 96769, regionCode: 'AURA', brand: 'TER', isCar: false, isSubstitution: false },
|
export const CARS_SUBSTITUTION_RANGES = [
|
||||||
{ min: 17750, max: 18146, regionCode: 'BFC', brand: 'MOBIGO', isCar: false, isSubstitution: false },
|
{ min: 30000, max: 31497, region: 'AURA' },
|
||||||
{ min: 81390, max: 81496, regionCode: 'GE', brand: 'FLUO', isCar: false, isSubstitution: false },
|
{ min: 33005, max: 34813, region: 'AURA' },
|
||||||
{ min: 86380, max: 88877, regionCode: 'GE', brand: 'FLUO', isCar: false, isSubstitution: false },
|
{ min: 404001, max: 412498, region: 'AURA' },
|
||||||
{ min: 96100, max: 96299, regionCode: 'GE', brand: 'FLUO', isCar: false, isSubstitution: false },
|
{ min: 35039, max: 35044, region: 'BFC' },
|
||||||
{ min: 16350, max: 16480, regionCode: 'HDF', brand: 'TER', isCar: false, isSubstitution: false },
|
{ min: 36501, max: 37304, region: 'BFC' },
|
||||||
{ min: 19701, max: 19979, regionCode: 'HDF', brand: 'TER', isCar: false, isSubstitution: false },
|
{ min: 415035, max: 418367, region: 'BFC' },
|
||||||
{ min: 13100, max: 13191, regionCode: 'NOR', brand: 'NOMAD', isCar: false, isSubstitution: false },
|
{ min: 419501, max: 419627, region: 'BFC' },
|
||||||
|
{ min: 424010, max: 424403, region: 'BFC' },
|
||||||
|
{ min: 67864, max: 67872, region: 'BRE' },
|
||||||
|
{ min: 421001, max: 422112, region: 'BRE' },
|
||||||
|
{ min: 442003, max: 442008, region: 'BRE' },
|
||||||
|
{ min: 40850, max: 40904, region: 'CVL' },
|
||||||
|
{ min: 424020, max: 426502, region: 'CVL' },
|
||||||
|
{ min: 43103, max: 45499, region: 'GE' },
|
||||||
|
{ min: 427250, max: 435837, region: 'GE' },
|
||||||
|
{ min: 45500, max: 46576, region: 'HDF' },
|
||||||
|
{ min: 85564, max: 85661, region: 'HDF' },
|
||||||
|
{ min: 436010, max: 436191, region: 'HDF' },
|
||||||
|
{ min: 437480, max: 437845, region: 'HDF' },
|
||||||
|
{ min: 439005, max: 439537, region: 'HDF' },
|
||||||
|
{ min: 48001, max: 48271, region: 'NOR' },
|
||||||
|
{ min: 442108, max: 442129, region: 'NOR' },
|
||||||
|
{ min: 446802, max: 447248, region: 'NOR' },
|
||||||
|
{ min: 70452, max: 70769, region: 'NA' },
|
||||||
|
{ min: 448014, max: 448812, region: 'NA' },
|
||||||
|
{ min: 450001, max: 451482, region: 'NA' },
|
||||||
|
{ min: 453009, max: 453872, region: 'NA' },
|
||||||
|
{ min: 455006, max: 455684, region: 'NA' },
|
||||||
|
{ min: 60401, max: 60444, region: 'OCC' },
|
||||||
|
{ min: 73100, max: 73303, region: 'OCC' },
|
||||||
|
{ min: 95470, max: 95478, region: 'OCC' },
|
||||||
|
{ min: 412062, max: 412494, region: 'OCC' },
|
||||||
|
{ min: 455005, max: 455238, region: 'OCC' },
|
||||||
|
{ min: 457001, max: 457452, region: 'OCC' },
|
||||||
|
{ min: 459663, max: 459691, region: 'OCC' },
|
||||||
|
{ min: 461001, max: 462221, region: 'OCC' },
|
||||||
|
{ min: 464040, max: 464140, region: 'PDL' },
|
||||||
|
{ min: 64001, max: 64006, region: 'PACA' },
|
||||||
|
{ min: 79000, max: 79002, region: 'PACA' },
|
||||||
|
{ min: 466752, max: 468300, region: 'PACA' },
|
||||||
|
{ min: 445602, max: 445619, region: 'IDF' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 3. TRAINS TER (route_type = 2)
|
||||||
|
export const TRAINS_TER_RANGES = [
|
||||||
|
// 6 CHIFFRES (Segmentation très précise)
|
||||||
|
{ min: 830102, max: 836407, region: 'GE' },
|
||||||
|
{ min: 837501, max: 840877, region: 'GE' },
|
||||||
|
{ min: 841200, max: 844948, region: 'HDF' },
|
||||||
|
{ min: 846400, max: 849966, region: 'HDF' },
|
||||||
|
{ min: 848950, max: 848977, region: 'NOR' },
|
||||||
|
{ min: 850016, max: 853728, region: 'NOR' },
|
||||||
|
{ min: 854007, max: 858397, region: 'BRE' },
|
||||||
|
{ min: 857000, max: 860993, region: 'PDL' },
|
||||||
|
{ min: 860001, max: 863714, region: 'CVL' },
|
||||||
|
{ min: 862551, max: 862589, region: 'PDL' },
|
||||||
|
{ min: 863031, max: 869129, region: 'NA' },
|
||||||
|
{ min: 870000, max: 872891, region: 'OCC' },
|
||||||
|
{ min: 873901, max: 873994, region: 'OCC' },
|
||||||
|
{ min: 876100, max: 878991, region: 'OCC' },
|
||||||
|
{ min: 873003, max: 874231, region: 'AURA' },
|
||||||
|
{ min: 879102, max: 881699, region: 'PACA' },
|
||||||
|
{ min: 882103, max: 890392, region: 'AURA' },
|
||||||
|
{ min: 891000, max: 895975, region: 'BFC' },
|
||||||
|
|
||||||
|
// 4 ET 5 CHIFFRES
|
||||||
|
{ min: 3100, max: 3483, region: 'NOR' },
|
||||||
|
{ min: 3903, max: 3924, region: 'CVL' },
|
||||||
|
{ min: 5900, max: 5921, region: 'BFC' },
|
||||||
|
{ min: 13100, max: 13191, region: 'NOR' },
|
||||||
|
{ min: 16350, max: 16480, region: 'HDF' },
|
||||||
|
{ min: 16521, max: 16587, region: 'NOR' },
|
||||||
|
{ min: 16843, max: 18999, region: 'AURA' },
|
||||||
|
{ min: 17353, max: 17705, region: 'PACA' },
|
||||||
|
{ min: 17750, max: 18146, region: 'BFC' },
|
||||||
|
{ min: 19701, max: 19979, region: 'HDF' },
|
||||||
|
{ min: 22244, max: 23026, region: 'OCC' },
|
||||||
|
{ min: 23101, max: 23794, region: 'AURA' },
|
||||||
|
{ min: 23722, max: 23831, region: 'GE' },
|
||||||
|
{ min: 33501, max: 33647, region: 'PACA' },
|
||||||
|
{ min: 81390, max: 81496, region: 'GE' },
|
||||||
|
{ min: 86002, max: 86104, region: 'PACA' },
|
||||||
|
{ min: 86380, max: 87470, region: 'GE' },
|
||||||
|
{ min: 86960, max: 86998, region: 'OCC' },
|
||||||
|
{ min: 88488, max: 88877, region: 'GE' },
|
||||||
|
{ min: 96100, max: 96299, region: 'GE' },
|
||||||
|
{ min: 96500, max: 96769, region: 'AURA' }
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Déduit la région et la marque (train/car) à partir d'un numéro GTFS
|
* Détecte la région probable à partir du nom ou de l'UIC d'une gare
|
||||||
* @param {string|number} trainNumStr
|
|
||||||
* @returns {Object|null}
|
|
||||||
*/
|
*/
|
||||||
export function resolveRegionByNumber(trainNumStr) {
|
export function guessRegionFromStation(stationNameOrUic) {
|
||||||
const num = parseInt(String(trainNumStr).replace(/\D/g, ''), 10);
|
if (!stationNameOrUic) return null;
|
||||||
if (isNaN(num)) return null;
|
const s = String(stationNameOrUic).toLowerCase();
|
||||||
|
|
||||||
|
if (s.includes('dijon') || s.includes('besançon') || s.includes('belfort') || s.includes('chalon') || s.includes('nevers') || s.includes('auxerre') || s.includes('mâcon') || s.includes('sens') || s.includes('bourgogne') || s.includes('franche-comte')) return 'BFC';
|
||||||
|
if (s.includes('lyon') || s.includes('grenoble') || s.includes('saint-etienne') || s.includes('clermont') || s.includes('chambery') || s.includes('annecy') || s.includes('valence') || s.includes('aura') || s.includes('alpes')) return 'AURA';
|
||||||
|
if (s.includes('marseille') || s.includes('nice') || s.includes('toulon') || s.includes('avignon') || s.includes('cannes') || s.includes('arles') || s.includes('antibes') || s.includes('paca') || s.includes('azur')) return 'PACA';
|
||||||
|
if (s.includes('toulouse') || s.includes('montpellier') || s.includes('nimes') || s.includes('perpignan') || s.includes('beziers') || s.includes('narbonne') || s.includes('tarbes') || s.includes('occitanie')) return 'OCC';
|
||||||
|
if (s.includes('bordeaux') || s.includes('limoges') || s.includes('poitiers') || s.includes('la rochelle') || s.includes('pau') || s.includes('angouleme') || s.includes('bayonne') || s.includes('agen') || s.includes('aquitaine')) return 'NA';
|
||||||
|
if (s.includes('strasbourg') || s.includes('metz') || s.includes('nancy') || s.includes('reims') || s.includes('mulhouse') || s.includes('colmar') || s.includes('troyes') || s.includes('charleville') || s.includes('alsace') || s.includes('lorraine')) return 'GE';
|
||||||
|
if (s.includes('lille') || s.includes('amiens') || s.includes('calais') || s.includes('dunkerque') || s.includes('arras') || s.includes('valenciennes') || s.includes('douai') || s.includes('hauts-de-france') || s.includes('nord')) return 'HDF';
|
||||||
|
if (s.includes('rouen') || s.includes('caen') || s.includes('le havre') || s.includes('cherbourg') || s.includes('evreux') || s.includes('dieppe') || s.includes('alencon') || s.includes('normandie')) return 'NOR';
|
||||||
|
if (s.includes('rennes') || s.includes('brest') || s.includes('quimper') || s.includes('vannes') || s.includes('saint-brieuc') || s.includes('lorient') || s.includes('saint-malo') || s.includes('bretagne')) return 'BRE';
|
||||||
|
if (s.includes('nantes') || s.includes('angers') || s.includes('le mans') || s.includes('saint-nazaire') || s.includes('la roche-sur-yon') || s.includes('cholet') || s.includes('laval') || s.includes('loire')) return 'PDL';
|
||||||
|
if (s.includes('orleans') || s.includes('tours') || s.includes('bourges') || s.includes('blois') || s.includes('chartres') || s.includes('chateauroux') || s.includes('vierzon')) return 'CVL';
|
||||||
|
if (s.includes('paris') || s.includes('saint-denis') || s.includes('versailles') || s.includes('melun')) return 'IDF';
|
||||||
|
|
||||||
for (const range of REGIONAL_RANGES) {
|
|
||||||
if (num >= range.min && num <= range.max) {
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moteur de résolution d'identité de circulation GTFS
|
||||||
|
* @param {string|number} trainNumStr
|
||||||
|
* @param {Object} options { stationContext, explicitMode }
|
||||||
|
*/
|
||||||
|
export function resolveCirculationIdentity(trainNumStr, options = {}) {
|
||||||
|
const { stationContext = null, explicitMode = '' } = options;
|
||||||
|
const num = parseInt(String(trainNumStr || '').replace(/\D/g, ''), 10);
|
||||||
|
const preferredRegion = guessRegionFromStation(stationContext);
|
||||||
|
const modeLower = String(explicitMode || '').toLowerCase();
|
||||||
|
|
||||||
|
const isExplicitCar = modeLower.includes('car') || modeLower.includes('autocar') || modeLower.includes('bus');
|
||||||
|
|
||||||
|
if (!isNaN(num)) {
|
||||||
|
// 1. Priorité A : Cars de substitution travaux (route_type = 2)
|
||||||
|
const subMatches = CARS_SUBSTITUTION_RANGES.filter(r => num >= r.min && num <= r.max);
|
||||||
|
if (subMatches.length > 0) {
|
||||||
|
const best = (preferredRegion && subMatches.find(m => m.region === preferredRegion)) || subMatches[0];
|
||||||
|
const meta = REGIONS_META[best.region] || REGIONS_META.AURA;
|
||||||
|
return {
|
||||||
|
isCar: true,
|
||||||
|
isSubstitution: true,
|
||||||
|
regionCode: best.region,
|
||||||
|
brand: meta.brand,
|
||||||
|
typeLabel: 'CAR',
|
||||||
|
logoFile: 'track-car.svg',
|
||||||
|
cssClass: meta.cssClass,
|
||||||
|
color: meta.color
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Priorité B : Cars TER Réguliers (route_type = 3)
|
||||||
|
const regCarMatches = CARS_REGULIERS_RANGES.filter(r => num >= r.min && num <= r.max);
|
||||||
|
if (regCarMatches.length > 0) {
|
||||||
|
const best = (preferredRegion && regCarMatches.find(m => m.region === preferredRegion)) || regCarMatches[0];
|
||||||
|
const meta = REGIONS_META[best.region] || REGIONS_META.AURA;
|
||||||
|
return {
|
||||||
|
isCar: true,
|
||||||
|
isSubstitution: false,
|
||||||
|
regionCode: best.region,
|
||||||
|
brand: meta.brand,
|
||||||
|
typeLabel: 'CAR',
|
||||||
|
logoFile: 'track-car.svg',
|
||||||
|
cssClass: meta.cssClass,
|
||||||
|
color: meta.color
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si le numéro est typique des séries autocar (4xxxxx), forcer Car même hors intervalle strict
|
||||||
|
if (num >= 400000 && num <= 469999) {
|
||||||
|
return {
|
||||||
|
isCar: true,
|
||||||
|
isSubstitution: true,
|
||||||
|
regionCode: preferredRegion || 'AURA',
|
||||||
|
brand: preferredRegion && REGIONS_META[preferredRegion] ? REGIONS_META[preferredRegion].brand : 'CAR TER',
|
||||||
|
typeLabel: 'CAR',
|
||||||
|
logoFile: 'track-car.svg',
|
||||||
|
cssClass: preferredRegion && REGIONS_META[preferredRegion] ? REGIONS_META[preferredRegion].cssClass : 'brand-teraura',
|
||||||
|
color: '#0077c8'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Priorité C : Trains TER (route_type = 2)
|
||||||
|
const trainMatches = TRAINS_TER_RANGES.filter(r => num >= r.min && num <= r.max);
|
||||||
|
if (trainMatches.length > 0) {
|
||||||
|
// Si conflit de chevauchement, prioriser la région de la gare si elle correspond
|
||||||
|
const best = (preferredRegion && trainMatches.find(m => m.region === preferredRegion)) ||
|
||||||
|
// Sinon favoriser la plage la plus spécifique (écart min/max le plus petit)
|
||||||
|
trainMatches.sort((a, b) => (a.max - a.min) - (b.max - b.min))[0];
|
||||||
|
|
||||||
|
const meta = REGIONS_META[best.region] || REGIONS_META.AURA;
|
||||||
|
return {
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
regionCode: best.region,
|
||||||
|
brand: meta.brand,
|
||||||
|
typeLabel: meta.brand,
|
||||||
|
logoFile: meta.logo,
|
||||||
|
cssClass: meta.cssClass,
|
||||||
|
color: meta.color
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback si rien n'a matché
|
||||||
|
if (isExplicitCar) {
|
||||||
|
return {
|
||||||
|
isCar: true,
|
||||||
|
isSubstitution: false,
|
||||||
|
regionCode: preferredRegion || 'SNCF',
|
||||||
|
brand: 'Car TER',
|
||||||
|
typeLabel: 'CAR',
|
||||||
|
logoFile: 'track-car.svg',
|
||||||
|
cssClass: 'brand-car',
|
||||||
|
color: '#666666'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
regionCode: preferredRegion || 'SNCF',
|
||||||
|
brand: 'TER',
|
||||||
|
typeLabel: 'TER',
|
||||||
|
logoFile: 'logo-ter.svg',
|
||||||
|
cssClass: 'brand-ter',
|
||||||
|
color: '#0077c8'
|
||||||
|
};
|
||||||
|
}
|
||||||
+182
-161
@@ -1,4 +1,5 @@
|
|||||||
import { resolveRegionByNumber } from './gtfsRegionalRanges';
|
import { resolveCirculationIdentity } from './gtfsRegionalRanges';
|
||||||
|
|
||||||
// Indexation optimisée des logos pour recherche rapide et insensible à la casse / accents
|
// Indexation optimisée des logos pour recherche rapide et insensible à la casse / accents
|
||||||
const normalizeStr = (s) => {
|
const normalizeStr = (s) => {
|
||||||
if (!s) return '';
|
if (!s) return '';
|
||||||
@@ -12,7 +13,7 @@ const normalizeStr = (s) => {
|
|||||||
// Table des logos issue de public/type/data.json
|
// Table des logos issue de public/type/data.json
|
||||||
const LOGOS_DATA = [
|
const LOGOS_DATA = [
|
||||||
{ file: "logo-aleop.svg", slug: "aleop", name: "Aleop" },
|
{ file: "logo-aleop.svg", slug: "aleop", name: "Aleop" },
|
||||||
{ file: "sncf-voyageurs-logo.png", slug: "auvergerhonealpes", name: "TER AURA" },
|
{ file: "logo-auvergerhonealpes.svg", slug: "auvergerhonealpes", name: "TER AURA" },
|
||||||
{ file: "logo-breizhgo.svg", slug: "breizhgo", name: "Breizhgo" },
|
{ file: "logo-breizhgo.svg", slug: "breizhgo", name: "Breizhgo" },
|
||||||
{ file: "logo-cfl.svg", slug: "cfl", name: "CFL" },
|
{ file: "logo-cfl.svg", slug: "cfl", name: "CFL" },
|
||||||
{ file: "logo-db.svg", slug: "db", name: "DB" },
|
{ file: "logo-db.svg", slug: "db", name: "DB" },
|
||||||
@@ -44,28 +45,16 @@ const LOGOS_DATA = [
|
|||||||
{ file: "logo-sncf1985.svg", slug: "sncf1985", name: "Sncf1985" },
|
{ file: "logo-sncf1985.svg", slug: "sncf1985", name: "Sncf1985" },
|
||||||
{ file: "logo-sncf1992.svg", slug: "sncf1992", name: "Sncf1992" },
|
{ file: "logo-sncf1992.svg", slug: "sncf1992", name: "Sncf1992" },
|
||||||
{ file: "logo-teoz.svg", slug: "teoz", name: "Teoz" },
|
{ file: "logo-teoz.svg", slug: "teoz", name: "Teoz" },
|
||||||
|
|
||||||
{ file: "logo-ter-rhone-alpes.svg", slug: "ter-rhone-alpes", name: "TER Rhone-Alpes" },
|
{ file: "logo-ter-rhone-alpes.svg", slug: "ter-rhone-alpes", name: "TER Rhone-Alpes" },
|
||||||
{ file: "logo-teralsace.svg", slug: "teralsace", name: "Ter Alsace" },
|
|
||||||
{ file: "logo-teraquitaine.svg", slug: "teraquitaine", name: "Ter Aquitaine" },
|
{ file: "logo-teraquitaine.svg", slug: "teraquitaine", name: "Ter Aquitaine" },
|
||||||
{ file: "logo-terbassenormandie.svg", slug: "terbassenormandie", name: "Ter Basse Normandie" },
|
|
||||||
{ file: "logo-terbourgogne.svg", slug: "terbourgogne", name: "Ter Bourgogne" },
|
|
||||||
{ file: "logo-terbretagne.svg", slug: "terbretagne", name: "Ter Bretagne" },
|
|
||||||
{ file: "logo-tercentre.svg", slug: "tercentre", name: "Ter Centre" },
|
|
||||||
{ file: "logo-terlanguedocroussillon.svg", slug: "terlanguedocroussillon", name: "Ter Languedoc-Roussillon" },
|
|
||||||
{ file: "logo-termidipyrenees.svg", slug: "termidipyrenees", name: "Ter Midi-Pyrenees" },
|
|
||||||
{ file: "logo-ternordpasdecalais.svg", slug: "ternordpasdecalais", name: "Ter Nord-pas-de-Calais" },
|
|
||||||
{ file: "logo-terpoitoucharentes.svg", slug: "terpoitoucharentes", name: "Ter Poitou Charentes" },
|
|
||||||
{ file: "logo-tgv.svg", slug: "tgv", name: "TGV" },
|
{ file: "logo-tgv.svg", slug: "tgv", name: "TGV" },
|
||||||
{ file: "logo-inoui.svg", slug: "inoui", name: "TGV InOui" },
|
{ file: "logo-inoui.svg", slug: "inoui", name: "TGV InOui" },
|
||||||
{ file: "logo-lyria.svg", slug: "lyria", name: "TGV Lyria" },
|
|
||||||
{ file: "logo-thalys.svg", slug: "thalys", name: "Thalys" },
|
{ file: "logo-thalys.svg", slug: "thalys", name: "Thalys" },
|
||||||
{ file: "logo-thello.svg", slug: "thello", name: "Thello" },
|
{ file: "logo-thello.svg", slug: "thello", name: "Thello" },
|
||||||
{ file: "track-car.svg", slug: "track-car", name: "Car TER" },
|
{ file: "track-car.svg", slug: "track-car", name: "Car TER" },
|
||||||
{ file: "logo-tramtrain.svg", slug: "tramtrain", name: "Tramtrain" },
|
{ file: "logo-tramtrain.svg", slug: "tramtrain", name: "Tramtrain" },
|
||||||
{ file: "logo-trenitalia.svg", slug: "trenitalia", name: "Trenitalia" },
|
{ file: "logo-trenitalia.svg", slug: "trenitalia", name: "Trenitalia" },
|
||||||
{ file: "logo-zou.svg", slug: "zou", name: "Zou" }
|
{ file: "logo-zou.svg", slug: "zou", name: "Zou" }
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const logoLookup = new Map();
|
const logoLookup = new Map();
|
||||||
@@ -83,11 +72,24 @@ LOGOS_DATA.forEach((item) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Détermine le logo SVG / PNG et le libellé de train à afficher à partir d'un objet horaire
|
* Détermine le logo SVG / PNG et le libellé de train à afficher à partir d'un objet horaire
|
||||||
* Supporte les données SNCF Open Data (GTFS / SIRI) et Supabase
|
* Intègre la détection déterministe GTFS (Cars réguliers, Cars substitution, Trains régionaux)
|
||||||
|
* @param {Object} horaire
|
||||||
|
* @param {string} [stationContext] - Nom de la gare courante pour arbitrage géographique
|
||||||
*/
|
*/
|
||||||
export function getTrainLogoAndMeta(horaire) {
|
export function getTrainLogoAndMeta(horaire, stationContext = null) {
|
||||||
if (!horaire) {
|
if (!horaire) {
|
||||||
return { logoSrc: '/type/sncf-voyageurs-logo.png', typeLabel: 'TER', numTrain: '' };
|
return {
|
||||||
|
logoSrc: '/type/logo-ter.svg',
|
||||||
|
logoFile: 'logo-ter.svg',
|
||||||
|
typeLabel: 'TER',
|
||||||
|
numTrain: '',
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
brand: 'TER',
|
||||||
|
regionCode: null,
|
||||||
|
brandClass: 'brand-ter',
|
||||||
|
brandColor: '#0077c8'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const opRaw = String(
|
const opRaw = String(
|
||||||
@@ -110,154 +112,173 @@ export function getTrainLogoAndMeta(horaire) {
|
|||||||
|
|
||||||
const opNorm = normalizeStr(opRaw);
|
const opNorm = normalizeStr(opRaw);
|
||||||
|
|
||||||
// 1. Recherche directe dans la base des logos
|
// 1. Grands transporteurs nationaux et internationaux prioritaires (Explicites)
|
||||||
let matchedLogo = logoLookup.get(opNorm);
|
|
||||||
|
|
||||||
// 2. Recherche par mots-clés si pas de correspondance exacte
|
|
||||||
if (!matchedLogo) {
|
|
||||||
if (opNorm.includes('inoui') || opNorm.includes('tgv')) {
|
|
||||||
matchedLogo = logoLookup.get('inoui') || logoLookup.get('tgv');
|
|
||||||
} else if (opNorm.includes('ouigoclassique')) {
|
|
||||||
matchedLogo = logoLookup.get('ouigoclassique');
|
|
||||||
} else if (opNorm.includes('ouigo')) {
|
|
||||||
matchedLogo = logoLookup.get('ouigo');
|
|
||||||
} else if (opNorm.includes('mobigo')) {
|
|
||||||
matchedLogo = logoLookup.get('mobigo');
|
|
||||||
} else if (opNorm.includes('fluo')) {
|
|
||||||
matchedLogo = logoLookup.get('fluo');
|
|
||||||
} else if (opNorm.includes('remi')) {
|
|
||||||
matchedLogo = logoLookup.get('remi');
|
|
||||||
} else if (opNorm.includes('nomad')) {
|
|
||||||
matchedLogo = logoLookup.get('nomad');
|
|
||||||
} else if (opNorm.includes('aleop')) {
|
|
||||||
matchedLogo = logoLookup.get('aleop');
|
|
||||||
} else if (opNorm.includes('breizhgo')) {
|
|
||||||
matchedLogo = logoLookup.get('breizhgo');
|
|
||||||
} else if (opNorm.includes('hautsdefrance')) {
|
|
||||||
matchedLogo = logoLookup.get('hautsdefrance');
|
|
||||||
} else if (opNorm.includes('lio')) {
|
|
||||||
matchedLogo = logoLookup.get('lio');
|
|
||||||
} else if (opNorm.includes('zou')) {
|
|
||||||
matchedLogo = logoLookup.get('zou');
|
|
||||||
} else if (opNorm.includes('intercites') || opNorm.includes('ic')) {
|
|
||||||
matchedLogo = logoLookup.get('intercites');
|
|
||||||
} else if (opNorm.includes('ice')) {
|
|
||||||
matchedLogo = logoLookup.get('ice');
|
|
||||||
} else if (opNorm.includes('eurostar')) {
|
|
||||||
matchedLogo = logoLookup.get('eurostar');
|
|
||||||
} else if (opNorm.includes('thalys')) {
|
|
||||||
matchedLogo = logoLookup.get('thalys');
|
|
||||||
} else if (opNorm.includes('lyria')) {
|
|
||||||
matchedLogo = logoLookup.get('lyria');
|
|
||||||
} else if (opNorm.includes('frecciarossa') || opNorm.includes('trenitalia')) {
|
|
||||||
matchedLogo = logoLookup.get('frecciarossa') || logoLookup.get('trenitalia');
|
|
||||||
} else if (opNorm.includes('renfe') || opNorm.includes('ave')) {
|
|
||||||
matchedLogo = logoLookup.get('renfeave');
|
|
||||||
} else if (opNorm.includes('car') || opNorm.includes('autocar') || opNorm.includes('bus')) {
|
|
||||||
matchedLogo = logoLookup.get('trackcar') || { file: 'sncf-voyageurs-logo.png', name: 'Car TER' };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Détermination du libellé affiché (typeLabel)
|
|
||||||
let typeLabel = opRaw || 'TER';
|
|
||||||
|
|
||||||
if (opNorm.includes('inoui') || opNorm.includes('tgvinoui')) {
|
if (opNorm.includes('inoui') || opNorm.includes('tgvinoui')) {
|
||||||
typeLabel = 'TGV inOUI';
|
return {
|
||||||
} else if (opNorm === 'tgv') {
|
logoSrc: '/type/logo-inoui.svg',
|
||||||
typeLabel = 'TGV';
|
logoFile: 'logo-inoui.svg',
|
||||||
} else if (opNorm.includes('ouigoclassique')) {
|
typeLabel: 'TGV inOUI',
|
||||||
typeLabel = 'OUIGO Train Classique';
|
numTrain,
|
||||||
} else if (opNorm.includes('ouigo')) {
|
isCar: false,
|
||||||
typeLabel = 'OUIGO';
|
isSubstitution: false,
|
||||||
} else if (opNorm.includes('mobigo')) {
|
brand: 'TGV inOUI',
|
||||||
typeLabel = 'MOBIGO';
|
regionCode: null,
|
||||||
} else if (opNorm.includes('fluo')) {
|
brandClass: 'brand-inoui',
|
||||||
typeLabel = 'FLUO';
|
brandColor: '#7a1f3d'
|
||||||
} else if (opNorm.includes('nomad')) {
|
};
|
||||||
typeLabel = 'NOMAD';
|
|
||||||
} else if (opNorm.includes('remi')) {
|
|
||||||
typeLabel = 'RÉMI';
|
|
||||||
} else if (opNorm.includes('aleop')) {
|
|
||||||
typeLabel = 'ALÉOP';
|
|
||||||
} else if (opNorm.includes('breizhgo')) {
|
|
||||||
typeLabel = 'BREIZHGO';
|
|
||||||
} else if (opNorm.includes('hautsdefrance')) {
|
|
||||||
typeLabel = 'TER HDF';
|
|
||||||
} else if (opNorm.includes('lio')) {
|
|
||||||
typeLabel = 'LIÒ';
|
|
||||||
} else if (opNorm.includes('zou')) {
|
|
||||||
typeLabel = 'ZOU !';
|
|
||||||
} else if (opNorm.includes('intercites') || opNorm === 'ic') {
|
|
||||||
typeLabel = 'INTERCITÉS';
|
|
||||||
} else if (opNorm.includes('ice')) {
|
|
||||||
typeLabel = 'ICE';
|
|
||||||
} else if (opNorm.includes('eurostar')) {
|
|
||||||
typeLabel = 'EUROSTAR';
|
|
||||||
} else if (opNorm.includes('lyria')) {
|
|
||||||
typeLabel = 'TGV Lyria';
|
|
||||||
} else if (opNorm.includes('car') || opNorm.includes('autocar') || opNorm.includes('bus')) {
|
|
||||||
typeLabel = 'Car TER';
|
|
||||||
} else if (opNorm.includes('ter')) {
|
|
||||||
typeLabel = 'TER';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let isCar = opNorm.includes('car') || opNorm.includes('autocar') || opNorm.includes('bus');
|
if (opNorm === 'tgv' || (opNorm.includes('tgv') && !opNorm.includes('ouigo') && !opNorm.includes('lyria'))) {
|
||||||
let isSubstitution = false;
|
return {
|
||||||
let brand = typeLabel;
|
logoSrc: '/type/logo-tgv.svg',
|
||||||
|
logoFile: 'logo-tgv.svg',
|
||||||
// 4. Application du plan de numérotation déterministe depuis GTFS (nouveau moteur)
|
typeLabel: 'TGV',
|
||||||
if ((typeLabel === 'TER' || typeLabel === 'AUTOCAR' || typeLabel === 'Car TER' || typeLabel === 'Car' || typeLabel === 'Autocar' || opRaw.toUpperCase().includes('CAR')) && numTrain) {
|
numTrain,
|
||||||
const regionMatch = resolveRegionByNumber(numTrain);
|
isCar: false,
|
||||||
if (regionMatch) {
|
isSubstitution: false,
|
||||||
if (regionMatch.isCar) {
|
brand: 'TGV',
|
||||||
isCar = true;
|
regionCode: null,
|
||||||
typeLabel = 'Car TER'; // Forcer l'affichage CAR
|
brandClass: 'brand-tgv',
|
||||||
}
|
brandColor: '#6e1e24'
|
||||||
if (regionMatch.isSubstitution) {
|
};
|
||||||
isSubstitution = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
brand = regionMatch.brand;
|
|
||||||
|
|
||||||
// Affiner le typeLabel selon la marque si train
|
|
||||||
if (!isCar) {
|
|
||||||
switch (brand) {
|
|
||||||
case 'FLUO': typeLabel = 'FLUO'; break;
|
|
||||||
case 'TER HDF': typeLabel = 'TER HDF'; break;
|
|
||||||
case 'NOMAD': typeLabel = 'NOMAD'; break;
|
|
||||||
case 'BREIZHGO': typeLabel = 'BREIZHGO'; break;
|
|
||||||
case 'ALEOP': typeLabel = 'ALÉOP'; break;
|
|
||||||
case 'REMI': typeLabel = 'RÉMI'; break;
|
|
||||||
case 'TER NA': typeLabel = 'TER NA'; break;
|
|
||||||
case 'LIO': typeLabel = 'LiO'; break;
|
|
||||||
case 'ZOU': typeLabel = 'ZOU !'; break;
|
|
||||||
case 'MOBIGO': typeLabel = 'MOBIGO'; break;
|
|
||||||
case 'TER AURA': typeLabel = 'TER AURA'; break;
|
|
||||||
default: typeLabel = 'TER';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tenter de charger le logo SVG de la marque si ce n'est pas un car
|
|
||||||
if (!isCar) {
|
|
||||||
const slugMatch = logoLookup.get(brand.toLowerCase().replace(/[^a-z]/g, ''));
|
|
||||||
if (slugMatch) {
|
|
||||||
matchedLogo = slugMatch;
|
|
||||||
} else if (brand === 'TER AURA') {
|
|
||||||
matchedLogo = logoLookup.get('terrhonealpes');
|
|
||||||
} else if (brand === 'TER NA') {
|
|
||||||
matchedLogo = logoLookup.get('teraquitaine');
|
|
||||||
} else if (brand === 'TER HDF') {
|
|
||||||
matchedLogo = logoLookup.get('hautsdefrance');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Pour les cars régionaux, on associe l'icône de car
|
|
||||||
matchedLogo = logoLookup.get('trackcar') || { file: 'track-car.svg', name: 'Car TER' };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const logoFile = (matchedLogo && matchedLogo.file) || (isCar ? 'track-car.svg' : 'sncf-voyageurs-logo.png');
|
if (opNorm.includes('ouigoclassique') || opNorm.includes('ouigotrainclassique')) {
|
||||||
const logoSrc = `/type/${logoFile}`;
|
return {
|
||||||
|
logoSrc: '/type/logo-ouigo-classique.svg',
|
||||||
|
logoFile: 'logo-ouigo-classique.svg',
|
||||||
|
typeLabel: 'OUIGO Classique',
|
||||||
|
numTrain,
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
brand: 'OUIGO Classique',
|
||||||
|
regionCode: null,
|
||||||
|
brandClass: 'brand-ouigo-classique',
|
||||||
|
brandColor: '#ff007f'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return { logoSrc, typeLabel, numTrain, isCar, isSubstitution, brand };
|
if (opNorm.includes('ouigo')) {
|
||||||
|
return {
|
||||||
|
logoSrc: '/type/logo-ouigo.svg',
|
||||||
|
logoFile: 'logo-ouigo.svg',
|
||||||
|
typeLabel: 'OUIGO',
|
||||||
|
numTrain,
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
brand: 'OUIGO',
|
||||||
|
regionCode: null,
|
||||||
|
brandClass: 'brand-ouigo',
|
||||||
|
brandColor: '#00a3e0'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opNorm.includes('intercites') || opNorm === 'ic') {
|
||||||
|
return {
|
||||||
|
logoSrc: '/type/logo-intercites.svg',
|
||||||
|
logoFile: 'logo-intercites.svg',
|
||||||
|
typeLabel: 'INTERCITÉS',
|
||||||
|
numTrain,
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
brand: 'INTERCITÉS',
|
||||||
|
regionCode: null,
|
||||||
|
brandClass: 'brand-intercites',
|
||||||
|
brandColor: '#a61c28'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opNorm.includes('eurostar')) {
|
||||||
|
return {
|
||||||
|
logoSrc: '/type/logo-eurostar.svg',
|
||||||
|
logoFile: 'logo-eurostar.svg',
|
||||||
|
typeLabel: 'EUROSTAR',
|
||||||
|
numTrain,
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
brand: 'EUROSTAR',
|
||||||
|
regionCode: null,
|
||||||
|
brandClass: 'brand-eurostar',
|
||||||
|
brandColor: '#003865'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opNorm.includes('ice') || opNorm.includes('db')) {
|
||||||
|
return {
|
||||||
|
logoSrc: '/type/logo-ice.svg',
|
||||||
|
logoFile: 'logo-ice.svg',
|
||||||
|
typeLabel: 'ICE',
|
||||||
|
numTrain,
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
brand: 'ICE',
|
||||||
|
regionCode: null,
|
||||||
|
brandClass: 'brand-ice',
|
||||||
|
brandColor: '#eb1800'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opNorm.includes('lyria')) {
|
||||||
|
return {
|
||||||
|
logoSrc: '/type/logo-lyria.svg',
|
||||||
|
logoFile: 'logo-lyria.svg',
|
||||||
|
typeLabel: 'TGV Lyria',
|
||||||
|
numTrain,
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
brand: 'TGV Lyria',
|
||||||
|
regionCode: null,
|
||||||
|
brandClass: 'brand-lyria',
|
||||||
|
brandColor: '#b4192b'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opNorm.includes('frecciarossa') || opNorm.includes('trenitalia')) {
|
||||||
|
return {
|
||||||
|
logoSrc: '/type/logo-frecciarossa.svg',
|
||||||
|
logoFile: 'logo-frecciarossa.svg',
|
||||||
|
typeLabel: 'Frecciarossa',
|
||||||
|
numTrain,
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
brand: 'Trenitalia',
|
||||||
|
regionCode: null,
|
||||||
|
brandClass: 'brand-trenitalia',
|
||||||
|
brandColor: '#c8102e'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opNorm.includes('renfe') || opNorm.includes('ave')) {
|
||||||
|
return {
|
||||||
|
logoSrc: '/type/logo-renfeave.svg',
|
||||||
|
logoFile: 'logo-renfeave.svg',
|
||||||
|
typeLabel: 'AVE',
|
||||||
|
numTrain,
|
||||||
|
isCar: false,
|
||||||
|
isSubstitution: false,
|
||||||
|
brand: 'Renfe',
|
||||||
|
regionCode: null,
|
||||||
|
brandClass: 'brand-renfe',
|
||||||
|
brandColor: '#6f2c91'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Moteur déterministe GTFS : Déduction par numéro de train et contexte de gare
|
||||||
|
const identity = resolveCirculationIdentity(numTrain, {
|
||||||
|
stationContext: stationContext || horaire.gare_depart_nom || horaire.gare_arrivee_nom || '',
|
||||||
|
explicitMode: opRaw
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
logoSrc: `/type/${identity.logoFile}`,
|
||||||
|
logoFile: identity.logoFile,
|
||||||
|
typeLabel: identity.typeLabel,
|
||||||
|
numTrain,
|
||||||
|
isCar: identity.isCar,
|
||||||
|
isSubstitution: identity.isSubstitution,
|
||||||
|
brand: identity.brand,
|
||||||
|
regionCode: identity.regionCode,
|
||||||
|
brandClass: identity.cssClass,
|
||||||
|
brandColor: identity.color
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user