mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Sauvegarde des compétences d'un élève [#16]
This commit is contained in:
@ -8,11 +8,15 @@ import WorkPlan from '@/components/Grades/WorkPlan';
|
||||
import Homeworks from '@/components/Grades/Homeworks';
|
||||
import SpecificEvaluations from '@/components/Grades/SpecificEvaluations';
|
||||
import Orientation from '@/components/Grades/Orientation';
|
||||
import GradesStatsCircle from '@/components/Grades/GradesStatsCircle';
|
||||
import Button from '@/components/Button';
|
||||
import logger from '@/utils/logger';
|
||||
import { FE_ADMIN_GRADES_STUDENT_COMPETENCIES_URL } from '@/utils/Url';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { fetchStudents } from '@/app/actions/subscriptionAction';
|
||||
|
||||
import {
|
||||
fetchStudents,
|
||||
fetchStudentCompetencies,
|
||||
} from '@/app/actions/subscriptionAction';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import { useClasses } from '@/context/ClassesContext';
|
||||
|
||||
@ -25,6 +29,8 @@ export default function Page() {
|
||||
});
|
||||
|
||||
const [students, setStudents] = useState([]);
|
||||
const [studentCompetencies, setStudentCompetencies] = useState(null);
|
||||
const [grades, setGrades] = useState({});
|
||||
|
||||
const academicResults = [
|
||||
{
|
||||
@ -106,6 +112,34 @@ export default function Page() {
|
||||
}
|
||||
}, [selectedEstablishmentId]);
|
||||
|
||||
// Charger les compétences et générer les grades à chaque changement d'élève sélectionné
|
||||
useEffect(() => {
|
||||
if (formData.selectedStudent) {
|
||||
fetchStudentCompetencies(formData.selectedStudent)
|
||||
.then((data) => {
|
||||
setStudentCompetencies(data);
|
||||
// Générer les grades à partir du retour API
|
||||
if (data && data.data) {
|
||||
const initialGrades = {};
|
||||
data.data.forEach((domaine) => {
|
||||
domaine.categories.forEach((cat) => {
|
||||
cat.competences.forEach((comp) => {
|
||||
initialGrades[comp.competence_id] = comp.score ?? 0;
|
||||
});
|
||||
});
|
||||
});
|
||||
setGrades(initialGrades);
|
||||
}
|
||||
})
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching studentCompetencies:', error)
|
||||
);
|
||||
} else {
|
||||
setGrades({});
|
||||
setStudentCompetencies(null);
|
||||
}
|
||||
}, [formData.selectedStudent]);
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-8">
|
||||
{/* Sélection de l'élève */}
|
||||
@ -145,13 +179,14 @@ export default function Page() {
|
||||
|
||||
{formData.selectedStudent && (
|
||||
<>
|
||||
<AcademicResults results={academicResults} />
|
||||
{/* <AcademicResults results={academicResults} /> */}
|
||||
<Attendance absences={absences} />
|
||||
<Remarks remarks={remarks} />
|
||||
<GradesStatsCircle grades={grades} />
|
||||
{/* <Remarks remarks={remarks} />
|
||||
<WorkPlan workPlan={workPlan} />
|
||||
<Homeworks homeworks={homeworks} />
|
||||
<SpecificEvaluations specificEvaluations={specificEvaluations} />
|
||||
<Orientation orientation={orientation} />
|
||||
<Orientation orientation={orientation} /> */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -11,18 +11,13 @@ import {
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import { Award } from 'lucide-react';
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
|
||||
// À remplacer par un fetch réel des compétences selon l'élève
|
||||
const mockCompetencies = [
|
||||
{ id: 1, name: 'Lire un texte court', score: null },
|
||||
{ id: 2, name: 'Résoudre un problème simple', score: null },
|
||||
{ id: 3, name: 'Exprimer une idée à l’oral', score: null },
|
||||
];
|
||||
import { useNotification } from '@/context/NotificationContext';
|
||||
|
||||
export default function StudentCompetenciesPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken();
|
||||
const { showNotification } = useNotification();
|
||||
const [studentCompetencies, setStudentCompetencies] = useState([]);
|
||||
const [grades, setGrades] = useState({});
|
||||
const studentId = searchParams.get('studentId');
|
||||
@ -70,14 +65,21 @@ export default function StudentCompetenciesPage() {
|
||||
competenceId,
|
||||
grade: score,
|
||||
}));
|
||||
|
||||
editStudentCompetencies(data, csrfToken)
|
||||
.then(() => {
|
||||
alert('Bilan de compétence enregistré !');
|
||||
showNotification(
|
||||
'Bilan de compétence sauvegardé avec succès',
|
||||
'success',
|
||||
'Succès'
|
||||
);
|
||||
router.back();
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Erreur lors de l'enregistrement du bilan");
|
||||
showNotification(
|
||||
"Erreur lors de l'enregistrement du bilan de compétence",
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -75,7 +75,9 @@ export default function GradeView({ data, grades, onGradeChange }) {
|
||||
{data.map((domaine) => (
|
||||
<div key={domaine.domaine_id} className="mb-8">
|
||||
<div
|
||||
className={'flex items-center justify-between cursor-pointer px-6 py-4 rounded-lg transition bg-emerald-50 border border-emerald-200 shadow-sm hover:bg-emerald-100'}
|
||||
className={
|
||||
'flex items-center justify-between cursor-pointer px-6 py-4 rounded-lg transition bg-emerald-50 border border-emerald-200 shadow-sm hover:bg-emerald-100'
|
||||
}
|
||||
onClick={() => toggleDomain(domaine.domaine_id)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
40
Front-End/src/components/Grades/GradesStatsCircle.js
Normal file
40
Front-End/src/components/Grades/GradesStatsCircle.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { CircularProgressbar, buildStyles } from 'react-circular-progressbar';
|
||||
import 'react-circular-progressbar/dist/styles.css';
|
||||
|
||||
export default function GradesStatsCircle({ grades }) {
|
||||
// grades : { [competence_id]: grade }
|
||||
const total = Object.keys(grades).length;
|
||||
const acquired = Object.values(grades).filter((g) => g === 3).length;
|
||||
const inProgress = Object.values(grades).filter((g) => g === 2).length;
|
||||
const notAcquired = Object.values(grades).filter((g) => g === 1).length;
|
||||
const notEvaluated = Object.values(grades).filter((g) => g === 0).length;
|
||||
|
||||
// Pourcentage d'acquis
|
||||
const percent = total ? Math.round((acquired / total) * 100) : 0;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-4 bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h2 className="text-xl font-semibold mb-2">Statistiques globales</h2>
|
||||
<div style={{ width: 120, height: 120 }}>
|
||||
<CircularProgressbar
|
||||
value={percent}
|
||||
text={`${percent}%`}
|
||||
styles={buildStyles({
|
||||
textColor: '#059669',
|
||||
pathColor: '#059669',
|
||||
trailColor: '#d1fae5',
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-sm mt-2">
|
||||
<span className="text-emerald-700 font-semibold">
|
||||
{acquired} acquis
|
||||
</span>
|
||||
<span className="text-yellow-600">{inProgress} en cours</span>
|
||||
<span className="text-red-500">{notAcquired} non acquis</span>
|
||||
<span className="text-gray-400">{notEvaluated} non évalués</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user