feat: Bilan de compétence d'un élève [#16]

This commit is contained in:
N3WT DE COMPET
2025-05-18 17:10:49 +02:00
parent e65e31014d
commit 5760c89105
14 changed files with 646 additions and 124 deletions

View File

@ -1,144 +1,158 @@
'use client';
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import SelectChoice from '@/components/SelectChoice';
import AcademicResults from '@/components/Grades/AcademicResults';
import Attendance from '@/components/Grades/Attendance';
import Remarks from '@/components/Grades/Remarks';
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 Button from '@/components/Button';
import Table from '@/components/Table';
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 { useEstablishment } from '@/context/EstablishmentContext';
import { useClasses } from '@/context/ClassesContext';
export default function Page() {
const router = useRouter();
const { selectedEstablishmentId } = useEstablishment();
const { getNiveauLabel } = useClasses();
const [formData, setFormData] = useState({
selectedStudent: null,
absences: [],
competenceReview: [
{ competence: 'Lecture', score: null },
{ competence: 'Écriture', score: null },
{ competence: 'Mathématiques', score: null },
{ competence: 'Sciences', score: null },
],
});
const students = [
{ id: 1, name: 'John Doe', class: 'CM2' },
{ id: 2, name: 'Jane Smith', class: 'CE1' },
{ id: 3, name: 'Alice Johnson', class: 'CM1' },
const [students, setStudents] = useState([]);
const academicResults = [
{
subject: 'Mathématiques',
grade: 16,
average: 14,
appreciation: 'Très bon travail',
},
{
subject: 'Français',
grade: 15,
average: 13,
appreciation: 'Bonne participation',
},
];
const absences = [
{ date: '2023-09-01', reason: 'Maladie' },
{ date: '2023-09-15', reason: 'Vacances' },
{ date: '2023-10-05', reason: 'Retard justifié' },
{ date: '2023-09-01', type: 'Absence', reason: 'Maladie', justified: true },
{ date: '2023-09-15', type: 'Retard', reason: 'Trafic', justified: false },
];
const handleChange = (field, value) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const remarks = [
{
date: '2023-09-10',
teacher: 'Mme Dupont',
comment: 'Participation active en classe.',
},
{
date: '2023-09-20',
teacher: 'M. Martin',
comment: 'Doit améliorer la concentration.',
},
];
const handleScoreChange = (index, score) => {
const updatedCompetenceReview = [...formData.competenceReview];
updatedCompetenceReview[index].score = score;
setFormData((prev) => ({
...prev,
competenceReview: updatedCompetenceReview,
}));
};
const workPlan = [
{
objective: 'Renforcer la lecture',
support: 'Exercices hebdomadaires',
followUp: 'En cours',
},
{
objective: 'Maîtriser les tables de multiplication',
support: 'Jeux éducatifs',
followUp: 'À démarrer',
},
];
const homeworks = [
{ title: 'Rédaction', dueDate: '2023-10-10', status: 'Rendu' },
{ title: 'Exercices de maths', dueDate: '2023-10-12', status: 'À faire' },
];
const specificEvaluations = [
{
test: 'Bilan de compétences',
date: '2023-09-25',
result: 'Bon niveau général',
},
];
const orientation = [
{
date: '2023-10-01',
counselor: 'Mme Leroy',
advice: 'Poursuivre en filière générale',
},
];
const handleChange = (field, value) =>
setFormData((prev) => ({ ...prev, [field]: value }));
useEffect(() => {
if (selectedEstablishmentId) {
fetchStudents(selectedEstablishmentId)
.then((studentsData) => {
setStudents(studentsData);
})
.catch((error) => logger.error('Error fetching students:', error));
}
}, [selectedEstablishmentId]);
return (
<div className="p-8 space-y-8">
<h1 className="heading-section">Suivi pédagogique</h1>
{/* Sélection de l'élève */}
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4">Sélectionner un élève</h2>
<SelectChoice
name="selectedStudent"
label="Élève"
placeHolder="lectionnez un élève"
selected={formData.selectedStudent}
callback={(e) => handleChange('selectedStudent', e.target.value)}
choices={students.map((student) => ({
value: student.id,
label: `${student.name} - Classe : ${student.class}`,
}))}
required
/>
</div>
{/* Liste des absences */}
{formData.selectedStudent && (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4">Liste des absences</h2>
<ul className="space-y-2">
{absences.map((absence, index) => (
<li
key={index}
className="flex justify-between items-center bg-gray-50 p-4 rounded-md border border-gray-100"
>
<span className="text-gray-800">{absence.date}</span>
<span className="text-gray-500 italic">{absence.reason}</span>
</li>
))}
</ul>
</div>
)}
{/* Bilan de compétence */}
{formData.selectedStudent && (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4">Bilan de compétence</h2>
<Table
data={formData.competenceReview}
columns={[
{
name: 'Compétence',
transform: (row) => row.competence,
},
{
name: '1',
transform: (row, index) => (
<input
type="radio"
name={`score-${index}`}
value="1"
checked={row.score === '1'}
onChange={() => handleScoreChange(index, '1')}
/>
),
},
{
name: '2',
transform: (row, index) => (
<input
type="radio"
name={`score-${index}`}
value="2"
checked={row.score === '2'}
onChange={() => handleScoreChange(index, '2')}
/>
),
},
{
name: '3',
transform: (row, index) => (
<input
type="radio"
name={`score-${index}`}
value="3"
checked={row.score === '3'}
onChange={() => handleScoreChange(index, '3')}
/>
),
},
]}
/>
<div className="mt-4">
<Button
text="Enregistrer"
onClick={() => logger.debug('FormData:', formData)}
primary
className="bg-emerald-500 text-white hover:bg-emerald-600"
<div className="flex flex-col sm:flex-row sm:items-end gap-4">
<div className="flex-1">
<SelectChoice
name="selectedStudent"
label="Élève"
placeHolder="Sélectionnez un élève"
selected={formData.selectedStudent || ''}
callback={(e) => handleChange('selectedStudent', e.target.value)}
choices={students.map((student) => ({
value: student.id,
label: `${student.last_name} ${student.first_name} - ${getNiveauLabel(student.level)} (${student.associated_class_name})`,
}))}
required
/>
</div>
<Button
text="Réaliser le bilan de compétences"
primary
disabled={!formData.selectedStudent}
onClick={() => {
const url = `${FE_ADMIN_GRADES_STUDENT_COMPETENCIES_URL}?studentId=${formData.selectedStudent}`;
router.push(`${url}`);
}}
className={`px-6 py-2 rounded-md shadow ${
!formData.selectedStudent
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-emerald-500 text-white hover:bg-emerald-600'
}`}
/>
</div>
</div>
{formData.selectedStudent && (
<>
<AcademicResults results={academicResults} />
<Attendance absences={absences} />
<Remarks remarks={remarks} />
<WorkPlan workPlan={workPlan} />
<Homeworks homeworks={homeworks} />
<SpecificEvaluations specificEvaluations={specificEvaluations} />
<Orientation orientation={orientation} />
</>
)}
</div>
);

View File

@ -0,0 +1,114 @@
'use client';
import React, { useState, useEffect } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import Button from '@/components/Button';
import GradeView from '@/components/Grades/GradeView';
import {
fetchStudentCompetencies,
editStudentCompetencies,
} from '@/app/actions/subscriptionAction';
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 à loral', score: null },
];
export default function StudentCompetenciesPage() {
const searchParams = useSearchParams();
const router = useRouter();
const csrfToken = useCsrfToken();
const [studentCompetencies, setStudentCompetencies] = useState([]);
const [grades, setGrades] = useState({});
const studentId = searchParams.get('studentId');
useEffect(() => {
fetchStudentCompetencies(studentId)
.then((data) => {
setStudentCompetencies(data);
})
.catch((error) =>
logger.error('Error fetching studentCompetencies:', error)
);
}, []);
useEffect(() => {
if (studentCompetencies.data) {
const initialGrades = {};
studentCompetencies.data.forEach((domaine) => {
domaine.categories.forEach((cat) => {
cat.competences.forEach((comp) => {
initialGrades[comp.competence_id] = comp.score ?? 0;
});
});
});
setGrades(initialGrades);
}
}, [studentCompetencies.data]);
const handleScoreChange = (competencyId, score) => {
setCompetencies((prev) =>
prev.map((comp) => (comp.id === competencyId ? { ...comp, score } : comp))
);
};
const handleGradeChange = (competenceId, level) => {
setGrades((prev) => ({
...prev,
[competenceId]: level,
}));
};
const handleSubmit = () => {
const data = Object.entries(grades).map(([competenceId, score]) => ({
studentId,
competenceId,
grade: score,
}));
editStudentCompetencies(data, csrfToken)
.then(() => {
alert('Bilan de compétence enregistré !');
router.back();
})
.catch((error) => {
alert("Erreur lors de l'enregistrement du bilan");
});
};
return (
<div className="h-full flex flex-col p-4">
<SectionHeader
icon={Award}
title="Bilan de compétence"
description="Evaluez les compétence de l'élève"
/>
<div className="flex-1 min-h-0 flex flex-col">
<form
className="flex-1 min-h-0 flex flex-col"
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
{/* Zone scrollable pour les compétences */}
<div className="flex-1 min-h-0 overflow-y-auto">
<GradeView
data={studentCompetencies.data}
grades={grades}
onGradeChange={handleGradeChange}
/>
</div>
<div className="mt-6 flex justify-end">
<Button text="Enregistrer le bilan" primary type="submit" />
</div>
</form>
</div>
</div>
);
}