mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
refactor: Création de nouveaux composants / update formulaire de
création de classe (#2)
This commit is contained in:
@ -3,8 +3,8 @@ from django.db.models.signals import post_migrate
|
|||||||
|
|
||||||
def create_specialite(sender, **kwargs):
|
def create_specialite(sender, **kwargs):
|
||||||
from .models import Specialite
|
from .models import Specialite
|
||||||
if not Specialite.objects.filter(nom='TRANSVERSE').exists():
|
if not Specialite.objects.filter(nom='GROUPE').exists():
|
||||||
Specialite.objects.create(nom='TRANSVERSE', codeCouleur='#FF0000')
|
Specialite.objects.create(nom='GROUPE', codeCouleur='#FF0000')
|
||||||
|
|
||||||
class GestionenseignantsConfig(AppConfig):
|
class GestionenseignantsConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
|||||||
@ -3,6 +3,7 @@ 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.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
|
||||||
class Specialite(models.Model):
|
class Specialite(models.Model):
|
||||||
nom = models.CharField(max_length=100)
|
nom = models.CharField(max_length=100)
|
||||||
@ -18,31 +19,36 @@ class Enseignant(models.Model):
|
|||||||
mail = models.EmailField(unique=True)
|
mail = models.EmailField(unique=True)
|
||||||
specialites = models.ManyToManyField(Specialite, related_name='enseignants')
|
specialites = models.ManyToManyField(Specialite, related_name='enseignants')
|
||||||
profilAssocie = models.ForeignKey(Profil, on_delete=models.CASCADE, null=True, blank=True)
|
profilAssocie = models.ForeignKey(Profil, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
dateCreation = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.nom} {self.prenom}"
|
return f"{self.nom} {self.prenom}"
|
||||||
|
|
||||||
class Classe(models.Model):
|
class Classe(models.Model):
|
||||||
nom_ambiance = models.CharField(max_length=255)
|
nom_ambiance = models.CharField(max_length=255, null=True, blank=True)
|
||||||
tranche_age = models.JSONField()
|
tranche_age = models.JSONField()
|
||||||
nombre_eleves = models.PositiveIntegerField()
|
nombre_eleves = models.PositiveIntegerField()
|
||||||
langue_enseignement = models.CharField(max_length=255)
|
langue_enseignement = models.CharField(max_length=255)
|
||||||
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)
|
||||||
|
|
||||||
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 = [
|
||||||
|
(1, 'Annuel'),
|
||||||
|
(2, 'Semestriel'),
|
||||||
|
(3, 'Trimestriel')
|
||||||
|
]
|
||||||
|
|
||||||
classe = models.OneToOneField(Classe, on_delete=models.SET_NULL, null=True, blank=True, related_name='planning')
|
classe = models.OneToOneField(Classe, on_delete=models.SET_NULL, null=True, blank=True, related_name='planning')
|
||||||
emploiDuTemps = JSONField(default=list)
|
emploiDuTemps = JSONField(default=dict)
|
||||||
|
type = models.IntegerField(choices=PLANNING_TYPE_CHOICES, default=1)
|
||||||
|
plageHoraire = models.JSONField(default=list)
|
||||||
|
joursOuverture = ArrayField(models.IntegerField(), default=list)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'Planning de {self.classe.nom_ambiance}'
|
return f'Planning de {self.classe.nom_ambiance}'
|
||||||
|
|
||||||
@receiver(post_save, sender=Classe)
|
|
||||||
def create_planning(sender, instance, created, **kwargs):
|
|
||||||
if created and not hasattr(instance, 'planning'):
|
|
||||||
Planning.objects.create(classe=instance)
|
|
||||||
|
|
||||||
|
|||||||
@ -36,10 +36,11 @@ class EnseignantSerializer(serializers.ModelSerializer):
|
|||||||
profilAssocie = ProfilSerializer(read_only=True)
|
profilAssocie = ProfilSerializer(read_only=True)
|
||||||
DroitLabel = serializers.SerializerMethodField()
|
DroitLabel = serializers.SerializerMethodField()
|
||||||
DroitValue = serializers.SerializerMethodField()
|
DroitValue = serializers.SerializerMethodField()
|
||||||
|
dateCreation_formattee = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Enseignant
|
model = Enseignant
|
||||||
fields = ['id', 'nom', 'prenom', 'mail', 'specialites', 'specialites_ids', 'classes_principal', 'profilAssocie', 'profilAssocie_id', 'DroitLabel', 'DroitValue']
|
fields = ['id', 'nom', 'prenom', 'mail', 'specialites', 'specialites_ids', 'classes_principal', 'profilAssocie', 'profilAssocie_id', 'DroitLabel', 'DroitValue', 'dateCreation', 'dateCreation_formattee']
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
specialites_data = validated_data.pop('specialites', None)
|
specialites_data = validated_data.pop('specialites', None)
|
||||||
@ -67,61 +68,144 @@ class EnseignantSerializer(serializers.ModelSerializer):
|
|||||||
def get_DroitValue(self, obj):
|
def get_DroitValue(self, obj):
|
||||||
return obj.profilAssocie.droit if obj.profilAssocie else None
|
return obj.profilAssocie.droit if obj.profilAssocie else None
|
||||||
|
|
||||||
|
def get_dateCreation_formattee(self, obj):
|
||||||
|
utc_time = timezone.localtime(obj.dateCreation) # Convertir en heure locale
|
||||||
|
local_tz = pytz.timezone(settings.TZ_APPLI)
|
||||||
|
local_time = utc_time.astimezone(local_tz)
|
||||||
|
return local_time.strftime("%d-%m-%Y %H:%M")
|
||||||
|
|
||||||
class PlanningSerializer(serializers.ModelSerializer):
|
class PlanningSerializer(serializers.ModelSerializer):
|
||||||
classe_id = serializers.PrimaryKeyRelatedField(queryset=Classe.objects.all(), source='classe')
|
emploiDuTemps = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Planning
|
model = Planning
|
||||||
fields = ['id', 'classe', 'classe_id', 'emploiDuTemps']
|
fields = ['id', 'emploiDuTemps', 'type', 'plageHoraire', 'joursOuverture']
|
||||||
|
|
||||||
|
def get_emploiDuTemps(self, obj):
|
||||||
|
emploi_du_temps = obj.emploiDuTemps
|
||||||
|
if obj.classe:
|
||||||
|
enseignants = obj.classe.enseignants.all() # Récupérer tous les enseignants associés à la classe
|
||||||
|
|
||||||
|
# Dictionnaire pour accéder rapidement aux spécialités des enseignants
|
||||||
|
specialite_enseignants = {}
|
||||||
|
for enseignant in enseignants:
|
||||||
|
for specialite in enseignant.specialites.all():
|
||||||
|
if specialite.nom not in specialite_enseignants:
|
||||||
|
specialite_enseignants[specialite.nom] = []
|
||||||
|
specialite_enseignants[specialite.nom].append(f"{enseignant.prenom} {enseignant.nom}")
|
||||||
|
|
||||||
|
if obj.type == 1: # Planning annuel
|
||||||
|
for day, events in emploi_du_temps.items():
|
||||||
|
for event in events:
|
||||||
|
# Ajouter les enseignants associés à la spécialité
|
||||||
|
event['teachers'] = specialite_enseignants.get(event['matiere'], [])
|
||||||
|
# Ajouter la couleur de la spécialité
|
||||||
|
event['color'] = next(
|
||||||
|
(specialite.codeCouleur for enseignant in enseignants for specialite in enseignant.specialites.all() if specialite.nom == event['matiere']),
|
||||||
|
"#FFFFFF" # Couleur par défaut si non trouvée
|
||||||
|
)
|
||||||
|
|
||||||
|
elif obj.type in [2, 3]: # Planning semestriel ou trimestriel
|
||||||
|
for period_key, period_value in emploi_du_temps.items():
|
||||||
|
for day, events in period_value.items():
|
||||||
|
if day in ['DateDebut', 'DateFin']:
|
||||||
|
continue # Ignorer les clés DateDebut et DateFin
|
||||||
|
for event in events:
|
||||||
|
print(f'event : {event}')
|
||||||
|
# Ajouter les enseignants associés à la spécialité
|
||||||
|
event['teachers'] = specialite_enseignants.get(event['matiere'], [])
|
||||||
|
# Ajouter la couleur de la spécialité
|
||||||
|
event['color'] = next(
|
||||||
|
(specialite.codeCouleur for enseignant in enseignants for specialite in enseignant.specialites.all() if specialite.nom == event['matiere']),
|
||||||
|
"#FFFFFF" # Couleur par défaut si non trouvée
|
||||||
|
)
|
||||||
|
|
||||||
|
return emploi_du_temps
|
||||||
|
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
internal_value = super().to_internal_value(data)
|
||||||
|
internal_value['emploiDuTemps'] = data.get('emploiDuTemps', {})
|
||||||
|
return internal_value
|
||||||
|
|
||||||
class ClasseSerializer(serializers.ModelSerializer):
|
class ClasseSerializer(serializers.ModelSerializer):
|
||||||
dateCreation_formattee = serializers.SerializerMethodField()
|
dateCreation_formattee = serializers.SerializerMethodField()
|
||||||
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 = serializers.SerializerMethodField()
|
planning = PlanningSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Classe
|
model = Classe
|
||||||
fields = ['id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement', 'enseignants', 'enseignants_ids', 'annee_scolaire', 'dateCreation', 'dateCreation_formattee', 'eleves', 'planning']
|
fields = [
|
||||||
|
'id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement',
|
||||||
|
'enseignants', 'enseignants_ids', 'annee_scolaire', 'dateCreation',
|
||||||
|
'dateCreation_formattee', 'eleves', 'planning', 'niveaux'
|
||||||
|
]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
planning_data = validated_data.pop('planning', None)
|
planning_data = validated_data.pop('planning', None)
|
||||||
enseignants_data = validated_data.pop('enseignants', [])
|
enseignants_data = validated_data.pop('enseignants', [])
|
||||||
classe = Classe.objects.create(**validated_data)
|
niveaux_data = validated_data.pop('niveaux', [])
|
||||||
|
|
||||||
|
classe = Classe.objects.create(
|
||||||
|
nom_ambiance=validated_data.get('nom_ambiance', ''),
|
||||||
|
tranche_age=validated_data.get('tranche_age', []),
|
||||||
|
nombre_eleves=validated_data.get('nombre_eleves', 0),
|
||||||
|
langue_enseignement=validated_data.get('langue_enseignement', ''),
|
||||||
|
annee_scolaire=validated_data.get('annee_scolaire', ''),
|
||||||
|
niveaux=niveaux_data
|
||||||
|
)
|
||||||
|
|
||||||
classe.enseignants.set(enseignants_data)
|
classe.enseignants.set(enseignants_data)
|
||||||
|
|
||||||
if planning_data and not hasattr(classe, 'planning'):
|
if planning_data and not hasattr(classe, 'planning'):
|
||||||
Planning.objects.create(classe=classe, **planning_data)
|
Planning.objects.create(
|
||||||
|
classe=classe,
|
||||||
|
emploiDuTemps=planning_data.get('emploiDuTemps', {}),
|
||||||
|
type=planning_data.get('type', 1),
|
||||||
|
plageHoraire=planning_data.get('plageHoraire', []),
|
||||||
|
joursOuverture=planning_data.get('joursOuverture', [])
|
||||||
|
)
|
||||||
|
|
||||||
return classe
|
return classe
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
planning_data = validated_data.pop('planning', None)
|
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', [])
|
||||||
|
|
||||||
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)
|
||||||
instance.nombre_eleves = validated_data.get('nombre_eleves', instance.nombre_eleves)
|
instance.nombre_eleves = validated_data.get('nombre_eleves', instance.nombre_eleves)
|
||||||
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.save()
|
instance.save()
|
||||||
instance.enseignants.set(enseignants_data)
|
instance.enseignants.set(enseignants_data)
|
||||||
|
|
||||||
if planning_data:
|
if planning_data:
|
||||||
planning = instance.planning
|
planning = instance.planning
|
||||||
planning.emploiDuTemps = planning_data.get('emploiDuTemps', planning.emploiDuTemps)
|
planning.emploiDuTemps = planning_data.get('emploiDuTemps', planning.emploiDuTemps)
|
||||||
|
planning.type = planning_data.get('type', planning.type)
|
||||||
|
planning.plageHoraire = planning_data.get('plageHoraire', planning.plageHoraire)
|
||||||
|
planning.joursOuverture = planning_data.get('joursOuverture', planning.joursOuverture)
|
||||||
|
|
||||||
planning.save()
|
planning.save()
|
||||||
|
|
||||||
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) # Convertir en heure locale
|
||||||
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")
|
||||||
|
|
||||||
def get_eleves(self, obj):
|
def get_eleves(self, obj):
|
||||||
elevesList = obj.eleves.all()
|
elevesList = obj.eleves.all()
|
||||||
filtered_eleves = []
|
filtered_eleves = []
|
||||||
for eleve in elevesList:
|
for eleve in elevesList:
|
||||||
ficheInscription=bdd.getObject(FicheInscription, "eleve__id", eleve.id)
|
ficheInscription = bdd.getObject(FicheInscription, "eleve__id", eleve.id)
|
||||||
if ficheInscription.etat == ficheInscription.EtatDossierInscription.DI_VALIDE:
|
if ficheInscription.etat == ficheInscription.EtatDossierInscription.DI_VALIDE:
|
||||||
filtered_eleves.append(eleve)
|
filtered_eleves.append(eleve)
|
||||||
return EleveSerializer(filtered_eleves, many=True, read_only=True).data
|
return EleveSerializer(filtered_eleves, many=True, read_only=True).data
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
|
|
||||||
from GestionEnseignants.views import EnseignantsView, EnseignantView, SpecialitesView, SpecialiteView, ClassesView, ClasseView
|
from GestionEnseignants.views import EnseignantsView, EnseignantView, SpecialitesView, SpecialiteView, ClassesView, ClasseView, PlanningsView, PlanningView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r'^enseignants$', EnseignantsView.as_view(), name="enseignants"),
|
re_path(r'^enseignants$', EnseignantsView.as_view(), name="enseignants"),
|
||||||
@ -14,4 +14,8 @@ urlpatterns = [
|
|||||||
re_path(r'^classes$', ClassesView.as_view(), name="classes"),
|
re_path(r'^classes$', ClassesView.as_view(), name="classes"),
|
||||||
re_path(r'^classe$', ClasseView.as_view(), name="classe"),
|
re_path(r'^classe$', ClasseView.as_view(), name="classe"),
|
||||||
re_path(r'^classe/([0-9]+)$', ClasseView.as_view(), name="classe"),
|
re_path(r'^classe/([0-9]+)$', ClasseView.as_view(), name="classe"),
|
||||||
|
|
||||||
|
re_path(r'^plannings$', PlanningsView.as_view(), name="plannings"),
|
||||||
|
re_path(r'^planning$', PlanningView.as_view(), name="planning"),
|
||||||
|
re_path(r'^planning/([0-9]+)$', PlanningView.as_view(), name="planning"),
|
||||||
]
|
]
|
||||||
@ -4,8 +4,8 @@ from django.utils.decorators import method_decorator
|
|||||||
from rest_framework.parsers import JSONParser
|
from rest_framework.parsers import JSONParser
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from .models import Enseignant, Specialite, Classe
|
from .models import Enseignant, Specialite, Classe, Planning
|
||||||
from .serializers import EnseignantSerializer, SpecialiteSerializer, ClasseSerializer
|
from .serializers import EnseignantSerializer, SpecialiteSerializer, ClasseSerializer, PlanningSerializer
|
||||||
from N3wtSchool import bdd
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
class EnseignantsView(APIView):
|
class EnseignantsView(APIView):
|
||||||
@ -189,3 +189,41 @@ class ClasseView(APIView):
|
|||||||
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(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class PlanningsView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
schedulesList=bdd.getAllObjects(Planning)
|
||||||
|
schedules_serializer=PlanningSerializer(schedulesList, many=True)
|
||||||
|
return JsonResponse(schedules_serializer.data, safe=False)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class PlanningView(APIView):
|
||||||
|
def get (self, request, _id):
|
||||||
|
planning = bdd.getObject(_objectName=Planning, _columnName='classe__id', _value=_id)
|
||||||
|
planning_serializer=PlanningSerializer(planning)
|
||||||
|
|
||||||
|
return JsonResponse(planning_serializer.data, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
planning_data=JSONParser().parse(request)
|
||||||
|
planning_serializer = PlanningSerializer(data=planning_data)
|
||||||
|
|
||||||
|
if planning_serializer.is_valid():
|
||||||
|
planning_serializer.save()
|
||||||
|
return JsonResponse(planning_serializer.data, safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(planning_serializer.errors, safe=False)
|
||||||
|
|
||||||
|
def put(self, request, _id):
|
||||||
|
planning_data=JSONParser().parse(request)
|
||||||
|
planning = bdd.getObject(_objectName=Planning, _columnName='classe__id', _value=_id)
|
||||||
|
planning_serializer = PlanningSerializer(planning, data=planning_data)
|
||||||
|
|
||||||
|
if planning_serializer.is_valid():
|
||||||
|
planning_serializer.save()
|
||||||
|
return JsonResponse(planning_serializer.data, safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(planning_serializer.errors, safe=False)
|
||||||
|
|||||||
118
Front-End/package-lock.json
generated
118
Front-End/package-lock.json
generated
@ -18,6 +18,8 @@
|
|||||||
"next-intl": "^3.24.0",
|
"next-intl": "^3.24.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-cookie": "^7.2.0",
|
"react-cookie": "^7.2.0",
|
||||||
|
"react-dnd": "^16.0.1",
|
||||||
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-phone-number-input": "^3.4.8",
|
"react-phone-number-input": "^3.4.8",
|
||||||
"react-tooltip": "^5.28.0"
|
"react-tooltip": "^5.28.0"
|
||||||
@ -106,6 +108,18 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.26.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
|
||||||
|
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.25.9",
|
"version": "7.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
||||||
@ -897,6 +911,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-dnd/asap": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@react-dnd/invariant": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@react-dnd/shallowequal": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -1720,9 +1752,10 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
"version": "0.6.0",
|
"version": "0.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@ -1733,7 +1766,9 @@
|
|||||||
"integrity": "sha512-4JwHNqaKZ19doQoNcBjsoYA+I7NqCH/mC/6f5cBWvdKzcK5TMmzLpq3Z/syVHMHJuDGFwJ+rPpGizvrqJybJow=="
|
"integrity": "sha512-4JwHNqaKZ19doQoNcBjsoYA+I7NqCH/mC/6f5cBWvdKzcK5TMmzLpq3Z/syVHMHJuDGFwJ+rPpGizvrqJybJow=="
|
||||||
},
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"path-key": "^3.1.0",
|
"path-key": "^3.1.0",
|
||||||
@ -1934,6 +1969,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/dnd-core": {
|
||||||
|
"version": "16.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
|
||||||
|
"integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-dnd/asap": "^5.0.1",
|
||||||
|
"@react-dnd/invariant": "^4.0.1",
|
||||||
|
"redux": "^4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/doctrine": {
|
"node_modules/doctrine": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -2562,7 +2608,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
@ -4396,6 +4441,45 @@
|
|||||||
"react": ">= 16.3.0"
|
"react": ">= 16.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-dnd": {
|
||||||
|
"version": "16.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
|
||||||
|
"integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-dnd/invariant": "^4.0.1",
|
||||||
|
"@react-dnd/shallowequal": "^4.0.1",
|
||||||
|
"dnd-core": "^16.0.1",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"hoist-non-react-statics": "^3.3.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/hoist-non-react-statics": ">= 3.3.1",
|
||||||
|
"@types/node": ">= 12",
|
||||||
|
"@types/react": ">= 16",
|
||||||
|
"react": ">= 16.14"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/hoist-non-react-statics": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dnd-html5-backend": {
|
||||||
|
"version": "16.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
|
||||||
|
"integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dnd-core": "^16.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -4529,6 +4613,15 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -4549,6 +4642,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.2",
|
"version": "1.5.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -5306,12 +5405,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/universal-cookie": {
|
"node_modules/universal-cookie": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz",
|
||||||
"integrity": "sha512-PvcyflJAYACJKr28HABxkGemML5vafHmiL4ICe3e+BEKXRMt0GaFLZhAwgv637kFFnnfiSJ8e6jknrKkMrU+PQ==",
|
"integrity": "sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/cookie": "^0.6.0",
|
"@types/cookie": "^0.6.0",
|
||||||
"cookie": "^0.6.0"
|
"cookie": "^0.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
|
|||||||
@ -20,6 +20,8 @@
|
|||||||
"next-intl": "^3.24.0",
|
"next-intl": "^3.24.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-cookie": "^7.2.0",
|
"react-cookie": "^7.2.0",
|
||||||
|
"react-dnd": "^16.0.1",
|
||||||
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-phone-number-input": "^3.4.8",
|
"react-phone-number-input": "^3.4.8",
|
||||||
"react-tooltip": "^5.28.0"
|
"react-tooltip": "^5.28.0"
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { Users, Clock, CalendarCheck, School, TrendingUp, UserCheck } from 'lucide-react';
|
import { Users, Clock, CalendarCheck, School, TrendingUp, UserCheck } from 'lucide-react';
|
||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
import { BK_GESTIONINSCRIPTION_CLASSES_URL } from '@/utils/Url';
|
import { BK_GESTIONENSEIGNANTS_CLASSES_URL } from '@/utils/Url';
|
||||||
import ClasseDetails from '@/components/ClasseDetails';
|
import ClasseDetails from '@/components/ClasseDetails';
|
||||||
|
|
||||||
// Composant StatCard pour afficher une statistique
|
// Composant StatCard pour afficher une statistique
|
||||||
@ -59,7 +59,7 @@ export default function DashboardPage() {
|
|||||||
const [classes, setClasses] = useState([]);
|
const [classes, setClasses] = useState([]);
|
||||||
|
|
||||||
const fetchClasses = () => {
|
const fetchClasses = () => {
|
||||||
fetch(`${BK_GESTIONINSCRIPTION_CLASSES_URL}`)
|
fetch(`${BK_GESTIONENSEIGNANTS_CLASSES_URL}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setClasses(data);
|
setClasses(data);
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import SpecialitiesSection from '@/components/SpecialitiesSection'
|
import { School, Calendar } from 'lucide-react';
|
||||||
import ClassesSection from '@/components/ClassesSection'
|
import TabsStructure from '@/components/Structure/Configuration/TabsStructure';
|
||||||
import TeachersSection from '@/components/TeachersSection';
|
import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement'
|
||||||
import { BK_GESTIONINSCRIPTION_SPECIALITES_URL,
|
import StructureManagement from '@/components/Structure/Configuration/StructureManagement'
|
||||||
BK_GESTIONINSCRIPTION_CLASSES_URL,
|
import { BK_GESTIONENSEIGNANTS_SPECIALITES_URL,
|
||||||
BK_GESTIONINSCRIPTION_SPECIALITE_URL,
|
BK_GESTIONENSEIGNANTS_CLASSES_URL,
|
||||||
BK_GESTIONINSCRIPTION_CLASSE_URL,
|
BK_GESTIONENSEIGNANTS_TEACHERS_URL,
|
||||||
BK_GESTIONINSCRIPTION_TEACHERS_URL,
|
BK_GESTIONENSEIGNANTS_PLANNINGS_URL } from '@/utils/Url';
|
||||||
BK_GESTIONINSCRIPTION_TEACHER_URL } from '@/utils/Url';
|
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
||||||
import useCsrfToken from '@/hooks/useCsrfToken';
|
import useCsrfToken from '@/hooks/useCsrfToken';
|
||||||
|
|
||||||
@ -16,6 +15,12 @@ export default function Page() {
|
|||||||
const [specialities, setSpecialities] = useState([]);
|
const [specialities, setSpecialities] = useState([]);
|
||||||
const [classes, setClasses] = useState([]);
|
const [classes, setClasses] = useState([]);
|
||||||
const [teachers, setTeachers] = useState([]);
|
const [teachers, setTeachers] = useState([]);
|
||||||
|
const [schedules, setSchedules] = useState([]);
|
||||||
|
const [activeTab, setActiveTab] = useState('Configuration');
|
||||||
|
const tabs = [
|
||||||
|
{ id: 'Configuration', title: "Configuration de l'école", icon: School },
|
||||||
|
{ id: 'Schedule', title: "Gestion de l'emploi du temps", icon: Calendar },
|
||||||
|
];
|
||||||
|
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
|
|
||||||
@ -28,10 +33,13 @@ export default function Page() {
|
|||||||
|
|
||||||
// Fetch data for classes
|
// Fetch data for classes
|
||||||
fetchClasses();
|
fetchClasses();
|
||||||
|
|
||||||
|
// Fetch data for schedules
|
||||||
|
fetchSchedules();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchSpecialities = () => {
|
const fetchSpecialities = () => {
|
||||||
fetch(`${BK_GESTIONINSCRIPTION_SPECIALITES_URL}`)
|
fetch(`${BK_GESTIONENSEIGNANTS_SPECIALITES_URL}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setSpecialities(data);
|
setSpecialities(data);
|
||||||
@ -42,7 +50,7 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchTeachers = () => {
|
const fetchTeachers = () => {
|
||||||
fetch(`${BK_GESTIONINSCRIPTION_TEACHERS_URL}`)
|
fetch(`${BK_GESTIONENSEIGNANTS_TEACHERS_URL}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setTeachers(data);
|
setTeachers(data);
|
||||||
@ -53,7 +61,7 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchClasses = () => {
|
const fetchClasses = () => {
|
||||||
fetch(`${BK_GESTIONINSCRIPTION_CLASSES_URL}`)
|
fetch(`${BK_GESTIONENSEIGNANTS_CLASSES_URL}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setClasses(data);
|
setClasses(data);
|
||||||
@ -63,6 +71,17 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchSchedules = () => {
|
||||||
|
fetch(`${BK_GESTIONENSEIGNANTS_PLANNINGS_URL}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
setSchedules(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching classes:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleCreate = (url, newData, setDatas) => {
|
const handleCreate = (url, newData, setDatas) => {
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -102,6 +121,26 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUpdatePlanning = (url, id, updatedData) => {
|
||||||
|
fetch(`${url}/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify(updatedData),
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Planning mis à jour avec succès :', data);
|
||||||
|
//setDatas(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Erreur :', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = (url, id, setDatas) => {
|
const handleDelete = (url, id, setDatas) => {
|
||||||
fetch(`${url}/${id}`, {
|
fetch(`${url}/${id}`, {
|
||||||
method:'DELETE',
|
method:'DELETE',
|
||||||
@ -127,30 +166,33 @@ export default function Page() {
|
|||||||
<div className='p-8'>
|
<div className='p-8'>
|
||||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||||
|
|
||||||
<SpecialitiesSection
|
<TabsStructure activeTab={activeTab} setActiveTab={setActiveTab} tabs={tabs} />
|
||||||
|
|
||||||
|
{activeTab === 'Configuration' && (
|
||||||
|
<>
|
||||||
|
<StructureManagement
|
||||||
specialities={specialities}
|
specialities={specialities}
|
||||||
setSpecialities={setSpecialities}
|
setSpecialities={setSpecialities}
|
||||||
handleCreate={(newData) => handleCreate(`${BK_GESTIONINSCRIPTION_SPECIALITE_URL}`, newData, setSpecialities)}
|
|
||||||
handleEdit={(id, updatedData) => handleEdit(`${BK_GESTIONINSCRIPTION_SPECIALITE_URL}`, id, updatedData, setSpecialities)}
|
|
||||||
handleDelete={(id) => handleDelete(`${BK_GESTIONINSCRIPTION_SPECIALITE_URL}`, id, setSpecialities)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TeachersSection
|
|
||||||
teachers={teachers}
|
teachers={teachers}
|
||||||
specialities={specialities}
|
setTeachers={setTeachers}
|
||||||
handleCreate={(newData) => handleCreate(`${BK_GESTIONINSCRIPTION_TEACHER_URL}`, newData, setTeachers)}
|
|
||||||
handleEdit={(id, updatedData) => handleEdit(`${BK_GESTIONINSCRIPTION_TEACHER_URL}`, id, updatedData, setTeachers)}
|
|
||||||
handleDelete={(id) => handleDelete(`${BK_GESTIONINSCRIPTION_TEACHER_URL}`, id, setTeachers)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ClassesSection
|
|
||||||
classes={classes}
|
classes={classes}
|
||||||
|
setClasses={setClasses}
|
||||||
|
handleCreate={handleCreate}
|
||||||
|
handleEdit={handleEdit}
|
||||||
|
handleDelete={handleDelete} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'Schedule' && (
|
||||||
|
<ScheduleManagement
|
||||||
|
schedules={schedules}
|
||||||
|
setSchedules={setSchedules}
|
||||||
|
handleUpdatePlanning={handleUpdatePlanning}
|
||||||
specialities={specialities}
|
specialities={specialities}
|
||||||
teachers={teachers}
|
teachers={teachers}
|
||||||
handleCreate={(newData) => handleCreate(`${BK_GESTIONINSCRIPTION_CLASSE_URL}`, newData, setClasses)}
|
classes={classes}
|
||||||
handleEdit={(id, updatedData) => handleEdit(`${BK_GESTIONINSCRIPTION_CLASSE_URL}`, id, updatedData, setClasses)}
|
|
||||||
handleDelete={(id) => handleDelete(`${BK_GESTIONINSCRIPTION_CLASSE_URL}`, id, setClasses)}
|
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import { BK_GESTIONINSCRIPTION_FICHESINSCRIPTION_URL,
|
|||||||
BK_GESTIONINSCRIPTION_SEND_URL,
|
BK_GESTIONINSCRIPTION_SEND_URL,
|
||||||
FR_ADMIN_SUBSCRIPTIONS_EDIT_URL,
|
FR_ADMIN_SUBSCRIPTIONS_EDIT_URL,
|
||||||
BK_GESTIONINSCRIPTION_ARCHIVE_URL,
|
BK_GESTIONINSCRIPTION_ARCHIVE_URL,
|
||||||
BK_GESTIONINSCRIPTION_CLASSES_URL,
|
BK_GESTIONENSEIGNANTS_CLASSES_URL,
|
||||||
BK_GESTIONINSCRIPTION_FICHEINSCRIPTION_URL,
|
BK_GESTIONINSCRIPTION_FICHEINSCRIPTION_URL,
|
||||||
BK_GESTIONINSCRIPTION_ELEVES_URL,
|
BK_GESTIONINSCRIPTION_ELEVES_URL,
|
||||||
BK_PROFILE_URL } from '@/utils/Url';
|
BK_PROFILE_URL } from '@/utils/Url';
|
||||||
@ -147,7 +147,7 @@ export default function Page({ params: { locale } }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchClasses = () => {
|
const fetchClasses = () => {
|
||||||
fetch(`${BK_GESTIONINSCRIPTION_CLASSES_URL}`)
|
fetch(`${BK_GESTIONENSEIGNANTS_CLASSES_URL}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setClasses(data);
|
setClasses(data);
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
|
||||||
import {NextIntlClientProvider} from 'next-intl';
|
|
||||||
import {getMessages} from 'next-intl/server';
|
import {getMessages} from 'next-intl/server';
|
||||||
import "@/css/tailwind.css";
|
import "@/css/tailwind.css";
|
||||||
|
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "N3WT-SCHOOL",
|
title: "N3WT-SCHOOL",
|
||||||
description: "Gestion de l'école",
|
description: "Gestion de l'école",
|
||||||
@ -21,10 +21,7 @@ export const metadata = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function RootLayout({ children, params: {locale}}) {
|
export default async function RootLayout({ children, params: { locale } }) {
|
||||||
|
|
||||||
// Providing all messages to the client
|
|
||||||
// side is the easiest way to get started
|
|
||||||
const messages = await getMessages();
|
const messages = await getMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -37,5 +34,3 @@ export default async function RootLayout({ children, params: {locale}}) {
|
|||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,17 @@
|
|||||||
const CheckBoxList = ({ items, formData, handleChange, fieldName, label, icon: Icon, className, itemLabelFunc = (item) => item.name }) => {
|
import React from 'react';
|
||||||
|
|
||||||
|
const CheckBoxList = ({
|
||||||
|
items,
|
||||||
|
formData,
|
||||||
|
handleChange,
|
||||||
|
fieldName,
|
||||||
|
label,
|
||||||
|
icon: Icon,
|
||||||
|
className,
|
||||||
|
itemLabelFunc = (item) => item.name,
|
||||||
|
labelAttenuated = () => false,
|
||||||
|
horizontal = false // Ajouter l'option horizontal
|
||||||
|
}) => {
|
||||||
const handleCheckboxChange = (e) => {
|
const handleCheckboxChange = (e) => {
|
||||||
handleChange(e);
|
handleChange(e);
|
||||||
};
|
};
|
||||||
@ -9,35 +22,49 @@ const CheckBoxList = ({ items, formData, handleChange, fieldName, label, icon: I
|
|||||||
{Icon && <Icon className="w-5 h-5 mr-2" />}
|
{Icon && <Icon className="w-5 h-5 mr-2" />}
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-2 grid grid-cols-1 gap-4">
|
<div className={`mt-2 grid ${horizontal ? 'grid-cols-6 gap-2' : 'grid-cols-1 gap-4'}`}>
|
||||||
{items.map(item => (
|
{items.map(item => {
|
||||||
<div key={item.id} className="flex items-center">
|
const isChecked = formData[fieldName].includes(parseInt(item.id));
|
||||||
|
const isAttenuated = labelAttenuated(item) && !isChecked;
|
||||||
|
return (
|
||||||
|
<div key={item.id} className={`flex ${horizontal ? 'flex-col items-center' : 'flex-row items-center'}`}>
|
||||||
|
{horizontal && (
|
||||||
|
<label
|
||||||
|
htmlFor={`${fieldName}-${item.id}`}
|
||||||
|
className={`block text-sm text-center mb-1 ${
|
||||||
|
isAttenuated ? 'text-gray-200' : 'font-bold text-emerald-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{itemLabelFunc(item)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
<input
|
<input
|
||||||
|
key={`${item.id}-${Math.random()}`}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={`${fieldName}-${item.id}`}
|
id={`${fieldName}-${item.id}`}
|
||||||
name={fieldName}
|
name={fieldName}
|
||||||
value={item.id}
|
value={item.id}
|
||||||
checked={formData[fieldName].includes(item.id)}
|
checked={isChecked}
|
||||||
onChange={handleCheckboxChange}
|
onChange={handleCheckboxChange}
|
||||||
className="form-checkbox h-4 w-4 rounded-md text-emerald-600 hover:ring-emerald-400 checked:bg-emerald-600 hover:border-emerald-500 hover:bg-emerald-500 cursor-pointer"
|
className={`form-checkbox h-4 w-4 rounded-mg text-emerald-600 hover:ring-emerald-400 checked:bg-emerald-600 hover:border-emerald-500 hover:bg-emerald-500 cursor-pointer ${horizontal ? 'mt-1' : 'mr-2'}`}
|
||||||
style={{ borderRadius: '6px', outline: 'none', boxShadow: 'none' }} // Remove focus styles
|
style={{ borderRadius: '6px', outline: 'none', boxShadow: 'none' }}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={`${fieldName}-${item.id}`} className="ml-2 block text-sm text-gray-900 flex items-center">
|
{!horizontal && (
|
||||||
|
<label
|
||||||
|
htmlFor={`${fieldName}-${item.id}`}
|
||||||
|
className={`block text-sm ${
|
||||||
|
isAttenuated ? 'text-gray-200' : 'font-bold text-emerald-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{itemLabelFunc(item)}
|
{itemLabelFunc(item)}
|
||||||
{item.codeCouleur && (
|
|
||||||
<div
|
|
||||||
className="w-4 h-4 rounded-full ml-2 hover:bg-opacity-70 transition duration-200 ease-in-out"
|
|
||||||
style={{ backgroundColor: item.codeCouleur }}
|
|
||||||
title={item.codeCouleur}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
</label>
|
</label>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CheckBoxList;
|
|
||||||
|
|
||||||
|
export default CheckBoxList;
|
||||||
|
|||||||
@ -1,180 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import Slider from '@/components/Slider'
|
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
|
||||||
import Button from '@/components/Button';
|
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
|
||||||
import CheckBoxList from '@/components/CheckBoxList';
|
|
||||||
import { Users, Maximize2, Globe, Calendar, GraduationCap } from 'lucide-react';
|
|
||||||
|
|
||||||
const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => {
|
|
||||||
const langues = [
|
|
||||||
{ value:'Français', label: 'Français'},
|
|
||||||
{ value:'Anglais', label: 'Anglais'},
|
|
||||||
{ value:'Espagnol', label: 'Espagnol'},
|
|
||||||
];
|
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
nom_ambiance: classe.nom_ambiance || '',
|
|
||||||
tranche_age: classe.tranche_age || [3, 6],
|
|
||||||
nombre_eleves: classe.nombre_eleves || '',
|
|
||||||
langue_enseignement: classe.langue_enseignement || 'Français',
|
|
||||||
annee_scolaire: classe.annee_scolaire || '',
|
|
||||||
enseignants_ids: classe.enseignants_ids || [],
|
|
||||||
planning: classe.planning || []
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
const target = e.target || e.currentTarget;
|
|
||||||
const { name, value, type, checked } = target;
|
|
||||||
|
|
||||||
console.log('type : ', type);
|
|
||||||
|
|
||||||
if (type === 'checkbox') {
|
|
||||||
setFormData((prevState) => {
|
|
||||||
const newValues = checked
|
|
||||||
? [...(prevState[name] || []), parseInt(value, 10)]
|
|
||||||
: (prevState[name] || []).filter((v) => v !== parseInt(value, 10));
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
[name]: newValues,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setFormData((prevState) => ({
|
|
||||||
...prevState,
|
|
||||||
[name]: type === 'radio' ? parseInt(value, 10) : value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSliderChange = (value) => {
|
|
||||||
setFormData(prevFormData => ({
|
|
||||||
...prevFormData,
|
|
||||||
tranche_age: value
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNumberChange = (e) => {
|
|
||||||
const { name, value } = e.target;
|
|
||||||
setFormData(prevFormData => ({
|
|
||||||
...prevFormData,
|
|
||||||
[name]: Number(value)
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
const updatedFormData = {
|
|
||||||
...formData,
|
|
||||||
planning: formData.planning || []
|
|
||||||
};
|
|
||||||
onSubmit(updatedFormData, isNew);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTeacherLabel = (teacher) => {
|
|
||||||
return (
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<span>{teacher.nom} {teacher.prenom} -</span>
|
|
||||||
{teacher.specialites.map(specialite => (
|
|
||||||
<span
|
|
||||||
key={specialite.id}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', marginLeft: '8px' }}
|
|
||||||
>
|
|
||||||
<span>{specialite.nom}</span>
|
|
||||||
<span
|
|
||||||
className="w-4 h-4 rounded-full ml-1"
|
|
||||||
style={{ backgroundColor: specialite.codeCouleur }}
|
|
||||||
title={specialite.nom}
|
|
||||||
></span>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4 mt-8">
|
|
||||||
<div>
|
|
||||||
<InputTextIcon
|
|
||||||
name="nom_ambiance"
|
|
||||||
type="text"
|
|
||||||
IconItem={Users}
|
|
||||||
placeholder="Nom d'ambiance"
|
|
||||||
value={formData.nom_ambiance}
|
|
||||||
onChange={handleChange}
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
|
||||||
Tranche d'âge
|
|
||||||
</label>
|
|
||||||
<Slider
|
|
||||||
min={2}
|
|
||||||
max={18}
|
|
||||||
value={formData.tranche_age}
|
|
||||||
onChange={handleSliderChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<InputTextIcon
|
|
||||||
name="nombre_eleves"
|
|
||||||
type="number"
|
|
||||||
IconItem={Maximize2}
|
|
||||||
placeholder="Capacité max"
|
|
||||||
value={formData.nombre_eleves}
|
|
||||||
onChange={handleChange}
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<SelectChoice
|
|
||||||
name="langue_enseignement"
|
|
||||||
label="Langue d'enseignement"
|
|
||||||
selected={formData.langue_enseignement}
|
|
||||||
callback={handleChange}
|
|
||||||
choices={langues}
|
|
||||||
IconItem={Globe}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<InputTextIcon
|
|
||||||
name="annee_scolaire"
|
|
||||||
type="text"
|
|
||||||
IconItem={Calendar}
|
|
||||||
placeholder="Année scolaire (20xx-20xx)"
|
|
||||||
value={formData.annee_scolaire}
|
|
||||||
onChange={handleChange}
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex space-x-4">
|
|
||||||
<CheckBoxList
|
|
||||||
items={teachers}
|
|
||||||
formData={formData}
|
|
||||||
handleChange={handleChange}
|
|
||||||
fieldName="enseignants_ids"
|
|
||||||
label="Enseignants"
|
|
||||||
icon={GraduationCap}
|
|
||||||
className="w-full mt-4"
|
|
||||||
itemLabelFunc={getTeacherLabel}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-end mt-4 space-x-4">
|
|
||||||
<Button text="Créer"
|
|
||||||
onClick={handleSubmit}
|
|
||||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
|
||||||
(!formData.nom_ambiance || !formData.nombre_eleves || !formData.annee_scolaire || formData.enseignants_ids.length === 0)
|
|
||||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
|
||||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
|
||||||
}`}
|
|
||||||
primary
|
|
||||||
disabled={(!formData.nom_ambiance || !formData.nombre_eleves || !formData.annee_scolaire || formData.enseignants_ids.length === 0)}
|
|
||||||
type="submit"
|
|
||||||
name="Create" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ClassForm;
|
|
||||||
@ -5,6 +5,17 @@ import React from 'react';
|
|||||||
export default function EventModal({ isOpen, onClose, eventData, setEventData }) {
|
export default function EventModal({ isOpen, onClose, eventData, setEventData }) {
|
||||||
const { addEvent, updateEvent, deleteEvent, schedules } = usePlanning();
|
const { addEvent, updateEvent, deleteEvent, schedules } = usePlanning();
|
||||||
|
|
||||||
|
// S'assurer que scheduleId est défini lors du premier rendu
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!eventData.scheduleId && schedules.length > 0) {
|
||||||
|
setEventData(prev => ({
|
||||||
|
...prev,
|
||||||
|
scheduleId: schedules[0].id,
|
||||||
|
color: schedules[0].color
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [schedules, eventData.scheduleId]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
const recurrenceOptions = [
|
const recurrenceOptions = [
|
||||||
@ -25,17 +36,6 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
{ value: 0, label: 'Dim' }
|
{ value: 0, label: 'Dim' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// S'assurer que scheduleId est défini lors du premier rendu
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!eventData.scheduleId && schedules.length > 0) {
|
|
||||||
setEventData(prev => ({
|
|
||||||
...prev,
|
|
||||||
scheduleId: schedules[0].id,
|
|
||||||
color: schedules[0].color
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}, [schedules, eventData.scheduleId]);
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ const InputColorIcon = ({ name, label, value, onChange, errorMsg, className }) =
|
|||||||
name={name}
|
name={name}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="flex-1 block rounded-r-md sm:text-sm border-none focus:ring-0 outline-none h-full p-0 w-8"
|
className="flex-1 block rounded-r-md sm:text-sm border-none focus:ring-0 outline-none h-full p-0 w-8 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
||||||
|
|||||||
@ -1,33 +1,27 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const RadioList = ({ items, formData, handleChange, fieldName, label, icon: Icon, className, itemLabelFunc}) => {
|
const RadioList = ({ items, formData, handleChange, fieldName, icon: Icon, className }) => {
|
||||||
return (
|
return (
|
||||||
<div className={`mb-4 ${className}`}>
|
<div className={`mb-4 ${className}`}>
|
||||||
<label className="block text-sm font-medium text-gray-700 flex items-center">
|
<div className="grid grid-cols-1 gap-4">
|
||||||
{Icon && <Icon className="w-5 h-5 mr-2" />}
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
<div className="mt-2 grid grid-cols-1 gap-4">
|
|
||||||
{items.map(item => (
|
{items.map(item => (
|
||||||
<div key={item.id} className="flex items-center">
|
<div key={item.id} className="flex items-center">
|
||||||
<input
|
<input
|
||||||
|
key={`${item.id}-${Math.random()}`}
|
||||||
type="radio"
|
type="radio"
|
||||||
id={`${fieldName}-${item.id}`}
|
id={`${fieldName}-${item.id}`}
|
||||||
name={fieldName}
|
name={fieldName}
|
||||||
value={item.id}
|
value={item.id}
|
||||||
checked={formData[fieldName] === item.id}
|
checked={parseInt(formData[fieldName], 10) === item.id}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="form-radio h-4 w-4 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600"
|
className="form-radio h-4 w-4 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 cursor-pointer"
|
||||||
|
style={{ outline: 'none', boxShadow: 'none' }}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={`${fieldName}-${item.id}`} className="ml-2 block text-sm text-gray-900 flex items-center">
|
<label
|
||||||
{itemLabelFunc(item)}
|
htmlFor={`${fieldName}-${item.id}`}
|
||||||
{item.codeCouleur && (
|
className="ml-2 block text-sm text-gray-900 flex items-center cursor-pointer"
|
||||||
<div
|
>
|
||||||
className="w-4 h-4 rounded-full ml-2"
|
{item.label}
|
||||||
style={{ backgroundColor: item.codeCouleur }}
|
|
||||||
title={item.codeCouleur}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -3,12 +3,12 @@ export default function SelectChoice({type, name, label, choices, callback, sele
|
|||||||
<>
|
<>
|
||||||
<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`}>
|
<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`}>
|
||||||
<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"
|
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"
|
||||||
type={type}
|
type={type}
|
||||||
id={name}
|
id={name}
|
||||||
name={name}
|
name={name}
|
||||||
|
|||||||
252
Front-End/src/components/Structure/Configuration/ClassForm.js
Normal file
252
Front-End/src/components/Structure/Configuration/ClassForm.js
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
|
import CheckBoxList from '@/components/CheckBoxList';
|
||||||
|
import PlanningConfiguration from '@/components/Structure/Configuration/PlanningConfiguration';
|
||||||
|
import TeachersSelectionConfiguration from '@/components/Structure/Configuration/TeachersSelectionConfiguration';
|
||||||
|
import { Users, Maximize2, Calendar, UserPlus } from 'lucide-react';
|
||||||
|
import { useClasseForm } from '@/context/ClasseFormContext';
|
||||||
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
|
|
||||||
|
const ClassForm = ({ onSubmit, isNew, teachers }) => {
|
||||||
|
|
||||||
|
const { formData, setFormData } = useClasseForm();
|
||||||
|
const { schoolYears, getNiveauxLabels, generateAgeToNiveaux, niveauxPremierCycle, niveauxSecondCycle, niveauxTroisiemeCycle, typeEmploiDuTemps, updatePlanning } = useClasses();
|
||||||
|
const [selectedTeachers, setSelectedTeachers] = useState(formData.enseignants_ids);
|
||||||
|
|
||||||
|
const handleTeacherSelection = (teacher) => {
|
||||||
|
setSelectedTeachers(prevState =>
|
||||||
|
prevState.includes(teacher.id)
|
||||||
|
? prevState.filter(id => id !== teacher.id)
|
||||||
|
: [...prevState, teacher.id]
|
||||||
|
);
|
||||||
|
setFormData(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
enseignants_ids: prevState.enseignants_ids.includes(teacher.id)
|
||||||
|
? prevState.enseignants_ids.filter(id => id !== teacher.id)
|
||||||
|
: [...prevState.enseignants_ids, teacher.id]
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTimeChange = (e, index) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
setFormData(prevState => {
|
||||||
|
const updatedTimes = [...prevState.plage_horaire];
|
||||||
|
updatedTimes[index] = value;
|
||||||
|
|
||||||
|
const updatedFormData = {
|
||||||
|
...prevState,
|
||||||
|
plage_horaire: updatedTimes,
|
||||||
|
};
|
||||||
|
|
||||||
|
updatedFormData.planning = updatePlanning(updatedFormData);
|
||||||
|
|
||||||
|
return updatedFormData;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleJoursChange = (e) => {
|
||||||
|
const { value, checked } = e.target;
|
||||||
|
const dayId = parseInt(value, 10);
|
||||||
|
|
||||||
|
setFormData((prevState) => {
|
||||||
|
const updatedJoursOuverture = checked
|
||||||
|
? [...prevState.jours_ouverture, dayId]
|
||||||
|
: prevState.jours_ouverture.filter((id) => id !== dayId);
|
||||||
|
|
||||||
|
const updatedFormData = {
|
||||||
|
...prevState,
|
||||||
|
jours_ouverture: updatedJoursOuverture,
|
||||||
|
};
|
||||||
|
|
||||||
|
updatedFormData.planning = updatePlanning(updatedFormData);
|
||||||
|
|
||||||
|
return updatedFormData;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const { name, value, type, checked } = e.target;
|
||||||
|
|
||||||
|
setFormData(prevState => {
|
||||||
|
let updatedFormData = { ...prevState };
|
||||||
|
|
||||||
|
if (type === 'checkbox') {
|
||||||
|
const newValues = checked
|
||||||
|
? [...(prevState[name] || []), parseInt(value)]
|
||||||
|
: (prevState[name] || []).filter(v => v !== parseInt(value));
|
||||||
|
updatedFormData[name] = newValues;
|
||||||
|
} else if (name === 'tranche_age') {
|
||||||
|
const [minAgeStr, maxAgeStr] = value.split('-');
|
||||||
|
const minAge = minAgeStr ? parseInt(minAgeStr) : null;
|
||||||
|
const maxAge = minAgeStr ? parseInt(maxAgeStr) : null;
|
||||||
|
const selectedNiveaux = generateAgeToNiveaux(minAge, maxAge);
|
||||||
|
const niveauxLabels = getNiveauxLabels(selectedNiveaux);
|
||||||
|
|
||||||
|
updatedFormData = {
|
||||||
|
...prevState,
|
||||||
|
[name]: value,
|
||||||
|
niveaux: selectedNiveaux.length > 0 ? selectedNiveaux : [],
|
||||||
|
niveaux_label: niveauxLabels.length > 0 ? niveauxLabels : []
|
||||||
|
};
|
||||||
|
} else if (type === 'radio') {
|
||||||
|
updatedFormData[name] = parseInt(value, 10);
|
||||||
|
} else {
|
||||||
|
updatedFormData[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Updated formData:', updatedFormData);
|
||||||
|
|
||||||
|
updatedFormData.planning = updatePlanning(updatedFormData);
|
||||||
|
|
||||||
|
console.log('Final formData:', updatedFormData);
|
||||||
|
return updatedFormData;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
onSubmit(formData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [minAge, maxAge] = formData.tranche_age.length === 2 ? formData.tranche_age : [null, null];
|
||||||
|
const selectedAgeGroup = generateAgeToNiveaux(minAge, maxAge);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-[80vh] overflow-y-auto">
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4 mt-8">
|
||||||
|
|
||||||
|
<div className="flex justify-between space-x-4">
|
||||||
|
{/* Section Ambiance */}
|
||||||
|
<div className="w-1/2 space-y-4">
|
||||||
|
<label className="block text-lg font-medium text-gray-700">Ambiance <i>(optionnel)</i></label>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<InputTextIcon
|
||||||
|
name="nom_ambiance"
|
||||||
|
type="text"
|
||||||
|
IconItem={Users}
|
||||||
|
placeholder="Nom de l'ambiance"
|
||||||
|
value={formData.nom_ambiance}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<InputTextIcon
|
||||||
|
name="tranche_age"
|
||||||
|
type="text"
|
||||||
|
IconItem={Maximize2}
|
||||||
|
placeholder="Tranche d'âge (ex: 3-6)"
|
||||||
|
value={formData.tranche_age}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Section Niveau */}
|
||||||
|
<div className="w-1/2 space-y-2">
|
||||||
|
<label className="block text-lg font-medium text-gray-700">Niveaux</label>
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<CheckBoxList
|
||||||
|
items={niveauxPremierCycle}
|
||||||
|
formData={formData}
|
||||||
|
handleChange={handleChange}
|
||||||
|
fieldName="niveaux"
|
||||||
|
labelAttenuated={(item) => !selectedAgeGroup.includes(parseInt(item.id))}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<CheckBoxList
|
||||||
|
items={niveauxSecondCycle}
|
||||||
|
formData={formData}
|
||||||
|
handleChange={handleChange}
|
||||||
|
fieldName="niveaux"
|
||||||
|
labelAttenuated={(item) => !selectedAgeGroup.includes(parseInt(item.id))}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<CheckBoxList
|
||||||
|
items={niveauxTroisiemeCycle}
|
||||||
|
formData={formData}
|
||||||
|
handleChange={handleChange}
|
||||||
|
fieldName="niveaux"
|
||||||
|
labelAttenuated={(item) => !selectedAgeGroup.includes(parseInt(item.id))}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between space-x-4">
|
||||||
|
{/* Section Capacité */}
|
||||||
|
<div className="w-1/2 space-y-4">
|
||||||
|
<label className="block text-lg font-medium text-gray-700">Capacité</label>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<InputTextIcon
|
||||||
|
name="nombre_eleves"
|
||||||
|
type="number"
|
||||||
|
IconItem={UserPlus}
|
||||||
|
placeholder="Capacité max"
|
||||||
|
value={formData.nombre_eleves}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Année scolaire */}
|
||||||
|
<div className="w-1/2 space-y-4">
|
||||||
|
<label className="block text-lg font-medium text-gray-700">Année scolaire</label>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<SelectChoice
|
||||||
|
name="annee_scolaire"
|
||||||
|
placeholder="Sélectionner l'année scolaire"
|
||||||
|
selected={formData.annee_scolaire}
|
||||||
|
callback={handleChange}
|
||||||
|
choices={schoolYears}
|
||||||
|
IconItem={Calendar}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Section Enseignants */}
|
||||||
|
<TeachersSelectionConfiguration formData={formData}
|
||||||
|
teachers={teachers}
|
||||||
|
handleTeacherSelection={handleTeacherSelection}
|
||||||
|
selectedTeachers={selectedTeachers}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Section Emploi du temps */}
|
||||||
|
<PlanningConfiguration formData={formData}
|
||||||
|
handleChange={handleChange}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
handleJoursChange={handleJoursChange}
|
||||||
|
typeEmploiDuTemps={typeEmploiDuTemps}
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex justify-end mt-4 space-x-4">
|
||||||
|
<Button
|
||||||
|
text={`${isNew ? "Créer" : "Modifier"}`}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||||
|
(formData.niveaux.length === 0 || !formData.annee_scolaire || !formData.nombre_eleves || formData.enseignants_ids.length === 0)
|
||||||
|
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||||
|
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||||
|
}`}
|
||||||
|
primary
|
||||||
|
disabled={(formData.niveaux.length === 0 || !formData.annee_scolaire || !formData.nombre_eleves || formData.enseignants_ids.length === 0)}
|
||||||
|
type="submit"
|
||||||
|
name="Create"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClassForm;
|
||||||
@ -3,11 +3,15 @@ import { useState } from 'react';
|
|||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import DropdownMenu from '@/components/DropdownMenu';
|
import DropdownMenu from '@/components/DropdownMenu';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import ClassForm from '@/components/ClassForm';
|
import ClassForm from '@/components/Structure/Configuration/ClassForm';
|
||||||
import ClasseDetails from '@/components/ClasseDetails';
|
import ClasseDetails from '@/components/ClasseDetails';
|
||||||
|
import { ClasseFormProvider } from '@/context/ClasseFormContext';
|
||||||
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
|
|
||||||
|
|
||||||
const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleEdit, handleDelete }) => {
|
const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleEdit, handleDelete }) => {
|
||||||
|
|
||||||
|
const { getNiveauxLabels } = useClasses();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isOpenDetails, setIsOpenDetails] = useState(false);
|
const [isOpenDetails, setIsOpenDetails] = useState(false);
|
||||||
const [editingClass, setEditingClass] = useState(null);
|
const [editingClass, setEditingClass] = useState(null);
|
||||||
@ -58,59 +62,97 @@ const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleE
|
|||||||
<div className="bg-white rounded-lg border border-gray-200 max-w-8xl ml-0">
|
<div className="bg-white rounded-lg border border-gray-200 max-w-8xl ml-0">
|
||||||
<Table
|
<Table
|
||||||
columns={[
|
columns={[
|
||||||
{ name: 'AMBIANCE', transform: (row) => row.nom_ambiance },
|
{
|
||||||
{ name: 'AGE', transform: (row) => `${row.tranche_age[0]} - ${row.tranche_age[1]} ans` },
|
name: 'AMBIANCE',
|
||||||
{ name: 'NOMBRE D\'ELEVES', transform: (row) => row.nombre_eleves },
|
transform: (row) => {
|
||||||
{ name: 'LANGUE D\'ENSEIGNEMENT', transform: (row) => row.langue_enseignement },
|
const ambiance = row.nom_ambiance ? row.nom_ambiance : '';
|
||||||
{ name: 'ANNEE SCOLAIRE', transform: (row) => row.annee_scolaire },
|
const trancheAge = row.tranche_age ? `${row.tranche_age} ans` : '';
|
||||||
|
|
||||||
|
if (ambiance && trancheAge) {
|
||||||
|
return `${ambiance} (${trancheAge})`;
|
||||||
|
} else if (ambiance) {
|
||||||
|
return ambiance;
|
||||||
|
} else if (trancheAge) {
|
||||||
|
return trancheAge;
|
||||||
|
} else {
|
||||||
|
return 'Non spécifié';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'NIVEAUX',
|
||||||
|
transform: (row) => {
|
||||||
|
const niveauxLabels = Array.isArray(row.niveaux) ? getNiveauxLabels(row.niveaux) : [];
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap justify-center items-center space-x-2">
|
||||||
|
{niveauxLabels.length > 0
|
||||||
|
? niveauxLabels.map((label, index) => (
|
||||||
|
<div
|
||||||
|
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'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ name: 'CAPACITÉ MAX', transform: (row) => row.nombre_eleves },
|
||||||
|
{ name: 'ANNÉE SCOLAIRE', transform: (row) => row.annee_scolaire },
|
||||||
{
|
{
|
||||||
name: 'ENSEIGNANTS',
|
name: 'ENSEIGNANTS',
|
||||||
transform: (row) => (
|
transform: (row) => (
|
||||||
<div key={row.id} className="flex 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 => (
|
{row.enseignants.map((teacher, index) => (
|
||||||
<div key={teacher.id} className="flex items-center space-x-1">
|
<div
|
||||||
<span>{teacher.nom} {teacher.prenom}</span>
|
key={teacher.id}
|
||||||
{teacher.specialites.map(specialite => (
|
className={`px-3 py-1 rounded-md shadow-sm ${
|
||||||
<span
|
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
||||||
key={specialite.id}
|
} border border-gray-200 text-gray-700`}
|
||||||
className="w-4 h-4 rounded-full"
|
>
|
||||||
style={{ backgroundColor: specialite.codeCouleur }}
|
<span className="font-bold">{teacher.nom} {teacher.prenom}</span>
|
||||||
title={specialite.nom}
|
|
||||||
></span>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ name: 'ACTIONS', transform: (row) => (
|
{ name: 'DATE DE CREATION', transform: (row) => row.dateCreation_formattee },
|
||||||
|
{
|
||||||
|
name: 'ACTIONS', transform: (row) => (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
|
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
|
||||||
items={[
|
items={[
|
||||||
{ label: 'Inspecter', icon: ZoomIn, onClick: () => openEditModalDetails(row) },
|
{ label: 'Inspecter', icon: ZoomIn, onClick: () => openEditModalDetails(row) },
|
||||||
{ label: 'Modifier', icon:Edit3, onClick: () => openEditModal(row) },
|
{ label: 'Modifier', icon: Edit3, onClick: () => openEditModal(row) },
|
||||||
{ label: 'Supprimer', icon: Trash2, onClick: () => handleDelete(row.id) }
|
{ label: 'Supprimer', icon: Trash2, onClick: () => handleDelete(row.id) }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
buttonClassName="text-gray-400 hover:text-gray-600"
|
buttonClassName="text-gray-400 hover:text-gray-600"
|
||||||
menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-10 flex flex-col items-center"
|
menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-10 flex flex-col items-center"
|
||||||
/>
|
/>
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
]}
|
]}
|
||||||
data={classes}
|
data={classes}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
|
<ClasseFormProvider initialClasse={editingClass || {}}>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
setIsOpen={setIsOpen}
|
setIsOpen={setIsOpen}
|
||||||
title={editingClass ? "Modification de la classe" : "Création d'une nouvelle classe"}
|
title={editingClass ? "Modification de la classe" : "Création d'une nouvelle classe"}
|
||||||
size='sm:w-1/4'
|
size='sm:w-1/2'
|
||||||
ContentComponent={() => (
|
ContentComponent={() => (
|
||||||
<ClassForm classe={editingClass || {}} onSubmit={handleModalSubmit} isNew={!editingClass} specialities={specialities} teachers={teachers} />
|
<ClassForm classe={editingClass || {}} onSubmit={handleModalSubmit} isNew={!editingClass} teachers={teachers} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</ClasseFormProvider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isOpenDetails && (
|
{isOpenDetails && (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpenDetails}
|
isOpen={isOpenDetails}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Calendar } from 'lucide-react';
|
||||||
|
|
||||||
|
const DateRange = ({ nameStart, nameEnd, valueStart, valueEnd, onChange, label }) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4 mt-4 p-4 border rounded-md shadow-sm bg-white">
|
||||||
|
<label className="block text-lg font-medium text-gray-700 mb-2">{label}</label>
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 items-center">
|
||||||
|
<div className="relative flex items-center">
|
||||||
|
<span className="mr-2">Du</span>
|
||||||
|
<Calendar className="w-5 h-5 text-emerald-500 absolute top-3 left-16" />
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
name={nameStart}
|
||||||
|
value={valueStart}
|
||||||
|
onChange={onChange}
|
||||||
|
className="block w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-emerald-500 focus:border-emerald-500 hover:ring-emerald-400 ml-8"
|
||||||
|
placeholder="Date de début"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex items-center">
|
||||||
|
<span className="mr-2">Au</span>
|
||||||
|
<Calendar className="w-5 h-5 text-emerald-500 absolute top-3 left-16" />
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
name={nameEnd}
|
||||||
|
value={valueEnd}
|
||||||
|
onChange={onChange}
|
||||||
|
className="block w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-emerald-500 focus:border-emerald-500 hover:ring-emerald-400 ml-8"
|
||||||
|
placeholder="Date de fin"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DateRange;
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import RadioList from '@/components/RadioList';
|
||||||
|
import DateRange from '@/components/Structure/Configuration/DateRange';
|
||||||
|
import TimeRange from '@/components/Structure/Configuration/TimeRange';
|
||||||
|
import CheckBoxList from '@/components/CheckBoxList';
|
||||||
|
|
||||||
|
const PlanningConfiguration = ({ formData, handleChange, handleTimeChange, handleJoursChange, typeEmploiDuTemps }) => {
|
||||||
|
const daysOfWeek = [
|
||||||
|
{ id: 1, name: 'lun' },
|
||||||
|
{ id: 2, name: 'mar' },
|
||||||
|
{ id: 3, name: 'mer' },
|
||||||
|
{ id: 4, name: 'jeu' },
|
||||||
|
{ id: 5, name: 'ven' },
|
||||||
|
{ id: 6, name: 'sam' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const isLabelAttenuated = (item) => {
|
||||||
|
return !formData.jours_ouverture.includes(parseInt(item.id));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<label className="mt-6 block text-2xl font-medium text-gray-700">Emploi du temps</label>
|
||||||
|
|
||||||
|
<div className="flex justify-between space-x-4 items-start">
|
||||||
|
<div className="w-1/2">
|
||||||
|
<RadioList
|
||||||
|
items={typeEmploiDuTemps}
|
||||||
|
formData={formData}
|
||||||
|
handleChange={handleChange}
|
||||||
|
fieldName="planning_type"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Plage horaire */}
|
||||||
|
<div className="w-1/2">
|
||||||
|
<TimeRange
|
||||||
|
startTime={formData.plage_horaire[0]}
|
||||||
|
endTime={formData.plage_horaire[1]}
|
||||||
|
onStartChange={(e) => handleTimeChange(e, 0)}
|
||||||
|
onEndChange={(e) => handleTimeChange(e, 1)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* CheckBoxList */}
|
||||||
|
<CheckBoxList
|
||||||
|
items={daysOfWeek}
|
||||||
|
formData={formData}
|
||||||
|
handleChange={handleJoursChange}
|
||||||
|
fieldName="jours_ouverture"
|
||||||
|
horizontal={true}
|
||||||
|
labelAttenuated={isLabelAttenuated}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* DateRange */}
|
||||||
|
<div className="space-y-4 w-full">
|
||||||
|
{formData.planning_type === 2 && (
|
||||||
|
<>
|
||||||
|
<DateRange
|
||||||
|
nameStart="date_debut_semestre_1"
|
||||||
|
nameEnd="date_fin_semestre_1"
|
||||||
|
valueStart={formData.date_debut_semestre_1}
|
||||||
|
valueEnd={formData.date_fin_semestre_1}
|
||||||
|
onChange={handleChange}
|
||||||
|
label="Semestre 1"
|
||||||
|
/>
|
||||||
|
<DateRange
|
||||||
|
nameStart="date_debut_semestre_2"
|
||||||
|
nameEnd="date_fin_semestre_2"
|
||||||
|
valueStart={formData.date_debut_semestre_2}
|
||||||
|
valueEnd={formData.date_fin_semestre_2}
|
||||||
|
onChange={handleChange}
|
||||||
|
label="Semestre 2"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{formData.planning_type === 3 && (
|
||||||
|
<>
|
||||||
|
<DateRange
|
||||||
|
nameStart="date_debut_trimestre_1"
|
||||||
|
nameEnd="date_fin_trimestre_1"
|
||||||
|
valueStart={formData.date_debut_trimestre_1}
|
||||||
|
valueEnd={formData.date_fin_trimestre_1}
|
||||||
|
onChange={handleChange}
|
||||||
|
label="Trimestre 1"
|
||||||
|
/>
|
||||||
|
<DateRange
|
||||||
|
nameStart="date_debut_trimestre_2"
|
||||||
|
nameEnd="date_fin_trimestre_2"
|
||||||
|
valueStart={formData.date_debut_trimestre_2}
|
||||||
|
valueEnd={formData.date_fin_trimestre_2}
|
||||||
|
onChange={handleChange}
|
||||||
|
label="Trimestre 2"
|
||||||
|
/>
|
||||||
|
<DateRange
|
||||||
|
nameStart="date_debut_trimestre_3"
|
||||||
|
nameEnd="date_fin_trimestre_3"
|
||||||
|
valueStart={formData.date_debut_trimestre_3}
|
||||||
|
valueEnd={formData.date_fin_trimestre_3}
|
||||||
|
onChange={handleChange}
|
||||||
|
label="Trimestre 3"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlanningConfiguration;
|
||||||
@ -3,7 +3,8 @@ import { useState } from 'react';
|
|||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import DropdownMenu from '@/components/DropdownMenu';
|
import DropdownMenu from '@/components/DropdownMenu';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import SpecialityForm from '@/components/SpecialityForm';
|
import SpecialityForm from '@/components/Structure/Configuration/SpecialityForm';
|
||||||
|
import { SpecialityFormProvider } from '@/context/SpecialityFormContext';
|
||||||
|
|
||||||
const SpecialitiesSection = ({ specialities, handleCreate, handleEdit, handleDelete }) => {
|
const SpecialitiesSection = ({ specialities, handleCreate, handleEdit, handleDelete }) => {
|
||||||
|
|
||||||
@ -46,14 +47,18 @@ const SpecialitiesSection = ({ specialities, handleCreate, handleEdit, handleDel
|
|||||||
<div className="bg-white rounded-lg border border-gray-200 max-w-4xl ml-0">
|
<div className="bg-white rounded-lg border border-gray-200 max-w-4xl ml-0">
|
||||||
<Table
|
<Table
|
||||||
columns={[
|
columns={[
|
||||||
{ name: 'NOM', transform: (row) => row.nom.toUpperCase() },
|
{
|
||||||
{ name: 'CODE', transform: (row) => (
|
name: 'INTITULÉ',
|
||||||
|
transform: (row) => (
|
||||||
<div
|
<div
|
||||||
className="w-4 h-4 rounded-full mx-auto"
|
className="inline-block px-3 py-1 rounded-full font-bold text-white"
|
||||||
style={{ backgroundColor: row.codeCouleur }}
|
style={{ backgroundColor: row.codeCouleur }}
|
||||||
title={row.codeCouleur}
|
title={row.codeCouleur}
|
||||||
></div>
|
>
|
||||||
)},
|
<span className="font-bold text-white">{row.nom.toUpperCase()}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
{ name: 'DATE DE CREATION', transform: (row) => row.dateCreation_formattee },
|
{ name: 'DATE DE CREATION', transform: (row) => row.dateCreation_formattee },
|
||||||
{ name: 'ACTIONS', transform: (row) => (
|
{ name: 'ACTIONS', transform: (row) => (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
@ -72,15 +77,17 @@ const SpecialitiesSection = ({ specialities, handleCreate, handleEdit, handleDel
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
|
<SpecialityFormProvider initialSpeciality={editingSpeciality || {}}>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
setIsOpen={setIsOpen}
|
setIsOpen={setIsOpen}
|
||||||
title={editingSpeciality ? "Modification de la spécialité" : "Création d'une nouvelle spécialité"}
|
title={editingSpeciality ? "Modification de la spécialité" : "Création d'une nouvelle spécialité"}
|
||||||
size='sm:w-1/6'
|
size='sm:w-1/6'
|
||||||
ContentComponent={() => (
|
ContentComponent={() => (
|
||||||
<SpecialityForm speciality={editingSpeciality || {}} onSubmit={handleModalSubmit} isNew={!editingSpeciality} />
|
<SpecialityForm onSubmit={handleModalSubmit} isNew={!editingSpeciality} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</SpecialityFormProvider>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -3,17 +3,23 @@ import { BookOpen, Palette } from 'lucide-react';
|
|||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
import InputColorIcon from '@/components/InputColorIcon';
|
import InputColorIcon from '@/components/InputColorIcon';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import { useSpecialityForm } from '@/context/SpecialityFormContext';
|
||||||
|
|
||||||
const SpecialityForm = ({ speciality = {}, onSubmit, isNew }) => {
|
const SpecialityForm = ({ onSubmit, isNew }) => {
|
||||||
const [nom, setNom] = useState(speciality.nom || '');
|
const { formData, setFormData } = useSpecialityForm();
|
||||||
const [codeCouleur, setCodeCouleur] = useState(speciality.codeCouleur || '#FFFFFF');
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleChange = (e) => {
|
||||||
const updatedData = {
|
const { name, value } = e.target;
|
||||||
nom,
|
|
||||||
codeCouleur,
|
setFormData((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
[name]: value,
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
onSubmit(updatedData, isNew);
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onSubmit(formData);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -21,33 +27,35 @@ const SpecialityForm = ({ speciality = {}, onSubmit, isNew }) => {
|
|||||||
<div>
|
<div>
|
||||||
<InputTextIcon
|
<InputTextIcon
|
||||||
type="text"
|
type="text"
|
||||||
|
name="nom"
|
||||||
IconItem={BookOpen}
|
IconItem={BookOpen}
|
||||||
placeholder="Nom de la spécialité"
|
placeholder="Nom de la spécialité"
|
||||||
value={nom}
|
value={formData.nom}
|
||||||
onChange={(e) => setNom(e.target.value)}
|
onChange={handleChange}
|
||||||
className="w-full mt-4"
|
className="w-full mt-4"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<InputColorIcon
|
<InputColorIcon
|
||||||
type="color"
|
type="color"
|
||||||
|
name="codeCouleur"
|
||||||
IconItem={Palette}
|
IconItem={Palette}
|
||||||
placeholder="Nom de la spécialité"
|
placeholder="Nom de la spécialité"
|
||||||
value={codeCouleur}
|
value={formData.codeCouleur}
|
||||||
onChange={(e) => setCodeCouleur(e.target.value)}
|
onChange={handleChange}
|
||||||
className="w-full mt-4"
|
className="w-full mt-4"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end mt-4 space-x-4">
|
<div className="flex justify-end mt-4 space-x-4">
|
||||||
<Button text="Créer"
|
<Button text={`${isNew ? "Créer" : "Modifier"}`}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||||
!nom
|
!formData.nom
|
||||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||||
}`}
|
}`}
|
||||||
primary
|
primary
|
||||||
disabled={!nom}
|
disabled={!formData.nom}
|
||||||
type="submit"
|
type="submit"
|
||||||
name="Create" />
|
name="Create" />
|
||||||
</div>
|
</div>
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SpecialitiesSection from '@/components/Structure/Configuration/SpecialitiesSection';
|
||||||
|
import TeachersSection from '@/components/Structure/Configuration/TeachersSection';
|
||||||
|
import ClassesSection from '@/components/Structure/Configuration/ClassesSection';
|
||||||
|
import { ClassesProvider } from '@/context/ClassesContext';
|
||||||
|
|
||||||
|
import { BK_GESTIONENSEIGNANTS_SPECIALITE_URL,
|
||||||
|
BK_GESTIONENSEIGNANTS_TEACHER_URL,
|
||||||
|
BK_GESTIONENSEIGNANTS_CLASSE_URL } from '@/utils/Url';
|
||||||
|
|
||||||
|
const StructureManagement = ({ specialities, setSpecialities, teachers, setTeachers, classes, setClasses, handleCreate, handleEdit, handleDelete }) => {
|
||||||
|
return (
|
||||||
|
<div className='p-8'>
|
||||||
|
<ClassesProvider>
|
||||||
|
<SpecialitiesSection
|
||||||
|
specialities={specialities}
|
||||||
|
setSpecialities={setSpecialities}
|
||||||
|
handleCreate={(newData) => handleCreate(`${BK_GESTIONENSEIGNANTS_SPECIALITE_URL}`, newData, setSpecialities)}
|
||||||
|
handleEdit={(id, updatedData) => handleEdit(`${BK_GESTIONENSEIGNANTS_SPECIALITE_URL}`, id, updatedData, setSpecialities)}
|
||||||
|
handleDelete={(id) => handleDelete(`${BK_GESTIONENSEIGNANTS_SPECIALITE_URL}`, id, setSpecialities)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TeachersSection
|
||||||
|
teachers={teachers}
|
||||||
|
specialities={specialities}
|
||||||
|
handleCreate={(newData) => handleCreate(`${BK_GESTIONENSEIGNANTS_TEACHER_URL}`, newData, setTeachers)}
|
||||||
|
handleEdit={(id, updatedData) => handleEdit(`${BK_GESTIONENSEIGNANTS_TEACHER_URL}`, id, updatedData, setTeachers)}
|
||||||
|
handleDelete={(id) => handleDelete(`${BK_GESTIONENSEIGNANTS_TEACHER_URL}`, id, setTeachers)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ClassesSection
|
||||||
|
classes={classes}
|
||||||
|
specialities={specialities}
|
||||||
|
teachers={teachers}
|
||||||
|
handleCreate={(newData) => handleCreate(`${BK_GESTIONENSEIGNANTS_CLASSE_URL}`, newData, setClasses)}
|
||||||
|
handleEdit={(id, updatedData) => handleEdit(`${BK_GESTIONENSEIGNANTS_CLASSE_URL}`, id, updatedData, setClasses)}
|
||||||
|
handleDelete={(id) => handleDelete(`${BK_GESTIONENSEIGNANTS_CLASSE_URL}`, id, setClasses)}
|
||||||
|
/>
|
||||||
|
</ClassesProvider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StructureManagement;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { School, Calendar } from 'lucide-react';
|
||||||
|
|
||||||
|
const TabsStructure = ({ activeTab, setActiveTab, tabs }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center mb-8">
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<button
|
||||||
|
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`}
|
||||||
|
onClick={() => setActiveTab(tab.id)}
|
||||||
|
>
|
||||||
|
<tab.icon className="w-5 h-5" />
|
||||||
|
<span>{tab.title}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TabsStructure;
|
||||||
@ -4,17 +4,10 @@ import InputTextIcon from '@/components/InputTextIcon';
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import CheckBoxList from '@/components/CheckBoxList';
|
import CheckBoxList from '@/components/CheckBoxList';
|
||||||
import ToggleSwitch from '@/components/ToggleSwitch'
|
import ToggleSwitch from '@/components/ToggleSwitch'
|
||||||
|
import { useTeacherForm } from '@/context/TeacherFormContext';
|
||||||
|
|
||||||
const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
|
const TeacherForm = ({ onSubmit, isNew, specialities }) => {
|
||||||
|
const { formData, setFormData } = useTeacherForm();
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
nom: teacher.nom || '',
|
|
||||||
prenom: teacher.prenom || '',
|
|
||||||
mail: teacher.mail || '',
|
|
||||||
specialites_ids: teacher.specialites_ids || [],
|
|
||||||
profilAssocie_id:teacher.profilAssocie_id || '',
|
|
||||||
droit: teacher.DroitValue || 0
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleToggleChange = () => {
|
const handleToggleChange = () => {
|
||||||
setFormData({ ...formData, droit: 1-formData.droit });
|
setFormData({ ...formData, droit: 1-formData.droit });
|
||||||
@ -24,8 +17,6 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
|
|||||||
const target = e.target || e.currentTarget;
|
const target = e.target || e.currentTarget;
|
||||||
const { name, value, type, checked } = target;
|
const { name, value, type, checked } = target;
|
||||||
|
|
||||||
console.log('type : ', type);
|
|
||||||
|
|
||||||
if (type === 'checkbox') {
|
if (type === 'checkbox') {
|
||||||
setFormData((prevState) => {
|
setFormData((prevState) => {
|
||||||
const newValues = checked
|
const newValues = checked
|
||||||
@ -52,6 +43,10 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
|
|||||||
return `${speciality.nom}`;
|
return `${speciality.nom}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isLabelAttenuated = (item) => {
|
||||||
|
return !formData.specialites_ids.includes(parseInt(item.id));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className="space-y-4 mt-8">
|
<form onSubmit={handleSubmit} className="space-y-4 mt-8">
|
||||||
<div>
|
<div>
|
||||||
@ -97,6 +92,7 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
|
|||||||
icon={BookOpen}
|
icon={BookOpen}
|
||||||
className="w-full mt-4"
|
className="w-full mt-4"
|
||||||
itemLabelFunc={getSpecialityLabel}
|
itemLabelFunc={getSpecialityLabel}
|
||||||
|
labelAttenuated={isLabelAttenuated}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
@ -107,7 +103,7 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end mt-4 space-x-4">
|
<div className="flex justify-end mt-4 space-x-4">
|
||||||
<Button text="Créer"
|
<Button text={`${isNew ? "Créer" : "Modifier"}`}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||||
(!formData.nom || !formData.prenom || !formData.mail || formData.specialites_ids.length === 0)
|
(!formData.nom || !formData.prenom || !formData.mail || formData.specialites_ids.length === 0)
|
||||||
@ -3,9 +3,10 @@ import { useState } from 'react';
|
|||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import DropdownMenu from '@/components/DropdownMenu';
|
import DropdownMenu from '@/components/DropdownMenu';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import TeacherForm from '@/components/TeacherForm';
|
import TeacherForm from '@/components/Structure/Configuration/TeacherForm';
|
||||||
import {BK_PROFILE_URL} from '@/utils/Url';
|
import {BK_PROFILE_URL} from '@/utils/Url';
|
||||||
import useCsrfToken from '@/hooks/useCsrfToken';
|
import useCsrfToken from '@/hooks/useCsrfToken';
|
||||||
|
import { TeacherFormProvider } from '@/context/TeacherFormContext';
|
||||||
|
|
||||||
const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, specialities }) => {
|
const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, specialities }) => {
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex justify-between items-center mb-4 max-w-7xl ml-0">
|
<div className="flex justify-between items-center mb-4 max-w-8xl ml-0">
|
||||||
<h2 className="text-3xl text-gray-800 flex items-center">
|
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||||
<GraduationCap className="w-8 h-8 mr-2" />
|
<GraduationCap className="w-8 h-8 mr-2" />
|
||||||
Enseignants
|
Enseignants
|
||||||
@ -108,36 +109,47 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
|||||||
<Plus className="w-5 h-5" />
|
<Plus className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white rounded-lg border border-gray-200 max-w-7xl ml-0">
|
<div className="bg-white rounded-lg border border-gray-200 max-w-8xl ml-0">
|
||||||
<Table
|
<Table
|
||||||
columns={[
|
columns={[
|
||||||
{ name: 'NOM', transform: (row) => row.nom },
|
{ name: 'NOM', transform: (row) => row.nom },
|
||||||
{ name: 'PRENOM', transform: (row) => row.prenom },
|
{ name: 'PRENOM', transform: (row) => row.prenom },
|
||||||
{ name: 'MAIL', transform: (row) => row.mail },
|
{ name: 'MAIL', transform: (row) => row.mail },
|
||||||
{ name: 'SPECIALITES',
|
{
|
||||||
|
name: 'SPÉCIALITÉS',
|
||||||
transform: (row) => (
|
transform: (row) => (
|
||||||
<div key={row.id} className="flex justify-center items-center space-x-2">
|
<div key={row.id} className="flex flex-wrap justify-center items-center space-x-2">
|
||||||
{row.specialites.map(specialite => (
|
{row.specialites.map(specialite => (
|
||||||
<span
|
<span
|
||||||
key={specialite.id}
|
key={specialite.id}
|
||||||
className="w-4 h-4 rounded-full"
|
className="px-3 py-1 rounded-full font-bold text-white"
|
||||||
style={{ backgroundColor: specialite.codeCouleur }}
|
style={{ backgroundColor: specialite.codeCouleur }}
|
||||||
title={specialite.nom}
|
title={specialite.nom}
|
||||||
></span>
|
>
|
||||||
|
{specialite.nom}
|
||||||
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ name: 'TYPE PROFIL',
|
{
|
||||||
|
name: 'TYPE PROFIL',
|
||||||
transform: (row) => {
|
transform: (row) => {
|
||||||
return row.profilAssocie
|
if (row.profilAssocie) {
|
||||||
?
|
const badgeClass = row.DroitLabel === 'ECOLE' ? 'bg-blue-100 text-blue-600' : 'bg-red-100 text-red-600';
|
||||||
|
return (
|
||||||
<div key={row.id} className="flex justify-center items-center space-x-2">
|
<div key={row.id} className="flex justify-center items-center space-x-2">
|
||||||
|
<span className={`px-3 py-1 rounded-full font-bold ${badgeClass}`}>
|
||||||
{row.DroitLabel}
|
{row.DroitLabel}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
: <i>Non définie</i>;
|
);
|
||||||
|
} else {
|
||||||
|
return <i>Non définie</i>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ name: 'DATE DE CREATION', transform: (row) => row.dateCreation_formattee },
|
||||||
{ name: 'ACTIONS', transform: (row) => (
|
{ name: 'ACTIONS', transform: (row) => (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
|
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
|
||||||
@ -155,6 +167,7 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
|
<TeacherFormProvider initialTeacher={editingTeacher || {}}>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
setIsOpen={setIsOpen}
|
setIsOpen={setIsOpen}
|
||||||
@ -164,6 +177,7 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
|||||||
<TeacherForm teacher={editingTeacher || {}} onSubmit={handleModalSubmit} isNew={!editingTeacher} specialities={specialities} />
|
<TeacherForm teacher={editingTeacher || {}} onSubmit={handleModalSubmit} isNew={!editingTeacher} specialities={specialities} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</TeacherFormProvider>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
|
||||||
|
const TeachersSelectionConfiguration = ({ formData, teachers, handleTeacherSelection, selectedTeachers }) => {
|
||||||
|
return (
|
||||||
|
<div className="mt-4" style={{ maxHeight: '300px', overflowY: 'auto' }}>
|
||||||
|
<label className="mt-6 block text-2xl font-medium text-gray-700 mb-2">Enseignants</label>
|
||||||
|
<label className={`block text-sm font-medium mb-4`}>Sélection : <span className={`${formData.enseignants_ids.length !== 0 ? 'text-emerald-400' : 'text-red-300'}`}>{formData.enseignants_ids.length}</span></label>
|
||||||
|
<Table
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
name: 'Nom',
|
||||||
|
transform: (row) => row.nom,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Prénom',
|
||||||
|
transform: (row) => row.prenom,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Spécialités',
|
||||||
|
transform: (row) => (
|
||||||
|
<div className="flex flex-wrap items-center">
|
||||||
|
{row.specialites.map(specialite => (
|
||||||
|
<span key={specialite.id} className="flex items-center mr-2 mb-1">
|
||||||
|
<div
|
||||||
|
className="w-4 h-4 rounded-full mr-2"
|
||||||
|
style={{ backgroundColor: specialite.codeCouleur }}
|
||||||
|
title={specialite.nom}
|
||||||
|
></div>
|
||||||
|
<span>{specialite.nom}</span>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
data={teachers}
|
||||||
|
onRowClick={handleTeacherSelection}
|
||||||
|
selectedRows={selectedTeachers}
|
||||||
|
isSelectable={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeachersSelectionConfiguration;
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const TimeRange = ({ startTime, endTime, onStartChange, onEndChange }) => {
|
||||||
|
return (
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<div className="w-1/2">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Heure de début</label>
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
name="startTime"
|
||||||
|
value={startTime}
|
||||||
|
onChange={onStartChange}
|
||||||
|
className="block w-full border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-1/2">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Heure de fin</label>
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
name="endTime"
|
||||||
|
value={endTime}
|
||||||
|
onChange={onEndChange}
|
||||||
|
className="block w-full border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TimeRange;
|
||||||
81
Front-End/src/components/Structure/Planning/ClassesInfo.js
Normal file
81
Front-End/src/components/Structure/Planning/ClassesInfo.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
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,38 @@
|
|||||||
|
import { useDrag } from 'react-dnd';
|
||||||
|
|
||||||
|
const DraggableSpeciality = ({ specialite }) => {
|
||||||
|
const [{ isDragging }, drag] = useDrag(() => ({
|
||||||
|
type: 'SPECIALITY',
|
||||||
|
item: { id: specialite.id,
|
||||||
|
name: specialite.nom,
|
||||||
|
color: specialite.codeCouleur,
|
||||||
|
teachers: specialite.teachers,
|
||||||
|
duree: specialite.duree },
|
||||||
|
collect: (monitor) => ({
|
||||||
|
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 (
|
||||||
|
<span
|
||||||
|
ref={drag}
|
||||||
|
className="speciality-tag p-2 m-1 rounded cursor-pointer"
|
||||||
|
style={{
|
||||||
|
backgroundColor: specialite.codeCouleur,
|
||||||
|
color: isColorDark(specialite.codeCouleur) ? 'white' : 'black',
|
||||||
|
opacity: isDragging ? 0.5 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{specialite.nom} ({specialite.teachers.join(', ')})
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DraggableSpeciality;
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useDrop } from 'react-dnd';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const DropTargetCell = ({ day, hour, course, onDrop }) => {
|
||||||
|
const [{ isOver }, drop] = useDrop(() => ({
|
||||||
|
accept: 'SPECIALITY',
|
||||||
|
drop: (item) => onDrop(item, hour, day),
|
||||||
|
collect: (monitor) => ({
|
||||||
|
isOver: monitor.isOver(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const isColorDark = (color) => {
|
||||||
|
if (!color) return false; // Vérification si color est défini
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isToday = (someDate) => {
|
||||||
|
const today = new Date();
|
||||||
|
return someDate.getDate() === today.getDate() &&
|
||||||
|
someDate.getMonth() === today.getMonth() &&
|
||||||
|
someDate.getFullYear() === today.getFullYear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const cellBackgroundColor = course ? course.color : 'transparent';
|
||||||
|
const cellTextColor = isColorDark(course?.color) ? '#E5E5E5' : '#333333';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
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`}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
backgroundColor: cellBackgroundColor,
|
||||||
|
color: cellTextColor
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center gap-1">
|
||||||
|
{course && (
|
||||||
|
<>
|
||||||
|
<div className="text-base font-bold">{course.matiere}</div>
|
||||||
|
<div className="text-sm">{course.teachers.join(", ")}</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DropTargetCell.propTypes = {
|
||||||
|
day: PropTypes.string.isRequired,
|
||||||
|
hour: PropTypes.number.isRequired,
|
||||||
|
course: PropTypes.object,
|
||||||
|
onDrop: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropTargetCell;
|
||||||
@ -0,0 +1,249 @@
|
|||||||
|
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;
|
||||||
130
Front-End/src/components/Structure/Planning/PlanningClassView.js
Normal file
130
Front-End/src/components/Structure/Planning/PlanningClassView.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import React, {useRef} from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { format, isToday, isSameDay, startOfWeek, addDays } from 'date-fns';
|
||||||
|
import { fr } from 'date-fns/locale';
|
||||||
|
import DropTargetCell from './DropTargetCell';
|
||||||
|
|
||||||
|
const PlanningClassView = ({ schedule, onDrop, planningType }) => {
|
||||||
|
const weekDays = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"];
|
||||||
|
const isCurrentWeek = weekDays.some(day => isSameDay(day, new Date()));
|
||||||
|
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 formatTime = (time) => {
|
||||||
|
const [hour, minute] = time.split(':');
|
||||||
|
return `${hour}h${minute}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCells = () => {
|
||||||
|
const cells = [];
|
||||||
|
const timeSlots = Array.from({ length: 12 }, (_, index) => index + 8); // Heures de 08:00 à 19:00
|
||||||
|
|
||||||
|
timeSlots.forEach(hour => {
|
||||||
|
cells.push(
|
||||||
|
<div key={`hour-${hour}`} 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.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 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>
|
||||||
|
{weekDayDates.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">
|
||||||
|
{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)` }}>
|
||||||
|
{renderCells()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PlanningClassView.propTypes = {
|
||||||
|
schedule: PropTypes.shape({
|
||||||
|
emploiDuTemps: PropTypes.objectOf(
|
||||||
|
PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
duree: PropTypes.string.isRequired,
|
||||||
|
heure: PropTypes.string.isRequired,
|
||||||
|
matiere: PropTypes.string.isRequired,
|
||||||
|
teachers: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
color: PropTypes.string.isRequired,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlanningClassView;
|
||||||
@ -0,0 +1,195 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
|
import { DndProvider } from 'react-dnd';
|
||||||
|
import { AnimatePresence, findSpring, motion } from 'framer-motion'; // Ajouter cet import
|
||||||
|
import PlanningClassView from '@/components/Structure/Planning/PlanningClassView';
|
||||||
|
import SpecialityEventModal from '@/components/Structure/Planning/SpecialityEventModal';
|
||||||
|
import ClassesInfo from '@/components/Structure/Planning/ClassesInfo';
|
||||||
|
import { BK_GESTIONENSEIGNANTS_PLANNING_URL } from '@/utils/Url';
|
||||||
|
import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react'
|
||||||
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
|
|
||||||
|
const ScheduleManagement = ({ schedules, setSchedules, handleUpdatePlanning, specialities, teachers, classes }) => {
|
||||||
|
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [selectedClass, setSelectedClass] = useState(null);
|
||||||
|
const [schedule, setSchedule] = useState(null);
|
||||||
|
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(() => {
|
||||||
|
if (selectedClass) {
|
||||||
|
scheduleId.current = selectedClass.planning.id;
|
||||||
|
setSchedule(selectedClass.planning);
|
||||||
|
} else {
|
||||||
|
setSchedule(null);
|
||||||
|
}
|
||||||
|
}, [selectedClass]);
|
||||||
|
|
||||||
|
// Synchroniser scheduleRef avec schedule
|
||||||
|
useEffect(() => {
|
||||||
|
scheduleRef.current = schedule;
|
||||||
|
}, [schedule]);
|
||||||
|
|
||||||
|
const handleClassSelect = (cls) => {
|
||||||
|
setSelectedClass(cls);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (schedule) {
|
||||||
|
console.log("Schedule data:", schedule);
|
||||||
|
}
|
||||||
|
}, [schedule]);
|
||||||
|
|
||||||
|
|
||||||
|
// Fonction onDrop dans PlanningClassView ou un composant parent
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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]) {
|
||||||
|
newSchedule.emploiDuTemps[day] = [];
|
||||||
|
}
|
||||||
|
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 newCourse = {
|
||||||
|
duree: '1',
|
||||||
|
heure: courseTime,
|
||||||
|
matiere: name,
|
||||||
|
teachers: teachers,
|
||||||
|
color: color,
|
||||||
|
};
|
||||||
|
|
||||||
|
// S'il existe déjà un cours à cette heure, on le remplace
|
||||||
|
if (existingCourseIndex !== -1) {
|
||||||
|
newSchedule.emploiDuTemps[day][existingCourseIndex] = newCourse;
|
||||||
|
} else {
|
||||||
|
// Sinon on ajoute le nouveau cours
|
||||||
|
newSchedule.emploiDuTemps[day].push(newCourse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mettez à jour l'état du planning
|
||||||
|
setSchedule(newSchedule)
|
||||||
|
|
||||||
|
// Appelez la fonction handleUpdatePlanning en dehors de setSchedule
|
||||||
|
handleUpdatePlanning(`${BK_GESTIONENSEIGNANTS_PLANNING_URL}`, scheduleId.current, newSchedule);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPeriodLabel = (period) => {
|
||||||
|
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) => {
|
||||||
|
if (planningType === 'TRIMESTRIEL') {
|
||||||
|
if (direction === 'prev') {
|
||||||
|
setCurrentPeriod(currentPeriod === 'T1' ? 'T3' : `T${parseInt(currentPeriod.slice(1)) - 1}`);
|
||||||
|
} else {
|
||||||
|
setCurrentPeriod(currentPeriod === 'T3' ? 'T1' : `T${parseInt(currentPeriod.slice(1)) + 1}`);
|
||||||
|
}
|
||||||
|
} else if (planningType === 'SEMESTRIEL') {
|
||||||
|
setCurrentPeriod(currentPeriod === 'S1' ? 'S2' : 'S1');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fonctionnalité de gestion des emplois du temps
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full overflow-hidden">
|
||||||
|
<DndProvider backend={HTML5Backend}>
|
||||||
|
<ClassesInfo classes={classes} onClassSelect={handleClassSelect}/>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center p-4 w-full">
|
||||||
|
<div className="flex items-center w-full">
|
||||||
|
<SelectChoice
|
||||||
|
name="planningType"
|
||||||
|
IconItem={Calendar}
|
||||||
|
selected={planningType}
|
||||||
|
choices={planningChoices}
|
||||||
|
callback={(e) => {
|
||||||
|
setPlanningType(e.target.value);
|
||||||
|
setCurrentPeriod(e.target.value === 'TRIMESTRIEL' ? 'T1' : 'S1');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{planningType !== 'ANNUEL' && (
|
||||||
|
<div className="flex items-center justify-center w-full">
|
||||||
|
<button
|
||||||
|
onClick={() => handlePeriodChange('prev')}
|
||||||
|
className={`mr-4 p-2 border rounded-lg ${
|
||||||
|
currentPeriod === 'T1' || currentPeriod === 'S1' ? 'bg-gray-300 text-gray-700 cursor-not-allowed' : 'bg-emerald-500 text-white hover:bg-emerald-600'
|
||||||
|
} transition-colors duration-300`}
|
||||||
|
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>
|
||||||
|
|
||||||
|
<div className="flex-1 max-h-[calc(100vh-192px)] overflow-hidden">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key="year"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -20 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
>
|
||||||
|
<PlanningClassView
|
||||||
|
schedule={schedule}
|
||||||
|
onDrop={onDrop}
|
||||||
|
planningType={planningType}
|
||||||
|
currentPeriod={currentPeriod}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</DndProvider>
|
||||||
|
{/* <SpecialityEventModal
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={() => setIsModalOpen(false)}
|
||||||
|
eventData={eventData}
|
||||||
|
setEventData={setEventData}
|
||||||
|
selectedClass={selectedClass}
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScheduleManagement;
|
||||||
@ -0,0 +1,186 @@
|
|||||||
|
import { usePlanning } from '@/context/PlanningContext';
|
||||||
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Users, BookOpen } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function SpecialityEventModal({ isOpen, onClose, eventData, setEventData, selectedClass }) {
|
||||||
|
const { addEvent, updateEvent, deleteEvent, schedules } = usePlanning();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
// Réinitialiser eventData lorsque la modale se ferme
|
||||||
|
setEventData({
|
||||||
|
scheduleId: '',
|
||||||
|
specialiteId: '',
|
||||||
|
specialities: [],
|
||||||
|
// Réinitialiser d'autres champs si nécessaire
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isOpen, setEventData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && selectedClass) {
|
||||||
|
|
||||||
|
setEventData(prev => ({
|
||||||
|
...prev,
|
||||||
|
scheduleId: selectedClass.id,
|
||||||
|
specialities: Array.from(new Map(
|
||||||
|
selectedClass.enseignants.flatMap(teacher =>
|
||||||
|
teacher.specialites.map(specialite => [specialite.id, {
|
||||||
|
id: specialite.id,
|
||||||
|
nom: specialite.nom,
|
||||||
|
codeCouleur: specialite.codeCouleur
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
).values())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [isOpen, selectedClass, setEventData]);
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!eventData.scheduleId) {
|
||||||
|
alert('Veuillez sélectionner une spécialité');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedSchedule = schedules.find(s => s.id === eventData.scheduleId);
|
||||||
|
|
||||||
|
if (eventData.id) {
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (eventData.id && confirm('Êtes-vous sûr de vouloir supprimer cet événement ?')) {
|
||||||
|
deleteEvent(eventData.id);
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-white p-6 rounded-lg w-full max-w-md">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">
|
||||||
|
{eventData.id ? 'Modifier l\'événement' : 'Nouvel événement'}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
{/* Sélection de la Spécialité */}
|
||||||
|
<div>
|
||||||
|
{eventData.scheduleId && eventData.specialities && eventData.specialities.length > 0 ? (
|
||||||
|
<SelectChoice
|
||||||
|
name={`spécialités-${eventData.scheduleId}`}
|
||||||
|
label="Spécialités"
|
||||||
|
selected={eventData.specialiteId ? eventData.specialiteId : ''}
|
||||||
|
choices={eventData.specialities.map(specialite => ({
|
||||||
|
value: specialite.id,
|
||||||
|
label: specialite.nom
|
||||||
|
}))}
|
||||||
|
callback={(event) => {
|
||||||
|
const selectedSpecialityId = event.target.value;
|
||||||
|
const selectedSpeciality = eventData.specialities.find(specialite => specialite.id === parseInt(selectedSpecialityId, 10));
|
||||||
|
setEventData({
|
||||||
|
...eventData,
|
||||||
|
specialiteId: selectedSpecialityId,
|
||||||
|
color: selectedSpeciality?.codeCouleur || '#10b981'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
IconItem={BookOpen}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p>Aucune spécialité disponible pour cette classe.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dates */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Début
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
value={format(new Date(eventData.start), "yyyy-MM-dd'T'HH:mm")}
|
||||||
|
onChange={(e) => setEventData({ ...eventData, start: 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>
|
||||||
|
<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 */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Lieu
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={eventData.location || ''}
|
||||||
|
onChange={(e) => setEventData({ ...eventData, location: e.target.value })}
|
||||||
|
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Boutons */}
|
||||||
|
<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
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
|
||||||
|
>
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="px-4 py-2 bg-emerald-600 text-white rounded hover:bg-emerald-700"
|
||||||
|
>
|
||||||
|
{eventData.id ? 'Modifier' : 'Créer'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Pagination from '@/components/Pagination'; // Correction du chemin d'importation
|
import Pagination from '@/components/Pagination'; // Correction du chemin d'importatio,
|
||||||
|
|
||||||
const Table = ({ data, columns, renderCell, itemsPerPage = 0, currentPage, totalPages, onPageChange }) => {
|
const Table = ({ data, columns, renderCell, itemsPerPage = 0, currentPage, totalPages, onPageChange, onRowClick, selectedRows, isSelectable = false }) => {
|
||||||
const handlePageChange = (newPage) => {
|
const handlePageChange = (newPage) => {
|
||||||
onPageChange(newPage);
|
onPageChange(newPage);
|
||||||
};
|
};
|
||||||
@ -21,9 +21,17 @@ const Table = ({ data, columns, renderCell, itemsPerPage = 0, currentPage, total
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data?.map((row, rowIndex) => (
|
{data?.map((row, rowIndex) => (
|
||||||
<tr key={rowIndex} className={` ${rowIndex % 2 === 0 ? 'bg-emerald-50' : ''}`}>
|
<tr
|
||||||
|
key={rowIndex}
|
||||||
|
className={`
|
||||||
|
${isSelectable ? 'cursor-pointer' : ''}
|
||||||
|
${selectedRows?.includes(row.id) ? 'bg-emerald-500 text-white' : rowIndex % 2 === 0 ? 'bg-emerald-50' : ''}
|
||||||
|
${isSelectable ? 'hover:bg-emerald-600' : ''}
|
||||||
|
`}
|
||||||
|
onClick={() => isSelectable && onRowClick && onRowClick(row)}
|
||||||
|
>
|
||||||
{columns.map((column, colIndex) => (
|
{columns.map((column, colIndex) => (
|
||||||
<td key={colIndex} className="py-2 px-4 border-b border-gray-200 text-center text-sm text-gray-700">
|
<td key={colIndex} className={`py-2 px-4 border-b border-gray-200 text-center text-sm ${selectedRows?.includes(row.id) ? 'text-white' : 'text-gray-700'}`} >
|
||||||
{renderCell ? renderCell(row, column.name) : column.transform(row)}
|
{renderCell ? renderCell(row, column.name) : column.transform(row)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
|||||||
70
Front-End/src/context/ClasseFormContext.js
Normal file
70
Front-End/src/context/ClasseFormContext.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import React, { createContext, useState, useContext } from 'react';
|
||||||
|
import { useClasses } from '@/context/ClassesContext'
|
||||||
|
|
||||||
|
const ClasseFormContext = createContext();
|
||||||
|
|
||||||
|
export const useClasseForm = () => useContext(ClasseFormContext);
|
||||||
|
|
||||||
|
export const ClasseFormProvider = ({ children, initialClasse }) => {
|
||||||
|
|
||||||
|
const { getNiveauxLabels, selectedDays } = useClasses();
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState(() => {
|
||||||
|
const planning = initialClasse.planning || {};
|
||||||
|
const emploiDuTemps = planning.emploiDuTemps || {};
|
||||||
|
|
||||||
|
const dateDebutSemestre1 = emploiDuTemps.S1 ? emploiDuTemps.S1.DateDebut : '';
|
||||||
|
const dateFinSemestre1 = emploiDuTemps.S1 ? emploiDuTemps.S1.DateFin : '';
|
||||||
|
const dateDebutSemestre2 = emploiDuTemps.S2 ? emploiDuTemps.S2.DateDebut : '';
|
||||||
|
const dateFinSemestre2 = emploiDuTemps.S2 ? emploiDuTemps.S2.DateFin : '';
|
||||||
|
|
||||||
|
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 : '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
nom_ambiance: initialClasse.nom_ambiance || '',
|
||||||
|
tranche_age: initialClasse.tranche_age || '',
|
||||||
|
nombre_eleves: initialClasse.nombre_eleves || '',
|
||||||
|
langue_enseignement: initialClasse.langue_enseignement || 'Français',
|
||||||
|
annee_scolaire: initialClasse.annee_scolaire || '',
|
||||||
|
enseignants_ids: initialClasse.enseignants_ids || [],
|
||||||
|
planning_type: planning.type || 1,
|
||||||
|
plage_horaire: planning.plageHoraire || ['08:30', '17:30'],
|
||||||
|
jours_ouverture: planning.joursOuverture || [1, 2, 4, 5],
|
||||||
|
niveaux: initialClasse.niveaux || [],
|
||||||
|
niveaux_label: getNiveauxLabels(initialClasse.niveaux || []),
|
||||||
|
date_debut_semestre_1: dateDebutSemestre1,
|
||||||
|
date_fin_semestre_1: dateFinSemestre1,
|
||||||
|
date_debut_semestre_2: dateDebutSemestre2,
|
||||||
|
date_fin_semestre_2: dateFinSemestre2,
|
||||||
|
date_debut_trimestre_1: dateDebutTrimestre1,
|
||||||
|
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: [] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClasseFormContext.Provider value={{ formData, setFormData }}>
|
||||||
|
{children}
|
||||||
|
</ClasseFormContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
140
Front-End/src/context/ClassesContext.js
Normal file
140
Front-End/src/context/ClassesContext.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import React, { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
const ClassesContext = createContext();
|
||||||
|
|
||||||
|
export const useClasses = () => useContext(ClassesContext);
|
||||||
|
|
||||||
|
export const ClassesProvider = ({ children }) => {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
|
const schoolYears = [
|
||||||
|
{ value: '', label: 'Sélectionner une période' },
|
||||||
|
{ value: `${currentYear}-${currentYear + 1}`, label: `${currentYear}-${currentYear + 1}` },
|
||||||
|
{ value: `${currentYear + 1}-${currentYear + 2}`, label: `${currentYear + 1}-${currentYear + 2}` },
|
||||||
|
{ value: `${currentYear + 2}-${currentYear + 3}`, label: `${currentYear + 2}-${currentYear + 3}` },
|
||||||
|
];
|
||||||
|
|
||||||
|
const niveauxPremierCycle = [
|
||||||
|
{ id: 1, name: 'TPS', age: 2 },
|
||||||
|
{ id: 2, name: 'PS', age: 3 },
|
||||||
|
{ id: 3, name: 'MS', age: 4 },
|
||||||
|
{ id: 4, name: 'GS', age: 5 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const niveauxSecondCycle = [
|
||||||
|
{ id: 5, name: 'CP', age: 6 },
|
||||||
|
{ id: 6, name: 'CE1', age: 7 },
|
||||||
|
{ id: 7, name: 'CE2', age: 8 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const niveauxTroisiemeCycle = [
|
||||||
|
{ id: 8, name: 'CM1', age: 9 },
|
||||||
|
{ id: 9, name: 'CM2', age: 10 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const allNiveaux = [...niveauxPremierCycle, ...niveauxSecondCycle, ...niveauxTroisiemeCycle];
|
||||||
|
|
||||||
|
const typeEmploiDuTemps = [
|
||||||
|
{ id: 1, label: 'Annuel' },
|
||||||
|
{ id: 2, label: 'Semestriel' },
|
||||||
|
{ id: 3, label: 'Trimestriel' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const selectedDays = {
|
||||||
|
1: 'lundi',
|
||||||
|
2: 'mardi',
|
||||||
|
3: 'mercredi',
|
||||||
|
4: 'jeudi',
|
||||||
|
5: 'vendredi',
|
||||||
|
6: 'samedi',
|
||||||
|
7: 'dimanche'
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNiveauxLabels = (niveaux) => {
|
||||||
|
return niveaux.map(niveauId => {
|
||||||
|
const niveau = allNiveaux.find(n => n.id === niveauId);
|
||||||
|
return niveau ? niveau.name : niveauId;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateAgeToNiveaux = (minAge, maxAge) => {
|
||||||
|
if (minAge === null || isNaN(minAge)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return allNiveaux
|
||||||
|
.filter(({ age }) => age === minAge || (age >= minAge && (maxAge !== null && !isNaN(maxAge) && age < maxAge)))
|
||||||
|
.map(({ id }) => id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePlanning = (formData) => {
|
||||||
|
|
||||||
|
let updatedPlanning = { ...formData.planning };
|
||||||
|
|
||||||
|
const emploiDuTemps = formData.jours_ouverture.reduce((acc, dayId) => {
|
||||||
|
const dayName = selectedDays[dayId];
|
||||||
|
if (dayName) {
|
||||||
|
acc[dayName] = [];
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
if (formData.planning_type === 1) {
|
||||||
|
updatedPlanning = {
|
||||||
|
type: 1,
|
||||||
|
plageHoraire: formData.plage_horaire,
|
||||||
|
joursOuverture: formData.jours_ouverture,
|
||||||
|
emploiDuTemps
|
||||||
|
};
|
||||||
|
} else if (formData.planning_type === 2) {
|
||||||
|
updatedPlanning = {
|
||||||
|
type: 2,
|
||||||
|
plageHoraire: formData.plage_horaire,
|
||||||
|
joursOuverture: formData.jours_ouverture,
|
||||||
|
emploiDuTemps: {
|
||||||
|
S1: {
|
||||||
|
DateDebut: formData.date_debut_semestre_1,
|
||||||
|
DateFin: formData.date_fin_semestre_1,
|
||||||
|
...emploiDuTemps
|
||||||
|
},
|
||||||
|
S2: {
|
||||||
|
DateDebut: formData.date_debut_semestre_2,
|
||||||
|
DateFin: formData.date_fin_semestre_2,
|
||||||
|
...emploiDuTemps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (formData.planning_type === 3) {
|
||||||
|
updatedPlanning = {
|
||||||
|
type: 3,
|
||||||
|
plageHoraire: formData.plage_horaire,
|
||||||
|
joursOuverture: formData.jours_ouverture,
|
||||||
|
emploiDuTemps: {
|
||||||
|
T1: {
|
||||||
|
DateDebut: formData.date_debut_trimestre_1,
|
||||||
|
DateFin: formData.date_fin_trimestre_1,
|
||||||
|
...emploiDuTemps
|
||||||
|
},
|
||||||
|
T2: {
|
||||||
|
DateDebut: formData.date_debut_trimestre_2,
|
||||||
|
DateFin: formData.date_fin_trimestre_2,
|
||||||
|
...emploiDuTemps
|
||||||
|
},
|
||||||
|
T3: {
|
||||||
|
DateDebut: formData.date_debut_trimestre_3,
|
||||||
|
DateFin: formData.date_fin_trimestre_3,
|
||||||
|
...emploiDuTemps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedPlanning;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClassesContext.Provider value={{ schoolYears, getNiveauxLabels, generateAgeToNiveaux, niveauxPremierCycle, niveauxSecondCycle, niveauxTroisiemeCycle, typeEmploiDuTemps, updatePlanning }}>
|
||||||
|
{children}
|
||||||
|
</ClassesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -13,6 +13,9 @@ import { mockEvents, mockSchedules } from '@/data/mockData';
|
|||||||
const PlanningContext = createContext();
|
const PlanningContext = createContext();
|
||||||
|
|
||||||
export function PlanningProvider({ children }) {
|
export function PlanningProvider({ children }) {
|
||||||
|
// const [events, setEvents] = useState([]);
|
||||||
|
// const [schedules, setSchedules] = useState([]);
|
||||||
|
// const [selectedSchedule, setSelectedSchedule] = useState(null);
|
||||||
const [events, setEvents] = useState(mockEvents);
|
const [events, setEvents] = useState(mockEvents);
|
||||||
const [schedules, setSchedules] = useState(mockSchedules);
|
const [schedules, setSchedules] = useState(mockSchedules);
|
||||||
const [selectedSchedule, setSelectedSchedule] = useState(mockSchedules[0].id);
|
const [selectedSchedule, setSelectedSchedule] = useState(mockSchedules[0].id);
|
||||||
|
|||||||
18
Front-End/src/context/SpecialityFormContext.js
Normal file
18
Front-End/src/context/SpecialityFormContext.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React, { createContext, useState, useContext } from 'react';
|
||||||
|
|
||||||
|
const SpecialityFormContext = createContext();
|
||||||
|
|
||||||
|
export const useSpecialityForm = () => useContext(SpecialityFormContext);
|
||||||
|
|
||||||
|
export const SpecialityFormProvider = ({ children, initialSpeciality }) => {
|
||||||
|
const [formData, setFormData] = useState(() => ({
|
||||||
|
nom: initialSpeciality.nom || '',
|
||||||
|
codeCouleur: initialSpeciality.codeCouleur || '#FFFFFF',
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SpecialityFormContext.Provider value={{ formData, setFormData }}>
|
||||||
|
{children}
|
||||||
|
</SpecialityFormContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
22
Front-End/src/context/TeacherFormContext.js
Normal file
22
Front-End/src/context/TeacherFormContext.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React, { createContext, useState, useContext } from 'react';
|
||||||
|
|
||||||
|
const TeacherFormContext = createContext();
|
||||||
|
|
||||||
|
export const useTeacherForm = () => useContext(TeacherFormContext);
|
||||||
|
|
||||||
|
export const TeacherFormProvider = ({ children, initialTeacher }) => {
|
||||||
|
const [formData, setFormData] = useState(() => ({
|
||||||
|
nom: initialTeacher.nom || '',
|
||||||
|
prenom: initialTeacher.prenom || '',
|
||||||
|
mail: initialTeacher.mail || '',
|
||||||
|
specialites_ids: initialTeacher.specialites_ids || [],
|
||||||
|
profilAssocie_id: initialTeacher.profilAssocie_id || '',
|
||||||
|
droit: initialTeacher.droit || 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TeacherFormContext.Provider value={{ formData, setFormData }}>
|
||||||
|
{children}
|
||||||
|
</TeacherFormContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -19,12 +19,14 @@ export const BK_GESTIONINSCRIPTION_FICHESINSCRIPTION_URL = `${BASE_URL}/GestionI
|
|||||||
export const BK_GESTIONINSCRIPTION_FICHEINSCRIPTION_URL = `${BASE_URL}/GestionInscriptions/ficheInscription`
|
export const BK_GESTIONINSCRIPTION_FICHEINSCRIPTION_URL = `${BASE_URL}/GestionInscriptions/ficheInscription`
|
||||||
export const BK_GESTIONINSCRIPTION_RECUPEREDERNIER_RESPONSABLE_URL = `${BASE_URL}/GestionInscriptions/recupereDernierResponsable`
|
export const BK_GESTIONINSCRIPTION_RECUPEREDERNIER_RESPONSABLE_URL = `${BASE_URL}/GestionInscriptions/recupereDernierResponsable`
|
||||||
export const BK_GESTIONINSCRIPTION_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messagerie`
|
export const BK_GESTIONINSCRIPTION_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messagerie`
|
||||||
export const BK_GESTIONINSCRIPTION_SPECIALITES_URL = `${BASE_URL}/GestionEnseignants/specialites`
|
export const BK_GESTIONENSEIGNANTS_SPECIALITES_URL = `${BASE_URL}/GestionEnseignants/specialites`
|
||||||
export const BK_GESTIONINSCRIPTION_SPECIALITE_URL = `${BASE_URL}//GestionEnseignants/specialite`
|
export const BK_GESTIONENSEIGNANTS_SPECIALITE_URL = `${BASE_URL}//GestionEnseignants/specialite`
|
||||||
export const BK_GESTIONINSCRIPTION_CLASSES_URL = `${BASE_URL}/GestionEnseignants/classes`
|
export const BK_GESTIONENSEIGNANTS_CLASSES_URL = `${BASE_URL}/GestionEnseignants/classes`
|
||||||
export const BK_GESTIONINSCRIPTION_CLASSE_URL = `${BASE_URL}/GestionEnseignants/classe`
|
export const BK_GESTIONENSEIGNANTS_CLASSE_URL = `${BASE_URL}/GestionEnseignants/classe`
|
||||||
export const BK_GESTIONINSCRIPTION_TEACHERS_URL = `${BASE_URL}/GestionEnseignants/enseignants`
|
export const BK_GESTIONENSEIGNANTS_TEACHERS_URL = `${BASE_URL}/GestionEnseignants/enseignants`
|
||||||
export const BK_GESTIONINSCRIPTION_TEACHER_URL = `${BASE_URL}/GestionEnseignants/enseignant`
|
export const BK_GESTIONENSEIGNANTS_TEACHER_URL = `${BASE_URL}/GestionEnseignants/enseignant`
|
||||||
|
export const BK_GESTIONENSEIGNANTS_PLANNINGS_URL = `${BASE_URL}/GestionEnseignants/plannings`
|
||||||
|
export const BK_GESTIONENSEIGNANTS_PLANNING_URL = `${BASE_URL}/GestionEnseignants/planning`
|
||||||
|
|
||||||
export const BK_GET_CSRF = `${BASE_URL}/GestionLogin/csrf`
|
export const BK_GET_CSRF = `${BASE_URL}/GestionLogin/csrf`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user