diff --git a/MEMORY.md b/MEMORY.md index 515976b..32ad156 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -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) diff --git a/manager/components/ReportCard.tsx b/manager/components/ReportCard.tsx index 900d592..47a533e 100644 --- a/manager/components/ReportCard.tsx +++ b/manager/components/ReportCard.tsx @@ -136,11 +136,11 @@ const ReportCard: React.FC = ({ 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 = ({ 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((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 = ({ data, updateData }) => {
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((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 = ({ 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((a, b: any) => a + (b !== '' ? Number(b) : 0), 0); return (
diff --git a/manager/components/Settings.tsx b/manager/components/Settings.tsx index ac859de..5e6e3cf 100644 --- a/manager/components/Settings.tsx +++ b/manager/components/Settings.tsx @@ -302,15 +302,6 @@ const Settings: React.FC = ({ data, updateData, setData }) => { }; - - const closeModal = () => { - setIsClosing(true); - setTimeout(() => { - setShowImportModal(false); - setIsClosing(false); - }, 300); - }; - const handleLogoUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { diff --git a/manager/server.selfhosted.js b/manager/server.selfhosted.js index eb319e5..a4a2eeb 100644 --- a/manager/server.selfhosted.js +++ b/manager/server.selfhosted.js @@ -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}`)); } diff --git a/manager/types.ts b/manager/types.ts index 5c82203..29e7e84 100644 --- a/manager/types.ts +++ b/manager/types.ts @@ -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 {