mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
feat: Configuration et gestion du planning [#2]
This commit is contained in:
@ -1,10 +1,21 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from GestionLogin.models import Profil
|
from GestionLogin.models import Profil
|
||||||
from django.db.models import JSONField
|
from django.db.models import JSONField
|
||||||
from django.db.models.signals import post_save
|
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
|
||||||
|
NIVEAU_CHOICES = [
|
||||||
|
(1, 'Très Petite Section (TPS)'),
|
||||||
|
(2, 'Petite Section (PS)'),
|
||||||
|
(3, 'Moyenne Section (MS)'),
|
||||||
|
(4, 'Grande Section (GS)'),
|
||||||
|
(5, 'Cours Préparatoire (CP)'),
|
||||||
|
(6, 'Cours Élémentaire 1 (CE1)'),
|
||||||
|
(7, 'Cours Élémentaire 2 (CE2)'),
|
||||||
|
(8, 'Cours Moyen 1 (CM1)'),
|
||||||
|
(9, 'Cours Moyen 2 (CM2)')
|
||||||
|
]
|
||||||
|
|
||||||
class Specialite(models.Model):
|
class Specialite(models.Model):
|
||||||
nom = models.CharField(max_length=100)
|
nom = models.CharField(max_length=100)
|
||||||
dateCreation = models.DateTimeField(auto_now=True)
|
dateCreation = models.DateTimeField(auto_now=True)
|
||||||
@ -25,6 +36,12 @@ class Enseignant(models.Model):
|
|||||||
return f"{self.nom} {self.prenom}"
|
return f"{self.nom} {self.prenom}"
|
||||||
|
|
||||||
class Classe(models.Model):
|
class Classe(models.Model):
|
||||||
|
PLANNING_TYPE_CHOICES = [
|
||||||
|
(1, 'Annuel'),
|
||||||
|
(2, 'Semestriel'),
|
||||||
|
(3, 'Trimestriel')
|
||||||
|
]
|
||||||
|
|
||||||
nom_ambiance = models.CharField(max_length=255, null=True, blank=True)
|
nom_ambiance = models.CharField(max_length=255, null=True, blank=True)
|
||||||
tranche_age = models.JSONField()
|
tranche_age = models.JSONField()
|
||||||
nombre_eleves = models.PositiveIntegerField()
|
nombre_eleves = models.PositiveIntegerField()
|
||||||
@ -32,23 +49,20 @@ class Classe(models.Model):
|
|||||||
annee_scolaire = models.CharField(max_length=9)
|
annee_scolaire = models.CharField(max_length=9)
|
||||||
dateCreation = models.DateTimeField(auto_now_add=True)
|
dateCreation = models.DateTimeField(auto_now_add=True)
|
||||||
enseignants = models.ManyToManyField(Enseignant, related_name='classes')
|
enseignants = models.ManyToManyField(Enseignant, related_name='classes')
|
||||||
niveaux = models.JSONField(default=list)
|
niveaux = ArrayField(models.IntegerField(choices=NIVEAU_CHOICES), default=list)
|
||||||
|
type = models.IntegerField(choices=PLANNING_TYPE_CHOICES, default=1)
|
||||||
|
plage_horaire = models.JSONField(default=list)
|
||||||
|
jours_ouverture = ArrayField(models.IntegerField(), default=list)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.nom_ambiance
|
return self.nom_ambiance
|
||||||
|
|
||||||
class Planning(models.Model):
|
class Planning(models.Model):
|
||||||
PLANNING_TYPE_CHOICES = [
|
niveau = models.IntegerField(choices=NIVEAU_CHOICES, null=True, blank=True)
|
||||||
(1, 'Annuel'),
|
classe = models.ForeignKey(Classe, null=True, blank=True, related_name='plannings', on_delete=models.CASCADE)
|
||||||
(2, 'Semestriel'),
|
|
||||||
(3, 'Trimestriel')
|
|
||||||
]
|
|
||||||
|
|
||||||
classe = models.OneToOneField(Classe, on_delete=models.SET_NULL, null=True, blank=True, related_name='planning')
|
|
||||||
emploiDuTemps = JSONField(default=dict)
|
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):
|
def __str__(self):
|
||||||
return f'Planning de {self.classe.nom_ambiance}'
|
return f'Planning de {self.niveau} pour {self.classe.nom_ambiance}'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import Enseignant, Specialite, Classe, Planning
|
from .models import Enseignant, Specialite, Classe, Planning, NIVEAU_CHOICES
|
||||||
from GestionInscriptions.models import FicheInscription
|
from GestionInscriptions.models import FicheInscription
|
||||||
from GestionInscriptions.serializers import EleveSerializer
|
from GestionInscriptions.serializers import EleveSerializer
|
||||||
from GestionLogin.serializers import ProfilSerializer
|
from GestionLogin.serializers import ProfilSerializer
|
||||||
@ -75,52 +75,52 @@ class EnseignantSerializer(serializers.ModelSerializer):
|
|||||||
return local_time.strftime("%d-%m-%Y %H:%M")
|
return local_time.strftime("%d-%m-%Y %H:%M")
|
||||||
|
|
||||||
class PlanningSerializer(serializers.ModelSerializer):
|
class PlanningSerializer(serializers.ModelSerializer):
|
||||||
emploiDuTemps = serializers.SerializerMethodField()
|
# emploiDuTemps = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Planning
|
model = Planning
|
||||||
fields = ['id', 'emploiDuTemps', 'type', 'plageHoraire', 'joursOuverture']
|
fields = ['id', 'niveau', 'emploiDuTemps']
|
||||||
|
|
||||||
def get_emploiDuTemps(self, obj):
|
# def get_emploiDuTemps(self, obj):
|
||||||
emploi_du_temps = obj.emploiDuTemps
|
# emploi_du_temps = obj.emploiDuTemps
|
||||||
if obj.classe:
|
# if obj.classe:
|
||||||
enseignants = obj.classe.enseignants.all() # Récupérer tous les enseignants associés à la 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
|
# # Dictionnaire pour accéder rapidement aux spécialités des enseignants
|
||||||
specialite_enseignants = {}
|
# specialite_enseignants = {}
|
||||||
for enseignant in enseignants:
|
# for enseignant in enseignants:
|
||||||
for specialite in enseignant.specialites.all():
|
# for specialite in enseignant.specialites.all():
|
||||||
if specialite.nom not in specialite_enseignants:
|
# if specialite.nom not in specialite_enseignants:
|
||||||
specialite_enseignants[specialite.nom] = []
|
# specialite_enseignants[specialite.nom] = []
|
||||||
specialite_enseignants[specialite.nom].append(f"{enseignant.prenom} {enseignant.nom}")
|
# specialite_enseignants[specialite.nom].append(f"{enseignant.prenom} {enseignant.nom}")
|
||||||
|
|
||||||
if obj.type == 1: # Planning annuel
|
# if obj.classe.type == 1: # Planning annuel
|
||||||
for day, events in emploi_du_temps.items():
|
# for day, events in emploi_du_temps.items():
|
||||||
for event in events:
|
# for event in events:
|
||||||
# Ajouter les enseignants associés à la spécialité
|
# # Ajouter les enseignants associés à la spécialité
|
||||||
event['teachers'] = specialite_enseignants.get(event['matiere'], [])
|
# event['teachers'] = specialite_enseignants.get(event['matiere'], [])
|
||||||
# Ajouter la couleur de la spécialité
|
# # Ajouter la couleur de la spécialité
|
||||||
event['color'] = next(
|
# event['color'] = next(
|
||||||
(specialite.codeCouleur for enseignant in enseignants for specialite in enseignant.specialites.all() if specialite.nom == event['matiere']),
|
# (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
|
# "#FFFFFF" # Couleur par défaut si non trouvée
|
||||||
)
|
# )
|
||||||
|
|
||||||
elif obj.type in [2, 3]: # Planning semestriel ou trimestriel
|
# elif obj.classe.type in [2, 3]: # Planning semestriel ou trimestriel
|
||||||
for period_key, period_value in emploi_du_temps.items():
|
# for period_key, period_value in emploi_du_temps.items():
|
||||||
for day, events in period_value.items():
|
# for day, events in period_value.items():
|
||||||
if day in ['DateDebut', 'DateFin']:
|
# if day in ['DateDebut', 'DateFin']:
|
||||||
continue # Ignorer les clés DateDebut et DateFin
|
# continue # Ignorer les clés DateDebut et DateFin
|
||||||
for event in events:
|
# for event in events:
|
||||||
print(f'event : {event}')
|
# print(f'event : {event}')
|
||||||
# Ajouter les enseignants associés à la spécialité
|
# # Ajouter les enseignants associés à la spécialité
|
||||||
event['teachers'] = specialite_enseignants.get(event['matiere'], [])
|
# event['teachers'] = specialite_enseignants.get(event['matiere'], [])
|
||||||
# Ajouter la couleur de la spécialité
|
# # Ajouter la couleur de la spécialité
|
||||||
event['color'] = next(
|
# event['color'] = next(
|
||||||
(specialite.codeCouleur for enseignant in enseignants for specialite in enseignant.specialites.all() if specialite.nom == event['matiere']),
|
# (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
|
# "#FFFFFF" # Couleur par défaut si non trouvée
|
||||||
)
|
# )
|
||||||
|
|
||||||
return emploi_du_temps
|
# return emploi_du_temps
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
internal_value = super().to_internal_value(data)
|
internal_value = super().to_internal_value(data)
|
||||||
@ -132,20 +132,23 @@ class ClasseSerializer(serializers.ModelSerializer):
|
|||||||
enseignants = EnseignantSerializer(many=True, read_only=True)
|
enseignants = EnseignantSerializer(many=True, read_only=True)
|
||||||
enseignants_ids = serializers.PrimaryKeyRelatedField(queryset=Enseignant.objects.all(), many=True, source='enseignants')
|
enseignants_ids = serializers.PrimaryKeyRelatedField(queryset=Enseignant.objects.all(), many=True, source='enseignants')
|
||||||
eleves = serializers.SerializerMethodField()
|
eleves = serializers.SerializerMethodField()
|
||||||
planning = PlanningSerializer()
|
niveaux = serializers.ListField(child=serializers.ChoiceField(choices=NIVEAU_CHOICES))
|
||||||
|
plannings_read = serializers.SerializerMethodField()
|
||||||
|
plannings = PlanningSerializer(many=True, write_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Classe
|
model = Classe
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement',
|
'id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement',
|
||||||
'enseignants', 'enseignants_ids', 'annee_scolaire', 'dateCreation',
|
'enseignants', 'enseignants_ids', 'annee_scolaire', 'dateCreation',
|
||||||
'dateCreation_formattee', 'eleves', 'planning', 'niveaux'
|
'dateCreation_formattee', 'eleves', 'niveaux', 'type', 'plage_horaire',
|
||||||
|
'jours_ouverture', 'plannings', 'plannings_read'
|
||||||
]
|
]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
planning_data = validated_data.pop('planning', None)
|
|
||||||
enseignants_data = validated_data.pop('enseignants', [])
|
enseignants_data = validated_data.pop('enseignants', [])
|
||||||
niveaux_data = validated_data.pop('niveaux', [])
|
niveaux_data = validated_data.pop('niveaux', [])
|
||||||
|
plannings_data = validated_data.pop('plannings', [])
|
||||||
|
|
||||||
classe = Classe.objects.create(
|
classe = Classe.objects.create(
|
||||||
nom_ambiance=validated_data.get('nom_ambiance', ''),
|
nom_ambiance=validated_data.get('nom_ambiance', ''),
|
||||||
@ -153,26 +156,27 @@ class ClasseSerializer(serializers.ModelSerializer):
|
|||||||
nombre_eleves=validated_data.get('nombre_eleves', 0),
|
nombre_eleves=validated_data.get('nombre_eleves', 0),
|
||||||
langue_enseignement=validated_data.get('langue_enseignement', ''),
|
langue_enseignement=validated_data.get('langue_enseignement', ''),
|
||||||
annee_scolaire=validated_data.get('annee_scolaire', ''),
|
annee_scolaire=validated_data.get('annee_scolaire', ''),
|
||||||
niveaux=niveaux_data
|
niveaux=niveaux_data,
|
||||||
|
type=validated_data.get('type', 1), # Ajouté ici
|
||||||
|
plage_horaire=validated_data.get('plage_horaire', ['08:30', '17:30']), # Ajouté ici
|
||||||
|
jours_ouverture=validated_data.get('jours_ouverture', [1, 2, 4, 5]) # Ajouté ici
|
||||||
)
|
)
|
||||||
|
|
||||||
classe.enseignants.set(enseignants_data)
|
classe.enseignants.set(enseignants_data)
|
||||||
|
|
||||||
if planning_data and not hasattr(classe, 'planning'):
|
for planning_data in plannings_data:
|
||||||
Planning.objects.create(
|
Planning.objects.create(
|
||||||
classe=classe,
|
classe=classe,
|
||||||
emploiDuTemps=planning_data.get('emploiDuTemps', {}),
|
niveau=planning_data['niveau'],
|
||||||
type=planning_data.get('type', 1),
|
emploiDuTemps=planning_data.get('emploiDuTemps', {})
|
||||||
plageHoraire=planning_data.get('plageHoraire', []),
|
|
||||||
joursOuverture=planning_data.get('joursOuverture', [])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return classe
|
return classe
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
planning_data = validated_data.pop('planning', None)
|
|
||||||
enseignants_data = validated_data.pop('enseignants', [])
|
enseignants_data = validated_data.pop('enseignants', [])
|
||||||
niveaux_data = validated_data.pop('niveaux', [])
|
niveaux_data = validated_data.pop('niveaux', [])
|
||||||
|
plannings_data = validated_data.pop('plannings', [])
|
||||||
|
|
||||||
instance.nom_ambiance = validated_data.get('nom_ambiance', instance.nom_ambiance)
|
instance.nom_ambiance = validated_data.get('nom_ambiance', instance.nom_ambiance)
|
||||||
instance.tranche_age = validated_data.get('tranche_age', instance.tranche_age)
|
instance.tranche_age = validated_data.get('tranche_age', instance.tranche_age)
|
||||||
@ -180,23 +184,34 @@ class ClasseSerializer(serializers.ModelSerializer):
|
|||||||
instance.langue_enseignement = validated_data.get('langue_enseignement', instance.langue_enseignement)
|
instance.langue_enseignement = validated_data.get('langue_enseignement', instance.langue_enseignement)
|
||||||
instance.annee_scolaire = validated_data.get('annee_scolaire', instance.annee_scolaire)
|
instance.annee_scolaire = validated_data.get('annee_scolaire', instance.annee_scolaire)
|
||||||
instance.niveaux = niveaux_data
|
instance.niveaux = niveaux_data
|
||||||
|
instance.type = validated_data.get('type', instance.type) # Ajouté ici
|
||||||
|
instance.plage_horaire = validated_data.get('plage_horaire', instance.plage_horaire) # Ajouté ici
|
||||||
|
instance.jours_ouverture = validated_data.get('jours_ouverture', instance.jours_ouverture) # Ajouté ici
|
||||||
|
|
||||||
instance.save()
|
instance.save()
|
||||||
instance.enseignants.set(enseignants_data)
|
instance.enseignants.set(enseignants_data)
|
||||||
|
|
||||||
if planning_data:
|
existing_plannings = {planning.niveau: planning for planning in instance.plannings.all()}
|
||||||
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)
|
|
||||||
|
|
||||||
|
for planning_data in plannings_data:
|
||||||
|
niveau = planning_data['niveau']
|
||||||
|
if niveau in existing_plannings:
|
||||||
|
# Mettre à jour le planning existant
|
||||||
|
planning = existing_plannings[niveau]
|
||||||
|
planning.emploiDuTemps = planning_data.get('emploiDuTemps', planning.emploiDuTemps)
|
||||||
planning.save()
|
planning.save()
|
||||||
|
else:
|
||||||
|
# Créer un nouveau planning si niveau non existant
|
||||||
|
Planning.objects.create(
|
||||||
|
classe=instance,
|
||||||
|
niveau=niveau,
|
||||||
|
emploiDuTemps=planning_data.get('emploiDuTemps', {})
|
||||||
|
)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def get_dateCreation_formattee(self, obj):
|
def get_dateCreation_formattee(self, obj):
|
||||||
utc_time = timezone.localtime(obj.dateCreation) # Convertir en heure locale
|
utc_time = timezone.localtime(obj.dateCreation)
|
||||||
local_tz = pytz.timezone(settings.TZ_APPLI)
|
local_tz = pytz.timezone(settings.TZ_APPLI)
|
||||||
local_time = utc_time.astimezone(local_tz)
|
local_time = utc_time.astimezone(local_tz)
|
||||||
return local_time.strftime("%d-%m-%Y %H:%M")
|
return local_time.strftime("%d-%m-%Y %H:%M")
|
||||||
@ -210,8 +225,12 @@ class ClasseSerializer(serializers.ModelSerializer):
|
|||||||
filtered_eleves.append(eleve)
|
filtered_eleves.append(eleve)
|
||||||
return EleveSerializer(filtered_eleves, many=True, read_only=True).data
|
return EleveSerializer(filtered_eleves, many=True, read_only=True).data
|
||||||
|
|
||||||
def get_planning(self, obj):
|
def get_plannings_read(self, obj):
|
||||||
from .serializers import PlanningSerializer
|
plannings = obj.plannings.all()
|
||||||
if obj.planning:
|
niveaux_dict = {niveau: {'niveau': niveau, 'planning': None} for niveau in obj.niveaux}
|
||||||
return PlanningSerializer(obj.planning).data
|
|
||||||
return None
|
for planning in plannings:
|
||||||
|
if planning.niveau in niveaux_dict:
|
||||||
|
niveaux_dict[planning.niveau]['planning'] = PlanningSerializer(planning).data
|
||||||
|
|
||||||
|
return list(niveaux_dict.values())
|
||||||
|
|||||||
@ -181,15 +181,24 @@ class ClasseView(APIView):
|
|||||||
|
|
||||||
def delete(self, request, _id):
|
def delete(self, request, _id):
|
||||||
classe = bdd.getObject(_objectName=Classe, _columnName='id', _value=_id)
|
classe = bdd.getObject(_objectName=Classe, _columnName='id', _value=_id)
|
||||||
if classe != None:
|
if classe is not None:
|
||||||
|
# Supprimer les plannings associés à la classe
|
||||||
|
for planning in classe.plannings.all():
|
||||||
|
print(f'Planning à supprimer : {planning}')
|
||||||
|
planning.delete()
|
||||||
|
|
||||||
|
# Retirer la classe des élèves associés
|
||||||
for eleve in classe.eleves.all():
|
for eleve in classe.eleves.all():
|
||||||
print(f'eleve a retirer la classe : {eleve}')
|
print(f'Eleve à retirer de la classe : {eleve}')
|
||||||
eleve.classeAssociee = None
|
eleve.classeAssociee = None
|
||||||
eleve.save()
|
eleve.save()
|
||||||
|
|
||||||
|
# Supprimer la classe
|
||||||
classe.delete()
|
classe.delete()
|
||||||
|
|
||||||
return JsonResponse("La suppression de la classe a été effectuée avec succès", safe=False)
|
return JsonResponse("La suppression de la classe a été effectuée avec succès", safe=False)
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
class PlanningsView(APIView):
|
class PlanningsView(APIView):
|
||||||
@ -219,7 +228,14 @@ class PlanningView(APIView):
|
|||||||
|
|
||||||
def put(self, request, _id):
|
def put(self, request, _id):
|
||||||
planning_data = JSONParser().parse(request)
|
planning_data = JSONParser().parse(request)
|
||||||
planning = bdd.getObject(_objectName=Planning, _columnName='classe__id', _value=_id)
|
|
||||||
|
try:
|
||||||
|
planning = Planning.objects.get(id=_id)
|
||||||
|
except Planning.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=404)
|
||||||
|
except Planning.MultipleObjectsReturned:
|
||||||
|
return JsonResponse({'error': 'Multiple objects found'}, status=400)
|
||||||
|
|
||||||
planning_serializer = PlanningSerializer(planning, data=planning_data)
|
planning_serializer = PlanningSerializer(planning, data=planning_data)
|
||||||
|
|
||||||
if planning_serializer.is_valid():
|
if planning_serializer.is_valid():
|
||||||
|
|||||||
7
Front-End/package-lock.json
generated
7
Front-End/package-lock.json
generated
@ -13,6 +13,7 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"framer-motion": "^11.11.11",
|
"framer-motion": "^11.11.11",
|
||||||
"ics": "^3.8.1",
|
"ics": "^3.8.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.453.0",
|
"lucide-react": "^0.453.0",
|
||||||
"next": "14.2.11",
|
"next": "14.2.11",
|
||||||
"next-intl": "^3.24.0",
|
"next-intl": "^3.24.0",
|
||||||
@ -3704,6 +3705,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"framer-motion": "^11.11.11",
|
"framer-motion": "^11.11.11",
|
||||||
"ics": "^3.8.1",
|
"ics": "^3.8.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.453.0",
|
"lucide-react": "^0.453.0",
|
||||||
"next": "14.2.11",
|
"next": "14.2.11",
|
||||||
"next-intl": "^3.24.0",
|
"next-intl": "^3.24.0",
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { BK_GESTIONENSEIGNANTS_SPECIALITES_URL,
|
|||||||
BK_GESTIONENSEIGNANTS_PLANNINGS_URL } from '@/utils/Url';
|
BK_GESTIONENSEIGNANTS_PLANNINGS_URL } from '@/utils/Url';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
||||||
import useCsrfToken from '@/hooks/useCsrfToken';
|
import useCsrfToken from '@/hooks/useCsrfToken';
|
||||||
|
import { ClassesProvider } from '@/context/ClassesContext';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [specialities, setSpecialities] = useState([]);
|
const [specialities, setSpecialities] = useState([]);
|
||||||
@ -121,8 +122,8 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdatePlanning = (url, id, updatedData) => {
|
const handleUpdatePlanning = (url, planningId, updatedData) => {
|
||||||
fetch(`${url}/${id}`, {
|
fetch(`${url}/${planningId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -141,6 +142,7 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleDelete = (url, id, setDatas) => {
|
const handleDelete = (url, id, setDatas) => {
|
||||||
fetch(`${url}/${id}`, {
|
fetch(`${url}/${id}`, {
|
||||||
method:'DELETE',
|
method:'DELETE',
|
||||||
@ -184,14 +186,12 @@ export default function Page() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'Schedule' && (
|
{activeTab === 'Schedule' && (
|
||||||
|
<ClassesProvider>
|
||||||
<ScheduleManagement
|
<ScheduleManagement
|
||||||
schedules={schedules}
|
|
||||||
setSchedules={setSchedules}
|
|
||||||
handleUpdatePlanning={handleUpdatePlanning}
|
handleUpdatePlanning={handleUpdatePlanning}
|
||||||
specialities={specialities}
|
|
||||||
teachers={teachers}
|
|
||||||
classes={classes}
|
classes={classes}
|
||||||
/>
|
/>
|
||||||
|
</ClassesProvider>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -32,7 +32,7 @@ const CheckBoxList = ({
|
|||||||
<label
|
<label
|
||||||
htmlFor={`${fieldName}-${item.id}`}
|
htmlFor={`${fieldName}-${item.id}`}
|
||||||
className={`block text-sm text-center mb-1 ${
|
className={`block text-sm text-center mb-1 ${
|
||||||
isAttenuated ? 'text-gray-200' : 'font-bold text-emerald-600'
|
isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{itemLabelFunc(item)}
|
{itemLabelFunc(item)}
|
||||||
@ -53,7 +53,7 @@ const CheckBoxList = ({
|
|||||||
<label
|
<label
|
||||||
htmlFor={`${fieldName}-${item.id}`}
|
htmlFor={`${fieldName}-${item.id}`}
|
||||||
className={`block text-sm ${
|
className={`block text-sm ${
|
||||||
isAttenuated ? 'text-gray-200' : 'font-bold text-emerald-600'
|
isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{itemLabelFunc(item)}
|
{itemLabelFunc(item)}
|
||||||
|
|||||||
16
Front-End/src/components/CustomLabels/LevelLabel.js
Normal file
16
Front-End/src/components/CustomLabels/LevelLabel.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const LevelLabel = ({ label, index }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`ml-2 px-3 py-1 rounded-md shadow-sm ${
|
||||||
|
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
||||||
|
} border border-gray-200 text-gray-700`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LevelLabel;
|
||||||
16
Front-End/src/components/CustomLabels/TeacherLabel.js
Normal file
16
Front-End/src/components/CustomLabels/TeacherLabel.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const TeacherLabel = ({ nom, prenom, index }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`ml-2 px-3 py-1 rounded-md shadow-sm ${
|
||||||
|
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
||||||
|
} border border-gray-200 text-gray-700`}
|
||||||
|
>
|
||||||
|
<span className="font-bold">{nom} {prenom}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeacherLabel;
|
||||||
@ -1,25 +1,34 @@
|
|||||||
export default function SelectChoice({type, name, label, choices, callback, selected, error, IconItem }) {
|
export default function SelectChoice({ type, name, label, choices, callback, selected, error, IconItem, disabled = false }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">{label}</label>
|
<label htmlFor={name} className="block text-sm font-medium text-gray-700">{label}</label>
|
||||||
<div className={`flex items-center border-2 border-gray-200 rounded-md hover:border-gray-400 focus-within:border-gray-500 h-8 mt-2`}>
|
<div
|
||||||
|
className={`flex items-center border-2 rounded-md ${disabled ? 'border-gray-200' : 'border-gray-200 hover:border-gray-400 focus-within:border-gray-500'} h-8 mt-2`}
|
||||||
|
>
|
||||||
<span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm h-full">
|
<span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm h-full">
|
||||||
{IconItem && <IconItem />}
|
{IconItem && <IconItem />}
|
||||||
</span>
|
</span>
|
||||||
<select
|
<select
|
||||||
className="mt-1 block w-full px-2 py-0 text-base rounded-r-md sm:text-sm border-none focus:ring-0 outline-none cursor-pointer"
|
className={`mt-1 block w-full px-2 py-0 text-base rounded-r-md sm:text-sm border-none focus:ring-0 outline-none cursor-pointer ${disabled ? 'bg-gray-100' : ''}`}
|
||||||
type={type}
|
type={type}
|
||||||
id={name}
|
id={name}
|
||||||
name={name}
|
name={name}
|
||||||
value={selected}
|
value={selected}
|
||||||
onChange={callback}
|
onChange={callback}
|
||||||
|
disabled={disabled} // Ajout de l'attribut disabled avec une valeur par défaut de false
|
||||||
>
|
>
|
||||||
{choices.map(({ value, label }, index) => <option key={value} value={value}>{label}</option>)}
|
{choices.map(({ value, label }, index) => (
|
||||||
|
<option key={value} value={value} className={value === '' ? 'italic' : ''}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{error && <p className="mt-2 text-sm text-red-600">{error}</p>}
|
{error && <p className="mt-2 text-sm text-red-600">{error}</p>}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ import { useClasses } from '@/context/ClassesContext';
|
|||||||
const ClassForm = ({ onSubmit, isNew, teachers }) => {
|
const ClassForm = ({ onSubmit, isNew, teachers }) => {
|
||||||
|
|
||||||
const { formData, setFormData } = useClasseForm();
|
const { formData, setFormData } = useClasseForm();
|
||||||
const { schoolYears, getNiveauxLabels, generateAgeToNiveaux, niveauxPremierCycle, niveauxSecondCycle, niveauxTroisiemeCycle, typeEmploiDuTemps, updatePlanning } = useClasses();
|
const { getNiveauNameById, schoolYears, getNiveauxLabels, getNiveauxTabs, generateAgeToNiveaux, niveauxPremierCycle, niveauxSecondCycle, niveauxTroisiemeCycle, typeEmploiDuTemps, updatePlannings } = useClasses();
|
||||||
const [selectedTeachers, setSelectedTeachers] = useState(formData.enseignants_ids);
|
const [selectedTeachers, setSelectedTeachers] = useState(formData.enseignants_ids);
|
||||||
|
|
||||||
const handleTeacherSelection = (teacher) => {
|
const handleTeacherSelection = (teacher) => {
|
||||||
@ -40,7 +40,8 @@ const ClassForm = ({ onSubmit, isNew, teachers }) => {
|
|||||||
plage_horaire: updatedTimes,
|
plage_horaire: updatedTimes,
|
||||||
};
|
};
|
||||||
|
|
||||||
updatedFormData.planning = updatePlanning(updatedFormData);
|
const existingPlannings = prevState.plannings || [];
|
||||||
|
updatedFormData.plannings = updatePlannings(updatedFormData, existingPlannings);
|
||||||
|
|
||||||
return updatedFormData;
|
return updatedFormData;
|
||||||
});
|
});
|
||||||
@ -60,7 +61,8 @@ const ClassForm = ({ onSubmit, isNew, teachers }) => {
|
|||||||
jours_ouverture: updatedJoursOuverture,
|
jours_ouverture: updatedJoursOuverture,
|
||||||
};
|
};
|
||||||
|
|
||||||
updatedFormData.planning = updatePlanning(updatedFormData);
|
const existingPlannings = prevState.plannings || [];
|
||||||
|
updatedFormData.plannings = updatePlannings(updatedFormData, existingPlannings);
|
||||||
|
|
||||||
return updatedFormData;
|
return updatedFormData;
|
||||||
});
|
});
|
||||||
@ -71,13 +73,15 @@ const ClassForm = ({ onSubmit, isNew, teachers }) => {
|
|||||||
const { name, value, type, checked } = e.target;
|
const { name, value, type, checked } = e.target;
|
||||||
|
|
||||||
setFormData(prevState => {
|
setFormData(prevState => {
|
||||||
let updatedFormData = { ...prevState };
|
|
||||||
|
// Copier l'état précédent
|
||||||
|
let newState = { ...prevState };
|
||||||
|
|
||||||
if (type === 'checkbox') {
|
if (type === 'checkbox') {
|
||||||
const newValues = checked
|
const newValues = checked
|
||||||
? [...(prevState[name] || []), parseInt(value)]
|
? [...(prevState[name] || []), parseInt(value)]
|
||||||
: (prevState[name] || []).filter(v => v !== parseInt(value));
|
: (prevState[name] || []).filter(v => v !== parseInt(value));
|
||||||
updatedFormData[name] = newValues;
|
newState[name] = newValues;
|
||||||
} else if (name === 'tranche_age') {
|
} else if (name === 'tranche_age') {
|
||||||
const [minAgeStr, maxAgeStr] = value.split('-');
|
const [minAgeStr, maxAgeStr] = value.split('-');
|
||||||
const minAge = minAgeStr ? parseInt(minAgeStr) : null;
|
const minAge = minAgeStr ? parseInt(minAgeStr) : null;
|
||||||
@ -85,24 +89,21 @@ const ClassForm = ({ onSubmit, isNew, teachers }) => {
|
|||||||
const selectedNiveaux = generateAgeToNiveaux(minAge, maxAge);
|
const selectedNiveaux = generateAgeToNiveaux(minAge, maxAge);
|
||||||
const niveauxLabels = getNiveauxLabels(selectedNiveaux);
|
const niveauxLabels = getNiveauxLabels(selectedNiveaux);
|
||||||
|
|
||||||
updatedFormData = {
|
newState = {
|
||||||
...prevState,
|
...prevState,
|
||||||
[name]: value,
|
[name]: value,
|
||||||
niveaux: selectedNiveaux.length > 0 ? selectedNiveaux : [],
|
niveaux: selectedNiveaux.length > 0 ? selectedNiveaux : [],
|
||||||
niveaux_label: niveauxLabels.length > 0 ? niveauxLabels : []
|
|
||||||
};
|
};
|
||||||
} else if (type === 'radio') {
|
} else if (type === 'radio') {
|
||||||
updatedFormData[name] = parseInt(value, 10);
|
newState[name] = parseInt(value, 10);
|
||||||
} else {
|
} else {
|
||||||
updatedFormData[name] = value;
|
newState[name] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Updated formData:', updatedFormData);
|
const existingPlannings = prevState.plannings || [];
|
||||||
|
newState.plannings = updatePlannings(newState, existingPlannings);
|
||||||
|
|
||||||
updatedFormData.planning = updatePlanning(updatedFormData);
|
return newState;
|
||||||
|
|
||||||
console.log('Final formData:', updatedFormData);
|
|
||||||
return updatedFormData;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import DropdownMenu from '@/components/DropdownMenu';
|
|||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import ClassForm from '@/components/Structure/Configuration/ClassForm';
|
import ClassForm from '@/components/Structure/Configuration/ClassForm';
|
||||||
import ClasseDetails from '@/components/ClasseDetails';
|
import ClasseDetails from '@/components/ClasseDetails';
|
||||||
|
import LevelLabel from '@/components/CustomLabels/LevelLabel';
|
||||||
|
import TeacherLabel from '@/components/CustomLabels/TeacherLabel';
|
||||||
import { ClasseFormProvider } from '@/context/ClasseFormContext';
|
import { ClasseFormProvider } from '@/context/ClasseFormContext';
|
||||||
import { useClasses } from '@/context/ClassesContext';
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
|
|
||||||
@ -87,13 +89,7 @@ const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleE
|
|||||||
<div className="flex flex-wrap justify-center items-center space-x-2">
|
<div className="flex flex-wrap justify-center items-center space-x-2">
|
||||||
{niveauxLabels.length > 0
|
{niveauxLabels.length > 0
|
||||||
? niveauxLabels.map((label, index) => (
|
? niveauxLabels.map((label, index) => (
|
||||||
<div
|
<LevelLabel key={index} label={label} index={index} />
|
||||||
key={index}
|
|
||||||
className={`px-3 py-1 rounded-md shadow-sm ${
|
|
||||||
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
|
||||||
} border border-gray-200 text-gray-700`}>
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
))
|
))
|
||||||
: 'Aucun niveau'}
|
: 'Aucun niveau'}
|
||||||
</div>
|
</div>
|
||||||
@ -107,14 +103,7 @@ const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleE
|
|||||||
transform: (row) => (
|
transform: (row) => (
|
||||||
<div key={row.id} className="flex flex-wrap justify-center items-center space-x-2">
|
<div key={row.id} className="flex flex-wrap justify-center items-center space-x-2">
|
||||||
{row.enseignants.map((teacher, index) => (
|
{row.enseignants.map((teacher, index) => (
|
||||||
<div
|
<TeacherLabel key={teacher.id} nom={teacher.nom} prenom={teacher.prenom} index={index} />
|
||||||
key={teacher.id}
|
|
||||||
className={`px-3 py-1 rounded-md shadow-sm ${
|
|
||||||
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
|
||||||
} border border-gray-200 text-gray-700`}
|
|
||||||
>
|
|
||||||
<span className="font-bold">{teacher.nom} {teacher.prenom}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -28,7 +28,7 @@ const PlanningConfiguration = ({ formData, handleChange, handleTimeChange, handl
|
|||||||
items={typeEmploiDuTemps}
|
items={typeEmploiDuTemps}
|
||||||
formData={formData}
|
formData={formData}
|
||||||
handleChange={handleChange}
|
handleChange={handleChange}
|
||||||
fieldName="planning_type"
|
fieldName="type"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -56,7 +56,7 @@ const PlanningConfiguration = ({ formData, handleChange, handleTimeChange, handl
|
|||||||
|
|
||||||
{/* DateRange */}
|
{/* DateRange */}
|
||||||
<div className="space-y-4 w-full">
|
<div className="space-y-4 w-full">
|
||||||
{formData.planning_type === 2 && (
|
{formData.type === 2 && (
|
||||||
<>
|
<>
|
||||||
<DateRange
|
<DateRange
|
||||||
nameStart="date_debut_semestre_1"
|
nameStart="date_debut_semestre_1"
|
||||||
@ -77,7 +77,7 @@ const PlanningConfiguration = ({ formData, handleChange, handleTimeChange, handl
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{formData.planning_type === 3 && (
|
{formData.type === 3 && (
|
||||||
<>
|
<>
|
||||||
<DateRange
|
<DateRange
|
||||||
nameStart="date_debut_trimestre_1"
|
nameStart="date_debut_trimestre_1"
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { School, Calendar } from 'lucide-react';
|
|
||||||
|
|
||||||
const TabsStructure = ({ activeTab, setActiveTab, tabs }) => {
|
const TabsStructure = ({ activeTab, setActiveTab, tabs }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center mb-8">
|
<div className="flex justify-center items-center w-full">
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
className={`tab px-4 py-2 mx-2 flex items-center space-x-2 ${activeTab === tab.id ? 'bg-emerald-600 text-white shadow-lg' : 'bg-emerald-200 text-emerald-600'} rounded-full`}
|
className={`tab px-4 py-2 mx-2 flex items-center justify-center space-x-2 ${activeTab === tab.id ? 'bg-emerald-600 text-white shadow-lg' : 'bg-emerald-200 text-emerald-600'} rounded-full`}
|
||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => setActiveTab(tab.id)}
|
||||||
>
|
>
|
||||||
<tab.icon className="w-5 h-5" />
|
<tab.icon className="w-5 h-5" />
|
||||||
@ -19,3 +18,6 @@ const TabsStructure = ({ activeTab, setActiveTab, tabs }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default TabsStructure;
|
export default TabsStructure;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,81 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
|
||||||
import { Users } from 'lucide-react';
|
|
||||||
import DraggableSpeciality from '@/components/Structure/Planning/DraggableSpeciality';
|
|
||||||
|
|
||||||
const groupSpecialitiesBySubject = (enseignants) => {
|
|
||||||
const groupedSpecialities = {};
|
|
||||||
|
|
||||||
enseignants.forEach(teacher => {
|
|
||||||
teacher.specialites.forEach(specialite => {
|
|
||||||
if (!groupedSpecialities[specialite.id]) {
|
|
||||||
groupedSpecialities[specialite.id] = {
|
|
||||||
...specialite,
|
|
||||||
teachers: [`${teacher.nom} ${teacher.prenom}`],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
groupedSpecialities[specialite.id].teachers.push(`${teacher.nom} ${teacher.prenom}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return Object.values(groupedSpecialities);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ClassesInfo = ({ classes, onClassSelect }) => {
|
|
||||||
const [selectedClass, setSelectedClass] = useState(null); // Nouvelle variable d'état pour la classe sélectionnée
|
|
||||||
|
|
||||||
const handleClassChange = (event) => {
|
|
||||||
const classId = event.target.value;
|
|
||||||
const selectedClass = classes.find(classe => classe.id === parseInt(classId));
|
|
||||||
setSelectedClass(selectedClass);
|
|
||||||
onClassSelect(selectedClass);
|
|
||||||
};
|
|
||||||
|
|
||||||
const classChoices = [
|
|
||||||
{ value: '', label: 'Sélectionner...' },
|
|
||||||
...classes.map(classe => ({
|
|
||||||
value: classe.id,
|
|
||||||
label: classe.nom_ambiance,
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
|
|
||||||
// S'assurer que `selectedClass` n'est pas null avant d'appeler `groupSpecialitiesBySubject`
|
|
||||||
const groupedSpecialities = selectedClass ? groupSpecialitiesBySubject(selectedClass.enseignants) : [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-4 bg-white shadow rounded mb-4">
|
|
||||||
{classes.length > 0 ? (
|
|
||||||
<SelectChoice
|
|
||||||
name="classes"
|
|
||||||
label="Classes"
|
|
||||||
IconItem={Users}
|
|
||||||
selected={selectedClass ? selectedClass.id : ''}
|
|
||||||
choices={classChoices}
|
|
||||||
callback={handleClassChange}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<p>Aucune classe disponible.</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedClass && (
|
|
||||||
<div className="specialities mt-4">
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-4">Spécialités</label>
|
|
||||||
{groupedSpecialities.map((specialite, index) => {
|
|
||||||
// Combiner l'ID de la spécialité avec les IDs des enseignants pour créer une clé unique
|
|
||||||
const uniqueId = `${specialite.id}-${specialite.teachers.map(teacher => teacher.id).join('-')}-${index}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DraggableSpeciality
|
|
||||||
key={uniqueId} // Utilisation de l'ID unique généré
|
|
||||||
specialite={specialite}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ClassesInfo;
|
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import TeacherLabel from '@/components/CustomLabels/TeacherLabel';
|
||||||
|
|
||||||
|
const ClassesInformation = ({ selectedClass, isPastYear }) => {
|
||||||
|
if (!selectedClass) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`w-full p-6 shadow-lg rounded-full border relative ${isPastYear ? 'bg-gray-200 border-gray-600' : 'bg-emerald-200 border-emerald-500'}`}>
|
||||||
|
<div className={`border-b pb-4 ${isPastYear ? 'border-gray-600' : 'border-emerald-500'}`}>
|
||||||
|
<p className="text-gray-700 text-center"><strong>{selectedClass.tranche_age} ans</strong></p>
|
||||||
|
</div>
|
||||||
|
<div className={`border-b pb-4 ${isPastYear ? 'border-gray-600' : 'border-emerald-500'}`}>
|
||||||
|
<div className="flex flex-wrap justify-center space-x-4">
|
||||||
|
{selectedClass.enseignants.map((teacher) => (
|
||||||
|
<div key={teacher.id} className="relative group mt-4">
|
||||||
|
<TeacherLabel nom={teacher.nom} prenom={teacher.prenom} />
|
||||||
|
<div className="absolute left-1/2 transform -translate-x-1/2 bottom-full mb-2 w-max px-4 py-2 text-white bg-gray-800 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||||
|
<p className="text-sm">{teacher.nom} {teacher.prenom}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClassesInformation;
|
||||||
76
Front-End/src/components/Structure/Planning/ClassesList.js
Normal file
76
Front-End/src/components/Structure/Planning/ClassesList.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { History, Clock, Users } from 'lucide-react';
|
||||||
|
|
||||||
|
const ClassesList = ({ classes, onClassSelect, selectedClassId }) => {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const currentMonth = new Date().getMonth();
|
||||||
|
const currentSchoolYearStart = currentMonth >= 8 ? currentYear : currentYear - 1;
|
||||||
|
|
||||||
|
const handleClassClick = (classe) => {
|
||||||
|
console.log(`Classe sélectionnée: ${classe.nom_ambiance}, Année scolaire: ${classe.annee_scolaire}`);
|
||||||
|
onClassSelect(classe);
|
||||||
|
};
|
||||||
|
|
||||||
|
const categorizedClasses = classes.reduce((acc, classe) => {
|
||||||
|
const { annee_scolaire } = classe;
|
||||||
|
const [startYear] = annee_scolaire.split('-').map(Number);
|
||||||
|
const category = startYear >= currentSchoolYearStart ? 'Actives' : 'Anciennes';
|
||||||
|
|
||||||
|
if (!acc[category]) {
|
||||||
|
acc[category] = [];
|
||||||
|
}
|
||||||
|
acc[category].push(classe);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full p-4 bg-gray-50 rounded-lg shadow-inner">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||||
|
<Users className="w-8 h-8 mr-2" />
|
||||||
|
Classes
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold mb-2 text-emerald-600 flex items-center space-x-2">
|
||||||
|
<Clock className="inline-block mr-2 w-5 h-5" /> Actives
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{categorizedClasses['Actives']?.map((classe) => (
|
||||||
|
<div
|
||||||
|
key={classe.id}
|
||||||
|
className={`flex items-center ${selectedClassId === classe.id ? 'bg-emerald-600 text-white' : 'bg-emerald-100 text-emerald-600'} border border-emerald-300 rounded-lg shadow-lg overflow-hidden hover:bg-emerald-300 hover:text-emerald-700 cursor-pointer p-4 mb-4`}
|
||||||
|
onClick={() => handleClassClick(classe)}
|
||||||
|
style={{ maxWidth: '400px' }}
|
||||||
|
>
|
||||||
|
<div className="flex-1 text-sm font-medium">{classe.nom_ambiance}</div>
|
||||||
|
<div className="flex-1 text-sm font-medium">{classe.annee_scolaire}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold mb-2 text-gray-600 flex items-center space-x-2">
|
||||||
|
<History className="inline-block mr-2 w-5 h-5" /> Anciennes
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{categorizedClasses['Anciennes']?.map((classe) => (
|
||||||
|
<div
|
||||||
|
key={classe.id}
|
||||||
|
className={`flex items-center ${selectedClassId === classe.id ? 'bg-gray-400 text-white' : 'bg-gray-100 text-gray-600'} border border-gray-300 rounded-lg shadow-lg overflow-hidden hover:bg-gray-300 hover:text-gray-700 cursor-pointer p-4 mb-4`}
|
||||||
|
onClick={() => handleClassClick(classe)}
|
||||||
|
style={{ maxWidth: '400px' }}
|
||||||
|
>
|
||||||
|
<div className="flex-1 text-sm font-medium">{classe.nom_ambiance}</div>
|
||||||
|
<div className="flex-1 text-sm font-medium">{classe.annee_scolaire}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClassesList;
|
||||||
@ -1,36 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
import { useDrag } from 'react-dnd';
|
import { useDrag } from 'react-dnd';
|
||||||
|
import { UserIcon } from 'lucide-react'; // Assure-toi d'importer l'icône que tu souhaites utiliser
|
||||||
|
|
||||||
const DraggableSpeciality = ({ specialite }) => {
|
const DraggableSpeciality = ({ speciality }) => {
|
||||||
const [{ isDragging }, drag] = useDrag(() => ({
|
const [{ isDragging }, drag] = useDrag(() => ({
|
||||||
type: 'SPECIALITY',
|
type: 'SPECIALITY',
|
||||||
item: { id: specialite.id,
|
item: {
|
||||||
name: specialite.nom,
|
id: speciality.id,
|
||||||
color: specialite.codeCouleur,
|
name: speciality.nom,
|
||||||
teachers: specialite.teachers,
|
color: speciality.codeCouleur,
|
||||||
duree: specialite.duree },
|
teachers: speciality.teachers
|
||||||
|
},
|
||||||
collect: (monitor) => ({
|
collect: (monitor) => ({
|
||||||
isDragging: monitor.isDragging(),
|
isDragging: !!monitor.isDragging(),
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const isColorDark = (color) => {
|
|
||||||
const r = parseInt(color.slice(1, 3), 16);
|
|
||||||
const g = parseInt(color.slice(3, 5), 16);
|
|
||||||
const b = parseInt(color.slice(5, 7), 16);
|
|
||||||
return ((r * 0.299 + g * 0.587 + b * 0.114) < 150);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
ref={drag}
|
ref={drag}
|
||||||
className="speciality-tag p-2 m-1 rounded cursor-pointer"
|
key={speciality.id}
|
||||||
style={{
|
className={`relative flex items-center px-4 py-2 rounded-full font-bold text-white text-center shadow-lg cursor-pointer transition-transform duration-200 ease-in-out transform ${isDragging ? 'opacity-50 scale-95' : 'scale-100 hover:scale-105 hover:shadow-xl'}`}
|
||||||
backgroundColor: specialite.codeCouleur,
|
style={{ backgroundColor: speciality.codeCouleur, minWidth: '200px', maxWidth: '400px' }}
|
||||||
color: isColorDark(specialite.codeCouleur) ? 'white' : 'black',
|
title={speciality.nom}
|
||||||
opacity: isDragging ? 0.5 : 1,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{specialite.nom} ({specialite.teachers.join(', ')})
|
{speciality.nom}
|
||||||
|
<span className="absolute top-0 right-0 mt-1 mr-1 flex items-center justify-center text-xs bg-black bg-opacity-50 rounded-full px-2 py-1">
|
||||||
|
<UserIcon size={16} className="ml-1" />
|
||||||
|
{speciality.teachers.length}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,17 +2,19 @@ import React from 'react';
|
|||||||
import { useDrop } from 'react-dnd';
|
import { useDrop } from 'react-dnd';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const DropTargetCell = ({ day, hour, course, onDrop }) => {
|
// Définition du composant DropTargetCell
|
||||||
const [{ isOver }, drop] = useDrop(() => ({
|
const DropTargetCell = ({ day, hour, courses, onDrop, onClick }) => {
|
||||||
|
const [{ isOver, canDrop }, drop] = useDrop(() => ({
|
||||||
accept: 'SPECIALITY',
|
accept: 'SPECIALITY',
|
||||||
drop: (item) => onDrop(item, hour, day),
|
drop: (item) => onDrop(item, hour, day),
|
||||||
collect: (monitor) => ({
|
collect: (monitor) => ({
|
||||||
isOver: monitor.isOver(),
|
isOver: monitor.isOver(),
|
||||||
|
canDrop: monitor.canDrop(),
|
||||||
}),
|
}),
|
||||||
}));
|
}), [hour, day]);
|
||||||
|
|
||||||
const isColorDark = (color) => {
|
const isColorDark = (color) => {
|
||||||
if (!color) return false; // Vérification si color est défini
|
if (!color) return false;
|
||||||
const r = parseInt(color.slice(1, 3), 16);
|
const r = parseInt(color.slice(1, 3), 16);
|
||||||
const g = parseInt(color.slice(3, 5), 16);
|
const g = parseInt(color.slice(3, 5), 16);
|
||||||
const b = parseInt(color.slice(5, 7), 16);
|
const b = parseInt(color.slice(5, 7), 16);
|
||||||
@ -26,39 +28,42 @@ const DropTargetCell = ({ day, hour, course, onDrop }) => {
|
|||||||
someDate.getFullYear() === today.getFullYear();
|
someDate.getFullYear() === today.getFullYear();
|
||||||
};
|
};
|
||||||
|
|
||||||
const cellBackgroundColor = course ? course.color : 'transparent';
|
// Vérifie si c'est une heure pleine
|
||||||
const cellTextColor = isColorDark(course?.color) ? '#E5E5E5' : '#333333';
|
const isFullHour = parseInt(hour.split(':')[1], 10) === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={drop}
|
ref={drop}
|
||||||
className={`h-20 relative border-b border-gray-100 cursor-pointer ${isToday(new Date(day)) ? 'bg-emerald-100/50 border-x border-emerald-600' : ''} hover:bg-emerald-100`}
|
onClick={() => onClick(hour, day)}
|
||||||
style={{
|
className={`relative cursor-pointer
|
||||||
display: 'flex',
|
${isToday(new Date(day)) ? 'bg-emerald-100/50 border-x border-emerald-600' : ''}
|
||||||
|
hover:bg-emerald-100 h-10 border-b
|
||||||
|
${isFullHour ? 'border-emerald-200' : 'border-gray-300'}
|
||||||
|
${isOver && canDrop ? 'bg-emerald-200' : ''}`} // Ajouté pour indiquer le drop
|
||||||
|
style={{ display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
backgroundColor: cellBackgroundColor,
|
width: '100%' }}
|
||||||
color: cellTextColor
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center gap-1">
|
{courses.map(course => (
|
||||||
{course && (
|
<div key={course.matiere}
|
||||||
<>
|
className="flex flex-row items-center justify-center gap-2"
|
||||||
<div className="text-base font-bold">{course.matiere}</div>
|
style={{ backgroundColor: course.color, color: isColorDark(course.color) ? '#E5E5E5' : '#333333', width: '100%', height: '100%' }}>
|
||||||
<div className="text-sm">{course.teachers.join(", ")}</div>
|
<div style={{ textAlign: 'center', fontWeight: 'bold' }}>{course.matiere}</div>
|
||||||
</>
|
<div style={{ fontStyle: 'italic', textAlign: 'center' }}>{course.teachers.join(', ')}</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
DropTargetCell.propTypes = {
|
DropTargetCell.propTypes = {
|
||||||
day: PropTypes.string.isRequired,
|
day: PropTypes.string.isRequired,
|
||||||
hour: PropTypes.number.isRequired,
|
hour: PropTypes.string.isRequired,
|
||||||
course: PropTypes.object,
|
courses: PropTypes.array.isRequired,
|
||||||
onDrop: PropTypes.func.isRequired
|
onDrop: PropTypes.func.isRequired,
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
formData: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DropTargetCell;
|
export default DropTargetCell;
|
||||||
|
|||||||
@ -1,249 +0,0 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
|
||||||
import { format, startOfWeek, addDays, isSameDay } from 'date-fns';
|
|
||||||
import { fr } from 'date-fns/locale';
|
|
||||||
import { isToday } from 'date-fns';
|
|
||||||
import DropTargetCell from '@/components/Structure/Planning/DropTargetCell';
|
|
||||||
import { BK_GESTIONENSEIGNANTS_PLANNING_URL } from '@/utils/Url';
|
|
||||||
|
|
||||||
const PlanningClassView = ({ schedule }) => {
|
|
||||||
const [currentTime, setCurrentTime] = useState(new Date());
|
|
||||||
const scrollContainerRef = React.useRef(null);
|
|
||||||
const scheduleIdRef = useRef(scheduleId);
|
|
||||||
const eventsRef = useRef(events);
|
|
||||||
|
|
||||||
const [planning, setPlanning] = useState([])
|
|
||||||
|
|
||||||
// Fonction pour récupérer les données du depuis l'API
|
|
||||||
const fetchPlanning = () => {
|
|
||||||
if (schedule) {
|
|
||||||
fetch(`${BK_GESTIONENSEIGNANTS_PLANNING_URL}/${schedule.id}`)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
console.log('DATA : ', data);
|
|
||||||
if (!data || data.emploiDuTemps.length === 0) {
|
|
||||||
setEvents([]);
|
|
||||||
setPlanning([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const planningData = data.emploiDuTemps || {};
|
|
||||||
console.log('succès : ', planningData);
|
|
||||||
|
|
||||||
let events = [];
|
|
||||||
Object.keys(planningData).forEach(day => {
|
|
||||||
if (planningData[day]) {
|
|
||||||
planningData[day].forEach(event => {
|
|
||||||
if (event) {
|
|
||||||
events.push({
|
|
||||||
...event,
|
|
||||||
day: day.toLowerCase(), // Ajouter une clé jour en minuscule pour faciliter le filtrage
|
|
||||||
scheduleId: data.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (JSON.stringify(events) !== JSON.stringify(eventsRef.current)) {
|
|
||||||
setEvents(events);
|
|
||||||
setPlanning(events);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Erreur lors de la récupération du planning :', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setEvents([])
|
|
||||||
setPlanning([]);
|
|
||||||
fetchPlanning();
|
|
||||||
}, [scheduleId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
scheduleIdRef.current = scheduleId;
|
|
||||||
// Mettre à jour la référence chaque fois que scheduleId change
|
|
||||||
}, [scheduleId]);
|
|
||||||
|
|
||||||
/*useEffect(() => {
|
|
||||||
eventsRef.current = events;
|
|
||||||
// Mettre à jour la référence chaque fois que events change
|
|
||||||
}, [events]);*/
|
|
||||||
|
|
||||||
// Déplacer ces déclarations avant leur utilisation
|
|
||||||
const timeSlots = Array.from({ length: 11 }, (_, i) => i + 8);
|
|
||||||
const weekStart = startOfWeek(currentDate, { weekStartsOn: 1 });
|
|
||||||
const weekDays = Array.from({ length: 6 }, (_, i) => addDays(weekStart, i));
|
|
||||||
|
|
||||||
// Maintenant on peut utiliser weekDays
|
|
||||||
const isCurrentWeek = weekDays.some(day => isSameDay(day, new Date()));
|
|
||||||
|
|
||||||
const getFilteredEvents = (day, hour) => {
|
|
||||||
const dayName = format(day, 'eeee', { locale: fr }).toLowerCase(); // Obtenir le nom du jour en minuscule pour le filtrage
|
|
||||||
|
|
||||||
if (!Array.isArray(planning)) {
|
|
||||||
console.error("planning n'est pas un tableau:", planning);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return planning.filter(event => {
|
|
||||||
const eventDay = event.day.toLowerCase(); // Convertir le jour de l'événement en minuscule
|
|
||||||
|
|
||||||
return (
|
|
||||||
eventDay === dayName &&
|
|
||||||
event.heure.startsWith(hour.toString().padStart(2, '0')) // Comparer l'heure sans les minutes
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/*const handleDrop = (item, hour, day) => {
|
|
||||||
const dateKey = format(new Date(day), 'yyyy-MM-dd');
|
|
||||||
const newEvent = {
|
|
||||||
id: `event-${dateKey}-${hour}`,
|
|
||||||
start: new Date(day).setHours(hour),
|
|
||||||
end: new Date(day).setHours(hour + 1),
|
|
||||||
matiere: item.matiere || item.name, // Assurer que 'matiere' soit défini
|
|
||||||
color: item.color || '#FFFFFF', // Ajouter une couleur par défaut si indéfini
|
|
||||||
teachers: item.teachers || [], // Assurer que 'teachers' soit défini
|
|
||||||
day: format(new Date(day), 'eeee', { locale: fr }).toLowerCase(), // Ajouter le jour
|
|
||||||
heure: hour.toString().padStart(2, '0') + ':00', // Ajouter l'heure ici
|
|
||||||
scheduleId: scheduleIdRef.current // Utiliser la référence à scheduleId ici
|
|
||||||
};
|
|
||||||
|
|
||||||
// Utiliser la référence pour accéder aux événements actuels
|
|
||||||
const existingEvents = eventsRef.current.filter(event => {
|
|
||||||
const eventHour = event.heure;
|
|
||||||
const eventDay = event.day;
|
|
||||||
|
|
||||||
console.log("DEBUG : " + event);
|
|
||||||
console.log("DEBUG : " + eventHour + " - " + hour.toString().padStart(2, '0') + ':00');
|
|
||||||
console.log("DEBUG : " + eventDay + " - " + format(new Date(day), 'eeee', { locale: fr }).toLowerCase());
|
|
||||||
console.log("DEBUG : " + event.scheduleId + " - " + scheduleIdRef.current);
|
|
||||||
console.log("----");
|
|
||||||
// Comparer la date, l'heure et le scheduleId pour trouver les événements correspondants
|
|
||||||
return eventHour === hour.toString().padStart(2, '0') + ':00' && eventDay === format(new Date(day), 'eeee', { locale: fr }).toLowerCase() && event.scheduleId === scheduleIdRef.current;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("existingEvents :", existingEvents);
|
|
||||||
if (existingEvents.length > 0) {
|
|
||||||
existingEvents.forEach(event => {
|
|
||||||
deleteEvent(event.id); // Supprimer l'événement existant
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ajouter l'événement à l'état global des événements
|
|
||||||
addEvent(newEvent);
|
|
||||||
|
|
||||||
setPlanning(prevEvents => {
|
|
||||||
// Filtrer les événements pour conserver ceux qui n'ont pas le même jour et créneau horaire
|
|
||||||
const updatedEvents = prevEvents.filter(event =>
|
|
||||||
!(event.day === newEvent.day && event.heure === newEvent.heure)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ajouter le nouvel événement
|
|
||||||
updatedEvents.push(newEvent);
|
|
||||||
|
|
||||||
return updatedEvents;
|
|
||||||
});
|
|
||||||
};*/
|
|
||||||
|
|
||||||
// Mettre à jour la position de la ligne toutes les minutes
|
|
||||||
useEffect(() => {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setCurrentTime(new Date());
|
|
||||||
}, 60000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Modifier l'useEffect pour l'auto-scroll
|
|
||||||
useEffect(() => {
|
|
||||||
if (scrollContainerRef.current && isCurrentWeek) {
|
|
||||||
const currentHour = new Date().getHours();
|
|
||||||
const scrollPosition = currentHour * 80;
|
|
||||||
// Ajout d'un délai pour laisser le temps au DOM de se mettre à jour
|
|
||||||
setTimeout(() => {
|
|
||||||
scrollContainerRef.current.scrollTop = scrollPosition - 200;
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}, [currentDate, isCurrentWeek]); // Ajout de currentDate dans les dépendances
|
|
||||||
|
|
||||||
|
|
||||||
// Calculer la position de la ligne de temps
|
|
||||||
const getCurrentTimePosition = () => {
|
|
||||||
const hours = currentTime.getHours();
|
|
||||||
const minutes = currentTime.getMinutes();
|
|
||||||
|
|
||||||
if (hours < 8 || hours > 18) {
|
|
||||||
return -1; // Hors de la plage horaire
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalMinutes = (hours - 8) * 60 + minutes; // Minutes écoulées depuis 8h
|
|
||||||
const cellHeight = 80; // Hauteur des cellules (par exemple 80px pour 20rem / 24)
|
|
||||||
|
|
||||||
const position = (totalMinutes / 60) * cellHeight;
|
|
||||||
|
|
||||||
return position;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex-1 max-h-[calc(100vh-192px)] overflow-hidden">
|
|
||||||
{/* En-tête des jours */}
|
|
||||||
<div className="grid gap-[1px] bg-gray-100 w-full" style={{ gridTemplateColumns: "2.5rem repeat(6, 1fr)" }}>
|
|
||||||
<div className="bg-white h-14"></div>
|
|
||||||
{weekDays.map((day) => (
|
|
||||||
<div
|
|
||||||
key={day}
|
|
||||||
className={`p-3 text-center border-b ${isToday(day) ? 'bg-emerald-400 border-x border-emerald-600' : 'bg-white'}`} >
|
|
||||||
<div className={`text font-medium ${isToday(day) ? 'text-white' : 'text-gray-500'}`}>
|
|
||||||
{format(day, 'EEEE', { locale: fr })}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Grille horaire */}
|
|
||||||
<div ref={scrollContainerRef} className="flex-1 overflow-y-auto relative">
|
|
||||||
{/* Ligne de temps actuelle */}
|
|
||||||
{isCurrentWeek && (
|
|
||||||
<div
|
|
||||||
className="absolute left-0 right-0 z-10 border-emerald-500 border pointer-events-none"
|
|
||||||
style={{
|
|
||||||
top: getCurrentTimePosition(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="absolute -left-2 -top-1 w-2 h-2 rounded-full bg-emerald-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid gap-[1px] bg-white" style={{ gridTemplateColumns: "2.5rem repeat(6, 1fr)" }}>
|
|
||||||
{timeSlots.map(hour => (
|
|
||||||
<React.Fragment key={hour}>
|
|
||||||
<div className="h-20 p-1 text-right text-sm text-gray-500 bg-gray-100 font-medium">
|
|
||||||
{`${hour.toString().padStart(2, '0')}:00`}
|
|
||||||
</div>
|
|
||||||
{weekDays.map(day => {
|
|
||||||
const filteredEvents = getFilteredEvents(day, hour);
|
|
||||||
|
|
||||||
return(
|
|
||||||
<DropTargetCell
|
|
||||||
key={`${hour}-${day}`}
|
|
||||||
hour={hour}
|
|
||||||
day={day}
|
|
||||||
onDrop={handleDrop}
|
|
||||||
onDateClick={onDateClick}
|
|
||||||
filteredEvents={filteredEvents}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PlanningClassView;
|
|
||||||
@ -1,113 +1,182 @@
|
|||||||
import React, {useRef} from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { format, isToday, isSameDay, startOfWeek, addDays } from 'date-fns';
|
import { format, addDays, startOfWeek } from 'date-fns';
|
||||||
import { fr } from 'date-fns/locale';
|
import { fr } from 'date-fns/locale';
|
||||||
import DropTargetCell from './DropTargetCell';
|
import DropTargetCell from '@/components/Structure/Planning/DropTargetCell';
|
||||||
|
import { useClasseForm } from '@/context/ClasseFormContext';
|
||||||
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
|
import { Calendar } from 'lucide-react';
|
||||||
|
import SpecialityEventModal from '@/components/Structure/Planning/SpecialityEventModal'; // Assurez-vous du bon chemin d'importation
|
||||||
|
|
||||||
const PlanningClassView = ({ schedule, onDrop, planningType }) => {
|
const PlanningClassView = ({ schedule, onDrop, selectedLevel, handleUpdatePlanning, classe }) => {
|
||||||
const weekDays = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"];
|
const { formData } = useClasseForm();
|
||||||
const isCurrentWeek = weekDays.some(day => isSameDay(day, new Date()));
|
const { determineInitialPeriod } = useClasses();
|
||||||
const scrollContainerRef = useRef(null);
|
|
||||||
// Calcul des dates des jours de la semaine
|
|
||||||
const startDate = startOfWeek(new Date(), { weekStartsOn: 1 }); // Début de la semaine (lundi)
|
|
||||||
const weekDayDates = weekDays.map((_, index) => addDays(startDate, index));
|
|
||||||
|
|
||||||
// Fonction pour formater l'heure
|
const [currentPeriod, setCurrentPeriod] = useState(schedule?.emploiDuTemps ? determineInitialPeriod(schedule.emploiDuTemps) : null);
|
||||||
const formatTime = (time) => {
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [hour, minute] = time.split(':');
|
const [selectedCell, setSelectedCell] = useState(null);
|
||||||
return `${hour}h${minute}`;
|
const [existingEvent, setExistingEvent] = useState(null);
|
||||||
};
|
|
||||||
|
|
||||||
const renderCells = () => {
|
useEffect(() => {
|
||||||
const cells = [];
|
if (schedule?.emploiDuTemps) {
|
||||||
const timeSlots = Array.from({ length: 12 }, (_, index) => index + 8); // Heures de 08:00 à 19:00
|
setCurrentPeriod(determineInitialPeriod(schedule.emploiDuTemps));
|
||||||
|
}
|
||||||
|
}, [schedule]);
|
||||||
|
|
||||||
timeSlots.forEach(hour => {
|
if (!schedule || !schedule.emploiDuTemps) {
|
||||||
cells.push(
|
return (
|
||||||
<div key={`hour-${hour}`} className="h-20 p-1 text-right text-sm text-gray-500 bg-gray-100 font-medium">
|
<div className="w-full p-4 bg-gray-50 rounded-lg shadow-inner">
|
||||||
{`${hour.toString().padStart(2, '0')}:00`}
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||||
|
<Calendar className="w-8 h-8 mr-2" />
|
||||||
|
Planning
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
weekDays.forEach(day => {
|
|
||||||
const daySchedule = schedule?.emploiDuTemps?.[day] || [];
|
|
||||||
const courses = daySchedule.filter(course => {
|
|
||||||
const courseHour = parseInt(course.heure.split(':')[0], 10);
|
|
||||||
const courseDuration = parseInt(course.duree, 10); // Utiliser la durée comme un nombre
|
|
||||||
return courseHour <= hour && hour < (courseHour + courseDuration);
|
|
||||||
});
|
|
||||||
|
|
||||||
const course = courses.length > 0 ? courses[0] : null;
|
|
||||||
|
|
||||||
cells.push(
|
|
||||||
<DropTargetCell
|
|
||||||
key={`${day}-${hour}`}
|
|
||||||
day={day}
|
|
||||||
hour={hour}
|
|
||||||
course={course}
|
|
||||||
onDrop={onDrop}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return cells;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Calculer la position de la ligne de temps
|
|
||||||
const getCurrentTimePosition = () => {
|
|
||||||
const hours = currentTime.getHours();
|
|
||||||
const minutes = currentTime.getMinutes();
|
|
||||||
|
|
||||||
if (hours < 8 || hours > 18) {
|
|
||||||
return -1; // Hors de la plage horaire
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalMinutes = (hours - 8) * 60 + minutes; // Minutes écoulées depuis 8h
|
const emploiDuTemps = schedule.emploiDuTemps[currentPeriod] || schedule.emploiDuTemps;
|
||||||
const cellHeight = 80; // Hauteur des cellules (par exemple 80px pour 20rem / 24)
|
const joursOuverture = Object.keys(emploiDuTemps);
|
||||||
|
const currentWeekDays = joursOuverture
|
||||||
|
.map(day => {
|
||||||
|
switch(day.toLowerCase()) {
|
||||||
|
case 'lundi': return 1;
|
||||||
|
case 'mardi': return 2;
|
||||||
|
case 'mercredi': return 3;
|
||||||
|
case 'jeudi': return 4;
|
||||||
|
case 'vendredi': return 5;
|
||||||
|
case 'samedi': return 6;
|
||||||
|
case 'dimanche': return 7;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort((a, b) => a - b) // Trier les jours dans l'ordre croissant
|
||||||
|
.map(day => addDays(startOfWeek(new Date(), { weekStartsOn: 1 }), day - 1)); // Calculer les dates à partir du lundi
|
||||||
|
|
||||||
const position = (totalMinutes / 60) * cellHeight;
|
const getFilteredEvents = (day, time, level) => {
|
||||||
|
const [hour, minute] = time.split(':').map(Number);
|
||||||
|
const startTime = hour + minute / 60; // Convertir l'heure en fraction d'heure
|
||||||
|
|
||||||
return position;
|
return emploiDuTemps[day.toLowerCase()]?.filter(event => {
|
||||||
|
const [eventHour, eventMinute] = event.heure.split(':').map(Number);
|
||||||
|
const eventStartTime = eventHour + eventMinute / 60;
|
||||||
|
const eventEndTime = eventStartTime + parseFloat(event.duree);
|
||||||
|
|
||||||
|
// Filtrer en fonction du selectedLevel
|
||||||
|
return schedule.niveau === level && startTime >= eventStartTime && startTime < eventEndTime;
|
||||||
|
}) || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCellClick = (hour, day) => {
|
||||||
|
const cellEvents = getFilteredEvents(day, hour, selectedLevel);
|
||||||
|
|
||||||
|
setSelectedCell({ hour, day, selectedLevel });
|
||||||
|
setExistingEvent(cellEvents.length ? cellEvents[0] : null);
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTimeSlots = () => {
|
||||||
|
const timeSlots = [];
|
||||||
|
|
||||||
|
for (let hour = parseInt(formData.plage_horaire[0], 10); hour <= parseInt(formData.plage_horaire[1], 10); hour++) {
|
||||||
|
const hourString = hour.toString().padStart(2, '0');
|
||||||
|
|
||||||
|
timeSlots.push(
|
||||||
|
<React.Fragment key={`${hourString}:00-${Math.random()}`}>
|
||||||
|
<div className="h-20 p-1 text-right text-sm text-gray-500 bg-gray-100 font-medium">
|
||||||
|
{`${hourString}:00`}
|
||||||
|
</div>
|
||||||
|
{currentWeekDays.map((date, index) => {
|
||||||
|
const day = format(date, 'iiii', { locale: fr }).toLowerCase();
|
||||||
|
const uniqueKey = `${hourString}:00-${day}-${index}`;
|
||||||
|
return (
|
||||||
|
<div key={uniqueKey} className="flex flex-col">
|
||||||
|
<DropTargetCell
|
||||||
|
hour={`${hourString}:00`}
|
||||||
|
day={day}
|
||||||
|
courses={getFilteredEvents(day, `${hourString}:00`, selectedLevel)}
|
||||||
|
onDrop={onDrop}
|
||||||
|
onClick={(hour, day) => handleCellClick(hour, day)}
|
||||||
|
/>
|
||||||
|
<DropTargetCell
|
||||||
|
hour={`${hourString}:30`}
|
||||||
|
day={day}
|
||||||
|
courses={getFilteredEvents(day, `${hourString}:30`, selectedLevel)}
|
||||||
|
onDrop={onDrop}
|
||||||
|
onClick={(hour, day) => handleCellClick(hour, day)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return timeSlots;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="w-full p-4 bg-gray-50 rounded-lg shadow-inner">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||||
|
<Calendar className="w-8 h-8 mr-2" />
|
||||||
|
Planning
|
||||||
|
</h2>
|
||||||
|
{schedule.emploiDuTemps.S1 && schedule.emploiDuTemps.S2 && (
|
||||||
|
<div>
|
||||||
|
<button onClick={() => setCurrentPeriod('S1')} className={`px-4 py-2 ${currentPeriod === 'S1' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
|
||||||
|
Semestre 1
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setCurrentPeriod('S2')} className={`px-4 py-2 ${currentPeriod === 'S2' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
|
||||||
|
Semestre 2
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{schedule.emploiDuTemps.T1 && schedule.emploiDuTemps.T2 && schedule.emploiDuTemps.T3 && (
|
||||||
|
<div>
|
||||||
|
<button onClick={() => setCurrentPeriod('T1')} className={`px-4 py-2 ${currentPeriod === 'T1' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
|
||||||
|
Trimestre 1
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setCurrentPeriod('T2')} className={`px-4 py-2 ${currentPeriod === 'T2' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
|
||||||
|
Trimestre 2
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setCurrentPeriod('T3')} className={`px-4 py-2 ${currentPeriod === 'T3' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
|
||||||
|
Trimestre 3
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="flex-1 max-h-[calc(100vh-192px)] overflow-hidden">
|
<div className="flex-1 max-h-[calc(100vh-192px)] overflow-hidden">
|
||||||
{/* En-tête des jours */}
|
{/* En-tête des jours */}
|
||||||
<div className="grid gap-[1px] bg-gray-100 w-full" style={{ gridTemplateColumns: "2.5rem repeat(6, 1fr)" }}>
|
<div className="grid w-full" style={{ gridTemplateColumns: `2.5rem repeat(${currentWeekDays.length}, 1fr)` }}>
|
||||||
<div className="bg-white h-14"></div>
|
<div className="bg-gray-50 h-14"></div>
|
||||||
{weekDayDates.map((day) => (
|
{currentWeekDays.map((date, index) => (
|
||||||
<div
|
<div
|
||||||
key={day}
|
key={`${date}-${index}`}
|
||||||
className={`p-3 text-center border-b ${isToday(day) ? 'bg-emerald-400 border-x border-emerald-600' : 'bg-white'}`} >
|
className="p-3 text-center bg-emerald-100 text-emerald-800 border-r border-emerald-200">
|
||||||
<div className={`text font-medium ${isToday(day) ? 'text-white' : 'text-gray-500'}`}>
|
<div className="text font-semibold">
|
||||||
{format(day, 'EEEE', { locale: fr })}
|
{format(date, 'EEEE', { locale: fr })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Grille horaire */}
|
{/* Contenu du planning */}
|
||||||
<div ref={scrollContainerRef} className="flex-1 overflow-y-auto relative">
|
<div className="flex-1 overflow-y-auto relative" style={{ maxHeight: 'calc(100vh - 300px)' }}>
|
||||||
{isCurrentWeek && (
|
<div className="grid bg-white relative" style={{ gridTemplateColumns: `2.5rem repeat(${currentWeekDays.length}, 1fr)` }}>
|
||||||
<div
|
{renderTimeSlots()}
|
||||||
className="absolute left-0 right-0 z-10 border-emerald-500 border pointer-events-none"
|
</div>
|
||||||
style={{
|
</div>
|
||||||
top: getCurrentTimePosition(),
|
</div>
|
||||||
}}
|
<SpecialityEventModal
|
||||||
>
|
isOpen={isModalOpen}
|
||||||
<div
|
onClose={() => setIsModalOpen(false)}
|
||||||
className="absolute -left-2 -top-1 w-2 h-2 rounded-full bg-emerald-500"
|
selectedCell={selectedCell}
|
||||||
|
existingEvent={existingEvent}
|
||||||
|
handleUpdatePlanning={handleUpdatePlanning}
|
||||||
|
classe={classe}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={`grid gap-[1px] bg-white`} style={{ gridTemplateColumns: `2.5rem repeat(6, 1fr)` }}>
|
|
||||||
{renderCells()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -124,6 +193,11 @@ PlanningClassView.propTypes = {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
).isRequired,
|
).isRequired,
|
||||||
|
plageHoraire: PropTypes.shape({
|
||||||
|
startHour: PropTypes.number.isRequired,
|
||||||
|
endHour: PropTypes.number.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
joursOuverture: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,75 +1,72 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { AnimatePresence, findSpring, motion } from 'framer-motion'; // Ajouter cet import
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import PlanningClassView from '@/components/Structure/Planning/PlanningClassView';
|
import PlanningClassView from '@/components/Structure/Planning/PlanningClassView';
|
||||||
import SpecialityEventModal from '@/components/Structure/Planning/SpecialityEventModal';
|
import SpecialitiesList from '@/components/Structure/Planning/SpecialitiesList';
|
||||||
import ClassesInfo from '@/components/Structure/Planning/ClassesInfo';
|
|
||||||
import { BK_GESTIONENSEIGNANTS_PLANNING_URL } from '@/utils/Url';
|
import { BK_GESTIONENSEIGNANTS_PLANNING_URL } from '@/utils/Url';
|
||||||
import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react'
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
import { ClasseFormProvider } from '@/context/ClasseFormContext';
|
||||||
|
import TabsStructure from '@/components/Structure/Configuration/TabsStructure';
|
||||||
|
import { Bookmark, Users, BookOpen, Newspaper } from 'lucide-react';
|
||||||
|
|
||||||
const ScheduleManagement = ({ schedules, setSchedules, handleUpdatePlanning, specialities, teachers, classes }) => {
|
const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const currentMonth = new Date().getMonth();
|
||||||
|
const currentSchoolYearStart = currentMonth >= 8 ? currentYear : currentYear - 1;
|
||||||
|
|
||||||
|
const [selectedClass, setSelectedClass] = useState(null);
|
||||||
|
const [selectedLevel, setSelectedLevel] = useState('');
|
||||||
|
const [schedule, setSchedule] = useState(null);
|
||||||
|
|
||||||
|
const { getNiveauxTabs } = useClasses();
|
||||||
|
const niveauxLabels = Array.isArray(selectedClass?.niveaux) ? getNiveauxTabs(selectedClass.niveaux) : [];
|
||||||
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [selectedClass, setSelectedClass] = useState(null);
|
const handleOpenModal = () => setIsModalOpen(true);
|
||||||
const [schedule, setSchedule] = useState(null);
|
const handleCloseModal = () => setIsModalOpen(false);
|
||||||
const scheduleRef = useRef(null);
|
|
||||||
const scheduleId = useRef(null);
|
|
||||||
const [planningType, setPlanningType] = useState('TRIMESTRIEL');
|
|
||||||
const [currentPeriod, setCurrentPeriod] = useState('T1');
|
|
||||||
|
|
||||||
const planningChoices = [
|
|
||||||
{ value: 'TRIMESTRIEL', label: 'TRIMESTRIEL' },
|
|
||||||
{ value: 'SEMESTRIEL', label: 'SEMESTRIEL' },
|
|
||||||
{ value: 'ANNUEL', label: 'ANNUEL' },
|
|
||||||
];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedClass) {
|
if (selectedClass) {
|
||||||
scheduleId.current = selectedClass.planning.id;
|
const defaultLevel = niveauxLabels.length > 0 ? niveauxLabels[0].id : '';
|
||||||
setSchedule(selectedClass.planning);
|
const niveau = selectedLevel || defaultLevel;
|
||||||
} else {
|
|
||||||
setSchedule(null);
|
setSelectedLevel(niveau);
|
||||||
|
|
||||||
|
const currentPlanning = selectedClass.plannings_read.find(planning => planning.niveau === niveau);
|
||||||
|
setSchedule(currentPlanning ? currentPlanning.planning : {});
|
||||||
}
|
}
|
||||||
}, [selectedClass]);
|
}, [selectedClass, niveauxLabels]);
|
||||||
|
|
||||||
// Synchroniser scheduleRef avec schedule
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scheduleRef.current = schedule;
|
if (selectedClass && selectedLevel) {
|
||||||
}, [schedule]);
|
const currentPlanning = selectedClass.plannings_read.find(planning => planning.niveau === selectedLevel);
|
||||||
|
setSchedule(currentPlanning ? currentPlanning.planning : {});
|
||||||
|
}
|
||||||
|
}, [selectedClass, selectedLevel]);
|
||||||
|
|
||||||
const handleClassSelect = (cls) => {
|
const handleLevelSelect = (niveau) => {
|
||||||
setSelectedClass(cls);
|
setSelectedLevel(niveau);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleClassSelect = (classId) => {
|
||||||
if (schedule) {
|
const selectedClasse = categorizedClasses['Actives'].find(classe => classe.id === classId);
|
||||||
console.log("Schedule data:", schedule);
|
setSelectedClass(selectedClasse);
|
||||||
}
|
setSelectedLevel('');
|
||||||
}, [schedule]);
|
};
|
||||||
|
|
||||||
|
|
||||||
// Fonction onDrop dans PlanningClassView ou un composant parent
|
|
||||||
const onDrop = (item, hour, day) => {
|
const onDrop = (item, hour, day) => {
|
||||||
// Les données de l'élément drag and drop (spécialité par exemple)
|
|
||||||
const { id, name, color, teachers } = item;
|
const { id, name, color, teachers } = item;
|
||||||
|
const newSchedule = { ...schedule, emploiDuTemps: schedule.emploiDuTemps || {} };
|
||||||
|
|
||||||
// Créez une nouvelle copie du planning
|
|
||||||
const newSchedule = { ...scheduleRef.current };
|
|
||||||
|
|
||||||
// Vérifiez s'il existe déjà une entrée pour le jour
|
|
||||||
if (!newSchedule.emploiDuTemps[day]) {
|
if (!newSchedule.emploiDuTemps[day]) {
|
||||||
newSchedule.emploiDuTemps[day] = [];
|
newSchedule.emploiDuTemps[day] = [];
|
||||||
}
|
}
|
||||||
const courseTime = `${hour.toString().padStart(2, '0')}:00`;
|
const courseTime = `${hour.toString().padStart(2, '0')}:00`;
|
||||||
|
|
||||||
// Rechercher s'il y a déjà un cours à l'heure spécifiée
|
const existingCourseIndex = newSchedule.emploiDuTemps[day].findIndex(course => course.heure === courseTime);
|
||||||
const existingCourseIndex = newSchedule.emploiDuTemps[day].findIndex(course =>
|
|
||||||
course.heure === courseTime
|
|
||||||
);
|
|
||||||
|
|
||||||
const newCourse = {
|
const newCourse = {
|
||||||
duree: '1',
|
duree: '1',
|
||||||
@ -79,90 +76,90 @@ const ScheduleManagement = ({ schedules, setSchedules, handleUpdatePlanning, spe
|
|||||||
color: color,
|
color: color,
|
||||||
};
|
};
|
||||||
|
|
||||||
// S'il existe déjà un cours à cette heure, on le remplace
|
|
||||||
if (existingCourseIndex !== -1) {
|
if (existingCourseIndex !== -1) {
|
||||||
newSchedule.emploiDuTemps[day][existingCourseIndex] = newCourse;
|
newSchedule.emploiDuTemps[day][existingCourseIndex] = newCourse;
|
||||||
} else {
|
} else {
|
||||||
// Sinon on ajoute le nouveau cours
|
|
||||||
newSchedule.emploiDuTemps[day].push(newCourse);
|
newSchedule.emploiDuTemps[day].push(newCourse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mettez à jour l'état du planning
|
// Mettre à jour scheduleRef
|
||||||
setSchedule(newSchedule)
|
setSchedule(newSchedule)
|
||||||
|
|
||||||
// Appelez la fonction handleUpdatePlanning en dehors de setSchedule
|
// Utiliser `handleUpdatePlanning` pour mettre à jour le planning du niveau de la classe
|
||||||
handleUpdatePlanning(`${BK_GESTIONENSEIGNANTS_PLANNING_URL}`, scheduleId.current, newSchedule);
|
const planningId = selectedClass.plannings_read.find(planning => planning.niveau === selectedLevel)?.planning.id;
|
||||||
};
|
if (planningId) {
|
||||||
|
console.log('newSchedule : ', newSchedule)
|
||||||
const getPeriodLabel = (period) => {
|
handleUpdatePlanning(BK_GESTIONENSEIGNANTS_PLANNING_URL, planningId, newSchedule);
|
||||||
switch(period) {
|
|
||||||
case 'T1': return '1er trimestre';
|
|
||||||
case 'T2': return '2e trimestre';
|
|
||||||
case 'T3': return '3e trimestre';
|
|
||||||
case 'S1': return '1er semestre';
|
|
||||||
case 'S2': return '2e semestre';
|
|
||||||
default: return period;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePeriodChange = (direction) => {
|
const categorizedClasses = classes.reduce((acc, classe) => {
|
||||||
if (planningType === 'TRIMESTRIEL') {
|
const { annee_scolaire } = classe;
|
||||||
if (direction === 'prev') {
|
const [startYear] = annee_scolaire.split('-').map(Number);
|
||||||
setCurrentPeriod(currentPeriod === 'T1' ? 'T3' : `T${parseInt(currentPeriod.slice(1)) - 1}`);
|
const category = startYear >= currentSchoolYearStart ? 'Actives' : 'Anciennes';
|
||||||
} else {
|
|
||||||
setCurrentPeriod(currentPeriod === 'T3' ? 'T1' : `T${parseInt(currentPeriod.slice(1)) + 1}`);
|
if (!acc[category]) {
|
||||||
}
|
acc[category] = [];
|
||||||
} else if (planningType === 'SEMESTRIEL') {
|
}
|
||||||
setCurrentPeriod(currentPeriod === 'S1' ? 'S2' : 'S1');
|
acc[category].push(classe);
|
||||||
}
|
return acc;
|
||||||
};
|
}, {});
|
||||||
|
|
||||||
// Fonctionnalité de gestion des emplois du temps
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full overflow-hidden">
|
<div className="flex flex-col h-full">
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<ClassesInfo classes={classes} onClassSelect={handleClassSelect}/>
|
<div className="p-4 bg-gray-100 border-b">
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
|
||||||
<div className="flex justify-between items-center p-4 w-full">
|
{/* Colonne Classes */}
|
||||||
<div className="flex items-center w-full">
|
<div className="p-4 bg-gray-50 rounded-lg shadow-inner">
|
||||||
<SelectChoice
|
<div className="flex justify-between items-center mb-4">
|
||||||
name="planningType"
|
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||||
IconItem={Calendar}
|
<Users className="w-8 h-8 mr-2" />
|
||||||
selected={planningType}
|
Classes
|
||||||
choices={planningChoices}
|
</h2>
|
||||||
callback={(e) => {
|
</div>
|
||||||
setPlanningType(e.target.value);
|
{categorizedClasses['Actives'] &&
|
||||||
setCurrentPeriod(e.target.value === 'TRIMESTRIEL' ? 'T1' : 'S1');
|
<TabsStructure
|
||||||
}}
|
activeTab={selectedClass?.id}
|
||||||
|
setActiveTab={handleClassSelect}
|
||||||
|
tabs={categorizedClasses['Actives'].map(classe => ({
|
||||||
|
id: classe.id,
|
||||||
|
title: classe.nom_ambiance,
|
||||||
|
icon: Users
|
||||||
|
}))}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{planningType !== 'ANNUEL' && (
|
{/* Colonne Niveaux */}
|
||||||
<div className="flex items-center justify-center w-full">
|
<div className="p-4 bg-gray-50 rounded-lg shadow-inner">
|
||||||
<button
|
<div className="flex justify-between items-center mb-4">
|
||||||
onClick={() => handlePeriodChange('prev')}
|
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||||
className={`mr-4 p-2 border rounded-lg ${
|
<Bookmark className="w-8 h-8 mr-2" />
|
||||||
currentPeriod === 'T1' || currentPeriod === 'S1' ? 'bg-gray-300 text-gray-700 cursor-not-allowed' : 'bg-emerald-500 text-white hover:bg-emerald-600'
|
Niveaux
|
||||||
} transition-colors duration-300`}
|
</h2>
|
||||||
disabled={currentPeriod === 'T1' || currentPeriod === 'S1'}
|
|
||||||
>
|
|
||||||
<ChevronLeft className="h-6 w-6" />
|
|
||||||
</button>
|
|
||||||
<span className="text-lg font-semibold mx-4">{getPeriodLabel(currentPeriod)}</span>
|
|
||||||
<button
|
|
||||||
onClick={() => handlePeriodChange('next')}
|
|
||||||
className={`ml-4 p-2 border rounded-lg ${
|
|
||||||
(planningType === 'TRIMESTRIEL' && currentPeriod === 'T3') || (planningType === 'SEMESTRIEL' && currentPeriod === 'S2') ? 'bg-gray-300 text-gray-700 cursor-not-allowed' : 'bg-emerald-500 text-white hover:bg-emerald-600'
|
|
||||||
} transition-colors duration-300`}
|
|
||||||
disabled={(planningType === 'TRIMESTRIEL' && currentPeriod === 'T3') || (planningType === 'SEMESTRIEL' && currentPeriod === 'S2')}
|
|
||||||
>
|
|
||||||
<ChevronRight className="h-6 w-6" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{niveauxLabels &&
|
||||||
|
<TabsStructure activeTab={selectedLevel} setActiveTab={handleLevelSelect} tabs={niveauxLabels} />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 max-h-[calc(100vh-192px)] overflow-hidden">
|
{/* Colonne Spécialités */}
|
||||||
|
<div className="p-4 bg-gray-50 rounded-lg shadow-inner">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||||
|
<BookOpen className="w-8 h-8 mr-2" />
|
||||||
|
Spécialités
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<SpecialitiesList teachers={selectedClass ? selectedClass.enseignants : []} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 p-4 overflow-y-auto">
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div
|
||||||
key="year"
|
key="year"
|
||||||
@ -170,26 +167,18 @@ const ScheduleManagement = ({ schedules, setSchedules, handleUpdatePlanning, spe
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, y: -20 }}
|
exit={{ opacity: 0, y: -20 }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
|
className="flex-1 relative"
|
||||||
>
|
>
|
||||||
<PlanningClassView
|
<ClasseFormProvider initialClasse={selectedClass || {}}>
|
||||||
schedule={schedule}
|
<PlanningClassView schedule={schedule} onDrop={onDrop} selectedLevel={selectedLevel} handleUpdatePlanning={handleUpdatePlanning} classe={selectedClass} />
|
||||||
onDrop={onDrop}
|
</ClasseFormProvider>
|
||||||
planningType={planningType}
|
|
||||||
currentPeriod={currentPeriod}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
{/* <SpecialityEventModal
|
|
||||||
isOpen={isModalOpen}
|
|
||||||
onClose={() => setIsModalOpen(false)}
|
|
||||||
eventData={eventData}
|
|
||||||
setEventData={setEventData}
|
|
||||||
selectedClass={selectedClass}
|
|
||||||
/> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ScheduleManagement;
|
export default ScheduleManagement;
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
|
import DraggableSpeciality from '@/components/Structure/Planning/DraggableSpeciality';
|
||||||
|
|
||||||
|
const SpecialitiesList = ({ teachers }) => {
|
||||||
|
const { groupSpecialitiesBySubject } = useClasses();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center w-full">
|
||||||
|
<div className="flex flex-wrap gap-2 mt-4 justify-center">
|
||||||
|
{groupSpecialitiesBySubject(teachers).map((speciality) => (
|
||||||
|
<DraggableSpeciality key={speciality.id} speciality={speciality} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SpecialitiesList;
|
||||||
|
|
||||||
|
|
||||||
@ -1,77 +1,151 @@
|
|||||||
import { usePlanning } from '@/context/PlanningContext';
|
import React, { useState, useEffect } from 'react';
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
import { format } from 'date-fns';
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
import React, { useEffect } from 'react';
|
import { useClasseForm } from '@/context/ClasseFormContext';
|
||||||
import { Users, BookOpen } from 'lucide-react';
|
import { BK_GESTIONENSEIGNANTS_PLANNING_URL } from '@/utils/Url';
|
||||||
|
import { BookOpen, Users } from 'lucide-react';
|
||||||
|
|
||||||
export default function SpecialityEventModal({ isOpen, onClose, eventData, setEventData, selectedClass }) {
|
const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, handleUpdatePlanning, classe }) => {
|
||||||
const { addEvent, updateEvent, deleteEvent, schedules } = usePlanning();
|
const { formData, setFormData } = useClasseForm();
|
||||||
|
const { groupSpecialitiesBySubject } = useClasses();
|
||||||
|
const [selectedSpeciality, setSelectedSpeciality] = useState('');
|
||||||
|
const [selectedTeacher, setSelectedTeacher] = useState('');
|
||||||
|
const [eventData, setEventData] = useState({
|
||||||
|
specialiteId: '',
|
||||||
|
teacherId: '',
|
||||||
|
start: '',
|
||||||
|
duration: '1h'
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
// Réinitialiser eventData lorsque la modale se ferme
|
// Réinitialiser eventData lorsque la modale se ferme
|
||||||
setEventData({
|
setEventData({
|
||||||
scheduleId: '',
|
|
||||||
specialiteId: '',
|
specialiteId: '',
|
||||||
specialities: [],
|
teacherId: '',
|
||||||
// Réinitialiser d'autres champs si nécessaire
|
start: '',
|
||||||
|
duration: '1h'
|
||||||
});
|
});
|
||||||
|
setSelectedSpeciality('');
|
||||||
|
setSelectedTeacher('');
|
||||||
}
|
}
|
||||||
}, [isOpen, setEventData]);
|
}, [isOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && selectedClass) {
|
if (isOpen) {
|
||||||
|
console.log('debug : ', selectedCell);
|
||||||
|
if (existingEvent) {
|
||||||
|
// Mode édition
|
||||||
|
setEventData(existingEvent);
|
||||||
|
setSelectedSpeciality(existingEvent.specialiteId);
|
||||||
|
setSelectedTeacher(existingEvent.teacherId);
|
||||||
|
} else {
|
||||||
|
// Mode création
|
||||||
setEventData(prev => ({
|
setEventData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
scheduleId: selectedClass.id,
|
start: selectedCell.hour,
|
||||||
specialities: Array.from(new Map(
|
duration: '1h'
|
||||||
selectedClass.enseignants.flatMap(teacher =>
|
|
||||||
teacher.specialites.map(specialite => [specialite.id, {
|
|
||||||
id: specialite.id,
|
|
||||||
nom: specialite.nom,
|
|
||||||
codeCouleur: specialite.codeCouleur
|
|
||||||
}])
|
|
||||||
)
|
|
||||||
).values())
|
|
||||||
}));
|
}));
|
||||||
|
setSelectedSpeciality('');
|
||||||
|
setSelectedTeacher('');
|
||||||
}
|
}
|
||||||
}, [isOpen, selectedClass, setEventData]);
|
}
|
||||||
|
}, [isOpen, existingEvent, selectedCell]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!eventData.scheduleId) {
|
if (!eventData.specialiteId) {
|
||||||
alert('Veuillez sélectionner une spécialité');
|
alert('Veuillez sélectionner une spécialité');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedSchedule = schedules.find(s => s.id === eventData.scheduleId);
|
if (!eventData.teacherId) {
|
||||||
|
alert('Veuillez sélectionner un enseignant');
|
||||||
if (eventData.id) {
|
return;
|
||||||
updateEvent(eventData.id, {
|
|
||||||
...eventData,
|
|
||||||
scheduleId: eventData.scheduleId,
|
|
||||||
color: eventData.color || selectedSchedule?.color
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
addEvent({
|
|
||||||
...eventData,
|
|
||||||
id: `event-${Date.now()}`,
|
|
||||||
scheduleId: eventData.scheduleId,
|
|
||||||
color: eventData.color || selectedSchedule?.color
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transformer eventData pour correspondre au format du planning
|
||||||
|
const selectedTeacherData = formData.enseignants.find(teacher => teacher.id === parseInt(eventData.teacherId, 10));
|
||||||
|
const newCourse = {
|
||||||
|
color: '#FF0000', // Vous pouvez définir la couleur de manière dynamique si nécessaire
|
||||||
|
teachers: selectedTeacherData ? [`${selectedTeacherData.nom} ${selectedTeacherData.prenom}`] : [],
|
||||||
|
heure: `${eventData.start}:00`,
|
||||||
|
duree: eventData.duration.replace('h', ''), // Supposons que '1h' signifie 1
|
||||||
|
matiere: 'GROUPE'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mettre à jour le planning
|
||||||
|
const updatedPlannings = classe.plannings_read.map(planning => {
|
||||||
|
if (planning.niveau === selectedCell.selectedLevel) {
|
||||||
|
const newEmploiDuTemps = { ...planning.emploiDuTemps };
|
||||||
|
|
||||||
|
if (!newEmploiDuTemps[selectedCell.day]) {
|
||||||
|
newEmploiDuTemps[selectedCell.day] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseTime = newCourse.heure;
|
||||||
|
const existingCourseIndex = newEmploiDuTemps[selectedCell.day].findIndex(course => course.heure === courseTime);
|
||||||
|
|
||||||
|
if (existingCourseIndex !== -1) {
|
||||||
|
newEmploiDuTemps[selectedCell.day][existingCourseIndex] = newCourse;
|
||||||
|
} else {
|
||||||
|
newEmploiDuTemps[selectedCell.day].push(newCourse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...planning,
|
||||||
|
emploiDuTemps: newEmploiDuTemps
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return planning;
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedPlanning = updatedPlannings.find(planning => planning.niveau === selectedCell.selectedLevel);
|
||||||
|
|
||||||
|
setFormData(prevFormData => ({
|
||||||
|
...prevFormData,
|
||||||
|
plannings: updatedPlannings
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Appeler handleUpdatePlanning avec les arguments appropriés
|
||||||
|
const planningId = updatedPlanning ? updatedPlanning.planning.id : null;
|
||||||
|
console.log("id : ", planningId)
|
||||||
|
if (planningId) {
|
||||||
|
handleUpdatePlanning(BK_GESTIONENSEIGNANTS_PLANNING_URL, planningId, updatedPlanning.emploiDuTemps);
|
||||||
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = () => {
|
const filteredTeachers = selectedSpeciality
|
||||||
if (eventData.id && confirm('Êtes-vous sûr de vouloir supprimer cet événement ?')) {
|
? formData.enseignants.filter(teacher =>
|
||||||
deleteEvent(eventData.id);
|
teacher.specialites_ids.includes(parseInt(selectedSpeciality, 10))
|
||||||
onClose();
|
)
|
||||||
}
|
: formData.enseignants;
|
||||||
|
|
||||||
|
const handleSpecialityChange = (e) => {
|
||||||
|
const specialityId = e.target.value;
|
||||||
|
setSelectedSpeciality(specialityId);
|
||||||
|
|
||||||
|
// Mettre à jour eventData
|
||||||
|
setEventData(prev => ({
|
||||||
|
...prev,
|
||||||
|
specialiteId: specialityId
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTeacherChange = (e) => {
|
||||||
|
const teacherId = e.target.value;
|
||||||
|
setSelectedTeacher(teacherId);
|
||||||
|
|
||||||
|
// Mettre à jour eventData
|
||||||
|
setEventData(prev => ({
|
||||||
|
...prev,
|
||||||
|
teacherId: teacherId
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -84,86 +158,59 @@ export default function SpecialityEventModal({ isOpen, onClose, eventData, setEv
|
|||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
{/* Sélection de la Spécialité */}
|
{/* Sélection de la Spécialité */}
|
||||||
<div>
|
<div>
|
||||||
{eventData.scheduleId && eventData.specialities && eventData.specialities.length > 0 ? (
|
|
||||||
<SelectChoice
|
<SelectChoice
|
||||||
name={`spécialités-${eventData.scheduleId}`}
|
name="specialites"
|
||||||
label="Spécialités"
|
label="Spécialités"
|
||||||
selected={eventData.specialiteId ? eventData.specialiteId : ''}
|
selected={selectedSpeciality}
|
||||||
choices={eventData.specialities.map(specialite => ({
|
choices={[
|
||||||
value: specialite.id,
|
{ value: '', label: 'Sélectionner une spécialité' },
|
||||||
label: specialite.nom
|
...groupSpecialitiesBySubject(formData.enseignants).map((speciality) => ({
|
||||||
}))}
|
value: speciality.id,
|
||||||
callback={(event) => {
|
label: speciality.nom
|
||||||
const selectedSpecialityId = event.target.value;
|
}))
|
||||||
const selectedSpeciality = eventData.specialities.find(specialite => specialite.id === parseInt(selectedSpecialityId, 10));
|
]}
|
||||||
setEventData({
|
callback={handleSpecialityChange}
|
||||||
...eventData,
|
|
||||||
specialiteId: selectedSpecialityId,
|
|
||||||
color: selectedSpeciality?.codeCouleur || '#10b981'
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
IconItem={BookOpen}
|
IconItem={BookOpen}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<p>Aucune spécialité disponible pour cette classe.</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Dates */}
|
{/* Sélection de l'enseignant */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<SelectChoice
|
||||||
Début
|
name="teachers"
|
||||||
</label>
|
label="Enseignants"
|
||||||
<input
|
selected={selectedTeacher}
|
||||||
type="datetime-local"
|
choices={[
|
||||||
value={format(new Date(eventData.start), "yyyy-MM-dd'T'HH:mm")}
|
{ value: '', label: 'Sélectionner un enseignant'},
|
||||||
onChange={(e) => setEventData({ ...eventData, start: new Date(e.target.value).toISOString() })}
|
...filteredTeachers.map(teacher => ({
|
||||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
value: teacher.id,
|
||||||
required
|
label: `${teacher.nom} ${teacher.prenom}`
|
||||||
|
}))
|
||||||
|
]}
|
||||||
|
callback={handleTeacherChange}
|
||||||
|
IconItem={Users}
|
||||||
|
disabled={!selectedSpeciality} // Désactive le sélecteur si aucune spécialité n'est sélectionnée
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
||||||
Fin
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="datetime-local"
|
|
||||||
value={format(new Date(eventData.end), "yyyy-MM-dd'T'HH:mm")}
|
|
||||||
onChange={(e) => setEventData({ ...eventData, end: new Date(e.target.value).toISOString() })}
|
|
||||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Lieu */}
|
{/* Durée */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Lieu
|
Durée
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={eventData.location || ''}
|
value={eventData.duration}
|
||||||
onChange={(e) => setEventData({ ...eventData, location: e.target.value })}
|
onChange={(e) => setEventData(prev => ({
|
||||||
|
...prev,
|
||||||
|
duration: e.target.value
|
||||||
|
}))}
|
||||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Boutons */}
|
{/* Boutons */}
|
||||||
<div className="flex justify-between gap-2 mt-6">
|
<div className="flex justify-between gap-2 mt-6">
|
||||||
<div>
|
|
||||||
{eventData.id && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleDelete}
|
|
||||||
className="px-4 py-2 text-red-600 hover:bg-red-50 rounded"
|
|
||||||
>
|
|
||||||
Supprimer
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@ -178,9 +225,10 @@ export default function SpecialityEventModal({ isOpen, onClose, eventData, setEv
|
|||||||
{eventData.id ? 'Modifier' : 'Créer'}
|
{eventData.id ? 'Modifier' : 'Créer'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default SpecialityEventModal;
|
||||||
|
|||||||
@ -1,66 +1,67 @@
|
|||||||
import React, { createContext, useState, useContext } from 'react';
|
import React, { createContext, useState, useContext, useEffect } from 'react';
|
||||||
import { useClasses } from '@/context/ClassesContext'
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
|
|
||||||
const ClasseFormContext = createContext();
|
const ClasseFormContext = createContext();
|
||||||
|
|
||||||
export const useClasseForm = () => useContext(ClasseFormContext);
|
export const useClasseForm = () => useContext(ClasseFormContext);
|
||||||
|
|
||||||
export const ClasseFormProvider = ({ children, initialClasse }) => {
|
export const ClasseFormProvider = ({ children, initialClasse }) => {
|
||||||
|
const { getNiveauxLabels } = useClasses();
|
||||||
|
const [formData, setFormData] = useState({});
|
||||||
|
|
||||||
const { getNiveauxLabels, selectedDays } = useClasses();
|
useEffect(() => {
|
||||||
|
const plannings = initialClasse.plannings_read || [];
|
||||||
|
|
||||||
const [formData, setFormData] = useState(() => {
|
const defaultEmploiDuTemps = {
|
||||||
const planning = initialClasse.planning || {};
|
lundi: [],
|
||||||
const emploiDuTemps = planning.emploiDuTemps || {};
|
mardi: [],
|
||||||
|
mercredi: [],
|
||||||
const dateDebutSemestre1 = emploiDuTemps.S1 ? emploiDuTemps.S1.DateDebut : '';
|
jeudi: [],
|
||||||
const dateFinSemestre1 = emploiDuTemps.S1 ? emploiDuTemps.S1.DateFin : '';
|
vendredi: [],
|
||||||
const dateDebutSemestre2 = emploiDuTemps.S2 ? emploiDuTemps.S2.DateDebut : '';
|
samedi: [],
|
||||||
const dateFinSemestre2 = emploiDuTemps.S2 ? emploiDuTemps.S2.DateFin : '';
|
dimanche: []
|
||||||
|
};
|
||||||
const dateDebutTrimestre1 = emploiDuTemps.T1 ? emploiDuTemps.T1.DateDebut : '';
|
|
||||||
const dateFinTrimestre1 = emploiDuTemps.T1 ? emploiDuTemps.T1.DateFin : '';
|
|
||||||
const dateDebutTrimestre2 = emploiDuTemps.T2 ? emploiDuTemps.T2.DateDebut : '';
|
|
||||||
const dateFinTrimestre2 = emploiDuTemps.T2 ? emploiDuTemps.T2.DateFin : '';
|
|
||||||
const dateDebutTrimestre3 = emploiDuTemps.T3 ? emploiDuTemps.T3.DateDebut : '';
|
|
||||||
const dateFinTrimestre3 = emploiDuTemps.T3 ? emploiDuTemps.T3.DateFin : '';
|
|
||||||
|
|
||||||
|
const generateEmploiDuTemps = (planningType) => {
|
||||||
|
if (planningType === 1) {
|
||||||
|
return defaultEmploiDuTemps;
|
||||||
|
} else if (planningType === 2) {
|
||||||
return {
|
return {
|
||||||
|
S1: { DateDebut: '', DateFin: '', ...defaultEmploiDuTemps },
|
||||||
|
S2: { DateDebut: '', DateFin: '', ...defaultEmploiDuTemps },
|
||||||
|
};
|
||||||
|
} else if (planningType === 3) {
|
||||||
|
return {
|
||||||
|
T1: { DateDebut: '', DateFin: '', ...defaultEmploiDuTemps },
|
||||||
|
T2: { DateDebut: '', DateFin: '', ...defaultEmploiDuTemps },
|
||||||
|
T3: { DateDebut: '', DateFin: '', ...defaultEmploiDuTemps },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const newFormData = {
|
||||||
nom_ambiance: initialClasse.nom_ambiance || '',
|
nom_ambiance: initialClasse.nom_ambiance || '',
|
||||||
tranche_age: initialClasse.tranche_age || '',
|
tranche_age: initialClasse.tranche_age || '',
|
||||||
nombre_eleves: initialClasse.nombre_eleves || '',
|
nombre_eleves: initialClasse.nombre_eleves || '',
|
||||||
langue_enseignement: initialClasse.langue_enseignement || 'Français',
|
langue_enseignement: initialClasse.langue_enseignement || 'Français',
|
||||||
annee_scolaire: initialClasse.annee_scolaire || '',
|
annee_scolaire: initialClasse.annee_scolaire || '',
|
||||||
|
enseignants: initialClasse.enseignants || [],
|
||||||
enseignants_ids: initialClasse.enseignants_ids || [],
|
enseignants_ids: initialClasse.enseignants_ids || [],
|
||||||
planning_type: planning.type || 1,
|
type: initialClasse.type || 1,
|
||||||
plage_horaire: planning.plageHoraire || ['08:30', '17:30'],
|
plage_horaire: initialClasse.plage_horaire || ['08:30', '17:30'],
|
||||||
jours_ouverture: planning.joursOuverture || [1, 2, 4, 5],
|
jours_ouverture: initialClasse.jours_ouverture || [1, 2, 4, 5],
|
||||||
niveaux: initialClasse.niveaux || [],
|
niveaux: initialClasse.niveaux || [],
|
||||||
niveaux_label: getNiveauxLabels(initialClasse.niveaux || []),
|
plannings: plannings.length ? plannings.map(planning => ({
|
||||||
date_debut_semestre_1: dateDebutSemestre1,
|
niveau: planning.planning.niveau,
|
||||||
date_fin_semestre_1: dateFinSemestre1,
|
emploiDuTemps: planning.planning.emploiDuTemps
|
||||||
date_debut_semestre_2: dateDebutSemestre2,
|
})) : (initialClasse.niveaux || []).map(niveau => ({
|
||||||
date_fin_semestre_2: dateFinSemestre2,
|
niveau: niveau,
|
||||||
date_debut_trimestre_1: dateDebutTrimestre1,
|
emploiDuTemps: generateEmploiDuTemps(initialClasse.type || 1)
|
||||||
date_fin_trimestre_1: dateFinTrimestre1,
|
}))
|
||||||
date_debut_trimestre_2: dateDebutTrimestre2,
|
|
||||||
date_fin_trimestre_2: dateFinTrimestre2,
|
|
||||||
date_debut_trimestre_3: dateDebutTrimestre3,
|
|
||||||
date_fin_trimestre_3: dateFinTrimestre3,
|
|
||||||
planning: {
|
|
||||||
type: planning.type || 1,
|
|
||||||
plageHoraire: planning.plageHoraire || ['08:30', '17:30'],
|
|
||||||
joursOuverture: planning.joursOuverture || [1, 2, 4, 5],
|
|
||||||
emploiDuTemps: planning.emploiDuTemps || {
|
|
||||||
S1: { DateDebut: '', DateFin: '', lundi: [], mardi: [], mercredi: [], jeudi: [], vendredi: [], samedi: [], dimanche: [] },
|
|
||||||
S2: { DateDebut: '', DateFin: '', lundi: [], mardi: [], mercredi: [], jeudi: [], vendredi: [], samedi: [], dimanche: [] },
|
|
||||||
T1: { DateDebut: '', DateFin: '', lundi: [], mardi: [], mercredi: [], jeudi: [], vendredi: [], samedi: [], dimanche: [] },
|
|
||||||
T2: { DateDebut: '', DateFin: '', lundi: [], mardi: [], mercredi: [], jeudi: [], vendredi: [], samedi: [], dimanche: [] },
|
|
||||||
T3: { DateDebut: '', DateFin: '', lundi: [], mardi: [], mercredi: [], jeudi: [], vendredi: [], samedi: [], dimanche: [] },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
setFormData(newFormData);
|
||||||
|
}, [initialClasse, getNiveauxLabels]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClasseFormContext.Provider value={{ formData, setFormData }}>
|
<ClasseFormContext.Provider value={{ formData, setFormData }}>
|
||||||
@ -68,3 +69,4 @@ export const ClasseFormProvider = ({ children, initialClasse }) => {
|
|||||||
</ClasseFormContext.Provider>
|
</ClasseFormContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import React, { createContext, useContext } from 'react';
|
import React, { createContext, useContext } from 'react';
|
||||||
|
import { School } from 'lucide-react';
|
||||||
|
|
||||||
const ClassesContext = createContext();
|
const ClassesContext = createContext();
|
||||||
|
|
||||||
@ -9,9 +10,9 @@ export const ClassesProvider = ({ children }) => {
|
|||||||
|
|
||||||
const schoolYears = [
|
const schoolYears = [
|
||||||
{ value: '', label: 'Sélectionner une période' },
|
{ value: '', label: 'Sélectionner une période' },
|
||||||
|
{ value: `${currentYear - 1}-${currentYear}`, label: `${currentYear - 1}-${currentYear}` },
|
||||||
{ value: `${currentYear}-${currentYear + 1}`, label: `${currentYear}-${currentYear + 1}` },
|
{ value: `${currentYear}-${currentYear + 1}`, label: `${currentYear}-${currentYear + 1}` },
|
||||||
{ value: `${currentYear + 1}-${currentYear + 2}`, label: `${currentYear + 1}-${currentYear + 2}` },
|
{ value: `${currentYear + 1}-${currentYear + 2}`, label: `${currentYear + 1}-${currentYear + 2}` },
|
||||||
{ value: `${currentYear + 2}-${currentYear + 3}`, label: `${currentYear + 2}-${currentYear + 3}` },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const niveauxPremierCycle = [
|
const niveauxPremierCycle = [
|
||||||
@ -57,6 +58,18 @@ export const ClassesProvider = ({ children }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getNiveauxTabs = (niveaux) => {
|
||||||
|
// Trier les niveaux par id
|
||||||
|
const sortedNiveaux = niveaux.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
// Mapper les labels correspondants
|
||||||
|
return sortedNiveaux.map(niveauId => {
|
||||||
|
const niveau = allNiveaux.find(n => n.id === niveauId);
|
||||||
|
return niveau ? { id: niveau.id, title: niveau.name, icon: School } : { id: 'unknown', title: 'Niveau inconnu', icon: null };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const generateAgeToNiveaux = (minAge, maxAge) => {
|
const generateAgeToNiveaux = (minAge, maxAge) => {
|
||||||
if (minAge === null || isNaN(minAge)) {
|
if (minAge === null || isNaN(minAge)) {
|
||||||
return [];
|
return [];
|
||||||
@ -67,30 +80,60 @@ export const ClassesProvider = ({ children }) => {
|
|||||||
.map(({ id }) => id);
|
.map(({ id }) => id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatePlanning = (formData) => {
|
const getNiveauNameById = (id) => {
|
||||||
|
const niveau = allNiveaux.find(item => item.id.toString() === id);
|
||||||
|
return niveau ? niveau.name : '';
|
||||||
|
};
|
||||||
|
|
||||||
let updatedPlanning = { ...formData.planning };
|
const getAmbianceText = (classe) => {
|
||||||
|
const ambiance = classe.nom_ambiance ? classe.nom_ambiance : '';
|
||||||
|
const trancheAge = classe.tranche_age ? `${classe.tranche_age} ans` : '';
|
||||||
|
|
||||||
|
if (ambiance && trancheAge) {
|
||||||
|
return `${ambiance} (${trancheAge})`;
|
||||||
|
} else if (ambiance) {
|
||||||
|
return ambiance;
|
||||||
|
} else if (trancheAge) {
|
||||||
|
return trancheAge;
|
||||||
|
} else {
|
||||||
|
return 'Non spécifié';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAmbianceName = (classe) => {
|
||||||
|
const ambiance = classe.nom_ambiance ? classe.nom_ambiance : '';
|
||||||
|
|
||||||
|
if (ambiance) {
|
||||||
|
return ambiance;
|
||||||
|
} else {
|
||||||
|
return 'Non spécifié';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePlannings = (formData, existingPlannings) => {
|
||||||
|
return formData.niveaux.map(niveau => {
|
||||||
|
let existingPlanning = existingPlannings.find(planning => planning.niveau === niveau);
|
||||||
|
|
||||||
const emploiDuTemps = formData.jours_ouverture.reduce((acc, dayId) => {
|
const emploiDuTemps = formData.jours_ouverture.reduce((acc, dayId) => {
|
||||||
const dayName = selectedDays[dayId];
|
const dayName = selectedDays[dayId];
|
||||||
|
console.log('dayId:', dayId, 'dayName:', dayName); // Ajout de log pour vérifier les correspondances
|
||||||
if (dayName) {
|
if (dayName) {
|
||||||
acc[dayName] = [];
|
acc[dayName] = existingPlanning?.emploiDuTemps?.[dayName] || [];
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
if (formData.planning_type === 1) {
|
console.log('Emploi du Temps initialisé :', emploiDuTemps);
|
||||||
|
|
||||||
|
let updatedPlanning;
|
||||||
|
if (formData.type === 1) {
|
||||||
updatedPlanning = {
|
updatedPlanning = {
|
||||||
type: 1,
|
niveau: niveau,
|
||||||
plageHoraire: formData.plage_horaire,
|
|
||||||
joursOuverture: formData.jours_ouverture,
|
|
||||||
emploiDuTemps
|
emploiDuTemps
|
||||||
};
|
};
|
||||||
} else if (formData.planning_type === 2) {
|
} else if (formData.type === 2) {
|
||||||
updatedPlanning = {
|
updatedPlanning = {
|
||||||
type: 2,
|
niveau: niveau,
|
||||||
plageHoraire: formData.plage_horaire,
|
|
||||||
joursOuverture: formData.jours_ouverture,
|
|
||||||
emploiDuTemps: {
|
emploiDuTemps: {
|
||||||
S1: {
|
S1: {
|
||||||
DateDebut: formData.date_debut_semestre_1,
|
DateDebut: formData.date_debut_semestre_1,
|
||||||
@ -104,11 +147,9 @@ export const ClassesProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (formData.planning_type === 3) {
|
} else if (formData.type === 3) {
|
||||||
updatedPlanning = {
|
updatedPlanning = {
|
||||||
type: 3,
|
niveau: niveau,
|
||||||
plageHoraire: formData.plage_horaire,
|
|
||||||
joursOuverture: formData.jours_ouverture,
|
|
||||||
emploiDuTemps: {
|
emploiDuTemps: {
|
||||||
T1: {
|
T1: {
|
||||||
DateDebut: formData.date_debut_trimestre_1,
|
DateDebut: formData.date_debut_trimestre_1,
|
||||||
@ -129,11 +170,59 @@ export const ClassesProvider = ({ children }) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedPlanning;
|
console.log('Updated Planning:', updatedPlanning);
|
||||||
|
|
||||||
|
// Fusionner les plannings existants avec les nouvelles données
|
||||||
|
return existingPlanning
|
||||||
|
? { ...existingPlanning, ...updatedPlanning }
|
||||||
|
: updatedPlanning;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupSpecialitiesBySubject = (enseignants) => {
|
||||||
|
const groupedSpecialities = {};
|
||||||
|
|
||||||
|
enseignants.forEach(teacher => {
|
||||||
|
teacher.specialites.forEach(specialite => {
|
||||||
|
if (!groupedSpecialities[specialite.id]) {
|
||||||
|
groupedSpecialities[specialite.id] = {
|
||||||
|
...specialite,
|
||||||
|
teachers: [`${teacher.nom} ${teacher.prenom}`],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
groupedSpecialities[specialite.id].teachers.push(`${teacher.nom} ${teacher.prenom}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.values(groupedSpecialities);
|
||||||
|
};
|
||||||
|
|
||||||
|
const determineInitialPeriod = (emploiDuTemps) => {
|
||||||
|
if (emploiDuTemps.S1 && emploiDuTemps.S2) {
|
||||||
|
return 'S1'; // Planning semestriel
|
||||||
|
} else if (emploiDuTemps.T1 && emploiDuTemps.T2 && emploiDuTemps.T3) {
|
||||||
|
return 'T1'; // Planning trimestriel
|
||||||
|
}
|
||||||
|
return ''; // Planning annuel ou autre
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClassesContext.Provider value={{ schoolYears, getNiveauxLabels, generateAgeToNiveaux, niveauxPremierCycle, niveauxSecondCycle, niveauxTroisiemeCycle, typeEmploiDuTemps, updatePlanning }}>
|
<ClassesContext.Provider value={{ schoolYears,
|
||||||
|
getNiveauxLabels,
|
||||||
|
getNiveauxTabs,
|
||||||
|
generateAgeToNiveaux,
|
||||||
|
niveauxPremierCycle,
|
||||||
|
niveauxSecondCycle,
|
||||||
|
niveauxTroisiemeCycle,
|
||||||
|
typeEmploiDuTemps,
|
||||||
|
updatePlannings,
|
||||||
|
getAmbianceText,
|
||||||
|
getAmbianceName,
|
||||||
|
groupSpecialitiesBySubject,
|
||||||
|
getNiveauNameById,
|
||||||
|
determineInitialPeriod
|
||||||
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</ClassesContext.Provider>
|
</ClassesContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user