From cb3f909fa4e7a53148cd13cf190c13b0670d35de Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Sun, 26 Jan 2025 15:43:11 +0100 Subject: [PATCH] =?UTF-8?q?refactor:=20Revue=20de=20la=20modale=20permetta?= =?UTF-8?q?nt=20de=20cr=C3=A9er=20un=20dossier=20d'inscription?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Back-End/School/serializers.py | 2 +- Back-End/School/views.py | 6 +- .../app/[locale]/admin/subscriptions/page.js | 3 +- Front-End/src/components/InputColorIcon.js | 28 - .../src/components/InputTextWithColorIcon.js | 34 + .../components/Inscription/InscriptionForm.js | 749 +++++++++--------- Front-End/src/components/Modal.js | 5 +- Front-End/src/components/Navigation.js | 30 + Front-End/src/components/StepTitle.js | 16 + .../Configuration/SpecialitiesSection.js | 302 +++++-- .../Structure/Configuration/SpecialityForm.js | 66 -- .../Configuration/StructureManagement.js | 5 +- .../Structure/Configuration/TeacherForm.js | 122 --- .../Configuration/TeachersSection.js | 487 ++++++++---- .../Tarification/DiscountsSection.js | 4 +- .../Structure/Tarification/FeesSection.js | 4 +- 16 files changed, 1049 insertions(+), 814 deletions(-) delete mode 100644 Front-End/src/components/InputColorIcon.js create mode 100644 Front-End/src/components/InputTextWithColorIcon.js create mode 100644 Front-End/src/components/Navigation.js create mode 100644 Front-End/src/components/StepTitle.js delete mode 100644 Front-End/src/components/Structure/Configuration/SpecialityForm.js delete mode 100644 Front-End/src/components/Structure/Configuration/TeacherForm.js diff --git a/Back-End/School/serializers.py b/Back-End/School/serializers.py index 7c63f88..39a69a6 100644 --- a/Back-End/School/serializers.py +++ b/Back-End/School/serializers.py @@ -76,7 +76,7 @@ class TeacherSerializer(serializers.ModelSerializer): return None def get_specialities_details(self, obj): - return [{'name': speciality.name, 'color_code': speciality.color_code} for speciality in obj.specialities.all()] + return [{'id': speciality.id, 'name': speciality.name, 'color_code': speciality.color_code} for speciality in obj.specialities.all()] class PlanningSerializer(serializers.ModelSerializer): class Meta: diff --git a/Back-End/School/views.py b/Back-End/School/views.py index 86bbc89..733d2f9 100644 --- a/Back-End/School/views.py +++ b/Back-End/School/views.py @@ -48,9 +48,9 @@ class SpecialityView(APIView): if speciality_serializer.is_valid(): speciality_serializer.save() - return JsonResponse(speciality_serializer.data, safe=False) + return JsonResponse(speciality_serializer.data, safe=False, status=201) - return JsonResponse(speciality_serializer.errors, safe=False) + return JsonResponse(speciality_serializer.errors, safe=False, status=400) def put(self, request, _id): speciality_data=JSONParser().parse(request) @@ -300,7 +300,7 @@ class FeeView(APIView): fee = Fee.objects.get(id=_id) except Fee.DoesNotExist: return JsonResponse({'error': 'No object found'}, status=404) - fee_serializer = FeeSerializer(fee, data=fee_data, partial=True) # Utilisation de partial=True + fee_serializer = FeeSerializer(fee, data=fee_data, partial=True) if fee_serializer.is_valid(): fee_serializer.save() return JsonResponse(fee_serializer.data, safe=False) diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index b07f616..6cb654f 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -805,8 +805,7 @@ const handleFileUpload = ({file, name, is_required, order}) => { ( { - return ( - <> -
- -
- - - - -
- {errorMsg &&

{errorMsg}

} -
- - ); -}; - -export default InputColorIcon; diff --git a/Front-End/src/components/InputTextWithColorIcon.js b/Front-End/src/components/InputTextWithColorIcon.js new file mode 100644 index 0000000..4953d7f --- /dev/null +++ b/Front-End/src/components/InputTextWithColorIcon.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { Palette } from 'lucide-react'; + +const InputTextWithColorIcon = ({ name, textValue, colorValue, onTextChange, onColorChange, placeholder, errorMsg }) => { + return ( +
+ +
+ + +
+
+ {errorMsg &&

{errorMsg}

} +
+ ); +}; + +export default InputTextWithColorIcon; \ No newline at end of file diff --git a/Front-End/src/components/Inscription/InscriptionForm.js b/Front-End/src/components/Inscription/InscriptionForm.js index adbe6d2..a33d607 100644 --- a/Front-End/src/components/Inscription/InscriptionForm.js +++ b/Front-End/src/components/Inscription/InscriptionForm.js @@ -5,9 +5,11 @@ import ToggleSwitch from '@/components/ToggleSwitch'; import Button from '@/components/Button'; import Table from '@/components/Table'; import FeesSection from '@/components/Structure/Tarification/FeesSection'; -import DiscountsSection from '../Structure/Tarification/DiscountsSection'; +import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection'; +import Navigation from '@/components/Navigation'; +import StepTitle from '@/components/StepTitle'; -const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit }) => { +const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit, currentStep }) => { const [formData, setFormData] = useState({ studentLastName: '', studentFirstName: '', @@ -22,12 +24,47 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r selectedTuitionFees: [] }); - const [step, setStep] = useState(1); + const [step, setStep] = useState(currentStep || 1); const [selectedStudent, setSelectedEleve] = useState(''); const [existingGuardians, setExistingGuardians] = useState([]); const [totalRegistrationAmount, setTotalRegistrationAmount] = useState(0); const [totalTuitionAmount, setTotalTuitionAmount] = useState(0); - const maxStep = 6 + + const stepTitles = { + 1: 'Nouvel élève', + 2: 'Nouveau Responsable', + 3: "Frais d'inscription", + 4: 'Frais de scolarité', + 5: 'Récapitulatif' + }; + + const steps = ['1', '2', '3', '4', 'Récap']; + + const isStep1Valid = formData.studentLastName && formData.studentFirstName; + const isStep2Valid = ( + (formData.responsableType === "new" && formData.guardianEmail.length > 0) || + (formData.responsableType === "existing" && formData.selectedGuardians.length > 0) + ); + const isStep3Valid = formData.selectedRegistrationFees.length > 0; + const isStep4Valid = formData.selectedTuitionFees.length > 0; + const isStep5Valid = isStep1Valid && isStep2Valid && isStep3Valid && isStep4Valid; + + const isStepValid = (stepNumber) => { + switch (stepNumber) { + case 1: + return isStep1Valid; + case 2: + return isStep2Valid; + case 3: + return isStep3Valid; + case 4: + return isStep4Valid; + case 5: + return isStep5Valid; + default: + return false; + } + }; useEffect(() => { // Calcul du montant total des frais d'inscription lors de l'initialisation @@ -39,6 +76,10 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r }, [registrationDiscounts, registrationFees]); + useEffect(() => { + setStep(currentStep || 1); + }, [currentStep]); + const handleToggleChange = () => { setFormData({ ...formData, autoMail: !formData.autoMail }); }; @@ -52,7 +93,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r }; const nextStep = () => { - if (step < maxStep) { + if (step < steps.length) { setStep(step + 1); } }; @@ -181,362 +222,358 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r return finalAmount.toFixed(2); }; - const isLabelAttenuated = (item) => { - return !formData.selectedRegistrationDiscounts.includes(parseInt(item.id)); - }; - - const isLabelFunction = (item) => { - return item.name + ' : ' + item.amount - }; - return ( -
- {step === 1 && ( -
-

Nouvel élève

- - -
- )} +
+ - {step === 2 && ( -
-

Responsable(s)

-
- - -
- {formData.responsableType === 'new' && ( - - )} + {step === 1 && ( +
+ + +
+ )} - {formData.responsableType === 'existing' && ( -
-
- - - - - - - - - {students.map((student, index) => ( - handleEleveSelection(student)} - > - - - - ))} - -
NomPrénom
{student.last_name}{student.first_name}
-
- {selectedStudent && ( -
-

Responsables associés à {selectedStudent.last_name} {selectedStudent.first_name} :

- {existingGuardians.map((guardian) => ( -
- -
- ))} -
- )} -
- )} -
- )} - - {step === 3 && ( -
-

Téléphone (optionnel)

- -
- )} - - {step === 4 && ( -
-

Frais d'inscription

- {registrationFees.length > 0 ? ( - <> -
- -
-

Réductions

-
- {registrationDiscounts.length > 0 ? ( - - ) : ( -

- Information - Aucune réduction n'a été créée sur les frais d'inscription. -

- )} -
- MONTANT TOTAL - }, - { - name: 'TOTAL', - transform: () => {totalRegistrationAmount} € - } - ]} - defaultTheme='bg-cyan-100' - /> - - ) : ( -

- Attention! - Aucun frais d'inscription n'a été créé. -

- )} - - - )} - - {step === 5 && ( -
-

Frais de scolarité

- {tuitionFees.length > 0 ? ( - <> -
- -
-

Réductions

-
- {tuitionDiscounts.length > 0 ? ( - - ) : ( -

- Information - Aucune réduction n'a été créée sur les frais de scolarité. -

- )} -
-
MONTANT TOTAL - }, - { - name: 'TOTAL', - transform: () => {totalTuitionAmount} € - } - ]} - defaultTheme='bg-cyan-100' - /> - - ) : ( -

- Attention! - Aucun frais de scolarité n'a été créé. -

- )} - - - )} - - {step === maxStep && ( -
-

Récapitulatif

-
-
-

Élève

-
- - - - - - - - - - - - -
NomPrénom
{formData.studentLastName}{formData.studentFirstName}
- -
-

Responsable(s)

- {formData.responsableType === 'new' && ( - - - - - - - - - - - - - -
EmailTéléphone
{formData.guardianEmail}{formData.guardianPhone}
- )} - {formData.responsableType === 'existing' && selectedStudent && ( -
-

Associé(s) à : {selectedStudent.nom} {selectedStudent.prenom}

- - - - - - - - - - {existingGuardians.filter(guardian => formData.selectedGuardians.includes(guardian.id)).map((guardian) => ( - - - - - - ))} - -
NomPrénomEmail
{guardian.last_name}{guardian.first_name}{guardian.email}
-
- )} -
-
-
- -
-
- )} - -
- {step > 1 && ( -
+ )} + +
+ {step > 1 && ( +
+
); } diff --git a/Front-End/src/components/Modal.js b/Front-End/src/components/Modal.js index 509aaa5..d84aaaf 100644 --- a/Front-End/src/components/Modal.js +++ b/Front-End/src/components/Modal.js @@ -1,13 +1,12 @@ import * as Dialog from '@radix-ui/react-dialog'; -import Button from '@/components/Button'; -const Modal = ({ isOpen, setIsOpen, title, ContentComponent, size }) => { +const Modal = ({ isOpen, setIsOpen, title, ContentComponent }) => { return ( -
+
{title} diff --git a/Front-End/src/components/Navigation.js b/Front-End/src/components/Navigation.js new file mode 100644 index 0000000..9ad5ba2 --- /dev/null +++ b/Front-End/src/components/Navigation.js @@ -0,0 +1,30 @@ +import React from 'react'; +import StepTitle from '@/components/StepTitle'; + +const Navigation = ({ steps, step, setStep, isStepValid, stepTitles }) => { + return ( +
+
+ {steps.map((stepLabel, index) => { + const isCurrentStep = step === index + 1; + + return ( +
+
+ {stepLabel} +
+
setStep(index + 1)} + style={{ transform: 'translateY(-50%)' }} + >
+
+ ); + })} +
+ +
+ ); +}; + +export default Navigation; \ No newline at end of file diff --git a/Front-End/src/components/StepTitle.js b/Front-End/src/components/StepTitle.js new file mode 100644 index 0000000..e27410d --- /dev/null +++ b/Front-End/src/components/StepTitle.js @@ -0,0 +1,16 @@ +import React from 'react'; + +const StepTitle = ({ title }) => { + return ( +
+
+
+
+
+
{title}
+
+
+ ); +}; + +export default StepTitle; \ No newline at end of file diff --git a/Front-End/src/components/Structure/Configuration/SpecialitiesSection.js b/Front-End/src/components/Structure/Configuration/SpecialitiesSection.js index b4149bc..06cc45c 100644 --- a/Front-End/src/components/Structure/Configuration/SpecialitiesSection.js +++ b/Front-End/src/components/Structure/Configuration/SpecialitiesSection.js @@ -1,93 +1,231 @@ -import { Trash2, MoreVertical, Edit3, Plus } from 'lucide-react'; +import { Plus, Trash2, Edit3, Check, X, BookOpen } from 'lucide-react'; import { useState } from 'react'; import Table from '@/components/Table'; -import DropdownMenu from '@/components/DropdownMenu'; -import Modal from '@/components/Modal'; -import SpecialityForm from '@/components/Structure/Configuration/SpecialityForm'; -import { SpecialityFormProvider } from '@/context/SpecialityFormContext'; +import Popup from '@/components/Popup'; +import InputTextWithColorIcon from '@/components/InputTextWithColorIcon'; +import { DndProvider, useDrag } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; -const SpecialitiesSection = ({ specialities, handleCreate, handleEdit, handleDelete }) => { +const ItemTypes = { + SPECIALITY: 'speciality', +}; - const [isOpen, setIsOpen] = useState(false); - const [editingSpeciality, setEditingSpeciality] = useState(null); - - const openEditModal = (speciality) => { - setIsOpen(true); - setEditingSpeciality(speciality); - } - - const closeEditModal = () => { - setIsOpen(false); - setEditingSpeciality(null); - }; - - const handleModalSubmit = (updatedData) => { - if (editingSpeciality) { - handleEdit(editingSpeciality.id, updatedData); - } else { - handleCreate(updatedData); - } - closeEditModal(); - }; +const SpecialityItem = ({ speciality }) => { + const [{ isDragging }, drag] = useDrag(() => ({ + type: ItemTypes.SPECIALITY, + item: { id: speciality.id, name: speciality.name }, + collect: (monitor) => ({ + isDragging: !!monitor.isDragging(), + }), + })); return ( -
-
-

Gestion des spécialités

- -
-
- ( -
- {row.name.toUpperCase()} -
- ) - }, - { name: 'DATE DE CREATION', transform: (row) => row.updated_date_formatted }, - { name: 'ACTIONS', transform: (row) => ( - } - items={[ - { label: 'Modifier', icon:Edit3, onClick: () => openEditModal(row) }, - { label: 'Supprimer', icon: Trash2, onClick: () => handleDelete(row.id) } - ] - } - 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" - /> - )} - ]} - data={specialities} - /> - - {isOpen && ( - - ( - - )} - /> - - )} +
+ {speciality.name}
); }; +const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, handleEdit, handleDelete }) => { + + const [newSpeciality, setNewSpeciality] = useState(null); + const [editingSpeciality, setEditingSpeciality] = useState(null); + const [formData, setFormData] = useState({}); + const [localErrors, setLocalErrors] = useState({}); + const [popupVisible, setPopupVisible] = useState(false); + const [popupMessage, setPopupMessage] = useState(""); + + const handleAddSpeciality = () => { + setNewSpeciality({ id: Date.now(), name: '', color_code: '' }); + }; + + const handleRemoveSpeciality = (id) => { + handleDelete(id) + .then(() => { + setSpecialities(prevSpecialities => prevSpecialities.filter(speciality => speciality.id !== id)); + }) + .catch(error => { + console.error(error); + }); + }; + + const handleSaveNewSpeciality = () => { + if ( + newSpeciality.name) { + handleCreate(newSpeciality) + .then((createdSpeciality) => { + setSpecialities([createdSpeciality, ...specialities]); + setNewSpeciality(null); + setLocalErrors({}); + }) + .catch(error => { + if (error && typeof error === 'object') { + setLocalErrors(error); + } else { + console.error(error); + } + }); + } else { + setPopupMessage("Tous les champs doivent être remplis et valides"); + setPopupVisible(true); + } + }; + + const handleUpdateSpeciality = (id, updatedSpeciality) => { + if ( + updatedSpeciality.name) { + handleEdit(id, updatedSpeciality) + .then((updatedSpeciality) => { + setSpecialities(specialities.map(speciality => speciality.id === id ? updatedSpeciality : speciality)); + setEditingSpeciality(null); + setLocalErrors({}); + }) + .catch(error => { + if (error && typeof error === 'object') { + setLocalErrors(error); + } else { + console.error(error); + } + }); + } else { + setPopupMessage("Tous les champs doivent être remplis et valides"); + setPopupVisible(true); + } + }; + + const handleChange = (e) => { + const { name, value } = e.target; + let parsedValue = value; + if (name.includes('_color')) { + parsedValue = value; + } + + const fieldName = name.includes('_color') ? 'color_code' : name; + if (editingSpeciality) { + setFormData((prevData) => ({ + ...prevData, + [fieldName]: parsedValue, + })); + } else if (newSpeciality) { + setNewSpeciality((prevData) => ({ + ...prevData, + [fieldName]: parsedValue, + })); + } + }; + + const renderSpecialityCell = (speciality, column) => { + const isEditing = editingSpeciality === speciality.id; + const isCreating = newSpeciality && newSpeciality.id === speciality.id; + const currentData = isEditing ? formData : newSpeciality; + + if (isEditing || isCreating) { + switch (column) { + case 'LIBELLE': + return ( + + ); + case 'ACTIONS': + return ( +
+ + +
+ ); + default: + return null; + } + } else { + switch (column) { + case 'LIBELLE': + return ( + + ); + case 'MISE A JOUR': + return speciality.updated_date_formatted; + case 'ACTIONS': + return ( +
+ + +
+ ); + default: + return null; + } + } + }; + + const columns = [ + { name: 'LIBELLE', label: 'Libellé' }, + { name: 'MISE A JOUR', label: 'Date mise à jour' }, + { name: 'ACTIONS', label: 'Actions' } + ]; + + return ( + +
+
+
+ +

Spécialités

+
+ +
+
+ setPopupVisible(false)} + onCancel={() => setPopupVisible(false)} + uniqueConfirmButton={true} + /> + + + ); +}; + export default SpecialitiesSection; diff --git a/Front-End/src/components/Structure/Configuration/SpecialityForm.js b/Front-End/src/components/Structure/Configuration/SpecialityForm.js deleted file mode 100644 index 39293eb..0000000 --- a/Front-End/src/components/Structure/Configuration/SpecialityForm.js +++ /dev/null @@ -1,66 +0,0 @@ -import { useState } from 'react'; -import { BookOpen, Palette } from 'lucide-react'; -import InputTextIcon from '@/components/InputTextIcon'; -import InputColorIcon from '@/components/InputColorIcon'; -import Button from '@/components/Button'; -import { useSpecialityForm } from '@/context/SpecialityFormContext'; - -const SpecialityForm = ({ onSubmit, isNew }) => { - const { formData, setFormData } = useSpecialityForm(); - - const handleChange = (e) => { - const { name, value } = e.target; - - setFormData((prevState) => ({ - ...prevState, - [name]: value, - })); - }; - - const handleSubmit = (e) => { - e.preventDefault(); - onSubmit(formData); - }; - - return ( -
-
- -
-
- -
-
-
-
- ); -}; - -export default SpecialityForm; diff --git a/Front-End/src/components/Structure/Configuration/StructureManagement.js b/Front-End/src/components/Structure/Configuration/StructureManagement.js index 0e187ba..e5fda63 100644 --- a/Front-End/src/components/Structure/Configuration/StructureManagement.js +++ b/Front-End/src/components/Structure/Configuration/StructureManagement.js @@ -9,7 +9,7 @@ const StructureManagement = ({ specialities, setSpecialities, teachers, setTeach return (
-
+
handleDelete(`${BE_SCHOOL_SPECIALITY_URL}`, id, setSpecialities)} />
-
+
handleCreate(`${BE_SCHOOL_TEACHER_URL}`, newData, setTeachers)} handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_TEACHER_URL}`, id, updatedData, setTeachers)} diff --git a/Front-End/src/components/Structure/Configuration/TeacherForm.js b/Front-End/src/components/Structure/Configuration/TeacherForm.js deleted file mode 100644 index 72d1510..0000000 --- a/Front-End/src/components/Structure/Configuration/TeacherForm.js +++ /dev/null @@ -1,122 +0,0 @@ -import React, { useState } from 'react'; -import { GraduationCap, Mail, BookOpen, Check } from 'lucide-react'; -import InputTextIcon from '@/components/InputTextIcon'; -import Button from '@/components/Button'; -import CheckBoxList from '@/components/CheckBoxList'; -import ToggleSwitch from '@/components/ToggleSwitch' -import { useTeacherForm } from '@/context/TeacherFormContext'; - -const TeacherForm = ({ onSubmit, isNew, specialities }) => { - const { formData, setFormData } = useTeacherForm(); - - const handleToggleChange = () => { - setFormData({ ...formData, droit: 1-formData.droit.id }); - }; - - const handleChange = (e) => { - const target = e.target || e.currentTarget; - const { name, value, type, checked } = target; - - 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.name}`; - }; - - const isLabelAttenuated = (item) => { - return !formData.specialities.includes(parseInt(item.id)); - }; - - return ( -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
- - ); -}; - -export default TeacherForm; diff --git a/Front-End/src/components/Structure/Configuration/TeachersSection.js b/Front-End/src/components/Structure/Configuration/TeachersSection.js index 5c4d4cb..2fadfa7 100644 --- a/Front-End/src/components/Structure/Configuration/TeachersSection.js +++ b/Front-End/src/components/Structure/Configuration/TeachersSection.js @@ -1,162 +1,359 @@ -import { Trash2, MoreVertical, Edit3, Plus } from 'lucide-react'; -import { useState } from 'react'; +import React, { useState, useEffect } from 'react'; +import { Plus, Edit3, Trash2, GraduationCap, Check, X } from 'lucide-react'; import Table from '@/components/Table'; -import DropdownMenu from '@/components/DropdownMenu'; -import Modal from '@/components/Modal'; -import TeacherForm from '@/components/Structure/Configuration/TeacherForm'; -import useCsrfToken from '@/hooks/useCsrfToken'; -import { TeacherFormProvider } from '@/context/TeacherFormContext'; +import Popup from '@/components/Popup'; +import InputTextIcon from '@/components/InputTextIcon'; +import ToggleSwitch from '@/components/ToggleSwitch'; import { createProfile, updateProfile } from '@/app/lib/authAction'; +import useCsrfToken from '@/hooks/useCsrfToken'; +import { DndProvider, useDrag, useDrop } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; -const TeachersSection = ({ teachers, specialities , handleCreate, handleEdit, handleDelete}) => { +const ItemTypes = { + SPECIALITY: 'speciality', +}; - const [isOpen, setIsOpen] = useState(false); - const [editingTeacher, setEditingTeacher] = useState(null); +const SpecialitiesDropZone = ({ teacher, handleSpecialitiesChange, specialities, isEditing }) => { + const [localSpecialities, setLocalSpecialities] = useState(teacher.specialities_details || []); - const csrfToken = useCsrfToken(); + useEffect(() => { + setLocalSpecialities(teacher.specialities_details || []); + }, [teacher.specialities_details]); - const openEditModal = (teacher) => { - setIsOpen(true); - setEditingTeacher(teacher); - } + useEffect(() => { + handleSpecialitiesChange(localSpecialities.map(speciality => speciality.id)); + }, [localSpecialities]); - const closeEditModal = () => { - setIsOpen(false); - setEditingTeacher(null); - }; - - const handleModalSubmit = (updatedData) => { - if (editingTeacher) { - // Modification du profil - const data = { - email: updatedData.email, - username: updatedData.email, - droit:updatedData.droit + const [{ isOver }, drop] = useDrop(() => ({ + accept: ItemTypes.SPECIALITY, + drop: (item) => { + const specialityDetails = specialities.find(speciality => speciality.id === item.id); + if (!localSpecialities.some(speciality => speciality.id === item.id)) { + setLocalSpecialities(prevSpecialities => [ + ...prevSpecialities, + { id: item.id, name: specialityDetails.name, color_code: specialityDetails.color_code } + ]); } + }, + collect: (monitor) => ({ + isOver: !!monitor.isOver(), + }), + })); - updateProfile(updatedData.associated_profile,data,csrfToken) - .then(response => { - console.log('Success:', response); - console.log('UpdateData:', updatedData); - handleEdit(editingTeacher.id, updatedData); - }) - .catch(error => { - console.error('Error fetching data:', error); - error = error.errorMessage; - console.log(error); - }); - } else { - // Création d'un profil associé à l'adresse mail du responsable saisie - // Le profil est inactif - const data = { - email: updatedData.email, - password: 'Provisoire01!', - username: updatedData.email, - is_active: 1, // On rend le profil actif : on considère qu'au moment de la configuration de l'école un abonnement a été souscrit - droit:updatedData.droit - } - createProfile(data,csrfToken) - .then(response => { - console.log('Success:', response); - console.log('UpdateData:', updatedData); - if (response.id) { - let idProfil = response.id; - updatedData.associated_profile = idProfil; - handleCreate(updatedData); - } - }) - .catch(error => { - console.error('Error fetching data:', error); - error = error.errorMessage; - console.log(error); - }); - } - closeEditModal(); + const handleRemoveSpeciality = (id) => { + setLocalSpecialities(prevSpecialities => { + const updatedSpecialities = prevSpecialities.filter(speciality => speciality.id !== id); + return updatedSpecialities; + }); }; return ( -
-
-

Gestion des enseignants

- -
-
-
row.last_name }, - { name: 'PRENOM', transform: (row) => row.first_name }, - { name: 'MAIL', transform: (row) => row.email }, - { - name: 'SPÉCIALITÉS', - transform: (row) => ( -
- {row.specialities_details.map((speciality,index) => ( - - {speciality.name} - - ))} -
- ) - }, - { - name: 'TYPE PROFIL', - transform: (row) => { - if (row.associated_profile) { - const badgeClass = row.droit.label === 'ECOLE' ? 'bg-blue-100 text-blue-600' : 'bg-red-100 text-red-600'; - return ( -
- - {row.droit.label} - -
- ); - } else { - return Non définie; - } - } - }, - { name: 'DATE DE CREATION', transform: (row) => row.updated_date_formatted }, - { name: 'ACTIONS', transform: (row) => ( - } - items={[ - { label: 'Modifier', icon:Edit3, onClick: () => openEditModal(row) }, - { label: 'Supprimer', icon: Trash2, onClick: () => handleDelete(row.id) } - ] - } - 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" - /> - )} - ]} - data={teachers} - /> - - {isOpen && ( - - ( - - )} - /> - - )} +
+ {localSpecialities.map((speciality, index) => ( +
+ + {speciality.name} + + {isEditing && ( + + )} +
+ ))}
); }; +const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, handleEdit, handleDelete }) => { + const csrfToken = useCsrfToken(); + const [editingTeacher, setEditingTeacher] = useState(null); + const [newTeacher, setNewTeacher] = useState(null); + const [formData, setFormData] = useState({}); + const [localErrors, setLocalErrors] = useState({}); + const [popupVisible, setPopupVisible] = useState(false); + const [popupMessage, setPopupMessage] = useState(""); + + const handleAddTeacher = () => { + setNewTeacher({ id: Date.now(), last_name: '', first_name: '', email: '', specialities: [], droit: 0 }); + setFormData({ last_name: '', first_name: '', email: '', specialities: [], droit: 0 }); + }; + + const handleRemoveTeacher = (id) => { + handleDelete(id) + .then(() => { + setTeachers(prevTeachers => prevTeachers.filter(teacher => teacher.id !== id)); + }) + .catch(error => { + console.error(error); + }); + }; + + const handleSaveNewTeacher = () => { + if (formData.last_name && formData.first_name && formData.email) { + const data = { + email: formData.email, + password: 'Provisoire01!', + username: formData.email, + is_active: 1, + droit: formData.droit, + }; + createProfile(data, csrfToken) + .then(response => { + console.log('Success:', response); + if (response.id) { + let idProfil = response.id; + newTeacher.associated_profile = idProfil; + handleCreate(newTeacher) + .then((createdTeacher) => { + setTeachers([createdTeacher, ...teachers]); + setNewTeacher(null); + setLocalErrors({}); + }); + } + }) + .catch(error => { + if (error && typeof error === 'object') { + setLocalErrors(error); + } else { + console.error(error); + } + }); + } else { + setPopupMessage("Tous les champs doivent être remplis et valides"); + setPopupVisible(true); + } + }; + + const handleUpdateTeacher = (id, updatedData) => { + console.log('UpdatedData:', updatedData); + const data = { + email: updatedData.email, + username: updatedData.email, + droit: updatedData.droit.id, + }; + updateProfile(updatedData.associated_profile, data, csrfToken) + .then(response => { + console.log('Success:', response); + handleEdit(id, updatedData) + .then((updatedTeacher) => { + setTeachers(prevTeachers => prevTeachers.map(teacher => teacher.id === id ? { ...teacher, ...updatedTeacher } : teacher)); + setEditingTeacher(null); + setFormData({}); + }) + }) + .catch(error => { + console.error(error); + }); + }; + + const handleChange = (e) => { + const { name, value } = e.target; + let parsedValue = value; + + if (editingTeacher) { + setFormData((prevData) => ({ + ...prevData, + [name]: parsedValue, + })); + } else if (newTeacher) { + setNewTeacher((prevData) => ({ + ...prevData, + [name]: parsedValue, + })); + setFormData((prevData) => ({ + ...prevData, + [name]: parsedValue, + })); + } + }; + + const handleSpecialitiesChange = (selectedSpecialities) => { + if (editingTeacher) { + setFormData((prevData) => ({ + ...prevData, + specialities: selectedSpecialities, + })); + } else if (newTeacher) { + setNewTeacher((prevData) => ({ + ...prevData, + specialities: selectedSpecialities, + })); + setFormData((prevData) => ({ + ...prevData, + specialities: selectedSpecialities, + })); + } + }; + + const renderTeacherCell = (teacher, column) => { + const isEditing = editingTeacher === teacher.id; + const isCreating = newTeacher && newTeacher.id === teacher.id; + const currentData = isEditing ? formData : newTeacher; + + if (isEditing || isCreating) { + switch (column) { + case 'NOM': + return ( + + ); + case 'PRENOM': + return ( + + ); + case 'EMAIL': + return ( + + ); + case 'SPECIALITES': + return ( + + ); + // case 'PROFIL': + // return ( + // + // ); + case 'ACTIONS': + return ( +
+ + +
+ ); + default: + return null; + } + } else { + switch (column) { + case 'NOM': + return teacher.last_name; + case 'PRENOM': + return teacher.first_name; + case 'EMAIL': + return teacher.email; + case 'SPECIALITES': + return ( + + ); + case 'PROFIL': + if (teacher.associated_profile) { + const badgeClass = teacher.droit.label === 'ECOLE' ? 'bg-blue-100 text-blue-600' : 'bg-red-100 text-red-600'; + return ( +
+ + {teacher.droit.label} + +
+ ); + } else { + return Non définie; + }; + case 'MISE A JOUR': + return teacher.updated_date_formatted; + case 'ACTIONS': + return ( +
+ + +
+ ); + default: + return null; + } + } + }; + + const columns = [ + { name: 'NOM', label: 'Nom' }, + { name: 'PRENOM', label: 'Prénom' }, + { name: 'EMAIL', label: 'Email' }, + { name: 'SPECIALITES', label: 'Spécialités' }, + { name: 'PROFIL', label: 'Profil' }, + { name: 'MISE A JOUR', label: 'Mise à jour' }, + { name: 'ACTIONS', label: 'Actions' } + ]; + + return ( + +
+
+
+ +

Enseignants

+
+ +
+
+ setPopupVisible(false)} + onCancel={() => setPopupVisible(false)} + uniqueConfirmButton={true} + /> + + + ); +}; + export default TeachersSection; diff --git a/Front-End/src/components/Structure/Tarification/DiscountsSection.js b/Front-End/src/components/Structure/Tarification/DiscountsSection.js index 6ebab1c..47a0a7c 100644 --- a/Front-End/src/components/Structure/Tarification/DiscountsSection.js +++ b/Front-End/src/components/Structure/Tarification/DiscountsSection.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Plus, Trash, Edit3, Check, X, Percent, EuroIcon, Tag } from 'lucide-react'; +import { Plus, Trash2, Edit3, Check, X, Percent, EuroIcon, Tag } from 'lucide-react'; import Table from '@/components/Table'; import InputTextIcon from '@/components/InputTextIcon'; import Popup from '@/components/Popup'; @@ -181,7 +181,7 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h onClick={() => handleRemoveDiscount(discount.id)} className="text-red-500 hover:text-red-700" > - + ); diff --git a/Front-End/src/components/Structure/Tarification/FeesSection.js b/Front-End/src/components/Structure/Tarification/FeesSection.js index 25669ab..2bec1d0 100644 --- a/Front-End/src/components/Structure/Tarification/FeesSection.js +++ b/Front-End/src/components/Structure/Tarification/FeesSection.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Plus, Trash, Edit3, Check, X, EyeOff, Eye, CreditCard, BookOpen } from 'lucide-react'; +import { Plus, Trash2, Edit3, Check, X, EyeOff, Eye, CreditCard, BookOpen } from 'lucide-react'; import Table from '@/components/Table'; import InputTextIcon from '@/components/InputTextIcon'; import Popup from '@/components/Popup'; @@ -190,7 +190,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl onClick={() => handleRemoveFee(fee.id)} className="text-red-500 hover:text-red-700" > - + );