feat: Création nouveau style / pagination profils annuaires

This commit is contained in:
N3WT DE COMPET
2025-05-06 19:54:46 +02:00
parent 4fd40ac5fc
commit 760ee0009e
25 changed files with 430 additions and 247 deletions

View File

@ -1,125 +1,50 @@
'use client';
import React, { useState, useEffect } from 'react';
import {
fetchProfileRoles,
updateProfileRoles,
deleteProfileRoles,
} from '@/app/actions/authAction';
import { dissociateGuardian } from '@/app/actions/subscriptionAction';
import { fetchProfileRoles } from '@/app/actions/authAction';
import logger from '@/utils/logger';
import { useEstablishment } from '@/context/EstablishmentContext';
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import { useCsrfToken } from '@/context/CsrfContext';
import ProfileDirectory from '@/components/ProfileDirectory';
import { PARENT_FILTER, SCHOOL_FILTER } from '@/utils/constants';
export default function Page() {
const [profileRoles, setProfileRoles] = useState([]);
const [profileRolesDatasParent, setProfileRolesDatasParent] = useState([]);
const [profileRolesDatasSchool, setProfileRolesDatasSchool] = useState([]);
const [reloadFetch, setReloadFetch] = useState(false);
const csrfToken = useCsrfToken();
const { selectedEstablishmentId } = useEstablishment();
const requestErrorHandler = (err) => {
logger.error('Error fetching data:', err);
};
useEffect(() => {
if (selectedEstablishmentId) {
// Fetch data for profileRoles
// Fetch data for profileRolesParent
handleProfiles();
}
}, [selectedEstablishmentId, reloadFetch]);
const handleProfiles = () => {
fetchProfileRoles(selectedEstablishmentId)
fetchProfileRoles(selectedEstablishmentId, PARENT_FILTER)
.then((data) => {
setProfileRoles(data);
setProfileRolesDatasParent(data);
})
.catch((error) => logger.error('Error fetching profileRoles:', error));
.catch(requestErrorHandler);
fetchProfileRoles(selectedEstablishmentId, SCHOOL_FILTER)
.then((data) => {
setProfileRolesDatasSchool(data);
})
.catch(requestErrorHandler);
setReloadFetch(false);
};
const handleEdit = (profileRole) => {
const updatedData = { ...profileRole, is_active: !profileRole.is_active };
return updateProfileRoles(profileRole.id, updatedData, csrfToken)
.then((data) => {
setProfileRoles((prevState) =>
prevState.map((item) => (item.id === profileRole.id ? data : item))
);
return data;
})
.catch((error) => {
logger.error('Error editing data:', error);
throw error;
});
};
const handleDelete = (id) => {
return deleteProfileRoles(id, csrfToken)
.then(() => {
setProfileRoles((prevState) =>
prevState.filter((item) => item.id !== id)
);
logger.debug('Profile deleted successfully:', id);
})
.catch((error) => {
logger.error('Error deleting profile:', error);
throw error;
});
};
const handleDissociate = (studentId, guardianId) => {
return dissociateGuardian(studentId, guardianId)
.then((response) => {
logger.debug('Guardian dissociated successfully:', guardianId);
// Vérifier si le Guardian a été supprimé
const isGuardianDeleted = response?.isGuardianDeleted;
// Mettre à jour le modèle profileRoles
setProfileRoles(
(prevState) =>
prevState
.map((profileRole) => {
if (profileRole.associated_person?.id === guardianId) {
if (isGuardianDeleted) {
// Si le Guardian est supprimé, retirer le profileRole
return null;
} else {
// Si le Guardian n'est pas supprimé, mettre à jour les élèves associés
const updatedStudents =
profileRole.associated_person.students.filter(
(student) => student.id !== studentId
);
return {
...profileRole,
associated_person: {
...profileRole.associated_person,
students: updatedStudents, // Mettre à jour les élèves associés
},
};
}
}
setReloadFetch(true);
return profileRole; // Conserver les autres profileRoles
})
.filter(Boolean) // Supprimer les entrées nulles
);
})
.catch((error) => {
logger.error('Error dissociating guardian:', error);
throw error;
});
};
return (
<div className="p-8">
<DjangoCSRFToken csrfToken={csrfToken} />
<div className="w-full p-4">
<ProfileDirectory
profileRoles={profileRoles}
handleActivateProfile={handleEdit}
handleDeleteProfile={handleDelete}
handleDissociateGuardian={handleDissociate}
/>
</div>
<div className="w-full h-full">
<ProfileDirectory
parentProfiles={profileRolesDatasParent}
schoolProfiles={profileRolesDatasSchool}
/>
</div>
);
}

View File

@ -13,7 +13,7 @@ import { useEstablishment } from '@/context/EstablishmentContext';
// Composant EventCard pour afficher les événements
const EventCard = ({ title, date, description, type }) => (
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-100 mb-4">
<div className="bg-stone-50 p-4 rounded-lg shadow-sm border border-gray-100 mb-4">
<div className="flex items-center gap-3">
<CalendarCheck className="text-blue-500" size={20} />
<div>
@ -125,7 +125,7 @@ export default function DashboardPage() {
{/* Événements et KPIs */}
<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">
<div className="lg:col-span-2 bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-100">
<h2 className="text-lg font-semibold mb-4">
{t('inscriptionTrends')}
</h2>
@ -136,24 +136,13 @@ export default function DashboardPage() {
</div>
{/* Événements à venir */}
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-100">
<div className="bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-100">
<h2 className="text-lg font-semibold mb-4">{t('upcomingEvents')}</h2>
{upcomingEvents.map((event, index) => (
<EventCard key={index} {...event} />
))}
</div>
</div>
<div className="flex flex-wrap">
{classes.map((classe) => (
<div
key={classe.id}
className="lg:col-span-2 bg-white p-6 rounded-lg shadow-sm border border-gray-100 mr-4"
>
<ClasseDetails classe={classe} />
</div>
))}
</div>
</div>
);
}

View File

@ -382,10 +382,6 @@ export default function CreateSubscriptionPage() {
(profile) => profile.id === formDataRef.current.existingProfileId
);
// Affichez le profil existant dans la console
console.log('Profil existant trouvé :', existingProfile?.email);
console.log('debug : ', initialGuardianEmail);
const guardians = (() => {
if (formDataRef.current.selectedGuardians.length > 0) {
// Cas 3 : Des guardians sont sélectionnés

View File

@ -47,9 +47,9 @@ import { getCurrentSchoolYear, getNextSchoolYear } from '@/utils/Date';
import {
RegistrationFormStatus,
CURRENT_YEAR,
NEXT_YEAR,
HISTORICAL,
CURRENT_YEAR_FILTER,
NEXT_YEAR_FILTER,
HISTORICAL_FILTER,
} from '@/utils/constants';
export default function Page({ params: { locale } }) {
@ -75,7 +75,7 @@ export default function Page({ params: { locale } }) {
useState(1);
const [searchTerm, setSearchTerm] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [activeTab, setActiveTab] = useState(CURRENT_YEAR);
const [activeTab, setActiveTab] = useState(CURRENT_YEAR_FILTER);
const [totalCurrentYear, setTotalCurrentYear] = useState(0);
const [totalNextYear, setTotalNextYear] = useState(0);
const [totalHistorical, setTotalHistorical] = useState(0);
@ -190,7 +190,7 @@ export default function Page({ params: { locale } }) {
Promise.all([
fetchRegisterForms(
selectedEstablishmentId,
CURRENT_YEAR,
CURRENT_YEAR_FILTER,
currentSchoolYearPage,
itemsPerPage,
searchTerm
@ -204,10 +204,10 @@ export default function Page({ params: { locale } }) {
})
.catch(requestErrorHandler),
fetchRegisterForms(selectedEstablishmentId, NEXT_YEAR)
fetchRegisterForms(selectedEstablishmentId, NEXT_YEAR_FILTER)
.then(registerFormNextYearDataHandler)
.catch(requestErrorHandler),
fetchRegisterForms(selectedEstablishmentId, HISTORICAL)
fetchRegisterForms(selectedEstablishmentId, HISTORICAL_FILTER)
.then(registerFormHistoricalDataHandler)
.catch(requestErrorHandler),
])
@ -232,17 +232,17 @@ export default function Page({ params: { locale } }) {
setIsLoading(true);
fetchRegisterForms(
selectedEstablishmentId,
CURRENT_YEAR,
CURRENT_YEAR_FILTER,
currentSchoolYearPage,
itemsPerPage,
searchTerm
)
.then(registerFormCurrrentYearDataHandler)
.catch(requestErrorHandler);
fetchRegisterForms(selectedEstablishmentId, NEXT_YEAR)
fetchRegisterForms(selectedEstablishmentId, NEXT_YEAR_FILTER)
.then(registerFormNextYearDataHandler)
.catch(requestErrorHandler);
fetchRegisterForms(selectedEstablishmentId, HISTORICAL)
fetchRegisterForms(selectedEstablishmentId, HISTORICAL_FILTER)
.then(registerFormHistoricalDataHandler)
.catch(requestErrorHandler);
@ -261,13 +261,13 @@ export default function Page({ params: { locale } }) {
* UseEffect to update page count of tab
*/
useEffect(() => {
if (activeTab === CURRENT_YEAR) {
if (activeTab === CURRENT_YEAR_FILTER) {
setTotalCurrentSchoolYearPages(
Math.ceil(totalCurrentYear / itemsPerPage)
);
} else if (activeTab === NEXT_YEAR) {
} else if (activeTab === NEXT_YEAR_FILTER) {
setTotalNextSchoolYearPages(Math.ceil(totalNextYear / itemsPerPage));
} else if (activeTab === HISTORICAL) {
} else if (activeTab === HISTORICAL_FILTER) {
setTotalHistoricalPages(Math.ceil(totalHistorical / itemsPerPage));
}
}, [currentSchoolYearPage]);
@ -376,11 +376,11 @@ export default function Page({ params: { locale } }) {
};
const handlePageChange = (newPage) => {
if (activeTab === CURRENT_YEAR) {
if (activeTab === CURRENT_YEAR_FILTER) {
setCurrentSchoolYearPage(newPage);
} else if (activeTab === NEXT_YEAR) {
} else if (activeTab === NEXT_YEAR_FILTER) {
setCurrentSchoolNextYearPage(newPage);
} else if (activeTab === HISTORICAL) {
} else if (activeTab === HISTORICAL_FILTER) {
setCurrentSchoolHistoricalYearPage(newPage);
}
};
@ -710,8 +710,8 @@ export default function Page({ params: { locale } }) {
</span>
</>
}
active={activeTab === CURRENT_YEAR}
onClick={() => setActiveTab(CURRENT_YEAR)}
active={activeTab === CURRENT_YEAR_FILTER}
onClick={() => setActiveTab(CURRENT_YEAR_FILTER)}
/>
{/* Tab pour l'année scolaire prochaine */}
@ -724,8 +724,8 @@ export default function Page({ params: { locale } }) {
</span>
</>
}
active={activeTab === NEXT_YEAR}
onClick={() => setActiveTab(NEXT_YEAR)}
active={activeTab === NEXT_YEAR_FILTER}
onClick={() => setActiveTab(NEXT_YEAR_FILTER)}
/>
{/* Tab pour l'historique */}
@ -738,16 +738,16 @@ export default function Page({ params: { locale } }) {
</span>
</>
}
active={activeTab === HISTORICAL}
onClick={() => setActiveTab(HISTORICAL)}
active={activeTab === HISTORICAL_FILTER}
onClick={() => setActiveTab(HISTORICAL_FILTER)}
/>
</div>
</div>
<div className="border-b border-gray-200 mb-6 w-full">
{activeTab === CURRENT_YEAR ||
activeTab === NEXT_YEAR ||
activeTab === HISTORICAL ? (
{activeTab === CURRENT_YEAR_FILTER ||
activeTab === NEXT_YEAR_FILTER ||
activeTab === HISTORICAL_FILTER ? (
<React.Fragment>
<div className="flex justify-between items-center mb-4 w-full">
<div className="relative flex-grow">
@ -779,25 +779,25 @@ export default function Page({ params: { locale } }) {
<Table
key={`${currentSchoolYearPage}-${searchTerm}`}
data={
activeTab === CURRENT_YEAR
activeTab === CURRENT_YEAR_FILTER
? registrationFormsDataCurrentYear
: activeTab === NEXT_YEAR
: activeTab === NEXT_YEAR_FILTER
? registrationFormsDataNextYear
: registrationFormsDataHistorical
}
columns={columns}
itemsPerPage={itemsPerPage}
currentPage={
activeTab === CURRENT_YEAR
activeTab === CURRENT_YEAR_FILTER
? currentSchoolYearPage
: activeTab === NEXT_YEAR
: activeTab === NEXT_YEAR_FILTER
? currentSchoolNextYearPage
: currentSchoolHistoricalYearPage
}
totalPages={
activeTab === CURRENT_YEAR
activeTab === CURRENT_YEAR_FILTER
? totalCurrentSchoolYearPages
: activeTab === NEXT_YEAR
: activeTab === NEXT_YEAR_FILTER
? totalNextSchoolYearPages
: totalHistoricalPages
}

View File

@ -9,7 +9,7 @@ import {
BE_AUTH_NEW_PASSWORD_URL,
FE_USERS_LOGIN_URL,
} from '@/utils/Url';
import logger from '@/utils/logger';
import { PARENT_FILTER } from '@/utils/constants';
const requestResponseHandler = async (response) => {
const body = await response.json();
@ -73,10 +73,21 @@ export const disconnect = () => {
signOut({ callbackUrl: FE_USERS_LOGIN_URL });
};
export const fetchProfileRoles = (establishment) => {
return fetch(
`${BE_AUTH_PROFILES_ROLES_URL}?establishment_id=${establishment}`
).then(requestResponseHandler);
export const fetchProfileRoles = (
establishment,
filter = PARENT_FILTER,
page = '',
pageSize = ''
) => {
let url = `${BE_AUTH_PROFILES_ROLES_URL}?filter=${filter}&establishment_id=${establishment}`;
if (page !== '' && pageSize !== '') {
url = `${BE_AUTH_PROFILES_ROLES_URL}?filter=${filter}&establishment_id=${establishment}&page=${page}&search=${search}`;
}
return fetch(url, {
headers: {
'Content-Type': 'application/json',
},
}).then(requestResponseHandler);
};
export const updateProfileRoles = (id, data, csrfToken) => {

View File

@ -6,7 +6,7 @@ import {
BE_SUBSCRIPTION_ABSENCES_URL,
} from '@/utils/Url';
import { CURRENT_YEAR, NEXT_YEAR, HISTORICAL } from '@/utils/constants';
import { CURRENT_YEAR_FILTER } from '@/utils/constants';
const requestResponseHandler = async (response) => {
const body = await response.json();
@ -21,7 +21,7 @@ const requestResponseHandler = async (response) => {
export const fetchRegisterForms = (
establishment,
filter = CURRENT_YEAR,
filter = CURRENT_YEAR_FILTER,
page = '',
pageSize = '',
search = ''

View File

@ -57,8 +57,8 @@ const PaymentModeSelector = ({
onClick={() => handleModeToggle(mode.id)}
className={`p-4 rounded-lg shadow-md text-center text-gray-700' ${
activePaymentModes.includes(mode.id)
? 'bg-emerald-300'
: 'bg-white'
? 'bg-emerald-100'
: 'bg-stone-50'
} hover:bg-emerald-200`}
>
{mode.name}

View File

@ -1,9 +1,18 @@
import React, { useState } from 'react';
import React, { useState, useEffect, act } from 'react';
import { Trash2, ToggleLeft, ToggleRight, Info, XCircle } from 'lucide-react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
import StatusLabel from '@/components/StatusLabel';
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
import SidebarTabs from '@/components/SidebarTabs';
import {
updateProfileRoles,
deleteProfileRoles,
} from '@/app/actions/authAction';
import { dissociateGuardian } from '@/app/actions/subscriptionAction';
import { useCsrfToken } from '@/context/CsrfContext';
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import logger from '@/utils/logger';
const roleTypeToLabel = (roleType) => {
switch (roleType) {
@ -31,25 +40,147 @@ const roleTypeToBadgeClass = (roleType) => {
}
};
const ProfileDirectory = ({
profileRoles,
handleActivateProfile,
handleDeleteProfile,
handleDissociateGuardian,
}) => {
const parentProfiles = profileRoles.filter(
(profileRole) => profileRole.role_type === 2
);
const schoolAdminProfiles = profileRoles.filter(
(profileRole) => profileRole.role_type !== 2
);
const ProfileDirectory = ({ parentProfiles, schoolProfiles }) => {
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState('');
const [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
const [confirmPopupMessage, setConfirmPopupMessage] = useState('');
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {});
const [visibleTooltipId, setVisibleTooltipId] = useState(null);
const [activeTab, setActiveTab] = useState('parent'); // Onglet actif
const [totalProfilesParentPages, setTotalProfilesParentPages] = useState(1);
const [totalProfilesSchoolPages, setTotalProfilesSchoolPages] = useState(1);
const [currentProfilesParentPage, setCurrentProfilesParentPage] = useState(1);
const [totalProfilesParent, setTotalProfilesParent] = useState(0);
const [totalProfilesSchool, setTotalProfilesSchool] = useState(0);
const [currentProfilesSchoolPage, setCurrentProfilesSchoolPage] = useState(1);
const [profileRolesParent, setProfileRolesParent] = useState([]);
const [profileRolesSchool, setProfileRolesSchool] = useState([]);
const itemsPerPage = 10; // Nombre d'éléments par page
const csrfToken = useCsrfToken();
const handleEdit = (profileRole) => {
const updatedData = { ...profileRole, is_active: !profileRole.is_active };
return updateProfileRoles(profileRole.id, updatedData, csrfToken)
.then((data) => {
setProfileRolesParent((prevState) =>
prevState.map((item) => (item.id === profileRole.id ? data : item))
);
return data;
})
.catch((error) => {
logger.error('Error editing data:', error);
throw error;
});
};
const handleDelete = (id) => {
return deleteProfileRoles(id, csrfToken)
.then(() => {
setProfileRolesParent((prevState) =>
prevState.filter((item) => item.id !== id)
);
logger.debug('Profile deleted successfully:', id);
})
.catch((error) => {
logger.error('Error deleting profile:', error);
throw error;
});
};
const handleDissociate = (studentId, guardianId) => {
return dissociateGuardian(studentId, guardianId)
.then((response) => {
logger.debug('Guardian dissociated successfully:', guardianId);
// Vérifier si le Guardian a été supprimé
const isGuardianDeleted = response?.isGuardianDeleted;
// Mettre à jour le modèle profileRolesParent
setProfileRolesParent(
(prevState) =>
prevState
.map((profileRole) => {
if (profileRole.associated_person?.id === guardianId) {
if (isGuardianDeleted) {
// Si le Guardian est supprimé, retirer le profileRole
return null;
} else {
// Si le Guardian n'est pas supprimé, mettre à jour les élèves associés
const updatedStudents =
profileRole.associated_person.students.filter(
(student) => student.id !== studentId
);
return {
...profileRole,
associated_person: {
...profileRole.associated_person,
students: updatedStudents, // Mettre à jour les élèves associés
},
};
}
}
return profileRole; // Conserver les autres profileRolesParent
})
.filter(Boolean) // Supprimer les entrées nulles
);
})
.catch((error) => {
logger.error('Error dissociating guardian:', error);
throw error;
});
};
const profilesRoleParentDataHandler = (data) => {
if (data) {
const { profilesRoles, count, page_size } = data;
if (profilesRoles) {
setProfileRolesParent(profilesRoles);
}
const calculatedTotalPages =
count === 0 ? 1 : Math.ceil(count / page_size);
setTotalProfilesParent(count);
setTotalProfilesParentPages(calculatedTotalPages);
}
};
const profilesRoleSchoolDataHandler = (data) => {
if (data) {
const { profilesRoles, count, page_size } = data;
if (profilesRoles) {
setProfileRolesSchool(profilesRoles);
}
const calculatedTotalPages =
count === 0 ? 1 : Math.ceil(count / page_size);
setTotalProfilesSchool(count);
setTotalProfilesSchoolPages(calculatedTotalPages);
}
};
useEffect(() => {
profilesRoleParentDataHandler(parentProfiles);
profilesRoleSchoolDataHandler(schoolProfiles);
if (activeTab === 'parent') {
setTotalProfilesParentPages(
Math.ceil(totalProfilesParent / itemsPerPage)
);
} else if (activeTab === 'school') {
setTotalProfilesSchoolPages(
Math.ceil(totalProfilesSchool / itemsPerPage)
);
}
}, [parentProfiles, schoolProfiles, activeTab]);
const handlePageChange = (newPage) => {
if (activeTab === 'parent') {
setCurrentProfilesParentPage(newPage);
} else if (activeTab === 'school') {
setCurrentProfilesSchoolPage(newPage);
}
};
const handleTooltipVisibility = (id) => {
setVisibleTooltipId(id); // Définir l'ID de la ligne pour laquelle la tooltip est visible
@ -64,7 +195,7 @@ const ProfileDirectory = ({
`Êtes-vous sûr de vouloir ${profileRole.is_active ? 'désactiver' : 'activer'} ce profil ?`
);
setConfirmPopupOnConfirm(() => () => {
handleActivateProfile(profileRole)
handleEdit(profileRole)
.then(() => {
setPopupMessage(
`Le profil a été ${profileRole.is_active ? 'désactivé' : 'activé'} avec succès.`
@ -85,7 +216,7 @@ const ProfileDirectory = ({
const handleConfirmDeleteProfile = (id) => {
setConfirmPopupMessage('Êtes-vous sûr de vouloir supprimer ce profil ?');
setConfirmPopupOnConfirm(() => () => {
handleDeleteProfile(id)
handleDelete(id)
.then(() => {
setPopupMessage('Le profil a été supprimé avec succès.');
setPopupVisible(true);
@ -105,7 +236,7 @@ const ProfileDirectory = ({
`Vous êtes sur le point de dissocier le responsable ${profileRole.associated_person?.guardian_name} de l'élève ${student.student_name}. Êtes-vous sûr de vouloir poursuivre cette opération ?`
);
setConfirmPopupOnConfirm(() => () => {
handleDissociateGuardian(student.id, profileRole.associated_person?.id)
handleDissociate(student.id, profileRole.associated_person?.id)
.then(() => {
setPopupMessage('Le responsable a été dissocié avec succès.');
setPopupVisible(true);
@ -315,23 +446,64 @@ const ProfileDirectory = ({
];
return (
<div className="bg-white rounded-lg shadow-lg w-3/5 p-6">
<div className="space-y-8">
<div className="max-h-128 overflow-y-auto border rounded p-4">
{parentProfiles.length === 0 ? (
<div>Aucun profil trouvé</div>
) : (
<Table data={parentProfiles} columns={parentColumns} />
)}
</div>
<div className="max-h-128 overflow-y-auto border rounded p-4">
{schoolAdminProfiles.length === 0 ? (
<div>Aucun profil trouvé</div>
) : (
<Table data={schoolAdminProfiles} columns={schoolAdminColumns} />
)}
</div>
</div>
<>
<DjangoCSRFToken csrfToken={csrfToken} />
<SidebarTabs
tabs={[
{
id: 'parent',
label: 'Parents',
content: (
<div className="h-full overflow-y-auto">
<Table
key={`parent-${currentProfilesParentPage}`}
data={
Array.isArray(profileRolesParent)
? profileRolesParent.slice(
(currentProfilesParentPage - 1) * itemsPerPage,
currentProfilesParentPage * itemsPerPage
)
: [] // Fallback to an empty array if profileRolesParent is not an array
}
columns={parentColumns}
itemsPerPage={itemsPerPage}
currentPage={currentProfilesParentPage}
totalPages={totalProfilesParentPages}
onPageChange={handlePageChange}
/>
</div>
),
},
{
id: 'school',
label: 'École',
content: (
<div className="h-full overflow-y-auto">
<Table
key={`school-${currentProfilesSchoolPage}`}
data={
Array.isArray(profileRolesSchool)
? profileRolesSchool.slice(
(currentProfilesSchoolPage - 1) * itemsPerPage,
currentProfilesSchoolPage * itemsPerPage
)
: [] // Fallback to an empty array if profileRolesSchool is not an array
}
columns={schoolAdminColumns}
itemsPerPage={itemsPerPage}
currentPage={currentProfilesSchoolPage}
totalPages={totalProfilesSchoolPages}
onPageChange={handlePageChange}
/>
</div>
),
},
]}
onTabChange={(newActiveTab) => {
setActiveTab(newActiveTab);
}}
/>
{/* Popups */}
<Popup
visible={popupVisible}
message={popupMessage}
@ -344,7 +516,7 @@ const ProfileDirectory = ({
onConfirm={confirmPopupOnConfirm}
onCancel={() => setConfirmPopupVisible(false)}
/>
</div>
</>
);
};

View File

@ -6,10 +6,8 @@ import ProfileSelector from '@/components/ProfileSelector';
const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
<div
onClick={onClick}
className={`flex items-center gap-3 px-2 py-2 rounded-md cursor-pointer ${
active
? 'bg-emerald-50 text-emerald-600'
: 'text-gray-600 hover:bg-gray-50'
className={`flex items-center gap-3 px-2 py-2 rounded-md cursor-pointer hover:bg-emerald-100 ${
active ? 'bg-emerald-50 text-emerald-600' : 'text-gray-600'
}`}
>
<Icon size={20} />
@ -35,7 +33,7 @@ function Sidebar({ currentPage, items, onCloseMobile }) {
};
return (
<div className="w-64 bg-white border-r h-full border-gray-200">
<div className="w-64 bg-stone-50 border-r h-full border-gray-200">
<div className="border-b border-gray-200 ">
<ProfileSelector className="border-none" />
</div>

View File

@ -1,34 +1,46 @@
import React, { useState } from 'react';
const SidebarTabs = ({ tabs }) => {
const SidebarTabs = ({ tabs, onTabChange }) => {
const [activeTab, setActiveTab] = useState(tabs[0].id);
const handleTabChange = (tabId) => {
setActiveTab(tabId);
if (onTabChange) {
onTabChange(tabId);
}
};
return (
<>
<div className="flex h-14 border-b-2 border-gray-200">
<div className="flex flex-col h-full">
{/* Tabs Header */}
<div className="flex h-14 bg-gray-50 border-b border-gray-200 shadow-sm">
{tabs.map((tab) => (
<button
key={tab.id}
className={`flex-1 p-4 ${
className={`flex-1 text-center p-4 font-medium transition-colors duration-200 ${
activeTab === tab.id
? 'border-b-2 border-emerald-500 text-emerald-500'
? 'border-b-4 border-emerald-500 text-emerald-600 bg-emerald-50 font-semibold'
: 'text-gray-500 hover:text-emerald-500'
}`}
onClick={() => setActiveTab(tab.id)}
onClick={() => handleTabChange(tab.id)}
>
{tab.label}
</button>
))}
</div>
{tabs.map((tab) => (
<div
key={tab.id}
className={`${activeTab === tab.id ? 'block h-[calc(100%-3.5rem)]' : 'hidden'}`}
>
{tab.content}
</div>
))}
</>
{/* Tabs Content */}
<div className="flex-1 overflow-y-auto p-4 rounded-b-lg shadow-inner">
{tabs.map((tab) => (
<div
key={tab.id}
className={`${activeTab === tab.id ? 'block' : 'hidden'}`}
>
{tab.content}
</div>
))}
</div>
</div>
);
};

View File

@ -1,6 +1,6 @@
// Composant StatCard pour afficher une statistique
const StatCard = ({ title, value, icon, color = 'blue' }) => (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-100">
<div className="bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-100">
<div className="flex justify-between items-start">
<div>
<h3 className="text-gray-500 text-sm font-medium">{title}</h3>

View File

@ -22,7 +22,7 @@ const StructureManagement = ({
handleDelete,
}) => {
return (
<div className="w-full p-4 mx-auto mt-6">
<div className="w-full">
<ClassesProvider>
<div className="mt-8 w-2/5">
<SpecialitiesSection

View File

@ -462,7 +462,7 @@ export default function FilesGroupsManagement({
}
return (
<div className="w-full p-4 mx-auto mt-6">
<div className="w-full">
{/* Modal pour les fichiers */}
<Modal
isOpen={isModalOpen}

View File

@ -360,7 +360,7 @@ const DiscountsSection = ({
data={newDiscount ? [newDiscount, ...discounts] : discounts}
columns={columns}
renderCell={renderDiscountCell}
defaultTheme="bg-yellow-100"
defaultTheme="bg-yellow-50"
/>
<Popup
visible={popupVisible}

View File

@ -50,7 +50,7 @@ const FeesManagement = ({
};
return (
<div className="w-full p-4 mx-auto mt-6">
<div className="w-full">
<div className="w-4/5 mx-auto flex items-center mt-8">
<hr className="flex-grow border-t-2 border-gray-300" />
<span className="mx-4 text-gray-600 font-semibold">

View File

@ -13,21 +13,21 @@ const Table = ({
onRowClick,
selectedRows,
isSelectable = false,
defaultTheme = 'bg-emerald-50',
defaultTheme = 'bg-emerald-50', // Blanc cassé pour les lignes paires
}) => {
const handlePageChange = (newPage) => {
onPageChange(newPage);
};
return (
<div className="bg-white rounded-lg border border-gray-200">
<table className="min-w-full bg-white">
<div className="bg-stone-50 rounded-lg border border-gray-300 shadow-md">
<table className="min-w-full bg-stone-50">
<thead>
<tr>
{columns.map((column, index) => (
<th
key={index}
className="py-2 px-4 border-b border-gray-200 bg-gray-100 text-center text-sm font-semibold text-gray-600"
className="py-2 px-4 border-b border-gray-300 bg-gray-100 text-center text-sm font-semibold text-gray-700"
>
{column.name}
</th>
@ -40,16 +40,21 @@ const Table = ({
key={rowIndex}
className={`
${isSelectable ? 'cursor-pointer' : ''}
${selectedRows?.includes(row.id) ? 'bg-emerald-300 text-white' : rowIndex % 2 === 0 ? `${defaultTheme}` : ''}
${isSelectable ? 'hover:bg-emerald-200' : ''}
${
selectedRows?.includes(row.id)
? 'bg-emerald-200 text-white'
: rowIndex % 2 === 0
? `${defaultTheme}`
: 'bg-stone-50' // Blanc cassé pour les lignes impaires
}
${isSelectable ? 'hover:bg-emerald-100' : ''}
`}
onClick={() => {
if (isSelectable && onRowClick) {
// Si la ligne est déjà sélectionnée, transmettre une indication explicite de désélection
if (selectedRows?.includes(row.id)) {
onRowClick({ deselected: true, row }); // Désélectionner
onRowClick({ deselected: true, row });
} else {
onRowClick(row); // Sélectionner
onRowClick(row);
}
}
}}
@ -57,7 +62,11 @@ const Table = ({
{columns.map((column, colIndex) => (
<td
key={colIndex}
className={`py-2 px-4 border-b border-gray-200 text-center text-sm ${selectedRows?.includes(row.id) ? 'text-white' : 'text-gray-700'}`}
className={`py-2 px-4 border-b border-gray-300 text-center text-sm ${
selectedRows?.includes(row.id)
? 'text-white'
: 'text-gray-700'
}`}
>
{renderCell
? renderCell(row, column.name)

View File

@ -26,6 +26,9 @@ export const RegistrationFormStatus = {
STATUS_SEPA_TO_SEND: 8,
};
export const CURRENT_YEAR = 'current_year';
export const NEXT_YEAR = 'next_year';
export const HISTORICAL = 'historical';
export const CURRENT_YEAR_FILTER = 'current_year';
export const NEXT_YEAR_FILTER = 'next_year';
export const HISTORICAL_FILTER = 'historical';
export const PARENT_FILTER = 'parents';
export const SCHOOL_FILTER = 'school';