mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Ajout des payementPlans dans le formulaire / ajout de la photo
This commit is contained in:
@ -375,8 +375,15 @@ export default function Page({ params: { locale } }) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Préparer les données JSON
|
||||
const jsonData = {
|
||||
status: 7,
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('status', 7);
|
||||
|
||||
// Ajouter les données JSON sous forme de chaîne
|
||||
formData.append('data', JSON.stringify(jsonData));
|
||||
formData.append('sepa_file', file);
|
||||
|
||||
// Appeler l'API pour uploader le fichier SEPA
|
||||
@ -868,7 +875,7 @@ export default function Page({ params: { locale } }) {
|
||||
<img
|
||||
src={`${BASE_URL}${row.student.photo}`}
|
||||
alt={`${row.student.first_name} ${row.student.last_name}`}
|
||||
className="w-10 h-10 object-cover transition-transform duration-200 hover:scale-125 cursor-pointer"
|
||||
className="w-10 h-10 object-cover transition-transform duration-200 hover:scale-125 cursor-pointer rounded-full"
|
||||
/>
|
||||
</a>
|
||||
) : (
|
||||
|
||||
@ -23,10 +23,8 @@ export default function Page() {
|
||||
const csrfToken = useCsrfToken();
|
||||
|
||||
const handleAcceptRF = (data) => {
|
||||
const { status, fusionParam } = data;
|
||||
const formData = new FormData();
|
||||
formData.append('status', status); // Ajoute le statut
|
||||
formData.append('fusion', fusionParam);
|
||||
formData.append('data', JSON.stringify(data));
|
||||
|
||||
setIsLoading(true);
|
||||
// Appeler l'API pour mettre à jour le RF
|
||||
|
||||
@ -5,7 +5,7 @@ import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import { FE_PARENTS_HOME_URL } from '@/utils/Url';
|
||||
import { editRegisterForm } from '@/app/actions/subscriptionAction';
|
||||
import { editRegisterFormWithBinaryFile } from '@/app/actions/subscriptionAction';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
export default function Page() {
|
||||
@ -18,7 +18,11 @@ export default function Page() {
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
try {
|
||||
const result = await editRegisterForm(studentId, data, csrfToken);
|
||||
const result = await editRegisterFormWithBinaryFile(
|
||||
studentId,
|
||||
data,
|
||||
csrfToken
|
||||
);
|
||||
logger.debug('Success:', result);
|
||||
router.push(FE_PARENTS_HOME_URL);
|
||||
} catch (error) {
|
||||
|
||||
@ -66,9 +66,12 @@ export default function ParentHomePage() {
|
||||
return;
|
||||
}
|
||||
|
||||
const jsonData = {
|
||||
status: 3,
|
||||
};
|
||||
const formData = new FormData();
|
||||
formData.append('data', JSON.stringify(jsonData));
|
||||
formData.append('sepa_file', uploadedFile); // Ajoute le fichier SEPA
|
||||
formData.append('status', 3); // Statut à envoyer
|
||||
|
||||
editRegisterFormWithBinaryFile(uploadingStudentId, formData, csrfToken)
|
||||
.then((response) => {
|
||||
|
||||
@ -6,6 +6,9 @@ export default function FileUpload({
|
||||
selectionMessage,
|
||||
onFileSelect,
|
||||
uploadedFileName,
|
||||
existingFile,
|
||||
required,
|
||||
errorMsg,
|
||||
}) {
|
||||
const [localFileName, setLocalFileName] = useState(uploadedFileName || '');
|
||||
const fileInputRef = useRef(null); // Utilisation de useRef pour cibler l'input
|
||||
@ -31,7 +34,10 @@ export default function FileUpload({
|
||||
|
||||
return (
|
||||
<div className="border p-4 rounded-md shadow-md">
|
||||
<h3 className="text-lg font-semibold mb-4">{`${selectionMessage}`}</h3>
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
{`${selectionMessage}`}
|
||||
{required && <span className="text-red-500 ml-1">*</span>}
|
||||
</h3>
|
||||
<div
|
||||
className="border-2 border-dashed border-gray-500 p-6 rounded-lg flex flex-col items-center justify-center cursor-pointer hover:border-emerald-500"
|
||||
onClick={() => fileInputRef.current.click()} // Utilisation de la référence pour ouvrir l'explorateur
|
||||
@ -52,10 +58,24 @@ export default function FileUpload({
|
||||
Déposez votre fichier ici
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
ou cliquez pour sélectionner un fichier PDF
|
||||
ou cliquez pour sélectionner un fichier
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Affichage du fichier existant */}
|
||||
{existingFile && !localFileName && (
|
||||
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
|
||||
<CloudUpload className="w-6 h-6 text-emerald-500" />
|
||||
<p className="text-sm font-medium text-gray-800">
|
||||
<span className="font-semibold">
|
||||
{existingFile.split('/').pop()}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Affichage du fichier sélectionné */}
|
||||
{localFileName && (
|
||||
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
|
||||
<CloudUpload className="w-6 h-6 text-emerald-500" />
|
||||
@ -64,6 +84,9 @@ export default function FileUpload({
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Message d'erreur */}
|
||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -14,6 +14,8 @@ import {
|
||||
import {
|
||||
fetchRegistrationPaymentModes,
|
||||
fetchTuitionPaymentModes,
|
||||
fetchRegistrationPaymentPlans,
|
||||
fetchTuitionPaymentPlans,
|
||||
} from '@/app/actions/schoolAction';
|
||||
import { BASE_URL } from '@/utils/Url';
|
||||
import logger from '@/utils/logger';
|
||||
@ -53,13 +55,14 @@ export default function InscriptionFormShared({
|
||||
nationality: '',
|
||||
attending_physician: '',
|
||||
level: '',
|
||||
registration_payment: '',
|
||||
tuition_payment: '',
|
||||
photo: '',
|
||||
});
|
||||
const [guardians, setGuardians] = 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([]);
|
||||
@ -194,6 +197,12 @@ export default function InscriptionFormShared({
|
||||
|
||||
// Fetch data for tuition payment modes
|
||||
handleTuitionPaymentModes();
|
||||
|
||||
// Fetch data for registration payment plans
|
||||
handleRegistrationPaymentPlans();
|
||||
|
||||
// Fetch data for tuition payment plans
|
||||
handleTuitionnPaymentPlans();
|
||||
}
|
||||
}, [selectedEstablishmentId]);
|
||||
|
||||
@ -223,6 +232,32 @@ export default function InscriptionFormShared({
|
||||
);
|
||||
};
|
||||
|
||||
const handleRegistrationPaymentPlans = () => {
|
||||
fetchRegistrationPaymentPlans(selectedEstablishmentId)
|
||||
.then((data) => {
|
||||
const activePaymentPlans = data.filter(
|
||||
(mode) => mode.is_active === true
|
||||
);
|
||||
setRegistrationPaymentPlans(activePaymentPlans);
|
||||
})
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching registration payment plans:', error)
|
||||
);
|
||||
};
|
||||
|
||||
const handleTuitionnPaymentPlans = () => {
|
||||
fetchTuitionPaymentPlans(selectedEstablishmentId)
|
||||
.then((data) => {
|
||||
const activePaymentPlans = data.filter(
|
||||
(mode) => mode.is_active === true
|
||||
);
|
||||
setTuitionPaymentPlans(activePaymentPlans);
|
||||
})
|
||||
.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.');
|
||||
@ -344,8 +379,12 @@ export default function InscriptionFormShared({
|
||||
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,
|
||||
};
|
||||
|
||||
console.log('jsonData : ', jsonData);
|
||||
|
||||
// Créer un objet FormData
|
||||
const formDataToSend = new FormData();
|
||||
|
||||
@ -450,6 +489,8 @@ export default function InscriptionFormShared({
|
||||
setFormData={setFormData}
|
||||
registrationPaymentModes={registrationPaymentModes}
|
||||
tuitionPaymentModes={tuitionPaymentModes}
|
||||
registrationPaymentPlans={registrationPaymentPlans}
|
||||
tuitionPaymentPlans={tuitionPaymentPlans}
|
||||
errors={errors}
|
||||
setIsPageValid={setIsPage3Valid}
|
||||
/>
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
import RadioList from '@/components/RadioList';
|
||||
|
||||
export default function PaymentMethodSelector({
|
||||
formData,
|
||||
setFormData,
|
||||
registrationPaymentModes,
|
||||
tuitionPaymentModes,
|
||||
registrationPaymentPlans,
|
||||
tuitionPaymentPlans,
|
||||
errors,
|
||||
setIsPageValid,
|
||||
}) {
|
||||
@ -14,6 +17,7 @@ export default function PaymentMethodSelector({
|
||||
(field) => getLocalError(field) !== ''
|
||||
);
|
||||
setIsPageValid(isValid);
|
||||
console.log('formdata : ', formData);
|
||||
}, [formData, setIsPageValid]);
|
||||
|
||||
const paymentModesOptions = [
|
||||
@ -23,19 +27,31 @@ export default function PaymentMethodSelector({
|
||||
{ id: 4, name: 'Espèce' },
|
||||
];
|
||||
|
||||
const paymentPlansOptions = [
|
||||
{ id: 1, name: '1 fois' },
|
||||
{ id: 3, name: '3 fois' },
|
||||
{ id: 10, name: '10 fois' },
|
||||
{ id: 12, name: '12 fois' },
|
||||
];
|
||||
|
||||
const getError = (field) => {
|
||||
return errors?.student?.[field]?.[0];
|
||||
};
|
||||
|
||||
const getLocalError = (field) => {
|
||||
if (
|
||||
// Student Form
|
||||
(field === 'registration_payment' &&
|
||||
(!formData.registration_payment ||
|
||||
String(formData.registration_payment).trim() === '')) ||
|
||||
(field === 'tuition_payment' &&
|
||||
(!formData.tuition_payment ||
|
||||
String(formData.tuition_payment).trim() === ''))
|
||||
String(formData.tuition_payment).trim() === '')) ||
|
||||
(field === 'registration_payment_plan' &&
|
||||
(!formData.registration_payment_plan ||
|
||||
String(formData.registration_payment_plan).trim() === '')) ||
|
||||
(field === 'tuition_payment_plan' &&
|
||||
(!formData.tuition_payment_plan ||
|
||||
String(formData.tuition_payment_plan).trim() === ''))
|
||||
) {
|
||||
return 'Champs requis';
|
||||
}
|
||||
@ -48,13 +64,12 @@ export default function PaymentMethodSelector({
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Frais d'inscription */}
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
{/* Titre */}
|
||||
<h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-2">
|
||||
Frais d'inscription
|
||||
</h2>
|
||||
|
||||
{/* Section d'information */}
|
||||
<div className="mb-6 bg-gray-50 p-4 rounded-lg border border-gray-100">
|
||||
<p className="text-gray-700 text-sm mb-2">
|
||||
<strong className="text-gray-900">Montant :</strong>{' '}
|
||||
@ -80,15 +95,42 @@ export default function PaymentMethodSelector({
|
||||
getLocalError('registration_payment')
|
||||
}
|
||||
/>
|
||||
|
||||
<RadioList
|
||||
sectionLabel="Choisissez une option"
|
||||
required
|
||||
items={paymentPlansOptions
|
||||
.filter((option) =>
|
||||
registrationPaymentPlans.some(
|
||||
(plan) => plan.frequency === option.id
|
||||
)
|
||||
)
|
||||
.map((option) => ({
|
||||
id: option.id,
|
||||
label: option.name,
|
||||
}))}
|
||||
formData={{
|
||||
...formData,
|
||||
registration_payment_plan: parseInt(
|
||||
formData.registration_payment_plan,
|
||||
10
|
||||
), // S'assurer que la valeur est un entier
|
||||
}}
|
||||
handleChange={(e) => {
|
||||
const value = parseInt(e.target.value, 10);
|
||||
onChange('registration_payment_plan', value); // Convertir la valeur en entier
|
||||
}}
|
||||
fieldName="registration_payment_plan"
|
||||
className="mt-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Frais de scolarité */}
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200 mt-12">
|
||||
{/* Titre */}
|
||||
<h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-2">
|
||||
Frais de scolarité
|
||||
</h2>
|
||||
|
||||
{/* Section d'information */}
|
||||
<div className="mb-6 bg-gray-50 p-4 rounded-lg border border-gray-100">
|
||||
<p className="text-gray-700 text-sm mb-2">
|
||||
<strong className="text-gray-900">Montant :</strong>{' '}
|
||||
@ -113,6 +155,26 @@ export default function PaymentMethodSelector({
|
||||
getError('tuition_payment') || getLocalError('tuition_payment')
|
||||
}
|
||||
/>
|
||||
|
||||
<RadioList
|
||||
sectionLabel="Choisissez une option"
|
||||
items={paymentPlansOptions
|
||||
.filter((option) =>
|
||||
tuitionPaymentPlans.some((plan) => plan.frequency === option.id)
|
||||
)
|
||||
.map((option) => ({
|
||||
id: option.id,
|
||||
label: option.name,
|
||||
}))}
|
||||
formData={formData}
|
||||
handleChange={(e) => onChange('tuition_payment_plan', e.target.value)}
|
||||
fieldName="tuition_payment_plan"
|
||||
className="mt-4"
|
||||
errorMsg={
|
||||
getError('tuition_payment_plan') ||
|
||||
getLocalError('tuition_payment_plan')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -51,6 +51,8 @@ export default function StudentInfoForm({
|
||||
level: data?.student?.level || '',
|
||||
registration_payment: data?.registration_payment || '',
|
||||
tuition_payment: data?.tuition_payment || '',
|
||||
registration_payment_plan: data?.registration_payment_plan || '',
|
||||
tuition_payment_plan: data?.tuition_payment_plan || '',
|
||||
totalRegistrationFees: data?.totalRegistrationFees,
|
||||
totalTuitionFees: data?.totalTuitionFees,
|
||||
});
|
||||
@ -96,7 +98,8 @@ export default function StudentInfoForm({
|
||||
(!formData.attending_physician ||
|
||||
formData.attending_physician.trim() === '')) ||
|
||||
(field === 'level' &&
|
||||
(!formData.level || String(formData.level).trim() === ''))
|
||||
(!formData.level || String(formData.level).trim() === '')) ||
|
||||
(field === 'photo' && !formData.photo)
|
||||
) {
|
||||
return 'Champs requis';
|
||||
}
|
||||
@ -230,6 +233,9 @@ export default function StudentInfoForm({
|
||||
<FileUpload
|
||||
selectionMessage="Sélectionnez une photo à uploader"
|
||||
onFileSelect={(file) => handlePhotoUpload(file)}
|
||||
existingFile={formData.photo}
|
||||
required
|
||||
errorMsg={getError('photo') || getLocalError('photo')}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -7,9 +7,17 @@ const RadioList = ({
|
||||
fieldName,
|
||||
icon: Icon,
|
||||
className,
|
||||
sectionLabel,
|
||||
required,
|
||||
}) => {
|
||||
return (
|
||||
<div className={`mb-4 ${className}`}>
|
||||
{sectionLabel && (
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-2">
|
||||
{sectionLabel}
|
||||
{required && <span className="text-red-500 ml-1">*</span>}
|
||||
</h3>
|
||||
)}
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{items.map((item) => (
|
||||
<div key={item.id} className="flex items-center">
|
||||
|
||||
Reference in New Issue
Block a user