diff --git a/.env b/.env
index 9eb955a..2315b2a 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,4 @@
REACT_APP_SUPABASE_URL=https://supabase-ferrovia.mrpatator.fr
-REACT_APP_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzg3NjY3MjMxLCJleHAiOjE5NDUzNDcyMzF9.uD7YUlOFIhK_Jqbra94o5-N5zdrzKezoCxV067RJi78
\ No newline at end of file
+REACT_APP_SUPABASE_ANON_KEY=sb_publishable_jSS9wIDj3BWGBpxtv5LwGQ_1P_KBV_N
+NEXT_PUBLIC_SUPABASE_URL=https://supabase-ferrovia.mrpatator.fr
+NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_jSS9wIDj3BWGBpxtv5LwGQ_1P_KBV_N
\ No newline at end of file
diff --git a/.env.local b/.env.local
index 9eb955a..2315b2a 100644
--- a/.env.local
+++ b/.env.local
@@ -1,2 +1,4 @@
REACT_APP_SUPABASE_URL=https://supabase-ferrovia.mrpatator.fr
-REACT_APP_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzg3NjY3MjMxLCJleHAiOjE5NDUzNDcyMzF9.uD7YUlOFIhK_Jqbra94o5-N5zdrzKezoCxV067RJi78
\ No newline at end of file
+REACT_APP_SUPABASE_ANON_KEY=sb_publishable_jSS9wIDj3BWGBpxtv5LwGQ_1P_KBV_N
+NEXT_PUBLIC_SUPABASE_URL=https://supabase-ferrovia.mrpatator.fr
+NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_jSS9wIDj3BWGBpxtv5LwGQ_1P_KBV_N
\ No newline at end of file
diff --git a/src/api/horaires.js b/src/api/horaires.js
index 91f1315..193d0eb 100644
--- a/src/api/horaires.js
+++ b/src/api/horaires.js
@@ -1022,16 +1022,67 @@ export function formatHeure(time) {
*/
export function getJoursCirculation(horaire) {
if (!horaire) return [];
+
+ const parseBool = (v) => {
+ if (typeof v === 'boolean') return v;
+ if (typeof v === 'number') return v === 1;
+ if (typeof v === 'string') {
+ const s = v.trim().toLowerCase();
+ return s === 'true' || s === 't' || s === '1' || s === 'oui' || s === 'yes' || s === 'y';
+ }
+ return false;
+ };
+
const jours = [];
- if (horaire.circule_lundi) jours.push('Lun');
- if (horaire.circule_mardi) jours.push('Mar');
- if (horaire.circule_mercredi) jours.push('Mer');
- if (horaire.circule_jeudi) jours.push('Jeu');
- if (horaire.circule_vendredi) jours.push('Ven');
- if (horaire.circule_samedi) jours.push('Sam');
- if (horaire.circule_dimanche) jours.push('Dim');
- return jours;
-}export async function updateQuaisReelsHoraire(horaireId, quaisParOrdre, isPropagation = false) {
+ if (parseBool(horaire.circule_lundi)) jours.push('Lun');
+ if (parseBool(horaire.circule_mardi)) jours.push('Mar');
+ if (parseBool(horaire.circule_mercredi)) jours.push('Mer');
+ if (parseBool(horaire.circule_jeudi)) jours.push('Jeu');
+ if (parseBool(horaire.circule_vendredi)) jours.push('Ven');
+ if (parseBool(horaire.circule_samedi)) jours.push('Sam');
+ if (parseBool(horaire.circule_dimanche)) jours.push('Dim');
+
+ if (jours.length > 0) return jours;
+
+ // Fallback si formaté en chaîne de caractères
+ const txt = String(horaire.jours_circulation || horaire.frequence || horaire.periodicite || '').toLowerCase();
+ if (txt) {
+ if (txt.includes('tous') || txt.includes('quotidien') || txt.includes('7j')) {
+ return ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'];
+ }
+ if (txt.includes('lun') && txt.includes('ven')) {
+ return ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven'];
+ }
+ if (txt.includes('lun') && txt.includes('sam')) {
+ return ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'];
+ }
+ if (txt.includes('sam') && txt.includes('dim')) {
+ return ['Sam', 'Dim'];
+ }
+ if (txt.includes('dim')) return ['Dim'];
+ if (txt.includes('sam')) return ['Sam'];
+ if (txt.includes('ven')) return ['Ven'];
+ }
+
+ // Fallback jours personnalisés
+ if (Array.isArray(horaire.jours_personnalises) && horaire.jours_personnalises.length > 0) {
+ const dows = new Set();
+ const mapDow = ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'];
+ horaire.jours_personnalises.forEach(j => {
+ if (j && j.circule !== false && j.date) {
+ const d = new Date(j.date).getDay();
+ if (!isNaN(d)) dows.add(mapDow[d]);
+ }
+ });
+ if (dows.size > 0) {
+ return ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'].filter(d => dows.has(d));
+ }
+ }
+
+ return [];
+}
+
+export async function updateQuaisReelsHoraire(horaireId, quaisParOrdre, isPropagation = false) {
if (!isSupabaseConfigured) {
return { data: null, error: null };
}
diff --git a/src/pages/fiches-horaires/FichesHoraires.jsx b/src/pages/fiches-horaires/FichesHoraires.jsx
index 9e9ef70..58317cf 100644
--- a/src/pages/fiches-horaires/FichesHoraires.jsx
+++ b/src/pages/fiches-horaires/FichesHoraires.jsx
@@ -620,6 +620,7 @@ export default function FichesHoraires() {
return;
}
+ const defaultJours = manualTrainForm.jours_circulation || 'Lun à Ven*';
const newTrain = {
id: `manual_${Date.now()}`,
numero_train: manualTrainForm.numero_train || `T${Math.floor(1000 + Math.random() * 9000)}`,
@@ -629,7 +630,8 @@ export default function FichesHoraires() {
gare_arrivee_nom: manualTrainForm.gare_arrivee_nom || orderedGaresForLine[orderedGaresForLine.length - 1]?.nom || 'Gare d\'arrivée',
heure_depart: manualTrainForm.heure_depart || '08:00',
heure_arrivee: manualTrainForm.heure_arrivee || '09:00',
- jours_circulation: manualTrainForm.jours_circulation || 'Lun à Ven',
+ jours_circulation: defaultJours,
+ _custom_jours: defaultJours,
est_substitution: !!manualTrainForm.est_substitution,
gares_desservies: []
};
@@ -644,12 +646,25 @@ export default function FichesHoraires() {
heure_depart: '08:00',
gare_arrivee_nom: '',
heure_arrivee: '09:00',
- jours_circulation: 'Lun à Ven',
+ jours_circulation: 'Lun à Ven*',
est_substitution: false
});
showToast(`Circulation ${newTrain.numero_train} ajoutée avec succès !`);
};
+ const handleUpdateTrainJours = (trainId, newJours) => {
+ setWizardOrderedTrains(prev => prev.map(t => {
+ if (t.id === trainId) {
+ return {
+ ...t,
+ _custom_jours: newJours,
+ jours_circulation: newJours
+ };
+ }
+ return t;
+ }));
+ };
+
// Helper to get direction of a circulation
const getCirculationDirection = (circ, orderedGares = []) => {
if (!circ) return 'aller';
@@ -1992,9 +2007,36 @@ export default function FichesHoraires() {
{train.heure_depart || '—'} → {train.heure_arrivee || '—'}
-
- {formatCirculationDaysCompact(train)}
-
+
|
@@ -2281,13 +2323,26 @@ export default function FichesHoraires() {
- setManualTrainForm(prev => ({ ...prev, jours_circulation: e.target.value }))}
- />
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)}
- {/* GTFS Step 3: Choix de la ligne */}
+ {/* GTFS Step 3: Choix de la ligne / Multi-lignes Accordion */}
{gtfsStep === 3 && gtfsParsedData && !gtfsLoading && (
-
- 3. Choix de la ligne à importer
- setGtfsStep(2)}>Retour au Visualisateur
-
+ {(() => {
+ const filteredRoutes = (gtfsParsedData.routes || []).filter(r => {
+ if (gtfsRouteTypeFilter !== 'all' && String(r.route_type) !== gtfsRouteTypeFilter) return false;
+ if (gtfsRouteSearch) {
+ const q = gtfsRouteSearch.toLowerCase();
+ const matchRoute = (r.route_id || '').toLowerCase().includes(q) ||
+ (r.route_short_name || '').toLowerCase().includes(q) ||
+ (r.route_long_name || '').toLowerCase().includes(q);
+ if (matchRoute) return true;
+ // Search also inside trips of this route
+ const routeTrips = gtfsParsedData.tripsByRoute[r.route_id] || [];
+ return routeTrips.some(t => (t.trip_id || '').toLowerCase().includes(q) || (t.trip_headsign || '').toLowerCase().includes(q) || (t.trip_short_name || '').toLowerCase().includes(q));
+ }
+ return true;
+ });
-
- setGtfsRouteSearch(e.target.value)}
- />
-
- {[
- { key: 'all', label: 'Toutes les lignes' },
- { key: '2', label: '🚆 Trains' },
- { key: '3', label: '🚌 Substitutions (Bus/Car)' },
- { key: '0', label: '🚊 Tram' }
- ].map(f => (
-
- ))}
-
-
+ const totalSelectedTripsCount = gtfsSelectedTripIds.size;
+ const selectedRoutesCount = new Set(
+ (gtfsParsedData.trips || [])
+ .filter(t => gtfsSelectedTripIds.has(t.trip_id))
+ .map(t => t.route_id)
+ ).size;
-
- {(gtfsParsedData.routes || [])
- .filter(r => {
- if (gtfsRouteTypeFilter !== 'all' && String(r.route_type) !== gtfsRouteTypeFilter) return false;
- if (gtfsRouteSearch) {
- const q = gtfsRouteSearch.toLowerCase();
- return (r.route_id || '').toLowerCase().includes(q) ||
- (r.route_short_name || '').toLowerCase().includes(q) ||
- (r.route_long_name || '').toLowerCase().includes(q);
- }
- return true;
- })
- .map(r => {
- const tripsCount = (gtfsParsedData.tripsByRoute[r.route_id] || []).length;
- const bgColor = r.route_color ? `#${r.route_color}` : '#2563eb';
- const textColor = r.route_text_color ? `#${r.route_text_color}` : '#ffffff';
- const isBus = String(r.route_type) === '3' || (r.route_type >= 700 && r.route_type <= 716);
- return (
- handleSelectGTFSRoute(r.route_id)}
- >
-
-
-
- {r.route_short_name || r.route_id}
-
-
- {isBus ? '🚌 Substitution' : '🚆 Train'}
-
-
-
- {r.route_long_name || r.route_short_name || r.route_id}
-
-
-
-
- {tripsCount} circulation(s)
-
- Sélectionner →
+ const allFilteredTrips = filteredRoutes.flatMap(r => gtfsParsedData.tripsByRoute[r.route_id] || []);
+ const allFilteredSelected = allFilteredTrips.length > 0 && allFilteredTrips.every(t => gtfsSelectedTripIds.has(t.trip_id));
+ const allAccordionOpen = filteredRoutes.length > 0 && filteredRoutes.every(r => gtfsOpenAccordionRouteIds.has(r.route_id));
+
+ return (
+
+
+
+
+ 3. Choix des lignes & circulations à importer
+
+
+ Sélectionnez les lignes et cochez les horaires que vous souhaitez importer dans votre base.
- );
- })}
-
+
+
+
+
+
+ setGtfsStep(2)}>Retour au Visualisateur
+
+
+
+ {/* Filter & Action bar */}
+
+
+ setGtfsRouteSearch(e.target.value)}
+ />
+
+ {[
+ { key: 'all', label: 'Toutes les lignes' },
+ { key: '2', label: '🚆 Trains' },
+ { key: '3', label: '🚌 Substitutions (Bus/Car)' },
+ { key: '0', label: '🚊 Tram' }
+ ].map(f => (
+
+ ))}
+
+
+
+
+ handleSelectAllRoutesAndTrips(filteredRoutes, !allFilteredSelected)}
+ >
+ {allFilteredSelected ? 'Tout désélectionner' : 'Tout sélectionner'}
+
+ {gtfsViewMode === 'accordion' && (
+ allAccordionOpen ? handleCollapseAllAccordion() : handleExpandAllAccordion(filteredRoutes)}
+ >
+ {allAccordionOpen ? 'Replier tout' : 'Déplier tout'}
+
+ )}
+
+
+
+ {/* ACCORDION VIEW MODE */}
+ {gtfsViewMode === 'accordion' && (
+
+ {filteredRoutes.length === 0 ? (
+
+
+ Aucune ligne ne correspond à votre recherche.
+
+ ) : (
+ filteredRoutes.map(r => {
+ const routeTrips = gtfsParsedData.tripsByRoute[r.route_id] || [];
+ const selectedInRoute = routeTrips.filter(t => gtfsSelectedTripIds.has(t.trip_id)).length;
+ const isAllRouteSelected = routeTrips.length > 0 && selectedInRoute === routeTrips.length;
+ const isPartiallySelected = selectedInRoute > 0 && !isAllRouteSelected;
+ const isOpen = gtfsOpenAccordionRouteIds.has(r.route_id);
+
+ const bgColor = r.route_color ? `#${r.route_color}` : '#2563eb';
+ const textColor = r.route_text_color ? `#${r.route_text_color}` : '#ffffff';
+ const isBus = String(r.route_type) === '3' || (r.route_type >= 700 && r.route_type <= 716);
+
+ return (
+ 0 ? 'has-selected' : ''}`}>
+ handleToggleAccordionRoute(r.route_id)}>
+
+ {
+ if (el) el.indeterminate = isPartiallySelected;
+ }}
+ onChange={(e) => {
+ e.stopPropagation();
+ handleToggleRouteSelection(r.route_id, !isAllRouteSelected);
+ }}
+ onClick={(e) => e.stopPropagation()}
+ style={{ width: 18, height: 18, cursor: 'pointer' }}
+ />
+
+ {r.route_short_name || r.route_id}
+
+
+ {isBus ? '🚌 Substitution' : '🚆 Train'}
+
+
+ {r.route_long_name || r.route_short_name || r.route_id}
+
+
+
+
+ 0 ? 'active' : ''}`}>
+ {selectedInRoute} / {routeTrips.length} circulation(s)
+
+
+
+
+
+ {isOpen && (
+
+
+
+ {routeTrips.length} circulations répertoriées sur cette ligne
+
+
+ handleToggleRouteSelection(r.route_id, !isAllRouteSelected)}
+ >
+ {isAllRouteSelected ? 'Désélectionner la ligne' : 'Sélectionner toute la ligne'}
+
+ handleSelectGTFSRoute(r.route_id)}
+ >
+ Vue détaillée →
+
+
+
+
+
+
+
+
+ |
+ {
+ if (el) el.indeterminate = isPartiallySelected;
+ }}
+ onChange={() => handleToggleRouteSelection(r.route_id, !isAllRouteSelected)}
+ />
+ |
+ Train / Sillon |
+ Départ |
+ Arrivée |
+ Arrêts |
+ Jours de circulation |
+ Service Annuel |
+
+
+
+ {routeTrips.map(trip => {
+ const isTripSelected = gtfsSelectedTripIds.has(trip.trip_id);
+ const stopTimes = gtfsParsedData.stopTimesByTrip[trip.trip_id] || [];
+ const firstStop = stopTimes[0];
+ const lastStop = stopTimes[stopTimes.length - 1];
+ const firstStopName = firstStop ? (gtfsParsedData.stopsById[firstStop.stop_id]?.stop_name || firstStop.stop_id) : '—';
+ const lastStopName = lastStop ? (gtfsParsedData.stopsById[lastStop.stop_id]?.stop_name || lastStop.stop_id) : '—';
+ const depTime = firstStop ? formatGTFSTime(firstStop.departure_time) : '--:--';
+ const arrTime = lastStop ? formatGTFSTime(lastStop.arrival_time) : '--:--';
+
+ const cal = gtfsParsedData.calendarByService[trip.service_id];
+ const matchedSA = matchServiceAnnuelForGTFSTrip(
+ trip,
+ gtfsParsedData.calendarByService,
+ gtfsParsedData.calendarDatesByService,
+ servicesAnnuels
+ );
+
+ return (
+ handleToggleGTFSTrip(trip.trip_id)}
+ >
+ | e.stopPropagation()}>
+ handleToggleGTFSTrip(trip.trip_id)}
+ style={{ width: 16, height: 16, cursor: 'pointer' }}
+ />
+ |
+
+
+ {trip.trip_headsign || trip.trip_short_name || trip.trip_id}
+
+ {trip.trip_short_name && trip.trip_headsign && trip.trip_short_name !== trip.trip_headsign && (
+ {trip.trip_short_name}
+ )}
+ |
+
+ {depTime}
+ {firstStopName}
+ |
+
+ {arrTime}
+ {lastStopName}
+ |
+
+
+ {stopTimes.length} arrêts
+
+ |
+
+ {cal ? (
+
+
+ {['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'].map((dayKey, idx) => {
+ const labels = ['L', 'M', 'M', 'J', 'V', 'S', 'D'];
+ const active = cal[dayKey] === '1';
+ return (
+
+ {labels[idx]}
+
+ );
+ })}
+
+
+ {formatGTFSDate(cal.start_date)} → {formatGTFSDate(cal.end_date)}
+
+
+ ) : (
+ Ponctuel
+ )}
+ |
+
+ {matchedSA ? (
+
+ 📅 {matchedSA.nom}
+
+ ) : (
+ ⚠️ Hors SA
+ )}
+ |
+
+ );
+ })}
+
+
+
+
+ )}
+
+ );
+ })
+ )}
+
+ )}
+
+ {/* GRID VIEW MODE */}
+ {gtfsViewMode === 'grid' && (
+
+ {filteredRoutes.map(r => {
+ const routeTrips = (gtfsParsedData.tripsByRoute[r.route_id] || []);
+ const tripsCount = routeTrips.length;
+ const selectedInRoute = routeTrips.filter(t => gtfsSelectedTripIds.has(t.trip_id)).length;
+ const bgColor = r.route_color ? `#${r.route_color}` : '#2563eb';
+ const textColor = r.route_text_color ? `#${r.route_text_color}` : '#ffffff';
+ const isBus = String(r.route_type) === '3' || (r.route_type >= 700 && r.route_type <= 716);
+ return (
+ 0 ? 'selected' : ''}`}
+ onClick={() => handleSelectGTFSRoute(r.route_id)}
+ >
+
+
+
+ {r.route_short_name || r.route_id}
+
+
+ {isBus ? '🚌 Substitution' : '🚆 Train'}
+
+
+
+ {r.route_long_name || r.route_short_name || r.route_id}
+
+
+
+
+ {selectedInRoute > 0 ? `${selectedInRoute} / ${tripsCount}` : `${tripsCount}`} circulation(s)
+
+ Sélectionner →
+
+
+ );
+ })}
+
+ )}
+
+ {/* STICKY BOTTOM ACTION BAR FOR MULTI-SELECTION */}
+ {totalSelectedTripsCount > 0 && (
+
+
+
+
+
+ {totalSelectedTripsCount} circulation(s) sélectionnée(s)
+
+
+ Réparties sur {selectedRoutesCount} ligne(s)
+
+
+
+
+ setGtfsSelectedTripIds(new Set())}
+ >
+ Tout désélectionner
+
+
+ {gtfsConverting ? (
+ <>
+
+ Conversion en cours...
+ >
+ ) : (
+ <>
+ Convertir et Importer les {totalSelectedTripsCount} circulations
+
+ >
+ )}
+
+
+
+ )}
+
+ );
+ })()}
)}
diff --git a/src/utils/ficheHorairePdfGenerator.js b/src/utils/ficheHorairePdfGenerator.js
index 0b7edaf..2bac9d1 100644
--- a/src/utils/ficheHorairePdfGenerator.js
+++ b/src/utils/ficheHorairePdfGenerator.js
@@ -82,15 +82,197 @@ export function isCirculationCar(circ) {
}
/**
- * Formate les jours de circulation en libellé compact conforme au modèle officiel
+ * Formate les jours de circulation en libellé compact conforme au modèle officiel SNCF / TER
*/
export function formatCirculationDaysCompact(horaire) {
if (!horaire) return 'Tous les jours';
+ // 0. Si l'utilisateur a explicitement personnalisé les jours pour cette circulation
+ if (horaire._custom_jours && typeof horaire._custom_jours === 'string') {
+ return horaire._custom_jours.trim();
+ }
+
+ const parseBool = (v) => {
+ if (typeof v === 'boolean') return v;
+ if (typeof v === 'number') return v === 1;
+ if (typeof v === 'string') {
+ const s = v.trim().toLowerCase();
+ return s === 'true' || s === 't' || s === '1' || s === 'oui' || s === 'yes' || s === 'y';
+ }
+ return false;
+ };
+
const numStr = String(horaire.numero_train || '').trim();
- // 1. Table de référence exacte des circulations TER BFC / AURA (Ligne Dijon <-> Seurre <-> Bourg-en-Bresse)
- const specificTrainDays = {
+ // 1. Analyse approfondie des jours personnalisés (dates réelles dans la base)
+ const jp = horaire.jours_personnalises;
+ if (Array.isArray(jp) && jp.length > 0) {
+ const activeDates = jp.filter(j => j && j.circule !== false && j.date);
+ if (activeDates.length > 0) {
+ const dowsCount = { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
+ activeDates.forEach(j => {
+ const dStr = String(j.date).slice(0, 10);
+ const parts = dStr.split('-');
+ if (parts.length === 3) {
+ const dt = new Date(Date.UTC(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10), 12, 0, 0));
+ const dow = dt.getUTCDay(); // 0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat
+ if (!isNaN(dow)) dowsCount[dow]++;
+ }
+ });
+
+ const maxCount = Math.max(...Object.values(dowsCount));
+ // Un jour de la semaine est considéré comme actif s'il représente au moins 10% des occurrences maximales
+ const threshold = Math.max(1, maxCount * 0.1);
+
+ const hasSun = dowsCount[0] >= threshold;
+ const hasMon = dowsCount[1] >= threshold;
+ const hasTue = dowsCount[2] >= threshold;
+ const hasWed = dowsCount[3] >= threshold;
+ const hasThu = dowsCount[4] >= threshold;
+ const hasFri = dowsCount[5] >= threshold;
+ const hasSat = dowsCount[6] >= threshold;
+
+ const activeDows = [hasSun, hasMon, hasTue, hasWed, hasThu, hasFri, hasSat].filter(Boolean).length;
+
+ if (activeDows === 7 || (hasMon && hasTue && hasWed && hasThu && hasFri && hasSat && hasSun)) {
+ return 'Tous les jours';
+ }
+ if (hasMon && hasTue && hasWed && hasThu && hasFri && !hasSat && !hasSun) {
+ return 'Lun à Ven*';
+ }
+ if (hasMon && hasTue && hasWed && hasThu && hasFri && hasSat && !hasSun) {
+ return 'Lun à Sam*';
+ }
+ if (!hasMon && !hasTue && !hasWed && !hasThu && !hasFri && hasSat && hasSun) {
+ return 'Sam et Dim';
+ }
+ if (activeDows === 1 && hasSun) {
+ return 'Dim et Fêtes';
+ }
+ if (activeDows === 1 && hasSat) {
+ return 'Sam';
+ }
+ if (activeDows === 1 && hasFri) {
+ return 'Ven';
+ }
+ if (hasFri && hasSun && !hasMon && !hasTue && !hasWed && !hasThu && !hasSat) {
+ return 'Ven, Dim et Fêtes';
+ }
+ if (hasMon && hasTue && hasWed && hasThu && !hasFri && !hasSat && !hasSun) {
+ return 'Lun à Jeu';
+ }
+ if (hasMon && hasTue && hasWed && hasThu && hasFri && !hasSat && hasSun) {
+ return 'Sauf Sam';
+ }
+ if (!hasMon && hasTue && hasWed && hasThu && hasFri && hasSat && !hasSun) {
+ return 'Mar à Sam';
+ }
+
+ const activeLabels = [];
+ if (hasMon) activeLabels.push('Lun');
+ if (hasTue) activeLabels.push('Mar');
+ if (hasWed) activeLabels.push('Mer');
+ if (hasThu) activeLabels.push('Jeu');
+ if (hasFri) activeLabels.push('Ven');
+ if (hasSat) activeLabels.push('Sam');
+ if (hasSun) activeLabels.push('Dim');
+ if (activeLabels.length > 0 && activeLabels.length < 7) {
+ return activeLabels.join(', ');
+ }
+ }
+ }
+
+ // 2. Analyse des booléens de jours de circulation
+ const cLu = parseBool(horaire.circule_lundi);
+ const cMa = parseBool(horaire.circule_mardi);
+ const cMe = parseBool(horaire.circule_mercredi);
+ const cJe = parseBool(horaire.circule_jeudi);
+ const cVe = parseBool(horaire.circule_vendredi);
+ const cSa = parseBool(horaire.circule_samedi);
+ const cDi = parseBool(horaire.circule_dimanche);
+ const cFer = parseBool(horaire.circule_jours_feries) || parseBool(horaire.circule_dimanches_feries);
+
+ const anyDayTrue = cLu || cMa || cMe || cJe || cVe || cSa || cDi;
+
+ if (anyDayTrue) {
+ const allWeek = cLu && cMa && cMe && cJe && cVe && cSa && cDi;
+ const lu_ve = cLu && cMa && cMe && cJe && cVe && !cSa && !cDi;
+ const lu_sa = cLu && cMa && cMe && cJe && cVe && cSa && !cDi;
+ const sa_di = !cLu && !cMa && !cMe && !cJe && !cVe && cSa && cDi;
+ const di_only = !cLu && !cMa && !cMe && !cJe && !cVe && !cSa && cDi;
+ const sa_only = !cLu && !cMa && !cMe && !cJe && !cVe && cSa && !cDi;
+ const ve_di = !cLu && !cMa && !cMe && !cJe && cVe && !cSa && cDi;
+ const ve_only = !cLu && !cMa && !cMe && !cJe && cVe && !cSa && !cDi;
+ const lu_je = cLu && cMa && cMe && cJe && !cVe && !cSa && !cDi;
+ const sauf_sa = cLu && cMa && cMe && cJe && cVe && !cSa && cDi;
+ const mar_sa = !cLu && cMa && cMe && cJe && cVe && cSa && !cDi;
+
+ if (allWeek) return 'Tous les jours';
+ if (lu_ve) return cFer ? 'Lun à Ven' : 'Lun à Ven*';
+ if (lu_sa) return cFer ? 'Lun à Sam' : 'Lun à Sam*';
+ if (sa_di) return cFer ? 'Sam, Dim et Fêtes' : 'Sam et Dim';
+ if (di_only) return cFer ? 'Dim et Fêtes' : 'Dim';
+ if (ve_di) return cFer ? 'Ven, Dim et Fêtes' : 'Ven et Dim';
+ if (sa_only) return 'Sam';
+ if (ve_only) return 'Ven';
+ if (lu_je) return 'Lun à Jeu';
+ if (sauf_sa) return 'Sauf Sam';
+ if (mar_sa) return 'Mar à Sam';
+
+ // Cas de 4 jours ouvrés (ex: Lu, Ma, Je, Ve)
+ if (cLu && cMa && !cMe && cJe && cVe && !cSa && !cDi) return 'Lu, Ma, Je, Ve';
+ if (cLu && !cMa && cMe && cJe && cVe && !cSa && !cDi) return 'Lu, Me, Je, Ve';
+ if (cLu && cMa && cMe && !cJe && cVe && !cSa && !cDi) return 'Lu, Ma, Me, Ve';
+
+ // Combinaisons de 2 jours
+ if (cMe && cVe && !cLu && !cMa && !cJe && !cSa && !cDi) return 'Mer, Ven';
+ if (cLu && cVe && !cMa && !cMe && !cJe && !cSa && !cDi) return 'Lun, Ven';
+ if (cLu && cMe && !cMa && !cJe && !cVe && !cSa && !cDi) return 'Lun, Mer';
+
+ // Jour unique
+ if (cLu && !cMa && !cMe && !cJe && !cVe && !cSa && !cDi) return 'Lun';
+ if (!cLu && cMa && !cMe && !cJe && !cVe && !cSa && !cDi) return 'Mar';
+ if (!cLu && !cMa && cMe && !cJe && !cVe && !cSa && !cDi) return 'Mer';
+ if (!cLu && !cMa && !cMe && cJe && !cVe && !cSa && !cDi) return 'Jeu';
+
+ const activeDays = [];
+ if (cLu) activeDays.push('Lu');
+ if (cMa) activeDays.push('Ma');
+ if (cMe) activeDays.push('Me');
+ if (cJe) activeDays.push('Je');
+ if (cVe) activeDays.push('Ve');
+ if (cSa) activeDays.push('Sa');
+ if (cDi) activeDays.push('Di');
+ if (activeDays.length > 0) return activeDays.join(', ');
+ }
+
+ // 3. Détection par champ texte (jours_circulation, frequence, periodicite)
+ const textDay = String(horaire.jours_circulation || horaire.frequence || horaire.periodicite || '').trim();
+ if (textDay) {
+ const tLower = textDay.toLowerCase();
+ if (/lun.*ven.*fer|lun.*ven\*/i.test(tLower)) return 'Lun à Ven*';
+ if (/lun.*ven/i.test(tLower) && !/sam/i.test(tLower) && !/dim/i.test(tLower)) return 'Lun à Ven';
+ if (/lun.*sam.*fer|lun.*sam\*/i.test(tLower)) return 'Lun à Sam*';
+ if (/lun.*sam/i.test(tLower) && !/dim/i.test(tLower)) return 'Lun à Sam';
+ if (/ven.*dim.*f[eê]t/i.test(tLower)) return 'Ven, Dim et Fêtes';
+ if (/dim.*f[eê]t/i.test(tLower)) return 'Dim et Fêtes';
+ if (/sam.*dim.*f[eê]t/i.test(tLower)) return 'Sam, Dim et Fêtes';
+ if (/sam.*dim|week-?end/i.test(tLower) && !/lun/i.test(tLower)) return 'Sam et Dim';
+ if (/sauf.*sam/i.test(tLower)) return 'Sauf Sam';
+ if (/sauf.*dim/i.test(tLower)) return 'Lun à Sam*';
+ if (/^dim(anche)?$/i.test(tLower)) return 'Dim';
+ if (/^sam(edi)?$/i.test(tLower)) return 'Sam';
+ if (/^ven(dredi)?$/i.test(tLower)) return 'Ven';
+ if (/^mer(credi)?$/i.test(tLower)) return 'Mer';
+ if (/tous.*jours|quotidien|7j/i.test(tLower)) return 'Tous les jours';
+
+ if (textDay.length <= 25 && !textDay.includes(';')) {
+ return textDay;
+ }
+ }
+
+ // 4. Table de référence de secours pour numéros standards TER BFC / AURA
+ const standardTrainDays = {
'891800': 'Lun à Ven*',
'891802': 'Lun à Ven*',
'891804': 'Tous les jours',
@@ -115,84 +297,10 @@ export function formatCirculationDaysCompact(horaire) {
'895922': 'Ven, Dim et Fêtes'
};
- if (numStr === '891818') {
- const textCheck = String(horaire.jours_circulation || '').toLowerCase();
- if (textCheck.includes('dim') || horaire.circule_dimanche === true) return 'Dim';
- return 'Lun à Ven*';
+ if (standardTrainDays[numStr]) {
+ return standardTrainDays[numStr];
}
- // 2. Détection par texte explicite (jours_circulation, frequence, periodicite)
- const textDay = String(horaire.jours_circulation || horaire.frequence || horaire.periodicite || '').toLowerCase().trim();
- if (textDay) {
- if (/lun.*ven.*fer|lun.*ven\*/i.test(textDay)) return 'Lun à Ven*';
- if (/lun.*ven/i.test(textDay) && !/sam/i.test(textDay) && !/dim/i.test(textDay)) return 'Lun à Ven';
- if (/lun.*sam.*fer|lun.*sam\*/i.test(textDay)) return 'Lun à Sam*';
- if (/lun.*sam/i.test(textDay) && !/dim/i.test(textDay)) return 'Lun à Sam';
- if (/sam.*dim|week-?end/i.test(textDay) && !/lun/i.test(textDay)) return 'Sam et Dim';
- if (/ven.*dim.*f[eê]t/i.test(textDay)) return 'Ven, Dim et Fêtes';
- if (/dim.*f[eê]t/i.test(textDay)) return 'Dim et Fêtes';
- if (/sauf.*sam/i.test(textDay)) return 'Sauf Sam';
- if (/^dim(anche)?$/i.test(textDay)) return 'Dim';
- if (/^sam(edi)?$/i.test(textDay)) return 'Sam';
- if (/^ven(dredi)?$/i.test(textDay)) return 'Ven';
- if (/tous.*jours|quotidien|7j/i.test(textDay)) return 'Tous les jours';
- }
-
- // 3. Détection par booléens ou indicateurs de jours (avec coercion robuste)
- const parseBool = (v) => {
- if (typeof v === 'boolean') return v;
- if (typeof v === 'number') return v === 1;
- if (typeof v === 'string') {
- const s = v.trim().toLowerCase();
- return s === 'true' || s === 't' || s === '1' || s === 'oui';
- }
- return false;
- };
-
- const cLu = parseBool(horaire.circule_lundi);
- const cMa = parseBool(horaire.circule_mardi);
- const cMe = parseBool(horaire.circule_mercredi);
- const cJe = parseBool(horaire.circule_jeudi);
- const cVe = parseBool(horaire.circule_vendredi);
- const cSa = parseBool(horaire.circule_samedi);
- const cDi = parseBool(horaire.circule_dimanche);
- const cFer = parseBool(horaire.circule_jours_feries);
-
- const hasAnyDayBool = [cLu, cMa, cMe, cJe, cVe, cSa, cDi].some(Boolean);
-
- if (hasAnyDayBool) {
- const lu_ve = cLu && cMa && cMe && cJe && cVe && !cSa && !cDi;
- const lu_sa = cLu && cMa && cMe && cJe && cVe && cSa && !cDi;
- const tous = cLu && cMa && cMe && cJe && cVe && cSa && cDi;
-
- if (tous) return 'Tous les jours';
- if (lu_ve) return cFer ? 'Lun à Ven' : 'Lun à Ven*';
- if (lu_sa) return cFer ? 'Lun à Sam' : 'Lun à Sam*';
- if (!cLu && !cMa && !cMe && !cJe && !cVe && cSa && cDi) return 'Sam et Dim';
- if (!cLu && !cMa && !cMe && !cJe && !cVe && !cSa && cDi) return cFer ? 'Dim et Fêtes' : 'Dim';
- if (!cLu && !cMa && !cMe && !cJe && cVe && !cSa && cDi) return 'Ven, Dim et Fêtes';
- if (cSa && !cDi && !lu_ve) return 'Sam';
- if (cLu && cMa && cMe && cJe && cVe && !cSa && cDi) return 'Sauf Sam';
-
- // Jours individuels
- const days = [];
- if (cLu) days.push('Lu');
- if (cMa) days.push('Ma');
- if (cMe) days.push('Me');
- if (cJe) days.push('Je');
- if (cVe) days.push('Ve');
- if (cSa) days.push('Sa');
- if (cDi) days.push('Di');
- if (days.length > 0) return days.join(', ');
- }
-
- // 4. Si la table de référence par numéro de train s'applique
- if (specificTrainDays[numStr]) {
- return specificTrainDays[numStr];
- }
-
- if (textDay) return textDay;
-
return 'Tous les jours';
}
|