mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 16:03:21 +00:00
feat: Gestion des profils des enseignants / Visualisation d'une classe [#4]
This commit is contained in:
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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: {
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
Reference in New Issue
Block a user