diff --git a/Back-End/Auth/pagination.py b/Back-End/Auth/pagination.py
new file mode 100644
index 0000000..23ef561
--- /dev/null
+++ b/Back-End/Auth/pagination.py
@@ -0,0 +1,20 @@
+from rest_framework.pagination import PageNumberPagination
+
+from N3wtSchool import settings
+
+class CustomProfilesPagination(PageNumberPagination):
+ page_size_query_param = 'page_size'
+ max_page_size = settings.NB_MAX_PAGE
+ page_size = settings.NB_RESULT_PROFILES_PER_PAGE
+
+ def get_paginated_response(self, data):
+ return ({
+ 'links': {
+ 'next': self.get_next_link(),
+ 'previous': self.get_previous_link()
+ },
+ 'count': self.page.paginator.count,
+ 'page_size': self.page_size,
+ 'max_page_size' : self.max_page_size,
+ 'profilesRoles': data }
+ )
\ No newline at end of file
diff --git a/Back-End/Auth/views.py b/Back-End/Auth/views.py
index d322df2..f1730cf 100644
--- a/Back-End/Auth/views.py
+++ b/Back-End/Auth/views.py
@@ -8,6 +8,7 @@ from django.middleware.csrf import get_token
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework import status
+from Auth.pagination import CustomProfilesPagination
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
@@ -20,13 +21,14 @@ import json
from . import validator
from .models import Profile, ProfileRole
from rest_framework.decorators import action, api_view
+from django.db.models import Q
from Auth.serializers import ProfileSerializer, ProfileRoleSerializer
from Subscriptions.models import RegistrationForm, Guardian
import Subscriptions.mailManager as mailer
import Subscriptions.util as util
import logging
-from N3wtSchool import bdd, error
+from N3wtSchool import bdd, error, settings
from rest_framework_simplejwt.authentication import JWTAuthentication
@@ -509,20 +511,55 @@ class ResetPasswordView(APIView):
return JsonResponse({'message': retour, "errorMessage": retourErreur, "errorFields": errorFields}, safe=False)
class ProfileRoleView(APIView):
+ pagination_class = CustomProfilesPagination
@swagger_auto_schema(
operation_description="Obtenir la liste des profile_roles",
responses={200: ProfileRoleSerializer(many=True)}
)
def get(self, request):
+ filter = request.GET.get('filter', '').strip()
+ page_size = request.GET.get('page_size', None)
establishment_id = request.GET.get('establishment_id', None)
+
+ # Gestion du page_size
+ if page_size is not None:
+ try:
+ page_size = int(page_size)
+ except ValueError:
+ page_size = settings.NB_RESULT_PROFILES_PER_PAGE
+
if establishment_id is None:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
- profiles_roles_List = bdd.getAllObjects(_objectName=ProfileRole)
- if profiles_roles_List:
- profiles_roles_List = profiles_roles_List.filter(establishment=establishment_id).distinct().order_by('-updated_date')
- profile_roles_serializer = ProfileRoleSerializer(profiles_roles_List, many=True)
- return JsonResponse(profile_roles_serializer.data, safe=False)
+ # Récupérer les ProfileRole en fonction du filtre
+ profiles_roles_List = ProfileRole.objects.filter(establishment_id=establishment_id)
+
+ if filter == 'parents':
+ profiles_roles_List = profiles_roles_List.filter(role_type=ProfileRole.RoleType.PROFIL_PARENT)
+ elif filter == 'school':
+ profiles_roles_List = profiles_roles_List.filter(
+ Q(role_type=ProfileRole.RoleType.PROFIL_ECOLE) |
+ Q(role_type=ProfileRole.RoleType.PROFIL_ADMIN)
+ )
+ else:
+ return JsonResponse({'error': 'Filtre invalide'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
+
+ # Trier les résultats par date de mise à jour
+ profiles_roles_List = profiles_roles_List.distinct().order_by('-updated_date')
+
+ if not profiles_roles_List:
+ return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
+
+ # Pagination
+ paginator = self.pagination_class()
+ page = paginator.paginate_queryset(profiles_roles_List, request)
+
+ if page is not None:
+ profile_roles_serializer = ProfileRoleSerializer(page, many=True)
+ response_data = paginator.get_paginated_response(profile_roles_serializer.data)
+ return JsonResponse(response_data, safe=False)
+
+ return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
@swagger_auto_schema(
operation_description="Créer un nouveau profile_role",
diff --git a/Back-End/N3wtSchool/settings.py b/Back-End/N3wtSchool/settings.py
index ab74235..14ce667 100644
--- a/Back-End/N3wtSchool/settings.py
+++ b/Back-End/N3wtSchool/settings.py
@@ -282,12 +282,13 @@ DATE_FORMAT = '%d-%m-%Y %H:%M'
EXPIRATION_SESSION_NB_SEC = 10
-NB_RESULT_PER_PAGE = 8
+NB_RESULT_SUBSCRIPTIONS_PER_PAGE = 8
+NB_RESULT_PROFILES_PER_PAGE = 15
NB_MAX_PAGE = 100
REST_FRAMEWORK = {
- 'DEFAULT_PAGINATION_CLASS': 'Subscriptions.pagination.CustomPagination',
- 'PAGE_SIZE': NB_RESULT_PER_PAGE,
+ 'DEFAULT_PAGINATION_CLASS': 'Subscriptions.pagination.CustomSubscriptionPagination',
+ 'PAGE_SIZE': NB_RESULT_SUBSCRIPTIONS_PER_PAGE,
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
diff --git a/Back-End/School/models.py b/Back-End/School/models.py
index 0ecfeb7..9120112 100644
--- a/Back-End/School/models.py
+++ b/Back-End/School/models.py
@@ -32,7 +32,7 @@ class Teacher(models.Model):
last_name = models.CharField(max_length=100)
first_name = models.CharField(max_length=100)
specialities = models.ManyToManyField(Speciality, blank=True)
- profile_role = models.OneToOneField(ProfileRole, on_delete=models.CASCADE, related_name='teacher_profile', blank=True)
+ profile_role = models.OneToOneField(ProfileRole, on_delete=models.CASCADE, related_name='teacher_profile', null=True, blank=True)
updated_date = models.DateTimeField(auto_now=True)
def __str__(self):
diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py
index daf2b24..c80285c 100644
--- a/Back-End/Subscriptions/models.py
+++ b/Back-End/Subscriptions/models.py
@@ -31,7 +31,7 @@ class Guardian(models.Model):
address = models.CharField(max_length=200, default="", blank=True)
phone = models.CharField(max_length=200, default="", blank=True)
profession = models.CharField(max_length=200, default="", blank=True)
- profile_role = models.OneToOneField(ProfileRole, on_delete=models.CASCADE, related_name='guardian_profile', blank=True)
+ profile_role = models.OneToOneField(ProfileRole, on_delete=models.CASCADE, related_name='guardian_profile', null=True, blank=True)
@property
def email(self):
diff --git a/Back-End/Subscriptions/pagination.py b/Back-End/Subscriptions/pagination.py
index f2a97dc..bb52e88 100644
--- a/Back-End/Subscriptions/pagination.py
+++ b/Back-End/Subscriptions/pagination.py
@@ -2,10 +2,10 @@ from rest_framework.pagination import PageNumberPagination
from N3wtSchool import settings
-class CustomPagination(PageNumberPagination):
+class CustomSubscriptionPagination(PageNumberPagination):
page_size_query_param = 'page_size'
max_page_size = settings.NB_MAX_PAGE
- page_size = settings.NB_RESULT_PER_PAGE
+ page_size = settings.NB_RESULT_SUBSCRIPTIONS_PER_PAGE
def get_paginated_response(self, data):
return ({
diff --git a/Back-End/Subscriptions/serializers.py b/Back-End/Subscriptions/serializers.py
index ff37c71..a0f6134 100644
--- a/Back-End/Subscriptions/serializers.py
+++ b/Back-End/Subscriptions/serializers.py
@@ -137,9 +137,19 @@ class StudentSerializer(serializers.ModelSerializer):
def create_or_update_guardians(self, guardians_data):
guardians_ids = []
for guardian_data in guardians_data:
+ guardian_id = guardian_data.get('id', None)
profile_role_data = guardian_data.pop('profile_role_data', None)
profile_role = guardian_data.pop('profile_role', None)
+ if guardian_id:
+ # Si un ID est fourni, récupérer ou mettre à jour le Guardian existant
+ guardian_instance, created = Guardian.objects.update_or_create(
+ id=guardian_id,
+ defaults=guardian_data
+ )
+ guardians_ids.append(guardian_instance.id)
+ continue
+
if profile_role_data:
# Vérifiez si 'profile_data' est fourni pour créer un nouveau profil
profile_data = profile_role_data.pop('profile_data', None)
@@ -410,4 +420,4 @@ class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
fields = '__all__'
-
+
diff --git a/Back-End/Subscriptions/views/register_form_views.py b/Back-End/Subscriptions/views/register_form_views.py
index 18db66a..d6a4ddf 100644
--- a/Back-End/Subscriptions/views/register_form_views.py
+++ b/Back-End/Subscriptions/views/register_form_views.py
@@ -15,7 +15,7 @@ import Subscriptions.mailManager as mailer
import Subscriptions.util as util
from Subscriptions.serializers import RegistrationFormSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileTemplateSerializer
-from Subscriptions.pagination import CustomPagination
+from Subscriptions.pagination import CustomSubscriptionPagination
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationSchoolFileTemplate, RegistrationFileGroup, RegistrationParentFileTemplate
from Subscriptions.automate import updateStateMachine
@@ -29,7 +29,7 @@ class RegisterFormView(APIView):
"""
Gère la liste des dossiers d’inscription, lecture et création.
"""
- pagination_class = CustomPagination
+ pagination_class = CustomSubscriptionPagination
@swagger_auto_schema(
manual_parameters=[
@@ -82,7 +82,7 @@ class RegisterFormView(APIView):
try:
page_size = int(page_size)
except ValueError:
- page_size = settings.NB_RESULT_PER_PAGE
+ page_size = settings.NB_RESULT_SUBSCRIPTIONS_PER_PAGE
# Récupérer les années scolaires
current_year = util.getCurrentSchoolYear()
@@ -179,7 +179,7 @@ class RegisterFormWithIdView(APIView):
"""
Gère la lecture, création, modification et suppression d’un dossier d’inscription.
"""
- pagination_class = CustomPagination
+ pagination_class = CustomSubscriptionPagination
@swagger_auto_schema(
responses={200: RegistrationFormSerializer()},
diff --git a/Front-End/src/app/[locale]/admin/directory/page.js b/Front-End/src/app/[locale]/admin/directory/page.js
index acd8732..ae6947d 100644
--- a/Front-End/src/app/[locale]/admin/directory/page.js
+++ b/Front-End/src/app/[locale]/admin/directory/page.js
@@ -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 (
-
-
-
-
+
);
}
diff --git a/Front-End/src/app/[locale]/admin/page.js b/Front-End/src/app/[locale]/admin/page.js
index 172f12a..781b29e 100644
--- a/Front-End/src/app/[locale]/admin/page.js
+++ b/Front-End/src/app/[locale]/admin/page.js
@@ -13,7 +13,7 @@ import { useEstablishment } from '@/context/EstablishmentContext';
// Composant EventCard pour afficher les événements
const EventCard = ({ title, date, description, type }) => (
-
+
@@ -125,7 +125,7 @@ export default function DashboardPage() {
{/* Événements et KPIs */}
{/* Graphique des inscriptions */}
-
+
{t('inscriptionTrends')}
@@ -136,24 +136,13 @@ export default function DashboardPage() {
{/* Événements à venir */}
-
+
{t('upcomingEvents')}
{upcomingEvents.map((event, index) => (
))}
-
-
- {classes.map((classe) => (
-
-
-
- ))}
-
);
}
diff --git a/Front-End/src/app/[locale]/admin/subscriptions/createSubscription/page.js b/Front-End/src/app/[locale]/admin/subscriptions/createSubscription/page.js
index e19909a..4c1a159 100644
--- a/Front-End/src/app/[locale]/admin/subscriptions/createSubscription/page.js
+++ b/Front-End/src/app/[locale]/admin/subscriptions/createSubscription/page.js
@@ -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
diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js
index 1f6e324..129bc25 100644
--- a/Front-End/src/app/[locale]/admin/subscriptions/page.js
+++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js
@@ -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 } }) {
>
}
- 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 } }) {
>
}
- 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 } }) {
>
}
- active={activeTab === HISTORICAL}
- onClick={() => setActiveTab(HISTORICAL)}
+ active={activeTab === HISTORICAL_FILTER}
+ onClick={() => setActiveTab(HISTORICAL_FILTER)}
/>
- {activeTab === CURRENT_YEAR ||
- activeTab === NEXT_YEAR ||
- activeTab === HISTORICAL ? (
+ {activeTab === CURRENT_YEAR_FILTER ||
+ activeTab === NEXT_YEAR_FILTER ||
+ activeTab === HISTORICAL_FILTER ? (
@@ -779,25 +779,25 @@ export default function Page({ params: { locale } }) {
{
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) => {
diff --git a/Front-End/src/app/actions/subscriptionAction.js b/Front-End/src/app/actions/subscriptionAction.js
index 2c8abcd..1d53db9 100644
--- a/Front-End/src/app/actions/subscriptionAction.js
+++ b/Front-End/src/app/actions/subscriptionAction.js
@@ -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 = ''
diff --git a/Front-End/src/components/PaymentModeSelector.js b/Front-End/src/components/PaymentModeSelector.js
index d381b64..b04e4a2 100644
--- a/Front-End/src/components/PaymentModeSelector.js
+++ b/Front-End/src/components/PaymentModeSelector.js
@@ -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}
diff --git a/Front-End/src/components/PaymentPlanSelector.js b/Front-End/src/components/PaymentPlanSelector.js
index f94fc50..e3b0c40 100644
--- a/Front-End/src/components/PaymentPlanSelector.js
+++ b/Front-End/src/components/PaymentPlanSelector.js
@@ -124,25 +124,40 @@ const PaymentPlanSelector = ({
const handleRowClick = (row) => {
const value = row.id;
+
if (selectedFrequency === value) {
setSelectedFrequency(null); // Désélectionner l'onglet si la ligne est déjà sélectionnée
} else {
setSelectedFrequency(value);
+
if (!dates[value]) {
const frequencyValue =
paymentPlansOptions.find((plan) => plan.id === value)?.frequency || 1;
+
const newDates = Array(frequencyValue)
.fill('')
.map((_, index) => {
const newDate = new Date();
- newDate.setDate(defaultDay);
+
+ // Validate defaultDay
+ const day =
+ typeof defaultDay === 'number' &&
+ defaultDay >= 1 &&
+ defaultDay <= 31
+ ? defaultDay
+ : 1; // Fallback to 1 if defaultDay is invalid
+
+ newDate.setDate(day);
+
if (value === 1) {
newDate.setMonth(newDate.getMonth() + index * 4); // Espacer de 4 mois pour le paiement en 3 fois
} else {
newDate.setMonth(newDate.getMonth() + index);
}
+
return newDate.toISOString().split('T')[0];
});
+
setDates((prevDates) => ({ ...prevDates, [value]: newDates }));
}
}
diff --git a/Front-End/src/components/ProfileDirectory.js b/Front-End/src/components/ProfileDirectory.js
index 98af5ab..751dd66 100644
--- a/Front-End/src/components/ProfileDirectory.js
+++ b/Front-End/src/components/ProfileDirectory.js
@@ -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 (
-
-
-
- {parentProfiles.length === 0 ? (
-
Aucun profil trouvé
- ) : (
-
- )}
-
-
- {schoolAdminProfiles.length === 0 ? (
-
Aucun profil trouvé
- ) : (
-
- )}
-
-
+ <>
+
+
+
+
+ ),
+ },
+ {
+ id: 'school',
+ label: 'École',
+ content: (
+
+ ),
+ },
+ ]}
+ onTabChange={(newActiveTab) => {
+ setActiveTab(newActiveTab);
+ }}
+ />
+ {/* Popups */}
setConfirmPopupVisible(false)}
/>
-
+ >
);
};
diff --git a/Front-End/src/components/Sidebar.js b/Front-End/src/components/Sidebar.js
index f8da173..77860b5 100644
--- a/Front-End/src/components/Sidebar.js
+++ b/Front-End/src/components/Sidebar.js
@@ -6,10 +6,8 @@ import ProfileSelector from '@/components/ProfileSelector';
const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
@@ -35,7 +33,7 @@ function Sidebar({ currentPage, items, onCloseMobile }) {
};
return (
-
+
diff --git a/Front-End/src/components/SidebarTabs.js b/Front-End/src/components/SidebarTabs.js
index 5f965b4..828dc5b 100644
--- a/Front-End/src/components/SidebarTabs.js
+++ b/Front-End/src/components/SidebarTabs.js
@@ -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 (
- <>
-
+
+ {/* Tabs Header */}
+
{tabs.map((tab) => (
))}
- {tabs.map((tab) => (
-
- {tab.content}
-
- ))}
- >
+
+ {/* Tabs Content */}
+
+ {tabs.map((tab) => (
+
+ {tab.content}
+
+ ))}
+
+
);
};
diff --git a/Front-End/src/components/StatCard.js b/Front-End/src/components/StatCard.js
index 20bda27..ad5a426 100644
--- a/Front-End/src/components/StatCard.js
+++ b/Front-End/src/components/StatCard.js
@@ -1,6 +1,6 @@
// Composant StatCard pour afficher une statistique
const StatCard = ({ title, value, icon, color = 'blue' }) => (
-
+
{title}
diff --git a/Front-End/src/components/Structure/Configuration/StructureManagement.js b/Front-End/src/components/Structure/Configuration/StructureManagement.js
index faf39d6..a044eab 100644
--- a/Front-End/src/components/Structure/Configuration/StructureManagement.js
+++ b/Front-End/src/components/Structure/Configuration/StructureManagement.js
@@ -22,7 +22,7 @@ const StructureManagement = ({
handleDelete,
}) => {
return (
-
+
+
{/* Modal pour les fichiers */}
+
diff --git a/Front-End/src/components/Table.js b/Front-End/src/components/Table.js
index 7456790..f8cd3ea 100644
--- a/Front-End/src/components/Table.js
+++ b/Front-End/src/components/Table.js
@@ -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 (
-
-
+
+
{columns.map((column, index) => (
|
{column.name}
|
@@ -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) => (
{renderCell
? renderCell(row, column.name)
diff --git a/Front-End/src/utils/constants.js b/Front-End/src/utils/constants.js
index f0ff33d..e08d288 100644
--- a/Front-End/src/utils/constants.js
+++ b/Front-End/src/utils/constants.js
@@ -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';
|