diff --git a/Back-End/GestionEnseignants/apps.py b/Back-End/GestionEnseignants/apps.py index 02c8cd3..b9397d8 100644 --- a/Back-End/GestionEnseignants/apps.py +++ b/Back-End/GestionEnseignants/apps.py @@ -3,8 +3,8 @@ from django.db.models.signals import post_migrate def create_specialite(sender, **kwargs): from .models import Specialite - if not Specialite.objects.filter(nom='TRANSVERSE').exists(): - Specialite.objects.create(nom='TRANSVERSE', codeCouleur='#FF0000') + if not Specialite.objects.filter(nom='GROUPE').exists(): + Specialite.objects.create(nom='GROUPE', codeCouleur='#FF0000') class GestionenseignantsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' diff --git a/Back-End/GestionEnseignants/models.py b/Back-End/GestionEnseignants/models.py index 3ad84aa..49bc16d 100644 --- a/Back-End/GestionEnseignants/models.py +++ b/Back-End/GestionEnseignants/models.py @@ -3,6 +3,7 @@ from GestionLogin.models import Profil from django.db.models import JSONField from django.db.models.signals import post_save from django.dispatch import receiver +from django.contrib.postgres.fields import ArrayField class Specialite(models.Model): nom = models.CharField(max_length=100) @@ -18,31 +19,36 @@ class Enseignant(models.Model): mail = models.EmailField(unique=True) specialites = models.ManyToManyField(Specialite, related_name='enseignants') profilAssocie = models.ForeignKey(Profil, on_delete=models.CASCADE, null=True, blank=True) + dateCreation = models.DateTimeField(auto_now=True) def __str__(self): return f"{self.nom} {self.prenom}" class Classe(models.Model): - nom_ambiance = models.CharField(max_length=255) + nom_ambiance = models.CharField(max_length=255, null=True, blank=True) tranche_age = models.JSONField() nombre_eleves = models.PositiveIntegerField() langue_enseignement = models.CharField(max_length=255) annee_scolaire = models.CharField(max_length=9) dateCreation = models.DateTimeField(auto_now_add=True) enseignants = models.ManyToManyField(Enseignant, related_name='classes') + niveaux = models.JSONField(default=list) def __str__(self): return self.nom_ambiance class Planning(models.Model): + PLANNING_TYPE_CHOICES = [ + (1, 'Annuel'), + (2, 'Semestriel'), + (3, 'Trimestriel') + ] + classe = models.OneToOneField(Classe, on_delete=models.SET_NULL, null=True, blank=True, related_name='planning') - emploiDuTemps = JSONField(default=list) + emploiDuTemps = JSONField(default=dict) + type = models.IntegerField(choices=PLANNING_TYPE_CHOICES, default=1) + plageHoraire = models.JSONField(default=list) + joursOuverture = ArrayField(models.IntegerField(), default=list) def __str__(self): return f'Planning de {self.classe.nom_ambiance}' - -@receiver(post_save, sender=Classe) -def create_planning(sender, instance, created, **kwargs): - if created and not hasattr(instance, 'planning'): - Planning.objects.create(classe=instance) - diff --git a/Back-End/GestionEnseignants/serializers.py b/Back-End/GestionEnseignants/serializers.py index e22c713..37da853 100644 --- a/Back-End/GestionEnseignants/serializers.py +++ b/Back-End/GestionEnseignants/serializers.py @@ -36,10 +36,11 @@ class EnseignantSerializer(serializers.ModelSerializer): profilAssocie = ProfilSerializer(read_only=True) DroitLabel = serializers.SerializerMethodField() DroitValue = serializers.SerializerMethodField() + dateCreation_formattee = serializers.SerializerMethodField() class Meta: model = Enseignant - fields = ['id', 'nom', 'prenom', 'mail', 'specialites', 'specialites_ids', 'classes_principal', 'profilAssocie', 'profilAssocie_id', 'DroitLabel', 'DroitValue'] + fields = ['id', 'nom', 'prenom', 'mail', 'specialites', 'specialites_ids', 'classes_principal', 'profilAssocie', 'profilAssocie_id', 'DroitLabel', 'DroitValue', 'dateCreation', 'dateCreation_formattee'] def create(self, validated_data): specialites_data = validated_data.pop('specialites', None) @@ -67,63 +68,146 @@ class EnseignantSerializer(serializers.ModelSerializer): def get_DroitValue(self, obj): return obj.profilAssocie.droit if obj.profilAssocie else None + def get_dateCreation_formattee(self, obj): + utc_time = timezone.localtime(obj.dateCreation) # Convertir en heure locale + local_tz = pytz.timezone(settings.TZ_APPLI) + local_time = utc_time.astimezone(local_tz) + return local_time.strftime("%d-%m-%Y %H:%M") + class PlanningSerializer(serializers.ModelSerializer): - classe_id = serializers.PrimaryKeyRelatedField(queryset=Classe.objects.all(), source='classe') + emploiDuTemps = serializers.SerializerMethodField() class Meta: model = Planning - fields = ['id', 'classe', 'classe_id', 'emploiDuTemps'] + fields = ['id', 'emploiDuTemps', 'type', 'plageHoraire', 'joursOuverture'] + + def get_emploiDuTemps(self, obj): + emploi_du_temps = obj.emploiDuTemps + if obj.classe: + enseignants = obj.classe.enseignants.all() # Récupérer tous les enseignants associés à la classe + + # Dictionnaire pour accéder rapidement aux spécialités des enseignants + specialite_enseignants = {} + for enseignant in enseignants: + for specialite in enseignant.specialites.all(): + if specialite.nom not in specialite_enseignants: + specialite_enseignants[specialite.nom] = [] + specialite_enseignants[specialite.nom].append(f"{enseignant.prenom} {enseignant.nom}") + + if obj.type == 1: # Planning annuel + for day, events in emploi_du_temps.items(): + for event in events: + # Ajouter les enseignants associés à la spécialité + event['teachers'] = specialite_enseignants.get(event['matiere'], []) + # Ajouter la couleur de la spécialité + event['color'] = next( + (specialite.codeCouleur for enseignant in enseignants for specialite in enseignant.specialites.all() if specialite.nom == event['matiere']), + "#FFFFFF" # Couleur par défaut si non trouvée + ) + + elif obj.type in [2, 3]: # Planning semestriel ou trimestriel + for period_key, period_value in emploi_du_temps.items(): + for day, events in period_value.items(): + if day in ['DateDebut', 'DateFin']: + continue # Ignorer les clés DateDebut et DateFin + for event in events: + print(f'event : {event}') + # Ajouter les enseignants associés à la spécialité + event['teachers'] = specialite_enseignants.get(event['matiere'], []) + # Ajouter la couleur de la spécialité + event['color'] = next( + (specialite.codeCouleur for enseignant in enseignants for specialite in enseignant.specialites.all() if specialite.nom == event['matiere']), + "#FFFFFF" # Couleur par défaut si non trouvée + ) + + return emploi_du_temps + + def to_internal_value(self, data): + internal_value = super().to_internal_value(data) + internal_value['emploiDuTemps'] = data.get('emploiDuTemps', {}) + return internal_value class ClasseSerializer(serializers.ModelSerializer): dateCreation_formattee = serializers.SerializerMethodField() enseignants = EnseignantSerializer(many=True, read_only=True) enseignants_ids = serializers.PrimaryKeyRelatedField(queryset=Enseignant.objects.all(), many=True, source='enseignants') eleves = serializers.SerializerMethodField() - planning = serializers.SerializerMethodField() + planning = PlanningSerializer() class Meta: model = Classe - fields = ['id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement', 'enseignants', 'enseignants_ids', 'annee_scolaire', 'dateCreation', 'dateCreation_formattee', 'eleves', 'planning'] + fields = [ + 'id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement', + 'enseignants', 'enseignants_ids', 'annee_scolaire', 'dateCreation', + 'dateCreation_formattee', 'eleves', 'planning', 'niveaux' + ] def create(self, validated_data): planning_data = validated_data.pop('planning', None) enseignants_data = validated_data.pop('enseignants', []) - classe = Classe.objects.create(**validated_data) + niveaux_data = validated_data.pop('niveaux', []) + + classe = Classe.objects.create( + nom_ambiance=validated_data.get('nom_ambiance', ''), + tranche_age=validated_data.get('tranche_age', []), + nombre_eleves=validated_data.get('nombre_eleves', 0), + langue_enseignement=validated_data.get('langue_enseignement', ''), + annee_scolaire=validated_data.get('annee_scolaire', ''), + niveaux=niveaux_data + ) + classe.enseignants.set(enseignants_data) + if planning_data and not hasattr(classe, 'planning'): - Planning.objects.create(classe=classe, **planning_data) + Planning.objects.create( + classe=classe, + emploiDuTemps=planning_data.get('emploiDuTemps', {}), + type=planning_data.get('type', 1), + plageHoraire=planning_data.get('plageHoraire', []), + joursOuverture=planning_data.get('joursOuverture', []) + ) + return classe def update(self, instance, validated_data): planning_data = validated_data.pop('planning', None) enseignants_data = validated_data.pop('enseignants', []) + niveaux_data = validated_data.pop('niveaux', []) + instance.nom_ambiance = validated_data.get('nom_ambiance', instance.nom_ambiance) instance.tranche_age = validated_data.get('tranche_age', instance.tranche_age) instance.nombre_eleves = validated_data.get('nombre_eleves', instance.nombre_eleves) instance.langue_enseignement = validated_data.get('langue_enseignement', instance.langue_enseignement) instance.annee_scolaire = validated_data.get('annee_scolaire', instance.annee_scolaire) + instance.niveaux = niveaux_data + instance.save() instance.enseignants.set(enseignants_data) - if planning_data: + + if planning_data: planning = instance.planning planning.emploiDuTemps = planning_data.get('emploiDuTemps', planning.emploiDuTemps) + planning.type = planning_data.get('type', planning.type) + planning.plageHoraire = planning_data.get('plageHoraire', planning.plageHoraire) + planning.joursOuverture = planning_data.get('joursOuverture', planning.joursOuverture) + planning.save() + return instance def get_dateCreation_formattee(self, obj): utc_time = timezone.localtime(obj.dateCreation) # Convertir en heure locale local_tz = pytz.timezone(settings.TZ_APPLI) local_time = utc_time.astimezone(local_tz) - return local_time.strftime("%d-%m-%Y %H:%M") def get_eleves(self, obj): elevesList = obj.eleves.all() filtered_eleves = [] for eleve in elevesList: - ficheInscription=bdd.getObject(FicheInscription, "eleve__id", eleve.id) + ficheInscription = bdd.getObject(FicheInscription, "eleve__id", eleve.id) if ficheInscription.etat == ficheInscription.EtatDossierInscription.DI_VALIDE: - filtered_eleves.append(eleve) + filtered_eleves.append(eleve) return EleveSerializer(filtered_eleves, many=True, read_only=True).data def get_planning(self, obj): diff --git a/Back-End/GestionEnseignants/urls.py b/Back-End/GestionEnseignants/urls.py index 41acf66..cfc4420 100644 --- a/Back-End/GestionEnseignants/urls.py +++ b/Back-End/GestionEnseignants/urls.py @@ -1,6 +1,6 @@ from django.urls import path, re_path -from GestionEnseignants.views import EnseignantsView, EnseignantView, SpecialitesView, SpecialiteView, ClassesView, ClasseView +from GestionEnseignants.views import EnseignantsView, EnseignantView, SpecialitesView, SpecialiteView, ClassesView, ClasseView, PlanningsView, PlanningView urlpatterns = [ re_path(r'^enseignants$', EnseignantsView.as_view(), name="enseignants"), @@ -14,4 +14,8 @@ urlpatterns = [ re_path(r'^classes$', ClassesView.as_view(), name="classes"), re_path(r'^classe$', ClasseView.as_view(), name="classe"), re_path(r'^classe/([0-9]+)$', ClasseView.as_view(), name="classe"), + + re_path(r'^plannings$', PlanningsView.as_view(), name="plannings"), + re_path(r'^planning$', PlanningView.as_view(), name="planning"), + re_path(r'^planning/([0-9]+)$', PlanningView.as_view(), name="planning"), ] \ No newline at end of file diff --git a/Back-End/GestionEnseignants/views.py b/Back-End/GestionEnseignants/views.py index af00734..c1facc4 100644 --- a/Back-End/GestionEnseignants/views.py +++ b/Back-End/GestionEnseignants/views.py @@ -4,8 +4,8 @@ from django.utils.decorators import method_decorator from rest_framework.parsers import JSONParser from rest_framework.views import APIView from django.core.cache import cache -from .models import Enseignant, Specialite, Classe -from .serializers import EnseignantSerializer, SpecialiteSerializer, ClasseSerializer +from .models import Enseignant, Specialite, Classe, Planning +from .serializers import EnseignantSerializer, SpecialiteSerializer, ClasseSerializer, PlanningSerializer from N3wtSchool import bdd class EnseignantsView(APIView): @@ -188,4 +188,42 @@ class ClasseView(APIView): eleve.save() classe.delete() - return JsonResponse("La suppression de la classe a été effectuée avec succès", safe=False) \ No newline at end of file + return JsonResponse("La suppression de la classe a été effectuée avec succès", safe=False) + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class PlanningsView(APIView): + def get(self, request): + schedulesList=bdd.getAllObjects(Planning) + schedules_serializer=PlanningSerializer(schedulesList, many=True) + return JsonResponse(schedules_serializer.data, safe=False) + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class PlanningView(APIView): + def get (self, request, _id): + planning = bdd.getObject(_objectName=Planning, _columnName='classe__id', _value=_id) + planning_serializer=PlanningSerializer(planning) + + return JsonResponse(planning_serializer.data, safe=False) + + def post(self, request): + planning_data=JSONParser().parse(request) + planning_serializer = PlanningSerializer(data=planning_data) + + if planning_serializer.is_valid(): + planning_serializer.save() + return JsonResponse(planning_serializer.data, safe=False) + + return JsonResponse(planning_serializer.errors, safe=False) + + def put(self, request, _id): + planning_data=JSONParser().parse(request) + planning = bdd.getObject(_objectName=Planning, _columnName='classe__id', _value=_id) + planning_serializer = PlanningSerializer(planning, data=planning_data) + + if planning_serializer.is_valid(): + planning_serializer.save() + return JsonResponse(planning_serializer.data, safe=False) + + return JsonResponse(planning_serializer.errors, safe=False) diff --git a/Front-End/package-lock.json b/Front-End/package-lock.json index 6cf2c1b..6940276 100644 --- a/Front-End/package-lock.json +++ b/Front-End/package-lock.json @@ -18,6 +18,8 @@ "next-intl": "^3.24.0", "react": "^18", "react-cookie": "^7.2.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18", "react-phone-number-input": "^3.4.8", "react-tooltip": "^5.28.0" @@ -106,6 +108,18 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -897,6 +911,24 @@ } } }, + "node_modules/@react-dnd/asap": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", + "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==", + "license": "MIT" + }, + "node_modules/@react-dnd/invariant": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", + "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==", + "license": "MIT" + }, + "node_modules/@react-dnd/shallowequal": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", + "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==", + "license": "MIT" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "dev": true, @@ -1720,9 +1752,10 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1733,7 +1766,9 @@ "integrity": "sha512-4JwHNqaKZ19doQoNcBjsoYA+I7NqCH/mC/6f5cBWvdKzcK5TMmzLpq3Z/syVHMHJuDGFwJ+rPpGizvrqJybJow==" }, "node_modules/cross-spawn": { - "version": "7.0.3", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -1934,6 +1969,17 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, + "node_modules/dnd-core": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", + "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", + "license": "MIT", + "dependencies": { + "@react-dnd/asap": "^5.0.1", + "@react-dnd/invariant": "^4.0.1", + "redux": "^4.2.0" + } + }, "node_modules/doctrine": { "version": "3.0.0", "dev": true, @@ -2562,7 +2608,6 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -4396,6 +4441,45 @@ "react": ">= 16.3.0" } }, + "node_modules/react-dnd": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", + "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", + "license": "MIT", + "dependencies": { + "@react-dnd/invariant": "^4.0.1", + "@react-dnd/shallowequal": "^4.0.1", + "dnd-core": "^16.0.1", + "fast-deep-equal": "^3.1.3", + "hoist-non-react-statics": "^3.3.2" + }, + "peerDependencies": { + "@types/hoist-non-react-statics": ">= 3.3.1", + "@types/node": ">= 12", + "@types/react": ">= 16", + "react": ">= 16.14" + }, + "peerDependenciesMeta": { + "@types/hoist-non-react-statics": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dnd-html5-backend": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", + "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", + "license": "MIT", + "dependencies": { + "dnd-core": "^16.0.1" + } + }, "node_modules/react-dom": { "version": "18.3.1", "license": "MIT", @@ -4529,6 +4613,15 @@ "node": ">=8.10.0" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "dev": true, @@ -4549,6 +4642,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "dev": true, @@ -5306,12 +5405,13 @@ } }, "node_modules/universal-cookie": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.0.tgz", - "integrity": "sha512-PvcyflJAYACJKr28HABxkGemML5vafHmiL4ICe3e+BEKXRMt0GaFLZhAwgv637kFFnnfiSJ8e6jknrKkMrU+PQ==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz", + "integrity": "sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==", + "license": "MIT", "dependencies": { "@types/cookie": "^0.6.0", - "cookie": "^0.6.0" + "cookie": "^0.7.2" } }, "node_modules/update-browserslist-db": { diff --git a/Front-End/package.json b/Front-End/package.json index 29d968c..09f5c06 100644 --- a/Front-End/package.json +++ b/Front-End/package.json @@ -20,6 +20,8 @@ "next-intl": "^3.24.0", "react": "^18", "react-cookie": "^7.2.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18", "react-phone-number-input": "^3.4.8", "react-tooltip": "^5.28.0" @@ -33,4 +35,4 @@ "postcss": "^8.4.47", "tailwindcss": "^3.4.14" } -} \ No newline at end of file +} diff --git a/Front-End/src/app/[locale]/admin/page.js b/Front-End/src/app/[locale]/admin/page.js index 0a7e5c0..85e18fa 100644 --- a/Front-End/src/app/[locale]/admin/page.js +++ b/Front-End/src/app/[locale]/admin/page.js @@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react'; import { useTranslations } from 'next-intl'; import { Users, Clock, CalendarCheck, School, TrendingUp, UserCheck } from 'lucide-react'; import Loader from '@/components/Loader'; -import { BK_GESTIONINSCRIPTION_CLASSES_URL } from '@/utils/Url'; +import { BK_GESTIONENSEIGNANTS_CLASSES_URL } from '@/utils/Url'; import ClasseDetails from '@/components/ClasseDetails'; // Composant StatCard pour afficher une statistique @@ -59,7 +59,7 @@ export default function DashboardPage() { const [classes, setClasses] = useState([]); const fetchClasses = () => { - fetch(`${BK_GESTIONINSCRIPTION_CLASSES_URL}`) + fetch(`${BK_GESTIONENSEIGNANTS_CLASSES_URL}`) .then(response => response.json()) .then(data => { setClasses(data); diff --git a/Front-End/src/app/[locale]/admin/structure/page.js b/Front-End/src/app/[locale]/admin/structure/page.js index c72a74f..74beeb5 100644 --- a/Front-End/src/app/[locale]/admin/structure/page.js +++ b/Front-End/src/app/[locale]/admin/structure/page.js @@ -1,14 +1,13 @@ 'use client' import React, { useState, useEffect } from 'react'; -import SpecialitiesSection from '@/components/SpecialitiesSection' -import ClassesSection from '@/components/ClassesSection' -import TeachersSection from '@/components/TeachersSection'; -import { BK_GESTIONINSCRIPTION_SPECIALITES_URL, - BK_GESTIONINSCRIPTION_CLASSES_URL, - BK_GESTIONINSCRIPTION_SPECIALITE_URL, - BK_GESTIONINSCRIPTION_CLASSE_URL, - BK_GESTIONINSCRIPTION_TEACHERS_URL, - BK_GESTIONINSCRIPTION_TEACHER_URL } from '@/utils/Url'; +import { School, Calendar } from 'lucide-react'; +import TabsStructure from '@/components/Structure/Configuration/TabsStructure'; +import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement' +import StructureManagement from '@/components/Structure/Configuration/StructureManagement' +import { BK_GESTIONENSEIGNANTS_SPECIALITES_URL, + BK_GESTIONENSEIGNANTS_CLASSES_URL, + BK_GESTIONENSEIGNANTS_TEACHERS_URL, + BK_GESTIONENSEIGNANTS_PLANNINGS_URL } from '@/utils/Url'; import DjangoCSRFToken from '@/components/DjangoCSRFToken' import useCsrfToken from '@/hooks/useCsrfToken'; @@ -16,6 +15,12 @@ export default function Page() { const [specialities, setSpecialities] = useState([]); const [classes, setClasses] = useState([]); const [teachers, setTeachers] = useState([]); + const [schedules, setSchedules] = useState([]); + const [activeTab, setActiveTab] = useState('Configuration'); + const tabs = [ + { id: 'Configuration', title: "Configuration de l'école", icon: School }, + { id: 'Schedule', title: "Gestion de l'emploi du temps", icon: Calendar }, + ]; const csrfToken = useCsrfToken(); @@ -28,10 +33,13 @@ export default function Page() { // Fetch data for classes fetchClasses(); + + // Fetch data for schedules + fetchSchedules(); }, []); const fetchSpecialities = () => { - fetch(`${BK_GESTIONINSCRIPTION_SPECIALITES_URL}`) + fetch(`${BK_GESTIONENSEIGNANTS_SPECIALITES_URL}`) .then(response => response.json()) .then(data => { setSpecialities(data); @@ -42,7 +50,7 @@ export default function Page() { }; const fetchTeachers = () => { - fetch(`${BK_GESTIONINSCRIPTION_TEACHERS_URL}`) + fetch(`${BK_GESTIONENSEIGNANTS_TEACHERS_URL}`) .then(response => response.json()) .then(data => { setTeachers(data); @@ -53,7 +61,7 @@ export default function Page() { }; const fetchClasses = () => { - fetch(`${BK_GESTIONINSCRIPTION_CLASSES_URL}`) + fetch(`${BK_GESTIONENSEIGNANTS_CLASSES_URL}`) .then(response => response.json()) .then(data => { setClasses(data); @@ -63,6 +71,17 @@ export default function Page() { }); }; + const fetchSchedules = () => { + fetch(`${BK_GESTIONENSEIGNANTS_PLANNINGS_URL}`) + .then(response => response.json()) + .then(data => { + setSchedules(data); + }) + .catch(error => { + console.error('Error fetching classes:', error); + }); + }; + const handleCreate = (url, newData, setDatas) => { fetch(url, { method: 'POST', @@ -102,6 +121,26 @@ export default function Page() { }); }; + const handleUpdatePlanning = (url, id, updatedData) => { + fetch(`${url}/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify(updatedData), + credentials: 'include' + }) + .then(response => response.json()) + .then(data => { + console.log('Planning mis à jour avec succès :', data); + //setDatas(data); + }) + .catch(error => { + console.error('Erreur :', error); + }); + }; + const handleDelete = (url, id, setDatas) => { fetch(`${url}/${id}`, { method:'DELETE', @@ -127,30 +166,33 @@ export default function Page() {