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 {
|
} else {
|
||||||
// Toggle existing record
|
// Toggle existing record
|
||||||
const newType = record.type === 'absence' ? 'presence' : 'absence';
|
const newType = record.type === 'absence' ? 'presence' : 'absence';
|
||||||
const modifiedRecord = { ...record, type: newType, justification: undefined, justificationAccepted: undefined };
|
|
||||||
updatedAttendance = updatedAttendance.map(a =>
|
// Encontrar TODOS os registros duplicados para a mesma aula e aluno
|
||||||
a.id === record.id ? modifiedRecord : a
|
const studentId = record.studentId;
|
||||||
);
|
const lessonId = (record as any).lessonId;
|
||||||
await fetch(`/api/frequencias/${record.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modifiedRecord) });
|
|
||||||
|
// 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);
|
setDbAttendance(updatedAttendance);
|
||||||
|
|
@ -309,6 +329,7 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
|
|
||||||
const matchedRecord = matchingRecords.find(a => a.type === 'presence' || !a.type) ||
|
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.justificationAccepted) ||
|
||||||
|
matchingRecords.find(a => a.type === 'absence' && a.justification) ||
|
||||||
matchingRecords[0];
|
matchingRecords[0];
|
||||||
|
|
||||||
if (matchedRecord) {
|
if (matchedRecord) {
|
||||||
|
|
@ -489,6 +510,7 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
|
|
||||||
const matchedRecord = matchingRecords.find(a => a.type === 'presence' || !a.type) ||
|
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.justificationAccepted) ||
|
||||||
|
matchingRecords.find(a => a.type === 'absence' && a.justification) ||
|
||||||
matchingRecords[0];
|
matchingRecords[0];
|
||||||
|
|
||||||
if (matchedRecord) {
|
if (matchedRecord) {
|
||||||
|
|
@ -607,6 +629,7 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
|
|
||||||
let record = matchingRecords.find(a => a.type === 'presence' || !a.type) ||
|
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.justificationAccepted) ||
|
||||||
|
matchingRecords.find(a => a.type === 'absence' && a.justification) ||
|
||||||
matchingRecords[0];
|
matchingRecords[0];
|
||||||
|
|
||||||
if (!record && now >= lessonStart) {
|
if (!record && now >= lessonStart) {
|
||||||
|
|
|
||||||
|
|
@ -807,7 +807,7 @@ export async function deleteAulas(ids) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
export async function getFrequencias() {
|
export async function getFrequencias() {
|
||||||
const { rows } = await pool.query(`
|
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
|
FROM frequencias ORDER BY created_at DESC
|
||||||
`);
|
`);
|
||||||
return rows.map(r => ({
|
return rows.map(r => ({
|
||||||
|
|
@ -837,12 +837,12 @@ export async function insertFrequencia(f) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateFrequencia(id, 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(
|
await pool.query(
|
||||||
`UPDATE frequencias
|
`UPDATE frequencias
|
||||||
SET tipo = $1, justificativa = $2, justificativa_aceita = $3, verificado = $4
|
SET tipo = $1, justificativa = $2, justificativa_aceita = $3, verificado = $4
|
||||||
WHERE id = $5`,
|
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)
|
// GET /api/portal/frequencia (SQL-First — Leitura direta do PostgreSQL)
|
||||||
app.get('/api/portal/frequencia', authMiddleware, async (req, res) => {
|
app.get('/api/portal/frequencia', authMiddleware, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { rows } = await pool.query(
|
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 WHERE aluno_id = $1 ORDER BY data DESC`,
|
FROM frequencias
|
||||||
[req.user.studentId]
|
WHERE aluno_id = $1
|
||||||
);
|
ORDER BY created_at DESC
|
||||||
|
`, [req.user.studentId]);
|
||||||
|
|
||||||
const attendance = rows.map(r => ({
|
const attendance = rows.map(r => ({
|
||||||
id: r.id,
|
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')) ||
|
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.justificationAccepted) ||
|
||||||
|
matchingRecords.find(a => a.type === 'absence' && a.justification) ||
|
||||||
matchingRecords[0];
|
matchingRecords[0];
|
||||||
|
|
||||||
// Se não encontrou registro real, verificar se precisa de justificativa associada
|
// Se não encontrou registro real, verificar se precisa de justificativa associada
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue