fix: normalização de IDs e melhoria no feedback de envio de provas (Portal/Manager)
This commit is contained in:
parent
5d1e7876be
commit
26dc4210eb
|
|
@ -144,7 +144,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ 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<ReportCardProps> = ({ data, updateData }) => {
|
|||
<div className="space-y-6">
|
||||
{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 (
|
||||
<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 className="flex items-center gap-2">
|
||||
{(() => {
|
||||
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<ReportCardProps> = ({ data, updateData }) => {
|
|||
</div>
|
||||
<div className="flex flex-col gap-6">
|
||||
{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<number>((a, b: any) => a + (b !== '' ? Number(b) : 0), 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
@ -121,6 +121,13 @@ export default function Avaliacoes() {
|
|||
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);
|
||||
|
||||
try {
|
||||
|
|
@ -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() {
|
|||
? <XCircle size={28} color="var(--color-danger)" />
|
||||
: modalType === 'confirm'
|
||||
? <AlertTriangle size={28} color="var(--color-warning)" />
|
||||
: modalType === 'loading'
|
||||
? <Loader2 size={28} color="var(--color-primary)" className="animate-spin" />
|
||||
: <CheckCircle2 size={28} color="var(--color-primary)" />
|
||||
}
|
||||
</div>
|
||||
<p style={{ fontSize: '0.9rem', fontWeight: 500, marginBottom: '1.5rem', lineHeight: 1.5 }}>
|
||||
<p style={{ fontSize: '0.9rem', fontWeight: 500, marginBottom: modalType === 'loading' ? 0 : '1.5rem', lineHeight: 1.5 }}>
|
||||
{modalMsg}
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: '0.75rem', justifyContent: 'center' }}>
|
||||
{modalType === 'confirm' ? (
|
||||
{modalType !== 'loading' && (
|
||||
<div style={{ display: 'flex', gap: '0.75rem', justifyContent: 'center' }}>
|
||||
{modalType === 'confirm' ? (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setShowModal(false)}
|
||||
|
|
@ -492,8 +514,9 @@ export default function Avaliacoes() {
|
|||
>
|
||||
OK
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div key={subjectId} className="glass-card animate-fade-in" style={{ marginBottom: '1.5rem', overflow: 'hidden' }}>
|
||||
|
|
@ -142,7 +142,7 @@ export default function Notas() {
|
|||
|
||||
<div style={{ padding: '1.5rem' }}>
|
||||
{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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue