feat: Création d'un annuaire / mise à jour du subscribe

This commit is contained in:
N3WT DE COMPET
2025-03-14 19:51:35 +01:00
parent cd9c10a88a
commit 6bd5704983
22 changed files with 585 additions and 185 deletions

View File

@ -22,7 +22,7 @@ const CheckBoxList = ({
<div className={`mt-2 grid ${horizontal ? 'grid-cols-6 gap-2' : 'grid-cols-1 gap-4'}`}>
{items.map(item => (
<CheckBox
key={item.id}
key={`${fieldName}-${item.id}`}
item={item}
formData={formData}
handleChange={handleChange}

View File

@ -291,6 +291,24 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
</div>
{formData.responsableType === 'new' && (
<>
<InputTextIcon
name="guardianLastName"
type="text"
IconItem={User}
placeholder="Nom du responsable (optionnel)"
value={formData.guardianLastName}
onChange={handleChange}
className="w-full mt-4"
/>
<InputTextIcon
name="guardianFirstName"
type="text"
IconItem={User}
placeholder="Prénom du responsable (optionnel)"
value={formData.guardianFirstName}
onChange={handleChange}
className="w-full mt-4"
/>
<InputTextIcon
name="guardianEmail"
type="email"

View File

@ -0,0 +1,218 @@
import React, { useState } from 'react';
import { Trash2, Eye, EyeOff, ToggleLeft, ToggleRight } from 'lucide-react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
import StatusLabel from '@/components/StatusLabel';
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
const roleTypeToLabel = (roleType) => {
switch (roleType) {
case 0:
return 'ECOLE';
case 1:
return 'ADMIN';
case 2:
return 'PARENT';
default:
return 'UNKNOWN';
}
};
const roleTypeToBadgeClass = (roleType) => {
switch (roleType) {
case 0:
return 'bg-blue-100 text-blue-600';
case 1:
return 'bg-red-100 text-red-600';
case 2:
return 'bg-green-100 text-green-600';
default:
return 'bg-gray-100 text-gray-600';
}
};
const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeleteProfile }) => {
const parentProfiles = profileRoles.filter(profileRole => profileRole.role_type === 2);
const schoolAdminProfiles = profileRoles.filter(profileRole => profileRole.role_type !== 2);
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState("");
const [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
const [confirmPopupMessage, setConfirmPopupMessage] = useState("");
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {});
const handleConfirmActivateProfile = (profileRole) => {
setConfirmPopupMessage(`Êtes-vous sûr de vouloir ${profileRole.is_active ? 'désactiver' : 'activer'} ce profil ?`);
setConfirmPopupOnConfirm(() => () => {
handleActivateProfile(profileRole)
.then(() => {
setPopupMessage(`Le profil a été ${profileRole.is_active ? 'désactivé' : 'activé'} avec succès.`);
setPopupVisible(true);
})
.catch(error => {
setPopupMessage(`Erreur lors de la ${profileRole.is_active ? 'désactivation' : 'activation'} du profil.`);
setPopupVisible(true);
});
setConfirmPopupVisible(false);
});
setConfirmPopupVisible(true);
};
const handleConfirmDeleteProfile = (id) => {
setConfirmPopupMessage("Êtes-vous sûr de vouloir supprimer ce profil ?");
setConfirmPopupOnConfirm(() => () => {
handleDeleteProfile(id)
.then(() => {
setPopupMessage("Le profil a été supprimé avec succès.");
setPopupVisible(true);
})
.catch(error => {
setPopupMessage("Erreur lors de la suppression du profil.");
setPopupVisible(true);
});
setConfirmPopupVisible(false);
});
setConfirmPopupVisible(true);
};
const parentColumns = [
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
{ name: 'Rôle', transform: (row) => (
<span className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}>
{roleTypeToLabel(row.role_type)}
</span>
)
},
{ name: 'Utilisateur', transform: (row) => row.associated_person?.guardian_name },
{ name: 'Elève(s) associé(s)', transform: (row) => (
<div className="flex flex-col justify-center space-y-2">
{row.associated_person?.students?.map(student => (
<span key={student.student_name} className="px-2 py-1 rounded-full text-gray-800">
{student.student_name}
</span>
))}
</div>
)
},
{ name: 'Etat du dossier d\'inscription', transform: (row) => (
<div className="flex flex-col justify-center items-center space-y-2">
{row.associated_person?.students?.map(student => (
<StatusLabel key={student.student_name} status={student.registration_status} showDropdown={false} />
))}
</div>
)
},
{
name: 'Actions',
transform: (row) => (
<div className="flex justify-center space-x-2">
<button
type="button"
className={row.is_active ? 'text-emerald-500 hover:text-emerald-700' : 'text-orange-500 hover:text-orange-700'}
onClick={() => handleConfirmActivateProfile(row)}
>
{row.is_active ? <ToggleRight className="w-5 h-5 " /> : <ToggleLeft className="w-5 h-5" />}
</button>
<button
type="button"
className="text-red-500 hover:text-red-700"
onClick={() => handleConfirmDeleteProfile(row.id)}
>
<Trash2 className="w-5 h-5" />
</button>
</div>
)
}
];
const schoolAdminColumns = [
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
{ name: 'Rôle', transform: (row) => (
<span className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}>
{roleTypeToLabel(row.role_type)}
</span>
)
},
{ name: 'Utilisateur', transform: (row) => row.associated_person?.teacher_name },
{ name: 'Classe(s) associée(s)', transform: (row) => (
<div className="flex flex-wrap justify-center space-x-2">
{row.associated_person?.classes?.map(classe => (
<span key={classe.id} className="px-2 py-1 rounded-full bg-gray-200 text-gray-800">
{classe.name}
</span>
))}
</div>
)
},
{ name: 'Spécialités', transform: (row) => (
<div className="flex flex-wrap justify-center space-x-2">
{row.associated_person?.specialities?.map(speciality => (
<SpecialityItem speciality={speciality} isDraggable={false}/>
))}
</div>
)
},
{
name: 'Actions',
transform: (row) => (
<div className="flex justify-center space-x-2">
<button
type="button"
className={row.is_active ? 'text-emerald-500 hover:text-emerald-700' : 'text-orange-500 hover:text-orange-700'}
onClick={() => handleConfirmActivateProfile(row)}
>
{row.is_active ? <ToggleRight className="w-5 h-5 " /> : <ToggleLeft className="w-5 h-5" />}
</button>
<button
type="button"
className="text-red-500 hover:text-red-700"
onClick={() => handleConfirmDeleteProfile(row.id)}
>
<Trash2 className="w-5 h-5" />
</button>
</div>
)
}
];
return (
<div className="bg-white rounded-lg shadow-lg w-full p-6">
<div className="space-y-8">
<div className="max-h-128 overflow-y-auto border rounded p-4">
{parentProfiles.length === 0 ? (
<div>Aucun profil trouvé</div>
) : (
<Table
data={parentProfiles}
columns={parentColumns}
/>
)}
</div>
<div className="max-h-128 overflow-y-auto border rounded p-4">
{schoolAdminProfiles.length === 0 ? (
<div>Aucun profil trouvé</div>
) : (
<Table
data={schoolAdminProfiles}
columns={schoolAdminColumns}
/>
)}
</div>
</div>
<Popup
visible={popupVisible}
message={popupMessage}
onConfirm={() => setPopupVisible(false)}
uniqueConfirmButton={true}
/>
<Popup
visible={confirmPopupVisible}
message={confirmPopupMessage}
onConfirm={confirmPopupOnConfirm}
onCancel={() => setConfirmPopupVisible(false)}
/>
</div>
);
};
export default ProfileDirectory;

View File

@ -4,6 +4,8 @@ import { SessionProvider } from "next-auth/react"
import { CsrfProvider } from '@/context/CsrfContext'
import { NextIntlClientProvider } from 'next-intl'
import { EstablishmentProvider } from '@/context/EstablishmentContext';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
export default function Providers({ children, messages, locale, session }) {
@ -13,13 +15,15 @@ export default function Providers({ children, messages, locale, session }) {
}
return (
<SessionProvider session={session}>
<CsrfProvider>
<EstablishmentProvider>
<NextIntlClientProvider messages={messages} locale={locale}>
{children}
</NextIntlClientProvider>
</EstablishmentProvider>
</CsrfProvider>
<DndProvider backend={HTML5Backend}>
<CsrfProvider>
<EstablishmentProvider>
<NextIntlClientProvider messages={messages} locale={locale}>
{children}
</NextIntlClientProvider>
</EstablishmentProvider>
</CsrfProvider>
</DndProvider>
</SessionProvider>
)
}

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Plus, Edit3, Trash2, GraduationCap, Check, X, Hand } from 'lucide-react';
import { Plus, Edit3, Trash2, GraduationCap, Check, X, Hand, Search } from 'lucide-react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
import ToggleSwitch from '@/components/ToggleSwitch';
@ -11,6 +11,8 @@ import InputText from '@/components/InputText';
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
import TeacherItem from './TeacherItem';
import logger from '@/utils/logger';
import { fetchProfiles } from '@/app/actions/authAction';
import { useEstablishment } from '@/context/EstablishmentContext';
const ItemTypes = {
SPECIALITY: 'speciality',
@ -103,14 +105,40 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
const [removePopupMessage, setRemovePopupMessage] = useState("");
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
const [confirmPopupMessage, setConfirmPopupMessage] = useState("");
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {});
const { selectedEstablishmentId } = useEstablishment();
const handleSelectProfile = (profile) => {
setNewTeacher((prevData) => ({
...prevData,
selectedProfile: profile,
}));
setFormData((prevData) => ({
...prevData,
selectedProfile: profile
}));
setConfirmPopupMessage(`Vous êtes sur le point de rattacher l'enseignant ${newTeacher?.first_name} ${newTeacher?.last_name} au profil ${profile.email} ID = ${profile.id}.`);
setConfirmPopupOnConfirm(() => () => {
setConfirmPopupVisible(false);
});
setConfirmPopupVisible(true);
};
const handleCancelConfirmation = () => {
setConfirmPopupVisible(false);
};
// Récupération des messages d'erreur
const getError = (field) => {
return localErrors?.[field]?.[0];
};
const handleAddTeacher = () => {
setNewTeacher({ id: Date.now(), last_name: '', first_name: '', email: '', specialities: [], droit: 0 });
setFormData({ last_name: '', first_name: '', email: '', specialities: [], droit: 0 });
setNewTeacher({ id: Date.now(), last_name: '', first_name: '', selectedProfile: null, specialities: [], droit: 0 });
setFormData({ last_name: '', first_name: '', selectedProfile: null, specialities: [], droit: 0});
};
const handleRemoveTeacher = (id) => {
@ -124,43 +152,32 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
};
const handleSaveNewTeacher = () => {
if (formData.last_name && formData.first_name && formData.email) {
if (formData.last_name && formData.first_name && formData.selectedProfile) {
const data = {
email: formData.email,
password: 'Provisoire01!',
username: formData.email,
is_active: 1,
droit: formData.droit,
last_name: formData.last_name,
first_name: formData.first_name,
profile_role_data: {
role_type: formData.droit,
establishment: selectedEstablishmentId,
is_active: true,
profile: formData.selectedProfile.id
},
specialities: formData.specialities
};
createProfile(data, csrfToken)
.then(response => {
logger.debug('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) => {
logger.error('Error:', error.message);
if (error.details) {
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
}
});
}
setLocalErrors({});
})
.catch((error) => {
logger.error('Error:', error.message);
if (error.details) {
handleCreate(data)
.then((createdTeacher) => {
setTeachers([createdTeacher, ...teachers]);
setNewTeacher(null);
setLocalErrors({});
})
.catch((error) => {
logger.error('Error:', error.message);
if (error.details) {
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
}
});
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis et valides");
setPopupVisible(true);
@ -260,7 +277,6 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
<div className="flex justify-center space-x-2">
<InputText
name="last_name"
label="nom"
value={currentData.last_name}
onChange={handleChange}
placeholder="Nom de l'enseignant"
@ -268,7 +284,6 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
/>
<InputText
name="first_name"
label="prénom"
value={currentData.first_name}
onChange={handleChange}
placeholder="Prénom de l'enseignant"
@ -276,16 +291,26 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
/>
</div>
);
case 'EMAIL':
return (
<InputText
name="email"
value={currentData.email}
onChange={handleChange}
placeholder="Email de l'enseignant"
errorMsg={getError('email')}
/>
);
case 'EMAIL':
return (
<div className="flex items-center space-x-2">
{currentData.selectedProfile ? (
<span className="text-gray-900">
{currentData.selectedProfile.email}
</span>
) : (
<span className="text-gray-500 italic">
Rechercher un profil existant
</span>
)}
<button
type="button"
onClick={() => setDirectoryPopupVisible(true)}
>
<Search className="w-5 h-5 text-emerald-500 hover:text-emerald-700"/>
</button>
</div>
);
case 'SPECIALITES':
return (
<SpecialitiesDropZone teacher={currentData} handleSpecialitiesChange={handleSpecialitiesChange} specialities={specialities} isEditing={isEditing || isCreating} />
@ -339,9 +364,9 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
</div>
);
case 'ADMINISTRATEUR':
if (teacher.associated_profile) {
const badgeClass = teacher.droit === 1 ? 'bg-red-100 text-red-600' : 'bg-blue-100 text-blue-600';
const label = teacher.droit === 1 ? 'OUI' : 'NON';
if (teacher.associated_profile_email) {
const badgeClass = teacher.role_type.role_type === 1 ? 'bg-red-100 text-red-600' : 'bg-blue-100 text-blue-600';
const label = teacher.role_type.role_type === 1 ? 'OUI' : 'NON';
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}`}>