fix: memory leak, syntax errors, typescript cleanup and grade interface
This commit is contained in:
parent
f52c5084c6
commit
74216f170d
|
|
@ -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] **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] **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] **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.
|
- [ ] 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)
|
### 💳 Módulo Financeiro (Portal do Aluno)
|
||||||
|
|
|
||||||
|
|
@ -136,11 +136,11 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
|
||||||
|
|
||||||
if (linkedExams.length > 0) {
|
if (linkedExams.length > 0) {
|
||||||
linkedExams.forEach(exam => {
|
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 : '';
|
periodGrades[exam.id] = existingGrade ? existingGrade.value : '';
|
||||||
});
|
});
|
||||||
} else {
|
} 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 : '';
|
periodGrades['direct'] = existingGrade ? existingGrade.value : '';
|
||||||
}
|
}
|
||||||
initialGrades[subject.id][period.id] = periodGrades;
|
initialGrades[subject.id][period.id] = periodGrades;
|
||||||
|
|
@ -187,7 +187,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
|
||||||
const periodSums: number[] = [];
|
const periodSums: number[] = [];
|
||||||
|
|
||||||
Object.values(subjectPeriods).forEach((examValues: any) => {
|
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 !== '')) {
|
if (Object.values(examValues).some(v => v !== '')) {
|
||||||
periodSums.push(sum);
|
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">
|
<div className="px-3 py-1 bg-white border border-slate-200 rounded-lg text-[10px] font-black text-slate-500">
|
||||||
MÉDIA: {(() => {
|
MÉDIA: {(() => {
|
||||||
const subjectGrades = studentGrades[subject.id] || {};
|
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);
|
const valid = pSums.filter(s => s > 0);
|
||||||
return valid.length > 0 ? (valid.reduce((a, b) => a + b, 0) / valid.length).toFixed(1) : '0.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 => {
|
{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 => e.subjectId === subject.id && e.periodId === period.id && e.status === 'published');
|
||||||
const periodGrades = studentGrades[subject.id]?.[period.id] || {};
|
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 (
|
return (
|
||||||
<div key={period.id} className="bg-white border border-slate-200 rounded-xl p-4 shadow-sm space-y-3 relative">
|
<div key={period.id} className="bg-white border border-slate-200 rounded-xl p-4 shadow-sm space-y-3 relative">
|
||||||
|
|
|
||||||
|
|
@ -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 handleLogoUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
|
|
|
||||||
|
|
@ -185,10 +185,6 @@ app.put('/api/school-data', async (req, res) => {
|
||||||
END $$;
|
END $$;
|
||||||
`).catch(err => console.error('[PostgreSQL] Erro ao inicializar colunas de automação:', err));
|
`).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();
|
schoolData.lastUpdated = new Date().toISOString();
|
||||||
await saveSchoolData(schoolData);
|
await saveSchoolData(schoolData);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
|
|
@ -262,7 +258,7 @@ app.get('/api/database/tables/:tableName/data', async (req, res) => {
|
||||||
const result = await pool.query(query);
|
const result = await pool.query(query);
|
||||||
res.json({ rows: result.rows, fields: result.fields.map(f => f.name) });
|
res.json({ rows: result.rows, fields: result.fields.map(f => f.name) });
|
||||||
} catch (error) {
|
} 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 });
|
res.status(500).json({ error: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -912,14 +908,6 @@ app.get('/api/alunos/:id/carne', async (req, res) => {
|
||||||
// INICIALIZAÇÃO
|
// INICIALIZAÇÃO
|
||||||
// ============================================================
|
// ============================================================
|
||||||
async function startServer() {
|
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
|
// Disparo Manual de Inadimplência e Lembretes
|
||||||
app.post('/api/disparar_cobrancas', async (req, res) => {
|
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.' }); }
|
} 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}`));
|
app.listen(PORT, '0.0.0.0', () => console.log(`🚀 EduManager Self-Hosted na porta ${PORT}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,7 @@ export interface Grade {
|
||||||
subjectId: string;
|
subjectId: string;
|
||||||
value: number;
|
value: number;
|
||||||
period: string; // e.g., "1º Bimestre", "Final"
|
period: string; // e.g., "1º Bimestre", "Final"
|
||||||
|
examId?: string; // Vincula a nota a uma avaliação específica
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Handout {
|
export interface Handout {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue