diff --git a/Back-End/N3wtSchool/mailManager.py b/Back-End/N3wtSchool/mailManager.py
index e3eacbe..be5c2db 100644
--- a/Back-End/N3wtSchool/mailManager.py
+++ b/Back-End/N3wtSchool/mailManager.py
@@ -225,4 +225,72 @@ def sendRegisterTeacher(recipients, establishment_id):
sendMail(subject=subject, message=html_message, recipients=recipients, connection=connection)
except Exception as e:
errorMessage = str(e)
+ return errorMessage
+
+
+def sendRefusDossier(recipients, establishment_id, student_name, notes):
+ """
+ Envoie un email au parent pour l'informer que son dossier d'inscription
+ nécessite des corrections.
+
+ Args:
+ recipients: Email du destinataire (parent)
+ establishment_id: ID de l'établissement
+ student_name: Nom complet de l'élève
+ notes: Motifs du refus (contenu du champ notes du RegistrationForm)
+
+ Returns:
+ str: Message d'erreur si échec, chaîne vide sinon
+ """
+ errorMessage = ''
+ try:
+ EMAIL_REFUS_SUBJECT = '[N3WT-SCHOOL] Dossier d\'inscription - Corrections requises'
+ context = {
+ 'BASE_URL': settings.BASE_URL,
+ 'URL_DJANGO': settings.URL_DJANGO,
+ 'student_name': student_name,
+ 'notes': notes
+ }
+ connection = getConnection(establishment_id)
+ subject = EMAIL_REFUS_SUBJECT
+ html_message = render_to_string('emails/refus_dossier.html', context)
+ sendMail(subject=subject, message=html_message, recipients=recipients, connection=connection)
+ logger.info(f"Email de refus envoyé à {recipients} pour l'élève {student_name}")
+ except Exception as e:
+ errorMessage = str(e)
+ logger.error(f"Erreur lors de l'envoi de l'email de refus: {errorMessage}")
+ return errorMessage
+
+
+def sendValidationDossier(recipients, establishment_id, student_name, class_name=None):
+ """
+ Envoie un email au parent pour l'informer que le dossier d'inscription
+ a été validé.
+
+ Args:
+ recipients: Email du destinataire (parent)
+ establishment_id: ID de l'établissement
+ student_name: Nom complet de l'élève
+ class_name: Nom de la classe attribuée (optionnel)
+
+ Returns:
+ str: Message d'erreur si échec, chaîne vide sinon
+ """
+ errorMessage = ''
+ try:
+ EMAIL_VALIDATION_SUBJECT = '[N3WT-SCHOOL] Dossier d\'inscription validé'
+ context = {
+ 'BASE_URL': settings.BASE_URL,
+ 'URL_DJANGO': settings.URL_DJANGO,
+ 'student_name': student_name,
+ 'class_name': class_name
+ }
+ connection = getConnection(establishment_id)
+ subject = EMAIL_VALIDATION_SUBJECT
+ html_message = render_to_string('emails/validation_dossier.html', context)
+ sendMail(subject=subject, message=html_message, recipients=recipients, connection=connection)
+ logger.info(f"Email de validation envoyé à {recipients} pour l'élève {student_name}")
+ except Exception as e:
+ errorMessage = str(e)
+ logger.error(f"Erreur lors de l'envoi de l'email de validation: {errorMessage}")
return errorMessage
\ No newline at end of file
diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py
index 1cf7897..a54767d 100644
--- a/Back-End/Subscriptions/models.py
+++ b/Back-End/Subscriptions/models.py
@@ -214,9 +214,28 @@ class RegistrationFileGroup(models.Model):
def __str__(self):
return f'{self.group.name} - {self.id}'
-def registration_file_path(instance, filename):
- # Génère le chemin : registration_files/dossier_rf_{student_id}/filename
- return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
+def registration_form_file_upload_to(instance, filename):
+ """
+ Génère le chemin de stockage pour les fichiers du dossier d'inscription.
+ Structure : Etablissement/dossier_NomEleve_PrenomEleve/filename
+ """
+ est_name = instance.establishment.name if instance.establishment else "unknown_establishment"
+ student_last = instance.student.last_name if instance.student else "unknown"
+ student_first = instance.student.first_name if instance.student else "unknown"
+ return f"{est_name}/dossier_{student_last}_{student_first}/{filename}"
+
+def _delete_file_if_exists(file_field):
+ """
+ Supprime le fichier physique s'il existe.
+ Utile pour éviter les suffixes automatiques Django lors du remplacement.
+ """
+ if file_field and file_field.name:
+ try:
+ if hasattr(file_field, 'path') and os.path.exists(file_field.path):
+ os.remove(file_field.path)
+ logger.debug(f"Fichier supprimé: {file_field.path}")
+ except Exception as e:
+ logger.error(f"Erreur lors de la suppression du fichier {file_field.name}: {e}")
class RegistrationForm(models.Model):
class RegistrationFormStatus(models.IntegerChoices):
@@ -238,17 +257,17 @@ class RegistrationForm(models.Model):
notes = models.CharField(max_length=200, blank=True)
registration_link_code = models.CharField(max_length=200, default="", blank=True)
registration_file = models.FileField(
- upload_to=registration_file_path,
+ upload_to=registration_form_file_upload_to,
null=True,
blank=True
)
sepa_file = models.FileField(
- upload_to=registration_file_path,
+ upload_to=registration_form_file_upload_to,
null=True,
blank=True
)
fusion_file = models.FileField(
- upload_to=registration_file_path,
+ upload_to=registration_form_file_upload_to,
null=True,
blank=True
)
@@ -285,13 +304,23 @@ class RegistrationForm(models.Model):
except RegistrationForm.DoesNotExist:
old_fileGroup = None
- # Vérifier si un fichier existant doit être remplacé
+ # Supprimer les anciens fichiers si remplacés (évite les suffixes Django)
if self.pk: # Si l'objet existe déjà dans la base de données
try:
old_instance = RegistrationForm.objects.get(pk=self.pk)
+
+ # Gestion du sepa_file
if old_instance.sepa_file and old_instance.sepa_file != self.sepa_file:
- # Supprimer l'ancien fichier
- old_instance.sepa_file.delete(save=False)
+ _delete_file_if_exists(old_instance.sepa_file)
+
+ # Gestion du registration_file
+ if old_instance.registration_file and old_instance.registration_file != self.registration_file:
+ _delete_file_if_exists(old_instance.registration_file)
+
+ # Gestion du fusion_file
+ if old_instance.fusion_file and old_instance.fusion_file != self.fusion_file:
+ _delete_file_if_exists(old_instance.fusion_file)
+
except RegistrationForm.DoesNotExist:
pass # L'objet n'existe pas encore, rien à supprimer
@@ -485,10 +514,18 @@ class RegistrationParentFileMaster(models.Model):
############################################################
def registration_school_file_upload_to(instance, filename):
+ """
+ Génère le chemin pour les fichiers templates école.
+ Structure : Etablissement/dossier_NomEleve_PrenomEleve/filename
+ """
return f"{instance.registration_form.establishment.name}/dossier_{instance.registration_form.student.last_name}_{instance.registration_form.student.first_name}/{filename}"
def registration_parent_file_upload_to(instance, filename):
- return f"registration_files/{instance.registration_form.establishment.name}/dossier_rf_{instance.registration_form.pk}/parent/{filename}"
+ """
+ Génère le chemin pour les fichiers à fournir par les parents.
+ Structure : Etablissement/dossier_NomEleve_PrenomEleve/parent/filename
+ """
+ return f"{instance.registration_form.establishment.name}/dossier_{instance.registration_form.student.last_name}_{instance.registration_form.student.first_name}/parent/{filename}"
####### Formulaires templates (par dossier d'inscription) #######
class RegistrationSchoolFileTemplate(models.Model):
@@ -503,15 +540,31 @@ class RegistrationSchoolFileTemplate(models.Model):
def __str__(self):
return self.name
+ def save(self, *args, **kwargs):
+ # Supprimer l'ancien fichier si remplacé (évite les suffixes Django)
+ if self.pk:
+ try:
+ old_instance = RegistrationSchoolFileTemplate.objects.get(pk=self.pk)
+ if old_instance.file and old_instance.file != self.file:
+ _delete_file_if_exists(old_instance.file)
+ except RegistrationSchoolFileTemplate.DoesNotExist:
+ pass
+ 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é.
+ Ignore les fichiers qui n'existent pas physiquement.
"""
registration_files = RegistrationSchoolFileTemplate.objects.filter(registration_form=register_form_id)
filenames = []
for reg_file in registration_files:
- filenames.append(reg_file.file.path)
+ if reg_file.file and hasattr(reg_file.file, 'path'):
+ if os.path.exists(reg_file.file.path):
+ filenames.append(reg_file.file.path)
+ else:
+ logger.warning(f"Fichier introuvable ignoré: {reg_file.file.path}")
return filenames
class StudentCompetency(models.Model):
@@ -544,20 +597,21 @@ class RegistrationParentFileTemplate(models.Model):
isValidated = models.BooleanField(default=False)
def __str__(self):
- return self.name
+ return self.master.name if self.master else f"ParentFile_{self.pk}"
def save(self, *args, **kwargs):
- if self.pk: # Si l'objet existe déjà dans la base de données
+ # Supprimer l'ancien fichier si remplacé (évite les suffixes Django)
+ if self.pk:
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.")
+ # Si le fichier change ou est supprimé
+ if old_instance.file:
+ if old_instance.file != self.file or not self.file or self.file.name == '':
+ _delete_file_if_exists(old_instance.file)
+ if not self.file or self.file.name == '':
+ self.file = None
except RegistrationParentFileTemplate.DoesNotExist:
- print("Ancienne instance introuvable.")
+ pass
super().save(*args, **kwargs)
@staticmethod
diff --git a/Back-End/Subscriptions/templates/emails/refus_dossier.html b/Back-End/Subscriptions/templates/emails/refus_dossier.html
new file mode 100644
index 0000000..8e36c44
--- /dev/null
+++ b/Back-End/Subscriptions/templates/emails/refus_dossier.html
@@ -0,0 +1,67 @@
+
+
+
+
+ Dossier d'inscription - Corrections requises
+
+
+
+
+
+
+
Bonjour,
+
Nous avons examiné le dossier d'inscription de {{ student_name }} et certaines corrections sont nécessaires avant de pouvoir le valider.
+
+
+ Motif(s) :
+ {{ notes }}
+
+
+
Veuillez vous connecter à votre espace pour effectuer les corrections demandées :
+
{{BASE_URL}}/users/login
+
+
Cordialement,
+
L'équipe N3wt School
+
+
+
+
+
diff --git a/Back-End/Subscriptions/templates/emails/validation_dossier.html b/Back-End/Subscriptions/templates/emails/validation_dossier.html
new file mode 100644
index 0000000..1454d21
--- /dev/null
+++ b/Back-End/Subscriptions/templates/emails/validation_dossier.html
@@ -0,0 +1,85 @@
+
+
+
+
+ Dossier d'inscription validé
+
+
+
+
+
+
+
Bonjour,
+
+
+
Félicitations !
+
Le dossier d'inscription de {{ student_name }} a été validé.
+
+
+ {% if class_name %}
+
+ Classe attribuée : {{ class_name }}
+
+ {% endif %}
+
+
Vous pouvez accéder à votre espace pour consulter les détails :
+
{{BASE_URL}}/users/login
+
+
Nous vous remercions de votre confiance et vous souhaitons une excellente année scolaire.
+
+
Cordialement,
+
L'équipe N3wt School
+
+
+
+
+
diff --git a/Back-End/Subscriptions/util.py b/Back-End/Subscriptions/util.py
index fbc834b..cb85046 100644
--- a/Back-End/Subscriptions/util.py
+++ b/Back-End/Subscriptions/util.py
@@ -19,7 +19,8 @@ from enum import Enum
import random
import string
from rest_framework.parsers import JSONParser
-from PyPDF2 import PdfMerger
+from PyPDF2 import PdfMerger, PdfReader
+from PyPDF2.errors import PdfReadError
import shutil
import logging
@@ -31,6 +32,29 @@ from rest_framework import status
logger = logging.getLogger(__name__)
+def save_file_replacing_existing(file_field, filename, content, save=True):
+ """
+ Sauvegarde un fichier en écrasant l'existant s'il porte le même nom.
+ Évite les suffixes automatiques Django (ex: fichier_N5QdZpk.pdf).
+
+ Args:
+ file_field: Le FileField Django (ex: registerForm.registration_file)
+ filename: Le nom du fichier à sauvegarder
+ content: Le contenu du fichier (File, BytesIO, ContentFile, etc.)
+ save: Si True, sauvegarde l'instance parente
+ """
+ # Supprimer l'ancien fichier s'il existe
+ if file_field and file_field.name:
+ try:
+ if hasattr(file_field, 'path') and os.path.exists(file_field.path):
+ os.remove(file_field.path)
+ logger.debug(f"[save_file] Ancien fichier supprimé: {file_field.path}")
+ except Exception as e:
+ logger.error(f"[save_file] Erreur suppression ancien fichier: {e}")
+
+ # Sauvegarder le nouveau fichier
+ file_field.save(filename, content, save=save)
+
def build_payload_from_request(request):
"""
Normalise la request en payload prêt à être donné au serializer.
@@ -344,12 +368,70 @@ def getArgFromRequest(_argument, _request):
def merge_files_pdf(file_paths):
"""
Fusionne plusieurs fichiers PDF et retourne le contenu fusionné en mémoire.
+ Les fichiers non-PDF (images) sont convertis en PDF avant fusion.
+ Les fichiers invalides sont ignorés avec un log d'erreur.
"""
merger = PdfMerger()
+ files_added = 0
+
+ # Extensions d'images supportées
+ image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.tif'}
- # Ajouter les fichiers valides au merger
for file_path in file_paths:
- merger.append(file_path)
+ # Vérifier que le fichier existe
+ if not os.path.exists(file_path):
+ logger.warning(f"[merge_files_pdf] Fichier introuvable, ignoré: {file_path}")
+ continue
+
+ file_ext = os.path.splitext(file_path)[1].lower()
+
+ # Si c'est une image, la convertir en PDF
+ if file_ext in image_extensions:
+ try:
+ from PIL import Image
+ from reportlab.lib.utils import ImageReader
+
+ img = Image.open(file_path)
+ img_width, img_height = img.size
+
+ # Créer un PDF en mémoire avec l'image
+ img_pdf = BytesIO()
+ c = canvas.Canvas(img_pdf, pagesize=(img_width, img_height))
+ c.drawImage(file_path, 0, 0, width=img_width, height=img_height)
+ c.save()
+ img_pdf.seek(0)
+
+ merger.append(img_pdf)
+ files_added += 1
+ logger.debug(f"[merge_files_pdf] Image convertie et ajoutée: {file_path}")
+ except Exception as e:
+ logger.error(f"[merge_files_pdf] Erreur lors de la conversion de l'image {file_path}: {e}")
+ continue
+
+ # Sinon, essayer de l'ajouter comme PDF
+ try:
+ # Valider que c'est un PDF lisible
+ with open(file_path, 'rb') as f:
+ PdfReader(f, strict=False)
+
+ # Si la validation passe, ajouter au merger
+ merger.append(file_path)
+ files_added += 1
+ logger.debug(f"[merge_files_pdf] PDF ajouté: {file_path}")
+ except PdfReadError as e:
+ logger.error(f"[merge_files_pdf] Fichier PDF invalide, ignoré: {file_path} - {e}")
+ except Exception as e:
+ logger.error(f"[merge_files_pdf] Erreur lors de la lecture du fichier {file_path}: {e}")
+
+ if files_added == 0:
+ logger.warning("[merge_files_pdf] Aucun fichier valide à fusionner")
+ # Retourner un PDF vide
+ empty_pdf = BytesIO()
+ c = canvas.Canvas(empty_pdf, pagesize=A4)
+ c.drawString(100, 750, "Aucun document à afficher")
+ c.save()
+ empty_pdf.seek(0)
+ return empty_pdf
# Sauvegarder le fichier fusionné en mémoire
merged_pdf = BytesIO()
@@ -378,25 +460,11 @@ def rfToPDF(registerForm, filename):
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
- if registerForm.registration_file and registerForm.registration_file.name:
- # Vérifiez si le chemin est déjà absolu ou relatif
- 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):
- 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 (écrase l'existant)
try:
- registerForm.registration_file.save(
- os.path.basename(filename), # Utiliser uniquement le nom de fichier
+ save_file_replacing_existing(
+ registerForm.registration_file,
+ os.path.basename(filename),
File(BytesIO(pdf.content)),
save=True
)
diff --git a/Back-End/Subscriptions/views/register_form_views.py b/Back-End/Subscriptions/views/register_form_views.py
index b371ab2..5441703 100644
--- a/Back-End/Subscriptions/views/register_form_views.py
+++ b/Back-End/Subscriptions/views/register_form_views.py
@@ -9,6 +9,7 @@ from drf_yasg import openapi
import json
import os
+import threading
from django.core.files import File
import N3wtSchool.mailManager as mailer
@@ -359,6 +360,26 @@ class RegisterFormWithIdView(APIView):
return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
elif _status == RegistrationForm.RegistrationFormStatus.RF_SENT:
if registerForm.status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW:
+ # Envoi de l'email de refus aux responsables légaux
+ try:
+ student = registerForm.student
+ student_name = f"{student.first_name} {student.last_name}"
+ notes = registerForm.notes or "Aucun motif spécifié"
+
+ guardians = student.guardians.all()
+ for guardian in guardians:
+ email = None
+ if hasattr(guardian, "profile_role") and guardian.profile_role and hasattr(guardian.profile_role, "profile") and guardian.profile_role.profile:
+ email = guardian.profile_role.profile.email
+ if not email:
+ email = getattr(guardian, "email", None)
+
+ if email:
+ logger.info(f"[RF_SENT] Envoi email de refus à {email} pour l'élève {student_name}")
+ mailer.sendRefusDossier(email, registerForm.establishment.pk, student_name, notes)
+ except Exception as e:
+ logger.error(f"[RF_SENT] Erreur lors de l'envoi de l'email de refus: {e}")
+
updateStateMachine(registerForm, 'EVENT_REFUSE')
util.delete_registration_files(registerForm)
elif _status == RegistrationForm.RegistrationFormStatus.RF_SEPA_SENT:
@@ -401,14 +422,23 @@ class RegisterFormWithIdView(APIView):
fileNames.extend(parent_file_templates)
# Création du fichier PDF fusionné
- merged_pdf_content = util.merge_files_pdf(fileNames)
+ merged_pdf_content = None
+ try:
+ merged_pdf_content = util.merge_files_pdf(fileNames)
- # Mise à jour du champ registration_file avec le fichier fusionné
- registerForm.fusion_file.save(
- f"dossier_complet.pdf",
- File(merged_pdf_content),
- save=True
- )
+ # Mise à jour du champ fusion_file avec le fichier fusionné
+ util.save_file_replacing_existing(
+ registerForm.fusion_file,
+ "dossier_complet.pdf",
+ File(merged_pdf_content),
+ save=True
+ )
+ except Exception as e:
+ logger.error(f"[RF_VALIDATED] Erreur lors de la fusion des fichiers: {e}")
+ finally:
+ # Libérer explicitement la mémoire du BytesIO
+ if merged_pdf_content is not None:
+ merged_pdf_content.close()
# Valorisation des StudentCompetency pour l'élève
try:
student = registerForm.student
@@ -450,6 +480,33 @@ class RegisterFormWithIdView(APIView):
except Exception as e:
logger.error(f"Erreur lors de la valorisation des StudentCompetency: {e}")
+ # Envoi de l'email de validation aux responsables légaux (en arrière-plan)
+ def send_validation_emails():
+ try:
+ student = registerForm.student
+ student_name = f"{student.first_name} {student.last_name}"
+ class_name = None
+ if student.associated_class:
+ class_name = student.associated_class.atmosphere_name
+
+ guardians = student.guardians.all()
+ for guardian in guardians:
+ email = None
+ if hasattr(guardian, "profile_role") and guardian.profile_role and hasattr(guardian.profile_role, "profile") and guardian.profile_role.profile:
+ email = guardian.profile_role.profile.email
+ if not email:
+ email = getattr(guardian, "email", None)
+
+ if email:
+ logger.info(f"[RF_VALIDATED] Envoi email de validation à {email} pour l'élève {student_name}")
+ mailer.sendValidationDossier(email, registerForm.establishment.pk, student_name, class_name)
+ except Exception as e:
+ logger.error(f"[RF_VALIDATED] Erreur lors de l'envoi de l'email de validation: {e}")
+
+ # Lancer l'envoi d'email dans un thread séparé pour ne pas bloquer la réponse
+ email_thread = threading.Thread(target=send_validation_emails)
+ email_thread.start()
+
updateStateMachine(registerForm, 'EVENT_VALIDATE')
# Retourner les données mises à jour
diff --git a/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js b/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js
index 886b6a6..567074a 100644
--- a/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js
+++ b/Front-End/src/app/[locale]/admin/subscriptions/validateSubscription/page.js
@@ -120,19 +120,9 @@ export default function Page() {
editFn(templateId, updateData, csrfToken)
.then((response) => {
logger.debug(`Document ${validated ? 'validé' : 'refusé'} (type: ${type}, id: ${templateId})`, response);
- showNotification(
- `Le document a bien été ${validated ? 'validé' : 'refusé'}.`,
- 'success',
- 'Succès'
- );
})
.catch((error) => {
logger.error('Erreur lors de la validation/refus du document:', error);
- showNotification(
- `Erreur lors de la ${validated ? 'validation' : 'refus'} du document.`,
- 'error',
- 'Erreur'
- );
});
};
diff --git a/Front-End/src/components/Form/FormRenderer.js b/Front-End/src/components/Form/FormRenderer.js
index 0576174..3a0458f 100644
--- a/Front-End/src/components/Form/FormRenderer.js
+++ b/Front-End/src/components/Form/FormRenderer.js
@@ -1,5 +1,6 @@
import logger from '@/utils/logger';
import { useForm, Controller } from 'react-hook-form';
+import { useEffect } from 'react';
import SelectChoice from './SelectChoice';
import InputTextIcon from './InputTextIcon';
import * as LucideIcons from 'lucide-react';
@@ -28,6 +29,7 @@ export function getIcon(name) {
export default function FormRenderer({
formConfig,
csrfToken,
+ initialValues = {},
onFormSubmit = (data) => {
alert(JSON.stringify(data, null, 2));
}, // Callback de soumission personnalisé (optionnel)
@@ -37,7 +39,14 @@ export default function FormRenderer({
control,
formState: { errors },
reset,
- } = useForm();
+ } = useForm({ defaultValues: initialValues });
+
+ // Réinitialiser le formulaire quand les valeurs initiales changent
+ useEffect(() => {
+ if (initialValues && Object.keys(initialValues).length > 0) {
+ reset(initialValues);
+ }
+ }, [initialValues, reset]);
// Fonction utilitaire pour envoyer les données au backend
const sendFormDataToBackend = async (formData) => {
diff --git a/Front-End/src/components/Inscription/DynamicFormsList.js b/Front-End/src/components/Inscription/DynamicFormsList.js
index 353076c..87b9b22 100644
--- a/Front-End/src/components/Inscription/DynamicFormsList.js
+++ b/Front-End/src/components/Inscription/DynamicFormsList.js
@@ -23,26 +23,6 @@ export default function DynamicFormsList({
onFileUpload, // nouvelle prop pour gérer l'upload (à passer depuis le parent)
}) {
const [currentTemplateIndex, setCurrentTemplateIndex] = useState(0);
- // Remet formsValidation à false et formsData à undefined lors de la sélection d'un document refusé
- useEffect(() => {
- const currentTemplate = schoolFileTemplates[currentTemplateIndex];
- if (
- currentTemplate &&
- currentTemplate.isValidated === false &&
- formsValidation[currentTemplate.id] !== true
- ) {
- setFormsValidation((prev) => {
- const newValidation = { ...prev };
- newValidation[currentTemplate.id] = false;
- return newValidation;
- });
- setFormsData((prev) => {
- const newData = { ...prev };
- delete newData[currentTemplate.id];
- return newData;
- });
- }
- }, [currentTemplateIndex, schoolFileTemplates, formsValidation]);
const [formsData, setFormsData] = useState({});
const [formsValidation, setFormsValidation] = useState({});
const fileInputRefs = React.useRef({});
@@ -51,37 +31,46 @@ export default function DynamicFormsList({
useEffect(() => {
// Initialisation complète de formsValidation et formsData pour chaque template
if (schoolFileTemplates && schoolFileTemplates.length > 0) {
- // Initialiser formsData pour chaque template (avec données existantes ou objet vide)
- const dataState = {};
- schoolFileTemplates.forEach((tpl) => {
- if (
- existingResponses &&
- existingResponses[tpl.id] &&
- Object.keys(existingResponses[tpl.id]).length > 0
- ) {
- dataState[tpl.id] = existingResponses[tpl.id];
- } else {
- dataState[tpl.id] = {};
- }
+ // Fusionner avec l'état existant pour préserver les données locales
+ setFormsData((prevData) => {
+ const dataState = { ...prevData };
+ schoolFileTemplates.forEach((tpl) => {
+ // Ne mettre à jour que si on n'a pas encore de données locales ou si les données du serveur ont changé
+ const hasLocalData = prevData[tpl.id] && Object.keys(prevData[tpl.id]).length > 0;
+ const hasServerData = existingResponses && existingResponses[tpl.id] && Object.keys(existingResponses[tpl.id]).length > 0;
+
+ if (!hasLocalData && hasServerData) {
+ // Pas de données locales mais données serveur : utiliser les données serveur
+ dataState[tpl.id] = existingResponses[tpl.id];
+ } else if (!hasLocalData && !hasServerData) {
+ // Pas de données du tout : initialiser à vide
+ dataState[tpl.id] = {};
+ }
+ // Si hasLocalData : on garde les données locales existantes
+ });
+ return dataState;
});
- setFormsData(dataState);
- // Initialiser formsValidation pour chaque template
- const validationState = {};
- schoolFileTemplates.forEach((tpl) => {
- if (
- existingResponses &&
- existingResponses[tpl.id] &&
- Object.keys(existingResponses[tpl.id]).length > 0
- ) {
- validationState[tpl.id] = true;
- } else {
- validationState[tpl.id] = false;
- }
+ // Fusionner avec l'état de validation existant
+ setFormsValidation((prevValidation) => {
+ const validationState = { ...prevValidation };
+ schoolFileTemplates.forEach((tpl) => {
+ const hasLocalValidation = prevValidation[tpl.id] === true;
+ const hasServerData = existingResponses && existingResponses[tpl.id] && Object.keys(existingResponses[tpl.id]).length > 0;
+
+ if (!hasLocalValidation && hasServerData) {
+ // Pas validé localement mais données serveur : marquer comme validé
+ validationState[tpl.id] = true;
+ } else if (validationState[tpl.id] === undefined) {
+ // Pas encore initialisé : initialiser à false
+ validationState[tpl.id] = false;
+ }
+ // Si hasLocalValidation : on garde l'état local existant
+ });
+ return validationState;
});
- setFormsValidation(validationState);
}
- }, [existingResponses]);
+ }, [existingResponses, schoolFileTemplates]);
// Mettre à jour la validation globale quand la validation des formulaires change
useEffect(() => {
@@ -163,6 +152,8 @@ export default function DynamicFormsList({
return schoolFileTemplates[currentTemplateIndex];
};
+ const currentTemplate = getCurrentTemplate();
+
// Handler d'upload pour formulaire existant
const handleUpload = async (file, selectedFile) => {
if (!file || !selectedFile) return;
@@ -399,6 +390,11 @@ export default function DynamicFormsList({
submitLabel:
currentTemplate.formTemplateData?.submitLabel || 'Valider',
}}
+ initialValues={
+ formsData[currentTemplate.id] ||
+ existingResponses[currentTemplate.id] ||
+ {}
+ }
onFormSubmit={(formData) =>
handleFormSubmit(formData, currentTemplate.id)
}
@@ -408,38 +404,45 @@ export default function DynamicFormsList({
) : (
// Formulaire existant (PDF, image, etc.)
-
- {/* Upload désactivé si validé par l'école */}
- {enable && currentTemplate.isValidated !== true && (
-
handleUpload(file, currentTemplate)}
- required
- enable={true}
+ {/* Cas validé : affichage en iframe */}
+ {currentTemplate.isValidated === true && currentTemplate.file && (
+
)}
- {/* Le label d'état est maintenant dans l'en-tête */}
+
+ {/* Cas non validé : bouton télécharger + upload */}
+ {currentTemplate.isValidated !== true && (
+
+ {/* Bouton télécharger le document source */}
+ {currentTemplate.file && (
+
+
+ Télécharger le document
+
+ )}
+
+ {/* Composant d'upload */}
+ {enable && (
+
handleUpload(file, currentTemplate)}
+ required
+ enable={true}
+ />
+ )}
+
+ )}
)}