feat: Signatures électroniques docuseal [#22]

This commit is contained in:
N3WT DE COMPET
2025-02-28 18:30:18 +01:00
parent 8897d523dc
commit c8c8941ec8
41 changed files with 984 additions and 549 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
DOCUSEAL_API_KEY="LRvUTQCbMSSpManYKshdQk9Do6rBQgjHyPrbGfxU3Jg"

View File

@ -0,0 +1 @@
# This file is intentionally left blank to make this directory a Python package.

View File

@ -0,0 +1,7 @@
from django.urls import path, re_path
from .views import generate_jwt_token, clone_template
urlpatterns = [
re_path(r'generateToken$', generate_jwt_token, name='generate_jwt_token'),
re_path(r'cloneTemplate$', clone_template, name='clone_template'),
]

View File

@ -0,0 +1,81 @@
from django.conf import settings
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
import jwt
import datetime
import requests
@csrf_exempt
@api_view(['POST'])
def generate_jwt_token(request):
# Vérifier la clé API
api_key = request.headers.get('X-Auth-Token')
print(f'api_key : {api_key}')
print(f'settings.DOCUSEAL_JWT["API_KEY"] : {settings.DOCUSEAL_JWT["API_KEY"]}')
if not api_key or api_key != settings.DOCUSEAL_JWT["API_KEY"]:
return Response({'error': 'Invalid API key'}, status=status.HTTP_401_UNAUTHORIZED)
# Récupérer les données de la requête
user_email = request.data.get('user_email')
documents_urls = request.data.get('documents_urls', [])
template_id = request.data.get('template_id') # Récupérer le template_id
# Vérifier les données requises
if not user_email:
return Response({'error': 'User email is required'}, status=status.HTTP_400_BAD_REQUEST)
# Utiliser la configuration JWT de DocuSeal depuis les settings
jwt_secret = settings.DOCUSEAL_JWT['API_KEY']
jwt_algorithm = settings.DOCUSEAL_JWT['ALGORITHM']
expiration_delta = settings.DOCUSEAL_JWT['EXPIRATION_DELTA']
# Définir le payload
payload = {
'user_email': user_email,
'documents_urls': documents_urls,
'template_id': template_id, # Ajouter le template_id au payload
'exp': datetime.datetime.utcnow() + expiration_delta # Temps d'expiration du token
}
# Générer le token JWT
token = jwt.encode(payload, jwt_secret, algorithm=jwt_algorithm)
return Response({'token': token}, status=status.HTTP_200_OK)
@csrf_exempt
@api_view(['POST'])
def clone_template(request):
# Vérifier la clé API
api_key = request.headers.get('X-Auth-Token')
if not api_key or api_key != settings.DOCUSEAL_JWT["API_KEY"]:
return Response({'error': 'Invalid API key'}, status=status.HTTP_401_UNAUTHORIZED)
# Récupérer les données de la requête
document_id = request.data.get('templateId')
email = request.data.get('email')
# Vérifier les données requises
if not document_id or not email :
return Response({'error': 'template ID, email are required'}, status=status.HTTP_400_BAD_REQUEST)
# URL de l'API de DocuSeal pour cloner le template
clone_url = f'https://docuseal.com/api/templates/{document_id}/clone'
# Faire la requête pour cloner le template
try:
response = requests.post(clone_url, json={'submitters': [{'email': email}]}, headers={
'Content-Type': 'application/json',
'X-Auth-Token': settings.DOCUSEAL_JWT['API_KEY']
})
if response.status_code != 200:
return Response({'error': 'Failed to clone template'}, status=response.status_code)
data = response.json()
return Response(data, status=status.HTTP_200_OK)
except requests.RequestException as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

View File

@ -0,0 +1,8 @@
class ContentSecurityPolicyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response['Content-Security-Policy'] = "frame-ancestors 'self' http://localhost:3000"
return response

View File

@ -251,6 +251,12 @@ DOCUMENT_DIR = 'documents'
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_ALL_HEADERS = True
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_HEADERS = [
'content-type',
'authorization',
'X-Auth-Token',
'x-csrftoken'
]
CORS_ALLOWED_ORIGINS = [
os.getenv('CORS_ALLOWED_ORIGINS', 'http://localhost:3000')
@ -333,5 +339,5 @@ DOCUSEAL_JWT = {
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'EXPIRATION_DELTA': timedelta(hours=1),
'API_KEY': '1kzHsXqN8P2ezUGT7TjVuBwM1hqtLsztrVSsQ87T7Mz'
'API_KEY': os.getenv('DOCUSEAL_API_KEY')
}

View File

@ -7,7 +7,7 @@ from Subscriptions.models import (
Fee,
Discount,
RegistrationFileGroup,
RegistrationFileTemplate
# RegistrationFileTemplate
)
from Auth.models import Profile
from School.models import (
@ -43,7 +43,7 @@ class Command(BaseCommand):
self.create_or_update_teachers()
self.create_or_update_school_classes()
self.create_or_update_registration_file_group()
self.create_or_update_registration_file_template()
# self.create_or_update_registration_file_template()
self.create_register_form()
def create_or_update_establishment(self):
@ -376,13 +376,21 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS('SchoolClasses initialized or updated successfully'))
def create_or_update_registration_file_group(self):
group_data = {
group_data_1 = {
"name": "LMDE",
"description": "Fichiers d'inscription de l'école LMDE"
}
self.registration_file_group, created = RegistrationFileGroup.objects.get_or_create(name=group_data["name"], defaults=group_data)
self.stdout.write(self.style.SUCCESS('RegistrationFileGroup initialized or updated successfully'))
self.registration_file_group_1, created = RegistrationFileGroup.objects.get_or_create(name=group_data_1["name"], defaults=group_data_1)
self.stdout.write(self.style.SUCCESS('RegistrationFileGroup 1 initialized or updated successfully'))
group_data_2 = {
"name": "LMDE 2",
"description": "Fichiers d'inscription de l'école LMDE 2"
}
self.registration_file_group_2, created = RegistrationFileGroup.objects.get_or_create(name=group_data_2["name"], defaults=group_data_2)
self.stdout.write(self.style.SUCCESS('RegistrationFileGroup 2 initialized or updated successfully'))
def create_or_update_registration_file_template(self):
script_dir = os.path.dirname(os.path.abspath(__file__))
@ -394,28 +402,28 @@ class Command(BaseCommand):
"file": "RIB LA MAISON DES ENFANTS.pdf",
"order": 0,
"is_required": False,
"group": self.registration_file_group
"group": self.registration_file_group_2 # Associer ce fichier au deuxième groupe
},
{
"name": "Contrat d'engagement 2024 2025",
"file": "Contrat d'engagement 2024 2025.pdf",
"order": 0,
"is_required": True,
"group": self.registration_file_group
"group": self.registration_file_group_1
},
{
"name": "Bulletin d'adhésion familiale scolaire",
"file": "Bulletin d'adhésion familiale scolaire.pdf",
"order": 0,
"is_required": True,
"group": self.registration_file_group
"group": self.registration_file_group_1
},
{
"name": "Fiche sanitaire de liaison",
"file": "Fiche sanitaire de liaison.pdf",
"order": 0,
"is_required": True,
"group": self.registration_file_group
"group": self.registration_file_group_1
}
]
@ -442,69 +450,79 @@ class Command(BaseCommand):
def create_register_form(self):
# Créer ou mettre à jour le profil associé au guardian
profile_data = {
"email": "anthony.casini.30@gmail.com",
"droit": 2,
"username": "anthony.casini.30@gmail.com",
"is_active": True,
"password": "Provisoire01!"
}
user, created = Profile.objects.update_or_create(
email=profile_data["email"],
defaults={
"username": profile_data["username"],
"email": profile_data["email"],
"is_active": profile_data["is_active"],
"droit": profile_data["droit"]
profiles_data = [
{
"email": "anthony.casini.30@gmail.com",
"droit": 2,
"username": "anthony.casini.30@gmail.com",
"is_active": True,
"password": "Provisoire01!"
},
{
"email": "anthony.audrey.34@gmail.com",
"droit": 2,
"username": "anthony.audrey.34@gmail.com",
"is_active": True,
"password": "Provisoire01!"
}
)
if created:
user.set_password(profile_data["password"])
user.save()
]
# Créer les données du guardian
guardian_data = {
"associated_profile_id": user.id,
"email": "anthony.casini.30@gmail.com",
}
for profile_data in profiles_data:
user, created = Profile.objects.update_or_create(
email=profile_data["email"],
defaults={
"username": profile_data["username"],
"email": profile_data["email"],
"is_active": profile_data["is_active"],
"droit": profile_data["droit"]
}
)
if created:
user.set_password(profile_data["password"])
user.save()
# Créer les données de l'étudiant
student_data = {
"last_name": "CASINI",
"first_name": "Giulia",
}
# Créer les données du guardian
guardian_data = {
"associated_profile_id": user.id,
"email": profile_data["email"],
}
# Créer ou mettre à jour l'étudiant et le guardian
student, created = Student.objects.get_or_create(
last_name=student_data["last_name"],
first_name=student_data["first_name"],
defaults=student_data
)
guardian, created = Guardian.objects.get_or_create(
last_name=guardian_data["email"],
defaults=guardian_data
)
student.guardians.add(guardian)
# Créer les données de l'étudiant
student_data = {
"last_name": f'lastname_{user.id}',
"first_name": f'firstname_{user.id}',
}
# Récupérer les frais et les réductions
fees = Fee.objects.filter(id__in=[1, 2, 3, 4])
discounts = Discount.objects.filter(id__in=[1])
# Créer ou mettre à jour l'étudiant et le guardian
student, created = Student.objects.get_or_create(
last_name=student_data["last_name"],
first_name=student_data["first_name"],
defaults=student_data
)
guardian, created = Guardian.objects.get_or_create(
last_name=guardian_data["email"],
defaults=guardian_data
)
student.guardians.add(guardian)
# Créer les données du formulaire d'inscription
register_form_data = {
"student": student,
"fileGroup": self.registration_file_group,
"establishment": Establishment.objects.get(id=1),
"status": 1
}
# Récupérer les frais et les réductions
fees = Fee.objects.filter(id__in=[1, 2, 3, 4])
discounts = Discount.objects.filter(id__in=[1])
# Créer ou mettre à jour le formulaire d'inscription
register_form, created = RegistrationForm.objects.get_or_create(student=student, defaults=register_form_data)
register_form.fees.set(fees)
register_form.discounts.set(discounts)
if not created:
register_form.fileGroup = self.registration_file_group
register_form.save()
# Créer les données du formulaire d'inscription
register_form_data = {
"student": student,
"fileGroup": self.registration_file_group_1,
"establishment": Establishment.objects.get(id=1),
"status": 1
}
# Créer ou mettre à jour le formulaire d'inscription
register_form, created = RegistrationForm.objects.get_or_create(student=student, defaults=register_form_data)
register_form.fees.set(fees)
register_form.discounts.set(discounts)
if not created:
register_form.fileGroup = self.registration_file_group_1
register_form.save()
self.stdout.write(self.style.SUCCESS('RegistrationForm initialized or updated successfully'))

View File

@ -89,7 +89,7 @@ class Student(models.Model):
siblings = models.ManyToManyField(Sibling, blank=True)
# Many-to-Many Relationship
registration_files = models.ManyToManyField('RegistrationFile', blank=True, related_name='students')
registration_files = models.ManyToManyField('RegistrationTemplate', blank=True, related_name='students')
# Many-to-Many Relationship
spoken_languages = models.ManyToManyField(Language, blank=True)
@ -162,7 +162,7 @@ class Student(models.Model):
return None
class RegistrationFileGroup(models.Model):
name = models.CharField(max_length=255)
name = models.CharField(max_length=255, default="")
description = models.TextField(blank=True, null=True)
def __str__(self):
@ -172,10 +172,35 @@ def registration_file_path(instance, filename):
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
class RegistrationTemplateMaster(models.Model):
groups = models.ManyToManyField(RegistrationFileGroup, related_name='template_masters')
template_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255, default="")
def __str__(self):
return f'{self.group.name} - {self.template_id}'
class RegistrationTemplate(models.Model):
master = models.ForeignKey(RegistrationTemplateMaster, on_delete=models.CASCADE, related_name='templates')
template_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255, default="")
registration_form = models.ForeignKey('RegistrationForm', on_delete=models.CASCADE, related_name='templates')
def __str__(self):
return self.name
@staticmethod
def get_files_from_rf(register_form_id):
"""
Récupère tous les fichiers liés à un dossier dinscription donné.
"""
registration_files = RegistrationTemplate.objects.filter(register_form_id=register_form_id).order_by('template__order')
filenames = []
for reg_file in registration_files:
filenames.append(reg_file.file.path)
return filenames
class RegistrationForm(models.Model):
"""
Gère le dossier dinscription lié à un élève donné.
"""
class RegistrationFormStatus(models.IntegerChoices):
RF_ABSENT = 0, _('Pas de dossier d\'inscription')
RF_CREATED = 1, _('Dossier d\'inscription créé')
@ -205,7 +230,7 @@ class RegistrationForm(models.Model):
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
fileGroup = models.ForeignKey(RegistrationFileGroup,
on_delete=models.CASCADE,
related_name='file_group',
related_name='register_forms',
null=True,
blank=True)
@ -213,56 +238,3 @@ class RegistrationForm(models.Model):
def __str__(self):
return "RF_" + self.student.last_name + "_" + self.student.first_name
class RegistrationFileTemplate(models.Model):
"""
Modèle pour stocker les fichiers "templates" dinscription.
"""
name = models.CharField(max_length=255)
file = models.FileField(upload_to='templates_files/', blank=True, null=True)
order = models.PositiveIntegerField(default=0) # Ajout du champ order
date_added = models.DateTimeField(auto_now_add=True)
is_required = models.BooleanField(default=False)
group = models.ForeignKey(RegistrationFileGroup, on_delete=models.CASCADE, related_name='file_templates', null=True, blank=True)
@property
def formatted_date_added(self):
if self.date_added:
return self.date_added.strftime('%d-%m-%Y')
return None
def __str__(self):
return self.name
def registration_file_upload_to(instance, filename):
return f"registration_files/dossier_rf_{instance.register_form.pk}/{filename}"
class RegistrationFile(models.Model):
"""
Fichier lié à un dossier dinscription particulier.
"""
name = models.CharField(max_length=255)
file = models.FileField(upload_to=registration_file_upload_to)
date_added = models.DateTimeField(auto_now_add=True)
template = models.OneToOneField(RegistrationFileTemplate, on_delete=models.CASCADE)
register_form = models.ForeignKey('RegistrationForm', on_delete=models.CASCADE, related_name='registration_files')
@property
def formatted_date_added(self):
if self.date_added:
return self.date_added.strftime('%d-%m-%Y')
return None
def __str__(self):
return self.name
@staticmethod
def get_files_from_rf(register_form_id):
"""
Récupère tous les fichiers liés à un dossier dinscription donné.
"""
registration_files = RegistrationFile.objects.filter(register_form_id=register_form_id).order_by('template__order')
filenames = []
for reg_file in registration_files:
filenames.append(reg_file.file.path)
return filenames

View File

@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import RegistrationFileTemplate, RegistrationFile, RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language
from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationTemplateMaster, RegistrationTemplate
from School.models import SchoolClass, Fee, Discount, FeeType
from School.serializers import FeeSerializer, DiscountSerializer
from Auth.models import Profile
@ -11,20 +11,38 @@ from django.utils import timezone
import pytz
from datetime import datetime
class RegistrationTemplateMasterSerializer(serializers.ModelSerializer):
class Meta:
model = RegistrationTemplateMaster
fields = '__all__'
class RegistrationTemplateSerializer(serializers.ModelSerializer):
class Meta:
model = RegistrationTemplate
fields = '__all__'
class GuardianSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Guardian
fields = ['id', 'email']
class RegistrationFormSimpleSerializer(serializers.ModelSerializer):
guardians = GuardianSimpleSerializer(many=True, source='student.guardians')
class Meta:
model = RegistrationForm
fields = ['student_id', 'guardians']
class RegistrationFileGroupSerializer(serializers.ModelSerializer):
registration_forms = serializers.SerializerMethodField()
class Meta:
model = RegistrationFileGroup
fields = '__all__'
class RegistrationFileSerializer(serializers.ModelSerializer):
class Meta:
model = RegistrationFile
fields = '__all__'
class RegistrationFileTemplateSerializer(serializers.ModelSerializer):
class Meta:
model = RegistrationFileTemplate
fields = '__all__'
def get_registration_forms(self, obj):
forms = RegistrationForm.objects.filter(fileGroup=obj)
return RegistrationFormSimpleSerializer(forms, many=True).data
class LanguageSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
@ -132,7 +150,7 @@ class RegistrationFormSerializer(serializers.ModelSerializer):
registration_file = serializers.FileField(required=False)
status_label = serializers.SerializerMethodField()
formatted_last_update = serializers.SerializerMethodField()
registration_files = RegistrationFileSerializer(many=True, required=False)
registration_files = RegistrationTemplateSerializer(many=True, required=False)
fees = serializers.PrimaryKeyRelatedField(queryset=Fee.objects.all(), many=True, required=False)
discounts = serializers.PrimaryKeyRelatedField(queryset=Discount.objects.all(), many=True, required=False)
totalRegistrationFees = serializers.SerializerMethodField()

View File

@ -7,8 +7,9 @@ from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archi
# SubClasses
from .views import StudentView, GuardianView, ChildrenListView, StudentListView
# Files
from .views import RegistrationFileTemplateView, RegistrationFileTemplateSimpleView, RegistrationFileView, RegistrationFileSimpleView
from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
from .views import registration_file_views
urlpatterns = [
re_path(r'^registerForms/(?P<id>[0-9]+)/archive$', archive, name="archive"),
@ -27,15 +28,13 @@ urlpatterns = [
# Page de formulaire d'inscription - RESPONSABLE
re_path(r'^lastGuardianId$', GuardianView.as_view(), name="lastGuardianId"),
# modèles de fichiers d'inscription
re_path(r'^registrationFileTemplates/(?P<id>[0-9]+)$', RegistrationFileTemplateSimpleView.as_view(), name="registrationFileTemplate"),
re_path(r'^registrationFileTemplates$', RegistrationFileTemplateView.as_view(), name='registrationFileTemplates'),
# fichiers d'inscription
re_path(r'^registrationFiles/(?P<id>[0-9]+)$', RegistrationFileSimpleView.as_view(), name='registrationFiles'),
re_path(r'^registrationFiles$', RegistrationFileView.as_view(), name="registrationFiles"),
re_path(r'^registrationFileGroups/(?P<id>[0-9]+)$', RegistrationFileGroupSimpleView.as_view(), name='registrationFileGroupDetail'),
re_path(r'^registrationFileGroups/(?P<id>[0-9]+)/registrationFiles$', get_registration_files_by_group, name="get_registration_files_by_group"),
re_path(r'^registrationFileGroups$', RegistrationFileGroupView.as_view(), name='registrationFileGroups'),
re_path(r'^registrationTemplateMasters/(?P<id>[0-9]+)$', RegistrationTemplateMasterSimpleView.as_view(), name='registrationTemplateMasters'),
re_path(r'^registrationTemplateMasters$', RegistrationTemplateMasterView.as_view(), name='registrationTemplateMasters'),
re_path(r'^registrationTemplates/(?P<id>[0-9]+)$', RegistrationTemplateSimpleView.as_view(), name='registrationTemplates'),
re_path(r'^registrationTemplates$', RegistrationTemplateView.as_view(), name="registrationTemplates"),
]

View File

@ -1,5 +1,5 @@
from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive
from .registration_file_views import RegistrationFileTemplateView, RegistrationFileTemplateSimpleView, RegistrationFileView, RegistrationFileSimpleView
from .registration_file_views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
from .student_views import StudentView, StudentListView, ChildrenListView
from .guardian_views import GuardianView
@ -10,10 +10,10 @@ __all__ = [
'send',
'resend',
'archive',
'RegistrationFileView',
'RegistrationFileSimpleView',
'RegistrationFileTemplateView',
'RegistrationFileTemplateSimpleView',
'RegistrationTemplateView',
'RegistrationTemplateSimpleView',
'RegistrationTemplateMasterView',
'RegistrationTemplateMasterSimpleView',
'RegistrationFileGroupView',
'RegistrationFileGroupSimpleView',
'get_registration_files_by_group',

View File

@ -19,7 +19,7 @@ import Subscriptions.util as util
from Subscriptions.serializers import RegistrationFormSerializer
from Subscriptions.pagination import CustomPagination
from Subscriptions.signals import clear_cache
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationFile, RegistrationFileGroup
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationTemplate, RegistrationFileGroup
from Subscriptions.automate import updateStateMachine
from N3wtSchool import settings, bdd
@ -252,7 +252,7 @@ class RegisterFormWithIdView(APIView):
registerForm.save()
# Récupération des fichiers d'inscription
fileNames = RegistrationFile.get_files_from_rf(registerForm.pk)
fileNames = RegistrationTemplate.get_files_from_rf(registerForm.pk)
if registerForm.registration_file:
fileNames.insert(0, registerForm.registration_file.path)

View File

@ -8,7 +8,7 @@ from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from Subscriptions.serializers import RegistrationFileGroupSerializer
from Subscriptions.models import RegistrationFileGroup, RegistrationFileTemplate
from Subscriptions.models import RegistrationFileGroup, RegistrationTemplateMaster
from N3wtSchool import bdd
class RegistrationFileGroupView(APIView):
@ -118,7 +118,7 @@ class RegistrationFileGroupSimpleView(APIView):
def get_registration_files_by_group(request, id):
try:
group = RegistrationFileGroup.objects.get(id=id)
templates = RegistrationFileTemplate.objects.filter(group=group)
templates = RegistrationTemplateMaster.objects.filter(group=group)
templates_data = list(templates.values())
return JsonResponse(templates_data, safe=False)
except RegistrationFileGroup.DoesNotExist:

View File

@ -1,5 +1,4 @@
from django.http.response import JsonResponse
from django.core.files import File
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from rest_framework.parsers import MultiPartParser, FormParser
@ -7,205 +6,154 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
import os
from Subscriptions.serializers import RegistrationFileTemplateSerializer, RegistrationFileSerializer
from Subscriptions.models import RegistrationFileTemplate, RegistrationFile
from Subscriptions.serializers import RegistrationTemplateMasterSerializer, RegistrationTemplateSerializer
from Subscriptions.models import RegistrationTemplateMaster, RegistrationTemplate
from N3wtSchool import bdd
class RegistrationFileTemplateView(APIView):
class RegistrationTemplateMasterView(APIView):
@swagger_auto_schema(
operation_description="Récupère tous les fichiers templates pour les dossiers d'inscription",
responses={200: RegistrationFileTemplateSerializer(many=True)}
operation_description="Récupère tous les masters de templates d'inscription",
responses={200: RegistrationTemplateMasterSerializer(many=True)}
)
def get(self, request):
"""
Récupère les fichiers templates pour les dossiers dinscription.
"""
files = RegistrationFileTemplate.objects.all()
serializer = RegistrationFileTemplateSerializer(files, many=True)
masters = RegistrationTemplateMaster.objects.all()
serializer = RegistrationTemplateMasterSerializer(masters, many=True)
return Response(serializer.data)
@swagger_auto_schema(
operation_description="Crée un nouveau fichier template pour les dossiers d'inscription",
request_body=RegistrationFileTemplateSerializer,
operation_description="Crée un nouveau master de template d'inscription",
request_body=RegistrationTemplateMasterSerializer,
responses={
201: RegistrationFileTemplateSerializer,
201: RegistrationTemplateMasterSerializer,
400: "Données invalides"
}
)
def post(self, request):
"""
Crée un fichier template pour les dossiers dinscription.
"""
serializer = RegistrationFileTemplateSerializer(data=request.data)
serializer = RegistrationTemplateMasterSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class RegistrationFileTemplateSimpleView(APIView):
"""
Gère les fichiers templates pour les dossiers dinscription.
"""
parser_classes = (MultiPartParser, FormParser)
class RegistrationTemplateMasterSimpleView(APIView):
@swagger_auto_schema(
operation_description="Récupère un fichier template spécifique",
operation_description="Récupère un master de template d'inscription spécifique",
responses={
200: RegistrationFileTemplateSerializer,
404: "Fichier template non trouvé"
200: RegistrationTemplateMasterSerializer,
404: "Master non trouvé"
}
)
def get(self, request, id):
"""
Récupère les fichiers templates pour les dossiers dinscription.
"""
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
if registationFileTemplate is None:
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileTemplateSerializer(registationFileTemplate)
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
if master is None:
return JsonResponse({"errorMessage":'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationTemplateMasterSerializer(master)
return JsonResponse(serializer.data, safe=False)
@swagger_auto_schema(
operation_description="Met à jour un fichier template existant",
request_body=RegistrationFileTemplateSerializer,
operation_description="Met à jour un master de template d'inscription existant",
request_body=RegistrationTemplateMasterSerializer,
responses={
201: RegistrationFileTemplateSerializer,
200: RegistrationTemplateMasterSerializer,
400: "Données invalides",
404: "Fichier template non trouvé"
404: "Master non trouvé"
}
)
def put(self, request, id):
"""
Met à jour un fichier template existant.
"""
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
if registationFileTemplate is None:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileTemplateSerializer(registationFileTemplate,data=request.data)
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
if master is None:
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationTemplateMasterSerializer(master, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
operation_description="Supprime un fichier template",
operation_description="Supprime un master de template d'inscription",
responses={
204: "Suppression réussie",
404: "Fichier template non trouvé"
404: "Master non trouvé"
}
)
def delete(self, request, id):
"""
Supprime un fichier template existant.
"""
registrationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
if registrationFileTemplate is not None:
registrationFileTemplate.file.delete() # Supprimer le fichier uploadé
registrationFileTemplate.delete()
return JsonResponse({'message': 'La suppression du fichier d\'inscription a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
if master is not None:
master.delete()
return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
else:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
class RegistrationFileView(APIView):
class RegistrationTemplateView(APIView):
@swagger_auto_schema(
operation_description="Récupère tous les fichiers d'inscription",
responses={200: RegistrationFileSerializer(many=True)}
operation_description="Récupère tous les templates d'inscription",
responses={200: RegistrationTemplateSerializer(many=True)}
)
def get(self, request):
"""
Récupère les fichiers liés à un dossier dinscription donné.
"""
files = RegistrationFile.objects.all()
serializer = RegistrationFileSerializer(files, many=True)
templates = RegistrationTemplate.objects.all()
serializer = RegistrationTemplateSerializer(templates, many=True)
return Response(serializer.data)
@swagger_auto_schema(
operation_description="Crée un nouveau fichier d'inscription",
request_body=RegistrationFileSerializer,
operation_description="Crée un nouveau template d'inscription",
request_body=RegistrationTemplateSerializer,
responses={
201: RegistrationFileSerializer,
201: RegistrationTemplateSerializer,
400: "Données invalides"
}
)
def post(self, request):
"""
Crée un RegistrationFile pour le RegistrationForm associé.
"""
serializer = RegistrationFileSerializer(data=request.data)
serializer = RegistrationTemplateSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class RegistrationFileSimpleView(APIView):
"""
Gère la création, mise à jour et suppression de fichiers liés à un dossier dinscription.
"""
parser_classes = (MultiPartParser, FormParser)
class RegistrationTemplateSimpleView(APIView):
@swagger_auto_schema(
operation_description="Récupère un fichier d'inscription spécifique",
operation_description="Récupère un template d'inscription spécifique",
responses={
200: RegistrationFileSerializer,
404: "Fichier non trouvé"
200: RegistrationTemplateSerializer,
404: "Template non trouvé"
}
)
def get(self, request, id):
"""
Récupère les fichiers liés à un dossier dinscription donné.
"""
registationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
if registationFile is None:
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileSerializer(registationFile)
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
if template is None:
return JsonResponse({"errorMessage":'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationTemplateSerializer(template)
return JsonResponse(serializer.data, safe=False)
@swagger_auto_schema(
operation_description="Met à jour un fichier d'inscription existant",
request_body=RegistrationFileSerializer,
operation_description="Met à jour un template d'inscription existant",
request_body=RegistrationTemplateSerializer,
responses={
200: openapi.Response(
description="Fichier mis à jour avec succès",
schema=RegistrationFileSerializer
),
200: RegistrationTemplateSerializer,
400: "Données invalides",
404: "Fichier non trouvé"
404: "Template non trouvé"
}
)
def put(self, request, id):
"""
Met à jour un RegistrationFile existant.
"""
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
if registrationFile is None:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileSerializer(registrationFile, data=request.data)
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
if template is None:
return JsonResponse({'erreur': 'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationTemplateSerializer(template, data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'message': 'Fichier mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
return Response({'message': 'Template mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
operation_description="Supprime un fichier d'inscription",
operation_description="Supprime un template d'inscription",
responses={
200: "Suppression réussie",
404: "Fichier non trouvé"
204: "Suppression réussie",
404: "Template non trouvé"
}
)
def delete(self, request, id):
"""
Supprime un RegistrationFile existant.
"""
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
if registrationFile is not None:
registrationFile.file.delete() # Supprimer le fichier uploadé
registrationFile.delete()
return JsonResponse({'message': 'La suppression du fichier a été effectuée avec succès'}, safe=False)
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
if template is not None:
template.delete()
return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
else:
return JsonResponse({'erreur': 'Le fichier n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
return JsonResponse({'erreur': 'Le template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)

View File

@ -2,3 +2,4 @@ NEXT_PUBLIC_API_URL=http://localhost:8080
NEXT_PUBLIC_USE_FAKE_DATA='false'
AUTH_SECRET='false'
NEXTAUTH_URL=http://localhost:3000
DOCUSEAL_API_KEY="LRvUTQCbMSSpManYKshdQk9Do6rBQgjHyPrbGfxU3Jg"

View File

@ -1,6 +0,0 @@
{
"i18n-ally.localesPaths": [
"messages"
],
"i18n-ally.keystyle": "nested"
}

View File

@ -22,8 +22,8 @@ import { createDatas,
fetchRegistrationPaymentModes,
fetchTuitionPaymentModes } from '@/app/actions/schoolAction';
import SidebarTabs from '@/components/SidebarTabs';
import FilesManagement from '@/components/Structure/Files/FilesManagement';
import { fetchRegisterFormFileTemplate } from '@/app/actions/subscriptionAction';
import FilesGroupsManagement from '@/components/Structure/Files/FilesGroupsManagement';
import { fetchRegistrationTemplateMaster } from '@/app/actions/subscriptionAction';
import logger from '@/utils/logger';
@ -70,7 +70,7 @@ export default function Page() {
handleTuitionFees();
// Fetch data for registration file templates
fetchRegisterFormFileTemplate()
fetchRegistrationTemplateMaster()
.then((data)=> {
setFichiers(data)
})
@ -301,7 +301,7 @@ export default function Page() {
{
id: 'Files',
label: 'Documents d\'inscription',
content: <FilesManagement csrfToken={csrfToken} />
content: <FilesGroupsManagement csrfToken={csrfToken} />
}
];

View File

@ -24,7 +24,7 @@ import {
createRegisterForm,
sendRegisterForm,
archiveRegisterForm,
fetchRegisterFormFileTemplate,
fetchRegistrationTemplateMaster,
fetchStudents,
editRegisterForm } from "@/app/actions/subscriptionAction"
@ -195,7 +195,7 @@ useEffect(() => {
fetchRegisterForms(ARCHIVED)
.then(registerFormArchivedDataHandler)
.catch(requestErrorHandler)
fetchRegisterFormFileTemplate()
fetchRegistrationTemplateMaster()
.then((data)=> {
logger.debug(data);
@ -254,7 +254,7 @@ useEffect(() => {
fetchRegisterForms(ARCHIVED)
.then(registerFormArchivedDataHandler)
.catch(requestErrorHandler)
fetchRegisterFormFileTemplate()
fetchRegistrationTemplateMaster()
.then((data)=> {setFichiers(data)})
.catch((err)=>{ err = err.message; logger.debug(err);});
} else {

View File

@ -6,12 +6,13 @@ import { useRouter } from 'next/navigation'; // Ajout de l'importation
import { User, MessageSquare, LogOut, Settings, Home } from 'lucide-react'; // Ajout de l'importation de l'icône Home
import Logo from '@/components/Logo'; // Ajout de l'importation du composant Logo
import { FE_PARENTS_HOME_URL,FE_PARENTS_MESSAGERIE_URL,FE_PARENTS_SETTINGS_URL } from '@/utils/Url'; // Ajout de l'importation de l'URL de la page d'accueil parent
import useLocalStorage from '@/hooks/useLocalStorage';
import { fetchMessages } from '@/app/actions/messagerieAction';
import ProtectedRoute from '@/components/ProtectedRoute';
import { disconnect } from '@/app/actions/authAction';
import Popup from '@/components/Popup';
import logger from '@/utils/logger';
import { useSession } from 'next-auth/react';
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
export default function Layout({
children,
@ -19,7 +20,8 @@ export default function Layout({
const router = useRouter(); // Définition de router
const [messages, setMessages] = useState([]);
const [userId, setUserId] = useLocalStorage("userId", '') ;
const { data: session, status } = useSession();
const [userId, setUserId] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isPopupVisible, setIsPopupVisible] = useState(false);
@ -32,27 +34,33 @@ export default function Layout({
disconnect();
};
useEffect(() => {
setIsLoading(true);
setUserId(userId)
fetchMessages(userId)
.then(data => {
if (data) {
setMessages(data);
}
logger.debug('Success :', data);
})
.catch(error => {
logger.error('Error fetching data:', error);
})
.finally(() => {
setIsLoading(false);
});
}, [userId]);
// useEffect(() => {
// if (status === 'loading') return;
// if (!session) {
// router.push(`${FE_USERS_LOGIN_URL}`);
// }
if (isLoading) {
return <div>Loading...</div>;
}
// const userIdFromSession = session.user.id;
// setUserId(userIdFromSession);
// setIsLoading(true);
// fetchMessages(userId)
// .then(data => {
// if (data) {
// setMessages(data);
// }
// logger.debug('Success :', data);
// })
// .catch(error => {
// logger.error('Error fetching data:', error);
// })
// .finally(() => {
// setIsLoading(false);
// });
// }, [userId]);
// if (isLoading) {
// return <div>Loading...</div>;
// }
return (
<ProtectedRoute>

View File

@ -4,22 +4,31 @@ import { useRouter } from 'next/navigation';
import Table from '@/components/Table';
import { Edit } from 'lucide-react';
import StatusLabel from '@/components/StatusLabel';
import useLocalStorage from '@/hooks/useLocalStorage';
import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url';
import { fetchChildren } from '@/app/actions/subscriptionAction';
import logger from '@/utils/logger';
import { useSession } from 'next-auth/react';
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
export default function ParentHomePage() {
const [actions, setActions] = useState([]);
const [children, setChildren] = useState([]);
const [userId, setUserId] = useLocalStorage("userId", '') ;
const { data: session, status } = useSession();
const [userId, setUserId] = useState(null);
const router = useRouter();
useEffect(() => {
if (!userId) return;
if (status === 'loading') return;
fetchChildren(userId).then(data => {
if (!session) {
router.push(`${FE_USERS_LOGIN_URL}`);
}
console.log(session);
const userIdFromSession = session.user.user_id;
setUserId(userIdFromSession);
fetchChildren(userIdFromSession).then(data => {
setChildren(data);
});
}, [userId]);

View File

@ -13,10 +13,10 @@ import {
FE_PARENTS_HOME_URL
} from '@/utils/Url';
import { login } from '@/app/actions/authAction';
import useLocalStorage from '@/hooks/useLocalStorage';
import { getSession } from 'next-auth/react';
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
import logger from '@/utils/logger';
import { useSession } from 'next-auth/react';
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
@ -28,8 +28,6 @@ export default function Page() {
const [isLoading, setIsLoading] = useState(false);
const [userId, setUserId] = useLocalStorage("userId", '') ;
const router = useRouter();
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
@ -55,7 +53,6 @@ export default function Page() {
}
const user = session.user;
logger.debug('User Session:', user);
localStorage.setItem('userId', user.id); // Stocker l'identifiant de l'utilisateur
if (user.droit === 0) {
// Vue ECOLE
} else if (user.droit === 1) {

View File

@ -2,9 +2,9 @@ import {
BE_SUBSCRIPTION_STUDENTS_URL,
BE_SUBSCRIPTION_CHILDRENS_URL,
BE_SUBSCRIPTION_REGISTERFORMS_URL,
BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL,
BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL,
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL,
BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL
BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL
} from '@/utils/Url';
export const PENDING = 'pending';
@ -101,10 +101,10 @@ export const archiveRegisterForm = (id) => {
}).then(requestResponseHandler)
}
export const fetchRegisterFormFile = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}`
export const fetchRegistrationTemplates = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`
if (id) {
url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${id}`;
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${id}`;
}
const request = new Request(
`${url}`,
@ -118,8 +118,8 @@ export const fetchRegisterFormFile = (id = null) => {
return fetch(request).then(requestResponseHandler)
};
export const editRegistrationFormFile= (fileId, data, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${fileId}`, {
export const editRegistrationTemplates= (fileId, data, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
method: 'PUT',
body: data,
headers: {
@ -130,21 +130,22 @@ export const editRegistrationFormFile= (fileId, data, csrfToken) => {
.then(requestResponseHandler)
}
export const createRegistrationFormFile = (data,csrfToken) => {
export const createRegistrationTemplates = (data,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}`, {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`, {
method: 'POST',
body: data,
body: JSON.stringify(data),
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
credentials: 'include',
})
.then(requestResponseHandler)
}
export const deleteRegisterFormFile= (fileId,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${fileId}`, {
export const deleteRegistrationTemplates= (fileId,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
@ -153,10 +154,10 @@ export const deleteRegisterFormFile= (fileId,csrfToken) => {
})
}
export const fetchRegisterFormFileTemplate = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}`;
export const fetchRegistrationTemplateMaster = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`;
if(id){
url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${id}`;
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${id}`;
}
const request = new Request(
`${url}`,
@ -170,21 +171,22 @@ export const fetchRegisterFormFileTemplate = (id = null) => {
return fetch(request).then(requestResponseHandler)
};
export const createRegistrationFormFileTemplate = (data,csrfToken) => {
export const createRegistrationTemplateMaster = (data,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}`, {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`, {
method: 'POST',
body: data,
body: JSON.stringify(data),
headers: {
'X-CSRFToken': csrfToken,
'Content-Type':'application/json'
},
credentials: 'include',
})
.then(requestResponseHandler)
}
export const deleteRegisterFormFileTemplate = (fileId,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${fileId}`, {
export const deleteRegistrationTemplateMaster = (fileId,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
@ -193,8 +195,8 @@ export const deleteRegisterFormFileTemplate = (fileId,csrfToken) => {
})
}
export const editRegistrationFormFileTemplate = (fileId, data, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${fileId}`, {
export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, {
method: 'PUT',
body: data,
headers: {

View File

@ -0,0 +1,17 @@
import React, { useEffect } from 'react';
import { DocusealBuilder as OriginalDocusealBuilder } from '@docuseal/react';
const DocusealBuilder = ({ onSave, onSend, ...props }) => {
useEffect(() => {
if (onSave) {
props.save = onSave;
}
if (onSend) {
props.send = onSend;
}
}, [onSave, onSend, props]);
return <OriginalDocusealBuilder {...props} />;
};
export default DocusealBuilder;

View File

@ -1,50 +0,0 @@
import React, { useState } from 'react';
import { Upload } from 'lucide-react';
export default function DraggableFileUpload({ fileName, onFileSelect }) {
const [dragActive, setDragActive] = useState(false);
const handleDragOver = (event) => {
event.preventDefault();
setDragActive(true);
};
const handleDragLeave = () => {
setDragActive(false);
};
const handleFileChosen = (selectedFile) => {
onFileSelect && onFileSelect(selectedFile);
};
const handleDrop = (event) => {
event.preventDefault();
setDragActive(false);
const droppedFile = event.dataTransfer.files[0];
handleFileChosen(droppedFile);
};
const handleFileChange = (event) => {
const selectedFile = event.target.files[0];
handleFileChosen(selectedFile);
};
return (
<div>
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={`border-2 border-dashed p-8 rounded-md ${dragActive ? 'border-blue-500' : 'border-gray-300'} flex flex-col items-center justify-center`}
style={{ height: '200px' }}
>
<input type="file" onChange={handleFileChange} className="hidden" id="fileInput" />
<label htmlFor="fileInput" className="cursor-pointer flex flex-col items-center">
<Upload size={48} className="text-gray-400 mb-2" />
<p className="text-center">{fileName || 'Glissez et déposez un fichier ici ou cliquez ici pour sélectionner un fichier'}</p>
</label>
</div>
</div>
);
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import Table from '@/components/Table';
export default function FilesToSign({ fileTemplates, columns }) {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">Fichiers à remplir</h2>
<Table
data={fileTemplates}
columns={columns}
itemsPerPage={5}
currentPage={1}
totalPages={1}
onPageChange={() => {}}
/>
</div>
);
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import Table from '@/components/Table';
export default function FilesToUpload({ fileTemplates, columns }) {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">Fichiers à uploader</h2>
<Table
data={fileTemplates}
columns={columns}
itemsPerPage={5}
currentPage={1}
totalPages={1}
onPageChange={() => {}}
/>
</div>
);
}

View File

@ -7,7 +7,7 @@ import Loader from '@/components/Loader';
import Button from '@/components/Button';
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import Table from '@/components/Table';
import { fetchRegisterFormFileTemplate, createRegistrationFormFile, fetchRegisterForm, deleteRegisterFormFile } from '@/app/actions/subscriptionAction';
import { fetchRegistrationTemplateMaster, createRegistrationTemplates, fetchRegisterForm, deleteRegistrationTemplates } from '@/app/actions/subscriptionAction';
import { fetchRegistrationFileFromGroup } from '@/app/actions/registerFileGroupAction';
import { Download, Upload, Trash2, Eye } from 'lucide-react';
import { BASE_URL } from '@/utils/Url';
@ -117,7 +117,7 @@ export default function InscriptionFormShared({
data.append('register_form', formData.id);
try {
const response = await createRegistrationFormFile(data, csrfToken);
const response = await createRegistrationTemplates(data, csrfToken);
if (response) {
setUploadedFiles(prev => {
const newFiles = prev.filter(f => parseInt(f.template) !== currentTemplateId);
@ -158,7 +158,7 @@ export default function InscriptionFormShared({
if (!fileToDelete) return;
try {
await deleteRegisterFormFile(fileToDelete.id, csrfToken);
await deleteRegistrationTemplates(fileToDelete.id, csrfToken);
setUploadedFiles(prev => prev.filter(f => parseInt(f.template) !== templateId));
} catch (error) {
logger.error('Error deleting file:', error);
@ -276,7 +276,7 @@ export default function InscriptionFormShared({
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">{requiredFileTemplates[currentPage - 2].name}</h2>
<iframe
src={`${BASE_URL}/data/${requiredFileTemplates[currentPage - 2].file}?signature=true`}
src={`${BASE_URL}/data/${requiredFileTemplates[currentPage - 2].file}`}
width="100%"
height="800px"
className="w-full" // Utiliser la classe CSS pour la largeur

View File

@ -0,0 +1,126 @@
import React from 'react';
import InputText from '@/components/InputText';
import SelectChoice from '@/components/SelectChoice';
import ResponsableInputFields from '@/components/Inscription/ResponsableInputFields';
const levels = [
{ value:'1', label: 'TPS - Très Petite Section'},
{ value:'2', label: 'PS - Petite Section'},
{ value:'3', label: 'MS - Moyenne Section'},
{ value:'4', label: 'GS - Grande Section'},
];
export default function StudentInfoForm({ formData, updateFormField, guardians, setGuardians, errors }) {
const getError = (field) => {
return errors?.student?.[field]?.[0];
};
return (
<>
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">Informations de l&apos;élève</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<InputText
name="last_name"
label="Nom"
value={formData.last_name}
onChange={(e) => updateFormField('last_name', e.target.value)}
required
errorMsg={getError('last_name')}
/>
<InputText
name="first_name"
label="Prénom"
value={formData.first_name}
onChange={(e) => updateFormField('first_name', e.target.value)}
errorMsg={getError('first_name')}
required
/>
<InputText
name="nationality"
label="Nationalité"
value={formData.nationality}
required
onChange={(e) => updateFormField('nationality', e.target.value)}
/>
<InputText
name="birth_date"
type="date"
label="Date de Naissance"
value={formData.birth_date}
onChange={(e) => updateFormField('birth_date', e.target.value)}
required
errorMsg={getError('birth_date')}
/>
<InputText
name="birth_place"
label="Lieu de Naissance"
value={formData.birth_place}
onChange={(e) => updateFormField('birth_place', e.target.value)}
required
errorMsg={getError('birth_place')}
/>
<InputText
name="birth_postal_code"
label="Code Postal de Naissance"
value={formData.birth_postal_code}
onChange={(e) => updateFormField('birth_postal_code', e.target.value)}
required
errorMsg={getError('birth_postal_code')}
/>
<div className="md:col-span-2">
<InputText
name="address"
label="Adresse"
value={formData.address}
onChange={(e) => updateFormField('address', e.target.value)}
required
errorMsg={getError('address')}
/>
</div>
<InputText
name="attending_physician"
label="Médecin Traitant"
value={formData.attending_physician}
onChange={(e) => updateFormField('attending_physician', e.target.value)}
required
errorMsg={getError('attending_physician')}
/>
<SelectChoice
name="level"
label="Niveau"
placeHolder="Sélectionner un niveau"
selected={formData.level}
callback={(e) => updateFormField('level', e.target.value)}
choices={levels}
required
errorMsg={getError('level')}
/>
</div>
</div>
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">Responsables</h2>
<ResponsableInputFields
guardians={guardians}
onGuardiansChange={(id, field, value) => {
const updatedGuardians = guardians.map(resp =>
resp.id === id ? { ...resp, [field]: value } : resp
);
setGuardians(updatedGuardians);
}}
addGuardian={(e) => {
e.preventDefault();
setGuardians([...guardians, { id: Date.now() }]);
}}
deleteGuardian={(index) => {
const newArray = [...guardians];
newArray.splice(index, 1);
setGuardians(newArray);
}}
errors={errors?.student?.guardians || []}
/>
</div>
</>
);
}

View File

@ -1,12 +1,12 @@
import * as Dialog from '@radix-ui/react-dialog';
const Modal = ({ isOpen, setIsOpen, title, ContentComponent }) => {
const Modal = ({ isOpen, setIsOpen, title, ContentComponent, modalClassName }) => {
return (
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
<Dialog.Content className="fixed inset-0 flex items-center justify-center p-4">
<div className="inline-block bg-white rounded-lg px-6 py-5 text-left shadow-xl transform transition-all sm:my-8 min-w-[500px] w-max m-12 h-max">
<div className={`inline-block bg-white rounded-lg px-6 py-5 text-left shadow-xl transform transition-all sm:my-8 ${modalClassName ? modalClassName : 'min-w-[500px] m-12 w-max max-h-[80vh] h-full'}`}>
<div className="flex justify-between items-start mb-4">
<Dialog.Title className="text-xl font-medium text-gray-900">
{title}
@ -23,7 +23,7 @@ const Modal = ({ isOpen, setIsOpen, title, ContentComponent }) => {
</button>
</Dialog.Close>
</div>
<div className="w-full">
<div className="w-full h-full">
<ContentComponent />
</div>
</div>

View File

@ -31,13 +31,10 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
return (
<div ref={containerRef}>
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
{label}
</label>
<div className="relative mt-1">
<button
type="button"
className="w-full bg-white border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-pointer focus:outline-none sm:text-sm"
className="w-full bg-white border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-pointer focus:outline-none sm:text-sm hover:border-emerald-500 focus:border-emerald-500"
onClick={() => setIsOpen(!isOpen)}
>
{selectedOptions.length > 0 ? (
@ -49,7 +46,7 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
))}
</div>
) : (
'Sélectionnez les niveaux'
<span>{label}</span>
)}
<ChevronDown className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none h-full w-5" />
</button>

View File

@ -291,6 +291,7 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
return (
<MultiSelect
name="levels"
label="Sélection de niveaux"
options={allNiveaux}
selectedOptions={currentData.levels ? currentData.levels.map(levelId => allNiveaux.find(level => level.id === levelId)) : []}
onChange={handleMultiSelectChange}

View File

@ -0,0 +1,200 @@
import React, { useState, useEffect } from 'react';
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
import { fetchRegistrationFileGroups } from '@/app/actions/registerFileGroupAction';
import DocusealBuilder from '@/components/DocusealBuilder'; // Import du composant wrapper
import logger from '@/utils/logger';
import { BE_DOCUSEAL_GET_JWT, BASE_URL } from '@/utils/Url';
import Button from '@/components/Button'; // Import du composant Button
import MultiSelect from '@/components/MultiSelect'; // Import du composant MultiSelect
import { createRegistrationTemplates } from '@/app/actions/subscriptionAction'; // Import de la fonction createRegistrationTemplates
import { useCsrfToken } from '@/context/CsrfContext';
export default function FileUpload({ handleCreateTemplateMaster, fileToEdit = null }) {
const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired
const [order, setOrder] = useState(0);
const [groups, setGroups] = useState([]);
const [token, setToken] = useState(null);
const [templateMaster, setTemplateMaster] = useState(null);
const [uploadedFileName, setUploadedFileName] = useState('');
const [selectedGroups, setSelectedGroups] = useState([]);
const [guardianEmails, setGuardianEmails] = useState([]);
const [registrationFormIds, setRegistrationFormIds] = useState([]);
const csrfToken = useCsrfToken();
useEffect(() => {
fetchRegistrationFileGroups().then(data => setGroups(data));
if (fileToEdit) {
setUploadedFileName(fileToEdit.name || '');
setSelectedGroups(fileToEdit.groups || []);
}
}, [fileToEdit]);
useEffect(() => {
const body = fileToEdit
? JSON.stringify({
user_email: 'n3wt.school@gmail.com',
template_id: fileToEdit.template_id
})
: JSON.stringify({
user_email: 'n3wt.school@gmail.com'
});
fetch('/api/docuseal/generateToken', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: body,
})
.then((response) => response.json())
.then((data) => {
setToken(data.token);
})
.catch((error) => console.error(error));
}, [fileToEdit]);
const handleFileNameChange = (event) => {
setUploadedFileName(event.target.value);
};
const handleGroupChange = (selectedGroups) => {
setSelectedGroups(selectedGroups);
const emails = selectedGroups.flatMap(group => group.registration_forms.flatMap(form => form.guardians.map(guardian => guardian.email)));
setGuardianEmails(emails); // Mettre à jour la variable d'état avec les emails des guardians
const registrationFormIds = selectedGroups.flatMap(group => group.registration_forms.map(form => form.student_id));
setRegistrationFormIds(registrationFormIds); // Mettre à jour la variable d'état avec les IDs des dossiers d'inscription
logger.debug('Emails des Guardians associés aux groupes sélectionnés:', emails);
logger.debug('IDs des dossiers d\'inscription associés aux groupes sélectionnés:', registrationFormIds);
};
const handleLoad = (detail) => {
const templateId = detail?.id;
setTemplateMaster(detail);
logger.debug('Master template created with ID:', templateId);
}
const handleUpload = (detail) => {
logger.debug('Uploaded file detail:', detail);
setUploadedFileName(detail.name);
};
const handleSubmit = () => {
logger.debug('Création du template master:', templateMaster?.id);
handleCreateTemplateMaster({
name: uploadedFileName,
group_ids: selectedGroups.map(group => group.id),
template_id: templateMaster?.id
});
guardianEmails.forEach((email, index) => {
cloneTemplate(templateMaster?.id, email)
.then(clonedDocument => {
// Sauvegarde des templates clonés dans la base de données
const data = {
name: `clone_${clonedDocument.id}`,
template_id: clonedDocument.id,
master: templateMaster?.id,
registration_form: registrationFormIds[index]
};
createRegistrationTemplates(data, csrfToken)
.then(response => {
logger.debug('Template enregistré avec succès:', response);
})
.catch(error => {
logger.error('Erreur lors de l\'enregistrement du template:', error);
});
// Logique pour envoyer chaque template au submitter
logger.debug('Sending template to:', email);
})
.catch(error => {
logger.error('Error during cloning or sending:', error);
});
});
};
const cloneTemplate = (templateId, email) => {
return fetch('/api/docuseal/cloneTemplate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId,
email
})
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.message); });
}
return response.json();
})
.then(data => {
logger.debug('Template cloned successfully:', data);
return data;
})
.catch(error => {
console.error('Error cloning template:', error);
throw error;
});
};
return (
<div className="h-full flex flex-col mt-4 space-y-4">
<div className="grid grid-cols-10 gap-4 items-start">
<div className="col-span-2">
<MultiSelect
name="groups"
label="Sélection de groupes de fichiers"
options={groups}
selectedOptions={selectedGroups}
onChange={handleGroupChange}
errorMsg={null}
/>
</div>
<div className="col-span-7">
{token && (
<DocusealBuilder
token={token}
headers={{
'Authorization': `Bearer ${token}`
}}
withSendButton={false}
withSignYourselfButton={false}
autosave={true}
language={'fr'}
onLoad={handleLoad}
onUpload={handleUpload}
className="h-full overflow-auto" // Ajouter overflow-auto pour permettre le défilement
style={{ maxHeight: '70vh' }} // Limiter la hauteur maximale du composant
// Il faut auter l'host correspondant (une fois passé en HTTPS)
//host="docuseal:3001"
/>
)}
</div>
<div className="col-span-1 flex justify-end">
<Button
text="Valider"
onClick={handleSubmit}
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
(uploadedFileName === '' || selectedGroups.length === 0)
? "bg-gray-300 text-gray-700 cursor-not-allowed"
: "bg-emerald-500 text-white hover:bg-emerald-600"
}`}
primary
disabled={uploadedFileName === '' || selectedGroups.length === 0}
/>
</div>
</div>
</div>
);
}

View File

@ -2,14 +2,15 @@ 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 FileUpload from '@/components/Structure/Files/FileUpload';
import { formatDate } from '@/utils/Date';
import { BASE_URL } from '@/utils/Url';
import {
fetchRegisterFormFileTemplate,
createRegistrationFormFileTemplate,
editRegistrationFormFileTemplate,
deleteRegisterFormFileTemplate
fetchRegistrationTemplateMaster,
createRegistrationTemplateMaster,
editRegistrationTemplateMaster,
deleteRegistrationTemplateMaster,
getRegisterFormFileTemplate
} from '@/app/actions/subscriptionAction';
import {
fetchRegistrationFileGroups,
@ -17,10 +18,9 @@ import {
deleteRegistrationFileGroup,
editRegistrationFileGroup
} from '@/app/actions/registerFileGroupAction';
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
import logger from '@/utils/logger';
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
export default function FilesManagement({ csrfToken }) {
export default function FilesGroupsManagement({ csrfToken }) {
const [fichiers, setFichiers] = useState([]);
const [groups, setGroups] = useState([]);
const [selectedGroup, setSelectedGroup] = useState(null);
@ -29,23 +29,18 @@ export default function FilesManagement({ csrfToken }) {
const [fileToEdit, setFileToEdit] = useState(null);
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
const [groupToEdit, setGroupToEdit] = useState(null);
const [token, setToken] = useState(null);
const [selectedFile, setSelectedFile] = useState(null);
// 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);
const groupInfos = file.groups.map(groupId => groups.find(g => g.id === groupId) || { id: groupId, name: 'Groupe inconnu' });
return {
...file,
group: groupInfo || { id: file.group, name: 'Groupe inconnu' }
groups: groupInfos
};
};
useEffect(() => {
Promise.all([
fetchRegisterFormFileTemplate(),
fetchRegistrationTemplateMaster(),
fetchRegistrationFileGroups()
]).then(([filesData, groupsData]) => {
setGroups(groupsData);
@ -57,12 +52,12 @@ export default function FilesManagement({ csrfToken }) {
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
setFichiers(transformedFiles);
}).catch(err => {
logger.debug(err.message);
console.log(err.message);
});
}, []);
const handleFileDelete = (fileId) => {
deleteRegisterFormFileTemplate(fileId, csrfToken)
deleteRegistrationTemplateMaster(fileId, csrfToken)
.then(response => {
if (response.ok) {
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
@ -72,7 +67,7 @@ export default function FilesManagement({ csrfToken }) {
}
})
.catch(error => {
logger.error('Error deleting file:', error);
console.error('Error deleting file:', error);
alert('Erreur lors de la suppression du fichier.');
});
};
@ -83,7 +78,45 @@ export default function FilesManagement({ csrfToken }) {
setIsModalOpen(true);
};
const handleFileUpload = ({file, name, is_required, order, groupId}) => {
const handleCreateTemplateMaster = ({name, group_ids, template_id}) => {
const data = {
name: name,
template_id: template_id,
groups: group_ids
};
console.log(data);
if (isEditing && fileToEdit) {
editRegistrationTemplateMaster(fileToEdit.id, data, 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 {
createRegistrationTemplateMaster(data, 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 handleTemplate = ({name, is_required, order, groupId, document_id}) => {
if (!name) {
alert('Veuillez entrer un nom de fichier.');
return;
@ -96,14 +129,16 @@ export default function FilesManagement({ csrfToken }) {
formData.append('name', name);
formData.append('is_required', is_required);
formData.append('order', order);
formData.append('document_id', document_id);
// 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)
editRegistrationTemplateMaster(fileToEdit.id, formData, csrfToken)
.then(data => {
// Transformer le fichier mis à jour avec les informations du groupe
const transformedFile = transformFileData(data, groups);
@ -115,11 +150,11 @@ export default function FilesManagement({ csrfToken }) {
setIsEditing(false);
})
.catch(error => {
logger.error('Error editing file:', error);
console.error('Error editing file:', error);
alert('Erreur lors de la modification du fichier');
});
} else {
createRegistrationFormFileTemplate(formData, csrfToken)
createRegistrationTemplateMaster(formData, csrfToken)
.then(data => {
// Transformer le nouveau fichier avec les informations du groupe
const transformedFile = transformFileData(data, groups);
@ -127,25 +162,33 @@ export default function FilesManagement({ csrfToken }) {
setIsModalOpen(false);
})
.catch(error => {
logger.error('Error uploading file:', error);
console.error('Error uploading file:', error);
});
}
};
const handleGroupSubmit = async (groupData) => {
try {
if (groupToEdit) {
const updatedGroup = await editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken);
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
setGroupToEdit(null);
} else {
const newGroup = await createRegistrationFileGroup(groupData, csrfToken);
setGroups([...groups, newGroup]);
}
setIsGroupModalOpen(false);
} catch (error) {
logger.error('Error handling group:', error);
alert('Erreur lors de l\'opération sur le groupe');
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');
});
}
};
@ -175,24 +218,20 @@ export default function FilesManagement({ csrfToken }) {
alert('Groupe supprimé avec succès.');
})
.catch(error => {
logger.error('Error deleting group:', 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);
return file.groups && file.groups.some(group => 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: 'Groupes', transform: (row) => row.groups && row.groups.length > 0 ? row.groups.map(group => group.name).join(', ') : 'Aucun' },
{ name: 'Actions', transform: (row) => (
<div className="flex items-center justify-center gap-2">
{row.file && (
@ -206,9 +245,6 @@ export default function FilesManagement({ csrfToken }) {
<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>
)}
];
@ -228,40 +264,24 @@ export default function FilesManagement({ csrfToken }) {
)}
];
// Fonction pour gérer la demande de signature
const handleSignatureRequest = (file) => {
fetch('http://localhost:8080/DocuSeal/generateToken', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
document_id: file.id,
user_email: 'anthony.casini.30@gmail.com',
url: file.file
}),
})
.then((response) => response.json())
.then((data) => {
console.log("token received : ", data.token);
setToken(data.token);
setSelectedFile(file);
})
.catch((error) => console.error(error));
};
return (
<div>
<Modal
isOpen={isModalOpen}
setIsOpen={setIsModalOpen}
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
setIsOpen={(isOpen) => {
setIsModalOpen(isOpen);
if (!isOpen) {
setFileToEdit(null);
}
}}
title={isEditing ? 'Modification du document' : 'Ajouter un document'}
ContentComponent={() => (
<FileUpload
onFileUpload={handleFileUpload}
handleCreateTemplateMaster={handleCreateTemplateMaster}
fileToEdit={fileToEdit}
/>
)}
modalClassName='w-4/5 h-4/5'
/>
<Modal
isOpen={isGroupModalOpen}
@ -324,14 +344,6 @@ export default function FilesManagement({ csrfToken }) {
/>
</div>
)}
{token && selectedFile && (
<DocusealBuilder
token={token}
headers={{
'Authorization': `Bearer Rh2CC75ZMZqirmtBGA5NRjUzj8hr9eDYTBeZxv3jgzb`
}}
/>
)}
</div>
);
}

View File

@ -1,48 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
const useLocalStorage = (key, initialValue) => {
const [storedValue, setStoredValue] = useState(() => {
try {
if (typeof window !== 'undefined') {
const item = window.localStorage.getItem(key);
// Vérifier si l'item existe et n'est pas undefined
return item !== null && item !== 'undefined'
? JSON.parse(item)
: initialValue;
}
return initialValue;
} catch (error) {
console.error('Error reading from localStorage:', error);
return initialValue;
}
});
useEffect(() => {
try {
// Vérifier si la valeur n'est pas undefined avant de la stocker
if (typeof storedValue !== 'undefined') {
window.localStorage.setItem(key, JSON.stringify(storedValue));
} else {
window.localStorage.removeItem(key);
}
} catch (error) {
console.error('Error writing to localStorage:', error);
}
}, [key, storedValue]);
const setValue = (value) => {
try {
// Permettre à la valeur d'être une fonction
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
} catch (error) {
console.error('Error updating localStorage value:', error);
}
};
return [storedValue, setValue];
};
export default useLocalStorage;

View File

@ -0,0 +1,36 @@
import { BE_DOCUSEAL_CLONE_TEMPLATE } from '@/utils/Url';
export default function handler(req, res) {
if (req.method === 'POST') {
const { templateId, email } = req.body;
fetch(BE_DOCUSEAL_CLONE_TEMPLATE, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Auth-Token': process.env.DOCUSEAL_API_KEY
},
body: JSON.stringify({
templateId,
email
})
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.message); });
}
return response.json();
})
.then(data => {
console.log('Template cloned successfully:', data);
res.status(200).json(data);
})
.catch(error => {
console.error('Error cloning template:', error);
res.status(500).json({ error: 'Internal Server Error' });
});
} else {
res.setHeader('Allow', ['POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}

View File

@ -0,0 +1,31 @@
import { BE_DOCUSEAL_GET_JWT } from '@/utils/Url';
export default function handler(req, res) {
if (req.method === 'POST') {
console.log('DOCUSEAL_API_KEY:', process.env.DOCUSEAL_API_KEY);
fetch(BE_DOCUSEAL_GET_JWT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Auth-Token': process.env.DOCUSEAL_API_KEY
},
body: JSON.stringify(req.body),
})
.then(response => {
console.log('Response status:', response.status);
return response.json().then(data => ({ status: response.status, data }));
})
.then(({ status, data }) => {
console.log('Response data:', data);
res.status(status).json(data);
})
.catch(error => {
console.error('Error:', error);
res.status(500).json({ error: 'Internal Server Error' });
});
} else {
res.setHeader('Allow', ['POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}

View File

@ -4,6 +4,9 @@ export const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
//URL-Back-End
// GESTION DocuSeal
export const BE_DOCUSEAL_GET_JWT = `${BASE_URL}/DocuSeal/generateToken`
export const BE_DOCUSEAL_CLONE_TEMPLATE = `${BASE_URL}/DocuSeal/cloneTemplate`
// GESTION LOGIN
export const BE_AUTH_NEW_PASSWORD_URL = `${BASE_URL}/Auth/newPassword`
@ -20,8 +23,8 @@ export const BE_AUTH_INFO_SESSION = `${BASE_URL}/Auth/infoSession`
export const BE_SUBSCRIPTION_STUDENTS_URL = `${BASE_URL}/Subscriptions/students` // Récupère la liste des élèves inscrits ou en cours d'inscriptions
export const BE_SUBSCRIPTION_CHILDRENS_URL = `${BASE_URL}/Subscriptions/children` // Récupère la liste des élèves d'un profil
export const BE_SUBSCRIPTION_REGISTERFORMS_URL = `${BASE_URL}/Subscriptions/registerForms`
export const BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL = `${BASE_URL}/Subscriptions/registrationFileTemplates`
export const BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL = `${BASE_URL}/Subscriptions/registrationFiles`
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_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`

View File

@ -35,11 +35,13 @@ services:
- 8080:8080
volumes:
- ./Back-End:/Back-End
env_file:
- .env
environment:
- TZ=Europe/Paris
- TEST_MODE=True
- CORS_ALLOWED_ORIGINS=http://localhost:3000
- CSRF_TRUSTED_ORIGINS=http://localhost:3000,http://localhost:8080
- CSRF_TRUSTED_ORIGINS=http://localhost:3000
links:
- "database:database"
- "redis:redis"
@ -66,17 +68,19 @@ services:
volumes:
- ./initDocusealUsers.sh:/docker-entrypoint-initdb.d/initDocusealUsers.sh
frontend:
build:
context: ./Front-End
args:
- BUILD_MODE=development
ports:
- 3000:3000
volumes:
- ./Front-End:/app
environment:
- TZ=Europe/Paris
depends_on:
- backend
# frontend:
# build:
# context: ./Front-End
# args:
# - BUILD_MODE=development
# ports:
# - 3000:3000
# volumes:
# - ./Front-End:/app
# env_file:
# - .env
# environment:
# - TZ=Europe/Paris
# depends_on:
# - backend