fix: memory leak, syntax errors, typescript cleanup and grade interface

This commit is contained in:
Sidney 2026-04-29 20:48:33 -03:00
parent f52c5084c6
commit 74216f170d
5 changed files with 34 additions and 27 deletions

View File

@ -23,6 +23,11 @@
- [x] **Controle de Refação (Retake Policy):** Adicionado botão de cadeado nos cards de Avaliações para permitir ou bloquear que alunos refaçam provas no portal (Regra 15).
- [x] **UI de Avaliações:** Padronização dos botões de edição ("Editar Prova" vs "Editar Atividade") e adição de botão de exclusão rápida direto no card.
- [x] **Correção de Vínculo de Notas:** Garantido que o `examId` seja sempre salvo nas notas geradas pelo Portal para preenchimento automático do Boletim Escolar no Manager.
- [x] **Fix Memory Leak:** Removido `pool.on('error')` que estava dentro da rota `PUT /api/school-data`, acumulando listeners a cada salvamento.
- [x] **Fix SyntaxError (Backticks):** Corrigido erro de sintaxe com backticks escapados na rota do Database Explorer que impedia o servidor de iniciar.
- [x] **Fix Static Serving Duplicado:** Consolidada a entrega de arquivos estáticos (dist) no `manager/server.selfhosted.js`, eliminando o erro 404 em produção.
- [x] **TypeScript Cleanup:** Corrigidos erros de tipo `unknown` nos `reduce()` do ReportCard.tsx e removida função órfã `closeModal` do Settings.tsx.
- [x] **Interface Grade Tipada:** Adicionado `examId?: string` à interface `Grade` em `types.ts`, eliminando casts `as any` inseguros.
- [ ] Próximo Passo: Iniciar testes de estresse no servidor self-hosted para submissão massiva de fotos de frequência.
### 💳 Módulo Financeiro (Portal do Aluno)

View File

@ -136,11 +136,11 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
if (linkedExams.length > 0) {
linkedExams.forEach(exam => {
const existingGrade = grades.find(g => g.studentId === student.id && g.subjectId === subject.id && g.period === period.id && (g as any).examId === exam.id);
const existingGrade = grades.find(g => g.studentId === student.id && g.subjectId === subject.id && g.period === period.id && g.examId === exam.id);
periodGrades[exam.id] = existingGrade ? existingGrade.value : '';
});
} else {
const existingGrade = grades.find(g => g.studentId === student.id && g.subjectId === subject.id && g.period === period.id && !(g as any).examId);
const existingGrade = grades.find(g => g.studentId === student.id && g.subjectId === subject.id && g.period === period.id && !g.examId);
periodGrades['direct'] = existingGrade ? existingGrade.value : '';
}
initialGrades[subject.id][period.id] = periodGrades;
@ -187,7 +187,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
const periodSums: number[] = [];
Object.values(subjectPeriods).forEach((examValues: any) => {
const sum = Object.values(examValues).reduce((a: number, b: any) => a + (b !== '' ? Number(b) : 0), 0);
const sum: number = Object.values(examValues).reduce<number>((a, b: any) => a + (b !== '' ? Number(b) : 0), 0);
if (Object.values(examValues).some(v => v !== '')) {
periodSums.push(sum);
}
@ -529,7 +529,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
<div className="px-3 py-1 bg-white border border-slate-200 rounded-lg text-[10px] font-black text-slate-500">
MÉDIA: {(() => {
const subjectGrades = studentGrades[subject.id] || {};
const pSums = Object.values(subjectGrades).map((exVals: any) => Object.values(exVals).reduce((a: number, b: any) => a + (b !== '' ? Number(b) : 0), 0));
const pSums: number[] = Object.values(subjectGrades).map((exVals: any) => Object.values(exVals).reduce<number>((a, b: any) => a + (b !== '' ? Number(b) : 0), 0));
const valid = pSums.filter(s => s > 0);
return valid.length > 0 ? (valid.reduce((a, b) => a + b, 0) / valid.length).toFixed(1) : '0.0';
})()}
@ -540,7 +540,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
{periods.map(period => {
const linkedExams = (data.exams || []).filter(e => e.subjectId === subject.id && e.periodId === period.id && e.status === 'published');
const periodGrades = studentGrades[subject.id]?.[period.id] || {};
const periodSum = Object.values(periodGrades).reduce((a: number, 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);
return (
<div key={period.id} className="bg-white border border-slate-200 rounded-xl p-4 shadow-sm space-y-3 relative">

View File

@ -302,15 +302,6 @@ const Settings: React.FC<SettingsProps> = ({ data, updateData, setData }) => {
};
const closeModal = () => {
setIsClosing(true);
setTimeout(() => {
setShowImportModal(false);
setIsClosing(false);
}, 300);
};
const handleLogoUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {

View File

@ -185,10 +185,6 @@ app.put('/api/school-data', async (req, res) => {
END $$;
`).catch(err => console.error('[PostgreSQL] Erro ao inicializar colunas de automação:', err));
pool.on('error', (err) => {
console.error('Erro inesperado no pool:', err);
});
schoolData.lastUpdated = new Date().toISOString();
await saveSchoolData(schoolData);
res.json({ success: true });
@ -262,7 +258,7 @@ app.get('/api/database/tables/:tableName/data', async (req, res) => {
const result = await pool.query(query);
res.json({ rows: result.rows, fields: result.fields.map(f => f.name) });
} catch (error) {
console.error(\`Erro ao buscar dados da tabela \${req.params.tableName}:\`, error);
console.error(`Erro ao buscar dados da tabela ${req.params.tableName}:`, error);
res.status(500).json({ error: error.message });
}
});
@ -912,14 +908,6 @@ app.get('/api/alunos/:id/carne', async (req, res) => {
// INICIALIZAÇÃO
// ============================================================
async function startServer() {
const distPath = path.join(__dirname, 'dist');
if (fs.existsSync(distPath)) {
app.use(express.static(distPath));
app.use((req, res, next) => req.path.startsWith('/api') ? next() : res.sendFile(path.join(distPath, 'index.html')));
} else {
const vite = await import('vite').then(m => m.createServer({ server: { middlewareMode: true }, appType: 'spa' }));
app.use(vite.middlewares);
}
// Disparo Manual de Inadimplência e Lembretes
app.post('/api/disparar_cobrancas', async (req, res) => {
@ -1029,6 +1017,28 @@ async function startServer() {
} catch (error) { return res.status(500).json({ error: 'Erro interno.' }); }
});
// ===================================================
// SERVE FRONTEND (Final Catch-all)
// ===================================================
const distPath = path.join(__dirname, 'dist');
if (fs.existsSync(distPath)) {
app.use(express.static(distPath));
app.use((req, res, next) => {
if (req.path.startsWith('/api') || req.path.startsWith('/storage')) return next();
res.sendFile(path.join(distPath, 'index.html'));
});
} else {
try {
const vite = await import('vite').then(m => m.createServer({
server: { middlewareMode: true },
appType: 'spa'
}));
app.use(vite.middlewares);
} catch (e) {
console.warn('Vite dev server not available and dist folder missing.');
}
}
app.listen(PORT, '0.0.0.0', () => console.log(`🚀 EduManager Self-Hosted na porta ${PORT}`));
}

View File

@ -194,6 +194,7 @@ export interface Grade {
subjectId: string;
value: number;
period: string; // e.g., "1º Bimestre", "Final"
examId?: string; // Vincula a nota a uma avaliação específica
}
export interface Handout {