mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
Merge remote-tracking branch 'origin/WIP_Inscriptions' into develop
This commit is contained in:
@ -4,11 +4,22 @@ from django.template.loader import get_template
|
|||||||
|
|
||||||
from xhtml2pdf import pisa
|
from xhtml2pdf import pisa
|
||||||
|
|
||||||
|
class PDFResult:
|
||||||
|
def __init__(self, content):
|
||||||
|
self.content = content
|
||||||
|
|
||||||
def render_to_pdf(template_src, context_dict={}):
|
def render_to_pdf(template_src, context_dict={}):
|
||||||
|
"""
|
||||||
|
Génère un PDF à partir d'un template HTML et retourne le contenu en mémoire.
|
||||||
|
"""
|
||||||
template = get_template(template_src)
|
template = get_template(template_src)
|
||||||
html = template.render(context_dict)
|
html = template.render(context_dict)
|
||||||
result = BytesIO()
|
result = BytesIO()
|
||||||
pdf = pisa.pisaDocument(BytesIO(html.encode("UTF-8")), result)
|
pdf = pisa.pisaDocument(BytesIO(html.encode("UTF-8")), result)
|
||||||
|
|
||||||
if pdf.err:
|
if pdf.err:
|
||||||
return HttpResponse("Invalid PDF", status_code=400, content_type='text/plain')
|
# Lever une exception ou retourner None en cas d'erreur
|
||||||
return HttpResponse(result.getvalue(), content_type='application/pdf')
|
raise ValueError("Erreur lors de la génération du PDF.")
|
||||||
|
|
||||||
|
# Retourner le contenu du PDF en mémoire
|
||||||
|
return PDFResult(result.getvalue())
|
||||||
@ -6,8 +6,8 @@ from Subscriptions.models import (
|
|||||||
Fee,
|
Fee,
|
||||||
Discount,
|
Discount,
|
||||||
RegistrationFileGroup,
|
RegistrationFileGroup,
|
||||||
RegistrationTemplateMaster,
|
RegistrationSchoolFileMaster,
|
||||||
RegistrationTemplate
|
RegistrationSchoolFileTemplate
|
||||||
)
|
)
|
||||||
from Auth.models import Profile, ProfileRole
|
from Auth.models import Profile, ProfileRole
|
||||||
from School.models import (
|
from School.models import (
|
||||||
|
|||||||
@ -9,6 +9,8 @@ from Establishment.models import Establishment
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
class Language(models.Model):
|
class Language(models.Model):
|
||||||
"""
|
"""
|
||||||
Représente une langue parlée par l’élève.
|
Représente une langue parlée par l’élève.
|
||||||
@ -83,7 +85,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('RegistrationTemplate', blank=True, related_name='students')
|
registration_files = models.ManyToManyField('RegistrationSchoolFileTemplate', 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)
|
||||||
@ -163,19 +165,13 @@ class RegistrationFileGroup(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.group.name} - {self.id}'
|
||||||
|
|
||||||
def registration_file_path(instance, filename):
|
def registration_file_path(instance, filename):
|
||||||
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
|
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
|
||||||
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
|
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
|
||||||
|
|
||||||
class RegistrationTemplateMaster(models.Model):
|
|
||||||
groups = models.ManyToManyField(RegistrationFileGroup, related_name='template_masters', blank=True)
|
|
||||||
id = models.IntegerField(primary_key=True)
|
|
||||||
name = models.CharField(max_length=255, default="")
|
|
||||||
is_required = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.group.name} - {self.id}'
|
|
||||||
|
|
||||||
class RegistrationForm(models.Model):
|
class RegistrationForm(models.Model):
|
||||||
class RegistrationFormStatus(models.IntegerChoices):
|
class RegistrationFormStatus(models.IntegerChoices):
|
||||||
RF_IDLE = 0, _('Pas de dossier d\'inscription')
|
RF_IDLE = 0, _('Pas de dossier d\'inscription')
|
||||||
@ -211,7 +207,7 @@ class RegistrationForm(models.Model):
|
|||||||
# Many-to-Many Relationship
|
# Many-to-Many Relationship
|
||||||
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
|
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
|
||||||
fileGroup = models.ForeignKey(RegistrationFileGroup,
|
fileGroup = models.ForeignKey(RegistrationFileGroup,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.SET_NULL,
|
||||||
related_name='register_forms',
|
related_name='register_forms',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True)
|
blank=True)
|
||||||
@ -223,16 +219,58 @@ class RegistrationForm(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
||||||
|
|
||||||
def registration_file_upload_to(instance, filename):
|
def save(self, *args, **kwargs):
|
||||||
return f"registration_files/dossier_rf_{instance.registration_form.pk}/{filename}"
|
# Vérifier si un fichier existant doit être remplacé
|
||||||
|
if self.pk: # Si l'objet existe déjà dans la base de données
|
||||||
|
try:
|
||||||
|
old_instance = RegistrationForm.objects.get(pk=self.pk)
|
||||||
|
if old_instance.sepa_file and old_instance.sepa_file != self.sepa_file:
|
||||||
|
# Supprimer l'ancien fichier
|
||||||
|
old_instance.sepa_file.delete(save=False)
|
||||||
|
except RegistrationForm.DoesNotExist:
|
||||||
|
pass # L'objet n'existe pas encore, rien à supprimer
|
||||||
|
|
||||||
class RegistrationTemplate(models.Model):
|
# Appeler la méthode save originale
|
||||||
master = models.ForeignKey(RegistrationTemplateMaster, on_delete=models.CASCADE, related_name='templates', blank=True)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def registration_school_file_upload_to(instance, filename):
|
||||||
|
return f"registration_files/dossier_rf_{instance.registration_form.pk}/school/{filename}"
|
||||||
|
|
||||||
|
def registration_parent_file_upload_to(instance, filename):
|
||||||
|
return f"registration_files/dossier_rf_{instance.registration_form.pk}/parent/{filename}"
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
####################### MASTER FILES ########################
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
####### DocuSeal masters (documents école, à signer ou pas) #######
|
||||||
|
class RegistrationSchoolFileMaster(models.Model):
|
||||||
|
groups = models.ManyToManyField(RegistrationFileGroup, related_name='school_file_masters', blank=True)
|
||||||
|
id = models.IntegerField(primary_key=True)
|
||||||
|
name = models.CharField(max_length=255, default="")
|
||||||
|
is_required = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.group.name} - {self.id}'
|
||||||
|
|
||||||
|
####### Parent files masters (documents à fournir par les parents) #######
|
||||||
|
class RegistrationParentFileMaster(models.Model):
|
||||||
|
groups = models.ManyToManyField(RegistrationFileGroup, related_name='parent_file_masters', blank=True)
|
||||||
|
name = models.CharField(max_length=255, default="")
|
||||||
|
description = models.CharField(blank=True, null=True)
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
####################### CLONE FILES ########################
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
####### DocuSeal templates (par dossier d'inscription) #######
|
||||||
|
class RegistrationSchoolFileTemplate(models.Model):
|
||||||
|
master = models.ForeignKey(RegistrationSchoolFileMaster, on_delete=models.CASCADE, related_name='school_file_templates', blank=True)
|
||||||
id = models.IntegerField(primary_key=True)
|
id = models.IntegerField(primary_key=True)
|
||||||
slug = models.CharField(max_length=255, default="")
|
slug = models.CharField(max_length=255, default="")
|
||||||
name = models.CharField(max_length=255, default="")
|
name = models.CharField(max_length=255, default="")
|
||||||
registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='templates', blank=True)
|
registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='school_file_templates', blank=True)
|
||||||
file = models.FileField(null=True,blank=True, upload_to=registration_file_upload_to)
|
file = models.FileField(null=True,blank=True, upload_to=registration_school_file_upload_to)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@ -242,7 +280,41 @@ class RegistrationTemplate(models.Model):
|
|||||||
"""
|
"""
|
||||||
Récupère tous les fichiers liés à un dossier d’inscription donné.
|
Récupère tous les fichiers liés à un dossier d’inscription donné.
|
||||||
"""
|
"""
|
||||||
registration_files = RegistrationTemplate.objects.filter(registration_form=register_form_id)
|
registration_files = RegistrationSchoolFileTemplate.objects.filter(registration_form=register_form_id)
|
||||||
|
filenames = []
|
||||||
|
for reg_file in registration_files:
|
||||||
|
filenames.append(reg_file.file.path)
|
||||||
|
return filenames
|
||||||
|
|
||||||
|
####### Parent files templates (par dossier d'inscription) #######
|
||||||
|
class RegistrationParentFileTemplate(models.Model):
|
||||||
|
master = models.ForeignKey(RegistrationParentFileMaster, on_delete=models.CASCADE, related_name='parent_file_templates', blank=True)
|
||||||
|
registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='parent_file_templates', blank=True)
|
||||||
|
file = models.FileField(null=True,blank=True, upload_to=registration_parent_file_upload_to)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.pk: # Si l'objet existe déjà dans la base de données
|
||||||
|
try:
|
||||||
|
old_instance = RegistrationParentFileTemplate.objects.get(pk=self.pk)
|
||||||
|
if old_instance.file and (not self.file or self.file.name == ''):
|
||||||
|
if os.path.exists(old_instance.file.path):
|
||||||
|
old_instance.file.delete(save=False)
|
||||||
|
self.file = None
|
||||||
|
else:
|
||||||
|
print(f"Le fichier {old_instance.file.path} n'existe pas.")
|
||||||
|
except RegistrationParentFileTemplate.DoesNotExist:
|
||||||
|
print("Ancienne instance introuvable.")
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_files_from_rf(register_form_id):
|
||||||
|
"""
|
||||||
|
Récupère tous les fichiers liés à un dossier d’inscription donné.
|
||||||
|
"""
|
||||||
|
registration_files = RegistrationParentFileTemplate.objects.filter(registration_form=register_form_id)
|
||||||
filenames = []
|
filenames = []
|
||||||
for reg_file in registration_files:
|
for reg_file in registration_files:
|
||||||
filenames.append(reg_file.file.path)
|
filenames.append(reg_file.file.path)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationTemplateMaster, RegistrationTemplate
|
from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster, RegistrationParentFileTemplate
|
||||||
from School.models import SchoolClass, Fee, Discount, FeeType
|
from School.models import SchoolClass, Fee, Discount, FeeType
|
||||||
from School.serializers import FeeSerializer, DiscountSerializer
|
from School.serializers import FeeSerializer, DiscountSerializer
|
||||||
from Auth.models import ProfileRole, Profile
|
from Auth.models import ProfileRole, Profile
|
||||||
@ -12,18 +12,43 @@ import pytz
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import Subscriptions.util as util
|
import Subscriptions.util as util
|
||||||
|
|
||||||
class RegistrationTemplateMasterSerializer(serializers.ModelSerializer):
|
class RegistrationSchoolFileMasterSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(required=False)
|
id = serializers.IntegerField(required=False)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RegistrationTemplateMaster
|
model = RegistrationSchoolFileMaster
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
class RegistrationTemplateSerializer(serializers.ModelSerializer):
|
class RegistrationParentFileMasterSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(required=False)
|
id = serializers.IntegerField(required=False)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RegistrationTemplate
|
model = RegistrationParentFileMaster
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
class RegistrationSchoolFileTemplateSerializer(serializers.ModelSerializer):
|
||||||
|
id = serializers.IntegerField(required=False)
|
||||||
|
file_url = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = RegistrationSchoolFileTemplate
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def get_file_url(self, obj):
|
||||||
|
# Retourne l'URL complète du fichier si disponible
|
||||||
|
return obj.file.url if obj.file else None
|
||||||
|
|
||||||
|
class RegistrationParentFileTemplateSerializer(serializers.ModelSerializer):
|
||||||
|
id = serializers.IntegerField(required=False)
|
||||||
|
file_url = serializers.SerializerMethodField()
|
||||||
|
master_name = serializers.CharField(source='master.name', read_only=True)
|
||||||
|
master_description = serializers.CharField(source='master.description', read_only=True)
|
||||||
|
class Meta:
|
||||||
|
model = RegistrationParentFileTemplate
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def get_file_url(self, obj):
|
||||||
|
# Retourne l'URL complète du fichier si disponible
|
||||||
|
return obj.file.url if obj.file else None
|
||||||
|
|
||||||
class GuardianSimpleSerializer(serializers.ModelSerializer):
|
class GuardianSimpleSerializer(serializers.ModelSerializer):
|
||||||
associated_profile_email = serializers.SerializerMethodField()
|
associated_profile_email = serializers.SerializerMethodField()
|
||||||
|
|
||||||
@ -199,7 +224,7 @@ class RegistrationFormSerializer(serializers.ModelSerializer):
|
|||||||
sepa_file = serializers.FileField(required=False)
|
sepa_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 = RegistrationTemplateSerializer(many=True, required=False)
|
registration_files = RegistrationSchoolFileTemplateSerializer(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()
|
||||||
@ -280,7 +305,7 @@ class RegistrationFormByParentSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RegistrationForm
|
model = RegistrationForm
|
||||||
fields = ['student', 'status']
|
fields = ['student', 'status', 'sepa_file']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(RegistrationFormByParentSerializer, self).__init__(*args, **kwargs)
|
super(RegistrationFormByParentSerializer, self).__init__(*args, **kwargs)
|
||||||
|
|||||||
@ -7,16 +7,27 @@ from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archi
|
|||||||
# SubClasses
|
# SubClasses
|
||||||
from .views import StudentView, GuardianView, ChildrenListView, StudentListView, DissociateGuardianView
|
from .views import StudentView, GuardianView, ChildrenListView, StudentListView, DissociateGuardianView
|
||||||
# Files
|
# Files
|
||||||
from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
|
from .views import (
|
||||||
|
RegistrationSchoolFileMasterView,
|
||||||
|
RegistrationSchoolFileMasterSimpleView,
|
||||||
|
RegistrationSchoolFileTemplateView,
|
||||||
|
RegistrationSchoolFileTemplateSimpleView,
|
||||||
|
RegistrationParentFileMasterSimpleView,
|
||||||
|
RegistrationParentFileMasterView,
|
||||||
|
RegistrationParentFileTemplateSimpleView,
|
||||||
|
RegistrationParentFileTemplateView
|
||||||
|
)
|
||||||
|
|
||||||
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||||
from .views import registration_file_views, get_templates_by_rf
|
from .views import registration_file_views, get_school_file_templates_by_rf, get_parent_file_templates_by_rf
|
||||||
|
|
||||||
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"),
|
||||||
re_path(r'^registerForms/(?P<id>[0-9]+)/resend$', resend, name="resend"),
|
re_path(r'^registerForms/(?P<id>[0-9]+)/resend$', resend, name="resend"),
|
||||||
re_path(r'^registerForms/(?P<id>[0-9]+)/send$', send, name="send"),
|
re_path(r'^registerForms/(?P<id>[0-9]+)/send$', send, name="send"),
|
||||||
re_path(r'^registerForms/(?P<id>[0-9]+)$', RegisterFormWithIdView.as_view(), name="registerForm"),
|
re_path(r'^registerForms/(?P<id>[0-9]+)$', RegisterFormWithIdView.as_view(), name="registerForm"),
|
||||||
re_path(r'^registerForms/(?P<id>[0-9]+)/templates$', get_templates_by_rf, name="get_templates_by_rf"),
|
re_path(r'^registerForms/(?P<id>[0-9]+)/school_file_templates$', get_school_file_templates_by_rf, name="get_school_file_templates_by_rf"),
|
||||||
|
re_path(r'^registerForms/(?P<id>[0-9]+)/parent_file_templates$', get_parent_file_templates_by_rf, name="get_parent_file_templates_by_rf"),
|
||||||
re_path(r'^registerForms$', RegisterFormView.as_view(), name="registerForms"),
|
re_path(r'^registerForms$', RegisterFormView.as_view(), name="registerForms"),
|
||||||
|
|
||||||
# Page INSCRIPTION - Liste des élèves
|
# Page INSCRIPTION - Liste des élèves
|
||||||
@ -33,11 +44,17 @@ urlpatterns = [
|
|||||||
re_path(r'^registrationFileGroups/(?P<id>[0-9]+)/templates$', get_registration_files_by_group, name="get_registration_files_by_group"),
|
re_path(r'^registrationFileGroups/(?P<id>[0-9]+)/templates$', get_registration_files_by_group, name="get_registration_files_by_group"),
|
||||||
re_path(r'^registrationFileGroups$', RegistrationFileGroupView.as_view(), name='registrationFileGroups'),
|
re_path(r'^registrationFileGroups$', RegistrationFileGroupView.as_view(), name='registrationFileGroups'),
|
||||||
|
|
||||||
re_path(r'^registrationTemplateMasters/(?P<id>[0-9]+)$', RegistrationTemplateMasterSimpleView.as_view(), name='registrationTemplateMasters'),
|
re_path(r'^registrationSchoolFileMasters/(?P<id>[0-9]+)$', RegistrationSchoolFileMasterSimpleView.as_view(), name='registrationSchoolFileMasters'),
|
||||||
re_path(r'^registrationTemplateMasters$', RegistrationTemplateMasterView.as_view(), name='registrationTemplateMasters'),
|
re_path(r'^registrationSchoolFileMasters$', RegistrationSchoolFileMasterView.as_view(), name='registrationSchoolFileMasters'),
|
||||||
|
|
||||||
re_path(r'^registrationTemplates/(?P<id>[0-9]+)$', RegistrationTemplateSimpleView.as_view(), name='registrationTemplates'),
|
re_path(r'^registrationParentFileMasters/(?P<id>[0-9]+)$', RegistrationParentFileMasterSimpleView.as_view(), name='registrationParentFileMasters'),
|
||||||
re_path(r'^registrationTemplates$', RegistrationTemplateView.as_view(), name="registrationTemplates"),
|
re_path(r'^registrationParentFileMasters$', RegistrationParentFileMasterView.as_view(), name="registrationParentFileMasters"),
|
||||||
|
|
||||||
|
re_path(r'^registrationSchoolFileTemplates/(?P<id>[0-9]+)$', RegistrationSchoolFileTemplateSimpleView.as_view(), name='registrationSchoolFileTemplates'),
|
||||||
|
re_path(r'^registrationSchoolFileTemplates$', RegistrationSchoolFileTemplateView.as_view(), name="registrationSchoolFileTemplates"),
|
||||||
|
|
||||||
|
re_path(r'^registrationParentFileTemplates/(?P<id>[0-9]+)$', RegistrationParentFileTemplateSimpleView.as_view(), name='registrationParentFileTemplates'),
|
||||||
|
re_path(r'^registrationParentFileTemplates$', RegistrationParentFileTemplateView.as_view(), name="registrationSchoolFileTregistrationParentFileTemplatesemplates"),
|
||||||
|
|
||||||
re_path(r'^students/(?P<student_id>[0-9]+)/guardians/(?P<guardian_id>[0-9]+)/dissociate', DissociateGuardianView.as_view(), name='dissociate-guardian'),
|
re_path(r'^students/(?P<student_id>[0-9]+)/guardians/(?P<guardian_id>[0-9]+)/dissociate', DissociateGuardianView.as_view(), name='dissociate-guardian'),
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,9 @@ from rest_framework.parsers import JSONParser
|
|||||||
from PyPDF2 import PdfMerger
|
from PyPDF2 import PdfMerger
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def recupereListeFichesInscription():
|
def recupereListeFichesInscription():
|
||||||
"""
|
"""
|
||||||
@ -121,7 +124,6 @@ def rfToPDF(registerForm, filename):
|
|||||||
Génère le PDF d'un dossier d'inscription et l'associe au RegistrationForm.
|
Génère le PDF d'un dossier d'inscription et l'associe au RegistrationForm.
|
||||||
"""
|
"""
|
||||||
filename = filename.replace(" ", "_")
|
filename = filename.replace(" ", "_")
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'pdf_title': f"Dossier d'inscription de {registerForm.student.first_name}",
|
'pdf_title': f"Dossier d'inscription de {registerForm.student.first_name}",
|
||||||
'signatureDate': convertToStr(_now(), '%d-%m-%Y'),
|
'signatureDate': convertToStr(_now(), '%d-%m-%Y'),
|
||||||
@ -131,20 +133,37 @@ def rfToPDF(registerForm, filename):
|
|||||||
|
|
||||||
# Générer le PDF
|
# Générer le PDF
|
||||||
pdf = renderers.render_to_pdf('pdfs/dossier_inscription.html', data)
|
pdf = renderers.render_to_pdf('pdfs/dossier_inscription.html', data)
|
||||||
|
if not pdf:
|
||||||
|
raise ValueError("Erreur lors de la génération du PDF.")
|
||||||
|
|
||||||
# Vérifier si un fichier avec le même nom existe déjà et le supprimer
|
# Vérifier si un fichier avec le même nom existe déjà et le supprimer
|
||||||
if registerForm.registration_file and os.path.exists(registerForm.registration_file.path):
|
if registerForm.registration_file and registerForm.registration_file.name:
|
||||||
os.remove(registerForm.registration_file.path)
|
# Vérifiez si le chemin est déjà absolu ou relatif
|
||||||
registerForm.registration_file.delete(save=False)
|
if os.path.isabs(registerForm.registration_file.name):
|
||||||
|
existing_file_path = registerForm.registration_file.name
|
||||||
|
else:
|
||||||
|
existing_file_path = os.path.join(settings.MEDIA_ROOT, registerForm.registration_file.name.lstrip('/'))
|
||||||
|
|
||||||
|
# Vérifier si le fichier existe et le supprimer
|
||||||
|
if os.path.exists(existing_file_path):
|
||||||
|
print(f'exist ! REMOVE')
|
||||||
|
os.remove(existing_file_path)
|
||||||
|
registerForm.registration_file.delete(save=False)
|
||||||
|
else:
|
||||||
|
print(f'File does not exist: {existing_file_path}')
|
||||||
|
|
||||||
# Enregistrer directement le fichier dans le champ registration_file
|
# Enregistrer directement le fichier dans le champ registration_file
|
||||||
registerForm.registration_file.save(
|
try:
|
||||||
os.path.basename(filename),
|
registerForm.registration_file.save(
|
||||||
File(BytesIO(pdf.content)), # Utilisation de BytesIO pour éviter l'écriture sur le disque
|
os.path.basename(filename), # Utiliser uniquement le nom de fichier
|
||||||
save=True
|
File(BytesIO(pdf.content)),
|
||||||
)
|
save=True
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lors de la sauvegarde du fichier PDF : {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
return registerForm.registration_file.path
|
return registerForm.registration_file
|
||||||
|
|
||||||
def delete_registration_files(registerForm):
|
def delete_registration_files(registerForm):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,5 +1,14 @@
|
|||||||
from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive, get_templates_by_rf
|
from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive, get_school_file_templates_by_rf, get_parent_file_templates_by_rf
|
||||||
from .registration_file_views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
|
from .registration_file_views import (
|
||||||
|
RegistrationSchoolFileMasterView,
|
||||||
|
RegistrationSchoolFileMasterSimpleView,
|
||||||
|
RegistrationSchoolFileTemplateView,
|
||||||
|
RegistrationSchoolFileTemplateSimpleView,
|
||||||
|
RegistrationParentFileMasterView,
|
||||||
|
RegistrationParentFileMasterSimpleView,
|
||||||
|
RegistrationParentFileTemplateSimpleView,
|
||||||
|
RegistrationParentFileTemplateView
|
||||||
|
)
|
||||||
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||||
from .student_views import StudentView, StudentListView, ChildrenListView
|
from .student_views import StudentView, StudentListView, ChildrenListView
|
||||||
from .guardian_views import GuardianView, DissociateGuardianView
|
from .guardian_views import GuardianView, DissociateGuardianView
|
||||||
@ -10,14 +19,19 @@ __all__ = [
|
|||||||
'send',
|
'send',
|
||||||
'resend',
|
'resend',
|
||||||
'archive',
|
'archive',
|
||||||
'RegistrationTemplateView',
|
'RegistrationSchoolFileTemplateView',
|
||||||
'RegistrationTemplateSimpleView',
|
'RegistrationSchoolFileTemplateSimpleView',
|
||||||
'RegistrationTemplateMasterView',
|
'RegistrationParentFileMasterSimpleView',
|
||||||
'RegistrationTemplateMasterSimpleView',
|
'RegistrationParentFileMasterView',
|
||||||
|
'RegistrationSchoolFileMasterView',
|
||||||
|
'RegistrationSchoolFileMasterSimpleView',
|
||||||
|
'RegistrationParentFileTemplateSimpleView',
|
||||||
|
'RegistrationParentFileTemplateView',
|
||||||
'RegistrationFileGroupView',
|
'RegistrationFileGroupView',
|
||||||
'RegistrationFileGroupSimpleView',
|
'RegistrationFileGroupSimpleView',
|
||||||
'get_registration_files_by_group',
|
'get_registration_files_by_group',
|
||||||
'get_templates_by_rf',
|
'get_school_file_templates_by_rf',
|
||||||
|
'get_parent_file_templates_by_rf'
|
||||||
'StudentView',
|
'StudentView',
|
||||||
'StudentListView',
|
'StudentListView',
|
||||||
'ChildrenListView',
|
'ChildrenListView',
|
||||||
|
|||||||
@ -14,9 +14,9 @@ from django.core.files import File
|
|||||||
import Subscriptions.mailManager as mailer
|
import Subscriptions.mailManager as mailer
|
||||||
import Subscriptions.util as util
|
import Subscriptions.util as util
|
||||||
|
|
||||||
from Subscriptions.serializers import RegistrationFormSerializer
|
from Subscriptions.serializers import RegistrationFormSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileTemplateSerializer
|
||||||
from Subscriptions.pagination import CustomPagination
|
from Subscriptions.pagination import CustomPagination
|
||||||
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationTemplate, RegistrationFileGroup
|
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationSchoolFileTemplate, RegistrationFileGroup, RegistrationParentFileTemplate
|
||||||
from Subscriptions.automate import updateStateMachine
|
from Subscriptions.automate import updateStateMachine
|
||||||
|
|
||||||
from N3wtSchool import settings, bdd
|
from N3wtSchool import settings, bdd
|
||||||
@ -254,30 +254,37 @@ class RegisterFormWithIdView(APIView):
|
|||||||
if _status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW:
|
if _status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW:
|
||||||
try:
|
try:
|
||||||
# Génération de la fiche d'inscription au format PDF
|
# Génération de la fiche d'inscription au format PDF
|
||||||
base_dir = f"data/registration_files/dossier_rf_{registerForm.pk}"
|
base_dir = os.path.join(settings.MEDIA_ROOT, f"registration_files/dossier_rf_{registerForm.pk}")
|
||||||
os.makedirs(base_dir, exist_ok=True)
|
os.makedirs(base_dir, exist_ok=True)
|
||||||
|
|
||||||
# Fichier PDF initial
|
# Fichier PDF initial
|
||||||
initial_pdf = f"{base_dir}/rf_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf"
|
initial_pdf = f"{base_dir}/Inscription_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf"
|
||||||
registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
|
registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
|
||||||
registerForm.save()
|
registerForm.save()
|
||||||
|
|
||||||
# Récupération des fichiers d'inscription
|
# Récupération des fichiers d'inscription
|
||||||
fileNames = RegistrationTemplate.get_files_from_rf(registerForm.pk)
|
# fileNames = RegistrationSchoolFileTemplate.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)
|
||||||
|
|
||||||
# Création du fichier PDF Fusionné
|
# # Création du fichier PDF Fusionné
|
||||||
merged_pdf_content = util.merge_files_pdf(fileNames)
|
# merged_pdf_content = util.merge_files_pdf(fileNames)
|
||||||
|
|
||||||
|
# # Mise à jour du champ registration_file avec le fichier fusionné
|
||||||
|
# registerForm.registration_file.save(
|
||||||
|
# f"dossier_complet.pdf",
|
||||||
|
# File(merged_pdf_content),
|
||||||
|
# save=True
|
||||||
|
# )
|
||||||
|
|
||||||
# Mise à jour du champ registration_file avec le fichier fusionné
|
|
||||||
registerForm.registration_file.save(
|
|
||||||
f"dossier_complet_{registerForm.pk}.pdf",
|
|
||||||
File(merged_pdf_content),
|
|
||||||
save=True
|
|
||||||
)
|
|
||||||
# Mise à jour de l'automate
|
# Mise à jour de l'automate
|
||||||
updateStateMachine(registerForm, 'EVENT_SIGNATURE')
|
# Vérification de la présence du fichier SEPA
|
||||||
|
if registerForm.sepa_file:
|
||||||
|
# Mise à jour de l'automate pour SEPA
|
||||||
|
updateStateMachine(registerForm, 'EVENT_SIGNATURE_SEPA')
|
||||||
|
else:
|
||||||
|
# Mise à jour de l'automate pour une signature classique
|
||||||
|
updateStateMachine(registerForm, 'EVENT_SIGNATURE')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
@ -413,10 +420,40 @@ def resend(request,id):
|
|||||||
operation_summary="Récupérer les fichiers à signer d'un dossier d'inscription donné"
|
operation_summary="Récupérer les fichiers à signer d'un dossier d'inscription donné"
|
||||||
)
|
)
|
||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
def get_templates_by_rf(request, id):
|
def get_school_file_templates_by_rf(request, id):
|
||||||
try:
|
try:
|
||||||
templates = RegistrationTemplate.objects.filter(registration_form=id)
|
# Récupérer les templates associés au RegistrationForm donné
|
||||||
templates_data = list(templates.values())
|
templates = RegistrationSchoolFileTemplate.objects.filter(registration_form=id)
|
||||||
return JsonResponse(templates_data, safe=False)
|
|
||||||
except RegistrationFileGroup.DoesNotExist:
|
# Sérialiser les données
|
||||||
return JsonResponse({'error': 'Le groupe de fichiers n\'a pas été trouvé'}, status=404)
|
serializer = RegistrationSchoolFileTemplateSerializer(templates, many=True)
|
||||||
|
|
||||||
|
# Retourner les données sérialisées
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
except RegistrationSchoolFileTemplate.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'Aucun template trouvé pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
method='get',
|
||||||
|
responses={200: openapi.Response('Success', schema=openapi.Schema(
|
||||||
|
type=openapi.TYPE_OBJECT,
|
||||||
|
properties={
|
||||||
|
'message': openapi.Schema(type=openapi.TYPE_STRING)
|
||||||
|
}
|
||||||
|
))},
|
||||||
|
operation_description="Récupère les pièces à fournir d'un dossier d'inscription donné",
|
||||||
|
operation_summary="Récupérer les pièces à fournir d'un dossier d'inscription donné"
|
||||||
|
)
|
||||||
|
@api_view(['GET'])
|
||||||
|
def get_parent_file_templates_by_rf(request, id):
|
||||||
|
try:
|
||||||
|
# Récupérer les pièces à fournir associés au RegistrationForm donné
|
||||||
|
parent_files = RegistrationParentFileTemplate.objects.filter(registration_form=id)
|
||||||
|
|
||||||
|
# Sérialiser les données
|
||||||
|
serializer = RegistrationParentFileTemplateSerializer(parent_files, many=True)
|
||||||
|
|
||||||
|
# Retourner les données sérialisées
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
except RegistrationParentFileTemplate.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'Aucune pièce à fournir trouvée pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
@ -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, RegistrationTemplateMaster
|
from Subscriptions.models import RegistrationFileGroup, RegistrationSchoolFileMaster
|
||||||
from N3wtSchool import bdd
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
class RegistrationFileGroupView(APIView):
|
class RegistrationFileGroupView(APIView):
|
||||||
@ -124,7 +124,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)
|
||||||
templateMasters = RegistrationTemplateMaster.objects.filter(groups=group)
|
templateMasters = RegistrationSchoolFileMaster.objects.filter(groups=group)
|
||||||
templates_data = list(templateMasters.values())
|
templates_data = list(templateMasters.values())
|
||||||
return JsonResponse(templates_data, safe=False)
|
return JsonResponse(templates_data, safe=False)
|
||||||
except RegistrationFileGroup.DoesNotExist:
|
except RegistrationFileGroup.DoesNotExist:
|
||||||
|
|||||||
@ -6,64 +6,64 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from Subscriptions.serializers import RegistrationTemplateMasterSerializer, RegistrationTemplateSerializer
|
from Subscriptions.serializers import RegistrationSchoolFileMasterSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileMasterSerializer, RegistrationParentFileTemplateSerializer
|
||||||
from Subscriptions.models import RegistrationTemplateMaster, RegistrationTemplate
|
from Subscriptions.models import RegistrationSchoolFileMaster, RegistrationSchoolFileTemplate, RegistrationParentFileMaster, RegistrationParentFileTemplate
|
||||||
from N3wtSchool import bdd
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
class RegistrationTemplateMasterView(APIView):
|
class RegistrationSchoolFileMasterView(APIView):
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère tous les masters de templates d'inscription",
|
operation_description="Récupère tous les masters de templates d'inscription",
|
||||||
responses={200: RegistrationTemplateMasterSerializer(many=True)}
|
responses={200: RegistrationSchoolFileMasterSerializer(many=True)}
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
masters = RegistrationTemplateMaster.objects.all()
|
masters = RegistrationSchoolFileMaster.objects.all()
|
||||||
serializer = RegistrationTemplateMasterSerializer(masters, many=True)
|
serializer = RegistrationSchoolFileMasterSerializer(masters, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Crée un nouveau master de template d'inscription",
|
operation_description="Crée un nouveau master de template d'inscription",
|
||||||
request_body=RegistrationTemplateMasterSerializer,
|
request_body=RegistrationSchoolFileMasterSerializer,
|
||||||
responses={
|
responses={
|
||||||
201: RegistrationTemplateMasterSerializer,
|
201: RegistrationSchoolFileMasterSerializer,
|
||||||
400: "Données invalides"
|
400: "Données invalides"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = RegistrationTemplateMasterSerializer(data=request.data)
|
serializer = RegistrationSchoolFileMasterSerializer(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 RegistrationSchoolFileMasterSimpleView(APIView):
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère un master de template d'inscription spécifique",
|
operation_description="Récupère un master de template d'inscription spécifique",
|
||||||
responses={
|
responses={
|
||||||
200: RegistrationTemplateMasterSerializer,
|
200: RegistrationSchoolFileMasterSerializer,
|
||||||
404: "Master 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)
|
master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _columnName='id', _value=id)
|
||||||
if master is None:
|
if master is None:
|
||||||
return JsonResponse({"errorMessage":'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
return JsonResponse({"errorMessage":'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
serializer = RegistrationTemplateMasterSerializer(master)
|
serializer = RegistrationSchoolFileMasterSerializer(master)
|
||||||
return JsonResponse(serializer.data, safe=False)
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Met à jour un master de template d'inscription existant",
|
operation_description="Met à jour un master de template d'inscription existant",
|
||||||
request_body=RegistrationTemplateMasterSerializer,
|
request_body=RegistrationSchoolFileMasterSerializer,
|
||||||
responses={
|
responses={
|
||||||
200: RegistrationTemplateMasterSerializer,
|
200: RegistrationSchoolFileMasterSerializer,
|
||||||
400: "Données invalides",
|
400: "Données invalides",
|
||||||
404: "Master 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)
|
master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _columnName='id', _value=id)
|
||||||
if master is None:
|
if master is None:
|
||||||
return JsonResponse({'erreur': 'Le master de template 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)
|
||||||
serializer = RegistrationTemplateMasterSerializer(master, data=request.data)
|
serializer = RegistrationSchoolFileMasterSerializer(master, data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
@ -77,67 +77,67 @@ class RegistrationTemplateMasterSimpleView(APIView):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
def delete(self, request, id):
|
def delete(self, request, id):
|
||||||
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
|
master = bdd.getObject(_objectName=RegistrationSchoolFileMaster, _columnName='id', _value=id)
|
||||||
if master is not None:
|
if master is not None:
|
||||||
master.delete()
|
master.delete()
|
||||||
return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||||
else:
|
else:
|
||||||
return JsonResponse({'erreur': 'Le master de template 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 RegistrationTemplateView(APIView):
|
class RegistrationSchoolFileTemplateView(APIView):
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère tous les templates d'inscription",
|
operation_description="Récupère tous les templates d'inscription",
|
||||||
responses={200: RegistrationTemplateSerializer(many=True)}
|
responses={200: RegistrationSchoolFileTemplateSerializer(many=True)}
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
templates = RegistrationTemplate.objects.all()
|
templates = RegistrationSchoolFileTemplate.objects.all()
|
||||||
serializer = RegistrationTemplateSerializer(templates, many=True)
|
serializer = RegistrationSchoolFileTemplateSerializer(templates, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Crée un nouveau template d'inscription",
|
operation_description="Crée un nouveau template d'inscription",
|
||||||
request_body=RegistrationTemplateSerializer,
|
request_body=RegistrationSchoolFileTemplateSerializer,
|
||||||
responses={
|
responses={
|
||||||
201: RegistrationTemplateSerializer,
|
201: RegistrationSchoolFileTemplateSerializer,
|
||||||
400: "Données invalides"
|
400: "Données invalides"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = RegistrationTemplateSerializer(data=request.data)
|
serializer = RegistrationSchoolFileTemplateSerializer(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 RegistrationSchoolFileTemplateSimpleView(APIView):
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère un template d'inscription spécifique",
|
operation_description="Récupère un template d'inscription spécifique",
|
||||||
responses={
|
responses={
|
||||||
200: RegistrationTemplateSerializer,
|
200: RegistrationSchoolFileTemplateSerializer,
|
||||||
404: "Template 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)
|
template = bdd.getObject(_objectName=RegistrationSchoolFileTemplate, _columnName='id', _value=id)
|
||||||
if template is None:
|
if template is None:
|
||||||
return JsonResponse({"errorMessage":'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
return JsonResponse({"errorMessage":'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
serializer = RegistrationTemplateSerializer(template)
|
serializer = RegistrationSchoolFileTemplateSerializer(template)
|
||||||
return JsonResponse(serializer.data, safe=False)
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Met à jour un template d'inscription existant",
|
operation_description="Met à jour un template d'inscription existant",
|
||||||
request_body=RegistrationTemplateSerializer,
|
request_body=RegistrationSchoolFileTemplateSerializer,
|
||||||
responses={
|
responses={
|
||||||
200: RegistrationTemplateSerializer,
|
200: RegistrationSchoolFileTemplateSerializer,
|
||||||
400: "Données invalides",
|
400: "Données invalides",
|
||||||
404: "Template 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)
|
template = bdd.getObject(_objectName=RegistrationSchoolFileTemplate, _columnName='id', _value=id)
|
||||||
if template is None:
|
if template is None:
|
||||||
return JsonResponse({'erreur': 'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
return JsonResponse({'erreur': 'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
serializer = RegistrationTemplateSerializer(template, data=request.data)
|
serializer = RegistrationSchoolFileTemplateSerializer(template, data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response({'message': 'Template 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)
|
||||||
@ -151,7 +151,156 @@ class RegistrationTemplateSimpleView(APIView):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
def delete(self, request, id):
|
def delete(self, request, id):
|
||||||
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
|
template = bdd.getObject(_objectName=RegistrationSchoolFileTemplate, _columnName='id', _value=id)
|
||||||
|
if template is not None:
|
||||||
|
template.delete()
|
||||||
|
return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
else:
|
||||||
|
return JsonResponse({'erreur': 'Le template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
class RegistrationParentFileMasterView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère tous les fichiers parents",
|
||||||
|
responses={200: RegistrationParentFileMasterSerializer(many=True)}
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
templates = RegistrationParentFileMaster.objects.all()
|
||||||
|
serializer = RegistrationParentFileMasterSerializer(templates, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Crée un nouveau fichier parent",
|
||||||
|
request_body=RegistrationParentFileMasterSerializer,
|
||||||
|
responses={
|
||||||
|
201: RegistrationParentFileMasterSerializer,
|
||||||
|
400: "Données invalides"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
serializer = RegistrationParentFileMasterSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
class RegistrationParentFileMasterSimpleView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère un fichier parent spécifique",
|
||||||
|
responses={
|
||||||
|
200: RegistrationParentFileMasterSerializer,
|
||||||
|
404: "Fichier parent non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileMaster, _columnName='id', _value=id)
|
||||||
|
if template is None:
|
||||||
|
return JsonResponse({"errorMessage":'Le fichier parent n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
serializer = RegistrationParentFileMasterSerializer(template)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Met à jour un fichier parent existant",
|
||||||
|
request_body=RegistrationParentFileMasterSerializer,
|
||||||
|
responses={
|
||||||
|
200: RegistrationParentFileMasterSerializer,
|
||||||
|
400: "Données invalides",
|
||||||
|
404: "Fichier parent non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def put(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileMaster, _columnName='id', _value=id)
|
||||||
|
if template is None:
|
||||||
|
return JsonResponse({'erreur': 'Le fichier parent n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
serializer = RegistrationParentFileMasterSerializer(template, data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response({'message': 'Fichier parent mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Supprime un fichier parent",
|
||||||
|
responses={
|
||||||
|
204: "Suppression réussie",
|
||||||
|
404: "Fichier parent non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def delete(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileMaster, _columnName='id', _value=id)
|
||||||
|
if template is not None:
|
||||||
|
template.delete()
|
||||||
|
return JsonResponse({'message': 'La suppression du fichier parent a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
else:
|
||||||
|
return JsonResponse({'erreur': 'Le fichier parent n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
class RegistrationParentFileTemplateView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère tous les templates d'inscription",
|
||||||
|
responses={200: RegistrationParentFileTemplateSerializer(many=True)}
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
templates = RegistrationParentFileTemplate.objects.all()
|
||||||
|
serializer = RegistrationParentFileTemplateSerializer(templates, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Crée un nouveau template d'inscription",
|
||||||
|
request_body=RegistrationParentFileTemplateSerializer,
|
||||||
|
responses={
|
||||||
|
201: RegistrationParentFileTemplateSerializer,
|
||||||
|
400: "Données invalides"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
serializer = RegistrationParentFileTemplateSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
class RegistrationParentFileTemplateSimpleView(APIView):
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupère un template d'inscription spécifique",
|
||||||
|
responses={
|
||||||
|
200: RegistrationParentFileTemplateSerializer,
|
||||||
|
404: "Template non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileTemplate, _columnName='id', _value=id)
|
||||||
|
if template is None:
|
||||||
|
return JsonResponse({"errorMessage":'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
serializer = RegistrationParentFileTemplateSerializer(template)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Met à jour un template d'inscription existant",
|
||||||
|
request_body=RegistrationParentFileTemplateSerializer,
|
||||||
|
responses={
|
||||||
|
200: RegistrationParentFileTemplateSerializer,
|
||||||
|
400: "Données invalides",
|
||||||
|
404: "Template non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def put(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileTemplate, _columnName='id', _value=id)
|
||||||
|
if template is None:
|
||||||
|
return JsonResponse({'erreur': 'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
serializer = RegistrationParentFileTemplateSerializer(template, data=request.data, partial=True)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response({'message': 'Template mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Supprime un template d'inscription",
|
||||||
|
responses={
|
||||||
|
204: "Suppression réussie",
|
||||||
|
404: "Template non trouvé"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def delete(self, request, id):
|
||||||
|
template = bdd.getObject(_objectName=RegistrationParentFileTemplate, _columnName='id', _value=id)
|
||||||
if template is not None:
|
if template is not None:
|
||||||
template.delete()
|
template.delete()
|
||||||
return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|||||||
@ -96,6 +96,13 @@ class ChildrenListView(APIView):
|
|||||||
|
|
||||||
students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__profile_role__profile__id', _value=id)
|
students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__profile_role__profile__id', _value=id)
|
||||||
if students:
|
if students:
|
||||||
students = students.filter(establishment=establishment_id).distinct()
|
students = students.filter(
|
||||||
|
establishment=establishment_id,
|
||||||
|
status__in=[
|
||||||
|
RegistrationForm.RegistrationFormStatus.RF_SENT,
|
||||||
|
RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW,
|
||||||
|
RegistrationForm.RegistrationFormStatus.RF_SEPA_SENT
|
||||||
|
]
|
||||||
|
).distinct()
|
||||||
students_serializer = RegistrationFormByParentSerializer(students, many=True)
|
students_serializer = RegistrationFormByParentSerializer(students, many=True)
|
||||||
return JsonResponse(students_serializer.data, safe=False)
|
return JsonResponse(students_serializer.data, safe=False)
|
||||||
|
|||||||
@ -23,10 +23,10 @@ import {
|
|||||||
fetchRegistrationPaymentModes,
|
fetchRegistrationPaymentModes,
|
||||||
fetchTuitionPaymentModes,
|
fetchTuitionPaymentModes,
|
||||||
} from '@/app/actions/schoolAction';
|
} from '@/app/actions/schoolAction';
|
||||||
import { fetchProfileRoles, fetchProfiles } from '@/app/actions/authAction';
|
import { fetchProfiles } from '@/app/actions/authAction';
|
||||||
import SidebarTabs from '@/components/SidebarTabs';
|
import SidebarTabs from '@/components/SidebarTabs';
|
||||||
import FilesGroupsManagement from '@/components/Structure/Files/FilesGroupsManagement';
|
import FilesGroupsManagement from '@/components/Structure/Files/FilesGroupsManagement';
|
||||||
import { fetchRegistrationTemplateMaster } from '@/app/actions/registerFileGroupAction';
|
import { fetchRegistrationSchoolFileMasters } from '@/app/actions/registerFileGroupAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
@ -75,8 +75,8 @@ export default function Page() {
|
|||||||
// Fetch data for tuition fees
|
// Fetch data for tuition fees
|
||||||
handleTuitionFees();
|
handleTuitionFees();
|
||||||
|
|
||||||
// Fetch data for registration file templates
|
// Fetch data for registration file schoolFileTemplates
|
||||||
fetchRegistrationTemplateMaster()
|
fetchRegistrationSchoolFileMasters()
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setFichiers(data);
|
setFichiers(data);
|
||||||
})
|
})
|
||||||
@ -275,7 +275,7 @@ export default function Page() {
|
|||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
id: 'Configuration',
|
id: 'Configuration',
|
||||||
label: "Configuration de l'école",
|
label: 'Classes',
|
||||||
content: (
|
content: (
|
||||||
<StructureManagement
|
<StructureManagement
|
||||||
specialities={specialities}
|
specialities={specialities}
|
||||||
@ -293,7 +293,7 @@ export default function Page() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Schedule',
|
id: 'Schedule',
|
||||||
label: "Gestion de l'emploi du temps",
|
label: 'Emploi du temps',
|
||||||
content: (
|
content: (
|
||||||
<ClassesProvider>
|
<ClassesProvider>
|
||||||
<ScheduleManagement
|
<ScheduleManagement
|
||||||
@ -305,7 +305,7 @@ export default function Page() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Fees',
|
id: 'Fees',
|
||||||
label: 'Tarifications',
|
label: 'Tarifs',
|
||||||
content: (
|
content: (
|
||||||
<FeesManagement
|
<FeesManagement
|
||||||
registrationDiscounts={registrationDiscounts}
|
registrationDiscounts={registrationDiscounts}
|
||||||
@ -332,7 +332,7 @@ export default function Page() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Files',
|
id: 'Files',
|
||||||
label: "Documents d'inscription",
|
label: 'Documents',
|
||||||
content: (
|
content: (
|
||||||
<FilesGroupsManagement
|
<FilesGroupsManagement
|
||||||
csrfToken={csrfToken}
|
csrfToken={csrfToken}
|
||||||
@ -343,7 +343,7 @@ export default function Page() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-8">
|
<div className="p-4">
|
||||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||||
|
|
||||||
<div className="w-full p-4">
|
<div className="w-full p-4">
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
Archive,
|
Archive,
|
||||||
FileText,
|
FileText,
|
||||||
CircleCheck,
|
CheckCircle,
|
||||||
Plus,
|
Plus,
|
||||||
XCircle,
|
XCircle,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@ -39,8 +39,10 @@ import {
|
|||||||
} from '@/app/actions/subscriptionAction';
|
} from '@/app/actions/subscriptionAction';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchRegistrationTemplateMaster,
|
fetchRegistrationSchoolFileMasters,
|
||||||
createRegistrationTemplates,
|
fetchRegistrationParentFileMasters,
|
||||||
|
createRegistrationSchoolFileTemplate,
|
||||||
|
createRegistrationParentFileTemplate,
|
||||||
fetchRegistrationFileGroups,
|
fetchRegistrationFileGroups,
|
||||||
cloneTemplate,
|
cloneTemplate,
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
@ -96,7 +98,8 @@ export default function Page({ params: { locale } }) {
|
|||||||
const [totalArchives, setTotalArchives] = useState(0);
|
const [totalArchives, setTotalArchives] = useState(0);
|
||||||
const [itemsPerPage, setItemsPerPage] = useState(10); // Définir le nombre d'éléments par page
|
const [itemsPerPage, setItemsPerPage] = useState(10); // Définir le nombre d'éléments par page
|
||||||
|
|
||||||
const [templateMasters, setTemplateMasters] = useState([]);
|
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
|
||||||
|
const [parentFileMasters, setParentFileMasters] = useState([]);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isOpenAffectationClasse, setIsOpenAffectationClasse] = useState(false);
|
const [isOpenAffectationClasse, setIsOpenAffectationClasse] = useState(false);
|
||||||
const [student, setStudent] = useState('');
|
const [student, setStudent] = useState('');
|
||||||
@ -239,9 +242,16 @@ export default function Page({ params: { locale } }) {
|
|||||||
fetchRegisterForms(selectedEstablishmentId, ARCHIVED)
|
fetchRegisterForms(selectedEstablishmentId, ARCHIVED)
|
||||||
.then(registerFormArchivedDataHandler)
|
.then(registerFormArchivedDataHandler)
|
||||||
.catch(requestErrorHandler),
|
.catch(requestErrorHandler),
|
||||||
fetchRegistrationTemplateMaster()
|
fetchRegistrationSchoolFileMasters()
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setTemplateMasters(data);
|
setSchoolFileMasters(data);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.debug(err.message);
|
||||||
|
}),
|
||||||
|
fetchRegistrationParentFileMasters()
|
||||||
|
.then((data) => {
|
||||||
|
setParentFileMasters(data);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.debug(err.message);
|
logger.debug(err.message);
|
||||||
@ -315,9 +325,9 @@ export default function Page({ params: { locale } }) {
|
|||||||
fetchRegisterForms(selectedEstablishmentId, ARCHIVED)
|
fetchRegisterForms(selectedEstablishmentId, ARCHIVED)
|
||||||
.then(registerFormArchivedDataHandler)
|
.then(registerFormArchivedDataHandler)
|
||||||
.catch(requestErrorHandler);
|
.catch(requestErrorHandler);
|
||||||
fetchRegistrationTemplateMaster()
|
fetchRegistrationSchoolFileMasters()
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setTemplateMasters(data);
|
setSchoolFileMasters(data);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
err = err.message;
|
err = err.message;
|
||||||
@ -521,41 +531,78 @@ export default function Page({ params: { locale } }) {
|
|||||||
|
|
||||||
createRegisterForm(data, csrfToken)
|
createRegisterForm(data, csrfToken)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
// Cloner les templates pour chaque templateMaster du fileGroup
|
// Cloner les schoolFileTemplates pour chaque templateMaster du fileGroup
|
||||||
const masters = templateMasters.filter((file) =>
|
const masters = schoolFileMasters.filter((file) =>
|
||||||
file.groups.includes(selectedFileGroup)
|
file.groups.includes(selectedFileGroup)
|
||||||
);
|
);
|
||||||
const clonePromises = masters.map((templateMaster, index) => {
|
const parent_masters = parentFileMasters.filter((file) =>
|
||||||
return cloneTemplate(
|
file.groups.includes(selectedFileGroup)
|
||||||
templateMaster.id,
|
);
|
||||||
updatedData.guardianEmail,
|
const clonePromises = masters
|
||||||
templateMaster.is_required
|
.map((templateMaster, index) => {
|
||||||
)
|
return cloneTemplate(
|
||||||
.then((clonedDocument) => {
|
templateMaster.id,
|
||||||
// Sauvegarde des templates clonés dans la base de données
|
updatedData.guardianEmail,
|
||||||
const cloneData = {
|
templateMaster.is_required
|
||||||
name: `${templateMaster.name}_${updatedData.guardianFirstName}_${updatedData.guardianLastName}`,
|
)
|
||||||
slug: clonedDocument.slug,
|
.then((clonedDocument) => {
|
||||||
id: clonedDocument.id,
|
// Sauvegarde des schoolFileTemplates clonés dans la base de données
|
||||||
master: templateMaster.id,
|
const cloneData = {
|
||||||
registration_form: data.student.id,
|
name: `${templateMaster.name}_${updatedData.guardianFirstName}_${updatedData.guardianLastName}`,
|
||||||
};
|
slug: clonedDocument.slug,
|
||||||
|
id: clonedDocument.id,
|
||||||
|
master: templateMaster.id,
|
||||||
|
registration_form: data.student.id,
|
||||||
|
};
|
||||||
|
|
||||||
return createRegistrationTemplates(cloneData, csrfToken)
|
return createRegistrationSchoolFileTemplate(
|
||||||
.then((response) => {
|
cloneData,
|
||||||
logger.debug('Template enregistré avec succès:', response);
|
csrfToken
|
||||||
})
|
)
|
||||||
.catch((error) => {
|
.then((response) => {
|
||||||
logger.error(
|
logger.debug('Template enregistré avec succès:', response);
|
||||||
"Erreur lors de l'enregistrement du template:",
|
})
|
||||||
error
|
.catch((error) => {
|
||||||
);
|
logger.error(
|
||||||
});
|
"Erreur lors de l'enregistrement du template:",
|
||||||
})
|
error
|
||||||
.catch((error) => {
|
);
|
||||||
logger.error('Error during cloning or sending:', error);
|
});
|
||||||
});
|
})
|
||||||
});
|
.catch((error) => {
|
||||||
|
logger.error('Error during cloning or sending:', error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Créer les parentFileTemplates pour chaque parentMaster
|
||||||
|
const parentClonePromises = parent_masters.map(
|
||||||
|
(parentMaster, index) => {
|
||||||
|
const parentTemplateData = {
|
||||||
|
master: parentMaster.id,
|
||||||
|
registration_form: data.student.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
return createRegistrationParentFileTemplate(
|
||||||
|
parentTemplateData,
|
||||||
|
csrfToken
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
logger.debug(
|
||||||
|
'Parent template enregistré avec succès:',
|
||||||
|
response
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error(
|
||||||
|
"Erreur lors de l'enregistrement du parent template:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Attendre que tous les clones soient créés
|
// Attendre que tous les clones soient créés
|
||||||
Promise.all(clonePromises)
|
Promise.all(clonePromises)
|
||||||
@ -670,7 +717,11 @@ export default function Page({ params: { locale } }) {
|
|||||||
const actions = {
|
const actions = {
|
||||||
1: [
|
1: [
|
||||||
{
|
{
|
||||||
icon: <Edit className="w-5 h-5 text-blue-500 hover:text-blue-700" />,
|
icon: (
|
||||||
|
<span title="Editer le dossier">
|
||||||
|
<Edit className="w-5 h-5 text-blue-500 hover:text-blue-700" />
|
||||||
|
</span>
|
||||||
|
),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
router.push(
|
router.push(
|
||||||
`${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}`
|
`${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}`
|
||||||
@ -678,7 +729,9 @@ export default function Page({ params: { locale } }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: (
|
icon: (
|
||||||
<Send className="w-5 h-5 text-green-500 hover:text-green-700" />
|
<span title="Envoyer le dossier">
|
||||||
|
<Send className="w-5 h-5 text-green-500 hover:text-green-700" />
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
sendConfirmRegisterForm(
|
sendConfirmRegisterForm(
|
||||||
@ -690,7 +743,11 @@ export default function Page({ params: { locale } }) {
|
|||||||
],
|
],
|
||||||
2: [
|
2: [
|
||||||
{
|
{
|
||||||
icon: <Edit className="w-5 h-5 text-blue-500 hover:text-blue-700" />,
|
icon: (
|
||||||
|
<span title="Editer le dossier">
|
||||||
|
<Edit className="w-5 h-5 text-blue-500 hover:text-blue-700" />
|
||||||
|
</span>
|
||||||
|
),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
router.push(
|
router.push(
|
||||||
`${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}`
|
`${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}`
|
||||||
@ -700,18 +757,26 @@ export default function Page({ params: { locale } }) {
|
|||||||
3: [
|
3: [
|
||||||
{
|
{
|
||||||
icon: (
|
icon: (
|
||||||
<CircleCheck className="w-5 h-5 text-green-500 hover:text-green-700" />
|
<span title="Valider le dossier">
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-500 hover:text-green-700" />
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
onClick: () =>
|
onClick: () => {
|
||||||
router.push(
|
const paymentSepa =
|
||||||
`${FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL}?studentId=${row.student.id}&firstName=${row.student.first_name}&lastName=${row.student.last_name}&paymentMode=${row.registration_payment}&file=${row.registration_file}`
|
row.registration_payment === 1 || row.tuition_payment === 1
|
||||||
),
|
? 1
|
||||||
|
: 0;
|
||||||
|
const url = `${FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL}?studentId=${row.student.id}&firstName=${row.student.first_name}&lastName=${row.student.last_name}&paymentSepa=${paymentSepa}&file=${row.registration_file}`;
|
||||||
|
router.push(`${url}`);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
5: [
|
5: [
|
||||||
{
|
{
|
||||||
icon: (
|
icon: (
|
||||||
<CircleCheck className="w-5 h-5 text-green-500 hover:text-green-700" />
|
<span title="Valider le dossier">
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-500 hover:text-green-700" />
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
onClick: () => openModalAssociationEleve(row.student),
|
onClick: () => openModalAssociationEleve(row.student),
|
||||||
},
|
},
|
||||||
@ -719,7 +784,9 @@ export default function Page({ params: { locale } }) {
|
|||||||
default: [
|
default: [
|
||||||
{
|
{
|
||||||
icon: (
|
icon: (
|
||||||
<Archive className="w-5 h-5 text-gray-500 hover:text-gray-700" />
|
<span title="Archiver le dossier">
|
||||||
|
<Archive className="w-5 h-5 text-gray-500 hover:text-gray-700" />
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
archiveFicheInscription(
|
archiveFicheInscription(
|
||||||
@ -785,17 +852,34 @@ export default function Page({ params: { locale } }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t('files'),
|
name: t('files'),
|
||||||
transform: (row) =>
|
transform: (row) => (
|
||||||
row.registration_file != null && (
|
<ul>
|
||||||
<ul>
|
{row.registration_file && (
|
||||||
<li className="flex justify-center items-center gap-2">
|
<li className="flex justify-center items-center gap-2">
|
||||||
<FileText size={16} />
|
<FileText size={16} />
|
||||||
<a href={`${BASE_URL}${row.registration_file}`} target="_blank">
|
<a
|
||||||
|
href={`${BASE_URL}${row.registration_file}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{row.registration_file?.split('/').pop()}
|
{row.registration_file?.split('/').pop()}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
)}
|
||||||
),
|
{row.sepa_file && (
|
||||||
|
<li className="flex justify-center items-center gap-2">
|
||||||
|
<FileText size={16} />
|
||||||
|
<a
|
||||||
|
href={`${BASE_URL}${row.sepa_file}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{row.sepa_file?.split('/').pop()}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Actions',
|
name: 'Actions',
|
||||||
@ -865,7 +949,7 @@ export default function Page({ params: { locale } }) {
|
|||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<>
|
<>
|
||||||
<CircleCheck size={16} className="mr-2" /> Rattacher
|
<CheckCircle size={16} className="mr-2" /> Rattacher
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
onClick: () => openModalAssociationEleve(row.student),
|
onClick: () => openModalAssociationEleve(row.student),
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export default function Page() {
|
|||||||
const studentId = searchParams.get('studentId');
|
const studentId = searchParams.get('studentId');
|
||||||
const firstName = searchParams.get('firstName');
|
const firstName = searchParams.get('firstName');
|
||||||
const lastName = searchParams.get('lastName');
|
const lastName = searchParams.get('lastName');
|
||||||
const paymentMode = searchParams.get('paymentMode');
|
const paymentSepa = searchParams.get('paymentSepa') === '1';
|
||||||
const file = searchParams.get('file');
|
const file = searchParams.get('file');
|
||||||
|
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
@ -45,7 +45,7 @@ export default function Page() {
|
|||||||
studentId={studentId}
|
studentId={studentId}
|
||||||
firstName={firstName}
|
firstName={firstName}
|
||||||
lastName={lastName}
|
lastName={lastName}
|
||||||
paymentMode={paymentMode}
|
paymentSepa={paymentSepa}
|
||||||
file={file}
|
file={file}
|
||||||
onAccept={handleAcceptRF}
|
onAccept={handleAcceptRF}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,21 +2,28 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { Edit, Users } from 'lucide-react';
|
import { Edit3, Users, Download, Eye, Upload } from 'lucide-react';
|
||||||
import StatusLabel from '@/components/StatusLabel';
|
import StatusLabel from '@/components/StatusLabel';
|
||||||
|
import FileUpload from '@/components/FileUpload';
|
||||||
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,
|
||||||
|
sendSEPARegisterForm,
|
||||||
|
} from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import ProfileSelector from '@/components/ProfileSelector';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
|
|
||||||
export default function ParentHomePage() {
|
export default function ParentHomePage() {
|
||||||
const [children, setChildren] = useState([]);
|
const [children, setChildren] = useState([]);
|
||||||
const [userId, setUserId] = useState(null);
|
const [userId, setUserId] = useState(null);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const { user, selectedEstablishmentId } = useEstablishment();
|
const { user, selectedEstablishmentId } = useEstablishment();
|
||||||
|
const [uploadingStudentId, setUploadingStudentId] = useState(null); // ID de l'étudiant pour l'upload
|
||||||
|
const [uploadedFile, setUploadedFile] = useState(null); // Fichier uploadé
|
||||||
|
const [uploadState, setUploadState] = useState('off'); // État "on" ou "off" pour l'affichage du composant
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const csrfToken = useCsrfToken();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const userIdFromSession = user.user_id;
|
const userIdFromSession = user.user_id;
|
||||||
@ -27,17 +34,62 @@ export default function ParentHomePage() {
|
|||||||
});
|
});
|
||||||
}, [selectedEstablishmentId]);
|
}, [selectedEstablishmentId]);
|
||||||
|
|
||||||
|
function handleView(eleveId) {
|
||||||
|
logger.debug(`View dossier for student id: ${eleveId}`);
|
||||||
|
router.push(
|
||||||
|
`${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}&view=true`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function handleEdit(eleveId) {
|
function handleEdit(eleveId) {
|
||||||
// Logique pour éditer le dossier de l'élève
|
|
||||||
logger.debug(`Edit dossier for student id: ${eleveId}`);
|
logger.debug(`Edit dossier for student id: ${eleveId}`);
|
||||||
router.push(
|
router.push(
|
||||||
`${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}`
|
`${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionColumns = [{ name: 'Action', transform: (row) => row.action }];
|
const handleFileUpload = (file) => {
|
||||||
|
if (!file) {
|
||||||
|
logger.error("Aucun fichier sélectionné pour l'upload.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUploadedFile(file); // Conserve le fichier en mémoire
|
||||||
|
logger.debug('Fichier sélectionné :', file.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (!uploadedFile || !uploadingStudentId) {
|
||||||
|
logger.error('Aucun fichier ou étudiant sélectionné.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('sepa_file', uploadedFile); // Ajoute le fichier SEPA
|
||||||
|
formData.append('status', 3); // Statut à envoyer
|
||||||
|
|
||||||
|
sendSEPARegisterForm(uploadingStudentId, formData, csrfToken)
|
||||||
|
.then((response) => {
|
||||||
|
logger.debug('RF mis à jour avec succès:', response);
|
||||||
|
// Logique supplémentaire après la mise à jour (par exemple, redirection ou notification)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors de la mise à jour du RF:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleUpload = (studentId) => {
|
||||||
|
if (uploadingStudentId === studentId && uploadState === 'on') {
|
||||||
|
// Si le composant est déjà affiché pour cet étudiant, on le masque
|
||||||
|
setUploadState('off');
|
||||||
|
setUploadingStudentId(null);
|
||||||
|
setUploadedFile(null); // Réinitialise le fichier
|
||||||
|
} else {
|
||||||
|
// Sinon, on l'affiche pour cet étudiant
|
||||||
|
setUploadState('on');
|
||||||
|
setUploadingStudentId(studentId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Définir les colonnes du tableau
|
|
||||||
const childrenColumns = [
|
const childrenColumns = [
|
||||||
{ name: 'Nom', transform: (row) => `${row.student.last_name}` },
|
{ name: 'Nom', transform: (row) => `${row.student.last_name}` },
|
||||||
{ name: 'Prénom', transform: (row) => `${row.student.first_name}` },
|
{ name: 'Prénom', transform: (row) => `${row.student.first_name}` },
|
||||||
@ -52,29 +104,76 @@ export default function ParentHomePage() {
|
|||||||
{
|
{
|
||||||
name: 'Actions',
|
name: 'Actions',
|
||||||
transform: (row) => (
|
transform: (row) => (
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center items-center gap-2">
|
||||||
<button
|
{row.status === 2 && (
|
||||||
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
|
<button
|
||||||
onClick={(e) => {
|
className="text-blue-500 hover:text-blue-700"
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
handleEdit(row.student.id);
|
e.stopPropagation();
|
||||||
}}
|
handleEdit(row.student.id);
|
||||||
aria-label="Modifier"
|
}}
|
||||||
>
|
aria-label="Remplir le dossier"
|
||||||
<Edit className="h-5 w-5" />
|
>
|
||||||
</button>
|
<Edit3 className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{row.status === 3 && (
|
||||||
|
<button
|
||||||
|
className="text-purple-500 hover:text-purple-700"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleView(row.student.id);
|
||||||
|
}}
|
||||||
|
aria-label="Visualiser le dossier"
|
||||||
|
>
|
||||||
|
<Eye className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{row.status === 7 && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="flex items-center justify-center w-8 h-8 rounded-full text-purple-500 hover:text-purple-700"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleView(row.student.id);
|
||||||
|
}}
|
||||||
|
aria-label="Visualiser le dossier"
|
||||||
|
>
|
||||||
|
<Eye className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
href={`${BASE_URL}${row.sepa_file}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center justify-center w-8 h-8 rounded-full text-green-500 hover:text-green-700"
|
||||||
|
aria-label="Télécharger le mandat SEPA"
|
||||||
|
>
|
||||||
|
<Download className="h-5 w-5" />
|
||||||
|
</a>
|
||||||
|
{/* Nouvelle action Upload */}
|
||||||
|
<button
|
||||||
|
className={`flex items-center justify-center w-8 h-8 rounded-full ${
|
||||||
|
uploadingStudentId === row.student.id && uploadState === 'on'
|
||||||
|
? 'bg-blue-100 text-blue-600 ring-3 ring-blue-500'
|
||||||
|
: 'text-blue-500 hover:text-blue-700'
|
||||||
|
}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleUpload(row.student.id); // Activer ou désactiver l'upload pour cet étudiant
|
||||||
|
}}
|
||||||
|
aria-label="Uploader un fichier"
|
||||||
|
>
|
||||||
|
<Upload className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const itemsPerPage = 5;
|
|
||||||
const totalPages = Math.ceil(children.length / itemsPerPage) || 1;
|
|
||||||
|
|
||||||
const handlePageChange = (newPage) => {
|
|
||||||
setCurrentPage(newPage);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-2 py-4 md:px-4 max-w-full">
|
<div className="px-2 py-4 md:px-4 max-w-full">
|
||||||
<div>
|
<div>
|
||||||
@ -86,13 +185,29 @@ export default function ParentHomePage() {
|
|||||||
<Table
|
<Table
|
||||||
data={children}
|
data={children}
|
||||||
columns={childrenColumns}
|
columns={childrenColumns}
|
||||||
itemsPerPage={itemsPerPage}
|
|
||||||
currentPage={currentPage}
|
|
||||||
totalPages={totalPages}
|
|
||||||
onPageChange={handlePageChange}
|
|
||||||
defaultTheme="bg-gray-50"
|
defaultTheme="bg-gray-50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Composant FileUpload et bouton Valider en dessous du tableau */}
|
||||||
|
{uploadState === 'on' && uploadingStudentId && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<FileUpload
|
||||||
|
selectionMessage="Sélectionnez un fichier à uploader"
|
||||||
|
onFileSelect={handleFileUpload}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className={`mt-4 px-6 py-2 rounded-md ${
|
||||||
|
uploadedFile
|
||||||
|
? 'bg-emerald-500 text-white hover:bg-emerald-600'
|
||||||
|
: 'bg-gray-300 text-gray-700 cursor-not-allowed'
|
||||||
|
}`}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={!uploadedFile}
|
||||||
|
>
|
||||||
|
Valider
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL,
|
BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL,
|
||||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL,
|
BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL,
|
||||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL,
|
BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL,
|
||||||
|
BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL,
|
||||||
|
BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL,
|
||||||
FE_API_DOCUSEAL_CLONE_URL,
|
FE_API_DOCUSEAL_CLONE_URL,
|
||||||
FE_API_DOCUSEAL_DOWNLOAD_URL,
|
FE_API_DOCUSEAL_DOWNLOAD_URL,
|
||||||
FE_API_DOCUSEAL_GENERATE_TOKEN,
|
FE_API_DOCUSEAL_GENERATE_TOKEN,
|
||||||
@ -18,6 +20,8 @@ const requestResponseHandler = async (response) => {
|
|||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FETCH requests
|
||||||
|
|
||||||
export async function fetchRegistrationFileGroups(establishment) {
|
export async function fetchRegistrationFileGroups(establishment) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}?establishment_id=${establishment}`,
|
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}?establishment_id=${establishment}`,
|
||||||
@ -34,6 +38,68 @@ export async function fetchRegistrationFileGroups(establishment) {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const fetchRegistrationFileFromGroup = async (groupId) => {
|
||||||
|
const response = await fetch(
|
||||||
|
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/school_file_templates`,
|
||||||
|
{
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
'Erreur lors de la récupération des fichiers associés au groupe'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchRegistrationSchoolFileMasters = (id = null) => {
|
||||||
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}`;
|
||||||
|
if (id) {
|
||||||
|
url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}/${id}`;
|
||||||
|
}
|
||||||
|
const request = new Request(`${url}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return fetch(request).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchRegistrationParentFileMasters = (id = null) => {
|
||||||
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}`;
|
||||||
|
if (id) {
|
||||||
|
url = `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`;
|
||||||
|
}
|
||||||
|
const request = new Request(`${url}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return fetch(request).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchRegistrationSchoolFileTemplates = (id = null) => {
|
||||||
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}`;
|
||||||
|
if (id) {
|
||||||
|
url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${id}`;
|
||||||
|
}
|
||||||
|
const request = new Request(`${url}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return fetch(request).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
// CREATE requests
|
||||||
|
|
||||||
export async function createRegistrationFileGroup(groupData, csrfToken) {
|
export async function createRegistrationFileGroup(groupData, csrfToken) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`,
|
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`,
|
||||||
@ -55,20 +121,55 @@ export async function createRegistrationFileGroup(groupData, csrfToken) {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRegistrationFileGroup(groupId, csrfToken) {
|
export const createRegistrationSchoolFileMaster = (data, csrfToken) => {
|
||||||
const response = await fetch(
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}`, {
|
||||||
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`,
|
method: 'POST',
|
||||||
{
|
body: JSON.stringify(data),
|
||||||
method: 'DELETE',
|
headers: {
|
||||||
headers: {
|
'X-CSRFToken': csrfToken,
|
||||||
'X-CSRFToken': csrfToken,
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
}
|
}).then(requestResponseHandler);
|
||||||
);
|
};
|
||||||
|
|
||||||
return response;
|
export const createRegistrationParentFileMaster = (data, csrfToken) => {
|
||||||
}
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRegistrationSchoolFileTemplate = (data, csrfToken) => {
|
||||||
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRegistrationParentFileTemplate = (data, csrfToken) => {
|
||||||
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
// EDIT requests
|
||||||
|
|
||||||
export const editRegistrationFileGroup = async (
|
export const editRegistrationFileGroup = async (
|
||||||
groupId,
|
groupId,
|
||||||
@ -94,113 +195,9 @@ export const editRegistrationFileGroup = async (
|
|||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchRegistrationFileFromGroup = async (groupId) => {
|
export const editRegistrationSchoolFileMaster = (fileId, data, csrfToken) => {
|
||||||
const response = await fetch(
|
|
||||||
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/templates`,
|
|
||||||
{
|
|
||||||
credentials: 'include',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(
|
|
||||||
'Erreur lors de la récupération des fichiers associés au groupe'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchRegistrationTemplates = (id = null) => {
|
|
||||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`;
|
|
||||||
if (id) {
|
|
||||||
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${id}`;
|
|
||||||
}
|
|
||||||
const request = new Request(`${url}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return fetch(request).then(requestResponseHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const editRegistrationTemplates = (fileId, data, csrfToken) => {
|
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
body: data,
|
|
||||||
headers: {
|
|
||||||
'X-CSRFToken': csrfToken,
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
}).then(requestResponseHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createRegistrationTemplates = (data, csrfToken) => {
|
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
headers: {
|
|
||||||
'X-CSRFToken': csrfToken,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
}).then(requestResponseHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteRegistrationTemplates = (fileId, csrfToken) => {
|
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'X-CSRFToken': csrfToken,
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchRegistrationTemplateMaster = (id = null) => {
|
|
||||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`;
|
|
||||||
if (id) {
|
|
||||||
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${id}`;
|
|
||||||
}
|
|
||||||
const request = new Request(`${url}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return fetch(request).then(requestResponseHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createRegistrationTemplateMaster = (data, csrfToken) => {
|
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
headers: {
|
|
||||||
'X-CSRFToken': csrfToken,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
}).then(requestResponseHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteRegistrationTemplateMaster = (fileId, csrfToken) => {
|
|
||||||
return fetch(
|
return fetch(
|
||||||
`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`,
|
`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}/${fileId}`,
|
||||||
{
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'X-CSRFToken': csrfToken,
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => {
|
|
||||||
return fetch(
|
|
||||||
`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`,
|
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@ -213,6 +210,128 @@ export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => {
|
|||||||
).then(requestResponseHandler);
|
).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const editRegistrationParentFileMaster = (id, data, csrfToken) => {
|
||||||
|
return fetch(
|
||||||
|
`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const editRegistrationSchoolFileTemplates = (
|
||||||
|
fileId,
|
||||||
|
data,
|
||||||
|
csrfToken
|
||||||
|
) => {
|
||||||
|
return fetch(
|
||||||
|
`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${fileId}`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
body: data,
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const editRegistrationParentFileTemplates = (
|
||||||
|
fileId,
|
||||||
|
data,
|
||||||
|
csrfToken
|
||||||
|
) => {
|
||||||
|
return fetch(
|
||||||
|
`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL}/${fileId}`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
body: data,
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
// DELETE requests
|
||||||
|
|
||||||
|
export async function deleteRegistrationFileGroup(groupId, csrfToken) {
|
||||||
|
const response = await fetch(
|
||||||
|
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteRegistrationSchoolFileMaster = (fileId, csrfToken) => {
|
||||||
|
return fetch(
|
||||||
|
`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}/${fileId}`,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteRegistrationParentFileMaster = (id, csrfToken) => {
|
||||||
|
return fetch(
|
||||||
|
`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteRegistrationSchoolFileTemplates = (fileId, csrfToken) => {
|
||||||
|
return fetch(
|
||||||
|
`${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${fileId}`,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteRegistrationParentFileTemplate = (id, csrfToken) => {
|
||||||
|
return fetch(
|
||||||
|
`${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL}/${id}`,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// API requests
|
||||||
|
|
||||||
export const cloneTemplate = (templateId, email, is_required) => {
|
export const cloneTemplate = (templateId, email, is_required) => {
|
||||||
return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, {
|
return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@ -153,9 +153,27 @@ export async function getRegisterFormFileTemplate(fileId) {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchTemplatesFromRegistrationFiles = async (id) => {
|
export const fetchSchoolFileTemplatesFromRegistrationFiles = async (id) => {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/templates`,
|
`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/school_file_templates`,
|
||||||
|
{
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
'Erreur lors de la récupération des fichiers associés au groupe'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchParentFileTemplatesFromRegistrationFiles = async (id) => {
|
||||||
|
const response = await fetch(
|
||||||
|
`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/parent_file_templates`,
|
||||||
{
|
{
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@ -1,105 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
|
|
||||||
import DraggableFileUpload from './DraggableFileUpload';
|
|
||||||
import { fetchRegistrationFileGroups } from '@/app/actions/registerFileGroupAction';
|
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
|
||||||
|
|
||||||
export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
|
||||||
const [fileName, setFileName] = useState('');
|
|
||||||
const [file, setFile] = useState(null);
|
|
||||||
const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired
|
|
||||||
const [order, setOrder] = useState(0);
|
|
||||||
const [groups, setGroups] = useState([]);
|
|
||||||
const [selectedGroup, setSelectedGroup] = useState('');
|
|
||||||
|
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchRegistrationFileGroups(selectedEstablishmentId).then((data) =>
|
|
||||||
setGroups(data)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (fileToEdit) {
|
|
||||||
setFileName(fileToEdit.name || '');
|
|
||||||
setIsRequired(fileToEdit.is_required || false);
|
|
||||||
setOrder(fileToEdit.fusion_order || 0);
|
|
||||||
setSelectedGroup(fileToEdit.group_id || '');
|
|
||||||
}
|
|
||||||
}, [fileToEdit]);
|
|
||||||
|
|
||||||
const handleFileNameChange = (event) => {
|
|
||||||
setFileName(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUpload = () => {
|
|
||||||
onFileUpload({
|
|
||||||
file,
|
|
||||||
name: fileName,
|
|
||||||
is_required: isRequired,
|
|
||||||
order: parseInt(order, 10),
|
|
||||||
groupId: selectedGroup || null,
|
|
||||||
});
|
|
||||||
setFile(null);
|
|
||||||
setFileName('');
|
|
||||||
setIsRequired(false);
|
|
||||||
setOrder(0);
|
|
||||||
setSelectedGroup('');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<DraggableFileUpload
|
|
||||||
fileName={fileName}
|
|
||||||
onFileSelect={(selectedFile) => {
|
|
||||||
setFile(selectedFile);
|
|
||||||
setFileName(selectedFile.name.replace(/\.[^/.]+$/, ''));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="flex mt-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Nom du fichier"
|
|
||||||
value={fileName}
|
|
||||||
onChange={handleFileNameChange}
|
|
||||||
className="flex-grow p-2 border border-gray-200 rounded-md"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={order}
|
|
||||||
onChange={(e) => setOrder(e.target.value)}
|
|
||||||
placeholder="Ordre de fusion"
|
|
||||||
className="p-2 border border-gray-200 rounded-md ml-2 w-20"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={handleUpload}
|
|
||||||
className={`p-2 rounded-md shadow transition duration-200 ml-2 ${fileName !== '' ? 'bg-emerald-600 text-white hover:bg-emerald-900' : 'bg-gray-300 text-gray-500 cursor-not-allowed'}`}
|
|
||||||
disabled={fileName === ''}
|
|
||||||
>
|
|
||||||
Ajouter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center mt-4">
|
|
||||||
<ToggleSwitch
|
|
||||||
label="Fichier à remplir obligatoirement"
|
|
||||||
checked={isRequired}
|
|
||||||
onChange={() => setIsRequired(!isRequired)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4">
|
|
||||||
<label className="block mb-2">Groupe</label>
|
|
||||||
<select
|
|
||||||
value={selectedGroup}
|
|
||||||
onChange={(e) => setSelectedGroup(e.target.value)}
|
|
||||||
className="w-full border rounded p-2"
|
|
||||||
>
|
|
||||||
<option value="">Aucun groupe</option>
|
|
||||||
{groups.map((group) => (
|
|
||||||
<option key={group.id} value={group.id}>
|
|
||||||
{group.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
60
Front-End/src/components/FileUpload.js
Normal file
60
Front-End/src/components/FileUpload.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React, { useState, useRef } from 'react';
|
||||||
|
import { CloudUpload } from 'lucide-react';
|
||||||
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
|
export default function FileUpload({ selectionMessage, onFileSelect, uploadedFileName }) {
|
||||||
|
const [localFileName, setLocalFileName] = useState(uploadedFileName || '');
|
||||||
|
const fileInputRef = useRef(null); // Utilisation de useRef pour cibler l'input
|
||||||
|
|
||||||
|
const handleFileChange = (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
setLocalFileName(file.name);
|
||||||
|
logger.debug('Fichier sélectionné:', file.name);
|
||||||
|
onFileSelect(file); // Appelle la fonction passée en prop
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileDrop = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const file = e.dataTransfer.files[0];
|
||||||
|
if (file) {
|
||||||
|
setLocalFileName(file.name);
|
||||||
|
logger.debug('Fichier déposé:', file.name);
|
||||||
|
onFileSelect(file); // Appelle la fonction passée en prop
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border p-4 rounded-md shadow-md">
|
||||||
|
<h3 className="text-lg font-semibold mb-4">{`${selectionMessage}`}</h3>
|
||||||
|
<div
|
||||||
|
className="border-2 border-dashed border-gray-500 p-6 rounded-lg flex flex-col items-center justify-center cursor-pointer hover:border-emerald-500"
|
||||||
|
onClick={() => fileInputRef.current.click()} // Utilisation de la référence pour ouvrir l'explorateur
|
||||||
|
onDragOver={(e) => e.preventDefault()}
|
||||||
|
onDrop={handleFileDrop}
|
||||||
|
>
|
||||||
|
<CloudUpload className="w-12 h-12 text-emerald-500 mb-4" /> {/* Icône de cloud */}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".pdf"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
className="hidden"
|
||||||
|
ref={fileInputRef} // Attachement de la référence
|
||||||
|
/>
|
||||||
|
<label htmlFor="fileInput" className="text-center text-gray-500">
|
||||||
|
<p className="text-lg font-semibold text-gray-800">Déposez votre fichier ici</p>
|
||||||
|
<p className="text-sm text-gray-500 mt-2">ou cliquez pour sélectionner un fichier PDF</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{localFileName && (
|
||||||
|
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
|
||||||
|
<CloudUpload className="w-6 h-6 text-emerald-500" />
|
||||||
|
<p className="text-sm font-medium text-gray-800">
|
||||||
|
<span className="font-semibold">{localFileName}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -11,6 +11,11 @@ export default function InputPhone({
|
|||||||
className,
|
className,
|
||||||
required,
|
required,
|
||||||
}) {
|
}) {
|
||||||
|
const handlePhoneChange = (phone) => {
|
||||||
|
// Appeler onChange avec un objet personnalisé
|
||||||
|
onChange({ target: { name, value: phone } });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${className}`}>
|
<div className={`${className}`}>
|
||||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
||||||
@ -21,7 +26,7 @@ export default function InputPhone({
|
|||||||
<PhoneInput
|
<PhoneInput
|
||||||
defaultCountry="fr"
|
defaultCountry="fr"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(phone) => onChange(phone)}
|
onChange={handlePhoneChange}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
name: name,
|
name: name,
|
||||||
required: required,
|
required: required,
|
||||||
|
|||||||
@ -1,19 +1,244 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
|
import FileUpload from '@/components/FileUpload';
|
||||||
|
import { Upload, Eye, Trash2, FileText } from 'lucide-react';
|
||||||
|
import { BASE_URL } from '@/utils/Url';
|
||||||
|
import Popup from '@/components/Popup';
|
||||||
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
|
export default function FilesToUpload({
|
||||||
|
parentFileTemplates,
|
||||||
|
uploadedFiles,
|
||||||
|
onFileUpload,
|
||||||
|
onFileDelete,
|
||||||
|
}) {
|
||||||
|
const [selectedFile, setSelectedFile] = useState(null); // État pour le fichier sélectionné
|
||||||
|
const [actionType, setActionType] = useState(null);
|
||||||
|
|
||||||
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
|
const [popupMessage, setPopupMessage] = useState('');
|
||||||
|
const [removePopupVisible, setRemovePopupVisible] = useState(false);
|
||||||
|
const [removePopupMessage, setRemovePopupMessage] = useState('');
|
||||||
|
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
||||||
|
|
||||||
|
// Vérification si un fichier est déjà uploadé
|
||||||
|
const isFileUploaded = (file) => {
|
||||||
|
return file && file.fileName; // Si `fileName` est défini, le fichier est considéré comme téléversé
|
||||||
|
};
|
||||||
|
|
||||||
|
// Récupération d'un fichier uploadé
|
||||||
|
const getUploadedFile = (templateId) => {
|
||||||
|
return uploadedFiles.find(
|
||||||
|
(file) => parseInt(file.id) === templateId && file.fileName
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpload = (file, selectedFile) => {
|
||||||
|
if (!file || !selectedFile) {
|
||||||
|
logger.error('Données manquantes pour le téléversement.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appeler la fonction de téléversement passée en prop
|
||||||
|
onFileUpload(file, selectedFile)
|
||||||
|
.then((response) => {
|
||||||
|
// Mettre à jour uploadedFiles avec les nouvelles données
|
||||||
|
const updatedFiles = uploadedFiles.map((f) =>
|
||||||
|
f.id === selectedFile.id
|
||||||
|
? {
|
||||||
|
...f,
|
||||||
|
fileName: response.data.fileName,
|
||||||
|
file: response.data.file_url,
|
||||||
|
}
|
||||||
|
: f
|
||||||
|
);
|
||||||
|
|
||||||
|
// Si le fichier n'existe pas encore, l'ajouter
|
||||||
|
if (!updatedFiles.find((f) => f.id === selectedFile.id)) {
|
||||||
|
updatedFiles.push({
|
||||||
|
id: selectedFile.id,
|
||||||
|
fileName: response.data.fileName,
|
||||||
|
file: response.data.file_url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors du téléversement du fichier :', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mettre à jour l'état local
|
||||||
|
setSelectedFile(null);
|
||||||
|
setActionType(null); // Réinitialiser l'action après l'upload
|
||||||
|
};
|
||||||
|
|
||||||
|
// Définition des colonnes
|
||||||
|
const columns = [
|
||||||
|
{ name: 'Nom du fichier', transform: (row) => row.master_name },
|
||||||
|
{
|
||||||
|
name: 'Description du fichier',
|
||||||
|
transform: (row) => row.master_description,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Statut',
|
||||||
|
transform: (row) => {
|
||||||
|
const uploadedFile = getUploadedFile(row.id);
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`px-2 py-1 rounded-md text-sm font-medium ${
|
||||||
|
isFileUploaded(uploadedFile)
|
||||||
|
? 'bg-green-50 text-green-600'
|
||||||
|
: 'bg-orange-50 text-orange-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isFileUploaded(uploadedFile) ? 'Chargé' : 'A ajouter'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Actions',
|
||||||
|
transform: (row) => {
|
||||||
|
const uploadedFile = getUploadedFile(row.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center gap-4">
|
||||||
|
{uploadedFile && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className={`flex items-center justify-center w-8 h-8 rounded-full ${
|
||||||
|
actionType === 'view' && selectedFile?.id === row.id
|
||||||
|
? 'bg-blue-100 text-blue-600 ring-3 ring-blue-500'
|
||||||
|
: 'text-blue-500 hover:text-blue-700'
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (actionType === 'view' && selectedFile?.id === row.id) {
|
||||||
|
setSelectedFile(null);
|
||||||
|
setActionType(null);
|
||||||
|
} else {
|
||||||
|
const uploadedFile = getUploadedFile(row.id);
|
||||||
|
setSelectedFile(uploadedFile || row); // Utiliser les données mises à jour
|
||||||
|
setActionType('view');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Eye className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="flex items-center justify-center w-8 h-8 rounded-full text-red-500 hover:text-red-700"
|
||||||
|
onClick={() => {
|
||||||
|
setRemovePopupVisible(true);
|
||||||
|
setRemovePopupMessage(
|
||||||
|
`Êtes-vous sûr(e) de vouloir supprimer le fichier "${row.master_name}" ?`
|
||||||
|
);
|
||||||
|
setRemovePopupOnConfirm(() => () => {
|
||||||
|
onFileDelete(row.id)
|
||||||
|
.then(() => {
|
||||||
|
setPopupMessage(
|
||||||
|
`Le fichier "${row.master_name}" a été supprimé avec succès.`
|
||||||
|
);
|
||||||
|
setPopupVisible(true);
|
||||||
|
setRemovePopupVisible(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error(
|
||||||
|
'Erreur lors de la suppression du fichier :',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
setPopupMessage(
|
||||||
|
`Erreur lors de la suppression du fichier "${row.master_name}".`
|
||||||
|
);
|
||||||
|
setPopupVisible(true);
|
||||||
|
setRemovePopupVisible(false);
|
||||||
|
});
|
||||||
|
setActionType(null);
|
||||||
|
setSelectedFile(null);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!uploadedFile && (
|
||||||
|
<button
|
||||||
|
className={`flex items-center justify-center w-8 h-8 rounded-full ${
|
||||||
|
actionType === 'upload' && selectedFile?.id === row.id
|
||||||
|
? 'bg-emerald-100 text-emerald-600 ring-3 ring-emerald-500'
|
||||||
|
: 'text-emerald-500 hover:text-emerald-700'
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (actionType === 'upload' && selectedFile?.id === row.id) {
|
||||||
|
setSelectedFile(null);
|
||||||
|
setActionType(null);
|
||||||
|
} else {
|
||||||
|
setSelectedFile(row);
|
||||||
|
setActionType('upload');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Upload className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function FilesToUpload({ fileTemplates, columns }) {
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
<div className="mt-8 mb-4 w-3/5">
|
||||||
<h2 className="text-xl font-bold mb-4 text-gray-800">
|
<div className="flex items-center justify-between mb-6">
|
||||||
Fichiers à uploader
|
<div className="flex items-center space-x-4">
|
||||||
</h2>
|
<div className="bg-emerald-100 p-3 rounded-full shadow-md">
|
||||||
<Table
|
<FileText className="w-8 h-8 text-emerald-600" />
|
||||||
data={fileTemplates}
|
</div>
|
||||||
columns={columns}
|
<div>
|
||||||
itemsPerPage={5}
|
<h2 className="text-2xl font-bold text-gray-800">
|
||||||
currentPage={1}
|
Pièces à fournir
|
||||||
totalPages={1}
|
</h2>
|
||||||
onPageChange={() => {}}
|
<p className="text-sm text-gray-500 italic">
|
||||||
|
Ajoutez les documents pour compléter votre inscription
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Table data={parentFileTemplates} columns={columns} />
|
||||||
|
{selectedFile && (
|
||||||
|
<div className="mt-4">
|
||||||
|
{actionType === 'view' && selectedFile.fileName ? (
|
||||||
|
<iframe
|
||||||
|
src={`${BASE_URL}/${selectedFile.fileName}`}
|
||||||
|
title="Document Viewer"
|
||||||
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
height: '75vh',
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : actionType === 'upload' ? (
|
||||||
|
<FileUpload
|
||||||
|
selectionMessage={`Téléversez le fichier ${selectedFile.master_name}`}
|
||||||
|
onFileSelect={(file) => handleUpload(file, selectedFile)}
|
||||||
|
uploadedFileName={selectedFile.fileName || ''}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Popup
|
||||||
|
visible={popupVisible}
|
||||||
|
message={popupMessage}
|
||||||
|
onConfirm={() => setPopupVisible(false)}
|
||||||
|
onCancel={() => setPopupVisible(false)}
|
||||||
|
uniqueConfirmButton={true}
|
||||||
|
/>
|
||||||
|
<Popup
|
||||||
|
visible={removePopupVisible}
|
||||||
|
message={removePopupMessage}
|
||||||
|
onConfirm={removePopupOnConfirm}
|
||||||
|
onCancel={() => setRemovePopupVisible(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,12 +1,5 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import {
|
import { User, Mail } from 'lucide-react';
|
||||||
User,
|
|
||||||
Mail,
|
|
||||||
Phone,
|
|
||||||
UserCheck,
|
|
||||||
DollarSign,
|
|
||||||
Percent,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
import ToggleSwitch from '@/components/ToggleSwitch';
|
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
@ -150,7 +143,7 @@ const InscriptionForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value, type } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormData((prevState) => ({
|
setFormData((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
[name]: value,
|
[name]: value,
|
||||||
@ -444,7 +437,7 @@ const InscriptionForm = ({
|
|||||||
/>
|
/>
|
||||||
<InputPhone
|
<InputPhone
|
||||||
name="guardianPhone"
|
name="guardianPhone"
|
||||||
label={t('Numéro de téléphone (optionnel)')}
|
label="Numéro de téléphone (optionnel)"
|
||||||
value={formData.guardianPhone}
|
value={formData.guardianPhone}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full mt-4"
|
className="w-full mt-4"
|
||||||
|
|||||||
@ -5,23 +5,19 @@ import Button from '@/components/Button';
|
|||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import {
|
import {
|
||||||
fetchRegisterForm,
|
fetchRegisterForm,
|
||||||
fetchTemplatesFromRegistrationFiles,
|
fetchSchoolFileTemplatesFromRegistrationFiles,
|
||||||
|
fetchParentFileTemplatesFromRegistrationFiles,
|
||||||
} from '@/app/actions/subscriptionAction';
|
} from '@/app/actions/subscriptionAction';
|
||||||
import {
|
import {
|
||||||
downloadTemplate,
|
downloadTemplate,
|
||||||
createRegistrationTemplates,
|
editRegistrationSchoolFileTemplates,
|
||||||
editRegistrationTemplates,
|
editRegistrationParentFileTemplates,
|
||||||
deleteRegistrationTemplates,
|
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
import {
|
import {
|
||||||
fetchRegistrationPaymentModes,
|
fetchRegistrationPaymentModes,
|
||||||
fetchTuitionPaymentModes,
|
fetchTuitionPaymentModes,
|
||||||
} from '@/app/actions/schoolAction';
|
} from '@/app/actions/schoolAction';
|
||||||
import { Download, Upload, Trash2, Eye } from 'lucide-react';
|
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import DraggableFileUpload from '@/components/DraggableFileUpload';
|
|
||||||
import Modal from '@/components/Modal';
|
|
||||||
import FileStatusLabel from '@/components/FileStatusLabel';
|
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import StudentInfoForm, {
|
import StudentInfoForm, {
|
||||||
validateStudentInfo,
|
validateStudentInfo,
|
||||||
@ -42,7 +38,6 @@ export default function InscriptionFormShared({
|
|||||||
csrfToken,
|
csrfToken,
|
||||||
selectedEstablishmentId,
|
selectedEstablishmentId,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
cancelUrl,
|
|
||||||
errors = {}, // Nouvelle prop pour les erreurs
|
errors = {}, // Nouvelle prop pour les erreurs
|
||||||
}) {
|
}) {
|
||||||
// États pour gérer les données du formulaire
|
// États pour gérer les données du formulaire
|
||||||
@ -69,13 +64,8 @@ export default function InscriptionFormShared({
|
|||||||
|
|
||||||
// États pour la gestion des fichiers
|
// États pour la gestion des fichiers
|
||||||
const [uploadedFiles, setUploadedFiles] = useState([]);
|
const [uploadedFiles, setUploadedFiles] = useState([]);
|
||||||
const [fileTemplates, setFileTemplates] = useState([]);
|
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
|
||||||
const [fileGroup, setFileGroup] = useState(null);
|
const [parentFileTemplates, setParentFileTemplates] = useState([]);
|
||||||
const [fileName, setFileName] = useState('');
|
|
||||||
const [file, setFile] = useState('');
|
|
||||||
const [showUploadModal, setShowUploadModal] = useState(false);
|
|
||||||
const [currentTemplateId, setCurrentTemplateId] = useState(null);
|
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
const isCurrentPageValid = () => {
|
const isCurrentPageValid = () => {
|
||||||
@ -110,7 +100,6 @@ export default function InscriptionFormShared({
|
|||||||
totalTuitionFees: data?.totalTuitionFees,
|
totalTuitionFees: data?.totalTuitionFees,
|
||||||
});
|
});
|
||||||
setGuardians(data?.student?.guardians || []);
|
setGuardians(data?.student?.guardians || []);
|
||||||
setUploadedFiles(data.registration_files || []);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -118,12 +107,23 @@ export default function InscriptionFormShared({
|
|||||||
}, [studentId]);
|
}, [studentId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchTemplatesFromRegistrationFiles(studentId).then((data) => {
|
fetchSchoolFileTemplatesFromRegistrationFiles(studentId).then((data) => {
|
||||||
setFileTemplates(data);
|
setSchoolFileTemplates(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchParentFileTemplatesFromRegistrationFiles(studentId).then((data) => {
|
||||||
|
setParentFileTemplates(data);
|
||||||
|
|
||||||
|
// Initialiser uploadedFiles avec uniquement les fichiers dont `file` n'est pas null
|
||||||
|
const filteredFiles = data
|
||||||
|
.filter((item) => item.file !== null)
|
||||||
|
.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
fileName: item.file,
|
||||||
|
}));
|
||||||
|
setUploadedFiles(filteredFiles);
|
||||||
});
|
});
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedEstablishmentId) {
|
if (selectedEstablishmentId) {
|
||||||
// Fetch data for registration payment modes
|
// Fetch data for registration payment modes
|
||||||
handleRegistrationPaymentModes();
|
handleRegistrationPaymentModes();
|
||||||
@ -164,71 +164,89 @@ export default function InscriptionFormShared({
|
|||||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Gestion du téléversement de fichiers
|
const handleFileUpload = (file, selectedFile) => {
|
||||||
const handleFileUpload = async (file, fileName) => {
|
if (!file || !selectedFile) {
|
||||||
if (!file || !currentTemplateId || !formData.id) {
|
logger.error('Données manquantes pour le téléversement.');
|
||||||
logger.error('Missing required data for upload');
|
return Promise.reject(
|
||||||
|
new Error('Données manquantes pour le téléversement.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateData = new FormData();
|
||||||
|
updateData.append('file', file);
|
||||||
|
|
||||||
|
return editRegistrationParentFileTemplates(
|
||||||
|
selectedFile.id,
|
||||||
|
updateData,
|
||||||
|
csrfToken
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
logger.debug('Template mis à jour avec succès :', response);
|
||||||
|
|
||||||
|
setUploadedFiles((prev) => {
|
||||||
|
const updatedFiles = prev.map((uploadedFile) =>
|
||||||
|
uploadedFile.id === selectedFile.id
|
||||||
|
? { ...uploadedFile, fileName: response.data.file } // Met à jour le fichier téléversé
|
||||||
|
: uploadedFile
|
||||||
|
);
|
||||||
|
|
||||||
|
// Si le fichier n'existe pas encore, l'ajouter
|
||||||
|
if (!updatedFiles.find((file) => file.id === selectedFile.id)) {
|
||||||
|
updatedFiles.push({
|
||||||
|
id: selectedFile.id,
|
||||||
|
fileName: response.data.file,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedFiles;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response; // Retourner la réponse pour signaler le succès
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors de la mise à jour du fichier :', error);
|
||||||
|
throw error; // Relancer l'erreur pour que l'appelant puisse la capturer
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteFile = (templateId) => {
|
||||||
|
const fileToDelete = uploadedFiles.find(
|
||||||
|
(file) => parseInt(file.id) === templateId && file.fileName
|
||||||
|
);
|
||||||
|
if (!fileToDelete) {
|
||||||
|
logger.error('Aucun fichier trouvé pour suppression.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = new FormData();
|
// Créer un FormData avec un champ vide pour "file"
|
||||||
data.append('file', file);
|
const updateData = new FormData();
|
||||||
data.append('name', fileName);
|
updateData.append('file', ''); // Envoyer chaine vide pour indiquer qu'aucun fichier n'est uploadé
|
||||||
data.append('template', currentTemplateId);
|
|
||||||
data.append('register_form', formData.id);
|
|
||||||
|
|
||||||
try {
|
return editRegistrationParentFileTemplates(
|
||||||
const response = await createRegistrationTemplates(data, csrfToken);
|
templateId,
|
||||||
if (response) {
|
updateData,
|
||||||
setUploadedFiles((prev) => {
|
csrfToken
|
||||||
const newFiles = prev.filter(
|
)
|
||||||
(f) => parseInt(f.template) !== currentTemplateId
|
.then((response) => {
|
||||||
);
|
logger.debug('Fichier supprimé avec succès dans la base :', response);
|
||||||
return [
|
|
||||||
...newFiles,
|
|
||||||
{
|
|
||||||
name: fileName,
|
|
||||||
template: currentTemplateId,
|
|
||||||
file: response.file,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rafraîchir les données du formulaire pour avoir les fichiers à jour
|
// Mettre à jour l'état local pour refléter la suppression
|
||||||
if (studentId) {
|
setUploadedFiles((prev) =>
|
||||||
fetchRegisterForm(studentId).then((data) => {
|
prev.map((uploadedFile) =>
|
||||||
setUploadedFiles(data.registration_files || []);
|
uploadedFile.id === templateId
|
||||||
});
|
? { ...uploadedFile, fileName: null, fileUrl: null } // Réinitialiser les champs liés au fichier
|
||||||
}
|
: uploadedFile
|
||||||
}
|
)
|
||||||
} catch (error) {
|
);
|
||||||
logger.error('Error uploading file:', error);
|
return response;
|
||||||
}
|
})
|
||||||
};
|
.catch((error) => {
|
||||||
|
logger.error(
|
||||||
// Vérification si un fichier est déjà uploadé
|
'Erreur lors de la suppression du fichier dans la base :',
|
||||||
const isFileUploaded = (templateId) => {
|
error
|
||||||
return uploadedFiles.find((template) => template.template === templateId);
|
);
|
||||||
};
|
throw error;
|
||||||
|
});
|
||||||
// Récupération d'un fichier uploadé
|
|
||||||
const getUploadedFile = (templateId) => {
|
|
||||||
return uploadedFiles.find((file) => parseInt(file.template) === templateId);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Suppression d'un fichier
|
|
||||||
const handleDeleteFile = async (templateId) => {
|
|
||||||
const fileToDelete = getUploadedFile(templateId);
|
|
||||||
if (!fileToDelete) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteRegistrationTemplates(fileToDelete.id, csrfToken);
|
|
||||||
setUploadedFiles((prev) =>
|
|
||||||
prev.filter((f) => parseInt(f.template) !== templateId)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error deleting file:', error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Soumission du formulaire
|
// Soumission du formulaire
|
||||||
@ -268,90 +286,12 @@ export default function InscriptionFormShared({
|
|||||||
setCurrentPage(currentPage - 1);
|
setCurrentPage(currentPage - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const requiredFileTemplates = fileTemplates;
|
|
||||||
|
|
||||||
// Configuration des colonnes pour le tableau des fichiers
|
|
||||||
const columns = [
|
|
||||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
|
||||||
{
|
|
||||||
name: 'Fichier à Remplir',
|
|
||||||
transform: (row) => (row.is_required ? 'Oui' : 'Non'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Fichier de référence',
|
|
||||||
transform: (row) =>
|
|
||||||
row.file && (
|
|
||||||
<div className="flex items-center justify-center gap-2">
|
|
||||||
{' '}
|
|
||||||
<a
|
|
||||||
href={`${BASE_URL}${row.file}`}
|
|
||||||
target="_blank"
|
|
||||||
className="text-blue-500 hover:text-blue-700"
|
|
||||||
>
|
|
||||||
<Download size={16} />
|
|
||||||
</a>{' '}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Statut',
|
|
||||||
transform: (row) =>
|
|
||||||
row.is_required && (
|
|
||||||
<FileStatusLabel
|
|
||||||
status={isFileUploaded(row.id) ? 'sent' : 'pending'}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Actions',
|
|
||||||
transform: (row) => {
|
|
||||||
if (!row.is_required) return null;
|
|
||||||
|
|
||||||
const uploadedFile = getUploadedFile(row.id);
|
|
||||||
|
|
||||||
if (uploadedFile) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center gap-2">
|
|
||||||
<a
|
|
||||||
href={`${BASE_URL}${uploadedFile.file}`}
|
|
||||||
target="_blank"
|
|
||||||
className="text-blue-500 hover:text-blue-700"
|
|
||||||
>
|
|
||||||
<Eye size={16} />
|
|
||||||
</a>
|
|
||||||
<button
|
|
||||||
className="text-red-500 hover:text-red-700"
|
|
||||||
onClick={() => handleDeleteFile(row.id)}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<Trash2 size={16} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className="text-emerald-500 hover:text-emerald-700"
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setCurrentTemplateId(row.id);
|
|
||||||
setShowUploadModal(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Upload size={16} />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Affichage du loader pendant le chargement
|
// Affichage du loader pendant le chargement
|
||||||
if (isLoading) return <Loader />;
|
if (isLoading) return <Loader />;
|
||||||
|
|
||||||
// Rendu du composant
|
// Rendu du composant
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="mx-auto p-6">
|
||||||
<form onSubmit={handleSubmit} className="space-y-8">
|
<form onSubmit={handleSubmit} className="space-y-8">
|
||||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||||
{/* Page 1 : Informations de l'élève et Responsables */}
|
{/* Page 1 : Informations de l'élève et Responsables */}
|
||||||
@ -368,84 +308,84 @@ export default function InscriptionFormShared({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Pages suivantes : Section Fichiers d'inscription */}
|
{/* Pages suivantes : Section Fichiers d'inscription */}
|
||||||
{currentPage > 1 && currentPage <= requiredFileTemplates.length + 1 && (
|
{currentPage > 1 && currentPage <= schoolFileTemplates.length + 1 && (
|
||||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
<div className="mt-8 mb-4 w-3/5">
|
||||||
{/* Titre du document */}
|
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||||
<div className="mb-4">
|
{/* Titre du document */}
|
||||||
<h2 className="text-lg font-semibold text-gray-800">
|
<div className="mb-4">
|
||||||
{requiredFileTemplates[currentPage - 2].name ||
|
<h2 className="text-lg font-semibold text-gray-800">
|
||||||
'Document sans nom'}
|
{schoolFileTemplates[currentPage - 2].name ||
|
||||||
</h2>
|
'Document sans nom'}
|
||||||
<p className="text-sm text-gray-500">
|
</h2>
|
||||||
{requiredFileTemplates[currentPage - 2].description ||
|
<p className="text-sm text-gray-500">
|
||||||
'Aucune description disponible pour ce document.'}
|
{schoolFileTemplates[currentPage - 2].description ||
|
||||||
</p>
|
'Aucune description disponible pour ce document.'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Affichage du formulaire ou du document */}
|
||||||
|
{schoolFileTemplates[currentPage - 2].file === null ? (
|
||||||
|
<DocusealForm
|
||||||
|
id="docusealForm"
|
||||||
|
src={
|
||||||
|
'https://docuseal.com/s/' +
|
||||||
|
schoolFileTemplates[currentPage - 2].slug
|
||||||
|
}
|
||||||
|
withDownloadButton={false}
|
||||||
|
onComplete={() => {
|
||||||
|
downloadTemplate(schoolFileTemplates[currentPage - 2].slug)
|
||||||
|
.then((data) => fetch(data))
|
||||||
|
.then((response) => response.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
const file = new File(
|
||||||
|
[blob],
|
||||||
|
`${schoolFileTemplates[currentPage - 2].name}.pdf`,
|
||||||
|
{ type: blob.type }
|
||||||
|
);
|
||||||
|
const updateData = new FormData();
|
||||||
|
updateData.append('file', file);
|
||||||
|
|
||||||
|
return editRegistrationSchoolFileTemplates(
|
||||||
|
schoolFileTemplates[currentPage - 2].id,
|
||||||
|
updateData,
|
||||||
|
csrfToken
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
logger.debug('EDIT TEMPLATE : ', data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('error editing template : ', error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<iframe
|
||||||
|
src={`${BASE_URL}/${schoolFileTemplates[currentPage - 2].file}`}
|
||||||
|
title="Document Viewer"
|
||||||
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
height: '75vh', // Ajuster la hauteur à 75% de la fenêtre
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Affichage du formulaire ou du document */}
|
|
||||||
{requiredFileTemplates[currentPage - 2].file === '' ? (
|
|
||||||
<DocusealForm
|
|
||||||
id="docusealForm"
|
|
||||||
src={
|
|
||||||
'https://docuseal.com/s/' +
|
|
||||||
requiredFileTemplates[currentPage - 2].slug
|
|
||||||
}
|
|
||||||
withDownloadButton={false}
|
|
||||||
onComplete={() => {
|
|
||||||
downloadTemplate(requiredFileTemplates[currentPage - 2].slug)
|
|
||||||
.then((data) => fetch(data))
|
|
||||||
.then((response) => response.blob())
|
|
||||||
.then((blob) => {
|
|
||||||
const file = new File(
|
|
||||||
[blob],
|
|
||||||
`${requiredFileTemplates[currentPage - 2].name}.pdf`,
|
|
||||||
{ type: blob.type }
|
|
||||||
);
|
|
||||||
const updateData = new FormData();
|
|
||||||
updateData.append('file', file);
|
|
||||||
|
|
||||||
return editRegistrationTemplates(
|
|
||||||
requiredFileTemplates[currentPage - 2].id,
|
|
||||||
updateData,
|
|
||||||
csrfToken
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
logger.debug('EDIT TEMPLATE : ', data);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error('error editing template : ', error);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<iframe
|
|
||||||
src={`${BASE_URL}/${requiredFileTemplates[currentPage - 2].file}`}
|
|
||||||
title="Document Viewer"
|
|
||||||
className="w-full"
|
|
||||||
style={{
|
|
||||||
height: '75vh', // Ajuster la hauteur à 75% de la fenêtre
|
|
||||||
border: 'none',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Dernière page : Section Fichiers parents */}
|
{/* Dernière page : Section Fichiers parents */}
|
||||||
{currentPage === requiredFileTemplates.length + 2 && (
|
{currentPage === schoolFileTemplates.length + 2 && (
|
||||||
<>
|
<FilesToUpload
|
||||||
<FilesToUpload
|
parentFileTemplates={parentFileTemplates}
|
||||||
fileTemplates={fileTemplates.filter(
|
uploadedFiles={uploadedFiles}
|
||||||
(template) => !template.is_required
|
onFileUpload={handleFileUpload}
|
||||||
)}
|
onFileDelete={handleDeleteFile}
|
||||||
columns={columns}
|
/>
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Boutons de contrôle */}
|
{/* Boutons de contrôle */}
|
||||||
<div className="flex justify-end space-x-4">
|
<div className="flex justify-center space-x-4">
|
||||||
<Button
|
<Button
|
||||||
text="Sauvegarder"
|
text="Sauvegarder"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
@ -462,7 +402,7 @@ export default function InscriptionFormShared({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentPage < requiredFileTemplates.length + 2 && (
|
{currentPage < schoolFileTemplates.length + 2 && (
|
||||||
<Button
|
<Button
|
||||||
text="Suivant"
|
text="Suivant"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -479,57 +419,11 @@ export default function InscriptionFormShared({
|
|||||||
name="Next"
|
name="Next"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentPage === requiredFileTemplates.length + 2 && (
|
{currentPage === schoolFileTemplates.length + 2 && (
|
||||||
<Button type="submit" text="Valider" primary />
|
<Button type="submit" text="Valider" primary />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{fileTemplates.length > 0 && (
|
|
||||||
<Modal
|
|
||||||
isOpen={showUploadModal}
|
|
||||||
setIsOpen={setShowUploadModal}
|
|
||||||
title="Téléverser un fichier"
|
|
||||||
ContentComponent={() => (
|
|
||||||
<>
|
|
||||||
<DraggableFileUpload
|
|
||||||
className="w-full"
|
|
||||||
fileName={fileName}
|
|
||||||
onFileSelect={(selectedFile) => {
|
|
||||||
if (selectedFile) {
|
|
||||||
setFile(selectedFile);
|
|
||||||
setFileName(selectedFile.name);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="mt-4 flex justify-center space-x-4">
|
|
||||||
<Button
|
|
||||||
text="Annuler"
|
|
||||||
onClick={() => {
|
|
||||||
setShowUploadModal(false);
|
|
||||||
setCurrentTemplateId(null);
|
|
||||||
setFile(null);
|
|
||||||
setFileName('');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text="Valider"
|
|
||||||
onClick={() => {
|
|
||||||
if (file && fileName) {
|
|
||||||
handleFileUpload(file, fileName);
|
|
||||||
setShowUploadModal(false);
|
|
||||||
setCurrentTemplateId(null);
|
|
||||||
setFile(null);
|
|
||||||
setFileName('');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
primary={true}
|
|
||||||
disabled={!file || !fileName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +1,35 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { DocusealBuilder } from '@docuseal/react';
|
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import { generateToken } from '@/app/actions/registerFileGroupAction';
|
import { generateToken } from '@/app/actions/registerFileGroupAction';
|
||||||
|
import {
|
||||||
|
fetchSchoolFileTemplatesFromRegistrationFiles,
|
||||||
|
fetchParentFileTemplatesFromRegistrationFiles,
|
||||||
|
} from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { GraduationCap, CloudUpload } from 'lucide-react';
|
import { GraduationCap } from 'lucide-react';
|
||||||
|
import FileUpload from '@/components/FileUpload';
|
||||||
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
|
||||||
export default function ValidateSubscription({
|
export default function ValidateSubscription({
|
||||||
studentId,
|
studentId,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
paymentMode,
|
paymentSepa,
|
||||||
file,
|
file,
|
||||||
onAccept,
|
onAccept,
|
||||||
}) {
|
}) {
|
||||||
const [token, setToken] = useState(null);
|
const [token, setToken] = useState(null);
|
||||||
const [uploadedFileName, setUploadedFileName] = useState('');
|
const [uploadedFileName, setUploadedFileName] = useState('');
|
||||||
|
const [selectedFile, setSelectedFile] = useState(null); // Nouvel état pour le fichier sélectionné
|
||||||
const [pdfUrl, setPdfUrl] = useState(`${BASE_URL}/${file}`);
|
const [pdfUrl, setPdfUrl] = useState(`${BASE_URL}/${file}`);
|
||||||
const [isSepa, setIsSepa] = useState(paymentMode === '1'); // Vérifie si le mode de paiement est SEPA
|
const [isSepa, setIsSepa] = useState(paymentSepa); // Vérifie si le mode de paiement est SEPA
|
||||||
const [currentPage, setCurrentPage] = useState(1); // Gestion des pages
|
const [currentPage, setCurrentPage] = useState(1); // Gestion des étapes
|
||||||
|
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]); // Stocke les fichiers schoolFileTemplates
|
||||||
|
const [parentFileTemplates, setParentFileTemplates] = useState([]); // Stocke les fichiers parentFileTemplates
|
||||||
|
const [mergeDocuments, setMergeDocuments] = useState(false); // État pour activer/désactiver la fusion des documents
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSepa) {
|
if (isSepa) {
|
||||||
@ -33,38 +43,67 @@ export default function ValidateSubscription({
|
|||||||
}
|
}
|
||||||
}, [isSepa]);
|
}, [isSepa]);
|
||||||
|
|
||||||
const handleUpload = (detail) => {
|
useEffect(() => {
|
||||||
logger.debug('Uploaded file detail:', detail);
|
// Récupérer les fichiers schoolFileTemplates pour l'étudiant
|
||||||
setUploadedFileName(detail.name);
|
fetchSchoolFileTemplatesFromRegistrationFiles(studentId)
|
||||||
};
|
.then((data) => {
|
||||||
|
setSchoolFileTemplates(data);
|
||||||
|
logger.debug('Fichiers schoolFileTemplates récupérés:', data);
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
logger.error(
|
||||||
|
'Erreur lors de la récupération des schoolFileTemplates:',
|
||||||
|
error
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Récupérer les fichiers parentFileTemplates pour l'étudiant
|
||||||
|
fetchParentFileTemplatesFromRegistrationFiles(studentId)
|
||||||
|
.then((data) => {
|
||||||
|
setParentFileTemplates(data);
|
||||||
|
logger.debug('Fichiers parentFileTemplates récupérés:', data);
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
logger.error(
|
||||||
|
'Erreur lors de la récupération des parentFileTemplates:',
|
||||||
|
error
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [studentId]);
|
||||||
|
|
||||||
const handleAccept = () => {
|
const handleAccept = () => {
|
||||||
const fileInput = document.getElementById('fileInput'); // Récupère l'élément input
|
if (!selectedFile && isSepa) {
|
||||||
const file = fileInput?.files[0]; // Récupère le fichier sélectionné
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
logger.error('Aucun fichier sélectionné pour le champ SEPA.');
|
logger.error('Aucun fichier sélectionné pour le champ SEPA.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ajouter le paramètre fusion dans l'URL
|
||||||
|
const fusionParam = mergeDocuments ? 'true' : 'false';
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
status: 7,
|
status: 7,
|
||||||
sepa_file: file,
|
sepa_file: selectedFile, // Utilise le fichier sélectionné depuis l'état
|
||||||
|
fusionParam: fusionParam,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Appeler la fonction passée par le parent pour mettre à jour le RF
|
// Appeler la fonction passée par le parent pour mettre à jour le RF
|
||||||
onAccept(data);
|
onAccept(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefuse = () => {
|
const handleToggleMergeDocuments = () => {
|
||||||
logger.debug("Dossier refusé pour l'étudiant:", studentId);
|
// Inverser l'état de mergeDocuments
|
||||||
// Logique pour refuser l'inscription
|
setMergeDocuments((prevState) => !prevState);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isValidateButtonDisabled = isSepa && !uploadedFileName;
|
const isValidateButtonDisabled = isSepa && !uploadedFileName;
|
||||||
|
|
||||||
const goToNextPage = () => {
|
const goToNextPage = () => {
|
||||||
if (currentPage < (isSepa ? 2 : 1)) {
|
const totalPages =
|
||||||
|
1 +
|
||||||
|
schoolFileTemplates.length +
|
||||||
|
parentFileTemplates.length +
|
||||||
|
(isSepa ? 1 : 0);
|
||||||
|
if (currentPage < totalPages) {
|
||||||
setCurrentPage(currentPage + 1);
|
setCurrentPage(currentPage + 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -75,93 +114,102 @@ export default function ValidateSubscription({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const totalPages =
|
||||||
|
1 +
|
||||||
|
schoolFileTemplates.length +
|
||||||
|
parentFileTemplates.length +
|
||||||
|
(isSepa ? 1 : 0);
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
if (currentPage === 1) {
|
||||||
|
// Page 1 : Afficher le PDF principal
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
src={pdfUrl}
|
||||||
|
title="Aperçu du PDF"
|
||||||
|
className="w-full h-[900px] border rounded-lg"
|
||||||
|
style={{
|
||||||
|
transform: 'scale(0.95)', // Dézoom léger pour une meilleure vue
|
||||||
|
transformOrigin: 'top center',
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
currentPage > 1 &&
|
||||||
|
currentPage <= 1 + schoolFileTemplates.length
|
||||||
|
) {
|
||||||
|
// Pages des schoolFileTemplates
|
||||||
|
const index = currentPage - 2; // Décalage pour correspondre à l'index du tableau
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
src={`${BASE_URL}/${schoolFileTemplates[index]?.file}`}
|
||||||
|
title={`Document ${index + 1}`}
|
||||||
|
className="w-full h-[900px] border rounded-lg"
|
||||||
|
style={{
|
||||||
|
transform: 'scale(0.95)', // Dézoom léger pour une meilleure vue
|
||||||
|
transformOrigin: 'top center',
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
currentPage > 1 + schoolFileTemplates.length &&
|
||||||
|
currentPage <= 1 + schoolFileTemplates.length + parentFileTemplates.length
|
||||||
|
) {
|
||||||
|
// Pages des parentFileTemplates
|
||||||
|
const index = currentPage - 2 - schoolFileTemplates.length; // Décalage pour correspondre à l'index du tableau
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
src={`${BASE_URL}/${parentFileTemplates[index]?.file}`}
|
||||||
|
title={`Document Parent ${index + 1}`}
|
||||||
|
className="w-full h-[900px] border rounded-lg"
|
||||||
|
style={{
|
||||||
|
transform: 'scale(0.95)', // Dézoom léger pour une meilleure vue
|
||||||
|
transformOrigin: 'top center',
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (currentPage === totalPages && isSepa) {
|
||||||
|
// Dernière page : Mandat SEPA
|
||||||
|
return (
|
||||||
|
<FileUpload
|
||||||
|
selectionMessage="Sélectionnez un mandat de prélèvement SEPA"
|
||||||
|
onFileSelect={(file) => {
|
||||||
|
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
||||||
|
setSelectedFile(file); // Stocke le fichier dans l'état
|
||||||
|
logger.debug('Fichier sélectionné:', file.name);
|
||||||
|
}}
|
||||||
|
uploadedFileName={uploadedFileName}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-8 space-y-6 bg-gray-50 rounded-lg shadow-lg">
|
<div className="space-y-6 p-6">
|
||||||
{/* Titre */}
|
<SectionHeader
|
||||||
<div className="flex items-center space-x-4">
|
icon={GraduationCap}
|
||||||
<div className="bg-emerald-100 p-3 rounded-full shadow-md">
|
title={`Dossier scolaire de ${firstName} ${lastName}`}
|
||||||
<GraduationCap className="w-8 h-8 text-emerald-600" />
|
description={`Année scolaire ${new Date().getFullYear()}-${new Date().getFullYear() + 1}`}
|
||||||
</div>
|
/>
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl font-bold text-gray-800">
|
|
||||||
Dossier scolaire de{' '}
|
|
||||||
<span className="text-emerald-600">
|
|
||||||
{firstName} {lastName}
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
<p className="text-sm text-gray-500 italic">
|
|
||||||
Année scolaire {new Date().getFullYear()}-
|
|
||||||
{new Date().getFullYear() + 1}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Contenu principal */}
|
{/* Contenu principal */}
|
||||||
{currentPage === 1 && (
|
<div className="p-6 items-center">{renderContent()}</div>
|
||||||
<div className="border p-6 rounded-lg shadow-md bg-white flex justify-center items-center">
|
|
||||||
<iframe
|
|
||||||
src={pdfUrl}
|
|
||||||
title="Aperçu du PDF"
|
|
||||||
className="w-full h-[900px] border rounded-lg"
|
|
||||||
style={{
|
|
||||||
transform: 'scale(0.95)', // Dézoom léger pour une meilleure vue
|
|
||||||
transformOrigin: 'top center',
|
|
||||||
border: 'none',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentPage === 2 && isSepa && (
|
{/* Option de fusion des documents (affichée uniquement sur la dernière page) */}
|
||||||
<div className="border p-4 rounded-md shadow-md">
|
{currentPage === totalPages && (
|
||||||
<h3 className="text-lg font-semibold mb-4">
|
<div className="flex items-center justify-between mt-6">
|
||||||
Sélection du mandat de pélèvement SEPA
|
<span className="text-gray-700">
|
||||||
</h3>
|
Fusionner les documents en un seul fichier PDF
|
||||||
<div
|
</span>
|
||||||
className="border-2 border-dashed border-gray-500 p-6 rounded-lg flex flex-col items-center justify-center cursor-pointer hover:border-emerald-500"
|
<ToggleSwitch
|
||||||
onClick={() => document.getElementById('fileInput').click()} // Ouvre l'explorateur de fichiers au clic
|
label="Fusionner"
|
||||||
onDragOver={(e) => e.preventDefault()}
|
checked={mergeDocuments}
|
||||||
onDrop={(e) => {
|
onChange={handleToggleMergeDocuments} // Appeler la fonction pour inverser l'état
|
||||||
e.preventDefault();
|
/>
|
||||||
const file = e.dataTransfer.files[0];
|
|
||||||
if (file) {
|
|
||||||
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
|
||||||
logger.debug('Fichier déposé:', file.name);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloudUpload className="w-12 h-12 text-emerald-500 mb-4" />{' '}
|
|
||||||
{/* Icône de cloud */}
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept=".pdf"
|
|
||||||
onChange={(e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
|
||||||
logger.debug('Fichier sélectionné:', file.name);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="hidden"
|
|
||||||
id="fileInput"
|
|
||||||
/>
|
|
||||||
<label htmlFor="fileInput" className="text-center text-gray-500">
|
|
||||||
<p className="text-lg font-semibold text-gray-800">
|
|
||||||
Déposez votre fichier ici
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-gray-500 mt-2">
|
|
||||||
ou cliquez pour sélectionner un fichier PDF
|
|
||||||
</p>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{uploadedFileName && (
|
|
||||||
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
|
|
||||||
<CloudUpload className="w-6 h-6 text-emerald-500" />
|
|
||||||
<p className="text-sm font-medium text-gray-800">
|
|
||||||
<span className="font-semibold">{uploadedFileName}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -174,7 +222,7 @@ export default function ValidateSubscription({
|
|||||||
className="bg-gray-300 text-gray-700 hover:bg-gray-400 px-6 py-2"
|
className="bg-gray-300 text-gray-700 hover:bg-gray-400 px-6 py-2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentPage < (isSepa ? 2 : 1) && (
|
{currentPage < totalPages && (
|
||||||
<Button
|
<Button
|
||||||
text="Suivant"
|
text="Suivant"
|
||||||
onClick={goToNextPage}
|
onClick={goToNextPage}
|
||||||
@ -182,7 +230,7 @@ export default function ValidateSubscription({
|
|||||||
className="bg-emerald-500 text-white hover:bg-emerald-600 px-6 py-2"
|
className="bg-emerald-500 text-white hover:bg-emerald-600 px-6 py-2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentPage === (isSepa ? 2 : 1) && (
|
{currentPage === totalPages && (
|
||||||
<Button
|
<Button
|
||||||
text="Valider"
|
text="Valider"
|
||||||
onClick={handleAccept}
|
onClick={handleAccept}
|
||||||
|
|||||||
44
Front-End/src/components/SectionHeader.js
Normal file
44
Front-End/src/components/SectionHeader.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Plus } from 'lucide-react';
|
||||||
|
|
||||||
|
const SectionHeader = ({
|
||||||
|
icon: Icon,
|
||||||
|
discountStyle = false,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
button = false,
|
||||||
|
buttonOpeningModal = false,
|
||||||
|
onClick = null
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className={`${discountStyle ? "bg-yellow-100" : "bg-emerald-100"} p-3 rounded-full shadow-md`}>
|
||||||
|
<Icon
|
||||||
|
className={discountStyle ?
|
||||||
|
"w-8 h-8 text-yellow-600" :
|
||||||
|
"w-8 h-8 text-emerald-600"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800">{title}</h2>
|
||||||
|
<p className="text-sm text-gray-500 italic">{description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{button && onClick && (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className={buttonOpeningModal ?
|
||||||
|
"flex items-center bg-emerald-200 text-emerald-700 p-2 rounded-full shadow-sm hover:bg-emerald-300" :
|
||||||
|
"text-emerald-500 hover:bg-emerald-200 rounded-full p-2"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Plus className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SectionHeader;
|
||||||
@ -9,7 +9,11 @@ const SidebarTabs = ({ tabs }) => {
|
|||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
className={`flex-1 p-4 ${activeTab === tab.id ? 'border-b-2 border-emerald-500 text-emerald-500' : 'text-gray-500 hover:text-emerald-500'}`}
|
className={`flex-1 p-4 ${
|
||||||
|
activeTab === tab.id
|
||||||
|
? 'border-b-2 border-emerald-500 text-emerald-500'
|
||||||
|
: 'text-gray-500 hover:text-emerald-500'
|
||||||
|
}`}
|
||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => setActiveTab(tab.id)}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
|
|||||||
@ -1,13 +1,4 @@
|
|||||||
import {
|
import { Trash2, Edit3, ZoomIn, Users, Check, X, Hand } from 'lucide-react';
|
||||||
Trash2,
|
|
||||||
Edit3,
|
|
||||||
Plus,
|
|
||||||
ZoomIn,
|
|
||||||
Users,
|
|
||||||
Check,
|
|
||||||
X,
|
|
||||||
Hand,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
@ -21,6 +12,7 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
|
|||||||
import { ESTABLISHMENT_ID } from '@/utils/Url';
|
import { ESTABLISHMENT_ID } from '@/utils/Url';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import ClasseDetails from '@/components/ClasseDetails';
|
import ClasseDetails from '@/components/ClasseDetails';
|
||||||
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
|
||||||
const ItemTypes = {
|
const ItemTypes = {
|
||||||
TEACHER: 'teacher',
|
TEACHER: 'teacher',
|
||||||
@ -553,19 +545,13 @@ const ClassesSection = ({
|
|||||||
return (
|
return (
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<SectionHeader
|
||||||
<div className="flex items-center mb-4">
|
icon={Users}
|
||||||
<Users className="w-6 h-6 text-emerald-500 mr-2" />
|
title="Liste des classes"
|
||||||
<h2 className="text-xl font-semibold">Classes</h2>
|
description="Gérez les classes de votre école"
|
||||||
</div>
|
button={true}
|
||||||
<button
|
onClick={handleAddClass}
|
||||||
type="button"
|
/>
|
||||||
onClick={handleAddClass}
|
|
||||||
className="text-emerald-500 hover:text-emerald-700"
|
|
||||||
>
|
|
||||||
<Plus className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Table
|
<Table
|
||||||
data={newClass ? [newClass, ...classes] : classes}
|
data={newClass ? [newClass, ...classes] : classes}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Plus, Trash2, Edit3, Check, X, BookOpen } from 'lucide-react';
|
import { Trash2, Edit3, Check, X, BookOpen } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
@ -6,7 +6,9 @@ import InputTextWithColorIcon from '@/components/InputTextWithColorIcon';
|
|||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
||||||
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
|
||||||
const SpecialitiesSection = ({
|
const SpecialitiesSection = ({
|
||||||
specialities,
|
specialities,
|
||||||
@ -26,6 +28,8 @@ const SpecialitiesSection = ({
|
|||||||
const [removePopupMessage, setRemovePopupMessage] = useState('');
|
const [removePopupMessage, setRemovePopupMessage] = useState('');
|
||||||
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
||||||
|
|
||||||
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
|
||||||
// Récupération des messages d'erreur
|
// Récupération des messages d'erreur
|
||||||
const getError = (field) => {
|
const getError = (field) => {
|
||||||
return localErrors?.[field]?.[0];
|
return localErrors?.[field]?.[0];
|
||||||
@ -49,7 +53,13 @@ const SpecialitiesSection = ({
|
|||||||
|
|
||||||
const handleSaveNewSpeciality = () => {
|
const handleSaveNewSpeciality = () => {
|
||||||
if (newSpeciality.name) {
|
if (newSpeciality.name) {
|
||||||
handleCreate(newSpeciality)
|
// Ajouter l'ID de l'établissement à la nouvelle spécialité
|
||||||
|
const specialityData = {
|
||||||
|
...newSpeciality,
|
||||||
|
establishment: selectedEstablishmentId, // Inclure l'ID de l'établissement
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCreate(specialityData)
|
||||||
.then((createdSpeciality) => {
|
.then((createdSpeciality) => {
|
||||||
setSpecialities([createdSpeciality, ...specialities]);
|
setSpecialities([createdSpeciality, ...specialities]);
|
||||||
setNewSpeciality(null);
|
setNewSpeciality(null);
|
||||||
@ -234,19 +244,13 @@ const SpecialitiesSection = ({
|
|||||||
return (
|
return (
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<SectionHeader
|
||||||
<div className="flex items-center mb-4">
|
icon={BookOpen}
|
||||||
<BookOpen className="w-6 h-6 text-emerald-500 mr-2" />
|
title="Liste des spécialités"
|
||||||
<h2 className="text-xl font-semibold">Spécialités</h2>
|
description="Gérez les spécialités de votre école"
|
||||||
</div>
|
button={true}
|
||||||
<button
|
onClick={handleAddSpeciality}
|
||||||
type="button"
|
/>
|
||||||
onClick={handleAddSpeciality}
|
|
||||||
className="text-emerald-500 hover:text-emerald-700"
|
|
||||||
>
|
|
||||||
<Plus className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Table
|
<Table
|
||||||
data={newSpeciality ? [newSpeciality, ...specialities] : specialities}
|
data={newSpeciality ? [newSpeciality, ...specialities] : specialities}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@ -22,9 +22,9 @@ const StructureManagement = ({
|
|||||||
handleDelete,
|
handleDelete,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-8xl mx-auto p-4 mt-6 space-y-8">
|
<div className="w-full mx-auto mt-6">
|
||||||
<ClassesProvider>
|
<ClassesProvider>
|
||||||
<div className="w-2/5 p-4 bg-white rounded-lg shadow-md">
|
<div className="mt-8 w-2/5">
|
||||||
<SpecialitiesSection
|
<SpecialitiesSection
|
||||||
specialities={specialities}
|
specialities={specialities}
|
||||||
setSpecialities={setSpecialities}
|
setSpecialities={setSpecialities}
|
||||||
@ -48,7 +48,7 @@ const StructureManagement = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-4/5 p-4 bg-white rounded-lg shadow-md">
|
<div className="w-4/5 mt-12">
|
||||||
<TeachersSection
|
<TeachersSection
|
||||||
teachers={teachers}
|
teachers={teachers}
|
||||||
setTeachers={setTeachers}
|
setTeachers={setTeachers}
|
||||||
@ -70,7 +70,7 @@ const StructureManagement = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full p-4 bg-white rounded-lg shadow-md">
|
<div className="w-full mt-12">
|
||||||
<ClassesSection
|
<ClassesSection
|
||||||
classes={classes}
|
classes={classes}
|
||||||
setClasses={setClasses}
|
setClasses={setClasses}
|
||||||
|
|||||||
@ -1,18 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import { Edit3, Trash2, GraduationCap, Check, X, Hand } from 'lucide-react';
|
||||||
Plus,
|
|
||||||
Edit3,
|
|
||||||
Trash2,
|
|
||||||
GraduationCap,
|
|
||||||
Check,
|
|
||||||
X,
|
|
||||||
Hand,
|
|
||||||
Search,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import ToggleSwitch from '@/components/ToggleSwitch';
|
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||||
import { createProfile, updateProfile } from '@/app/actions/authAction';
|
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
@ -20,8 +10,8 @@ import InputText from '@/components/InputText';
|
|||||||
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
||||||
import TeacherItem from './TeacherItem';
|
import TeacherItem from './TeacherItem';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { fetchProfiles } from '@/app/actions/authAction';
|
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
|
||||||
const ItemTypes = {
|
const ItemTypes = {
|
||||||
SPECIALITY: 'speciality',
|
SPECIALITY: 'speciality',
|
||||||
@ -578,19 +568,13 @@ const TeachersSection = ({
|
|||||||
return (
|
return (
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<SectionHeader
|
||||||
<div className="flex items-center mb-4">
|
icon={GraduationCap}
|
||||||
<GraduationCap className="w-6 h-6 text-emerald-500 mr-2" />
|
title="Liste des enseignants.es"
|
||||||
<h2 className="text-xl font-semibold">Enseignants</h2>
|
description="Gérez les enseignants.es de votre école"
|
||||||
</div>
|
button={true}
|
||||||
<button
|
onClick={handleAddTeacher}
|
||||||
type="button"
|
/>
|
||||||
onClick={handleAddTeacher}
|
|
||||||
className="text-emerald-500 hover:text-emerald-700"
|
|
||||||
>
|
|
||||||
<Plus className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Table
|
<Table
|
||||||
data={newTeacher ? [newTeacher, ...teachers] : teachers}
|
data={newTeacher ? [newTeacher, ...teachers] : teachers}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@ -1,31 +1,22 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
|
|
||||||
import {
|
import {
|
||||||
fetchRegistrationFileGroups,
|
fetchRegistrationFileGroups,
|
||||||
createRegistrationTemplates,
|
createRegistrationSchoolFileTemplate,
|
||||||
cloneTemplate,
|
cloneTemplate,
|
||||||
generateToken,
|
generateToken,
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
import { DocusealBuilder } from '@docuseal/react';
|
import { DocusealBuilder } from '@docuseal/react';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import {
|
|
||||||
BE_DOCUSEAL_GET_JWT,
|
|
||||||
BASE_URL,
|
|
||||||
FE_API_DOCUSEAL_GENERATE_TOKEN,
|
|
||||||
} from '@/utils/Url';
|
|
||||||
import Button from '@/components/Button'; // Import du composant Button
|
|
||||||
import MultiSelect from '@/components/MultiSelect'; // Import du composant MultiSelect
|
import MultiSelect from '@/components/MultiSelect'; // Import du composant MultiSelect
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
export default function FileUpload({
|
export default function FileUploadDocuSeal({
|
||||||
handleCreateTemplateMaster,
|
handleCreateTemplateMaster,
|
||||||
handleEditTemplateMaster,
|
handleEditTemplateMaster,
|
||||||
fileToEdit = null,
|
fileToEdit = null,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}) {
|
}) {
|
||||||
const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired
|
|
||||||
const [order, setOrder] = useState(0);
|
|
||||||
const [groups, setGroups] = useState([]);
|
const [groups, setGroups] = useState([]);
|
||||||
const [token, setToken] = useState(null);
|
const [token, setToken] = useState(null);
|
||||||
const [templateMaster, setTemplateMaster] = useState(null);
|
const [templateMaster, setTemplateMaster] = useState(null);
|
||||||
@ -61,10 +52,6 @@ export default function FileUpload({
|
|||||||
);
|
);
|
||||||
}, [fileToEdit]);
|
}, [fileToEdit]);
|
||||||
|
|
||||||
const handleFileNameChange = (event) => {
|
|
||||||
setUploadedFileName(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGroupChange = (selectedGroups) => {
|
const handleGroupChange = (selectedGroups) => {
|
||||||
setSelectedGroups(selectedGroups);
|
setSelectedGroups(selectedGroups);
|
||||||
|
|
||||||
@ -120,7 +107,7 @@ export default function FileUpload({
|
|||||||
logger.debug('creation du clone avec required : ', is_required);
|
logger.debug('creation du clone avec required : ', is_required);
|
||||||
cloneTemplate(templateMaster?.id, guardian.email, is_required)
|
cloneTemplate(templateMaster?.id, guardian.email, is_required)
|
||||||
.then((clonedDocument) => {
|
.then((clonedDocument) => {
|
||||||
// Sauvegarde des templates clonés dans la base de données
|
// Sauvegarde des schoolFileTemplates clonés dans la base de données
|
||||||
const data = {
|
const data = {
|
||||||
name: `${uploadedFileName}_${guardian.first_name}_${guardian.last_name}`,
|
name: `${uploadedFileName}_${guardian.first_name}_${guardian.last_name}`,
|
||||||
slug: clonedDocument.slug,
|
slug: clonedDocument.slug,
|
||||||
@ -128,7 +115,7 @@ export default function FileUpload({
|
|||||||
master: templateMaster?.id,
|
master: templateMaster?.id,
|
||||||
registration_form: guardian.registration_form,
|
registration_form: guardian.registration_form,
|
||||||
};
|
};
|
||||||
createRegistrationTemplates(data, csrfToken)
|
createRegistrationSchoolFileTemplate(data, csrfToken)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
logger.debug('Template enregistré avec succès:', response);
|
logger.debug('Template enregistré avec succès:', response);
|
||||||
onSuccess();
|
onSuccess();
|
||||||
@ -1,36 +1,40 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import { Download, Edit3, Trash2, FolderPlus, Signature } from 'lucide-react';
|
||||||
Plus,
|
|
||||||
Download,
|
|
||||||
Edit3,
|
|
||||||
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/Structure/Files/FileUpload';
|
import FileUploadDocuSeal from '@/components/Structure/Files/FileUploadDocuSeal';
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import {
|
import {
|
||||||
|
// GET
|
||||||
fetchRegistrationFileGroups,
|
fetchRegistrationFileGroups,
|
||||||
|
fetchRegistrationSchoolFileMasters,
|
||||||
|
fetchRegistrationSchoolFileTemplates,
|
||||||
|
fetchRegistrationParentFileMasters,
|
||||||
|
// POST
|
||||||
createRegistrationFileGroup,
|
createRegistrationFileGroup,
|
||||||
deleteRegistrationFileGroup,
|
createRegistrationSchoolFileMaster,
|
||||||
|
createRegistrationParentFileMaster,
|
||||||
|
// PUT
|
||||||
editRegistrationFileGroup,
|
editRegistrationFileGroup,
|
||||||
fetchRegistrationTemplateMaster,
|
editRegistrationSchoolFileMaster,
|
||||||
createRegistrationTemplateMaster,
|
editRegistrationParentFileMaster,
|
||||||
editRegistrationTemplateMaster,
|
// DELETE
|
||||||
deleteRegistrationTemplateMaster,
|
deleteRegistrationFileGroup,
|
||||||
fetchRegistrationTemplates,
|
deleteRegistrationSchoolFileMaster,
|
||||||
|
deleteRegistrationParentFileMaster,
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
|
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import ParentFilesSection from '@/components/Structure/Files/ParentFilesSection';
|
||||||
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
|
||||||
export default function FilesGroupsManagement({
|
export default function FilesGroupsManagement({
|
||||||
csrfToken,
|
csrfToken,
|
||||||
selectedEstablishmentId,
|
selectedEstablishmentId,
|
||||||
}) {
|
}) {
|
||||||
const [templateMasters, setTemplateMasters] = useState([]);
|
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
|
||||||
const [templates, setTemplates] = useState([]);
|
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
|
||||||
|
const [parentFiles, setParentFileMasters] = useState([]);
|
||||||
const [groups, setGroups] = useState([]);
|
const [groups, setGroups] = useState([]);
|
||||||
const [selectedGroup, setSelectedGroup] = useState(null);
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
@ -39,6 +43,10 @@ export default function FilesGroupsManagement({
|
|||||||
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
||||||
const [groupToEdit, setGroupToEdit] = useState(null);
|
const [groupToEdit, setGroupToEdit] = useState(null);
|
||||||
const [reloadTemplates, setReloadTemplates] = useState(false);
|
const [reloadTemplates, setReloadTemplates] = useState(false);
|
||||||
|
const [editingDocumentId, setEditingDocumentId] = useState(null);
|
||||||
|
const [formData, setFormData] = useState({});
|
||||||
|
|
||||||
|
const [uploadedFileName, setUploadedFileName] = useState('');
|
||||||
|
|
||||||
const handleReloadTemplates = () => {
|
const handleReloadTemplates = () => {
|
||||||
setReloadTemplates(true);
|
setReloadTemplates(true);
|
||||||
@ -61,19 +69,28 @@ export default function FilesGroupsManagement({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEstablishmentId) {
|
if (selectedEstablishmentId) {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
fetchRegistrationTemplateMaster(),
|
fetchRegistrationSchoolFileMasters(),
|
||||||
fetchRegistrationFileGroups(selectedEstablishmentId),
|
fetchRegistrationFileGroups(selectedEstablishmentId),
|
||||||
fetchRegistrationTemplates(),
|
fetchRegistrationSchoolFileTemplates(),
|
||||||
|
fetchRegistrationParentFileMasters(),
|
||||||
])
|
])
|
||||||
.then(([filesTemplateMasters, groupsData, filesTemplates]) => {
|
.then(
|
||||||
setGroups(groupsData);
|
([
|
||||||
setTemplates(filesTemplates);
|
dataSchoolFileMasters,
|
||||||
// Transformer chaque fichier pour inclure les informations complètes du groupe
|
groupsData,
|
||||||
const transformedFiles = filesTemplateMasters.map((file) =>
|
dataSchoolFileTemplates,
|
||||||
transformFileData(file, groupsData)
|
dataParentFileMasters,
|
||||||
);
|
]) => {
|
||||||
setTemplateMasters(transformedFiles);
|
setGroups(groupsData);
|
||||||
})
|
setSchoolFileTemplates(dataSchoolFileTemplates);
|
||||||
|
setParentFileMasters(dataParentFileMasters);
|
||||||
|
// Transformer chaque fichier pour inclure les informations complètes du groupe
|
||||||
|
const transformedFiles = dataSchoolFileMasters.map((file) =>
|
||||||
|
transformFileData(file, groupsData)
|
||||||
|
);
|
||||||
|
setSchoolFileMasters(transformedFiles);
|
||||||
|
}
|
||||||
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err.message);
|
console.log(err.message);
|
||||||
})
|
})
|
||||||
@ -85,7 +102,7 @@ export default function FilesGroupsManagement({
|
|||||||
|
|
||||||
const deleteTemplateMaster = (templateMaster) => {
|
const deleteTemplateMaster = (templateMaster) => {
|
||||||
// Supprimer les clones associés via l'API DocuSeal
|
// Supprimer les clones associés via l'API DocuSeal
|
||||||
const removeClonesPromises = templates
|
const removeClonesPromises = schoolFileTemplates
|
||||||
.filter((template) => template.master === templateMaster.id)
|
.filter((template) => template.master === templateMaster.id)
|
||||||
.map((template) => removeTemplate(template.id));
|
.map((template) => removeTemplate(template.id));
|
||||||
|
|
||||||
@ -100,11 +117,11 @@ export default function FilesGroupsManagement({
|
|||||||
logger.debug('Master et clones supprimés avec succès de DocuSeal.');
|
logger.debug('Master et clones supprimés avec succès de DocuSeal.');
|
||||||
|
|
||||||
// Supprimer le template master de la base de données
|
// Supprimer le template master de la base de données
|
||||||
deleteRegistrationTemplateMaster(templateMaster.id, csrfToken)
|
deleteRegistrationSchoolFileMaster(templateMaster.id, csrfToken)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setTemplateMasters(
|
setSchoolFileMasters(
|
||||||
templateMasters.filter(
|
schoolFileMasters.filter(
|
||||||
(fichier) => fichier.id !== templateMaster.id
|
(fichier) => fichier.id !== templateMaster.id
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -175,11 +192,11 @@ export default function FilesGroupsManagement({
|
|||||||
};
|
};
|
||||||
logger.debug(data);
|
logger.debug(data);
|
||||||
|
|
||||||
createRegistrationTemplateMaster(data, csrfToken)
|
createRegistrationSchoolFileMaster(data, 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);
|
||||||
setTemplateMasters((prevFiles) => [...prevFiles, transformedFile]);
|
setSchoolFileMasters((prevFiles) => [...prevFiles, transformedFile]);
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -196,11 +213,11 @@ export default function FilesGroupsManagement({
|
|||||||
};
|
};
|
||||||
logger.debug(data);
|
logger.debug(data);
|
||||||
|
|
||||||
editRegistrationTemplateMaster(id, data, csrfToken)
|
editRegistrationSchoolFileMaster(id, data, 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);
|
||||||
setTemplateMasters((prevFichiers) =>
|
setSchoolFileMasters((prevFichiers) =>
|
||||||
prevFichiers.map((f) => (f.id === id ? transformedFile : f))
|
prevFichiers.map((f) => (f.id === id ? transformedFile : f))
|
||||||
);
|
);
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
@ -228,7 +245,13 @@ export default function FilesGroupsManagement({
|
|||||||
alert("Erreur lors de l'opération sur le groupe");
|
alert("Erreur lors de l'opération sur le groupe");
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
createRegistrationFileGroup(groupData, csrfToken)
|
// Ajouter l'établissement sélectionné lors de la création d'un nouveau groupe
|
||||||
|
const newGroupData = {
|
||||||
|
...groupData,
|
||||||
|
establishment: selectedEstablishmentId,
|
||||||
|
};
|
||||||
|
|
||||||
|
createRegistrationFileGroup(newGroupData, csrfToken)
|
||||||
.then((newGroup) => {
|
.then((newGroup) => {
|
||||||
setGroups([...groups, newGroup]);
|
setGroups([...groups, newGroup]);
|
||||||
setIsGroupModalOpen(false);
|
setIsGroupModalOpen(false);
|
||||||
@ -246,13 +269,13 @@ export default function FilesGroupsManagement({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleGroupDelete = (groupId) => {
|
const handleGroupDelete = (groupId) => {
|
||||||
// Vérifier si des templateMasters utilisent ce groupe
|
// Vérifier si des schoolFileMasters utilisent ce groupe
|
||||||
const filesInGroup = templateMasters.filter(
|
const filesInGroup = schoolFileMasters.filter(
|
||||||
(file) => file.group && file.group.id === groupId
|
(file) => file.group && file.group.id === groupId
|
||||||
);
|
);
|
||||||
if (filesInGroup.length > 0) {
|
if (filesInGroup.length > 0) {
|
||||||
alert(
|
alert(
|
||||||
"Impossible de supprimer ce groupe car il contient des templateMasters. Veuillez d'abord retirer tous les templateMasters de ce groupe."
|
"Impossible de supprimer ce groupe car il contient des schoolFileMasters. Veuillez d'abord retirer tous les schoolFileMasters de ce groupe."
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -279,7 +302,62 @@ export default function FilesGroupsManagement({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredFiles = templateMasters.filter((file) => {
|
const handleCreate = (newParentFile) => {
|
||||||
|
return createRegistrationParentFileMaster(newParentFile, csrfToken)
|
||||||
|
.then((response) => {
|
||||||
|
const createdFile = response;
|
||||||
|
// Ajouter le nouveau fichier parent à la liste existante
|
||||||
|
setParentFileMasters((prevFiles) => [...prevFiles, createdFile]);
|
||||||
|
logger.debug('Document parent créé avec succès:', createdFile);
|
||||||
|
return createdFile;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors de la création du document parent:', error);
|
||||||
|
alert(
|
||||||
|
'Une erreur est survenue lors de la création du document parent.'
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (id, updatedFile) => {
|
||||||
|
return editRegistrationParentFileMaster(id, updatedFile, csrfToken)
|
||||||
|
.then((response) => {
|
||||||
|
const modifiedFile = response.data; // Extraire les données mises à jour
|
||||||
|
// Mettre à jour la liste des fichiers parents
|
||||||
|
setParentFileMasters((prevFiles) =>
|
||||||
|
prevFiles.map((file) => (file.id === id ? modifiedFile : file))
|
||||||
|
);
|
||||||
|
logger.debug('Document parent mis à jour avec succès:', modifiedFile);
|
||||||
|
return modifiedFile; // Retourner le fichier mis à jour
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error(
|
||||||
|
'Erreur lors de la modification du document parent:',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
alert(
|
||||||
|
'Une erreur est survenue lors de la modification du document parent.'
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (id) => {
|
||||||
|
return deleteRegistrationParentFileMaster(id, csrfToken)
|
||||||
|
.then(() => {
|
||||||
|
// Mettre à jour la liste des fichiers parents en supprimant l'élément correspondant
|
||||||
|
setParentFileMasters((prevFiles) =>
|
||||||
|
prevFiles.filter((file) => file.id !== id)
|
||||||
|
);
|
||||||
|
logger.debug('Document parent supprimé avec succès:', id);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors de la suppression du fichier parent:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredFiles = schoolFileMasters.filter((file) => {
|
||||||
if (!selectedGroup) return true;
|
if (!selectedGroup) return true;
|
||||||
return (
|
return (
|
||||||
file.groups &&
|
file.groups &&
|
||||||
@ -288,9 +366,9 @@ export default function FilesGroupsManagement({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const columnsFiles = [
|
const columnsFiles = [
|
||||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
{ name: 'Nom du formulaire', transform: (row) => row.name },
|
||||||
{
|
{
|
||||||
name: 'Groupes',
|
name: "Dossiers d'inscription",
|
||||||
transform: (row) =>
|
transform: (row) =>
|
||||||
row.groups && row.groups.length > 0
|
row.groups && row.groups.length > 0
|
||||||
? row.groups.map((group) => group.name).join(', ')
|
? row.groups.map((group) => group.name).join(', ')
|
||||||
@ -327,7 +405,7 @@ export default function FilesGroupsManagement({
|
|||||||
];
|
];
|
||||||
|
|
||||||
const columnsGroups = [
|
const columnsGroups = [
|
||||||
{ name: 'Nom du groupe', transform: (row) => row.name },
|
{ name: 'Nom du dossier', transform: (row) => row.name },
|
||||||
{ name: 'Description', transform: (row) => row.description },
|
{ name: 'Description', transform: (row) => row.description },
|
||||||
{
|
{
|
||||||
name: 'Actions',
|
name: 'Actions',
|
||||||
@ -351,7 +429,8 @@ export default function FilesGroupsManagement({
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="w-full mx-auto mt-6">
|
||||||
|
{/* Modal pour les fichiers */}
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
setIsOpen={(isOpen) => {
|
setIsOpen={(isOpen) => {
|
||||||
@ -362,7 +441,7 @@ export default function FilesGroupsManagement({
|
|||||||
}}
|
}}
|
||||||
title={isEditing ? 'Modification du document' : 'Ajouter un document'}
|
title={isEditing ? 'Modification du document' : 'Ajouter un document'}
|
||||||
ContentComponent={() => (
|
ContentComponent={() => (
|
||||||
<FileUpload
|
<FileUploadDocuSeal
|
||||||
handleCreateTemplateMaster={handleCreateTemplateMaster}
|
handleCreateTemplateMaster={handleCreateTemplateMaster}
|
||||||
handleEditTemplateMaster={handleEditTemplateMaster}
|
handleEditTemplateMaster={handleEditTemplateMaster}
|
||||||
fileToEdit={fileToEdit}
|
fileToEdit={fileToEdit}
|
||||||
@ -371,13 +450,15 @@ export default function FilesGroupsManagement({
|
|||||||
)}
|
)}
|
||||||
modalClassName="w-4/5 h-4/5"
|
modalClassName="w-4/5 h-4/5"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Modal pour les groupes */}
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isGroupModalOpen}
|
isOpen={isGroupModalOpen}
|
||||||
setIsOpen={setIsGroupModalOpen}
|
setIsOpen={setIsGroupModalOpen}
|
||||||
title={
|
title={
|
||||||
groupToEdit
|
groupToEdit
|
||||||
? 'Modifier le groupe'
|
? 'Modifier le groupe'
|
||||||
: 'Ajouter un groupe de templateMasters'
|
: 'Ajouter un groupe de schoolFileMasters'
|
||||||
}
|
}
|
||||||
ContentComponent={() => (
|
ContentComponent={() => (
|
||||||
<RegistrationFileGroupForm
|
<RegistrationFileGroupForm
|
||||||
@ -386,61 +467,45 @@ export default function FilesGroupsManagement({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="mt-8 mb-4">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
{/* Section Groupes de fichiers */}
|
||||||
<h2 className="text-xl font-bold">Groupes de fichiers</h2>
|
<div className="mt-8 w-3/5">
|
||||||
<button
|
<SectionHeader
|
||||||
onClick={() => setIsGroupModalOpen(true)}
|
icon={FolderPlus}
|
||||||
className="flex items-center bg-blue-600 text-white p-2 rounded-full shadow hover:bg-blue-900 transition duration-200"
|
title="Dossiers d'inscriptions"
|
||||||
>
|
description="Gérez les dossiers d'inscription pour organiser vos documents."
|
||||||
<FolderPlus className="w-5 h-5" />
|
button={true}
|
||||||
</button>
|
buttonOpeningModal={true}
|
||||||
</div>
|
onClick={() => setIsGroupModalOpen(true)}
|
||||||
<Table
|
|
||||||
data={groups}
|
|
||||||
columns={columnsGroups}
|
|
||||||
itemsPerPage={5}
|
|
||||||
currentPage={1}
|
|
||||||
totalPages={Math.ceil(groups.length / 5)}
|
|
||||||
/>
|
/>
|
||||||
|
<Table data={groups} columns={columnsGroups} />
|
||||||
</div>
|
</div>
|
||||||
{groups.length > 0 && (
|
|
||||||
<div className="mt-8">
|
{/* Section Fichiers */}
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="mt-12 mb-4 w-3/5">
|
||||||
<h2 className="text-xl font-bold">Fichiers</h2>
|
<SectionHeader
|
||||||
<div className="flex items-center gap-4">
|
icon={Signature}
|
||||||
<select
|
title="Formulaires à remplir"
|
||||||
className="border rounded p-2"
|
description="Gérez les formulaires nécessitant une signature électronique."
|
||||||
value={selectedGroup || ''}
|
button={true}
|
||||||
onChange={(e) => setSelectedGroup(e.target.value)}
|
buttonOpeningModal={true}
|
||||||
>
|
onClick={() => {
|
||||||
<option value="">Tous les groupes</option>
|
setIsModalOpen(true);
|
||||||
{groups.map((group) => (
|
setIsEditing(false);
|
||||||
<option key={group.id} value={group.id}>
|
}}
|
||||||
{group.name}
|
/>
|
||||||
</option>
|
<Table data={filteredFiles} columns={columnsFiles} />
|
||||||
))}
|
</div>
|
||||||
</select>
|
|
||||||
<button
|
{/* Section Pièces à fournir */}
|
||||||
onClick={() => {
|
<ParentFilesSection
|
||||||
setIsModalOpen(true);
|
parentFiles={parentFiles}
|
||||||
setIsEditing(false);
|
setParentFileMasters={setParentFileMasters}
|
||||||
}}
|
groups={groups}
|
||||||
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
|
handleCreate={handleCreate}
|
||||||
>
|
handleEdit={handleEdit}
|
||||||
<Plus className="w-5 h-5" />
|
handleDelete={handleDelete}
|
||||||
</button>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Table
|
|
||||||
data={filteredFiles}
|
|
||||||
columns={columnsFiles}
|
|
||||||
itemsPerPage={10}
|
|
||||||
currentPage={1}
|
|
||||||
totalPages={Math.ceil(filteredFiles.length / 10)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,343 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Plus, Download, Edit, Trash2, FolderPlus, Signature } from 'lucide-react';
|
|
||||||
import Modal from '@/components/Modal';
|
|
||||||
import Table from '@/components/Table';
|
|
||||||
import FileUpload from '@/components/FileUpload';
|
|
||||||
import { formatDate } from '@/utils/Date';
|
|
||||||
import { BASE_URL } from '@/utils/Url';
|
|
||||||
import {
|
|
||||||
fetchRegisterFormFileTemplate,
|
|
||||||
createRegistrationFormFileTemplate,
|
|
||||||
editRegistrationFormFileTemplate,
|
|
||||||
deleteRegisterFormFileTemplate,
|
|
||||||
getRegisterFormFileTemplate
|
|
||||||
} from '@/app/actions/subscriptionAction';
|
|
||||||
import {
|
|
||||||
fetchRegistrationFileGroups,
|
|
||||||
createRegistrationFileGroup,
|
|
||||||
deleteRegistrationFileGroup,
|
|
||||||
editRegistrationFileGroup
|
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
|
||||||
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
|
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
|
||||||
|
|
||||||
export default function FilesManagement({ csrfToken }) {
|
|
||||||
const [fichiers, setFichiers] = useState([]);
|
|
||||||
const [groups, setGroups] = useState([]);
|
|
||||||
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
|
||||||
const [fileToEdit, setFileToEdit] = useState(null);
|
|
||||||
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
|
|
||||||
const [groupToEdit, setGroupToEdit] = useState(null);
|
|
||||||
|
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
|
||||||
|
|
||||||
// Fonction pour transformer les données des fichiers avec les informations complètes du groupe
|
|
||||||
const transformFileData = (file, groups) => {
|
|
||||||
if (!file.group) return file;
|
|
||||||
|
|
||||||
const groupInfo = groups.find(g => g.id === file.group);
|
|
||||||
return {
|
|
||||||
...file,
|
|
||||||
group: groupInfo || { id: file.group, name: 'Groupe inconnu' }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
Promise.all([
|
|
||||||
fetchRegisterFormFileTemplate(),
|
|
||||||
fetchRegistrationFileGroups(selectedEstablishmentId)
|
|
||||||
]).then(([filesData, groupsData]) => {
|
|
||||||
setGroups(groupsData);
|
|
||||||
// Sélectionner automatiquement le premier groupe s'il existe
|
|
||||||
if (groupsData.length > 0) {
|
|
||||||
setSelectedGroup(groupsData[0].id.toString());
|
|
||||||
}
|
|
||||||
// Transformer chaque fichier pour inclure les informations complètes du groupe
|
|
||||||
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
|
|
||||||
setFichiers(transformedFiles);
|
|
||||||
}).catch(err => {
|
|
||||||
console.log(err.message);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleFileDelete = (fileId) => {
|
|
||||||
deleteRegisterFormFileTemplate(fileId, csrfToken)
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
|
|
||||||
alert('Fichier supprimé avec succès.');
|
|
||||||
} else {
|
|
||||||
alert('Erreur lors de la suppression du fichier.');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error deleting file:', error);
|
|
||||||
alert('Erreur lors de la suppression du fichier.');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFileEdit = (file) => {
|
|
||||||
setIsEditing(true);
|
|
||||||
setFileToEdit(file);
|
|
||||||
setIsModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFileUpload = ({file, name, is_required, order, groupId}) => {
|
|
||||||
if (!name) {
|
|
||||||
alert('Veuillez entrer un nom de fichier.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
if(file) {
|
|
||||||
formData.append('file', file);
|
|
||||||
}
|
|
||||||
formData.append('name', name);
|
|
||||||
formData.append('is_required', is_required);
|
|
||||||
formData.append('order', order);
|
|
||||||
|
|
||||||
// Modification ici : vérifier si groupId existe et n'est pas vide
|
|
||||||
if (groupId && groupId !== '') {
|
|
||||||
formData.append('group', groupId); // Notez que le nom du champ est 'group' et non 'group_id'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEditing && fileToEdit) {
|
|
||||||
editRegistrationFormFileTemplate(fileToEdit.id, formData, csrfToken)
|
|
||||||
.then(data => {
|
|
||||||
// Transformer le fichier mis à jour avec les informations du groupe
|
|
||||||
const transformedFile = transformFileData(data, groups);
|
|
||||||
setFichiers(prevFichiers =>
|
|
||||||
prevFichiers.map(f => f.id === fileToEdit.id ? transformedFile : f)
|
|
||||||
);
|
|
||||||
setIsModalOpen(false);
|
|
||||||
setFileToEdit(null);
|
|
||||||
setIsEditing(false);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error editing file:', error);
|
|
||||||
alert('Erreur lors de la modification du fichier');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
createRegistrationFormFileTemplate(formData, csrfToken)
|
|
||||||
.then(data => {
|
|
||||||
// Transformer le nouveau fichier avec les informations du groupe
|
|
||||||
const transformedFile = transformFileData(data, groups);
|
|
||||||
setFichiers(prevFiles => [...prevFiles, transformedFile]);
|
|
||||||
setIsModalOpen(false);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error uploading file:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGroupSubmit = (groupData) => {
|
|
||||||
if (groupToEdit) {
|
|
||||||
editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken)
|
|
||||||
.then(updatedGroup => {
|
|
||||||
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
|
|
||||||
setGroupToEdit(null);
|
|
||||||
setIsGroupModalOpen(false);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error handling group:', error);
|
|
||||||
alert('Erreur lors de l\'opération sur le groupe');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
createRegistrationFileGroup(groupData, csrfToken)
|
|
||||||
.then(newGroup => {
|
|
||||||
setGroups([...groups, newGroup]);
|
|
||||||
setIsGroupModalOpen(false);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error handling group:', error);
|
|
||||||
alert('Erreur lors de l\'opération sur le groupe');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGroupEdit = (group) => {
|
|
||||||
setGroupToEdit(group);
|
|
||||||
setIsGroupModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGroupDelete = (groupId) => {
|
|
||||||
// Vérifier si des fichiers utilisent ce groupe
|
|
||||||
const filesInGroup = fichiers.filter(file => file.group && file.group.id === groupId);
|
|
||||||
if (filesInGroup.length > 0) {
|
|
||||||
alert('Impossible de supprimer ce groupe car il contient des fichiers. Veuillez d\'abord retirer tous les fichiers de ce groupe.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.confirm('Êtes-vous sûr de vouloir supprimer ce groupe ?')) {
|
|
||||||
deleteRegistrationFileGroup(groupId, csrfToken)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 409) {
|
|
||||||
throw new Error('Ce groupe est lié à des inscriptions existantes.');
|
|
||||||
}
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Erreur lors de la suppression du groupe.');
|
|
||||||
}
|
|
||||||
setGroups(groups.filter(group => group.id !== groupId));
|
|
||||||
alert('Groupe supprimé avec succès.');
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error deleting group:', error);
|
|
||||||
alert(error.message || 'Erreur lors de la suppression du groupe. Vérifiez qu\'aucune inscription n\'utilise ce groupe.');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ajouter cette fonction de filtrage
|
|
||||||
const filteredFiles = fichiers.filter(file => {
|
|
||||||
if (!selectedGroup) return true;
|
|
||||||
return file.group && file.group.id === parseInt(selectedGroup);
|
|
||||||
});
|
|
||||||
|
|
||||||
const columnsFiles = [
|
|
||||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
|
||||||
{ name: 'Groupe', transform: (row) => row.group ? row.group.name : 'Aucun' },
|
|
||||||
{ name: 'Date de création', transform: (row) => formatDate(new Date (row.date_added),"DD/MM/YYYY hh:mm:ss") },
|
|
||||||
{ name: 'Fichier Obligatoire', transform: (row) => row.is_required ? 'Oui' : 'Non' },
|
|
||||||
{ name: 'Ordre de fusion', transform: (row) => row.order },
|
|
||||||
{ name: 'Actions', transform: (row) => (
|
|
||||||
<div className="flex items-center justify-center gap-2">
|
|
||||||
{row.file && (
|
|
||||||
<a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
|
|
||||||
<Download size={16} />
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
<button onClick={() => handleFileEdit(row)} className="text-blue-500 hover:text-blue-700">
|
|
||||||
<Edit size={16} />
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
|
|
||||||
<Trash2 size={16} />
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleSignatureRequest(row)} className="text-green-500 hover:text-green-700">
|
|
||||||
<Signature size={16} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
];
|
|
||||||
|
|
||||||
const columnsGroups = [
|
|
||||||
{ name: 'Nom du groupe', transform: (row) => row.name },
|
|
||||||
{ name: 'Description', transform: (row) => row.description },
|
|
||||||
{ name: 'Actions', transform: (row) => (
|
|
||||||
<div className="flex items-center justify-center gap-2">
|
|
||||||
<button onClick={() => handleGroupEdit(row)} className="text-blue-500 hover:text-blue-700">
|
|
||||||
<Edit size={16} />
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleGroupDelete(row.id)} className="text-red-500 hover:text-red-700">
|
|
||||||
<Trash2 size={16} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Fonction pour gérer la demande de signature
|
|
||||||
const handleSignatureRequest = (file) => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', file);
|
|
||||||
console.log('Demande de signature pour le fichier :', file);
|
|
||||||
|
|
||||||
fetch('http://localhost:8080:/DocuSeal/generateToken', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer NFPZy6BBGvYs1BwTuXMQ3XAu5N1kLFiXWftGQhkiz2A',
|
|
||||||
},
|
|
||||||
body: formData,
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Erreur lors du téléversement du document : ' + response.statusText);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
const documentId = data.documentId;
|
|
||||||
console.log('Document téléversé avec succès, ID :', documentId);
|
|
||||||
onUpload(documentId);
|
|
||||||
});
|
|
||||||
.catch((error) => console.error(error));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Modal
|
|
||||||
isOpen={isModalOpen}
|
|
||||||
setIsOpen={setIsModalOpen}
|
|
||||||
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
|
|
||||||
ContentComponent={() => (
|
|
||||||
<FileUpload
|
|
||||||
onFileUpload={handleFileUpload}
|
|
||||||
fileToEdit={fileToEdit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Modal
|
|
||||||
isOpen={isGroupModalOpen}
|
|
||||||
setIsOpen={setIsGroupModalOpen}
|
|
||||||
title={groupToEdit ? "Modifier le groupe" : "Ajouter un groupe de fichiers"}
|
|
||||||
ContentComponent={() => (
|
|
||||||
<RegistrationFileGroupForm
|
|
||||||
onSubmit={handleGroupSubmit}
|
|
||||||
initialData={groupToEdit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="mt-8 mb-4">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h2 className="text-xl font-bold">Groupes de fichiers</h2>
|
|
||||||
<button
|
|
||||||
onClick={() => setIsGroupModalOpen(true)}
|
|
||||||
className="flex items-center bg-blue-600 text-white p-2 rounded-full shadow hover:bg-blue-900 transition duration-200"
|
|
||||||
>
|
|
||||||
<FolderPlus className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Table
|
|
||||||
data={groups}
|
|
||||||
columns={columnsGroups}
|
|
||||||
itemsPerPage={5}
|
|
||||||
currentPage={1}
|
|
||||||
totalPages={Math.ceil(groups.length / 5)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{groups.length > 0 && (
|
|
||||||
<div className="mt-8">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h2 className="text-xl font-bold">Fichiers</h2>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<select
|
|
||||||
className="border rounded p-2"
|
|
||||||
value={selectedGroup || ''}
|
|
||||||
onChange={(e) => setSelectedGroup(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="">Tous les groupes</option>
|
|
||||||
{groups.map(group => (
|
|
||||||
<option key={group.id} value={group.id}>{group.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
|
|
||||||
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
|
|
||||||
>
|
|
||||||
<Plus className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Table
|
|
||||||
data={filteredFiles}
|
|
||||||
columns={columnsFiles}
|
|
||||||
itemsPerPage={10}
|
|
||||||
currentPage={1}
|
|
||||||
totalPages={Math.ceil(filteredFiles.length / 10)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
273
Front-End/src/components/Structure/Files/ParentFilesSection.js
Normal file
273
Front-End/src/components/Structure/Files/ParentFilesSection.js
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Plus, Edit3, Trash2, Check, X, FileText } from 'lucide-react';
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import InputText from '@/components/InputText';
|
||||||
|
import MultiSelect from '@/components/MultiSelect';
|
||||||
|
import Popup from '@/components/Popup';
|
||||||
|
import logger from '@/utils/logger';
|
||||||
|
import { createRegistrationParentFileTemplate } from '@/app/actions/registerFileGroupAction';
|
||||||
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
|
||||||
|
export default function ParentFilesSection({ parentFiles, groups, handleCreate, handleEdit, handleDelete }) {
|
||||||
|
const [editingDocumentId, setEditingDocumentId] = useState(null);
|
||||||
|
const [formData, setFormData] = useState(null);
|
||||||
|
const [selectedGroups, setSelectedGroups] = useState([]); // Gestion des groupes sélectionnés
|
||||||
|
|
||||||
|
const [guardianDetails, setGuardianDetails] = useState([]);
|
||||||
|
|
||||||
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
|
const [popupMessage, setPopupMessage] = useState("");
|
||||||
|
const [removePopupVisible, setRemovePopupVisible] = useState(false);
|
||||||
|
const [removePopupMessage, setRemovePopupMessage] = useState("");
|
||||||
|
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
||||||
|
|
||||||
|
const csrfToken = useCsrfToken();
|
||||||
|
|
||||||
|
const handleAddEmptyRequiredDocument = () => {
|
||||||
|
setEditingDocumentId('new');
|
||||||
|
setFormData({ name: '', description: '', groups: [] });
|
||||||
|
setSelectedGroups([]); // Réinitialiser les groupes sélectionnés
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditDocument = (document) => {
|
||||||
|
setEditingDocumentId(document.id);
|
||||||
|
setFormData(document);
|
||||||
|
const initialSelectedGroups = document.groups.map((groupId) =>
|
||||||
|
groups.find((group) => group.id === groupId)
|
||||||
|
);
|
||||||
|
setSelectedGroups(initialSelectedGroups);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveDocument = () => {
|
||||||
|
if (!formData.name) {
|
||||||
|
alert('Le nom de la pièce est requis.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedFormData = {
|
||||||
|
...formData,
|
||||||
|
groups: selectedGroups.map((group) => group.id),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (editingDocumentId === 'new') {
|
||||||
|
handleCreate(updatedFormData).then((createdDocument) => {
|
||||||
|
setEditingDocumentId(null);
|
||||||
|
setFormData(null);
|
||||||
|
setSelectedGroups([]);
|
||||||
|
|
||||||
|
guardianDetails.forEach((guardian, index) => {
|
||||||
|
// Création des templates
|
||||||
|
const data = {
|
||||||
|
master: createdDocument?.id,
|
||||||
|
registration_form: guardian.registration_form
|
||||||
|
};
|
||||||
|
console.log(guardian)
|
||||||
|
createRegistrationParentFileTemplate(data, csrfToken)
|
||||||
|
.then(response => {
|
||||||
|
logger.debug('Template enregistré avec succès:', response);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
logger.error('Erreur lors de l\'enregistrement du template:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleEdit(editingDocumentId, updatedFormData).then(() => {
|
||||||
|
setEditingDocumentId(null);
|
||||||
|
setFormData(null);
|
||||||
|
setSelectedGroups([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveDocument = (id) => {
|
||||||
|
return handleDelete(id)
|
||||||
|
.then(() => {
|
||||||
|
setEditingDocumentId(null);
|
||||||
|
setFormData(null);
|
||||||
|
setSelectedGroups([]);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelEdit = () => {
|
||||||
|
setEditingDocumentId(null);
|
||||||
|
setFormData(null);
|
||||||
|
setSelectedGroups([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGroupChange = (selected) => {
|
||||||
|
setSelectedGroups(selected);
|
||||||
|
console.log('selected : ', selected)
|
||||||
|
|
||||||
|
// Extraire les guardians associés aux register_forms des groupes sélectionnés
|
||||||
|
const details = selected.flatMap(group =>
|
||||||
|
group.registration_forms.flatMap(form =>
|
||||||
|
form.guardians.map(guardian => ({
|
||||||
|
email: guardian.associated_profile_email,
|
||||||
|
last_name: form.last_name, // Extraire depuis form
|
||||||
|
first_name: form.first_name, // Extraire depuis form
|
||||||
|
registration_form: form.student_id // Utiliser student_id comme ID du register_form
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Guardians associés : ", details);
|
||||||
|
setGuardianDetails(details); // Mettre à jour la variable d'état avec les détails des guardians
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRequiredDocumentCell = (document, column) => {
|
||||||
|
const isEditing = editingDocumentId === document.id || (editingDocumentId === 'new' && !document.id);
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
switch (column) {
|
||||||
|
case 'Nom de la pièce':
|
||||||
|
return (
|
||||||
|
<InputText
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
placeholder="Nom de la pièce"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'Description':
|
||||||
|
return (
|
||||||
|
<InputText
|
||||||
|
name="description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
|
placeholder="Description"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'Dossiers d\'inscription':
|
||||||
|
return (
|
||||||
|
<MultiSelect
|
||||||
|
name="groups"
|
||||||
|
label="Sélection de groupes de fichiers"
|
||||||
|
options={groups}
|
||||||
|
selectedOptions={selectedGroups}
|
||||||
|
onChange={handleGroupChange}
|
||||||
|
errorMsg={null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'Actions':
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center space-x-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleSaveDocument}
|
||||||
|
className="text-green-500 hover:text-green-700"
|
||||||
|
>
|
||||||
|
<Check className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleCancelEdit}
|
||||||
|
className="text-red-500 hover:text-red-700"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (column) {
|
||||||
|
case 'Nom de la pièce':
|
||||||
|
return <span>{document.name}</span>;
|
||||||
|
case 'Description':
|
||||||
|
return <span>{document.description}</span>;
|
||||||
|
case 'Dossiers d\'inscription':
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{document.groups
|
||||||
|
.map((groupId) => groups.find((group) => group.id === groupId)?.name || 'Dossiers d\'inscription inconnu')
|
||||||
|
.join(', ')}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case 'Actions':
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center space-x-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleEditDocument(document)}
|
||||||
|
className="text-blue-500 hover:text-blue-700"
|
||||||
|
>
|
||||||
|
<Edit3 className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setRemovePopupVisible(true);
|
||||||
|
setRemovePopupMessage(
|
||||||
|
`Attentions ! \nVous êtes sur le point de supprimer le document "${document.name}".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?`
|
||||||
|
);
|
||||||
|
setRemovePopupOnConfirm(() => () => {
|
||||||
|
handleRemoveDocument(document.id)
|
||||||
|
.then(() => {
|
||||||
|
setPopupMessage(`Le document "${document.name}" a été correctement supprimé.`);
|
||||||
|
setPopupVisible(true);
|
||||||
|
setRemovePopupVisible(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors de la suppression du document:', error);
|
||||||
|
setPopupMessage(`Erreur lors de la suppression du document "${document.name}".`);
|
||||||
|
setPopupVisible(true);
|
||||||
|
setRemovePopupVisible(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="text-red-500 hover:text-red-700"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columnsRequiredDocuments = [
|
||||||
|
{ name: 'Nom de la pièce', transform: (row) => renderRequiredDocumentCell(row, 'Nom de la pièce') },
|
||||||
|
{ name: 'Description', transform: (row) => renderRequiredDocumentCell(row, 'Description') },
|
||||||
|
{ name: 'Dossiers d\'inscription', transform: (row) => renderRequiredDocumentCell(row, 'Dossiers d\'inscription') },
|
||||||
|
{ name: 'Actions', transform: (row) => renderRequiredDocumentCell(row, 'Actions') },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-12 w-4/5">
|
||||||
|
<SectionHeader
|
||||||
|
icon={FileText}
|
||||||
|
title="Pièces à fournir"
|
||||||
|
description="Configurez la liste des documents que les parents doivent fournir."
|
||||||
|
button={true}
|
||||||
|
onClick={handleAddEmptyRequiredDocument}
|
||||||
|
/>
|
||||||
|
<Table
|
||||||
|
data={editingDocumentId === 'new' ? [formData, ...parentFiles] : parentFiles}
|
||||||
|
columns={columnsRequiredDocuments}
|
||||||
|
/>
|
||||||
|
<Popup
|
||||||
|
visible={popupVisible}
|
||||||
|
message={popupMessage}
|
||||||
|
onConfirm={() => setPopupVisible(false)}
|
||||||
|
onCancel={() => setPopupVisible(false)}
|
||||||
|
uniqueConfirmButton={true}
|
||||||
|
/>
|
||||||
|
<Popup
|
||||||
|
visible={removePopupVisible}
|
||||||
|
message={removePopupMessage}
|
||||||
|
onConfirm={removePopupOnConfirm}
|
||||||
|
onCancel={() => setRemovePopupVisible(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,20 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import { Trash2, Edit3, Check, X, Percent, EuroIcon, Tag } from 'lucide-react';
|
||||||
Plus,
|
|
||||||
Trash2,
|
|
||||||
Edit3,
|
|
||||||
Check,
|
|
||||||
X,
|
|
||||||
Percent,
|
|
||||||
EuroIcon,
|
|
||||||
Tag,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { ESTABLISHMENT_ID } from '@/utils/Url';
|
import { ESTABLISHMENT_ID } from '@/utils/Url';
|
||||||
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
|
||||||
const DiscountsSection = ({
|
const DiscountsSection = ({
|
||||||
discounts,
|
discounts,
|
||||||
@ -347,22 +339,15 @@ const DiscountsSection = ({
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 mt-8">
|
||||||
{!subscriptionMode && (
|
<SectionHeader
|
||||||
<div className="flex justify-between items-center">
|
icon={Tag}
|
||||||
<div className="flex items-center mb-4">
|
discountStyle={true}
|
||||||
<Tag className="w-6 h-6 text-emerald-500 mr-2" />
|
title={`${type == 0 ? "Liste des réductions sur les frais d'inscription" : 'Liste des réductions sur les frais de scolarité'}`}
|
||||||
<h2 className="text-xl font-semibold">Liste des réductions</h2>
|
description={`${subscriptionMode ? 'Sélectionnez' : 'Gérez'} ${type == 0 ? " vos réductions sur les frais d'inscription" : ' vos réductions sur les frais de scolarité'}`}
|
||||||
</div>
|
button={!subscriptionMode}
|
||||||
<button
|
onClick={handleAddDiscount}
|
||||||
type="button"
|
/>
|
||||||
onClick={handleAddDiscount}
|
|
||||||
className="text-emerald-500 hover:text-emerald-700"
|
|
||||||
>
|
|
||||||
<Plus className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Table
|
<Table
|
||||||
data={newDiscount ? [newDiscount, ...discounts] : discounts}
|
data={newDiscount ? [newDiscount, ...discounts] : discounts}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
||||||
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
|
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
|
||||||
import PaymentPlanSelector from '@/components/PaymentPlanSelector';
|
import PaymentPlanSelector from '@/components/PaymentPlanSelector';
|
||||||
@ -50,185 +50,181 @@ const FeesManagement = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full mx-auto p-2 mt-6 space-y-6">
|
<div className="w-full mx-auto mt-6">
|
||||||
<div className="bg-white p-2 rounded-lg shadow-md">
|
<div className="w-4/5 mx-auto flex items-center mt-8">
|
||||||
<h2 className="text-2xl font-semibold mb-4">
|
<hr className="flex-grow border-t-2 border-gray-300" />
|
||||||
Frais d'inscription
|
<span className="mx-4 text-gray-600 font-semibold">
|
||||||
</h2>
|
Frais d'inscription
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
</span>
|
||||||
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
|
<hr className="flex-grow border-t-2 border-gray-300" />
|
||||||
<FeesSection
|
</div>
|
||||||
fees={registrationFees}
|
|
||||||
setFees={setRegistrationFees}
|
<div className="mt-8 w-4/5">
|
||||||
discounts={registrationDiscounts}
|
<FeesSection
|
||||||
handleCreate={(newData) =>
|
fees={registrationFees}
|
||||||
handleCreate(
|
setFees={setRegistrationFees}
|
||||||
`${BE_SCHOOL_FEES_URL}`,
|
discounts={registrationDiscounts}
|
||||||
newData,
|
handleCreate={(newData) =>
|
||||||
setRegistrationFees
|
handleCreate(`${BE_SCHOOL_FEES_URL}`, newData, setRegistrationFees)
|
||||||
)
|
}
|
||||||
}
|
handleEdit={(id, updatedData) =>
|
||||||
handleEdit={(id, updatedData) =>
|
handleEdit(
|
||||||
handleEdit(
|
`${BE_SCHOOL_FEES_URL}`,
|
||||||
`${BE_SCHOOL_FEES_URL}`,
|
id,
|
||||||
id,
|
updatedData,
|
||||||
updatedData,
|
setRegistrationFees
|
||||||
setRegistrationFees
|
)
|
||||||
)
|
}
|
||||||
}
|
handleDelete={(id) =>
|
||||||
handleDelete={(id) =>
|
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setRegistrationFees)
|
||||||
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setRegistrationFees)
|
}
|
||||||
}
|
type={0}
|
||||||
type={0}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div className="mt-12 w-4/5">
|
||||||
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
|
<DiscountsSection
|
||||||
<DiscountsSection
|
discounts={registrationDiscounts}
|
||||||
discounts={registrationDiscounts}
|
setDiscounts={setRegistrationDiscounts}
|
||||||
setDiscounts={setRegistrationDiscounts}
|
handleCreate={(newData) =>
|
||||||
handleCreate={(newData) =>
|
handleCreate(
|
||||||
handleCreate(
|
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
newData,
|
||||||
newData,
|
setRegistrationDiscounts
|
||||||
setRegistrationDiscounts
|
)
|
||||||
)
|
}
|
||||||
}
|
handleEdit={(id, updatedData) =>
|
||||||
handleEdit={(id, updatedData) =>
|
handleEdit(
|
||||||
handleEdit(
|
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
id,
|
||||||
id,
|
updatedData,
|
||||||
updatedData,
|
setRegistrationDiscounts
|
||||||
setRegistrationDiscounts
|
)
|
||||||
)
|
}
|
||||||
}
|
handleDelete={(id) =>
|
||||||
handleDelete={(id) =>
|
handleDelete(
|
||||||
handleDelete(
|
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
id,
|
||||||
id,
|
setRegistrationDiscounts
|
||||||
setRegistrationDiscounts
|
)
|
||||||
)
|
}
|
||||||
}
|
onDiscountDelete={(id) => handleDiscountDelete(id, 0)}
|
||||||
onDiscountDelete={(id) => handleDiscountDelete(id, 0)}
|
type={0}
|
||||||
type={0}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div className="mt-12 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
|
<div className="col-span-1 mt-4">
|
||||||
<PaymentPlanSelector
|
<PaymentPlanSelector
|
||||||
paymentPlans={registrationPaymentPlans}
|
paymentPlans={registrationPaymentPlans}
|
||||||
setPaymentPlans={setRegistrationPaymentPlans}
|
setPaymentPlans={setRegistrationPaymentPlans}
|
||||||
handleEdit={(id, updatedData) =>
|
handleEdit={(id, updatedData) =>
|
||||||
handleEdit(
|
handleEdit(
|
||||||
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
|
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
|
||||||
id,
|
id,
|
||||||
updatedData,
|
updatedData,
|
||||||
setRegistrationPaymentPlans
|
setRegistrationPaymentPlans
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
type={0}
|
type={0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
|
<div className="col-span-1 mt-4">
|
||||||
<PaymentModeSelector
|
<PaymentModeSelector
|
||||||
paymentModes={registrationPaymentModes}
|
paymentModes={registrationPaymentModes}
|
||||||
setPaymentModes={setRegistrationPaymentModes}
|
setPaymentModes={setRegistrationPaymentModes}
|
||||||
handleEdit={(id, updatedData) =>
|
handleEdit={(id, updatedData) =>
|
||||||
handleEdit(
|
handleEdit(
|
||||||
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
|
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
|
||||||
id,
|
id,
|
||||||
updatedData,
|
updatedData,
|
||||||
setRegistrationPaymentModes
|
setRegistrationPaymentModes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
type={0}
|
type={0}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-2 rounded-lg shadow-md">
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">Frais de scolarité</h2>
|
<div className="w-4/5 mx-auto flex items-center mt-16">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<hr className="flex-grow border-t-2 border-gray-300" />
|
||||||
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
|
<span className="mx-4 text-gray-600 font-semibold">
|
||||||
<FeesSection
|
Frais de scolarité
|
||||||
fees={tuitionFees}
|
</span>
|
||||||
setFees={setTuitionFees}
|
<hr className="flex-grow border-t-2 border-gray-300" />
|
||||||
discounts={tuitionDiscounts}
|
</div>
|
||||||
handleCreate={(newData) =>
|
|
||||||
handleCreate(`${BE_SCHOOL_FEES_URL}`, newData, setTuitionFees)
|
<div className="mt-8 w-4/5">
|
||||||
}
|
<FeesSection
|
||||||
handleEdit={(id, updatedData) =>
|
fees={tuitionFees}
|
||||||
handleEdit(
|
setFees={setTuitionFees}
|
||||||
`${BE_SCHOOL_FEES_URL}`,
|
discounts={tuitionDiscounts}
|
||||||
id,
|
handleCreate={(newData) =>
|
||||||
updatedData,
|
handleCreate(`${BE_SCHOOL_FEES_URL}`, newData, setTuitionFees)
|
||||||
setTuitionFees
|
}
|
||||||
)
|
handleEdit={(id, updatedData) =>
|
||||||
}
|
handleEdit(`${BE_SCHOOL_FEES_URL}`, id, updatedData, setTuitionFees)
|
||||||
handleDelete={(id) =>
|
}
|
||||||
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setTuitionFees)
|
handleDelete={(id) =>
|
||||||
}
|
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setTuitionFees)
|
||||||
type={1}
|
}
|
||||||
/>
|
type={1}
|
||||||
</div>
|
/>
|
||||||
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
|
</div>
|
||||||
<DiscountsSection
|
<div className="mt-12 w-4/5">
|
||||||
discounts={tuitionDiscounts}
|
<DiscountsSection
|
||||||
setDiscounts={setTuitionDiscounts}
|
discounts={tuitionDiscounts}
|
||||||
handleCreate={(newData) =>
|
setDiscounts={setTuitionDiscounts}
|
||||||
handleCreate(
|
handleCreate={(newData) =>
|
||||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
handleCreate(
|
||||||
newData,
|
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||||
setTuitionDiscounts
|
newData,
|
||||||
)
|
setTuitionDiscounts
|
||||||
}
|
)
|
||||||
handleEdit={(id, updatedData) =>
|
}
|
||||||
handleEdit(
|
handleEdit={(id, updatedData) =>
|
||||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
handleEdit(
|
||||||
id,
|
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||||
updatedData,
|
id,
|
||||||
setTuitionDiscounts
|
updatedData,
|
||||||
)
|
setTuitionDiscounts
|
||||||
}
|
)
|
||||||
handleDelete={(id) =>
|
}
|
||||||
handleDelete(
|
handleDelete={(id) =>
|
||||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
handleDelete(`${BE_SCHOOL_DISCOUNTS_URL}`, id, setTuitionDiscounts)
|
||||||
id,
|
}
|
||||||
setTuitionDiscounts
|
onDiscountDelete={(id) => handleDiscountDelete(id, 1)}
|
||||||
)
|
type={1}
|
||||||
}
|
/>
|
||||||
onDiscountDelete={(id) => handleDiscountDelete(id, 1)}
|
</div>
|
||||||
type={1}
|
<div className="mt-12 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
/>
|
<div className="col-span-1 mt-4">
|
||||||
</div>
|
<PaymentPlanSelector
|
||||||
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
|
paymentPlans={tuitionPaymentPlans}
|
||||||
<PaymentPlanSelector
|
setPaymentPlans={setTuitionPaymentPlans}
|
||||||
paymentPlans={tuitionPaymentPlans}
|
handleEdit={(id, updatedData) =>
|
||||||
setPaymentPlans={setTuitionPaymentPlans}
|
handleEdit(
|
||||||
handleEdit={(id, updatedData) =>
|
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
|
||||||
handleEdit(
|
id,
|
||||||
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
|
updatedData,
|
||||||
id,
|
setRegistrationPaymentPlans
|
||||||
updatedData,
|
)
|
||||||
setRegistrationPaymentPlans
|
}
|
||||||
)
|
type={1}
|
||||||
}
|
/>
|
||||||
type={1}
|
</div>
|
||||||
/>
|
<div className="col-span-1 mt-4">
|
||||||
</div>
|
<PaymentModeSelector
|
||||||
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
|
paymentModes={tuitionPaymentModes}
|
||||||
<PaymentModeSelector
|
setPaymentModes={setTuitionPaymentModes}
|
||||||
paymentModes={tuitionPaymentModes}
|
handleEdit={(id, updatedData) =>
|
||||||
setPaymentModes={setTuitionPaymentModes}
|
handleEdit(
|
||||||
handleEdit={(id, updatedData) =>
|
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
|
||||||
handleEdit(
|
id,
|
||||||
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
|
updatedData,
|
||||||
id,
|
setTuitionPaymentModes
|
||||||
updatedData,
|
)
|
||||||
setTuitionPaymentModes
|
}
|
||||||
)
|
type={1}
|
||||||
}
|
/>
|
||||||
type={1}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,20 +1,11 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import { Trash2, Edit3, Check, X, EyeOff, Eye, CreditCard } from 'lucide-react';
|
||||||
Plus,
|
|
||||||
Trash2,
|
|
||||||
Edit3,
|
|
||||||
Check,
|
|
||||||
X,
|
|
||||||
EyeOff,
|
|
||||||
Eye,
|
|
||||||
CreditCard,
|
|
||||||
BookOpen,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
|
||||||
import { ESTABLISHMENT_ID } from '@/utils/Url';
|
import { ESTABLISHMENT_ID } from '@/utils/Url';
|
||||||
|
|
||||||
@ -325,21 +316,13 @@ const FeesSection = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{!subscriptionMode && (
|
<SectionHeader
|
||||||
<div className="flex justify-between items-center">
|
icon={CreditCard}
|
||||||
<div className="flex items-center mb-4">
|
title={`${type == 0 ? "Liste des frais d'inscription" : 'Liste des frais de scolarité'}`}
|
||||||
<CreditCard className="w-6 h-6 text-emerald-500 mr-2" />
|
description={`${subscriptionMode ? 'Sélectionnez' : 'Gérez'} ${type == 0 ? " vos frais d'inscription" : ' vos frais de scolarité'}`}
|
||||||
<h2 className="text-xl font-semibold">Liste des frais</h2>
|
button={!subscriptionMode}
|
||||||
</div>
|
onClick={handleAddFee}
|
||||||
<button
|
/>
|
||||||
type="button"
|
|
||||||
onClick={handleAddFee}
|
|
||||||
className="text-emerald-500 hover:text-emerald-700"
|
|
||||||
>
|
|
||||||
<Plus className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Table
|
<Table
|
||||||
data={newFee ? [newFee, ...fees] : fees}
|
data={newFee ? [newFee, ...fees] : fees}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Pagination from '@/components/Pagination'; // Correction du chemin d'importatio,
|
import Pagination from '@/components/Pagination'; // Correction du chemin d'importation
|
||||||
|
|
||||||
const Table = ({
|
const Table = ({
|
||||||
data,
|
data,
|
||||||
@ -43,7 +43,16 @@ const Table = ({
|
|||||||
${selectedRows?.includes(row.id) ? 'bg-emerald-300 text-white' : rowIndex % 2 === 0 ? `${defaultTheme}` : ''}
|
${selectedRows?.includes(row.id) ? 'bg-emerald-300 text-white' : rowIndex % 2 === 0 ? `${defaultTheme}` : ''}
|
||||||
${isSelectable ? 'hover:bg-emerald-200' : ''}
|
${isSelectable ? 'hover:bg-emerald-200' : ''}
|
||||||
`}
|
`}
|
||||||
onClick={() => isSelectable && onRowClick && onRowClick(row)}
|
onClick={() => {
|
||||||
|
if (isSelectable && onRowClick) {
|
||||||
|
// Si la ligne est déjà sélectionnée, transmettre une indication explicite de désélection
|
||||||
|
if (selectedRows?.includes(row.id)) {
|
||||||
|
onRowClick({ deselected: true, row }); // Désélectionner
|
||||||
|
} else {
|
||||||
|
onRowClick(row); // Sélectionner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{columns.map((column, colIndex) => (
|
{columns.map((column, colIndex) => (
|
||||||
<td
|
<td
|
||||||
@ -83,6 +92,10 @@ Table.propTypes = {
|
|||||||
currentPage: PropTypes.number.isRequired,
|
currentPage: PropTypes.number.isRequired,
|
||||||
totalPages: PropTypes.number.isRequired,
|
totalPages: PropTypes.number.isRequired,
|
||||||
onPageChange: PropTypes.func.isRequired,
|
onPageChange: PropTypes.func.isRequired,
|
||||||
|
onRowClick: PropTypes.func,
|
||||||
|
selectedRows: PropTypes.arrayOf(PropTypes.any),
|
||||||
|
isSelectable: PropTypes.bool,
|
||||||
|
defaultTheme: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Table;
|
export default Table;
|
||||||
|
|||||||
@ -25,9 +25,11 @@ 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_REGISTRATION_TEMPLATE_MASTER_URL = `${BASE_URL}/Subscriptions/registrationTemplateMasters`;
|
|
||||||
export const BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationTemplates`;
|
|
||||||
export const BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`;
|
export const BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`;
|
||||||
|
export const BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL = `${BASE_URL}/Subscriptions/registrationSchoolFileMasters`;
|
||||||
|
export const BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationSchoolFileTemplates`;
|
||||||
|
export const BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL = `${BASE_URL}/Subscriptions/registrationParentFileMasters`;
|
||||||
|
export const BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationParentFileTemplates`;
|
||||||
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`;
|
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`;
|
||||||
|
|
||||||
//GESTION ECOLE
|
//GESTION ECOLE
|
||||||
|
|||||||
Reference in New Issue
Block a user