diff --git a/manager/components/AttendanceQuery.tsx b/manager/components/AttendanceQuery.tsx index 62a477a..a50bfa7 100644 --- a/manager/components/AttendanceQuery.tsx +++ b/manager/components/AttendanceQuery.tsx @@ -271,42 +271,78 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee const startY = await addHeader(doc, data); doc.setFontSize(18); - doc.text('Relatório de Frequência', 14, startY + 10); + doc.text('Relatorio Geral de Frequencia da Turma', 14, startY + 10); doc.setFontSize(11); - 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 + 18); + doc.text(`Data de Emissao: ${new Date().toLocaleDateString('pt-BR')}`, 14, startY + 24); - const classAttendance = dbAttendance.filter(record => - record.classId === classObj.id && record.date.startsWith(selectedDate) - ); + const classStudents = data.students + .filter(s => s.classId === classObj.id && s.status === 'active') + .sort((a, b) => a.name.localeCompare(b.name)); - const tableData = classAttendance.map(record => { - const student = data.students.find(s => s.id === record.studentId); - const time = new Date(record.date).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - let justMotivo = record.justification || '-'; - if (justMotivo.startsWith('{')) { - try { - const parsed = JSON.parse(justMotivo); - justMotivo = parsed.motivo || justMotivo; - } catch (e) { } - } + const tableData = classStudents.map(student => { + const studentActualRecords = dbAttendance.filter(a => a.studentId === student.id && a.classId === classObj.id); + const classLessonsRaw = (data.lessons || []).filter(l => l.classId === classObj.id && l.status !== 'cancelled'); + + const deduplicatedLessons = classLessonsRaw.filter((lesson, index, self) => + index === self.findIndex((t) => ( + t.date === lesson.date && t.startTime === lesson.startTime + )) + ); + + let presences = 0; + let absences = 0; + let justified = 0; + const now = new Date(); + + deduplicatedLessons.forEach(lesson => { + const lessonStart = new Date(lesson.date + 'T' + (lesson.startTime || '00:00') + ':00'); + const lessonEnd = new Date(lesson.date + 'T' + (lesson.endTime || '23:59') + ':00'); + + const matchingRecords = studentActualRecords.filter(a => { + if ((a as any).lessonId === lesson.id) return true; + if (a.date === `${lesson.date}T${lesson.startTime || '00:00'}:00`) return true; + const recordTime = new Date(a.date); + return recordTime >= lessonStart && recordTime <= lessonEnd; + }); + + const matchedRecord = matchingRecords.find(a => a.type === 'presence' || !a.type) || + matchingRecords.find(a => a.type === 'absence' && a.justificationAccepted) || + matchingRecords[0]; + + if (matchedRecord) { + if (matchedRecord.type === 'absence') { + if (matchedRecord.justificationAccepted) justified++; + else absences++; + } else if (matchedRecord.type === 'presence' || !matchedRecord.type) { + presences++; + } + } else if (now > lessonEnd) { + absences++; + } + }); + + const totalWithJustified = presences + absences + justified; + const attendanceRate = totalWithJustified > 0 ? ((presences + justified) / totalWithJustified) * 100 : 100; return [ - student?.name || 'Desconhecido', - time, - record.type === 'absence' ? (record.justificationAccepted ? 'Falta Justificada' : 'Falta') : 'Presente', - justMotivo + student.name, + student.enrollmentNumber || '—', + `${presences} P`, + `${absences} F`, + `${justified} J`, + `${attendanceRate.toFixed(0)}%` ]; }); (doc as any).autoTable({ startY: startY + 30, - head: [['Aluno', 'Horário', 'Status', 'Justificativa']], + head: [['Aluno', 'Matricula', 'Presencas', 'Faltas', 'Justificadas', 'Frequencia %']], body: tableData, }); - doc.save(`frequencia_${classObj.name}_${selectedDate}.pdf`); + doc.save(`frequencia_geral_${classObj.name}.pdf`); } catch (error) { console.error('Error exporting PDF:', error); } finally { diff --git a/manager/components/Students.tsx b/manager/components/Students.tsx index 7934286..d1292a7 100644 --- a/manager/components/Students.tsx +++ b/manager/components/Students.tsx @@ -953,13 +953,27 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId content = content.replace(/{{escola}}/g, data.profile.name || ''); content = content.replace(/{{cnpj_escola}}/g, data.profile.cnpj || ''); - newContracts.push({ - id: crypto.randomUUID(), + const contractId = crypto.randomUUID(); + const contractPayload = { + id: contractId, studentId: studentToSave.id, title: `Contrato - ${course.name}`, content, createdAt: new Date().toISOString() - }); + }; + newContracts.push(contractPayload); + + try { + await fetch('/api/contratos', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(contractPayload) + }); + } catch (contractErr) { + console.error('Erro ao salvar contrato no PostgreSQL:', contractErr); + } } const newData = { @@ -1177,10 +1191,19 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId if (student) { setEditingStudent(student); - setFormData({ ...defaultData, ...student }); + setFormData({ + ...defaultData, + ...student, + generateContract: false, + generateFee: false + }); } else { setEditingStudent(null); - setFormData(defaultData); + setFormData({ + ...defaultData, + generateContract: true, + generateFee: false + }); } setShowModal(true); }; @@ -1892,17 +1915,7 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId
-
- setFormData({...formData, generateFee: e.target.checked} as any)} - /> - -
- +
{ + if (!selectedDate) return false; + const att = attendance.find(a => { + if (!a.date) return false; + const cleanDate = a.date.substring(0, 19); + const cleanSelected = selectedDate.substring(0, 19); + return cleanDate === cleanSelected; + }); + return !!(att && att.justification); + }; + const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { @@ -290,7 +301,6 @@ export default function Frequencia() { if (att) { if (att.type === 'presence' || (att.verified && att.type !== 'absence')) return false; - if (att.justification) return false; } return true; }); @@ -817,6 +827,28 @@ export default function Frequencia() { )}
+ {selectedLessonHasJustification() && ( +
+ +
+ Atenção: Aula Já Justificada! + Não é possível enviar uma nova justificativa para esta aula, pois ela já possui uma justificativa cadastrada. +
+
+ )} +