refactor: Création de composants et uniformisation des modales (#2)

This commit is contained in:
N3WT DE COMPET
2024-11-24 18:42:42 +01:00
parent 5946cbdee6
commit d51778ba54
18 changed files with 362 additions and 299 deletions

View File

@ -4,3 +4,8 @@ from django.apps import AppConfig
class GestionenseignantsConfig(AppConfig): class GestionenseignantsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'GestionEnseignants' name = 'GestionEnseignants'
def ready(self):
from .models import Specialite
if not Specialite.objects.filter(nom='TRANSVERSE').exists():
Specialite.objects.create(nom='TRANSVERSE', codeCouleur='#FF0000')

View File

@ -7,8 +7,8 @@ class Profil(AbstractUser):
class Droits(models.IntegerChoices): class Droits(models.IntegerChoices):
PROFIL_UNDEFINED = -1, _('NON DEFINI') PROFIL_UNDEFINED = -1, _('NON DEFINI')
PROFIL_ECOLE = 0, _('ECOLE') PROFIL_ECOLE = 0, _('ECOLE')
PROFIL_PARENT = 1, _('PARENT') PROFIL_ADMIN = 1, _('ADMIN')
PROFIL_ADMIN = 2, _('ADMIN') PROFIL_PARENT = 2, _('PARENT')
email = models.EmailField(max_length=255, unique=True, default="", validators=[EmailValidator()]) email = models.EmailField(max_length=255, unique=True, default="", validators=[EmailValidator()])

View File

@ -335,7 +335,7 @@ export default function Page({ params: { locale } }) {
password: 'Provisoire01!', password: 'Provisoire01!',
username: updatedData.responsableEmail, username: updatedData.responsableEmail,
is_active: 0, // On rend le profil inactif : impossible de s'y connecter dans la fenêtre du login tant qu'il ne s'est pas inscrit is_active: 0, // On rend le profil inactif : impossible de s'y connecter dans la fenêtre du login tant qu'il ne s'est pas inscrit
droit:1 droit:2 // Profil PARENT
}), }),
} }
); );

View File

@ -80,13 +80,12 @@ export default function Page() {
localStorage.setItem('userId', data.profil); // Stocker l'identifiant de l'utilisateur localStorage.setItem('userId', data.profil); // Stocker l'identifiant de l'utilisateur
if (data.droit == 0) { if (data.droit == 0) {
// Vue ECOLE // Vue ECOLE
} else if (data.droit == 1) { } else if (data.droit == 1) {
// Vue PARENT
router.push(`${FR_PARENTS_HOME_URL}`);
} else if (data.droit == 2) {
// Vue ADMIN // Vue ADMIN
router.push(`${FR_ADMIN_SUBSCRIPTIONS_URL}`); router.push(`${FR_ADMIN_SUBSCRIPTIONS_URL}`);
} else if (data.droit == 2) {
// Vue PARENT
router.push(`${FR_PARENTS_HOME_URL}`);
} else { } else {
// Cas anormal // Cas anormal
} }

View File

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
const Button = ({ text, onClick, href, className, primary, icon }) => { const Button = ({ text, onClick, href, className, primary, icon, disabled}) => {
const router = useRouter(); const router = useRouter();
const baseClass = 'px-4 py-2 rounded-md text-white h-8 flex items-center justify-center'; const baseClass = 'px-4 py-2 rounded-md text-white h-8 flex items-center justify-center';
const primaryClass = 'bg-emerald-500 hover:bg-emerald-600'; const primaryClass = 'bg-emerald-500 hover:bg-emerald-600';
const secondaryClass = 'bg-gray-300 hover:bg-gray-400 text-black'; const secondaryClass = 'bg-gray-300 hover:bg-gray-400 text-black';
const buttonClass = `${baseClass} ${primary ? primaryClass : secondaryClass} ${className}`; const buttonClass = `${baseClass} ${primary && !disabled ? primaryClass : secondaryClass} ${className}`;
const handleClick = (e) => { const handleClick = (e) => {
if (href) { if (href) {
@ -17,7 +17,7 @@ const Button = ({ text, onClick, href, className, primary, icon }) => {
}; };
return ( return (
<button className={buttonClass} onClick={handleClick}> <button className={buttonClass} onClick={handleClick} disabled={disabled}>
{icon && <span className="mr-2">{icon}</span>} {icon && <span className="mr-2">{icon}</span>}
{text} {text}
</button> </button>

View File

@ -1,13 +1,24 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import Slider from '@/components/Slider' import Slider from '@/components/Slider'
import InputTextIcon from '@/components/InputTextIcon';
import Button from '@/components/Button';
import SelectChoice from '@/components/SelectChoice';
import RadioList from '@/components/RadioList';
import { Users, Maximize2, Globe, Calendar, GraduationCap } from 'lucide-react';
const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => { const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => {
const langues = [
{ value:'Français', label: 'Français'},
{ value:'Anglais', label: 'Anglais'},
{ value:'Espagnol', label: 'Espagnol'},
];
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
nom_ambiance: classe.nom_ambiance || '', nom_ambiance: classe.nom_ambiance || '',
tranche_age: classe.tranche_age || [3, 6], tranche_age: classe.tranche_age || [3, 6],
nombre_eleves: classe.nombre_eleves || '', nombre_eleves: classe.nombre_eleves || '',
langue_enseignement: classe.langue_enseignement || 'Français', langue_enseignement: classe.langue_enseignement || 'Français',
annee_scolaire: classe.annee_scolaire || '2024-2025', annee_scolaire: classe.annee_scolaire || '',
specialites_ids: classe.specialites_ids || [], specialites_ids: classe.specialites_ids || [],
enseignant_principal_id: classe.enseignant_principal_id || null, enseignant_principal_id: classe.enseignant_principal_id || null,
}); });
@ -49,19 +60,21 @@ const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => {
}); });
}; };
const getTeacherLabel = (teacher) => {
return `${teacher.nom} ${teacher.prenom} (${teacher.specialite.nom})`;
};
return ( return (
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4 mt-8">
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <InputTextIcon
Nom d'ambiance
</label>
<input
type="text"
placeholder="Nom de l'ambiance"
name="nom_ambiance" name="nom_ambiance"
type="text"
IconItem={Users}
placeholder="Nom d'ambiance"
value={formData.nom_ambiance} value={formData.nom_ambiance}
onChange={handleChange} onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 italic" className="w-full"
/> />
</div> </div>
<div> <div>
@ -69,51 +82,43 @@ const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => {
Tranche d'âge Tranche d'âge
</label> </label>
<Slider <Slider
min={3} min={2}
max={12} max={18}
value={formData.tranche_age} value={formData.tranche_age}
onChange={handleSliderChange} onChange={handleSliderChange}
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <InputTextIcon
Nombre d'élèves
</label>
<input
type="number"
name="nombre_eleves" name="nombre_eleves"
type="number"
IconItem={Maximize2}
placeholder="Capacité max"
value={formData.nombre_eleves} value={formData.nombre_eleves}
onChange={handleNumberChange} onChange={handleChange}
min="1" className="w-full"
max="40"
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 sm:text-sm"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <SelectChoice
Langue d'enseignement
</label>
<select
name="langue_enseignement" name="langue_enseignement"
value={formData.langue_enseignement} label="Langue d'enseignement"
onChange={handleChange} selected={formData.langue_enseignement}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 sm:text-sm" callback={handleChange}
> choices={langues}
<option value="Français">Français</option> IconItem={Globe}
<option value="Anglais">Anglais</option> required
<option value="Espagnol">Espagnol</option> />
</select>
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <InputTextIcon
Année scolaire
</label>
<input
type="text"
name="annee_scolaire" name="annee_scolaire"
type="text"
IconItem={Calendar}
placeholder="Année scolaire (20xx-20xx)"
value={formData.annee_scolaire} value={formData.annee_scolaire}
onChange={handleChange} onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 sm:text-sm" className="w-full"
/> />
</div> </div>
<div> <div>
@ -144,41 +149,30 @@ const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => {
))} ))}
</div> </div>
</div> </div>
<div> <div className="flex space-x-4">
<label className="block text-sm font-medium text-gray-700"> <RadioList
Enseignant principal items={teachers}
</label> formData={formData}
<div className="mt-2 grid grid-cols-1 gap-4"> handleChange={handleChange}
{teachers.map(teacher => ( fieldName="enseignant_principal_id"
<div key={teacher.id} className="flex items-center"> label="Enseignant principal"
<input itemLabelFunc={getTeacherLabel}
type="radio" icon={GraduationCap}
id={`teacher-${teacher.id}`} className="w-full mt-4"
name="enseignant_principal_id"
value={teacher.id}
checked={formData.enseignant_principal_id === teacher.id}
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"
/> />
<label htmlFor={`teacher-${teacher.id}`} className="ml-2 block text-sm text-gray-900 flex items-center">
{teacher.nom} {teacher.prenom}
</label>
</div>
))}
</div>
</div> </div>
<div className="flex justify-end mt-4 space-x-4"> <div className="flex justify-end mt-4 space-x-4">
<button <Button text="Créer"
onClick={handleSubmit} onClick={handleSubmit}
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${ className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
(!formData.nom_ambiance || !formData.nombre_eleves) (!formData.nom_ambiance || !formData.nombre_eleves || !formData.annee_scolaire || !formData.enseignant_principal_id)
? "bg-gray-300 text-gray-700 cursor-not-allowed" ? "bg-gray-300 text-gray-700 cursor-not-allowed"
: "bg-emerald-500 text-white hover:bg-emerald-600" : "bg-emerald-500 text-white hover:bg-emerald-600"
}`} }`}
disabled={(!formData.nom_ambiance || !formData.nombre_eleves)} primary
> disabled={(!formData.nom_ambiance || !formData.nombre_eleves || !formData.annee_scolaire || !formData.enseignant_principal_id)}
Soumettre type="submit"
</button> name="Create" />
</div> </div>
</form> </form>
); );

View File

@ -107,7 +107,9 @@ const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleE
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
setIsOpen={setIsOpen} setIsOpen={setIsOpen}
title={editingClass ? "Modification de la classe" : "Création d'une nouvelle classe"} ContentComponent={() => ( title={editingClass ? "Modification de la classe" : "Création d'une nouvelle classe"}
size='sm:w-1/4'
ContentComponent={() => (
<ClassForm classe={editingClass || {}} onSubmit={handleModalSubmit} isNew={!editingClass} specialities={specialities} teachers={teachers} /> <ClassForm classe={editingClass || {}} onSubmit={handleModalSubmit} isNew={!editingClass} specialities={specialities} teachers={teachers} />
)} )}
/> />

View File

@ -0,0 +1,28 @@
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={`flex items-center border-2 border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} hover:border-gray-400 focus-within:border-gray-500 h-8 w-1/3`}>
<span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm h-full">
<Palette className="w-5 h-5" />
</span>
<input
type="color"
id={name}
name={name}
value={value}
onChange={onChange}
className="flex-1 block rounded-r-md sm:text-sm border-none focus:ring-0 outline-none h-full p-0 w-8"
/>
</div>
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
</div>
</>
);
};
export default InputColorIcon;

View File

@ -1,6 +1,8 @@
import { useState } from 'react'; import { useState } from 'react';
import { User, Mail, Phone, UserCheck } from 'lucide-react'; import { User, Mail, Phone, UserCheck } from 'lucide-react';
import InputTextIcon from '@/components/InputTextIcon'; import InputTextIcon from '@/components/InputTextIcon';
import ToggleSwitch from '@/components/ToggleSwitch';
import Button from '@/components/Button';
const InscriptionForm = ( { eleves, onSubmit }) => { const InscriptionForm = ( { eleves, onSubmit }) => {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
@ -9,18 +11,17 @@ const InscriptionForm = ( { eleves, onSubmit }) => {
responsableEmail: '', responsableEmail: '',
responsableTel: '', responsableTel: '',
selectedResponsables: [], selectedResponsables: [],
responsableType: 'new' responsableType: 'new',
autoMail: false
}); });
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const [selectedEleve, setSelectedEleve] = useState(''); const [selectedEleve, setSelectedEleve] = useState('');
const [existingResponsables, setExistingResponsables] = useState([]); const [existingResponsables, setExistingResponsables] = useState([]);
const [autoMail, setAutoMail] = useState(false);
const maxStep = 4 const maxStep = 4
const handleToggleChange = () => { const handleToggleChange = () => {
setAutoMail(!autoMail); setFormData({ ...formData, autoMail: !formData.autoMail });
setFormData({ ...formData, autoMail: !autoMail });
}; };
const handleChange = (e) => { const handleChange = (e) => {
@ -62,7 +63,7 @@ const InscriptionForm = ( { eleves, onSubmit }) => {
}; };
const submit = () => { const submit = () => {
onSubmit({ ...formData, autoMail }); onSubmit(formData);
} }
return ( return (
@ -167,7 +168,7 @@ const InscriptionForm = ( { eleves, onSubmit }) => {
onChange={() => handleResponsableSelection(responsable.id)} onChange={() => handleResponsableSelection(responsable.id)}
/> />
<span className="text-gray-900"> <span className="text-gray-900">
{responsable.nom && responsable.prenom ? `${responsable.nom} ${responsable.prenom}` : `adresse mail : ${responsable.mail}`} {responsable.nom && responsable.prenom ? `${responsable.nom} ${responsable.prenom}` : `${responsable.mail}`}
</span> </span>
</label> </label>
</div> </div>
@ -232,37 +233,26 @@ const InscriptionForm = ( { eleves, onSubmit }) => {
)} )}
</div> </div>
</div> </div>
<div className="flex items-center mt-4"> <div className='mt-4'>
<label className="mr-2 text-gray-600">Envoi automatique</label> <ToggleSwitch
<div className="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"> label="Envoi automatique"
<input checked={formData.autoMail}
type="checkbox"
name="toggle"
id="toggle"
checked={autoMail}
onChange={handleToggleChange} onChange={handleToggleChange}
className="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer border-emerald-500 checked:right-0 checked:border-emerald-500 checked:bg-emerald-500 focus:border-emerald-500 focus:bg-emerald-500 hover:border-emerald-500 hover:bg-emerald-500"
/> />
<label
htmlFor="toggle"
className={`toggle-label block overflow-hidden h-6 rounded-full cursor-pointer transition-colors duration-200 ${autoMail ? 'bg-emerald-300' : 'bg-gray-300'}`}
></label>
</div>
</div> </div>
</div> </div>
)} )}
<div className="flex justify-end mt-4 space-x-4"> <div className="flex justify-end mt-4 space-x-4">
{step > 1 && ( {step > 1 && (
<button <Button text="Précédent"
onClick={prevStep} onClick={prevStep}
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md shadow-sm hover:bg-gray-400 focus:outline-none" className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md shadow-sm hover:bg-gray-400 focus:outline-none"
> secondary
Précédent name="Previous" />
</button>
)} )}
{step < maxStep ? ( {step < maxStep ? (
<button <Button text="Suivant"
onClick={nextStep} onClick={nextStep}
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${ className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
(step === 1 && (!formData.eleveNom || !formData.elevePrenom)) || (step === 1 && (!formData.eleveNom || !formData.elevePrenom)) ||
@ -275,16 +265,14 @@ const InscriptionForm = ( { eleves, onSubmit }) => {
(step === 2 && formData.responsableType === "new" && !formData.responsableEmail) || (step === 2 && formData.responsableType === "new" && !formData.responsableEmail) ||
(step === 2 && formData.responsableType === "existing" && formData.selectedResponsables.length === 0) (step === 2 && formData.responsableType === "existing" && formData.selectedResponsables.length === 0)
} }
> primary
Suivant name="Next" />
</button>
) : ( ) : (
<button <Button text="Créer"
onClick={submit} onClick={submit}
className="px-4 py-2 bg-emerald-500 text-white rounded-md shadow-sm hover:bg-emerald-600 focus:outline-none" className="px-4 py-2 bg-emerald-500 text-white rounded-md shadow-sm hover:bg-emerald-600 focus:outline-none"
> primary
Créer name="Create" />
</button>
)} )}
</div> </div>
</div> </div>

View File

@ -1,4 +1,5 @@
import * as Dialog from '@radix-ui/react-dialog'; 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, size }) => {
return ( return (
@ -13,14 +14,14 @@ const Modal = ({ isOpen, setIsOpen, title, ContentComponent, size }) => {
<div className="mt-2"> <div className="mt-2">
<ContentComponent /> <ContentComponent />
</div> </div>
<div className="mt-4 flex justify-end"> <div className="mt-4 flex justify-end space-x-4">
<Dialog.Close asChild> <Dialog.Close asChild>
<button <Button text="Fermer"
className="px-4 py-2 rounded-md shadow-sm focus:outline-none bg-gray-300 text-gray-700 hover:bg-gray-400"
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
> className="px-4 py-2 rounded-md shadow-sm focus:outline-none bg-gray-300 text-gray-700 hover:bg-gray-400"
Fermer secondary
</button> type="submit"
name="Create" />
</Dialog.Close> </Dialog.Close>
</div> </div>
</div> </div>

View File

@ -0,0 +1,39 @@
import React from 'react';
const RadioList = ({ items, formData, handleChange, fieldName, label, icon: Icon, className, itemLabelFunc}) => {
return (
<div className={`mb-4 ${className}`}>
<label className="block text-sm font-medium text-gray-700 flex items-center">
{Icon && <Icon className="w-5 h-5 mr-2" />}
{label}
</label>
<div className="mt-2 grid grid-cols-1 gap-4">
{items.map(item => (
<div key={item.id} className="flex items-center">
<input
type="radio"
id={`${fieldName}-${item.id}`}
name={fieldName}
value={item.id}
checked={formData[fieldName] === item.id}
onChange={handleChange}
className="form-radio h-4 w-4 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600"
/>
<label htmlFor={`${fieldName}-${item.id}`} className="ml-2 block text-sm text-gray-900 flex items-center">
{itemLabelFunc(item)}
{item.codeCouleur && (
<div
className="w-4 h-4 rounded-full ml-2"
style={{ backgroundColor: item.codeCouleur }}
title={item.codeCouleur}
></div>
)}
</label>
</div>
))}
</div>
</div>
);
};
export default RadioList;

View File

@ -1,10 +1,14 @@
export default function SelectChoice({type, name, label, choices, callback, selected, error }) { export default function SelectChoice({type, name, label, choices, callback, selected, error, IconItem }) {
return ( return (
<> <>
<div className="mb-4"> <div className="mb-4">
<label htmlFor={name} className="block text-sm font-medium text-gray-700">{label}</label> <label htmlFor={name} className="block text-sm font-medium text-gray-700">{label}</label>
<div className={`flex items-center border-2 border-gray-200 rounded-md hover:border-gray-400 focus-within:border-gray-500 h-8`}>
<span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm h-full">
{IconItem && <IconItem />}
</span>
<select <select
className={`mt-1 block w-full px-3 py-2 text-base border ${error ? 'border-red-500' : 'border-gray-300'} focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md`} className="mt-1 block w-full px-2 py-0 text-base rounded-r-md sm:text-sm border-none focus:ring-0 outline-none"
type={type} type={type}
id={name} id={name}
name={name} name={name}
@ -13,6 +17,7 @@ export default function SelectChoice({type, name, label, choices, callback, sele
> >
{choices.map(({ value, label }, index) => <option key={value} value={value}>{label}</option>)} {choices.map(({ value, label }, index) => <option key={value} value={value}>{label}</option>)}
</select> </select>
</div>
{error && <p className="mt-2 text-sm text-red-600">{error}</p>} {error && <p className="mt-2 text-sm text-red-600">{error}</p>}
</div> </div>
</> </>

View File

@ -23,6 +23,7 @@ const Slider = ({ min, max, value, onChange }) => {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-2 w-1/2"> <div className="flex items-center space-x-2 w-1/2">
<span className="text-emerald-600">{value[0]}</span> <span className="text-emerald-600">{value[0]}</span>
<input <input
@ -34,7 +35,7 @@ const Slider = ({ min, max, value, onChange }) => {
className="w-full h-2 bg-emerald-200 rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-opacity-50" className="w-full h-2 bg-emerald-200 rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-opacity-50"
/> />
</div> </div>
<div className="flex items-center space-x-2 w-1/2"> <div className="flex items-center space-x-2 w-1/2 justify-end">
<span className="text-emerald-600">{value[1]}</span> <span className="text-emerald-600">{value[1]}</span>
<input <input
type="range" type="range"
@ -46,6 +47,7 @@ const Slider = ({ min, max, value, onChange }) => {
/> />
</div> </div>
</div> </div>
</div>
); );
}; };

View File

@ -75,7 +75,9 @@ const SpecialitiesSection = ({ specialities, handleCreate, handleEdit, handleDel
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
setIsOpen={setIsOpen} setIsOpen={setIsOpen}
title={editingSpeciality ? "Modification de la spécialité" : "Création d'une nouvelle spécialité"} ContentComponent={() => ( title={editingSpeciality ? "Modification de la spécialité" : "Création d'une nouvelle spécialité"}
size='sm:w-1/6'
ContentComponent={() => (
<SpecialityForm speciality={editingSpeciality || {}} onSubmit={handleModalSubmit} isNew={!editingSpeciality} /> <SpecialityForm speciality={editingSpeciality || {}} onSubmit={handleModalSubmit} isNew={!editingSpeciality} />
)} )}
/> />

View File

@ -1,4 +1,8 @@
import { useState } from 'react'; 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';
const SpecialityForm = ({ speciality = {}, onSubmit, isNew }) => { const SpecialityForm = ({ speciality = {}, onSubmit, isNew }) => {
const [nom, setNom] = useState(speciality.nom || ''); const [nom, setNom] = useState(speciality.nom || '');
@ -13,35 +17,39 @@ const SpecialityForm = ({ speciality = {}, onSubmit, isNew }) => {
}; };
return ( return (
<div className="p-4"> <div className="space-y-4 mt-8">
<div> <div>
<input <InputTextIcon
type="text" type="text"
IconItem={BookOpen}
placeholder="Nom de la spécialité" placeholder="Nom de la spécialité"
value={nom} value={nom}
onChange={(e) => setNom(e.target.value)} onChange={(e) => setNom(e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 italic" className="w-full mt-4"
/> />
</div> </div>
<div className="mt-4"> <div className="mt-4">
<label className="block text-sm font-medium text-gray-700"> <InputColorIcon
Code couleur de la spécialité
</label>
<input
type="color" type="color"
IconItem={Palette}
placeholder="Nom de la spécialité"
value={codeCouleur} value={codeCouleur}
onChange={(e) => setCodeCouleur(e.target.value)} onChange={(e) => setCodeCouleur(e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 h-10 w-10 p-0 cursor-pointer" className="w-full mt-4"
style={{ appearance: 'none', borderRadius: '0' }}
/> />
</div> </div>
<div className="flex justify-end mt-4 space-x-4"> <div className="flex justify-end mt-4 space-x-4">
<button <Button text="Créer"
onClick={handleSubmit} onClick={handleSubmit}
className="px-4 py-2 bg-emerald-500 text-white rounded-md shadow-sm hover:bg-emerald-600 focus:outline-none" className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
> !nom
Soumettre ? "bg-gray-300 text-gray-700 cursor-not-allowed"
</button> : "bg-emerald-500 text-white hover:bg-emerald-600"
}`}
primary
disabled={!nom}
type="submit"
name="Create" />
</div> </div>
</div> </div>
); );

View File

@ -1,10 +1,11 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { GraduationCap, Mail, BookOpen } from 'lucide-react';
import InputTextIcon from '@/components/InputTextIcon';
import Button from '@/components/Button';
import RadioList from '@/components/RadioList';
import ToggleSwitch from '@/components/ToggleSwitch'
const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => { const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
const profils = [
{ value: 0, label: "École" },
{ value: 2, label: "Administrateur" },
];
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
nom: teacher.nom || '', nom: teacher.nom || '',
@ -13,13 +14,17 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
specialite_id: teacher.specialite_id || '', specialite_id: teacher.specialite_id || '',
classes: teacher.classes || [], classes: teacher.classes || [],
profilAssocie_id: teacher.profilAssocie_id || [], profilAssocie_id: teacher.profilAssocie_id || [],
DroitValue: teacher.DroitValue || 0 droit: teacher.DroitValue || 0
}); });
const handleToggleChange = () => {
console.log('new value : ', 1-formData.droit)
setFormData({ ...formData, droit: 1-formData.droit });
};
const handleChange = (e) => { const handleChange = (e) => {
const { name, value, type } = e.target; const { name, value, type } = e.target;
const newValue = type === 'radio' ? parseInt(value, 10) : value; const newValue = type === 'radio' ? parseInt(value, 10) : value;
console.log(`Name: ${name}, Value: ${newValue}`);
setFormData((prevState) => ({ setFormData((prevState) => ({
...prevState, ...prevState,
[name]: newValue, [name]: newValue,
@ -31,112 +36,70 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
}; };
return ( return (
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4 mt-8">
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <InputTextIcon
Nom
</label>
<input
type="text"
placeholder="Nom de l'enseignant"
name="nom" name="nom"
type="text"
IconItem={GraduationCap}
placeholder="Nom de l'enseignant"
value={formData.nom} value={formData.nom}
onChange={handleChange} onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 italic" className="w-full"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <InputTextIcon
Prénom
</label>
<input
type="text"
placeholder="Prénom de l'enseignant"
name="prenom" name="prenom"
type="text"
IconItem={GraduationCap}
placeholder="Prénom de l'enseignant"
value={formData.prenom} value={formData.prenom}
onChange={handleChange} onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 italic" className="w-full"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <InputTextIcon
Adresse email
</label>
<input
type="text"
placeholder="email de l'enseignant"
name="mail" name="mail"
type="email"
IconItem={Mail}
placeholder="Email de l'enseignant"
value={formData.mail} value={formData.mail}
onChange={handleChange} onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 italic" className="w-full mt-4"
/> />
</div> </div>
<div> <div className="flex space-x-4">
<label className="block text-sm font-medium text-gray-700"> <RadioList
Spécialités items={specialities}
</label> formData={formData}
<div className="mt-2 grid grid-cols-1 gap-4"> handleChange={handleChange}
{specialities.map(speciality => ( fieldName="specialite_id"
<div key={speciality.id} className="flex items-center"> label="Spécialités"
<input icon={BookOpen}
type="radio" className="w-full mt-4"
id={`speciality-${speciality.id}`}
name="specialite_id"
value={speciality.id}
checked={formData.specialite_id === speciality.id}
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"
/> />
<label htmlFor={`speciality-${speciality.id}`} className="ml-2 block text-sm text-gray-900 flex items-center">
{speciality.nom}
<div
className="w-4 h-4 rounded-full ml-2"
style={{ backgroundColor: speciality.codeCouleur }}
title={speciality.codeCouleur}
></div>
</label>
</div> </div>
))} <div className='mt-4'>
</div> <ToggleSwitch
</div> label="Administrateur"
<div> checked={formData.droit}
<label className="block text-sm font-medium text-gray-700"> onChange={handleToggleChange}
Types de profil
</label>
<div className="mt-2 grid grid-cols-1 gap-4">
{profils.map((profil) => (
<div key={profil.value} className="flex items-center">
<input
type="radio"
id={`profil-${profil.value}`}
name="DroitValue"
value={profil.value}
checked={formData.DroitValue === profil.value}
onChange={handleChange}
className="form-radio h-4 w-4 text-emerald-600 focus:ring-emerald-500"
/> />
<label
htmlFor={`profil-${profil.value}`}
className="ml-2 block text-sm text-gray-900"
>
{profil.label}
</label>
</div>
))}
</div>
</div> </div>
<div className="flex justify-end mt-4 space-x-4"> <div className="flex justify-end mt-4 space-x-4">
<button <Button text="Créer"
onClick={handleSubmit} onClick={handleSubmit}
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${ className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
(!formData.nom || !formData.prenom || !formData.mail || !formData.specialite_id) (!formData.nom || !formData.prenom || !formData.mail || !formData.specialite_id)
? "bg-gray-300 text-gray-700 cursor-not-allowed" ? "bg-gray-300 text-gray-700 cursor-not-allowed"
: "bg-emerald-500 text-white hover:bg-emerald-600" : "bg-emerald-500 text-white hover:bg-emerald-600"
}`} }`}
primary
disabled={(!formData.nom || !formData.prenom || !formData.mail || !formData.specialite_id)} disabled={(!formData.nom || !formData.prenom || !formData.mail || !formData.specialite_id)}
> type="submit"
Soumettre name="Create" />
</button>
</div> </div>
</form> </form>
); );

View File

@ -39,7 +39,7 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
body: JSON.stringify( { body: JSON.stringify( {
email: updatedData.mail, email: updatedData.mail,
username: updatedData.mail, username: updatedData.mail,
droit:updatedData.DroitValue droit:updatedData.droit
}), }),
} }
); );
@ -71,7 +71,7 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
password: 'Provisoire01!', password: 'Provisoire01!',
username: updatedData.mail, username: updatedData.mail,
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 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.DroitValue droit:updatedData.droit
}), }),
} }
); );
@ -159,7 +159,9 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
setIsOpen={setIsOpen} setIsOpen={setIsOpen}
title={editingTeacher ? "Modification de l'enseignant" : "Création d'un nouvel enseignant"} ContentComponent={() => ( 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} /> <TeacherForm teacher={editingTeacher || {}} onSubmit={handleModalSubmit} isNew={!editingTeacher} specialities={specialities} />
)} )}
/> />

View File

@ -0,0 +1,25 @@
import React from 'react';
const ToggleSwitch = ({ label, checked, onChange }) => {
return (
<div className="flex items-center mt-4">
<label className="mr-2 text-gray-600">{label}</label>
<div className="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
<input
type="checkbox"
name="toggle"
id="toggle"
checked={checked}
onChange={onChange}
className="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer border-emerald-500 checked:right-0 checked:border-emerald-500 checked:bg-emerald-500 focus:border-emerald-500 focus:bg-emerald-500 hover:border-emerald-500 hover:bg-emerald-500"
/>
<label
htmlFor="toggle"
className={`toggle-label block overflow-hidden h-6 rounded-full cursor-pointer transition-colors duration-200 ${checked ? 'bg-emerald-300' : 'bg-gray-300'}`}
></label>
</div>
</div>
);
};
export default ToggleSwitch;