feat: Gestion des profils des enseignants / Visualisation d'une classe [#4]

This commit is contained in:
N3WT DE COMPET
2024-11-23 20:02:51 +01:00
parent af0cd1c840
commit 81d1dfa9a7
26 changed files with 792 additions and 178 deletions

View File

@ -4,6 +4,8 @@ import React, { useState, useEffect } from 'react';
import { useTranslations } from 'next-intl';
import { Users, Clock, CalendarCheck, School, TrendingUp, UserCheck } from 'lucide-react';
import Loader from '@/components/Loader';
import { BK_GESTIONINSCRIPTION_CLASSES_URL } from '@/utils/Url';
import ClasseDetails from '@/components/ClasseDetails';
// Composant StatCard pour afficher une statistique
const StatCard = ({ title, value, icon, change, color = "blue" }) => (
@ -54,7 +56,23 @@ export default function DashboardPage() {
}
});
const [classes, setClasses] = useState([]);
const fetchClasses = () => {
fetch(`${BK_GESTIONINSCRIPTION_CLASSES_URL}`)
.then(response => response.json())
.then(data => {
setClasses(data);
})
.catch(error => {
console.error('Error fetching classes:', error);
});
};
useEffect(() => {
// Fetch data for classes
fetchClasses();
// Simulation de chargement des données
setTimeout(() => {
setStats({
@ -120,7 +138,7 @@ export default function DashboardPage() {
</div>
{/* Événements et KPIs */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
{/* Graphique des inscriptions */}
<div className="lg:col-span-2 bg-white p-6 rounded-lg shadow-sm border border-gray-100">
<h2 className="text-lg font-semibold mb-4">{t('inscriptionTrends')}</h2>
@ -138,6 +156,14 @@ export default function DashboardPage() {
))}
</div>
</div>
<div className="flex flex-wrap">
{classes.map((classe) => (
<div className="lg:col-span-2 bg-white p-6 rounded-lg shadow-sm border border-gray-100 mr-4">
<ClasseDetails key={classe.id} classe={classe} />
</div>
))}
</div>
</div>
);
}

View File

@ -1,10 +1,8 @@
'use client'
import React, { useState, useEffect } from 'react';
import Table from '@/components/Table';
import SpecialitiesSection from '@/components/SpecialitiesSection'
import ClassesSection from '@/components/ClassesSection'
import TeachersSection from '@/components/TeachersSection';
import { User, School } from 'lucide-react'
import { BK_GESTIONINSCRIPTION_SPECIALITES_URL,
BK_GESTIONINSCRIPTION_CLASSES_URL,
BK_GESTIONINSCRIPTION_SPECIALITE_URL,
@ -66,7 +64,6 @@ export default function Page() {
};
const handleCreate = (url, newData, setDatas) => {
console.log('SEND POST :', JSON.stringify(newData));
fetch(url, {
method: 'POST',
headers: {

View File

@ -13,19 +13,29 @@ import Button from '@/components/Button';
import DropdownMenu from "@/components/DropdownMenu";
import { swapFormatDate } from '@/utils/Date';
import { formatPhoneNumber } from '@/utils/Telephone';
import { MoreVertical, Send, Edit, Trash2, FileText, ChevronUp, UserPlus } from 'lucide-react';
import { MoreVertical, Send, Edit, Trash2, FileText, ChevronUp, UserPlus, CheckCircle } from 'lucide-react';
import Modal from '@/components/Modal';
import InscriptionForm from '@/components/Inscription/InscriptionForm'
import AffectationClasseForm from '@/components/AffectationClasseForm'
import { BK_GESTIONINSCRIPTION_FICHESINSCRIPTION_URL, BK_GESTIONINSCRIPTION_SEND_URL, FR_ADMIN_STUDENT_EDIT_SUBSCRIBE, BK_GESTIONINSCRIPTION_ARCHIVE_URL } from '@/utils/Url';
import { BK_GESTIONINSCRIPTION_FICHESINSCRIPTION_URL,
BK_GESTIONINSCRIPTION_SEND_URL,
FR_ADMIN_STUDENT_EDIT_SUBSCRIBE,
BK_GESTIONINSCRIPTION_ARCHIVE_URL,
BK_GESTIONINSCRIPTION_CLASSES_URL,
BK_GESTIONINSCRIPTION_FICHEINSCRIPTION_URL } from '@/utils/Url';
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
import useCsrfToken from '@/hooks/useCsrfToken';
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
export default function Page({ params: { locale } }) {
const t = useTranslations('students');
const [ficheInscriptions, setFicheInscriptions] = useState([]);
const [ficheInscriptionsData, setFicheInscriptionsData] = useState([]);
const [fichesInscriptionsDataArchivees, setFicheInscriptionsDataArchivees] = useState([]);
const [fichesInscriptionsDataEnCours, setFichesInscriptionsDataEnCours] = useState([]);
const [fichesInscriptionsDataInscrits, setichesInscriptionsDataInscrits] = useState([]);
const [fichesInscriptionsDataArchivees, setFichesInscriptionsDataArchivees] = useState([]);
// const [filter, setFilter] = useState('*');
const [searchTerm, setSearchTerm] = useState('');
const [alertPage, setAlertPage] = useState(false);
@ -33,22 +43,32 @@ export default function Page({ params: { locale } }) {
const [ficheArchivee, setFicheArchivee] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [popup, setPopup] = useState({ visible: false, message: '', onConfirm: null });
const [activeTab, setActiveTab] = useState('all');
const [activeTab, setActiveTab] = useState('pending');
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [totalStudents, setTotalStudents] = useState(0);
const [totalPending, setTotalPending] = useState(0);
const [totalSubscribed, setTotalSubscribed] = useState(0);
const [totalArchives, setTotalArchives] = useState(0);
const [itemsPerPage, setItemsPerPage] = useState(5); // Définir le nombre d'éléments par page
const [isOpen, setIsOpen] = useState(false);
const [isOpenAffectationClasse, setIsOpenAffectationClasse] = useState(false);
const [eleve, setEleve] = useState('');
const [classes, setClasses] = useState([]);
const csrfToken = useCsrfToken();
const openModal = () => {
setIsOpen(true);
}
const openModalAssociationEleve = (eleveSelected) => {
setIsOpenAffectationClasse(true);
setEleve(eleveSelected);
}
// Modifier la fonction fetchData pour inclure le terme de recherche
const fetchData = (page, pageSize, search = '') => {
const url = `${BK_GESTIONINSCRIPTION_FICHESINSCRIPTION_URL}/all?page=${page}&page_size=${pageSize}&search=${search}`;
const url = `${BK_GESTIONINSCRIPTION_FICHESINSCRIPTION_URL}/pending?page=${page}&page_size=${pageSize}&search=${search}`;
fetch(url, {
headers: {
'Content-Type': 'application/json',
@ -58,11 +78,33 @@ export default function Page({ params: { locale } }) {
setIsLoading(false);
if (data) {
const { fichesInscriptions, count } = data;
setFicheInscriptionsData(fichesInscriptions);
setFichesInscriptionsDataEnCours(fichesInscriptions);
const calculatedTotalPages = Math.ceil(count / pageSize);
setTotalStudents(count);
setTotalPending(count);
setTotalPages(calculatedTotalPages);
}
console.log('Success PENDING:', data);
})
.catch(error => {
console.error('Error fetching data:', error);
setIsLoading(false);
});
};
const fetchDataSubscribed = () => {
fetch(`${BK_GESTIONINSCRIPTION_FICHESINSCRIPTION_URL}/subscribed`, {
headers: {
'Content-Type': 'application/json',
},
}).then(response => response.json())
.then(data => {
setIsLoading(false);
if (data) {
const { fichesInscriptions, count } = data;
setTotalSubscribed(count);
setichesInscriptionsDataInscrits(fichesInscriptions);
}
console.log('Success SUBSCRIBED:', data);
})
.catch(error => {
console.error('Error fetching data:', error);
@ -81,7 +123,7 @@ export default function Page({ params: { locale } }) {
if (data) {
const { fichesInscriptions, count } = data;
setTotalArchives(count);
setFicheInscriptionsDataArchivees(fichesInscriptions);
setFichesInscriptionsDataArchivees(fichesInscriptions);
}
console.log('Success ARCHIVED:', data);
})
@ -91,14 +133,31 @@ export default function Page({ params: { locale } }) {
});
};
const fetchClasses = () => {
fetch(`${BK_GESTIONINSCRIPTION_CLASSES_URL}`)
.then(response => response.json())
.then(data => {
setClasses(data);
console.log("classes : ", data)
})
.catch(error => {
console.error('Error fetching classes:', error);
});
};
useEffect(() => {
fetchClasses();
}, []);
useEffect(() => {
const fetchDataAndSetState = () => {
if (!useFakeData) {
fetchData(currentPage, itemsPerPage, searchTerm);
fetchDataSubscribed();
fetchDataArchived();
} else {
setTimeout(() => {
setFicheInscriptionsData(mockFicheInscription);
setFichesInscriptionsDataEnCours(mockFicheInscription);
setIsLoading(false);
}, 1000);
}
@ -183,13 +242,37 @@ export default function Page({ params: { locale } }) {
fetchData(newPage, itemsPerPage); // Appeler fetchData directement ici
};
const validateAndAssociate = (updatedData) => {
fetch(`${BK_GESTIONINSCRIPTION_FICHEINSCRIPTION_URL}/${eleve.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify(updatedData),
credentials: 'include'
})
.then(response => response.json())
.then(data => {
console.log('Succès :', data);
})
.catch(error => {
console.error('Erreur :', error);
});
}
const columns = [
{ name: t('studentName'), transform: (row) => row.eleve.nom },
{ name: t('studentFistName'), transform: (row) => row.eleve.prenom },
{ name: t('mainContactMail'), transform: (row) => row.eleve.responsables[0].mail },
{ name: t('phone'), transform: (row) => formatPhoneNumber(row.eleve.responsables[0].telephone) },
{ name: t('lastUpdateDate'), transform: (row) => swapFormatDate(row.dateMAJ, "DD-MM-YYYY hh:mm:ss", "DD/MM/YYYY hh:mm") },
{ name: t('registrationFileStatus'), transform: (row) => <StatusLabel etat={row.etat} onChange={(newStatus) => updateStatusAction(row.eleve.id, newStatus)} /> },
{ name: t('lastUpdateDate'), transform: (row) => row.dateMAJ_formattee},
{ name: t('registrationFileStatus'), transform: (row) => (
<div className="flex justify-center items-center h-full">
<StatusLabel etat={row.etat} onChange={(newStatus) => updateStatusAction(row.eleve.id, newStatus)} showDropdown={false} />
</div>
)
},
{ name: t('files'), transform: (row) => (
<ul>
{row.fichiers?.map((fichier, fileIndex) => (
@ -228,6 +311,22 @@ const columns = [
),
onClick: () => window.location.href = `${FR_ADMIN_STUDENT_EDIT_SUBSCRIBE}?idEleve=${row.eleve.id}&id=1`,
}] : []),
...(row.etat === 3 ? [{
label: (
<>
<CheckCircle size={16} className="mr-2" /> Valider
</>
),
onClick: () => openModalAssociationEleve(row.eleve),
}] : []),
...(row.etat === 5 ? [{
label: (
<>
<CheckCircle size={16} className="mr-2" /> Rattacher
</>
),
onClick: () => openModalAssociationEleve(row.eleve),
}] : []),
...(row.etat !== 6 ? [{
label: (
<>
@ -242,6 +341,53 @@ const columns = [
/>
) },
];
const columnsSubscribed = [
{ name: t('studentName'), transform: (row) => row.eleve.nom },
{ name: t('studentFistName'), transform: (row) => row.eleve.prenom },
{ name: t('lastUpdateDate'), transform: (row) => row.dateMAJ_formattee},
{ name: t('class'), transform: (row) => row.eleve.classeAssocieeName},
{ name: t('registrationFileStatus'), transform: (row) => (
<div className="flex justify-center items-center h-full">
<StatusLabel etat={row.etat} onChange={(newStatus) => updateStatusAction(row.eleve.id, newStatus)} showDropdown={false} />
</div>
)
},
{ name: t('files'), transform: (row) => (
<ul>
{row.fichiers?.map((fichier, fileIndex) => (
<li key={fileIndex} className="flex items-center gap-2">
<FileText size={16} />
<a href={fichier.url}>{fichier.nom}</a>
</li>
))}
</ul>
) },
{ name: 'Actions', transform: (row) => (
<DropdownMenu
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
items={[
{ label: (
<>
<CheckCircle size={16} className="mr-2" /> Rattacher
</>
),
onClick: () => openModalAssociationEleve(row.eleve)
},
{ label: (
<>
<Trash2 size={16} className="mr-2 text-red-700" /> Archiver
</>
),
onClick: () => archiveFicheInscription(row.eleve.id, row.eleve.nom, row.eleve.prenom),
}
]}
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"
/>
) },
];
if (isLoading) {
@ -262,22 +408,22 @@ const columns = [
<div className='p-8'>
<div className="border-b border-gray-200 mb-6">
<div className="flex gap-8">
<Tab
text={<>
{t('allStudents')}
<span className="ml-2 text-sm text-gray-400">({totalStudents})</span>
</>}
active={activeTab === 'all'}
onClick={() => setActiveTab('all')}
/>
<Tab
text={<>
{t('pending')}
<span className="ml-2 text-sm text-gray-400">({12})</span>
<span className="ml-2 text-sm text-gray-400">({totalPending})</span>
</>}
active={activeTab === 'pending'}
onClick={() => setActiveTab('pending')}
/>
<Tab
text={<>
{t('subscribed')}
<span className="ml-2 text-sm text-gray-400">({totalSubscribed})</span>
</>}
active={activeTab === 'subscribed'}
onClick={() => setActiveTab('subscribed')}
/>
<Tab
text={<>
{t('archived')}
@ -309,10 +455,21 @@ const columns = [
</div>
</div>
<DjangoCSRFToken csrfToken={csrfToken} />
<Table
key={`${currentPage}-${searchTerm}`}
data={(activeTab === 'all' || activeTab === 'pending') ? ficheInscriptionsData : fichesInscriptionsDataArchivees}
columns={columns}
data={
activeTab === 'pending'
? fichesInscriptionsDataEnCours
: activeTab === 'subscribed'
? fichesInscriptionsDataInscrits
: fichesInscriptionsDataArchivees
}
columns={
activeTab === 'subscribed'
? columnsSubscribed
: columns
}
itemsPerPage={itemsPerPage}
currentPage={currentPage}
totalPages={totalPages}
@ -327,6 +484,20 @@ const columns = [
}}
onCancel={() => setPopup({ ...popup, visible: false })}
/>
{isOpenAffectationClasse && (
<Modal
isOpen={isOpenAffectationClasse}
setIsOpen={setIsOpenAffectationClasse}
title="Affectation à une classe"
ContentComponent={() => (
<AffectationClasseForm
eleve={eleve}
onSubmit={validateAndAssociate}
classes={classes}
/>
)}
/>
)}
</div>
);
}

View File

@ -8,7 +8,7 @@ import InputTextIcon from '@/components/InputTextIcon';
import Loader from '@/components/Loader'; // Importez le composant Loader
import Button from '@/components/Button'; // Importez le composant Button
import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
import { BK_LOGIN_URL, FR_ADMIN_STUDENT_EDIT_SUBSCRIBE, FR_PARENTS_HOME_URL, FR_USERS_NEW_PASSWORD_URL, FR_USERS_SUBSCRIBE_URL } from '@/utils/Url';
import { BK_LOGIN_URL, FR_ADMIN_STUDENT_EDIT_SUBSCRIBE, FR_ADMIN_STUDENT_URL, FR_PARENTS_HOME_URL, FR_USERS_NEW_PASSWORD_URL, FR_USERS_SUBSCRIBE_URL } from '@/utils/Url';
import useLocalStorage from '@/hooks/useLocalStorage';
import useCsrfToken from '@/hooks/useCsrfToken';
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
@ -78,7 +78,19 @@ export default function Page() {
setErrorMessage("")
if(isOK(data)){
localStorage.setItem('userId', data.profil); // Stocker l'identifiant de l'utilisateur
router.push(`${FR_PARENTS_HOME_URL}`);
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_STUDENT_URL}`);
} else {
// Cas anormal
}
} else {
if(data.errorFields){
setUserFieldError(data.errorFields.email)

View 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;

View 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;

View File

@ -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>
);
};

View File

@ -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

View File

@ -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">

View File

@ -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">

View File

@ -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>

View File

@ -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}
/>