mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-03 16:51:26 +00:00
878 lines
28 KiB
JavaScript
878 lines
28 KiB
JavaScript
// Import des dépendances nécessaires
|
|
import React, { useState, useEffect } from 'react';
|
|
import Button from '@/components/Form/Button';
|
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
|
import {
|
|
fetchSchoolFileTemplatesFromRegistrationFiles,
|
|
fetchParentFileTemplatesFromRegistrationFiles,
|
|
fetchRegistrationSchoolFileMasters,
|
|
saveFormResponses,
|
|
fetchFormResponses,
|
|
autoSaveRegisterForm,
|
|
} from '@/app/actions/subscriptionAction';
|
|
import {
|
|
downloadTemplate,
|
|
editRegistrationSchoolFileTemplates,
|
|
editRegistrationParentFileTemplates,
|
|
} from '@/app/actions/registerFileGroupAction';
|
|
import {
|
|
fetchRegistrationPaymentModes,
|
|
fetchTuitionPaymentModes,
|
|
fetchRegistrationPaymentPlans,
|
|
fetchTuitionPaymentPlans,
|
|
} from '@/app/actions/schoolAction';
|
|
import { fetchProfiles } from '@/app/actions/authAction';
|
|
import { BASE_URL, FE_PARENTS_HOME_URL } from '@/utils/Url';
|
|
import logger from '@/utils/logger';
|
|
import FilesToUpload from '@/components/Inscription/FilesToUpload';
|
|
import DynamicFormsList from '@/components/Inscription/DynamicFormsList';
|
|
import AutoSaveIndicator from '@/components/AutoSaveIndicator';
|
|
import StudentInfoForm from '@/components/Inscription/StudentInfoForm';
|
|
import ResponsableInputFields from '@/components/Inscription/ResponsableInputFields';
|
|
import SiblingInputFields from '@/components/Inscription/SiblingInputFields';
|
|
import PaymentMethodSelector from '@/components/Inscription/PaymentMethodSelector';
|
|
import ProgressStep from '@/components/ProgressStep';
|
|
import { CheckCircle, Hourglass } from 'lucide-react';
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
/**
|
|
* Composant de formulaire d'inscription partagé
|
|
* @param {string} studentId - ID de l'étudiant
|
|
* @param {string} csrfToken - Token CSRF pour la sécurité
|
|
* @param {function} onSubmit - Fonction de soumission du formulaire
|
|
* @param {string} cancelUrl - URL de redirection en cas d'annulation
|
|
* @param {object} errors - Erreurs de validation du formulaire
|
|
*/
|
|
export default function InscriptionFormShared({
|
|
studentId,
|
|
csrfToken,
|
|
selectedEstablishmentId,
|
|
apiDocuseal,
|
|
onSubmit,
|
|
errors = {}, // Nouvelle prop pour les erreurs
|
|
enable = true,
|
|
}) {
|
|
// États pour gérer les données du formulaire
|
|
const [formData, setFormData] = useState({
|
|
id: '',
|
|
photo: null,
|
|
last_name: '',
|
|
first_name: '',
|
|
gender: '',
|
|
address: '',
|
|
birth_date: '',
|
|
birth_place: '',
|
|
birth_postal_code: '',
|
|
nationality: '',
|
|
attending_physician: '',
|
|
level: '',
|
|
photo: '',
|
|
});
|
|
const [guardians, setGuardians] = useState([]);
|
|
const [siblings, setSiblings] = useState([]);
|
|
|
|
const [registrationPaymentModes, setRegistrationPaymentModes] = useState([]);
|
|
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
|
|
const [registrationPaymentPlans, setRegistrationPaymentPlans] = useState([]);
|
|
const [tuitionPaymentPlans, setTuitionPaymentPlans] = useState([]);
|
|
|
|
// États pour la gestion des fichiers
|
|
const [uploadedFiles, setUploadedFiles] = useState([]);
|
|
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
|
|
const [parentFileTemplates, setParentFileTemplates] = useState([]);
|
|
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
|
|
const [formResponses, setFormResponses] = useState({});
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
|
|
const [isPage1Valid, setIsPage1Valid] = useState(false);
|
|
const [isPage2Valid, setIsPage2Valid] = useState(false);
|
|
const [isPage3Valid, setIsPage3Valid] = useState(false);
|
|
const [isPage4Valid, setIsPage4Valid] = useState(false);
|
|
const [isPage5Valid, setIsPage5Valid] = useState(false);
|
|
const [isPage6Valid, setIsPage6Valid] = useState(false);
|
|
|
|
const [hasInteracted, setHasInteracted] = useState(false);
|
|
|
|
// État pour suivre l'index du fichier en cours
|
|
const [currentTemplateIndex, setCurrentTemplateIndex] = useState(0);
|
|
|
|
const [profiles, setProfiles] = useState([]);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [lastSaved, setLastSaved] = useState(null);
|
|
const [autoSaveEnabled, setAutoSaveEnabled] = useState(true);
|
|
|
|
const router = useRouter();
|
|
|
|
// Mettre à jour les états en fonction de la valeur de `enable`
|
|
useEffect(() => {
|
|
if (!enable) {
|
|
setIsPage1Valid(true);
|
|
setIsPage2Valid(true);
|
|
setIsPage3Valid(true);
|
|
setIsPage4Valid(true);
|
|
setIsPage5Valid(true);
|
|
setIsPage6Valid(true);
|
|
}
|
|
}, [enable]);
|
|
|
|
useEffect(() => {
|
|
// Trouver le premier template non signé
|
|
const firstUnsignedIndex = schoolFileTemplates.findIndex(
|
|
(template) => template.file === null
|
|
);
|
|
|
|
// Mettre à jour l'index du template actuel
|
|
if (firstUnsignedIndex !== -1) {
|
|
setCurrentTemplateIndex(firstUnsignedIndex);
|
|
} else {
|
|
// Si tous les templates sont signés, définir un index hors limites
|
|
setCurrentTemplateIndex(0);
|
|
}
|
|
}, [schoolFileTemplates]);
|
|
|
|
useEffect(() => {
|
|
// Vérifier si tous les formulaires maîtres sont complétés
|
|
const allCompleted =
|
|
schoolFileMasters.length === 0 ||
|
|
schoolFileMasters.every((master) => master.completed === true);
|
|
|
|
// Mettre à jour isPage5Valid en fonction de cette condition
|
|
setIsPage5Valid(allCompleted);
|
|
|
|
if (allCompleted) {
|
|
setCurrentTemplateIndex(0);
|
|
}
|
|
}, [schoolFileMasters]);
|
|
|
|
useEffect(() => {
|
|
// Vérifier si tous les documents avec is_required = true ont leur champ "file" différent de null
|
|
const allRequiredUploaded = parentFileTemplates
|
|
.filter((template) => template.is_required) // Ne garder que les documents requis
|
|
.every((template) => template.file !== null); // Vérifier que chaque fichier requis est uploadé
|
|
|
|
// Mettre à jour isPage6Valid en fonction de cette condition
|
|
setIsPage6Valid(allRequiredUploaded);
|
|
logger.debug(allRequiredUploaded);
|
|
}, [parentFileTemplates]);
|
|
|
|
// Auto-sauvegarde périodique (toutes les 30 secondes)
|
|
useEffect(() => {
|
|
if (!enable || !autoSaveEnabled) return;
|
|
|
|
const interval = setInterval(() => {
|
|
autoSave();
|
|
}, 30000); // 30 secondes
|
|
|
|
return () => clearInterval(interval);
|
|
}, [enable, autoSaveEnabled, formData, guardians, siblings]);
|
|
|
|
// Auto-sauvegarde quand les données changent (avec debounce)
|
|
useEffect(() => {
|
|
if (!enable || !autoSaveEnabled) return;
|
|
|
|
const timeout = setTimeout(() => {
|
|
autoSave();
|
|
}, 2000); // Attendre 2 secondes après le dernier changement
|
|
|
|
return () => clearTimeout(timeout);
|
|
}, [formData, guardians, siblings]);
|
|
|
|
/**
|
|
* Fonction d'auto-sauvegarde qui sauvegarde les données en cours
|
|
*/
|
|
const autoSave = async () => {
|
|
if (!autoSaveEnabled || !studentId || isSaving) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setIsSaving(true);
|
|
logger.debug('Auto-sauvegarde en cours...', {
|
|
studentId,
|
|
formDataKeys: Object.keys(formData),
|
|
paymentFields: {
|
|
registration_payment: formData.registration_payment,
|
|
registration_payment_plan: formData.registration_payment_plan,
|
|
tuition_payment: formData.tuition_payment,
|
|
tuition_payment_plan: formData.tuition_payment_plan,
|
|
},
|
|
guardians: guardians.length,
|
|
siblings: siblings.length,
|
|
currentPage,
|
|
});
|
|
|
|
// Fonction helper pour nettoyer les données avant sauvegarde
|
|
const cleanDataForAutoSave = (data) => {
|
|
const cleaned = {};
|
|
Object.keys(data).forEach((key) => {
|
|
const value = data[key];
|
|
// Garder seulement les valeurs non-vides et valides
|
|
if (value !== null && value !== undefined && value !== '') {
|
|
// Pour les dates, vérifier le format
|
|
if (key === 'birth_date' && value) {
|
|
// Vérifier que la date est dans un format valide
|
|
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
if (dateRegex.test(value)) {
|
|
cleaned[key] = value;
|
|
}
|
|
}
|
|
// Pour les codes postaux, vérifier que c'est un nombre
|
|
else if (key === 'birth_postal_code' && value) {
|
|
if (!isNaN(value) && value.toString().trim() !== '') {
|
|
cleaned[key] = parseInt(value);
|
|
}
|
|
}
|
|
// Pour les champs de paiement, toujours les inclure s'ils ont une valeur
|
|
else if (key.includes('payment') && value) {
|
|
cleaned[key] = value;
|
|
}
|
|
// Pour les autres champs, garder la valeur si elle n'est pas vide
|
|
else if (value.toString().trim() !== '') {
|
|
cleaned[key] = value;
|
|
}
|
|
}
|
|
});
|
|
return cleaned;
|
|
};
|
|
|
|
// Préparer les données à sauvegarder avec nettoyage
|
|
const cleanedFormData = cleanDataForAutoSave(formData);
|
|
|
|
const dataToSave = {
|
|
student: cleanedFormData,
|
|
guardians: guardians.filter(
|
|
(guardian) =>
|
|
guardian &&
|
|
(guardian.first_name || guardian.last_name || guardian.email)
|
|
),
|
|
siblings: siblings.filter(
|
|
(sibling) => sibling && (sibling.first_name || sibling.last_name)
|
|
),
|
|
currentPage: currentPage,
|
|
};
|
|
|
|
// Utiliser la fonction d'auto-save dédiée
|
|
await autoSaveRegisterForm(studentId, dataToSave, csrfToken);
|
|
|
|
setLastSaved(new Date());
|
|
logger.debug('Auto-sauvegarde réussie');
|
|
} catch (error) {
|
|
logger.error("Erreur lors de l'auto-sauvegarde:", error);
|
|
// Ne pas afficher d'erreur à l'utilisateur pour l'auto-save
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Gère la sauvegarde à chaque changement d'étape
|
|
*/
|
|
const saveStepData = async () => {
|
|
await autoSave();
|
|
};
|
|
|
|
/**
|
|
* Gère la soumission d'un formulaire dynamique
|
|
*/
|
|
const handleDynamicFormSubmit = async (formData, templateId) => {
|
|
try {
|
|
logger.debug('Soumission du formulaire dynamique:', {
|
|
templateId,
|
|
formData,
|
|
csrfToken: !!csrfToken,
|
|
});
|
|
|
|
// Trouver le template correspondant pour récupérer sa configuration
|
|
const currentTemplate = schoolFileMasters.find(
|
|
(master) => master.id === templateId
|
|
);
|
|
if (!currentTemplate) {
|
|
throw new Error(`Template avec l'ID ${templateId} non trouvé`);
|
|
}
|
|
|
|
// Construire la structure complète avec la configuration et les réponses
|
|
const formTemplateData = {
|
|
id: currentTemplate.id,
|
|
title:
|
|
currentTemplate.formMasterData?.title ||
|
|
currentTemplate.title ||
|
|
currentTemplate.name ||
|
|
'Formulaire',
|
|
fields: (
|
|
currentTemplate.formMasterData?.fields ||
|
|
currentTemplate.fields ||
|
|
[]
|
|
).map((field) => ({
|
|
...field,
|
|
// Ajouter la réponse de l'utilisateur selon le type de champ
|
|
...(field.type === 'checkbox'
|
|
? { checked: formData[field.id] || false }
|
|
: {}),
|
|
...(field.type === 'radio' ? { selected: formData[field.id] } : {}),
|
|
...(field.type === 'text' ||
|
|
field.type === 'textarea' ||
|
|
field.type === 'email'
|
|
? { value: formData[field.id] || '' }
|
|
: {}),
|
|
})),
|
|
submitLabel: currentTemplate.formMasterData?.submitLabel || 'Valider',
|
|
responses: formData, // Garder aussi les réponses brutes pour facilité d'accès
|
|
};
|
|
|
|
// Sauvegarder les réponses du formulaire via l'API RegistrationSchoolFileTemplate
|
|
logger.debug('Appel API saveFormResponses avec:', {
|
|
templateId,
|
|
formTemplateData,
|
|
});
|
|
const result = await saveFormResponses(
|
|
templateId,
|
|
formTemplateData,
|
|
csrfToken
|
|
);
|
|
logger.debug("Réponse de l'API:", result);
|
|
|
|
// Mettre à jour l'état local des réponses
|
|
setFormResponses((prev) => ({
|
|
...prev,
|
|
[templateId]: formData,
|
|
}));
|
|
|
|
// Mettre à jour l'état local pour indiquer que le formulaire est complété
|
|
setSchoolFileMasters((prevMasters) => {
|
|
return prevMasters.map((master) =>
|
|
master.id === templateId
|
|
? { ...master, completed: true, responses: formData }
|
|
: master
|
|
);
|
|
});
|
|
|
|
logger.debug('Formulaire dynamique sauvegardé avec succès');
|
|
return Promise.resolve();
|
|
} catch (error) {
|
|
logger.error('Erreur lors de la soumission du formulaire dynamique:', {
|
|
templateId,
|
|
error: error.message,
|
|
stack: error.stack,
|
|
});
|
|
// Afficher l'erreur à l'utilisateur
|
|
alert(`Erreur lors de la sauvegarde du formulaire: ${error.message}`);
|
|
return Promise.reject(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Gère les changements de validation des formulaires dynamiques
|
|
*/
|
|
const handleDynamicFormsValidationChange = (isValid) => {
|
|
setIsPage5Valid(isValid);
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchSchoolFileTemplatesFromRegistrationFiles(studentId).then((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);
|
|
});
|
|
|
|
fetchProfiles()
|
|
.then((data) => {
|
|
setProfiles(data);
|
|
})
|
|
.catch((error) => logger.error('Error fetching profiles : ', error));
|
|
|
|
if (selectedEstablishmentId) {
|
|
// Fetch data for school file masters
|
|
fetchRegistrationSchoolFileMasters(selectedEstablishmentId)
|
|
.then(async (data) => {
|
|
logger.debug('School file masters fetched:', data);
|
|
setSchoolFileMasters(data);
|
|
|
|
// Récupérer les données existantes de chaque template
|
|
const responsesMap = {};
|
|
for (const master of data) {
|
|
if (master.id) {
|
|
try {
|
|
const templateData = await fetchFormResponses(master.id);
|
|
if (templateData && templateData.formTemplateData) {
|
|
// Si on a les réponses brutes sauvegardées, les utiliser
|
|
if (templateData.formTemplateData.responses) {
|
|
responsesMap[master.id] =
|
|
templateData.formTemplateData.responses;
|
|
} else {
|
|
// Sinon, extraire les réponses depuis les champs
|
|
const responses = {};
|
|
if (templateData.formTemplateData.fields) {
|
|
templateData.formTemplateData.fields.forEach((field) => {
|
|
if (
|
|
field.type === 'checkbox' &&
|
|
field.checked !== undefined
|
|
) {
|
|
responses[field.id] = field.checked;
|
|
} else if (
|
|
field.type === 'radio' &&
|
|
field.selected !== undefined
|
|
) {
|
|
responses[field.id] = field.selected;
|
|
} else if (
|
|
(field.type === 'text' ||
|
|
field.type === 'textarea' ||
|
|
field.type === 'email') &&
|
|
field.value !== undefined
|
|
) {
|
|
responses[field.id] = field.value;
|
|
}
|
|
});
|
|
}
|
|
responsesMap[master.id] = responses;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logger.debug(
|
|
`Pas de données existantes pour le template ${master.id}:`,
|
|
error
|
|
);
|
|
// Ce n'est pas critique si un template n'a pas de données
|
|
}
|
|
}
|
|
}
|
|
setFormResponses(responsesMap);
|
|
})
|
|
.catch((error) =>
|
|
logger.error('Error fetching school file masters:', error)
|
|
);
|
|
|
|
// Fetch data for registration payment modes
|
|
handleRegistrationPaymentModes();
|
|
|
|
// Fetch data for tuition payment modes
|
|
handleTuitionPaymentModes();
|
|
|
|
// Fetch data for registration payment plans
|
|
handleRegistrationPaymentPlans();
|
|
|
|
// Fetch data for tuition payment plans
|
|
handleTuitionnPaymentPlans();
|
|
}
|
|
}, [selectedEstablishmentId]);
|
|
|
|
const handleRegistrationPaymentModes = () => {
|
|
fetchRegistrationPaymentModes(selectedEstablishmentId)
|
|
.then((data) => {
|
|
setRegistrationPaymentModes(data);
|
|
})
|
|
.catch((error) =>
|
|
logger.error('Error fetching registration payment modes:', error)
|
|
);
|
|
};
|
|
|
|
const handleTuitionPaymentModes = () => {
|
|
fetchTuitionPaymentModes(selectedEstablishmentId)
|
|
.then((data) => {
|
|
setTuitionPaymentModes(data);
|
|
})
|
|
.catch((error) =>
|
|
logger.error('Error fetching tuition payment modes:', error)
|
|
);
|
|
};
|
|
|
|
const handleRegistrationPaymentPlans = () => {
|
|
fetchRegistrationPaymentPlans(selectedEstablishmentId)
|
|
.then((data) => {
|
|
setRegistrationPaymentPlans(data);
|
|
})
|
|
.catch((error) =>
|
|
logger.error('Error fetching registration payment plans:', error)
|
|
);
|
|
};
|
|
|
|
const handleTuitionnPaymentPlans = () => {
|
|
fetchTuitionPaymentPlans(selectedEstablishmentId)
|
|
.then((data) => {
|
|
setTuitionPaymentPlans(data);
|
|
})
|
|
.catch((error) =>
|
|
logger.error('Error fetching registration tuition plans:', error)
|
|
);
|
|
};
|
|
|
|
const handleFileUpload = (file, selectedFile) => {
|
|
if (!file || !selectedFile) {
|
|
logger.error('Données manquantes pour le téléversement.');
|
|
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;
|
|
});
|
|
|
|
// Mettre à jour parentFileTemplates
|
|
setParentFileTemplates((prevTemplates) =>
|
|
prevTemplates.map((template) =>
|
|
template.id === selectedFile.id
|
|
? { ...template, file: response.data.file }
|
|
: template
|
|
)
|
|
);
|
|
|
|
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;
|
|
}
|
|
|
|
// Créer un FormData avec un champ vide pour "file"
|
|
const updateData = new FormData();
|
|
updateData.append('file', ''); // Envoyer chaine vide pour indiquer qu'aucun fichier n'est uploadé
|
|
|
|
return editRegistrationParentFileTemplates(
|
|
templateId,
|
|
updateData,
|
|
csrfToken
|
|
)
|
|
.then((response) => {
|
|
logger.debug('Fichier supprimé avec succès dans la base :', response);
|
|
|
|
setIsPage6Valid(false);
|
|
|
|
// Mettre à jour l'état local pour refléter la suppression
|
|
setUploadedFiles((prev) =>
|
|
prev.map((uploadedFile) =>
|
|
uploadedFile.id === templateId
|
|
? { ...uploadedFile, fileName: null, fileUrl: null } // Réinitialiser les champs liés au fichier
|
|
: uploadedFile
|
|
)
|
|
);
|
|
|
|
// Mettre à jour l'état local pour refléter la suppression dans parentFileTemplates
|
|
setParentFileTemplates((prevTemplates) =>
|
|
prevTemplates.map((template) =>
|
|
template.id === templateId ? { ...template, file: null } : template
|
|
)
|
|
);
|
|
return response;
|
|
})
|
|
.catch((error) => {
|
|
logger.error(
|
|
'Erreur lors de la suppression du fichier dans la base :',
|
|
error
|
|
);
|
|
throw error;
|
|
});
|
|
};
|
|
|
|
// Soumission du formulaire
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
|
|
// Vérifier si le mode de paiement sélectionné est un prélèvement SEPA
|
|
const isSepaPayment = formData.isSepa === 1;
|
|
|
|
// Préparer les données JSON
|
|
const jsonData = {
|
|
student: {
|
|
...formData,
|
|
guardians: guardians,
|
|
siblings: siblings.map(({ id, ...rest }) =>
|
|
id && typeof id === 'string' && id.startsWith('temp-')
|
|
? rest
|
|
: { id, ...rest }
|
|
), // Supprimer les IDs temporaires
|
|
},
|
|
establishment: selectedEstablishmentId,
|
|
status: isSepaPayment ? 8 : 3,
|
|
tuition_payment: formData.tuition_payment,
|
|
registration_payment: formData.registration_payment,
|
|
tuition_payment_plan: formData.tuition_payment_plan,
|
|
registration_payment_plan: formData.registration_payment_plan,
|
|
};
|
|
|
|
// Créer un objet FormData
|
|
const formDataToSend = new FormData();
|
|
|
|
// Ajouter les données JSON sous forme de chaîne
|
|
formDataToSend.append('data', JSON.stringify(jsonData));
|
|
|
|
// Ajouter la photo si elle est présente
|
|
if (formData.photo) {
|
|
formDataToSend.append('photo', formData.photo);
|
|
}
|
|
|
|
logger.debug('submit : ', jsonData);
|
|
|
|
// Appeler la fonction onSubmit avec les données FormData
|
|
onSubmit(formDataToSend);
|
|
};
|
|
|
|
const handleNextPage = async () => {
|
|
// Sauvegarder avant de passer à l'étape suivante
|
|
await saveStepData();
|
|
setHasInteracted(false);
|
|
setCurrentPage(currentPage + 1);
|
|
};
|
|
|
|
const handlePreviousPage = async () => {
|
|
// Sauvegarder avant de revenir à l'étape précédente
|
|
await saveStepData();
|
|
setCurrentPage(currentPage - 1);
|
|
};
|
|
|
|
const stepTitles = {
|
|
1: 'Elève',
|
|
2: 'Responsables légaux',
|
|
3: 'Frères et soeurs',
|
|
4: 'Modalités de paiement',
|
|
5: 'Formulaires à signer',
|
|
6: 'Pièces à fournir',
|
|
};
|
|
|
|
const steps = [
|
|
'Élève',
|
|
'Responsable',
|
|
'Fratrie',
|
|
'Paiement',
|
|
'Formulaires',
|
|
'Documents parent',
|
|
];
|
|
|
|
const isStepValid = (stepNumber) => {
|
|
switch (stepNumber) {
|
|
case 1:
|
|
return isPage1Valid;
|
|
case 2:
|
|
return isPage2Valid;
|
|
case 3:
|
|
return isPage3Valid;
|
|
case 4:
|
|
return isPage4Valid;
|
|
case 5:
|
|
return isPage5Valid;
|
|
case 6:
|
|
return isPage6Valid;
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// Rendu du composant
|
|
return (
|
|
<div className="mx-auto p-6">
|
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
|
<ProgressStep
|
|
steps={steps}
|
|
stepTitles={stepTitles}
|
|
currentStep={currentPage}
|
|
setStep={setCurrentPage}
|
|
isStepValid={isStepValid}
|
|
/>
|
|
|
|
{/* Indicateur de sauvegarde automatique */}
|
|
{enable && (
|
|
<AutoSaveIndicator
|
|
isSaving={isSaving}
|
|
lastSaved={lastSaved}
|
|
autoSaveEnabled={autoSaveEnabled}
|
|
onToggleAutoSave={() => setAutoSaveEnabled(!autoSaveEnabled)}
|
|
/>
|
|
)}
|
|
|
|
<div className="flex-1 h-full mt-6">
|
|
{/* Page 1 : Informations sur l'élève */}
|
|
{currentPage === 1 && (
|
|
<StudentInfoForm
|
|
studentId={studentId}
|
|
formData={formData}
|
|
setFormData={setFormData}
|
|
guardians={guardians}
|
|
setGuardians={setGuardians}
|
|
setSiblings={setSiblings}
|
|
errors={errors}
|
|
setIsPageValid={setIsPage1Valid}
|
|
hasInteracted={hasInteracted}
|
|
setHasInteracted={setHasInteracted}
|
|
enable={enable}
|
|
/>
|
|
)}
|
|
|
|
{/* Page 2 : Informations sur les responsables légaux */}
|
|
{currentPage === 2 && (
|
|
<ResponsableInputFields
|
|
guardians={guardians}
|
|
setGuardians={setGuardians}
|
|
profiles={profiles}
|
|
errors={errors}
|
|
setIsPageValid={setIsPage2Valid}
|
|
enable={enable}
|
|
/>
|
|
)}
|
|
|
|
{/* Étape 3 : Frères et Sœurs */}
|
|
{currentPage === 3 && (
|
|
<SiblingInputFields
|
|
siblings={siblings}
|
|
setSiblings={setSiblings}
|
|
setFormData={setFormData}
|
|
errors={errors.siblings || []}
|
|
setIsPageValid={setIsPage3Valid}
|
|
enable={enable}
|
|
/>
|
|
)}
|
|
|
|
{/* Page 4 : Informations sur les modalités de paiement */}
|
|
{currentPage === 4 && (
|
|
<>
|
|
<PaymentMethodSelector
|
|
formData={formData}
|
|
setFormData={setFormData}
|
|
registrationPaymentModes={registrationPaymentModes}
|
|
tuitionPaymentModes={tuitionPaymentModes}
|
|
registrationPaymentPlans={registrationPaymentPlans}
|
|
tuitionPaymentPlans={tuitionPaymentPlans}
|
|
errors={errors}
|
|
setIsPageValid={setIsPage4Valid}
|
|
enable={enable}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{/* Page 5 : Formulaires dynamiques d'inscription */}
|
|
{currentPage === 5 && (
|
|
<DynamicFormsList
|
|
schoolFileMasters={schoolFileMasters}
|
|
existingResponses={formResponses}
|
|
onFormSubmit={handleDynamicFormSubmit}
|
|
onValidationChange={handleDynamicFormsValidationChange}
|
|
enable={enable}
|
|
/>
|
|
)}
|
|
|
|
{/* Dernière page : Section Fichiers parents */}
|
|
{currentPage === 6 && (
|
|
<FilesToUpload
|
|
parentFileTemplates={parentFileTemplates}
|
|
uploadedFiles={uploadedFiles}
|
|
onFileUpload={handleFileUpload}
|
|
onFileDelete={handleDeleteFile}
|
|
enable={enable}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Boutons de contrôle */}
|
|
<div className="flex justify-center space-x-4 mt-12">
|
|
{enable ? (
|
|
<>
|
|
{currentPage > 1 && (
|
|
<Button
|
|
text="Précédent"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
handlePreviousPage();
|
|
}}
|
|
primary
|
|
/>
|
|
)}
|
|
{currentPage < steps.length ? (
|
|
<Button
|
|
text="Suivant"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
handleNextPage();
|
|
}}
|
|
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
|
(currentPage === 1 && !isPage1Valid) ||
|
|
(currentPage === 2 && !isPage2Valid) ||
|
|
(currentPage === 3 && !isPage3Valid) ||
|
|
(currentPage === 4 && !isPage4Valid) ||
|
|
(currentPage === 5 && !isPage5Valid)
|
|
? 'bg-gray-300 text-gray-700 cursor-not-allowed'
|
|
: 'bg-emerald-500 text-white hover:bg-emerald-600'
|
|
}`}
|
|
disabled={
|
|
(currentPage === 1 && !isPage1Valid) ||
|
|
(currentPage === 2 && !isPage2Valid) ||
|
|
(currentPage === 3 && !isPage3Valid) ||
|
|
(currentPage === 4 && !isPage4Valid) ||
|
|
(currentPage === 5 && !isPage5Valid)
|
|
}
|
|
primary
|
|
name="Next"
|
|
/>
|
|
) : (
|
|
<Button
|
|
text="Valider"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
handleSubmit(e);
|
|
}}
|
|
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
|
currentPage === 6 && !isPage6Valid
|
|
? 'bg-gray-300 text-gray-700 cursor-not-allowed'
|
|
: 'bg-emerald-500 text-white hover:bg-emerald-600'
|
|
}`}
|
|
disabled={currentPage === 6 && !isPage6Valid}
|
|
primary
|
|
/>
|
|
)}
|
|
</>
|
|
) : (
|
|
<Button
|
|
onClick={() => router.push(FE_PARENTS_HOME_URL)}
|
|
text="Quitter"
|
|
primary
|
|
className="bg-emerald-500 text-white hover:bg-emerald-600"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|