fix: refatoracao para visualizacao de notas, provas na lixeira e listagem de funcionarios

This commit is contained in:
Sidney 2026-05-24 20:09:35 -03:00
parent bc440d7dbe
commit e33a5aac3d
8 changed files with 203 additions and 35 deletions

View File

@ -40,17 +40,17 @@ const Employees: React.FC = () => {
// Mapeamento caso a API retorne os nomes das colunas diferentes do TS // Mapeamento caso a API retorne os nomes das colunas diferentes do TS
const mappedEmployees = (empData.funcionarios || []).map((e: any) => ({ const mappedEmployees = (empData.funcionarios || []).map((e: any) => ({
id: e.id, id: e.id,
name: e.nome, name: e.name || e.nome,
cpf: e.cpf, cpf: e.cpf,
email: e.email, email: e.email,
phone: e.telefone, phone: e.phone || e.telefone,
admissionDate: e.data_admissao ? e.data_admissao.substring(0, 10) : '', admissionDate: e.hireDate || (e.data_admissao ? e.data_admissao.substring(0, 10) : ''),
categoryId: e.categoria_id categoryId: e.categoryId || e.categoria_id
})); }));
const mappedCategories = (catData.categorias || []).map((c: any) => ({ const mappedCategories = (catData.categorias || []).map((c: any) => ({
id: c.id, id: c.id,
name: c.nome name: c.name || c.nome
})); }));
setEmployees(mappedEmployees); setEmployees(mappedEmployees);

View File

@ -32,16 +32,16 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
const { provas } = await res.json(); const { provas } = await res.json();
setDbExams(provas.map((p: any) => ({ setDbExams(provas.map((p: any) => ({
id: p.id, id: p.id,
classId: p.turma_id, classId: p.classId || p.turma_id,
subjectId: p.disciplina_id, subjectId: p.subjectId || p.disciplina_id,
periodId: p.periodo_id, periodId: p.periodId || p.periodo_id,
title: p.titulo, title: p.title || p.titulo,
durationMinutes: p.duracao_minutos, durationMinutes: p.durationMinutes || p.duracao_minutos,
status: p.status, status: p.status,
allowRetake: p.permitir_refacao, allowRetake: p.allowRetake ?? p.permitir_refacao ?? false,
isDeleted: p.is_deleted, isDeleted: p.isDeleted ?? p.is_deleted ?? false,
evaluationType: p.evaluation_type || 'exam', evaluationType: p.evaluationType || p.evaluation_type || 'exam',
questions: [] // questoes carregadas sob demanda questions: p.questions || []
}))); })));
} }
} catch(e) { } catch(e) {
@ -137,7 +137,18 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
showConfirm( showConfirm(
'Mover para Lixeira', 'Mover para Lixeira',
'Tem certeza que deseja mover esta avaliação para a lixeira? Ela será ocultada para os alunos, mas as notas no boletim continuarão intactas.', 'Tem certeza que deseja mover esta avaliação para a lixeira? Ela será ocultada para os alunos, mas as notas no boletim continuarão intactas.',
() => { async () => {
try {
const targetExam = exams.find(e => e.id === examId);
if (targetExam) {
await fetch(`/api/provas/${examId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...targetExam, isDeleted: true })
});
}
} catch (e) { console.error('Erro ao deletar prova:', e); }
const updatedExams = exams.map(e => e.id === examId ? { ...e, isDeleted: true } : e); const updatedExams = exams.map(e => e.id === examId ? { ...e, isDeleted: true } : e);
updateData({ exams: updatedExams }); updateData({ exams: updatedExams });
dbService.saveData({ ...data, exams: updatedExams }); dbService.saveData({ ...data, exams: updatedExams });
@ -146,7 +157,18 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
); );
}; };
const handleRestoreExam = (examId: string) => { const handleRestoreExam = async (examId: string) => {
try {
const targetExam = exams.find(e => e.id === examId);
if (targetExam) {
await fetch(`/api/provas/${examId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...targetExam, isDeleted: false })
});
}
} catch (e) { console.error('Erro ao restaurar prova:', e); }
const updatedExams = exams.map(e => e.id === examId ? { ...e, isDeleted: false } : e); const updatedExams = exams.map(e => e.id === examId ? { ...e, isDeleted: false } : e);
updateData({ exams: updatedExams }); updateData({ exams: updatedExams });
dbService.saveData({ ...data, exams: updatedExams }); dbService.saveData({ ...data, exams: updatedExams });

View File

@ -43,15 +43,18 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
const [dbClasses, setDbClasses] = useState<Class[]>(data.classes || []); const [dbClasses, setDbClasses] = useState<Class[]>(data.classes || []);
const [dbCourses, setDbCourses] = useState<any[]>(data.courses || []); const [dbCourses, setDbCourses] = useState<any[]>(data.courses || []);
const [dbExams, setDbExams] = useState<any[]>(data.exams || []);
const loadClassesAndCourses = async () => { const loadClassesAndCourses = async () => {
try { try {
const [clsRes, crsRes] = await Promise.all([ const [clsRes, crsRes, examsRes] = await Promise.all([
fetch('/api/turmas'), fetch('/api/turmas'),
fetch('/api/cursos') fetch('/api/cursos'),
fetch('/api/provas')
]); ]);
const clsData = await clsRes.json(); const clsData = await clsRes.json();
const crsData = await crsRes.json(); const crsData = await crsRes.json();
const examsData = await examsRes.json();
if (clsData.turmas) { if (clsData.turmas) {
setDbClasses(clsData.turmas.map((t: any) => ({ setDbClasses(clsData.turmas.map((t: any) => ({
@ -63,8 +66,19 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
if (crsData.cursos) { if (crsData.cursos) {
setDbCourses(crsData.cursos); setDbCourses(crsData.cursos);
} }
if (examsData.provas) {
setDbExams(examsData.provas.map((p: any) => ({
id: p.id,
classId: p.classId || p.turma_id,
subjectId: p.subjectId || p.disciplina_id,
periodId: p.periodId || p.periodo_id,
title: p.title || p.titulo,
evaluationType: p.evaluationType || p.evaluation_type || 'exam',
isDeleted: p.isDeleted ?? p.is_deleted ?? false
})));
}
} catch(e) { } catch(e) {
console.error('Erro ao buscar turmas/cursos:', e); console.error('Erro ao buscar dados:', e);
} }
}; };
@ -255,10 +269,9 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
initialGrades[subject.id] = {}; initialGrades[subject.id] = {};
periods.forEach(period => { periods.forEach(period => {
const periodGrades: any = {}; const periodGrades: any = {};
const linkedExams = (data.exams || []).filter(e => const linkedExams = (dbExams || []).filter(e =>
String(e.subjectId).trim() === String(subject.id).trim() && String(e.subjectId).trim() === String(subject.id).trim() &&
String(e.periodId).trim() === String(period.id).trim() && String(e.periodId).trim() === String(period.id).trim()
!!subsMap[String(e.id).trim()]
); );
if (linkedExams.length > 0) { if (linkedExams.length > 0) {
@ -653,8 +666,8 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
) : ( ) : (
<div className="space-y-6"> <div className="space-y-6">
{subjects.map(subject => { {subjects.map(subject => {
// Encontrar provas vinculadas a esta disciplina // Encontrar provas ativas vinculadas a esta disciplina
const linkedExams = (data.exams || []).filter(e => String(e.subjectId).trim() === String(subject.id).trim()); const linkedExams = (dbExams || []).filter(e => String(e.subjectId).trim() === String(subject.id).trim());
return ( return (
<div key={subject.id} className="bg-slate-50 rounded-2xl p-6 border border-slate-100 space-y-4"> <div key={subject.id} className="bg-slate-50 rounded-2xl p-6 border border-slate-100 space-y-4">
@ -665,9 +678,8 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{(() => { {(() => {
const linkedExams = (data.exams || []).filter(e => const linkedExams = (dbExams || []).filter(e =>
String(e.subjectId).trim() === String(subject.id).trim() && String(e.subjectId).trim() === String(subject.id).trim()
!!studentSubmissions[String(e.id).trim()]
); );
const provasCount = linkedExams.filter(e => (e as any).evaluationType !== 'activity').length; const provasCount = linkedExams.filter(e => (e as any).evaluationType !== 'activity').length;
const atividadesCount = linkedExams.filter(e => (e as any).evaluationType === 'activity').length; const atividadesCount = linkedExams.filter(e => (e as any).evaluationType === 'activity').length;
@ -705,10 +717,9 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
</div> </div>
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{periods.map(period => { {periods.map(period => {
const linkedExams = (data.exams || []).filter(e => const linkedExams = (dbExams || []).filter(e =>
String(e.subjectId).trim() === String(subject.id).trim() && String(e.subjectId).trim() === String(subject.id).trim() &&
String(e.periodId).trim() === String(period.id).trim() && String(e.periodId).trim() === String(period.id).trim()
!!studentSubmissions[String(e.id).trim()]
); );
const periodGrades = studentGrades[subject.id]?.[period.id] || {}; const periodGrades = studentGrades[subject.id]?.[period.id] || {};
const validPeriodValues = Object.values(periodGrades).filter(v => v !== ''); const validPeriodValues = Object.values(periodGrades).filter(v => v !== '');

View File

@ -0,0 +1,8 @@
import fs from 'fs';
const content = fs.readFileSync('manager/components/Students.tsx', 'utf8');
const lines = content.split('\n');
lines.forEach((l, i) => {
if (l.includes('Alunos')) {
console.log(`[${i+1}] ${l.trim()}`);
}
});

View File

@ -0,0 +1,8 @@
import fs from 'fs';
const content = fs.readFileSync('manager/components/ReportCard.tsx', 'utf8');
const lines = content.split('\n');
lines.forEach((l, i) => {
if (l.toLowerCase().includes('exam') || l.toLowerCase().includes('prova')) {
console.log(`[${i+1}] ${l.trim()}`);
}
});

View File

@ -0,0 +1,8 @@
import fs from 'fs';
const content = fs.readFileSync('manager/services/database.js', 'utf8');
const lines = content.split('\n');
lines.forEach((l, i) => {
if (l.toLowerCase().includes('getalunos')) {
console.log(`[${i+1}] ${l.trim()}`);
}
});

View File

@ -0,0 +1,61 @@
const { Pool } = require('pg');
const pool = new Pool({
host: '150.230.87.131',
port: 5432,
database: 'edumanager',
user: 'edumanager',
password: 'EduManager2026!Seguro',
ssl: false
});
async function migrateFuncionarios() {
const client = await pool.connect();
try {
const { rows } = await client.query('SELECT data FROM school_data LIMIT 1');
if (!rows.length) { console.log('school_data vazio!'); return; }
const schoolData = rows[0].data;
const categories = schoolData.employeeCategories || [];
console.log(`\n📄 Migrando ${categories.length} categorias...`);
let catOk = 0;
for (const c of categories) {
try {
await client.query(
`INSERT INTO categorias_funcionarios (id, nome) VALUES ($1, $2) ON CONFLICT (id) DO NOTHING`,
[c.id, c.name]
);
catOk++;
} catch (e) {
console.warn(` ⚠️ Categoria ${c.id}: ${e.message}`);
}
}
console.log(`${catOk}/${categories.length} categorias migradas!`);
const employees = schoolData.employees || [];
console.log(`\n👥 Migrando ${employees.length} funcionarios...`);
let empOk = 0;
for (const e of employees) {
try {
await client.query(
`INSERT INTO funcionarios (id, nome, email, telefone, data_admissao, cpf, categoria_id)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (id) DO NOTHING`,
[e.id, e.name, e.email || '', e.phone || '', e.hireDate || null, e.cpf || '', e.categoryId || null]
);
empOk++;
} catch (err) {
console.warn(` ⚠️ Funcionario ${e.id}: ${err.message}`);
}
}
console.log(`${empOk}/${employees.length} funcionarios migrados!`);
} catch (err) {
console.error('❌ ERRO:', err);
} finally {
client.release();
await pool.end();
}
}
migrateFuncionarios();

View File

@ -544,12 +544,25 @@ export async function deleteDisciplina(id) {
// ============================================================ // ============================================================
export async function getFuncionarios() { export async function getFuncionarios() {
const { rows } = await pool.query('SELECT * FROM funcionarios ORDER BY nome ASC'); const { rows } = await pool.query('SELECT * FROM funcionarios ORDER BY nome ASC');
return rows; return rows.map(r => ({
id: r.id,
name: r.nome,
cpf: r.cpf,
email: r.email,
phone: r.telefone,
categoryId: r.categoria_id,
hireDate: r.data_admissao,
createdAt: r.created_at
}));
} }
export async function getCategoriasFuncionarios() { export async function getCategoriasFuncionarios() {
const { rows } = await pool.query('SELECT * FROM categorias_funcionarios ORDER BY nome ASC'); const { rows } = await pool.query('SELECT * FROM categorias_funcionarios ORDER BY nome ASC');
return rows; return rows.map(r => ({
id: r.id,
name: r.nome,
createdAt: r.created_at
}));
} }
export async function insertFuncionario(f) { export async function insertFuncionario(f) {
@ -609,7 +622,16 @@ export async function deleteCategoriaFuncionario(id) {
// ============================================================ // ============================================================
export async function getAlunos() { export async function getAlunos() {
const result = await pool.query("SELECT * FROM alunos ORDER BY nome ASC"); const result = await pool.query("SELECT * FROM alunos ORDER BY nome ASC");
return result.rows; return result.rows.map(r => ({
...r,
classId: r.turma_id,
name: r.nome,
status: r.status,
cpf: r.cpf,
phone: r.telefone,
registrationDate: r.data_matricula,
contractTemplateId: r.modelo_contrato_id
}));
} }
export async function insertAluno(a) { export async function insertAluno(a) {
@ -775,8 +797,36 @@ export async function deleteAulas(ids) {
// PROVAS & QUESTÕES (FASE 5) // PROVAS & QUESTÕES (FASE 5)
// ============================================================ // ============================================================
export async function getProvas() { export async function getProvas() {
const result = await pool.query('SELECT * FROM provas ORDER BY created_at DESC'); const { rows: provasRows } = await pool.query('SELECT * FROM provas ORDER BY created_at DESC');
return result.rows;
// Mapear campos para camelCase e buscar questoes para compatibilidade com o frontend antigo
const provasFormatadas = [];
for (const p of provasRows) {
const { rows: questoesRows } = await pool.query('SELECT * FROM questoes_provas WHERE prova_id = $1 ORDER BY ordem ASC', [p.id]);
provasFormatadas.push({
id: p.id,
classId: p.turma_id,
subjectId: p.disciplina_id,
periodId: p.periodo_id,
title: p.titulo,
durationMinutes: p.duracao_minutos,
status: p.status,
allowRetake: p.permitir_refacao,
isDeleted: p.is_deleted,
evaluationType: p.evaluation_type || 'exam',
questions: questoesRows.map(q => ({
id: q.id,
examId: q.prova_id,
text: q.texto,
options: q.opcoes || [],
correctAnswer: q.resposta_correta,
order: q.ordem,
imageUrl: q.url_imagem
}))
});
}
return provasFormatadas;
} }
export async function getQuestoesDaProva(provaId) { export async function getQuestoesDaProva(provaId) {