import React, { useState, useEffect, useMemo } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInWithCustomToken, signInAnonymously, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, collection, doc, setDoc, onSnapshot, addDoc, deleteDoc, updateDoc } from 'firebase/firestore'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line } from 'recharts'; import { User, GraduationCap, TrendingUp, CheckCircle2, AlertCircle, BookOpen, Users, ChevronRight, ClipboardList, Plus, Trash2, Lock, LogOut, Upload, FileSpreadsheet, MessageSquare, Search, ArrowLeft, ThumbsUp, UserPlus } from 'lucide-react'; // --- Configuration Firebase --- const firebaseConfig = JSON.parse(__firebase_config); const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); const appId = typeof __app_id !== 'undefined' ? __app_id : 'pestel-eval-app'; const App = () => { const [user, setUser] = useState(null); const [role, setRole] = useState(null); const [currentStudent, setCurrentStudent] = useState(null); const [selectedStudentForTeacher, setSelectedStudentForTeacher] = useState(null); const [students, setStudents] = useState([]); const [allEvaluations, setAllEvaluations] = useState([]); const [activeStep, setActiveStep] = useState(0); const [newStudentName, setNewStudentName] = useState(""); const [newStudentClass, setNewStudentClass] = useState(""); const [loading, setLoading] = useState(true); const [importing, setImporting] = useState(false); const [searchQuery, setSearchQuery] = useState(""); // --- Chargement de SheetJS --- useEffect(() => { const script = document.createElement('script'); script.src = "https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"; script.async = true; document.body.appendChild(script); return () => { document.body.removeChild(script); }; }, []); // --- Initialisation Auth --- useEffect(() => { const initAuth = async () => { try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } } catch (err) { console.error("Auth error:", err); } }; initAuth(); const unsubscribe = onAuthStateChanged(auth, (u) => { setUser(u); setLoading(false); }); return () => unsubscribe(); }, []); // --- Données --- useEffect(() => { if (!user) return; const unsubStudents = onSnapshot(collection(db, 'artifacts', appId, 'public', 'data', 'students'), (snap) => { const list = snap.docs.map(doc => ({ id: doc.id, ...doc.data() })); list.sort((a, b) => (a.class || "").localeCompare(b.class || "") || a.name.localeCompare(b.name)); setStudents(list); }); const unsubEvals = onSnapshot(collection(db, 'artifacts', appId, 'public', 'data', 'evaluations'), (snap) => { setAllEvaluations(snap.docs.map(doc => ({ id: doc.id, ...doc.data() }))); }); return () => { unsubStudents(); unsubEvals(); }; }, [user]); // --- Logique Métier --- const skills = [ { id: 'ident', label: 'Identifier les facteurs du macro-environnement' }, { id: 'expl', label: 'Expliquer les composantes du modèle PESTEL' }, { id: 'impact', label: 'Analyser les impacts des facteurs sur l\'activité' }, { id: 'mobil', label: 'Mobiliser PESTEL dans une situation contextualisée' } ]; const phases = [ { id: 'diagnostique', label: 'Phase Diagnostique', desc: 'Rappel des notions' }, { id: 'formative', label: 'Phase Formative', desc: 'Exercices guidés' }, { id: 'sommative', label: 'Phase Sommative', desc: 'Cas pratique autonome' } ]; const handleUpdateEval = async (phaseId, skillId, value) => { if (!currentStudent || !user) return; const evalId = `${currentStudent.id}_${phaseId}`; const evalRef = doc(db, 'artifacts', appId, 'public', 'data', 'evaluations', evalId); const existing = allEvaluations.find(e => e.id === evalId) || { scores: {}, teacherFeedback: {} }; const newScores = { ...existing.scores, [skillId]: parseInt(value) }; await setDoc(evalRef, { ...existing, studentId: currentStudent.id, studentName: currentStudent.name, studentClass: currentStudent.class || "Non définie", phaseId, scores: newScores, updatedAt: Date.now() }); }; const handleTeacherFeedback = async (studentId, phaseId, skillId, comment) => { if (!user) return; const evalId = `${studentId}_${phaseId}`; const evalRef = doc(db, 'artifacts', appId, 'public', 'data', 'evaluations', evalId); const existing = allEvaluations.find(e => e.id === evalId); const teacherFeedback = existing?.teacherFeedback || {}; teacherFeedback[skillId] = comment; if (existing) { await updateDoc(evalRef, { teacherFeedback }); } else { await setDoc(evalRef, { studentId, phaseId, scores: {}, teacherFeedback, updatedAt: Date.now() }); } }; const handleAddStudent = async () => { if (!user || !newStudentName.trim()) return; try { await addDoc(collection(db, 'artifacts', appId, 'public', 'data', 'students'), { name: newStudentName.trim(), class: newStudentClass.trim() || "Non définie", createdAt: Date.now() }); setNewStudentName(""); setNewStudentClass(""); setSearchQuery(""); // Réinitialise la recherche pour voir le nouvel élève } catch (err) { console.error("Erreur lors de l'ajout manuel:", err); } }; const handleDeleteStudent = async (id) => { if (!user) return; if (confirm(`Voulez-vous vraiment supprimer cet élève ?`)) { await deleteDoc(doc(db, 'artifacts', appId, 'public', 'data', 'students', id)); } }; const handleFileUpload = (e) => { const file = e.target.files[0]; if (!file || !window.XLSX) return; setImporting(true); const reader = new FileReader(); reader.onload = async (evt) => { const bstr = evt.target.result; const wb = window.XLSX.read(bstr, { type: 'binary' }); const ws = wb.Sheets[wb.SheetNames[0]]; const data = window.XLSX.utils.sheet_to_json(ws); for (const row of data) { const nom = row.Nom || row.nom || ""; const prenom = row.Prénom || row.prenom || ""; const classe = row.Classe || row.classe || "Non définie"; if (nom || prenom) { await addDoc(collection(db, 'artifacts', appId, 'public', 'data', 'students'), { name: `${prenom} ${nom}`.trim(), class: String(classe), createdAt: Date.now() }); } } setImporting(false); e.target.value = null; }; reader.readAsBinaryString(file); }; const getProgressionForId = (studentId) => { return phases.map(p => { const evalData = allEvaluations.find(e => e.studentId === studentId && e.phaseId === p.id); const scores = evalData ? Object.values(evalData.scores) : []; const avg = scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / skills.length : 0; return { name: p.label.split(' ')[1], score: avg }; }); }; const filteredStudents = useMemo(() => { return students.filter(s => s.name.toLowerCase().includes(searchQuery.toLowerCase()) || s.class.toLowerCase().includes(searchQuery.toLowerCase()) ); }, [students, searchQuery]); if (loading) return
Chargement de l'application...
; // --- LOGIN --- if (!role) { return (

Eco-Eval PESTEL

Portail d'auto-évaluation pédagogique

); } // --- SÉLECTION ÉLÈVE --- if (role === 'student' && !currentStudent) { return (

Bonjour, qui êtes-vous ?

setSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-2 bg-slate-50 border border-slate-200 rounded-xl outline-none focus:ring-2 focus:ring-indigo-500" />
{filteredStudents.map(s => ( ))}
); } return (
{role === 'student' ? ( /* --- VUE ÉLÈVE --- */

Auto-évaluation

Analyser le macro-environnement de l'entreprise

{phases.map((p, idx) => ( ))}

{phases[activeStep].label}

{phases[activeStep].desc}

{skills.map(skill => { const currentEval = allEvaluations.find(e => e.studentId === currentStudent.id && e.phaseId === phases[activeStep].id); const value = currentEval?.scores?.[skill.id] || 0; const feedback = currentEval?.teacherFeedback?.[skill.id]; return (
= 2 ? 'bg-indigo-100 text-indigo-700' : 'bg-slate-100 text-slate-500' }`}> {['Non débuté', 'Débutant', 'En progrès', 'Acquis', 'Maîtrisé'][value]}
handleUpdateEval(phases[activeStep].id, skill.id, e.target.value)} className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-indigo-600" /> {feedback && (
Commentaire du professeur :

"{feedback}"

)}
); })}

Progression

Conseils

Surveille les bulles de commentaires oranges dans tes phases d'évaluation pour lire les conseils personnalisés de ton professeur.

) : ( /* --- VUE ENSEIGNANT --- */
{!selectedStudentForTeacher ? ( /* --- LISTE DES ÉLÈVES (PROF) --- */

Suivi Individualisé

Gérez vos élèves et validez leurs compétences

{students.length} Élèves
{/* FORMULAIRE D'AJOUT MANUEL (CORRIGÉ ET DÉPLACÉ) */}

Ajouter un élève manuellement

setNewStudentName(e.target.value)} className="flex-[2] bg-slate-50 border border-slate-200 rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500" /> setNewStudentClass(e.target.value)} className="flex-1 bg-slate-50 border border-slate-200 rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500" />
setSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-2 bg-slate-50 border border-slate-200 rounded-xl outline-none border-transparent focus:border-indigo-300" />
{filteredStudents.map(s => { const hasFeedback = allEvaluations.some(e => e.studentId === s.id && e.teacherFeedback && Object.keys(e.teacherFeedback).length > 0); const isComplete = allEvaluations.some(e => e.studentId === s.id && e.phaseId === 'sommative'); return (
setSelectedStudentForTeacher(s)} className="p-4 bg-slate-50 border border-slate-100 rounded-2xl hover:border-indigo-300 hover:shadow-md transition-all cursor-pointer relative group">

{s.name}

{s.class}
{hasFeedback && }
{isComplete ? "Validé" : "En cours"}
); })} {filteredStudents.length === 0 && (
Aucun élève ne correspond à votre recherche.
)}
) : ( /* --- DÉTAIL ÉLÈVE + VALIDATION (PROF) --- */
{selectedStudentForTeacher.name.charAt(0)}

{selectedStudentForTeacher.name}

Classe : {selectedStudentForTeacher.class}

{phases.map(phase => (

{phase.label}

{phase.desc}
{skills.map(skill => { const evalData = allEvaluations.find(e => e.studentId === selectedStudentForTeacher.id && e.phaseId === phase.id); const score = evalData?.scores?.[skill.id] || 0; const feedback = evalData?.teacherFeedback?.[skill.id] || ""; return (

{skill.label}

= 3 ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-500'}`}> Auto-éval : {score}/4