From c8c8941ec875b541cfb55c3504a0e951f36163ef Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Fri, 28 Feb 2025 18:30:18 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Signatures=20=C3=A9lectroniques=20docus?= =?UTF-8?q?eal=20[#22]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 1 + Back-End/DocuSeal/__init__.py | 1 + Back-End/DocuSeal/urls.py | 7 + Back-End/DocuSeal/views.py | 81 +++++++ Back-End/N3wtSchool/middleware.py | 8 + Back-End/N3wtSchool/settings.py | 8 +- .../management/commands/init_mock_datas.py | 150 +++++++------ Back-End/Subscriptions/models.py | 90 +++----- Back-End/Subscriptions/serializers.py | 40 +++- Back-End/Subscriptions/urls.py | 17 +- Back-End/Subscriptions/views/__init__.py | 10 +- .../views/register_form_views.py | 4 +- .../views/registration_file_group_views.py | 4 +- .../views/registration_file_views.py | 190 ++++++----------- Front-End/.env | 3 +- Front-End/.vscode/settings.json | 6 - .../src/app/[locale]/admin/structure/page.js | 8 +- .../app/[locale]/admin/subscriptions/page.js | 6 +- Front-End/src/app/[locale]/parents/layout.js | 52 +++-- Front-End/src/app/[locale]/parents/page.js | 17 +- .../src/app/[locale]/users/login/page.js | 5 +- .../src/app/actions/subscriptionAction.js | 46 ++-- Front-End/src/components/DocusealBuilder.js | 17 ++ .../src/components/DraggableFileUpload.js | 50 ----- .../{FileUpload.js => FileUpload copy.js} | 0 .../src/components/Inscription/FilesToSign.js | 18 ++ .../components/Inscription/FilesToUpload.js | 18 ++ .../Inscription/InscriptionFormShared.js | 8 +- .../components/Inscription/StudentInfoForm.js | 126 +++++++++++ Front-End/src/components/Modal.js | 6 +- Front-End/src/components/MultiSelect.js | 7 +- .../Structure/Configuration/ClassesSection.js | 1 + .../components/Structure/Files/FileUpload.js | 200 ++++++++++++++++++ ...Management.js => FilesGroupsManagement.js} | 174 ++++++++------- .../Files}/RegistrationFileGroupForm.js | 0 .../Files}/RegistrationFileGroupList.js | 0 Front-End/src/hooks/useLocalStorage.js | 48 ----- .../src/pages/api/docuseal/cloneTemplate.js | 36 ++++ .../src/pages/api/docuseal/generateToken.js | 31 +++ Front-End/src/utils/Url.js | 7 +- docker-compose.yml | 32 +-- 41 files changed, 984 insertions(+), 549 deletions(-) create mode 100644 .env create mode 100644 Back-End/DocuSeal/__init__.py create mode 100644 Back-End/DocuSeal/urls.py create mode 100644 Back-End/DocuSeal/views.py create mode 100644 Back-End/N3wtSchool/middleware.py delete mode 100644 Front-End/.vscode/settings.json create mode 100644 Front-End/src/components/DocusealBuilder.js delete mode 100644 Front-End/src/components/DraggableFileUpload.js rename Front-End/src/components/{FileUpload.js => FileUpload copy.js} (100%) create mode 100644 Front-End/src/components/Inscription/FilesToSign.js create mode 100644 Front-End/src/components/Inscription/FilesToUpload.js create mode 100644 Front-End/src/components/Inscription/StudentInfoForm.js create mode 100644 Front-End/src/components/Structure/Files/FileUpload.js rename Front-End/src/components/Structure/Files/{FilesManagement.js => FilesGroupsManagement.js} (69%) rename Front-End/src/components/{ => Structure/Files}/RegistrationFileGroupForm.js (100%) rename Front-End/src/components/{ => Structure/Files}/RegistrationFileGroupList.js (100%) delete mode 100644 Front-End/src/hooks/useLocalStorage.js create mode 100644 Front-End/src/pages/api/docuseal/cloneTemplate.js create mode 100644 Front-End/src/pages/api/docuseal/generateToken.js diff --git a/.env b/.env new file mode 100644 index 0000000..1951496 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DOCUSEAL_API_KEY="LRvUTQCbMSSpManYKshdQk9Do6rBQgjHyPrbGfxU3Jg" \ No newline at end of file diff --git a/Back-End/DocuSeal/__init__.py b/Back-End/DocuSeal/__init__.py new file mode 100644 index 0000000..d64ffa4 --- /dev/null +++ b/Back-End/DocuSeal/__init__.py @@ -0,0 +1 @@ +# This file is intentionally left blank to make this directory a Python package. diff --git a/Back-End/DocuSeal/urls.py b/Back-End/DocuSeal/urls.py new file mode 100644 index 0000000..671544d --- /dev/null +++ b/Back-End/DocuSeal/urls.py @@ -0,0 +1,7 @@ +from django.urls import path, re_path +from .views import generate_jwt_token, clone_template + +urlpatterns = [ + re_path(r'generateToken$', generate_jwt_token, name='generate_jwt_token'), + re_path(r'cloneTemplate$', clone_template, name='clone_template'), +] diff --git a/Back-End/DocuSeal/views.py b/Back-End/DocuSeal/views.py new file mode 100644 index 0000000..18297c0 --- /dev/null +++ b/Back-End/DocuSeal/views.py @@ -0,0 +1,81 @@ +from django.conf import settings +from django.http import JsonResponse +from django.views.decorators.csrf import csrf_exempt +from rest_framework.decorators import api_view +from rest_framework.response import Response +from rest_framework import status +import jwt +import datetime +import requests + +@csrf_exempt +@api_view(['POST']) +def generate_jwt_token(request): + # Vérifier la clé API + api_key = request.headers.get('X-Auth-Token') + print(f'api_key : {api_key}') + print(f'settings.DOCUSEAL_JWT["API_KEY"] : {settings.DOCUSEAL_JWT["API_KEY"]}') + if not api_key or api_key != settings.DOCUSEAL_JWT["API_KEY"]: + return Response({'error': 'Invalid API key'}, status=status.HTTP_401_UNAUTHORIZED) + + # Récupérer les données de la requête + user_email = request.data.get('user_email') + documents_urls = request.data.get('documents_urls', []) + template_id = request.data.get('template_id') # Récupérer le template_id + + # Vérifier les données requises + if not user_email: + return Response({'error': 'User email is required'}, status=status.HTTP_400_BAD_REQUEST) + + # Utiliser la configuration JWT de DocuSeal depuis les settings + jwt_secret = settings.DOCUSEAL_JWT['API_KEY'] + jwt_algorithm = settings.DOCUSEAL_JWT['ALGORITHM'] + expiration_delta = settings.DOCUSEAL_JWT['EXPIRATION_DELTA'] + + # Définir le payload + payload = { + 'user_email': user_email, + 'documents_urls': documents_urls, + 'template_id': template_id, # Ajouter le template_id au payload + 'exp': datetime.datetime.utcnow() + expiration_delta # Temps d'expiration du token + } + + # Générer le token JWT + token = jwt.encode(payload, jwt_secret, algorithm=jwt_algorithm) + + return Response({'token': token}, status=status.HTTP_200_OK) + +@csrf_exempt +@api_view(['POST']) +def clone_template(request): + # Vérifier la clé API + api_key = request.headers.get('X-Auth-Token') + if not api_key or api_key != settings.DOCUSEAL_JWT["API_KEY"]: + return Response({'error': 'Invalid API key'}, status=status.HTTP_401_UNAUTHORIZED) + + # Récupérer les données de la requête + document_id = request.data.get('templateId') + email = request.data.get('email') + + # Vérifier les données requises + if not document_id or not email : + return Response({'error': 'template ID, email are required'}, status=status.HTTP_400_BAD_REQUEST) + + # URL de l'API de DocuSeal pour cloner le template + clone_url = f'https://docuseal.com/api/templates/{document_id}/clone' + + # Faire la requête pour cloner le template + try: + response = requests.post(clone_url, json={'submitters': [{'email': email}]}, headers={ + 'Content-Type': 'application/json', + 'X-Auth-Token': settings.DOCUSEAL_JWT['API_KEY'] + }) + + if response.status_code != 200: + return Response({'error': 'Failed to clone template'}, status=response.status_code) + + data = response.json() + return Response(data, status=status.HTTP_200_OK) + + except requests.RequestException as e: + return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) \ No newline at end of file diff --git a/Back-End/N3wtSchool/middleware.py b/Back-End/N3wtSchool/middleware.py new file mode 100644 index 0000000..b923652 --- /dev/null +++ b/Back-End/N3wtSchool/middleware.py @@ -0,0 +1,8 @@ +class ContentSecurityPolicyMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + response['Content-Security-Policy'] = "frame-ancestors 'self' http://localhost:3000" + return response diff --git a/Back-End/N3wtSchool/settings.py b/Back-End/N3wtSchool/settings.py index 4232560..ef5315e 100644 --- a/Back-End/N3wtSchool/settings.py +++ b/Back-End/N3wtSchool/settings.py @@ -251,6 +251,12 @@ DOCUMENT_DIR = 'documents' CORS_ORIGIN_ALLOW_ALL = True CORS_ALLOW_ALL_HEADERS = True CORS_ALLOW_CREDENTIALS = True +CORS_ALLOW_HEADERS = [ + 'content-type', + 'authorization', + 'X-Auth-Token', + 'x-csrftoken' +] CORS_ALLOWED_ORIGINS = [ os.getenv('CORS_ALLOWED_ORIGINS', 'http://localhost:3000') @@ -333,5 +339,5 @@ DOCUSEAL_JWT = { 'ALGORITHM': 'HS256', 'SIGNING_KEY': SECRET_KEY, 'EXPIRATION_DELTA': timedelta(hours=1), - 'API_KEY': '1kzHsXqN8P2ezUGT7TjVuBwM1hqtLsztrVSsQ87T7Mz' + 'API_KEY': os.getenv('DOCUSEAL_API_KEY') } \ No newline at end of file diff --git a/Back-End/School/management/commands/init_mock_datas.py b/Back-End/School/management/commands/init_mock_datas.py index 8fd8ac4..2f74379 100644 --- a/Back-End/School/management/commands/init_mock_datas.py +++ b/Back-End/School/management/commands/init_mock_datas.py @@ -7,7 +7,7 @@ from Subscriptions.models import ( Fee, Discount, RegistrationFileGroup, - RegistrationFileTemplate + # RegistrationFileTemplate ) from Auth.models import Profile from School.models import ( @@ -43,7 +43,7 @@ class Command(BaseCommand): self.create_or_update_teachers() self.create_or_update_school_classes() self.create_or_update_registration_file_group() - self.create_or_update_registration_file_template() + # self.create_or_update_registration_file_template() self.create_register_form() def create_or_update_establishment(self): @@ -376,13 +376,21 @@ class Command(BaseCommand): self.stdout.write(self.style.SUCCESS('SchoolClasses initialized or updated successfully')) def create_or_update_registration_file_group(self): - group_data = { + group_data_1 = { "name": "LMDE", "description": "Fichiers d'inscription de l'école LMDE" } - self.registration_file_group, created = RegistrationFileGroup.objects.get_or_create(name=group_data["name"], defaults=group_data) - self.stdout.write(self.style.SUCCESS('RegistrationFileGroup initialized or updated successfully')) + self.registration_file_group_1, created = RegistrationFileGroup.objects.get_or_create(name=group_data_1["name"], defaults=group_data_1) + self.stdout.write(self.style.SUCCESS('RegistrationFileGroup 1 initialized or updated successfully')) + + group_data_2 = { + "name": "LMDE 2", + "description": "Fichiers d'inscription de l'école LMDE 2" + } + + self.registration_file_group_2, created = RegistrationFileGroup.objects.get_or_create(name=group_data_2["name"], defaults=group_data_2) + self.stdout.write(self.style.SUCCESS('RegistrationFileGroup 2 initialized or updated successfully')) def create_or_update_registration_file_template(self): script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -394,28 +402,28 @@ class Command(BaseCommand): "file": "RIB LA MAISON DES ENFANTS.pdf", "order": 0, "is_required": False, - "group": self.registration_file_group + "group": self.registration_file_group_2 # Associer ce fichier au deuxième groupe }, { "name": "Contrat d'engagement 2024 2025", "file": "Contrat d'engagement 2024 2025.pdf", "order": 0, "is_required": True, - "group": self.registration_file_group + "group": self.registration_file_group_1 }, { "name": "Bulletin d'adhésion familiale scolaire", "file": "Bulletin d'adhésion familiale scolaire.pdf", "order": 0, "is_required": True, - "group": self.registration_file_group + "group": self.registration_file_group_1 }, { "name": "Fiche sanitaire de liaison", "file": "Fiche sanitaire de liaison.pdf", "order": 0, "is_required": True, - "group": self.registration_file_group + "group": self.registration_file_group_1 } ] @@ -442,69 +450,79 @@ class Command(BaseCommand): def create_register_form(self): # Créer ou mettre à jour le profil associé au guardian - profile_data = { - "email": "anthony.casini.30@gmail.com", - "droit": 2, - "username": "anthony.casini.30@gmail.com", - "is_active": True, - "password": "Provisoire01!" - } - - user, created = Profile.objects.update_or_create( - email=profile_data["email"], - defaults={ - "username": profile_data["username"], - "email": profile_data["email"], - "is_active": profile_data["is_active"], - "droit": profile_data["droit"] + profiles_data = [ + { + "email": "anthony.casini.30@gmail.com", + "droit": 2, + "username": "anthony.casini.30@gmail.com", + "is_active": True, + "password": "Provisoire01!" + }, + { + "email": "anthony.audrey.34@gmail.com", + "droit": 2, + "username": "anthony.audrey.34@gmail.com", + "is_active": True, + "password": "Provisoire01!" } - ) - if created: - user.set_password(profile_data["password"]) - user.save() + ] - # Créer les données du guardian - guardian_data = { - "associated_profile_id": user.id, - "email": "anthony.casini.30@gmail.com", - } + for profile_data in profiles_data: + user, created = Profile.objects.update_or_create( + email=profile_data["email"], + defaults={ + "username": profile_data["username"], + "email": profile_data["email"], + "is_active": profile_data["is_active"], + "droit": profile_data["droit"] + } + ) + if created: + user.set_password(profile_data["password"]) + user.save() - # Créer les données de l'étudiant - student_data = { - "last_name": "CASINI", - "first_name": "Giulia", - } + # Créer les données du guardian + guardian_data = { + "associated_profile_id": user.id, + "email": profile_data["email"], + } - # Créer ou mettre à jour l'étudiant et le guardian - student, created = Student.objects.get_or_create( - last_name=student_data["last_name"], - first_name=student_data["first_name"], - defaults=student_data - ) - guardian, created = Guardian.objects.get_or_create( - last_name=guardian_data["email"], - defaults=guardian_data - ) - student.guardians.add(guardian) + # Créer les données de l'étudiant + student_data = { + "last_name": f'lastname_{user.id}', + "first_name": f'firstname_{user.id}', + } - # Récupérer les frais et les réductions - fees = Fee.objects.filter(id__in=[1, 2, 3, 4]) - discounts = Discount.objects.filter(id__in=[1]) + # Créer ou mettre à jour l'étudiant et le guardian + student, created = Student.objects.get_or_create( + last_name=student_data["last_name"], + first_name=student_data["first_name"], + defaults=student_data + ) + guardian, created = Guardian.objects.get_or_create( + last_name=guardian_data["email"], + defaults=guardian_data + ) + student.guardians.add(guardian) - # Créer les données du formulaire d'inscription - register_form_data = { - "student": student, - "fileGroup": self.registration_file_group, - "establishment": Establishment.objects.get(id=1), - "status": 1 - } + # Récupérer les frais et les réductions + fees = Fee.objects.filter(id__in=[1, 2, 3, 4]) + discounts = Discount.objects.filter(id__in=[1]) - # Créer ou mettre à jour le formulaire d'inscription - register_form, created = RegistrationForm.objects.get_or_create(student=student, defaults=register_form_data) - register_form.fees.set(fees) - register_form.discounts.set(discounts) - if not created: - register_form.fileGroup = self.registration_file_group - register_form.save() + # Créer les données du formulaire d'inscription + register_form_data = { + "student": student, + "fileGroup": self.registration_file_group_1, + "establishment": Establishment.objects.get(id=1), + "status": 1 + } + + # Créer ou mettre à jour le formulaire d'inscription + register_form, created = RegistrationForm.objects.get_or_create(student=student, defaults=register_form_data) + register_form.fees.set(fees) + register_form.discounts.set(discounts) + if not created: + register_form.fileGroup = self.registration_file_group_1 + register_form.save() self.stdout.write(self.style.SUCCESS('RegistrationForm initialized or updated successfully')) \ No newline at end of file diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py index 9272b15..a943ead 100644 --- a/Back-End/Subscriptions/models.py +++ b/Back-End/Subscriptions/models.py @@ -89,7 +89,7 @@ class Student(models.Model): siblings = models.ManyToManyField(Sibling, blank=True) # Many-to-Many Relationship - registration_files = models.ManyToManyField('RegistrationFile', blank=True, related_name='students') + registration_files = models.ManyToManyField('RegistrationTemplate', blank=True, related_name='students') # Many-to-Many Relationship spoken_languages = models.ManyToManyField(Language, blank=True) @@ -162,7 +162,7 @@ class Student(models.Model): return None class RegistrationFileGroup(models.Model): - name = models.CharField(max_length=255) + name = models.CharField(max_length=255, default="") description = models.TextField(blank=True, null=True) def __str__(self): @@ -172,10 +172,35 @@ def registration_file_path(instance, filename): # Génère le chemin : registration_files/dossier_rf_{student_id}/filename return f'registration_files/dossier_rf_{instance.student_id}/{filename}' +class RegistrationTemplateMaster(models.Model): + groups = models.ManyToManyField(RegistrationFileGroup, related_name='template_masters') + template_id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=255, default="") + + def __str__(self): + return f'{self.group.name} - {self.template_id}' + +class RegistrationTemplate(models.Model): + master = models.ForeignKey(RegistrationTemplateMaster, on_delete=models.CASCADE, related_name='templates') + template_id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=255, default="") + registration_form = models.ForeignKey('RegistrationForm', on_delete=models.CASCADE, related_name='templates') + + def __str__(self): + return self.name + + @staticmethod + def get_files_from_rf(register_form_id): + """ + Récupère tous les fichiers liés à un dossier d’inscription donné. + """ + registration_files = RegistrationTemplate.objects.filter(register_form_id=register_form_id).order_by('template__order') + filenames = [] + for reg_file in registration_files: + filenames.append(reg_file.file.path) + return filenames + class RegistrationForm(models.Model): - """ - Gère le dossier d’inscription lié à un élève donné. - """ class RegistrationFormStatus(models.IntegerChoices): RF_ABSENT = 0, _('Pas de dossier d\'inscription') RF_CREATED = 1, _('Dossier d\'inscription créé') @@ -205,7 +230,7 @@ class RegistrationForm(models.Model): discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms') fileGroup = models.ForeignKey(RegistrationFileGroup, on_delete=models.CASCADE, - related_name='file_group', + related_name='register_forms', null=True, blank=True) @@ -213,56 +238,3 @@ class RegistrationForm(models.Model): def __str__(self): return "RF_" + self.student.last_name + "_" + self.student.first_name - -class RegistrationFileTemplate(models.Model): - """ - Modèle pour stocker les fichiers "templates" d’inscription. - """ - name = models.CharField(max_length=255) - file = models.FileField(upload_to='templates_files/', blank=True, null=True) - order = models.PositiveIntegerField(default=0) # Ajout du champ order - date_added = models.DateTimeField(auto_now_add=True) - is_required = models.BooleanField(default=False) - group = models.ForeignKey(RegistrationFileGroup, on_delete=models.CASCADE, related_name='file_templates', null=True, blank=True) - - @property - def formatted_date_added(self): - if self.date_added: - return self.date_added.strftime('%d-%m-%Y') - return None - - def __str__(self): - return self.name - -def registration_file_upload_to(instance, filename): - return f"registration_files/dossier_rf_{instance.register_form.pk}/{filename}" - -class RegistrationFile(models.Model): - """ - Fichier lié à un dossier d’inscription particulier. - """ - name = models.CharField(max_length=255) - file = models.FileField(upload_to=registration_file_upload_to) - date_added = models.DateTimeField(auto_now_add=True) - template = models.OneToOneField(RegistrationFileTemplate, on_delete=models.CASCADE) - register_form = models.ForeignKey('RegistrationForm', on_delete=models.CASCADE, related_name='registration_files') - - @property - def formatted_date_added(self): - if self.date_added: - return self.date_added.strftime('%d-%m-%Y') - return None - - def __str__(self): - return self.name - - @staticmethod - def get_files_from_rf(register_form_id): - """ - Récupère tous les fichiers liés à un dossier d’inscription donné. - """ - registration_files = RegistrationFile.objects.filter(register_form_id=register_form_id).order_by('template__order') - filenames = [] - for reg_file in registration_files: - filenames.append(reg_file.file.path) - return filenames diff --git a/Back-End/Subscriptions/serializers.py b/Back-End/Subscriptions/serializers.py index eb147e7..90d8517 100644 --- a/Back-End/Subscriptions/serializers.py +++ b/Back-End/Subscriptions/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import RegistrationFileTemplate, RegistrationFile, RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language +from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationTemplateMaster, RegistrationTemplate from School.models import SchoolClass, Fee, Discount, FeeType from School.serializers import FeeSerializer, DiscountSerializer from Auth.models import Profile @@ -11,20 +11,38 @@ from django.utils import timezone import pytz from datetime import datetime +class RegistrationTemplateMasterSerializer(serializers.ModelSerializer): + class Meta: + model = RegistrationTemplateMaster + fields = '__all__' + +class RegistrationTemplateSerializer(serializers.ModelSerializer): + class Meta: + model = RegistrationTemplate + fields = '__all__' + +class GuardianSimpleSerializer(serializers.ModelSerializer): + class Meta: + model = Guardian + fields = ['id', 'email'] + +class RegistrationFormSimpleSerializer(serializers.ModelSerializer): + guardians = GuardianSimpleSerializer(many=True, source='student.guardians') + + class Meta: + model = RegistrationForm + fields = ['student_id', 'guardians'] + class RegistrationFileGroupSerializer(serializers.ModelSerializer): + registration_forms = serializers.SerializerMethodField() + class Meta: model = RegistrationFileGroup fields = '__all__' -class RegistrationFileSerializer(serializers.ModelSerializer): - class Meta: - model = RegistrationFile - fields = '__all__' - -class RegistrationFileTemplateSerializer(serializers.ModelSerializer): - class Meta: - model = RegistrationFileTemplate - fields = '__all__' + def get_registration_forms(self, obj): + forms = RegistrationForm.objects.filter(fileGroup=obj) + return RegistrationFormSimpleSerializer(forms, many=True).data class LanguageSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) @@ -132,7 +150,7 @@ class RegistrationFormSerializer(serializers.ModelSerializer): registration_file = serializers.FileField(required=False) status_label = serializers.SerializerMethodField() formatted_last_update = serializers.SerializerMethodField() - registration_files = RegistrationFileSerializer(many=True, required=False) + registration_files = RegistrationTemplateSerializer(many=True, required=False) fees = serializers.PrimaryKeyRelatedField(queryset=Fee.objects.all(), many=True, required=False) discounts = serializers.PrimaryKeyRelatedField(queryset=Discount.objects.all(), many=True, required=False) totalRegistrationFees = serializers.SerializerMethodField() diff --git a/Back-End/Subscriptions/urls.py b/Back-End/Subscriptions/urls.py index 5b6252c..beba111 100644 --- a/Back-End/Subscriptions/urls.py +++ b/Back-End/Subscriptions/urls.py @@ -7,8 +7,9 @@ from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archi # SubClasses from .views import StudentView, GuardianView, ChildrenListView, StudentListView # Files -from .views import RegistrationFileTemplateView, RegistrationFileTemplateSimpleView, RegistrationFileView, RegistrationFileSimpleView +from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group +from .views import registration_file_views urlpatterns = [ re_path(r'^registerForms/(?P[0-9]+)/archive$', archive, name="archive"), @@ -27,15 +28,13 @@ urlpatterns = [ # Page de formulaire d'inscription - RESPONSABLE re_path(r'^lastGuardianId$', GuardianView.as_view(), name="lastGuardianId"), - # modèles de fichiers d'inscription - re_path(r'^registrationFileTemplates/(?P[0-9]+)$', RegistrationFileTemplateSimpleView.as_view(), name="registrationFileTemplate"), - re_path(r'^registrationFileTemplates$', RegistrationFileTemplateView.as_view(), name='registrationFileTemplates'), - - # fichiers d'inscription - re_path(r'^registrationFiles/(?P[0-9]+)$', RegistrationFileSimpleView.as_view(), name='registrationFiles'), - re_path(r'^registrationFiles$', RegistrationFileView.as_view(), name="registrationFiles"), - re_path(r'^registrationFileGroups/(?P[0-9]+)$', RegistrationFileGroupSimpleView.as_view(), name='registrationFileGroupDetail'), re_path(r'^registrationFileGroups/(?P[0-9]+)/registrationFiles$', get_registration_files_by_group, name="get_registration_files_by_group"), re_path(r'^registrationFileGroups$', RegistrationFileGroupView.as_view(), name='registrationFileGroups'), + + re_path(r'^registrationTemplateMasters/(?P[0-9]+)$', RegistrationTemplateMasterSimpleView.as_view(), name='registrationTemplateMasters'), + re_path(r'^registrationTemplateMasters$', RegistrationTemplateMasterView.as_view(), name='registrationTemplateMasters'), + + re_path(r'^registrationTemplates/(?P[0-9]+)$', RegistrationTemplateSimpleView.as_view(), name='registrationTemplates'), + re_path(r'^registrationTemplates$', RegistrationTemplateView.as_view(), name="registrationTemplates"), ] \ No newline at end of file diff --git a/Back-End/Subscriptions/views/__init__.py b/Back-End/Subscriptions/views/__init__.py index 755cfd8..bd5e65f 100644 --- a/Back-End/Subscriptions/views/__init__.py +++ b/Back-End/Subscriptions/views/__init__.py @@ -1,5 +1,5 @@ from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive -from .registration_file_views import RegistrationFileTemplateView, RegistrationFileTemplateSimpleView, RegistrationFileView, RegistrationFileSimpleView +from .registration_file_views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group from .student_views import StudentView, StudentListView, ChildrenListView from .guardian_views import GuardianView @@ -10,10 +10,10 @@ __all__ = [ 'send', 'resend', 'archive', - 'RegistrationFileView', - 'RegistrationFileSimpleView', - 'RegistrationFileTemplateView', - 'RegistrationFileTemplateSimpleView', + 'RegistrationTemplateView', + 'RegistrationTemplateSimpleView', + 'RegistrationTemplateMasterView', + 'RegistrationTemplateMasterSimpleView', 'RegistrationFileGroupView', 'RegistrationFileGroupSimpleView', 'get_registration_files_by_group', diff --git a/Back-End/Subscriptions/views/register_form_views.py b/Back-End/Subscriptions/views/register_form_views.py index 1464ff4..efe08bf 100644 --- a/Back-End/Subscriptions/views/register_form_views.py +++ b/Back-End/Subscriptions/views/register_form_views.py @@ -19,7 +19,7 @@ import Subscriptions.util as util from Subscriptions.serializers import RegistrationFormSerializer from Subscriptions.pagination import CustomPagination from Subscriptions.signals import clear_cache -from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationFile, RegistrationFileGroup +from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationTemplate, RegistrationFileGroup from Subscriptions.automate import updateStateMachine from N3wtSchool import settings, bdd @@ -252,7 +252,7 @@ class RegisterFormWithIdView(APIView): registerForm.save() # Récupération des fichiers d'inscription - fileNames = RegistrationFile.get_files_from_rf(registerForm.pk) + fileNames = RegistrationTemplate.get_files_from_rf(registerForm.pk) if registerForm.registration_file: fileNames.insert(0, registerForm.registration_file.path) diff --git a/Back-End/Subscriptions/views/registration_file_group_views.py b/Back-End/Subscriptions/views/registration_file_group_views.py index b71dfc5..21da8c4 100644 --- a/Back-End/Subscriptions/views/registration_file_group_views.py +++ b/Back-End/Subscriptions/views/registration_file_group_views.py @@ -8,7 +8,7 @@ from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from Subscriptions.serializers import RegistrationFileGroupSerializer -from Subscriptions.models import RegistrationFileGroup, RegistrationFileTemplate +from Subscriptions.models import RegistrationFileGroup, RegistrationTemplateMaster from N3wtSchool import bdd class RegistrationFileGroupView(APIView): @@ -118,7 +118,7 @@ class RegistrationFileGroupSimpleView(APIView): def get_registration_files_by_group(request, id): try: group = RegistrationFileGroup.objects.get(id=id) - templates = RegistrationFileTemplate.objects.filter(group=group) + templates = RegistrationTemplateMaster.objects.filter(group=group) templates_data = list(templates.values()) return JsonResponse(templates_data, safe=False) except RegistrationFileGroup.DoesNotExist: diff --git a/Back-End/Subscriptions/views/registration_file_views.py b/Back-End/Subscriptions/views/registration_file_views.py index e115b03..7df225a 100644 --- a/Back-End/Subscriptions/views/registration_file_views.py +++ b/Back-End/Subscriptions/views/registration_file_views.py @@ -1,5 +1,4 @@ from django.http.response import JsonResponse -from django.core.files import File from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from rest_framework.parsers import MultiPartParser, FormParser @@ -7,205 +6,154 @@ from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import status -import os - -from Subscriptions.serializers import RegistrationFileTemplateSerializer, RegistrationFileSerializer -from Subscriptions.models import RegistrationFileTemplate, RegistrationFile +from Subscriptions.serializers import RegistrationTemplateMasterSerializer, RegistrationTemplateSerializer +from Subscriptions.models import RegistrationTemplateMaster, RegistrationTemplate from N3wtSchool import bdd - -class RegistrationFileTemplateView(APIView): +class RegistrationTemplateMasterView(APIView): @swagger_auto_schema( - operation_description="Récupère tous les fichiers templates pour les dossiers d'inscription", - responses={200: RegistrationFileTemplateSerializer(many=True)} + operation_description="Récupère tous les masters de templates d'inscription", + responses={200: RegistrationTemplateMasterSerializer(many=True)} ) def get(self, request): - """ - Récupère les fichiers templates pour les dossiers d’inscription. - """ - files = RegistrationFileTemplate.objects.all() - serializer = RegistrationFileTemplateSerializer(files, many=True) + masters = RegistrationTemplateMaster.objects.all() + serializer = RegistrationTemplateMasterSerializer(masters, many=True) return Response(serializer.data) @swagger_auto_schema( - operation_description="Crée un nouveau fichier template pour les dossiers d'inscription", - request_body=RegistrationFileTemplateSerializer, + operation_description="Crée un nouveau master de template d'inscription", + request_body=RegistrationTemplateMasterSerializer, responses={ - 201: RegistrationFileTemplateSerializer, + 201: RegistrationTemplateMasterSerializer, 400: "Données invalides" } ) def post(self, request): - """ - Crée un fichier template pour les dossiers d’inscription. - """ - serializer = RegistrationFileTemplateSerializer(data=request.data) + serializer = RegistrationTemplateMasterSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - -class RegistrationFileTemplateSimpleView(APIView): - """ - Gère les fichiers templates pour les dossiers d’inscription. - """ - parser_classes = (MultiPartParser, FormParser) - +class RegistrationTemplateMasterSimpleView(APIView): @swagger_auto_schema( - operation_description="Récupère un fichier template spécifique", + operation_description="Récupère un master de template d'inscription spécifique", responses={ - 200: RegistrationFileTemplateSerializer, - 404: "Fichier template non trouvé" + 200: RegistrationTemplateMasterSerializer, + 404: "Master non trouvé" } ) def get(self, request, id): - """ - Récupère les fichiers templates pour les dossiers d’inscription. - """ - registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id) - if registationFileTemplate is None: - return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) - serializer = RegistrationFileTemplateSerializer(registationFileTemplate) + master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id) + if master is None: + return JsonResponse({"errorMessage":'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + serializer = RegistrationTemplateMasterSerializer(master) return JsonResponse(serializer.data, safe=False) @swagger_auto_schema( - operation_description="Met à jour un fichier template existant", - request_body=RegistrationFileTemplateSerializer, + operation_description="Met à jour un master de template d'inscription existant", + request_body=RegistrationTemplateMasterSerializer, responses={ - 201: RegistrationFileTemplateSerializer, + 200: RegistrationTemplateMasterSerializer, 400: "Données invalides", - 404: "Fichier template non trouvé" + 404: "Master non trouvé" } ) def put(self, request, id): - """ - Met à jour un fichier template existant. - """ - registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id) - if registationFileTemplate is None: - return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) - serializer = RegistrationFileTemplateSerializer(registationFileTemplate,data=request.data) + master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id) + if master is None: + return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + serializer = RegistrationTemplateMasterSerializer(master, data=request.data) if serializer.is_valid(): serializer.save() - return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( - operation_description="Supprime un fichier template", + operation_description="Supprime un master de template d'inscription", responses={ 204: "Suppression réussie", - 404: "Fichier template non trouvé" + 404: "Master non trouvé" } ) def delete(self, request, id): - """ - Supprime un fichier template existant. - """ - registrationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id) - if registrationFileTemplate is not None: - registrationFileTemplate.file.delete() # Supprimer le fichier uploadé - registrationFileTemplate.delete() - return JsonResponse({'message': 'La suppression du fichier d\'inscription a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT) + master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id) + if master is not None: + master.delete() + return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT) else: - return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) -class RegistrationFileView(APIView): +class RegistrationTemplateView(APIView): @swagger_auto_schema( - operation_description="Récupère tous les fichiers d'inscription", - responses={200: RegistrationFileSerializer(many=True)} + operation_description="Récupère tous les templates d'inscription", + responses={200: RegistrationTemplateSerializer(many=True)} ) def get(self, request): - """ - Récupère les fichiers liés à un dossier d’inscription donné. - """ - files = RegistrationFile.objects.all() - serializer = RegistrationFileSerializer(files, many=True) + templates = RegistrationTemplate.objects.all() + serializer = RegistrationTemplateSerializer(templates, many=True) return Response(serializer.data) @swagger_auto_schema( - operation_description="Crée un nouveau fichier d'inscription", - request_body=RegistrationFileSerializer, + operation_description="Crée un nouveau template d'inscription", + request_body=RegistrationTemplateSerializer, responses={ - 201: RegistrationFileSerializer, + 201: RegistrationTemplateSerializer, 400: "Données invalides" } ) def post(self, request): - """ - Crée un RegistrationFile pour le RegistrationForm associé. - """ - serializer = RegistrationFileSerializer(data=request.data) + serializer = RegistrationTemplateSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - -class RegistrationFileSimpleView(APIView): - """ - Gère la création, mise à jour et suppression de fichiers liés à un dossier d’inscription. - """ - parser_classes = (MultiPartParser, FormParser) - +class RegistrationTemplateSimpleView(APIView): @swagger_auto_schema( - operation_description="Récupère un fichier d'inscription spécifique", + operation_description="Récupère un template d'inscription spécifique", responses={ - 200: RegistrationFileSerializer, - 404: "Fichier non trouvé" + 200: RegistrationTemplateSerializer, + 404: "Template non trouvé" } ) def get(self, request, id): - """ - Récupère les fichiers liés à un dossier d’inscription donné. - """ - registationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id) - if registationFile is None: - return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) - serializer = RegistrationFileSerializer(registationFile) + template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id) + if template is None: + return JsonResponse({"errorMessage":'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + serializer = RegistrationTemplateSerializer(template) return JsonResponse(serializer.data, safe=False) @swagger_auto_schema( - operation_description="Met à jour un fichier d'inscription existant", - request_body=RegistrationFileSerializer, + operation_description="Met à jour un template d'inscription existant", + request_body=RegistrationTemplateSerializer, responses={ - 200: openapi.Response( - description="Fichier mis à jour avec succès", - schema=RegistrationFileSerializer - ), + 200: RegistrationTemplateSerializer, 400: "Données invalides", - 404: "Fichier non trouvé" + 404: "Template non trouvé" } ) def put(self, request, id): - """ - Met à jour un RegistrationFile existant. - """ - registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id) - if registrationFile is None: - return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) - serializer = RegistrationFileSerializer(registrationFile, data=request.data) + template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id) + if template is None: + return JsonResponse({'erreur': 'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + serializer = RegistrationTemplateSerializer(template, data=request.data) if serializer.is_valid(): serializer.save() - return Response({'message': 'Fichier mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK) + return Response({'message': 'Template mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( - operation_description="Supprime un fichier d'inscription", + operation_description="Supprime un template d'inscription", responses={ - 200: "Suppression réussie", - 404: "Fichier non trouvé" + 204: "Suppression réussie", + 404: "Template non trouvé" } ) def delete(self, request, id): - """ - Supprime un RegistrationFile existant. - """ - registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id) - if registrationFile is not None: - registrationFile.file.delete() # Supprimer le fichier uploadé - registrationFile.delete() - return JsonResponse({'message': 'La suppression du fichier a été effectuée avec succès'}, safe=False) + template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id) + if template is not None: + template.delete() + return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT) else: - return JsonResponse({'erreur': 'Le fichier n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + return JsonResponse({'erreur': 'Le template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) diff --git a/Front-End/.env b/Front-End/.env index 992c7ed..2c1e0f1 100644 --- a/Front-End/.env +++ b/Front-End/.env @@ -1,4 +1,5 @@ NEXT_PUBLIC_API_URL=http://localhost:8080 NEXT_PUBLIC_USE_FAKE_DATA='false' AUTH_SECRET='false' -NEXTAUTH_URL=http://localhost:3000 \ No newline at end of file +NEXTAUTH_URL=http://localhost:3000 +DOCUSEAL_API_KEY="LRvUTQCbMSSpManYKshdQk9Do6rBQgjHyPrbGfxU3Jg" \ No newline at end of file diff --git a/Front-End/.vscode/settings.json b/Front-End/.vscode/settings.json deleted file mode 100644 index c99754e..0000000 --- a/Front-End/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "i18n-ally.localesPaths": [ - "messages" - ], - "i18n-ally.keystyle": "nested" -} \ No newline at end of file diff --git a/Front-End/src/app/[locale]/admin/structure/page.js b/Front-End/src/app/[locale]/admin/structure/page.js index b14362d..6646c49 100644 --- a/Front-End/src/app/[locale]/admin/structure/page.js +++ b/Front-End/src/app/[locale]/admin/structure/page.js @@ -22,8 +22,8 @@ import { createDatas, fetchRegistrationPaymentModes, fetchTuitionPaymentModes } from '@/app/actions/schoolAction'; import SidebarTabs from '@/components/SidebarTabs'; -import FilesManagement from '@/components/Structure/Files/FilesManagement'; -import { fetchRegisterFormFileTemplate } from '@/app/actions/subscriptionAction'; +import FilesGroupsManagement from '@/components/Structure/Files/FilesGroupsManagement'; +import { fetchRegistrationTemplateMaster } from '@/app/actions/subscriptionAction'; import logger from '@/utils/logger'; @@ -70,7 +70,7 @@ export default function Page() { handleTuitionFees(); // Fetch data for registration file templates - fetchRegisterFormFileTemplate() + fetchRegistrationTemplateMaster() .then((data)=> { setFichiers(data) }) @@ -301,7 +301,7 @@ export default function Page() { { id: 'Files', label: 'Documents d\'inscription', - content: + content: } ]; diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index 5960325..ef82b52 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -24,7 +24,7 @@ import { createRegisterForm, sendRegisterForm, archiveRegisterForm, - fetchRegisterFormFileTemplate, + fetchRegistrationTemplateMaster, fetchStudents, editRegisterForm } from "@/app/actions/subscriptionAction" @@ -195,7 +195,7 @@ useEffect(() => { fetchRegisterForms(ARCHIVED) .then(registerFormArchivedDataHandler) .catch(requestErrorHandler) - fetchRegisterFormFileTemplate() + fetchRegistrationTemplateMaster() .then((data)=> { logger.debug(data); @@ -254,7 +254,7 @@ useEffect(() => { fetchRegisterForms(ARCHIVED) .then(registerFormArchivedDataHandler) .catch(requestErrorHandler) - fetchRegisterFormFileTemplate() + fetchRegistrationTemplateMaster() .then((data)=> {setFichiers(data)}) .catch((err)=>{ err = err.message; logger.debug(err);}); } else { diff --git a/Front-End/src/app/[locale]/parents/layout.js b/Front-End/src/app/[locale]/parents/layout.js index 4622932..c05f118 100644 --- a/Front-End/src/app/[locale]/parents/layout.js +++ b/Front-End/src/app/[locale]/parents/layout.js @@ -6,12 +6,13 @@ import { useRouter } from 'next/navigation'; // Ajout de l'importation import { User, MessageSquare, LogOut, Settings, Home } from 'lucide-react'; // Ajout de l'importation de l'icône Home import Logo from '@/components/Logo'; // Ajout de l'importation du composant Logo import { FE_PARENTS_HOME_URL,FE_PARENTS_MESSAGERIE_URL,FE_PARENTS_SETTINGS_URL } from '@/utils/Url'; // Ajout de l'importation de l'URL de la page d'accueil parent -import useLocalStorage from '@/hooks/useLocalStorage'; import { fetchMessages } from '@/app/actions/messagerieAction'; import ProtectedRoute from '@/components/ProtectedRoute'; import { disconnect } from '@/app/actions/authAction'; import Popup from '@/components/Popup'; import logger from '@/utils/logger'; +import { useSession } from 'next-auth/react'; +import { FE_USERS_LOGIN_URL } from '@/utils/Url'; export default function Layout({ children, @@ -19,7 +20,8 @@ export default function Layout({ const router = useRouter(); // Définition de router const [messages, setMessages] = useState([]); - const [userId, setUserId] = useLocalStorage("userId", '') ; + const { data: session, status } = useSession(); + const [userId, setUserId] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isPopupVisible, setIsPopupVisible] = useState(false); @@ -32,27 +34,33 @@ export default function Layout({ disconnect(); }; - useEffect(() => { - setIsLoading(true); - setUserId(userId) - fetchMessages(userId) - .then(data => { - if (data) { - setMessages(data); - } - logger.debug('Success :', data); - }) - .catch(error => { - logger.error('Error fetching data:', error); - }) - .finally(() => { - setIsLoading(false); - }); - }, [userId]); + // useEffect(() => { + // if (status === 'loading') return; + // if (!session) { + // router.push(`${FE_USERS_LOGIN_URL}`); + // } - if (isLoading) { - return
Loading...
; - } + // const userIdFromSession = session.user.id; + // setUserId(userIdFromSession); + // setIsLoading(true); + // fetchMessages(userId) + // .then(data => { + // if (data) { + // setMessages(data); + // } + // logger.debug('Success :', data); + // }) + // .catch(error => { + // logger.error('Error fetching data:', error); + // }) + // .finally(() => { + // setIsLoading(false); + // }); + // }, [userId]); + + // if (isLoading) { + // return
Loading...
; + // } return ( diff --git a/Front-End/src/app/[locale]/parents/page.js b/Front-End/src/app/[locale]/parents/page.js index e64fc2b..c104b4c 100644 --- a/Front-End/src/app/[locale]/parents/page.js +++ b/Front-End/src/app/[locale]/parents/page.js @@ -4,22 +4,31 @@ import { useRouter } from 'next/navigation'; import Table from '@/components/Table'; import { Edit } from 'lucide-react'; import StatusLabel from '@/components/StatusLabel'; -import useLocalStorage from '@/hooks/useLocalStorage'; import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url'; 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'; export default function ParentHomePage() { const [actions, setActions] = useState([]); const [children, setChildren] = useState([]); - const [userId, setUserId] = useLocalStorage("userId", '') ; + const { data: session, status } = useSession(); + const [userId, setUserId] = useState(null); const router = useRouter(); useEffect(() => { - if (!userId) return; + if (status === 'loading') return; - fetchChildren(userId).then(data => { + if (!session) { + router.push(`${FE_USERS_LOGIN_URL}`); + } + console.log(session); + const userIdFromSession = session.user.user_id; + setUserId(userIdFromSession); + + fetchChildren(userIdFromSession).then(data => { setChildren(data); }); }, [userId]); diff --git a/Front-End/src/app/[locale]/users/login/page.js b/Front-End/src/app/[locale]/users/login/page.js index b56e1e1..d100398 100644 --- a/Front-End/src/app/[locale]/users/login/page.js +++ b/Front-End/src/app/[locale]/users/login/page.js @@ -13,10 +13,10 @@ import { FE_PARENTS_HOME_URL } from '@/utils/Url'; import { login } from '@/app/actions/authAction'; -import useLocalStorage from '@/hooks/useLocalStorage'; 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'; const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true'; @@ -28,8 +28,6 @@ export default function Page() { const [isLoading, setIsLoading] = useState(false); - const [userId, setUserId] = useLocalStorage("userId", '') ; - const router = useRouter(); const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken @@ -55,7 +53,6 @@ export default function Page() { } const user = session.user; logger.debug('User Session:', user); - localStorage.setItem('userId', user.id); // Stocker l'identifiant de l'utilisateur if (user.droit === 0) { // Vue ECOLE } else if (user.droit === 1) { diff --git a/Front-End/src/app/actions/subscriptionAction.js b/Front-End/src/app/actions/subscriptionAction.js index 76cbb0c..f65004e 100644 --- a/Front-End/src/app/actions/subscriptionAction.js +++ b/Front-End/src/app/actions/subscriptionAction.js @@ -2,9 +2,9 @@ import { BE_SUBSCRIPTION_STUDENTS_URL, BE_SUBSCRIPTION_CHILDRENS_URL, BE_SUBSCRIPTION_REGISTERFORMS_URL, - BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL, + BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL, BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL, - BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL + BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL } from '@/utils/Url'; export const PENDING = 'pending'; @@ -101,10 +101,10 @@ export const archiveRegisterForm = (id) => { }).then(requestResponseHandler) } -export const fetchRegisterFormFile = (id = null) => { - let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}` +export const fetchRegistrationTemplates = (id = null) => { + let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}` if (id) { - url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${id}`; + url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${id}`; } const request = new Request( `${url}`, @@ -118,8 +118,8 @@ export const fetchRegisterFormFile = (id = null) => { return fetch(request).then(requestResponseHandler) }; -export const editRegistrationFormFile= (fileId, data, csrfToken) => { - return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${fileId}`, { +export const editRegistrationTemplates= (fileId, data, csrfToken) => { + return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, { method: 'PUT', body: data, headers: { @@ -130,21 +130,22 @@ export const editRegistrationFormFile= (fileId, data, csrfToken) => { .then(requestResponseHandler) } -export const createRegistrationFormFile = (data,csrfToken) => { +export const createRegistrationTemplates = (data,csrfToken) => { - return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}`, { + return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`, { method: 'POST', - body: data, + body: JSON.stringify(data), headers: { 'X-CSRFToken': csrfToken, + 'Content-Type': 'application/json', }, credentials: 'include', }) .then(requestResponseHandler) } -export const deleteRegisterFormFile= (fileId,csrfToken) => { - return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${fileId}`, { +export const deleteRegistrationTemplates= (fileId,csrfToken) => { + return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, { method: 'DELETE', headers: { 'X-CSRFToken': csrfToken, @@ -153,10 +154,10 @@ export const deleteRegisterFormFile= (fileId,csrfToken) => { }) } -export const fetchRegisterFormFileTemplate = (id = null) => { - let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}`; +export const fetchRegistrationTemplateMaster = (id = null) => { + let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`; if(id){ - url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${id}`; + url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${id}`; } const request = new Request( `${url}`, @@ -170,21 +171,22 @@ export const fetchRegisterFormFileTemplate = (id = null) => { return fetch(request).then(requestResponseHandler) }; -export const createRegistrationFormFileTemplate = (data,csrfToken) => { +export const createRegistrationTemplateMaster = (data,csrfToken) => { - return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}`, { + return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`, { method: 'POST', - body: data, + body: JSON.stringify(data), headers: { 'X-CSRFToken': csrfToken, + 'Content-Type':'application/json' }, credentials: 'include', }) .then(requestResponseHandler) } -export const deleteRegisterFormFileTemplate = (fileId,csrfToken) => { - return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${fileId}`, { +export const deleteRegistrationTemplateMaster = (fileId,csrfToken) => { + return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, { method: 'DELETE', headers: { 'X-CSRFToken': csrfToken, @@ -193,8 +195,8 @@ export const deleteRegisterFormFileTemplate = (fileId,csrfToken) => { }) } -export const editRegistrationFormFileTemplate = (fileId, data, csrfToken) => { - return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${fileId}`, { +export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => { + return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, { method: 'PUT', body: data, headers: { diff --git a/Front-End/src/components/DocusealBuilder.js b/Front-End/src/components/DocusealBuilder.js new file mode 100644 index 0000000..44c4868 --- /dev/null +++ b/Front-End/src/components/DocusealBuilder.js @@ -0,0 +1,17 @@ +import React, { useEffect } from 'react'; +import { DocusealBuilder as OriginalDocusealBuilder } from '@docuseal/react'; + +const DocusealBuilder = ({ onSave, onSend, ...props }) => { + useEffect(() => { + if (onSave) { + props.save = onSave; + } + if (onSend) { + props.send = onSend; + } + }, [onSave, onSend, props]); + + return ; +}; + +export default DocusealBuilder; \ No newline at end of file diff --git a/Front-End/src/components/DraggableFileUpload.js b/Front-End/src/components/DraggableFileUpload.js deleted file mode 100644 index 38a4bbb..0000000 --- a/Front-End/src/components/DraggableFileUpload.js +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useState } from 'react'; -import { Upload } from 'lucide-react'; - -export default function DraggableFileUpload({ fileName, onFileSelect }) { - const [dragActive, setDragActive] = useState(false); - - - const handleDragOver = (event) => { - event.preventDefault(); - setDragActive(true); - }; - - const handleDragLeave = () => { - setDragActive(false); - }; - - const handleFileChosen = (selectedFile) => { - onFileSelect && onFileSelect(selectedFile); - }; - - const handleDrop = (event) => { - event.preventDefault(); - setDragActive(false); - const droppedFile = event.dataTransfer.files[0]; - handleFileChosen(droppedFile); - }; - - const handleFileChange = (event) => { - const selectedFile = event.target.files[0]; - handleFileChosen(selectedFile); - }; - - return ( -
-
- - -
-
- ); -} \ No newline at end of file diff --git a/Front-End/src/components/FileUpload.js b/Front-End/src/components/FileUpload copy.js similarity index 100% rename from Front-End/src/components/FileUpload.js rename to Front-End/src/components/FileUpload copy.js diff --git a/Front-End/src/components/Inscription/FilesToSign.js b/Front-End/src/components/Inscription/FilesToSign.js new file mode 100644 index 0000000..26a44a8 --- /dev/null +++ b/Front-End/src/components/Inscription/FilesToSign.js @@ -0,0 +1,18 @@ +import React from 'react'; +import Table from '@/components/Table'; + +export default function FilesToSign({ fileTemplates, columns }) { + return ( +
+

Fichiers à remplir

+ {}} + /> + + ); +} \ No newline at end of file diff --git a/Front-End/src/components/Inscription/FilesToUpload.js b/Front-End/src/components/Inscription/FilesToUpload.js new file mode 100644 index 0000000..78a713a --- /dev/null +++ b/Front-End/src/components/Inscription/FilesToUpload.js @@ -0,0 +1,18 @@ +import React from 'react'; +import Table from '@/components/Table'; + +export default function FilesToUpload({ fileTemplates, columns }) { + return ( +
+

Fichiers à uploader

+
{}} + /> + + ); +} \ No newline at end of file diff --git a/Front-End/src/components/Inscription/InscriptionFormShared.js b/Front-End/src/components/Inscription/InscriptionFormShared.js index 29a875b..f32d094 100644 --- a/Front-End/src/components/Inscription/InscriptionFormShared.js +++ b/Front-End/src/components/Inscription/InscriptionFormShared.js @@ -7,7 +7,7 @@ import Loader from '@/components/Loader'; import Button from '@/components/Button'; import DjangoCSRFToken from '@/components/DjangoCSRFToken'; import Table from '@/components/Table'; -import { fetchRegisterFormFileTemplate, createRegistrationFormFile, fetchRegisterForm, deleteRegisterFormFile } from '@/app/actions/subscriptionAction'; +import { fetchRegistrationTemplateMaster, createRegistrationTemplates, fetchRegisterForm, deleteRegistrationTemplates } from '@/app/actions/subscriptionAction'; import { fetchRegistrationFileFromGroup } from '@/app/actions/registerFileGroupAction'; import { Download, Upload, Trash2, Eye } from 'lucide-react'; import { BASE_URL } from '@/utils/Url'; @@ -117,7 +117,7 @@ export default function InscriptionFormShared({ data.append('register_form', formData.id); try { - const response = await createRegistrationFormFile(data, csrfToken); + const response = await createRegistrationTemplates(data, csrfToken); if (response) { setUploadedFiles(prev => { const newFiles = prev.filter(f => parseInt(f.template) !== currentTemplateId); @@ -158,7 +158,7 @@ export default function InscriptionFormShared({ if (!fileToDelete) return; try { - await deleteRegisterFormFile(fileToDelete.id, csrfToken); + await deleteRegistrationTemplates(fileToDelete.id, csrfToken); setUploadedFiles(prev => prev.filter(f => parseInt(f.template) !== templateId)); } catch (error) { logger.error('Error deleting file:', error); @@ -276,7 +276,7 @@ export default function InscriptionFormShared({

{requiredFileTemplates[currentPage - 2].name}