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 */}
Actions Métiers
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} sillons
{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" >
Infra & Voies
{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" >
Maillage
{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 {linesList.length} ligne(s) répertoriée(s)
{/* Segmented Radio Group WCS pour filtrer l'état de ponctualité */}
État de circulation
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
) : (
{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 })} > ); })}
N° Train Type Ligne Commerciale Départ Arrivée Arrêts Voie Statut Régulation Actions
{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.

) : (
{filteredPerturbations.map(p => ( setSelectedItem({ type: 'perturbation', data: p })} > ))}
Train Type d'aléa Trajet Affecté Impact / Retard Cause Déclarée État Actions
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
); }