Ajout des cgu

This commit is contained in:
2026-05-21 09:58:04 +02:00
parent 5efd4dcf35
commit 87c099433b
14 changed files with 802 additions and 32 deletions
+3 -2
View File
@@ -1,10 +1,11 @@
<!doctype html>
<html lang="en">
<html lang="fr">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>datasentinel</title>
<meta name="description" content="Data Sentinel — Plateforme de monitoring de la qualité des données by XEFI" />
<title>Data Sentinel — by XEFI</title>
</head>
<body>
<div id="root"></div>
+5 -4
View File
@@ -12,7 +12,9 @@ import Fournisseur from './pages/Fournisseur/Fournisseur';
import Historique from './pages/Historique/Historique';
import Admin from './pages/Admin/Admin';
import Users from './pages/Users/Users';
import Documentation from './pages/Documentation/Documentation';
import MentionsLegales from './pages/MentionsLegales/MentionsLegales';
import CGU from './pages/CGU/CGU';
/* Composant pour protéger les routes nécessitant une authentification */
function ProtectedRoute({ children }) {
@@ -31,7 +33,7 @@ function App() {
<AuthProvider>
<Router>
<Routes>
{/* Route publique : login */}
{/* Routes publiques */}
<Route
path="/login"
element={
@@ -40,9 +42,8 @@ function App() {
</PublicRoute>
}
/>
{/* Route publique : mentions légales (sans sidebar) */}
<Route path="/mentions-legales" element={<MentionsLegales />} />
<Route path="/cgu" element={<CGU />} />
{/* Routes protégées avec Layout */}
<Route
@@ -62,6 +63,7 @@ function App() {
<Route path="historique" element={<Historique />} />
<Route path="admin" element={<Admin />} />
<Route path="utilisateurs" element={<Users />} />
<Route path="documentation" element={<Documentation />} />
</Route>
{/* Redirection par défaut */}
@@ -73,4 +75,3 @@ function App() {
}
export default App;
+30 -11
View File
@@ -5,7 +5,7 @@ import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
import styles from './Header.module.css';
export default function Header({ onToggleSidebar }) {
export default function Header({ onToggleSidebar, sidebarOpen }) {
const { user, logout } = useAuth();
const navigate = useNavigate();
@@ -15,26 +15,45 @@ export default function Header({ onToggleSidebar }) {
}
return (
<header className={styles.header}>
<header className={styles.header} role="banner">
{/* Lien d'évitement pour l'accessibilité clavier */}
<a href="#main-content" className={styles.skipLink}>
Aller au contenu principal
</a>
<div className={styles.left}>
{/* Bouton burger pour mobile */}
<button className={styles.burgerBtn} onClick={onToggleSidebar} aria-label="Menu">
<Menu size={20} color="var(--color-white)" />
<button
className={styles.burgerBtn}
onClick={onToggleSidebar}
aria-label={sidebarOpen ? 'Fermer le menu' : 'Ouvrir le menu'}
aria-expanded={sidebarOpen}
aria-controls="sidebar"
>
<Menu size={20} color="var(--color-white)" aria-hidden="true" />
</button>
<div className={styles.logo}>
<span className={styles.logoIcon}></span>
<div className={styles.logo} role="img" aria-label="Data Sentinel">
<span className={styles.logoIcon} aria-hidden="true"></span>
<span className={styles.logoText}>Data Sentinel</span>
</div>
<span className={styles.byXefi}>by XEFI</span>
<span className={styles.byXefi} aria-label="by XEFI">by XEFI</span>
</div>
<div className={styles.right}>
<button className={styles.iconBtn} aria-label="Notifications">
<Bell size={18} color="var(--color-white)" />
<Bell size={18} color="var(--color-white)" aria-hidden="true" />
</button>
{user && <span className={styles.username}>{user.nom}</span>}
<button className={styles.logoutBtn} onClick={handleLogout}>
<LogOut size={15} />
{user && (
<span className={styles.username} aria-label={`Connecté en tant que ${user.nom}`}>
{user.nom}
</span>
)}
<button
className={styles.logoutBtn}
onClick={handleLogout}
aria-label="Se déconnecter"
>
<LogOut size={15} aria-hidden="true" />
Déconnexion
</button>
</div>
@@ -99,3 +99,43 @@
.logoutBtn:hover {
text-decoration: underline;
}
/* Lien d'évitement accessible (skip link) */
.skipLink {
position: absolute;
top: -100px;
left: 8px;
background: var(--color-white);
color: var(--color-text);
padding: 8px 16px;
border-radius: var(--radius);
font-size: 13px;
font-weight: 600;
text-decoration: none;
z-index: 9999;
transition: top 0.1s;
}
.skipLink:focus {
top: 8px;
}
/* Focus visible sur tous les éléments interactifs */
.burgerBtn:focus-visible,
.iconBtn:focus-visible,
.logoutBtn:focus-visible {
outline: 2px solid var(--color-white);
outline-offset: 2px;
border-radius: 2px;
}
/* Responsive : masquer le nom utilisateur sur très petit écran */
@media (max-width: 480px) {
.username {
display: none;
}
.byXefi {
display: none;
}
}
@@ -11,9 +11,12 @@ export default function Layout() {
return (
<div className={styles.root}>
<Header onToggleSidebar={() => setSidebarOpen(o => !o)} />
<Header
onToggleSidebar={() => setSidebarOpen(o => !o)}
sidebarOpen={sidebarOpen}
/>
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
<main className={styles.main}>
<main id="main-content" className={styles.main} tabIndex="-1">
<Outlet />
</main>
</div>
+30 -13
View File
@@ -10,6 +10,7 @@ import {
List,
Book,
Shield,
FileText,
} from 'lucide-react';
import styles from './Sidebar.module.css';
@@ -21,8 +22,9 @@ function NavItem({ to, icon: Icon, label }) {
className={({ isActive }) =>
`${styles.navItem} ${isActive ? styles.active : ''}`
}
aria-current={undefined}
>
<Icon size={16} />
<Icon size={16} aria-hidden="true" />
<span>{label}</span>
</NavLink>
);
@@ -32,12 +34,23 @@ export default function Sidebar({ isOpen, onClose }) {
return (
<>
{/* Overlay mobile pour fermer la sidebar */}
{isOpen && <div className={styles.overlay} onClick={onClose} />}
{isOpen && (
<div
className={styles.overlay}
onClick={onClose}
role="presentation"
aria-hidden="true"
/>
)}
<aside className={`${styles.sidebar} ${isOpen ? styles.open : ''}`}>
<nav className={styles.nav}>
<aside
id="sidebar"
className={`${styles.sidebar} ${isOpen ? styles.open : ''}`}
aria-label="Navigation principale"
>
<nav className={styles.nav} aria-label="Menu de navigation">
{/* Navigation principale */}
<div className={styles.section}>
<div className={styles.section} role="group" aria-label="Monitoring">
<NavItem to="/dashboard" icon={LayoutGrid} label="Dashboard" />
<NavItem to="/consolidee" icon={Layers} label="Vue consolidée" />
<NavItem to="/contrat" icon={List} label="Contrat" />
@@ -45,28 +58,32 @@ export default function Sidebar({ isOpen, onClose }) {
<NavItem to="/historique" icon={Clock} label="Historique" />
</div>
<div className={styles.separator} />
<div className={styles.separator} role="separator" />
{/* Section administration */}
<div className={styles.sectionLabel}>ADMIN</div>
<div className={styles.section}>
<div className={styles.sectionLabel} aria-hidden="true">ADMIN</div>
<div className={styles.section} role="group" aria-label="Administration">
<NavItem to="/admin" icon={Settings} label="Nomenclature" />
<NavItem to="/utilisateurs" icon={Users} label="Utilisateurs" />
<NavItem to="/journal" icon={List} label="Journal" />
</div>
</nav>
{/* Liens de pied de sidebar */}
<div className={styles.footer}>
<nav className={styles.footer} aria-label="Liens utiles">
<NavLink to="/documentation" className={styles.footerLink}>
<Book size={13} />
<Book size={13} aria-hidden="true" />
Documentation
</NavLink>
<NavLink to="/mentions-legales" className={styles.footerLink}>
<Shield size={13} />
<Shield size={13} aria-hidden="true" />
Mentions légales
</NavLink>
</div>
<NavLink to="/cgu" className={styles.footerLink}>
<FileText size={13} aria-hidden="true" />
CGU
</NavLink>
</nav>
</aside>
</>
);
+77
View File
@@ -20,6 +20,13 @@
--shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
--header-height: 56px;
--sidebar-width: 220px;
/* Breakpoints (référence pour JS si nécessaire) */
--bp-xs: 320px;
--bp-sm: 640px;
--bp-md: 1024px;
--bp-lg: 1280px;
--bp-xl: 1920px;
}
/* Reset */
@@ -83,3 +90,73 @@ img {
max-width: 100%;
height: auto;
}
/* ========================
ACCESSIBILITÉ
======================== */
/* Focus visible global — indispensable pour la navigation clavier (WCAG 2.1 SC 2.4.7) */
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
border-radius: 2px;
}
/* Masque le focus sur les clics souris (uniquement clavier) */
:focus:not(:focus-visible) {
outline: none;
}
/* Texte masqué visuellement mais lisible par les lecteurs d'écran */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Réduire les animations pour les utilisateurs sensibles */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* ========================
RESPONSIVITÉ GLOBALE
======================== */
/* Tableaux responsifs : scroll horizontal sur petit écran */
table {
width: 100%;
border-collapse: collapse;
}
@media (max-width: 640px) {
table {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
}
/* Inputs et selects pleine largeur sur mobile */
@media (max-width: 640px) {
input[type="text"],
input[type="email"],
input[type="password"],
input[type="date"],
input[type="search"],
select,
textarea {
width: 100%;
}
}
+161
View File
@@ -0,0 +1,161 @@
// src/pages/CGU/CGU.jsx
// Conditions Générales d'Utilisation — page publique sans sidebar
import { useNavigate } from 'react-router-dom';
import { ArrowLeft } from 'lucide-react';
import Button from '../../components/common/Button';
import styles from './CGU.module.css';
export default function CGU() {
const navigate = useNavigate();
return (
<div className={styles.page}>
<div className={styles.container}>
<header className={styles.header}>
<span className={styles.logo} aria-label="Data Sentinel"> Data Sentinel</span>
<Button variant="link" onClick={() => navigate(-1)} aria-label="Retour à la page précédente">
<ArrowLeft size={14} />
Retour
</Button>
</header>
<main id="main-content">
<h1 className={styles.mainTitle}>Conditions Générales d'Utilisation</h1>
<p className={styles.subtitle}>Version 1.0 — en vigueur depuis le 1er janvier 2026</p>
<section className={styles.section} aria-labelledby="art1">
<h2 id="art1">Article 1 — Objet et champ d'application</h2>
<p>
Les présentes Conditions Générales d'Utilisation (ci-après « CGU ») régissent l'accès et
l'utilisation de la plateforme <strong>Data Sentinel</strong>, outil interne de monitoring
de la qualité des données développé par COYAUD Anthony pour le compte de <strong>XEFI SA</strong>
(ci-après « l'Éditeur »).
</p>
<p>
Data Sentinel est une application web à usage professionnel interne, accessible uniquement
aux collaborateurs habilités de XEFI et de ses filiales. Toute utilisation de la plateforme
implique l'acceptation pleine et entière des présentes CGU.
</p>
</section>
<section className={styles.section} aria-labelledby="art2">
<h2 id="art2">Article 2 — Accès au service</h2>
<p>
L'accès à Data Sentinel est réservé aux utilisateurs disposant d'un compte valide créé
par un administrateur système XEFI. L'authentification s'effectue via un identifiant et
un mot de passe fournis par l'entreprise.
</p>
<ul className={styles.list}>
<li>Chaque utilisateur est responsable de la confidentialité de ses identifiants.</li>
<li>Le partage de comptes est strictement interdit.</li>
<li>Tout accès non autorisé doit être signalé immédiatement au service informatique.</li>
<li>L'Éditeur se réserve le droit de suspendre tout accès en cas d'utilisation non conforme.</li>
</ul>
</section>
<section className={styles.section} aria-labelledby="art3">
<h2 id="art3">Article 3 Description du service</h2>
<p>Data Sentinel offre les fonctionnalités suivantes :</p>
<ul className={styles.list}>
<li><strong>Dashboard</strong> Vue d'ensemble des indicateurs clés de qualité des données (KPI).</li>
<li><strong>Monitoring</strong> — Suivi en temps réel des monitorings CRM, Sage et Infocentre.</li>
<li><strong>Vue consolidée</strong> — Agrégation des résultats par service et catégorie.</li>
<li><strong>Historique</strong> — Consultation et export des données historiques.</li>
<li><strong>Administration</strong> — Gestion des nomenclatures, utilisateurs et journaux.</li>
</ul>
</section>
<section className={styles.section} aria-labelledby="art4">
<h2 id="art4">Article 4 — Obligations de l'utilisateur</h2>
<p>L'utilisateur s'engage à :</p>
<ul className={styles.list}>
<li>Utiliser la plateforme exclusivement dans le cadre de ses missions professionnelles.</li>
<li>Ne pas tenter de contourner les mécanismes de sécurité (authentification, contrôle d'accès).</li>
<li>Ne pas extraire, reproduire ou diffuser les données sans autorisation.</li>
<li>Signaler tout dysfonctionnement ou anomalie de sécurité à l'équipe informatique.</li>
<li>Respecter les règles internes XEFI relatives à la sécurité des systèmes d'information.</li>
</ul>
</section>
<section className={styles.section} aria-labelledby="art5">
<h2 id="art5">Article 5 — Protection des données personnelles (RGPD)</h2>
<p>
Conformément au Règlement Général sur la Protection des Données (UE 2016/679), XEFI SA
traite les données personnelles des utilisateurs dans le strict respect de la réglementation.
</p>
<div className={styles.infoBox} role="note" aria-label="Informations RGPD">
<p><strong>Responsable du traitement :</strong> XEFI SA — 2507 Avenue de l'Europe, 69140 Rillieux-la-Pape</p>
<p><strong>Délégué à la Protection des Données (DPO) :</strong> dpo@xefi.fr</p>
<p><strong>Finalité :</strong> Authentification, traçabilité des actions, sécurité du système.</p>
<p><strong>Durée de conservation :</strong> 24 mois à compter de la dernière connexion.</p>
<p><strong>Droits :</strong> Accès, rectification, effacement, portabilité à exercer auprès de dpo@xefi.fr.</p>
</div>
</section>
<section className={styles.section} aria-labelledby="art6">
<h2 id="art6">Article 6 Accessibilité</h2>
<p>
XEFI s'engage à rendre Data Sentinel accessible conformément au Référentiel Général
d'Amélioration de l'Accessibilité (RGAA). L'application respecte les principes WCAG 2.1
niveau AA : perceptibilité, utilisabilité, compréhension et robustesse.
</p>
<ul className={styles.list}>
<li>Navigation clavier intégrale supportée.</li>
<li>Compatibilité avec les lecteurs d'écran (ARIA labels, landmarks, roles).</li>
<li>Contrastes de couleurs conformes aux exigences WCAG 2.1 AA (ratio 4.5:1 minimum).</li>
<li>Interface responsive adaptée aux écrans de 320px à 1920px.</li>
</ul>
<p>
Pour signaler un problème d'accessibilité : <strong>dpo@xefi.fr</strong>
</p>
</section>
<section className={styles.section} aria-labelledby="art7">
<h2 id="art7">Article 7 Disponibilité et maintenance</h2>
<p>
L'Éditeur s'efforce de maintenir la plateforme disponible 24h/24, 7j/7, sans garantie
de continuité de service. Des interruptions peuvent survenir pour maintenance planifiée
ou en cas d'incident technique. Les utilisateurs seront prévenus dans la mesure du possible.
</p>
</section>
<section className={styles.section} aria-labelledby="art8">
<h2 id="art8">Article 8 — Propriété intellectuelle</h2>
<p>
L'ensemble des éléments composant Data Sentinel (code source, design, données, documentation)
est la propriété exclusive de <strong>XEFI SA</strong>. Toute reproduction, modification
ou diffusion sans autorisation écrite préalable est interdite.
</p>
<p>Application développée par <strong>COYAUD Anthony</strong> Bachelor DBI, Nexa Digital School, 2026.</p>
</section>
<section className={styles.section} aria-labelledby="art9">
<h2 id="art9">Article 9 Modification des CGU</h2>
<p>
XEFI SA se réserve le droit de modifier les présentes CGU à tout moment. Les utilisateurs
seront informés de toute modification substantielle. La poursuite de l'utilisation de la
plateforme après notification vaut acceptation des nouvelles conditions.
</p>
</section>
<section className={styles.section} aria-labelledby="art10">
<h2 id="art10">Article 10 — Droit applicable et contact</h2>
<p>Les présentes CGU sont soumises au droit français. Pour toute question :</p>
<div className={styles.infoBox} role="note" aria-label="Coordonnées de contact">
<p><strong>XEFI SA</strong></p>
<p>2507 Avenue de l'Europe 69140 Rillieux-la-Pape</p>
<p>Email : dpo@xefi.fr</p>
</div>
</section>
</main>
<footer className={styles.footer} role="contentinfo">
<p>Data Sentinel v1.0 © XEFI SA 2026</p>
<nav aria-label="Liens légaux">
<a href="/mentions-legales">Mentions légales</a>
</nav>
</footer>
</div>
</div>
);
}
+134
View File
@@ -0,0 +1,134 @@
/* src/pages/CGU/CGU.module.css */
.page {
min-height: 100vh;
background-color: var(--color-white);
color: var(--color-text);
}
.container {
max-width: 760px;
margin: 0 auto;
padding: 24px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
}
.logo {
font-size: 20px;
font-weight: bold;
color: var(--color-primary);
}
.mainTitle {
font-size: 28px;
font-weight: 700;
color: var(--color-text);
text-align: center;
margin-bottom: 8px;
}
.subtitle {
text-align: center;
color: var(--color-text-secondary);
font-size: 13px;
margin-bottom: 48px;
}
.section {
margin-bottom: 36px;
}
.section h2 {
font-size: 16px;
font-weight: 700;
color: var(--color-text);
margin-bottom: 12px;
padding-left: 14px;
border-left: 4px solid var(--color-primary);
}
.section p {
margin: 8px 0;
line-height: 1.65;
color: var(--color-text-secondary);
}
.list {
list-style: disc;
padding-left: 24px;
margin: 10px 0;
}
.list li {
margin: 6px 0;
line-height: 1.6;
color: var(--color-text-secondary);
}
.infoBox {
background-color: var(--color-gray-light);
border-left: 4px solid var(--color-primary);
border-radius: var(--radius);
padding: 16px 20px;
margin-top: 12px;
}
.infoBox p {
margin: 4px 0;
font-size: 13px;
}
.footer {
margin-top: 64px;
padding: 16px 24px;
background-color: var(--color-black);
color: var(--color-white);
text-align: center;
margin-left: -24px;
margin-right: -24px;
margin-bottom: -24px;
font-size: 13px;
display: flex;
flex-direction: column;
gap: 8px;
}
.footer nav {
display: flex;
justify-content: center;
gap: 16px;
}
.footer nav a {
color: #aaa;
font-size: 12px;
text-decoration: none;
}
.footer nav a:hover {
color: var(--color-white);
text-decoration: underline;
}
/* Responsive */
@media (max-width: 640px) {
.container {
padding: 16px;
}
.mainTitle {
font-size: 22px;
}
.header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
}
@@ -108,3 +108,49 @@
padding: 32px 0;
text-align: center;
}
/* Responsive */
@media (max-width: 640px) {
.page {
padding: 12px;
}
.pageHeader {
flex-direction: column;
align-items: flex-start;
gap: 12px;
margin-bottom: 20px;
}
.title {
font-size: 20px;
}
.kpiGrid {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.kpiValue {
font-size: 28px;
}
.filtres {
flex-direction: column;
}
.select {
width: 100%;
min-width: unset;
}
.grid {
grid-template-columns: 1fr;
}
}
@media (min-width: 1280px) {
.grid {
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
}
@@ -0,0 +1,85 @@
// src/pages/Documentation/Documentation.jsx
// Page documentation technique de Data Sentinel
import { ExternalLink, Book, Code, Database, Shield } from 'lucide-react';
import styles from './Documentation.module.css';
const sections = [
{
icon: Book,
title: 'Guide utilisateur',
items: [
{ label: 'Dashboard — Lecture des KPI et monitorings', desc: 'Comprendre les indicateurs clés : total, sans erreur, en erreur, total erreurs.' },
{ label: 'Vue consolidée — Agrégation par service', desc: 'Analyse croisée des résultats par service métier et catégorie de monitoring.' },
{ label: 'Historique — Export et graphiques', desc: 'Consultation des données passées avec export CSV et visualisation en courbe.' },
{ label: 'Gestion des contrats et fournisseurs', desc: 'Référentiel des contrats de service et fournisseurs associés.' },
],
},
{
icon: Code,
title: 'Architecture technique',
items: [
{ label: 'Frontend — React 18 + Vite', desc: 'SPA React 18 avec CSS Modules, React Router v7, Recharts. Build Vite.' },
{ label: 'Backend — API REST', desc: 'API REST avec authentification JWT. Endpoints documentés via Swagger.' },
{ label: 'Base de données — SQL Server', desc: 'SQL Server avec vues SQL optimisées et jobs CRON pour les agrégations.' },
{ label: 'Infrastructure — NEXERN (XEFI)', desc: 'Hébergement souverain français sur les datacenters NEXERN de XEFI.' },
],
},
{
icon: Database,
title: 'Monitorings disponibles',
items: [
{ label: 'CRM — Données clients et prospects', desc: 'Surveillance de la cohérence et de la complétude des données CRM.' },
{ label: 'Sage — Données comptables et financières', desc: 'Contrôle qualité des flux comptables issus de Sage.' },
{ label: 'Infocentre — Reporting décisionnel', desc: 'Validation des données alimentant les rapports de direction.' },
],
},
{
icon: Shield,
title: 'Sécurité et conformité',
items: [
{ label: 'Authentification JWT', desc: 'Token JWT stocké en sessionStorage, expiration automatique.' },
{ label: 'Contrôle d\'accès RBAC', desc: 'Rôles : Administrateur, Superviseur, Consultant. Accès aux fonctions selon le rôle.' },
{ label: 'Conformité RGPD', desc: 'Conservation 24 mois, DPO joignable sur dpo@xefi.fr, droits exercés à la demande.' },
{ label: 'Accessibilité RGAA / WCAG 2.1 AA', desc: 'Navigation clavier, ARIA, contrastes conformes, responsive 320px1920px.' },
],
},
];
export default function Documentation() {
return (
<div className={styles.page}>
<div className={styles.pageHeader}>
<h1 className={styles.title}>Documentation</h1>
<p className={styles.subtitle}>Référence technique et guide d'utilisation de Data Sentinel</p>
</div>
<div className={styles.grid}>
{sections.map(({ icon: Icon, title, items }) => (
<section key={title} className={styles.card} aria-labelledby={`section-${title.replace(/\s+/g, '-')}`}>
<div className={styles.cardHeader}>
<Icon size={18} className={styles.cardIcon} aria-hidden="true" />
<h2 id={`section-${title.replace(/\s+/g, '-')}`} className={styles.cardTitle}>{title}</h2>
</div>
<ul className={styles.list}>
{items.map(({ label, desc }) => (
<li key={label} className={styles.item}>
<span className={styles.itemLabel}>{label}</span>
<span className={styles.itemDesc}>{desc}</span>
</li>
))}
</ul>
</section>
))}
</div>
<div className={styles.footer}>
<p>Data Sentinel v1.0 — Développé par COYAUD Anthony, Nexa Digital School 2026</p>
<p>
<a href="/cgu" className={styles.link}>CGU</a>
{' · '}
<a href="/mentions-legales" className={styles.link}>Mentions légales</a>
</p>
</div>
</div>
);
}
@@ -0,0 +1,108 @@
/* src/pages/Documentation/Documentation.module.css */
.page {
max-width: 1100px;
}
.pageHeader {
margin-bottom: 32px;
}
.title {
font-size: 22px;
font-weight: 700;
color: var(--color-text);
margin-bottom: 6px;
}
.subtitle {
color: var(--color-text-secondary);
font-size: 13px;
}
.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
margin-bottom: 40px;
}
.card {
background-color: var(--color-white);
border: 1px solid var(--color-gray-border);
border-radius: var(--radius);
padding: 20px;
box-shadow: var(--shadow);
}
.cardHeader {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--color-gray-border);
}
.cardIcon {
color: var(--color-primary);
flex-shrink: 0;
}
.cardTitle {
font-size: 15px;
font-weight: 700;
color: var(--color-text);
}
.list {
display: flex;
flex-direction: column;
gap: 12px;
}
.item {
display: flex;
flex-direction: column;
gap: 2px;
}
.itemLabel {
font-size: 13px;
font-weight: 600;
color: var(--color-text);
}
.itemDesc {
font-size: 12px;
color: var(--color-text-secondary);
line-height: 1.5;
}
.footer {
border-top: 1px solid var(--color-gray-border);
padding-top: 20px;
text-align: center;
font-size: 12px;
color: var(--color-text-secondary);
display: flex;
flex-direction: column;
gap: 6px;
}
.link {
color: var(--color-text-secondary);
text-decoration: none;
}
.link:hover {
color: var(--color-text);
text-decoration: underline;
}
/* Responsive */
@media (max-width: 768px) {
.grid {
grid-template-columns: 1fr;
}
}
@@ -0,0 +1,60 @@
// src/pages/Journal/Journal.jsx
// Page Journal d'audit — historique des actions utilisateurs
import { useState, useEffect } from 'react';
import { getJournal } from '../../services/api';
import DataTable from '../../components/common/DataTable';
import LoadingSpinner from '../../components/common/LoadingSpinner';
import ErrorMessage from '../../components/common/ErrorMessage';
import styles from './Journal.module.css';
export default function Journal() {
const [entries, setEntries] = useState([]);
const [loading, setLoading] = useState(true);
const [erreur, setErreur] = useState('');
useEffect(() => {
async function load() {
setLoading(true);
setErreur('');
try {
const data = await getJournal();
setEntries(Array.isArray(data) ? data : []);
} catch (e) {
setErreur(e.message || 'Erreur lors du chargement du journal.');
} finally {
setLoading(false);
}
}
load();
}, []);
const columns = [
{ key: 'date_action', label: 'Date' },
{ key: 'utilisateur', label: 'Utilisateur' },
{ key: 'action', label: 'Action' },
{ key: 'detail', label: 'Détail' },
{ key: 'ip', label: 'Adresse IP' },
];
return (
<div className={styles.page}>
<h1 className={styles.title}>Journal d'audit</h1>
<p className={styles.description}>
Historique des actions effectuées sur la plateforme par les utilisateurs.
</p>
{loading ? (
<LoadingSpinner message="Chargement du journal..." />
) : erreur ? (
<ErrorMessage message={erreur} />
) : (
<DataTable
columns={columns}
data={entries}
pageSize={50}
emptyMessage="Aucune entrée dans le journal."
/>
)}
</div>
);
}
@@ -0,0 +1,18 @@
/* src/pages/Journal/Journal.module.css */
.page {
max-width: 1200px;
}
.title {
font-size: 22px;
font-weight: 700;
margin-bottom: 8px;
color: var(--color-text);
}
.description {
color: var(--color-text-secondary);
font-size: 13px;
margin-bottom: 24px;
}