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,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;
|
||||
|
||||
Reference in New Issue
Block a user