From 5adb683d4edcc7911941be0edf7d03cc85e7c02d Mon Sep 17 00:00:00 2001 From: Sidney Date: Fri, 1 May 2026 11:31:10 -0300 Subject: [PATCH] =?UTF-8?q?refactor:=20se=C3=A7=C3=A3o=20de=20desempenho?= =?UTF-8?q?=20acad=C3=AAmico=20(notas,=20acertos/erros)=20no=20hist=C3=B3r?= =?UTF-8?q?ico=20do=20aluno?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manager/components/Students.tsx | 116 ++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/manager/components/Students.tsx b/manager/components/Students.tsx index 1c361a1..8a35773 100644 --- a/manager/components/Students.tsx +++ b/manager/components/Students.tsx @@ -40,6 +40,11 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId const [selectedClassId, setSelectedClassId] = useState(null); const [isSaving, setIsSaving] = useState(false); + // Academic History State + const [historyGrades, setHistoryGrades] = useState([]); + const [historySubmissions, setHistorySubmissions] = useState>({}); + const [isLoadingAcademic, setIsLoadingAcademic] = useState(false); + // Form State const [formData, setFormData] = useState>({ name: '', @@ -99,6 +104,43 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId } }, [deepLinkStudentId, deepLinkClassId, data.students]); + // Fetch Academic History when modal opens + useEffect(() => { + if (viewingStudentHistory) { + const fetchAcademic = async () => { + setIsLoadingAcademic(true); + try { + const t = new Date().getTime(); + const [resGrades, resSubs] = await Promise.all([ + fetch(`/api/notas/${viewingStudentHistory.id}?t=${t}`), + fetch(`/api/student-submissions/${viewingStudentHistory.id}?t=${t}`) + ]); + + if (resGrades.ok) { + const json = await resGrades.json(); + setHistoryGrades(json.notas || []); + } + if (resSubs.ok) { + const { submissions } = await resSubs.json(); + const subsMap: Record = {}; + (submissions || []).forEach((s: any) => { + subsMap[String(s.prova_id).trim()] = { acertos: s.acertos, erros: s.erros }; + }); + setHistorySubmissions(subsMap); + } + } catch (e) { + console.error("Erro ao buscar histórico acadêmico:", e); + } finally { + setIsLoadingAcademic(false); + } + }; + fetchAcademic(); + } else { + setHistoryGrades([]); + setHistorySubmissions({}); + } + }, [viewingStudentHistory]); + // Helper para normalizar URLs de fotos (vacina contra cache antigo) const normalizePhotoUrl = (url?: string) => { if (!url || typeof url !== 'string') return ''; @@ -1936,6 +1978,80 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId + + {/* Academic Performance Section */} +
+

+ Desempenho Acadêmico +

+ + {isLoadingAcademic ? ( +
+ +

Carregando Notas...

+
+ ) : historyGrades.length > 0 ? ( +
+ {data.subjects?.map(subject => { + const subjectGrades = historyGrades.filter(g => String(g.disciplina_id).trim() === String(subject.id).trim()); + if (subjectGrades.length === 0) return null; + + return ( +
+
+
{subject.name}
+ Média: {(subjectGrades.reduce((a, b) => a + Number(b.valor), 0) / subjectGrades.length).toFixed(1)} +
+
+ {subjectGrades.map((grade: any) => { + const exam = data.exams?.find(e => String(e.id).trim() === String(grade.prova_id).trim()); + const period = data.periods?.find(p => String(p.id).trim() === String(grade.periodo_id).trim()); + const stats = historySubmissions[String(grade.prova_id).trim()]; + const isActivity = exam?.evaluationType === 'activity'; + + return ( +
+
+
+ + {isActivity ? 'Atividade' : 'Prova'} + + {exam?.title || 'Avaliação'} + • {period?.name || 'Período'} +
+ {stats && ( +
+ + {stats.acertos} Acertos + + + {stats.erros} Erros + +
+ )} +
+
+
+

Nota

+

= 7 ? 'text-emerald-600' : Number(grade.valor) >= 5 ? 'text-amber-500' : 'text-rose-600'}`}> + {Number(grade.valor).toFixed(1)} +

+
+
+
+ ); + })} +
+
+ ); + })} +
+ ) : ( +
+

Nenhum desempenho acadêmico registrado ainda.

+
+ )} +
{/* Payments Section */}