mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Gestion des absences du jour [#16]
This commit is contained in:
@ -131,33 +131,3 @@ class PaymentMode(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.get_mode_display()} - {self.get_type_display()}"
|
return f"{self.get_mode_display()} - {self.get_type_display()}"
|
||||||
|
|
||||||
class AbsenceMoment(models.IntegerChoices):
|
|
||||||
MORNING = 1, 'Morning'
|
|
||||||
AFTERNOON = 2, 'Afternoon'
|
|
||||||
TOTAL = 3, 'Total'
|
|
||||||
|
|
||||||
class AbsenceReason(models.IntegerChoices):
|
|
||||||
JUSTIFIED_ABSENCE = 1, 'Justified Absence'
|
|
||||||
UNJUSTIFIED_ABSENCE = 2, 'Unjustified Absence'
|
|
||||||
JUSTIFIED_LATE = 3, 'Justified Late'
|
|
||||||
UNJUSTIFIED_LATE = 4, 'Unjustified Late'
|
|
||||||
|
|
||||||
class AbsenceManagement(models.Model):
|
|
||||||
day = models.DateField()
|
|
||||||
moment = models.IntegerField(
|
|
||||||
choices=AbsenceMoment.choices,
|
|
||||||
default=AbsenceMoment.TOTAL
|
|
||||||
)
|
|
||||||
reason = models.IntegerField(
|
|
||||||
choices=AbsenceReason.choices,
|
|
||||||
default=AbsenceReason.UNJUSTIFIED_ABSENCE
|
|
||||||
)
|
|
||||||
student = models.ForeignKey(
|
|
||||||
'Subscriptions.Student',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='absences'
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.student} - {self.day} - {self.get_moment_display()} - {self.get_reason_display()}"
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan, PaymentMode, AbsenceManagement
|
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan, PaymentMode
|
||||||
from Auth.models import Profile, ProfileRole
|
from Auth.models import Profile, ProfileRole
|
||||||
from Subscriptions.models import Student
|
from Subscriptions.models import Student
|
||||||
from Establishment.models import Establishment
|
from Establishment.models import Establishment
|
||||||
@ -8,11 +8,6 @@ from N3wtSchool import settings, bdd
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
class AbsenceManagementSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = AbsenceManagement
|
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
class SpecialitySerializer(serializers.ModelSerializer):
|
class SpecialitySerializer(serializers.ModelSerializer):
|
||||||
updated_date_formatted = serializers.SerializerMethodField()
|
updated_date_formatted = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
|||||||
@ -17,8 +17,6 @@ from .views import (
|
|||||||
PaymentPlanDetailView,
|
PaymentPlanDetailView,
|
||||||
PaymentModeListCreateView,
|
PaymentModeListCreateView,
|
||||||
PaymentModeDetailView,
|
PaymentModeDetailView,
|
||||||
AbsenceManagementListCreateView,
|
|
||||||
AbsenceManagementDetailView
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -45,7 +43,4 @@ urlpatterns = [
|
|||||||
|
|
||||||
re_path(r'^paymentModes$', PaymentModeListCreateView.as_view(), name="payment_mode_list_create"),
|
re_path(r'^paymentModes$', PaymentModeListCreateView.as_view(), name="payment_mode_list_create"),
|
||||||
re_path(r'^paymentModes/(?P<id>[0-9]+)$', PaymentModeDetailView.as_view(), name="payment_mode_detail"),
|
re_path(r'^paymentModes/(?P<id>[0-9]+)$', PaymentModeDetailView.as_view(), name="payment_mode_detail"),
|
||||||
|
|
||||||
re_path(r'^absences$', AbsenceManagementListCreateView.as_view(), name="absence_list_create"),
|
|
||||||
re_path(r'^absences/(?P<id>[0-9]+)$', AbsenceManagementDetailView.as_view(), name="absence_detail"),
|
|
||||||
]
|
]
|
||||||
@ -413,48 +413,3 @@ class PaymentModeDetailView(APIView):
|
|||||||
|
|
||||||
def delete(self, request, id):
|
def delete(self, request, id):
|
||||||
return delete_object(PaymentMode, id)
|
return delete_object(PaymentMode, id)
|
||||||
|
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
|
||||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
|
||||||
class AbsenceManagementListCreateView(APIView):
|
|
||||||
def get(self, request):
|
|
||||||
absences = AbsenceManagement.objects.all()
|
|
||||||
serializer = AbsenceManagementSerializer(absences, many=True)
|
|
||||||
return JsonResponse(serializer.data, safe=False, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
serializer = AbsenceManagementSerializer(data=request.data)
|
|
||||||
if serializer.is_valid():
|
|
||||||
serializer.save()
|
|
||||||
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
|
|
||||||
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
|
||||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
|
||||||
class AbsenceManagementDetailView(APIView):
|
|
||||||
def get(self, request, id):
|
|
||||||
try:
|
|
||||||
absence = AbsenceManagement.objects.get(id=id)
|
|
||||||
serializer = AbsenceManagementSerializer(absence)
|
|
||||||
return JsonResponse(serializer.data, safe=False, status=status.HTTP_200_OK)
|
|
||||||
except AbsenceManagement.DoesNotExist:
|
|
||||||
return JsonResponse({"error": "Absence not found"}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
def put(self, request, id):
|
|
||||||
try:
|
|
||||||
absence = AbsenceManagement.objects.get(id=id)
|
|
||||||
serializer = AbsenceManagementSerializer(absence, data=request.data)
|
|
||||||
if serializer.is_valid():
|
|
||||||
serializer.save()
|
|
||||||
return JsonResponse(serializer.data, safe=False, status=status.HTTP_200_OK)
|
|
||||||
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
except AbsenceManagement.DoesNotExist:
|
|
||||||
return JsonResponse({"error": "Absence not found"}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
def delete(self, request, id):
|
|
||||||
try:
|
|
||||||
absence = AbsenceManagement.objects.get(id=id)
|
|
||||||
absence.delete()
|
|
||||||
return JsonResponse(safe=False, status=status.HTTP_204_NO_CONTENT)
|
|
||||||
except AbsenceManagement.DoesNotExist:
|
|
||||||
return JsonResponse({"error": "Absence not found"}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|||||||
@ -357,3 +357,34 @@ class RegistrationParentFileTemplate(models.Model):
|
|||||||
for reg_file in registration_files:
|
for reg_file in registration_files:
|
||||||
filenames.append(reg_file.file.path)
|
filenames.append(reg_file.file.path)
|
||||||
return filenames
|
return filenames
|
||||||
|
|
||||||
|
class AbsenceMoment(models.IntegerChoices):
|
||||||
|
MORNING = 1, 'Morning'
|
||||||
|
AFTERNOON = 2, 'Afternoon'
|
||||||
|
TOTAL = 3, 'Total'
|
||||||
|
|
||||||
|
class AbsenceReason(models.IntegerChoices):
|
||||||
|
JUSTIFIED_ABSENCE = 1, 'Justified Absence'
|
||||||
|
UNJUSTIFIED_ABSENCE = 2, 'Unjustified Absence'
|
||||||
|
JUSTIFIED_LATE = 3, 'Justified Late'
|
||||||
|
UNJUSTIFIED_LATE = 4, 'Unjustified Late'
|
||||||
|
|
||||||
|
class AbsenceManagement(models.Model):
|
||||||
|
day = models.DateField()
|
||||||
|
moment = models.IntegerField(
|
||||||
|
choices=AbsenceMoment.choices,
|
||||||
|
default=AbsenceMoment.TOTAL
|
||||||
|
)
|
||||||
|
reason = models.IntegerField(
|
||||||
|
choices=AbsenceReason.choices,
|
||||||
|
default=AbsenceReason.UNJUSTIFIED_ABSENCE
|
||||||
|
)
|
||||||
|
student = models.ForeignKey(
|
||||||
|
Student,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='absences'
|
||||||
|
)
|
||||||
|
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='absences')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.student} - {self.day} - {self.get_moment_display()} - {self.get_reason_display()}"
|
||||||
@ -1,5 +1,5 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster, RegistrationParentFileTemplate
|
from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster, RegistrationParentFileTemplate, AbsenceManagement
|
||||||
from School.models import SchoolClass, Fee, Discount, FeeType
|
from School.models import SchoolClass, Fee, Discount, FeeType
|
||||||
from School.serializers import FeeSerializer, DiscountSerializer
|
from School.serializers import FeeSerializer, DiscountSerializer
|
||||||
from Auth.models import ProfileRole, Profile
|
from Auth.models import ProfileRole, Profile
|
||||||
@ -12,6 +12,11 @@ import pytz
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import Subscriptions.util as util
|
import Subscriptions.util as util
|
||||||
|
|
||||||
|
class AbsenceManagementSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = AbsenceManagement
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
class RegistrationSchoolFileMasterSerializer(serializers.ModelSerializer):
|
class RegistrationSchoolFileMasterSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(required=False)
|
id = serializers.IntegerField(required=False)
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@ -15,7 +15,9 @@ from .views import (
|
|||||||
RegistrationParentFileMasterSimpleView,
|
RegistrationParentFileMasterSimpleView,
|
||||||
RegistrationParentFileMasterView,
|
RegistrationParentFileMasterView,
|
||||||
RegistrationParentFileTemplateSimpleView,
|
RegistrationParentFileTemplateSimpleView,
|
||||||
RegistrationParentFileTemplateView
|
RegistrationParentFileTemplateView,
|
||||||
|
AbsenceManagementListCreateView,
|
||||||
|
AbsenceManagementDetailView
|
||||||
)
|
)
|
||||||
|
|
||||||
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||||
@ -58,4 +60,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
re_path(r'^students/(?P<student_id>[0-9]+)/guardians/(?P<guardian_id>[0-9]+)/dissociate', DissociateGuardianView.as_view(), name='dissociate-guardian'),
|
re_path(r'^students/(?P<student_id>[0-9]+)/guardians/(?P<guardian_id>[0-9]+)/dissociate', DissociateGuardianView.as_view(), name='dissociate-guardian'),
|
||||||
|
|
||||||
|
re_path(r'^absences$', AbsenceManagementListCreateView.as_view(), name="absence_list_create"),
|
||||||
|
re_path(r'^absences/(?P<id>[0-9]+)$', AbsenceManagementDetailView.as_view(), name="absence_detail"),
|
||||||
|
|
||||||
]
|
]
|
||||||
@ -12,6 +12,7 @@ from .registration_file_views import (
|
|||||||
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||||
from .student_views import StudentView, StudentListView, ChildrenListView
|
from .student_views import StudentView, StudentListView, ChildrenListView
|
||||||
from .guardian_views import GuardianView, DissociateGuardianView
|
from .guardian_views import GuardianView, DissociateGuardianView
|
||||||
|
from .absences_views import AbsenceManagementDetailView, AbsenceManagementListCreateView
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'RegisterFormView',
|
'RegisterFormView',
|
||||||
@ -36,5 +37,7 @@ __all__ = [
|
|||||||
'StudentListView',
|
'StudentListView',
|
||||||
'ChildrenListView',
|
'ChildrenListView',
|
||||||
'GuardianView',
|
'GuardianView',
|
||||||
'DissociateGuardianView'
|
'DissociateGuardianView',
|
||||||
|
'AbsenceManagementDetailView',
|
||||||
|
'AbsenceManagementListCreateView'
|
||||||
]
|
]
|
||||||
|
|||||||
50
Back-End/Subscriptions/views/absences_views.py
Normal file
50
Back-End/Subscriptions/views/absences_views.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from django.http.response import JsonResponse
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework import status
|
||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
|
from drf_yasg import openapi
|
||||||
|
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from Subscriptions.serializers import AbsenceManagementSerializer
|
||||||
|
from Subscriptions.models import AbsenceManagement
|
||||||
|
from N3wtSchool.bdd import delete_object
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class AbsenceManagementListCreateView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
absences = AbsenceManagement.objects.all()
|
||||||
|
serializer = AbsenceManagementSerializer(absences, many=True)
|
||||||
|
return JsonResponse(serializer.data, safe=False, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
serializer = AbsenceManagementSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
|
||||||
|
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class AbsenceManagementDetailView(APIView):
|
||||||
|
def get(self, request, id):
|
||||||
|
try:
|
||||||
|
absence = AbsenceManagement.objects.get(id=id)
|
||||||
|
serializer = AbsenceManagementSerializer(absence)
|
||||||
|
return JsonResponse(serializer.data, safe=False, status=status.HTTP_200_OK)
|
||||||
|
except AbsenceManagement.DoesNotExist:
|
||||||
|
return JsonResponse({"error": "Absence not found"}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def put(self, request, id):
|
||||||
|
try:
|
||||||
|
absence = AbsenceManagement.objects.get(id=id)
|
||||||
|
serializer = AbsenceManagementSerializer(absence, data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return JsonResponse(serializer.data, safe=False, status=status.HTTP_200_OK)
|
||||||
|
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
except AbsenceManagement.DoesNotExist:
|
||||||
|
return JsonResponse({"error": "Absence not found"}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def delete(self, request, id):
|
||||||
|
return delete_object(AbsenceManagement, id)
|
||||||
@ -1,10 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Plus, Users, Layers, CheckCircle } from 'lucide-react';
|
import { Users, Layers, CheckCircle, Clock } from 'lucide-react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import MultiSelect from '@/components/MultiSelect';
|
|
||||||
import InputText from '@/components/InputText';
|
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import { fetchClasse } from '@/app/actions/schoolAction';
|
import { fetchClasse } from '@/app/actions/schoolAction';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
@ -13,6 +11,17 @@ import { useClasses } from '@/context/ClassesContext';
|
|||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
|
import CheckBox from '@/components/CheckBox';
|
||||||
|
import InputText from '@/components/InputText';
|
||||||
|
import {
|
||||||
|
fetchAbsences,
|
||||||
|
createAbsences,
|
||||||
|
editAbsences,
|
||||||
|
deleteAbsences,
|
||||||
|
} from '@/app/actions/subscriptionAction';
|
||||||
|
|
||||||
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@ -20,56 +29,67 @@ export default function Page() {
|
|||||||
const [classe, setClasse] = useState([]);
|
const [classe, setClasse] = useState([]);
|
||||||
const { getNiveauxLabels, getNiveauLabel } = useClasses();
|
const { getNiveauxLabels, getNiveauLabel } = useClasses();
|
||||||
|
|
||||||
const [students, setStudents] = useState([]);
|
|
||||||
const [groups, setGroups] = useState([]);
|
|
||||||
const [newGroup, setNewGroup] = useState({
|
|
||||||
name: '',
|
|
||||||
level: null,
|
|
||||||
students: [],
|
|
||||||
});
|
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
const [popupMessage, setPopupMessage] = useState('');
|
const [popupMessage, setPopupMessage] = useState('');
|
||||||
const [selectedLevels, setSelectedLevels] = useState([]); // Par défaut, tous les niveaux sont sélectionnés
|
const [selectedLevels, setSelectedLevels] = useState([]); // Par défaut, tous les niveaux sont sélectionnés
|
||||||
const [filteredStudents, setFilteredStudents] = useState([]);
|
const [filteredStudents, setFilteredStudents] = useState([]);
|
||||||
const [isEditingAttendance, setIsEditingAttendance] = useState(false); // État pour le mode édition
|
const [isEditingAttendance, setIsEditingAttendance] = useState(false); // État pour le mode édition
|
||||||
const [attendance, setAttendance] = useState({}); // État pour les cases cochées
|
const [attendance, setAttendance] = useState({}); // État pour les cases cochées
|
||||||
const [absences, setAbsences] = useState({}); // État pour stocker les absences
|
const [fetchedAbsences, setFetchedAbsences] = useState({}); // Absences récupérées depuis le backend
|
||||||
const [absenceDetails, setAbsenceDetails] = useState({
|
const [formAbsences, setFormAbsences] = useState({}); // Absences modifiées localement
|
||||||
day: '',
|
|
||||||
reason: null,
|
const csrfToken = useCsrfToken();
|
||||||
moment: null,
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
}); // Détails d'absence
|
|
||||||
|
|
||||||
// AbsenceMoment constants
|
// AbsenceMoment constants
|
||||||
const AbsenceMoment = {
|
const AbsenceMoment = {
|
||||||
MORNING: { value: 1, label: 'Morning' },
|
MORNING: { value: 1, label: 'Matinée' },
|
||||||
AFTERNOON: { value: 2, label: 'Afternoon' },
|
AFTERNOON: { value: 2, label: 'Après-midi' },
|
||||||
TOTAL: { value: 3, label: 'Total' },
|
TOTAL: { value: 3, label: 'Journée' },
|
||||||
};
|
};
|
||||||
|
|
||||||
// AbsenceReason constants
|
// AbsenceReason constants
|
||||||
const AbsenceReason = {
|
const AbsenceReason = {
|
||||||
JUSTIFIED_ABSENCE: { value: 1, label: 'Justified Absence' },
|
JUSTIFIED_ABSENCE: { value: 1, label: 'Absence justifiée' },
|
||||||
UNJUSTIFIED_ABSENCE: { value: 2, label: 'Unjustified Absence' },
|
UNJUSTIFIED_ABSENCE: { value: 2, label: 'Absence non justifiée' },
|
||||||
JUSTIFIED_LATE: { value: 3, label: 'Justified Late' },
|
JUSTIFIED_LATE: { value: 3, label: 'Retard justifié' },
|
||||||
UNJUSTIFIED_LATE: { value: 4, label: 'Unjustified Late' },
|
UNJUSTIFIED_LATE: { value: 4, label: 'Retard non justifié' },
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('Absences enregistrées :', absences);
|
// Récupérer les données de la classe et initialiser les élèves filtrés
|
||||||
}, [absences]);
|
if (schoolClassId) {
|
||||||
|
fetchClasse(schoolClassId)
|
||||||
|
.then((classeData) => {
|
||||||
|
logger.debug('Classes récupérées :', classeData);
|
||||||
|
setClasse(classeData);
|
||||||
|
setFilteredStudents(classeData.students); // Initialiser les élèves filtrés
|
||||||
|
setSelectedLevels(getNiveauxLabels(classeData.levels)); // Initialiser les niveaux sélectionnés
|
||||||
|
})
|
||||||
|
.catch(requestErrorHandler);
|
||||||
|
}
|
||||||
|
}, [schoolClassId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialiser les niveaux sélectionnés avec tous les niveaux disponibles
|
// Récupérer les absences pour l'établissement sélectionné
|
||||||
if (classe?.levels?.length > 0) {
|
if (selectedEstablishmentId) {
|
||||||
const initialLevels = getNiveauxLabels(classe.levels);
|
fetchAbsences(selectedEstablishmentId)
|
||||||
setSelectedLevels(initialLevels);
|
.then((data) => {
|
||||||
|
const absencesById = data.reduce((acc, absence) => {
|
||||||
|
acc[absence.student] = absence;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
setFetchedAbsences(absencesById);
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
logger.error('Erreur lors de la récupération des absences :', error)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [classe]);
|
}, [selectedEstablishmentId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Filtrer les élèves en fonction des niveaux sélectionnés
|
// Filtrer les élèves en fonction des niveaux sélectionnés
|
||||||
if (selectedLevels.length > 0) {
|
if (classe && selectedLevels.length > 0) {
|
||||||
const filtered = classe.students.filter((student) =>
|
const filtered = classe.students.filter((student) =>
|
||||||
selectedLevels.includes(getNiveauLabel(student.level))
|
selectedLevels.includes(getNiveauLabel(student.level))
|
||||||
);
|
);
|
||||||
@ -80,29 +100,34 @@ export default function Page() {
|
|||||||
}, [selectedLevels, classe]);
|
}, [selectedLevels, classe]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialiser l'état des cases cochées avec tous les élèves présents par défaut
|
// Initialiser `attendance` et `formAbsences` en fonction des élèves filtrés et des absences
|
||||||
if (filteredStudents.length > 0) {
|
if (filteredStudents.length > 0 && fetchedAbsences) {
|
||||||
const initialAttendance = filteredStudents.reduce((acc, student) => {
|
const today = new Date().toISOString().split('T')[0];
|
||||||
acc[student.id] = true; // Tous les élèves sont cochés par défaut
|
|
||||||
return acc;
|
const initialAttendance = {};
|
||||||
}, {});
|
const initialFormAbsences = {};
|
||||||
|
|
||||||
|
filteredStudents.forEach((student) => {
|
||||||
|
const existingAbsence =
|
||||||
|
fetchedAbsences[student.id] &&
|
||||||
|
fetchedAbsences[student.id].day === today
|
||||||
|
? fetchedAbsences[student.id]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (existingAbsence) {
|
||||||
|
// Si une absence existe pour aujourd'hui, décocher la case et pré-remplir les champs
|
||||||
|
initialAttendance[student.id] = false;
|
||||||
|
initialFormAbsences[student.id] = { ...existingAbsence };
|
||||||
|
} else {
|
||||||
|
// Sinon, cocher la case par défaut
|
||||||
|
initialAttendance[student.id] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
setAttendance(initialAttendance);
|
setAttendance(initialAttendance);
|
||||||
|
setFormAbsences(initialFormAbsences);
|
||||||
}
|
}
|
||||||
}, [filteredStudents]);
|
}, [filteredStudents, fetchedAbsences]);
|
||||||
|
|
||||||
const handleCreateGroup = () => {
|
|
||||||
if (!newGroup.name || !newGroup.level || !newGroup.students.length) {
|
|
||||||
setPopupMessage(
|
|
||||||
'Tous les champs doivent être remplis pour créer un groupe.'
|
|
||||||
);
|
|
||||||
setPopupVisible(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedGroups = [...groups, newGroup];
|
|
||||||
setGroups(updatedGroups);
|
|
||||||
setNewGroup({ name: '', level: null, students: [] });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLevelClick = (label) => {
|
const handleLevelClick = (label) => {
|
||||||
setSelectedLevels(
|
setSelectedLevels(
|
||||||
@ -118,11 +143,17 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleValidateAttendance = () => {
|
const handleValidateAttendance = () => {
|
||||||
console.log('Présence validée :', attendance);
|
// Filtrer les absences modifiées uniquement pour les étudiants décochés (absents)
|
||||||
console.log('Absences enregistrées :', absences);
|
const absencesToUpdate = Object.entries(formAbsences).filter(
|
||||||
|
([studentId, absenceData]) =>
|
||||||
|
!attendance[studentId] && // L'étudiant est décoché (absent)
|
||||||
|
JSON.stringify(absenceData) !==
|
||||||
|
JSON.stringify(fetchedAbsences[studentId]) // Les données ont été modifiées
|
||||||
|
);
|
||||||
|
|
||||||
// Exemple : Envoyer les absences à une API
|
// Envoyer les absences modifiées à une API
|
||||||
Object.entries(absences).forEach(([studentId, absenceData]) => {
|
absencesToUpdate.forEach(([studentId, absenceData]) => {
|
||||||
|
console.log('Modification absence élève : ', studentId);
|
||||||
saveAbsence(studentId, absenceData);
|
saveAbsence(studentId, absenceData);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -130,25 +161,70 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAttendanceChange = (studentId) => {
|
const handleAttendanceChange = (studentId) => {
|
||||||
|
const today = new Date().toISOString().split('T')[0]; // Obtenir la date actuelle au format YYYY-MM-DD
|
||||||
|
|
||||||
setAttendance((prev) => {
|
setAttendance((prev) => {
|
||||||
const updatedAttendance = {
|
const updatedAttendance = {
|
||||||
...prev,
|
...prev,
|
||||||
[studentId]: !prev[studentId], // Inverser l'état de présence
|
[studentId]: !prev[studentId], // Inverser l'état de présence
|
||||||
};
|
};
|
||||||
|
|
||||||
// Si l'élève est décoché (absent), initialiser les champs d'absence
|
// Si l'élève est décoché (absent)
|
||||||
if (!updatedAttendance[studentId]) {
|
if (!updatedAttendance[studentId]) {
|
||||||
setAbsences((prev) => ({
|
// Vérifier s'il existe une absence pour le jour actuel
|
||||||
...prev,
|
const existingAbsence = Object.values(fetchedAbsences).find(
|
||||||
[studentId]: {
|
(absence) => absence.student === studentId && absence.day === today
|
||||||
day: '',
|
);
|
||||||
reason: null,
|
|
||||||
moment: null,
|
if (existingAbsence) {
|
||||||
},
|
// Afficher l'absence existante pour le jour actuel
|
||||||
}));
|
setFormAbsences((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[studentId]: {
|
||||||
|
...existingAbsence,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
// Initialiser des champs vides pour créer une nouvelle absence
|
||||||
|
setFormAbsences((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[studentId]: {
|
||||||
|
day: today,
|
||||||
|
reason: null,
|
||||||
|
moment: null,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Si l'élève est recoché, supprimer ses données d'absence
|
// Si l'élève est recoché (présent), supprimer l'absence existante
|
||||||
setAbsences((prev) => {
|
const existingAbsence = Object.values(fetchedAbsences).find(
|
||||||
|
(absence) => absence.student === studentId && absence.day === today
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingAbsence) {
|
||||||
|
// Appeler la fonction pour supprimer l'absence
|
||||||
|
deleteAbsences(existingAbsence.id, csrfToken)
|
||||||
|
.then(() => {
|
||||||
|
console.log(
|
||||||
|
`Absence pour l'élève ${studentId} supprimée avec succès.`
|
||||||
|
);
|
||||||
|
// Mettre à jour les absences récupérées
|
||||||
|
setFetchedAbsences((prev) => {
|
||||||
|
const updatedAbsences = { ...prev };
|
||||||
|
delete updatedAbsences[studentId];
|
||||||
|
return updatedAbsences;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(
|
||||||
|
`Erreur lors de la suppression de l'absence pour l'élève ${studentId}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer les données d'absence dans `formAbsences`
|
||||||
|
setFormAbsences((prev) => {
|
||||||
const updatedAbsences = { ...prev };
|
const updatedAbsences = { ...prev };
|
||||||
delete updatedAbsences[studentId];
|
delete updatedAbsences[studentId];
|
||||||
return updatedAbsences;
|
return updatedAbsences;
|
||||||
@ -159,26 +235,64 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveAbsence = async (studentId, absenceData) => {
|
const saveAbsence = (studentId, absenceData) => {
|
||||||
try {
|
if (!absenceData.reason || !studentId || !absenceData.moment) {
|
||||||
const response = await fetch('/api/absences', {
|
console.error('Tous les champs requis doivent être fournis.');
|
||||||
method: 'POST',
|
return;
|
||||||
headers: {
|
}
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
studentId,
|
|
||||||
...absenceData,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
const payload = {
|
||||||
throw new Error("Erreur lors de l'enregistrement de l'absence");
|
student: studentId,
|
||||||
}
|
day: absenceData.day,
|
||||||
|
reason: absenceData.reason,
|
||||||
|
moment: absenceData.moment,
|
||||||
|
establishment: selectedEstablishmentId,
|
||||||
|
};
|
||||||
|
|
||||||
console.log(`Absence pour l'élève ${studentId} enregistrée avec succès.`);
|
if (absenceData.id) {
|
||||||
} catch (error) {
|
// Modifier une absence existante
|
||||||
console.error('Erreur :', error);
|
editAbsences(absenceData.id, payload, csrfToken)
|
||||||
|
.then(() => {
|
||||||
|
console.log(
|
||||||
|
`Absence pour l'élève ${studentId} modifiée avec succès.`
|
||||||
|
);
|
||||||
|
// Mettre à jour fetchedAbsences et formAbsences localement
|
||||||
|
setFetchedAbsences((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[studentId]: { ...prev[studentId], ...payload },
|
||||||
|
}));
|
||||||
|
setFormAbsences((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[studentId]: { ...prev[studentId], ...payload },
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(
|
||||||
|
`Erreur lors de la modification de l'absence pour l'élève ${studentId}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Créer une nouvelle absence
|
||||||
|
createAbsences(payload, csrfToken)
|
||||||
|
.then((response) => {
|
||||||
|
console.log(`Absence pour l'élève ${studentId} créée avec succès.`);
|
||||||
|
// Mettre à jour fetchedAbsences et formAbsences localement
|
||||||
|
setFetchedAbsences((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[studentId]: { id: response.id, ...payload },
|
||||||
|
}));
|
||||||
|
setFormAbsences((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[studentId]: { id: response.id, ...payload },
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(
|
||||||
|
`Erreur lors de la création de l'absence pour l'élève ${studentId}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -186,15 +300,7 @@ export default function Page() {
|
|||||||
logger.error('Error fetching data:', err);
|
logger.error('Error fetching data:', err);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const today = new Date().toISOString().split('T')[0]; // Obtenez la date actuelle au format YYYY-MM-DD
|
||||||
fetchClasse(schoolClassId)
|
|
||||||
.then((classeData) => {
|
|
||||||
logger.debug('Classes récupérées :', classeData);
|
|
||||||
setClasse(classeData);
|
|
||||||
setFilteredStudents(classeData.students);
|
|
||||||
})
|
|
||||||
.catch(requestErrorHandler);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
@ -259,146 +365,185 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section Élèves */}
|
{/* Affichage de la date du jour */}
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4 bg-white p-4 rounded-lg shadow-md">
|
||||||
<h2 className="text-xl font-semibold flex items-center">
|
<div className="flex items-center space-x-3">
|
||||||
<Users className="w-6 h-6 mr-2" />
|
<div className="flex items-center justify-center w-10 h-10 bg-emerald-100 text-emerald-600 rounded-full">
|
||||||
Élèves
|
<Clock className="w-6 h-6" />
|
||||||
</h2>
|
</div>
|
||||||
{!isEditingAttendance ? (
|
<h2 className="text-lg font-semibold text-gray-800">
|
||||||
<Button
|
Appel du jour :{' '}
|
||||||
text="Faire l'appel"
|
<span className="ml-2 text-emerald-600">{today}</span>
|
||||||
onClick={handleToggleAttendanceMode}
|
</h2>
|
||||||
primary
|
</div>
|
||||||
className="px-4 py-2"
|
<div className="flex items-center">
|
||||||
/>
|
{!isEditingAttendance ? (
|
||||||
) : (
|
<Button
|
||||||
<Button
|
text="Faire l'appel"
|
||||||
text="Valider l'appel"
|
onClick={handleToggleAttendanceMode}
|
||||||
onClick={handleValidateAttendance}
|
primary
|
||||||
primary
|
className="px-4 py-2 bg-emerald-500 text-white rounded-lg shadow hover:bg-emerald-600 transition-all"
|
||||||
className="px-4 py-2"
|
/>
|
||||||
/>
|
) : (
|
||||||
)}
|
<Button
|
||||||
|
text="Valider l'appel"
|
||||||
|
onClick={handleValidateAttendance}
|
||||||
|
primary
|
||||||
|
className="px-4 py-2 bg-emerald-500 text-white rounded-lg shadow hover:bg-emerald-600 transition-all"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
name: 'photo',
|
name: 'Nom',
|
||||||
transform: (row) => (
|
transform: (row) => (
|
||||||
<div className="flex justify-center items-center">
|
<div className="text-center">{row.last_name}</div>
|
||||||
{row.photo ? (
|
),
|
||||||
<a
|
},
|
||||||
href={`${BASE_URL}${row.photo}`}
|
{
|
||||||
target="_blank"
|
name: 'Prénom',
|
||||||
rel="noopener noreferrer"
|
transform: (row) => (
|
||||||
>
|
<div className="text-center">{row.first_name}</div>
|
||||||
<img
|
),
|
||||||
src={`${BASE_URL}${row.photo}`}
|
},
|
||||||
alt={`${row.first_name} ${row.last_name}`}
|
{
|
||||||
className="w-10 h-10 object-cover transition-transform duration-200 hover:scale-125 cursor-pointer rounded-full"
|
name: 'Niveau',
|
||||||
/>
|
transform: (row) => (
|
||||||
</a>
|
<div className="text-center">{getNiveauLabel(row.level)}</div>
|
||||||
) : (
|
|
||||||
<div className="w-10 h-10 flex items-center justify-center bg-gray-200 rounded-full">
|
|
||||||
<span className="text-gray-500 text-sm font-semibold">
|
|
||||||
{row.first_name[0]}
|
|
||||||
{row.last_name[0]}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ name: 'Nom', transform: (row) => row.last_name },
|
|
||||||
{ name: 'Prénom', transform: (row) => row.first_name },
|
|
||||||
{ name: 'Niveau', transform: (row) => getNiveauLabel(row.level) },
|
|
||||||
...(isEditingAttendance
|
...(isEditingAttendance
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
name: 'Présent',
|
name: "Gestion de l'appel",
|
||||||
transform: (row) => (
|
transform: (row) => (
|
||||||
<input
|
<div className="flex flex-col gap-2 items-center">
|
||||||
type="checkbox"
|
{/* Case à cocher pour la présence */}
|
||||||
checked={attendance[row.id] || false}
|
<CheckBox
|
||||||
onChange={() => handleAttendanceChange(row.id)}
|
item={{ id: row.id }}
|
||||||
className="w-5 h-5"
|
formData={{
|
||||||
/>
|
attendance: attendance[row.id] ? [row.id] : [],
|
||||||
|
}}
|
||||||
|
handleChange={() => handleAttendanceChange(row.id)}
|
||||||
|
fieldName="attendance"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Champs pour le motif et le moment */}
|
||||||
|
{!attendance[row.id] && (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2">
|
||||||
|
<SelectChoice
|
||||||
|
name={`reason-${row.id}`}
|
||||||
|
label=""
|
||||||
|
placeHolder="Motif"
|
||||||
|
selected={formAbsences[row.id]?.reason || ''}
|
||||||
|
callback={(e) =>
|
||||||
|
setFormAbsences((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[row.id]: {
|
||||||
|
...prev[row.id],
|
||||||
|
reason: parseInt(e.target.value, 10),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
choices={Object.values(AbsenceReason).map(
|
||||||
|
(reason) => ({
|
||||||
|
value: reason.value,
|
||||||
|
label: reason.label,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectChoice
|
||||||
|
name={`moment-${row.id}`}
|
||||||
|
label=""
|
||||||
|
placeHolder="Durée"
|
||||||
|
selected={formAbsences[row.id]?.moment || ''}
|
||||||
|
callback={(e) =>
|
||||||
|
setFormAbsences((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[row.id]: {
|
||||||
|
...prev[row.id],
|
||||||
|
moment: parseInt(e.target.value, 10),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
choices={Object.values(AbsenceMoment).map(
|
||||||
|
(moment) => ({
|
||||||
|
value: moment.value,
|
||||||
|
label: moment.label,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Détails d'absence",
|
|
||||||
transform: (row) =>
|
|
||||||
!attendance[row.id] && (
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-2">
|
|
||||||
{/* Champ pour le jour */}
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
value={absences[row.id]?.day || ''}
|
|
||||||
onChange={(e) =>
|
|
||||||
setAbsences((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[row.id]: {
|
|
||||||
...prev[row.id],
|
|
||||||
day: e.target.value,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-emerald-500 focus:border-emerald-500 sm:text-sm"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Champ pour le motif */}
|
|
||||||
<SelectChoice
|
|
||||||
name={`reason-${row.id}`}
|
|
||||||
label=""
|
|
||||||
placeHolder="Motif"
|
|
||||||
selected={absences[row.id]?.reason || ''}
|
|
||||||
callback={(e) =>
|
|
||||||
setAbsences((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[row.id]: {
|
|
||||||
...prev[row.id],
|
|
||||||
reason: parseInt(e.target.value, 10),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
choices={Object.values(AbsenceReason).map(
|
|
||||||
(reason) => ({
|
|
||||||
value: reason.value,
|
|
||||||
label: reason.label,
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Champ pour le moment */}
|
|
||||||
<SelectChoice
|
|
||||||
name={`moment-${row.id}`}
|
|
||||||
label=""
|
|
||||||
placeHolder="Moment"
|
|
||||||
selected={absences[row.id]?.moment || ''}
|
|
||||||
callback={(e) =>
|
|
||||||
setAbsences((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[row.id]: {
|
|
||||||
...prev[row.id],
|
|
||||||
moment: parseInt(e.target.value, 10),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
choices={Object.values(AbsenceMoment).map(
|
|
||||||
(moment) => ({
|
|
||||||
value: moment.value,
|
|
||||||
label: moment.label,
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
: []),
|
: [
|
||||||
|
{
|
||||||
|
name: 'Statut',
|
||||||
|
transform: (row) => {
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
const absence =
|
||||||
|
formAbsences[row.id] ||
|
||||||
|
Object.values(fetchedAbsences).find(
|
||||||
|
(absence) =>
|
||||||
|
absence.student === row.id && absence.day === today
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!absence) {
|
||||||
|
return (
|
||||||
|
<div className="text-center text-green-500 flex justify-center items-center gap-2">
|
||||||
|
<CheckCircle className="w-5 h-5" />
|
||||||
|
Présent
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (absence.reason) {
|
||||||
|
case AbsenceReason.JUSTIFIED_LATE.value:
|
||||||
|
return (
|
||||||
|
<div className="text-center text-yellow-500 flex justify-center items-center gap-2">
|
||||||
|
<Clock className="w-5 h-5" />
|
||||||
|
Retard justifié
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case AbsenceReason.UNJUSTIFIED_LATE.value:
|
||||||
|
return (
|
||||||
|
<div className="text-center text-red-500 flex justify-center items-center gap-2">
|
||||||
|
<Clock className="w-5 h-5" />
|
||||||
|
Retard non justifié
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case AbsenceReason.JUSTIFIED_ABSENCE.value:
|
||||||
|
return (
|
||||||
|
<div className="text-center text-blue-500 flex justify-center items-center gap-2">
|
||||||
|
<CheckCircle className="w-5 h-5" />
|
||||||
|
Absence justifiée
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case AbsenceReason.UNJUSTIFIED_ABSENCE.value:
|
||||||
|
return (
|
||||||
|
<div className="text-center text-red-500 flex justify-center items-center gap-2">
|
||||||
|
<CheckCircle className="w-5 h-5" />
|
||||||
|
Absence non justifiée
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<div className="text-center text-gray-500 flex justify-center items-center gap-2">
|
||||||
|
<CheckCircle className="w-5 h-5" />
|
||||||
|
Statut inconnu
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
]}
|
]}
|
||||||
data={filteredStudents} // Utiliser les élèves filtrés
|
data={filteredStudents} // Utiliser les élèves filtrés
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export default function Page() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEstablishmentId) {
|
if (selectedEstablishmentId) {
|
||||||
|
setIsLoading(true);
|
||||||
fetchClasses(selectedEstablishmentId)
|
fetchClasses(selectedEstablishmentId)
|
||||||
.then((classesData) => {
|
.then((classesData) => {
|
||||||
logger.debug('Classes récupérées :', classesData);
|
logger.debug('Classes récupérées :', classesData);
|
||||||
@ -47,6 +48,7 @@ export default function Page() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
setClasses(filteredClasses); // Mettre à jour les classes filtrées
|
setClasses(filteredClasses); // Mettre à jour les classes filtrées
|
||||||
|
setIsLoading(false);
|
||||||
})
|
})
|
||||||
.catch(requestErrorHandler);
|
.catch(requestErrorHandler);
|
||||||
}
|
}
|
||||||
@ -71,7 +73,7 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading || classes.length === 0) {
|
if (isLoading) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
BE_SUBSCRIPTION_CHILDRENS_URL,
|
BE_SUBSCRIPTION_CHILDRENS_URL,
|
||||||
BE_SUBSCRIPTION_REGISTERFORMS_URL,
|
BE_SUBSCRIPTION_REGISTERFORMS_URL,
|
||||||
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL,
|
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL,
|
||||||
|
BE_SUBSCRIPTION_ABSENCES_URL,
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
|
|
||||||
export const PENDING = 'pending';
|
export const PENDING = 'pending';
|
||||||
@ -213,3 +214,50 @@ export const dissociateGuardian = async (studentId, guardianId) => {
|
|||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchAbsences = (establishment) => {
|
||||||
|
return fetch(
|
||||||
|
`${BE_SUBSCRIPTION_ABSENCES_URL}?establishment_id=${establishment}`
|
||||||
|
).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createAbsences = (data, csrfToken) => {
|
||||||
|
return fetch(`${BE_SUBSCRIPTION_ABSENCES_URL}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const editAbsences = (absenceId, payload, csrfToken) => {
|
||||||
|
return fetch(`${BE_SUBSCRIPTION_ABSENCES_URL}/${absenceId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload), // Sérialisez les données en JSON
|
||||||
|
credentials: 'include',
|
||||||
|
}).then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
return response.json().then((error) => {
|
||||||
|
throw new Error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteAbsences = (id, csrfToken) => {
|
||||||
|
return fetch(`${BE_SUBSCRIPTION_ABSENCES_URL}/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export const BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL = `${BASE_UR
|
|||||||
export const BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL = `${BASE_URL}/Subscriptions/registrationParentFileMasters`;
|
export const BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL = `${BASE_URL}/Subscriptions/registrationParentFileMasters`;
|
||||||
export const BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationParentFileTemplates`;
|
export const BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationParentFileTemplates`;
|
||||||
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`;
|
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`;
|
||||||
|
export const BE_SUBSCRIPTION_ABSENCES_URL = `${BASE_URL}/Subscriptions/absences`;
|
||||||
|
|
||||||
//GESTION ECOLE
|
//GESTION ECOLE
|
||||||
export const BE_SCHOOL_SPECIALITIES_URL = `${BASE_URL}/School/specialities`;
|
export const BE_SCHOOL_SPECIALITIES_URL = `${BASE_URL}/School/specialities`;
|
||||||
|
|||||||
Reference in New Issue
Block a user