fix(attendance): corrige timezone shift (Rule 25), priorizacao de justificativas sobre faltas vazias e resolve duplicatas no toggle do manager
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m10s
Details
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m10s
Details
This commit is contained in:
parent
18c635d72a
commit
1d56f91d5a
|
|
@ -146,11 +146,31 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ 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<AttendanceQueryProps> = ({ 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<AttendanceQueryProps> = ({ 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<AttendanceQueryProps> = ({ 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) {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue