import React, { useEffect, useState, useCallback, useRef } from 'react';
import { getAllHoraires } from '../../api/horaires';
import { getAllPerturbations } from '../../api/perturbations';
import { getAllTravaux } from '../../api/travaux';
import { getAllMateriels } from '../../api/materielsRoulants';
import { getCurrentUser, getScopedRegionId } from '../../utils/userUtils';
import './Dashboard.css';
export default function Dashboard() {
// ---------------------------------------------------------------------------
// ÉTATS DES DONNÉES & UTILISATEUR
// ---------------------------------------------------------------------------
const [loading, setLoading] = useState(true);
const [lastRefreshed, setLastRefreshed] = useState(new Date());
const [currentUser, setCurrentUser] = useState(null);
const [scopedRegionId, setScopedRegionId] = useState(null);
// Données brutes
const [horaires, setHoraires] = useState([]);
const [perturbations, setPerturbations] = useState([]);
const [travaux, setTravaux] = useState([]);
const [materiels, setMateriels] = useState([]);
// Navigation par onglet ('overview', 'schedules', 'incidents', 'works', 'fleet')
const [activeTab, setActiveTab] = useState('overview');
// Options & Auto-refresh
const [autoRefresh, setAutoRefresh] = useState(false);
const [refreshCountdown, setRefreshCountdown] = useState(30);
// Filtres et recherche
const [searchTerm, setSearchTerm] = useState('');
const [selectedTypeFilter, setSelectedTypeFilter] = useState('all');
const [selectedLineFilter, setSelectedLineFilter] = useState('');
const [selectedPerturbationType, setSelectedPerturbationType] = useState('all');
const [punctualityFilterMode, setPunctualityFilterMode] = useState('all'); // 'all', 'delayed', 'ontime'
const [onlyWithStops, setOnlyWithStops] = useState(false);
const [onlyWithTrack, setOnlyWithTrack] = useState(false);
// Limite dynamique des départs imminents (contrôlée par wcs-counter)
const [imminentLimit, setImminentLimit] = useState(6);
// Modal de détail interactif
const [selectedItem, setSelectedItem] = useState(null); // { type: 'horaire'|'perturbation', data: ... }
// Références WCS Web Components
const tabsRef = useRef(null);
const modalRef = useRef(null);
const switchRef = useRef(null);
const radioGroupRef = useRef(null);
const checkStopsRef = useRef(null);
const checkTrackRef = useRef(null);
const counterRef = useRef(null);
// ---------------------------------------------------------------------------
// CHARGEMENT COMPLET DES DONNÉES DU RÉSEAU
// ---------------------------------------------------------------------------
const loadDashboardData = useCallback(async () => {
setLoading(true);
try {
const user = getCurrentUser();
setCurrentUser(user);
const regionId = getScopedRegionId(user);
setScopedRegionId(regionId || null);
// Chargement en parallèle de toutes les entités du système
const [horairesRes, perturbationsRes, travauxRes, materielsRes] = await Promise.all([
getAllHoraires(regionId),
getAllPerturbations(regionId),
getAllTravaux(regionId).catch(() => ({ data: [] })),
getAllMateriels().catch(() => ({ data: [] }))
]);
setHoraires(horairesRes?.data || []);
setPerturbations(perturbationsRes?.data || []);
setTravaux(travauxRes?.data || []);
setMateriels(materielsRes?.data || []);
setLastRefreshed(new Date());
setRefreshCountdown(30);
} catch (error) {
console.error('[Dashboard] Erreur de chargement des données ferroviaires:', error);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
loadDashboardData();
}, [loadDashboardData]);
// Synchronisation WCS Tabs
useEffect(() => {
const tabsEl = tabsRef.current;
if (tabsEl) {
const handleTabChange = (e) => {
const key = e.detail?.selectedKey || e.detail?.tabName;
if (key) setActiveTab(key);
};
tabsEl.addEventListener('tabChange', handleTabChange);
return () => tabsEl.removeEventListener('tabChange', handleTabChange);
}
}, []);
// Synchronisation WCS Modal
useEffect(() => {
const modalEl = modalRef.current;
if (modalEl) {
const handleModalClose = () => setSelectedItem(null);
modalEl.addEventListener('wcsDialogClosed', handleModalClose);
return () => modalEl.removeEventListener('wcsDialogClosed', handleModalClose);
}
}, []);
// Synchronisation WCS Switch Auto-Refresh
useEffect(() => {
const switchEl = switchRef.current;
if (switchEl) {
const handleSwitch = (e) => {
setAutoRefresh(!!e.detail?.checked);
};
switchEl.addEventListener('wcsChange', handleSwitch);
return () => switchEl.removeEventListener('wcsChange', handleSwitch);
}
}, []);
// Synchronisation WCS Radio-Group (Punctuality mode)
useEffect(() => {
const rgEl = radioGroupRef.current;
if (rgEl) {
const handleRadioChange = (e) => {
if (e.detail?.value) {
setPunctualityFilterMode(e.detail.value);
}
};
rgEl.addEventListener('wcsChange', handleRadioChange);
return () => rgEl.removeEventListener('wcsChange', handleRadioChange);
}
}, [activeTab]);
// Synchronisation WCS Checkboxes
useEffect(() => {
const stopsEl = checkStopsRef.current;
const trackEl = checkTrackRef.current;
const handleStopsChange = (e) => setOnlyWithStops(!!e.detail?.checked);
const handleTrackChange = (e) => setOnlyWithTrack(!!e.detail?.checked);
if (stopsEl) stopsEl.addEventListener('wcsChange', handleStopsChange);
if (trackEl) trackEl.addEventListener('wcsChange', handleTrackChange);
return () => {
if (stopsEl) stopsEl.removeEventListener('wcsChange', handleStopsChange);
if (trackEl) trackEl.removeEventListener('wcsChange', handleTrackChange);
};
}, [activeTab]);
// Synchronisation WCS Counter (Nombre de trains affichés)
useEffect(() => {
const counterEl = counterRef.current;
if (counterEl) {
const handleCounterChange = (e) => {
if (typeof e.detail?.value === 'number') {
setImminentLimit(e.detail.value);
}
};
counterEl.addEventListener('wcsChange', handleCounterChange);
return () => counterEl.removeEventListener('wcsChange', handleCounterChange);
}
}, [activeTab]);
// Timer de rafraîchissement automatique
useEffect(() => {
if (!autoRefresh) return undefined;
const interval = setInterval(() => {
setRefreshCountdown((prev) => {
if (prev <= 1) {
loadDashboardData();
return 30;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(interval);
}, [autoRefresh, loadDashboardData]);
// ---------------------------------------------------------------------------
// CALCULS STATISTIQUES & INDICATEURS MÉTIERS SNCF
// ---------------------------------------------------------------------------
const totalHoraires = horaires.length;
const activePerturbations = perturbations.filter(p => p.actif);
const countRetards = activePerturbations.filter(p => p.type_perturbation === 'retard').length;
const countSuppressions = activePerturbations.filter(p => p.type_perturbation === 'suppression').length;
const countModifs = activePerturbations.filter(p => p.type_perturbation === 'modification_parcours').length;
// Calcul du taux de ponctualité contractuel SNCF
const punctualityRate = totalHoraires > 0
? Math.max(0, Math.min(100, Math.round(((totalHoraires - activePerturbations.length) / totalHoraires) * 100)))
: 100;
// Extraction des gares, lignes et types uniques
const uniqueGares = new Set();
const uniqueLignes = new Set();
const trainTypes = new Set();
horaires.forEach(h => {
if (h.gare_depart_nom) uniqueGares.add(h.gare_depart_nom);
if (h.gare_arrivee_nom) uniqueGares.add(h.gare_arrivee_nom);
if (h.ligne_nom) uniqueLignes.add(h.ligne_nom);
if (h.type_train) trainTypes.add(h.type_train);
if (Array.isArray(h.gares_desservies)) {
h.gares_desservies.forEach(g => {
if (g.gare_nom) uniqueGares.add(g.gare_nom);
});
}
});
const linesList = Array.from(uniqueLignes).sort();
const typesList = Array.from(trainTypes).sort();
// Navigation applicative
const navigate = (path) => {
window.location.href = path;
};
const formatHeure = (h) => {
if (!h) return '—';
return String(h).substring(0, 5);
};
// Circulations filtrées
const filteredHoraires = horaires.filter(h => {
const pert = activePerturbations.find(p => p.horaire_id === h.id || p.numero_train === h.numero_train);
const hasIssue = !!pert;
// Filtre ponctualité (radio group)
if (punctualityFilterMode === 'delayed' && !hasIssue) return false;
if (punctualityFilterMode === 'ontime' && hasIssue) return false;
// Filtre checkboxes
if (onlyWithStops && (!Array.isArray(h.gares_desservies) || h.gares_desservies.length === 0)) return false;
if (onlyWithTrack && !h.quai_depart && (!Array.isArray(h.attributions_quais) || h.attributions_quais.length === 0)) return false;
// Filtre texte
if (searchTerm.trim()) {
const q = searchTerm.toLowerCase();
const matchNum = String(h.numero_train || '').toLowerCase().includes(q);
const matchDepart = String(h.gare_depart_nom || '').toLowerCase().includes(q);
const matchArrivee = String(h.gare_arrivee_nom || '').toLowerCase().includes(q);
const matchLigne = String(h.ligne_nom || '').toLowerCase().includes(q);
if (!matchNum && !matchDepart && !matchArrivee && !matchLigne) return false;
}
// Filtre type de train
if (selectedTypeFilter !== 'all' && h.type_train !== selectedTypeFilter) {
return false;
}
// Filtre ligne
if (selectedLineFilter && h.ligne_nom !== selectedLineFilter) {
return false;
}
return true;
});
// Perturbations filtrées
const filteredPerturbations = perturbations.filter(p => {
if (selectedPerturbationType === 'retard' && p.type_perturbation !== 'retard') return false;
if (selectedPerturbationType === 'suppression' && p.type_perturbation !== 'suppression') return false;
if (selectedPerturbationType === 'active' && !p.actif) return false;
return true;
});
// Départs imminents (triés par heure de départ) avec limite ajustable via wcs-counter
const imminentDepartures = [...horaires]
.sort((a, b) => String(a.heure_depart || '').localeCompare(String(b.heure_depart || '')))
.slice(0, imminentLimit);
return (
{/* ====================================================================
0. BANDEAU GALACTIQUE OFFICIEL SNCF (WCS-GALACTIC & WCS-GALACTIC-MENU)
==================================================================== */}
Portail Groupe SNCF
SNCF Réseau Infra
SNCF Voyageurs Mobilités
{/* ====================================================================
1. FIL D'ARIANE WCS (WCS-BREADCRUMB)
==================================================================== */}
Console Ferrovia
Supervision & Exploitation
{scopedRegionId ? `Secteur Régional ${scopedRegionId}` : 'Réseau National Unifié'}
{/* ====================================================================
2. CONTROLBAR SNCF MÉTIERS & HEADER SUPÉRIEUR
==================================================================== */}
Supervision Réseau & Exploitation
{scopedRegionId ? (
Secteur Régional : {scopedRegionId}
) : (
Réseau National & Multi-Régions
)}
0 ? 'is-disrupted' : 'is-nominal'}`}>
0 ? 'warning' : 'check_circle'}>
{activePerturbations.length > 0
? `${activePerturbations.length} Aléa(s) d'exploitation en cours`
: 'Exploitation Nominale'}
Console de Pilotage Ferroviaire
{currentUser?.prenom || currentUser?.nom ? (
, {currentUser.prenom || currentUser.nom}
) : null}
Supervision en temps réel des circulations, régulation des voies en gare, suivi de la ponctualité contractuelle et information voyageurs.
Synchro : {lastRefreshed.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
{autoRefresh && (
({refreshCountdown}s)
)}
Auto-sync :
Actualisation automatique de la console toutes les 30 secondes
Actualiser
Interroger immédiatement la base de données Supabase
navigate('/perturbations/creer')}
>
Signaler un aléa
Ouvrir le formulaire de déclaration d'incident et régulation
{/* Actions Métiers avec wcs-dropdown-header */}
Circulations & Voies
navigate('/horaires')}>
Nouvelle circulation
navigate('/horaires/attribution-quais')}>
Régulation voies & quais
navigate('/attribution-materiel-roulant')}>
Affectation rames
Aléas & Infrastructure
navigate('/perturbations/creer')}>
Déclarer un incident
navigate('/travaux/creer')}>
Déclarer des travaux
Information Voyageurs
navigate('/fiches-horaires')}>
Fiches horaires & affiches
navigate('/annonces-sonores')}>
Annonces sonores en gare
{/* ====================================================================
3. ALERTE DE SUPERVISION OPÉRATIONNELLE (WCS ALERT)
==================================================================== */}
{activePerturbations.length > 0 ? (
Alerte Réseau : {activePerturbations.length} perturbation(s) active(s) nécessitant une régulation
{countRetards} retard(s) cumulé(s), {countSuppressions} circulation(s) supprimée(s){countModifs > 0 ? ` et ${countModifs} modification(s) de tracé` : ''}. Les plans de transport de substitution et les affichages voyageurs sont actualisés.
setActiveTab('incidents')}
>
Examiner les perturbations
) : (
Situation Nominale : Aucune perturbation active signalée
L'ensemble des circulations ferroviaires s'effectue dans le respect des plages horaires et de la régularité cible.
100% de conformité observée
)}
{/* ====================================================================
4. CARTES KPI EXÉCUTIVES (WCS CARDS + SKELETONS LORS DU CHARGEMENT)
==================================================================== */}
{loading ? (
{[1, 2, 3, 4, 5].map(i => (
))}
) : (
{/* KPI 1 : Régularité / Ponctualité */}
setActiveTab('overview')}
id="kpi-punctuality-card"
>
= 95 ? 'wcs-success' : 'wcs-warning'}
shape="rounded"
>
{punctualityRate >= 95 ? 'Conforme SLA' : 'Sous surveillance'}
{punctualityRate}%
Taux de Régularité
Objectif cible contractuel SNCF : 95.0%
Détail ponctualité
Taux calculé d'après les circulations théoriques moins les incidents actifs
{/* KPI 2 : Circulations programmées */}
setActiveTab('schedules')}
id="kpi-trains-card"
>
{totalHoraires}
Circulations Actives
{totalHoraires - activePerturbations.length} trains conformes à l'horaire
Plan de transport
{/* KPI 3 : Perturbations & Aléas */}
setActiveTab('incidents')}
id="kpi-alerts-card"
>
0 ? 'wcs-danger' : 'wcs-success'}
shape="rounded"
>
{activePerturbations.length} active(s)
{perturbations.length}
Aléas Enregistrés
{countRetards} retard(s) · {countSuppressions} suppression(s)
Gestion des incidents
{/* KPI 4 : Travaux & Chantiers */}
setActiveTab('works')}
id="kpi-works-card"
>
{travaux.length}
Chantiers & Travaux
Impact voies et bus de substitution
{travaux.reduce((acc, t) => acc + (t.substitutions?.length || 0), 0)} substitution(s)
Suivi chantiers
{/* KPI 5 : Réseau & Gares */}
setActiveTab('fleet')}
id="kpi-network-card"
>
{uniqueGares.size}
Gares & Points d'Arrêt
{uniqueLignes.size} lignes · {materiels.length} rames affectées
{uniqueLignes.size} lignes commerciales
Réseau & Matériels
)}
{/* ====================================================================
5. NAVIGATION PAR ONGLETS AVEC WCS ACTION-BAR & WCS TABS
==================================================================== */}
Poste de Commande & Exploitation
{scopedRegionId ? `Secteur ${scopedRegionId}` : 'National'}
0 ? 'wcs-warning' : 'wcs-success'} shape="rounded">
{punctualityRate}% ponctualité
setActiveTab('overview')}
>
setActiveTab('schedules')}
>
setActiveTab('incidents')}
>
setActiveTab('works')}
>
setActiveTab('fleet')}
>
{/* Barre de filtrage avancée WCS pour les onglets de circulations avec wcs-form-field */}
{(activeTab === 'schedules' || activeTab === 'overview') && (
Recherche globale
setSearchTerm(e.target.value)}
>
Filtrage temps réel du plan de transport
Ligne commerciale
setSelectedLineFilter(e.target.value)}
>
Toutes les lignes commerciales
{linesList.map(l => (
{l}
))}
{linesList.length} ligne(s) répertoriée(s)
{/* Segmented Radio Group WCS pour filtrer l'état de ponctualité */}
setSelectedTypeFilter('all')}
>
{typesList.map(t => (
setSelectedTypeFilter(t)}
>
))}
)}
{/* ==================================================================
6. CONTENU DES ONGLETS
================================================================== */}
{/* ----------------------------------------------------------------
ONGLET 1 : VUE D'ENSEMBLE
---------------------------------------------------------------- */}
{activeTab === 'overview' && (
{/* Colonne Gauche : Départs & Incidents */}
{/* Widget Départs Imminents avec WCS-COUNTER */}
Départs Imminents & Plan de Transport
setActiveTab('schedules')}
>
Voir tout ({totalHoraires})
{/* Contrôle du volume avec WCS Counter */}
Volume de circulations affichées :
{imminentDepartures.length} / {totalHoraires}
{loading ? (
{[1, 2, 3].map(i => (
))}
) : imminentDepartures.length === 0 ? (
Aucune circulation enregistrée
Ajoutez des horaires ou importez un plan de transport régional.
navigate('/horaires')}>
Ajouter un horaire
) : (
{imminentDepartures.map(h => {
const pert = activePerturbations.find(p => p.horaire_id === h.id || p.numero_train === h.numero_train);
const isDelayed = pert?.type_perturbation === 'retard';
const isCancelled = pert?.type_perturbation === 'suppression';
return (
setSelectedItem({ type: 'horaire', data: h })}
>
{h.numero_train}
{h.type_train && (
{h.type_train}
)}
{h.ligne_nom && (
· {h.ligne_nom}
)}
{isCancelled ? (
Supprimé
) : isDelayed ? (
+{pert.duree_retard_minutes || 0} min
) : (
À l'heure
)}
{formatHeure(h.heure_depart)}
{h.gare_depart_nom || 'Départ'}
{formatHeure(h.heure_arrivee)}
{h.gare_arrivee_nom || 'Arrivée'}
{h.quai_depart && (
Voie {h.quai_depart}
)}
{Array.isArray(h.gares_desservies) && h.gares_desservies.length > 0 && (
{h.gares_desservies.length} arrêt(s) intermédiaire(s)
)}
Détails & Régulation
);
})}
)}
{/* Widget Accordion Perturbations Actives */}
Aléas & Incidents en Cours ({activePerturbations.length})
setActiveTab('incidents')}
>
Tous ({perturbations.length})
{activePerturbations.length === 0 ? (
Aucune perturbation active
Le trafic est entièrement régulier sans retard majeur signalé sur le réseau.
) : (
{activePerturbations.slice(0, 5).map(p => (
Train {p.horaire?.numero_train || p.numero_train || 'N/A'}
({p.horaire?.gare_depart_nom} → {p.horaire?.gare_arrivee_nom})
{p.type_perturbation === 'retard' ? `Retard +${p.duree_retard_minutes || 0} min` :
p.type_perturbation === 'suppression' ? 'Supprimé' : 'Modifié'}
Motif d'exploitation : {p.cause || 'Non spécifié'}
{p.cause_details && (
{p.cause_details}
)}
Signalé le {p.date_debut ? new Date(p.date_debut).toLocaleTimeString() : '—'}
setSelectedItem({ type: 'perturbation', data: p })}
>
Détails complets
navigate(`/perturbations/modifier/${p.id}`)}
>
Réguler
))}
)}
{/* Colonne Droite : Raccourcis Opérationnels & Synthèse Métiers */}
{/* Centre d'Actions d'Exploitation */}
Centre d'Actions d'Exploitation
{/* Synthèse du Réseau & Indicateurs Métiers */}
Indicateurs Régionaux SNCF
Conformité Plan de Transport
{punctualityRate}%
Matériels Roulants Actifs
{materiels.length} Rames
Flotte OK
Chantiers programmés
{travaux.length} Sites
Voies
Points d'arrêt surveillés
{uniqueGares.size} Gares
Opérationnel
)}
{/* ----------------------------------------------------------------
ONGLET 2 : CIRCULATIONS & HORAIRES (TABLE BOOTSTRAP SNCF MÉTIERS)
---------------------------------------------------------------- */}
{activeTab === 'schedules' && (
Plan de Transport & Circulations
{filteredHoraires.length} circulation(s) affichée(s) sur {totalHoraires} au référentiel
navigate('/horaires')}>
Nouvel Horaire
{filteredHoraires.length === 0 ? (
Aucune circulation trouvée
Modifiez vos critères de recherche ou réinitialisez les filtres.
{
setSearchTerm('');
setSelectedTypeFilter('all');
setSelectedLineFilter('');
setPunctualityFilterMode('all');
setOnlyWithStops(false);
setOnlyWithTrack(false);
}}
>
Réinitialiser tous les filtres
) : (
N° Train
Type
Ligne Commerciale
Départ
Arrivée
Arrêts
Voie
Statut Régulation
Actions
{filteredHoraires.map(h => {
const pert = activePerturbations.find(p => p.horaire_id === h.id || p.numero_train === h.numero_train);
const isDelayed = pert?.type_perturbation === 'retard';
const isCancelled = pert?.type_perturbation === 'suppression';
return (
setSelectedItem({ type: 'horaire', data: h })}
>
{h.numero_train}
{h.type_train || 'TER'}
{h.ligne_nom || '—'}
{formatHeure(h.heure_depart)}
{h.gare_depart_nom}
{formatHeure(h.heure_arrivee)}
{h.gare_arrivee_nom}
{Array.isArray(h.gares_desservies) ? h.gares_desservies.length : 0} arrêt(s)
{h.quai_depart ? (
Voie {h.quai_depart}
) : (
—
)}
{isCancelled ? (
Supprimé
) : isDelayed ? (
+{pert.duree_retard_minutes} min
) : (
À l'heure
)}
{
e.stopPropagation();
setSelectedItem({ type: 'horaire', data: h });
}}
>
Détails
e.stopPropagation()}>
Train {h.numero_train}
{ e.stopPropagation(); navigate('/horaires/attribution-quais'); }}>
Attribuer voie
{ e.stopPropagation(); navigate('/perturbations/creer'); }}>
Déclarer retard
{ e.stopPropagation(); navigate('/attribution-materiel-roulant'); }}>
Affecter rame
);
})}
)}
)}
{/* ----------------------------------------------------------------
ONGLET 3 : INCIDENTS & ALÉAS (SUPERVISION DISRUPTIONS)
---------------------------------------------------------------- */}
{activeTab === 'incidents' && (
Gestion des Perturbations & Aléas d'Exploitation
{filteredPerturbations.length} perturbation(s) enregistrée(s)
navigate('/perturbations/creer')}
>
Déclarer un retard
navigate('/perturbations')}
>
Module complet
Filtres :
setSelectedPerturbationType('all')}
>
setSelectedPerturbationType('active')}
>
setSelectedPerturbationType('retard')}
>
setSelectedPerturbationType('suppression')}
>
{filteredPerturbations.length === 0 ? (
Aucune perturbation dans cette vue
Toutes les circulations respectent les plages horaires contractuelles.
) : (
Train
Type d'aléa
Trajet Affecté
Impact / Retard
Cause Déclarée
État
Actions
{filteredPerturbations.map(p => (
setSelectedItem({ type: 'perturbation', data: p })}
>
Train {p.horaire?.numero_train || p.numero_train || 'N/A'}
{p.type_perturbation === 'retard' ? 'Retard' :
p.type_perturbation === 'suppression' ? 'Suppression' : 'Modification'}
{p.horaire?.gare_depart_nom || 'Départ'} → {p.horaire?.gare_arrivee_nom || 'Arrivée'}
{p.type_perturbation === 'retard' ? (
+{p.duree_retard_minutes || 0} min
) : p.type_perturbation === 'suppression' ? (
Circulation Annulée
) : (
Itinéraire Adapté
)}
{p.cause || 'Motif non spécifié'}
{p.actif ? 'En cours' : 'Résolu'}
{
e.stopPropagation();
navigate(`/perturbations/modifier/${p.id}`);
}}
>
Réguler
))}
)}
)}
{/* ----------------------------------------------------------------
ONGLET 4 : TRAVAUX & CHANTIERS D'INFRASTRUCTURE
---------------------------------------------------------------- */}
{activeTab === 'works' && (
Chantiers d'Infrastructure & Substitutions Routières
{travaux.length} chantier(s) répertorié(s) sur le réseau
navigate('/travaux/creer')}>
Nouveau Chantier
{travaux.length === 0 ? (
Aucun chantier programmé
Aucune interruption de voie ni bus de substitution n'est actuellement déclaré.
) : (
{travaux.map(t => (
{t.titre || 'Chantier SNCF Réseau'}
Du {t.date_debut ? new Date(t.date_debut).toLocaleDateString() : '—'} au {t.date_fin ? new Date(t.date_fin).toLocaleDateString() : '—'}
{t.substitutions?.length || 0} autocar(s)
{t.description || 'Travaux de modernisation et renouvellement d\'infrastructure des voies ferrées.'}
Section : {t.ligne_nom || t.gare_depart_nom || 'Secteur régional'}
navigate('/travaux')}
>
Accéder au dossier
))}
)}
)}
{/* ----------------------------------------------------------------
ONGLET 5 : FLOTTE & GARES (WCS LIST ITEMS)
---------------------------------------------------------------- */}
{activeTab === 'fleet' && (
Parc de Matériel Roulant & Maillage Gares
{materiels.length} rame(s) · {uniqueGares.size} gare(s) connectée(s)
navigate('/attribution-materiel-roulant')}
>
Affectation Rames
navigate('/gares')}
>
Gestion Gares
{/* Flotte Rames (avec wcs-list-item) */}
Flotte de Matériel Roulant
{materiels.length} unités actives
{materiels.length === 0 ? (
Aucun matériel répertorié
Configurez le parc de rames TER et TGV dans le module dédié.
) : (
{materiels.slice(0, 6).map(m => (
{m.nom || m.serie || 'Rame SNCF'}
{m.type && {m.type} }
Vitesse limite : {m.vitesse_max || '160'} km/h · Motorisation : {m.alimentation || 'Électrique / Bi-mode'}
Capacité : {m.capacite || m.places_assises || '280'} places
Série : {m.serie || 'SNCF Z-51500'}
Disponibilité : 100%
navigate('/attribution-materiel-roulant')}
>
Affecter
))}
)}
{/* Hubs & Gares (avec wcs-list-item) */}
Principaux Hubs Ferroviaires
{uniqueGares.size} gares
{Array.from(uniqueGares).slice(0, 7).map(g => (
{g}
Pôle d'échange multimodal & correspondances régionales
Voies équipées
Accès PMR
navigate('/horaires/attribution-quais')}
>
Quais & Voies
))}
)}
{/* ====================================================================
7. MODAL INTERACTIF DÉTAIL TRAIN / PERTURBATION AVEC WCS STEPPER
==================================================================== */}
{selectedItem?.type === 'perturbation'
? `Détail de l'Aléa — Train ${selectedItem?.data?.horaire?.numero_train || selectedItem?.data?.numero_train || ''}`
: `Fiche d'Exploitation — Train ${selectedItem?.data?.numero_train || ''}`}
{selectedItem && (
{/* Jalonnement Stepper SNCF pour un horaire */}
{selectedItem.type === 'horaire' && (() => {
const pert = perturbations.find(p => p.actif && (p.horaire_id === selectedItem.data.id || p.numero_train === selectedItem.data.numero_train));
const isCancelled = pert?.type_perturbation === 'suppression';
const isDelayed = pert?.type_perturbation === 'retard';
return (
Jalonnement Opérationnel du Train
{isCancelled ? 'Circulation Annulée' : isDelayed ? `Retard (+${pert.duree_retard_minutes || 0}m)` : 'Phase active : En circulation'}
Préparation
Mise à Quai
{isCancelled ? 'Annulé' : isDelayed ? 'Retardé' : 'En route'}
Terminus
);
})()}
{/* Contenu Fiche Circulation */}
{selectedItem.type === 'horaire' && (
Identité du Sillon Ferroviaire
{selectedItem.data.type_train || 'TER'}
N° {selectedItem.data.numero_train}
Ligne : {selectedItem.data.ligne_nom || 'Non spécifiée'}
Voie au départ : {selectedItem.data.quai_depart ? `Voie ${selectedItem.data.quai_depart}` : 'Non attribuée'}
Horaires Contractuels
Départ de {selectedItem.data.gare_depart_nom} : {formatHeure(selectedItem.data.heure_depart)}
Arrivée à {selectedItem.data.gare_arrivee_nom} : {formatHeure(selectedItem.data.heure_arrivee)}
Jours de circulation : {selectedItem.data.jours_circulation || 'Tous les jours'}
{/* Arrêts intermédiaires */}
{Array.isArray(selectedItem.data.gares_desservies) && selectedItem.data.gares_desservies.length > 0 && (
Dessertes & Arrêts Intermédiaires ({selectedItem.data.gares_desservies.length})
{selectedItem.data.gare_depart_nom}
{formatHeure(selectedItem.data.heure_depart)}
{selectedItem.data.gares_desservies.map((g, idx) => (
{g.gare_nom}
{formatHeure(g.heure_passage || g.heure_depart || g.heure_arrivee)}
))}
{selectedItem.data.gare_arrivee_nom}
{formatHeure(selectedItem.data.heure_arrivee)}
)}
)}
{/* Contenu Perturbation */}
{selectedItem.type === 'perturbation' && (
Nature de l'Incident
{selectedItem.data.type_perturbation === 'retard' ? `Retard +${selectedItem.data.duree_retard_minutes || 0} min` :
selectedItem.data.type_perturbation === 'suppression' ? 'Circulation Supprimée' : 'Modification d\'itinéraire'}
Statut : {selectedItem.data.actif ? 'Incident Actif en cours' : 'Incident Résolu'}
Circulation Affectée
Train N° {selectedItem.data.horaire?.numero_train || selectedItem.data.numero_train}
Trajet : {selectedItem.data.horaire?.gare_depart_nom} → {selectedItem.data.horaire?.gare_arrivee_nom}
Cause & Diagnostic d'Exploitation
{selectedItem.data.cause || 'Aucun motif d\'exploitation renseigné dans le système.'}
)}
)}
setSelectedItem(null)}
>
Fermer
{selectedItem?.type === 'perturbation' ? (
navigate(`/perturbations/modifier/${selectedItem.data.id}`)}
>
Modifier la perturbation
) : (
navigate('/horaires')}
>
Gérer les horaires
)}
{/* ====================================================================
8. PIED DE PAGE CORPORATE SNCF (WCS-FOOTER)
==================================================================== */}
FERROVIA PANEL · DIRECTION DE L'EXPLOITATION FERROVIAIRE
Système certifié WCS SNCF (Web Component System) & Bootstrap SNCF Métiers
© {new Date().getFullYear()} SNCF · Tous droits réservés · Supervision Réseau & Information Voyageurs
Supabase Connecté
v2.5.0-WCS
);
}