From 7c3e04cf060cc17901a8520742ead7476abf4ba6 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Wed, 6 May 2026 18:45:23 +0200 Subject: [PATCH] Admin cockpit --- frontend/src/App.jsx | 447 ++++++++++++++++++++++++++++++++++++++++ frontend/src/styles.css | 360 ++++++++++++++++++++++++++++++++ 2 files changed, 807 insertions(+) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 123014d..75ec53f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -296,6 +296,7 @@ function TutorApp({ currentUser, onLogout }) { const [programTree, setProgramTree] = useState(null) const [selectedProgramNodeKey, setSelectedProgramNodeKey] = useState('') const [programReviewForm, setProgramReviewForm] = useState({ status: 'needs_changes', comment: '' }) + const [activeAdminTab, setActiveAdminTab] = useState('overview') const [studentProgram, setStudentProgram] = useState(null) const [activeAssetIndex, setActiveAssetIndex] = useState(0) const [activeSectionIndex, setActiveSectionIndex] = useState(0) @@ -1682,6 +1683,452 @@ function TutorApp({ currentUser, onLogout }) { ) } + if (!isStudentUser) { + const validatedCards = cardProgress.filter((item) => item.validated).length + const averageComprehension = cardProgress.length + ? Math.round(cardProgress.reduce((sum, item) => sum + item.comprehension_score, 0) / cardProgress.length) + : 0 + const reviewRows = programRows.filter(({ node }) => node.type === 'lesson' || node.type === 'fiche' || node.type === 'card') + const reviewQueue = reviewRows.filter(({ node }) => ['needs_changes', 'blocked', 'draft', undefined].includes(node.review?.status)) + const selectedNodeStatus = selectedProgramNode?.status || {} + const totalLessonCards = studentProgram?.assets?.reduce((sum, asset) => sum + (asset.sections?.length || 0), 0) || 0 + const remainingCards = Math.max(totalLessonCards - validatedCards, 0) + const adminTabs = [ + { id: 'overview', label: 'Vue d’ensemble' }, + { id: 'students', label: 'Élèves' }, + { id: 'program', label: 'Programme global' }, + { id: 'validation', label: 'Validation contenus' }, + { id: 'factory', label: 'Atelier génération' }, + { id: 'settings', label: 'Paramètres globaux' }, + ] + + return ( +
+
+
+

Poste de pilotage

+

Professeur TOP

+

Suivi des élèves, validation des contenus et pilotage du programme.

+
+
+ {currentUser.username} · {currentUser.role} + +
+
+ + + +
+ {activeAdminTab === 'overview' && ( + <> +
+
+ Élèves actifs + {students.length} +

{selectedStudent ? `${selectedStudent.first_name} sélectionné` : 'Aucun élève sélectionné'}

+
+
+ Leçon active + {studentProgram?.title || 'À affecter'} +

{studentProgram ? `${studentProgram.asset_count} fiche(s) · ${totalLessonCards} cards` : 'Passe par l’onglet Élèves'}

+
+
+ Compréhension + {averageComprehension}% +

{validatedCards} card(s) validée(s), {remainingCards} restante(s)

+
+
+ File de revue + {reviewQueue.length} +

Contenus à relire, modifier ou intégrer

+
+
+ +
+
+
+
+ Priorités +

À traiter maintenant

+
+
+
+ + + +
+
+ +
+
+
+ Production +

État du programme

+
+ +
+
+ Leçons: {programStatus?.lesson_count ?? 0} + CM1: {programStatus?.lessons_by_grade?.CM1 ?? 0} + Dossier: {programStatus?.content_root_exists === false ? 'absent' : 'OK'} +
+
+
+ + )} + + {activeAdminTab === 'students' && ( +
+ + +
+ {selectedStudent ? ( + <> +
+
+ Fiche élève +

{selectedStudent.first_name}

+

{selectedStudent.grade} · {selectedStudent.age} ans

+
+
+ +
+
+ Leçon courante + {studentProgram?.title || 'Aucune leçon'} +

{studentProgram ? `${studentProgram.subject} · ${studentProgram.asset_count} fiche(s)` : 'Affecte une leçon ci-dessous.'}

+
+
+ Résultats + {averageComprehension}% +

{cardProgress.length} card(s) observée(s)

+
+
+ Contenu fait + {validatedCards} +

{remainingCards} card(s) encore à travailler

+
+
+ +
+ + {studentProgram?.assets?.length > 0 && ( + <> + + + + )} + +