mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
refactor: Création de composants et uniformisation des modales (#2)
This commit is contained in:
@ -4,3 +4,8 @@ from django.apps import AppConfig
|
||||
class GestionenseignantsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'GestionEnseignants'
|
||||
|
||||
def ready(self):
|
||||
from .models import Specialite
|
||||
if not Specialite.objects.filter(nom='TRANSVERSE').exists():
|
||||
Specialite.objects.create(nom='TRANSVERSE', codeCouleur='#FF0000')
|
||||
|
||||
@ -7,8 +7,8 @@ class Profil(AbstractUser):
|
||||
class Droits(models.IntegerChoices):
|
||||
PROFIL_UNDEFINED = -1, _('NON DEFINI')
|
||||
PROFIL_ECOLE = 0, _('ECOLE')
|
||||
PROFIL_PARENT = 1, _('PARENT')
|
||||
PROFIL_ADMIN = 2, _('ADMIN')
|
||||
PROFIL_ADMIN = 1, _('ADMIN')
|
||||
PROFIL_PARENT = 2, _('PARENT')
|
||||
|
||||
email = models.EmailField(max_length=255, unique=True, default="", validators=[EmailValidator()])
|
||||
|
||||
|
||||
@ -335,7 +335,7 @@ export default function Page({ params: { locale } }) {
|
||||
password: 'Provisoire01!',
|
||||
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
|
||||
droit:1
|
||||
droit:2 // Profil PARENT
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
@ -80,13 +80,12 @@ export default function Page() {
|
||||
localStorage.setItem('userId', data.profil); // Stocker l'identifiant de l'utilisateur
|
||||
if (data.droit == 0) {
|
||||
// Vue ECOLE
|
||||
|
||||
} else if (data.droit == 1) {
|
||||
// Vue PARENT
|
||||
router.push(`${FR_PARENTS_HOME_URL}`);
|
||||
} else if (data.droit == 2) {
|
||||
// Vue ADMIN
|
||||
router.push(`${FR_ADMIN_SUBSCRIPTIONS_URL}`);
|
||||
} else if (data.droit == 2) {
|
||||
// Vue PARENT
|
||||
router.push(`${FR_PARENTS_HOME_URL}`);
|
||||
} else {
|
||||
// Cas anormal
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
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 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 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) => {
|
||||
if (href) {
|
||||
@ -17,7 +17,7 @@ const Button = ({ text, onClick, href, className, primary, icon }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<button className={buttonClass} onClick={handleClick}>
|
||||
<button className={buttonClass} onClick={handleClick} disabled={disabled}>
|
||||
{icon && <span className="mr-2">{icon}</span>}
|
||||
{text}
|
||||
</button>
|
||||
|
||||
@ -1,13 +1,24 @@
|
||||
import React, { useState } from 'react';
|
||||
import Slider from '@/components/Slider'
|
||||
import InputTextIcon from '@/components/InputTextIcon';
|
||||
import Button from '@/components/Button';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
import RadioList from '@/components/RadioList';
|
||||
import { Users, Maximize2, Globe, Calendar, GraduationCap } from 'lucide-react';
|
||||
|
||||
const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => {
|
||||
const langues = [
|
||||
{ value:'Français', label: 'Français'},
|
||||
{ value:'Anglais', label: 'Anglais'},
|
||||
{ value:'Espagnol', label: 'Espagnol'},
|
||||
];
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
nom_ambiance: classe.nom_ambiance || '',
|
||||
tranche_age: classe.tranche_age || [3, 6],
|
||||
nombre_eleves: classe.nombre_eleves || '',
|
||||
langue_enseignement: classe.langue_enseignement || 'Français',
|
||||
annee_scolaire: classe.annee_scolaire || '2024-2025',
|
||||
annee_scolaire: classe.annee_scolaire || '',
|
||||
specialites_ids: classe.specialites_ids || [],
|
||||
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 (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4 mt-8">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Nom d'ambiance
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nom de l'ambiance"
|
||||
<InputTextIcon
|
||||
name="nom_ambiance"
|
||||
type="text"
|
||||
IconItem={Users}
|
||||
placeholder="Nom d'ambiance"
|
||||
value={formData.nom_ambiance}
|
||||
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>
|
||||
@ -69,51 +82,43 @@ const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => {
|
||||
Tranche d'âge
|
||||
</label>
|
||||
<Slider
|
||||
min={3}
|
||||
max={12}
|
||||
min={2}
|
||||
max={18}
|
||||
value={formData.tranche_age}
|
||||
onChange={handleSliderChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Nombre d'élèves
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
<InputTextIcon
|
||||
name="nombre_eleves"
|
||||
type="number"
|
||||
IconItem={Maximize2}
|
||||
placeholder="Capacité max"
|
||||
value={formData.nombre_eleves}
|
||||
onChange={handleNumberChange}
|
||||
min="1"
|
||||
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"
|
||||
onChange={handleChange}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Langue d'enseignement
|
||||
</label>
|
||||
<select
|
||||
name="langue_enseignement"
|
||||
value={formData.langue_enseignement}
|
||||
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"
|
||||
>
|
||||
<option value="Français">Français</option>
|
||||
<option value="Anglais">Anglais</option>
|
||||
<option value="Espagnol">Espagnol</option>
|
||||
</select>
|
||||
<SelectChoice
|
||||
name="langue_enseignement"
|
||||
label="Langue d'enseignement"
|
||||
selected={formData.langue_enseignement}
|
||||
callback={handleChange}
|
||||
choices={langues}
|
||||
IconItem={Globe}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Année scolaire
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
<InputTextIcon
|
||||
name="annee_scolaire"
|
||||
type="text"
|
||||
IconItem={Calendar}
|
||||
placeholder="Année scolaire (20xx-20xx)"
|
||||
value={formData.annee_scolaire}
|
||||
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>
|
||||
@ -144,41 +149,30 @@ const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Enseignant principal
|
||||
</label>
|
||||
<div className="mt-2 grid grid-cols-1 gap-4">
|
||||
{teachers.map(teacher => (
|
||||
<div key={teacher.id} className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
id={`teacher-${teacher.id}`}
|
||||
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 className="flex space-x-4">
|
||||
<RadioList
|
||||
items={teachers}
|
||||
formData={formData}
|
||||
handleChange={handleChange}
|
||||
fieldName="enseignant_principal_id"
|
||||
label="Enseignant principal"
|
||||
itemLabelFunc={getTeacherLabel}
|
||||
icon={GraduationCap}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end mt-4 space-x-4">
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
(!formData.nom_ambiance || !formData.nombre_eleves)
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
disabled={(!formData.nom_ambiance || !formData.nombre_eleves)}
|
||||
>
|
||||
Soumettre
|
||||
</button>
|
||||
<Button text="Créer"
|
||||
onClick={handleSubmit}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
(!formData.nom_ambiance || !formData.nombre_eleves || !formData.annee_scolaire || !formData.enseignant_principal_id)
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
primary
|
||||
disabled={(!formData.nom_ambiance || !formData.nombre_eleves || !formData.annee_scolaire || !formData.enseignant_principal_id)}
|
||||
type="submit"
|
||||
name="Create" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@ -107,9 +107,11 @@ const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleE
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
title={editingClass ? "Modification de la classe" : "Création d'une nouvelle classe"} ContentComponent={() => (
|
||||
<ClassForm classe={editingClass || {}} onSubmit={handleModalSubmit} isNew={!editingClass} specialities={specialities} teachers={teachers} />
|
||||
)}
|
||||
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} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{isOpenDetails && (
|
||||
|
||||
28
Front-End/src/components/InputColorIcon.js
Normal file
28
Front-End/src/components/InputColorIcon.js
Normal 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;
|
||||
@ -1,6 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { User, Mail, Phone, UserCheck } from 'lucide-react';
|
||||
import InputTextIcon from '@/components/InputTextIcon';
|
||||
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||
import Button from '@/components/Button';
|
||||
|
||||
const InscriptionForm = ( { eleves, onSubmit }) => {
|
||||
const [formData, setFormData] = useState({
|
||||
@ -9,18 +11,17 @@ const InscriptionForm = ( { eleves, onSubmit }) => {
|
||||
responsableEmail: '',
|
||||
responsableTel: '',
|
||||
selectedResponsables: [],
|
||||
responsableType: 'new'
|
||||
responsableType: 'new',
|
||||
autoMail: false
|
||||
});
|
||||
|
||||
const [step, setStep] = useState(1);
|
||||
const [selectedEleve, setSelectedEleve] = useState('');
|
||||
const [existingResponsables, setExistingResponsables] = useState([]);
|
||||
const [autoMail, setAutoMail] = useState(false);
|
||||
const maxStep = 4
|
||||
|
||||
const handleToggleChange = () => {
|
||||
setAutoMail(!autoMail);
|
||||
setFormData({ ...formData, autoMail: !autoMail });
|
||||
const handleToggleChange = () => {
|
||||
setFormData({ ...formData, autoMail: !formData.autoMail });
|
||||
};
|
||||
|
||||
const handleChange = (e) => {
|
||||
@ -62,7 +63,7 @@ const InscriptionForm = ( { eleves, onSubmit }) => {
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
onSubmit({ ...formData, autoMail });
|
||||
onSubmit(formData);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -167,7 +168,7 @@ const InscriptionForm = ( { eleves, onSubmit }) => {
|
||||
onChange={() => handleResponsableSelection(responsable.id)}
|
||||
/>
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
@ -232,59 +233,46 @@ const InscriptionForm = ( { eleves, onSubmit }) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-4">
|
||||
<label className="mr-2 text-gray-600">Envoi automatique</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={autoMail}
|
||||
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 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
|
||||
onClick={prevStep}
|
||||
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md shadow-sm hover:bg-gray-400 focus:outline-none"
|
||||
>
|
||||
Précédent
|
||||
</button>
|
||||
<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
|
||||
onClick={nextStep}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
(step === 1 && (!formData.eleveNom || !formData.elevePrenom)) ||
|
||||
(step === 2 && formData.responsableType === "new" && !formData.responsableEmail) ||
|
||||
(step === 2 && formData.responsableType === "existing" && formData.selectedResponsables.length === 0)
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
disabled={(step === 1 && (!formData.eleveNom || !formData.elevePrenom)) ||
|
||||
(step === 2 && formData.responsableType === "new" && !formData.responsableEmail) ||
|
||||
(step === 2 && formData.responsableType === "existing" && formData.selectedResponsables.length === 0)
|
||||
}
|
||||
>
|
||||
Suivant
|
||||
</button>
|
||||
<Button text="Suivant"
|
||||
onClick={nextStep}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
(step === 1 && (!formData.eleveNom || !formData.elevePrenom)) ||
|
||||
(step === 2 && formData.responsableType === "new" && !formData.responsableEmail) ||
|
||||
(step === 2 && formData.responsableType === "existing" && formData.selectedResponsables.length === 0)
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
disabled={(step === 1 && (!formData.eleveNom || !formData.elevePrenom)) ||
|
||||
(step === 2 && formData.responsableType === "new" && !formData.responsableEmail) ||
|
||||
(step === 2 && formData.responsableType === "existing" && formData.selectedResponsables.length === 0)
|
||||
}
|
||||
primary
|
||||
name="Next" />
|
||||
) : (
|
||||
<button
|
||||
onClick={submit}
|
||||
className="px-4 py-2 bg-emerald-500 text-white rounded-md shadow-sm hover:bg-emerald-600 focus:outline-none"
|
||||
>
|
||||
Créer
|
||||
</button>
|
||||
<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" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import Button from '@/components/Button';
|
||||
|
||||
const Modal = ({ isOpen, setIsOpen, title, ContentComponent, size }) => {
|
||||
return (
|
||||
@ -13,14 +14,14 @@ const Modal = ({ isOpen, setIsOpen, title, ContentComponent, size }) => {
|
||||
<div className="mt-2">
|
||||
<ContentComponent />
|
||||
</div>
|
||||
<div className="mt-4 flex justify-end">
|
||||
<div className="mt-4 flex justify-end space-x-4">
|
||||
<Dialog.Close asChild>
|
||||
<button
|
||||
className="px-4 py-2 rounded-md shadow-sm focus:outline-none bg-gray-300 text-gray-700 hover:bg-gray-400"
|
||||
<Button text="Fermer"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Fermer
|
||||
</button>
|
||||
className="px-4 py-2 rounded-md shadow-sm focus:outline-none bg-gray-300 text-gray-700 hover:bg-gray-400"
|
||||
secondary
|
||||
type="submit"
|
||||
name="Create" />
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
39
Front-End/src/components/RadioList.js
Normal file
39
Front-End/src/components/RadioList.js
Normal 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;
|
||||
@ -1,18 +1,23 @@
|
||||
export default function SelectChoice({type, name, label, choices, callback, selected, error }) {
|
||||
export default function SelectChoice({type, name, label, choices, callback, selected, error, IconItem }) {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">{label}</label>
|
||||
<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`}
|
||||
type={type}
|
||||
id={name}
|
||||
name={name}
|
||||
value={selected}
|
||||
onChange={callback}
|
||||
>
|
||||
{choices.map(({ value, label }, index) => <option key={value} value={value}>{label}</option>)}
|
||||
</select>
|
||||
<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
|
||||
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}
|
||||
id={name}
|
||||
name={name}
|
||||
value={selected}
|
||||
onChange={callback}
|
||||
>
|
||||
{choices.map(({ value, label }, index) => <option key={value} value={value}>{label}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
{error && <p className="mt-2 text-sm text-red-600">{error}</p>}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -23,28 +23,30 @@ const Slider = ({ min, max, value, onChange }) => {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center space-x-2 w-1/2">
|
||||
<span className="text-emerald-600">{value[0]}</span>
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
value={value[0]}
|
||||
onChange={handleMinChange}
|
||||
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 className="flex items-center space-x-2 w-1/2">
|
||||
<span className="text-emerald-600">{value[1]}</span>
|
||||
<input
|
||||
type="range"
|
||||
min={value[0] + 1}
|
||||
max={max}
|
||||
value={value[1]}
|
||||
onChange={handleMaxChange}
|
||||
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 className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-2 w-1/2">
|
||||
<span className="text-emerald-600">{value[0]}</span>
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
value={value[0]}
|
||||
onChange={handleMinChange}
|
||||
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 className="flex items-center space-x-2 w-1/2 justify-end">
|
||||
<span className="text-emerald-600">{value[1]}</span>
|
||||
<input
|
||||
type="range"
|
||||
min={value[0] + 1}
|
||||
max={max}
|
||||
value={value[1]}
|
||||
onChange={handleMaxChange}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -75,9 +75,11 @@ const SpecialitiesSection = ({ specialities, handleCreate, handleEdit, handleDel
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
title={editingSpeciality ? "Modification de la spécialité" : "Création d'une nouvelle spécialité"} ContentComponent={() => (
|
||||
<SpecialityForm speciality={editingSpeciality || {}} onSubmit={handleModalSubmit} isNew={!editingSpeciality} />
|
||||
)}
|
||||
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} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
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 [nom, setNom] = useState(speciality.nom || '');
|
||||
@ -13,35 +17,39 @@ const SpecialityForm = ({ speciality = {}, onSubmit, isNew }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="space-y-4 mt-8">
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nom de la spécialité"
|
||||
value={nom}
|
||||
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"
|
||||
<InputTextIcon
|
||||
type="text"
|
||||
IconItem={BookOpen}
|
||||
placeholder="Nom de la spécialité"
|
||||
value={nom}
|
||||
onChange={(e) => setNom(e.target.value)}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Code couleur de la spécialité
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
value={codeCouleur}
|
||||
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"
|
||||
style={{ appearance: 'none', borderRadius: '0' }}
|
||||
<InputColorIcon
|
||||
type="color"
|
||||
IconItem={Palette}
|
||||
placeholder="Nom de la spécialité"
|
||||
value={codeCouleur}
|
||||
onChange={(e) => setCodeCouleur(e.target.value)}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end mt-4 space-x-4">
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className="px-4 py-2 bg-emerald-500 text-white rounded-md shadow-sm hover:bg-emerald-600 focus:outline-none"
|
||||
>
|
||||
Soumettre
|
||||
</button>
|
||||
<Button text="Créer"
|
||||
onClick={handleSubmit}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
!nom
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
primary
|
||||
disabled={!nom}
|
||||
type="submit"
|
||||
name="Create" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
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 profils = [
|
||||
{ value: 0, label: "École" },
|
||||
{ value: 2, label: "Administrateur" },
|
||||
];
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
nom: teacher.nom || '',
|
||||
@ -13,13 +14,17 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
|
||||
specialite_id: teacher.specialite_id || '',
|
||||
classes: teacher.classes || [],
|
||||
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 { name, value, type } = e.target;
|
||||
const newValue = type === 'radio' ? parseInt(value, 10) : value;
|
||||
console.log(`Name: ${name}, Value: ${newValue}`);
|
||||
setFormData((prevState) => ({
|
||||
...prevState,
|
||||
[name]: newValue,
|
||||
@ -31,112 +36,70 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4 mt-8">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Nom
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nom de l'enseignant"
|
||||
<InputTextIcon
|
||||
name="nom"
|
||||
type="text"
|
||||
IconItem={GraduationCap}
|
||||
placeholder="Nom de l'enseignant"
|
||||
value={formData.nom}
|
||||
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>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Prénom
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Prénom de l'enseignant"
|
||||
<InputTextIcon
|
||||
name="prenom"
|
||||
type="text"
|
||||
IconItem={GraduationCap}
|
||||
placeholder="Prénom de l'enseignant"
|
||||
value={formData.prenom}
|
||||
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>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Adresse email
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="email de l'enseignant"
|
||||
name="mail"
|
||||
value={formData.mail}
|
||||
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"
|
||||
<InputTextIcon
|
||||
name="mail"
|
||||
type="email"
|
||||
IconItem={Mail}
|
||||
placeholder="Email de l'enseignant"
|
||||
value={formData.mail}
|
||||
onChange={handleChange}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Spécialités
|
||||
</label>
|
||||
<div className="mt-2 grid grid-cols-1 gap-4">
|
||||
{specialities.map(speciality => (
|
||||
<div key={speciality.id} className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
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="flex space-x-4">
|
||||
<RadioList
|
||||
items={specialities}
|
||||
formData={formData}
|
||||
handleChange={handleChange}
|
||||
fieldName="specialite_id"
|
||||
label="Spécialités"
|
||||
icon={BookOpen}
|
||||
className="w-full mt-4"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
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 className='mt-4'>
|
||||
<ToggleSwitch
|
||||
label="Administrateur"
|
||||
checked={formData.droit}
|
||||
onChange={handleToggleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end mt-4 space-x-4">
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
(!formData.nom || !formData.prenom || !formData.mail || !formData.specialite_id)
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
disabled={(!formData.nom || !formData.prenom || !formData.mail || !formData.specialite_id)}
|
||||
>
|
||||
Soumettre
|
||||
</button>
|
||||
<Button text="Créer"
|
||||
onClick={handleSubmit}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
(!formData.nom || !formData.prenom || !formData.mail || !formData.specialite_id)
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
primary
|
||||
disabled={(!formData.nom || !formData.prenom || !formData.mail || !formData.specialite_id)}
|
||||
type="submit"
|
||||
name="Create" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@ -39,7 +39,7 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
||||
body: JSON.stringify( {
|
||||
email: updatedData.mail,
|
||||
username: updatedData.mail,
|
||||
droit:updatedData.DroitValue
|
||||
droit:updatedData.droit
|
||||
}),
|
||||
}
|
||||
);
|
||||
@ -71,7 +71,7 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
||||
password: 'Provisoire01!',
|
||||
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
|
||||
droit:updatedData.DroitValue
|
||||
droit:updatedData.droit
|
||||
}),
|
||||
}
|
||||
);
|
||||
@ -159,9 +159,11 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
title={editingTeacher ? "Modification de l'enseignant" : "Création d'un nouvel enseignant"} ContentComponent={() => (
|
||||
<TeacherForm teacher={editingTeacher || {}} onSubmit={handleModalSubmit} isNew={!editingTeacher} specialities={specialities} />
|
||||
)}
|
||||
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} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
25
Front-End/src/components/ToggleSwitch.js
Normal file
25
Front-End/src/components/ToggleSwitch.js
Normal 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;
|
||||
Reference in New Issue
Block a user