RELEASE V.1.2.1 - Voici les fonctionnalités :

->Refonte visuelle + Prise en charge du mode sombre par l'ensemble des composants.
This commit is contained in:
mathis committed 2026-09-22 22:14:21 +02:00
1 parent d61422ddbf
commit 37ab831aac
44 files changed
+17261 -7636

No files matched your search

+465 -213
View File
@@ -16,6 +16,7 @@ export default function Dashboard() {
// État du chargement
const [loading, setLoading] = useState(true);
const [lastRefreshed, setLastRefreshed] = useState(new Date());
// Perturbations récentes
const [recentPerturbations, setRecentPerturbations] = useState([]);
@@ -23,12 +24,14 @@ export default function Dashboard() {
// Horaires récents
const [recentHoraires, setRecentHoraires] = useState([]);
const [scopedRegionId, setScopedRegionId] = useState(null);
const [currentUser, setCurrentUser] = useState(null);
// Fonction de chargement des données
const loadDashboardData = useCallback(async () => {
setLoading(true);
try {
const user = getCurrentUser();
setCurrentUser(user);
const regionId = getScopedRegionId(user);
setScopedRegionId(regionId || null);
@@ -88,10 +91,11 @@ export default function Dashboard() {
});
// Perturbations récentes (5 dernières)
setRecentPerturbations(perturbations.slice(0, 5));
setRecentPerturbations(perturbations.slice(0, 6));
// Horaires récents (5 derniers)
setRecentHoraires(horaires.slice(0, 5));
// Horaires récents (6 derniers)
setRecentHoraires(horaires.slice(0, 6));
setLastRefreshed(new Date());
} catch (error) {
console.error('Erreur lors du chargement du dashboard:', error);
@@ -115,224 +119,472 @@ export default function Dashboard() {
window.location.href = path;
};
// Rendu des cartes de statistiques
const renderStatCard = (title, value, icon, color = 'primary', subtitle = null) => (
<div className={`stat-card stat-card-${color}`}>
<div className="stat-card-header">
<div className="stat-card-icon-wrap">
<wcs-mat-icon icon={icon}></wcs-mat-icon>
</div>
<div className="stat-card-content">
<span className="stat-card-title">{title}</span>
<span className="stat-card-value">{loading ? '...' : value}</span>
{subtitle && <span className="stat-card-subtitle">{subtitle}</span>}
</div>
</div>
</div>
);
// Rendu de la liste des perturbations récentes
const renderRecentPerturbations = () => (
<div className="dashboard-widget widget-panel">
<div className="widget-header widget-header-panel">
<h3>
<span className="widget-title-icon"><wcs-mat-icon icon="warning"></wcs-mat-icon></span>
Perturbations récentes
</h3>
<wcs-button
className="wcs-secondary wcs-small widget-link-btn"
onClick={() => navigate('/perturbations')}
>
Voir tout
</wcs-button>
</div>
<div className="widget-content widget-content-panel">
{loading ? (
<div className="dashboard-state widget-state-panel">
<wcs-spinner mode="border" />
<p>Chargement...</p>
</div>
) : recentPerturbations.length === 0 ? (
<div className="dashboard-state dashboard-state-empty widget-state-panel">
<wcs-mat-icon className="dashboard-state-icon" icon="check_circle"></wcs-mat-icon>
<p>Aucune perturbation</p>
</div>
) : (
<div className="perturbations-list widget-list-panel">
{recentPerturbations.map(p => (
<div key={p.id} className="perturbation-item widget-list-item">
<div className="perturbation-item-header widget-item-main">
<div className="widget-item-text">
<strong>{p.horaire?.numero_train || 'N/A'}</strong>
<span className="perturbation-item-route">
{p.horaire?.gare_depart_nom} → {p.horaire?.gare_arrivee_nom}
</span>
</div>
<wcs-badge className={
p.type_perturbation === 'retard' ? 'wcs-warning' :
p.type_perturbation === 'suppression' ? 'wcs-danger' :
'wcs-info'
}>
{p.type_perturbation === 'retard' ? `Retard +${p.duree_retard_minutes || 0} min` :
p.type_perturbation === 'suppression' ? 'Supprime' :
'Modifié'}
</wcs-badge>
</div>
<div className="perturbation-item-details">
<span>
<wcs-mat-icon icon="info"></wcs-mat-icon>
{p.cause || 'Non spécifiée'}
</span>
<span className={p.actif ? 'status-active' : 'status-inactive'}>
{p.actif ? '● Active' : '○ Inactive'}
</span>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
// Rendu de la liste des horaires récents
const renderRecentHoraires = () => (
<div className="dashboard-widget widget-panel">
<div className="widget-header widget-header-panel">
<h3>
<span className="widget-title-icon"><wcs-mat-icon icon="schedule"></wcs-mat-icon></span>
Horaires récents
</h3>
<wcs-button
className="wcs-secondary wcs-small widget-link-btn"
onClick={() => navigate('/horaires')}
>
Voir tout
</wcs-button>
</div>
<div className="widget-content widget-content-panel">
{loading ? (
<div className="dashboard-state widget-state-panel">
<wcs-spinner mode="border" />
<p>Chargement...</p>
</div>
) : recentHoraires.length === 0 ? (
<div className="dashboard-state dashboard-state-empty widget-state-panel">
<wcs-mat-icon className="dashboard-state-icon" icon="train"></wcs-mat-icon>
<p>Aucun horaire</p>
</div>
) : (
<div className="horaires-list widget-list-panel">
{recentHoraires.map(h => (
<div key={h.id} className="horaire-item widget-list-item">
<div className="horaire-item-header widget-item-main">
<div className="widget-item-text">
<strong>{h.numero_train}</strong>
{h.type_train && (
<wcs-badge className="wcs-info horaire-type-badge">
{h.type_train}
</wcs-badge>
)}
</div>
{h.ligne_nom && (
<wcs-badge className="wcs-secondary">{h.ligne_nom}</wcs-badge>
)}
</div>
<div className="horaire-item-route">
<span>
{h.gare_depart_nom} ({formatHeure(h.heure_depart)})
</span>
<wcs-mat-icon icon="arrow_forward"></wcs-mat-icon>
<span>
{h.gare_arrivee_nom} ({formatHeure(h.heure_arrivee)})
</span>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
// Rendu des actions rapides
const renderQuickActions = () => (
<div className="dashboard-widget widget-panel">
<div className="widget-header widget-header-panel">
<h3>
<span className="widget-title-icon"><wcs-mat-icon icon="bolt"></wcs-mat-icon></span>
Actions rapides
</h3>
</div>
<div className="widget-content widget-content-panel">
<div className="quick-actions">
<a className="quick-action-row" href="/perturbations/creer">
<span className="quick-action-row-left">
<span className="quick-action-icon"><wcs-mat-icon icon="add"></wcs-mat-icon></span>
<span>Créer une perturbation</span>
</span>
<wcs-mat-icon icon="arrow_forward"></wcs-mat-icon>
</a>
<a className="quick-action-row" href="/horaires">
<span className="quick-action-row-left">
<span className="quick-action-icon"><wcs-mat-icon icon="schedule"></wcs-mat-icon></span>
<span>Gérer les horaires</span>
</span>
<wcs-mat-icon icon="arrow_forward"></wcs-mat-icon>
</a>
<a className="quick-action-row" href="/gares">
<span className="quick-action-row-left">
<span className="quick-action-icon"><wcs-mat-icon icon="location_on"></wcs-mat-icon></span>
<span>Gérer les gares</span>
</span>
<wcs-mat-icon icon="arrow_forward"></wcs-mat-icon>
</a>
<button className="quick-action-row quick-action-row-refresh" type="button" onClick={loadDashboardData}>
<span className="quick-action-row-left">
<span className="quick-action-icon"><wcs-mat-icon icon="refresh"></wcs-mat-icon></span>
<span>Actualiser les données</span>
</span>
<wcs-mat-icon icon="refresh"></wcs-mat-icon>
</button>
</div>
</div>
</div>
);
// Ponctualité théorique estimée
const calculatePunctuality = () => {
if (!stats.horaires.total) return 100;
const impacted = stats.perturbations.actives;
const rate = Math.max(0, Math.min(100, Math.round(((stats.horaires.total - impacted) / stats.horaires.total) * 100)));
return rate;
};
return (
<div className="sncf-groupe dashboard-page panel-layout">
<div className="panel-content">
<header className="dashboard-hero">
<div>
<p className="dashboard-kicker">Panel de gestion</p>
<h1>Tableau de bord</h1>
<p className="dashboard-subtitle">Suivez l'activite des horaires, perturbations et donnees d'exploitation en temps reel.</p>
<div className="dashboard-page-root">
<div className="dashboard-container">
{/* ================================================================
HERO BANNER
================================================================ */}
<section className="dashboard-hero-card">
<div className="dashboard-hero-content">
<div className="dashboard-hero-kicker-row">
<span className="dashboard-hero-kicker">
<wcs-mat-icon icon="speed"></wcs-mat-icon>
Centre de supervision ferroviaire
</span>
{scopedRegionId && (
<span className="dashboard-region-badge">
<wcs-mat-icon icon="location_on"></wcs-mat-icon>
Région : {scopedRegionId}
</span>
)}
</div>
<h1 className="dashboard-hero-title">
Tableau de bord
{currentUser?.nom ? <span className="dashboard-user-greeting">, {currentUser.prenom || currentUser.nom}</span> : null}
</h1>
<p className="dashboard-hero-desc">
Pilotage centralisé des circulations, gestion du plan de transport, détection des aléas et état des infrastructures en temps réel.
</p>
</div>
<div className="dashboard-hero-side">
<wcs-button className="wcs-primary" onClick={loadDashboardData}>
<wcs-mat-icon icon="refresh"></wcs-mat-icon>
Actualiser
</wcs-button>
{scopedRegionId && <p>Region courante : <strong>{scopedRegionId}</strong></p>}
</div>
</header>
<div className="stats-grid">
{renderStatCard('Horaires', stats.horaires.total, 'schedule', 'primary', `${stats.horaires.actifs} actifs`)}
{renderStatCard('Perturbations', stats.perturbations.total, 'warning', 'warning', `${stats.perturbations.actives} actives`)}
{renderStatCard('Gares', stats.gares.total, 'location_on', 'info', 'Desservies')}
{renderStatCard('Lignes', stats.lignes.total, 'route', 'secondary', 'Actives')}
{renderStatCard('Types de trains', stats.typesTrains.total, 'train', 'success', 'Disponibles')}
{renderStatCard('Retards', stats.perturbations.retards, 'schedule', 'warning', 'En cours')}
<div className="dashboard-hero-actions">
<div className="dashboard-sync-status">
<span className="dashboard-pulse-dot"></span>
<span>Dernière synchro : {lastRefreshed.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}</span>
</div>
<div className="dashboard-btn-group">
<wcs-button
className="wcs-primary"
onClick={loadDashboardData}
disabled={loading}
>
<wcs-mat-icon icon="refresh"></wcs-mat-icon>
Actualiser
</wcs-button>
<wcs-button
className="wcs-secondary dashboard-quick-create-btn"
onClick={() => navigate('/perturbations/creer')}
>
<wcs-mat-icon icon="warning"></wcs-mat-icon>
Signaler un incident
</wcs-button>
</div>
</div>
</section>
{/* ================================================================
SUPERVISION STATUS BAR
================================================================ */}
<div className="dashboard-status-bar">
<div className="dashboard-status-item">
<div className={`dashboard-status-indicator ${stats.perturbations.actives > 0 ? 'is-warning' : 'is-success'}`}>
<wcs-mat-icon icon={stats.perturbations.actives > 0 ? 'warning' : 'check_circle'}></wcs-mat-icon>
</div>
<div className="dashboard-status-text">
<span className="dashboard-status-label">État du Réseau</span>
<strong>{stats.perturbations.actives > 0 ? `${stats.perturbations.actives} incident(s) en cours` : 'Circulation normale et fluide'}</strong>
</div>
</div>
<div className="dashboard-status-divider"></div>
<div className="dashboard-status-item">
<div className="dashboard-status-indicator is-info">
<wcs-mat-icon icon="analytics"></wcs-mat-icon>
</div>
<div className="dashboard-status-text">
<span className="dashboard-status-label">Taux de régularité théorique</span>
<strong>{calculatePunctuality()}% à l'heure</strong>
</div>
</div>
<div className="dashboard-status-divider"></div>
<div className="dashboard-status-item">
<div className="dashboard-status-indicator is-secondary">
<wcs-mat-icon icon="train"></wcs-mat-icon>
</div>
<div className="dashboard-status-text">
<span className="dashboard-status-label">Sillons actifs</span>
<strong>{stats.horaires.total} circulations programmées</strong>
</div>
</div>
</div>
<div className="widgets-grid">
<div className="widgets-column">
{renderRecentPerturbations()}
{renderQuickActions()}
{/* ================================================================
KPI CARDS
================================================================ */}
<div className="dashboard-kpis-grid">
{/* Sillons / Horaires */}
<div className="dashboard-kpi-card card-horaires" onClick={() => navigate('/horaires')}>
<div className="dashboard-kpi-top">
<div className="dashboard-kpi-icon-wrapper">
<wcs-mat-icon icon="schedule"></wcs-mat-icon>
</div>
<span className="dashboard-kpi-badge">{stats.horaires.actifs} actifs</span>
</div>
<div className="dashboard-kpi-number">{loading ? '...' : stats.horaires.total}</div>
<div className="dashboard-kpi-title">Horaires & Sillons</div>
<div className="dashboard-kpi-footer">
<span>Gérer les plans de transport</span>
<wcs-mat-icon icon="arrow_forward"></wcs-mat-icon>
</div>
</div>
{/* Perturbations */}
<div className="dashboard-kpi-card card-perturbations" onClick={() => navigate('/perturbations')}>
<div className="dashboard-kpi-top">
<div className="dashboard-kpi-icon-wrapper">
<wcs-mat-icon icon="warning"></wcs-mat-icon>
</div>
<span className={`dashboard-kpi-badge ${stats.perturbations.actives > 0 ? 'badge-danger' : 'badge-neutral'}`}>
{stats.perturbations.actives} actives
</span>
</div>
<div className="dashboard-kpi-number">{loading ? '...' : stats.perturbations.total}</div>
<div className="dashboard-kpi-title">Perturbations & Aléas</div>
<div className="dashboard-kpi-footer">
<span>{stats.perturbations.retards} retards · {stats.perturbations.suppressions} suppressions</span>
<wcs-mat-icon icon="arrow_forward"></wcs-mat-icon>
</div>
</div>
{/* Gares */}
<div className="dashboard-kpi-card card-gares" onClick={() => navigate('/gares')}>
<div className="dashboard-kpi-top">
<div className="dashboard-kpi-icon-wrapper">
<wcs-mat-icon icon="location_on"></wcs-mat-icon>
</div>
<span className="dashboard-kpi-badge">Réseau</span>
</div>
<div className="dashboard-kpi-number">{loading ? '...' : stats.gares.total}</div>
<div className="dashboard-kpi-title">Gares & Points d'arrêt</div>
<div className="dashboard-kpi-footer">
<span>Configuration des quais & services</span>
<wcs-mat-icon icon="arrow_forward"></wcs-mat-icon>
</div>
</div>
{/* Lignes */}
<div className="dashboard-kpi-card card-lignes" onClick={() => navigate('/horaires')}>
<div className="dashboard-kpi-top">
<div className="dashboard-kpi-icon-wrapper">
<wcs-mat-icon icon="route"></wcs-mat-icon>
</div>
<span className="dashboard-kpi-badge">Maillage</span>
</div>
<div className="dashboard-kpi-number">{loading ? '...' : stats.lignes.total}</div>
<div className="dashboard-kpi-title">Lignes Commerciales</div>
<div className="dashboard-kpi-footer">
<span>{stats.typesTrains.total} matériels types</span>
<wcs-mat-icon icon="arrow_forward"></wcs-mat-icon>
</div>
</div>
<div className="widgets-column">{renderRecentHoraires()}</div>
</div>
{/* ================================================================
MAIN PANELS GRID
================================================================ */}
<div className="dashboard-main-grid">
{/* Colonne gauche : Perturbations et Actions */}
<div className="dashboard-column">
{/* Widget Perturbations récentes */}
<div className="dashboard-widget-card">
<div className="dashboard-widget-header">
<div className="dashboard-widget-title-wrap">
<div className="dashboard-widget-icon icon-warning">
<wcs-mat-icon icon="warning_amber"></wcs-mat-icon>
</div>
<div>
<h3 className="dashboard-widget-title">Incidents & Perturbations</h3>
<span className="dashboard-widget-subtitle">Derniers aléas déclarés sur le réseau</span>
</div>
</div>
<wcs-button
className="wcs-secondary wcs-small"
onClick={() => navigate('/perturbations')}
>
Tout voir ({stats.perturbations.total})
</wcs-button>
</div>
<div className="dashboard-widget-body">
{loading ? (
<div className="dashboard-loading-box">
<wcs-spinner mode="border"></wcs-spinner>
<span>Chargement des incidents...</span>
</div>
) : recentPerturbations.length === 0 ? (
<div className="dashboard-empty-box">
<div className="dashboard-empty-icon is-success">
<wcs-mat-icon icon="check_circle_outline"></wcs-mat-icon>
</div>
<h4>Aucune perturbation active</h4>
<p>Toutes les circulations ferroviaires s'effectuent conformément au plan de transport.</p>
</div>
) : (
<div className="dashboard-incident-list">
{recentPerturbations.map(p => (
<div
key={p.id}
className={`dashboard-incident-item ${p.type_perturbation === 'suppression' ? 'item-suppression' : 'item-retard'}`}
onClick={() => navigate(`/perturbations/modifier/${p.id}`)}
>
<div className="dashboard-incident-header">
<div className="dashboard-incident-train-badge">
<wcs-mat-icon icon="train"></wcs-mat-icon>
<strong>{p.horaire?.numero_train || 'Train'}</strong>
</div>
<span className={`dashboard-badge-pill ${
p.type_perturbation === 'retard' ? 'pill-warning' :
p.type_perturbation === 'suppression' ? 'pill-danger' :
'pill-info'
}`}>
<wcs-mat-icon icon={p.type_perturbation === 'suppression' ? 'block' : 'schedule'}></wcs-mat-icon>
{p.type_perturbation === 'retard' ? `+${p.duree_retard_minutes || 0} min` :
p.type_perturbation === 'suppression' ? 'Supprimé' :
'Modifié'}
</span>
</div>
<div className="dashboard-incident-route">
<span className="station-name">{p.horaire?.gare_depart_nom || 'Départ'}</span>
<wcs-mat-icon icon="trending_flat"></wcs-mat-icon>
<span className="station-name">{p.horaire?.gare_arrivee_nom || 'Arrivée'}</span>
</div>
<div className="dashboard-incident-footer">
<span className="dashboard-incident-cause">
<wcs-mat-icon icon="info_outline"></wcs-mat-icon>
{p.cause || 'Motif d\'exploitation non spécifié'}
</span>
<span className={`dashboard-status-tag ${p.actif ? 'tag-active' : 'tag-inactive'}`}>
{p.actif ? 'En cours' : 'Résolu'}
</span>
</div>
</div>
))}
</div>
)}
</div>
</div>
{/* Widget Actions Rapides */}
<div className="dashboard-widget-card">
<div className="dashboard-widget-header">
<div className="dashboard-widget-title-wrap">
<div className="dashboard-widget-icon icon-primary">
<wcs-mat-icon icon="bolt"></wcs-mat-icon>
</div>
<div>
<h3 className="dashboard-widget-title">Centre d'actions rapides</h3>
<span className="dashboard-widget-subtitle">Raccourcis de gestion et exploitation</span>
</div>
</div>
</div>
<div className="dashboard-widget-body">
<div className="dashboard-quick-grid">
<a className="dashboard-action-tile" href="/perturbations/creer">
<div className="dashboard-action-icon bg-amber">
<wcs-mat-icon icon="add_alert"></wcs-mat-icon>
</div>
<div className="dashboard-action-info">
<strong>Déclarer un retard / incident</strong>
<span>Ajuster les horaires et diffuser l'information</span>
</div>
<wcs-mat-icon className="action-chevron" icon="chevron_right"></wcs-mat-icon>
</a>
<a className="dashboard-action-tile" href="/travaux/creer">
<div className="dashboard-action-icon bg-orange">
<wcs-mat-icon icon="construction"></wcs-mat-icon>
</div>
<div className="dashboard-action-info">
<strong>Programmer des travaux</strong>
<span>Définir des chantiers et bus de substitution</span>
</div>
<wcs-mat-icon className="action-chevron" icon="chevron_right"></wcs-mat-icon>
</a>
<a className="dashboard-action-tile" href="/horaires/attribution-quais">
<div className="dashboard-action-icon bg-sky">
<wcs-mat-icon icon="alt_route"></wcs-mat-icon>
</div>
<div className="dashboard-action-info">
<strong>Attribution des voies & quais</strong>
<span>Régulation schématique des départs et arrivées</span>
</div>
<wcs-mat-icon className="action-chevron" icon="chevron_right"></wcs-mat-icon>
</a>
<a className="dashboard-action-tile" href="/attribution-materiel-roulant">
<div className="dashboard-action-icon bg-indigo">
<wcs-mat-icon icon="directions_railway"></wcs-mat-icon>
</div>
<div className="dashboard-action-info">
<strong>Roulement du matériel</strong>
<span>Affectation des rames et compositions</span>
</div>
<wcs-mat-icon className="action-chevron" icon="chevron_right"></wcs-mat-icon>
</a>
</div>
</div>
</div>
</div>
{/* Colonne droite : Horaires & Circulation */}
<div className="dashboard-column">
<div className="dashboard-widget-card">
<div className="dashboard-widget-header">
<div className="dashboard-widget-title-wrap">
<div className="dashboard-widget-icon icon-info">
<wcs-mat-icon icon="schedule"></wcs-mat-icon>
</div>
<div>
<h3 className="dashboard-widget-title">Plan de transport récent</h3>
<span className="dashboard-widget-subtitle">Circulations programmées au référentiel</span>
</div>
</div>
<wcs-button
className="wcs-secondary wcs-small"
onClick={() => navigate('/horaires')}
>
Tous les horaires ({stats.horaires.total})
</wcs-button>
</div>
<div className="dashboard-widget-body">
{loading ? (
<div className="dashboard-loading-box">
<wcs-spinner mode="border"></wcs-spinner>
<span>Chargement des horaires...</span>
</div>
) : recentHoraires.length === 0 ? (
<div className="dashboard-empty-box">
<div className="dashboard-empty-icon is-info">
<wcs-mat-icon icon="train"></wcs-mat-icon>
</div>
<h4>Aucun horaire enregistré</h4>
<p>Commencez par créer des circulations ou importez une fiche horaire.</p>
<wcs-button
className="wcs-primary wcs-small"
onClick={() => navigate('/horaires')}
>
Ajouter un horaire
</wcs-button>
</div>
) : (
<div className="dashboard-schedule-list">
{recentHoraires.map(h => (
<div
key={h.id}
className="dashboard-schedule-item"
onClick={() => navigate('/horaires')}
>
<div className="dashboard-schedule-header">
<div className="dashboard-schedule-badges">
<span className="train-id-badge">
<wcs-mat-icon icon="train"></wcs-mat-icon>
{h.numero_train}
</span>
{h.type_train && (
<span className="train-type-pill">{h.type_train}</span>
)}
</div>
{h.ligne_nom && (
<span className="line-pill">
<wcs-mat-icon icon="timeline"></wcs-mat-icon>
{h.ligne_nom}
</span>
)}
</div>
<div className="dashboard-schedule-timeline">
<div className="timeline-station departure">
<span className="station-time">{formatHeure(h.heure_depart)}</span>
<span className="station-dot"></span>
<span className="station-title">{h.gare_depart_nom || 'Départ'}</span>
</div>
<div className="timeline-connector">
<span className="connector-line"></span>
<wcs-mat-icon icon="chevron_right"></wcs-mat-icon>
</div>
<div className="timeline-station arrival">
<span className="station-time">{formatHeure(h.heure_arrivee)}</span>
<span className="station-dot"></span>
<span className="station-title">{h.gare_arrivee_nom || 'Arrivée'}</span>
</div>
</div>
{h.gares_desservies && h.gares_desservies.length > 0 && (
<div className="dashboard-schedule-stops">
<wcs-mat-icon icon="more_horiz"></wcs-mat-icon>
<span>{h.gares_desservies.length} arrêt(s) intermédiaire(s)</span>
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
{/* Carte état du système / infos d'exploitation */}
<div className="dashboard-system-card">
<div className="dashboard-system-header">
<div className="dashboard-system-icon">
<wcs-mat-icon icon="storage"></wcs-mat-icon>
</div>
<div>
<h4>État des services d'exploitation</h4>
<p>Système opérationnel & données à jour</p>
</div>
</div>
<div className="dashboard-system-stats">
<div className="system-stat-col">
<span>Circulations</span>
<strong>{stats.horaires.total}</strong>
</div>
<div className="system-stat-col">
<span>Gares reliées</span>
<strong>{stats.gares.total}</strong>
</div>
<div className="system-stat-col">
<span>Incidents</span>
<strong className={stats.perturbations.actives > 0 ? 'text-warning' : 'text-success'}>
{stats.perturbations.actives}
</strong>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);