mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Signatures électroniques docuseal [#22]
This commit is contained in:
1
.env
Normal file
1
.env
Normal file
@ -0,0 +1 @@
|
||||
DOCUSEAL_API_KEY="LRvUTQCbMSSpManYKshdQk9Do6rBQgjHyPrbGfxU3Jg"
|
||||
1
Back-End/DocuSeal/__init__.py
Normal file
1
Back-End/DocuSeal/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# This file is intentionally left blank to make this directory a Python package.
|
||||
7
Back-End/DocuSeal/urls.py
Normal file
7
Back-End/DocuSeal/urls.py
Normal 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'),
|
||||
]
|
||||
81
Back-End/DocuSeal/views.py
Normal file
81
Back-End/DocuSeal/views.py
Normal 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)
|
||||
8
Back-End/N3wtSchool/middleware.py
Normal file
8
Back-End/N3wtSchool/middleware.py
Normal 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
|
||||
@ -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')
|
||||
}
|
||||
@ -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,14 +450,24 @@ class Command(BaseCommand):
|
||||
|
||||
def create_register_form(self):
|
||||
# Créer ou mettre à jour le profil associé au guardian
|
||||
profile_data = {
|
||||
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!"
|
||||
}
|
||||
]
|
||||
|
||||
for profile_data in profiles_data:
|
||||
user, created = Profile.objects.update_or_create(
|
||||
email=profile_data["email"],
|
||||
defaults={
|
||||
@ -466,13 +484,13 @@ class Command(BaseCommand):
|
||||
# Créer les données du guardian
|
||||
guardian_data = {
|
||||
"associated_profile_id": user.id,
|
||||
"email": "anthony.casini.30@gmail.com",
|
||||
"email": profile_data["email"],
|
||||
}
|
||||
|
||||
# Créer les données de l'étudiant
|
||||
student_data = {
|
||||
"last_name": "CASINI",
|
||||
"first_name": "Giulia",
|
||||
"last_name": f'lastname_{user.id}',
|
||||
"first_name": f'firstname_{user.id}',
|
||||
}
|
||||
|
||||
# Créer ou mettre à jour l'étudiant et le guardian
|
||||
@ -494,7 +512,7 @@ class Command(BaseCommand):
|
||||
# Créer les données du formulaire d'inscription
|
||||
register_form_data = {
|
||||
"student": student,
|
||||
"fileGroup": self.registration_file_group,
|
||||
"fileGroup": self.registration_file_group_1,
|
||||
"establishment": Establishment.objects.get(id=1),
|
||||
"status": 1
|
||||
}
|
||||
@ -504,7 +522,7 @@ class Command(BaseCommand):
|
||||
register_form.fees.set(fees)
|
||||
register_form.discounts.set(discounts)
|
||||
if not created:
|
||||
register_form.fileGroup = self.registration_file_group
|
||||
register_form.fileGroup = self.registration_file_group_1
|
||||
register_form.save()
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('RegistrationForm initialized or updated successfully'))
|
||||
@ -89,7 +89,7 @@ class Student(models.Model):
|
||||
siblings = models.ManyToManyField(Sibling, blank=True)
|
||||
|
||||
# Many-to-Many Relationship
|
||||
registration_files = models.ManyToManyField('RegistrationFile', blank=True, related_name='students')
|
||||
registration_files = models.ManyToManyField('RegistrationTemplate', blank=True, related_name='students')
|
||||
|
||||
# Many-to-Many Relationship
|
||||
spoken_languages = models.ManyToManyField(Language, blank=True)
|
||||
@ -162,7 +162,7 @@ class Student(models.Model):
|
||||
return None
|
||||
|
||||
class RegistrationFileGroup(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
name = models.CharField(max_length=255, default="")
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
@ -172,10 +172,35 @@ def registration_file_path(instance, filename):
|
||||
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
|
||||
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
|
||||
|
||||
class RegistrationTemplateMaster(models.Model):
|
||||
groups = models.ManyToManyField(RegistrationFileGroup, related_name='template_masters')
|
||||
template_id = models.IntegerField(primary_key=True)
|
||||
name = models.CharField(max_length=255, default="")
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.group.name} - {self.template_id}'
|
||||
|
||||
class RegistrationTemplate(models.Model):
|
||||
master = models.ForeignKey(RegistrationTemplateMaster, on_delete=models.CASCADE, related_name='templates')
|
||||
template_id = models.IntegerField(primary_key=True)
|
||||
name = models.CharField(max_length=255, default="")
|
||||
registration_form = models.ForeignKey('RegistrationForm', on_delete=models.CASCADE, related_name='templates')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def get_files_from_rf(register_form_id):
|
||||
"""
|
||||
Récupère tous les fichiers liés à un dossier d’inscription donné.
|
||||
"""
|
||||
registration_files = RegistrationTemplate.objects.filter(register_form_id=register_form_id).order_by('template__order')
|
||||
filenames = []
|
||||
for reg_file in registration_files:
|
||||
filenames.append(reg_file.file.path)
|
||||
return filenames
|
||||
|
||||
class RegistrationForm(models.Model):
|
||||
"""
|
||||
Gère le dossier d’inscription lié à un élève donné.
|
||||
"""
|
||||
class RegistrationFormStatus(models.IntegerChoices):
|
||||
RF_ABSENT = 0, _('Pas de dossier d\'inscription')
|
||||
RF_CREATED = 1, _('Dossier d\'inscription créé')
|
||||
@ -205,7 +230,7 @@ class RegistrationForm(models.Model):
|
||||
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
|
||||
fileGroup = models.ForeignKey(RegistrationFileGroup,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='file_group',
|
||||
related_name='register_forms',
|
||||
null=True,
|
||||
blank=True)
|
||||
|
||||
@ -213,56 +238,3 @@ class RegistrationForm(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
||||
|
||||
class RegistrationFileTemplate(models.Model):
|
||||
"""
|
||||
Modèle pour stocker les fichiers "templates" d’inscription.
|
||||
"""
|
||||
name = models.CharField(max_length=255)
|
||||
file = models.FileField(upload_to='templates_files/', blank=True, null=True)
|
||||
order = models.PositiveIntegerField(default=0) # Ajout du champ order
|
||||
date_added = models.DateTimeField(auto_now_add=True)
|
||||
is_required = models.BooleanField(default=False)
|
||||
group = models.ForeignKey(RegistrationFileGroup, on_delete=models.CASCADE, related_name='file_templates', null=True, blank=True)
|
||||
|
||||
@property
|
||||
def formatted_date_added(self):
|
||||
if self.date_added:
|
||||
return self.date_added.strftime('%d-%m-%Y')
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def registration_file_upload_to(instance, filename):
|
||||
return f"registration_files/dossier_rf_{instance.register_form.pk}/{filename}"
|
||||
|
||||
class RegistrationFile(models.Model):
|
||||
"""
|
||||
Fichier lié à un dossier d’inscription particulier.
|
||||
"""
|
||||
name = models.CharField(max_length=255)
|
||||
file = models.FileField(upload_to=registration_file_upload_to)
|
||||
date_added = models.DateTimeField(auto_now_add=True)
|
||||
template = models.OneToOneField(RegistrationFileTemplate, on_delete=models.CASCADE)
|
||||
register_form = models.ForeignKey('RegistrationForm', on_delete=models.CASCADE, related_name='registration_files')
|
||||
|
||||
@property
|
||||
def formatted_date_added(self):
|
||||
if self.date_added:
|
||||
return self.date_added.strftime('%d-%m-%Y')
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def get_files_from_rf(register_form_id):
|
||||
"""
|
||||
Récupère tous les fichiers liés à un dossier d’inscription donné.
|
||||
"""
|
||||
registration_files = RegistrationFile.objects.filter(register_form_id=register_form_id).order_by('template__order')
|
||||
filenames = []
|
||||
for reg_file in registration_files:
|
||||
filenames.append(reg_file.file.path)
|
||||
return filenames
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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"),
|
||||
]
|
||||
@ -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',
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
from django.http.response import JsonResponse
|
||||
from django.core.files import File
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg import openapi
|
||||
from rest_framework.parsers import MultiPartParser, FormParser
|
||||
@ -7,205 +6,154 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
|
||||
import os
|
||||
|
||||
from Subscriptions.serializers import RegistrationFileTemplateSerializer, RegistrationFileSerializer
|
||||
from Subscriptions.models import RegistrationFileTemplate, RegistrationFile
|
||||
from Subscriptions.serializers import RegistrationTemplateMasterSerializer, RegistrationTemplateSerializer
|
||||
from Subscriptions.models import RegistrationTemplateMaster, RegistrationTemplate
|
||||
from N3wtSchool import bdd
|
||||
|
||||
|
||||
class RegistrationFileTemplateView(APIView):
|
||||
class RegistrationTemplateMasterView(APIView):
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère tous les fichiers templates pour les dossiers d'inscription",
|
||||
responses={200: RegistrationFileTemplateSerializer(many=True)}
|
||||
operation_description="Récupère tous les masters de templates d'inscription",
|
||||
responses={200: RegistrationTemplateMasterSerializer(many=True)}
|
||||
)
|
||||
def get(self, request):
|
||||
"""
|
||||
Récupère les fichiers templates pour les dossiers d’inscription.
|
||||
"""
|
||||
files = RegistrationFileTemplate.objects.all()
|
||||
serializer = RegistrationFileTemplateSerializer(files, many=True)
|
||||
masters = RegistrationTemplateMaster.objects.all()
|
||||
serializer = RegistrationTemplateMasterSerializer(masters, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Crée un nouveau fichier template pour les dossiers d'inscription",
|
||||
request_body=RegistrationFileTemplateSerializer,
|
||||
operation_description="Crée un nouveau master de template d'inscription",
|
||||
request_body=RegistrationTemplateMasterSerializer,
|
||||
responses={
|
||||
201: RegistrationFileTemplateSerializer,
|
||||
201: RegistrationTemplateMasterSerializer,
|
||||
400: "Données invalides"
|
||||
}
|
||||
)
|
||||
def post(self, request):
|
||||
"""
|
||||
Crée un fichier template pour les dossiers d’inscription.
|
||||
"""
|
||||
serializer = RegistrationFileTemplateSerializer(data=request.data)
|
||||
serializer = RegistrationTemplateMasterSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class RegistrationFileTemplateSimpleView(APIView):
|
||||
"""
|
||||
Gère les fichiers templates pour les dossiers d’inscription.
|
||||
"""
|
||||
parser_classes = (MultiPartParser, FormParser)
|
||||
|
||||
class RegistrationTemplateMasterSimpleView(APIView):
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère un fichier template spécifique",
|
||||
operation_description="Récupère un master de template d'inscription spécifique",
|
||||
responses={
|
||||
200: RegistrationFileTemplateSerializer,
|
||||
404: "Fichier template non trouvé"
|
||||
200: RegistrationTemplateMasterSerializer,
|
||||
404: "Master non trouvé"
|
||||
}
|
||||
)
|
||||
def get(self, request, id):
|
||||
"""
|
||||
Récupère les fichiers templates pour les dossiers d’inscription.
|
||||
"""
|
||||
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
|
||||
if registationFileTemplate is None:
|
||||
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileTemplateSerializer(registationFileTemplate)
|
||||
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
|
||||
if master is None:
|
||||
return JsonResponse({"errorMessage":'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationTemplateMasterSerializer(master)
|
||||
return JsonResponse(serializer.data, safe=False)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Met à jour un fichier template existant",
|
||||
request_body=RegistrationFileTemplateSerializer,
|
||||
operation_description="Met à jour un master de template d'inscription existant",
|
||||
request_body=RegistrationTemplateMasterSerializer,
|
||||
responses={
|
||||
201: RegistrationFileTemplateSerializer,
|
||||
200: RegistrationTemplateMasterSerializer,
|
||||
400: "Données invalides",
|
||||
404: "Fichier template non trouvé"
|
||||
404: "Master non trouvé"
|
||||
}
|
||||
)
|
||||
def put(self, request, id):
|
||||
"""
|
||||
Met à jour un fichier template existant.
|
||||
"""
|
||||
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
|
||||
if registationFileTemplate is None:
|
||||
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileTemplateSerializer(registationFileTemplate,data=request.data)
|
||||
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
|
||||
if master is None:
|
||||
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationTemplateMasterSerializer(master, data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Supprime un fichier template",
|
||||
operation_description="Supprime un master de template d'inscription",
|
||||
responses={
|
||||
204: "Suppression réussie",
|
||||
404: "Fichier template non trouvé"
|
||||
404: "Master non trouvé"
|
||||
}
|
||||
)
|
||||
def delete(self, request, id):
|
||||
"""
|
||||
Supprime un fichier template existant.
|
||||
"""
|
||||
registrationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
|
||||
if registrationFileTemplate is not None:
|
||||
registrationFileTemplate.file.delete() # Supprimer le fichier uploadé
|
||||
registrationFileTemplate.delete()
|
||||
return JsonResponse({'message': 'La suppression du fichier d\'inscription a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
|
||||
if master is not None:
|
||||
master.delete()
|
||||
return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||
else:
|
||||
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
class RegistrationFileView(APIView):
|
||||
class RegistrationTemplateView(APIView):
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère tous les fichiers d'inscription",
|
||||
responses={200: RegistrationFileSerializer(many=True)}
|
||||
operation_description="Récupère tous les templates d'inscription",
|
||||
responses={200: RegistrationTemplateSerializer(many=True)}
|
||||
)
|
||||
def get(self, request):
|
||||
"""
|
||||
Récupère les fichiers liés à un dossier d’inscription donné.
|
||||
"""
|
||||
files = RegistrationFile.objects.all()
|
||||
serializer = RegistrationFileSerializer(files, many=True)
|
||||
templates = RegistrationTemplate.objects.all()
|
||||
serializer = RegistrationTemplateSerializer(templates, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Crée un nouveau fichier d'inscription",
|
||||
request_body=RegistrationFileSerializer,
|
||||
operation_description="Crée un nouveau template d'inscription",
|
||||
request_body=RegistrationTemplateSerializer,
|
||||
responses={
|
||||
201: RegistrationFileSerializer,
|
||||
201: RegistrationTemplateSerializer,
|
||||
400: "Données invalides"
|
||||
}
|
||||
)
|
||||
def post(self, request):
|
||||
"""
|
||||
Crée un RegistrationFile pour le RegistrationForm associé.
|
||||
"""
|
||||
serializer = RegistrationFileSerializer(data=request.data)
|
||||
serializer = RegistrationTemplateSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
|
||||
class RegistrationFileSimpleView(APIView):
|
||||
"""
|
||||
Gère la création, mise à jour et suppression de fichiers liés à un dossier d’inscription.
|
||||
"""
|
||||
parser_classes = (MultiPartParser, FormParser)
|
||||
|
||||
class RegistrationTemplateSimpleView(APIView):
|
||||
@swagger_auto_schema(
|
||||
operation_description="Récupère un fichier d'inscription spécifique",
|
||||
operation_description="Récupère un template d'inscription spécifique",
|
||||
responses={
|
||||
200: RegistrationFileSerializer,
|
||||
404: "Fichier non trouvé"
|
||||
200: RegistrationTemplateSerializer,
|
||||
404: "Template non trouvé"
|
||||
}
|
||||
)
|
||||
def get(self, request, id):
|
||||
"""
|
||||
Récupère les fichiers liés à un dossier d’inscription donné.
|
||||
"""
|
||||
registationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
|
||||
if registationFile is None:
|
||||
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileSerializer(registationFile)
|
||||
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
|
||||
if template is None:
|
||||
return JsonResponse({"errorMessage":'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationTemplateSerializer(template)
|
||||
return JsonResponse(serializer.data, safe=False)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Met à jour un fichier d'inscription existant",
|
||||
request_body=RegistrationFileSerializer,
|
||||
operation_description="Met à jour un template d'inscription existant",
|
||||
request_body=RegistrationTemplateSerializer,
|
||||
responses={
|
||||
200: openapi.Response(
|
||||
description="Fichier mis à jour avec succès",
|
||||
schema=RegistrationFileSerializer
|
||||
),
|
||||
200: RegistrationTemplateSerializer,
|
||||
400: "Données invalides",
|
||||
404: "Fichier non trouvé"
|
||||
404: "Template non trouvé"
|
||||
}
|
||||
)
|
||||
def put(self, request, id):
|
||||
"""
|
||||
Met à jour un RegistrationFile existant.
|
||||
"""
|
||||
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
|
||||
if registrationFile is None:
|
||||
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationFileSerializer(registrationFile, data=request.data)
|
||||
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
|
||||
if template is None:
|
||||
return JsonResponse({'erreur': 'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
serializer = RegistrationTemplateSerializer(template, data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response({'message': 'Fichier mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
|
||||
return Response({'message': 'Template mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="Supprime un fichier d'inscription",
|
||||
operation_description="Supprime un template d'inscription",
|
||||
responses={
|
||||
200: "Suppression réussie",
|
||||
404: "Fichier non trouvé"
|
||||
204: "Suppression réussie",
|
||||
404: "Template non trouvé"
|
||||
}
|
||||
)
|
||||
def delete(self, request, id):
|
||||
"""
|
||||
Supprime un RegistrationFile existant.
|
||||
"""
|
||||
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
|
||||
if registrationFile is not None:
|
||||
registrationFile.file.delete() # Supprimer le fichier uploadé
|
||||
registrationFile.delete()
|
||||
return JsonResponse({'message': 'La suppression du fichier a été effectuée avec succès'}, safe=False)
|
||||
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
|
||||
if template is not None:
|
||||
template.delete()
|
||||
return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||
else:
|
||||
return JsonResponse({'erreur': 'Le fichier n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
return JsonResponse({'erreur': 'Le template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@ -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"
|
||||
6
Front-End/.vscode/settings.json
vendored
6
Front-End/.vscode/settings.json
vendored
@ -1,6 +0,0 @@
|
||||
{
|
||||
"i18n-ally.localesPaths": [
|
||||
"messages"
|
||||
],
|
||||
"i18n-ally.keystyle": "nested"
|
||||
}
|
||||
@ -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} />
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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: {
|
||||
|
||||
17
Front-End/src/components/DocusealBuilder.js
Normal file
17
Front-End/src/components/DocusealBuilder.js
Normal 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;
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
18
Front-End/src/components/Inscription/FilesToSign.js
Normal file
18
Front-End/src/components/Inscription/FilesToSign.js
Normal 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>
|
||||
);
|
||||
}
|
||||
18
Front-End/src/components/Inscription/FilesToUpload.js
Normal file
18
Front-End/src/components/Inscription/FilesToUpload.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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
|
||||
|
||||
126
Front-End/src/components/Inscription/StudentInfoForm.js
Normal file
126
Front-End/src/components/Inscription/StudentInfoForm.js
Normal 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'é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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
200
Front-End/src/components/Structure/Files/FileUpload.js
Normal file
200
Front-End/src/components/Structure/Files/FileUpload.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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 {
|
||||
const handleGroupSubmit = (groupData) => {
|
||||
if (groupToEdit) {
|
||||
const updatedGroup = await editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken);
|
||||
editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken)
|
||||
.then(updatedGroup => {
|
||||
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);
|
||||
})
|
||||
.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>
|
||||
);
|
||||
}
|
||||
@ -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;
|
||||
36
Front-End/src/pages/api/docuseal/cloneTemplate.js
Normal file
36
Front-End/src/pages/api/docuseal/cloneTemplate.js
Normal 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`);
|
||||
}
|
||||
}
|
||||
31
Front-End/src/pages/api/docuseal/generateToken.js
Normal file
31
Front-End/src/pages/api/docuseal/generateToken.js
Normal 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`);
|
||||
}
|
||||
}
|
||||
@ -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`
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user