mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +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_ORIGIN_ALLOW_ALL = True
|
||||||
CORS_ALLOW_ALL_HEADERS = True
|
CORS_ALLOW_ALL_HEADERS = True
|
||||||
CORS_ALLOW_CREDENTIALS = True
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
CORS_ALLOW_HEADERS = [
|
||||||
|
'content-type',
|
||||||
|
'authorization',
|
||||||
|
'X-Auth-Token',
|
||||||
|
'x-csrftoken'
|
||||||
|
]
|
||||||
|
|
||||||
CORS_ALLOWED_ORIGINS = [
|
CORS_ALLOWED_ORIGINS = [
|
||||||
os.getenv('CORS_ALLOWED_ORIGINS', 'http://localhost:3000')
|
os.getenv('CORS_ALLOWED_ORIGINS', 'http://localhost:3000')
|
||||||
@ -333,5 +339,5 @@ DOCUSEAL_JWT = {
|
|||||||
'ALGORITHM': 'HS256',
|
'ALGORITHM': 'HS256',
|
||||||
'SIGNING_KEY': SECRET_KEY,
|
'SIGNING_KEY': SECRET_KEY,
|
||||||
'EXPIRATION_DELTA': timedelta(hours=1),
|
'EXPIRATION_DELTA': timedelta(hours=1),
|
||||||
'API_KEY': '1kzHsXqN8P2ezUGT7TjVuBwM1hqtLsztrVSsQ87T7Mz'
|
'API_KEY': os.getenv('DOCUSEAL_API_KEY')
|
||||||
}
|
}
|
||||||
@ -7,7 +7,7 @@ from Subscriptions.models import (
|
|||||||
Fee,
|
Fee,
|
||||||
Discount,
|
Discount,
|
||||||
RegistrationFileGroup,
|
RegistrationFileGroup,
|
||||||
RegistrationFileTemplate
|
# RegistrationFileTemplate
|
||||||
)
|
)
|
||||||
from Auth.models import Profile
|
from Auth.models import Profile
|
||||||
from School.models import (
|
from School.models import (
|
||||||
@ -43,7 +43,7 @@ class Command(BaseCommand):
|
|||||||
self.create_or_update_teachers()
|
self.create_or_update_teachers()
|
||||||
self.create_or_update_school_classes()
|
self.create_or_update_school_classes()
|
||||||
self.create_or_update_registration_file_group()
|
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()
|
self.create_register_form()
|
||||||
|
|
||||||
def create_or_update_establishment(self):
|
def create_or_update_establishment(self):
|
||||||
@ -376,13 +376,21 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write(self.style.SUCCESS('SchoolClasses initialized or updated successfully'))
|
self.stdout.write(self.style.SUCCESS('SchoolClasses initialized or updated successfully'))
|
||||||
|
|
||||||
def create_or_update_registration_file_group(self):
|
def create_or_update_registration_file_group(self):
|
||||||
group_data = {
|
group_data_1 = {
|
||||||
"name": "LMDE",
|
"name": "LMDE",
|
||||||
"description": "Fichiers d'inscription de l'école 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.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 initialized or updated successfully'))
|
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):
|
def create_or_update_registration_file_template(self):
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
@ -394,28 +402,28 @@ class Command(BaseCommand):
|
|||||||
"file": "RIB LA MAISON DES ENFANTS.pdf",
|
"file": "RIB LA MAISON DES ENFANTS.pdf",
|
||||||
"order": 0,
|
"order": 0,
|
||||||
"is_required": False,
|
"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",
|
"name": "Contrat d'engagement 2024 2025",
|
||||||
"file": "Contrat d'engagement 2024 2025.pdf",
|
"file": "Contrat d'engagement 2024 2025.pdf",
|
||||||
"order": 0,
|
"order": 0,
|
||||||
"is_required": True,
|
"is_required": True,
|
||||||
"group": self.registration_file_group
|
"group": self.registration_file_group_1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Bulletin d'adhésion familiale scolaire",
|
"name": "Bulletin d'adhésion familiale scolaire",
|
||||||
"file": "Bulletin d'adhésion familiale scolaire.pdf",
|
"file": "Bulletin d'adhésion familiale scolaire.pdf",
|
||||||
"order": 0,
|
"order": 0,
|
||||||
"is_required": True,
|
"is_required": True,
|
||||||
"group": self.registration_file_group
|
"group": self.registration_file_group_1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Fiche sanitaire de liaison",
|
"name": "Fiche sanitaire de liaison",
|
||||||
"file": "Fiche sanitaire de liaison.pdf",
|
"file": "Fiche sanitaire de liaison.pdf",
|
||||||
"order": 0,
|
"order": 0,
|
||||||
"is_required": True,
|
"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):
|
def create_register_form(self):
|
||||||
# Créer ou mettre à jour le profil associé au guardian
|
# Créer ou mettre à jour le profil associé au guardian
|
||||||
profile_data = {
|
profiles_data = [
|
||||||
"email": "anthony.casini.30@gmail.com",
|
{
|
||||||
"droit": 2,
|
"email": "anthony.casini.30@gmail.com",
|
||||||
"username": "anthony.casini.30@gmail.com",
|
"droit": 2,
|
||||||
"is_active": True,
|
"username": "anthony.casini.30@gmail.com",
|
||||||
"password": "Provisoire01!"
|
"is_active": True,
|
||||||
}
|
"password": "Provisoire01!"
|
||||||
|
},
|
||||||
user, created = Profile.objects.update_or_create(
|
{
|
||||||
email=profile_data["email"],
|
"email": "anthony.audrey.34@gmail.com",
|
||||||
defaults={
|
"droit": 2,
|
||||||
"username": profile_data["username"],
|
"username": "anthony.audrey.34@gmail.com",
|
||||||
"email": profile_data["email"],
|
"is_active": True,
|
||||||
"is_active": profile_data["is_active"],
|
"password": "Provisoire01!"
|
||||||
"droit": profile_data["droit"]
|
|
||||||
}
|
}
|
||||||
)
|
]
|
||||||
if created:
|
|
||||||
user.set_password(profile_data["password"])
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
# Créer les données du guardian
|
for profile_data in profiles_data:
|
||||||
guardian_data = {
|
user, created = Profile.objects.update_or_create(
|
||||||
"associated_profile_id": user.id,
|
email=profile_data["email"],
|
||||||
"email": "anthony.casini.30@gmail.com",
|
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
|
# Créer les données du guardian
|
||||||
student_data = {
|
guardian_data = {
|
||||||
"last_name": "CASINI",
|
"associated_profile_id": user.id,
|
||||||
"first_name": "Giulia",
|
"email": profile_data["email"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Créer ou mettre à jour l'étudiant et le guardian
|
# Créer les données de l'étudiant
|
||||||
student, created = Student.objects.get_or_create(
|
student_data = {
|
||||||
last_name=student_data["last_name"],
|
"last_name": f'lastname_{user.id}',
|
||||||
first_name=student_data["first_name"],
|
"first_name": f'firstname_{user.id}',
|
||||||
defaults=student_data
|
}
|
||||||
)
|
|
||||||
guardian, created = Guardian.objects.get_or_create(
|
|
||||||
last_name=guardian_data["email"],
|
|
||||||
defaults=guardian_data
|
|
||||||
)
|
|
||||||
student.guardians.add(guardian)
|
|
||||||
|
|
||||||
# Récupérer les frais et les réductions
|
# Créer ou mettre à jour l'étudiant et le guardian
|
||||||
fees = Fee.objects.filter(id__in=[1, 2, 3, 4])
|
student, created = Student.objects.get_or_create(
|
||||||
discounts = Discount.objects.filter(id__in=[1])
|
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
|
# Récupérer les frais et les réductions
|
||||||
register_form_data = {
|
fees = Fee.objects.filter(id__in=[1, 2, 3, 4])
|
||||||
"student": student,
|
discounts = Discount.objects.filter(id__in=[1])
|
||||||
"fileGroup": self.registration_file_group,
|
|
||||||
"establishment": Establishment.objects.get(id=1),
|
|
||||||
"status": 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Créer ou mettre à jour le formulaire d'inscription
|
# Créer les données du formulaire d'inscription
|
||||||
register_form, created = RegistrationForm.objects.get_or_create(student=student, defaults=register_form_data)
|
register_form_data = {
|
||||||
register_form.fees.set(fees)
|
"student": student,
|
||||||
register_form.discounts.set(discounts)
|
"fileGroup": self.registration_file_group_1,
|
||||||
if not created:
|
"establishment": Establishment.objects.get(id=1),
|
||||||
register_form.fileGroup = self.registration_file_group
|
"status": 1
|
||||||
register_form.save()
|
}
|
||||||
|
|
||||||
|
# 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'))
|
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)
|
siblings = models.ManyToManyField(Sibling, blank=True)
|
||||||
|
|
||||||
# Many-to-Many Relationship
|
# 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
|
# Many-to-Many Relationship
|
||||||
spoken_languages = models.ManyToManyField(Language, blank=True)
|
spoken_languages = models.ManyToManyField(Language, blank=True)
|
||||||
@ -162,7 +162,7 @@ class Student(models.Model):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
class RegistrationFileGroup(models.Model):
|
class RegistrationFileGroup(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255, default="")
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
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
|
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
|
||||||
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
|
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
|
||||||
|
|
||||||
|
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):
|
class RegistrationForm(models.Model):
|
||||||
"""
|
|
||||||
Gère le dossier d’inscription lié à un élève donné.
|
|
||||||
"""
|
|
||||||
class RegistrationFormStatus(models.IntegerChoices):
|
class RegistrationFormStatus(models.IntegerChoices):
|
||||||
RF_ABSENT = 0, _('Pas de dossier d\'inscription')
|
RF_ABSENT = 0, _('Pas de dossier d\'inscription')
|
||||||
RF_CREATED = 1, _('Dossier d\'inscription créé')
|
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')
|
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
|
||||||
fileGroup = models.ForeignKey(RegistrationFileGroup,
|
fileGroup = models.ForeignKey(RegistrationFileGroup,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='file_group',
|
related_name='register_forms',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True)
|
blank=True)
|
||||||
|
|
||||||
@ -213,56 +238,3 @@ class RegistrationForm(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
||||||
|
|
||||||
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 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.models import SchoolClass, Fee, Discount, FeeType
|
||||||
from School.serializers import FeeSerializer, DiscountSerializer
|
from School.serializers import FeeSerializer, DiscountSerializer
|
||||||
from Auth.models import Profile
|
from Auth.models import Profile
|
||||||
@ -11,20 +11,38 @@ from django.utils import timezone
|
|||||||
import pytz
|
import pytz
|
||||||
from datetime import datetime
|
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):
|
class RegistrationFileGroupSerializer(serializers.ModelSerializer):
|
||||||
|
registration_forms = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RegistrationFileGroup
|
model = RegistrationFileGroup
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
class RegistrationFileSerializer(serializers.ModelSerializer):
|
def get_registration_forms(self, obj):
|
||||||
class Meta:
|
forms = RegistrationForm.objects.filter(fileGroup=obj)
|
||||||
model = RegistrationFile
|
return RegistrationFormSimpleSerializer(forms, many=True).data
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
class RegistrationFileTemplateSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = RegistrationFileTemplate
|
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
class LanguageSerializer(serializers.ModelSerializer):
|
class LanguageSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(required=False)
|
id = serializers.IntegerField(required=False)
|
||||||
@ -132,7 +150,7 @@ class RegistrationFormSerializer(serializers.ModelSerializer):
|
|||||||
registration_file = serializers.FileField(required=False)
|
registration_file = serializers.FileField(required=False)
|
||||||
status_label = serializers.SerializerMethodField()
|
status_label = serializers.SerializerMethodField()
|
||||||
formatted_last_update = 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)
|
fees = serializers.PrimaryKeyRelatedField(queryset=Fee.objects.all(), many=True, required=False)
|
||||||
discounts = serializers.PrimaryKeyRelatedField(queryset=Discount.objects.all(), many=True, required=False)
|
discounts = serializers.PrimaryKeyRelatedField(queryset=Discount.objects.all(), many=True, required=False)
|
||||||
totalRegistrationFees = serializers.SerializerMethodField()
|
totalRegistrationFees = serializers.SerializerMethodField()
|
||||||
|
|||||||
@ -7,8 +7,9 @@ from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archi
|
|||||||
# SubClasses
|
# SubClasses
|
||||||
from .views import StudentView, GuardianView, ChildrenListView, StudentListView
|
from .views import StudentView, GuardianView, ChildrenListView, StudentListView
|
||||||
# Files
|
# 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 RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||||
|
from .views import registration_file_views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r'^registerForms/(?P<id>[0-9]+)/archive$', archive, name="archive"),
|
re_path(r'^registerForms/(?P<id>[0-9]+)/archive$', archive, name="archive"),
|
||||||
@ -27,15 +28,13 @@ urlpatterns = [
|
|||||||
# Page de formulaire d'inscription - RESPONSABLE
|
# Page de formulaire d'inscription - RESPONSABLE
|
||||||
re_path(r'^lastGuardianId$', GuardianView.as_view(), name="lastGuardianId"),
|
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]+)$', 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/(?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'^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 .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 .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||||
from .student_views import StudentView, StudentListView, ChildrenListView
|
from .student_views import StudentView, StudentListView, ChildrenListView
|
||||||
from .guardian_views import GuardianView
|
from .guardian_views import GuardianView
|
||||||
@ -10,10 +10,10 @@ __all__ = [
|
|||||||
'send',
|
'send',
|
||||||
'resend',
|
'resend',
|
||||||
'archive',
|
'archive',
|
||||||
'RegistrationFileView',
|
'RegistrationTemplateView',
|
||||||
'RegistrationFileSimpleView',
|
'RegistrationTemplateSimpleView',
|
||||||
'RegistrationFileTemplateView',
|
'RegistrationTemplateMasterView',
|
||||||
'RegistrationFileTemplateSimpleView',
|
'RegistrationTemplateMasterSimpleView',
|
||||||
'RegistrationFileGroupView',
|
'RegistrationFileGroupView',
|
||||||
'RegistrationFileGroupSimpleView',
|
'RegistrationFileGroupSimpleView',
|
||||||
'get_registration_files_by_group',
|
'get_registration_files_by_group',
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import Subscriptions.util as util
|
|||||||
from Subscriptions.serializers import RegistrationFormSerializer
|
from Subscriptions.serializers import RegistrationFormSerializer
|
||||||
from Subscriptions.pagination import CustomPagination
|
from Subscriptions.pagination import CustomPagination
|
||||||
from Subscriptions.signals import clear_cache
|
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 Subscriptions.automate import updateStateMachine
|
||||||
|
|
||||||
from N3wtSchool import settings, bdd
|
from N3wtSchool import settings, bdd
|
||||||
@ -252,7 +252,7 @@ class RegisterFormWithIdView(APIView):
|
|||||||
registerForm.save()
|
registerForm.save()
|
||||||
|
|
||||||
# Récupération des fichiers d'inscription
|
# 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:
|
if registerForm.registration_file:
|
||||||
fileNames.insert(0, registerForm.registration_file.path)
|
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 drf_yasg import openapi
|
||||||
|
|
||||||
from Subscriptions.serializers import RegistrationFileGroupSerializer
|
from Subscriptions.serializers import RegistrationFileGroupSerializer
|
||||||
from Subscriptions.models import RegistrationFileGroup, RegistrationFileTemplate
|
from Subscriptions.models import RegistrationFileGroup, RegistrationTemplateMaster
|
||||||
from N3wtSchool import bdd
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
class RegistrationFileGroupView(APIView):
|
class RegistrationFileGroupView(APIView):
|
||||||
@ -118,7 +118,7 @@ class RegistrationFileGroupSimpleView(APIView):
|
|||||||
def get_registration_files_by_group(request, id):
|
def get_registration_files_by_group(request, id):
|
||||||
try:
|
try:
|
||||||
group = RegistrationFileGroup.objects.get(id=id)
|
group = RegistrationFileGroup.objects.get(id=id)
|
||||||
templates = RegistrationFileTemplate.objects.filter(group=group)
|
templates = RegistrationTemplateMaster.objects.filter(group=group)
|
||||||
templates_data = list(templates.values())
|
templates_data = list(templates.values())
|
||||||
return JsonResponse(templates_data, safe=False)
|
return JsonResponse(templates_data, safe=False)
|
||||||
except RegistrationFileGroup.DoesNotExist:
|
except RegistrationFileGroup.DoesNotExist:
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
from django.http.response import JsonResponse
|
from django.http.response import JsonResponse
|
||||||
from django.core.files import File
|
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
from rest_framework.parsers import MultiPartParser, FormParser
|
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.views import APIView
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
import os
|
from Subscriptions.serializers import RegistrationTemplateMasterSerializer, RegistrationTemplateSerializer
|
||||||
|
from Subscriptions.models import RegistrationTemplateMaster, RegistrationTemplate
|
||||||
from Subscriptions.serializers import RegistrationFileTemplateSerializer, RegistrationFileSerializer
|
|
||||||
from Subscriptions.models import RegistrationFileTemplate, RegistrationFile
|
|
||||||
from N3wtSchool import bdd
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
|
class RegistrationTemplateMasterView(APIView):
|
||||||
class RegistrationFileTemplateView(APIView):
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère tous les fichiers templates pour les dossiers d'inscription",
|
operation_description="Récupère tous les masters de templates d'inscription",
|
||||||
responses={200: RegistrationFileTemplateSerializer(many=True)}
|
responses={200: RegistrationTemplateMasterSerializer(many=True)}
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""
|
masters = RegistrationTemplateMaster.objects.all()
|
||||||
Récupère les fichiers templates pour les dossiers d’inscription.
|
serializer = RegistrationTemplateMasterSerializer(masters, many=True)
|
||||||
"""
|
|
||||||
files = RegistrationFileTemplate.objects.all()
|
|
||||||
serializer = RegistrationFileTemplateSerializer(files, many=True)
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Crée un nouveau fichier template pour les dossiers d'inscription",
|
operation_description="Crée un nouveau master de template d'inscription",
|
||||||
request_body=RegistrationFileTemplateSerializer,
|
request_body=RegistrationTemplateMasterSerializer,
|
||||||
responses={
|
responses={
|
||||||
201: RegistrationFileTemplateSerializer,
|
201: RegistrationTemplateMasterSerializer,
|
||||||
400: "Données invalides"
|
400: "Données invalides"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
"""
|
serializer = RegistrationTemplateMasterSerializer(data=request.data)
|
||||||
Crée un fichier template pour les dossiers d’inscription.
|
|
||||||
"""
|
|
||||||
serializer = RegistrationFileTemplateSerializer(data=request.data)
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
class RegistrationTemplateMasterSimpleView(APIView):
|
||||||
class RegistrationFileTemplateSimpleView(APIView):
|
|
||||||
"""
|
|
||||||
Gère les fichiers templates pour les dossiers d’inscription.
|
|
||||||
"""
|
|
||||||
parser_classes = (MultiPartParser, FormParser)
|
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@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={
|
responses={
|
||||||
200: RegistrationFileTemplateSerializer,
|
200: RegistrationTemplateMasterSerializer,
|
||||||
404: "Fichier template non trouvé"
|
404: "Master non trouvé"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def get(self, request, id):
|
def get(self, request, id):
|
||||||
"""
|
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
|
||||||
Récupère les fichiers templates pour les dossiers d’inscription.
|
if master is None:
|
||||||
"""
|
return JsonResponse({"errorMessage":'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
|
serializer = RegistrationTemplateMasterSerializer(master)
|
||||||
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)
|
|
||||||
return JsonResponse(serializer.data, safe=False)
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Met à jour un fichier template existant",
|
operation_description="Met à jour un master de template d'inscription existant",
|
||||||
request_body=RegistrationFileTemplateSerializer,
|
request_body=RegistrationTemplateMasterSerializer,
|
||||||
responses={
|
responses={
|
||||||
201: RegistrationFileTemplateSerializer,
|
200: RegistrationTemplateMasterSerializer,
|
||||||
400: "Données invalides",
|
400: "Données invalides",
|
||||||
404: "Fichier template non trouvé"
|
404: "Master non trouvé"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def put(self, request, id):
|
def put(self, request, id):
|
||||||
"""
|
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
|
||||||
Met à jour un fichier template existant.
|
if master is None:
|
||||||
"""
|
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
|
serializer = RegistrationTemplateMasterSerializer(master, data=request.data)
|
||||||
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)
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
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)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Supprime un fichier template",
|
operation_description="Supprime un master de template d'inscription",
|
||||||
responses={
|
responses={
|
||||||
204: "Suppression réussie",
|
204: "Suppression réussie",
|
||||||
404: "Fichier template non trouvé"
|
404: "Master non trouvé"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def delete(self, request, id):
|
def delete(self, request, id):
|
||||||
"""
|
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
|
||||||
Supprime un fichier template existant.
|
if master is not None:
|
||||||
"""
|
master.delete()
|
||||||
registrationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
|
return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||||
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)
|
|
||||||
else:
|
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(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère tous les fichiers d'inscription",
|
operation_description="Récupère tous les templates d'inscription",
|
||||||
responses={200: RegistrationFileSerializer(many=True)}
|
responses={200: RegistrationTemplateSerializer(many=True)}
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""
|
templates = RegistrationTemplate.objects.all()
|
||||||
Récupère les fichiers liés à un dossier d’inscription donné.
|
serializer = RegistrationTemplateSerializer(templates, many=True)
|
||||||
"""
|
|
||||||
files = RegistrationFile.objects.all()
|
|
||||||
serializer = RegistrationFileSerializer(files, many=True)
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Crée un nouveau fichier d'inscription",
|
operation_description="Crée un nouveau template d'inscription",
|
||||||
request_body=RegistrationFileSerializer,
|
request_body=RegistrationTemplateSerializer,
|
||||||
responses={
|
responses={
|
||||||
201: RegistrationFileSerializer,
|
201: RegistrationTemplateSerializer,
|
||||||
400: "Données invalides"
|
400: "Données invalides"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
"""
|
serializer = RegistrationTemplateSerializer(data=request.data)
|
||||||
Crée un RegistrationFile pour le RegistrationForm associé.
|
|
||||||
"""
|
|
||||||
serializer = RegistrationFileSerializer(data=request.data)
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
class RegistrationTemplateSimpleView(APIView):
|
||||||
|
|
||||||
class RegistrationFileSimpleView(APIView):
|
|
||||||
"""
|
|
||||||
Gère la création, mise à jour et suppression de fichiers liés à un dossier d’inscription.
|
|
||||||
"""
|
|
||||||
parser_classes = (MultiPartParser, FormParser)
|
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@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={
|
responses={
|
||||||
200: RegistrationFileSerializer,
|
200: RegistrationTemplateSerializer,
|
||||||
404: "Fichier non trouvé"
|
404: "Template non trouvé"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def get(self, request, id):
|
def get(self, request, id):
|
||||||
"""
|
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
|
||||||
Récupère les fichiers liés à un dossier d’inscription donné.
|
if template is None:
|
||||||
"""
|
return JsonResponse({"errorMessage":'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
registationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
|
serializer = RegistrationTemplateSerializer(template)
|
||||||
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)
|
|
||||||
return JsonResponse(serializer.data, safe=False)
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Met à jour un fichier d'inscription existant",
|
operation_description="Met à jour un template d'inscription existant",
|
||||||
request_body=RegistrationFileSerializer,
|
request_body=RegistrationTemplateSerializer,
|
||||||
responses={
|
responses={
|
||||||
200: openapi.Response(
|
200: RegistrationTemplateSerializer,
|
||||||
description="Fichier mis à jour avec succès",
|
|
||||||
schema=RegistrationFileSerializer
|
|
||||||
),
|
|
||||||
400: "Données invalides",
|
400: "Données invalides",
|
||||||
404: "Fichier non trouvé"
|
404: "Template non trouvé"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def put(self, request, id):
|
def put(self, request, id):
|
||||||
"""
|
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
|
||||||
Met à jour un RegistrationFile existant.
|
if template is None:
|
||||||
"""
|
return JsonResponse({'erreur': 'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
|
serializer = RegistrationTemplateSerializer(template, data=request.data)
|
||||||
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)
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
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)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Supprime un fichier d'inscription",
|
operation_description="Supprime un template d'inscription",
|
||||||
responses={
|
responses={
|
||||||
200: "Suppression réussie",
|
204: "Suppression réussie",
|
||||||
404: "Fichier non trouvé"
|
404: "Template non trouvé"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def delete(self, request, id):
|
def delete(self, request, id):
|
||||||
"""
|
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
|
||||||
Supprime un RegistrationFile existant.
|
if template is not None:
|
||||||
"""
|
template.delete()
|
||||||
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
|
return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||||
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)
|
|
||||||
else:
|
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)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
NEXT_PUBLIC_API_URL=http://localhost:8080
|
NEXT_PUBLIC_API_URL=http://localhost:8080
|
||||||
NEXT_PUBLIC_USE_FAKE_DATA='false'
|
NEXT_PUBLIC_USE_FAKE_DATA='false'
|
||||||
AUTH_SECRET='false'
|
AUTH_SECRET='false'
|
||||||
NEXTAUTH_URL=http://localhost:3000
|
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,
|
fetchRegistrationPaymentModes,
|
||||||
fetchTuitionPaymentModes } from '@/app/actions/schoolAction';
|
fetchTuitionPaymentModes } from '@/app/actions/schoolAction';
|
||||||
import SidebarTabs from '@/components/SidebarTabs';
|
import SidebarTabs from '@/components/SidebarTabs';
|
||||||
import FilesManagement from '@/components/Structure/Files/FilesManagement';
|
import FilesGroupsManagement from '@/components/Structure/Files/FilesGroupsManagement';
|
||||||
import { fetchRegisterFormFileTemplate } from '@/app/actions/subscriptionAction';
|
import { fetchRegistrationTemplateMaster } from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ export default function Page() {
|
|||||||
handleTuitionFees();
|
handleTuitionFees();
|
||||||
|
|
||||||
// Fetch data for registration file templates
|
// Fetch data for registration file templates
|
||||||
fetchRegisterFormFileTemplate()
|
fetchRegistrationTemplateMaster()
|
||||||
.then((data)=> {
|
.then((data)=> {
|
||||||
setFichiers(data)
|
setFichiers(data)
|
||||||
})
|
})
|
||||||
@ -301,7 +301,7 @@ export default function Page() {
|
|||||||
{
|
{
|
||||||
id: 'Files',
|
id: 'Files',
|
||||||
label: 'Documents d\'inscription',
|
label: 'Documents d\'inscription',
|
||||||
content: <FilesManagement csrfToken={csrfToken} />
|
content: <FilesGroupsManagement csrfToken={csrfToken} />
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import {
|
|||||||
createRegisterForm,
|
createRegisterForm,
|
||||||
sendRegisterForm,
|
sendRegisterForm,
|
||||||
archiveRegisterForm,
|
archiveRegisterForm,
|
||||||
fetchRegisterFormFileTemplate,
|
fetchRegistrationTemplateMaster,
|
||||||
fetchStudents,
|
fetchStudents,
|
||||||
editRegisterForm } from "@/app/actions/subscriptionAction"
|
editRegisterForm } from "@/app/actions/subscriptionAction"
|
||||||
|
|
||||||
@ -195,7 +195,7 @@ useEffect(() => {
|
|||||||
fetchRegisterForms(ARCHIVED)
|
fetchRegisterForms(ARCHIVED)
|
||||||
.then(registerFormArchivedDataHandler)
|
.then(registerFormArchivedDataHandler)
|
||||||
.catch(requestErrorHandler)
|
.catch(requestErrorHandler)
|
||||||
fetchRegisterFormFileTemplate()
|
fetchRegistrationTemplateMaster()
|
||||||
.then((data)=> {
|
.then((data)=> {
|
||||||
logger.debug(data);
|
logger.debug(data);
|
||||||
|
|
||||||
@ -254,7 +254,7 @@ useEffect(() => {
|
|||||||
fetchRegisterForms(ARCHIVED)
|
fetchRegisterForms(ARCHIVED)
|
||||||
.then(registerFormArchivedDataHandler)
|
.then(registerFormArchivedDataHandler)
|
||||||
.catch(requestErrorHandler)
|
.catch(requestErrorHandler)
|
||||||
fetchRegisterFormFileTemplate()
|
fetchRegistrationTemplateMaster()
|
||||||
.then((data)=> {setFichiers(data)})
|
.then((data)=> {setFichiers(data)})
|
||||||
.catch((err)=>{ err = err.message; logger.debug(err);});
|
.catch((err)=>{ err = err.message; logger.debug(err);});
|
||||||
} else {
|
} 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 { 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 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 { 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 { fetchMessages } from '@/app/actions/messagerieAction';
|
||||||
import ProtectedRoute from '@/components/ProtectedRoute';
|
import ProtectedRoute from '@/components/ProtectedRoute';
|
||||||
import { disconnect } from '@/app/actions/authAction';
|
import { disconnect } from '@/app/actions/authAction';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||||
|
|
||||||
export default function Layout({
|
export default function Layout({
|
||||||
children,
|
children,
|
||||||
@ -19,7 +20,8 @@ export default function Layout({
|
|||||||
|
|
||||||
const router = useRouter(); // Définition de router
|
const router = useRouter(); // Définition de router
|
||||||
const [messages, setMessages] = useState([]);
|
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 [isLoading, setIsLoading] = useState(true);
|
||||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||||
|
|
||||||
@ -32,27 +34,33 @@ export default function Layout({
|
|||||||
disconnect();
|
disconnect();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
setIsLoading(true);
|
// if (status === 'loading') return;
|
||||||
setUserId(userId)
|
// if (!session) {
|
||||||
fetchMessages(userId)
|
// router.push(`${FE_USERS_LOGIN_URL}`);
|
||||||
.then(data => {
|
// }
|
||||||
if (data) {
|
|
||||||
setMessages(data);
|
|
||||||
}
|
|
||||||
logger.debug('Success :', data);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
logger.error('Error fetching data:', error);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
}, [userId]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
// const userIdFromSession = session.user.id;
|
||||||
return <div>Loading...</div>;
|
// 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 (
|
return (
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
|
|||||||
@ -4,22 +4,31 @@ import { useRouter } from 'next/navigation';
|
|||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { Edit } from 'lucide-react';
|
import { Edit } from 'lucide-react';
|
||||||
import StatusLabel from '@/components/StatusLabel';
|
import StatusLabel from '@/components/StatusLabel';
|
||||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
|
||||||
import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url';
|
import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url';
|
||||||
import { fetchChildren } from '@/app/actions/subscriptionAction';
|
import { fetchChildren } from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||||
|
|
||||||
export default function ParentHomePage() {
|
export default function ParentHomePage() {
|
||||||
const [actions, setActions] = useState([]);
|
const [actions, setActions] = useState([]);
|
||||||
const [children, setChildren] = useState([]);
|
const [children, setChildren] = useState([]);
|
||||||
const [userId, setUserId] = useLocalStorage("userId", '') ;
|
const { data: session, status } = useSession();
|
||||||
|
const [userId, setUserId] = useState(null);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
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);
|
setChildren(data);
|
||||||
});
|
});
|
||||||
}, [userId]);
|
}, [userId]);
|
||||||
|
|||||||
@ -13,10 +13,10 @@ import {
|
|||||||
FE_PARENTS_HOME_URL
|
FE_PARENTS_HOME_URL
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
import { login } from '@/app/actions/authAction';
|
import { login } from '@/app/actions/authAction';
|
||||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
|
||||||
import { getSession } from 'next-auth/react';
|
import { getSession } from 'next-auth/react';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
|
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
|
||||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||||
|
|
||||||
@ -28,8 +28,6 @@ export default function Page() {
|
|||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const [userId, setUserId] = useLocalStorage("userId", '') ;
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
|
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
|
||||||
|
|
||||||
@ -55,7 +53,6 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
const user = session.user;
|
const user = session.user;
|
||||||
logger.debug('User Session:', user);
|
logger.debug('User Session:', user);
|
||||||
localStorage.setItem('userId', user.id); // Stocker l'identifiant de l'utilisateur
|
|
||||||
if (user.droit === 0) {
|
if (user.droit === 0) {
|
||||||
// Vue ECOLE
|
// Vue ECOLE
|
||||||
} else if (user.droit === 1) {
|
} else if (user.droit === 1) {
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import {
|
|||||||
BE_SUBSCRIPTION_STUDENTS_URL,
|
BE_SUBSCRIPTION_STUDENTS_URL,
|
||||||
BE_SUBSCRIPTION_CHILDRENS_URL,
|
BE_SUBSCRIPTION_CHILDRENS_URL,
|
||||||
BE_SUBSCRIPTION_REGISTERFORMS_URL,
|
BE_SUBSCRIPTION_REGISTERFORMS_URL,
|
||||||
BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL,
|
BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL,
|
||||||
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL,
|
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL,
|
||||||
BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL
|
BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
|
|
||||||
export const PENDING = 'pending';
|
export const PENDING = 'pending';
|
||||||
@ -101,10 +101,10 @@ export const archiveRegisterForm = (id) => {
|
|||||||
}).then(requestResponseHandler)
|
}).then(requestResponseHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchRegisterFormFile = (id = null) => {
|
export const fetchRegistrationTemplates = (id = null) => {
|
||||||
let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}`
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`
|
||||||
if (id) {
|
if (id) {
|
||||||
url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${id}`;
|
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${id}`;
|
||||||
}
|
}
|
||||||
const request = new Request(
|
const request = new Request(
|
||||||
`${url}`,
|
`${url}`,
|
||||||
@ -118,8 +118,8 @@ export const fetchRegisterFormFile = (id = null) => {
|
|||||||
return fetch(request).then(requestResponseHandler)
|
return fetch(request).then(requestResponseHandler)
|
||||||
};
|
};
|
||||||
|
|
||||||
export const editRegistrationFormFile= (fileId, data, csrfToken) => {
|
export const editRegistrationTemplates= (fileId, data, csrfToken) => {
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${fileId}`, {
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: data,
|
body: data,
|
||||||
headers: {
|
headers: {
|
||||||
@ -130,21 +130,22 @@ export const editRegistrationFormFile= (fileId, data, csrfToken) => {
|
|||||||
.then(requestResponseHandler)
|
.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',
|
method: 'POST',
|
||||||
body: data,
|
body: JSON.stringify(data),
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': csrfToken,
|
'X-CSRFToken': csrfToken,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
})
|
||||||
.then(requestResponseHandler)
|
.then(requestResponseHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteRegisterFormFile= (fileId,csrfToken) => {
|
export const deleteRegistrationTemplates= (fileId,csrfToken) => {
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${fileId}`, {
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': csrfToken,
|
'X-CSRFToken': csrfToken,
|
||||||
@ -153,10 +154,10 @@ export const deleteRegisterFormFile= (fileId,csrfToken) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchRegisterFormFileTemplate = (id = null) => {
|
export const fetchRegistrationTemplateMaster = (id = null) => {
|
||||||
let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}`;
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`;
|
||||||
if(id){
|
if(id){
|
||||||
url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${id}`;
|
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${id}`;
|
||||||
}
|
}
|
||||||
const request = new Request(
|
const request = new Request(
|
||||||
`${url}`,
|
`${url}`,
|
||||||
@ -170,21 +171,22 @@ export const fetchRegisterFormFileTemplate = (id = null) => {
|
|||||||
return fetch(request).then(requestResponseHandler)
|
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',
|
method: 'POST',
|
||||||
body: data,
|
body: JSON.stringify(data),
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': csrfToken,
|
'X-CSRFToken': csrfToken,
|
||||||
|
'Content-Type':'application/json'
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
})
|
||||||
.then(requestResponseHandler)
|
.then(requestResponseHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteRegisterFormFileTemplate = (fileId,csrfToken) => {
|
export const deleteRegistrationTemplateMaster = (fileId,csrfToken) => {
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${fileId}`, {
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': csrfToken,
|
'X-CSRFToken': csrfToken,
|
||||||
@ -193,8 +195,8 @@ export const deleteRegisterFormFileTemplate = (fileId,csrfToken) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const editRegistrationFormFileTemplate = (fileId, data, csrfToken) => {
|
export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => {
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${fileId}`, {
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: data,
|
body: data,
|
||||||
headers: {
|
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 Button from '@/components/Button';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import Table from '@/components/Table';
|
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 { fetchRegistrationFileFromGroup } from '@/app/actions/registerFileGroupAction';
|
||||||
import { Download, Upload, Trash2, Eye } from 'lucide-react';
|
import { Download, Upload, Trash2, Eye } from 'lucide-react';
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
@ -117,7 +117,7 @@ export default function InscriptionFormShared({
|
|||||||
data.append('register_form', formData.id);
|
data.append('register_form', formData.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await createRegistrationFormFile(data, csrfToken);
|
const response = await createRegistrationTemplates(data, csrfToken);
|
||||||
if (response) {
|
if (response) {
|
||||||
setUploadedFiles(prev => {
|
setUploadedFiles(prev => {
|
||||||
const newFiles = prev.filter(f => parseInt(f.template) !== currentTemplateId);
|
const newFiles = prev.filter(f => parseInt(f.template) !== currentTemplateId);
|
||||||
@ -158,7 +158,7 @@ export default function InscriptionFormShared({
|
|||||||
if (!fileToDelete) return;
|
if (!fileToDelete) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteRegisterFormFile(fileToDelete.id, csrfToken);
|
await deleteRegistrationTemplates(fileToDelete.id, csrfToken);
|
||||||
setUploadedFiles(prev => prev.filter(f => parseInt(f.template) !== templateId));
|
setUploadedFiles(prev => prev.filter(f => parseInt(f.template) !== templateId));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error deleting file:', 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">
|
<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>
|
<h2 className="text-xl font-bold mb-4 text-gray-800">{requiredFileTemplates[currentPage - 2].name}</h2>
|
||||||
<iframe
|
<iframe
|
||||||
src={`${BASE_URL}/data/${requiredFileTemplates[currentPage - 2].file}?signature=true`}
|
src={`${BASE_URL}/data/${requiredFileTemplates[currentPage - 2].file}`}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="800px"
|
height="800px"
|
||||||
className="w-full" // Utiliser la classe CSS pour la largeur
|
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';
|
import * as Dialog from '@radix-ui/react-dialog';
|
||||||
|
|
||||||
const Modal = ({ isOpen, setIsOpen, title, ContentComponent }) => {
|
const Modal = ({ isOpen, setIsOpen, title, ContentComponent, modalClassName }) => {
|
||||||
return (
|
return (
|
||||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
<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">
|
<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">
|
<div className="flex justify-between items-start mb-4">
|
||||||
<Dialog.Title className="text-xl font-medium text-gray-900">
|
<Dialog.Title className="text-xl font-medium text-gray-900">
|
||||||
{title}
|
{title}
|
||||||
@ -23,7 +23,7 @@ const Modal = ({ isOpen, setIsOpen, title, ContentComponent }) => {
|
|||||||
</button>
|
</button>
|
||||||
</Dialog.Close>
|
</Dialog.Close>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full h-full">
|
||||||
<ContentComponent />
|
<ContentComponent />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -31,13 +31,10 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
<div className="relative mt-1">
|
<div className="relative mt-1">
|
||||||
<button
|
<button
|
||||||
type="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)}
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
>
|
>
|
||||||
{selectedOptions.length > 0 ? (
|
{selectedOptions.length > 0 ? (
|
||||||
@ -49,7 +46,7 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
|
|||||||
))}
|
))}
|
||||||
</div>
|
</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" />
|
<ChevronDown className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none h-full w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -291,6 +291,7 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
|
|||||||
return (
|
return (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
name="levels"
|
name="levels"
|
||||||
|
label="Sélection de niveaux"
|
||||||
options={allNiveaux}
|
options={allNiveaux}
|
||||||
selectedOptions={currentData.levels ? currentData.levels.map(levelId => allNiveaux.find(level => level.id === levelId)) : []}
|
selectedOptions={currentData.levels ? currentData.levels.map(levelId => allNiveaux.find(level => level.id === levelId)) : []}
|
||||||
onChange={handleMultiSelectChange}
|
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 { Plus, Download, Edit, Trash2, FolderPlus, Signature } from 'lucide-react';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import FileUpload from '@/components/FileUpload';
|
import FileUpload from '@/components/Structure/Files/FileUpload';
|
||||||
import { formatDate } from '@/utils/Date';
|
import { formatDate } from '@/utils/Date';
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import {
|
import {
|
||||||
fetchRegisterFormFileTemplate,
|
fetchRegistrationTemplateMaster,
|
||||||
createRegistrationFormFileTemplate,
|
createRegistrationTemplateMaster,
|
||||||
editRegistrationFormFileTemplate,
|
editRegistrationTemplateMaster,
|
||||||
deleteRegisterFormFileTemplate
|
deleteRegistrationTemplateMaster,
|
||||||
|
getRegisterFormFileTemplate
|
||||||
} from '@/app/actions/subscriptionAction';
|
} from '@/app/actions/subscriptionAction';
|
||||||
import {
|
import {
|
||||||
fetchRegistrationFileGroups,
|
fetchRegistrationFileGroups,
|
||||||
@ -17,10 +18,9 @@ import {
|
|||||||
deleteRegistrationFileGroup,
|
deleteRegistrationFileGroup,
|
||||||
editRegistrationFileGroup
|
editRegistrationFileGroup
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
|
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
|
||||||
import logger from '@/utils/logger';
|
|
||||||
|
|
||||||
export default function FilesManagement({ csrfToken }) {
|
export default function FilesGroupsManagement({ csrfToken }) {
|
||||||
const [fichiers, setFichiers] = useState([]);
|
const [fichiers, setFichiers] = useState([]);
|
||||||
const [groups, setGroups] = useState([]);
|
const [groups, setGroups] = useState([]);
|
||||||
const [selectedGroup, setSelectedGroup] = useState(null);
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
||||||
@ -29,23 +29,18 @@ export default function FilesManagement({ csrfToken }) {
|
|||||||
const [fileToEdit, setFileToEdit] = useState(null);
|
const [fileToEdit, setFileToEdit] = useState(null);
|
||||||
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
||||||
const [groupToEdit, setGroupToEdit] = useState(null);
|
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) => {
|
const transformFileData = (file, groups) => {
|
||||||
if (!file.group) return file;
|
const groupInfos = file.groups.map(groupId => groups.find(g => g.id === groupId) || { id: groupId, name: 'Groupe inconnu' });
|
||||||
|
|
||||||
const groupInfo = groups.find(g => g.id === file.group);
|
|
||||||
return {
|
return {
|
||||||
...file,
|
...file,
|
||||||
group: groupInfo || { id: file.group, name: 'Groupe inconnu' }
|
groups: groupInfos
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
fetchRegisterFormFileTemplate(),
|
fetchRegistrationTemplateMaster(),
|
||||||
fetchRegistrationFileGroups()
|
fetchRegistrationFileGroups()
|
||||||
]).then(([filesData, groupsData]) => {
|
]).then(([filesData, groupsData]) => {
|
||||||
setGroups(groupsData);
|
setGroups(groupsData);
|
||||||
@ -57,12 +52,12 @@ export default function FilesManagement({ csrfToken }) {
|
|||||||
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
|
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
|
||||||
setFichiers(transformedFiles);
|
setFichiers(transformedFiles);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
logger.debug(err.message);
|
console.log(err.message);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleFileDelete = (fileId) => {
|
const handleFileDelete = (fileId) => {
|
||||||
deleteRegisterFormFileTemplate(fileId, csrfToken)
|
deleteRegistrationTemplateMaster(fileId, csrfToken)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
|
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
|
||||||
@ -72,7 +67,7 @@ export default function FilesManagement({ csrfToken }) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error('Error deleting file:', error);
|
console.error('Error deleting file:', error);
|
||||||
alert('Erreur lors de la suppression du fichier.');
|
alert('Erreur lors de la suppression du fichier.');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -83,7 +78,45 @@ export default function FilesManagement({ csrfToken }) {
|
|||||||
setIsModalOpen(true);
|
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) {
|
if (!name) {
|
||||||
alert('Veuillez entrer un nom de fichier.');
|
alert('Veuillez entrer un nom de fichier.');
|
||||||
return;
|
return;
|
||||||
@ -96,14 +129,16 @@ export default function FilesManagement({ csrfToken }) {
|
|||||||
formData.append('name', name);
|
formData.append('name', name);
|
||||||
formData.append('is_required', is_required);
|
formData.append('is_required', is_required);
|
||||||
formData.append('order', order);
|
formData.append('order', order);
|
||||||
|
formData.append('document_id', document_id);
|
||||||
|
|
||||||
// Modification ici : vérifier si groupId existe et n'est pas vide
|
// Modification ici : vérifier si groupId existe et n'est pas vide
|
||||||
if (groupId && groupId !== '') {
|
if (groupId && groupId !== '') {
|
||||||
formData.append('group', groupId); // Notez que le nom du champ est 'group' et non 'group_id'
|
formData.append('group', groupId); // Notez que le nom du champ est 'group' et non 'group_id'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (isEditing && fileToEdit) {
|
if (isEditing && fileToEdit) {
|
||||||
editRegistrationFormFileTemplate(fileToEdit.id, formData, csrfToken)
|
editRegistrationTemplateMaster(fileToEdit.id, formData, csrfToken)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
// Transformer le fichier mis à jour avec les informations du groupe
|
// Transformer le fichier mis à jour avec les informations du groupe
|
||||||
const transformedFile = transformFileData(data, groups);
|
const transformedFile = transformFileData(data, groups);
|
||||||
@ -115,11 +150,11 @@ export default function FilesManagement({ csrfToken }) {
|
|||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error('Error editing file:', error);
|
console.error('Error editing file:', error);
|
||||||
alert('Erreur lors de la modification du fichier');
|
alert('Erreur lors de la modification du fichier');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
createRegistrationFormFileTemplate(formData, csrfToken)
|
createRegistrationTemplateMaster(formData, csrfToken)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
// Transformer le nouveau fichier avec les informations du groupe
|
// Transformer le nouveau fichier avec les informations du groupe
|
||||||
const transformedFile = transformFileData(data, groups);
|
const transformedFile = transformFileData(data, groups);
|
||||||
@ -127,25 +162,33 @@ export default function FilesManagement({ csrfToken }) {
|
|||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error('Error uploading file:', error);
|
console.error('Error uploading file:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGroupSubmit = async (groupData) => {
|
const handleGroupSubmit = (groupData) => {
|
||||||
try {
|
if (groupToEdit) {
|
||||||
if (groupToEdit) {
|
editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken)
|
||||||
const updatedGroup = await editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken);
|
.then(updatedGroup => {
|
||||||
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
|
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
|
||||||
setGroupToEdit(null);
|
setGroupToEdit(null);
|
||||||
} else {
|
setIsGroupModalOpen(false);
|
||||||
const newGroup = await createRegistrationFileGroup(groupData, csrfToken);
|
})
|
||||||
setGroups([...groups, newGroup]);
|
.catch(error => {
|
||||||
}
|
console.error('Error handling group:', error);
|
||||||
setIsGroupModalOpen(false);
|
alert('Erreur lors de l\'opération sur le groupe');
|
||||||
} catch (error) {
|
});
|
||||||
logger.error('Error handling group:', error);
|
} else {
|
||||||
alert('Erreur lors de l\'opération sur le groupe');
|
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.');
|
alert('Groupe supprimé avec succès.');
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.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.');
|
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 => {
|
const filteredFiles = fichiers.filter(file => {
|
||||||
if (!selectedGroup) return true;
|
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 = [
|
const columnsFiles = [
|
||||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
{ name: 'Nom du fichier', transform: (row) => row.name },
|
||||||
{ name: 'Groupe', transform: (row) => row.group ? row.group.name : 'Aucun' },
|
{ name: 'Groupes', transform: (row) => row.groups && row.groups.length > 0 ? row.groups.map(group => group.name).join(', ') : 'Aucun' },
|
||||||
{ name: 'Date de création', transform: (row) => formatDate(new Date (row.date_added),"DD/MM/YYYY hh:mm:ss") },
|
|
||||||
{ name: 'Fichier Obligatoire', transform: (row) => row.is_required ? 'Oui' : 'Non' },
|
|
||||||
{ name: 'Ordre de fusion', transform: (row) => row.order },
|
|
||||||
{ name: 'Actions', transform: (row) => (
|
{ name: 'Actions', transform: (row) => (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
{row.file && (
|
{row.file && (
|
||||||
@ -206,9 +245,6 @@ export default function FilesManagement({ csrfToken }) {
|
|||||||
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
|
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
|
||||||
<Trash2 size={16} />
|
<Trash2 size={16} />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => handleSignatureRequest(row)} className="text-green-500 hover:text-green-700">
|
|
||||||
<Signature size={16} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
setIsOpen={setIsModalOpen}
|
setIsOpen={(isOpen) => {
|
||||||
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
|
setIsModalOpen(isOpen);
|
||||||
|
if (!isOpen) {
|
||||||
|
setFileToEdit(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
title={isEditing ? 'Modification du document' : 'Ajouter un document'}
|
||||||
ContentComponent={() => (
|
ContentComponent={() => (
|
||||||
<FileUpload
|
<FileUpload
|
||||||
onFileUpload={handleFileUpload}
|
handleCreateTemplateMaster={handleCreateTemplateMaster}
|
||||||
fileToEdit={fileToEdit}
|
fileToEdit={fileToEdit}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
modalClassName='w-4/5 h-4/5'
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isGroupModalOpen}
|
isOpen={isGroupModalOpen}
|
||||||
@ -324,14 +344,6 @@ export default function FilesManagement({ csrfToken }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{token && selectedFile && (
|
|
||||||
<DocusealBuilder
|
|
||||||
token={token}
|
|
||||||
headers={{
|
|
||||||
'Authorization': `Bearer Rh2CC75ZMZqirmtBGA5NRjUzj8hr9eDYTBeZxv3jgzb`
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</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
|
//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
|
// GESTION LOGIN
|
||||||
export const BE_AUTH_NEW_PASSWORD_URL = `${BASE_URL}/Auth/newPassword`
|
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_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_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_REGISTERFORMS_URL = `${BASE_URL}/Subscriptions/registerForms`
|
||||||
export const BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL = `${BASE_URL}/Subscriptions/registrationFileTemplates`
|
export const BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL = `${BASE_URL}/Subscriptions/registrationTemplateMasters`
|
||||||
export const BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL = `${BASE_URL}/Subscriptions/registrationFiles`
|
export const BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationTemplates`
|
||||||
export const BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`
|
export const BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`
|
||||||
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`
|
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`
|
||||||
|
|
||||||
|
|||||||
@ -35,11 +35,13 @@ services:
|
|||||||
- 8080:8080
|
- 8080:8080
|
||||||
volumes:
|
volumes:
|
||||||
- ./Back-End:/Back-End
|
- ./Back-End:/Back-End
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Paris
|
- TZ=Europe/Paris
|
||||||
- TEST_MODE=True
|
- TEST_MODE=True
|
||||||
- CORS_ALLOWED_ORIGINS=http://localhost:3000
|
- CORS_ALLOWED_ORIGINS=http://localhost:3000
|
||||||
- CSRF_TRUSTED_ORIGINS=http://localhost:3000,http://localhost:8080
|
- CSRF_TRUSTED_ORIGINS=http://localhost:3000
|
||||||
links:
|
links:
|
||||||
- "database:database"
|
- "database:database"
|
||||||
- "redis:redis"
|
- "redis:redis"
|
||||||
@ -66,17 +68,19 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./initDocusealUsers.sh:/docker-entrypoint-initdb.d/initDocusealUsers.sh
|
- ./initDocusealUsers.sh:/docker-entrypoint-initdb.d/initDocusealUsers.sh
|
||||||
|
|
||||||
frontend:
|
# frontend:
|
||||||
build:
|
# build:
|
||||||
context: ./Front-End
|
# context: ./Front-End
|
||||||
args:
|
# args:
|
||||||
- BUILD_MODE=development
|
# - BUILD_MODE=development
|
||||||
ports:
|
# ports:
|
||||||
- 3000:3000
|
# - 3000:3000
|
||||||
volumes:
|
# volumes:
|
||||||
- ./Front-End:/app
|
# - ./Front-End:/app
|
||||||
environment:
|
# env_file:
|
||||||
- TZ=Europe/Paris
|
# - .env
|
||||||
depends_on:
|
# environment:
|
||||||
- backend
|
# - TZ=Europe/Paris
|
||||||
|
# depends_on:
|
||||||
|
# - backend
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user