diff --git a/manager/components/ReportCard.tsx b/manager/components/ReportCard.tsx index 901e806..60cf7a9 100644 --- a/manager/components/ReportCard.tsx +++ b/manager/components/ReportCard.tsx @@ -144,7 +144,7 @@ const ReportCard: React.FC = ({ data, updateData }) => { initialGrades[subject.id] = {}; periods.forEach(period => { const periodGrades: any = {}; - const linkedExams = (data.exams || []).filter(e => e.subjectId === subject.id && e.periodId === period.id && e.status === 'published'); + const linkedExams = (data.exams || []).filter(e => String(e.subjectId) === String(subject.id) && String(e.periodId) === String(period.id) && e.status === 'published'); if (linkedExams.length > 0) { linkedExams.forEach(exam => { @@ -518,7 +518,7 @@ const ReportCard: React.FC = ({ data, updateData }) => {
{subjects.map(subject => { // Encontrar provas vinculadas a esta disciplina - const linkedExams = (data.exams || []).filter(e => e.subjectId === subject.id && e.status === 'published'); + const linkedExams = (data.exams || []).filter(e => String(e.subjectId) === String(subject.id) && e.status === 'published'); return (
@@ -529,7 +529,7 @@ const ReportCard: React.FC = ({ data, updateData }) => {
{(() => { - const linkedExams = (data.exams || []).filter(e => e.subjectId === subject.id && e.status === 'published'); + const linkedExams = (data.exams || []).filter(e => String(e.subjectId) === String(subject.id) && e.status === 'published'); const provasCount = linkedExams.filter(e => (e as any).evaluationType !== 'activity').length; const atividadesCount = linkedExams.filter(e => (e as any).evaluationType === 'activity').length; return ( @@ -561,7 +561,7 @@ const ReportCard: React.FC = ({ data, updateData }) => {
{periods.map(period => { - const linkedExams = (data.exams || []).filter(e => e.subjectId === subject.id && e.periodId === period.id && e.status === 'published'); + const linkedExams = (data.exams || []).filter(e => String(e.subjectId) === String(subject.id) && String(e.periodId) === String(period.id) && e.status === 'published'); const periodGrades = studentGrades[subject.id]?.[period.id] || {}; const periodSum: number = Object.values(periodGrades).reduce((a, b: any) => a + (b !== '' ? Number(b) : 0), 0); diff --git a/manager/server.selfhosted.js b/manager/server.selfhosted.js index 83e927f..4196c7c 100644 --- a/manager/server.selfhosted.js +++ b/manager/server.selfhosted.js @@ -306,8 +306,8 @@ app.get('/api/student-submissions/:studentId', async (req, res) => { try { const { studentId } = req.params; const { rows } = await pool.query( - 'SELECT prova_id, acertos, erros FROM provas_submissoes WHERE aluno_id = $1', - [studentId] + 'SELECT prova_id as "prova_id", acertos, erros FROM provas_submissoes WHERE aluno_id = $1', + [String(studentId)] ); res.json({ submissions: rows }); } catch (err) { @@ -321,7 +321,12 @@ app.get('/api/student-submissions/:studentId', async (req, res) => { // ============================================================ app.get('/api/notas/:alunoId', async (req, res) => { try { - const notas = await getNotasByAluno(req.params.alunoId); + 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', + [req.params.alunoId] + ); + // Garantir cast numérico para evitar erro de .toFixed no frontend + const notas = dbNotas.map(n => ({ ...n, valor: Number(n.valor) })); res.json({ notas }); } catch (err) { console.error('Erro ao buscar notas do aluno:', err); diff --git a/portal/server.selfhosted.js b/portal/server.selfhosted.js index b238d2b..0716d8e 100644 --- a/portal/server.selfhosted.js +++ b/portal/server.selfhosted.js @@ -264,9 +264,9 @@ app.get('/api/portal/notas', authMiddleware, async (req, res) => { ); const enrichedGrades = grades.map((g) => { - const subject = subjects.find((s) => s.id === g.subjectId); - const exam = g.examId ? (schoolData.exams || []).find(e => e.id === g.examId) : null; - const periodObj = (schoolData.periods || []).find(p => p.id === g.period); + const subject = subjects.find((s) => String(s.id) === String(g.subjectId)); + const exam = g.examId ? (schoolData.exams || []).find(e => String(e.id) === String(g.examId)) : null; + const periodObj = (schoolData.periods || []).find(p => String(p.id) === String(g.period)); const submission = g.examId ? submissions.find(s => String(s.prova_id) === String(g.examId)) : null; diff --git a/portal/src/pages/Avaliacoes.tsx b/portal/src/pages/Avaliacoes.tsx index 289bbbf..8572589 100644 --- a/portal/src/pages/Avaliacoes.tsx +++ b/portal/src/pages/Avaliacoes.tsx @@ -3,7 +3,7 @@ import { useAuth } from '../context/AuthContext'; import type { Exam, ExamSubmission } from '../types'; import { ClipboardList, Clock, ChevronLeft, ChevronRight, Send, CheckCircle2, - XCircle, Award, AlertTriangle, Timer, ArrowLeft + XCircle, Award, AlertTriangle, Timer, ArrowLeft, Loader2 } from 'lucide-react'; import { normalizePhotoUrl } from '../helpers'; @@ -40,7 +40,7 @@ export default function Avaliacoes() { // In-app modal state (replaces native alert/confirm) const [modalMsg, setModalMsg] = useState(''); - const [modalType, setModalType] = useState<'info' | 'error' | 'confirm'>('info'); + const [modalType, setModalType] = useState<'info' | 'error' | 'confirm' | 'loading'>('info'); const [showModal, setShowModal] = useState(false); const [confirmCallback, setConfirmCallback] = useState<(() => void) | null>(null); @@ -120,6 +120,13 @@ export default function Avaliacoes() { const handleSubmit = async (autoSubmit = false) => { if (submitting || !activeExam) return; setSubmitting(true); + + const typeLabel = (activeExam as any).evaluationType === 'activity' ? 'atividade' : 'prova'; + + // Show Loading Modal + setModalType('loading'); + setModalMsg(`Enviando sua ${typeLabel}... Por favor, aguarde e não feche esta janela.`); + setShowModal(true); if (timerRef.current) clearInterval(timerRef.current); @@ -136,16 +143,28 @@ export default function Avaliacoes() { const data = await res.json(); if (data.success) { - setResult(data.result); - setView('result'); - fetchExams(); + // Show Success Modal + setModalType('info'); + setModalMsg(`Sua ${typeLabel} foi enviada com sucesso! Clique em OK para ver seu resultado.`); + setShowModal(true); + + // Wait for user to click OK before showing result? + // No, user wants result. But let's follow the "sent successfully" request. + // We'll set a callback to OK button to show result + setConfirmCallback(() => { + setResult(data.result); + setView('result'); + fetchExams(); + }); } else { - showAppAlert(data.error || 'Erro ao enviar prova.', 'error'); + const errorCode = `ERR-${activeExam.id.substring(0, 4)}-${new Date().getTime().toString().slice(-4)}`; + showAppAlert(`Não foi possível enviar sua nota. Tente novamente ou contate o suporte. (Código: ${errorCode})`, 'error'); if (!autoSubmit) setView('listing'); } } catch (err) { console.error(err); - showAppAlert('Erro de conexão ao enviar prova.', 'error'); + const errorCode = `CONN-ERR-${new Date().getTime().toString().slice(-4)}`; + showAppAlert(`Erro de conexão ao enviar prova. Verifique sua internet. (Código: ${errorCode})`, 'error'); } finally { setSubmitting(false); } @@ -445,14 +464,17 @@ export default function Avaliacoes() { ? : modalType === 'confirm' ? + : modalType === 'loading' + ? : }
-

+

{modalMsg}

-
- {modalType === 'confirm' ? ( + {modalType !== 'loading' && ( +
+ {modalType === 'confirm' ? ( <> - )} -
+ )} +
+ )}
)} diff --git a/portal/src/pages/Notas.tsx b/portal/src/pages/Notas.tsx index 568c2fa..143e784 100644 --- a/portal/src/pages/Notas.tsx +++ b/portal/src/pages/Notas.tsx @@ -52,7 +52,7 @@ export default function Notas() { ? allSubjects : [...new Set(grades.map(g => g.subjectId))].map(id => ({ id, - name: grades.find(g => g.subjectId === id)?.subjectName || id + name: grades.find(g => String(g.subjectId) === String(id))?.subjectName || id })); // General average logic @@ -132,7 +132,7 @@ export default function Notas() { {displaySubjects.map((subject, idx) => { const subjectId = typeof subject === 'string' ? subject : subject.id; const subjectName = typeof subject === 'string' ? subject : subject.name; - const subjectGrades = grades.filter(g => g.subjectId === subjectId); + const subjectGrades = grades.filter(g => String(g.subjectId) === String(subjectId)); return (
@@ -142,7 +142,7 @@ export default function Notas() {
{periods.map(period => { - const periodGrades = subjectGrades.filter(g => (g.periodName || g.period) === period); + const periodGrades = subjectGrades.filter(g => String(g.periodName || g.period) === String(period)); if (periodGrades.length === 0) return null; const periodTotal = periodGrades.reduce((sum, g) => sum + g.value, 0);