EPIDOCS — Frontend¶
Description¶
EPIDOCS est un système de gestion documentaire pour les étudiants d'EPITECH Bénin. Le frontend fournit deux interfaces distinctes : un portail étudiant (demande de documents, suivi, réclamations) et un dashboard administrateur (gestion des modèles, génération de documents, maintenance système).
- Repository : EpitechAfrik/epidocs-frontend
- URL production : https://mydocs.epitools.bj
- Port :
127.0.0.1:7012(hôte) →3000(conteneur) - Registry :
ghcr.io/epitechafrik/epidocs-frontend
Fonctionnalités principales¶
Portail étudiant¶
- Demande de documents : certificats de scolarité, lettres de recommandation, etc.
- Suivi de demande : timeline visuelle avec tracking par code unique
- Réclamations : soumission et suivi des réclamations
- Gestion de compte : sessions actives, demandes de modification d'informations
- Notes d'information : téléchargement de PDFs publiés par l'administration
- Annonces : bandeaux d'annonces ciblés par classe
- Avatar généré : identicon dynamique (boring-avatars) affiché dans la navbar étudiant
Dashboard administrateur¶
- Modèles de documents : upload, extraction d'attributs, activation/désactivation
- Gestion des étudiants : recherche, suspension, migration entre classes, validation du statut diplômé (PGE3/PGE5)
- Génération de documents : génération individuelle (wizard 3 étapes) et en lot par classe
- Notes d'information : upload de PDFs ciblés par classe, activation/désactivation, suivi des téléchargements
- Gestion des campus : création, édition, managers, templates par campus, champs personnalisés
- Statistiques : tableau de bord avec métriques d'utilisation
- Historique : consultation de l'historique documentaire par étudiant
Fonctionnalités superadmin¶
- Mode maintenance : activation/désactivation avec redirection automatique des étudiants
- Maintenance planifiée : programmation avec date de début/fin
- Annonces : création et gestion de bandeaux d'information
- Analytics : métriques détaillées par période
- Logs d'audit : traçabilité des actions administratives, présentée en timeline lisible groupée par jour
- Intégrations & clés API : gestion des comptes de service et des clés API pour les services externes (ex. Zeno)
- Sauvegarde : export/import de la base de données
Architecture¶
Stack technique¶
| Composant | Technologie | Version |
|---|---|---|
| Framework | Next.js (App Router) | 16.1.0 |
| Runtime | Node.js | 20 (Alpine) |
| UI | React | 19.0.0 |
| Composants | Chakra UI | 2.8.2 |
| Styling | Tailwind CSS | 3.4.1 |
| HTTP | Axios | 1.7.3 |
| Formulaires | React Hook Form | 7.62.0 |
| Data fetching | TanStack React Query | 5.90.21 |
| Animations | Framer Motion | 11.3.12 |
| Avatars | boring-avatars | 1.11.2 |
| Gestionnaire de paquets | pnpm | 10.18.3 |
| Conteneur | Docker (node:20-alpine) | — |
Structure des dossiers¶
src/
├── app/
│ ├── admin/ # Dashboard administrateur
│ │ ├── components/ # Navbar, Sidebar
│ │ ├── admins/ # Gestion des admins
│ │ ├── analytics/ # Analytics (superadmin)
│ │ ├── announcements/ # Annonces (superadmin)
│ │ ├── audit-logs/ # Logs d'audit (superadmin)
│ │ ├── backup/ # Sauvegarde BDD (superadmin)
│ │ ├── batch-generate/ # Génération en lot
│ │ ├── campus/ # Gestion des campus
│ │ ├── dashboard/ # Statistiques
│ │ ├── documents/ # Historique documents
│ │ ├── generate-document/ # Génération unitaire
│ │ ├── info-notes/ # Notes d'information (PDFs)
│ │ ├── info-requests/ # Demandes de modification
│ │ ├── integrations/ # Comptes de service & clés API (superadmin)
│ │ ├── migration/ # Migration étudiants
│ │ ├── models/ # Modèles de documents
│ │ ├── pending/ # Étudiants en attente
│ │ ├── students/ # Gestion étudiants
│ │ ├── system-status/ # Maintenance (superadmin)
│ │ └── layout.js
│ │
│ ├── student/ # Portail étudiant
│ │ ├── components/ # Navbar, Profile, Banners
│ │ ├── historiques/ # Historique documents
│ │ ├── mes-demandes/ # Demandes de modification
│ │ ├── reclamations/ # Réclamations
│ │ ├── sessions/ # Gestion sessions
│ │ └── layout.js
│ │
│ ├── auth/ # Authentification OAuth
│ ├── maintenance/ # Page maintenance
│ └── documents/tracking/ # Suivi public par code
│
├── services/ # Couche API (Axios)
│ ├── axios.js # Instance + intercepteurs
│ ├── auth/authServices.js # Auth (login, logout, sessions)
│ ├── admin/adminServices.js # Opérations admin
│ ├── admin/announcementService.js
│ ├── admin/infoNotesService.js
│ ├── admin/systemStatusService.js
│ ├── admin/integrationServices.js # Comptes de service & clés API
│ │ (campus CRUD via adminServices.js)
│ └── students/index.js # Opérations étudiant
│
├── context/ # React Context
│ ├── userContext.js # Auth + user state
│ └── sidebarContext.js # Sidebar state
│
├── middlewares/ # Helpers côté client
│ ├── protectedRoute.js # Route protégée par droits
│ └── isAuthorizedShow.js # Affichage conditionnel
│
├── components/
│ └── AppAvatar.js # Avatar généré (boring-avatars)
├── utils/ # Utilitaires (storage, dates)
├── constant/ # Constantes (couleurs)
└── proxy.js # Middleware Next.js (auth guard)
Rôles et permissions¶
Il y a trois rôles de base : superadmin, admin, student. Un superadmin a implicitement tous les droits (et accès aux pages « Système » réservées, cf. MENU_ITEMS dans sidebar.js). Un admin n'a accès qu'aux droits qui lui ont été explicitement accordés.
Depuis la refonte des droits (epidocs-api PR #25) : rôle ≠ accès
Avant cette évolution, un admin non-superadmin pouvait accéder à toutes les pages/actions admin par défaut (seul superadmin était vraiment restreint). Désormais, chaque page sensible et chaque bouton d'action vérifie un droit précis en plus du rôle. Un admin sans le droit correspondant reçoit un 403 (ou voit l'entrée de menu/le bouton disparaître). Voir Erreurs courantes pour le diagnostic « un admin perd l'accès après ce déploiement ».
Catalogue de droits dynamique (depuis le 2026-09-09)¶
Le système a longtemps reposé sur une liste hardcodée de 17 droits (RIGHTS_OPTIONS/RIGHT_GROUPS dans src/app/admin/admins/page.js) : chaque nouveau droit créé côté backend nécessitait un déploiement frontend rien que pour ajouter sa case à cocher. Ce n'est plus le cas : la page /admin/admins charge désormais le catalogue en direct au montage via adminServices.getRightsCatalogue() → GET /admin/rights.
[
{ "value": "can_scan_attendance", "label": "Scanner les présences", "description": "...", "group": "presence" },
{ "value": "can_manage_data_integrity", "label": "Gérer l'intégrité des données", "description": "...", "group": "reports" }
]
- Le backend possède 24 droits à la dernière vérification (2026-09-09) — la liste exacte n'a plus vocation à être recopiée ici,
GET /admin/rightsest la source de vérité vivante. Historique complet (labels/descriptions FR d'origine) : commitc4d7672du repoepidocs-frontend. - Groupes connus :
students,documents,uploads,campus,complaints,presence,reports, et un 8ᵉ groupeother— le fallback que le backend renvoie pour tout droit sans métadonnée assignée côté backend (voirapps/user/admin_views.py::RightListView, repoepidocs-api). - Le frontend ne possède que le mapping visuel par groupe (icône + label FR d'affichage), dans la constante
GROUP_METAdu même fichier :
group (backend) |
Label affiché | Icône (react-icons/fi) |
|---|---|---|
students |
Étudiants | FiUsers |
documents |
Documents | FiFileText |
uploads |
Imports | FiUploadCloud |
campus |
Campus | FiMapPin |
complaints |
Réclamations | FiMessageSquare |
presence |
Présence | FiCamera |
reports |
Rapports | FiBarChart2 |
other (ou tout groupe inconnu) |
(le nom brut du groupe) / « Autres » | FiShield |
Ajouter le support d'un nouveau groupe backend
Si le backend introduit un nouveau group (ex. "finance") sans mettre à jour GROUP_META, ce droit apparaît quand même dans l'UI (regroupé sous son id brut, avec une icône générique) — il n'est jamais silencieusement perdu, contrairement à l'ancien système. Pour lui donner un vrai libellé/icône : ajouter une entrée dans GROUP_META (constante en tête de src/app/admin/admins/page.js) et dans GROUP_ORDER si un ordre d'affichage précis est souhaité. Aucune migration ni redéploiement backend requis pour ce point.
États de chargement : pendant le fetch, le sélecteur de droits (RightsSelector) affiche un spinner ; en cas d'échec réseau, un message d'erreur explicite plutôt qu'une liste vide silencieuse.
Comment un droit est vérifié côté frontend — trois couches indépendantes, à ne jamais confondre :
- Rôle :
RoleGuarddanssrc/app/admin/layout.jsvérifie seulementrole === "admin" || role === "superadmin"— la porte d'entrée du dashboard admin, indépendante des droits fins. - Menu (cosmétique, PAS une protection) :
src/app/admin/components/sidebar.jsfiltreMENU_ITEMSvia le hookfilteredMenuItems— chaque item (ou section entière, ex. « Présence ») porte unrequiredRightoptionnel, comparé àuser.rights. Une section dont tous les items sont filtrés disparaît entièrement du menu. Ça ne bloque rien : ça masque juste le lien. - Page (la vraie protection) :
src/middlewares/protectedRoute.js(composantProtectedRoute) doit envelopper le contenu de chaquepage.jssensible et rediriger vers/unauthorizedsi aucun desrequiredRightsn'est présent.
Incident réel (corrigé le 2026-09-09) : 6 pages « Présence » sans protection au niveau page
Les 6 pages sous /admin/attendance/* (scan, guards, reasons, photos, qrcodes, logs) n'avaient aucun ProtectedRoute dans leur code — seule la sidebar cachait le lien via requiredRight. N'importe quel admin authentifié (même sans le droit can_manage_attendance) pouvait charger la page complète en tapant l'URL directement, contournant totalement le gate. Fixé en ajoutant <ProtectedRoute requiredRights={[...]}> à chacune (voir Présence Campus — Attendance).
Leçon pour toute nouvelle page admin : ajouter un requiredRight dans sidebar.js ne protège rien. Il faut systématiquement vérifier/ajouter le ProtectedRoute correspondant dans le page.js lui-même. Aucun outil ne détecte cet oubli automatiquement à ce jour (pas de test dédié) — à auditer manuellement si une nouvelle page semble accessible sans le droit attendu.
Les droits sont toujours assignés via les mêmes endpoints qu'avant (POST /users à la création, PUT /users à l'édition, champ rights: [...]) — seul le catalogue de valeurs possibles est désormais dynamique plutôt que figé dans le code frontend.
Fraîcheur de la session — GET /auth/me n'est appelé qu'une fois¶
src/context/userContext.js (UserProvider/initializeUser) n'appelle GET /auth/me qu'une seule fois, au premier montage de l'app (protégé par un useRef initialized) — jamais re-fetché sur une navigation interne (Next.js App Router, pas de rechargement de page) ni sur un focus fenêtre. Le résultat (dont rights[]) est aussi mis en cache dans localStorage pour un affichage immédiat au chargement suivant, avant confirmation par l'API.
Conséquence concrète : si un droit est ajouté à un admin (ou un nouveau droit créé côté backend) pendant que cet admin a déjà l'app ouverte dans un onglet, il ne le verra apparaître dans la sidebar/les pages qu'après un rechargement complet (F5) ou une reconnexion — jamais spontanément. Ce n'est pas un bug côté données (vérifié : la base a bien le droit assigné dès l'instant où il l'a été) — c'est uniquement une question de fraîcheur du cache client. Aucun impact de sécurité : les vrais appels API sont toujours réévalués côté backend indépendamment de ce que montre l'UI.
Piste d'amélioration non implémentée
Un refetch périodique de /auth/me (ou au retour de focus fenêtre) éliminerait ce décalage, mais n'a pas été jugé prioritaire — la solution de contournement (recharger la page) est simple et rare à devoir invoquer. À évaluer si ça devient une gêne récurrente.
Overrides de templates par classe (ClassDocumentType)¶
En plus des overrides de template par campus (existants), un type de document peut désormais avoir un template spécifique par classe/section (PGE1, PGE2, PGE3, PGE4, PGE5, PSO, CodingAcademy) — utile par exemple pour une lettre de recommandation dont le contenu diffère selon le cycle.
Ordre de résolution du template au moment de la génération (du plus prioritaire au moins prioritaire) :
- Override campus — si l'étudiant appartient à un campus qui a un template pour ce type de document
- Override classe — sinon, si sa classe a un template pour ce type de document
- Template global — sinon, le template par défaut du type de document
Debugging « pourquoi cet étudiant a reçu le mauvais template »
Toujours vérifier dans cet ordre : campus de l'étudiant → override campus existant ? puis classe de l'étudiant → override classe existant ? puis template global. Les onglets « Overrides campus » et « Overrides classe » du drawer /admin/models permettent de visualiser rapidement ce qui est défini à chaque niveau pour un type de document donné.
Endpoints (voir backend.md pour le détail) : GET/POST /documents/admin/classes/<classe>/templates, DELETE /documents/admin/classes/<classe>/templates/<doc_id>. Gated par can_manage_document_types pour la création/suppression (la consultation reste ouverte à tout admin, comme pour les overrides campus). Service frontend : getClassTemplates / uploadClassTemplate / deleteClassTemplate dans src/services/admin/adminServices.js.
Diagrammes¶
Architecture globale¶
graph TB
User[Utilisateur / Navigateur]
Nginx["Nginx<br/>Reverse proxy + TLS"]
Frontend["Frontend Next.js<br/>:3000"]
Backend["Backend Django<br/>API REST"]
DB["Base de données"]
Azure["Microsoft Azure AD<br/>(OAuth SSO)"]
User --> Nginx
Nginx --> Frontend
Frontend --> Backend
Backend --> DB
Frontend --> Azure
Backend --> Azure
Flux d'authentification¶
sequenceDiagram
participant User as Utilisateur
participant FE as Frontend Next.js
participant Azure as Microsoft OAuth
participant BE as Backend Django
participant Cookie as Cookie auth_token
User->>FE: Clic "Connexion Microsoft"
FE->>Azure: Redirect OAuth
Azure->>FE: Callback /auth/callback?code=XXX
FE->>BE: POST /auth/sign_in {code, session_state}
BE->>Azure: Validation du token
BE->>Cookie: Set auth_token (HttpOnly)
BE-->>FE: {user, role, rights}
FE->>FE: Stockage user dans localStorage
FE->>FE: Redirection selon rôle (admin/student)
Flux de génération de document¶
sequenceDiagram
participant Admin as Admin (Frontend)
participant API as Backend API
participant PDF as Moteur PDF
Admin->>API: GET /documents/available-document-types?studentEmail=xxx
API-->>Admin: {documents: {allowed_documents: [...]}}
Admin->>API: POST /documents/admin/generate-for-student<br/>{studentEmail, docId}
API->>PDF: Génération du PDF
PDF-->>API: Blob PDF
API-->>Admin: Content-Type: application/pdf
Admin->>Admin: Téléchargement automatique
Flux de génération en lot¶
sequenceDiagram
participant Admin as Superadmin
participant API as Backend API
participant PDF as Moteur PDF
Admin->>API: POST /documents/admin/batch-generate<br/>{classe: "PGE1", doc_id: "certificat_scolarite"}
API->>PDF: Génération N PDFs
PDF-->>API: ZIP contenant tous les PDFs
API-->>Admin: Content-Type: application/zip<br/>X-Batch-Total: 45<br/>X-Batch-Generated: 43<br/>X-Batch-Errors: 2
Admin->>Admin: Téléchargement ZIP + résumé
Flux des notes d'information¶
sequenceDiagram
participant Admin as Admin (Frontend)
participant API as Backend API
participant Student as Étudiant (Frontend)
Admin->>API: POST /documents/admin/info-notes<br/>(multipart: title, pdf_file, target_classes)
API-->>Admin: {id, title, targetClasses, isActive}
Student->>API: GET /documents/available-document-types
API-->>Student: {documents: {...}, infoNotes: [{id, title, ...}]}
Student->>API: GET /documents/info-notes/:id/download
API-->>Student: Content-Type: application/pdf (blob)
Student->>Student: Téléchargement automatique
Docker¶
Dockerfile¶
Image multi-stage basée sur node:20-alpine avec pnpm. Les variables NEXT_PUBLIC_* sont injectées au build via ARG.
- Stage builder : installation des dépendances + build Next.js
- Stage runner : copie du build optimisé, exécution en production
docker-compose.prod.yml¶
services:
frontend:
image: ghcr.io/epitechafrik/epidocs-frontend:latest
container_name: epidocs_frontend_container
restart: unless-stopped
ports:
- "127.0.0.1:7012:3000"
env_file:
- .env
environment:
- NODE_ENV=production
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
- NEXT_PUBLIC_WEB_URL=${NEXT_PUBLIC_WEB_URL}
networks:
- epidocs-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
epidocs-network:
driver: bridge
Points importants :
- Le port est bindé sur
127.0.0.1uniquement (accessible via Nginx seulement) - Le healthcheck utilise
wget(pas decurlsur Alpine par défaut) - Logging limité à 30 Mo (3 fichiers de 10 Mo)
- Réseau bridge dédié
epidocs-network
Routes de l'application¶
Routes publiques (sans authentification)¶
| Route | Description |
|---|---|
/auth |
Page de connexion Microsoft OAuth |
/auth/callback |
Callback OAuth |
/noaccount |
Page "aucun compte" |
/register |
Inscription étudiant en attente |
/desactivated |
Compte désactivé |
/documents/tracking/:id |
Suivi public d'un document |
/maintenance |
Page de maintenance |
/unauthorized |
Accès non autorisé |
Routes admin (role: admin ou superadmin)¶
| Route | Description |
|---|---|
/admin |
Dashboard principal |
/admin/models |
Gestion des modèles de documents (templates globaux, overrides campus, overrides classe) |
/admin/informations |
Clés {{...}} disponibles pour les modèles de documents — voir Modèles de documents |
/admin/students |
Gestion des étudiants |
/admin/pending |
Validation des inscriptions |
/admin/migration |
Migration de classe |
/admin/info-requests |
Demandes de modification |
/admin/dashboard |
Statistiques |
/admin/documents |
Historique documentaire |
/admin/admins |
Gestion des admins |
/admin/generate-document |
Génération unitaire |
/admin/batch-generate |
Génération en lot (superadmin) |
/admin/campus |
Gestion des campus |
/admin/info-notes |
Notes d'information (PDFs) |
/admin/system-status |
Mode maintenance (superadmin) |
/admin/announcements |
Annonces (superadmin) |
/admin/analytics |
Analytics (superadmin) |
/admin/audit-logs |
Logs d'audit (superadmin) |
/admin/integrations |
Comptes de service & clés API (superadmin) |
/admin/backup |
Sauvegarde BDD (superadmin) |
/admin/data-integrity |
Intégrité des données (can_manage_data_integrity) — voir ci-dessous |
/admin/attendance/scan |
Console de scan admin (can_scan_attendance) — voir Présence Campus |
/admin/attendance/guards |
Agents & chaperons (can_manage_attendance) |
/admin/attendance/reasons |
Motifs de suspension présence (can_manage_attendance) |
/admin/attendance/photos |
Import photos étudiants (can_manage_attendance) |
/admin/attendance/qrcodes |
Envoi de QR codes (can_manage_attendance) |
/admin/attendance/logs |
Historique des scans — vue d'ensemble (can_manage_attendance) |
/admin/attendance/scanners |
Suivi par scanneur — détail par agent/chaperon (can_manage_attendance) |
Routes présence publiques (agent/chaperon, cookie attendance_session, hors /admin)¶
| Route | Description |
|---|---|
/attendance |
Écran de connexion par code PIN |
/attendance/scan |
Console de scan agent/chaperon (même composant que /admin/attendance/scan, voir Présence Campus) |
Routes étudiant (role: student)¶
| Route | Description |
|---|---|
/student |
Page principale + profil |
/student/historiques |
Historique des demandes |
/student/mes-demandes |
Demandes de modification |
/student/reclamations |
Réclamations |
/student/sessions |
Sessions actives |
Fonctionnalités détaillées¶
Portail étudiant — redesign UI¶
Le portail étudiant a été entièrement redesigné (commit 2cfe100). Points notables :
- Split-screen : profil à gauche, contenu principal à droite (layout existant conservé)
- DocumentCard : refonte complète du composant de demande de document — meilleure hiérarchie visuelle, états de chargement, gestion des erreurs inline
- Navbar : intègre désormais un
AppAvatar(boring-avatars, variantbeam) généré à partir du nom de l'utilisateur — pas d'image externe - Historique (
/student/historiques) : les documents récents des classes précédentes sont clairement séparés des demandes actives - Modal de génération : stabilisée — les états de chargement et les erreurs sont correctement gérés sans rechargement intempestif
Migration et validation du statut diplômé (/admin/migration)¶
La page migration permet deux opérations distinctes sur une sélection d'étudiants :
1. Migration de classe¶
Déplace les étudiants sélectionnés vers une autre section (ex. PGE1 → PGE2) avec choix de l'année scolaire.
2. Validation du statut diplômé¶
Marque les étudiants PGE3 ou PGE5 comme diplômés. Une fois diplômé, l'étudiant voit une Attestation de scolarité à la place du Certificat de scolarité.
Raccourcis de sélection rapide
Des boutons "TEK3 (PGE3)" et "MSc Pro 2 (PGE5)" permettent de sélectionner tous les étudiants d'une section en une action (requête avec pageSize=9999).
Détection automatique des groupes
Lors de l'ouverture du modal, le frontend analyse la sélection et constitue deux groupes à partir du champ section de chaque étudiant. Si la sélection est mixte, le modal affiche le résumé par groupe :
12 étudiant(s) seront marqués diplômés en PGE3 (TEK 3)
8 étudiant(s) seront marqués diplômés en PGE5 (MSc Pro 2)
Requêtes parallèles
Deux appels sont effectués en parallèle (Promise.all) — un par groupe — vers POST /student/admin/set-graduation-status/ avec le champ section approprié. Les résultats sont fusionnés et affichés dans une vue unifiée.
Colonne "Statut diplôme" dans le tableau
- Badge vert
PGE3 ✓siPGE3Status === "success" - Badge vert
PGE5 ✓siPGE5Status === "success" - Tiret
—si les deux statuts sont absents, vides ou null
Nommage des champs camelCase — deux endpoints différents
Les deux endpoints ne retournent pas les mêmes noms de champs :
GET /student/(fiche étudiant) →pge3Status,pge5Status(tout en minuscules)GET /student/admin/list/(migration) →PGE3Status,PGE5Status(majuscules préservées)
Adapter le nom du champ selon l'endpoint utilisé dans le composant.
Reset
Un bouton reset (icône ✕, rouge) apparaît dès qu'un filtre ou une sélection est actif.
sequenceDiagram
participant Admin as Admin (Frontend)
participant API as Backend API
Admin->>Admin: Clique "TEK3 (PGE3)"
Admin->>API: GET /student/admin/list/?section=PGE3&pageSize=9999
API-->>Admin: {results: [...tous les PGE3]}
Admin->>Admin: Sélectionne tous les logins
Admin->>Admin: Clique "Valider diplômé"
Admin->>Admin: Groupe les logins par section (PGE3 / PGE5)
par Appels parallèles
Admin->>API: POST /student/admin/set-graduation-status/ {emails: [...], section: "PGE3"}
Admin->>API: POST /student/admin/set-graduation-status/ {emails: [...], section: "PGE5"}
end
API-->>Admin: {updated, not_found_emails, wrong_section_emails}
Admin->>Admin: Fusionne résultats et affiche le rapport
Gestion des campus (/admin/campus)¶
La page campus permet de gérer les établissements EPITECH rattachés à la plateforme.
Tableau de bord campus¶
Liste tous les campus avec : code, nom, ville, pays, directeur et statut (actif/inactif).
Créer un campus (superadmin uniquement)¶
Champs obligatoires : Code (ex. BEN) et Nom. Champs optionnels : ville, pays, adresse, téléphone, email, directeur (nom + titre FR/EN).
Drawer de détail — 3 onglets¶
Onglet Infos
- Formulaire d'édition des informations du campus
- Champs personnalisés (
extraFields) : paires clé/valeur libres utilisables dans les templates .docx via{{campus_extra_<clé>}}(ex.{{campus_extra_ape}}) - Bouton "Désactiver" (superadmin uniquement, action réversible)
Onglet Managers
Assigne des admins existants comme managers d'un campus. Un manager est un admin avec responsabilité spécifique sur ce campus.
Onglet Templates
Permet d'uploader des overrides de templates par campus : si un template campus existe pour un type de document, il est utilisé à la place du template global. Supporte les modèles FR et EN (.docx).
sequenceDiagram
participant Admin as Admin (Frontend)
participant API as Backend API
Admin->>API: GET /campus/
API-->>Admin: [{id, code, name, city, isActive, ...}]
Admin->>Admin: Clique sur un campus
par Chargement parallèle
Admin->>API: GET /campus/:id/managers/
Admin->>API: GET /campus/:id/templates/
Admin->>API: GET /admin/admins/?limit=100
end
API-->>Admin: managers, templates, admins
Admin->>API: POST /campus/:id/templates/ (multipart: document_type, modele, modele_en)
API-->>Admin: template créé/mis à jour
Nommage camelCase vs snake_case
L'API campus retourne du camelCase (directorName, isActive, extraFields). Le service frontend normalise automatiquement — ne pas utiliser snake_case pour ces champs.
Gestion des admins (/admin/admins)¶
La page a été entièrement redesignée pour rendre le système de droits (voir Rôles et permissions) réellement lisible — l'ancienne version affichait un badge coloré distinct par droit accordé (jusqu'à 17 badges par ligne, à l'époque du système hardcodé) et une modale react-select à plat, jugées surchargées et peu scannables. react-select n'est plus utilisé dans ce fichier (il reste utilisé ailleurs dans l'app pour d'autres multi-sélections, sans lien avec ce changement). Depuis le 2026-09-09, en plus de ce redesign, le catalogue de droits lui-même est chargé dynamiquement (voir plus haut) au lieu d'être une liste figée dans ce fichier.
Liste des admins — PermissionsSummary
Au lieu d'un badge par droit, chaque admin affiche un chip neutre par domaine où il a au moins un droit accordé, avec le compte accordé/total (ex. « Étudiants 3/8 ») et une infobulle listant les droits exacts accordés dans ce domaine. Un superadmin affiche un unique badge rouge « Toutes les permissions » à la place (cosmétique uniquement — voir l'avertissement plus bas sur ce que ça garantit réellement).
Modales Ajouter / Modifier — RightsSelector
L'attribution des droits se fait via un accordéon replié par défaut, un panneau par domaine (les groupes renvoyés par GET /admin/rights, voir plus haut) :
- Chaque en-tête de panneau affiche une pastille de comptage (ex.
3/8) et se déplie/replie indépendamment - À l'édition d'un admin existant, les domaines où il a déjà au moins un droit s'ouvrent automatiquement (
computeDefaultOpenIndexes) - Chaque droit est une case à cocher avec son libellé et sa description, pas seulement son nom technique
sequenceDiagram
participant Admin as Superadmin (Frontend)
participant API as Backend API
Admin->>API: GET /users/list?page=1&limit=10
API-->>Admin: {users: [{email, rights: [...], isSuperadmin, isActive}], total}
Admin->>Admin: Regroupe rights[] par domaine → PermissionsSummary
Admin->>Admin: Clique "Modifier" sur un admin
Admin->>Admin: RightsSelector — ouvre les domaines avec droits existants
Admin->>Admin: Coche/décoche des droits
Admin->>API: PUT /users {email, rights: [...], isActive}
API-->>Admin: 200 OK
Intégrations & clés API (/admin/integrations)¶
Écran réservé aux superadmins permettant de gérer les comptes de service et les clés API utilisés par les services externes (ex. Zeno) pour s'authentifier auprès d'EPIDOCS sans session/cookie. L'entrée du menu est masquée pour les non-superadmins, et la page applique un garde de rôle (403 côté API).
Base des routes : <API>/integrations/v1 (JSON camelCase, session admin via cookie JWT). Service frontend : src/services/admin/integrationServices.js.
Comptes de service¶
Liste des comptes avec leur statut (actif/inactif), leurs permissions (badges), le nombre de clés actives et une sous-liste dépliable des clés (préfixe, statut, dernière utilisation). Actions disponibles :
- Créer un compte (génère automatiquement une 1ʳᵉ clé) — avec choix des scopes
- Renommer / éditer (nom, description, statut actif, scopes)
- Activer / désactiver — désactiver coupe immédiatement l'accès de toutes les clés du compte
- Supprimer (irréversible — supprime le compte et toutes ses clés, avec confirmation)
Permissions (scopes)
Chaque compte porte des scopes définissant ses droits : read (« Lecture ») et/ou generate (« Génération »). Ils se choisissent à la création et à l'édition via des cases à cocher (défaut ["read", "generate"], au moins une requise). Le champ scopes est envoyé dans le POST de création et le PATCH d'édition ; une modification prend effet immédiatement sur toutes les clés du compte. Les scopes sont affichés en badges dans la liste des comptes.
Clés API¶
Pour chaque clé : émettre une nouvelle clé, régénérer (rotate) — révoque l'ancienne et en émet une nouvelle —, ou révoquer (confirmation, coupe l'accès immédiatement).
La clé brute n'est affichée qu'une seule fois
La clé en clair (apiKey) n'est renvoyée que par les réponses de création de compte, émission de clé et rotation. Le backend ne stocke que le hash, et les GET ne renvoient jamais la clé brute (seulement le prefix — 8 premiers caractères — pour l'identifier).
Après ces 3 actions, le frontend affiche la clé dans une modale « Copier » avec l'avertissement « Cette clé ne sera plus jamais affichée ». La fermeture sur l'overlay est désactivée pour éviter une perte accidentelle.
| Bouton / action UI | Appel |
|---|---|
| Charger l'écran | GET /integrations/v1/admin/service-accounts |
| Nouveau service | POST /integrations/v1/admin/service-accounts → modale clé |
| Activer / Désactiver / Renommer | PATCH /integrations/v1/admin/service-accounts/{id} |
| Supprimer le service | DELETE /integrations/v1/admin/service-accounts/{id} |
| Générer une clé | POST /integrations/v1/admin/service-accounts/{id}/keys → modale clé |
| Régénérer (rotate) | POST /integrations/v1/admin/keys/{keyId}/rotate → modale clé |
| Révoquer | POST /integrations/v1/admin/keys/{keyId}/revoke |
Logs d'audit (/admin/audit-logs)¶
Écran réservé aux superadmins retraçant toutes les actions sur la plateforme. L'affichage est une timeline lisible groupée par jour (et non un tableau de données brutes) :
- Humanisation des valeurs : chaque action a un libellé FR, une couleur et une icône ; les actions non mappées (snake_case) sont converties automatiquement en texte lisible. Les rôles et les cibles (
type / id→ « Étudiant #12 ») sont également formatés. - Phrase lisible par entrée : email de l'acteur + badge de rôle, puis « a généré un document · Étudiant #12 ».
- Temps relatif (« il y a 2 h ») avec la date complète au survol ; entrées regroupées sous des en-têtes « Aujourd'hui » / « Hier » / date complète.
- Vue détail : un clic sur une entrée ouvre un drawer latéral avec tous les champs formatés (utilisateur, rôle, cible, date absolue + relative, IP, identifiant, et un bloc JSON
details/metadatasi l'API en renvoie). - Filtres : recherche par email d'acteur (avec debounce) et sélection par type d'action ; pagination (25 par page).
Fiche étudiant (/admin/students)¶
Le panneau de détail affiche toujours le champ Statut diplôme (endpoint /student/ → pge3Status) :
- Badge
PGE3 diplômé ✓sipge3Status === "success" - Badge
PGE5 diplômé ✓sipge5Status === "success" - Tiret
—si aucun diplôme validé
Page modèles (/admin/models)¶
Spinner de chargement initial pendant la récupération des modèles, évitant le flash "Aucun document trouvé".
Le drawer de détail d'un type de document a 3 onglets : « Templates globaux », « Overrides campus », et « Overrides classe » (voir Overrides de templates par classe). Les 3 onglets suivent le même schéma : liste des overrides existants avec badges de statut FR/EN, zone d'upload (sélecteur + fichiers FR/EN), bouton de suppression. La consultation est ouverte à tout admin ; la création/suppression d'un override nécessite can_manage_document_types.
Quelles clés {{...}} utiliser dans un modèle .docx, comment en ajouter une nouvelle, et ce qui se passe (ou pas) à un remplacement de modèle : voir Modèles de documents — clés dynamiques.
Intégrité des données (/admin/data-integrity)¶
Page générique consommant le registre DataCheck du backend (voir backend.md et operations-manuelles.md du repo epidocs-api pour le détail du framework et comment ajouter un nouveau check). Droit : can_manage_data_integrity.
GET /admin/data-checks→ une ligne par check enregistré, affichée en alerte (orange sicount > 0, vert sinon) avec libellé, description et compteur.- Un clic déplie le détail paginé (
GET /admin/data-checks/<check_id>?page=&limit=) : le frontend ne connaît pas à l'avance les colonnes — elles sont dérivées dynamiquement des clés du premier enregistrement renvoyé (Object.keys(results[0])), donc un nouveau check ajouté côté backend s'affiche automatiquement sans changement frontend, quelle que soit la forme de ses données. - Bouton « Corriger » par ligne (
POST .../fixavec{ids: [pk]}) et « Tout corriger » par check (POST .../fixsans body, avec confirmation modale). - L'identifiant de chaque ligne (le champ à envoyer dans
ids) est lu depuisidField, renvoyé par l'API à la fois sur la liste (GET /admin/data-checks, par check) et sur le détail (GET /admin/data-checks/<check_id>, au niveau racine) — ne jamais deviner ce champ (ex. supposerloginouid) : un check futur peut avoir une autre clé primaire.
Service : src/services/admin/dataIntegrityService.js (getDataChecks, getDataCheckDetail, fixDataCheck). Page : src/app/admin/data-integrity/page.js.
Édition directe des informations étudiant (/admin/students)¶
Bouton « Modifier les informations » dans le drawer de détail d'un étudiant (visible seulement avec le droit can_edit_student_info), distinct des demandes de modification initiées par l'étudiant lui-même (can_review_info_changes / /admin/info-requests) — celui-ci est pour un admin qui corrige un typo directement, sans passer par une demande.
- Formulaire avec les champs éditables (
id_etudiant,civilite,nom,prenom,ville,pays,date_naissance,ville_naissance,ville_etude,nationalite,prenom_mere,nom_mere,prenom_pere,nom_pere,specialite) — un champ laissé vide n'est pas envoyé (seuls les champs réellement modifiés par rapport à leur valeur initiale sont inclus dans lePATCH), pour ne jamais écraser silencieusement un champ que l'admin n'a pas touché (plusieurs de ces champs — nationalité, parents — ne sont de toute façon pas pré-remplis aujourd'hui, l'endpointGET /studentsne les renvoyant pas encore). PATCH /student/admin/<login>/editrenvoiegeneratedDocumentsCount: si > 0, une bannière d'avertissement apparaît dans le drawer, pointant vers la page « Historique des documents » (can_view_document_history) et rappelant la possibilité de supprimer un document pour forcer sa régénération (can_delete_documents) — sans nouvelle plomberie, juste un lien vers ces deux pages déjà existantes.
Service : updateStudentInfo dans src/services/admin/adminServices.js.
Intercepteurs Axios¶
L'instance Axios (src/services/axios.js) gère automatiquement :
- Requêtes :
Content-Type: application/jsonpar défaut,withCredentials: true - Erreur 401 : redirection vers
/auth(sauf si déjà sur la page auth) - Erreur 503 : mode maintenance — redirige les étudiants vers
/maintenance, les admins/superadmins ne sont pas redirigés
Variables d'environnement¶
| Variable | Description | Exemple |
|---|---|---|
NEXT_PUBLIC_WEB_URL |
URL publique du frontend | https://mydocs.epitools.bj |
NEXT_PUBLIC_API_URL |
URL publique du backend API | https://api-mydocs.epitools.bj |
NODE_ENV |
Mode d'exécution | production |
Variables NEXT_PUBLIC_*
Les variables préfixées par NEXT_PUBLIC_ sont exposées au navigateur. Ne jamais y mettre de secrets. Ces variables sont injectées au build time (pas au runtime), donc un changement nécessite un rebuild de l'image Docker.
Pipeline CI/CD¶
Déclencheurs¶
- Push sur
mainoudevelop - Pull request vers
main - Dispatch manuel
Jobs¶
graph LR
CI["CI<br/>Lint + Build"] --> Docker["Build Docker<br/>Push GHCR"]
Docker --> Deploy["Déploiement<br/>SCP + SSH"]
Deploy --> Health["Health Check"]
- CI : checkout → setup pnpm → install → lint → build → upload artifact
- Build Docker : build image multi-stage → push
ghcr.io/epitechafrik/epidocs-frontend:latest - Deploy : SCP du
docker-compose.prod.yml+.env→ SSH pour pull et restart conteneur - Health check : vérification que le conteneur répond sur le port 3000
Secrets GitHub nécessaires¶
| Secret | Description |
|---|---|
NEXT_PUBLIC_API_URL |
URL du backend |
NEXT_PUBLIC_WEB_URL |
URL du frontend |
SSH_PRIVATE_KEY |
Clé SSH pour accès au VPS |
VPS_IP |
Adresse IP du VPS |
VPS_USER |
Utilisateur SSH (root) |
ENV_FILE |
Contenu complet du fichier .env |
GITHUB_TOKEN |
Token GHCR (auto-fourni) |
Rollback¶
Procédure¶
# 1. Lister les images disponibles
docker images ghcr.io/epitechafrik/epidocs-frontend --format "table {{.Tag}}\t{{.CreatedAt}}"
# 2. Modifier le tag dans docker-compose.prod.yml
# image: ghcr.io/epitechafrik/epidocs-frontend:sha-XXXXXXX
# 3. Redéployer
cd /root/projects/epidocs/epidocs-frontend
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d
# 4. Vérifier
docker ps | grep epidocs_frontend
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:7012
Après le rollback¶
- Investiguer la cause sur
main - Pousser un correctif
- Le pipeline redéploiera automatiquement