mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Gestion des profils des enseignants / Visualisation d'une classe [#4]
This commit is contained in:
68
Front-End/src/components/AffectationClasseForm.js
Normal file
68
Front-End/src/components/AffectationClasseForm.js
Normal file
@ -0,0 +1,68 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const AffectationClasseForm = ({ eleve, onSubmit, classes }) => {
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
classeAssocie_id: eleve.classeAssocie_id || null,
|
||||
});
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type } = e.target;
|
||||
setFormData((prevState) => ({
|
||||
...prevState,
|
||||
[name]: parseInt(value, 10),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSubmit({
|
||||
eleve: {
|
||||
...formData
|
||||
},
|
||||
etat:5
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Classes
|
||||
</label>
|
||||
<div className="mt-2 grid grid-cols-1 gap-4">
|
||||
{classes.map(classe => (
|
||||
<div key={classe.id} className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
id={`classe-${classe.id}`}
|
||||
name="classeAssocie_id"
|
||||
value={classe.id}
|
||||
checked={formData.classeAssocie_id === classe.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={`classe-${classe.id}`} className="ml-2 block text-sm text-gray-900 flex items-center">
|
||||
{classe.nom_ambiance}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</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.classeAssocie_id )
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
disabled={(!formData.classeAssocie_id)}
|
||||
>
|
||||
Associer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AffectationClasseForm;
|
||||
72
Front-End/src/components/ClasseDetails.js
Normal file
72
Front-End/src/components/ClasseDetails.js
Normal file
@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import Table from '@/components/Table';
|
||||
import { GraduationCap } from 'lucide-react';
|
||||
|
||||
const ClasseDetails = ({ classe }) => {
|
||||
if (!classe) return null;
|
||||
|
||||
const nombreElevesInscrits = classe.eleves.length;
|
||||
const capaciteTotale = classe.nombre_eleves;
|
||||
const pourcentage = Math.round((nombreElevesInscrits / capaciteTotale) * 100);
|
||||
|
||||
const getColor = (pourcentage) => {
|
||||
if (pourcentage < 50) return 'bg-emerald-500';
|
||||
if (pourcentage < 75) return 'bg-orange-500';
|
||||
return 'bg-red-500';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="mb-4 flex justify-between items-center">
|
||||
{/* Section Enseignant Principal */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="bg-gray-100 p-3 rounded-lg shadow-md flex items-center space-x-4">
|
||||
<GraduationCap className="w-10 h-10 text-gray-600" />
|
||||
<div>
|
||||
<p className="italic text-gray-600">Enseignant Principal :</p>
|
||||
<p className="font-bold text-gray-800">
|
||||
{classe.enseignant_principal ? (
|
||||
`${classe.enseignant_principal.nom} ${classe.enseignant_principal.prenom}`
|
||||
) : (
|
||||
<i>Non assigné</i>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section Capacité de la Classe */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center">
|
||||
<span className="font-bold text-gray-700 mr-4">
|
||||
{nombreElevesInscrits}/{capaciteTotale}
|
||||
</span>
|
||||
<div className="w-32 bg-gray-200 rounded-full h-6 shadow-inner">
|
||||
<div
|
||||
className={`h-full rounded-full ${getColor(pourcentage)}`}
|
||||
style={{ width: `${pourcentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="ml-4 font-bold text-gray-700">
|
||||
{pourcentage}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-semibold mb-4">Liste des élèves</h3>
|
||||
<div className="bg-white rounded-lg border border-gray-200 shadow-md">
|
||||
<Table
|
||||
columns={[
|
||||
{ name: 'NOM', transform: (row) => row.nom },
|
||||
{ name: 'PRENOM', transform: (row) => row.prenom },
|
||||
{ name: 'AGE', transform: (row) => `${row.age}` }
|
||||
]}
|
||||
data={classe.eleves}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClasseDetails;
|
||||
@ -1,13 +1,15 @@
|
||||
import { School, Trash2, MoreVertical, Edit3, Plus, ZoomIn } from 'lucide-react';
|
||||
import { Users, Trash2, MoreVertical, Edit3, Plus, ZoomIn } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import Table from '@/components/Table';
|
||||
import DropdownMenu from '@/components/DropdownMenu';
|
||||
import Modal from '@/components/Modal';
|
||||
import ClassForm from '@/components/ClassForm';
|
||||
import ClasseDetails from '@/components/ClasseDetails';
|
||||
|
||||
const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleEdit, handleDelete }) => {
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isOpenDetails, setIsOpenDetails] = useState(false);
|
||||
const [editingClass, setEditingClass] = useState(null);
|
||||
|
||||
const openEditModal = (classe) => {
|
||||
@ -15,11 +17,21 @@ const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleE
|
||||
setEditingClass(classe);
|
||||
}
|
||||
|
||||
const openEditModalDetails = (classe) => {
|
||||
setIsOpenDetails(true);
|
||||
setEditingClass(classe);
|
||||
}
|
||||
|
||||
const closeEditModal = () => {
|
||||
setIsOpen(false);
|
||||
setEditingClass(null);
|
||||
};
|
||||
|
||||
const closeEditModalDetails = () => {
|
||||
setIsOpenDetails(false);
|
||||
setEditingClass(null);
|
||||
};
|
||||
|
||||
const handleModalSubmit = (updatedData) => {
|
||||
if (editingClass) {
|
||||
handleEdit(editingClass.id, updatedData);
|
||||
@ -29,15 +41,11 @@ const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleE
|
||||
closeEditModal();
|
||||
};
|
||||
|
||||
const handleInspect = (data) => {
|
||||
console.log('inspect classe : ', data)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-center mb-4 max-w-8xl ml-0">
|
||||
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||
<School className="w-8 h-8 mr-2" />
|
||||
<Users className="w-8 h-8 mr-2" />
|
||||
Classes
|
||||
</h2>
|
||||
<button
|
||||
@ -82,7 +90,7 @@ const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleE
|
||||
<DropdownMenu
|
||||
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
|
||||
items={[
|
||||
{ label: 'Inspecter', icon: ZoomIn, onClick: () => handleInspect(row) },
|
||||
{ label: 'Inspecter', icon: ZoomIn, onClick: () => openEditModalDetails(row) },
|
||||
{ label: 'Modifier', icon:Edit3, onClick: () => openEditModal(row) },
|
||||
{ label: 'Supprimer', icon: Trash2, onClick: () => handleDelete(row.id) }
|
||||
]
|
||||
@ -104,6 +112,25 @@ const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleE
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{isOpenDetails && (
|
||||
<Modal
|
||||
isOpen={isOpenDetails}
|
||||
setIsOpen={setIsOpenDetails}
|
||||
title={(
|
||||
<div className="flex items-center">
|
||||
<Users className="w-8 h-8 mr-2" />
|
||||
{editingClass ? (
|
||||
<>
|
||||
{editingClass.nom_ambiance} - {editingClass.tranche_age[0]} à {editingClass.tranche_age[1]} ans
|
||||
</>
|
||||
) : ''}
|
||||
</div>
|
||||
)}
|
||||
ContentComponent={() => (
|
||||
<ClasseDetails classe={editingClass} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -16,7 +16,7 @@ const Modal = ({ isOpen, setIsOpen, title, ContentComponent }) => {
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Dialog.Close asChild>
|
||||
<button
|
||||
className="inline-flex justify-center 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 bg-gray-300 text-gray-700 hover:bg-gray-400"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Fermer
|
||||
|
||||
@ -31,7 +31,7 @@ function Sidebar({ currentPage, items }) {
|
||||
{/* Sidebar */}
|
||||
<div className="w-64 bg-white border-r border-gray-200 py-6 px-4">
|
||||
<div className="flex items-center mb-8 px-2">
|
||||
<div className="text-xl font-semibold">Collège Saint-Joseph</div>
|
||||
<div className="text-xl font-semibold">Ecole NEWT</div>
|
||||
</div>
|
||||
|
||||
<nav className="space-y-1">
|
||||
|
||||
@ -20,7 +20,7 @@ const Table = ({ data, columns, renderCell, itemsPerPage = 0, currentPage, total
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, rowIndex) => (
|
||||
{data?.map((row, rowIndex) => (
|
||||
<tr key={rowIndex} className={` ${rowIndex % 2 === 0 ? 'bg-emerald-50' : ''}`}>
|
||||
{columns.map((column, colIndex) => (
|
||||
<td key={colIndex} className="py-2 px-4 border-b border-gray-200 text-center text-sm text-gray-700">
|
||||
|
||||
@ -1,17 +1,25 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
|
||||
const profils = [
|
||||
{ value: 0, label: "École" },
|
||||
{ value: 2, label: "Administrateur" },
|
||||
];
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
nom: teacher.nom || '',
|
||||
prenom: teacher.prenom || '',
|
||||
mail: teacher.mail || '',
|
||||
specialite_id: teacher.specialite_id || 1,
|
||||
classes: teacher.classes || []
|
||||
specialite_id: teacher.specialite_id || '',
|
||||
classes: teacher.classes || [],
|
||||
profilAssocie_id: teacher.profilAssocie_id || [],
|
||||
DroitValue: teacher.DroitValue || 0
|
||||
});
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type } = e.target;
|
||||
const newValue = type === 'radio' ? parseInt(value) : value;
|
||||
const newValue = type === 'radio' ? parseInt(value, 10) : value;
|
||||
console.log(`Name: ${name}, Value: ${newValue}`);
|
||||
setFormData((prevState) => ({
|
||||
...prevState,
|
||||
[name]: newValue,
|
||||
@ -91,15 +99,41 @@ const TeacherForm = ({ teacher, onSubmit, isNew, specialities }) => {
|
||||
))}
|
||||
</div>
|
||||
</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>
|
||||
<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.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)}
|
||||
disabled={(!formData.nom || !formData.prenom || !formData.mail || !formData.specialite_id)}
|
||||
>
|
||||
Soumettre
|
||||
</button>
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import { School, Trash2, MoreVertical, Edit3, Plus } from 'lucide-react';
|
||||
import { GraduationCap, Trash2, MoreVertical, Edit3, Plus } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import Table from '@/components/Table';
|
||||
import DropdownMenu from '@/components/DropdownMenu';
|
||||
import Modal from '@/components/Modal';
|
||||
import TeacherForm from '@/components/TeacherForm';
|
||||
import {BK_PROFILE_URL} from '@/utils/Url';
|
||||
import useCsrfToken from '@/hooks/useCsrfToken';
|
||||
|
||||
const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, specialities }) => {
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [editingTeacher, setEditingTeacher] = useState(null);
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
|
||||
const openEditModal = (teacher) => {
|
||||
setIsOpen(true);
|
||||
setEditingTeacher(teacher);
|
||||
@ -21,9 +26,70 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
||||
|
||||
const handleModalSubmit = (updatedData) => {
|
||||
if (editingTeacher) {
|
||||
handleEdit(editingTeacher.id, updatedData);
|
||||
// Modification du profil
|
||||
const request = new Request(
|
||||
`${BK_PROFILE_URL}/${updatedData.profilAssocie_id}`,
|
||||
{
|
||||
method:'PUT',
|
||||
headers: {
|
||||
'Content-Type':'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify( {
|
||||
email: updatedData.mail,
|
||||
username: updatedData.mail,
|
||||
droit:updatedData.DroitValue
|
||||
}),
|
||||
}
|
||||
);
|
||||
fetch(request).then(response => response.json())
|
||||
.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 {
|
||||
handleCreate(updatedData);
|
||||
// Création d'un profil associé à l'adresse mail du responsable saisie
|
||||
// Le profil est inactif
|
||||
const request = new Request(
|
||||
`${BK_PROFILE_URL}`,
|
||||
{
|
||||
method:'POST',
|
||||
headers: {
|
||||
'Content-Type':'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify( {
|
||||
email: updatedData.mail,
|
||||
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
|
||||
}),
|
||||
}
|
||||
);
|
||||
fetch(request).then(response => response.json())
|
||||
.then(response => {
|
||||
console.log('Success:', response);
|
||||
console.log('UpdateData:', updatedData);
|
||||
if (response.id) {
|
||||
let idProfil = response.id;
|
||||
updatedData.profilAssocie_id = idProfil;
|
||||
handleCreate(updatedData);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error);
|
||||
error = error.errorMessage;
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
closeEditModal();
|
||||
};
|
||||
@ -32,7 +98,7 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-center mb-4 max-w-7xl ml-0">
|
||||
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||
<School className="w-8 h-8 mr-2" />
|
||||
<GraduationCap className="w-8 h-8 mr-2" />
|
||||
Enseignants
|
||||
</h2>
|
||||
<button
|
||||
@ -49,7 +115,9 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
||||
{ name: 'PRENOM', transform: (row) => row.prenom },
|
||||
{ name: 'MAIL', transform: (row) => row.mail },
|
||||
{ name: 'SPECIALITE',
|
||||
transform: (row) => (
|
||||
transform: (row) => {
|
||||
return row.specialite
|
||||
?
|
||||
<div key={row.id} className="flex justify-center items-center space-x-2">
|
||||
<span
|
||||
key={row.specialite.id}
|
||||
@ -57,8 +125,19 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
||||
style={{ backgroundColor: row.specialite.codeCouleur }}
|
||||
title={row.specialite.nom}
|
||||
></span>
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
: <i>Non définie</i>;
|
||||
}
|
||||
},
|
||||
{ name: 'TYPE PROFIL',
|
||||
transform: (row) => {
|
||||
return row.profilAssocie
|
||||
?
|
||||
<div key={row.id} className="flex justify-center items-center space-x-2">
|
||||
{row.DroitLabel}
|
||||
</div>
|
||||
: <i>Non définie</i>;
|
||||
}
|
||||
},
|
||||
{ name: 'ACTIONS', transform: (row) => (
|
||||
<DropdownMenu
|
||||
@ -72,8 +151,6 @@ const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, spe
|
||||
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"
|
||||
/>
|
||||
)}
|
||||
// { name: 'SPECIALITE', transform: (row) => row.specialite_id },
|
||||
// { name: 'CLASSES', transform: (row) => row.classe },
|
||||
]}
|
||||
data={teachers}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user