mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
feat: Gestion des pièces à fournir par les parents (configuration école)
This commit is contained in:
@ -163,6 +163,9 @@ class RegistrationFileGroup(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.group.name} - {self.id}'
|
||||||
|
|
||||||
def registration_file_path(instance, filename):
|
def registration_file_path(instance, filename):
|
||||||
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
|
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
|
||||||
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
|
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
|
||||||
@ -211,7 +214,7 @@ class RegistrationForm(models.Model):
|
|||||||
# Many-to-Many Relationship
|
# Many-to-Many Relationship
|
||||||
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
|
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
|
||||||
fileGroup = models.ForeignKey(RegistrationFileGroup,
|
fileGroup = models.ForeignKey(RegistrationFileGroup,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.SET_NULL,
|
||||||
related_name='register_forms',
|
related_name='register_forms',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True)
|
blank=True)
|
||||||
@ -223,6 +226,20 @@ class RegistrationForm(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Appeler la méthode save originale
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def registration_file_upload_to(instance, filename):
|
def registration_file_upload_to(instance, filename):
|
||||||
return f"registration_files/dossier_rf_{instance.registration_form.pk}/{filename}"
|
return f"registration_files/dossier_rf_{instance.registration_form.pk}/{filename}"
|
||||||
|
|
||||||
@ -246,4 +263,10 @@ class RegistrationTemplate(models.Model):
|
|||||||
filenames = []
|
filenames = []
|
||||||
for reg_file in registration_files:
|
for reg_file in registration_files:
|
||||||
filenames.append(reg_file.file.path)
|
filenames.append(reg_file.file.path)
|
||||||
return filenames
|
return filenames
|
||||||
|
|
||||||
|
class RegistrationParentFile(models.Model):
|
||||||
|
groups = models.ManyToManyField(RegistrationFileGroup, related_name='parent_files', blank=True)
|
||||||
|
name = models.CharField(max_length=255, default="")
|
||||||
|
description = models.CharField(blank=True, null=True)
|
||||||
|
file = models.FileField(null=True,blank=True, upload_to=registration_file_upload_to)
|
||||||
@ -1,5 +1,5 @@
|
|||||||
from rest_framework import serializers
|
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, RegistrationTemplateMaster, RegistrationTemplate, RegistrationParentFile
|
||||||
from School.models import SchoolClass, Fee, Discount, FeeType
|
from School.models import SchoolClass, Fee, Discount, FeeType
|
||||||
from School.serializers import FeeSerializer, DiscountSerializer
|
from School.serializers import FeeSerializer, DiscountSerializer
|
||||||
from Auth.models import ProfileRole, Profile
|
from Auth.models import ProfileRole, Profile
|
||||||
@ -24,6 +24,12 @@ class RegistrationTemplateSerializer(serializers.ModelSerializer):
|
|||||||
model = RegistrationTemplate
|
model = RegistrationTemplate
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
class RegistrationParentFileSerializer(serializers.ModelSerializer):
|
||||||
|
id = serializers.IntegerField(required=False)
|
||||||
|
class Meta:
|
||||||
|
model = RegistrationParentFile
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
class GuardianSimpleSerializer(serializers.ModelSerializer):
|
class GuardianSimpleSerializer(serializers.ModelSerializer):
|
||||||
associated_profile_email = serializers.SerializerMethodField()
|
associated_profile_email = serializers.SerializerMethodField()
|
||||||
|
|
||||||
@ -280,7 +286,7 @@ class RegistrationFormByParentSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RegistrationForm
|
model = RegistrationForm
|
||||||
fields = ['student', 'status']
|
fields = ['student', 'status', 'sepa_file']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(RegistrationFormByParentSerializer, self).__init__(*args, **kwargs)
|
super(RegistrationFormByParentSerializer, self).__init__(*args, **kwargs)
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archi
|
|||||||
# SubClasses
|
# SubClasses
|
||||||
from .views import StudentView, GuardianView, ChildrenListView, StudentListView, DissociateGuardianView
|
from .views import StudentView, GuardianView, ChildrenListView, StudentListView, DissociateGuardianView
|
||||||
# Files
|
# Files
|
||||||
from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
|
from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView, RegistrationParentFileSimpleView, RegistrationParentFileView
|
||||||
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
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_templates_by_rf
|
||||||
|
|
||||||
@ -39,6 +39,9 @@ urlpatterns = [
|
|||||||
re_path(r'^registrationTemplates/(?P<id>[0-9]+)$', RegistrationTemplateSimpleView.as_view(), name='registrationTemplates'),
|
re_path(r'^registrationTemplates/(?P<id>[0-9]+)$', RegistrationTemplateSimpleView.as_view(), name='registrationTemplates'),
|
||||||
re_path(r'^registrationTemplates$', RegistrationTemplateView.as_view(), name="registrationTemplates"),
|
re_path(r'^registrationTemplates$', RegistrationTemplateView.as_view(), name="registrationTemplates"),
|
||||||
|
|
||||||
|
re_path(r'^registrationParentFiles/(?P<id>[0-9]+)$', RegistrationParentFileSimpleView.as_view(), name='registrationParentFiles'),
|
||||||
|
re_path(r'^registrationParentFiles$', RegistrationParentFileView.as_view(), name="registrationParentFiles"),
|
||||||
|
|
||||||
re_path(r'^students/(?P<student_id>[0-9]+)/guardians/(?P<guardian_id>[0-9]+)/dissociate', DissociateGuardianView.as_view(), name='dissociate-guardian'),
|
re_path(r'^students/(?P<student_id>[0-9]+)/guardians/(?P<guardian_id>[0-9]+)/dissociate', DissociateGuardianView.as_view(), name='dissociate-guardian'),
|
||||||
|
|
||||||
]
|
]
|
||||||
@ -1,5 +1,5 @@
|
|||||||
from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive, get_templates_by_rf
|
from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive, get_templates_by_rf
|
||||||
from .registration_file_views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
|
from .registration_file_views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView, RegistrationParentFileView, RegistrationParentFileSimpleView
|
||||||
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||||
from .student_views import StudentView, StudentListView, ChildrenListView
|
from .student_views import StudentView, StudentListView, ChildrenListView
|
||||||
from .guardian_views import GuardianView, DissociateGuardianView
|
from .guardian_views import GuardianView, DissociateGuardianView
|
||||||
@ -12,6 +12,8 @@ __all__ = [
|
|||||||
'archive',
|
'archive',
|
||||||
'RegistrationTemplateView',
|
'RegistrationTemplateView',
|
||||||
'RegistrationTemplateSimpleView',
|
'RegistrationTemplateSimpleView',
|
||||||
|
'RegistrationParentFileSimpleView',
|
||||||
|
'RegistrationParentFileView',
|
||||||
'RegistrationTemplateMasterView',
|
'RegistrationTemplateMasterView',
|
||||||
'RegistrationTemplateMasterSimpleView',
|
'RegistrationTemplateMasterSimpleView',
|
||||||
'RegistrationFileGroupView',
|
'RegistrationFileGroupView',
|
||||||
|
|||||||
159
Back-End/Subscriptions/views/registration_file_views copy.py
Normal file
159
Back-End/Subscriptions/views/registration_file_views copy.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
from django.http.response import JsonResponse
|
||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
|
from drf_yasg import openapi
|
||||||
|
from rest_framework.parsers import MultiPartParser, FormParser
|
||||||
|
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 N3wtSchool import bdd
|
||||||
|
|
||||||
|
class RegistrationTemplateMasterView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère tous les masters de templates d'inscription",
|
||||||
|
responses={200: RegistrationTemplateMasterSerializer(many=True)}
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
masters = RegistrationTemplateMaster.objects.all()
|
||||||
|
serializer = RegistrationTemplateMasterSerializer(masters, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Crée un nouveau master de template d'inscription",
|
||||||
|
request_body=RegistrationTemplateMasterSerializer,
|
||||||
|
responses={
|
||||||
|
201: RegistrationTemplateMasterSerializer,
|
||||||
|
400: "Données invalides"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
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 RegistrationTemplateMasterSimpleView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère un master de template d'inscription spécifique",
|
||||||
|
responses={
|
||||||
|
200: RegistrationTemplateMasterSerializer,
|
||||||
|
404: "Master non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get(self, request, id):
|
||||||
|
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 master de template d'inscription existant",
|
||||||
|
request_body=RegistrationTemplateMasterSerializer,
|
||||||
|
responses={
|
||||||
|
200: RegistrationTemplateMasterSerializer,
|
||||||
|
400: "Données invalides",
|
||||||
|
404: "Master non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def put(self, request, id):
|
||||||
|
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_200_OK)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Supprime un master de template d'inscription",
|
||||||
|
responses={
|
||||||
|
204: "Suppression réussie",
|
||||||
|
404: "Master non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def delete(self, request, id):
|
||||||
|
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 master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
class RegistrationTemplateView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère tous les templates d'inscription",
|
||||||
|
responses={200: RegistrationTemplateSerializer(many=True)}
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
templates = RegistrationTemplate.objects.all()
|
||||||
|
serializer = RegistrationTemplateSerializer(templates, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Crée un nouveau template d'inscription",
|
||||||
|
request_body=RegistrationTemplateSerializer,
|
||||||
|
responses={
|
||||||
|
201: RegistrationTemplateSerializer,
|
||||||
|
400: "Données invalides"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
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 RegistrationTemplateSimpleView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère un template d'inscription spécifique",
|
||||||
|
responses={
|
||||||
|
200: RegistrationTemplateSerializer,
|
||||||
|
404: "Template non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get(self, request, id):
|
||||||
|
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 template d'inscription existant",
|
||||||
|
request_body=RegistrationTemplateSerializer,
|
||||||
|
responses={
|
||||||
|
200: RegistrationTemplateSerializer,
|
||||||
|
400: "Données invalides",
|
||||||
|
404: "Template non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def put(self, request, id):
|
||||||
|
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': '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=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 template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
@ -6,8 +6,8 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from Subscriptions.serializers import RegistrationTemplateMasterSerializer, RegistrationTemplateSerializer
|
from Subscriptions.serializers import RegistrationTemplateMasterSerializer, RegistrationTemplateSerializer, RegistrationParentFileSerializer
|
||||||
from Subscriptions.models import RegistrationTemplateMaster, RegistrationTemplate
|
from Subscriptions.models import RegistrationTemplateMaster, RegistrationTemplate, RegistrationParentFile
|
||||||
from N3wtSchool import bdd
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
class RegistrationTemplateMasterView(APIView):
|
class RegistrationTemplateMasterView(APIView):
|
||||||
@ -157,3 +157,78 @@ class RegistrationTemplateSimpleView(APIView):
|
|||||||
return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||||
else:
|
else:
|
||||||
return JsonResponse({'erreur': 'Le template 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)
|
||||||
|
|
||||||
|
class RegistrationParentFileView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère tous les fichiers parents",
|
||||||
|
responses={200: RegistrationParentFileSerializer(many=True)}
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
templates = RegistrationParentFile.objects.all()
|
||||||
|
serializer = RegistrationParentFileSerializer(templates, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Crée un nouveau fichier parent",
|
||||||
|
request_body=RegistrationParentFileSerializer,
|
||||||
|
responses={
|
||||||
|
201: RegistrationParentFileSerializer,
|
||||||
|
400: "Données invalides"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
serializer = RegistrationParentFileSerializer(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 RegistrationParentFileSimpleView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère un fichier parent spécifique",
|
||||||
|
responses={
|
||||||
|
200: RegistrationParentFileSerializer,
|
||||||
|
404: "Fichier parent non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFile, _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 = RegistrationParentFileSerializer(template)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Met à jour un fichier parent existant",
|
||||||
|
request_body=RegistrationParentFileSerializer,
|
||||||
|
responses={
|
||||||
|
200: RegistrationParentFileSerializer,
|
||||||
|
400: "Données invalides",
|
||||||
|
404: "Fichier parent non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def put(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFile, _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 = RegistrationParentFileSerializer(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=RegistrationParentFile, _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)
|
||||||
@ -2,13 +2,13 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { Edit, Users } from 'lucide-react';
|
import { Edit3, Users, Download, Eye } from 'lucide-react';
|
||||||
import StatusLabel from '@/components/StatusLabel';
|
import StatusLabel from '@/components/StatusLabel';
|
||||||
import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url';
|
import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url';
|
||||||
import { fetchChildren } from '@/app/actions/subscriptionAction';
|
import { fetchChildren } from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
import { FE_USERS_LOGIN_URL, BASE_URL } from '@/utils/Url';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
export default function ParentHomePage() {
|
export default function ParentHomePage() {
|
||||||
@ -40,10 +40,12 @@ export default function ParentHomePage() {
|
|||||||
if (!selectedEstablishmentId && userEstablishments.length > 0) {
|
if (!selectedEstablishmentId && userEstablishments.length > 0) {
|
||||||
setSelectedEstablishmentId(userEstablishments[0].id);
|
setSelectedEstablishmentId(userEstablishments[0].id);
|
||||||
}
|
}
|
||||||
console.log(selectedEstablishmentId)
|
|
||||||
fetchChildren(userIdFromSession, selectedEstablishmentId).then(data => {
|
if (selectedEstablishmentId) {
|
||||||
setChildren(data);
|
fetchChildren(userIdFromSession, selectedEstablishmentId).then(data => {
|
||||||
});
|
setChildren(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [status, session, selectedEstablishmentId]);
|
}, [status, session, selectedEstablishmentId]);
|
||||||
|
|
||||||
@ -52,6 +54,12 @@ export default function ParentHomePage() {
|
|||||||
setSelectedEstablishmentId(establishmentId);
|
setSelectedEstablishmentId(establishmentId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function handleView(eleveId) {
|
||||||
|
// Logique pour éditer le dossier de l'élève
|
||||||
|
logger.debug(`View dossier for student id: ${eleveId}`);
|
||||||
|
router.push(`${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}&view=true`);
|
||||||
|
}
|
||||||
|
|
||||||
function handleEdit(eleveId) {
|
function handleEdit(eleveId) {
|
||||||
// Logique pour éditer le dossier de l'élève
|
// Logique pour éditer le dossier de l'élève
|
||||||
logger.debug(`Edit dossier for student id: ${eleveId}`);
|
logger.debug(`Edit dossier for student id: ${eleveId}`);
|
||||||
@ -77,17 +85,57 @@ export default function ParentHomePage() {
|
|||||||
{
|
{
|
||||||
name: 'Actions',
|
name: 'Actions',
|
||||||
transform: (row) => (
|
transform: (row) => (
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center items-center gap-2">
|
||||||
<button
|
{/* Actions en fonction du statut */}
|
||||||
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
|
{row.status === 2 && (
|
||||||
onClick={(e) => {
|
<button
|
||||||
e.stopPropagation();
|
className="text-blue-500 hover:text-blue-700"
|
||||||
handleEdit(row.student.id);
|
onClick={(e) => {
|
||||||
}}
|
e.stopPropagation();
|
||||||
aria-label="Modifier"
|
handleEdit(row.student.id); // Remplir le dossier
|
||||||
>
|
}}
|
||||||
<Edit className="h-5 w-5" />
|
aria-label="Remplir le dossier"
|
||||||
</button>
|
>
|
||||||
|
<Edit3 className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{row.status === 3 && (
|
||||||
|
<button
|
||||||
|
className="text-purple-500 hover:text-purple-700"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleView(row.student.id); // Visualiser le dossier
|
||||||
|
}}
|
||||||
|
aria-label="Visualiser le dossier"
|
||||||
|
>
|
||||||
|
<Eye className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{row.status === 7 && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="text-purple-500 hover:text-purple-700"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleView(row.student.id); // Visualiser le dossier
|
||||||
|
}}
|
||||||
|
aria-label="Visualiser le dossier"
|
||||||
|
>
|
||||||
|
<Eye className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
href={`${BASE_URL}${row.sepa_file}`} // Télécharger le mandat SEPA
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-green-500 hover:text-green-700"
|
||||||
|
aria-label="Télécharger le mandat SEPA"
|
||||||
|
>
|
||||||
|
<Download className="h-5 w-5" />
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL,
|
import { BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL,
|
||||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL,
|
BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL,
|
||||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL,
|
BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL,
|
||||||
|
BE_SUBSCRIPTION_REGISTRATION_PARENT_FILES_URL,
|
||||||
FE_API_DOCUSEAL_CLONE_URL,
|
FE_API_DOCUSEAL_CLONE_URL,
|
||||||
FE_API_DOCUSEAL_DOWNLOAD_URL,
|
FE_API_DOCUSEAL_DOWNLOAD_URL,
|
||||||
FE_API_DOCUSEAL_GENERATE_TOKEN
|
FE_API_DOCUSEAL_GENERATE_TOKEN
|
||||||
@ -91,6 +92,60 @@ export const fetchRegistrationFileFromGroup = async (groupId) => {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const fetchRegistrationParentFiles = (id = null) => {
|
||||||
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILES_URL}`
|
||||||
|
if (id) {
|
||||||
|
url = `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILES_URL}/${id}`;
|
||||||
|
}
|
||||||
|
const request = new Request(
|
||||||
|
`${url}`,
|
||||||
|
{
|
||||||
|
method:'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type':'application/json'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return fetch(request).then(requestResponseHandler)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRegistrationParentFiles = (data,csrfToken) => {
|
||||||
|
|
||||||
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILES_URL}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
.then(requestResponseHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const editRegistrationParentFiles = (id, data, csrfToken) => {
|
||||||
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILES_URL}/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
.then(requestResponseHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteRegistrationParentFiles = (id, csrfToken) => {
|
||||||
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILES_URL}/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const fetchRegistrationTemplates = (id = null) => {
|
export const fetchRegistrationTemplates = (id = null) => {
|
||||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`
|
||||||
if (id) {
|
if (id) {
|
||||||
|
|||||||
@ -1,101 +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 (
|
|
||||||
<div>
|
|
||||||
<DraggableFileUpload
|
|
||||||
fileName={fileName}
|
|
||||||
onFileSelect={(selectedFile) => {
|
|
||||||
setFile(selectedFile);
|
|
||||||
setFileName(selectedFile.name.replace(/\.[^/.]+$/, ""));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="flex mt-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Nom du fichier"
|
|
||||||
value={fileName}
|
|
||||||
onChange={handleFileNameChange}
|
|
||||||
className="flex-grow p-2 border border-gray-200 rounded-md"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={order}
|
|
||||||
onChange={(e) => setOrder(e.target.value)}
|
|
||||||
placeholder="Ordre de fusion"
|
|
||||||
className="p-2 border border-gray-200 rounded-md ml-2 w-20"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={handleUpload}
|
|
||||||
className={`p-2 rounded-md shadow transition duration-200 ml-2 ${fileName !== "" ? 'bg-emerald-600 text-white hover:bg-emerald-900' : 'bg-gray-300 text-gray-500 cursor-not-allowed'}`}
|
|
||||||
disabled={fileName === ""}
|
|
||||||
>
|
|
||||||
Ajouter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center mt-4">
|
|
||||||
<ToggleSwitch
|
|
||||||
label="Fichier à remplir obligatoirement"
|
|
||||||
checked={isRequired}
|
|
||||||
onChange={() => setIsRequired(!isRequired)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4">
|
|
||||||
<label className="block mb-2">Groupe</label>
|
|
||||||
<select
|
|
||||||
value={selectedGroup}
|
|
||||||
onChange={(e) => setSelectedGroup(e.target.value)}
|
|
||||||
className="w-full border rounded p-2"
|
|
||||||
>
|
|
||||||
<option value="">Aucun groupe</option>
|
|
||||||
{groups.map(group => (
|
|
||||||
<option key={group.id} value={group.id}>{group.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -3,6 +3,11 @@ import { PhoneInput } from 'react-international-phone';
|
|||||||
import 'react-international-phone/style.css';
|
import 'react-international-phone/style.css';
|
||||||
|
|
||||||
export default function InputPhone({ name, label, value, onChange, errorMsg, className, required }) {
|
export default function InputPhone({ name, label, value, onChange, errorMsg, className, required }) {
|
||||||
|
const handlePhoneChange = (phone) => {
|
||||||
|
// Appeler onChange avec un objet personnalisé
|
||||||
|
onChange({ target: { name, value: phone } });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${className}`}>
|
<div className={`${className}`}>
|
||||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
||||||
@ -13,7 +18,7 @@ export default function InputPhone({ name, label, value, onChange, errorMsg, cla
|
|||||||
<PhoneInput
|
<PhoneInput
|
||||||
defaultCountry="fr"
|
defaultCountry="fr"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(phone) => onChange(phone)}
|
onChange={handlePhoneChange}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
name: name,
|
name: name,
|
||||||
required: required,
|
required: required,
|
||||||
|
|||||||
59
Front-End/src/components/Inscription/FileUpload.js
Normal file
59
Front-End/src/components/Inscription/FileUpload.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React, { useState } 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 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 (
|
||||||
|
<div className="border p-4 rounded-md shadow-md">
|
||||||
|
<h3 className="text-lg font-semibold mb-4">{`${selectionMessage}`}</h3>
|
||||||
|
<div
|
||||||
|
className="border-2 border-dashed border-gray-500 p-6 rounded-lg flex flex-col items-center justify-center cursor-pointer hover:border-emerald-500"
|
||||||
|
onClick={() => document.getElementById('fileInput').click()} // Ouvre l'explorateur de fichiers au clic
|
||||||
|
onDragOver={(e) => e.preventDefault()}
|
||||||
|
onDrop={handleFileDrop}
|
||||||
|
>
|
||||||
|
<CloudUpload className="w-12 h-12 text-emerald-500 mb-4" /> {/* Icône de cloud */}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".pdf"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
className="hidden"
|
||||||
|
id="fileInput"
|
||||||
|
/>
|
||||||
|
<label htmlFor="fileInput" className="text-center text-gray-500">
|
||||||
|
<p className="text-lg font-semibold text-gray-800">Déposez votre fichier ici</p>
|
||||||
|
<p className="text-sm text-gray-500 mt-2">ou cliquez pour sélectionner un fichier PDF</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{localFileName && (
|
||||||
|
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
|
||||||
|
<CloudUpload className="w-6 h-6 text-emerald-500" />
|
||||||
|
<p className="text-sm font-medium text-gray-800">
|
||||||
|
<span className="font-semibold">{localFileName}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -120,7 +120,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value, type } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormData((prevState) => ({
|
setFormData((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
[name]: value,
|
[name]: value,
|
||||||
@ -365,7 +365,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
/>
|
/>
|
||||||
<InputPhone
|
<InputPhone
|
||||||
name="guardianPhone"
|
name="guardianPhone"
|
||||||
label={t('Numéro de téléphone (optionnel)')}
|
label="Numéro de téléphone (optionnel)"
|
||||||
value={formData.guardianPhone}
|
value={formData.guardianPhone}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full mt-4"
|
className="w-full mt-4"
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import logger from '@/utils/logger';
|
|||||||
import StudentInfoForm, { validateStudentInfo } from '@/components/Inscription/StudentInfoForm';
|
import StudentInfoForm, { validateStudentInfo } from '@/components/Inscription/StudentInfoForm';
|
||||||
import FilesToUpload from '@/components/Inscription/FilesToUpload';
|
import FilesToUpload from '@/components/Inscription/FilesToUpload';
|
||||||
import { DocusealForm } from '@docuseal/react';
|
import { DocusealForm } from '@docuseal/react';
|
||||||
|
import FileUpload from '@/components/Inscription/FileUpload';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composant de formulaire d'inscription partagé
|
* Composant de formulaire d'inscription partagé
|
||||||
@ -385,6 +386,14 @@ export default function InscriptionFormShared({
|
|||||||
fileTemplates={fileTemplates.filter(template => !template.is_required)}
|
fileTemplates={fileTemplates.filter(template => !template.is_required)}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
/>
|
/>
|
||||||
|
<FileUpload
|
||||||
|
selectionMessage='Sélectionnez un fichier'
|
||||||
|
onFileSelect={(file) => {
|
||||||
|
setUploadedFiles(file.name); // Stocke uniquement le nom du fichier
|
||||||
|
logger.debug('Fichier sélectionné:', file.name);
|
||||||
|
}}
|
||||||
|
uploadedFileName={uploadedFiles}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { BASE_URL } from '@/utils/Url';
|
|||||||
import { generateToken } from '@/app/actions/registerFileGroupAction';
|
import { generateToken } from '@/app/actions/registerFileGroupAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { GraduationCap, CloudUpload } from 'lucide-react';
|
import { GraduationCap, CloudUpload } from 'lucide-react';
|
||||||
|
import FileUpload from '@/components/Inscription/FileUpload';
|
||||||
|
|
||||||
export default function ValidateSubscription({ studentId, firstName, lastName, paymentMode, file, onAccept }) {
|
export default function ValidateSubscription({ studentId, firstName, lastName, paymentMode, file, onAccept }) {
|
||||||
const [token, setToken] = useState(null);
|
const [token, setToken] = useState(null);
|
||||||
@ -100,47 +101,14 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{currentPage === 2 && isSepa && (
|
{currentPage === 2 && isSepa && (
|
||||||
<div className="border p-4 rounded-md shadow-md">
|
<FileUpload
|
||||||
<h3 className="text-lg font-semibold mb-4">Sélection du mandat de pélèvement SEPA</h3>
|
selectionMessage='Sélectionnez un mandat de prélèvement SEPA'
|
||||||
<div
|
onFileSelect={(file) => {
|
||||||
className="border-2 border-dashed border-gray-500 p-6 rounded-lg flex flex-col items-center justify-center cursor-pointer hover:border-emerald-500"
|
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
||||||
onClick={() => document.getElementById('fileInput').click()} // Ouvre l'explorateur de fichiers au clic
|
logger.debug('Fichier sélectionné:', file.name);
|
||||||
onDragOver={(e) => e.preventDefault()}
|
}}
|
||||||
onDrop={(e) => {
|
uploadedFileName={uploadedFileName}
|
||||||
e.preventDefault();
|
/>
|
||||||
const file = e.dataTransfer.files[0];
|
|
||||||
if (file) {
|
|
||||||
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
|
||||||
logger.debug('Fichier déposé:', file.name);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloudUpload className="w-12 h-12 text-emerald-500 mb-4" /> {/* Icône de cloud */}
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept=".pdf"
|
|
||||||
onChange={(e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
|
||||||
logger.debug('Fichier sélectionné:', file.name);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="hidden"
|
|
||||||
id="fileInput"
|
|
||||||
/>
|
|
||||||
<label htmlFor="fileInput" className="text-center text-gray-500">
|
|
||||||
<p className="text-lg font-semibold text-gray-800">Déposez votre fichier ici</p>
|
|
||||||
<p className="text-sm text-gray-500 mt-2">ou cliquez pour sélectionner un fichier PDF</p>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{uploadedFileName && (
|
|
||||||
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
|
|
||||||
<CloudUpload className="w-6 h-6 text-emerald-500" />
|
|
||||||
<p className="text-sm font-medium text-gray-800"><span className="font-semibold">{uploadedFileName}</span></p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Boutons de navigation */}
|
{/* Boutons de navigation */}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import InputTextWithColorIcon from '@/components/InputTextWithColorIcon';
|
|||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
||||||
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, handleEdit, handleDelete }) => {
|
const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, handleEdit, handleDelete }) => {
|
||||||
@ -21,6 +22,8 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
|
|||||||
const [removePopupMessage, setRemovePopupMessage] = useState("");
|
const [removePopupMessage, setRemovePopupMessage] = useState("");
|
||||||
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
||||||
|
|
||||||
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
|
||||||
// Récupération des messages d'erreur
|
// Récupération des messages d'erreur
|
||||||
const getError = (field) => {
|
const getError = (field) => {
|
||||||
return localErrors?.[field]?.[0];
|
return localErrors?.[field]?.[0];
|
||||||
@ -41,9 +44,14 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveNewSpeciality = () => {
|
const handleSaveNewSpeciality = () => {
|
||||||
if (
|
if (newSpeciality.name) {
|
||||||
newSpeciality.name) {
|
// Ajouter l'ID de l'établissement à la nouvelle spécialité
|
||||||
handleCreate(newSpeciality)
|
const specialityData = {
|
||||||
|
...newSpeciality,
|
||||||
|
establishment: selectedEstablishmentId, // Inclure l'ID de l'établissement
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCreate(specialityData)
|
||||||
.then((createdSpeciality) => {
|
.then((createdSpeciality) => {
|
||||||
setSpecialities([createdSpeciality, ...specialities]);
|
setSpecialities([createdSpeciality, ...specialities]);
|
||||||
setNewSpeciality(null);
|
setNewSpeciality(null);
|
||||||
@ -52,8 +60,8 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error('Error:', error.message);
|
logger.error('Error:', error.message);
|
||||||
if (error.details) {
|
if (error.details) {
|
||||||
logger.error('Form errors:', error.details);
|
logger.error('Form errors:', error.details);
|
||||||
setLocalErrors(error.details);
|
setLocalErrors(error.details);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import MultiSelect from '@/components/MultiSelect'; // Import du composant Multi
|
|||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
export default function FileUpload({ handleCreateTemplateMaster, handleEditTemplateMaster, fileToEdit = null, onSuccess }) {
|
export default function FileUploadDocuSeal({ handleCreateTemplateMaster, handleEditTemplateMaster, fileToEdit = null, onSuccess }) {
|
||||||
const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired
|
const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired
|
||||||
const [order, setOrder] = useState(0);
|
const [order, setOrder] = useState(0);
|
||||||
const [groups, setGroups] = useState([]);
|
const [groups, setGroups] = useState([]);
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Plus, Download, Edit3, Trash2, FolderPlus, Signature } from 'lucide-react';
|
import { Plus, Download, Edit3, Trash2, FolderPlus, Signature, FileText, Check, X } from 'lucide-react';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import FileUpload from '@/components/Structure/Files/FileUpload';
|
import FileUploadDocuSeal from '@/components/Structure/Files/FileUploadDocuSeal';
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import {
|
import {
|
||||||
fetchRegistrationFileGroups,
|
fetchRegistrationFileGroups,
|
||||||
@ -13,10 +13,15 @@ import {
|
|||||||
createRegistrationTemplateMaster,
|
createRegistrationTemplateMaster,
|
||||||
editRegistrationTemplateMaster,
|
editRegistrationTemplateMaster,
|
||||||
deleteRegistrationTemplateMaster,
|
deleteRegistrationTemplateMaster,
|
||||||
fetchRegistrationTemplates
|
fetchRegistrationTemplates,
|
||||||
|
fetchRegistrationParentFiles,
|
||||||
|
createRegistrationParentFiles,
|
||||||
|
editRegistrationParentFiles,
|
||||||
|
deleteRegistrationParentFiles
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
|
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import ParentFilesSection from '@/components/Structure/Files/ParentFilesSection';
|
||||||
|
|
||||||
export default function FilesGroupsManagement({ csrfToken, selectedEstablishmentId }) {
|
export default function FilesGroupsManagement({ csrfToken, selectedEstablishmentId }) {
|
||||||
const [templateMasters, setTemplateMasters] = useState([]);
|
const [templateMasters, setTemplateMasters] = useState([]);
|
||||||
@ -29,6 +34,10 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
|
|||||||
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
||||||
const [groupToEdit, setGroupToEdit] = useState(null);
|
const [groupToEdit, setGroupToEdit] = useState(null);
|
||||||
const [reloadTemplates, setReloadTemplates] = useState(false);
|
const [reloadTemplates, setReloadTemplates] = useState(false);
|
||||||
|
const [editingDocumentId, setEditingDocumentId] = useState(null);
|
||||||
|
const [formData, setFormData] = useState({});
|
||||||
|
|
||||||
|
const [parentFiles, setParentFiles] = useState([]);
|
||||||
|
|
||||||
const handleReloadTemplates = () => {
|
const handleReloadTemplates = () => {
|
||||||
setReloadTemplates(true);
|
setReloadTemplates(true);
|
||||||
@ -47,10 +56,12 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
|
|||||||
Promise.all([
|
Promise.all([
|
||||||
fetchRegistrationTemplateMaster(),
|
fetchRegistrationTemplateMaster(),
|
||||||
fetchRegistrationFileGroups(selectedEstablishmentId),
|
fetchRegistrationFileGroups(selectedEstablishmentId),
|
||||||
fetchRegistrationTemplates()
|
fetchRegistrationTemplates(),
|
||||||
]).then(([filesTemplateMasters, groupsData, filesTemplates]) => {
|
fetchRegistrationParentFiles()
|
||||||
|
]).then(([filesTemplateMasters, groupsData, filesTemplates, filesParentFiles]) => {
|
||||||
setGroups(groupsData);
|
setGroups(groupsData);
|
||||||
setTemplates(filesTemplates);
|
setTemplates(filesTemplates);
|
||||||
|
setParentFiles(filesParentFiles);
|
||||||
// Transformer chaque fichier pour inclure les informations complètes du groupe
|
// Transformer chaque fichier pour inclure les informations complètes du groupe
|
||||||
const transformedFiles = filesTemplateMasters.map(file => transformFileData(file, groupsData));
|
const transformedFiles = filesTemplateMasters.map(file => transformFileData(file, groupsData));
|
||||||
setTemplateMasters(transformedFiles);
|
setTemplateMasters(transformedFiles);
|
||||||
@ -189,7 +200,13 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
|
|||||||
alert('Erreur lors de l\'opération sur le groupe');
|
alert('Erreur lors de l\'opération sur le groupe');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
createRegistrationFileGroup(groupData, csrfToken)
|
// Ajouter l'établissement sélectionné lors de la création d'un nouveau groupe
|
||||||
|
const newGroupData = {
|
||||||
|
...groupData,
|
||||||
|
establishment: selectedEstablishmentId
|
||||||
|
};
|
||||||
|
|
||||||
|
createRegistrationFileGroup(newGroupData, csrfToken)
|
||||||
.then(newGroup => {
|
.then(newGroup => {
|
||||||
setGroups([...groups, newGroup]);
|
setGroups([...groups, newGroup]);
|
||||||
setIsGroupModalOpen(false);
|
setIsGroupModalOpen(false);
|
||||||
@ -233,14 +250,135 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderRequiredDocumentCell = (document, column) => {
|
||||||
|
const isEditing = editingDocumentId === document.id;
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
switch (column) {
|
||||||
|
case 'Nom de la pièce':
|
||||||
|
return (
|
||||||
|
<InputText
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => handleChange(e, 'name')}
|
||||||
|
placeholder="Nom de la pièce"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'Description':
|
||||||
|
return (
|
||||||
|
<InputText
|
||||||
|
name="description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) => handleChange(e, 'description')}
|
||||||
|
placeholder="Description"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'Actions':
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center space-x-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleSaveDocument(document.id)}
|
||||||
|
className="text-green-500 hover:text-green-700"
|
||||||
|
>
|
||||||
|
<Check className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleCancelEdit}
|
||||||
|
className="text-red-500 hover:text-red-700"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (column) {
|
||||||
|
case 'Nom de la pièce':
|
||||||
|
return <span>{document.name}</span>;
|
||||||
|
case 'Description':
|
||||||
|
return <span>{document.description}</span>;
|
||||||
|
case 'Actions':
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center space-x-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleEditDocument(document)}
|
||||||
|
className="text-blue-500 hover:text-blue-700"
|
||||||
|
>
|
||||||
|
<Edit3 className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleDeleteRequiredDocument(document.id)}
|
||||||
|
className="text-red-500 hover:text-red-700"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreate = (newParentFile) => {
|
||||||
|
return createRegistrationParentFiles(newParentFile, csrfToken)
|
||||||
|
.then((createdFile) => {
|
||||||
|
// Ajouter le nouveau fichier parent à la liste existante
|
||||||
|
setParentFiles((prevFiles) => [...prevFiles, createdFile]);
|
||||||
|
logger.debug('Document parent créé avec succès:', createdFile);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors de la création du document parent:', error);
|
||||||
|
alert('Une erreur est survenue lors de la création du document parent.');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (id, updatedFile) => {
|
||||||
|
return editRegistrationParentFiles(id, updatedFile, csrfToken)
|
||||||
|
.then((response) => {
|
||||||
|
const modifiedFile = response.data; // Extraire les données mises à jour
|
||||||
|
// Mettre à jour la liste des fichiers parents
|
||||||
|
setParentFiles((prevFiles) =>
|
||||||
|
prevFiles.map((file) => (file.id === id ? modifiedFile : file))
|
||||||
|
);
|
||||||
|
logger.debug('Document parent mis à jour avec succès:', modifiedFile);
|
||||||
|
return modifiedFile; // Retourner le fichier mis à jour
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors de la modification du document parent:', error);
|
||||||
|
alert('Une erreur est survenue lors de la modification du document parent.');
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (id) => {
|
||||||
|
return deleteRegistrationParentFiles(id, csrfToken)
|
||||||
|
.then(() => {
|
||||||
|
// Mettre à jour la liste des fichiers parents en supprimant l'élément correspondant
|
||||||
|
setParentFiles((prevFiles) => prevFiles.filter((file) => file.id !== id));
|
||||||
|
logger.debug('Document parent supprimé avec succès:', id);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors de la suppression du fichier parent:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const filteredFiles = templateMasters.filter(file => {
|
const filteredFiles = templateMasters.filter(file => {
|
||||||
if (!selectedGroup) return true;
|
if (!selectedGroup) return true;
|
||||||
return file.groups && file.groups.some(group => group.id === parseInt(selectedGroup));
|
return file.groups && file.groups.some(group => group.id === parseInt(selectedGroup));
|
||||||
});
|
});
|
||||||
|
|
||||||
const columnsFiles = [
|
const columnsFiles = [
|
||||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
{ name: 'Nom du formulaire', transform: (row) => row.name },
|
||||||
{ name: 'Groupes', transform: (row) => row.groups && row.groups.length > 0 ? row.groups.map(group => group.name).join(', ') : 'Aucun' },
|
{ name: 'Dossiers d\'inscription', transform: (row) => row.groups && row.groups.length > 0 ? row.groups.map(group => group.name).join(', ') : 'Aucun' },
|
||||||
{ name: 'Actions', transform: (row) => (
|
{ name: 'Actions', transform: (row) => (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
{row.file && (
|
{row.file && (
|
||||||
@ -259,7 +397,7 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
|
|||||||
];
|
];
|
||||||
|
|
||||||
const columnsGroups = [
|
const columnsGroups = [
|
||||||
{ name: 'Nom du groupe', transform: (row) => row.name },
|
{ name: 'Nom du dossier', transform: (row) => row.name },
|
||||||
{ name: 'Description', transform: (row) => row.description },
|
{ name: 'Description', transform: (row) => row.description },
|
||||||
{ name: 'Actions', transform: (row) => (
|
{ name: 'Actions', transform: (row) => (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
@ -273,8 +411,15 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
|
|||||||
)}
|
)}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const columnsRequiredDocuments = [
|
||||||
|
{ name: 'Nom de la pièce', transform: (row) => renderRequiredDocumentCell(row, 'Nom de la pièce') },
|
||||||
|
{ name: 'Description', transform: (row) => renderRequiredDocumentCell(row, 'Description') },
|
||||||
|
{ name: 'Actions', transform: (row) => renderRequiredDocumentCell(row, 'Actions') },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="space-y-12">
|
||||||
|
{/* Modal pour les fichiers */}
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
setIsOpen={(isOpen) => {
|
setIsOpen={(isOpen) => {
|
||||||
@ -285,19 +430,21 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
|
|||||||
}}
|
}}
|
||||||
title={isEditing ? 'Modification du document' : 'Ajouter un document'}
|
title={isEditing ? 'Modification du document' : 'Ajouter un document'}
|
||||||
ContentComponent={() => (
|
ContentComponent={() => (
|
||||||
<FileUpload
|
<FileUploadDocuSeal
|
||||||
handleCreateTemplateMaster={handleCreateTemplateMaster}
|
handleCreateTemplateMaster={handleCreateTemplateMaster}
|
||||||
handleEditTemplateMaster={handleEditTemplateMaster}
|
handleEditTemplateMaster={handleEditTemplateMaster}
|
||||||
fileToEdit={fileToEdit}
|
fileToEdit={fileToEdit}
|
||||||
onSuccess={handleReloadTemplates}
|
onSuccess={handleReloadTemplates}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
modalClassName='w-4/5 h-4/5'
|
modalClassName="w-4/5 h-4/5"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Modal pour les groupes */}
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isGroupModalOpen}
|
isOpen={isGroupModalOpen}
|
||||||
setIsOpen={setIsGroupModalOpen}
|
setIsOpen={setIsGroupModalOpen}
|
||||||
title={groupToEdit ? "Modifier le groupe" : "Ajouter un groupe de templateMasters"}
|
title={groupToEdit ? 'Modifier le groupe' : 'Ajouter un groupe de templateMasters'}
|
||||||
ContentComponent={() => (
|
ContentComponent={() => (
|
||||||
<RegistrationFileGroupForm
|
<RegistrationFileGroupForm
|
||||||
onSubmit={handleGroupSubmit}
|
onSubmit={handleGroupSubmit}
|
||||||
@ -305,56 +452,73 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="mt-8 mb-4">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
{/* Section Groupes de fichiers */}
|
||||||
<h2 className="text-xl font-bold">Groupes de fichiers</h2>
|
<div className="mt-8 mb-4 w-3/5">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="bg-emerald-100 p-3 rounded-full shadow-md">
|
||||||
|
<FolderPlus className="w-8 h-8 text-emerald-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800">Dossiers d'inscriptions</h2>
|
||||||
|
<p className="text-sm text-gray-500 italic">
|
||||||
|
Gérez les dossiers d'inscription pour organiser vos documents.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsGroupModalOpen(true)}
|
onClick={() => setIsGroupModalOpen(true)}
|
||||||
className="flex items-center bg-blue-600 text-white p-2 rounded-full shadow hover:bg-blue-900 transition duration-200"
|
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-700 transition duration-200"
|
||||||
>
|
>
|
||||||
<FolderPlus className="w-5 h-5" />
|
<Plus className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Table
|
<Table
|
||||||
data={groups}
|
data={groups}
|
||||||
columns={columnsGroups}
|
columns={columnsGroups}
|
||||||
itemsPerPage={5}
|
|
||||||
currentPage={1}
|
|
||||||
totalPages={Math.ceil(groups.length / 5)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{groups.length > 0 && (
|
|
||||||
<div className="mt-8">
|
{/* Section Fichiers */}
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="mt-8 mb-4 w-3/5">
|
||||||
<h2 className="text-xl font-bold">Fichiers</h2>
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center space-x-4">
|
||||||
<select
|
<div className="bg-emerald-100 p-3 rounded-full shadow-md">
|
||||||
className="border rounded p-2"
|
<Signature className="w-8 h-8 text-emerald-600" />
|
||||||
value={selectedGroup || ''}
|
</div>
|
||||||
onChange={(e) => setSelectedGroup(e.target.value)}
|
<div>
|
||||||
>
|
<h2 className="text-2xl font-bold text-gray-800">Formulaires à remplir</h2>
|
||||||
<option value="">Tous les groupes</option>
|
<p className="text-sm text-gray-500 italic">
|
||||||
{groups.map(group => (
|
Gérez les formulaires nécessitant une signature électronique.
|
||||||
<option key={group.id} value={group.id}>{group.name}</option>
|
</p>
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
|
|
||||||
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
|
|
||||||
>
|
|
||||||
<Plus className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Table
|
<button
|
||||||
data={filteredFiles}
|
onClick={() => {
|
||||||
columns={columnsFiles}
|
setIsModalOpen(true);
|
||||||
itemsPerPage={10}
|
setIsEditing(false);
|
||||||
currentPage={1}
|
}}
|
||||||
totalPages={Math.ceil(filteredFiles.length / 10)}
|
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-700 transition duration-200"
|
||||||
/>
|
>
|
||||||
|
<Plus className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<Table
|
||||||
|
data={filteredFiles}
|
||||||
|
columns={columnsFiles}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Section Pièces à fournir */}
|
||||||
|
<ParentFilesSection
|
||||||
|
parentFiles={parentFiles}
|
||||||
|
setParentFiles={setParentFiles}
|
||||||
|
groups={groups}
|
||||||
|
handleCreate={handleCreate}
|
||||||
|
handleEdit={handleEdit}
|
||||||
|
handleDelete={handleDelete}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,343 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Plus, Download, Edit, Trash2, FolderPlus, Signature } from 'lucide-react';
|
|
||||||
import Modal from '@/components/Modal';
|
|
||||||
import Table from '@/components/Table';
|
|
||||||
import FileUpload from '@/components/FileUpload';
|
|
||||||
import { formatDate } from '@/utils/Date';
|
|
||||||
import { BASE_URL } from '@/utils/Url';
|
|
||||||
import {
|
|
||||||
fetchRegisterFormFileTemplate,
|
|
||||||
createRegistrationFormFileTemplate,
|
|
||||||
editRegistrationFormFileTemplate,
|
|
||||||
deleteRegisterFormFileTemplate,
|
|
||||||
getRegisterFormFileTemplate
|
|
||||||
} from '@/app/actions/subscriptionAction';
|
|
||||||
import {
|
|
||||||
fetchRegistrationFileGroups,
|
|
||||||
createRegistrationFileGroup,
|
|
||||||
deleteRegistrationFileGroup,
|
|
||||||
editRegistrationFileGroup
|
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
|
||||||
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
|
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
|
||||||
|
|
||||||
export default function FilesManagement({ csrfToken }) {
|
|
||||||
const [fichiers, setFichiers] = useState([]);
|
|
||||||
const [groups, setGroups] = useState([]);
|
|
||||||
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
|
||||||
const [fileToEdit, setFileToEdit] = useState(null);
|
|
||||||
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
|
||||||
const [groupToEdit, setGroupToEdit] = useState(null);
|
|
||||||
|
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
|
||||||
|
|
||||||
// Fonction pour transformer les données des fichiers avec les informations complètes du groupe
|
|
||||||
const transformFileData = (file, groups) => {
|
|
||||||
if (!file.group) return file;
|
|
||||||
|
|
||||||
const groupInfo = groups.find(g => g.id === file.group);
|
|
||||||
return {
|
|
||||||
...file,
|
|
||||||
group: groupInfo || { id: file.group, name: 'Groupe inconnu' }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
Promise.all([
|
|
||||||
fetchRegisterFormFileTemplate(),
|
|
||||||
fetchRegistrationFileGroups(selectedEstablishmentId)
|
|
||||||
]).then(([filesData, groupsData]) => {
|
|
||||||
setGroups(groupsData);
|
|
||||||
// Sélectionner automatiquement le premier groupe s'il existe
|
|
||||||
if (groupsData.length > 0) {
|
|
||||||
setSelectedGroup(groupsData[0].id.toString());
|
|
||||||
}
|
|
||||||
// Transformer chaque fichier pour inclure les informations complètes du groupe
|
|
||||||
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
|
|
||||||
setFichiers(transformedFiles);
|
|
||||||
}).catch(err => {
|
|
||||||
console.log(err.message);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleFileDelete = (fileId) => {
|
|
||||||
deleteRegisterFormFileTemplate(fileId, csrfToken)
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
|
|
||||||
alert('Fichier supprimé avec succès.');
|
|
||||||
} else {
|
|
||||||
alert('Erreur lors de la suppression du fichier.');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error deleting file:', error);
|
|
||||||
alert('Erreur lors de la suppression du fichier.');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFileEdit = (file) => {
|
|
||||||
setIsEditing(true);
|
|
||||||
setFileToEdit(file);
|
|
||||||
setIsModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFileUpload = ({file, name, is_required, order, groupId}) => {
|
|
||||||
if (!name) {
|
|
||||||
alert('Veuillez entrer un nom de fichier.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
if(file) {
|
|
||||||
formData.append('file', file);
|
|
||||||
}
|
|
||||||
formData.append('name', name);
|
|
||||||
formData.append('is_required', is_required);
|
|
||||||
formData.append('order', order);
|
|
||||||
|
|
||||||
// Modification ici : vérifier si groupId existe et n'est pas vide
|
|
||||||
if (groupId && groupId !== '') {
|
|
||||||
formData.append('group', groupId); // Notez que le nom du champ est 'group' et non 'group_id'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEditing && fileToEdit) {
|
|
||||||
editRegistrationFormFileTemplate(fileToEdit.id, formData, csrfToken)
|
|
||||||
.then(data => {
|
|
||||||
// Transformer le fichier mis à jour avec les informations du groupe
|
|
||||||
const transformedFile = transformFileData(data, groups);
|
|
||||||
setFichiers(prevFichiers =>
|
|
||||||
prevFichiers.map(f => f.id === fileToEdit.id ? transformedFile : f)
|
|
||||||
);
|
|
||||||
setIsModalOpen(false);
|
|
||||||
setFileToEdit(null);
|
|
||||||
setIsEditing(false);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error editing file:', error);
|
|
||||||
alert('Erreur lors de la modification du fichier');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
createRegistrationFormFileTemplate(formData, csrfToken)
|
|
||||||
.then(data => {
|
|
||||||
// Transformer le nouveau fichier avec les informations du groupe
|
|
||||||
const transformedFile = transformFileData(data, groups);
|
|
||||||
setFichiers(prevFiles => [...prevFiles, transformedFile]);
|
|
||||||
setIsModalOpen(false);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error uploading file:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGroupSubmit = (groupData) => {
|
|
||||||
if (groupToEdit) {
|
|
||||||
editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken)
|
|
||||||
.then(updatedGroup => {
|
|
||||||
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
|
|
||||||
setGroupToEdit(null);
|
|
||||||
setIsGroupModalOpen(false);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error handling group:', error);
|
|
||||||
alert('Erreur lors de l\'opération sur le groupe');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
createRegistrationFileGroup(groupData, csrfToken)
|
|
||||||
.then(newGroup => {
|
|
||||||
setGroups([...groups, newGroup]);
|
|
||||||
setIsGroupModalOpen(false);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error handling group:', error);
|
|
||||||
alert('Erreur lors de l\'opération sur le groupe');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGroupEdit = (group) => {
|
|
||||||
setGroupToEdit(group);
|
|
||||||
setIsGroupModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGroupDelete = (groupId) => {
|
|
||||||
// Vérifier si des fichiers utilisent ce groupe
|
|
||||||
const filesInGroup = fichiers.filter(file => file.group && file.group.id === groupId);
|
|
||||||
if (filesInGroup.length > 0) {
|
|
||||||
alert('Impossible de supprimer ce groupe car il contient des fichiers. Veuillez d\'abord retirer tous les fichiers de ce groupe.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.confirm('Êtes-vous sûr de vouloir supprimer ce groupe ?')) {
|
|
||||||
deleteRegistrationFileGroup(groupId, csrfToken)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 409) {
|
|
||||||
throw new Error('Ce groupe est lié à des inscriptions existantes.');
|
|
||||||
}
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Erreur lors de la suppression du groupe.');
|
|
||||||
}
|
|
||||||
setGroups(groups.filter(group => group.id !== groupId));
|
|
||||||
alert('Groupe supprimé avec succès.');
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error deleting group:', error);
|
|
||||||
alert(error.message || 'Erreur lors de la suppression du groupe. Vérifiez qu\'aucune inscription n\'utilise ce groupe.');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ajouter cette fonction de filtrage
|
|
||||||
const filteredFiles = fichiers.filter(file => {
|
|
||||||
if (!selectedGroup) return true;
|
|
||||||
return file.group && file.group.id === parseInt(selectedGroup);
|
|
||||||
});
|
|
||||||
|
|
||||||
const columnsFiles = [
|
|
||||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
|
||||||
{ name: 'Groupe', transform: (row) => row.group ? row.group.name : 'Aucun' },
|
|
||||||
{ name: 'Date de création', transform: (row) => formatDate(new Date (row.date_added),"DD/MM/YYYY hh:mm:ss") },
|
|
||||||
{ name: 'Fichier Obligatoire', transform: (row) => row.is_required ? 'Oui' : 'Non' },
|
|
||||||
{ name: 'Ordre de fusion', transform: (row) => row.order },
|
|
||||||
{ name: 'Actions', transform: (row) => (
|
|
||||||
<div className="flex items-center justify-center gap-2">
|
|
||||||
{row.file && (
|
|
||||||
<a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
|
|
||||||
<Download size={16} />
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
<button onClick={() => handleFileEdit(row)} className="text-blue-500 hover:text-blue-700">
|
|
||||||
<Edit size={16} />
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
|
|
||||||
<Trash2 size={16} />
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleSignatureRequest(row)} className="text-green-500 hover:text-green-700">
|
|
||||||
<Signature size={16} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
];
|
|
||||||
|
|
||||||
const columnsGroups = [
|
|
||||||
{ name: 'Nom du groupe', transform: (row) => row.name },
|
|
||||||
{ name: 'Description', transform: (row) => row.description },
|
|
||||||
{ name: 'Actions', transform: (row) => (
|
|
||||||
<div className="flex items-center justify-center gap-2">
|
|
||||||
<button onClick={() => handleGroupEdit(row)} className="text-blue-500 hover:text-blue-700">
|
|
||||||
<Edit size={16} />
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleGroupDelete(row.id)} className="text-red-500 hover:text-red-700">
|
|
||||||
<Trash2 size={16} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Fonction pour gérer la demande de signature
|
|
||||||
const handleSignatureRequest = (file) => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', file);
|
|
||||||
console.log('Demande de signature pour le fichier :', file);
|
|
||||||
|
|
||||||
fetch('http://localhost:8080:/DocuSeal/generateToken', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer NFPZy6BBGvYs1BwTuXMQ3XAu5N1kLFiXWftGQhkiz2A',
|
|
||||||
},
|
|
||||||
body: formData,
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Erreur lors du téléversement du document : ' + response.statusText);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
const documentId = data.documentId;
|
|
||||||
console.log('Document téléversé avec succès, ID :', documentId);
|
|
||||||
onUpload(documentId);
|
|
||||||
});
|
|
||||||
.catch((error) => console.error(error));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Modal
|
|
||||||
isOpen={isModalOpen}
|
|
||||||
setIsOpen={setIsModalOpen}
|
|
||||||
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
|
|
||||||
ContentComponent={() => (
|
|
||||||
<FileUpload
|
|
||||||
onFileUpload={handleFileUpload}
|
|
||||||
fileToEdit={fileToEdit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Modal
|
|
||||||
isOpen={isGroupModalOpen}
|
|
||||||
setIsOpen={setIsGroupModalOpen}
|
|
||||||
title={groupToEdit ? "Modifier le groupe" : "Ajouter un groupe de fichiers"}
|
|
||||||
ContentComponent={() => (
|
|
||||||
<RegistrationFileGroupForm
|
|
||||||
onSubmit={handleGroupSubmit}
|
|
||||||
initialData={groupToEdit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="mt-8 mb-4">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h2 className="text-xl font-bold">Groupes de fichiers</h2>
|
|
||||||
<button
|
|
||||||
onClick={() => setIsGroupModalOpen(true)}
|
|
||||||
className="flex items-center bg-blue-600 text-white p-2 rounded-full shadow hover:bg-blue-900 transition duration-200"
|
|
||||||
>
|
|
||||||
<FolderPlus className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Table
|
|
||||||
data={groups}
|
|
||||||
columns={columnsGroups}
|
|
||||||
itemsPerPage={5}
|
|
||||||
currentPage={1}
|
|
||||||
totalPages={Math.ceil(groups.length / 5)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{groups.length > 0 && (
|
|
||||||
<div className="mt-8">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h2 className="text-xl font-bold">Fichiers</h2>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<select
|
|
||||||
className="border rounded p-2"
|
|
||||||
value={selectedGroup || ''}
|
|
||||||
onChange={(e) => setSelectedGroup(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="">Tous les groupes</option>
|
|
||||||
{groups.map(group => (
|
|
||||||
<option key={group.id} value={group.id}>{group.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
|
|
||||||
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
|
|
||||||
>
|
|
||||||
<Plus className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Table
|
|
||||||
data={filteredFiles}
|
|
||||||
columns={columnsFiles}
|
|
||||||
itemsPerPage={10}
|
|
||||||
currentPage={1}
|
|
||||||
totalPages={Math.ceil(filteredFiles.length / 10)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
246
Front-End/src/components/Structure/Files/ParentFilesSection.js
Normal file
246
Front-End/src/components/Structure/Files/ParentFilesSection.js
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Plus, Edit3, Trash2, Check, X, FileText } from 'lucide-react';
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import InputText from '@/components/InputText';
|
||||||
|
import MultiSelect from '@/components/MultiSelect';
|
||||||
|
import Popup from '@/components/Popup';
|
||||||
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
|
export default function ParentFilesSection({ parentFiles, groups, handleCreate, handleEdit, handleDelete }) {
|
||||||
|
const [editingDocumentId, setEditingDocumentId] = useState(null);
|
||||||
|
const [formData, setFormData] = useState(null);
|
||||||
|
const [selectedGroups, setSelectedGroups] = useState([]); // Gestion des groupes sélectionnés
|
||||||
|
|
||||||
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
|
const [popupMessage, setPopupMessage] = useState("");
|
||||||
|
const [removePopupVisible, setRemovePopupVisible] = useState(false);
|
||||||
|
const [removePopupMessage, setRemovePopupMessage] = useState("");
|
||||||
|
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
||||||
|
|
||||||
|
const handleAddEmptyRequiredDocument = () => {
|
||||||
|
setEditingDocumentId('new');
|
||||||
|
setFormData({ name: '', description: '', groups: [] });
|
||||||
|
setSelectedGroups([]); // Réinitialiser les groupes sélectionnés
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditDocument = (document) => {
|
||||||
|
setEditingDocumentId(document.id);
|
||||||
|
setFormData(document);
|
||||||
|
const initialSelectedGroups = document.groups.map((groupId) =>
|
||||||
|
groups.find((group) => group.id === groupId)
|
||||||
|
);
|
||||||
|
setSelectedGroups(initialSelectedGroups);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveDocument = () => {
|
||||||
|
if (!formData.name) {
|
||||||
|
alert('Le nom de la pièce est requis.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedFormData = {
|
||||||
|
...formData,
|
||||||
|
groups: selectedGroups.map((group) => group.id),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (editingDocumentId === 'new') {
|
||||||
|
handleCreate(updatedFormData).then(() => {
|
||||||
|
setEditingDocumentId(null);
|
||||||
|
setFormData(null);
|
||||||
|
setSelectedGroups([]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleEdit(editingDocumentId, updatedFormData).then(() => {
|
||||||
|
setEditingDocumentId(null);
|
||||||
|
setFormData(null);
|
||||||
|
setSelectedGroups([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveDocument = (id) => {
|
||||||
|
return handleDelete(id)
|
||||||
|
.then(() => {
|
||||||
|
setEditingDocumentId(null);
|
||||||
|
setFormData(null);
|
||||||
|
setSelectedGroups([]);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelEdit = () => {
|
||||||
|
setEditingDocumentId(null);
|
||||||
|
setFormData(null);
|
||||||
|
setSelectedGroups([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGroupChange = (selected) => {
|
||||||
|
setSelectedGroups(selected);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRequiredDocumentCell = (document, column) => {
|
||||||
|
const isEditing = editingDocumentId === document.id || (editingDocumentId === 'new' && !document.id);
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
switch (column) {
|
||||||
|
case 'Nom de la pièce':
|
||||||
|
return (
|
||||||
|
<InputText
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
placeholder="Nom de la pièce"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'Description':
|
||||||
|
return (
|
||||||
|
<InputText
|
||||||
|
name="description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
|
placeholder="Description"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'Dossiers d\'inscription':
|
||||||
|
return (
|
||||||
|
<MultiSelect
|
||||||
|
name="groups"
|
||||||
|
label="Sélection de groupes de fichiers"
|
||||||
|
options={groups.map((group) => ({ id: group.id, name: group.name }))}
|
||||||
|
selectedOptions={selectedGroups}
|
||||||
|
onChange={handleGroupChange}
|
||||||
|
errorMsg={null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'Actions':
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center space-x-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleSaveDocument}
|
||||||
|
className="text-green-500 hover:text-green-700"
|
||||||
|
>
|
||||||
|
<Check className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleCancelEdit}
|
||||||
|
className="text-red-500 hover:text-red-700"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (column) {
|
||||||
|
case 'Nom de la pièce':
|
||||||
|
return <span>{document.name}</span>;
|
||||||
|
case 'Description':
|
||||||
|
return <span>{document.description}</span>;
|
||||||
|
case 'Dossiers d\'inscription':
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{document.groups
|
||||||
|
.map((groupId) => groups.find((group) => group.id === groupId)?.name || 'Dossiers d\'inscription inconnu')
|
||||||
|
.join(', ')}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case 'Actions':
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center space-x-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleEditDocument(document)}
|
||||||
|
className="text-blue-500 hover:text-blue-700"
|
||||||
|
>
|
||||||
|
<Edit3 className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setRemovePopupVisible(true);
|
||||||
|
setRemovePopupMessage(
|
||||||
|
`Attentions ! \nVous êtes sur le point de supprimer le document "${document.name}".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?`
|
||||||
|
);
|
||||||
|
setRemovePopupOnConfirm(() => () => {
|
||||||
|
handleRemoveDocument(document.id)
|
||||||
|
.then(() => {
|
||||||
|
setPopupMessage(`Le document "${document.name}" a été correctement supprimé.`);
|
||||||
|
setPopupVisible(true);
|
||||||
|
setRemovePopupVisible(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors de la suppression du document:', error);
|
||||||
|
setPopupMessage(`Erreur lors de la suppression du document "${document.name}".`);
|
||||||
|
setPopupVisible(true);
|
||||||
|
setRemovePopupVisible(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="text-red-500 hover:text-red-700"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columnsRequiredDocuments = [
|
||||||
|
{ name: 'Nom de la pièce', transform: (row) => renderRequiredDocumentCell(row, 'Nom de la pièce') },
|
||||||
|
{ name: 'Description', transform: (row) => renderRequiredDocumentCell(row, 'Description') },
|
||||||
|
{ name: 'Dossiers d\'inscription', transform: (row) => renderRequiredDocumentCell(row, 'Dossiers d\'inscription') },
|
||||||
|
{ name: 'Actions', transform: (row) => renderRequiredDocumentCell(row, 'Actions') },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-8 mb-4 w-4/5">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="bg-emerald-100 p-3 rounded-full shadow-md">
|
||||||
|
<FileText className="w-8 h-8 text-emerald-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800">Pièces à fournir</h2>
|
||||||
|
<p className="text-sm text-gray-500 italic">
|
||||||
|
Configurez la liste des documents que les parents doivent fournir.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleAddEmptyRequiredDocument}
|
||||||
|
className="text-emerald-500 hover:text-emerald-700"
|
||||||
|
>
|
||||||
|
<Plus className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
data={editingDocumentId === 'new' ? [formData, ...parentFiles] : parentFiles}
|
||||||
|
columns={columnsRequiredDocuments}
|
||||||
|
/>
|
||||||
|
<Popup
|
||||||
|
visible={popupVisible}
|
||||||
|
message={popupMessage}
|
||||||
|
onConfirm={() => setPopupVisible(false)}
|
||||||
|
onCancel={() => setPopupVisible(false)}
|
||||||
|
uniqueConfirmButton={true}
|
||||||
|
/>
|
||||||
|
<Popup
|
||||||
|
visible={removePopupVisible}
|
||||||
|
message={removePopupMessage}
|
||||||
|
onConfirm={removePopupOnConfirm}
|
||||||
|
onCancel={() => setRemovePopupVisible(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@ export const BE_SUBSCRIPTION_REGISTERFORMS_URL = `${BASE_URL}/Subscriptions/regi
|
|||||||
export const BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL = `${BASE_URL}/Subscriptions/registrationTemplateMasters`
|
export const BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL = `${BASE_URL}/Subscriptions/registrationTemplateMasters`
|
||||||
export const BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationTemplates`
|
export const BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationTemplates`
|
||||||
export const BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`
|
export const BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`
|
||||||
|
export const BE_SUBSCRIPTION_REGISTRATION_PARENT_FILES_URL = `${BASE_URL}/Subscriptions/registrationParentFiles`
|
||||||
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`
|
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user