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:
1 parent
d61422ddbf
commit
37ab831aac
44 files changed
+17261
-7636
No files matched your search
+465
-213
@@ -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>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user