feat: ajout des documents d'inscription [#20]

This commit is contained in:
Luc SORIGNET
2025-01-25 12:19:30 +01:00
parent 799e1c6717
commit b8ef34a04b
12 changed files with 449 additions and 321 deletions

View File

@ -182,6 +182,10 @@ class Student(models.Model):
return self.birth_date.strftime('%d-%m-%Y')
return None
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}'
class RegistrationForm(models.Model):
"""
Gère le dossier dinscription lié à un élève donné.
@ -201,7 +205,11 @@ class RegistrationForm(models.Model):
last_update = models.DateTimeField(auto_now=True)
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=settings.DOCUMENT_DIR, default="", blank=True)
registration_file = models.FileField(
upload_to=registration_file_path,
null=True,
blank=True
)
associated_rf = models.CharField(max_length=200, default="", blank=True)
def __str__(self):

View File

@ -93,31 +93,57 @@ def getArgFromRequest(_argument, _request):
def merge_files_pdf(filenames, output_filename):
"""
Insère plusieurs fichiers PDF dans un seul document de sortie.
Fusionne plusieurs fichiers PDF en un seul document.
Vérifie l'existence des fichiers sources avant la fusion.
"""
merger = pymupdf.open()
valid_files = []
# Vérifier l'existence des fichiers et ne garder que ceux qui existent
for filename in filenames:
if os.path.exists(filename):
valid_files.append(filename)
# Fusionner les fichiers valides
for filename in valid_files:
merger.insert_file(filename)
# S'assurer que le dossier de destination existe
os.makedirs(os.path.dirname(output_filename), exist_ok=True)
# Sauvegarder le fichier fusionné
merger.save(output_filename)
merger.close()
return output_filename
def rfToPDF(registerForm, filename):
"""
Génère le PDF dun dossier dinscription et lassocie au RegistrationForm.
Génère le PDF d'un dossier d'inscription et l'associe au RegistrationForm.
"""
# Ajout du fichier d'inscriptions
data = {
'pdf_title': "Dossier d'inscription de %s"%registerForm.student.first_name,
'pdf_title': f"Dossier d'inscription de {registerForm.student.first_name}",
'signatureDate': convertToStr(_now(), '%d-%m-%Y'),
'signatureTime': convertToStr(_now(), '%H:%M'),
'student': registerForm.student,
}
PDFFileName = filename
# S'assurer que le dossier parent existe
os.makedirs(os.path.dirname(filename), exist_ok=True)
# Générer le PDF
pdf = renderers.render_to_pdf('pdfs/dossier_inscription.html', data)
pathFichier = Path(filename)
if os.path.exists(str(pathFichier)):
print(f'File exists : {str(pathFichier)}')
os.remove(str(pathFichier))
receipt_file = BytesIO(pdf.content)
registerForm.fichierInscription = File(receipt_file, PDFFileName)
registerForm.fichierInscription.save()
# Écrire le fichier directement
with open(filename, 'wb') as f:
f.write(pdf.content)
# Mettre à jour le champ registration_file du registerForm
with open(filename, 'rb') as f:
registerForm.registration_file.save(
os.path.basename(filename),
File(f),
save=True
)
return registerForm.registration_file

View File

@ -179,20 +179,37 @@ class RegisterFormView(APIView):
registerForm = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=_id)
if _status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW:
# Le parent a complété le dossier d'inscription, il est soumis à validation par l'école
json.dumps(studentForm_data)
try:
# Génération de la fiche d'inscription au format PDF
PDFFileName = "rf_%s_%s.pdf"%(registerForm.student.last_name, registerForm.student.first_name)
path = Path(f"registration_files/dossier_rf_{registerForm.pk}/{PDFFileName}")
registerForm.fichierInscription = util.rfToPDF(registerForm, path)
base_dir = f"registration_files/dossier_rf_{registerForm.pk}"
os.makedirs(base_dir, exist_ok=True)
# Fichier PDF initial
initial_pdf = f"{base_dir}/rf_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf"
registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
registerForm.save()
# Récupération des fichiers d'inscription
fileNames = RegistrationFile.get_files_from_rf(registerForm.pk)
fileNames.insert(0,path)
# Création du fichier PDF Fusionné avec le dossier complet
output_path = f"registration_files/dossier_rf_{registerForm.pk}/dossier_{registerForm.pk}.pdf"
util.merge_files_pdf(fileNames, output_path)
if registerForm.registration_file:
fileNames.insert(0, registerForm.registration_file.path)
# Création du fichier PDF Fusionné
merged_pdf = f"{base_dir}/dossier_complet_{registerForm.pk}.pdf"
util.merge_files_pdf(fileNames, merged_pdf)
# Mise à jour du champ registration_file avec le fichier fusionné
with open(merged_pdf, 'rb') as f:
registerForm.registration_file.save(
os.path.basename(merged_pdf),
File(f),
save=True
)
# Mise à jour de l'automate
updateStateMachine(registerForm, 'saisiDI')
except Exception as e:
return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
elif _status == RegistrationForm.RegistrationFormStatus.RF_VALIDATED:
# L'école a validé le dossier d'inscription
# Mise à jour de l'automate

View File

@ -16,29 +16,10 @@ export default function Page() {
const studentId = searchParams.get('studentId'); // Changé de codeDI à studentId
const [initialData, setInitialData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [formErrors, setFormErrors] = useState({});
const csrfToken = useCsrfToken();
useEffect(() => {
if (useFakeData) {
setInitialData(mockStudent);
} else {
fetchRegisterForm(studentId)
.then(data => {
console.log('Fetched data:', data); // Pour le débogage
const formattedData = {
...data,
guardians: data.guardians || []
};
setInitialData(formattedData);
})
.catch(error => {
console.error('Error fetching student data:', error);
});
}
setIsLoading(false);
}, [studentId]); // Dépendance changée à studentId
const handleSubmit = (data) => {
if (useFakeData) {
@ -64,11 +45,10 @@ export default function Page() {
return (
<InscriptionFormShared
initialData={initialData}
studentId={studentId}
csrfToken={csrfToken}
onSubmit={handleSubmit}
cancelUrl={FE_ADMIN_SUBSCRIPTIONS_URL}
isLoading={isLoading}
errors={formErrors}
/>
);

View File

@ -320,11 +320,8 @@ useEffect(()=>{
};
const createRF = (updatedData) => {
console.log('createRF updatedData:', updatedData);
if (updatedData.selectedGuardians.length !== 0) {
const selectedGuardiansIds = updatedData.selectedGuardians.map(guardianId => guardianId)
const data = {
student: {
last_name: updatedData.studentLastName,
@ -335,66 +332,55 @@ useEffect(()=>{
createRegisterForm(data, csrfToken)
.then(data => {
console.log('Success:', data);
setRegistrationFormsDataPending(prevState => {
if (prevState) {
return [...prevState, data];
}
return [data];
});
setTotalPending(totalPending+1);
// Mise à jour immédiate des données
setRegistrationFormsDataPending(prevState => [...(prevState || []), data]);
setTotalPending(prev => prev + 1);
if (updatedData.autoMail) {
sendConfirmRegisterForm(data.student.id, updatedData.studentLastName, updatedData.studentFirstName);
}
closeModal();
// Forcer le rechargement complet des données
setReloadFetch(true);
})
.catch((error) => {
console.error('Error:', error);
});
}
else {
// Création d'un profil associé à l'adresse mail du responsable saisie
// Le profil est inactif
} else {
const data = {
email: updatedData.guardianEmail,
password: 'Provisoire01!',
username: updatedData.guardianEmail,
is_active: 0, // On rend le profil inactif : impossible de s'y connecter dans la fenêtre du login tant qu'il ne s'est pas inscrit
droit:2 // Profil PARENT
is_active: 0,
droit: 2
}
createProfile(data, csrfToken)
.then(response => {
console.log('Success:', response);
if (response.id) {
let idProfile = response.id;
const data = {
student: {
last_name: updatedData.studentLastName,
first_name: updatedData.studentFirstName,
guardians: [
{
guardians: [{
email: updatedData.guardianEmail,
phone: updatedData.guardianPhone,
associated_profile: idProfile // Association entre le responsable de l'élève et le profil créé par défaut précédemment
}
],
associated_profile: response.id
}],
sibling: []
}
};
createRegisterForm(data, csrfToken)
.then(data => {
console.log('Success:', data);
setRegistrationFormsDataPending(prevState => {
if (prevState && prevState.length > 0) {
return [...prevState, data];
}
return prevState;
});
setTotalPending(totalPending+1);
// Mise à jour immédiate des données
setRegistrationFormsDataPending(prevState => [...(prevState || []), data]);
setTotalPending(prev => prev + 1);
if (updatedData.autoMail) {
sendConfirmRegisterForm(data.student.id, updatedData.studentLastName, updatedData.studentFirstName);
}
closeModal();
// Forcer le rechargement complet des données
setReloadFetch(true);
})
.catch((error) => {
console.error('Error:', error);
@ -402,17 +388,11 @@ useEffect(()=>{
}
})
.catch(error => {
console.error('Error fetching data:', error);
error = error.errorMessage;
console.log(error);
console.error('Error:', error);
});
}
closeModal();
setReloadFetch(true);
}
const columns = [
{ name: t('studentName'), transform: (row) => row.student.last_name },
{ name: t('studentFistName'), transform: (row) => row.student.first_name },
@ -426,11 +406,11 @@ const columns = [
)
},
{ name: t('files'), transform: (row) =>
(row.registerForms != null) &&(
(row.registration_file != null) &&(
<ul>
<li className="flex items-center gap-2">
<FileText size={16} />
<a href={ `${BASE_URL}${row.registerForms}`} target='_blank'>{row.registerForms?.split('/').pop()}</a>
<a href={ `${BASE_URL}${row.registration_file}`} target='_blank'>{row.registration_file?.split('/').pop()}</a>
</li>
</ul>
) },
@ -507,11 +487,11 @@ const columnsSubscribed = [
)
},
{ name: t('files'), transform: (row) =>
(row.registerForm != null) &&(
(row.registration_file != null) &&(
<ul>
<li className="flex items-center gap-2">
<FileText size={16} />
<a href={ `${BASE_URL}${row.registerForm}`} target='_blank'>{row.registerForm?.split('/').pop()}</a>
<a href={ `${BASE_URL}${row.registration_file}`} target='_blank'>{row.registration_file?.split('/').pop()}</a>
</li>
</ul>
) },
@ -677,7 +657,7 @@ const handleFileUpload = ({file, name, is_required, order}) => {
text={(
<>
{t('subscribeFiles')}
<span className="ml-2 text-sm text-gray-400">({totalSubscribed})</span>
<span className="ml-2 text-sm text-gray-400">({fichiers.length})</span>
</>
)}
active={activeTab === 'subscribeFiles'}
@ -735,12 +715,14 @@ const handleFileUpload = ({file, name, is_required, order}) => {
{/*SI STATE == subscribeFiles */}
{activeTab === 'subscribeFiles' && (
<div>
<div className="flex justify-end mb-4">
<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 ml-4"
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>
<Modal
isOpen={isModalOpen}
setIsOpen={setIsModalOpen}

View File

@ -16,60 +16,17 @@ export default function Page() {
const router = useRouter();
const [initialData, setInitialData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const csrfToken = useCsrfToken();
const [currentProfil, setCurrentProfil] = useState("");
const [lastGuardianId, setLastGuardianId] = useState(1);
useEffect(() => {
if (!studentId || !idProfil) {
console.error('Missing studentId or idProfil');
return;
}
if (useFakeData) {
setInitialData(mockStudent);
setLastGuardianId(999);
setIsLoading(false);
} else {
Promise.all([
// Fetch eleve data
fetchRegisterForm(studentId),
// Fetch last guardian ID
fetchLastGuardian()
])
.then(async ([studentData, guardianData]) => {
const formattedData = {
...studentData,
guardians: studentData.guardians || []
};
setInitialData(formattedData);
setLastGuardianId(guardianData.lastid);
let profils = studentData.profils;
const currentProf = profils.find(profil => profil.id === idProfil);
if (currentProf) {
setCurrentProfil(currentProf);
}
})
.catch(error => {
console.error('Error fetching data:', error);
})
.finally(() => {
setIsLoading(false);
});
}
}, [studentId, idProfil]);
const handleSubmit = async (data) => {
if (useFakeData) {
console.log('Fake submit:', data);
return;
}
try {
const result = await editRegisterForm(studentId, data, csrfToken);
console.log('Success:', result);
router.push(FE_PARENTS_HOME_URL);
@ -80,7 +37,7 @@ export default function Page() {
return (
<InscriptionFormShared
initialData={initialData}
studentId={studentId}
csrfToken={csrfToken}
onSubmit={handleSubmit}
cancelUrl={FE_PARENTS_HOME_URL}

View File

@ -41,7 +41,7 @@ export const fetchRegisterForms = (type=PENDING, page='', pageSize='', search =
};
export const fetchRegisterForm = (id) =>{
return fetch(`${BE_SUBSCRIPTION_STUDENT_URL}/${id}`) // Utilisation de studentId au lieu de codeDI
return fetch(`${BE_SUBSCRIPTION_REGISTERFORM_URL}/${id}`) // Utilisation de studentId au lieu de codeDI
.then(requestResponseHandler)
}
export const fetchLastGuardian = () =>{
@ -98,9 +98,15 @@ export const sendRegisterForm = (id) => {
}
export const fetchRegisterFormFileTemplate = () => {
export const fetchRegisterFormFile = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}`
if (id) {
url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${id}`;
}
const request = new Request(
`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}`,
`${url}`,
{
method:'GET',
headers: {
@ -111,18 +117,17 @@ export const fetchRegisterFormFileTemplate = () => {
return fetch(request).then(requestResponseHandler)
};
export const fetchRegisterFormFile = (id) => {
const request = new Request(
`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${id}`,
{
method:'GET',
export const editRegistrationFormFile= (fileId, data, csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${fileId}`, {
method: 'PUT',
body: data,
headers: {
'Content-Type':'application/json'
'X-CSRFToken': csrfToken,
},
credentials: 'include',
})
.then(requestResponseHandler)
}
);
return fetch(request).then(requestResponseHandler)
};
export const createRegistrationFormFile = (data,csrfToken) => {
@ -137,6 +142,33 @@ export const createRegistrationFormFile = (data,csrfToken) => {
.then(requestResponseHandler)
}
export const deleteRegisterFormFile= (fileId,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${fileId}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include',
})
}
export const fetchRegisterFormFileTemplate = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}`;
if(id){
url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${id}`;
}
const request = new Request(
`${url}`,
{
method:'GET',
headers: {
'Content-Type':'application/json'
},
}
);
return fetch(request).then(requestResponseHandler)
};
export const createRegistrationFormFileTemplate = (data,csrfToken) => {
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}`, {

View File

@ -1,9 +1,9 @@
import React, { useState } from 'react';
const AffectationClasseForm = ({ eleve, onSubmit, classes }) => {
const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
const [formData, setFormData] = useState({
classeAssocie_id: eleve.classeAssocie_id || null,
classeAssocie_id: eleve?.classeAssocie_id || null,
});
const handleChange = (e) => {

View File

@ -0,0 +1,33 @@
import React from 'react';
import { Check, Clock } from 'lucide-react';
const FileStatusLabel = ({ status }) => {
const getStatusConfig = () => {
switch (status) {
case 'sent':
return {
label: 'Envoyé',
className: 'bg-green-50 text-green-600',
icon: <Check size={16} className="text-green-600" />
};
case 'pending':
default:
return {
label: 'En attente',
className: 'bg-orange-50 text-orange-600',
icon: <Clock size={16} className="text-orange-600" />
};
}
};
const { label, className, icon } = getStatusConfig();
return (
<div className={`flex items-center justify-center gap-2 px-3 py-1 rounded-md text-sm font-medium ${className}`}>
{icon}
<span>{label}</span>
</div>
);
};
export default FileStatusLabel;

View File

@ -1,3 +1,4 @@
// Import des dépendances nécessaires
import React, { useState, useEffect } from 'react';
import InputText from '@/components/InputText';
import SelectChoice from '@/components/SelectChoice';
@ -6,12 +7,14 @@ import Loader from '@/components/Loader';
import Button from '@/components/Button';
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import Table from '@/components/Table';
import { fetchRegisterFormFileTemplate, createRegistrationFormFile } from '@/app/lib/subscriptionAction';
import { Download, Upload } from 'lucide-react';
import { fetchRegisterFormFileTemplate, createRegistrationFormFile, fetchRegisterForm, deleteRegisterFormFile } from '@/app/lib/subscriptionAction';
import { Download, Upload, Trash2, Eye } from 'lucide-react';
import { BASE_URL } from '@/utils/Url';
import DraggableFileUpload from '@/app/[locale]/admin/subscriptions/components/DraggableFileUpload';
import Modal from '@/components/Modal';
import FileStatusLabel from '@/components/FileStatusLabel';
// Définition des niveaux scolaires disponibles
const levels = [
{ value:'1', label: 'TPS - Très Petite Section'},
{ value:'2', label: 'PS - Petite Section'},
@ -19,32 +22,28 @@ const levels = [
{ value:'4', label: 'GS - Grande Section'},
];
/**
* 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({
initialData,
studentId,
csrfToken,
onSubmit,
cancelUrl,
isLoading = false,
errors = {} // Nouvelle prop pour les erreurs
}) {
// États pour gérer les données du formulaire
const [isLoading, setIsLoading] = useState(true);
const [formData, setFormData] = useState({});
const [formData, setFormData] = useState(() => ({
id: initialData?.id || '',
last_name: initialData?.last_name || '',
first_name: initialData?.first_name || '',
address: initialData?.address || '',
birth_date: initialData?.birth_date || '',
birth_place: initialData?.birth_place || '',
birth_postal_code: initialData?.birth_postal_code || '',
nationality: initialData?.nationality || '',
attending_physician: initialData?.attending_physician || '',
level: initialData?.level || ''
}));
const [guardians, setGuardians] = useState(() =>
initialData?.guardians || []
);
const [guardians, setGuardians] = useState([]);
// États pour la gestion des fichiers
const [uploadedFiles, setUploadedFiles] = useState([]);
const [fileTemplates, setFileTemplates] = useState([]);
const [fileName, setFileName] = useState("");
@ -52,33 +51,48 @@ export default function InscriptionFormShared({
const [showUploadModal, setShowUploadModal] = useState(false);
const [currentTemplateId, setCurrentTemplateId] = useState(null);
// Chargement initial des données
// Mettre à jour les données quand initialData change
useEffect(() => {
if (initialData) {
if (studentId) {
fetchRegisterForm(studentId).then((data) => {
console.log(data);
setFormData({
id: initialData.id || '',
last_name: initialData.last_name || '',
first_name: initialData.first_name || '',
address: initialData.address || '',
birth_date: initialData.birth_date || '',
birth_place: initialData.birth_place || '',
birth_postal_code: initialData.birth_postal_code || '',
nationality: initialData.nationality || '',
attending_physician: initialData.attending_physician || '',
level: initialData.level || ''
id: data?.student?.id || '',
last_name: data?.student?.last_name || '',
first_name: data?.student?.first_name || '',
address: data?.student?.address || '',
birth_date: data?.student?.birth_date || '',
birth_place: data?.student?.birth_place || '',
birth_postal_code: data?.student?.birth_postal_code || '',
nationality: data?.student?.nationality || '',
attending_physician: data?.student?.attending_physician || '',
level: data?.student?.level || ''
});
setGuardians(initialData.guardians || []);
setGuardians(data?.student?.guardians || []);
setUploadedFiles(data.registration_files || []);
});
fetchRegisterFormFileTemplate().then((data) => {
setFileTemplates(data);
});
setIsLoading(false);
}
}, [initialData]);
}, [studentId]);
// Fonctions de gestion du formulaire et des fichiers
const updateFormField = (field, value) => {
setFormData(prev => ({...prev, [field]: value}));
};
// Gestion du téléversement de fichiers
const handleFileUpload = async (file, fileName) => {
if (!file || !currentTemplateId || !formData.id) {
console.error('Missing required data for upload');
return;
}
const data = new FormData();
data.append('file', file);
data.append('name', fileName);
@ -86,16 +100,55 @@ export default function InscriptionFormShared({
data.append('register_form', formData.id);
try {
await createRegistrationFormFile(data, csrfToken);
// Optionnellement, rafraîchir la liste des fichiers
fetchRegisterFormFileTemplate().then((data) => {
setFileTemplates(data);
const response = await createRegistrationFormFile(data, csrfToken);
if (response) {
setUploadedFiles(prev => {
const newFiles = prev.filter(f => parseInt(f.template) !== currentTemplateId);
return [...newFiles, {
name: fileName,
template: currentTemplateId,
file: response.file
}];
});
// Rafraîchir les données du formulaire pour avoir les fichiers à jour
if (studentId) {
fetchRegisterForm(studentId).then((data) => {
setUploadedFiles(data.registration_files || []);
});
}
}
} catch (error) {
console.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 deleteRegisterFormFile(fileToDelete.id, csrfToken);
setUploadedFiles(prev => prev.filter(f => parseInt(f.template) !== templateId));
} catch (error) {
console.error('Error deleting file:', error);
}
};
// Soumission du formulaire
const handleSubmit = (e) => {
e.preventDefault();
const data ={
@ -107,36 +160,70 @@ export default function InscriptionFormShared({
onSubmit(data);
};
// Récupération des messages d'erreur
const getError = (field) => {
return errors?.student?.[field]?.[0];
};
const getGuardianError = (index, field) => {
return errors?.student?.guardians?.[index]?.[field]?.[0];
};
// 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: 'Actions', transform: (row) => (
{ 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">
{row.is_required &&
<button className="text-emerald-500 hover:text-emerald-700" type="button" onClick={() => {
<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>
}
</div>
) },
);
}},
];
// Affichage du loader pendant le chargement
if (isLoading) return <Loader />;
// Rendu du composant
return (
<div className="max-w-4xl mx-auto p-6">
<form onSubmit={handleSubmit} className="space-y-8">
@ -245,6 +332,7 @@ export default function InscriptionFormShared({
</div>
{/* Section Fichiers d'inscription */}
{fileTemplates.length > 0 && (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">Fichiers à remplir</h2>
<Table
@ -256,6 +344,7 @@ export default function InscriptionFormShared({
onPageChange={() => {}}
/>
</div>
)}
{/* Boutons de contrôle */}
<div className="flex justify-end space-x-4">
@ -263,6 +352,7 @@ export default function InscriptionFormShared({
<Button type="submit" text="Valider" primary />
</div>
</form>
{fileTemplates.length > 0 && (
<Modal
isOpen={showUploadModal}
setIsOpen={setShowUploadModal}
@ -273,34 +363,41 @@ export default function InscriptionFormShared({
className="w-full"
fileName={fileName}
onFileSelect={(selectedFile) => {
if (selectedFile) {
setFile(selectedFile);
setFileName(selectedFile.name);
}
}}
>
<input type="hidden" name="template" value={currentTemplateId} />
<input type="hidden" name="register_form" value={formData.id} />
</DraggableFileUpload>
/>
<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={() => {
setShowUploadModal(false);
if (file && fileName) {
handleFileUpload(file, fileName);
setShowUploadModal(false);
setCurrentTemplateId(null);
setFile(null);
setFileName("");
}
}}
primary={true}
disabled={!file || !fileName}
/>
</div>
</>
)}
/>
)}
</div>
);
}

View File

@ -4,6 +4,7 @@ import Button from '@/components/Button';
import React from 'react';
import { useTranslations } from 'next-intl';
import 'react-phone-number-input/style.css'
import { Trash2, Plus } from 'lucide-react';
export default function ResponsableInputFields({guardians, onGuardiansChange, addGuardian, deleteGuardian, errors = []}) {
const t = useTranslations('ResponsableInputFields');
@ -19,10 +20,9 @@ export default function ResponsableInputFields({guardians, onGuardiansChange, ad
<div className='flex justify-between items-center mb-4'>
<h3 className='text-xl font-bold'>{t('responsable')} {index+1}</h3>
{guardians.length > 1 && (
<Button
text={t('delete')}
<Trash2
className="w-5 h-5 text-red-500 cursor-pointer hover:text-red-700 transition-colors"
onClick={() => deleteGuardian(index)}
className="w-32"
/>
)}
</div>
@ -102,13 +102,9 @@ export default function ResponsableInputFields({guardians, onGuardiansChange, ad
))}
<div className="flex justify-center">
<Button
text={t('add_responsible')}
<Plus
className="w-8 h-8 text-green-500 cursor-pointer hover:text-green-700 transition-colors border-2 border-green-500 hover:border-green-700 rounded-full p-1"
onClick={(e) => addGuardian(e)}
primary
icon={<i className="icon profile-add" />}
type="button"
className="w-64"
/>
</div>
</div>

View File

@ -14,7 +14,7 @@ const useCsrfToken = () => {
if (data) {
if(data.csrfToken != token) {
setToken(data.csrfToken);
console.log('------------> CSRF Token reçu:', data.csrfToken);
//console.log('------------> CSRF Token reçu:', data.csrfToken);
}
}
})