feat: Génération du bilan de compétence en PDF [#16]

This commit is contained in:
N3WT DE COMPET
2025-05-21 20:44:37 +02:00
parent eb7805e54e
commit 0fe6c76189
14 changed files with 357 additions and 67 deletions

View File

@ -2,7 +2,7 @@ import React from 'react';
export default function Attendance({ absences }) {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<div className="w-full bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-4">Présence et assiduité</h2>
<ol className="relative border-l border-emerald-200">
{absences.map((absence, idx) => (

View File

@ -0,0 +1,65 @@
import React from 'react';
export default function GradesDomainBarChart({ studentCompetencies }) {
if (!studentCompetencies?.data) return null;
// Calcul du score moyen par domaine
const domainStats = studentCompetencies.data.map((domaine) => {
const allScores = domaine.categories.flatMap(
(cat) =>
cat.competences
.map((comp) => comp.score ?? 0)
.filter((score) => score > 0) // Ignorer les notes à 0
);
const avg =
allScores.length > 0
? (allScores.reduce((a, b) => a + b, 0) / allScores.length).toFixed(2)
: 0;
return {
name: domaine.domaine_nom,
avg: Number(avg),
count: allScores.length,
};
});
// Détermine la couleur de la jauge selon la moyenne
const getBarGradient = (avg) => {
if (avg > 0 && avg <= 1) return 'bg-gradient-to-r from-red-200 to-red-400';
if (avg > 1 && avg <= 2)
return 'bg-gradient-to-r from-yellow-200 to-yellow-400';
if (avg > 2) return 'bg-gradient-to-r from-emerald-200 to-emerald-500';
return 'bg-gray-200';
};
return (
<div className="w-3/4 flex flex-col items-center gap-4 bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-semibold mb-2">Moyenne par domaine</h2>
<div className="w-full flex flex-col gap-2">
{domainStats.map((d) => (
<div key={d.name} className="flex items-center w-full">
<span className="font-medium text-left" style={{ width: '30%' }}>
{d.name}
</span>
<div className="flex items-center" style={{ width: '40%' }}>
<div className="w-full bg-emerald-100 h-3 rounded overflow-hidden">
<div
className={`h-3 rounded ${getBarGradient(d.avg)}`}
style={{ width: `${d.avg * 33.33}%` }}
/>
</div>
</div>
<span
className="text-xs font-semibold text-emerald-700 text-left pl-2 flex-shrink-0"
style={{ width: '10%' }}
>
{d.avg}
</span>
<span className="text-gray-500 text-left" style={{ width: '20%' }}>
({`compétences évaluées ${d.count}`})
</span>
</div>
))}
</div>
</div>
);
}

View File

@ -14,7 +14,7 @@ export default function GradesStatsCircle({ grades }) {
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">
<div className="w-full flex flex-col items-center gap-4 bg-stone-50 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

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { BASE_URL } from '@/utils/Url';
export default function StudentInput({
@ -13,6 +13,18 @@ export default function StudentInput({
const [suggestions, setSuggestions] = useState([]);
const [selectedIndex, setSelectedIndex] = useState(-1);
// Désélectionner si l'input ne correspond plus à l'élève sélectionné
useEffect(() => {
if (
selectedStudent &&
inputValue !==
`${selectedStudent.last_name} ${selectedStudent.first_name}`
) {
setSelectedStudent(null);
}
// eslint-disable-next-line
}, [inputValue]);
const handleInputChange = async (e) => {
const value = e.target.value;
setInputValue(value);
@ -31,9 +43,7 @@ export default function StudentInput({
const handleSuggestionClick = (student) => {
setSelectedStudent(student);
setInputValue(
`${student.last_name} ${student.first_name} (${student.level}) - ${student.associated_class_name}`
);
setInputValue(`${student.last_name} ${student.first_name}`);
setSuggestions([]);
};