mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Ajout de la sélection des modes de paiements / refactoring de
l'automate
This commit is contained in:
@ -4,27 +4,21 @@ import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared';
|
||||
import { FE_ADMIN_SUBSCRIPTIONS_URL } from '@/utils/Url';
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import { editRegisterForm } from '@/app/actions/subscriptionAction';
|
||||
import logger from '@/utils/logger';
|
||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const idProfil = searchParams.get('id');
|
||||
const studentId = searchParams.get('studentId'); // Changé de codeDI à studentId
|
||||
|
||||
const [initialData, setInitialData] = useState(null);
|
||||
|
||||
const [formErrors, setFormErrors] = useState({});
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
|
||||
|
||||
const handleSubmit = (data) => {
|
||||
if (useFakeData) {
|
||||
logger.debug('Fake submit:', data);
|
||||
return;
|
||||
}
|
||||
|
||||
editRegisterForm(studentId, data, csrfToken)
|
||||
|
||||
@ -46,6 +40,7 @@ export default function Page() {
|
||||
<InscriptionFormShared
|
||||
studentId={studentId}
|
||||
csrfToken={csrfToken}
|
||||
selectedEstablishmentId={selectedEstablishmentId}
|
||||
onSubmit={handleSubmit}
|
||||
cancelUrl={FE_ADMIN_SUBSCRIPTIONS_URL}
|
||||
errors={formErrors}
|
||||
|
||||
@ -10,7 +10,7 @@ import Loader from '@/components/Loader';
|
||||
import AlertWithModal from '@/components/AlertWithModal';
|
||||
import DropdownMenu from "@/components/DropdownMenu";
|
||||
import { formatPhoneNumber } from '@/utils/Telephone';
|
||||
import { MoreVertical, Send, Edit, Trash2, FileText, CheckCircle, Plus, TicketX } from 'lucide-react';
|
||||
import { MoreVertical, Send, Edit, Archive, FileText, CircleCheck, Plus, XCircle } from 'lucide-react';
|
||||
import Modal from '@/components/Modal';
|
||||
import InscriptionForm from '@/components/Inscription/InscriptionForm'
|
||||
import AffectationClasseForm from '@/components/AffectationClasseForm'
|
||||
@ -68,7 +68,7 @@ export default function Page({ params: { locale } }) {
|
||||
const [totalPending, setTotalPending] = useState(0);
|
||||
const [totalSubscribed, setTotalSubscribed] = useState(0);
|
||||
const [totalArchives, setTotalArchives] = useState(0);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(5); // Définir le nombre d'éléments par page
|
||||
const [itemsPerPage, setItemsPerPage] = useState(10); // Définir le nombre d'éléments par page
|
||||
|
||||
const [templateMasters, setTemplateMasters] = useState([]);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -577,39 +577,39 @@ useEffect(()=>{
|
||||
const actions = {
|
||||
1: [
|
||||
{
|
||||
icon: <Send className="w-5 h-5 text-blue-500 hover:text-blue-700" />,
|
||||
onClick: () => sendConfirmRegisterForm(row.student.id, row.student.last_name, row.student.first_name),
|
||||
icon: <Edit className="w-5 h-5 text-blue-500 hover:text-blue-700" />,
|
||||
onClick: () => window.location.href = `${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}&id=1`,
|
||||
},
|
||||
{
|
||||
icon: <Edit className="w-5 h-5 text-green-500 hover:text-green-700" />,
|
||||
onClick: () => window.location.href = `${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}&id=1`,
|
||||
icon: <Send className="w-5 h-5 text-green-500 hover:text-green-700" />,
|
||||
onClick: () => sendConfirmRegisterForm(row.student.id, row.student.last_name, row.student.first_name),
|
||||
},
|
||||
],
|
||||
2: [
|
||||
{
|
||||
icon: <Edit className="w-5 h-5 text-green-500 hover:text-green-700" />,
|
||||
icon: <Edit className="w-5 h-5 text-blue-500 hover:text-blue-700" />,
|
||||
onClick: () => window.location.href = `${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}&id=1`,
|
||||
},
|
||||
],
|
||||
3: [
|
||||
{
|
||||
icon: <CheckCircle className="w-5 h-5 text-emerald-500 hover:text-emerald-700" />,
|
||||
icon: <CircleCheck className="w-5 h-5 text-green-500 hover:text-green-700" />,
|
||||
onClick: () => openModalAssociationEleve(row.student),
|
||||
},
|
||||
{
|
||||
icon: <TicketX className="w-5 h-5 text-red-500 hover:text-red-700" />,
|
||||
icon: <XCircle className="w-5 h-5 text-red-500 hover:text-red-700" />,
|
||||
onClick: () => refuseRegistrationForm(row.student.id, row.student.last_name, row.student.first_name, row.student.guardians[0].associated_profile_email),
|
||||
},
|
||||
],
|
||||
5: [
|
||||
{
|
||||
icon: <CheckCircle className="w-5 h-5 text-emerald-500 hover:text-emerald-700" />,
|
||||
icon: <CircleCheck className="w-5 h-5 text-green-500 hover:text-green-700" />,
|
||||
onClick: () => openModalAssociationEleve(row.student),
|
||||
},
|
||||
],
|
||||
default: [
|
||||
{
|
||||
icon: <Trash2 className="w-5 h-5 text-red-500 hover:text-red-700" />,
|
||||
icon: <Archive className="w-5 h-5 text-gray-500 hover:text-gray-700" />,
|
||||
onClick: () => archiveFicheInscription(row.student.id, row.student.last_name, row.student.first_name),
|
||||
},
|
||||
],
|
||||
@ -703,14 +703,14 @@ const columnsSubscribed = [
|
||||
items={[
|
||||
{ label: (
|
||||
<>
|
||||
<CheckCircle size={16} className="mr-2" /> Rattacher
|
||||
<CircleCheck size={16} className="mr-2" /> Rattacher
|
||||
</>
|
||||
),
|
||||
onClick: () => openModalAssociationEleve(row.student)
|
||||
},
|
||||
{ label: (
|
||||
<>
|
||||
<Trash2 size={16} className="mr-2 text-red-700" /> Archiver
|
||||
<Archive size={16} className="mr-2 text-red-700" /> Archiver
|
||||
</>
|
||||
),
|
||||
onClick: () => archiveFicheInscription(row.student.id, row.student.last_name, row.student.first_name),
|
||||
|
||||
@ -6,7 +6,7 @@ const FileStatusLabel = ({ status }) => {
|
||||
switch (status) {
|
||||
case 'sent':
|
||||
return {
|
||||
label: 'Envoyé',
|
||||
label: 'En attente',
|
||||
className: 'bg-green-50 text-green-600',
|
||||
icon: <Check size={16} className="text-green-600" />
|
||||
};
|
||||
|
||||
@ -1,20 +1,17 @@
|
||||
// Import des dépendances nécessaires
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import InputText from '@/components/InputText';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
import ResponsableInputFields from '@/components/Inscription/ResponsableInputFields';
|
||||
import Loader from '@/components/Loader';
|
||||
import Button from '@/components/Button';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||
import Table from '@/components/Table';
|
||||
import { fetchRegisterForm, fetchTemplatesFromRegistrationFiles } from '@/app/actions/subscriptionAction';
|
||||
import { fetchRegistrationFileFromGroup,
|
||||
fetchRegistrationTemplateMaster,
|
||||
downloadTemplate,
|
||||
createRegistrationTemplates,
|
||||
editRegistrationTemplates,
|
||||
deleteRegistrationTemplates
|
||||
} from '@/app/actions/registerFileGroupAction';
|
||||
import { downloadTemplate,
|
||||
createRegistrationTemplates,
|
||||
editRegistrationTemplates,
|
||||
deleteRegistrationTemplates
|
||||
} 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';
|
||||
@ -22,10 +19,8 @@ import Modal from '@/components/Modal';
|
||||
import FileStatusLabel from '@/components/FileStatusLabel';
|
||||
import logger from '@/utils/logger';
|
||||
import StudentInfoForm, { validateStudentInfo } from '@/components/Inscription/StudentInfoForm';
|
||||
import FilesToSign from '@/components/Inscription/FilesToSign';
|
||||
import FilesToUpload from '@/components/Inscription/FilesToUpload';
|
||||
import { DocusealForm } from '@docuseal/react';
|
||||
import { ESTABLISHMENT_ID } from '@/utils/Url';
|
||||
|
||||
/**
|
||||
* Composant de formulaire d'inscription partagé
|
||||
@ -38,6 +33,7 @@ import { ESTABLISHMENT_ID } from '@/utils/Url';
|
||||
export default function InscriptionFormShared({
|
||||
studentId,
|
||||
csrfToken,
|
||||
selectedEstablishmentId,
|
||||
onSubmit,
|
||||
cancelUrl,
|
||||
errors = {} // Nouvelle prop pour les erreurs
|
||||
@ -54,11 +50,16 @@ export default function InscriptionFormShared({
|
||||
birth_postal_code: '',
|
||||
nationality: '',
|
||||
attending_physician: '',
|
||||
level: ''
|
||||
level: '',
|
||||
registration_payment: '',
|
||||
tuition_payment: ''
|
||||
});
|
||||
|
||||
const [guardians, setGuardians] = useState([]);
|
||||
|
||||
const [registrationPaymentModes, setRegistrationPaymentModes] = useState([]);
|
||||
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
|
||||
|
||||
// États pour la gestion des fichiers
|
||||
const [uploadedFiles, setUploadedFiles] = useState([]);
|
||||
const [fileTemplates, setFileTemplates] = useState([]);
|
||||
@ -95,7 +96,11 @@ export default function InscriptionFormShared({
|
||||
birth_postal_code: data?.student?.birth_postal_code || '',
|
||||
nationality: data?.student?.nationality || '',
|
||||
attending_physician: data?.student?.attending_physician || '',
|
||||
level: data?.student?.level || ''
|
||||
level: data?.student?.level || '',
|
||||
registration_payment: data?.registration_payment || '',
|
||||
tuition_payment: data?.tuition_payment || '',
|
||||
totalRegistrationFees: data?.totalRegistrationFees,
|
||||
totalTuitionFees: data?.totalTuitionFees,
|
||||
});
|
||||
setGuardians(data?.student?.guardians || []);
|
||||
setUploadedFiles(data.registration_files || []);
|
||||
@ -111,6 +116,34 @@ export default function InscriptionFormShared({
|
||||
})
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEstablishmentId) {
|
||||
// Fetch data for registration payment modes
|
||||
handleRegistrationPaymentModes();
|
||||
|
||||
// Fetch data for tuition payment modes
|
||||
handleTuitionPaymentModes();
|
||||
}
|
||||
}, [selectedEstablishmentId]);
|
||||
|
||||
const handleRegistrationPaymentModes = () => {
|
||||
fetchRegistrationPaymentModes(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
const activePaymentModes = data.filter(mode => mode.is_active === true);
|
||||
setRegistrationPaymentModes(activePaymentModes);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching registration payment modes:', error));
|
||||
};
|
||||
|
||||
const handleTuitionPaymentModes = () => {
|
||||
fetchTuitionPaymentModes(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
const activePaymentModes = data.filter(mode => mode.is_active === true);
|
||||
setTuitionPaymentModes(activePaymentModes);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching tuition payment modes:', error));
|
||||
};
|
||||
|
||||
// Fonctions de gestion du formulaire et des fichiers
|
||||
const updateFormField = (field, value) => {
|
||||
setFormData(prev => ({...prev, [field]: value}));
|
||||
@ -186,8 +219,10 @@ export default function InscriptionFormShared({
|
||||
...formData,
|
||||
guardians
|
||||
},
|
||||
establishment: 1,
|
||||
status:3
|
||||
establishment: selectedEstablishmentId,
|
||||
status:3,
|
||||
tuition_payment:formData.tuition_payment,
|
||||
registration_payment:formData.registration_payment
|
||||
}
|
||||
onSubmit(data);
|
||||
};
|
||||
@ -200,16 +235,11 @@ export default function InscriptionFormShared({
|
||||
...formData,
|
||||
guardians
|
||||
},
|
||||
establishment: 1
|
||||
establishment: selectedEstablishmentId
|
||||
}
|
||||
onSubmit(data);
|
||||
};
|
||||
|
||||
// Récupération des messages d'erreur
|
||||
const getError = (field) => {
|
||||
return errors?.student?.[field]?.[0];
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
setCurrentPage(currentPage + 1);
|
||||
};
|
||||
@ -290,6 +320,8 @@ export default function InscriptionFormShared({
|
||||
updateFormField={updateFormField}
|
||||
guardians={guardians}
|
||||
setGuardians={setGuardians}
|
||||
registrationPaymentModes={registrationPaymentModes}
|
||||
tuitionPaymentModes={tuitionPaymentModes}
|
||||
errors={errors}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
|
||||
export default function PaymentMethodSelector({ formData, title, name, updateFormField, selected, paymentModes, paymentModesOptions, amount, getError }) {
|
||||
console.log(paymentModes)
|
||||
console.log(selected)
|
||||
return (
|
||||
<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">{title}</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> {amount} €
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<SelectChoice
|
||||
name={name}
|
||||
label="Mode de Paiement"
|
||||
placeHolder="Sélectionner un mode de paiement"
|
||||
selected={selected || ''}
|
||||
callback={(e) => updateFormField(name, e.target.value)}
|
||||
choices={paymentModes.map((mode) => ({
|
||||
value: mode.mode,
|
||||
label: paymentModesOptions.find(option => option.id === mode.mode)?.name || 'Mode inconnu'
|
||||
}))}
|
||||
required
|
||||
errorMsg={getError('payment_method')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import InputText from '@/components/InputText';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
import ResponsableInputFields from '@/components/Inscription/ResponsableInputFields';
|
||||
import PaymentMethodSelector from '@/components/Inscription/PaymentMethodSelector';
|
||||
|
||||
const levels = [
|
||||
{ value:'1', label: 'TPS - Très Petite Section'},
|
||||
@ -10,6 +11,13 @@ const levels = [
|
||||
{ value:'4', label: 'GS - Grande Section'},
|
||||
];
|
||||
|
||||
const paymentModesOptions = [
|
||||
{ id: 1, name: 'Prélèvement SEPA' },
|
||||
{ id: 2, name: 'Virement' },
|
||||
{ id: 3, name: 'Chèque' },
|
||||
{ id: 4, name: 'Espèce' },
|
||||
];
|
||||
|
||||
// Fonction de validation pour vérifier les champs requis
|
||||
export function validateStudentInfo(formData) {
|
||||
const requiredFields = [
|
||||
@ -32,7 +40,7 @@ export function validateStudentInfo(formData) {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
export default function StudentInfoForm({ formData, updateFormField, guardians, setGuardians, errors }) {
|
||||
export default function StudentInfoForm({ formData, updateFormField, guardians, setGuardians, registrationPaymentModes, tuitionPaymentModes, errors }) {
|
||||
const getError = (field) => {
|
||||
return errors?.student?.[field]?.[0];
|
||||
};
|
||||
@ -143,6 +151,30 @@ export default function StudentInfoForm({ formData, updateFormField, guardians,
|
||||
errors={errors?.student?.guardians || []}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PaymentMethodSelector
|
||||
formData={formData}
|
||||
title="Frais d'inscription"
|
||||
name="registration_payment"
|
||||
updateFormField={updateFormField}
|
||||
selected={formData.registration_payment}
|
||||
paymentModes={registrationPaymentModes}
|
||||
paymentModesOptions={paymentModesOptions}
|
||||
amount={formData.totalRegistrationFees}
|
||||
getError={getError}
|
||||
/>
|
||||
|
||||
<PaymentMethodSelector
|
||||
formData={formData}
|
||||
title="Frais de scolarité"
|
||||
name="tuition_payment"
|
||||
updateFormField={updateFormField}
|
||||
selected={formData.tuition_payment}
|
||||
paymentModes={tuitionPaymentModes}
|
||||
paymentModesOptions={paymentModesOptions}
|
||||
amount={formData.totalTuitionFees}
|
||||
getError={getError}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -5,9 +5,9 @@ import DropdownMenu from './DropdownMenu';
|
||||
const StatusLabel = ({ status, onChange, showDropdown = true }) => {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const statusOptions = [
|
||||
{ value: 1, label: 'Créé' },
|
||||
{ value: 2, label: 'Envoyé' },
|
||||
{ value: 3, label: 'En Validation' },
|
||||
{ value: 1, label: 'A envoyer' },
|
||||
{ value: 2, label: 'En attente' },
|
||||
{ value: 3, label: 'Signé' },
|
||||
{ value: 4, label: 'A Relancer' },
|
||||
{ value: 5, label: 'Validé' },
|
||||
{ value: 6, label: 'Archivé' },
|
||||
|
||||
@ -4,7 +4,6 @@ import DiscountsSection from '@/components/Structure/Tarification/DiscountsSecti
|
||||
import PaymentPlanSelector from '@/components/PaymentPlanSelector';
|
||||
import PaymentModeSelector from '@/components/PaymentModeSelector';
|
||||
import { BE_SCHOOL_FEES_URL, BE_SCHOOL_DISCOUNTS_URL, BE_SCHOOL_PAYMENT_PLANS_URL, BE_SCHOOL_PAYMENT_MODES_URL } from '@/utils/Url';
|
||||
import { set } from 'lodash';
|
||||
|
||||
const FeesManagement = ({ registrationDiscounts,
|
||||
setRegistrationDiscounts,
|
||||
|
||||
@ -16,7 +16,6 @@ export const EstablishmentProvider = ({ children }) => {
|
||||
|
||||
if (session && session.user) {
|
||||
setUser(session.user);
|
||||
console.log("getSession");
|
||||
const userEstablishments = session.user.roles.map(role => ({
|
||||
id: role.establishment__id,
|
||||
name: role.establishment__name,
|
||||
|
||||
Reference in New Issue
Block a user