mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Gestion multi-profil multi-école
This commit is contained in:
@ -3,7 +3,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Sidebar from '@/components/Sidebar';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import {useTranslations} from 'next-intl';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Image from 'next/image';
|
||||
import {
|
||||
Users,
|
||||
@ -35,6 +35,9 @@ import ProtectedRoute from '@/components/ProtectedRoute';
|
||||
import { getGravatarUrl } from '@/utils/gravatar';
|
||||
import Footer from '@/components/Footer';
|
||||
import { getRightStr, RIGHTS } from '@/utils/rights';
|
||||
import { getSession } from 'next-auth/react';
|
||||
import logger from '@/utils/logger';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
@ -51,11 +54,12 @@ export default function Layout({
|
||||
"settings": { "id": "settings", "name": t('settings'), "url": FE_ADMIN_SETTINGS_URL, "icon": Settings }
|
||||
};
|
||||
|
||||
const [establishment, setEstablishment] = useState(null);
|
||||
const [establishments, setEstablishments] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||
const [user, setUser] = useState(null);
|
||||
const { data: session } = useSession();
|
||||
const { selectedEstablishmentId, setSelectedEstablishmentId, profileRole, setProfileRole } = useEstablishment();
|
||||
|
||||
const pathname = usePathname();
|
||||
const currentPage = pathname.split('/').pop();
|
||||
@ -80,7 +84,7 @@ export default function Layout({
|
||||
content: (
|
||||
<div className="px-4 py-2">
|
||||
<div className="font-medium">{user?.email || 'Utilisateur'}</div>
|
||||
<div className="text-xs text-gray-400">{getRightStr(user?.role) || ''}</div>
|
||||
<div className="text-xs text-gray-400">{getRightStr(profileRole) || ''}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@ -106,17 +110,34 @@ export default function Layout({
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
fetchEstablishment()
|
||||
.then(data => {
|
||||
setEstablishment(data);
|
||||
getSession()
|
||||
.then(session => {
|
||||
if (session && session.user) {
|
||||
setUser(session.user);
|
||||
setEstablishments(session.user.roles.map(role => ({
|
||||
id: role.establishment__id,
|
||||
name: role.establishment__name,
|
||||
role_type: role.role_type
|
||||
})));
|
||||
// Sélectionner l'établissement depuis le localStorage ou le premier établissement par défaut
|
||||
const storedEstablishmentId = localStorage.getItem('selectedEstablishmentId');
|
||||
if (storedEstablishmentId) {
|
||||
setSelectedEstablishmentId(storedEstablishmentId);
|
||||
const storedProfileRole = session.user.roles.find(role => role.establishment__id === parseInt(storedEstablishmentId))?.role_type;
|
||||
setProfileRole(storedProfileRole);
|
||||
} else if (session.user.roles.length > 0) {
|
||||
setSelectedEstablishmentId(session.user.roles[0].establishment__id);
|
||||
setProfileRole(session.user.roles[0].role_type);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching establishment : ', error))
|
||||
.finally(() => setIsLoading(false));
|
||||
.catch(err => {
|
||||
logger.error('Error fetching session:', err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUser = async () => {
|
||||
const fetchUser = async () => {
|
||||
if (session) { // Vérifier que la session existe
|
||||
const userData = await getUser();
|
||||
setUser(userData);
|
||||
@ -126,8 +147,6 @@ export default function Layout({
|
||||
fetchUser();
|
||||
}, [session]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<ProtectedRoute requiredRight={RIGHTS.ADMIN}>
|
||||
{!isLoading && (
|
||||
@ -138,12 +157,16 @@ export default function Layout({
|
||||
style={{ height: '100vh' }} // Force la hauteur à 100% de la hauteur de la vue
|
||||
>
|
||||
<Sidebar
|
||||
establishment={establishment}
|
||||
establishments={establishments}
|
||||
currentPage={currentPage}
|
||||
items={Object.values(sidebarItems)}
|
||||
|
||||
onCloseMobile={toggleSidebar}
|
||||
|
||||
onEstablishmentChange={(establishmentId) => {
|
||||
const parsedEstablishmentId = parseInt(establishmentId, 10);
|
||||
setSelectedEstablishmentId(parsedEstablishmentId);
|
||||
const role = session.user.roles.find(role => role.establishment__id === parsedEstablishmentId)?.role_type;
|
||||
setProfileRole(role);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@ -39,47 +39,61 @@ export default function DashboardPage() {
|
||||
|
||||
|
||||
const [classes, setClasses] = useState([]);
|
||||
|
||||
const [establishmentId, setEstablishmentId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch data for classes
|
||||
fetchClasses().then(data => {
|
||||
setClasses(data);
|
||||
logger.info('Classes fetched:', data);
|
||||
const nbMaxStudents = data.reduce((acc, classe) => acc + classe.number_of_students, 0);
|
||||
const nbStudents = data.reduce((acc, classe) => acc + classe.students.length, 0);
|
||||
setStructureCapacity(nbMaxStudents);
|
||||
setTotalStudents(nbStudents);
|
||||
|
||||
getSession()
|
||||
.then(session => {
|
||||
if (session && session.user) {
|
||||
setEstablishmentId(session.user.establishment);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error fetching classes:', error);
|
||||
.catch(err => {
|
||||
logger.error('Error fetching session:', err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
fetchRegisterForms().then(data => {
|
||||
logger.info('Pending registrations fetched:', data);
|
||||
setPendingRegistration(data.count);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error fetching pending registrations:', error);
|
||||
});
|
||||
useEffect(() => {
|
||||
if (establishmentId) {
|
||||
// Fetch data for classes
|
||||
fetchClasses(establishmentId).then(data => {
|
||||
setClasses(data);
|
||||
logger.info('Classes fetched:', data);
|
||||
const nbMaxStudents = data.reduce((acc, classe) => acc + classe.number_of_students, 0);
|
||||
const nbStudents = data.reduce((acc, classe) => acc + classe.students.length, 0);
|
||||
setStructureCapacity(nbMaxStudents);
|
||||
setTotalStudents(nbStudents);
|
||||
|
||||
fetchUpcomingEvents().then(data => {
|
||||
setUpcomingEvents(data);
|
||||
}).catch(error => {
|
||||
logger.error('Error fetching upcoming events:', error);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error fetching classes:', error);
|
||||
});
|
||||
|
||||
// Simulation de chargement des données
|
||||
setTimeout(() => {
|
||||
setMonthlyStats({
|
||||
inscriptions: [150, 180, 210, 245],
|
||||
completionRate: 78
|
||||
});
|
||||
setIsLoading(false);
|
||||
}, 1000);
|
||||
}
|
||||
, []);
|
||||
fetchRegisterForms().then(data => {
|
||||
logger.info('Pending registrations fetched:', data);
|
||||
setPendingRegistration(data.count);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error fetching pending registrations:', error);
|
||||
});
|
||||
|
||||
fetchUpcomingEvents().then(data => {
|
||||
setUpcomingEvents(data);
|
||||
}).catch(error => {
|
||||
logger.error('Error fetching upcoming events:', error);
|
||||
});
|
||||
|
||||
// Simulation de chargement des données
|
||||
setTimeout(() => {
|
||||
setMonthlyStats({
|
||||
inscriptions: [150, 180, 210, 245],
|
||||
completionRate: 78
|
||||
});
|
||||
setIsLoading(false);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
, [establishmentId]);
|
||||
|
||||
|
||||
if (isLoading) return <Loader />;
|
||||
|
||||
@ -27,13 +27,13 @@ import {
|
||||
fetchRegistrationTemplateMaster
|
||||
} from "@/app/actions/registerFileGroupAction";
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
export default function Page() {
|
||||
const [specialities, setSpecialities] = useState([]);
|
||||
const [classes, setClasses] = useState([]);
|
||||
const [teachers, setTeachers] = useState([]);
|
||||
const [schedules, setSchedules] = useState([]); // Add this line
|
||||
const [schedules, setSchedules] = useState([]);
|
||||
const [registrationDiscounts, setRegistrationDiscounts] = useState([]);
|
||||
const [tuitionDiscounts, setTuitionDiscounts] = useState([]);
|
||||
const [registrationFees, setRegistrationFees] = useState([]);
|
||||
@ -45,54 +45,57 @@ export default function Page() {
|
||||
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch data for specialities
|
||||
handleSpecialities();
|
||||
if (selectedEstablishmentId) {
|
||||
// Fetch data for specialities
|
||||
handleSpecialities();
|
||||
|
||||
// Fetch data for teachers
|
||||
handleTeachers();
|
||||
// Fetch data for teachers
|
||||
handleTeachers();
|
||||
|
||||
// Fetch data for classes
|
||||
handleClasses();
|
||||
// Fetch data for classes
|
||||
handleClasses();
|
||||
|
||||
// Fetch data for schedules
|
||||
handleSchedules();
|
||||
// Fetch data for schedules
|
||||
handleSchedules();
|
||||
|
||||
// Fetch data for registration discounts
|
||||
handleRegistrationDiscounts();
|
||||
// Fetch data for registration discounts
|
||||
handleRegistrationDiscounts();
|
||||
|
||||
// Fetch data for tuition discounts
|
||||
handleTuitionDiscounts();
|
||||
// Fetch data for tuition discounts
|
||||
handleTuitionDiscounts();
|
||||
|
||||
// Fetch data for registration fees
|
||||
handleRegistrationFees();
|
||||
// Fetch data for registration fees
|
||||
handleRegistrationFees();
|
||||
|
||||
// Fetch data for tuition fees
|
||||
handleTuitionFees();
|
||||
// Fetch data for tuition fees
|
||||
handleTuitionFees();
|
||||
|
||||
// Fetch data for registration file templates
|
||||
fetchRegistrationTemplateMaster()
|
||||
.then((data)=> {
|
||||
setFichiers(data)
|
||||
})
|
||||
.catch(error => logger.error('Error fetching files:', error));
|
||||
// Fetch data for registration file templates
|
||||
fetchRegistrationTemplateMaster()
|
||||
.then((data)=> {
|
||||
setFichiers(data)
|
||||
})
|
||||
.catch(error => logger.error('Error fetching files:', error));
|
||||
|
||||
// Fetch data for registration payment plans
|
||||
handleRegistrationPaymentPlans();
|
||||
// Fetch data for registration payment plans
|
||||
handleRegistrationPaymentPlans();
|
||||
|
||||
// Fetch data for tuition payment plans
|
||||
handleTuitionPaymentPlans();
|
||||
// Fetch data for tuition payment plans
|
||||
handleTuitionPaymentPlans();
|
||||
|
||||
// Fetch data for registration payment modes
|
||||
handleRegistrationPaymentModes();
|
||||
// Fetch data for registration payment modes
|
||||
handleRegistrationPaymentModes();
|
||||
|
||||
// Fetch data for tuition payment modes
|
||||
handleTuitionPaymentModes();
|
||||
}, []);
|
||||
// Fetch data for tuition payment modes
|
||||
handleTuitionPaymentModes();
|
||||
}
|
||||
}, [selectedEstablishmentId]);
|
||||
|
||||
const handleSpecialities = () => {
|
||||
fetchSpecialities()
|
||||
fetchSpecialities(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setSpecialities(data);
|
||||
})
|
||||
@ -100,7 +103,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleTeachers = () => {
|
||||
fetchTeachers()
|
||||
fetchTeachers(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setTeachers(data);
|
||||
})
|
||||
@ -108,7 +111,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleClasses = () => {
|
||||
fetchClasses()
|
||||
fetchClasses(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setClasses(data);
|
||||
})
|
||||
@ -124,7 +127,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleRegistrationDiscounts = () => {
|
||||
fetchRegistrationDiscounts()
|
||||
fetchRegistrationDiscounts(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setRegistrationDiscounts(data);
|
||||
})
|
||||
@ -132,7 +135,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleTuitionDiscounts = () => {
|
||||
fetchTuitionDiscounts()
|
||||
fetchTuitionDiscounts(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setTuitionDiscounts(data);
|
||||
})
|
||||
@ -140,7 +143,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleRegistrationFees = () => {
|
||||
fetchRegistrationFees()
|
||||
fetchRegistrationFees(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setRegistrationFees(data);
|
||||
})
|
||||
@ -148,7 +151,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleTuitionFees = () => {
|
||||
fetchTuitionFees()
|
||||
fetchTuitionFees(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setTuitionFees(data);
|
||||
})
|
||||
@ -156,7 +159,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleRegistrationPaymentPlans = () => {
|
||||
fetchRegistrationPaymentPlans()
|
||||
fetchRegistrationPaymentPlans(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setRegistrationPaymentPlans(data);
|
||||
})
|
||||
@ -164,7 +167,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleTuitionPaymentPlans = () => {
|
||||
fetchTuitionPaymentPlans()
|
||||
fetchTuitionPaymentPlans(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setTuitionPaymentPlans(data);
|
||||
})
|
||||
@ -172,7 +175,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleRegistrationPaymentModes = () => {
|
||||
fetchRegistrationPaymentModes()
|
||||
fetchRegistrationPaymentModes(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setRegistrationPaymentModes(data);
|
||||
})
|
||||
@ -180,7 +183,7 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleTuitionPaymentModes = () => {
|
||||
fetchTuitionPaymentModes()
|
||||
fetchTuitionPaymentModes(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setTuitionPaymentModes(data);
|
||||
})
|
||||
|
||||
@ -16,6 +16,7 @@ import Modal from '@/components/Modal';
|
||||
import InscriptionForm from '@/components/Inscription/InscriptionForm'
|
||||
import AffectationClasseForm from '@/components/AffectationClasseForm'
|
||||
import { getSession } from 'next-auth/react';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
import {
|
||||
PENDING,
|
||||
@ -50,7 +51,6 @@ import {
|
||||
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
import { ESTABLISHMENT_ID } from '@/utils/Url';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
export default function Page({ params: { locale } }) {
|
||||
@ -87,9 +87,8 @@ export default function Page({ params: { locale } }) {
|
||||
const [tuitionFees, setTuitionFees] = useState([]);
|
||||
const [groups, setGroups] = useState([]);
|
||||
|
||||
const [establishmentId, setEstablishmentId] = useState(null);
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
@ -167,23 +166,11 @@ const registerFormArchivedDataHandler = (data) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getSession()
|
||||
.then(session => {
|
||||
if (session && session.user) {
|
||||
setEstablishmentId(session.user.establishment);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Error fetching session:', err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (establishmentId) {
|
||||
if (selectedEstablishmentId) {
|
||||
const fetchInitialData = () => {
|
||||
Promise.all([
|
||||
fetchClasses(),
|
||||
fetchStudents(establishmentId) // Utiliser l'ID de l'établissement ici
|
||||
fetchClasses(selectedEstablishmentId),
|
||||
fetchStudents(selectedEstablishmentId) // Utiliser l'ID de l'établissement ici
|
||||
])
|
||||
.then(([classesData, studentsData]) => {
|
||||
setClasses(classesData);
|
||||
@ -198,21 +185,21 @@ useEffect(() => {
|
||||
|
||||
fetchInitialData();
|
||||
}
|
||||
}, [establishmentId]);
|
||||
}, [selectedEstablishmentId]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (establishmentId) {
|
||||
if (selectedEstablishmentId) {
|
||||
const fetchDataAndSetState = () => {
|
||||
setIsLoading(true);
|
||||
Promise.all([
|
||||
fetchRegisterForms(establishmentId, PENDING, currentPage, itemsPerPage, searchTerm)
|
||||
fetchRegisterForms(selectedEstablishmentId, PENDING, currentPage, itemsPerPage, searchTerm)
|
||||
.then(registerFormPendingDataHandler)
|
||||
.catch(requestErrorHandler),
|
||||
fetchRegisterForms(establishmentId, SUBSCRIBED)
|
||||
fetchRegisterForms(selectedEstablishmentId, SUBSCRIBED)
|
||||
.then(registerFormSubscribedDataHandler)
|
||||
.catch(requestErrorHandler),
|
||||
fetchRegisterForms(establishmentId, ARCHIVED)
|
||||
fetchRegisterForms(selectedEstablishmentId, ARCHIVED)
|
||||
.then(registerFormArchivedDataHandler)
|
||||
.catch(requestErrorHandler),
|
||||
fetchRegistrationTemplateMaster()
|
||||
@ -222,27 +209,27 @@ useEffect(() => {
|
||||
.catch(err => {
|
||||
logger.debug(err.message);
|
||||
}),
|
||||
fetchRegistrationDiscounts()
|
||||
fetchRegistrationDiscounts(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setRegistrationDiscounts(data);
|
||||
})
|
||||
.catch(requestErrorHandler),
|
||||
fetchTuitionDiscounts()
|
||||
fetchTuitionDiscounts(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setTuitionDiscounts(data);
|
||||
})
|
||||
.catch(requestErrorHandler),
|
||||
fetchRegistrationFees()
|
||||
fetchRegistrationFees(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setRegistrationFees(data);
|
||||
})
|
||||
.catch(requestErrorHandler),
|
||||
fetchTuitionFees()
|
||||
fetchTuitionFees(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setTuitionFees(data);
|
||||
})
|
||||
.catch(requestErrorHandler),
|
||||
fetchRegistrationFileGroups()
|
||||
fetchRegistrationFileGroups(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
setGroups(data);
|
||||
})
|
||||
@ -263,20 +250,20 @@ useEffect(() => {
|
||||
|
||||
fetchDataAndSetState();
|
||||
}
|
||||
}, [establishmentId, reloadFetch, currentPage, searchTerm]);
|
||||
}, [selectedEstablishmentId, reloadFetch, currentPage, searchTerm]);
|
||||
|
||||
useEffect(() => {
|
||||
if (establishmentId) {
|
||||
if (selectedEstablishmentId) {
|
||||
const fetchDataAndSetState = () => {
|
||||
|
||||
setIsLoading(true);
|
||||
fetchRegisterForms(establishmentId, PENDING, currentPage, itemsPerPage, searchTerm)
|
||||
fetchRegisterForms(selectedEstablishmentId, PENDING, currentPage, itemsPerPage, searchTerm)
|
||||
.then(registerFormPendingDataHandler)
|
||||
.catch(requestErrorHandler)
|
||||
fetchRegisterForms(establishmentId, SUBSCRIBED)
|
||||
fetchRegisterForms(selectedEstablishmentId, SUBSCRIBED)
|
||||
.then(registerFormSubscribedDataHandler)
|
||||
.catch(requestErrorHandler)
|
||||
fetchRegisterForms(establishmentId, ARCHIVED)
|
||||
fetchRegisterForms(selectedEstablishmentId, ARCHIVED)
|
||||
.then(registerFormArchivedDataHandler)
|
||||
.catch(requestErrorHandler)
|
||||
fetchRegistrationTemplateMaster()
|
||||
@ -545,7 +532,7 @@ useEffect(()=>{
|
||||
const columns = [
|
||||
{ name: t('studentName'), transform: (row) => row.student.last_name },
|
||||
{ name: t('studentFistName'), transform: (row) => row.student.first_name },
|
||||
{ name: t('mainContactMail'), transform: (row) => row.student.guardians[0].email },
|
||||
{ name: t('mainContactMail'), transform: (row) => row.student.guardians[0].associated_profile_email },
|
||||
{ name: t('phone'), transform: (row) => formatPhoneNumber(row.student.guardians[0].phone) },
|
||||
{ name: t('lastUpdateDate'), transform: (row) => row.formatted_last_update},
|
||||
{ name: t('registrationFileStatus'), transform: (row) => (
|
||||
|
||||
@ -37,8 +37,9 @@ export default function Layout({
|
||||
setIsPopupVisible(false);
|
||||
disconnect();
|
||||
};
|
||||
useEffect(() => {
|
||||
const fetchUser = async () => {
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUser = async () => {
|
||||
if (session) { // Vérifier que la session existe
|
||||
const userData = await getUser();
|
||||
setUser(userData);
|
||||
@ -47,6 +48,7 @@ export default function Layout({
|
||||
|
||||
fetchUser();
|
||||
}, [session]);
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// if (status === 'loading') return;
|
||||
@ -81,7 +83,7 @@ const dropdownItems = [
|
||||
content: (
|
||||
<div className="px-4 py-2">
|
||||
<div className="font-medium">{user?.email || 'Utilisateur'}</div>
|
||||
<div className="text-xs text-gray-400">{getRightStr(user?.role) || ''}</div>
|
||||
<div className="text-xs text-gray-400">{getRightStr(user?.roles[0]?.role_type) || ''}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
@ -9,13 +9,15 @@ import { fetchChildren } from '@/app/actions/subscriptionAction';
|
||||
import logger from '@/utils/logger';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
export default function ParentHomePage() {
|
||||
|
||||
const [children, setChildren] = useState([]);
|
||||
const { data: session, status } = useSession();
|
||||
const [userId, setUserId] = useState(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [establishments, setEstablishments] = useState([]);
|
||||
const { selectedEstablishmentId, setSelectedEstablishmentId } = useEstablishment();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@ -24,21 +26,42 @@ export default function ParentHomePage() {
|
||||
|
||||
if (!session) {
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
}
|
||||
console.log(session);
|
||||
const userIdFromSession = session.user.user_id;
|
||||
setUserId(userIdFromSession);
|
||||
} else {
|
||||
const userIdFromSession = session.user.user_id;
|
||||
setUserId(userIdFromSession);
|
||||
|
||||
fetchChildren(userIdFromSession).then(data => {
|
||||
setChildren(data);
|
||||
});
|
||||
}, [userId]);
|
||||
const userEstablishments = session.user.roles.map(role => ({
|
||||
id: role.establishment__id,
|
||||
name: role.establishment__name,
|
||||
role_type: role.role_type
|
||||
}));
|
||||
setEstablishments(userEstablishments);
|
||||
|
||||
const storedEstablishmentId = localStorage.getItem('selectedEstablishmentId');
|
||||
if (storedEstablishmentId) {
|
||||
setSelectedEstablishmentId(storedEstablishmentId);
|
||||
} else if (userEstablishments.length > 0) {
|
||||
setSelectedEstablishmentId(userEstablishments[0].id);
|
||||
}
|
||||
|
||||
fetchChildren(userIdFromSession, storedEstablishmentId).then(data => {
|
||||
setChildren(data);
|
||||
});
|
||||
}
|
||||
}, [status, session, selectedEstablishmentId]);
|
||||
|
||||
const handleEstablishmentChange = (e) => {
|
||||
const establishmentId = parseInt(e.target.value, 10);
|
||||
setSelectedEstablishmentId(establishmentId);
|
||||
localStorage.setItem('selectedEstablishmentId', establishmentId);
|
||||
};
|
||||
|
||||
function handleEdit(eleveId) {
|
||||
// Logique pour éditer le dossier de l'élève
|
||||
logger.debug(`Edit dossier for student id: ${eleveId}`);
|
||||
router.push(`${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}`);
|
||||
}
|
||||
|
||||
const actionColumns = [
|
||||
{ name: 'Action', transform: (row) => row.action },
|
||||
];
|
||||
@ -106,6 +129,22 @@ export default function ParentHomePage() {
|
||||
<Users className="h-6 w-6 text-emerald-600" />
|
||||
Enfants
|
||||
</h2>
|
||||
{establishments.length > 1 && (
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2">Sélectionnez un établissement :</label>
|
||||
<select
|
||||
value={selectedEstablishmentId}
|
||||
onChange={handleEstablishmentChange}
|
||||
className="block w-full mt-1 p-2 border border-gray-300 rounded-md"
|
||||
>
|
||||
{establishments.map(establishment => (
|
||||
<option key={establishment.id} value={establishment.id}>
|
||||
{establishment.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div className="overflow-x-auto">
|
||||
<Table
|
||||
data={children}
|
||||
|
||||
@ -13,18 +13,20 @@ import {
|
||||
FE_PARENTS_HOME_URL
|
||||
} from '@/utils/Url';
|
||||
import { login } from '@/app/actions/authAction';
|
||||
import { getSession } from 'next-auth/react';
|
||||
import { getSession } from 'next-auth/react';
|
||||
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
|
||||
import logger from '@/utils/logger';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import ProfileSelector from '@/components/ProfileSelector'; // Importez le composant ProfileSelector
|
||||
|
||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||
|
||||
export default function Page() {
|
||||
const searchParams = useSearchParams();
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [userFieldError,setUserFieldError] = useState("")
|
||||
const [passwordFieldError,setPasswordFieldError] = useState("")
|
||||
const [userFieldError, setUserFieldError] = useState("")
|
||||
const [passwordFieldError, setPasswordFieldError] = useState("")
|
||||
const [selectedProfile, setSelectedProfile] = useState(1); // Par défaut, sélectionnez ADMIN
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
@ -35,52 +37,54 @@ export default function Page() {
|
||||
return data.errorMessage === ""
|
||||
}
|
||||
|
||||
function handleFormLogin(formData) {
|
||||
setIsLoading(true);
|
||||
function handleFormLogin(formData) {
|
||||
setIsLoading(true);
|
||||
|
||||
login({
|
||||
email: formData.get('login'),
|
||||
password: formData.get('password'),
|
||||
}).then(result => {
|
||||
logger.debug('Sign In Result', result);
|
||||
setIsLoading(false);
|
||||
if (result.error) {
|
||||
setErrorMessage(result.error);
|
||||
} else {
|
||||
getSession().then(session => {
|
||||
if (!session || !session.user) {
|
||||
throw new Error('Session not found');
|
||||
}
|
||||
const user = session.user;
|
||||
logger.debug('User Session:', user);
|
||||
login({
|
||||
email: formData.get('login'),
|
||||
password: formData.get('password'),
|
||||
role_type: selectedProfile // Utilisez le profil sélectionné
|
||||
}).then(result => {
|
||||
logger.debug('Sign In Result', result);
|
||||
setIsLoading(false);
|
||||
if (result.error) {
|
||||
setErrorMessage(result.error);
|
||||
} else {
|
||||
getSession().then(session => {
|
||||
if (!session || !session.user) {
|
||||
throw new Error('Session not found');
|
||||
}
|
||||
const user = session.user;
|
||||
logger.debug('User Session:', user);
|
||||
|
||||
if (user.establishment_id) {
|
||||
localStorage.setItem('establishment_id', user.establishment_id);
|
||||
}
|
||||
|
||||
if (user.droit === 0) {
|
||||
// Vue ECOLE
|
||||
} else if (user.droit === 1) {
|
||||
// Vue ADMIN
|
||||
const roles = user.roles.filter(role => role.role_type === selectedProfile);
|
||||
if (roles.length > 0) {
|
||||
// const establishment = roles[0].establishment;
|
||||
// localStorage.setItem('establishment_id', establishment);
|
||||
|
||||
// Redirection en fonction du rôle
|
||||
if (roles[0].role_type === 1) {
|
||||
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
||||
} else if (user.droit === 2) {
|
||||
// Vue PARENT
|
||||
} else if (roles[0].role_type === 2) {
|
||||
router.push(FE_PARENTS_HOME_URL);
|
||||
} else {
|
||||
// Cas anormal
|
||||
}
|
||||
}).catch(error => {
|
||||
logger.error('Error during session retrieval:', error);
|
||||
setIsLoading(false);
|
||||
setErrorMessage('An error occurred during session retrieval.');
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
logger.error('Error during sign in:', error);
|
||||
setIsLoading(false);
|
||||
setErrorMessage('An error occurred during sign in.');
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setErrorMessage('No roles found for the specified role type.');
|
||||
}
|
||||
}).catch(error => {
|
||||
logger.error('Error during session retrieval:', error);
|
||||
setIsLoading(false);
|
||||
setErrorMessage('An error occurred during session retrieval.');
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
logger.error('Error during sign in:', error);
|
||||
setIsLoading(false);
|
||||
setErrorMessage('An error occurred during sign in.');
|
||||
});
|
||||
}
|
||||
|
||||
if (isLoading === true) {
|
||||
return <Loader /> // Affichez le composant Loader
|
||||
@ -94,7 +98,8 @@ export default function Page() {
|
||||
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); handleFormLogin(new FormData(e.target)); }}>
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
<InputTextIcon name="login" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full mb-5" />
|
||||
<InputTextIcon name="password" type="password" IconItem={KeySquare} label="Mot de passe" placeholder="Mot de passe" errorMsg={passwordFieldError} className="w-full" />
|
||||
<InputTextIcon name="password" type="password" IconItem={KeySquare} label="Mot de passe" placeholder="Mot de passe" errorMsg={passwordFieldError} className="w-full mb-5" />
|
||||
<ProfileSelector selectedProfile={selectedProfile} setSelectedProfile={setSelectedProfile} />
|
||||
<div className="input-group mb-4">
|
||||
</div>
|
||||
<label className="text-red-500">{errorMessage}</label>
|
||||
|
||||
@ -30,6 +30,7 @@ export const login = (data) => {
|
||||
redirect: false,
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
role_type: data.role_type,
|
||||
})
|
||||
};
|
||||
|
||||
@ -194,7 +195,11 @@ export const getUser = async () => {
|
||||
return {
|
||||
id: session.user.user_id,
|
||||
email: session.user.email,
|
||||
role: session.user.droit
|
||||
roles: session.user.roles.map(role => ({
|
||||
role_type: role.role_type,
|
||||
establishment_id: role.establishment__id,
|
||||
establishment_name: role.establishment__name
|
||||
}))
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@ -7,8 +7,7 @@ import {
|
||||
BE_SCHOOL_DISCOUNTS_URL,
|
||||
BE_SCHOOL_PAYMENT_PLANS_URL,
|
||||
BE_SCHOOL_PAYMENT_MODES_URL,
|
||||
BE_SCHOOL_ESTABLISHMENT_URL,
|
||||
ESTABLISHMENT_ID
|
||||
BE_SCHOOL_ESTABLISHMENT_URL
|
||||
} from '@/utils/Url';
|
||||
|
||||
const requestResponseHandler = async (response) => {
|
||||
@ -24,18 +23,18 @@ const requestResponseHandler = async (response) => {
|
||||
}
|
||||
|
||||
|
||||
export const fetchSpecialities = () => {
|
||||
return fetch(`${BE_SCHOOL_SPECIALITIES_URL}`)
|
||||
export const fetchSpecialities = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_SPECIALITIES_URL}?establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const fetchTeachers = () => {
|
||||
return fetch(`${BE_SCHOOL_TEACHERS_URL}`)
|
||||
export const fetchTeachers = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_TEACHERS_URL}?establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const fetchClasses = () => {
|
||||
return fetch(`${BE_SCHOOL_SCHOOLCLASSES_URL}`)
|
||||
export const fetchClasses = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_SCHOOLCLASSES_URL}?establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
|
||||
@ -44,48 +43,48 @@ export const fetchSchedules = () => {
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const fetchRegistrationDiscounts = () => {
|
||||
return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=registration`)
|
||||
export const fetchRegistrationDiscounts = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=registration&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const fetchTuitionDiscounts = () => {
|
||||
return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=tuition`)
|
||||
export const fetchTuitionDiscounts = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=tuition&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const fetchRegistrationFees = () => {
|
||||
return fetch(`${BE_SCHOOL_FEES_URL}?filter=registration`)
|
||||
export const fetchRegistrationFees = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_FEES_URL}?filter=registration&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const fetchTuitionFees = () => {
|
||||
return fetch(`${BE_SCHOOL_FEES_URL}?filter=tuition`)
|
||||
export const fetchTuitionFees = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_FEES_URL}?filter=tuition&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const fetchRegistrationPaymentPlans = () => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=registration`)
|
||||
export const fetchRegistrationPaymentPlans = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=registration&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const fetchTuitionPaymentPlans = () => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=tuition`)
|
||||
export const fetchTuitionPaymentPlans = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=tuition&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const fetchRegistrationPaymentModes = () => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=registration`)
|
||||
export const fetchRegistrationPaymentModes = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=registration&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const fetchTuitionPaymentModes = () => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=tuition`)
|
||||
export const fetchTuitionPaymentModes = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=tuition&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const fetchEstablishment = () => {
|
||||
return fetch(`${BE_SCHOOL_ESTABLISHMENT_URL}/${ESTABLISHMENT_ID}`)
|
||||
export const fetchEstablishment = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_ESTABLISHMENT_URL}/${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
|
||||
@ -100,7 +100,7 @@ export const archiveRegisterForm = (id) => {
|
||||
}
|
||||
|
||||
export const fetchStudents = (id=null, establishment) => {
|
||||
const url = (id)?`${BE_SUBSCRIPTION_STUDENTS_URL}/${id}`:`${BE_SUBSCRIPTION_STUDENTS_URL}?establishment_id=${establishment}`;
|
||||
const url = (id)?`${BE_SUBSCRIPTION_STUDENTS_URL}/${id}?establishment_id=${establishment}`:`${BE_SUBSCRIPTION_STUDENTS_URL}?establishment_id=${establishment}`;
|
||||
const request = new Request(
|
||||
url,
|
||||
{
|
||||
@ -114,9 +114,9 @@ export const fetchStudents = (id=null, establishment) => {
|
||||
|
||||
};
|
||||
|
||||
export const fetchChildren = (id) =>{
|
||||
export const fetchChildren = (id, establishment) =>{
|
||||
const request = new Request(
|
||||
`${BE_SUBSCRIPTION_CHILDRENS_URL}/${id}`,
|
||||
`${BE_SUBSCRIPTION_CHILDRENS_URL}/${id}?establishment_id=${establishment}`,
|
||||
{
|
||||
method:'GET',
|
||||
headers: {
|
||||
|
||||
31
Front-End/src/components/ProfileSelector.js
Normal file
31
Front-End/src/components/ProfileSelector.js
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
const ProfileSelector = ({ selectedProfile, setSelectedProfile }) => {
|
||||
return (
|
||||
<div className="flex space-x-4">
|
||||
<button
|
||||
type="button"
|
||||
className={`px-4 py-2 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-blue-300 ${selectedProfile === 1 ? 'bg-blue-500 text-white ring-2 ring-blue-500' : 'bg-gray-200'}`}
|
||||
onClick={() => setSelectedProfile(1)}
|
||||
>
|
||||
ADMINISTRATEUR
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`px-4 py-2 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-blue-300 ${selectedProfile === 0 ? 'bg-blue-500 text-white ring-2 ring-blue-500' : 'bg-gray-200'}`}
|
||||
onClick={() => setSelectedProfile(0)}
|
||||
>
|
||||
PROFESSEUR
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`px-4 py-2 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-blue-300 ${selectedProfile === 2 ? 'bg-blue-500 text-white ring-2 ring-blue-500' : 'bg-gray-200'}`}
|
||||
onClick={() => setSelectedProfile(2)}
|
||||
>
|
||||
PARENT
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileSelector;
|
||||
@ -18,8 +18,10 @@ const ProtectedRoute = ({ children, requiredRight }) => {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
// Vérifier le rôle de l'utilisateur
|
||||
if (session && requiredRight && session.user.droit !== requiredRight) {
|
||||
// Vérifier si l'utilisateur a au moins un rôle correspondant au requiredRight
|
||||
const hasRequiredRight = session?.user?.roles?.some(role => role.role_type === requiredRight);
|
||||
|
||||
if (session && requiredRight && !hasRequiredRight) {
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { SessionProvider } from "next-auth/react"
|
||||
import { CsrfProvider } from '@/context/CsrfContext'
|
||||
import { NextIntlClientProvider } from 'next-intl'
|
||||
import { EstablishmentProvider } from '@/context/EstablishmentContext';
|
||||
|
||||
|
||||
export default function Providers({ children, messages, locale, session }) {
|
||||
if (!locale) {
|
||||
@ -12,9 +14,11 @@ export default function Providers({ children, messages, locale, session }) {
|
||||
return (
|
||||
<SessionProvider session={session}>
|
||||
<CsrfProvider>
|
||||
<NextIntlClientProvider messages={messages} locale={locale}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
<EstablishmentProvider>
|
||||
<NextIntlClientProvider messages={messages} locale={locale}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</EstablishmentProvider>
|
||||
</CsrfProvider>
|
||||
</SessionProvider>
|
||||
)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
|
||||
<div
|
||||
@ -14,8 +15,9 @@ const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
function Sidebar({ establishment, currentPage, items }) {
|
||||
function Sidebar({ establishments, currentPage, items, onCloseMobile, onEstablishmentChange }) {
|
||||
const router = useRouter();
|
||||
const { selectedEstablishmentId, setSelectedEstablishmentId, setProfileRole } = useEstablishment();
|
||||
const [selectedItem, setSelectedItem] = useState(currentPage);
|
||||
|
||||
useEffect(() => {
|
||||
@ -25,31 +27,49 @@ function Sidebar({ establishment, currentPage, items }) {
|
||||
const handleItemClick = (url) => {
|
||||
setSelectedItem(url);
|
||||
router.push(url);
|
||||
if (onCloseMobile) {
|
||||
onCloseMobile();
|
||||
}
|
||||
};
|
||||
|
||||
return <>
|
||||
{/* Sidebar */}
|
||||
const handleEstablishmentChange = (e) => {
|
||||
const establishmentId = parseInt(e.target.value, 10);
|
||||
setSelectedEstablishmentId(establishmentId);
|
||||
const role = establishments.find(est => est.id === establishmentId)?.role_type;
|
||||
setProfileRole(role);
|
||||
onEstablishmentChange(establishmentId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-64 bg-white border-r h-full border-gray-200 py-6 px-4">
|
||||
<div className="flex items-center mb-8 px-2">
|
||||
<div className="text-xl font-semibold">{establishment?.name}</div>
|
||||
<select
|
||||
value={selectedEstablishmentId}
|
||||
onChange={handleEstablishmentChange}
|
||||
className="form-select block w-full mt-1"
|
||||
>
|
||||
{establishments.map(establishment => (
|
||||
<option key={establishment.id} value={establishment.id}>
|
||||
{establishment.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<nav className="space-y-1">
|
||||
{
|
||||
items.map((item) => (
|
||||
<SidebarItem
|
||||
key={item.id}
|
||||
icon={item.icon}
|
||||
text={item.name}
|
||||
active={item.id === selectedItem}
|
||||
url={item.url}
|
||||
onClick={() => handleItemClick(item.url)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{items.map((item) => (
|
||||
<SidebarItem
|
||||
key={item.id}
|
||||
icon={item.icon}
|
||||
text={item.name}
|
||||
active={item.id === selectedItem}
|
||||
url={item.url}
|
||||
onClick={() => handleItemClick(item.url)}
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sidebar;
|
||||
@ -329,7 +329,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
|
||||
<TeacherItem key={teacher.id} teacher={teacher} />
|
||||
);
|
||||
case 'EMAIL':
|
||||
return teacher.email;
|
||||
return teacher.associated_profile_email;
|
||||
case 'SPECIALITES':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2 flex-wrap">
|
||||
|
||||
341
Front-End/src/components/Structure/Files/FilesManagement.js
Normal file
341
Front-End/src/components/Structure/Files/FilesManagement.js
Normal file
@ -0,0 +1,341 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Plus, Download, Edit, Trash2, FolderPlus, Signature } from 'lucide-react';
|
||||
import Modal from '@/components/Modal';
|
||||
import Table from '@/components/Table';
|
||||
import FileUpload from '@/components/FileUpload';
|
||||
import { formatDate } from '@/utils/Date';
|
||||
import { BASE_URL } from '@/utils/Url';
|
||||
import {
|
||||
fetchRegisterFormFileTemplate,
|
||||
createRegistrationFormFileTemplate,
|
||||
editRegistrationFormFileTemplate,
|
||||
deleteRegisterFormFileTemplate,
|
||||
getRegisterFormFileTemplate
|
||||
} from '@/app/actions/subscriptionAction';
|
||||
import {
|
||||
fetchRegistrationFileGroups,
|
||||
createRegistrationFileGroup,
|
||||
deleteRegistrationFileGroup,
|
||||
editRegistrationFileGroup
|
||||
} from '@/app/actions/registerFileGroupAction';
|
||||
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
|
||||
|
||||
export default function FilesManagement({ csrfToken }) {
|
||||
const [fichiers, setFichiers] = useState([]);
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [selectedGroup, setSelectedGroup] = useState(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [fileToEdit, setFileToEdit] = useState(null);
|
||||
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
||||
const [groupToEdit, setGroupToEdit] = useState(null);
|
||||
const [token]
|
||||
|
||||
// Fonction pour transformer les données des fichiers avec les informations complètes du groupe
|
||||
const transformFileData = (file, groups) => {
|
||||
if (!file.group) return file;
|
||||
|
||||
const groupInfo = groups.find(g => g.id === file.group);
|
||||
return {
|
||||
...file,
|
||||
group: groupInfo || { id: file.group, name: 'Groupe inconnu' }
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
fetchRegisterFormFileTemplate(),
|
||||
fetchRegistrationFileGroups()
|
||||
]).then(([filesData, groupsData]) => {
|
||||
setGroups(groupsData);
|
||||
// Sélectionner automatiquement le premier groupe s'il existe
|
||||
if (groupsData.length > 0) {
|
||||
setSelectedGroup(groupsData[0].id.toString());
|
||||
}
|
||||
// Transformer chaque fichier pour inclure les informations complètes du groupe
|
||||
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
|
||||
setFichiers(transformedFiles);
|
||||
}).catch(err => {
|
||||
console.log(err.message);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleFileDelete = (fileId) => {
|
||||
deleteRegisterFormFileTemplate(fileId, csrfToken)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
|
||||
alert('Fichier supprimé avec succès.');
|
||||
} else {
|
||||
alert('Erreur lors de la suppression du fichier.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error deleting file:', error);
|
||||
alert('Erreur lors de la suppression du fichier.');
|
||||
});
|
||||
};
|
||||
|
||||
const handleFileEdit = (file) => {
|
||||
setIsEditing(true);
|
||||
setFileToEdit(file);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const handleFileUpload = ({file, name, is_required, order, groupId}) => {
|
||||
if (!name) {
|
||||
alert('Veuillez entrer un nom de fichier.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
if(file) {
|
||||
formData.append('file', file);
|
||||
}
|
||||
formData.append('name', name);
|
||||
formData.append('is_required', is_required);
|
||||
formData.append('order', order);
|
||||
|
||||
// Modification ici : vérifier si groupId existe et n'est pas vide
|
||||
if (groupId && groupId !== '') {
|
||||
formData.append('group', groupId); // Notez que le nom du champ est 'group' et non 'group_id'
|
||||
}
|
||||
|
||||
if (isEditing && fileToEdit) {
|
||||
editRegistrationFormFileTemplate(fileToEdit.id, formData, csrfToken)
|
||||
.then(data => {
|
||||
// Transformer le fichier mis à jour avec les informations du groupe
|
||||
const transformedFile = transformFileData(data, groups);
|
||||
setFichiers(prevFichiers =>
|
||||
prevFichiers.map(f => f.id === fileToEdit.id ? transformedFile : f)
|
||||
);
|
||||
setIsModalOpen(false);
|
||||
setFileToEdit(null);
|
||||
setIsEditing(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error editing file:', error);
|
||||
alert('Erreur lors de la modification du fichier');
|
||||
});
|
||||
} else {
|
||||
createRegistrationFormFileTemplate(formData, csrfToken)
|
||||
.then(data => {
|
||||
// Transformer le nouveau fichier avec les informations du groupe
|
||||
const transformedFile = transformFileData(data, groups);
|
||||
setFichiers(prevFiles => [...prevFiles, transformedFile]);
|
||||
setIsModalOpen(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error uploading file:', error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleGroupSubmit = (groupData) => {
|
||||
if (groupToEdit) {
|
||||
editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken)
|
||||
.then(updatedGroup => {
|
||||
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
|
||||
setGroupToEdit(null);
|
||||
setIsGroupModalOpen(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error handling group:', error);
|
||||
alert('Erreur lors de l\'opération sur le groupe');
|
||||
});
|
||||
} else {
|
||||
createRegistrationFileGroup(groupData, csrfToken)
|
||||
.then(newGroup => {
|
||||
setGroups([...groups, newGroup]);
|
||||
setIsGroupModalOpen(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error handling group:', error);
|
||||
alert('Erreur lors de l\'opération sur le groupe');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleGroupEdit = (group) => {
|
||||
setGroupToEdit(group);
|
||||
setIsGroupModalOpen(true);
|
||||
};
|
||||
|
||||
const handleGroupDelete = (groupId) => {
|
||||
// Vérifier si des fichiers utilisent ce groupe
|
||||
const filesInGroup = fichiers.filter(file => file.group && file.group.id === groupId);
|
||||
if (filesInGroup.length > 0) {
|
||||
alert('Impossible de supprimer ce groupe car il contient des fichiers. Veuillez d\'abord retirer tous les fichiers de ce groupe.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.confirm('Êtes-vous sûr de vouloir supprimer ce groupe ?')) {
|
||||
deleteRegistrationFileGroup(groupId, csrfToken)
|
||||
.then((response) => {
|
||||
if (response.status === 409) {
|
||||
throw new Error('Ce groupe est lié à des inscriptions existantes.');
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la suppression du groupe.');
|
||||
}
|
||||
setGroups(groups.filter(group => group.id !== groupId));
|
||||
alert('Groupe supprimé avec succès.');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error deleting group:', error);
|
||||
alert(error.message || 'Erreur lors de la suppression du groupe. Vérifiez qu\'aucune inscription n\'utilise ce groupe.');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Ajouter cette fonction de filtrage
|
||||
const filteredFiles = fichiers.filter(file => {
|
||||
if (!selectedGroup) return true;
|
||||
return file.group && file.group.id === parseInt(selectedGroup);
|
||||
});
|
||||
|
||||
const columnsFiles = [
|
||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
||||
{ name: 'Groupe', transform: (row) => row.group ? row.group.name : 'Aucun' },
|
||||
{ name: 'Date de création', transform: (row) => formatDate(new Date (row.date_added),"DD/MM/YYYY hh:mm:ss") },
|
||||
{ name: 'Fichier Obligatoire', transform: (row) => row.is_required ? 'Oui' : 'Non' },
|
||||
{ name: 'Ordre de fusion', transform: (row) => row.order },
|
||||
{ name: 'Actions', transform: (row) => (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{row.file && (
|
||||
<a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
|
||||
<Download size={16} />
|
||||
</a>
|
||||
)}
|
||||
<button onClick={() => handleFileEdit(row)} className="text-blue-500 hover:text-blue-700">
|
||||
<Edit size={16} />
|
||||
</button>
|
||||
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
<button onClick={() => handleSignatureRequest(row)} className="text-green-500 hover:text-green-700">
|
||||
<Signature size={16} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
];
|
||||
|
||||
const columnsGroups = [
|
||||
{ name: 'Nom du groupe', transform: (row) => row.name },
|
||||
{ name: 'Description', transform: (row) => row.description },
|
||||
{ name: 'Actions', transform: (row) => (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<button onClick={() => handleGroupEdit(row)} className="text-blue-500 hover:text-blue-700">
|
||||
<Edit size={16} />
|
||||
</button>
|
||||
<button onClick={() => handleGroupDelete(row.id)} className="text-red-500 hover:text-red-700">
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
];
|
||||
|
||||
// Fonction pour gérer la demande de signature
|
||||
const handleSignatureRequest = (file) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
console.log('Demande de signature pour le fichier :', file);
|
||||
|
||||
fetch('http://localhost:8080:/DocuSeal/generateToken', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer NFPZy6BBGvYs1BwTuXMQ3XAu5N1kLFiXWftGQhkiz2A',
|
||||
},
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors du téléversement du document : ' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
const documentId = data.documentId;
|
||||
console.log('Document téléversé avec succès, ID :', documentId);
|
||||
onUpload(documentId);
|
||||
});
|
||||
.catch((error) => console.error(error));
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
setIsOpen={setIsModalOpen}
|
||||
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
|
||||
ContentComponent={() => (
|
||||
<FileUpload
|
||||
onFileUpload={handleFileUpload}
|
||||
fileToEdit={fileToEdit}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Modal
|
||||
isOpen={isGroupModalOpen}
|
||||
setIsOpen={setIsGroupModalOpen}
|
||||
title={groupToEdit ? "Modifier le groupe" : "Ajouter un groupe de fichiers"}
|
||||
ContentComponent={() => (
|
||||
<RegistrationFileGroupForm
|
||||
onSubmit={handleGroupSubmit}
|
||||
initialData={groupToEdit}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 mb-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-bold">Groupes de fichiers</h2>
|
||||
<button
|
||||
onClick={() => setIsGroupModalOpen(true)}
|
||||
className="flex items-center bg-blue-600 text-white p-2 rounded-full shadow hover:bg-blue-900 transition duration-200"
|
||||
>
|
||||
<FolderPlus className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<Table
|
||||
data={groups}
|
||||
columns={columnsGroups}
|
||||
itemsPerPage={5}
|
||||
currentPage={1}
|
||||
totalPages={Math.ceil(groups.length / 5)}
|
||||
/>
|
||||
</div>
|
||||
{groups.length > 0 && (
|
||||
<div className="mt-8">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-bold">Fichiers</h2>
|
||||
<div className="flex items-center gap-4">
|
||||
<select
|
||||
className="border rounded p-2"
|
||||
value={selectedGroup || ''}
|
||||
onChange={(e) => setSelectedGroup(e.target.value)}
|
||||
>
|
||||
<option value="">Tous les groupes</option>
|
||||
{groups.map(group => (
|
||||
<option key={group.id} value={group.id}>{group.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
|
||||
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>
|
||||
<Table
|
||||
data={filteredFiles}
|
||||
columns={columnsFiles}
|
||||
itemsPerPage={10}
|
||||
currentPage={1}
|
||||
totalPages={Math.ceil(filteredFiles.length / 10)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
45
Front-End/src/context/EstablishmentContext.js
Normal file
45
Front-End/src/context/EstablishmentContext.js
Normal file
@ -0,0 +1,45 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
|
||||
const EstablishmentContext = createContext();
|
||||
|
||||
export const EstablishmentProvider = ({ children }) => {
|
||||
const [selectedEstablishmentId, setSelectedEstablishmentId] = useState(() => {
|
||||
// Récupérer l'ID de l'établissement depuis le localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('selectedEstablishmentId') || null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const [profileRole, setProfileRole] = useState(() => {
|
||||
// Récupérer le rôle du profil depuis le localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('profileRole') || null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Sauvegarder l'ID de l'établissement dans le localStorage
|
||||
if (selectedEstablishmentId) {
|
||||
localStorage.setItem('selectedEstablishmentId', selectedEstablishmentId);
|
||||
}
|
||||
}, [selectedEstablishmentId]);
|
||||
|
||||
useEffect(() => {
|
||||
// Sauvegarder le rôle du profil dans le localStorage
|
||||
if (profileRole) {
|
||||
localStorage.setItem('profileRole', profileRole);
|
||||
}
|
||||
}, [profileRole]);
|
||||
|
||||
return (
|
||||
<EstablishmentContext.Provider value={{ selectedEstablishmentId, setSelectedEstablishmentId, profileRole, setProfileRole }}>
|
||||
{children}
|
||||
</EstablishmentContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useEstablishment = () => {
|
||||
return useContext(EstablishmentContext);
|
||||
};
|
||||
@ -11,24 +11,23 @@ const options = {
|
||||
name: 'Credentials',
|
||||
credentials: {
|
||||
email: { label: 'Email', type: 'email' },
|
||||
password: { label: 'Password', type: 'password' }
|
||||
password: { label: 'Password', type: 'password' },
|
||||
role_type: { label: 'Role Type', type: 'text' }
|
||||
},
|
||||
authorize: async (credentials, req) => {
|
||||
try {
|
||||
const data = {
|
||||
email: credentials.email,
|
||||
password: credentials.password
|
||||
password: credentials.password,
|
||||
role_type: credentials.role_type
|
||||
};
|
||||
|
||||
const user = await getJWT(data);
|
||||
|
||||
if (user) {
|
||||
logger.debug("API response:", user);
|
||||
return user;
|
||||
}
|
||||
logger.error('Invalid credentials')
|
||||
} catch (error) {
|
||||
logger.error('Invalid credentials')
|
||||
throw new Error(error.message || 'Invalid credentials');
|
||||
}
|
||||
}
|
||||
@ -88,16 +87,15 @@ const options = {
|
||||
},
|
||||
async session({ session, token }) {
|
||||
if (token && token?.token) {
|
||||
const {user_id, droit, email, establishment} = jwt_decode.decode(token.token);
|
||||
const { user_id, email, roles } = jwt_decode.decode(token.token);
|
||||
session.user = {
|
||||
...session.user,
|
||||
token: token.token,
|
||||
refresh: token.refresh
|
||||
refresh: token.refresh,
|
||||
user_id: user_id,
|
||||
email: email,
|
||||
roles: roles
|
||||
};
|
||||
session.user.user_id = user_id;
|
||||
session.user.droit = droit;
|
||||
session.user.email = email;
|
||||
session.user.establishment = establishment;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
@ -40,7 +40,9 @@ export const BE_SCHOOL_FEES_URL = `${BASE_URL}/School/fees`;
|
||||
export const BE_SCHOOL_DISCOUNTS_URL = `${BASE_URL}/School/discounts`;
|
||||
export const BE_SCHOOL_PAYMENT_PLANS_URL = `${BASE_URL}/School/paymentPlans`;
|
||||
export const BE_SCHOOL_PAYMENT_MODES_URL = `${BASE_URL}/School/paymentModes`;
|
||||
export const BE_SCHOOL_ESTABLISHMENT_URL = `${BASE_URL}/School/establishments`;
|
||||
|
||||
// ESTABLISHMENT
|
||||
export const BE_SCHOOL_ESTABLISHMENT_URL = `${BASE_URL}/Establishment/establishments`;
|
||||
|
||||
|
||||
// GESTION PLANNING
|
||||
|
||||
Reference in New Issue
Block a user