diff --git a/Back-End/GestionEnseignants/models.py b/Back-End/GestionEnseignants/models.py index ca32ef6..3ad84aa 100644 --- a/Back-End/GestionEnseignants/models.py +++ b/Back-End/GestionEnseignants/models.py @@ -1,5 +1,8 @@ from django.db import models from GestionLogin.models import Profil +from django.db.models import JSONField +from django.db.models.signals import post_save +from django.dispatch import receiver class Specialite(models.Model): nom = models.CharField(max_length=100) @@ -13,7 +16,7 @@ class Enseignant(models.Model): nom = models.CharField(max_length=100) prenom = models.CharField(max_length=100) mail = models.EmailField(unique=True) - specialite = models.ForeignKey(Specialite, on_delete=models.SET_NULL, null=True, blank=True, related_name='enseignants') + specialites = models.ManyToManyField(Specialite, related_name='enseignants') profilAssocie = models.ForeignKey(Profil, on_delete=models.CASCADE, null=True, blank=True) def __str__(self): @@ -26,9 +29,20 @@ class Classe(models.Model): langue_enseignement = models.CharField(max_length=255) annee_scolaire = models.CharField(max_length=9) dateCreation = models.DateTimeField(auto_now_add=True) - specialites = models.ManyToManyField(Specialite, related_name='classes') - enseignant_principal = models.ForeignKey(Enseignant, on_delete=models.SET_NULL, null=True, blank=True, related_name='classes_principal') - + enseignants = models.ManyToManyField(Enseignant, related_name='classes') + def __str__(self): return self.nom_ambiance +class Planning(models.Model): + classe = models.OneToOneField(Classe, on_delete=models.SET_NULL, null=True, blank=True, related_name='planning') + emploiDuTemps = JSONField(default=list) + + def __str__(self): + return f'Planning de {self.classe.nom_ambiance}' + +@receiver(post_save, sender=Classe) +def create_planning(sender, instance, created, **kwargs): + if created and not hasattr(instance, 'planning'): + Planning.objects.create(classe=instance) + diff --git a/Back-End/GestionEnseignants/serializers.py b/Back-End/GestionEnseignants/serializers.py index 4872c6f..e22c713 100644 --- a/Back-End/GestionEnseignants/serializers.py +++ b/Back-End/GestionEnseignants/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import Enseignant, Specialite, Classe +from .models import Enseignant, Specialite, Classe, Planning from GestionInscriptions.models import FicheInscription from GestionInscriptions.serializers import EleveSerializer from GestionLogin.serializers import ProfilSerializer @@ -21,40 +21,93 @@ class SpecialiteSerializer(serializers.ModelSerializer): return local_time.strftime("%d-%m-%Y %H:%M") -class ClasseSerializer(serializers.ModelSerializer): +class EnseignantDetailSerializer(serializers.ModelSerializer): specialites = SpecialiteSerializer(many=True, read_only=True) - specialites_ids = serializers.PrimaryKeyRelatedField(queryset=Specialite.objects.all(), many=True, source='specialites') - dateCreation_formattee = serializers.SerializerMethodField() - enseignant_principal = serializers.SerializerMethodField() - enseignant_principal_id = serializers.PrimaryKeyRelatedField(queryset=Enseignant.objects.all(), source='enseignant_principal', write_only=False, read_only=False) - eleves = serializers.SerializerMethodField() class Meta: - model = Classe - fields = ['id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement', 'specialites', 'specialites_ids', 'enseignant_principal', 'enseignant_principal_id', 'annee_scolaire', 'dateCreation', 'dateCreation_formattee', 'eleves'] + model = Enseignant + fields = ['id', 'nom', 'prenom', 'mail', 'specialites'] - def get_enseignant_principal(self, obj): - from .serializers import EnseignantDetailSerializer - if obj.enseignant_principal: - return EnseignantDetailSerializer(obj.enseignant_principal).data - return None +class EnseignantSerializer(serializers.ModelSerializer): + specialites = SpecialiteSerializer(many=True, read_only=True) + specialites_ids = serializers.PrimaryKeyRelatedField(queryset=Specialite.objects.all(), many=True, source='specialites') + profilAssocie_id = serializers.PrimaryKeyRelatedField(queryset=Profil.objects.all(), source='profilAssocie', write_only=False, read_only=False) + classes_principal = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='classes') + profilAssocie = ProfilSerializer(read_only=True) + DroitLabel = serializers.SerializerMethodField() + DroitValue = serializers.SerializerMethodField() + + class Meta: + model = Enseignant + fields = ['id', 'nom', 'prenom', 'mail', 'specialites', 'specialites_ids', 'classes_principal', 'profilAssocie', 'profilAssocie_id', 'DroitLabel', 'DroitValue'] def create(self, validated_data): - specialites_data = validated_data.pop('specialites', []) - classe = Classe.objects.create(**validated_data) - classe.specialites.set(specialites_data) - return classe + specialites_data = validated_data.pop('specialites', None) + profilAssocie = validated_data.pop('profilAssocie', None) + enseignant = Enseignant.objects.create(**validated_data) + enseignant.specialites.set(specialites_data) + if profilAssocie: + enseignant.profilAssocie = profilAssocie + enseignant.save() + return enseignant def update(self, instance, validated_data): specialites_data = validated_data.pop('specialites', []) + instance.nom = validated_data.get('nom', instance.nom) + instance.prenom = validated_data.get('prenom', instance.prenom) + instance.mail = validated_data.get('mail', instance.mail) + instance.profilAssocie = validated_data.get('profilAssocie', instance.profilAssocie) + instance.save() + instance.specialites.set(specialites_data) + return instance + + def get_DroitLabel(self, obj): + return obj.profilAssocie.get_droit_display() if obj.profilAssocie else None + + def get_DroitValue(self, obj): + return obj.profilAssocie.droit if obj.profilAssocie else None + +class PlanningSerializer(serializers.ModelSerializer): + classe_id = serializers.PrimaryKeyRelatedField(queryset=Classe.objects.all(), source='classe') + + class Meta: + model = Planning + fields = ['id', 'classe', 'classe_id', 'emploiDuTemps'] + +class ClasseSerializer(serializers.ModelSerializer): + dateCreation_formattee = serializers.SerializerMethodField() + enseignants = EnseignantSerializer(many=True, read_only=True) + enseignants_ids = serializers.PrimaryKeyRelatedField(queryset=Enseignant.objects.all(), many=True, source='enseignants') + eleves = serializers.SerializerMethodField() + planning = serializers.SerializerMethodField() + + class Meta: + model = Classe + fields = ['id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement', 'enseignants', 'enseignants_ids', 'annee_scolaire', 'dateCreation', 'dateCreation_formattee', 'eleves', 'planning'] + + def create(self, validated_data): + planning_data = validated_data.pop('planning', None) + enseignants_data = validated_data.pop('enseignants', []) + classe = Classe.objects.create(**validated_data) + classe.enseignants.set(enseignants_data) + if planning_data and not hasattr(classe, 'planning'): + Planning.objects.create(classe=classe, **planning_data) + return classe + + def update(self, instance, validated_data): + planning_data = validated_data.pop('planning', None) + enseignants_data = validated_data.pop('enseignants', []) instance.nom_ambiance = validated_data.get('nom_ambiance', instance.nom_ambiance) instance.tranche_age = validated_data.get('tranche_age', instance.tranche_age) instance.nombre_eleves = validated_data.get('nombre_eleves', instance.nombre_eleves) instance.langue_enseignement = validated_data.get('langue_enseignement', instance.langue_enseignement) instance.annee_scolaire = validated_data.get('annee_scolaire', instance.annee_scolaire) - instance.enseignant_principal = validated_data.get('enseignant_principal', instance.enseignant_principal) instance.save() - instance.specialites.set(specialites_data) + instance.enseignants.set(enseignants_data) + if planning_data: + planning = instance.planning + planning.emploiDuTemps = planning_data.get('emploiDuTemps', planning.emploiDuTemps) + planning.save() return instance def get_dateCreation_formattee(self, obj): @@ -73,39 +126,8 @@ class ClasseSerializer(serializers.ModelSerializer): filtered_eleves.append(eleve) return EleveSerializer(filtered_eleves, many=True, read_only=True).data -class EnseignantSerializer(serializers.ModelSerializer): - specialite = SpecialiteSerializer(read_only=True) - specialite_id = serializers.PrimaryKeyRelatedField(queryset=Specialite.objects.all(), source='specialite', write_only=False, read_only=False) - profilAssocie_id = serializers.PrimaryKeyRelatedField(queryset=Profil.objects.all(), source='profilAssocie', write_only=False, read_only=False) - classes_principal = ClasseSerializer(many=True, read_only=True) - profilAssocie = ProfilSerializer(read_only=True) - DroitLabel = serializers.SerializerMethodField() - DroitValue = serializers.SerializerMethodField() - - class Meta: - model = Enseignant - fields = ['id', 'nom', 'prenom', 'mail', 'specialite', 'specialite_id', 'classes_principal', 'profilAssocie', 'profilAssocie_id', 'DroitLabel', 'DroitValue'] - - def create(self, validated_data): - specialite = validated_data.pop('specialite', None) - profilAssocie = validated_data.pop('profilAssocie', None) - enseignant = Enseignant.objects.create(**validated_data) - if specialite: - enseignant.specialite = specialite - if profilAssocie: - enseignant.profilAssocie = profilAssocie - enseignant.save() - return enseignant - - def get_DroitLabel(self, obj): - return obj.profilAssocie.get_droit_display() if obj.profilAssocie else None - - def get_DroitValue(self, obj): - return obj.profilAssocie.droit if obj.profilAssocie else None - -class EnseignantDetailSerializer(serializers.ModelSerializer): - specialite = SpecialiteSerializer(read_only=True) - - class Meta: - model = Enseignant - fields = ['id', 'nom', 'prenom', 'mail', 'specialite'] + def get_planning(self, obj): + from .serializers import PlanningSerializer + if obj.planning: + return PlanningSerializer(obj.planning).data + return None diff --git a/Back-End/GestionEnseignants/views.py b/Back-End/GestionEnseignants/views.py index 4e4054e..af00734 100644 --- a/Back-End/GestionEnseignants/views.py +++ b/Back-End/GestionEnseignants/views.py @@ -81,6 +81,8 @@ class SpecialitesView(APIView): specialitesList = bdd.getAllObjects(Specialite) specialites_serializer = SpecialiteSerializer(specialitesList, many=True) + return JsonResponse(specialites_serializer.data, safe=False) + return JsonResponse(specialite_serializer.errors, safe=False) diff --git a/Front-End/src/components/CheckBoxList.js b/Front-End/src/components/CheckBoxList.js new file mode 100644 index 0000000..470f6f3 --- /dev/null +++ b/Front-End/src/components/CheckBoxList.js @@ -0,0 +1,43 @@ +const CheckBoxList = ({ items, formData, handleChange, fieldName, label, icon: Icon, className, itemLabelFunc = (item) => item.name }) => { + const handleCheckboxChange = (e) => { + handleChange(e); + }; + + return ( +
+ +
+ {items.map(item => ( +
+ + +
+ ))} +
+
+ ); + }; + + export default CheckBoxList; + \ No newline at end of file diff --git a/Front-End/src/components/ClassForm.js b/Front-End/src/components/ClassForm.js index 45df9b7..4646f78 100644 --- a/Front-End/src/components/ClassForm.js +++ b/Front-End/src/components/ClassForm.js @@ -3,7 +3,7 @@ import Slider from '@/components/Slider' import InputTextIcon from '@/components/InputTextIcon'; import Button from '@/components/Button'; import SelectChoice from '@/components/SelectChoice'; -import RadioList from '@/components/RadioList'; +import CheckBoxList from '@/components/CheckBoxList'; import { Users, Maximize2, Globe, Calendar, GraduationCap } from 'lucide-react'; const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => { @@ -19,17 +19,32 @@ const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => { nombre_eleves: classe.nombre_eleves || '', langue_enseignement: classe.langue_enseignement || 'Français', annee_scolaire: classe.annee_scolaire || '', - specialites_ids: classe.specialites_ids || [], - enseignant_principal_id: classe.enseignant_principal_id || null, + enseignants_ids: classe.enseignants_ids || [], + planning: classe.planning || [] }); const handleChange = (e) => { - const { name, value, type } = e.target; - const newValue = type === 'radio' ? parseInt(value) : value; - setFormData((prevState) => ({ - ...prevState, - [name]: newValue, - })); + 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) => { @@ -48,21 +63,33 @@ const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => { }; const handleSubmit = () => { - onSubmit(formData, isNew); + const updatedFormData = { + ...formData, + planning: formData.planning || [] + }; + onSubmit(updatedFormData, isNew); }; - const handleSpecialityChange = (id) => { - setFormData(prevFormData => { - const specialites_ids = prevFormData.specialites_ids.includes(id) - ? prevFormData.specialites_ids.filter(specialityId => specialityId !== id) - : [...prevFormData.specialites_ids, id]; - return { ...prevFormData, specialites_ids }; - }); - }; - - const getTeacherLabel = (teacher) => { - return `${teacher.nom} ${teacher.prenom} (${teacher.specialite.nom})`; - }; + const getTeacherLabel = (teacher) => { + return ( +
+ {teacher.nom} {teacher.prenom} - + {teacher.specialites.map(specialite => ( + + {specialite.nom} + + + ))} +
+ ); + }; return (
@@ -121,56 +148,28 @@ const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => { className="w-full" /> -
- -
- {specialities.map(speciality => ( -
- handleSpecialityChange(speciality.id)} - className="h-4 w-4 text-emerald-600 border-gray-300 rounded focus:ring-emerald-500" - /> - -
- ))} -
-
-
diff --git a/Front-End/src/components/ClassesSection.js b/Front-End/src/components/ClassesSection.js index 366e08c..3a81697 100644 --- a/Front-End/src/components/ClassesSection.js +++ b/Front-End/src/components/ClassesSection.js @@ -64,28 +64,25 @@ const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleE { name: 'LANGUE D\'ENSEIGNEMENT', transform: (row) => row.langue_enseignement }, { name: 'ANNEE SCOLAIRE', transform: (row) => row.annee_scolaire }, { - name: 'SPECIALITES', + name: 'ENSEIGNANTS', transform: (row) => (
- {row.specialites.map(specialite => ( - + {row.enseignants.map(teacher => ( +
+ {teacher.nom} {teacher.prenom} + {teacher.specialites.map(specialite => ( + + ))} +
))}
) }, - { - name: 'ENSEIGNANT PRINCIPAL', - transform: (row) => { - return row.enseignant_principal - ? `${row.enseignant_principal.nom || ''} ${row.enseignant_principal.prenom || ''}` - : Non assigné; - } - }, { name: 'ACTIONS', transform: (row) => ( } diff --git a/Front-End/src/components/TeacherForm.js b/Front-End/src/components/TeacherForm.js index ed48f51..d529d8e 100644 --- a/Front-End/src/components/TeacherForm.js +++ b/Front-End/src/components/TeacherForm.js @@ -1,8 +1,8 @@ import React, { useState } from 'react'; -import { GraduationCap, Mail, BookOpen } from 'lucide-react'; +import { GraduationCap, Mail, BookOpen, Check } from 'lucide-react'; import InputTextIcon from '@/components/InputTextIcon'; import Button from '@/components/Button'; -import RadioList from '@/components/RadioList'; +import CheckBoxList from '@/components/CheckBoxList'; import ToggleSwitch from '@/components/ToggleSwitch' const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => { @@ -11,30 +11,47 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => { nom: teacher.nom || '', prenom: teacher.prenom || '', mail: teacher.mail || '', - specialite_id: teacher.specialite_id || '', - classes: teacher.classes || [], - profilAssocie_id: teacher.profilAssocie_id || [], + specialites_ids: teacher.specialites_ids || [], + profilAssocie_id:teacher.profilAssocie_id || '', droit: teacher.DroitValue || 0 }); const handleToggleChange = () => { - console.log('new value : ', 1-formData.droit) setFormData({ ...formData, droit: 1-formData.droit }); }; const handleChange = (e) => { - const { name, value, type } = e.target; - const newValue = type === 'radio' ? parseInt(value, 10) : value; - setFormData((prevState) => ({ - ...prevState, - [name]: newValue, - })); + 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 handleSubmit = () => { onSubmit(formData, isNew); }; + const getSpecialityLabel = (speciality) => { + return `${speciality.nom}`; + }; + return (
@@ -71,14 +88,15 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => { />
-
@@ -92,12 +110,12 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
diff --git a/Front-End/src/components/TeachersSection.js b/Front-End/src/components/TeachersSection.js index 2204b6d..582c6af 100644 --- a/Front-End/src/components/TeachersSection.js +++ b/Front-End/src/components/TeachersSection.js @@ -114,20 +114,19 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe { name: 'NOM', transform: (row) => row.nom }, { name: 'PRENOM', transform: (row) => row.prenom }, { name: 'MAIL', transform: (row) => row.mail }, - { name: 'SPECIALITE', - transform: (row) => { - return row.specialite - ? + { name: 'SPECIALITES', + transform: (row) => (
+ {row.specialites.map(specialite => ( -
- : Non définie; - } + ))} + + ) }, { name: 'TYPE PROFIL', transform: (row) => { diff --git a/Front-End/src/components/ToggleSwitch.js b/Front-End/src/components/ToggleSwitch.js index 284a559..f13147b 100644 --- a/Front-End/src/components/ToggleSwitch.js +++ b/Front-End/src/components/ToggleSwitch.js @@ -1,25 +1,35 @@ -import React from 'react'; +import { useRef } from 'react'; const ToggleSwitch = ({ label, checked, onChange }) => { - return ( -
- -
- - -
-
- ); + const inputRef = useRef(null); + + const handleChange = (e) => { + onChange(e); + if (inputRef.current) { + inputRef.current.blur(); // Remove focus + } + }; + + return ( +
+ +
+ + +
+
+ ); }; export default ToggleSwitch;