diff --git a/Back-End/N3wtSchool/renderers.py b/Back-End/N3wtSchool/renderers.py index 96b2b67..9411cf3 100644 --- a/Back-End/N3wtSchool/renderers.py +++ b/Back-End/N3wtSchool/renderers.py @@ -4,11 +4,22 @@ from django.template.loader import get_template from xhtml2pdf import pisa +class PDFResult: + def __init__(self, content): + self.content = content + def render_to_pdf(template_src, context_dict={}): + """ + Génère un PDF à partir d'un template HTML et retourne le contenu en mémoire. + """ template = get_template(template_src) - html = template.render(context_dict) + html = template.render(context_dict) result = BytesIO() pdf = pisa.pisaDocument(BytesIO(html.encode("UTF-8")), result) + if pdf.err: - return HttpResponse("Invalid PDF", status_code=400, content_type='text/plain') - return HttpResponse(result.getvalue(), content_type='application/pdf') \ No newline at end of file + # Lever une exception ou retourner None en cas d'erreur + raise ValueError("Erreur lors de la génération du PDF.") + + # Retourner le contenu du PDF en mémoire + return PDFResult(result.getvalue()) \ 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 0ca75d1..fe69619 100644 --- a/Back-End/School/management/commands/init_mock_datas.py +++ b/Back-End/School/management/commands/init_mock_datas.py @@ -6,8 +6,8 @@ from Subscriptions.models import ( Fee, Discount, RegistrationFileGroup, - RegistrationTemplateMaster, - RegistrationTemplate + RegistrationSchoolFileMaster, + RegistrationSchoolFileTemplate ) from Auth.models import Profile, ProfileRole from School.models import ( diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py index 86f5b85..a0dd4af 100644 --- a/Back-End/Subscriptions/models.py +++ b/Back-End/Subscriptions/models.py @@ -9,6 +9,8 @@ from Establishment.models import Establishment from datetime import datetime +import os + class Language(models.Model): """ Représente une langue parlée par l’élève. @@ -83,7 +85,7 @@ class Student(models.Model): siblings = models.ManyToManyField(Sibling, blank=True) # Many-to-Many Relationship - registration_files = models.ManyToManyField('RegistrationTemplate', blank=True, related_name='students') + registration_files = models.ManyToManyField('RegistrationSchoolFileTemplate', blank=True, related_name='students') # Many-to-Many Relationship spoken_languages = models.ManyToManyField(Language, blank=True) @@ -163,19 +165,13 @@ class RegistrationFileGroup(models.Model): def __str__(self): return self.name + def __str__(self): + return f'{self.group.name} - {self.id}' + 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', blank=True) - id = models.IntegerField(primary_key=True) - name = models.CharField(max_length=255, default="") - is_required = models.BooleanField(default=False) - - def __str__(self): - return f'{self.group.name} - {self.id}' - class RegistrationForm(models.Model): class RegistrationFormStatus(models.IntegerChoices): RF_IDLE = 0, _('Pas de dossier d\'inscription') @@ -211,7 +207,7 @@ class RegistrationForm(models.Model): # Many-to-Many Relationship discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms') fileGroup = models.ForeignKey(RegistrationFileGroup, - on_delete=models.CASCADE, + on_delete=models.SET_NULL, related_name='register_forms', null=True, blank=True) @@ -223,16 +219,58 @@ class RegistrationForm(models.Model): def __str__(self): return "RF_" + self.student.last_name + "_" + self.student.first_name -def registration_file_upload_to(instance, filename): - return f"registration_files/dossier_rf_{instance.registration_form.pk}/{filename}" + def save(self, *args, **kwargs): + # Vérifier si un fichier existant doit être remplacé + if self.pk: # Si l'objet existe déjà dans la base de données + try: + old_instance = RegistrationForm.objects.get(pk=self.pk) + if old_instance.sepa_file and old_instance.sepa_file != self.sepa_file: + # Supprimer l'ancien fichier + old_instance.sepa_file.delete(save=False) + except RegistrationForm.DoesNotExist: + pass # L'objet n'existe pas encore, rien à supprimer -class RegistrationTemplate(models.Model): - master = models.ForeignKey(RegistrationTemplateMaster, on_delete=models.CASCADE, related_name='templates', blank=True) + # Appeler la méthode save originale + super().save(*args, **kwargs) + +def registration_school_file_upload_to(instance, filename): + return f"registration_files/dossier_rf_{instance.registration_form.pk}/school/{filename}" + +def registration_parent_file_upload_to(instance, filename): + return f"registration_files/dossier_rf_{instance.registration_form.pk}/parent/{filename}" + +############################################################# +####################### MASTER FILES ######################## +############################################################# + +####### DocuSeal masters (documents école, à signer ou pas) ####### +class RegistrationSchoolFileMaster(models.Model): + groups = models.ManyToManyField(RegistrationFileGroup, related_name='school_file_masters', blank=True) + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=255, default="") + is_required = models.BooleanField(default=False) + + def __str__(self): + return f'{self.group.name} - {self.id}' + +####### Parent files masters (documents à fournir par les parents) ####### +class RegistrationParentFileMaster(models.Model): + groups = models.ManyToManyField(RegistrationFileGroup, related_name='parent_file_masters', blank=True) + name = models.CharField(max_length=255, default="") + description = models.CharField(blank=True, null=True) + +############################################################ +####################### CLONE FILES ######################## +############################################################ + +####### DocuSeal templates (par dossier d'inscription) ####### +class RegistrationSchoolFileTemplate(models.Model): + master = models.ForeignKey(RegistrationSchoolFileMaster, on_delete=models.CASCADE, related_name='school_file_templates', blank=True) id = models.IntegerField(primary_key=True) slug = models.CharField(max_length=255, default="") name = models.CharField(max_length=255, default="") - registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='templates', blank=True) - file = models.FileField(null=True,blank=True, upload_to=registration_file_upload_to) + registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='school_file_templates', blank=True) + file = models.FileField(null=True,blank=True, upload_to=registration_school_file_upload_to) def __str__(self): return self.name @@ -242,8 +280,42 @@ class RegistrationTemplate(models.Model): """ Récupère tous les fichiers liés à un dossier d’inscription donné. """ - registration_files = RegistrationTemplate.objects.filter(registration_form=register_form_id) + registration_files = RegistrationSchoolFileTemplate.objects.filter(registration_form=register_form_id) filenames = [] for reg_file in registration_files: filenames.append(reg_file.file.path) - return filenames \ No newline at end of file + return filenames + +####### Parent files templates (par dossier d'inscription) ####### +class RegistrationParentFileTemplate(models.Model): + master = models.ForeignKey(RegistrationParentFileMaster, on_delete=models.CASCADE, related_name='parent_file_templates', blank=True) + registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='parent_file_templates', blank=True) + file = models.FileField(null=True,blank=True, upload_to=registration_parent_file_upload_to) + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + if self.pk: # Si l'objet existe déjà dans la base de données + try: + old_instance = RegistrationParentFileTemplate.objects.get(pk=self.pk) + if old_instance.file and (not self.file or self.file.name == ''): + if os.path.exists(old_instance.file.path): + old_instance.file.delete(save=False) + self.file = None + else: + print(f"Le fichier {old_instance.file.path} n'existe pas.") + except RegistrationParentFileTemplate.DoesNotExist: + print("Ancienne instance introuvable.") + super().save(*args, **kwargs) + + @staticmethod + def get_files_from_rf(register_form_id): + """ + Récupère tous les fichiers liés à un dossier d’inscription donné. + """ + registration_files = RegistrationParentFileTemplate.objects.filter(registration_form=register_form_id) + 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 36519df..4348a49 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 RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationTemplateMaster, RegistrationTemplate +from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster, RegistrationParentFileTemplate from School.models import SchoolClass, Fee, Discount, FeeType from School.serializers import FeeSerializer, DiscountSerializer from Auth.models import ProfileRole, Profile @@ -12,18 +12,43 @@ import pytz from datetime import datetime import Subscriptions.util as util -class RegistrationTemplateMasterSerializer(serializers.ModelSerializer): +class RegistrationSchoolFileMasterSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) class Meta: - model = RegistrationTemplateMaster + model = RegistrationSchoolFileMaster fields = '__all__' -class RegistrationTemplateSerializer(serializers.ModelSerializer): +class RegistrationParentFileMasterSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) class Meta: - model = RegistrationTemplate + model = RegistrationParentFileMaster fields = '__all__' +class RegistrationSchoolFileTemplateSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + file_url = serializers.SerializerMethodField() + + class Meta: + model = RegistrationSchoolFileTemplate + fields = '__all__' + + def get_file_url(self, obj): + # Retourne l'URL complète du fichier si disponible + return obj.file.url if obj.file else None + +class RegistrationParentFileTemplateSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + file_url = serializers.SerializerMethodField() + master_name = serializers.CharField(source='master.name', read_only=True) + master_description = serializers.CharField(source='master.description', read_only=True) + class Meta: + model = RegistrationParentFileTemplate + fields = '__all__' + + def get_file_url(self, obj): + # Retourne l'URL complète du fichier si disponible + return obj.file.url if obj.file else None + class GuardianSimpleSerializer(serializers.ModelSerializer): associated_profile_email = serializers.SerializerMethodField() @@ -199,7 +224,7 @@ class RegistrationFormSerializer(serializers.ModelSerializer): sepa_file = serializers.FileField(required=False) status_label = serializers.SerializerMethodField() formatted_last_update = serializers.SerializerMethodField() - registration_files = RegistrationTemplateSerializer(many=True, required=False) + registration_files = RegistrationSchoolFileTemplateSerializer(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() @@ -280,7 +305,7 @@ class RegistrationFormByParentSerializer(serializers.ModelSerializer): class Meta: model = RegistrationForm - fields = ['student', 'status'] + fields = ['student', 'status', 'sepa_file'] def __init__(self, *args, **kwargs): super(RegistrationFormByParentSerializer, self).__init__(*args, **kwargs) diff --git a/Back-End/Subscriptions/urls.py b/Back-End/Subscriptions/urls.py index b652644..00cdc91 100644 --- a/Back-End/Subscriptions/urls.py +++ b/Back-End/Subscriptions/urls.py @@ -7,16 +7,27 @@ from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archi # SubClasses from .views import StudentView, GuardianView, ChildrenListView, StudentListView, DissociateGuardianView # Files -from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView +from .views import ( + RegistrationSchoolFileMasterView, + RegistrationSchoolFileMasterSimpleView, + RegistrationSchoolFileTemplateView, + RegistrationSchoolFileTemplateSimpleView, + RegistrationParentFileMasterSimpleView, + RegistrationParentFileMasterView, + RegistrationParentFileTemplateSimpleView, + RegistrationParentFileTemplateView +) + from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group -from .views import registration_file_views, get_templates_by_rf +from .views import registration_file_views, get_school_file_templates_by_rf, get_parent_file_templates_by_rf urlpatterns = [ re_path(r'^registerForms/(?P[0-9]+)/archive$', archive, name="archive"), re_path(r'^registerForms/(?P[0-9]+)/resend$', resend, name="resend"), re_path(r'^registerForms/(?P[0-9]+)/send$', send, name="send"), re_path(r'^registerForms/(?P[0-9]+)$', RegisterFormWithIdView.as_view(), name="registerForm"), - re_path(r'^registerForms/(?P[0-9]+)/templates$', get_templates_by_rf, name="get_templates_by_rf"), + re_path(r'^registerForms/(?P[0-9]+)/school_file_templates$', get_school_file_templates_by_rf, name="get_school_file_templates_by_rf"), + re_path(r'^registerForms/(?P[0-9]+)/parent_file_templates$', get_parent_file_templates_by_rf, name="get_parent_file_templates_by_rf"), re_path(r'^registerForms$', RegisterFormView.as_view(), name="registerForms"), # Page INSCRIPTION - Liste des élèves @@ -33,11 +44,17 @@ urlpatterns = [ re_path(r'^registrationFileGroups/(?P[0-9]+)/templates$', 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'^registrationSchoolFileMasters/(?P[0-9]+)$', RegistrationSchoolFileMasterSimpleView.as_view(), name='registrationSchoolFileMasters'), + re_path(r'^registrationSchoolFileMasters$', RegistrationSchoolFileMasterView.as_view(), name='registrationSchoolFileMasters'), - re_path(r'^registrationTemplates/(?P[0-9]+)$', RegistrationTemplateSimpleView.as_view(), name='registrationTemplates'), - re_path(r'^registrationTemplates$', RegistrationTemplateView.as_view(), name="registrationTemplates"), + re_path(r'^registrationParentFileMasters/(?P[0-9]+)$', RegistrationParentFileMasterSimpleView.as_view(), name='registrationParentFileMasters'), + re_path(r'^registrationParentFileMasters$', RegistrationParentFileMasterView.as_view(), name="registrationParentFileMasters"), + + re_path(r'^registrationSchoolFileTemplates/(?P[0-9]+)$', RegistrationSchoolFileTemplateSimpleView.as_view(), name='registrationSchoolFileTemplates'), + re_path(r'^registrationSchoolFileTemplates$', RegistrationSchoolFileTemplateView.as_view(), name="registrationSchoolFileTemplates"), + + re_path(r'^registrationParentFileTemplates/(?P[0-9]+)$', RegistrationParentFileTemplateSimpleView.as_view(), name='registrationParentFileTemplates'), + re_path(r'^registrationParentFileTemplates$', RegistrationParentFileTemplateView.as_view(), name="registrationSchoolFileTregistrationParentFileTemplatesemplates"), re_path(r'^students/(?P[0-9]+)/guardians/(?P[0-9]+)/dissociate', DissociateGuardianView.as_view(), name='dissociate-guardian'), diff --git a/Back-End/Subscriptions/util.py b/Back-End/Subscriptions/util.py index b440d5d..3f2406b 100644 --- a/Back-End/Subscriptions/util.py +++ b/Back-End/Subscriptions/util.py @@ -19,6 +19,9 @@ from rest_framework.parsers import JSONParser from PyPDF2 import PdfMerger import shutil +import logging + +logger = logging.getLogger(__name__) def recupereListeFichesInscription(): """ @@ -121,7 +124,6 @@ def rfToPDF(registerForm, filename): Génère le PDF d'un dossier d'inscription et l'associe au RegistrationForm. """ filename = filename.replace(" ", "_") - data = { 'pdf_title': f"Dossier d'inscription de {registerForm.student.first_name}", 'signatureDate': convertToStr(_now(), '%d-%m-%Y'), @@ -131,20 +133,37 @@ def rfToPDF(registerForm, filename): # Générer le PDF pdf = renderers.render_to_pdf('pdfs/dossier_inscription.html', data) + if not pdf: + raise ValueError("Erreur lors de la génération du PDF.") # Vérifier si un fichier avec le même nom existe déjà et le supprimer - if registerForm.registration_file and os.path.exists(registerForm.registration_file.path): - os.remove(registerForm.registration_file.path) - registerForm.registration_file.delete(save=False) + if registerForm.registration_file and registerForm.registration_file.name: + # Vérifiez si le chemin est déjà absolu ou relatif + if os.path.isabs(registerForm.registration_file.name): + existing_file_path = registerForm.registration_file.name + else: + existing_file_path = os.path.join(settings.MEDIA_ROOT, registerForm.registration_file.name.lstrip('/')) + + # Vérifier si le fichier existe et le supprimer + if os.path.exists(existing_file_path): + print(f'exist ! REMOVE') + os.remove(existing_file_path) + registerForm.registration_file.delete(save=False) + else: + print(f'File does not exist: {existing_file_path}') # Enregistrer directement le fichier dans le champ registration_file - registerForm.registration_file.save( - os.path.basename(filename), - File(BytesIO(pdf.content)), # Utilisation de BytesIO pour éviter l'écriture sur le disque - save=True - ) + try: + registerForm.registration_file.save( + os.path.basename(filename), # Utiliser uniquement le nom de fichier + File(BytesIO(pdf.content)), + save=True + ) + except Exception as e: + logger.error(f"Erreur lors de la sauvegarde du fichier PDF : {e}") + raise - return registerForm.registration_file.path + return registerForm.registration_file def delete_registration_files(registerForm): """ diff --git a/Back-End/Subscriptions/views/__init__.py b/Back-End/Subscriptions/views/__init__.py index 6ae84c4..fb24394 100644 --- a/Back-End/Subscriptions/views/__init__.py +++ b/Back-End/Subscriptions/views/__init__.py @@ -1,5 +1,14 @@ -from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive, get_templates_by_rf -from .registration_file_views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView +from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive, get_school_file_templates_by_rf, get_parent_file_templates_by_rf +from .registration_file_views import ( + RegistrationSchoolFileMasterView, + RegistrationSchoolFileMasterSimpleView, + RegistrationSchoolFileTemplateView, + RegistrationSchoolFileTemplateSimpleView, + RegistrationParentFileMasterView, + RegistrationParentFileMasterSimpleView, + RegistrationParentFileTemplateSimpleView, + RegistrationParentFileTemplateView +) 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, DissociateGuardianView @@ -10,14 +19,19 @@ __all__ = [ 'send', 'resend', 'archive', - 'RegistrationTemplateView', - 'RegistrationTemplateSimpleView', - 'RegistrationTemplateMasterView', - 'RegistrationTemplateMasterSimpleView', + 'RegistrationSchoolFileTemplateView', + 'RegistrationSchoolFileTemplateSimpleView', + 'RegistrationParentFileMasterSimpleView', + 'RegistrationParentFileMasterView', + 'RegistrationSchoolFileMasterView', + 'RegistrationSchoolFileMasterSimpleView', + 'RegistrationParentFileTemplateSimpleView', + 'RegistrationParentFileTemplateView', 'RegistrationFileGroupView', 'RegistrationFileGroupSimpleView', 'get_registration_files_by_group', - 'get_templates_by_rf', + 'get_school_file_templates_by_rf', + 'get_parent_file_templates_by_rf' 'StudentView', 'StudentListView', 'ChildrenListView', diff --git a/Back-End/Subscriptions/views/register_form_views.py b/Back-End/Subscriptions/views/register_form_views.py index b803cde..2646af3 100644 --- a/Back-End/Subscriptions/views/register_form_views.py +++ b/Back-End/Subscriptions/views/register_form_views.py @@ -14,9 +14,9 @@ from django.core.files import File import Subscriptions.mailManager as mailer import Subscriptions.util as util -from Subscriptions.serializers import RegistrationFormSerializer +from Subscriptions.serializers import RegistrationFormSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileTemplateSerializer from Subscriptions.pagination import CustomPagination -from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationTemplate, RegistrationFileGroup +from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationSchoolFileTemplate, RegistrationFileGroup, RegistrationParentFileTemplate from Subscriptions.automate import updateStateMachine from N3wtSchool import settings, bdd @@ -254,30 +254,37 @@ class RegisterFormWithIdView(APIView): if _status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW: try: # Génération de la fiche d'inscription au format PDF - base_dir = f"data/registration_files/dossier_rf_{registerForm.pk}" + base_dir = os.path.join(settings.MEDIA_ROOT, f"registration_files/dossier_rf_{registerForm.pk}") os.makedirs(base_dir, exist_ok=True) # Fichier PDF initial - initial_pdf = f"{base_dir}/rf_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf" + initial_pdf = f"{base_dir}/Inscription_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf" registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf) registerForm.save() # Récupération des fichiers d'inscription - fileNames = RegistrationTemplate.get_files_from_rf(registerForm.pk) - if registerForm.registration_file: - fileNames.insert(0, registerForm.registration_file.path) + # fileNames = RegistrationSchoolFileTemplate.get_files_from_rf(registerForm.pk) + # if registerForm.registration_file: + # fileNames.insert(0, registerForm.registration_file.path) - # Création du fichier PDF Fusionné - merged_pdf_content = util.merge_files_pdf(fileNames) + # # Création du fichier PDF Fusionné + # merged_pdf_content = util.merge_files_pdf(fileNames) - # Mise à jour du champ registration_file avec le fichier fusionné - registerForm.registration_file.save( - f"dossier_complet_{registerForm.pk}.pdf", - File(merged_pdf_content), - save=True - ) + # # Mise à jour du champ registration_file avec le fichier fusionné + # registerForm.registration_file.save( + # f"dossier_complet.pdf", + # File(merged_pdf_content), + # save=True + # ) + # Mise à jour de l'automate - updateStateMachine(registerForm, 'EVENT_SIGNATURE') + # Vérification de la présence du fichier SEPA + if registerForm.sepa_file: + # Mise à jour de l'automate pour SEPA + updateStateMachine(registerForm, 'EVENT_SIGNATURE_SEPA') + else: + # Mise à jour de l'automate pour une signature classique + updateStateMachine(registerForm, 'EVENT_SIGNATURE') except Exception as e: return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) @@ -413,10 +420,40 @@ def resend(request,id): operation_summary="Récupérer les fichiers à signer d'un dossier d'inscription donné" ) @api_view(['GET']) -def get_templates_by_rf(request, id): +def get_school_file_templates_by_rf(request, id): try: - templates = RegistrationTemplate.objects.filter(registration_form=id) - templates_data = list(templates.values()) - return JsonResponse(templates_data, safe=False) - except RegistrationFileGroup.DoesNotExist: - return JsonResponse({'error': 'Le groupe de fichiers n\'a pas été trouvé'}, status=404) \ No newline at end of file + # Récupérer les templates associés au RegistrationForm donné + templates = RegistrationSchoolFileTemplate.objects.filter(registration_form=id) + + # Sérialiser les données + serializer = RegistrationSchoolFileTemplateSerializer(templates, many=True) + + # Retourner les données sérialisées + return JsonResponse(serializer.data, safe=False) + except RegistrationSchoolFileTemplate.DoesNotExist: + return JsonResponse({'error': 'Aucun template trouvé pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND) + +@swagger_auto_schema( + method='get', + responses={200: openapi.Response('Success', schema=openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + 'message': openapi.Schema(type=openapi.TYPE_STRING) + } + ))}, + operation_description="Récupère les pièces à fournir d'un dossier d'inscription donné", + operation_summary="Récupérer les pièces à fournir d'un dossier d'inscription donné" +) +@api_view(['GET']) +def get_parent_file_templates_by_rf(request, id): + try: + # Récupérer les pièces à fournir associés au RegistrationForm donné + parent_files = RegistrationParentFileTemplate.objects.filter(registration_form=id) + + # Sérialiser les données + serializer = RegistrationParentFileTemplateSerializer(parent_files, many=True) + + # Retourner les données sérialisées + return JsonResponse(serializer.data, safe=False) + except RegistrationParentFileTemplate.DoesNotExist: + return JsonResponse({'error': 'Aucune pièce à fournir trouvée pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND) \ No newline at end of file diff --git a/Back-End/Subscriptions/views/registration_file_group_views.py b/Back-End/Subscriptions/views/registration_file_group_views.py index cce88e9..c9c42d5 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, RegistrationTemplateMaster +from Subscriptions.models import RegistrationFileGroup, RegistrationSchoolFileMaster from N3wtSchool import bdd class RegistrationFileGroupView(APIView): @@ -124,7 +124,7 @@ class RegistrationFileGroupSimpleView(APIView): def get_registration_files_by_group(request, id): try: group = RegistrationFileGroup.objects.get(id=id) - templateMasters = RegistrationTemplateMaster.objects.filter(groups=group) + templateMasters = RegistrationSchoolFileMaster.objects.filter(groups=group) templates_data = list(templateMasters.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 7df225a..84fc514 100644 --- a/Back-End/Subscriptions/views/registration_file_views.py +++ b/Back-End/Subscriptions/views/registration_file_views.py @@ -6,64 +6,64 @@ from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import status -from Subscriptions.serializers import RegistrationTemplateMasterSerializer, RegistrationTemplateSerializer -from Subscriptions.models import RegistrationTemplateMaster, RegistrationTemplate +from Subscriptions.serializers import RegistrationSchoolFileMasterSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileMasterSerializer, RegistrationParentFileTemplateSerializer +from Subscriptions.models import RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster, RegistrationParentFileTemplate from N3wtSchool import bdd -class RegistrationTemplateMasterView(APIView): +class RegistrationSchoolFileMasterView(APIView): @swagger_auto_schema( operation_description="Récupère tous les masters de templates d'inscription", - responses={200: RegistrationTemplateMasterSerializer(many=True)} + responses={200: RegistrationSchoolFileMasterSerializer(many=True)} ) def get(self, request): - masters = RegistrationTemplateMaster.objects.all() - serializer = RegistrationTemplateMasterSerializer(masters, many=True) + masters = RegistrationSchoolFileMaster.objects.all() + serializer = RegistrationSchoolFileMasterSerializer(masters, many=True) return Response(serializer.data) @swagger_auto_schema( operation_description="Crée un nouveau master de template d'inscription", - request_body=RegistrationTemplateMasterSerializer, + request_body=RegistrationSchoolFileMasterSerializer, responses={ - 201: RegistrationTemplateMasterSerializer, + 201: RegistrationSchoolFileMasterSerializer, 400: "Données invalides" } ) def post(self, request): - serializer = RegistrationTemplateMasterSerializer(data=request.data) + serializer = RegistrationSchoolFileMasterSerializer(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 RegistrationTemplateMasterSimpleView(APIView): +class RegistrationSchoolFileMasterSimpleView(APIView): @swagger_auto_schema( operation_description="Récupère un master de template d'inscription spécifique", responses={ - 200: RegistrationTemplateMasterSerializer, + 200: RegistrationSchoolFileMasterSerializer, 404: "Master non trouvé" } ) def get(self, request, id): - master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id) + master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _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) + serializer = RegistrationSchoolFileMasterSerializer(master) return JsonResponse(serializer.data, safe=False) @swagger_auto_schema( operation_description="Met à jour un master de template d'inscription existant", - request_body=RegistrationTemplateMasterSerializer, + request_body=RegistrationSchoolFileMasterSerializer, responses={ - 200: RegistrationTemplateMasterSerializer, + 200: RegistrationSchoolFileMasterSerializer, 400: "Données invalides", 404: "Master non trouvé" } ) def put(self, request, id): - master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id) + master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _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) + serializer = RegistrationSchoolFileMasterSerializer(master, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) @@ -77,67 +77,67 @@ class RegistrationTemplateMasterSimpleView(APIView): } ) def delete(self, request, id): - master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id) + master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _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 master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) -class RegistrationTemplateView(APIView): +class RegistrationSchoolFileTemplateView(APIView): @swagger_auto_schema( operation_description="Récupère tous les templates d'inscription", - responses={200: RegistrationTemplateSerializer(many=True)} + responses={200: RegistrationSchoolFileTemplateSerializer(many=True)} ) def get(self, request): - templates = RegistrationTemplate.objects.all() - serializer = RegistrationTemplateSerializer(templates, many=True) + templates = RegistrationSchoolFileTemplate.objects.all() + serializer = RegistrationSchoolFileTemplateSerializer(templates, many=True) return Response(serializer.data) @swagger_auto_schema( operation_description="Crée un nouveau template d'inscription", - request_body=RegistrationTemplateSerializer, + request_body=RegistrationSchoolFileTemplateSerializer, responses={ - 201: RegistrationTemplateSerializer, + 201: RegistrationSchoolFileTemplateSerializer, 400: "Données invalides" } ) def post(self, request): - serializer = RegistrationTemplateSerializer(data=request.data) + serializer = RegistrationSchoolFileTemplateSerializer(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 RegistrationTemplateSimpleView(APIView): +class RegistrationSchoolFileTemplateSimpleView(APIView): @swagger_auto_schema( operation_description="Récupère un template d'inscription spécifique", responses={ - 200: RegistrationTemplateSerializer, + 200: RegistrationSchoolFileTemplateSerializer, 404: "Template non trouvé" } ) def get(self, request, id): - template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id) + template = bdd.getObject(_objectName=RegistrationSchoolFileTemplate, _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) + serializer = RegistrationSchoolFileTemplateSerializer(template) return JsonResponse(serializer.data, safe=False) @swagger_auto_schema( operation_description="Met à jour un template d'inscription existant", - request_body=RegistrationTemplateSerializer, + request_body=RegistrationSchoolFileTemplateSerializer, responses={ - 200: RegistrationTemplateSerializer, + 200: RegistrationSchoolFileTemplateSerializer, 400: "Données invalides", 404: "Template non trouvé" } ) def put(self, request, id): - template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id) + template = bdd.getObject(_objectName=RegistrationSchoolFileTemplate, _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) + serializer = RegistrationSchoolFileTemplateSerializer(template, data=request.data) if serializer.is_valid(): serializer.save() return Response({'message': 'Template mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK) @@ -151,7 +151,156 @@ class RegistrationTemplateSimpleView(APIView): } ) def delete(self, request, id): - template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id) + template = bdd.getObject(_objectName=RegistrationSchoolFileTemplate, _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 template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + +class RegistrationParentFileMasterView(APIView): + @swagger_auto_schema( + operation_description="Récupère tous les fichiers parents", + responses={200: RegistrationParentFileMasterSerializer(many=True)} + ) + def get(self, request): + templates = RegistrationParentFileMaster.objects.all() + serializer = RegistrationParentFileMasterSerializer(templates, many=True) + return Response(serializer.data) + + @swagger_auto_schema( + operation_description="Crée un nouveau fichier parent", + request_body=RegistrationParentFileMasterSerializer, + responses={ + 201: RegistrationParentFileMasterSerializer, + 400: "Données invalides" + } + ) + def post(self, request): + serializer = RegistrationParentFileMasterSerializer(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 RegistrationParentFileMasterSimpleView(APIView): + @swagger_auto_schema( + operation_description="Récupère un fichier parent spécifique", + responses={ + 200: RegistrationParentFileMasterSerializer, + 404: "Fichier parent non trouvé" + } + ) + def get(self, request, id): + template = bdd.getObject(_objectName=RegistrationParentFileMaster, _columnName='id', _value=id) + if template is None: + return JsonResponse({"errorMessage":'Le fichier parent n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + serializer = RegistrationParentFileMasterSerializer(template) + return JsonResponse(serializer.data, safe=False) + + @swagger_auto_schema( + operation_description="Met à jour un fichier parent existant", + request_body=RegistrationParentFileMasterSerializer, + responses={ + 200: RegistrationParentFileMasterSerializer, + 400: "Données invalides", + 404: "Fichier parent non trouvé" + } + ) + def put(self, request, id): + template = bdd.getObject(_objectName=RegistrationParentFileMaster, _columnName='id', _value=id) + if template is None: + return JsonResponse({'erreur': 'Le fichier parent n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + serializer = RegistrationParentFileMasterSerializer(template, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response({'message': 'Fichier parent 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 parent", + responses={ + 204: "Suppression réussie", + 404: "Fichier parent non trouvé" + } + ) + def delete(self, request, id): + template = bdd.getObject(_objectName=RegistrationParentFileMaster, _columnName='id', _value=id) + if template is not None: + template.delete() + return JsonResponse({'message': 'La suppression du fichier parent a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT) + else: + return JsonResponse({'erreur': 'Le fichier parent n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + +class RegistrationParentFileTemplateView(APIView): + @swagger_auto_schema( + operation_description="Récupère tous les templates d'inscription", + responses={200: RegistrationParentFileTemplateSerializer(many=True)} + ) + def get(self, request): + templates = RegistrationParentFileTemplate.objects.all() + serializer = RegistrationParentFileTemplateSerializer(templates, many=True) + return Response(serializer.data) + + @swagger_auto_schema( + operation_description="Crée un nouveau template d'inscription", + request_body=RegistrationParentFileTemplateSerializer, + responses={ + 201: RegistrationParentFileTemplateSerializer, + 400: "Données invalides" + } + ) + def post(self, request): + serializer = RegistrationParentFileTemplateSerializer(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 RegistrationParentFileTemplateSimpleView(APIView): + @swagger_auto_schema( + operation_description="Récupère un template d'inscription spécifique", + responses={ + 200: RegistrationParentFileTemplateSerializer, + 404: "Template non trouvé" + } + ) + def get(self, request, id): + template = bdd.getObject(_objectName=RegistrationParentFileTemplate, _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 = RegistrationParentFileTemplateSerializer(template) + return JsonResponse(serializer.data, safe=False) + + @swagger_auto_schema( + operation_description="Met à jour un template d'inscription existant", + request_body=RegistrationParentFileTemplateSerializer, + responses={ + 200: RegistrationParentFileTemplateSerializer, + 400: "Données invalides", + 404: "Template non trouvé" + } + ) + def put(self, request, id): + template = bdd.getObject(_objectName=RegistrationParentFileTemplate, _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 = RegistrationParentFileTemplateSerializer(template, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + 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 template d'inscription", + responses={ + 204: "Suppression réussie", + 404: "Template non trouvé" + } + ) + def delete(self, request, id): + template = bdd.getObject(_objectName=RegistrationParentFileTemplate, _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) diff --git a/Back-End/Subscriptions/views/student_views.py b/Back-End/Subscriptions/views/student_views.py index 02cfed4..8abcd99 100644 --- a/Back-End/Subscriptions/views/student_views.py +++ b/Back-End/Subscriptions/views/student_views.py @@ -96,6 +96,13 @@ class ChildrenListView(APIView): students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__profile_role__profile__id', _value=id) if students: - students = students.filter(establishment=establishment_id).distinct() + students = students.filter( + establishment=establishment_id, + status__in=[ + RegistrationForm.RegistrationFormStatus.RF_SENT, + RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW, + RegistrationForm.RegistrationFormStatus.RF_SEPA_SENT + ] + ).distinct() students_serializer = RegistrationFormByParentSerializer(students, many=True) return JsonResponse(students_serializer.data, safe=False) diff --git a/Front-End/src/app/[locale]/admin/structure/page.js b/Front-End/src/app/[locale]/admin/structure/page.js index f095821..5f59ae3 100644 --- a/Front-End/src/app/[locale]/admin/structure/page.js +++ b/Front-End/src/app/[locale]/admin/structure/page.js @@ -23,10 +23,10 @@ import { fetchRegistrationPaymentModes, fetchTuitionPaymentModes, } from '@/app/actions/schoolAction'; -import { fetchProfileRoles, fetchProfiles } from '@/app/actions/authAction'; +import { fetchProfiles } from '@/app/actions/authAction'; import SidebarTabs from '@/components/SidebarTabs'; import FilesGroupsManagement from '@/components/Structure/Files/FilesGroupsManagement'; -import { fetchRegistrationTemplateMaster } from '@/app/actions/registerFileGroupAction'; +import { fetchRegistrationSchoolFileMasters } from '@/app/actions/registerFileGroupAction'; import logger from '@/utils/logger'; import { useEstablishment } from '@/context/EstablishmentContext'; @@ -75,8 +75,8 @@ export default function Page() { // Fetch data for tuition fees handleTuitionFees(); - // Fetch data for registration file templates - fetchRegistrationTemplateMaster() + // Fetch data for registration file schoolFileTemplates + fetchRegistrationSchoolFileMasters() .then((data) => { setFichiers(data); }) @@ -275,7 +275,7 @@ export default function Page() { const tabs = [ { id: 'Configuration', - label: "Configuration de l'école", + label: 'Classes', content: ( +
diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index ae63976..20e88fe 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -16,7 +16,7 @@ import { Edit, Archive, FileText, - CircleCheck, + CheckCircle, Plus, XCircle, } from 'lucide-react'; @@ -39,8 +39,10 @@ import { } from '@/app/actions/subscriptionAction'; import { - fetchRegistrationTemplateMaster, - createRegistrationTemplates, + fetchRegistrationSchoolFileMasters, + fetchRegistrationParentFileMasters, + createRegistrationSchoolFileTemplate, + createRegistrationParentFileTemplate, fetchRegistrationFileGroups, cloneTemplate, } from '@/app/actions/registerFileGroupAction'; @@ -96,7 +98,8 @@ export default function Page({ params: { locale } }) { const [totalArchives, setTotalArchives] = useState(0); const [itemsPerPage, setItemsPerPage] = useState(10); // Définir le nombre d'éléments par page - const [templateMasters, setTemplateMasters] = useState([]); + const [schoolFileMasters, setSchoolFileMasters] = useState([]); + const [parentFileMasters, setParentFileMasters] = useState([]); const [isOpen, setIsOpen] = useState(false); const [isOpenAffectationClasse, setIsOpenAffectationClasse] = useState(false); const [student, setStudent] = useState(''); @@ -239,9 +242,16 @@ export default function Page({ params: { locale } }) { fetchRegisterForms(selectedEstablishmentId, ARCHIVED) .then(registerFormArchivedDataHandler) .catch(requestErrorHandler), - fetchRegistrationTemplateMaster() + fetchRegistrationSchoolFileMasters() .then((data) => { - setTemplateMasters(data); + setSchoolFileMasters(data); + }) + .catch((err) => { + logger.debug(err.message); + }), + fetchRegistrationParentFileMasters() + .then((data) => { + setParentFileMasters(data); }) .catch((err) => { logger.debug(err.message); @@ -315,9 +325,9 @@ export default function Page({ params: { locale } }) { fetchRegisterForms(selectedEstablishmentId, ARCHIVED) .then(registerFormArchivedDataHandler) .catch(requestErrorHandler); - fetchRegistrationTemplateMaster() + fetchRegistrationSchoolFileMasters() .then((data) => { - setTemplateMasters(data); + setSchoolFileMasters(data); }) .catch((err) => { err = err.message; @@ -521,41 +531,78 @@ export default function Page({ params: { locale } }) { createRegisterForm(data, csrfToken) .then((data) => { - // Cloner les templates pour chaque templateMaster du fileGroup - const masters = templateMasters.filter((file) => + // Cloner les schoolFileTemplates pour chaque templateMaster du fileGroup + const masters = schoolFileMasters.filter((file) => file.groups.includes(selectedFileGroup) ); - const clonePromises = masters.map((templateMaster, index) => { - return cloneTemplate( - templateMaster.id, - updatedData.guardianEmail, - templateMaster.is_required - ) - .then((clonedDocument) => { - // Sauvegarde des templates clonés dans la base de données - const cloneData = { - name: `${templateMaster.name}_${updatedData.guardianFirstName}_${updatedData.guardianLastName}`, - slug: clonedDocument.slug, - id: clonedDocument.id, - master: templateMaster.id, - registration_form: data.student.id, - }; + const parent_masters = parentFileMasters.filter((file) => + file.groups.includes(selectedFileGroup) + ); + const clonePromises = masters + .map((templateMaster, index) => { + return cloneTemplate( + templateMaster.id, + updatedData.guardianEmail, + templateMaster.is_required + ) + .then((clonedDocument) => { + // Sauvegarde des schoolFileTemplates clonés dans la base de données + const cloneData = { + name: `${templateMaster.name}_${updatedData.guardianFirstName}_${updatedData.guardianLastName}`, + slug: clonedDocument.slug, + id: clonedDocument.id, + master: templateMaster.id, + registration_form: data.student.id, + }; - return createRegistrationTemplates(cloneData, csrfToken) - .then((response) => { - logger.debug('Template enregistré avec succès:', response); - }) - .catch((error) => { - logger.error( - "Erreur lors de l'enregistrement du template:", - error - ); - }); - }) - .catch((error) => { - logger.error('Error during cloning or sending:', error); - }); - }); + return createRegistrationSchoolFileTemplate( + cloneData, + csrfToken + ) + .then((response) => { + logger.debug('Template enregistré avec succès:', response); + }) + .catch((error) => { + logger.error( + "Erreur lors de l'enregistrement du template:", + error + ); + }); + }) + .catch((error) => { + logger.error('Error during cloning or sending:', error); + }); + }) + .catch((error) => { + logger.error('Error:', error); + }); + + // Créer les parentFileTemplates pour chaque parentMaster + const parentClonePromises = parent_masters.map( + (parentMaster, index) => { + const parentTemplateData = { + master: parentMaster.id, + registration_form: data.student.id, + }; + + return createRegistrationParentFileTemplate( + parentTemplateData, + csrfToken + ) + .then((response) => { + logger.debug( + 'Parent template enregistré avec succès:', + response + ); + }) + .catch((error) => { + logger.error( + "Erreur lors de l'enregistrement du parent template:", + error + ); + }); + } + ); // Attendre que tous les clones soient créés Promise.all(clonePromises) @@ -670,7 +717,11 @@ export default function Page({ params: { locale } }) { const actions = { 1: [ { - icon: , + icon: ( + + + + ), onClick: () => router.push( `${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}` @@ -678,7 +729,9 @@ export default function Page({ params: { locale } }) { }, { icon: ( - + + + ), onClick: () => sendConfirmRegisterForm( @@ -690,7 +743,11 @@ export default function Page({ params: { locale } }) { ], 2: [ { - icon: , + icon: ( + + + + ), onClick: () => router.push( `${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}` @@ -700,18 +757,26 @@ export default function Page({ params: { locale } }) { 3: [ { icon: ( - + + + ), - onClick: () => - router.push( - `${FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL}?studentId=${row.student.id}&firstName=${row.student.first_name}&lastName=${row.student.last_name}&paymentMode=${row.registration_payment}&file=${row.registration_file}` - ), + onClick: () => { + const paymentSepa = + row.registration_payment === 1 || row.tuition_payment === 1 + ? 1 + : 0; + const url = `${FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL}?studentId=${row.student.id}&firstName=${row.student.first_name}&lastName=${row.student.last_name}&paymentSepa=${paymentSepa}&file=${row.registration_file}`; + router.push(`${url}`); + }, }, ], 5: [ { icon: ( - + + + ), onClick: () => openModalAssociationEleve(row.student), }, @@ -719,7 +784,9 @@ export default function Page({ params: { locale } }) { default: [ { icon: ( - + + + ), onClick: () => archiveFicheInscription( @@ -785,17 +852,34 @@ export default function Page({ params: { locale } }) { }, { name: t('files'), - transform: (row) => - row.registration_file != null && ( - + ), }, { name: 'Actions', @@ -865,7 +949,7 @@ export default function Page({ params: { locale } }) { { label: ( <> - Rattacher + Rattacher ), onClick: () => openModalAssociationEleve(row.student), diff --git a/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js b/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js index 63cf2f8..132395a 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js @@ -15,7 +15,7 @@ export default function Page() { const studentId = searchParams.get('studentId'); const firstName = searchParams.get('firstName'); const lastName = searchParams.get('lastName'); - const paymentMode = searchParams.get('paymentMode'); + const paymentSepa = searchParams.get('paymentSepa') === '1'; const file = searchParams.get('file'); const csrfToken = useCsrfToken(); @@ -45,7 +45,7 @@ export default function Page() { studentId={studentId} firstName={firstName} lastName={lastName} - paymentMode={paymentMode} + paymentSepa={paymentSepa} file={file} onAccept={handleAcceptRF} /> diff --git a/Front-End/src/app/[locale]/parents/page.js b/Front-End/src/app/[locale]/parents/page.js index c306638..599b93b 100644 --- a/Front-End/src/app/[locale]/parents/page.js +++ b/Front-End/src/app/[locale]/parents/page.js @@ -2,21 +2,28 @@ import React, { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import Table from '@/components/Table'; -import { Edit, Users } from 'lucide-react'; +import { Edit3, Users, Download, Eye, Upload } from 'lucide-react'; import StatusLabel from '@/components/StatusLabel'; +import FileUpload from '@/components/FileUpload'; import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url'; -import { fetchChildren } from '@/app/actions/subscriptionAction'; +import { + fetchChildren, + sendSEPARegisterForm, +} from '@/app/actions/subscriptionAction'; import logger from '@/utils/logger'; +import { BASE_URL } from '@/utils/Url'; import { useEstablishment } from '@/context/EstablishmentContext'; -import ProfileSelector from '@/components/ProfileSelector'; +import { useCsrfToken } from '@/context/CsrfContext'; export default function ParentHomePage() { const [children, setChildren] = useState([]); const [userId, setUserId] = useState(null); - const [currentPage, setCurrentPage] = useState(1); const { user, selectedEstablishmentId } = useEstablishment(); - + const [uploadingStudentId, setUploadingStudentId] = useState(null); // ID de l'étudiant pour l'upload + const [uploadedFile, setUploadedFile] = useState(null); // Fichier uploadé + const [uploadState, setUploadState] = useState('off'); // État "on" ou "off" pour l'affichage du composant const router = useRouter(); + const csrfToken = useCsrfToken(); useEffect(() => { const userIdFromSession = user.user_id; @@ -27,17 +34,62 @@ export default function ParentHomePage() { }); }, [selectedEstablishmentId]); + function handleView(eleveId) { + logger.debug(`View dossier for student id: ${eleveId}`); + router.push( + `${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}&view=true` + ); + } + function handleEdit(eleveId) { - // Logique pour éditer le dossier de l'élève logger.debug(`Edit dossier for student id: ${eleveId}`); router.push( `${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}` ); } - const actionColumns = [{ name: 'Action', transform: (row) => row.action }]; + const handleFileUpload = (file) => { + if (!file) { + logger.error("Aucun fichier sélectionné pour l'upload."); + return; + } + setUploadedFile(file); // Conserve le fichier en mémoire + logger.debug('Fichier sélectionné :', file.name); + }; + + const handleSubmit = () => { + if (!uploadedFile || !uploadingStudentId) { + logger.error('Aucun fichier ou étudiant sélectionné.'); + return; + } + + const formData = new FormData(); + formData.append('sepa_file', uploadedFile); // Ajoute le fichier SEPA + formData.append('status', 3); // Statut à envoyer + + sendSEPARegisterForm(uploadingStudentId, formData, csrfToken) + .then((response) => { + logger.debug('RF mis à jour avec succès:', response); + // Logique supplémentaire après la mise à jour (par exemple, redirection ou notification) + }) + .catch((error) => { + logger.error('Erreur lors de la mise à jour du RF:', error); + }); + }; + + const toggleUpload = (studentId) => { + if (uploadingStudentId === studentId && uploadState === 'on') { + // Si le composant est déjà affiché pour cet étudiant, on le masque + setUploadState('off'); + setUploadingStudentId(null); + setUploadedFile(null); // Réinitialise le fichier + } else { + // Sinon, on l'affiche pour cet étudiant + setUploadState('on'); + setUploadingStudentId(studentId); + } + }; - // Définir les colonnes du tableau const childrenColumns = [ { name: 'Nom', transform: (row) => `${row.student.last_name}` }, { name: 'Prénom', transform: (row) => `${row.student.first_name}` }, @@ -52,29 +104,76 @@ export default function ParentHomePage() { { name: 'Actions', transform: (row) => ( -
- +
+ {row.status === 2 && ( + + )} + + {row.status === 3 && ( + + )} + + {row.status === 7 && ( + <> + + + + + {/* Nouvelle action Upload */} + + + )}
), }, ]; - const itemsPerPage = 5; - const totalPages = Math.ceil(children.length / itemsPerPage) || 1; - - const handlePageChange = (newPage) => { - setCurrentPage(newPage); - }; - return (
@@ -86,13 +185,29 @@ export default function ParentHomePage() { + {/* Composant FileUpload et bouton Valider en dessous du tableau */} + {uploadState === 'on' && uploadingStudentId && ( +
+ + +
+ )} ); diff --git a/Front-End/src/app/actions/registerFileGroupAction.js b/Front-End/src/app/actions/registerFileGroupAction.js index ad1d077..ad5e6ee 100644 --- a/Front-End/src/app/actions/registerFileGroupAction.js +++ b/Front-End/src/app/actions/registerFileGroupAction.js @@ -1,7 +1,9 @@ import { BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL, - BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL, - BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL, + BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL, + BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL, + BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL, + BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL, FE_API_DOCUSEAL_CLONE_URL, FE_API_DOCUSEAL_DOWNLOAD_URL, FE_API_DOCUSEAL_GENERATE_TOKEN, @@ -18,6 +20,8 @@ const requestResponseHandler = async (response) => { throw error; }; +// FETCH requests + export async function fetchRegistrationFileGroups(establishment) { const response = await fetch( `${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}?establishment_id=${establishment}`, @@ -34,6 +38,68 @@ export async function fetchRegistrationFileGroups(establishment) { return response.json(); } +export const fetchRegistrationFileFromGroup = async (groupId) => { + const response = await fetch( + `${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/school_file_templates`, + { + credentials: 'include', + headers: { + Accept: 'application/json', + }, + } + ); + if (!response.ok) { + throw new Error( + 'Erreur lors de la récupération des fichiers associés au groupe' + ); + } + return response.json(); +}; + +export const fetchRegistrationSchoolFileMasters = (id = null) => { + let url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}`; + if (id) { + url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}/${id}`; + } + const request = new Request(`${url}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + return fetch(request).then(requestResponseHandler); +}; + +export const fetchRegistrationParentFileMasters = (id = null) => { + let url = `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}`; + if (id) { + url = `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`; + } + const request = new Request(`${url}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + return fetch(request).then(requestResponseHandler); +}; + +export const fetchRegistrationSchoolFileTemplates = (id = null) => { + let url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}`; + if (id) { + url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${id}`; + } + const request = new Request(`${url}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + return fetch(request).then(requestResponseHandler); +}; + +// CREATE requests + export async function createRegistrationFileGroup(groupData, csrfToken) { const response = await fetch( `${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`, @@ -55,20 +121,55 @@ export async function createRegistrationFileGroup(groupData, csrfToken) { return response.json(); } -export async function deleteRegistrationFileGroup(groupId, csrfToken) { - const response = await fetch( - `${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, - { - method: 'DELETE', - headers: { - 'X-CSRFToken': csrfToken, - }, - credentials: 'include', - } - ); +export const createRegistrationSchoolFileMaster = (data, csrfToken) => { + return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}`, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'X-CSRFToken': csrfToken, + 'Content-Type': 'application/json', + }, + credentials: 'include', + }).then(requestResponseHandler); +}; - return response; -} +export const createRegistrationParentFileMaster = (data, csrfToken) => { + return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}`, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'X-CSRFToken': csrfToken, + 'Content-Type': 'application/json', + }, + credentials: 'include', + }).then(requestResponseHandler); +}; + +export const createRegistrationSchoolFileTemplate = (data, csrfToken) => { + return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}`, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'X-CSRFToken': csrfToken, + 'Content-Type': 'application/json', + }, + credentials: 'include', + }).then(requestResponseHandler); +}; + +export const createRegistrationParentFileTemplate = (data, csrfToken) => { + return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL}`, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'X-CSRFToken': csrfToken, + 'Content-Type': 'application/json', + }, + credentials: 'include', + }).then(requestResponseHandler); +}; + +// EDIT requests export const editRegistrationFileGroup = async ( groupId, @@ -94,113 +195,9 @@ export const editRegistrationFileGroup = async ( return response.json(); }; -export const fetchRegistrationFileFromGroup = async (groupId) => { - const response = await fetch( - `${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/templates`, - { - credentials: 'include', - headers: { - Accept: 'application/json', - }, - } - ); - if (!response.ok) { - throw new Error( - 'Erreur lors de la récupération des fichiers associés au groupe' - ); - } - return response.json(); -}; - -export const fetchRegistrationTemplates = (id = null) => { - let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`; - if (id) { - url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${id}`; - } - const request = new Request(`${url}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - return fetch(request).then(requestResponseHandler); -}; - -export const editRegistrationTemplates = (fileId, data, csrfToken) => { - return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, { - method: 'PUT', - body: data, - headers: { - 'X-CSRFToken': csrfToken, - }, - credentials: 'include', - }).then(requestResponseHandler); -}; - -export const createRegistrationTemplates = (data, csrfToken) => { - return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`, { - method: 'POST', - body: JSON.stringify(data), - headers: { - 'X-CSRFToken': csrfToken, - 'Content-Type': 'application/json', - }, - credentials: 'include', - }).then(requestResponseHandler); -}; - -export const deleteRegistrationTemplates = (fileId, csrfToken) => { - return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, { - method: 'DELETE', - headers: { - 'X-CSRFToken': csrfToken, - }, - credentials: 'include', - }); -}; - -export const fetchRegistrationTemplateMaster = (id = null) => { - let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`; - if (id) { - url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${id}`; - } - const request = new Request(`${url}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - return fetch(request).then(requestResponseHandler); -}; - -export const createRegistrationTemplateMaster = (data, csrfToken) => { - return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`, { - method: 'POST', - body: JSON.stringify(data), - headers: { - 'X-CSRFToken': csrfToken, - 'Content-Type': 'application/json', - }, - credentials: 'include', - }).then(requestResponseHandler); -}; - -export const deleteRegistrationTemplateMaster = (fileId, csrfToken) => { +export const editRegistrationSchoolFileMaster = (fileId, data, csrfToken) => { return fetch( - `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, - { - method: 'DELETE', - headers: { - 'X-CSRFToken': csrfToken, - }, - credentials: 'include', - } - ); -}; - -export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => { - return fetch( - `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, + `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}/${fileId}`, { method: 'PUT', body: JSON.stringify(data), @@ -213,6 +210,128 @@ export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => { ).then(requestResponseHandler); }; +export const editRegistrationParentFileMaster = (id, data, csrfToken) => { + return fetch( + `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`, + { + method: 'PUT', + body: JSON.stringify(data), + headers: { + 'X-CSRFToken': csrfToken, + 'Content-Type': 'application/json', + }, + credentials: 'include', + } + ).then(requestResponseHandler); +}; + +export const editRegistrationSchoolFileTemplates = ( + fileId, + data, + csrfToken +) => { + return fetch( + `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${fileId}`, + { + method: 'PUT', + body: data, + headers: { + 'X-CSRFToken': csrfToken, + }, + credentials: 'include', + } + ).then(requestResponseHandler); +}; + +export const editRegistrationParentFileTemplates = ( + fileId, + data, + csrfToken +) => { + return fetch( + `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL}/${fileId}`, + { + method: 'PUT', + body: data, + headers: { + 'X-CSRFToken': csrfToken, + }, + credentials: 'include', + } + ).then(requestResponseHandler); +}; + +// DELETE requests + +export async function deleteRegistrationFileGroup(groupId, csrfToken) { + const response = await fetch( + `${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, + { + method: 'DELETE', + headers: { + 'X-CSRFToken': csrfToken, + }, + credentials: 'include', + } + ); + + return response; +} + +export const deleteRegistrationSchoolFileMaster = (fileId, csrfToken) => { + return fetch( + `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}/${fileId}`, + { + method: 'DELETE', + headers: { + 'X-CSRFToken': csrfToken, + }, + credentials: 'include', + } + ); +}; + +export const deleteRegistrationParentFileMaster = (id, csrfToken) => { + return fetch( + `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`, + { + method: 'DELETE', + headers: { + 'X-CSRFToken': csrfToken, + }, + credentials: 'include', + } + ); +}; + +export const deleteRegistrationSchoolFileTemplates = (fileId, csrfToken) => { + return fetch( + `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${fileId}`, + { + method: 'DELETE', + headers: { + 'X-CSRFToken': csrfToken, + }, + credentials: 'include', + } + ); +}; + +export const deleteRegistrationParentFileTemplate = (id, csrfToken) => { + return fetch( + `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL}/${id}`, + { + method: 'DELETE', + headers: { + 'X-CSRFToken': csrfToken, + }, + credentials: 'include', + } + ); +}; + +// API requests + export const cloneTemplate = (templateId, email, is_required) => { return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, { method: 'POST', diff --git a/Front-End/src/app/actions/subscriptionAction.js b/Front-End/src/app/actions/subscriptionAction.js index 6788be7..c8cda56 100644 --- a/Front-End/src/app/actions/subscriptionAction.js +++ b/Front-End/src/app/actions/subscriptionAction.js @@ -153,9 +153,27 @@ export async function getRegisterFormFileTemplate(fileId) { return response.json(); } -export const fetchTemplatesFromRegistrationFiles = async (id) => { +export const fetchSchoolFileTemplatesFromRegistrationFiles = async (id) => { const response = await fetch( - `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/templates`, + `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/school_file_templates`, + { + credentials: 'include', + headers: { + Accept: 'application/json', + }, + } + ); + if (!response.ok) { + throw new Error( + 'Erreur lors de la récupération des fichiers associés au groupe' + ); + } + return response.json(); +}; + +export const fetchParentFileTemplatesFromRegistrationFiles = async (id) => { + const response = await fetch( + `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/parent_file_templates`, { credentials: 'include', headers: { diff --git a/Front-End/src/components/FileUpload copy.js b/Front-End/src/components/FileUpload copy.js deleted file mode 100644 index da7ebbe..0000000 --- a/Front-End/src/components/FileUpload copy.js +++ /dev/null @@ -1,105 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch -import DraggableFileUpload from './DraggableFileUpload'; -import { fetchRegistrationFileGroups } from '@/app/actions/registerFileGroupAction'; -import { useEstablishment } from '@/context/EstablishmentContext'; - -export default function FileUpload({ onFileUpload, fileToEdit = null }) { - const [fileName, setFileName] = useState(''); - const [file, setFile] = useState(null); - const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired - const [order, setOrder] = useState(0); - const [groups, setGroups] = useState([]); - const [selectedGroup, setSelectedGroup] = useState(''); - - const { selectedEstablishmentId } = useEstablishment(); - - useEffect(() => { - fetchRegistrationFileGroups(selectedEstablishmentId).then((data) => - setGroups(data) - ); - - if (fileToEdit) { - setFileName(fileToEdit.name || ''); - setIsRequired(fileToEdit.is_required || false); - setOrder(fileToEdit.fusion_order || 0); - setSelectedGroup(fileToEdit.group_id || ''); - } - }, [fileToEdit]); - - const handleFileNameChange = (event) => { - setFileName(event.target.value); - }; - - const handleUpload = () => { - onFileUpload({ - file, - name: fileName, - is_required: isRequired, - order: parseInt(order, 10), - groupId: selectedGroup || null, - }); - setFile(null); - setFileName(''); - setIsRequired(false); - setOrder(0); - setSelectedGroup(''); - }; - - return ( -
- { - setFile(selectedFile); - setFileName(selectedFile.name.replace(/\.[^/.]+$/, '')); - }} - /> -
- - setOrder(e.target.value)} - placeholder="Ordre de fusion" - className="p-2 border border-gray-200 rounded-md ml-2 w-20" - /> - -
-
- setIsRequired(!isRequired)} - /> -
-
- - -
-
- ); -} diff --git a/Front-End/src/components/FileUpload.js b/Front-End/src/components/FileUpload.js new file mode 100644 index 0000000..d5020f2 --- /dev/null +++ b/Front-End/src/components/FileUpload.js @@ -0,0 +1,60 @@ +import React, { useState, useRef } from 'react'; +import { CloudUpload } from 'lucide-react'; +import logger from '@/utils/logger'; + +export default function FileUpload({ selectionMessage, onFileSelect, uploadedFileName }) { + const [localFileName, setLocalFileName] = useState(uploadedFileName || ''); + const fileInputRef = useRef(null); // Utilisation de useRef pour cibler l'input + + const handleFileChange = (e) => { + const file = e.target.files[0]; + if (file) { + setLocalFileName(file.name); + logger.debug('Fichier sélectionné:', file.name); + onFileSelect(file); // Appelle la fonction passée en prop + } + }; + + const handleFileDrop = (e) => { + e.preventDefault(); + const file = e.dataTransfer.files[0]; + if (file) { + setLocalFileName(file.name); + logger.debug('Fichier déposé:', file.name); + onFileSelect(file); // Appelle la fonction passée en prop + } + }; + + return ( +
+

{`${selectionMessage}`}

+
fileInputRef.current.click()} // Utilisation de la référence pour ouvrir l'explorateur + onDragOver={(e) => e.preventDefault()} + onDrop={handleFileDrop} + > + {/* Icône de cloud */} + + +
+ {localFileName && ( +
+ +

+ {localFileName} +

+
+ )} +
+ ); +} \ No newline at end of file diff --git a/Front-End/src/components/InputPhone.js b/Front-End/src/components/InputPhone.js index 254cf16..a908da1 100644 --- a/Front-End/src/components/InputPhone.js +++ b/Front-End/src/components/InputPhone.js @@ -11,6 +11,11 @@ export default function InputPhone({ className, required, }) { + const handlePhoneChange = (phone) => { + // Appeler onChange avec un objet personnalisé + onChange({ target: { name, value: phone } }); + }; + return (
{}} +
+
+
+
+ +
+
+

+ Pièces à fournir +

+

+ Ajoutez les documents pour compléter votre inscription +

+
+
+
+
+ {selectedFile && ( +
+ {actionType === 'view' && selectedFile.fileName ? ( +