feat: Contratos 100% SQL-First + cache-buster em todos os módulos + persistência de aba ativa no F5
This commit is contained in:
parent
15a7a9fef2
commit
1dc753c9c9
|
|
@ -25,9 +25,9 @@
|
||||||
| **Funcionários** | `employees`, `employeeCategories` | `funcionarios`, `categorias_funcionarios` | SQL Estruturado (`/api/funcionarios`) | SQL Estruturado (`POST`/`PUT`/`DELETE` `/api/funcionarios`) | Não aplicável | 🟢 **100% SQL-First** — Totalmente independente do JSON. |
|
| **Funcionários** | `employees`, `employeeCategories` | `funcionarios`, `categorias_funcionarios` | SQL Estruturado (`/api/funcionarios`) | SQL Estruturado (`POST`/`PUT`/`DELETE` `/api/funcionarios`) | Não aplicável | 🟢 **100% SQL-First** — Totalmente independente do JSON. |
|
||||||
| **Boletim (Notas)** | `grades`, `periods` | `notas_boletim`, `periodos` | SQL Estruturado (`/api/notas`) | SQL Estruturado (`/api/notas`) | SQL Estruturado (Arithmetic Mean direto do SQL) | 🟢 **100% SQL-First** |
|
| **Boletim (Notas)** | `grades`, `periods` | `notas_boletim`, `periodos` | SQL Estruturado (`/api/notas`) | SQL Estruturado (`/api/notas`) | SQL Estruturado (Arithmetic Mean direto do SQL) | 🟢 **100% SQL-First** |
|
||||||
| **Avaliações (Provas)** | `exams` | `provas`, `questoes_provas` | SQL Estruturado (`GET /api/provas` → estado `dbExams`) | SQL Estruturado (`POST`/`PUT`/`DELETE` `/api/provas` com sync de questões) | SQL Estruturado (Tabelas `provas` e `questoes_provas`) | 🟢 **100% SQL-First** — Zero chamadas `dbService.saveData` no frontend. Questões sincronizadas inline. Reverse-sync automático no backend. |
|
| **Avaliações (Provas)** | `exams` | `provas`, `questoes_provas` | SQL Estruturado (`GET /api/provas` → estado `dbExams`) | SQL Estruturado (`POST`/`PUT`/`DELETE` `/api/provas` com sync de questões) | SQL Estruturado (Tabelas `provas` e `questoes_provas`) | 🟢 **100% SQL-First** — Zero chamadas `dbService.saveData` no frontend. Questões sincronizadas inline. Reverse-sync automático no backend. |
|
||||||
| **Frequências (Chamadas)** | `attendance` | `frequencias` | Híbrido (Lê contexto JSON do Manager) | SQL Estruturado (`POST /api/frequencias`) + Atualiza local | SQL Estruturado (Query direta no Postgres) | 🟡 **Híbrido / Em Transição** — Backend SQL-First; frontend do Manager sincroniza em duas vias. |
|
| **Frequências (Chamadas)** | `attendance` | `frequencias` | SQL Estruturado (`GET /api/frequencias` → estado `dbAttendance`) | SQL Estruturado (`POST`/`PUT`/`DELETE` `/api/frequencias`) | SQL Estruturado (Query direta no Postgres) | 🟢 **100% SQL-First** — Zero chamadas `dbService.saveData` no frontend. Reverse-sync automático no backend. |
|
||||||
| **Aulas e Diários** | `lessons` | `aulas` | Híbrido (Lê contexto JSON do Manager) | SQL Estruturado (`/api/aulas/lote`) + Atualiza local | SQL Estruturado (Query direta no Postgres) | 🟡 **Híbrido / Em Transição** — Backend SQL-First; Frontend ainda acoplado ao contexto JSON. |
|
| **Aulas e Diários** | `lessons` | `aulas` | SQL Estruturado (`GET /api/aulas` → estado `dbLessons`) | SQL Estruturado (`POST`/`DELETE` `/api/aulas/lote`) | SQL Estruturado (Query direta no Postgres) | 🟢 **100% SQL-First** — Zero chamadas `dbService.saveData` no frontend. Reverse-sync automático no backend. |
|
||||||
| **Contratos** | `contracts`, `contractTemplates` | `contratos`, `modelos_contrato` | Híbrido (Lê contexto JSON) | Híbrido (Escrita via JSON com trigger de sync no banco) | SQL Estruturado (Lê de `contratos`) | 🟡 **Híbrido / Em Transição** |
|
| **Contratos** | `contracts`, `contractTemplates` | `contratos`, `modelos_contrato` | SQL Estruturado (`GET /api/contratos` e `GET /api/modelos-contrato`) | SQL Estruturado (`POST`/`PUT`/`DELETE`) | SQL Estruturado (Lê de `contratos`) | 🟢 **100% SQL-First** — Zero chamadas `dbService.saveData` no frontend. Reverse-sync no backend. |
|
||||||
| **Configurações Globais** | `profile`, `evolutionConfig`, `messageTemplates` | `configuracoes` | Pendente (Lê contexto JSON) | Pendente (Escreve via `dbService.saveData` no JSON) | Pendente (Lê do JSON) | 🔴 **Pendente** — Último bloco a ser migrado. |
|
| **Configurações Globais** | `profile`, `evolutionConfig`, `messageTemplates` | `configuracoes` | Pendente (Lê contexto JSON) | Pendente (Escreve via `dbService.saveData` no JSON) | Pendente (Lê do JSON) | 🔴 **Pendente** — Último bloco a ser migrado. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -36,18 +36,15 @@
|
||||||
|
|
||||||
| Status | Qtd. Módulos | Módulos |
|
| Status | Qtd. Módulos | Módulos |
|
||||||
|:---|:---:|:---|
|
|:---|:---:|:---|
|
||||||
| 🟢 100% SQL-First | **5** | Alunos, Financeiro, Funcionários, Boletim, Avaliações |
|
| 🟢 100% SQL-First | **8** | Alunos, Financeiro, Funcionários, Boletim, Avaliações, Frequências, Aulas, Contratos |
|
||||||
| 🟡 Híbrido | **3** | Frequências, Aulas, Contratos |
|
| 🟡 Híbrido | **0** | Nenhum |
|
||||||
| 🔴 Pendente | **1** | Configurações Globais |
|
| 🔴 Pendente | **1** | Configurações Globais |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Próximos Passos (Ordem Sugerida de Prioridade)
|
## Próximos Passos (Ordem Sugerida de Prioridade)
|
||||||
|
|
||||||
1. **Frequências (Chamadas)** — Frontend do Manager (`AttendanceQuery.tsx` / `LessonSchedule.tsx`) precisa ler do estado `dbAttendance` via SQL ao invés do contexto JSON `data.attendance`.
|
1. **Configurações Globais** — Criar tabela `configuracoes` no PostgreSQL e migrar `profile`, `evolutionConfig`, `messageTemplates` com rotas CRUD dedicadas.
|
||||||
2. **Aulas e Diários** — Frontend do Manager (`LessonSchedule.tsx`) precisa ler aulas do estado SQL (`dbLessons`) e eliminar a dependência de `data.lessons`.
|
|
||||||
3. **Contratos** — Frontend do Manager (`Contracts.tsx`) precisa ler/gravar via `/api/contratos` e eliminar `dbService.saveData`.
|
|
||||||
4. **Configurações Globais** — Criar tabela `configuracoes` no PostgreSQL e migrar `profile`, `evolutionConfig`, `messageTemplates` com rotas CRUD dedicadas.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,38 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
const [currentJustificationText, setCurrentJustificationText] = useState('');
|
const [currentJustificationText, setCurrentJustificationText] = useState('');
|
||||||
const [currentRecordForJustification, setCurrentRecordForJustification] = useState<Attendance | null>(null);
|
const [currentRecordForJustification, setCurrentRecordForJustification] = useState<Attendance | null>(null);
|
||||||
|
|
||||||
|
const [dbAttendance, setDbAttendance] = useState<Attendance[]>(data.attendance || []);
|
||||||
|
|
||||||
|
const loadAttendance = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/frequencias');
|
||||||
|
if (res.ok) {
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.frequencias) {
|
||||||
|
setDbAttendance(json.frequencias.map((r: any) => ({
|
||||||
|
id: r.id,
|
||||||
|
studentId: r.studentId || r.aluno_id,
|
||||||
|
classId: r.classId || r.turma_id,
|
||||||
|
lessonId: r.lessonId || r.aula_id,
|
||||||
|
date: r.date || r.data,
|
||||||
|
photo: r.photo || r.foto || r.foto_url,
|
||||||
|
verified: r.verified ?? r.verificado ?? false,
|
||||||
|
type: r.type || r.tipo || 'presence',
|
||||||
|
justification: r.justification || r.justificativa,
|
||||||
|
justificationAccepted: r.justificationAccepted ?? r.justificativa_aceita ?? false,
|
||||||
|
createdAt: r.createdAt || r.created_at
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Erro ao carregar frequencias do SQL:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadAttendance();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Helper para normalizar URLs de fotos (vacina contra cache antigo)
|
// Helper para normalizar URLs de fotos (vacina contra cache antigo)
|
||||||
const normalizePhotoUrl = (url?: string) => {
|
const normalizePhotoUrl = (url?: string) => {
|
||||||
if (!url || typeof url !== 'string') return '';
|
if (!url || typeof url !== 'string') return '';
|
||||||
|
|
@ -72,8 +104,8 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleAttendanceStatus = (record: any) => {
|
const toggleAttendanceStatus = async (record: any) => {
|
||||||
let updatedAttendance = [...(data.attendance || [])];
|
let updatedAttendance = [...dbAttendance];
|
||||||
|
|
||||||
if (record.isVirtual) {
|
if (record.isVirtual) {
|
||||||
// Ação do botão do Admin: criar o registro real a partir do virtual
|
// Ação do botão do Admin: criar o registro real a partir do virtual
|
||||||
|
|
@ -106,10 +138,10 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
|
|
||||||
if (existingIdx >= 0) {
|
if (existingIdx >= 0) {
|
||||||
updatedAttendance[existingIdx] = { ...updatedAttendance[existingIdx], type: newType, justification: undefined, justificationAccepted: undefined };
|
updatedAttendance[existingIdx] = { ...updatedAttendance[existingIdx], type: newType, justification: undefined, justificationAccepted: undefined };
|
||||||
fetch(`/api/frequencias/${updatedAttendance[existingIdx].id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatedAttendance[existingIdx]) });
|
await fetch(`/api/frequencias/${updatedAttendance[existingIdx].id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatedAttendance[existingIdx]) });
|
||||||
} else {
|
} else {
|
||||||
updatedAttendance.push(newRecord);
|
updatedAttendance.push(newRecord);
|
||||||
fetch('/api/frequencias', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newRecord) });
|
await fetch('/api/frequencias', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newRecord) });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Toggle existing record
|
// Toggle existing record
|
||||||
|
|
@ -118,14 +150,14 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
updatedAttendance = updatedAttendance.map(a =>
|
updatedAttendance = updatedAttendance.map(a =>
|
||||||
a.id === record.id ? modifiedRecord : a
|
a.id === record.id ? modifiedRecord : a
|
||||||
);
|
);
|
||||||
fetch(`/api/frequencias/${record.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modifiedRecord) });
|
await fetch(`/api/frequencias/${record.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modifiedRecord) });
|
||||||
}
|
}
|
||||||
|
|
||||||
updateData({ attendance: updatedAttendance });
|
setDbAttendance(updatedAttendance);
|
||||||
showAlert('Sucesso', 'Status de frequência atualizado com sucesso.', 'success');
|
showAlert('Sucesso', 'Status de frequência atualizado com sucesso.', 'success');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteAttachmentRecord = () => {
|
const handleDeleteAttachmentRecord = async () => {
|
||||||
if (!attendanceForAttachment || !attendanceForAttachment.justification) return;
|
if (!attendanceForAttachment || !attendanceForAttachment.justification) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -134,14 +166,14 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
delete parsed.arquivo;
|
delete parsed.arquivo;
|
||||||
const updatedJustification = JSON.stringify(parsed);
|
const updatedJustification = JSON.stringify(parsed);
|
||||||
|
|
||||||
const updatedAttendance = (data.attendance || []).map(a =>
|
const updatedAttendance = dbAttendance.map(a =>
|
||||||
a.id === attendanceForAttachment.id ? { ...a, justification: updatedJustification } : a
|
a.id === attendanceForAttachment.id ? { ...a, justification: updatedJustification } : a
|
||||||
);
|
);
|
||||||
|
|
||||||
const modifiedRecord = updatedAttendance.find(a => a.id === attendanceForAttachment.id);
|
const modifiedRecord = updatedAttendance.find(a => a.id === attendanceForAttachment.id);
|
||||||
fetch(`/api/frequencias/${attendanceForAttachment.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modifiedRecord) });
|
await fetch(`/api/frequencias/${attendanceForAttachment.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modifiedRecord) });
|
||||||
|
|
||||||
updateData({ attendance: updatedAttendance });
|
setDbAttendance(updatedAttendance);
|
||||||
setViewingAttachment(null);
|
setViewingAttachment(null);
|
||||||
setAttendanceForAttachment(null);
|
setAttendanceForAttachment(null);
|
||||||
showAlert('Sucesso', 'Arquivo removido com sucesso.', 'success');
|
showAlert('Sucesso', 'Arquivo removido com sucesso.', 'success');
|
||||||
|
|
@ -171,7 +203,7 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
}, 400);
|
}, 400);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddAbsence = () => {
|
const handleAddAbsence = async () => {
|
||||||
if (!absenceStudentId || !absenceJustification || !absenceLessonId) {
|
if (!absenceStudentId || !absenceJustification || !absenceLessonId) {
|
||||||
showAlert('Atenção', "⚠️ Por favor, preencha todos os campos da justificativa.", 'warning');
|
showAlert('Atenção', "⚠️ Por favor, preencha todos os campos da justificativa.", 'warning');
|
||||||
return;
|
return;
|
||||||
|
|
@ -190,12 +222,12 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is already a record for this lesson specifically
|
// Check if there is already a record for this lesson specifically
|
||||||
const existingIndex = (data.attendance || []).findIndex(a =>
|
const existingIndex = dbAttendance.findIndex(a =>
|
||||||
a.studentId === absenceStudentId &&
|
a.studentId === absenceStudentId &&
|
||||||
((a as any).lessonId === lesson.id || a.date === `${lesson.date}T${lesson.startTime || '00:00'}:00`)
|
((a as any).lessonId === lesson.id || a.date === `${lesson.date}T${lesson.startTime || '00:00'}:00`)
|
||||||
);
|
);
|
||||||
|
|
||||||
let updatedAttendance = [...(data.attendance || [])];
|
let updatedAttendance = [...dbAttendance];
|
||||||
|
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
updatedAttendance[existingIndex] = {
|
updatedAttendance[existingIndex] = {
|
||||||
|
|
@ -206,7 +238,7 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
verified: true,
|
verified: true,
|
||||||
lessonId: lesson.id as any
|
lessonId: lesson.id as any
|
||||||
};
|
};
|
||||||
fetch(`/api/frequencias/${updatedAttendance[existingIndex].id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatedAttendance[existingIndex]) });
|
await fetch(`/api/frequencias/${updatedAttendance[existingIndex].id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatedAttendance[existingIndex]) });
|
||||||
} else {
|
} else {
|
||||||
const newAbsence: Attendance = {
|
const newAbsence: Attendance = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
|
|
@ -220,11 +252,10 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
...(lesson ? { lessonId: lesson.id } : {}) as any
|
...(lesson ? { lessonId: lesson.id } : {}) as any
|
||||||
};
|
};
|
||||||
updatedAttendance.push(newAbsence);
|
updatedAttendance.push(newAbsence);
|
||||||
fetch('/api/frequencias', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newAbsence) });
|
await fetch('/api/frequencias', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newAbsence) });
|
||||||
}
|
}
|
||||||
|
|
||||||
updateData({ attendance: updatedAttendance });
|
setDbAttendance(updatedAttendance);
|
||||||
dbService.saveData({ ...data, attendance: updatedAttendance });
|
|
||||||
|
|
||||||
setAbsenceStudentId('');
|
setAbsenceStudentId('');
|
||||||
setAbsenceJustification('');
|
setAbsenceJustification('');
|
||||||
|
|
@ -246,7 +277,7 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
doc.text(`Data: ${new Date(selectedDate).toLocaleDateString()}`, 14, startY + 18);
|
doc.text(`Data: ${new Date(selectedDate).toLocaleDateString()}`, 14, startY + 18);
|
||||||
doc.text(`Turma: ${classObj.name}`, 14, startY + 24);
|
doc.text(`Turma: ${classObj.name}`, 14, startY + 24);
|
||||||
|
|
||||||
const classAttendance = (data.attendance || []).filter(record =>
|
const classAttendance = dbAttendance.filter(record =>
|
||||||
record.classId === classObj.id && record.date.startsWith(selectedDate)
|
record.classId === classObj.id && record.date.startsWith(selectedDate)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -310,7 +341,7 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{data.classes.map(classObj => {
|
{data.classes.map(classObj => {
|
||||||
const classStudents = data.students.filter(s => s.classId === classObj.id && s.status === 'active');
|
const classStudents = data.students.filter(s => s.classId === classObj.id && s.status === 'active');
|
||||||
const attendanceCount = (data.attendance || []).filter(a => a.classId === classObj.id && a.date.startsWith(selectedDate)).length;
|
const attendanceCount = dbAttendance.filter(a => a.classId === classObj.id && a.date.startsWith(selectedDate)).length;
|
||||||
const course = data.courses.find(c => c.id === classObj.courseId);
|
const course = data.courses.find(c => c.id === classObj.courseId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -395,7 +426,7 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
}
|
}
|
||||||
|
|
||||||
return classStudents.map(student => {
|
return classStudents.map(student => {
|
||||||
const studentActualRecords = (data.attendance || []).filter(a => a.studentId === student.id && a.classId === selectedClass.id);
|
const studentActualRecords = dbAttendance.filter(a => a.studentId === student.id && a.classId === selectedClass.id);
|
||||||
const classLessonsRaw = (data.lessons || []).filter(l => l.classId === selectedClass.id && l.status !== 'cancelled');
|
const classLessonsRaw = (data.lessons || []).filter(l => l.classId === selectedClass.id && l.status !== 'cancelled');
|
||||||
|
|
||||||
const deduplicatedLessons = classLessonsRaw.filter((lesson, index, self) =>
|
const deduplicatedLessons = classLessonsRaw.filter((lesson, index, self) =>
|
||||||
|
|
@ -510,10 +541,10 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const studentClassIds = new Set([
|
const studentClassIds = new Set([
|
||||||
selectedClass.id,
|
selectedClass.id,
|
||||||
...(data.attendance || []).filter(a => a.studentId === selectedStudent.id).map(a => a.classId)
|
...dbAttendance.filter(a => a.studentId === selectedStudent.id).map(a => a.classId)
|
||||||
].filter(Boolean));
|
].filter(Boolean));
|
||||||
|
|
||||||
const actualRecords = (data.attendance || [])
|
const actualRecords = dbAttendance
|
||||||
.filter(a => a.studentId === selectedStudent.id);
|
.filter(a => a.studentId === selectedStudent.id);
|
||||||
|
|
||||||
const classLessonsRaw = (data.lessons || [])
|
const classLessonsRaw = (data.lessons || [])
|
||||||
|
|
@ -745,12 +776,11 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
||||||
|
|
||||||
{hasPendingJustification && (
|
{hasPendingJustification && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
const updated = (data.attendance || []).map(a => a.id === record.id ? { ...a, justificationAccepted: true } : a);
|
const updated = dbAttendance.map(a => a.id === record.id ? { ...a, justificationAccepted: true } : a);
|
||||||
const modifiedRecord = updated.find(a => a.id === record.id);
|
const modifiedRecord = updated.find(a => a.id === record.id);
|
||||||
fetch(`/api/frequencias/${record.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modifiedRecord) });
|
await fetch(`/api/frequencias/${record.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modifiedRecord) });
|
||||||
updateData({ attendance: updated });
|
setDbAttendance(updated);
|
||||||
dbService.saveData({ ...data, attendance: updated });
|
|
||||||
showAlert('Sucesso', 'Justificativa aceita com sucesso.', 'success');
|
showAlert('Sucesso', 'Justificativa aceita com sucesso.', 'success');
|
||||||
}}
|
}}
|
||||||
className="text-[10px] px-2 py-1.5 bg-indigo-600 text-white font-bold rounded hover:bg-indigo-700 transition-colors"
|
className="text-[10px] px-2 py-1.5 bg-indigo-600 text-white font-bold rounded hover:bg-indigo-700 transition-colors"
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
const [resC, resT] = await Promise.all([
|
const [resC, resT] = await Promise.all([
|
||||||
fetch('/api/contratos'),
|
fetch(`/api/contratos?t=${Date.now()}`),
|
||||||
fetch('/api/modelos-contrato')
|
fetch(`/api/modelos-contrato?t=${Date.now()}`)
|
||||||
]);
|
]);
|
||||||
if (resC.ok) {
|
if (resC.ok) {
|
||||||
const json = await resC.json();
|
const json = await resC.json();
|
||||||
|
|
@ -172,9 +172,6 @@ const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
||||||
body: JSON.stringify(formData)
|
body: JSON.stringify(formData)
|
||||||
}).then(() => loadData());
|
}).then(() => loadData());
|
||||||
|
|
||||||
updateData({
|
|
||||||
contracts: dbContracts.map(c => c.id === (formData as any).id ? { ...c, ...formData } : c)
|
|
||||||
});
|
|
||||||
closeModal();
|
closeModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +188,6 @@ const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
||||||
body: JSON.stringify(newContract)
|
body: JSON.stringify(newContract)
|
||||||
}).then(() => loadData());
|
}).then(() => loadData());
|
||||||
|
|
||||||
updateData({ contracts: [...dbContracts, newContract] });
|
|
||||||
closeModal();
|
closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -247,7 +243,6 @@ const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
||||||
body: JSON.stringify({ ...templateFormData, id: updatedTemplates.find(t=>t.name===templateFormData.name)?.id || crypto.randomUUID() })
|
body: JSON.stringify({ ...templateFormData, id: updatedTemplates.find(t=>t.name===templateFormData.name)?.id || crypto.randomUUID() })
|
||||||
}).then(() => loadData());
|
}).then(() => loadData());
|
||||||
}
|
}
|
||||||
updateData({ contractTemplates: updatedTemplates });
|
|
||||||
closeTemplateModal();
|
closeTemplateModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -276,7 +271,7 @@ const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
||||||
'Excluir Contrato',
|
'Excluir Contrato',
|
||||||
'Tem certeza que deseja excluir este contrato?',
|
'Tem certeza que deseja excluir este contrato?',
|
||||||
() => {
|
() => {
|
||||||
updateData({ contracts: dbContracts.filter(c => c.id !== id) });
|
fetch(`/api/contratos/${id}`, { method: 'DELETE' }).then(() => loadData());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -286,7 +281,7 @@ const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
||||||
'Excluir Modelo',
|
'Excluir Modelo',
|
||||||
'Tem certeza que deseja excluir este modelo de contrato?',
|
'Tem certeza que deseja excluir este modelo de contrato?',
|
||||||
() => {
|
() => {
|
||||||
updateData({ contractTemplates: dbTemplates.filter(t => t.id !== id) });
|
fetch(`/api/modelos-contrato/${id}`, { method: 'DELETE' }).then(() => loadData());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ const Employees: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
setIsLoadingData(true);
|
setIsLoadingData(true);
|
||||||
const [empRes, catRes] = await Promise.all([
|
const [empRes, catRes] = await Promise.all([
|
||||||
fetch('/api/funcionarios'),
|
fetch(`/api/funcionarios?t=${Date.now()}`),
|
||||||
fetch('/api/categorias_funcionarios')
|
fetch(`/api/categorias_funcionarios?t=${Date.now()}`)
|
||||||
]);
|
]);
|
||||||
const empData = await empRes.json();
|
const empData = await empRes.json();
|
||||||
const catData = await catRes.json();
|
const catData = await catRes.json();
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
|
|
||||||
const loadExams = async () => {
|
const loadExams = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/provas');
|
const res = await fetch(`/api/provas?t=${Date.now()}`);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const { provas } = await res.json();
|
const { provas } = await res.json();
|
||||||
setDbExams(provas.map((p: any) => ({
|
setDbExams(provas.map((p: any) => ({
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ const Finance: React.FC<FinanceProps> = ({ data, updateData }) => {
|
||||||
|
|
||||||
const fetchPostgresPayments = async () => {
|
const fetchPostgresPayments = async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('/api/admin/cobrancas');
|
const resp = await fetch(`/api/admin/cobrancas?t=${Date.now()}`);
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const records = await resp.json();
|
const records = await resp.json();
|
||||||
const normalized = (records || []).map((r: any) => {
|
const normalized = (records || []).map((r: any) => {
|
||||||
|
|
@ -260,7 +260,7 @@ const Finance: React.FC<FinanceProps> = ({ data, updateData }) => {
|
||||||
setIsFetchingSupabase(true);
|
setIsFetchingSupabase(true);
|
||||||
setSelectedSupabaseRows([]);
|
setSelectedSupabaseRows([]);
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('/api/admin/cobrancas');
|
const resp = await fetch(`/api/admin/cobrancas?t=${Date.now()}`);
|
||||||
if (!resp.ok) throw new Error('API fetch failed');
|
if (!resp.ok) throw new Error('API fetch failed');
|
||||||
const records = await resp.json();
|
const records = await resp.json();
|
||||||
setSupabaseRecords(records || []);
|
setSupabaseRecords(records || []);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
const [dbLessons, setDbLessons] = useState<Lesson[]>([]);
|
const [dbLessons, setDbLessons] = useState<Lesson[]>([]);
|
||||||
const loadLessons = async () => {
|
const loadLessons = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/aulas?turma_id=${classObj.id}`);
|
const res = await fetch(`/api/aulas?turma_id=${classObj.id}&t=${Date.now()}`);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
setDbLessons(json.aulas || []);
|
setDbLessons(json.aulas || []);
|
||||||
|
|
@ -167,8 +167,6 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
body: JSON.stringify({ aulas: newLessons })
|
body: JSON.stringify({ aulas: newLessons })
|
||||||
}).then(() => loadLessons());
|
}).then(() => loadLessons());
|
||||||
|
|
||||||
const updatedLessons = [...(data.lessons || []), ...newLessons];
|
|
||||||
|
|
||||||
// Notificar alunos sobre novas aulas extras geradas
|
// Notificar alunos sobre novas aulas extras geradas
|
||||||
const datesList = newLessons.map(l => new Date(l.date + 'T12:00:00Z').toLocaleDateString('pt-BR')).join(', ');
|
const datesList = newLessons.map(l => new Date(l.date + 'T12:00:00Z').toLocaleDateString('pt-BR')).join(', ');
|
||||||
const notifMsg = `Novas aulas extras foram agendadas para os dias: ${datesList} (${startTime} às ${endTime}).`;
|
const notifMsg = `Novas aulas extras foram agendadas para os dias: ${datesList} (${startTime} às ${endTime}).`;
|
||||||
|
|
@ -177,8 +175,8 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
const newNotifs = notifyLessonAction('Aulas Extras Agendadas', notifMsg, waMsg);
|
const newNotifs = notifyLessonAction('Aulas Extras Agendadas', notifMsg, waMsg);
|
||||||
const updatedNotifications = [...(data.notifications || []), ...newNotifs];
|
const updatedNotifications = [...(data.notifications || []), ...newNotifs];
|
||||||
|
|
||||||
updateData({ lessons: updatedLessons, notifications: updatedNotifications });
|
updateData({ notifications: updatedNotifications });
|
||||||
dbService.saveData({ ...data, lessons: updatedLessons, notifications: updatedNotifications });
|
dbService.saveData({ ...data, notifications: updatedNotifications });
|
||||||
|
|
||||||
setShowGenerateModal(false);
|
setShowGenerateModal(false);
|
||||||
|
|
||||||
|
|
@ -317,8 +315,8 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ aulas: updatedLessons })
|
body: JSON.stringify({ aulas: updatedLessons })
|
||||||
}).then(() => loadLessons());
|
}).then(() => loadLessons());
|
||||||
updateData({ lessons: [...(data.lessons || []).filter(dl => dl.classId !== classObj.id), ...updatedLessons], notifications: updatedNotifications });
|
updateData({ notifications: updatedNotifications });
|
||||||
await dbService.saveData({ ...data, lessons: [...(data.lessons || []).filter(dl => dl.classId !== classObj.id), ...updatedLessons], notifications: updatedNotifications });
|
await dbService.saveData({ ...data, notifications: updatedNotifications });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -336,7 +334,7 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
|
|
||||||
const handleUncancelLesson = async (lesson: Lesson) => {
|
const handleUncancelLesson = async (lesson: Lesson) => {
|
||||||
setIsClosing(true);
|
setIsClosing(true);
|
||||||
const updatedLessons: Lesson[] = (data.lessons || []).map(l =>
|
const updatedLessons: Lesson[] = dbLessons.map(l =>
|
||||||
l.id === lesson.id ? { ...l, status: 'scheduled', cancelReason: undefined } : l
|
l.id === lesson.id ? { ...l, status: 'scheduled', cancelReason: undefined } : l
|
||||||
);
|
);
|
||||||
fetch('/api/aulas/lote', {
|
fetch('/api/aulas/lote', {
|
||||||
|
|
@ -344,8 +342,6 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ aulas: updatedLessons })
|
body: JSON.stringify({ aulas: updatedLessons })
|
||||||
}).then(() => loadLessons());
|
}).then(() => loadLessons());
|
||||||
updateData({ lessons: [...(data.lessons || []).filter(dl => dl.classId !== classObj.id), ...updatedLessons] });
|
|
||||||
await dbService.saveData({ ...data, lessons: [...(data.lessons || []).filter(dl => dl.classId !== classObj.id), ...updatedLessons] });
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShowLessonDetail(null);
|
setShowLessonDetail(null);
|
||||||
|
|
@ -397,10 +393,8 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
body: JSON.stringify({ aulas: updatedClassLessons })
|
body: JSON.stringify({ aulas: updatedClassLessons })
|
||||||
}).then(() => loadLessons());
|
}).then(() => loadLessons());
|
||||||
|
|
||||||
const allUpdatedLessons = [...(data.lessons || []).filter(dl => dl.classId !== classObj.id), ...updatedClassLessons];
|
updateData({ notifications: updatedNotifications, attendance: updatedAttendance });
|
||||||
|
await dbService.saveData({ ...data, notifications: updatedNotifications, attendance: updatedAttendance });
|
||||||
updateData({ lessons: allUpdatedLessons, notifications: updatedNotifications, attendance: updatedAttendance });
|
|
||||||
await dbService.saveData({ ...data, lessons: allUpdatedLessons, notifications: updatedNotifications, attendance: updatedAttendance });
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShowLessonDetail(null);
|
setShowLessonDetail(null);
|
||||||
|
|
@ -415,14 +409,17 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
const handleCancelAllFuture = () => {
|
const handleCancelAllFuture = () => {
|
||||||
showConfirm('Cancelar Cronograma', 'Deseja realmente cancelar TODAS as aulas futuras não realizadas? Não haverá reposição e a ação atualizará todas para Cancelada.', async () => {
|
showConfirm('Cancelar Cronograma', 'Deseja realmente cancelar TODAS as aulas futuras não realizadas? Não haverá reposição e a ação atualizará todas para Cancelada.', async () => {
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
const updatedLessons = (data.lessons || []).map(l => {
|
const updatedLessons = dbLessons.map(l => {
|
||||||
if (l.classId === classObj.id && l.status === 'scheduled' && l.date >= today) {
|
if (l.status === 'scheduled' && l.date >= today) {
|
||||||
return { ...l, status: 'cancelled', cancelReason: 'Cancelamento Geral de Cronograma' };
|
return { ...l, status: 'cancelled', cancelReason: 'Cancelamento Geral de Cronograma' };
|
||||||
}
|
}
|
||||||
return l;
|
return l;
|
||||||
});
|
});
|
||||||
updateData({ lessons: updatedLessons as Lesson[] });
|
fetch('/api/aulas/lote', {
|
||||||
await dbService.saveData({ ...data, lessons: updatedLessons as Lesson[] });
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ aulas: updatedLessons })
|
||||||
|
}).then(() => loadLessons());
|
||||||
showAlert('Sucesso', 'Cronograma futuro cancelado.', 'success');
|
showAlert('Sucesso', 'Cronograma futuro cancelado.', 'success');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -430,14 +427,17 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
const handleUncancelAllFuture = () => {
|
const handleUncancelAllFuture = () => {
|
||||||
showConfirm('Reativar Cronograma', 'Deseja realmente reativar TODAS as aulas futuras que estavam canceladas?', async () => {
|
showConfirm('Reativar Cronograma', 'Deseja realmente reativar TODAS as aulas futuras que estavam canceladas?', async () => {
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
const updatedLessons = (data.lessons || []).map(l => {
|
const updatedLessons = dbLessons.map(l => {
|
||||||
if (l.classId === classObj.id && l.status === 'cancelled' && l.date >= today) {
|
if (l.status === 'cancelled' && l.date >= today) {
|
||||||
return { ...l, status: 'scheduled', cancelReason: undefined };
|
return { ...l, status: 'scheduled', cancelReason: undefined };
|
||||||
}
|
}
|
||||||
return l;
|
return l;
|
||||||
});
|
});
|
||||||
updateData({ lessons: updatedLessons as Lesson[] });
|
fetch('/api/aulas/lote', {
|
||||||
await dbService.saveData({ ...data, lessons: updatedLessons as Lesson[] });
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ aulas: updatedLessons })
|
||||||
|
}).then(() => loadLessons());
|
||||||
showAlert('Sucesso', 'Cronograma futuro reativado com sucesso.', 'success');
|
showAlert('Sucesso', 'Cronograma futuro reativado com sucesso.', 'success');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -454,9 +454,6 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedLessons = (data.lessons || []).filter(l => l.classId !== classObj.id);
|
|
||||||
updateData({ lessons: updatedLessons });
|
|
||||||
await dbService.saveData({ ...data, lessons: updatedLessons });
|
|
||||||
await loadLessons();
|
await loadLessons();
|
||||||
showAlert('Sucesso', 'Cronograma completo excluído.', 'success');
|
showAlert('Sucesso', 'Cronograma completo excluído.', 'success');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
|
||||||
|
|
||||||
const loadStudents = async () => {
|
const loadStudents = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/alunos');
|
const res = await fetch(`/api/alunos?t=${Date.now()}`);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
setDbStudents(json.alunos || []);
|
setDbStudents(json.alunos || []);
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,12 @@ const App = () => {
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||||
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
||||||
const [currentView, setCurrentView] = useState<View>(View.Dashboard);
|
const [currentView, setCurrentView] = useState<View>(() => {
|
||||||
|
return (localStorage.getItem('manager_active_tab') as View) || View.Dashboard;
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('manager_active_tab', currentView);
|
||||||
|
}, [currentView]);
|
||||||
const [deepLinkStudentId, setDeepLinkStudentId] = useState<string | null>(null);
|
const [deepLinkStudentId, setDeepLinkStudentId] = useState<string | null>(null);
|
||||||
const [deepLinkClassId, setDeepLinkClassId] = useState<string | null>(null);
|
const [deepLinkClassId, setDeepLinkClassId] = useState<string | null>(null);
|
||||||
// Initial load from LocalStorage for speed (fallback), then IDB
|
// Initial load from LocalStorage for speed (fallback), then IDB
|
||||||
|
|
|
||||||
|
|
@ -607,26 +607,68 @@ app.get('/api/modelos-contrato', async (req, res) => {
|
||||||
try { res.json({ modelos: await getModelosContrato() }); } catch (e) { res.status(500).json({ error: 'Erro' }); }
|
try { res.json({ modelos: await getModelosContrato() }); } catch (e) { res.status(500).json({ error: 'Erro' }); }
|
||||||
});
|
});
|
||||||
app.post('/api/modelos-contrato', async (req, res) => {
|
app.post('/api/modelos-contrato', async (req, res) => {
|
||||||
try { await insertModeloContrato(req.body); res.json({ success: true }); } catch (e) { res.status(500).json({ error: 'Erro' }); }
|
try {
|
||||||
|
await insertModeloContrato(req.body);
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
appData.contractTemplates = await getModelosContrato();
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (e) { res.status(500).json({ error: 'Erro' }); }
|
||||||
});
|
});
|
||||||
app.put('/api/modelos-contrato/:id', async (req, res) => {
|
app.put('/api/modelos-contrato/:id', async (req, res) => {
|
||||||
try { await updateModeloContrato(req.params.id, req.body); res.json({ success: true }); } catch (e) { res.status(500).json({ error: 'Erro' }); }
|
try {
|
||||||
|
await updateModeloContrato(req.params.id, req.body);
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
appData.contractTemplates = await getModelosContrato();
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (e) { res.status(500).json({ error: 'Erro' }); }
|
||||||
});
|
});
|
||||||
app.delete('/api/modelos-contrato/:id', async (req, res) => {
|
app.delete('/api/modelos-contrato/:id', async (req, res) => {
|
||||||
try { await deleteModeloContrato(req.params.id); res.json({ success: true }); } catch (e) { res.status(500).json({ error: 'Erro' }); }
|
try {
|
||||||
|
await deleteModeloContrato(req.params.id);
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
appData.contractTemplates = await getModelosContrato();
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (e) { res.status(500).json({ error: 'Erro' }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/contratos', async (req, res) => {
|
app.get('/api/contratos', async (req, res) => {
|
||||||
try { res.json({ contratos: await getContratos() }); } catch (e) { res.status(500).json({ error: 'Erro' }); }
|
try { res.json({ contratos: await getContratos() }); } catch (e) { res.status(500).json({ error: 'Erro' }); }
|
||||||
});
|
});
|
||||||
app.post('/api/contratos', async (req, res) => {
|
app.post('/api/contratos', async (req, res) => {
|
||||||
try { await insertContrato(req.body); res.json({ success: true }); } catch (e) { res.status(500).json({ error: 'Erro' }); }
|
try {
|
||||||
|
await insertContrato(req.body);
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
appData.contracts = await getContratos();
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (e) { res.status(500).json({ error: 'Erro' }); }
|
||||||
});
|
});
|
||||||
app.put('/api/contratos/:id', async (req, res) => {
|
app.put('/api/contratos/:id', async (req, res) => {
|
||||||
try { await updateContrato(req.params.id, req.body); res.json({ success: true }); } catch (e) { res.status(500).json({ error: 'Erro' }); }
|
try {
|
||||||
|
await updateContrato(req.params.id, req.body);
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
appData.contracts = await getContratos();
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (e) { res.status(500).json({ error: 'Erro' }); }
|
||||||
});
|
});
|
||||||
app.delete('/api/contratos/:id', async (req, res) => {
|
app.delete('/api/contratos/:id', async (req, res) => {
|
||||||
try { await deleteContrato(req.params.id); res.json({ success: true }); } catch (e) { res.status(500).json({ error: 'Erro' }); }
|
try {
|
||||||
|
await deleteContrato(req.params.id);
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
appData.contracts = await getContratos();
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (e) { res.status(500).json({ error: 'Erro' }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -647,6 +689,14 @@ app.post('/api/aulas/lote', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { aulas } = req.body;
|
const { aulas } = req.body;
|
||||||
await insertAulas(aulas);
|
await insertAulas(aulas);
|
||||||
|
|
||||||
|
// Reverse sync to legacy JSON
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
const dbAulas = await getAllAulas();
|
||||||
|
appData.lessons = dbAulas;
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao inserir aulas em lote:', error);
|
console.error('Erro ao inserir aulas em lote:', error);
|
||||||
|
|
@ -658,6 +708,14 @@ app.delete('/api/aulas/lote', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { ids } = req.body;
|
const { ids } = req.body;
|
||||||
await deleteAulas(ids);
|
await deleteAulas(ids);
|
||||||
|
|
||||||
|
// Reverse sync to legacy JSON
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
const dbAulas = await getAllAulas();
|
||||||
|
appData.lessons = dbAulas;
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao deletar aulas em lote:', error);
|
console.error('Erro ao deletar aulas em lote:', error);
|
||||||
|
|
@ -681,6 +739,14 @@ app.get('/api/frequencias', async (req, res) => {
|
||||||
app.post('/api/frequencias', async (req, res) => {
|
app.post('/api/frequencias', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await insertFrequencia(req.body);
|
await insertFrequencia(req.body);
|
||||||
|
|
||||||
|
// Reverse sync to legacy JSON
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
const dbFrequencias = await getFrequencias();
|
||||||
|
appData.attendance = dbFrequencias;
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao inserir frequencia:', error);
|
console.error('Erro ao inserir frequencia:', error);
|
||||||
|
|
@ -691,6 +757,14 @@ app.post('/api/frequencias', async (req, res) => {
|
||||||
app.put('/api/frequencias/:id', async (req, res) => {
|
app.put('/api/frequencias/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await updateFrequencia(req.params.id, req.body);
|
await updateFrequencia(req.params.id, req.body);
|
||||||
|
|
||||||
|
// Reverse sync to legacy JSON
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
const dbFrequencias = await getFrequencias();
|
||||||
|
appData.attendance = dbFrequencias;
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao atualizar frequencia:', error);
|
console.error('Erro ao atualizar frequencia:', error);
|
||||||
|
|
@ -701,6 +775,14 @@ app.put('/api/frequencias/:id', async (req, res) => {
|
||||||
app.delete('/api/frequencias/:id', async (req, res) => {
|
app.delete('/api/frequencias/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await deleteFrequencia(req.params.id);
|
await deleteFrequencia(req.params.id);
|
||||||
|
|
||||||
|
// Reverse sync to legacy JSON
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
const dbFrequencias = await getFrequencias();
|
||||||
|
appData.attendance = dbFrequencias;
|
||||||
|
appData.lastUpdated = new Date().toISOString();
|
||||||
|
await saveSchoolData(appData);
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao deletar frequencia:', error);
|
console.error('Erro ao deletar frequencia:', error);
|
||||||
|
|
|
||||||
|
|
@ -593,6 +593,8 @@ app.post('/api/portal/frequencia/justificar', authMiddleware, upload.single('arq
|
||||||
|
|
||||||
const submittedAt = new Date().toISOString();
|
const submittedAt = new Date().toISOString();
|
||||||
|
|
||||||
|
let recordIndex = attendance.findIndex(a => a.studentId === req.user.studentId && a.date === fullDateStr);
|
||||||
|
|
||||||
if (recordIndex !== -1) {
|
if (recordIndex !== -1) {
|
||||||
const existing = attendance[recordIndex];
|
const existing = attendance[recordIndex];
|
||||||
if (existing.type === 'presence') return res.status(400).json({ error: 'Não é possível justificar uma presença' });
|
if (existing.type === 'presence') return res.status(400).json({ error: 'Não é possível justificar uma presença' });
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue