fix: biometric attendance timezone shift and mandatory lesson binding
This commit is contained in:
parent
1552e5cb19
commit
a1b5075e41
|
|
@ -231,11 +231,41 @@ const AttendanceCapture: React.FC<AttendanceCaptureProps> = ({ data, updateData
|
|||
return;
|
||||
}
|
||||
|
||||
// Encontrar a aula ativa para esta turma/aluno no momento da captura
|
||||
const nowLocal = new Date();
|
||||
const activeLesson = (data.lessons || []).find(l => {
|
||||
if (l.classId !== detectedClassId || l.status === 'cancelled') return false;
|
||||
const lessonDate = l.date; // YYYY-MM-DD
|
||||
const startStr = `${lessonDate}T${l.startTime || '00:00'}:00`;
|
||||
const endStr = `${lessonDate}T${l.endTime || '23:59'}:00`;
|
||||
const lessonStart = new Date(startStr);
|
||||
const lessonEnd = new Date(endStr);
|
||||
// Janela: 30 min antes do início até o fim da aula
|
||||
const windowStart = new Date(lessonStart.getTime() - 30 * 60 * 1000);
|
||||
return nowLocal >= windowStart && nowLocal <= lessonEnd;
|
||||
});
|
||||
|
||||
// REGRA ESTRITA: A presença só pode ser marcada se houver uma aula ativa
|
||||
if (!activeLesson) {
|
||||
showAlert('Atenção', "Nenhuma aula ativa detectada para esta turma no momento. A presença só pode ser registrada a partir de 30 minutos antes do início até o término da aula.", 'warning');
|
||||
cancelCapture();
|
||||
return;
|
||||
}
|
||||
|
||||
// Gerar string de data local (YYYY-MM-DDTHH:MM:SS) sem fuso UTC para evitar o bug do dia seguinte
|
||||
const localDateStr = nowLocal.getFullYear() + '-' +
|
||||
String(nowLocal.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(nowLocal.getDate()).padStart(2, '0') + 'T' +
|
||||
String(nowLocal.getHours()).padStart(2, '0') + ':' +
|
||||
String(nowLocal.getMinutes()).padStart(2, '0') + ':' +
|
||||
String(nowLocal.getSeconds()).padStart(2, '0');
|
||||
|
||||
const newAttendance: Attendance = {
|
||||
id: crypto.randomUUID(),
|
||||
studentId: detectedStudentId,
|
||||
classId: detectedClassId,
|
||||
date: new Date().toISOString(),
|
||||
lessonId: activeLesson.id, // Vínculo obrigatório agora
|
||||
date: localDateStr,
|
||||
photo: capturedImage,
|
||||
type: 'presence',
|
||||
verified: true
|
||||
|
|
|
|||
|
|
@ -39,12 +39,18 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
|||
const [showAbsenceModal, setShowAbsenceModal] = useState(false);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const [isClosing2, setIsClosing2] = useState(false);
|
||||
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
// Função para obter data local YYYY-MM-DD sem risco de fuso horário UTC
|
||||
const getTodayLocal = () => {
|
||||
const d = new Date();
|
||||
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
||||
};
|
||||
|
||||
const [selectedDate, setSelectedDate] = useState(getTodayLocal());
|
||||
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
|
||||
|
||||
const [absenceStudentId, setAbsenceStudentId] = useState('');
|
||||
const [absenceJustification, setAbsenceJustification] = useState('');
|
||||
const [absenceDate, setAbsenceDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
const [absenceDate, setAbsenceDate] = useState(getTodayLocal());
|
||||
const [absenceLessonId, setAbsenceLessonId] = useState('');
|
||||
const [viewingAttachment, setViewingAttachment] = useState<string | null>(null);
|
||||
const [attendanceForAttachment, setAttendanceForAttachment] = useState<Attendance | null>(null);
|
||||
|
|
@ -74,11 +80,19 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
|
|||
const lesson = data.lessons.find(l => l.id === record.id.replace('v-', ''));
|
||||
const newType = (record.type === 'absence' || record.type === 'awaiting') ? 'presence' : 'absence';
|
||||
|
||||
const now = new Date();
|
||||
const localDate = now.getFullYear() + '-' +
|
||||
String(now.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(now.getDate()).padStart(2, '0') + 'T' +
|
||||
String(now.getHours()).padStart(2, '0') + ':' +
|
||||
String(now.getMinutes()).padStart(2, '0') + ':' +
|
||||
String(now.getSeconds()).padStart(2, '0');
|
||||
|
||||
const newRecord: Attendance = {
|
||||
id: crypto.randomUUID(),
|
||||
studentId: record.studentId,
|
||||
classId: record.classId,
|
||||
date: lesson ? `${lesson.date}T${lesson.startTime || '00:00'}:00` : new Date().toISOString(),
|
||||
date: lesson ? `${lesson.date}T${lesson.startTime || '00:00'}:00` : localDate,
|
||||
verified: true,
|
||||
type: newType,
|
||||
...(lesson ? { lessonId: lesson.id } : {}) // Vinculação rígida para não haver duplicidade
|
||||
|
|
|
|||
|
|
@ -161,7 +161,8 @@ export interface Attendance {
|
|||
id: string;
|
||||
studentId: string;
|
||||
classId: string;
|
||||
date: string; // ISO String
|
||||
lessonId?: string;
|
||||
date: string; // ISO String ou Local ISO
|
||||
photo?: string; // Base64 (Optional for absences)
|
||||
verified: boolean;
|
||||
type?: 'presence' | 'absence';
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ export interface Attendance {
|
|||
id: string;
|
||||
studentId: string;
|
||||
classId: string;
|
||||
date: string; // ISO String (UTC)
|
||||
lessonId?: string;
|
||||
date: string; // ISO String (UTC) ou Local ISO
|
||||
photo?: string;
|
||||
verified: boolean;
|
||||
type?: 'presence' | 'absence';
|
||||
|
|
|
|||
Loading…
Reference in New Issue