diff --git a/manager/components/AttendanceQuery.tsx b/manager/components/AttendanceQuery.tsx index a50bfa7..13d38ca 100644 --- a/manager/components/AttendanceQuery.tsx +++ b/manager/components/AttendanceQuery.tsx @@ -146,11 +146,31 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee } else { // Toggle existing record const newType = record.type === 'absence' ? 'presence' : 'absence'; - const modifiedRecord = { ...record, type: newType, justification: undefined, justificationAccepted: undefined }; - updatedAttendance = updatedAttendance.map(a => - a.id === record.id ? modifiedRecord : a - ); - await fetch(`/api/frequencias/${record.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modifiedRecord) }); + + // Encontrar TODOS os registros duplicados para a mesma aula e aluno + const studentId = record.studentId; + const lessonId = (record as any).lessonId; + + // Determina quais registros modificar (por lessonId ou por horário se não tiver lessonId) + const matchingIds = updatedAttendance + .filter(a => a.studentId === studentId && ((a as any).lessonId === lessonId || (!lessonId && a.date === record.date))) + .map(a => a.id); + + // Atualiza todos na memória para refletir a mudança instantaneamente + matchingIds.forEach(id => { + const r = updatedAttendance.find(a => a.id === id); + if (r) { + const modifiedRecord = { ...r, type: newType, justification: null, justificationAccepted: null }; + updatedAttendance = updatedAttendance.map(a => a.id === id ? modifiedRecord : a); + + // Dispara a atualização silenciosa para o backend de cada duplicata + fetch(`/api/frequencias/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(modifiedRecord) + }).catch(console.error); + } + }); } setDbAttendance(updatedAttendance); @@ -309,6 +329,7 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee const matchedRecord = matchingRecords.find(a => a.type === 'presence' || !a.type) || matchingRecords.find(a => a.type === 'absence' && a.justificationAccepted) || + matchingRecords.find(a => a.type === 'absence' && a.justification) || matchingRecords[0]; if (matchedRecord) { @@ -489,6 +510,7 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee const matchedRecord = matchingRecords.find(a => a.type === 'presence' || !a.type) || matchingRecords.find(a => a.type === 'absence' && a.justificationAccepted) || + matchingRecords.find(a => a.type === 'absence' && a.justification) || matchingRecords[0]; if (matchedRecord) { @@ -607,6 +629,7 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee let record = matchingRecords.find(a => a.type === 'presence' || !a.type) || matchingRecords.find(a => a.type === 'absence' && a.justificationAccepted) || + matchingRecords.find(a => a.type === 'absence' && a.justification) || matchingRecords[0]; if (!record && now >= lessonStart) { diff --git a/manager/services/database.js b/manager/services/database.js index 435bf6c..5acc238 100644 --- a/manager/services/database.js +++ b/manager/services/database.js @@ -807,7 +807,7 @@ export async function deleteAulas(ids) { // ============================================================ export async function getFrequencias() { const { rows } = await pool.query(` - SELECT *, TO_CHAR(data, 'YYYY-MM-DD"T"HH24:MI:SS') as formatted_data + SELECT *, TO_CHAR(data AT TIME ZONE 'America/Sao_Paulo', 'YYYY-MM-DD"T"HH24:MI:SS') as formatted_data FROM frequencias ORDER BY created_at DESC `); return rows.map(r => ({ @@ -837,12 +837,12 @@ export async function insertFrequencia(f) { } export async function updateFrequencia(id, f) { - // Update apenas dos campos que podem mudar + // Update apenas dos campos que podem mudar, mapeando undefined para null await pool.query( `UPDATE frequencias SET tipo = $1, justificativa = $2, justificativa_aceita = $3, verificado = $4 WHERE id = $5`, - [f.type, f.justification, f.justificationAccepted, f.verified, id] + [f.type, f.justification !== undefined ? f.justification : null, f.justificationAccepted !== undefined ? f.justificationAccepted : null, f.verified !== undefined ? f.verified : null, id] ); } diff --git a/portal/server.selfhosted.js b/portal/server.selfhosted.js index 6706543..7fac2a9 100644 --- a/portal/server.selfhosted.js +++ b/portal/server.selfhosted.js @@ -537,11 +537,12 @@ app.get('/api/portal/notas', authMiddleware, async (req, res) => { // GET /api/portal/frequencia (SQL-First — Leitura direta do PostgreSQL) app.get('/api/portal/frequencia', authMiddleware, async (req, res) => { try { - const { rows } = await pool.query( - `SELECT *, TO_CHAR(data, 'YYYY-MM-DD"T"HH24:MI:SS') as formatted_data - FROM frequencias WHERE aluno_id = $1 ORDER BY data DESC`, - [req.user.studentId] - ); + const { rows } = await pool.query(` + SELECT *, TO_CHAR(data AT TIME ZONE 'America/Sao_Paulo', 'YYYY-MM-DD"T"HH24:MI:SS') as formatted_data + FROM frequencias + WHERE aluno_id = $1 + ORDER BY created_at DESC + `, [req.user.studentId]); const attendance = rows.map(r => ({ id: r.id, diff --git a/portal/src/pages/Frequencia.tsx b/portal/src/pages/Frequencia.tsx index 5743380..439f55a 100644 --- a/portal/src/pages/Frequencia.tsx +++ b/portal/src/pages/Frequencia.tsx @@ -214,6 +214,7 @@ export default function Frequencia() { let record = matchingRecords.find(a => a.type === 'presence' || (a.verified === true && a.type !== 'absence')) || matchingRecords.find(a => a.type === 'absence' && a.justificationAccepted) || + matchingRecords.find(a => a.type === 'absence' && a.justification) || matchingRecords[0]; // Se não encontrou registro real, verificar se precisa de justificativa associada