Merge remote-tracking branch 'origin/WIP_Inscriptions' into develop

This commit is contained in:
Luc SORIGNET
2025-04-25 10:55:54 +02:00
39 changed files with 2400 additions and 1610 deletions

View File

@ -1,19 +1,244 @@
import React from 'react';
import React, { useState } from 'react';
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 (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">
Fichiers à uploader
</h2>
<Table
data={fileTemplates}
columns={columns}
itemsPerPage={5}
currentPage={1}
totalPages={1}
onPageChange={() => {}}
<div className="mt-8 mb-4 w-3/5">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-4">
<div className="bg-emerald-100 p-3 rounded-full shadow-md">
<FileText className="w-8 h-8 text-emerald-600" />
</div>
<div>
<h2 className="text-2xl font-bold text-gray-800">
Pièces à fournir
</h2>
<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>
);

View File

@ -1,12 +1,5 @@
import { useState, useEffect, useRef } from 'react';
import {
User,
Mail,
Phone,
UserCheck,
DollarSign,
Percent,
} from 'lucide-react';
import { User, Mail } from 'lucide-react';
import InputTextIcon from '@/components/InputTextIcon';
import ToggleSwitch from '@/components/ToggleSwitch';
import Button from '@/components/Button';
@ -150,7 +143,7 @@ const InscriptionForm = ({
};
const handleChange = (e) => {
const { name, value, type } = e.target;
const { name, value } = e.target;
setFormData((prevState) => ({
...prevState,
[name]: value,
@ -444,7 +437,7 @@ const InscriptionForm = ({
/>
<InputPhone
name="guardianPhone"
label={t('Numéro de téléphone (optionnel)')}
label="Numéro de téléphone (optionnel)"
value={formData.guardianPhone}
onChange={handleChange}
className="w-full mt-4"

View File

@ -5,23 +5,19 @@ import Button from '@/components/Button';
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import {
fetchRegisterForm,
fetchTemplatesFromRegistrationFiles,
fetchSchoolFileTemplatesFromRegistrationFiles,
fetchParentFileTemplatesFromRegistrationFiles,
} from '@/app/actions/subscriptionAction';
import {
downloadTemplate,
createRegistrationTemplates,
editRegistrationTemplates,
deleteRegistrationTemplates,
editRegistrationSchoolFileTemplates,
editRegistrationParentFileTemplates,
} from '@/app/actions/registerFileGroupAction';
import {
fetchRegistrationPaymentModes,
fetchTuitionPaymentModes,
} from '@/app/actions/schoolAction';
import { Download, Upload, Trash2, Eye } from 'lucide-react';
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 StudentInfoForm, {
validateStudentInfo,
@ -42,7 +38,6 @@ export default function InscriptionFormShared({
csrfToken,
selectedEstablishmentId,
onSubmit,
cancelUrl,
errors = {}, // Nouvelle prop pour les erreurs
}) {
// États pour gérer les données du formulaire
@ -69,13 +64,8 @@ export default function InscriptionFormShared({
// États pour la gestion des fichiers
const [uploadedFiles, setUploadedFiles] = useState([]);
const [fileTemplates, setFileTemplates] = useState([]);
const [fileGroup, setFileGroup] = useState(null);
const [fileName, setFileName] = useState('');
const [file, setFile] = useState('');
const [showUploadModal, setShowUploadModal] = useState(false);
const [currentTemplateId, setCurrentTemplateId] = useState(null);
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
const [parentFileTemplates, setParentFileTemplates] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const isCurrentPageValid = () => {
@ -110,7 +100,6 @@ export default function InscriptionFormShared({
totalTuitionFees: data?.totalTuitionFees,
});
setGuardians(data?.student?.guardians || []);
setUploadedFiles(data.registration_files || []);
});
setIsLoading(false);
@ -118,12 +107,23 @@ export default function InscriptionFormShared({
}, [studentId]);
useEffect(() => {
fetchTemplatesFromRegistrationFiles(studentId).then((data) => {
setFileTemplates(data);
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);
});
}, []);
useEffect(() => {
if (selectedEstablishmentId) {
// Fetch data for registration payment modes
handleRegistrationPaymentModes();
@ -164,71 +164,89 @@ export default function InscriptionFormShared({
setFormData((prev) => ({ ...prev, [field]: value }));
};
// Gestion du téléversement de fichiers
const handleFileUpload = async (file, fileName) => {
if (!file || !currentTemplateId || !formData.id) {
logger.error('Missing required data for upload');
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;
});
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;
}
const data = new FormData();
data.append('file', file);
data.append('name', fileName);
data.append('template', currentTemplateId);
data.append('register_form', formData.id);
// 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é
try {
const response = await createRegistrationTemplates(data, csrfToken);
if (response) {
setUploadedFiles((prev) => {
const newFiles = prev.filter(
(f) => parseInt(f.template) !== currentTemplateId
);
return [
...newFiles,
{
name: fileName,
template: currentTemplateId,
file: response.file,
},
];
});
return editRegistrationParentFileTemplates(
templateId,
updateData,
csrfToken
)
.then((response) => {
logger.debug('Fichier supprimé avec succès dans la base :', response);
// Rafraîchir les données du formulaire pour avoir les fichiers à jour
if (studentId) {
fetchRegisterForm(studentId).then((data) => {
setUploadedFiles(data.registration_files || []);
});
}
}
} catch (error) {
logger.error('Error uploading file:', error);
}
};
// Vérification si un fichier est déjà uploadé
const isFileUploaded = (templateId) => {
return uploadedFiles.find((template) => template.template === templateId);
};
// 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);
}
// 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
)
);
return response;
})
.catch((error) => {
logger.error(
'Erreur lors de la suppression du fichier dans la base :',
error
);
throw error;
});
};
// Soumission du formulaire
@ -268,90 +286,12 @@ export default function InscriptionFormShared({
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
if (isLoading) return <Loader />;
// Rendu du composant
return (
<div className="max-w-4xl mx-auto p-6">
<div className="mx-auto p-6">
<form onSubmit={handleSubmit} className="space-y-8">
<DjangoCSRFToken csrfToken={csrfToken} />
{/* Page 1 : Informations de l'élève et Responsables */}
@ -368,84 +308,84 @@ export default function InscriptionFormShared({
)}
{/* Pages suivantes : Section Fichiers d'inscription */}
{currentPage > 1 && currentPage <= requiredFileTemplates.length + 1 && (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
{/* Titre du document */}
<div className="mb-4">
<h2 className="text-lg font-semibold text-gray-800">
{requiredFileTemplates[currentPage - 2].name ||
'Document sans nom'}
</h2>
<p className="text-sm text-gray-500">
{requiredFileTemplates[currentPage - 2].description ||
'Aucune description disponible pour ce document.'}
</p>
{currentPage > 1 && currentPage <= schoolFileTemplates.length + 1 && (
<div className="mt-8 mb-4 w-3/5">
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
{/* Titre du document */}
<div className="mb-4">
<h2 className="text-lg font-semibold text-gray-800">
{schoolFileTemplates[currentPage - 2].name ||
'Document sans nom'}
</h2>
<p className="text-sm text-gray-500">
{schoolFileTemplates[currentPage - 2].description ||
'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>
{/* 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>
)}
{/* Dernière page : Section Fichiers parents */}
{currentPage === requiredFileTemplates.length + 2 && (
<>
<FilesToUpload
fileTemplates={fileTemplates.filter(
(template) => !template.is_required
)}
columns={columns}
/>
</>
{currentPage === schoolFileTemplates.length + 2 && (
<FilesToUpload
parentFileTemplates={parentFileTemplates}
uploadedFiles={uploadedFiles}
onFileUpload={handleFileUpload}
onFileDelete={handleDeleteFile}
/>
)}
{/* Boutons de contrôle */}
<div className="flex justify-end space-x-4">
<div className="flex justify-center space-x-4">
<Button
text="Sauvegarder"
onClick={handleSave}
@ -462,7 +402,7 @@ export default function InscriptionFormShared({
}}
/>
)}
{currentPage < requiredFileTemplates.length + 2 && (
{currentPage < schoolFileTemplates.length + 2 && (
<Button
text="Suivant"
onClick={(e) => {
@ -479,57 +419,11 @@ export default function InscriptionFormShared({
name="Next"
/>
)}
{currentPage === requiredFileTemplates.length + 2 && (
{currentPage === schoolFileTemplates.length + 2 && (
<Button type="submit" text="Valider" primary />
)}
</div>
</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>
);
}

View File

@ -1,25 +1,35 @@
'use client';
import React, { useState, useEffect } from 'react';
import { DocusealBuilder } from '@docuseal/react';
import Button from '@/components/Button';
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
import { BASE_URL } from '@/utils/Url';
import { generateToken } from '@/app/actions/registerFileGroupAction';
import {
fetchSchoolFileTemplatesFromRegistrationFiles,
fetchParentFileTemplatesFromRegistrationFiles,
} from '@/app/actions/subscriptionAction';
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({
studentId,
firstName,
lastName,
paymentMode,
paymentSepa,
file,
onAccept,
}) {
const [token, setToken] = useState(null);
const [uploadedFileName, setUploadedFileName] = useState('');
const [selectedFile, setSelectedFile] = useState(null); // Nouvel état pour le fichier sélectionné
const [pdfUrl, setPdfUrl] = useState(`${BASE_URL}/${file}`);
const [isSepa, setIsSepa] = useState(paymentMode === '1'); // Vérifie si le mode de paiement est SEPA
const [currentPage, setCurrentPage] = useState(1); // Gestion des pages
const [isSepa, setIsSepa] = useState(paymentSepa); // Vérifie si le mode de paiement est SEPA
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(() => {
if (isSepa) {
@ -33,38 +43,67 @@ export default function ValidateSubscription({
}
}, [isSepa]);
const handleUpload = (detail) => {
logger.debug('Uploaded file detail:', detail);
setUploadedFileName(detail.name);
};
useEffect(() => {
// Récupérer les fichiers schoolFileTemplates pour l'étudiant
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 fileInput = document.getElementById('fileInput'); // Récupère l'élément input
const file = fileInput?.files[0]; // Récupère le fichier sélectionné
if (!file) {
if (!selectedFile && isSepa) {
logger.error('Aucun fichier sélectionné pour le champ SEPA.');
return;
}
// Ajouter le paramètre fusion dans l'URL
const fusionParam = mergeDocuments ? 'true' : 'false';
const data = {
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
onAccept(data);
};
const handleRefuse = () => {
logger.debug("Dossier refusé pour l'étudiant:", studentId);
// Logique pour refuser l'inscription
const handleToggleMergeDocuments = () => {
// Inverser l'état de mergeDocuments
setMergeDocuments((prevState) => !prevState);
};
const isValidateButtonDisabled = isSepa && !uploadedFileName;
const goToNextPage = () => {
if (currentPage < (isSepa ? 2 : 1)) {
const totalPages =
1 +
schoolFileTemplates.length +
parentFileTemplates.length +
(isSepa ? 1 : 0);
if (currentPage < totalPages) {
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 (
<div className="p-8 space-y-6 bg-gray-50 rounded-lg shadow-lg">
{/* Titre */}
<div className="flex items-center space-x-4">
<div className="bg-emerald-100 p-3 rounded-full shadow-md">
<GraduationCap className="w-8 h-8 text-emerald-600" />
</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>
<div className="space-y-6 p-6">
<SectionHeader
icon={GraduationCap}
title={`Dossier scolaire de ${firstName} ${lastName}`}
description={`Année scolaire ${new Date().getFullYear()}-${new Date().getFullYear() + 1}`}
/>
{/* Contenu principal */}
{currentPage === 1 && (
<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>
)}
<div className="p-6 items-center">{renderContent()}</div>
{currentPage === 2 && isSepa && (
<div className="border p-4 rounded-md shadow-md">
<h3 className="text-lg font-semibold mb-4">
Sélection du mandat de pélèvement SEPA
</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={() => document.getElementById('fileInput').click()} // Ouvre l'explorateur de fichiers au clic
onDragOver={(e) => e.preventDefault()}
onDrop={(e) => {
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>
)}
{/* Option de fusion des documents (affichée uniquement sur la dernière page) */}
{currentPage === totalPages && (
<div className="flex items-center justify-between mt-6">
<span className="text-gray-700">
Fusionner les documents en un seul fichier PDF
</span>
<ToggleSwitch
label="Fusionner"
checked={mergeDocuments}
onChange={handleToggleMergeDocuments} // Appeler la fonction pour inverser l'état
/>
</div>
)}
@ -174,7 +222,7 @@ export default function ValidateSubscription({
className="bg-gray-300 text-gray-700 hover:bg-gray-400 px-6 py-2"
/>
)}
{currentPage < (isSepa ? 2 : 1) && (
{currentPage < totalPages && (
<Button
text="Suivant"
onClick={goToNextPage}
@ -182,7 +230,7 @@ export default function ValidateSubscription({
className="bg-emerald-500 text-white hover:bg-emerald-600 px-6 py-2"
/>
)}
{currentPage === (isSepa ? 2 : 1) && (
{currentPage === totalPages && (
<Button
text="Valider"
onClick={handleAccept}