fix: robustez nos IDs (trim) e substituição de ícone Loader2 por RefreshCw para compatibilidade

This commit is contained in:
Sidney 2026-05-01 11:00:29 -03:00
parent 26dc4210eb
commit e2cb0376cf
6 changed files with 45 additions and 15 deletions

View File

@ -144,15 +144,15 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
initialGrades[subject.id] = {}; initialGrades[subject.id] = {};
periods.forEach(period => { periods.forEach(period => {
const periodGrades: any = {}; const periodGrades: any = {};
const linkedExams = (data.exams || []).filter(e => String(e.subjectId) === String(subject.id) && String(e.periodId) === String(period.id) && e.status === 'published'); const linkedExams = (data.exams || []).filter(e => String(e.subjectId).trim() === String(subject.id).trim() && String(e.periodId).trim() === String(period.id).trim() && e.status === 'published');
if (linkedExams.length > 0) { if (linkedExams.length > 0) {
linkedExams.forEach(exam => { linkedExams.forEach(exam => {
const existingGrade = dbNotas.find(g => String(g.disciplina_id) === String(subject.id) && String(g.periodo_id) === String(period.id) && String(g.prova_id) === String(exam.id)); const existingGrade = dbNotas.find(g => String(g.disciplina_id).trim() === String(subject.id).trim() && String(g.periodo_id).trim() === String(period.id).trim() && String(g.prova_id).trim() === String(exam.id).trim());
periodGrades[exam.id] = existingGrade ? Number(existingGrade.valor) : ''; periodGrades[exam.id] = existingGrade ? Number(existingGrade.valor) : '';
}); });
} else { } else {
const existingGrade = dbNotas.find(g => String(g.disciplina_id) === String(subject.id) && String(g.periodo_id) === String(period.id) && !g.prova_id); const existingGrade = dbNotas.find(g => String(g.disciplina_id).trim() === String(subject.id).trim() && String(g.periodo_id).trim() === String(period.id).trim() && !g.prova_id);
periodGrades['direct'] = existingGrade ? Number(existingGrade.valor) : ''; periodGrades['direct'] = existingGrade ? Number(existingGrade.valor) : '';
} }
initialGrades[subject.id][period.id] = periodGrades; initialGrades[subject.id][period.id] = periodGrades;
@ -518,7 +518,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
<div className="space-y-6"> <div className="space-y-6">
{subjects.map(subject => { {subjects.map(subject => {
// Encontrar provas vinculadas a esta disciplina // Encontrar provas vinculadas a esta disciplina
const linkedExams = (data.exams || []).filter(e => String(e.subjectId) === String(subject.id) && e.status === 'published'); const linkedExams = (data.exams || []).filter(e => String(e.subjectId).trim() === String(subject.id).trim() && e.status === 'published');
return ( return (
<div key={subject.id} className="bg-slate-50 rounded-2xl p-6 border border-slate-100 space-y-4"> <div key={subject.id} className="bg-slate-50 rounded-2xl p-6 border border-slate-100 space-y-4">
@ -529,7 +529,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{(() => { {(() => {
const linkedExams = (data.exams || []).filter(e => String(e.subjectId) === String(subject.id) && e.status === 'published'); const linkedExams = (data.exams || []).filter(e => String(e.subjectId).trim() === String(subject.id).trim() && e.status === 'published');
const provasCount = linkedExams.filter(e => (e as any).evaluationType !== 'activity').length; const provasCount = linkedExams.filter(e => (e as any).evaluationType !== 'activity').length;
const atividadesCount = linkedExams.filter(e => (e as any).evaluationType === 'activity').length; const atividadesCount = linkedExams.filter(e => (e as any).evaluationType === 'activity').length;
return ( return (
@ -561,7 +561,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
</div> </div>
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{periods.map(period => { {periods.map(period => {
const linkedExams = (data.exams || []).filter(e => String(e.subjectId) === String(subject.id) && String(e.periodId) === String(period.id) && e.status === 'published'); const linkedExams = (data.exams || []).filter(e => String(e.subjectId).trim() === String(subject.id).trim() && String(e.periodId).trim() === String(period.id).trim() && e.status === 'published');
const periodGrades = studentGrades[subject.id]?.[period.id] || {}; const periodGrades = studentGrades[subject.id]?.[period.id] || {};
const periodSum: number = Object.values(periodGrades).reduce<number>((a, b: any) => a + (b !== '' ? Number(b) : 0), 0); const periodSum: number = Object.values(periodGrades).reduce<number>((a, b: any) => a + (b !== '' ? Number(b) : 0), 0);

View File

@ -0,0 +1,13 @@
import pg from 'pg';
const DATABASE_URL = 'postgresql://edumanager:EduManager2026!Seguro@127.0.0.1:5432/edumanager';
const pool = new pg.Pool({ connectionString: DATABASE_URL });
async function check() {
try {
const { rows } = await pool.query(\"SELECT (data->'grades') as grades FROM school_data WHERE id = 1\");
console.log('GRADES_JSON_COUNT:' + (rows[0]?.grades?.length || 0));
const { rows: n } = await pool.query(\"SELECT count(*) FROM notas_boletim\");
console.log('NOTAS_TABLE_COUNT:' + n[0].count);
} catch (err) { console.log('ERROR:' + err.message); }
finally { await pool.end(); }
}
check();

View File

@ -0,0 +1,17 @@
import pg from 'pg';
const DATABASE_URL = 'postgresql://edumanager:EduManager2026!Seguro@127.0.0.1:5432/edumanager';
const pool = new pg.Pool({ connectionString: DATABASE_URL });
async function listAll() {
try {
const { rows: notas } = await pool.query('SELECT * FROM notas_boletim LIMIT 20');
console.log('--- NOTAS NA TABELA ---');
console.log(JSON.stringify(notas, null, 2));
const { rows: subs } = await pool.query('SELECT * FROM provas_submissoes LIMIT 10');
console.log('--- SUBMISSÕES NA TABELA ---');
console.log(JSON.stringify(subs, null, 2));
} catch (err) { console.log('ERROR:' + err.message); }
finally { await pool.end(); }
}
listAll();

View File

@ -306,8 +306,8 @@ app.get('/api/student-submissions/:studentId', async (req, res) => {
try { try {
const { studentId } = req.params; const { studentId } = req.params;
const { rows } = await pool.query( const { rows } = await pool.query(
'SELECT prova_id as "prova_id", acertos, erros FROM provas_submissoes WHERE aluno_id = $1', 'SELECT prova_id as "prova_id", acertos, erros FROM provas_submissoes WHERE TRIM(aluno_id) = TRIM($1)',
[String(studentId)] [String(studentId).trim()]
); );
res.json({ submissions: rows }); res.json({ submissions: rows });
} catch (err) { } catch (err) {
@ -322,8 +322,8 @@ app.get('/api/student-submissions/:studentId', async (req, res) => {
app.get('/api/notas/:alunoId', async (req, res) => { app.get('/api/notas/:alunoId', async (req, res) => {
try { try {
const { rows: dbNotas } = await pool.query( const { rows: dbNotas } = await pool.query(
'SELECT id, aluno_id as "aluno_id", disciplina_id as "disciplina_id", periodo_id as "periodo_id", prova_id as "prova_id", valor as "valor" FROM notas_boletim WHERE aluno_id = $1', 'SELECT id, aluno_id as "aluno_id", disciplina_id as "disciplina_id", periodo_id as "periodo_id", prova_id as "prova_id", valor as "valor" FROM notas_boletim WHERE TRIM(aluno_id) = TRIM($1)',
[req.params.alunoId] [String(req.params.alunoId).trim()]
); );
// Garantir cast numérico para evitar erro de .toFixed no frontend // Garantir cast numérico para evitar erro de .toFixed no frontend
const notas = dbNotas.map(n => ({ ...n, valor: Number(n.valor) })); const notas = dbNotas.map(n => ({ ...n, valor: Number(n.valor) }));

View File

@ -264,9 +264,9 @@ app.get('/api/portal/notas', authMiddleware, async (req, res) => {
); );
const enrichedGrades = grades.map((g) => { const enrichedGrades = grades.map((g) => {
const subject = subjects.find((s) => String(s.id) === String(g.subjectId)); const subject = subjects.find((s) => String(s.id).trim() === String(g.subjectId).trim());
const exam = g.examId ? (schoolData.exams || []).find(e => String(e.id) === String(g.examId)) : null; const exam = g.examId ? (schoolData.exams || []).find(e => String(e.id).trim() === String(g.examId).trim()) : null;
const periodObj = (schoolData.periods || []).find(p => String(p.id) === String(g.period)); const periodObj = (schoolData.periods || []).find(p => String(p.id).trim() === String(g.period).trim());
const submission = g.examId ? submissions.find(s => String(s.prova_id) === String(g.examId)) : null; const submission = g.examId ? submissions.find(s => String(s.prova_id) === String(g.examId)) : null;

View File

@ -3,7 +3,7 @@ import { useAuth } from '../context/AuthContext';
import type { Exam, ExamSubmission } from '../types'; import type { Exam, ExamSubmission } from '../types';
import { import {
ClipboardList, Clock, ChevronLeft, ChevronRight, Send, CheckCircle2, ClipboardList, Clock, ChevronLeft, ChevronRight, Send, CheckCircle2,
XCircle, Award, AlertTriangle, Timer, ArrowLeft, Loader2 XCircle, Award, AlertTriangle, Timer, ArrowLeft, RefreshCw
} from 'lucide-react'; } from 'lucide-react';
import { normalizePhotoUrl } from '../helpers'; import { normalizePhotoUrl } from '../helpers';
@ -465,7 +465,7 @@ export default function Avaliacoes() {
: modalType === 'confirm' : modalType === 'confirm'
? <AlertTriangle size={28} color="var(--color-warning)" /> ? <AlertTriangle size={28} color="var(--color-warning)" />
: modalType === 'loading' : modalType === 'loading'
? <Loader2 size={28} color="var(--color-primary)" className="animate-spin" /> ? <RefreshCw size={28} color="var(--color-primary)" className="animate-spin" />
: <CheckCircle2 size={28} color="var(--color-primary)" /> : <CheckCircle2 size={28} color="var(--color-primary)" />
} }
</div> </div>