Skip to content

Commit 727e992

Browse files
committed
maquette Tableau de bord conformité prévisionnelle
1 parent 123182b commit 727e992

File tree

5 files changed

+180
-0
lines changed

5 files changed

+180
-0
lines changed

apps/front/src/App.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ReferentielOuvragesPage } from './pages/ReferentielOuvrages';
1010
import { SuiviMesuresPage } from './pages/SuiviMesures';
1111
import { SuiviDecisionsPage } from './pages/SuiviDecisions';
1212
import { SuiviDepotsPage } from './pages/SuiviDepots';
13+
import { SuiviConformitePage } from './pages/SuiviConformite';
1314
import CallbackPage from './pages/CallbackPage';
1415
import MockAuthorizationPage from './pages/MockAuthorizationPage';
1516
import { DesignSystemPage } from './pages/DesignSystemPage';
@@ -135,6 +136,14 @@ function App() {
135136
</ProtectedRoute>
136137
}
137138
/>
139+
<Route
140+
path={AppRoutes.SUIVI_CONFORMITE}
141+
element={
142+
<ProtectedRoute>
143+
<SuiviConformitePage />
144+
</ProtectedRoute>
145+
}
146+
/>
138147
</Routes>
139148
</main>
140149
</div>

apps/front/src/components/Header.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ export function AppHeader() {
8989
linkProps: { href: AppRoutes.SUIVI_DEPOTS },
9090
isActive: isNavItemActive(AppRoutes.SUIVI_DEPOTS),
9191
},
92+
{
93+
text: 'Conformité prévisionnelle',
94+
linkProps: { href: AppRoutes.SUIVI_CONFORMITE },
95+
isActive: isNavItemActive(AppRoutes.SUIVI_CONFORMITE),
96+
},
9297
{
9398
text: 'Historique des décisions',
9499
linkProps: { href: AppRoutes.SUIVI_DECISIONS },
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export interface ConformiteDetail {
2+
parametre: string;
3+
valeur: number;
4+
seuil: number;
5+
statut: 'Conforme' | 'Non conforme';
6+
}
7+
8+
export interface Conformite {
9+
id: string;
10+
systeme: string;
11+
periode: string;
12+
statut: 'Conforme' | 'Non conforme' | 'En évaluation';
13+
details: ConformiteDetail[];
14+
}
15+
16+
export const MOCK_CONFORMITE: Conformite[] = [
17+
{
18+
id: 'c1',
19+
systeme: 'Station Nord',
20+
periode: '2025-01',
21+
statut: 'Conforme',
22+
details: [
23+
{ parametre: 'DCO', valeur: 110, seuil: 125, statut: 'Conforme' },
24+
{ parametre: 'DBO5', valeur: 30, seuil: 40, statut: 'Conforme' },
25+
],
26+
},
27+
{
28+
id: 'c2',
29+
systeme: 'Station Sud',
30+
periode: '2025-01',
31+
statut: 'Non conforme',
32+
details: [
33+
{ parametre: 'DCO', valeur: 140, seuil: 125, statut: 'Non conforme' },
34+
{ parametre: 'DBO5', valeur: 45, seuil: 40, statut: 'Non conforme' },
35+
],
36+
},
37+
{
38+
id: 'c3',
39+
systeme: 'Déversoir Ouest',
40+
periode: '2025-01',
41+
statut: 'En évaluation',
42+
details: [],
43+
},
44+
];
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { useState, useMemo } from 'react';
2+
import { fr } from '@codegouvfr/react-dsfr';
3+
import { Table } from '@codegouvfr/react-dsfr/Table';
4+
import { Badge } from '@codegouvfr/react-dsfr/Badge';
5+
import { Button } from '@codegouvfr/react-dsfr/Button';
6+
import { Alert } from '@codegouvfr/react-dsfr/Alert';
7+
import { Input } from '@codegouvfr/react-dsfr/Input';
8+
import { Select } from '@codegouvfr/react-dsfr/Select';
9+
import { createModal } from '@codegouvfr/react-dsfr/Modal';
10+
import { MOCK_CONFORMITE, type Conformite } from '../data/mockConformite';
11+
12+
const modal = createModal({
13+
id: 'modal-details-conformite',
14+
isOpenedByDefault: false,
15+
});
16+
17+
export const SuiviConformitePage = () => {
18+
const [selectedConformite, setSelectedConformite] = useState<Conformite | null>(null);
19+
const [systeme, setSysteme] = useState('');
20+
const [statut, setStatut] = useState('');
21+
22+
const systemes = useMemo(() => Array.from(new Set(MOCK_CONFORMITE.map((c) => c.systeme))), []);
23+
24+
const filtered = useMemo(() => {
25+
return MOCK_CONFORMITE.filter((c) => {
26+
return (
27+
(systeme === '' || c.systeme.toLowerCase().includes(systeme.toLowerCase())) &&
28+
(statut === '' || c.statut === statut)
29+
);
30+
});
31+
}, [systeme, statut]);
32+
33+
const openModal = (c: Conformite) => {
34+
setSelectedConformite(c);
35+
modal.open();
36+
};
37+
38+
const getBadgeSeverity = (s: string) => {
39+
switch (s) {
40+
case 'Conforme':
41+
return 'success';
42+
case 'Non conforme':
43+
return 'error';
44+
case 'En évaluation':
45+
return 'info';
46+
default:
47+
return undefined;
48+
}
49+
};
50+
51+
const tableData = filtered.map((c) => [
52+
c.periode,
53+
c.systeme,
54+
<Badge severity={getBadgeSeverity(c.statut)}>{c.statut}</Badge>,
55+
c.details.length > 0 ? (
56+
<Button priority="tertiary" size="small" onClick={() => openModal(c)}>
57+
Détails
58+
</Button>
59+
) : (
60+
'-'
61+
),
62+
]);
63+
64+
return (
65+
<div className={fr.cx('fr-container', 'fr-py-4w')}>
66+
<h1 className={fr.cx('fr-h1')}>Conformité prévisionnelle</h1>
67+
<p className={fr.cx('fr-text--lead')}>Visualisez le statut de conformité et le détail des calculs par système.</p>
68+
69+
<Alert
70+
small
71+
description="Données issues de Roseau (base J-7). Cette vue est fournie à titre indicatif."
72+
severity="info"
73+
className={fr.cx('fr-mb-4w')}
74+
/>
75+
76+
<div className={fr.cx('fr-grid-row', 'fr-grid-row--gutters', 'fr-mb-4w')}>
77+
<div className={fr.cx('fr-col-12', 'fr-col-md-6')}>
78+
<Input
79+
label="Système (recherche)"
80+
nativeInputProps={{
81+
onChange: (e) => setSysteme(e.target.value),
82+
value: systeme,
83+
list: 'systemes-list',
84+
}}
85+
/>
86+
<datalist id="systemes-list">
87+
{systemes.map((s) => (
88+
<option key={s} value={s} />
89+
))}
90+
</datalist>
91+
</div>
92+
<div className={fr.cx('fr-col-12', 'fr-col-md-6')}>
93+
<Select label="Statut" nativeSelectProps={{ onChange: (e) => setStatut(e.target.value), value: statut }}>
94+
<option value="">Tous les statuts</option>
95+
<option value="Conforme">Conforme</option>
96+
<option value="Non conforme">Non conforme</option>
97+
<option value="En évaluation">En évaluation</option>
98+
</Select>
99+
</div>
100+
</div>
101+
102+
<Table data={tableData} headers={['Période', 'Système', 'Statut', 'Détail']} />
103+
104+
<modal.Component
105+
title={`Détails de conformité : ${selectedConformite?.systeme} (${selectedConformite?.periode})`}
106+
>
107+
{selectedConformite && (
108+
<Table
109+
data={selectedConformite.details.map((d) => [
110+
d.parametre,
111+
d.valeur.toString(),
112+
d.seuil.toString(),
113+
d.statut,
114+
])}
115+
headers={['Paramètre', 'Valeur', 'Seuil', 'Statut']}
116+
/>
117+
)}
118+
</modal.Component>
119+
</div>
120+
);
121+
};

apps/front/src/routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const AppRoutes = {
1313
SUIVI_MESURES: '/suivi/mesures',
1414
SUIVI_DECISIONS: '/suivi/decisions',
1515
SUIVI_DEPOTS: '/suivi/depots',
16+
SUIVI_CONFORMITE: '/suivi/conformite',
1617
} as const;
1718

1819
export const getControleRoute = (depotId: string) => `/controle/${depotId}`;

0 commit comments

Comments
 (0)