diff --git a/DataSentinel/src/App.jsx b/DataSentinel/src/App.jsx index 24306f8..94b658e 100644 --- a/DataSentinel/src/App.jsx +++ b/DataSentinel/src/App.jsx @@ -1,7 +1,8 @@ // src/App.jsx // Configuration du routing React Router v6 avec protection des routes import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; -import { AuthProvider, useAuth } from './context/AuthContext'; +import { AuthProvider } from './context/AuthContext'; +import { useAuth } from './context/useAuth'; import Layout from './components/Layout/Layout'; import Login from './pages/Login/Login'; import Dashboard from './pages/Dashboard/Dashboard'; diff --git a/DataSentinel/src/__tests__/AuthContext.test.jsx b/DataSentinel/src/__tests__/AuthContext.test.jsx index 439cba2..5c596f4 100644 --- a/DataSentinel/src/__tests__/AuthContext.test.jsx +++ b/DataSentinel/src/__tests__/AuthContext.test.jsx @@ -1,7 +1,8 @@ /* Tests du contexte d'authentification : login / logout */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { renderHook, act } from '@testing-library/react'; -import { AuthProvider, useAuth } from '../context/AuthContext'; +import { AuthProvider } from '../context/AuthContext'; +import { useAuth } from '../context/useAuth'; import * as api from '../services/api'; describe('AuthContext', () => { diff --git a/DataSentinel/src/components/Layout/Header.jsx b/DataSentinel/src/components/Layout/Header.jsx index 3692e77..72c538d 100644 --- a/DataSentinel/src/components/Layout/Header.jsx +++ b/DataSentinel/src/components/Layout/Header.jsx @@ -1,7 +1,7 @@ /* En-tête fixe de l'application Data Sentinel */ import { Bell, LogOut, Menu } from 'lucide-react'; -import { useAuth } from '../../context/AuthContext'; +import { useAuth } from '../../context/useAuth'; import { useNavigate } from 'react-router-dom'; import styles from './Header.module.css'; diff --git a/DataSentinel/src/components/Layout/Sidebar.jsx b/DataSentinel/src/components/Layout/Sidebar.jsx index 082bd2d..8b6fba9 100644 --- a/DataSentinel/src/components/Layout/Sidebar.jsx +++ b/DataSentinel/src/components/Layout/Sidebar.jsx @@ -13,7 +13,7 @@ import { FileText, ScrollText, } from 'lucide-react'; -import { useAuth } from '../../context/AuthContext'; +import { useAuth } from '../../context/useAuth'; import styles from './Sidebar.module.css'; /* Un lien de navigation avec icône */ diff --git a/DataSentinel/src/context/AuthContext.jsx b/DataSentinel/src/context/AuthContext.jsx index aac8a26..dc3b5aa 100644 --- a/DataSentinel/src/context/AuthContext.jsx +++ b/DataSentinel/src/context/AuthContext.jsx @@ -1,10 +1,9 @@ /* Contexte d'authentification global : gestion de l'utilisateur connecté */ -import { createContext, useContext, useState } from 'react'; +import { useState } from 'react'; import * as api from '../services/api'; import { USER_KEY } from '../services/api'; - -const AuthContext = createContext(null); +import { AuthContext } from './authContextObject'; /* Fournisseur du contexte d'authentification */ export function AuthProvider({ children }) { @@ -39,11 +38,3 @@ export function AuthProvider({ children }) { ); } -/* Hook personnalisé pour accéder au contexte d'authentification */ -export function useAuth() { - const context = useContext(AuthContext); - if (!context) { - throw new Error('useAuth doit être utilisé dans un AuthProvider'); - } - return context; -} diff --git a/DataSentinel/src/context/authContextObject.js b/DataSentinel/src/context/authContextObject.js new file mode 100644 index 0000000..b1cba8b --- /dev/null +++ b/DataSentinel/src/context/authContextObject.js @@ -0,0 +1,7 @@ +/* Objet de contexte isolé : un fichier qui exporte un composant ne doit + exporter que des composants, sans quoi le rafraîchissement à chaud de Vite + (react-refresh) cesse de fonctionner pour ce fichier. */ + +import { createContext } from 'react'; + +export const AuthContext = createContext(null); diff --git a/DataSentinel/src/context/useAuth.js b/DataSentinel/src/context/useAuth.js new file mode 100644 index 0000000..429359d --- /dev/null +++ b/DataSentinel/src/context/useAuth.js @@ -0,0 +1,15 @@ +/* Hook d'accès au contexte d'authentification. + + Séparé de AuthContext.jsx : un fichier qui exporte à la fois un composant + et autre chose casse le rafraîchissement à chaud de Vite (react-refresh). */ + +import { useContext } from 'react'; +import { AuthContext } from './authContextObject'; + +export function useAuth() { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth doit être utilisé dans un AuthProvider'); + } + return context; +} diff --git a/DataSentinel/src/pages/Dashboard/Dashboard.jsx b/DataSentinel/src/pages/Dashboard/Dashboard.jsx index 979484a..ae98b6c 100644 --- a/DataSentinel/src/pages/Dashboard/Dashboard.jsx +++ b/DataSentinel/src/pages/Dashboard/Dashboard.jsx @@ -18,6 +18,7 @@ export default function Dashboard() { const [filtreCategorie, setFiltreCategorie] = useState(''); const [loading, setLoading] = useState(true); const [erreur, setErreur] = useState(''); + const [rafraichissement, setRafraichissement] = useState(0); /* Chargement initial des filtres. On n'utilise pas les référentiels SERVICE / CATEGORIE complets : ils @@ -52,39 +53,57 @@ export default function Dashboard() { /* 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) { + setLoading(true); setFiltreService(service); if (filtreCategorie && !categoriesDisponibles(service).includes(filtreCategorie)) { setFiltreCategorie(''); } } - /* Chargement des données dashboard */ - const loadData = useCallback(async () => { + function handleCategorieChange(categorie) { setLoading(true); - setErreur(''); - try { - const [sum, data] = await Promise.all([ - getDashboardSummary(), - getDashboard(filtreService, filtreCategorie), - ]); - setSummary(sum); - setMonitorings(Array.isArray(data) ? data : []); - } catch (e) { - setErreur(e.message || 'Erreur lors du chargement du dashboard.'); - } finally { - setLoading(false); - } - }, [filtreService, filtreCategorie]); + setFiltreCategorie(categorie); + } + /* Le bouton Actualiser relance l'effet en changeant cette valeur. */ + function handleActualiser() { + setLoading(true); + setRafraichissement(compteur => compteur + 1); + } + + /* Les données sont rechargées à chaque changement de filtre. L'indicateur de + chargement est allumé par les gestionnaires d'événement ci-dessus, jamais + dans l'effet : y appeler setState de façon synchrone provoquerait un rendu + en cascade. */ useEffect(() => { - loadData(); - }, [loadData]); + let annule = false; + + async function charger() { + try { + const [sum, data] = await Promise.all([ + getDashboardSummary(), + getDashboard(filtreService, filtreCategorie), + ]); + if (annule) return; + setSummary(sum); + setMonitorings(Array.isArray(data) ? data : []); + setErreur(''); + } catch (e) { + if (!annule) setErreur(e.message || 'Erreur lors du chargement du dashboard.'); + } finally { + if (!annule) setLoading(false); + } + } + + charger(); + return () => { annule = true; }; + }, [filtreService, filtreCategorie, rafraichissement]); return (

Dashboard

- @@ -131,7 +150,7 @@ export default function Dashboard() {