fix(front): corrige les 4 erreurs eslint restantes
Le dépôt sortait 7 erreurs eslint au début de cette série de correctifs, dont certaines antérieures. Il n'en reste aucune. - Dashboard et Users appelaient setState de façon synchrone dans un effet, ce qui provoque des rendus en cascade. Le chargement initial passe par une fonction asynchrone qui n'écrit qu'après l'await, avec annulation au démontage. Sur le Dashboard, l'indicateur de chargement est allumé par les gestionnaires d'événement, et le bouton Actualiser relance l'effet via un compteur plutôt qu'en appelant le chargement. - L'import ExternalLink de Documentation.jsx n'était pas utilisé. - AuthContext.jsx exportait à la fois un composant, le contexte et un hook, ce qui désactivait le rafraîchissement à chaud de Vite pour ce fichier. Le contexte et le hook vivent désormais dans leurs propres modules ; les 5 fichiers concernés sont mis à jour.
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
// src/App.jsx
|
// src/App.jsx
|
||||||
// Configuration du routing React Router v6 avec protection des routes
|
// Configuration du routing React Router v6 avec protection des routes
|
||||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
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 Layout from './components/Layout/Layout';
|
||||||
import Login from './pages/Login/Login';
|
import Login from './pages/Login/Login';
|
||||||
import Dashboard from './pages/Dashboard/Dashboard';
|
import Dashboard from './pages/Dashboard/Dashboard';
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/* Tests du contexte d'authentification : login / logout */
|
/* Tests du contexte d'authentification : login / logout */
|
||||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
import { renderHook, act } from '@testing-library/react';
|
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';
|
import * as api from '../services/api';
|
||||||
|
|
||||||
describe('AuthContext', () => {
|
describe('AuthContext', () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* En-tête fixe de l'application Data Sentinel */
|
/* En-tête fixe de l'application Data Sentinel */
|
||||||
|
|
||||||
import { Bell, LogOut, Menu } from 'lucide-react';
|
import { Bell, LogOut, Menu } from 'lucide-react';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/useAuth';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import styles from './Header.module.css';
|
import styles from './Header.module.css';
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
FileText,
|
FileText,
|
||||||
ScrollText,
|
ScrollText,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/useAuth';
|
||||||
import styles from './Sidebar.module.css';
|
import styles from './Sidebar.module.css';
|
||||||
|
|
||||||
/* Un lien de navigation avec icône */
|
/* Un lien de navigation avec icône */
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
/* Contexte d'authentification global : gestion de l'utilisateur connecté */
|
/* 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 * as api from '../services/api';
|
||||||
import { USER_KEY } from '../services/api';
|
import { USER_KEY } from '../services/api';
|
||||||
|
import { AuthContext } from './authContextObject';
|
||||||
const AuthContext = createContext(null);
|
|
||||||
|
|
||||||
/* Fournisseur du contexte d'authentification */
|
/* Fournisseur du contexte d'authentification */
|
||||||
export function AuthProvider({ children }) {
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ export default function Dashboard() {
|
|||||||
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('');
|
||||||
|
const [rafraichissement, setRafraichissement] = useState(0);
|
||||||
|
|
||||||
/* Chargement initial des filtres.
|
/* Chargement initial des filtres.
|
||||||
On n'utilise pas les référentiels SERVICE / CATEGORIE complets : ils
|
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 :
|
/* 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. */
|
on la remet à zéro dans le même rendu, pour ne déclencher qu'un seul appel. */
|
||||||
function handleServiceChange(service) {
|
function handleServiceChange(service) {
|
||||||
|
setLoading(true);
|
||||||
setFiltreService(service);
|
setFiltreService(service);
|
||||||
if (filtreCategorie && !categoriesDisponibles(service).includes(filtreCategorie)) {
|
if (filtreCategorie && !categoriesDisponibles(service).includes(filtreCategorie)) {
|
||||||
setFiltreCategorie('');
|
setFiltreCategorie('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chargement des données dashboard */
|
function handleCategorieChange(categorie) {
|
||||||
const loadData = useCallback(async () => {
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setErreur('');
|
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(() => {
|
||||||
|
let annule = false;
|
||||||
|
|
||||||
|
async function charger() {
|
||||||
try {
|
try {
|
||||||
const [sum, data] = await Promise.all([
|
const [sum, data] = await Promise.all([
|
||||||
getDashboardSummary(),
|
getDashboardSummary(),
|
||||||
getDashboard(filtreService, filtreCategorie),
|
getDashboard(filtreService, filtreCategorie),
|
||||||
]);
|
]);
|
||||||
|
if (annule) return;
|
||||||
setSummary(sum);
|
setSummary(sum);
|
||||||
setMonitorings(Array.isArray(data) ? data : []);
|
setMonitorings(Array.isArray(data) ? data : []);
|
||||||
|
setErreur('');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setErreur(e.message || 'Erreur lors du chargement du dashboard.');
|
if (!annule) setErreur(e.message || 'Erreur lors du chargement du dashboard.');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
if (!annule) setLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [filtreService, filtreCategorie]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
charger();
|
||||||
loadData();
|
return () => { annule = true; };
|
||||||
}, [loadData]);
|
}, [filtreService, filtreCategorie, rafraichissement]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.page}>
|
<div className={styles.page}>
|
||||||
<div className={styles.pageHeader}>
|
<div className={styles.pageHeader}>
|
||||||
<h1 className={styles.title}>Dashboard</h1>
|
<h1 className={styles.title}>Dashboard</h1>
|
||||||
<button className={styles.refreshBtn} onClick={loadData} disabled={loading}>
|
<button className={styles.refreshBtn} onClick={handleActualiser} disabled={loading}>
|
||||||
<RefreshCw size={14} className={loading ? styles.spinning : ''} />
|
<RefreshCw size={14} className={loading ? styles.spinning : ''} />
|
||||||
Actualiser
|
Actualiser
|
||||||
</button>
|
</button>
|
||||||
@@ -131,7 +150,7 @@ export default function Dashboard() {
|
|||||||
<select
|
<select
|
||||||
className={styles.select}
|
className={styles.select}
|
||||||
value={filtreCategorie}
|
value={filtreCategorie}
|
||||||
onChange={e => setFiltreCategorie(e.target.value)}
|
onChange={e => handleCategorieChange(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="">Toutes les catégories</option>
|
<option value="">Toutes les catégories</option>
|
||||||
{categories.map(c => (
|
{categories.map(c => (
|
||||||
@@ -144,7 +163,7 @@ export default function Dashboard() {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<LoadingSpinner message="Chargement du dashboard..." />
|
<LoadingSpinner message="Chargement du dashboard..." />
|
||||||
) : erreur ? (
|
) : erreur ? (
|
||||||
<ErrorMessage message={erreur} onRetry={loadData} />
|
<ErrorMessage message={erreur} onRetry={handleActualiser} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{monitorings.length === 0 ? (
|
{monitorings.length === 0 ? (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// src/pages/Documentation/Documentation.jsx
|
// src/pages/Documentation/Documentation.jsx
|
||||||
// Page documentation technique de Data Sentinel
|
// Page documentation technique de Data Sentinel
|
||||||
import { ExternalLink, Book, Code, Database, Shield } from 'lucide-react';
|
import { Book, Code, Database, Shield } from 'lucide-react';
|
||||||
import styles from './Documentation.module.css';
|
import styles from './Documentation.module.css';
|
||||||
|
|
||||||
const sections = [
|
const sections = [
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/useAuth';
|
||||||
import styles from './Login.module.css';
|
import styles from './Login.module.css';
|
||||||
|
|
||||||
/* Génère deux entiers aléatoires pour le captcha additif */
|
/* Génère deux entiers aléatoires pour le captcha additif */
|
||||||
|
|||||||
@@ -43,9 +43,25 @@ export default function Users() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
/* Chargement initial. Le drapeau `annule` évite d'écrire dans un composant
|
||||||
|
déjà démonté si l'utilisateur quitte la page pendant la requête. */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
charger();
|
let annule = false;
|
||||||
}, [charger]);
|
|
||||||
|
async function chargerAuMontage() {
|
||||||
|
try {
|
||||||
|
const data = await getUsers();
|
||||||
|
if (!annule) setUsers(Array.isArray(data) ? data : []);
|
||||||
|
} catch (e) {
|
||||||
|
if (!annule) setErreur(e.message || 'Erreur lors du chargement des utilisateurs.');
|
||||||
|
} finally {
|
||||||
|
if (!annule) setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chargerAuMontage();
|
||||||
|
return () => { annule = true; };
|
||||||
|
}, []);
|
||||||
|
|
||||||
/* Ouverture des modales */
|
/* Ouverture des modales */
|
||||||
function ouvrirCreation() {
|
function ouvrirCreation() {
|
||||||
|
|||||||
Reference in New Issue
Block a user