feat(dashboard): filtres services/catégories restreints aux résultats
Les select de service et catégorie utilisaient getServices()/getCategories(), soit les référentiels complets — dont certaines entrées (ex. "Business Intelligence") n'ont aucun monitoring rattaché et menaient sur un dashboard vide. Bascule vers le nouvel endpoint GET /dashboard/filtres (VUE_CONSO), avec filtrage en cascade : sélectionner un service restreint la liste des catégories proposées à celles qui ont réellement des résultats pour ce service.
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
/* Page principale du dashboard : KPI cards + grille de monitorings */
|
/* Page principale du dashboard : KPI cards + grille de monitorings */
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||||
import { RefreshCw } from 'lucide-react';
|
import { RefreshCw } from 'lucide-react';
|
||||||
import { getDashboard, getDashboardSummary, getServices, getCategories } from '../../services/api';
|
import { getDashboard, getDashboardSummary, getDashboardFiltres } from '../../services/api';
|
||||||
import MonitoringCard from '../../components/common/MonitoringCard';
|
import MonitoringCard from '../../components/common/MonitoringCard';
|
||||||
import LoadingSpinner from '../../components/common/LoadingSpinner';
|
import LoadingSpinner from '../../components/common/LoadingSpinner';
|
||||||
import ErrorMessage from '../../components/common/ErrorMessage';
|
import ErrorMessage from '../../components/common/ErrorMessage';
|
||||||
@@ -22,19 +22,22 @@ export default function Dashboard() {
|
|||||||
const [summary, setSummary] = useState(null);
|
const [summary, setSummary] = useState(null);
|
||||||
const [monitorings, setMonitorings] = useState([]);
|
const [monitorings, setMonitorings] = useState([]);
|
||||||
const [services, setServices] = useState([]);
|
const [services, setServices] = useState([]);
|
||||||
const [categories, setCategories] = useState([]);
|
const [combinaisons, setCombinaisons] = useState([]);
|
||||||
const [filtreService, setFiltreService] = useState('');
|
const [filtreService, setFiltreService] = useState('');
|
||||||
const [filtreCategorie, setFiltreCategorie] = useState('');
|
const [filtreCategorie, setFiltreCategorie] = useState('');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [erreur, setErreur] = useState('');
|
const [erreur, setErreur] = useState('');
|
||||||
|
|
||||||
/* Chargement initial des filtres */
|
/* Chargement initial des filtres.
|
||||||
|
On n'utilise pas les référentiels SERVICE / CATEGORIE complets : ils
|
||||||
|
contiennent des entrées sans aucun monitoring rattaché (ex. « Business
|
||||||
|
Intelligence »), qui mèneraient sur un dashboard vide. */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function loadFiltres() {
|
async function loadFiltres() {
|
||||||
try {
|
try {
|
||||||
const [svcs, cats] = await Promise.all([getServices(), getCategories()]);
|
const filtres = await getDashboardFiltres();
|
||||||
setServices(svcs);
|
setServices(filtres.services);
|
||||||
setCategories(cats);
|
setCombinaisons(filtres.combinaisons);
|
||||||
} catch {
|
} catch {
|
||||||
/* Non bloquant, les filtres restent vides */
|
/* Non bloquant, les filtres restent vides */
|
||||||
}
|
}
|
||||||
@@ -42,6 +45,28 @@ export default function Dashboard() {
|
|||||||
loadFiltres();
|
loadFiltres();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
/* Catégories proposées : restreintes au service sélectionné, le cas échéant */
|
||||||
|
const categoriesDisponibles = useCallback(service => {
|
||||||
|
const pertinentes = service
|
||||||
|
? combinaisons.filter(c => c.service === service)
|
||||||
|
: combinaisons;
|
||||||
|
return [...new Set(pertinentes.map(c => c.categorie))].sort();
|
||||||
|
}, [combinaisons]);
|
||||||
|
|
||||||
|
const categories = useMemo(
|
||||||
|
() => categoriesDisponibles(filtreService),
|
||||||
|
[categoriesDisponibles, filtreService],
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Changer de service peut rendre la catégorie sélectionnée sans résultat :
|
||||||
|
on la remet à zéro dans le même rendu, pour ne déclencher qu'un seul appel. */
|
||||||
|
function handleServiceChange(service) {
|
||||||
|
setFiltreService(service);
|
||||||
|
if (filtreCategorie && !categoriesDisponibles(service).includes(filtreCategorie)) {
|
||||||
|
setFiltreCategorie('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Chargement des données dashboard */
|
/* Chargement des données dashboard */
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -105,11 +130,11 @@ export default function Dashboard() {
|
|||||||
<select
|
<select
|
||||||
className={styles.select}
|
className={styles.select}
|
||||||
value={filtreService}
|
value={filtreService}
|
||||||
onChange={e => setFiltreService(e.target.value)}
|
onChange={e => handleServiceChange(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="">Tous les services</option>
|
<option value="">Tous les services</option>
|
||||||
{services.map(s => (
|
{services.map(s => (
|
||||||
<option key={s.id_service} value={s.nom_service}>{s.nom_service}</option>
|
<option key={s} value={s}>{s}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<select
|
<select
|
||||||
@@ -119,7 +144,7 @@ export default function Dashboard() {
|
|||||||
>
|
>
|
||||||
<option value="">Toutes les catégories</option>
|
<option value="">Toutes les catégories</option>
|
||||||
{categories.map(c => (
|
{categories.map(c => (
|
||||||
<option key={c.id_categorie} value={c.intitule_categorie}>{c.intitule_categorie}</option>
|
<option key={c} value={c}>{c}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -245,6 +245,18 @@ export async function getDashboard(service, categorie) {
|
|||||||
})) : data;
|
})) : data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Récupère les valeurs de filtre qui ramènent au moins un monitoring (VUE_CONSO).
|
||||||
|
Évite de proposer un service ou une catégorie du référentiel qui n'a aucun
|
||||||
|
monitoring rattaché, et donc de mener l'utilisateur sur un écran vide. */
|
||||||
|
export async function getDashboardFiltres() {
|
||||||
|
const data = await fetchApi('/dashboard/filtres');
|
||||||
|
return {
|
||||||
|
services: Array.isArray(data?.services) ? data.services : [],
|
||||||
|
categories: Array.isArray(data?.categories) ? data.categories : [],
|
||||||
|
combinaisons: Array.isArray(data?.combinaisons) ? data.combinaisons : [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/* Récupère le résumé global KPI du dashboard */
|
/* Récupère le résumé global KPI du dashboard */
|
||||||
export async function getDashboardSummary() {
|
export async function getDashboardSummary() {
|
||||||
const data = await fetchApi('/dashboard/summary');
|
const data = await fetchApi('/dashboard/summary');
|
||||||
|
|||||||
Reference in New Issue
Block a user