mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
refactor: Revue de la modale permettant de créer un dossier
d'inscription
This commit is contained in:
@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Palette } from 'lucide-react';
|
||||
|
||||
const InputColorIcon = ({ name, label, value, onChange, errorMsg, className }) => {
|
||||
return (
|
||||
<>
|
||||
<div className={`mb-4 ${className}`}>
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">{label}</label>
|
||||
<div className={`mt-1 flex items-stretch border border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} hover:border-gray-400 focus-within:border-gray-500`}>
|
||||
<span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
||||
<Palette className="w-5 h-5" />
|
||||
</span>
|
||||
<input
|
||||
type="color"
|
||||
id={name}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="flex-1 h-8 w-full sm:text-sm border-none focus:ring-0 outline-none rounded-r-md cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputColorIcon;
|
||||
34
Front-End/src/components/InputTextWithColorIcon.js
Normal file
34
Front-End/src/components/InputTextWithColorIcon.js
Normal file
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { Palette } from 'lucide-react';
|
||||
|
||||
const InputTextWithColorIcon = ({ name, textValue, colorValue, onTextChange, onColorChange, placeholder, errorMsg }) => {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="text"
|
||||
name={name}
|
||||
value={textValue}
|
||||
onChange={onTextChange}
|
||||
placeholder={placeholder}
|
||||
className={`flex-1 px-2 py-1 border ${errorMsg ? 'border-red-500' : 'border-gray-300'} rounded-md`}
|
||||
/>
|
||||
<div className="relative flex items-center space-x-2">
|
||||
<input
|
||||
type="color"
|
||||
name={`${name}_color`}
|
||||
value={colorValue}
|
||||
onChange={onColorChange}
|
||||
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
||||
/>
|
||||
<Palette className="w-6 h-6 text-gray-500" />
|
||||
<div
|
||||
className="w-6 h-6 rounded-full border border-gray-300"
|
||||
style={{ backgroundColor: colorValue }}
|
||||
></div>
|
||||
</div>
|
||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputTextWithColorIcon;
|
||||
@ -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 (
|
||||
<div className="space-y-4 mt-8">
|
||||
{step === 1 && (
|
||||
<div>
|
||||
<h2 className="text-l font-bold mb-4">Nouvel élève</h2>
|
||||
<InputTextIcon
|
||||
name="studentLastName"
|
||||
type="text"
|
||||
IconItem={User}
|
||||
placeholder="Nom de l'élève"
|
||||
value={formData.studentLastName}
|
||||
onChange={handleChange}
|
||||
className="w-full"
|
||||
/>
|
||||
<InputTextIcon
|
||||
name="studentFirstName"
|
||||
type="text"
|
||||
IconItem={User}
|
||||
placeholder="Prénom de l'élève"
|
||||
value={formData.studentFirstName}
|
||||
onChange={handleChange}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-4 mt-6">
|
||||
<Navigation
|
||||
steps={steps}
|
||||
step={step}
|
||||
setStep={setStep}
|
||||
isStepValid={isStepValid}
|
||||
stepTitles={stepTitles}
|
||||
/>
|
||||
|
||||
{step === 2 && (
|
||||
<div className="mt-6">
|
||||
<h2 className="text-l font-bold mb-4">Responsable(s)</h2>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<label className="flex items-center space-x-3">
|
||||
<input
|
||||
type="radio"
|
||||
name="responsableType"
|
||||
value="new"
|
||||
checked={formData.responsableType === 'new'}
|
||||
onChange={handleChange}
|
||||
className="form-radio h-3 w-3 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 checked:h-3 checked:w-3"
|
||||
/>
|
||||
<span className="text-gray-900">Nouveau Responsable</span>
|
||||
</label>
|
||||
<label className="flex items-center space-x-3">
|
||||
<input
|
||||
type="radio"
|
||||
name="responsableType"
|
||||
value="existing"
|
||||
checked={formData.responsableType === 'existing'}
|
||||
onChange={handleChange}
|
||||
className="form-radio h-3 w-3 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 checked:h-3 checked:w-3"
|
||||
/>
|
||||
<span className="text-gray-900">Responsable Existant</span>
|
||||
</label>
|
||||
</div>
|
||||
{formData.responsableType === 'new' && (
|
||||
<InputTextIcon
|
||||
name="guardianEmail"
|
||||
type="email"
|
||||
IconItem={Mail}
|
||||
placeholder="Email du responsable"
|
||||
value={formData.guardianEmail}
|
||||
onChange={handleChange}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
)}
|
||||
{step === 1 && (
|
||||
<div className="mt-6">
|
||||
<InputTextIcon
|
||||
name="studentLastName"
|
||||
type="text"
|
||||
IconItem={User}
|
||||
placeholder="Nom de l'élève"
|
||||
value={formData.studentLastName}
|
||||
onChange={handleChange}
|
||||
className="w-full"
|
||||
/>
|
||||
<InputTextIcon
|
||||
name="studentFirstName"
|
||||
type="text"
|
||||
IconItem={User}
|
||||
placeholder="Prénom de l'élève"
|
||||
value={formData.studentFirstName}
|
||||
onChange={handleChange}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formData.responsableType === 'existing' && (
|
||||
<div className="mt-4">
|
||||
<div className="mt-4" style={{ maxHeight: '300px', overflowY: 'auto' }}>
|
||||
<table className="min-w-full bg-white border">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 border">Nom</th>
|
||||
<th className="px-4 py-2 border">Prénom</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{students.map((student, index) => (
|
||||
<tr
|
||||
key={student.id}
|
||||
className={`cursor-pointer ${selectedStudent && selectedStudent.id === student.id ? 'bg-emerald-600 text-white' : index % 2 === 0 ? 'bg-emerald-100' : ''}`}
|
||||
onClick={() => handleEleveSelection(student)}
|
||||
>
|
||||
<td className="px-4 py-2 border">{student.last_name}</td>
|
||||
<td className="px-4 py-2 border">{student.first_name}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{selectedStudent && (
|
||||
<div className="mt-4">
|
||||
<h3 className="font-bold">Responsables associés à {selectedStudent.last_name} {selectedStudent.first_name} :</h3>
|
||||
{existingGuardians.map((guardian) => (
|
||||
<div key={guardian.id}>
|
||||
<label className="flex items-center space-x-3 mt-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.selectedGuardians.includes(guardian.id)}
|
||||
className="form-checkbox h-5 w-5 text-emerald-600"
|
||||
onChange={() => handleResponsableSelection(guardian.id)}
|
||||
/>
|
||||
<span className="text-gray-900">
|
||||
{guardian.last_name && guardian.first_name ? `${guardian.last_name} ${guardian.first_name}` : `${guardian.email}`}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 3 && (
|
||||
<div className="mt-6">
|
||||
<h2 className="text-l font-bold mb-4">Téléphone (optionnel)</h2>
|
||||
<InputTextIcon
|
||||
name="guardianPhone"
|
||||
type="tel"
|
||||
IconItem={Phone}
|
||||
placeholder="Numéro de téléphone"
|
||||
value={formData.guardianPhone}
|
||||
onChange={handleChange}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 4 && (
|
||||
<div>
|
||||
<h2 className="text-l font-bold mb-4">Frais d'inscription</h2>
|
||||
{registrationFees.length > 0 ? (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<FeesSection
|
||||
fees={registrationFees}
|
||||
type={0}
|
||||
subscriptionMode={true}
|
||||
selectedFees={formData.selectedRegistrationFees}
|
||||
handleFeeSelection={handleRegistrationFeeSelection}
|
||||
/>
|
||||
</div>
|
||||
<h2 className="text-l font-bold mb-4">Réductions</h2>
|
||||
<div className="mb-4">
|
||||
{registrationDiscounts.length > 0 ? (
|
||||
<DiscountsSection
|
||||
discounts={registrationDiscounts}
|
||||
type={0}
|
||||
subscriptionMode={true}
|
||||
selectedDiscounts={formData.selectedRegistrationDiscounts}
|
||||
handleDiscountSelection={handleRegistrationDiscountSelection}
|
||||
/>
|
||||
) : (
|
||||
<p className="bg-orange-100 border border-orange-400 text-orange-700 px-4 py-3 rounded relative" role="alert">
|
||||
<strong className="font-bold">Information</strong>
|
||||
<span className="block sm:inline"> Aucune réduction n'a été créée sur les frais d'inscription.</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Table
|
||||
data={[ {id: 1}]}
|
||||
columns={[
|
||||
{
|
||||
name: 'LIBELLE',
|
||||
transform: () => <span>MONTANT TOTAL</span>
|
||||
},
|
||||
{
|
||||
name: 'TOTAL',
|
||||
transform: () => <b>{totalRegistrationAmount} €</b>
|
||||
}
|
||||
]}
|
||||
defaultTheme='bg-cyan-100'
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<p className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
||||
<strong className="font-bold">Attention!</strong>
|
||||
<span className="block sm:inline"> Aucun frais d'inscription n'a été créé.</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
)}
|
||||
|
||||
{step === 5 && (
|
||||
<div>
|
||||
<h2 className="text-l font-bold mb-4">Frais de scolarité</h2>
|
||||
{tuitionFees.length > 0 ? (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<FeesSection
|
||||
fees={tuitionFees}
|
||||
type={1}
|
||||
subscriptionMode={true}
|
||||
selectedFees={formData.selectedTuitionFees}
|
||||
handleFeeSelection={handleTuitionFeeSelection}
|
||||
/>
|
||||
</div>
|
||||
<h2 className="text-l font-bold mb-4">Réductions</h2>
|
||||
<div className="mb-4">
|
||||
{tuitionDiscounts.length > 0 ? (
|
||||
<DiscountsSection
|
||||
discounts={tuitionDiscounts}
|
||||
type={1}
|
||||
subscriptionMode={true}
|
||||
selectedDiscounts={formData.selectedTuitionDiscounts}
|
||||
handleDiscountSelection={handleTuitionDiscountSelection}
|
||||
/>
|
||||
) : (
|
||||
<p className="bg-orange-100 border border-orange-400 text-orange-700 px-4 py-3 rounded relative" role="alert">
|
||||
<strong className="font-bold">Information</strong>
|
||||
<span className="block sm:inline"> Aucune réduction n'a été créée sur les frais de scolarité.</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Table
|
||||
data={[ {id: 1}]}
|
||||
columns={[
|
||||
{
|
||||
name: 'LIBELLE',
|
||||
transform: () => <span>MONTANT TOTAL</span>
|
||||
},
|
||||
{
|
||||
name: 'TOTAL',
|
||||
transform: () => <b>{totalTuitionAmount} €</b>
|
||||
}
|
||||
]}
|
||||
defaultTheme='bg-cyan-100'
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<p className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
||||
<strong className="font-bold">Attention!</strong>
|
||||
<span className="block sm:inline"> Aucun frais de scolarité n'a été créé.</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
)}
|
||||
|
||||
{step === maxStep && (
|
||||
<div>
|
||||
<h2 className="text-l font-bold mb-4">Récapitulatif</h2>
|
||||
<div className="space-y-4">
|
||||
<section>
|
||||
<h3 className="font-bold">Élève</h3>
|
||||
<table className="min-w-full bg-white border">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 border">Nom</th>
|
||||
<th className="px-4 py-2 border">Prénom</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="px-4 py-2 border">{formData.studentLastName}</td>
|
||||
<td className="px-4 py-2 border">{formData.studentFirstName}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section>
|
||||
<h3 className="font-bold">Responsable(s)</h3>
|
||||
{formData.responsableType === 'new' && (
|
||||
<table className="min-w-full bg-white border">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 border">Email</th>
|
||||
<th className="px-4 py-2 border">Téléphone</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="px-4 py-2 border">{formData.guardianEmail}</td>
|
||||
<td className="px-4 py-2 border">{formData.guardianPhone}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
{formData.responsableType === 'existing' && selectedStudent && (
|
||||
<div>
|
||||
<p>Associé(s) à : {selectedStudent.nom} {selectedStudent.prenom}</p>
|
||||
<table className="min-w-full bg-white border">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 border">Nom</th>
|
||||
<th className="px-4 py-2 border">Prénom</th>
|
||||
<th className="px-4 py-2 border">Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{existingGuardians.filter(guardian => formData.selectedGuardians.includes(guardian.id)).map((guardian) => (
|
||||
<tr key={guardian.id}>
|
||||
<td className="px-4 py-2 border">{guardian.last_name}</td>
|
||||
<td className="px-4 py-2 border">{guardian.first_name}</td>
|
||||
<td className="px-4 py-2 border">{guardian.email}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
<div className='mt-4'>
|
||||
<ToggleSwitch
|
||||
label="Envoi automatique"
|
||||
checked={formData.autoMail}
|
||||
onChange={handleToggleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end mt-4 space-x-4">
|
||||
{step > 1 && (
|
||||
<Button text="Précédent"
|
||||
onClick={prevStep}
|
||||
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md shadow-sm hover:bg-gray-400 focus:outline-none"
|
||||
secondary
|
||||
name="Previous" />
|
||||
)}
|
||||
{step < maxStep ? (
|
||||
<Button text="Suivant"
|
||||
onClick={nextStep}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
(step === 1 && (!formData.studentLastName || !formData.studentFirstName)) ||
|
||||
(step === 2 && formData.responsableType === "new" && !formData.guardianEmail) ||
|
||||
(step === 2 && formData.responsableType === "existing" && formData.selectedGuardians.length === 0)
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
disabled={(step === 1 && (!formData.studentLastName || !formData.studentFirstName)) ||
|
||||
(step === 2 && formData.responsableType === "new" && !formData.guardianEmail) ||
|
||||
(step === 2 && formData.responsableType === "existing" && formData.selectedGuardians.length === 0)
|
||||
}
|
||||
primary
|
||||
name="Next" />
|
||||
) : (
|
||||
<Button text="Créer"
|
||||
onClick={submit}
|
||||
className="px-4 py-2 bg-emerald-500 text-white rounded-md shadow-sm hover:bg-emerald-600 focus:outline-none"
|
||||
primary
|
||||
name="Create" />
|
||||
)}
|
||||
{step === 2 && (
|
||||
<div className="mt-6">
|
||||
<InputTextIcon
|
||||
name="guardianPhone"
|
||||
type="tel"
|
||||
IconItem={Phone}
|
||||
placeholder="Numéro de téléphone (optionnel)"
|
||||
value={formData.guardianPhone}
|
||||
onChange={handleChange}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
<div className="flex flex-col space-y-4 mt-6">
|
||||
<label className="flex items-center space-x-3">
|
||||
<input
|
||||
type="radio"
|
||||
name="responsableType"
|
||||
value="new"
|
||||
checked={formData.responsableType === 'new'}
|
||||
onChange={handleChange}
|
||||
className="form-radio h-3 w-3 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 checked:h-3 checked:w-3"
|
||||
/>
|
||||
<span className="text-gray-900">Nouveau Responsable</span>
|
||||
</label>
|
||||
<label className="flex items-center space-x-3">
|
||||
<input
|
||||
type="radio"
|
||||
name="responsableType"
|
||||
value="existing"
|
||||
checked={formData.responsableType === 'existing'}
|
||||
onChange={handleChange}
|
||||
className="form-radio h-3 w-3 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 checked:h-3 checked:w-3"
|
||||
/>
|
||||
<span className="text-gray-900">Responsable Existant</span>
|
||||
</label>
|
||||
</div>
|
||||
{formData.responsableType === 'new' && (
|
||||
<InputTextIcon
|
||||
name="guardianEmail"
|
||||
type="email"
|
||||
IconItem={Mail}
|
||||
placeholder="Email du responsable"
|
||||
value={formData.guardianEmail}
|
||||
onChange={handleChange}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
)}
|
||||
|
||||
{formData.responsableType === 'existing' && (
|
||||
<div className="mt-4">
|
||||
<div className="mt-4" style={{ maxHeight: '300px', overflowY: 'auto' }}>
|
||||
<table className="min-w-full bg-white border">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 border">Nom</th>
|
||||
<th className="px-4 py-2 border">Prénom</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{students.map((student, index) => (
|
||||
<tr
|
||||
key={student.id}
|
||||
className={`cursor-pointer ${selectedStudent && selectedStudent.id === student.id ? 'bg-emerald-600 text-white' : index % 2 === 0 ? 'bg-emerald-100' : ''}`}
|
||||
onClick={() => handleEleveSelection(student)}
|
||||
>
|
||||
<td className="px-4 py-2 border">{student.last_name}</td>
|
||||
<td className="px-4 py-2 border">{student.first_name}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{selectedStudent && (
|
||||
<div className="mt-4">
|
||||
<h3 className="font-bold">Responsables associés à {selectedStudent.last_name} {selectedStudent.first_name} :</h3>
|
||||
{existingGuardians.map((guardian) => (
|
||||
<div key={guardian.id}>
|
||||
<label className="flex items-center space-x-3 mt-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.selectedGuardians.includes(guardian.id)}
|
||||
className="form-checkbox h-5 w-5 text-emerald-600"
|
||||
onChange={() => handleResponsableSelection(guardian.id)}
|
||||
/>
|
||||
<span className="text-gray-900">
|
||||
{guardian.last_name && guardian.first_name ? `${guardian.last_name} ${guardian.first_name}` : `${guardian.email}`}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 3 && (
|
||||
<div>
|
||||
{registrationFees.length > 0 ? (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<FeesSection
|
||||
fees={registrationFees}
|
||||
type={0}
|
||||
subscriptionMode={true}
|
||||
selectedFees={formData.selectedRegistrationFees}
|
||||
handleFeeSelection={handleRegistrationFeeSelection}
|
||||
/>
|
||||
</div>
|
||||
<StepTitle title='Réductions' />
|
||||
<div className="mb-4">
|
||||
{registrationDiscounts.length > 0 ? (
|
||||
<DiscountsSection
|
||||
discounts={registrationDiscounts}
|
||||
type={0}
|
||||
subscriptionMode={true}
|
||||
selectedDiscounts={formData.selectedRegistrationDiscounts}
|
||||
handleDiscountSelection={handleRegistrationDiscountSelection}
|
||||
/>
|
||||
) : (
|
||||
<p className="bg-orange-100 border border-orange-400 text-orange-700 px-4 py-3 rounded relative" role="alert">
|
||||
<strong className="font-bold">Information</strong>
|
||||
<span className="block sm:inline"> Aucune réduction n'a été créée sur les frais d'inscription.</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<StepTitle title='Montant total' />
|
||||
<Table
|
||||
data={[ {id: 1}]}
|
||||
columns={[
|
||||
{
|
||||
name: 'LIBELLE',
|
||||
transform: () => <span>MONTANT TOTAL</span>
|
||||
},
|
||||
{
|
||||
name: 'TOTAL',
|
||||
transform: () => <b>{totalRegistrationAmount} €</b>
|
||||
}
|
||||
]}
|
||||
defaultTheme='bg-cyan-100'
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<p className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
||||
<strong className="font-bold">Attention!</strong>
|
||||
<span className="block sm:inline"> Aucun frais d'inscription n'a été créé.</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 4 && (
|
||||
<div>
|
||||
{tuitionFees.length > 0 ? (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<FeesSection
|
||||
fees={tuitionFees}
|
||||
type={1}
|
||||
subscriptionMode={true}
|
||||
selectedFees={formData.selectedTuitionFees}
|
||||
handleFeeSelection={handleTuitionFeeSelection}
|
||||
/>
|
||||
</div>
|
||||
<StepTitle title='Réductions' />
|
||||
<div className="mb-4">
|
||||
{tuitionDiscounts.length > 0 ? (
|
||||
<DiscountsSection
|
||||
discounts={tuitionDiscounts}
|
||||
type={1}
|
||||
subscriptionMode={true}
|
||||
selectedDiscounts={formData.selectedTuitionDiscounts}
|
||||
handleDiscountSelection={handleTuitionDiscountSelection}
|
||||
/>
|
||||
) : (
|
||||
<p className="bg-orange-100 border border-orange-400 text-orange-700 px-4 py-3 rounded relative" role="alert">
|
||||
<strong className="font-bold">Information</strong>
|
||||
<span className="block sm:inline"> Aucune réduction n'a été créée sur les frais de scolarité.</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<StepTitle title='Montant total' />
|
||||
<Table
|
||||
data={[ {id: 1}]}
|
||||
columns={[
|
||||
{
|
||||
name: 'LIBELLE',
|
||||
transform: () => <span>MONTANT TOTAL</span>
|
||||
},
|
||||
{
|
||||
name: 'TOTAL',
|
||||
transform: () => <b>{totalTuitionAmount} €</b>
|
||||
}
|
||||
]}
|
||||
defaultTheme='bg-cyan-100'
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<p className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
||||
<strong className="font-bold">Attention!</strong>
|
||||
<span className="block sm:inline"> Aucun frais de scolarité n'a été créé.</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === steps.length && (
|
||||
<div>
|
||||
<div className="space-y-4">
|
||||
<section>
|
||||
<h3 className="font-bold">Élève</h3>
|
||||
<table className="min-w-full bg-white border">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 border">Nom</th>
|
||||
<th className="px-4 py-2 border">Prénom</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="px-4 py-2 border">{formData.studentLastName}</td>
|
||||
<td className="px-4 py-2 border">{formData.studentFirstName}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section>
|
||||
<h3 className="font-bold">Responsable(s)</h3>
|
||||
{formData.responsableType === 'new' && (
|
||||
<table className="min-w-full bg-white border">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 border">Email</th>
|
||||
<th className="px-4 py-2 border">Téléphone</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="px-4 py-2 border">{formData.guardianEmail}</td>
|
||||
<td className="px-4 py-2 border">{formData.guardianPhone}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
{formData.responsableType === 'existing' && selectedStudent && (
|
||||
<div>
|
||||
<p>Associé(s) à : {selectedStudent.nom} {selectedStudent.prenom}</p>
|
||||
<table className="min-w-full bg-white border">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 border">Nom</th>
|
||||
<th className="px-4 py-2 border">Prénom</th>
|
||||
<th className="px-4 py-2 border">Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{existingGuardians.filter(guardian => formData.selectedGuardians.includes(guardian.id)).map((guardian) => (
|
||||
<tr key={guardian.id}>
|
||||
<td className="px-4 py-2 border">{guardian.last_name}</td>
|
||||
<td className="px-4 py-2 border">{guardian.first_name}</td>
|
||||
<td className="px-4 py-2 border">{guardian.email}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
<div className='mt-4'>
|
||||
<ToggleSwitch
|
||||
label="Envoi automatique"
|
||||
checked={formData.autoMail}
|
||||
onChange={handleToggleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end mt-4 space-x-4">
|
||||
{step > 1 && (
|
||||
<Button text="Précédent"
|
||||
onClick={prevStep}
|
||||
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md shadow-sm hover:bg-gray-400 focus:outline-none"
|
||||
secondary
|
||||
name="Previous" />
|
||||
)}
|
||||
{step < steps.length ? (
|
||||
<Button text="Suivant"
|
||||
onClick={nextStep}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
(
|
||||
(step === 1 && !isStep1Valid) ||
|
||||
(step === 2 && !isStep2Valid) ||
|
||||
(step === 3 && !isStep3Valid) ||
|
||||
(step === 4 && !isStep4Valid)
|
||||
)
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
disabled={
|
||||
(
|
||||
(step === 1 && !isStep1Valid) ||
|
||||
(step === 2 && !isStep2Valid) ||
|
||||
(step === 3 && !isStep3Valid) ||
|
||||
(step === 4 && !isStep4Valid)
|
||||
)
|
||||
}
|
||||
primary
|
||||
name="Next" />
|
||||
) : (
|
||||
<Button text="Valider"
|
||||
onClick={submit}
|
||||
className="px-4 py-2 bg-emerald-500 text-white rounded-md shadow-sm hover:bg-emerald-600 focus:outline-none"
|
||||
primary
|
||||
name="Create" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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 (
|
||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||
<Dialog.Content className="fixed inset-0 flex items-center justify-center">
|
||||
<div className={`inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:p-6 ${size ? size : 'sm:w-full' }`} style={{ minWidth: '300px', width: 'auto' }}>
|
||||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle" style={{ width: '600px', maxWidth: '90%' }}>
|
||||
<div className="flex justify-between items-start">
|
||||
<Dialog.Title className="text-xl font-medium text-gray-900">
|
||||
{title}
|
||||
|
||||
30
Front-End/src/components/Navigation.js
Normal file
30
Front-End/src/components/Navigation.js
Normal file
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import StepTitle from '@/components/StepTitle';
|
||||
|
||||
const Navigation = ({ steps, step, setStep, isStepValid, stepTitles }) => {
|
||||
return (
|
||||
<div className="relative mb-4">
|
||||
<div className="relative flex justify-between">
|
||||
{steps.map((stepLabel, index) => {
|
||||
const isCurrentStep = step === index + 1;
|
||||
|
||||
return (
|
||||
<div key={index} className="flex flex-col items-center">
|
||||
<div className={`mb-4 ${isCurrentStep ? 'text-gray-500 font-extrabold' : 'text-gray-500'}`}>
|
||||
{stepLabel}
|
||||
</div>
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full mb-4 ${isStepValid(index + 1) ? 'bg-emerald-500' : 'bg-yellow-300'} cursor-pointer`}
|
||||
onClick={() => setStep(index + 1)}
|
||||
style={{ transform: 'translateY(-50%)' }}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<StepTitle title={stepTitles[step]} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navigation;
|
||||
16
Front-End/src/components/StepTitle.js
Normal file
16
Front-End/src/components/StepTitle.js
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
const StepTitle = ({ title }) => {
|
||||
return (
|
||||
<div className="relative mb-4">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center">
|
||||
<div className="px-4 bg-white text-gray-500">{title}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StepTitle;
|
||||
@ -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 (
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-center mb-4 max-w-4xl ml-0">
|
||||
<h2 className="text-xl font-bold mb-4">Gestion des spécialités</h2>
|
||||
<button
|
||||
onClick={() => openEditModal(null)} // ouvrir le modal pour créer une nouvelle spécialité
|
||||
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg border border-gray-200 max-w-4xl ml-0">
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
name: 'INTITULÉ',
|
||||
transform: (row) => (
|
||||
<div
|
||||
className="inline-block px-3 py-1 rounded-full font-bold text-white"
|
||||
style={{ backgroundColor: row.color_code }}
|
||||
title={row.color_code}
|
||||
>
|
||||
<span className="font-bold text-white">{row.name.toUpperCase()}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{ name: 'DATE DE CREATION', transform: (row) => row.updated_date_formatted },
|
||||
{ name: 'ACTIONS', transform: (row) => (
|
||||
<DropdownMenu
|
||||
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<SpecialityFormProvider initialSpeciality={editingSpeciality || {}}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
title={editingSpeciality ? "Modification de la spécialité" : "Création d'une nouvelle spécialité"}
|
||||
size='sm:w-1/6'
|
||||
ContentComponent={() => (
|
||||
<SpecialityForm onSubmit={handleModalSubmit} isNew={!editingSpeciality} />
|
||||
)}
|
||||
/>
|
||||
</SpecialityFormProvider>
|
||||
)}
|
||||
<div
|
||||
ref={drag}
|
||||
className="inline-block px-3 py-1 rounded-full font-bold text-white text-center"
|
||||
style={{ backgroundColor: speciality.color_code, opacity: isDragging ? 0.5 : 1 }}
|
||||
title={speciality.name}
|
||||
>
|
||||
{speciality.name}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<InputTextWithColorIcon
|
||||
name="name"
|
||||
textValue={currentData.name}
|
||||
colorValue={currentData.color_code}
|
||||
onTextChange={handleChange}
|
||||
onColorChange={handleChange}
|
||||
placeholder="Nom de la spécialité"
|
||||
errorMsg={localErrors && localErrors.name && Array.isArray(localErrors.name) ? localErrors.name[0] : ''}
|
||||
/>
|
||||
);
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => (isEditing ? handleUpdateSpeciality(editingSpeciality, formData) : handleSaveNewSpeciality())}
|
||||
className="text-green-500 hover:text-green-700"
|
||||
>
|
||||
<Check className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => (isEditing ? setEditingSpeciality(null) : setNewSpeciality(null))}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
switch (column) {
|
||||
case 'LIBELLE':
|
||||
return (
|
||||
<SpecialityItem key={speciality.id} speciality={speciality} />
|
||||
);
|
||||
case 'MISE A JOUR':
|
||||
return speciality.updated_date_formatted;
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditingSpeciality(speciality.id) || setFormData(speciality)}
|
||||
className="text-blue-500 hover:text-blue-700"
|
||||
>
|
||||
<Edit3 className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveSpeciality(speciality.id)}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ name: 'LIBELLE', label: 'Libellé' },
|
||||
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
|
||||
{ name: 'ACTIONS', label: 'Actions' }
|
||||
];
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center mb-4">
|
||||
<BookOpen className="w-6 h-6 text-emerald-500 mr-2" />
|
||||
<h2 className="text-xl font-semibold">Spécialités</h2>
|
||||
</div>
|
||||
<button type="button" onClick={handleAddSpeciality} className="text-emerald-500 hover:text-emerald-700">
|
||||
<Plus className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<Table
|
||||
data={newSpeciality ? [newSpeciality, ...specialities] : specialities}
|
||||
columns={columns}
|
||||
renderCell={renderSpecialityCell}
|
||||
/>
|
||||
<Popup
|
||||
visible={popupVisible}
|
||||
message={popupMessage}
|
||||
onConfirm={() => setPopupVisible(false)}
|
||||
onCancel={() => setPopupVisible(false)}
|
||||
uniqueConfirmButton={true}
|
||||
/>
|
||||
</div>
|
||||
</DndProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpecialitiesSection;
|
||||
|
||||
@ -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 (
|
||||
<div className="space-y-4 mt-8">
|
||||
<div>
|
||||
<InputTextIcon
|
||||
type="text"
|
||||
name="name"
|
||||
IconItem={BookOpen}
|
||||
placeholder="Nom de la spécialité"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<InputColorIcon
|
||||
type="color"
|
||||
name="color_code"
|
||||
IconItem={Palette}
|
||||
placeholder="Nom de la spécialité"
|
||||
value={formData.color_code}
|
||||
onChange={handleChange}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
</div>
|
||||
<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.name
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
primary
|
||||
disabled={!formData.name}
|
||||
type="submit"
|
||||
name="Create" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpecialityForm;
|
||||
@ -9,7 +9,7 @@ const StructureManagement = ({ specialities, setSpecialities, teachers, setTeach
|
||||
return (
|
||||
<div className="max-w-8xl mx-auto p-4 mt-6 space-y-6">
|
||||
<ClassesProvider>
|
||||
<div className="p-4 bg-white rounded-lg shadow-md">
|
||||
<div className="max-w-4xl p-4 bg-white rounded-lg shadow-md">
|
||||
<SpecialitiesSection
|
||||
specialities={specialities}
|
||||
setSpecialities={setSpecialities}
|
||||
@ -18,9 +18,10 @@ const StructureManagement = ({ specialities, setSpecialities, teachers, setTeach
|
||||
handleDelete={(id) => handleDelete(`${BE_SCHOOL_SPECIALITY_URL}`, id, setSpecialities)}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4 bg-white rounded-lg shadow-md">
|
||||
<div className="max-w-8xl p-4 bg-white rounded-lg shadow-md">
|
||||
<TeachersSection
|
||||
teachers={teachers}
|
||||
setTeachers={setTeachers}
|
||||
specialities={specialities}
|
||||
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_TEACHER_URL}`, newData, setTeachers)}
|
||||
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_TEACHER_URL}`, id, updatedData, setTeachers)}
|
||||
|
||||
@ -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 (
|
||||
<form onSubmit={handleSubmit} className="space-y-4 mt-8">
|
||||
<div>
|
||||
<InputTextIcon
|
||||
name="last_name"
|
||||
type="text"
|
||||
IconItem={GraduationCap}
|
||||
placeholder="Nom de l'enseignant"
|
||||
value={formData.last_name}
|
||||
onChange={handleChange}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<InputTextIcon
|
||||
name="first_name"
|
||||
type="text"
|
||||
IconItem={GraduationCap}
|
||||
placeholder="Prénom de l'enseignant"
|
||||
value={formData.first_name}
|
||||
onChange={handleChange}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<InputTextIcon
|
||||
name="email"
|
||||
type="email"
|
||||
IconItem={Mail}
|
||||
placeholder="Email de l'enseignant"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<CheckBoxList
|
||||
items={specialities}
|
||||
formData={formData}
|
||||
handleChange={handleChange}
|
||||
fieldName="specialities"
|
||||
label="Spécialités"
|
||||
icon={BookOpen}
|
||||
className="w-full mt-4"
|
||||
itemLabelFunc={getSpecialityLabel}
|
||||
labelAttenuated={isLabelAttenuated}
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-4'>
|
||||
<ToggleSwitch
|
||||
label="Administrateur"
|
||||
checked={formData.droit.id}
|
||||
onChange={handleToggleChange}
|
||||
/>
|
||||
</div>
|
||||
<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.last_name || !formData.first_name || !formData.email || formData.specialities.length === 0)
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
primary
|
||||
disabled={(!formData.last_name || !formData.first_name || !formData.email || formData.specialities.length === 0)}
|
||||
type="submit"
|
||||
name="Create" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeacherForm;
|
||||
@ -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 (
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-center mb-4 max-w-8xl ml-0">
|
||||
<h2 className="text-xl font-bold mb-4">Gestion des enseignants</h2>
|
||||
<button
|
||||
onClick={() => openEditModal(null)} // ouvrir le modal pour créer une nouvelle spécialité
|
||||
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg border border-gray-200 max-w-8xl ml-0">
|
||||
<Table
|
||||
columns={[
|
||||
{ name: 'NOM', transform: (row) => row.last_name },
|
||||
{ name: 'PRENOM', transform: (row) => row.first_name },
|
||||
{ name: 'MAIL', transform: (row) => row.email },
|
||||
{
|
||||
name: 'SPÉCIALITÉS',
|
||||
transform: (row) => (
|
||||
<div key={row.id} className="flex flex-wrap justify-center items-center space-x-2">
|
||||
{row.specialities_details.map((speciality,index) => (
|
||||
<span
|
||||
key={`${speciality.id}-${index}`}
|
||||
className="px-3 py-1 rounded-full font-bold text-white"
|
||||
style={{ backgroundColor: speciality.color_code }}
|
||||
title={speciality.name}
|
||||
>
|
||||
{speciality.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
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 (
|
||||
<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.droit.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <i>Non définie</i>;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ name: 'DATE DE CREATION', transform: (row) => row.updated_date_formatted },
|
||||
{ name: 'ACTIONS', transform: (row) => (
|
||||
<DropdownMenu
|
||||
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<TeacherFormProvider initialTeacher={editingTeacher || {}}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
title={editingTeacher ? "Modification de l'enseignant" : "Création d'un nouvel enseignant"}
|
||||
size='sm:w-1/4'
|
||||
ContentComponent={() => (
|
||||
<TeacherForm teacher={editingTeacher || {}} onSubmit={handleModalSubmit} isNew={!editingTeacher} specialities={specialities} />
|
||||
)}
|
||||
/>
|
||||
</TeacherFormProvider>
|
||||
)}
|
||||
<div ref={drop} className="p-2 rounded-md flex flex-col items-center">
|
||||
{localSpecialities.map((speciality, index) => (
|
||||
<div key={`${speciality.id}-${index}`} className="flex items-center space-x-2 mb-2">
|
||||
<span
|
||||
className="px-3 py-1 rounded-full font-bold text-white"
|
||||
style={{ backgroundColor: speciality.color_code }}
|
||||
title={speciality.name}
|
||||
>
|
||||
{speciality.name}
|
||||
</span>
|
||||
{isEditing && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveSpeciality(speciality.id)}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<InputTextIcon
|
||||
name="last_name"
|
||||
value={currentData.last_name}
|
||||
onChange={handleChange}
|
||||
placeholder="Nom de l'enseignant"
|
||||
errorMsg={localErrors && localErrors.last_name && Array.isArray(localErrors.last_name) ? localErrors.last_name[0] : ''}
|
||||
/>
|
||||
);
|
||||
case 'PRENOM':
|
||||
return (
|
||||
<InputTextIcon
|
||||
name="first_name"
|
||||
value={currentData.first_name}
|
||||
onChange={handleChange}
|
||||
placeholder="Prénom de l'enseignant"
|
||||
errorMsg={localErrors && localErrors.first_name && Array.isArray(localErrors.firstname) ? localErrors.firstname[0] : ''}
|
||||
/>
|
||||
);
|
||||
case 'EMAIL':
|
||||
return (
|
||||
<InputTextIcon
|
||||
name="email"
|
||||
value={currentData.email}
|
||||
onChange={handleChange}
|
||||
placeholder="Email de l'enseignant"
|
||||
errorMsg={localErrors && localErrors.email && Array.isArray(localErrors.email) ? localErrors.email[0] : ''}
|
||||
/>
|
||||
);
|
||||
case 'SPECIALITES':
|
||||
return (
|
||||
<SpecialitiesDropZone teacher={currentData} handleSpecialitiesChange={handleSpecialitiesChange} specialities={specialities} isEditing={isEditing || isCreating} />
|
||||
);
|
||||
// case 'PROFIL':
|
||||
// return (
|
||||
// <ToggleSwitch
|
||||
// name="profile"
|
||||
// checked={currentData.profile}
|
||||
// onChange={handleChange}
|
||||
// />
|
||||
// );
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => (isEditing ? handleUpdateTeacher(editingTeacher, formData) : handleSaveNewTeacher())}
|
||||
className="text-green-500 hover:text-green-700"
|
||||
>
|
||||
<Check className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => (isEditing ? setEditingTeacher(null) : setNewTeacher(null))}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
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 (
|
||||
<SpecialitiesDropZone teacher={teacher} handleSpecialitiesChange={handleSpecialitiesChange} specialities={specialities} isEditing={false} />
|
||||
);
|
||||
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 (
|
||||
<div key={teacher.id} className="flex justify-center items-center space-x-2">
|
||||
<span className={`px-3 py-1 rounded-full font-bold ${badgeClass}`}>
|
||||
{teacher.droit.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <i>Non définie</i>;
|
||||
};
|
||||
case 'MISE A JOUR':
|
||||
return teacher.updated_date_formatted;
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditingTeacher(teacher.id) || setFormData(teacher)}
|
||||
className="text-blue-500 hover:text-blue-700"
|
||||
>
|
||||
<Edit3 className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveTeacher(teacher.id)}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
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 (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center mb-4">
|
||||
<GraduationCap className="w-6 h-6 text-emerald-500 mr-2" />
|
||||
<h2 className="text-xl font-semibold">Enseignants</h2>
|
||||
</div>
|
||||
<button type="button" onClick={handleAddTeacher} className="text-emerald-500 hover:text-emerald-700">
|
||||
<Plus className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<Table
|
||||
data={newTeacher ? [newTeacher, ...teachers] : teachers}
|
||||
columns={columns}
|
||||
renderCell={renderTeacherCell}
|
||||
/>
|
||||
<Popup
|
||||
visible={popupVisible}
|
||||
message={popupMessage}
|
||||
onConfirm={() => setPopupVisible(false)}
|
||||
onCancel={() => setPopupVisible(false)}
|
||||
uniqueConfirmButton={true}
|
||||
/>
|
||||
</div>
|
||||
</DndProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeachersSection;
|
||||
|
||||
@ -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"
|
||||
>
|
||||
<Trash className="w-5 h-5" />
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -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"
|
||||
>
|
||||
<Trash className="w-5 h-5" />
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user