fix(attendance/enrollment): resolve attendance shifts rendering presences as absences and finalize dynamic pre-matricula forms
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m18s
Details
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m18s
Details
This commit is contained in:
parent
1d56f91d5a
commit
6530a81ef6
|
|
@ -213,8 +213,17 @@ const Classes: React.FC<ClassesProps> = ({ data, updateData, onNavigateToClass }
|
|||
body: JSON.stringify({ aulas: classLessons })
|
||||
}).catch(e => console.warn('Erro ao salvar aulas no SQL:', e));
|
||||
}
|
||||
updateData({ lessons: updatedLessons });
|
||||
dbService.saveData({ ...data, lessons: updatedLessons });
|
||||
|
||||
// Sincroniza também a lista global de turmas para o JSON não deletá-la na sync reversa
|
||||
let updatedClasses = [...(data.classes || [])];
|
||||
if (editingClass) {
|
||||
updatedClasses = updatedClasses.map(c => c.id === editingClass.id ? newClass : c);
|
||||
} else {
|
||||
updatedClasses.push(newClass);
|
||||
}
|
||||
|
||||
updateData({ classes: updatedClasses, lessons: updatedLessons });
|
||||
dbService.saveData({ ...data, classes: updatedClasses, lessons: updatedLessons });
|
||||
|
||||
await loadData();
|
||||
closeModal();
|
||||
|
|
@ -250,6 +259,14 @@ const Classes: React.FC<ClassesProps> = ({ data, updateData, onNavigateToClass }
|
|||
try {
|
||||
const res = await fetch(`/api/turmas/${id}`, { method: 'DELETE' });
|
||||
if (!res.ok) throw new Error('Failed to delete');
|
||||
|
||||
// Remove a turma e suas aulas associadas do estado global/JSON para manter sincronizado
|
||||
const updatedClasses = (data.classes || []).filter(c => c.id !== id);
|
||||
const updatedLessons = (data.lessons || []).filter(l => l.classId !== id);
|
||||
|
||||
updateData({ classes: updatedClasses, lessons: updatedLessons });
|
||||
dbService.saveData({ ...data, classes: updatedClasses, lessons: updatedLessons });
|
||||
|
||||
await loadData();
|
||||
} catch (err) {
|
||||
showAlert('Erro', 'Ocorreu um erro ao deletar a turma.', 'error');
|
||||
|
|
|
|||
|
|
@ -91,6 +91,12 @@ const Courses: React.FC<CoursesProps> = ({ data, updateData }) => {
|
|||
};
|
||||
|
||||
try {
|
||||
const finalCourseId = editingCourse ? editingCourse.id : crypto.randomUUID();
|
||||
const finalCourse: Course = {
|
||||
...finalData,
|
||||
id: finalCourseId
|
||||
};
|
||||
|
||||
if (editingCourse) {
|
||||
const res = await fetch(`/api/cursos/${editingCourse.id}`, {
|
||||
method: 'PUT',
|
||||
|
|
@ -102,10 +108,22 @@ const Courses: React.FC<CoursesProps> = ({ data, updateData }) => {
|
|||
const res = await fetch('/api/cursos', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...payload, id: crypto.randomUUID() })
|
||||
body: JSON.stringify({ ...payload, id: finalCourseId })
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed');
|
||||
}
|
||||
|
||||
// Sincroniza a lista global de cursos para o JSON
|
||||
let updatedCourses = [...(data.courses || [])];
|
||||
if (editingCourse) {
|
||||
updatedCourses = updatedCourses.map(c => c.id === editingCourse.id ? finalCourse : c);
|
||||
} else {
|
||||
updatedCourses.push(finalCourse);
|
||||
}
|
||||
|
||||
updateData({ courses: updatedCourses });
|
||||
dbService.saveData({ ...data, courses: updatedCourses });
|
||||
|
||||
await loadData();
|
||||
closeModal();
|
||||
} catch (err) {
|
||||
|
|
@ -152,6 +170,12 @@ const Courses: React.FC<CoursesProps> = ({ data, updateData }) => {
|
|||
try {
|
||||
const res = await fetch(`/api/cursos/${id}`, { method: 'DELETE' });
|
||||
if (!res.ok) throw new Error('Failed to delete');
|
||||
|
||||
// Sincroniza a remoção do curso no estado global/JSON
|
||||
const updatedCourses = (data.courses || []).filter(c => c.id !== id);
|
||||
updateData({ courses: updatedCourses });
|
||||
dbService.saveData({ ...data, courses: updatedCourses });
|
||||
|
||||
await loadData();
|
||||
} catch (err) {
|
||||
showAlert('Erro', 'Ocorreu um erro ao excluir o curso.', 'error');
|
||||
|
|
|
|||
|
|
@ -189,7 +189,12 @@ const PreMatricula: React.FC<Props> = ({ data, onConvert }) => {
|
|||
const rows = filtered.map(i => [
|
||||
i.nome, i.email, i.telefone, i.turmaNome, i.status,
|
||||
new Date(i.createdAt).toLocaleDateString('pt-BR'),
|
||||
...Array.from(allKeys).map(k => (i.respostas || {})[k] || '')
|
||||
...Array.from(allKeys).map(k => {
|
||||
const val = (i.respostas || {})[k] || '';
|
||||
return typeof val === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(val)
|
||||
? val.split('-').reverse().join('/')
|
||||
: val;
|
||||
})
|
||||
]);
|
||||
const csv = [headers.join(';'), ...rows.map(r => r.join(';'))].join('\n');
|
||||
const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
|
||||
|
|
@ -813,7 +818,33 @@ const PreMatricula: React.FC<Props> = ({ data, onConvert }) => {
|
|||
📄 Ver Documento
|
||||
</a>
|
||||
) : (
|
||||
<span className="text-slate-800 font-semibold truncate">{String(value)}</span>
|
||||
<span className="text-slate-800 font-semibold truncate">
|
||||
{(() => {
|
||||
if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
||||
const dateFormatted = value.split('-').reverse().join('/');
|
||||
const isBirthDateField = label.toLowerCase().includes('nasc') || label.toLowerCase().includes('nascimento');
|
||||
if (isBirthDateField) {
|
||||
const parts = value.split('-');
|
||||
const birthYear = parseInt(parts[0], 10);
|
||||
const birthMonth = parseInt(parts[1], 10);
|
||||
const birthDay = parseInt(parts[2], 10);
|
||||
|
||||
// Forçando fuso brasileiro para calcular a idade exata hoje (Regra 45)
|
||||
const todayStr = new Date().toLocaleString('en-US', { timeZone: 'America/Sao_Paulo' });
|
||||
const today = new Date(todayStr);
|
||||
|
||||
let age = today.getFullYear() - birthYear;
|
||||
const monthDiff = (today.getMonth() + 1) - birthMonth;
|
||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDay)) {
|
||||
age--;
|
||||
}
|
||||
return `${dateFormatted} (${age} anos)`;
|
||||
}
|
||||
return dateFormatted;
|
||||
}
|
||||
return String(value);
|
||||
})()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -807,7 +807,7 @@ export async function deleteAulas(ids) {
|
|||
// ============================================================
|
||||
export async function getFrequencias() {
|
||||
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
|
||||
SELECT *, TO_CHAR(data, 'YYYY-MM-DD"T"HH24:MI:SS') as formatted_data
|
||||
FROM frequencias ORDER BY created_at DESC
|
||||
`);
|
||||
return rows.map(r => ({
|
||||
|
|
|
|||
|
|
@ -538,7 +538,7 @@ app.get('/api/portal/notas', authMiddleware, async (req, res) => {
|
|||
app.get('/api/portal/frequencia', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
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
|
||||
SELECT *, TO_CHAR(data, 'YYYY-MM-DD"T"HH24:MI:SS') as formatted_data
|
||||
FROM frequencias
|
||||
WHERE aluno_id = $1
|
||||
ORDER BY created_at DESC
|
||||
|
|
|
|||
Loading…
Reference in New Issue