mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-03 16:51:26 +00:00
feat(frontend): fusion liste des frais et message compte existant [#NEWTS-9]
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { DollarSign } from 'lucide-react';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
const paymentModesOptions = [
|
||||
{ id: 1, name: 'Prélèvement SEPA' },
|
||||
@ -9,8 +10,14 @@ const paymentModesOptions = [
|
||||
{ id: 4, name: 'Espèce' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Affiche les modes de paiement communs aux deux types de frais.
|
||||
* Quand `allPaymentModes` est fourni (mode unifié), un mode activé est créé
|
||||
* pour les deux types (inscription 0 ET scolarité 1).
|
||||
*/
|
||||
const PaymentModeSelector = ({
|
||||
paymentModes,
|
||||
allPaymentModes,
|
||||
setPaymentModes,
|
||||
handleCreate,
|
||||
handleDelete,
|
||||
@ -19,23 +26,45 @@ const PaymentModeSelector = ({
|
||||
const [activePaymentModes, setActivePaymentModes] = useState([]);
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
|
||||
const modes = useMemo(
|
||||
() =>
|
||||
Array.isArray(allPaymentModes)
|
||||
? allPaymentModes
|
||||
: Array.isArray(paymentModes)
|
||||
? paymentModes
|
||||
: [],
|
||||
[allPaymentModes, paymentModes]
|
||||
);
|
||||
const unified = !!allPaymentModes;
|
||||
|
||||
useEffect(() => {
|
||||
const activeModes = paymentModes.map((mode) => mode.mode);
|
||||
const activeModes = [...new Set(modes.map((mode) => mode.mode))];
|
||||
setActivePaymentModes(activeModes);
|
||||
}, [paymentModes]);
|
||||
}, [modes]);
|
||||
|
||||
const handleModeToggle = (modeId) => {
|
||||
const updatedMode = paymentModes.find((mode) => mode.mode === modeId);
|
||||
const isActive = !!updatedMode;
|
||||
|
||||
const isActive = activePaymentModes.includes(modeId);
|
||||
if (!isActive) {
|
||||
handleCreate({
|
||||
mode: modeId,
|
||||
type,
|
||||
establishment: selectedEstablishmentId,
|
||||
});
|
||||
if (unified) {
|
||||
[0, 1].forEach((t) =>
|
||||
handleCreate({
|
||||
mode: modeId,
|
||||
type: t,
|
||||
establishment: selectedEstablishmentId,
|
||||
}).catch((e) => logger.error(e))
|
||||
);
|
||||
} else {
|
||||
handleCreate({
|
||||
mode: modeId,
|
||||
type,
|
||||
establishment: selectedEstablishmentId,
|
||||
}).catch((e) => logger.error(e));
|
||||
}
|
||||
} else {
|
||||
handleDelete(updatedMode.id, null);
|
||||
const toDelete = modes.filter((m) => m.mode === modeId);
|
||||
toDelete.forEach((m) =>
|
||||
handleDelete(m.id, null).catch((e) => logger.error(e))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { Calendar } from 'lucide-react';
|
||||
import Table from '@/components/Table';
|
||||
import Popup from '@/components/Popup';
|
||||
@ -13,8 +13,22 @@ const paymentPlansOptions = [
|
||||
{ id: 4, name: '12 fois', frequency: 12 },
|
||||
];
|
||||
|
||||
/**
|
||||
* Affiche les plans de paiement communs aux deux types de frais.
|
||||
* Quand `allPaymentPlans` est fourni (mode unifié), un plan coché est créé pour
|
||||
* les deux types (inscription 0 ET scolarité 1) en même temps.
|
||||
*
|
||||
* Props (mode unifié) :
|
||||
* allPaymentPlans : [{plan_type, type, ...}, ...] - liste combinée des deux types
|
||||
* handleCreate : (data) => Promise - avec type et establishment déjà présent dans data
|
||||
* handleDelete : (id) => Promise
|
||||
*
|
||||
* Props (mode legacy) :
|
||||
* paymentPlans, handleCreate, handleDelete, type
|
||||
*/
|
||||
const PaymentPlanSelector = ({
|
||||
paymentPlans,
|
||||
allPaymentPlans,
|
||||
handleCreate,
|
||||
handleDelete,
|
||||
type,
|
||||
@ -24,38 +38,63 @@ const PaymentPlanSelector = ({
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
const [checkedPlans, setCheckedPlans] = useState([]);
|
||||
|
||||
// Vérifie si un plan existe pour ce type (par id)
|
||||
const plans = useMemo(
|
||||
() =>
|
||||
Array.isArray(allPaymentPlans)
|
||||
? allPaymentPlans
|
||||
: Array.isArray(paymentPlans)
|
||||
? paymentPlans
|
||||
: [],
|
||||
[allPaymentPlans, paymentPlans]
|
||||
);
|
||||
const unified = !!allPaymentPlans;
|
||||
|
||||
// Un plan est coché si au moins un enregistrement existe pour cette option
|
||||
const isChecked = (planOption) => checkedPlans.includes(planOption.id);
|
||||
|
||||
// Création ou suppression du plan
|
||||
const handlePlanToggle = (planOption) => {
|
||||
const updatedPlan = paymentPlans.find(
|
||||
(plan) => plan.plan_type === planOption.id
|
||||
);
|
||||
if (isChecked(planOption)) {
|
||||
// Supprimer tous les enregistrements correspondant à cette option (les deux types en mode unifié)
|
||||
const toDelete = plans.filter(
|
||||
(p) =>
|
||||
(typeof p.plan_type === 'object' ? p.plan_type.id : p.plan_type) ===
|
||||
planOption.id
|
||||
);
|
||||
setCheckedPlans((prev) => prev.filter((id) => id !== planOption.id));
|
||||
handleDelete(updatedPlan.id, null);
|
||||
toDelete.forEach((p) =>
|
||||
handleDelete(p.id, null).catch((e) => logger.error(e))
|
||||
);
|
||||
} else {
|
||||
setCheckedPlans((prev) => [...prev, planOption.id]);
|
||||
handleCreate({
|
||||
plan_type: planOption.id,
|
||||
type,
|
||||
establishment: selectedEstablishmentId,
|
||||
});
|
||||
if (unified) {
|
||||
// Créer pour inscription (0) et scolarité (1)
|
||||
[0, 1].forEach((t) =>
|
||||
handleCreate({
|
||||
plan_type: planOption.id,
|
||||
type: t,
|
||||
establishment: selectedEstablishmentId,
|
||||
}).catch((e) => logger.error(e))
|
||||
);
|
||||
} else {
|
||||
handleCreate({
|
||||
plan_type: planOption.id,
|
||||
type,
|
||||
establishment: selectedEstablishmentId,
|
||||
}).catch((e) => logger.error(e));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (paymentPlans && paymentPlans.length > 0) {
|
||||
setCheckedPlans(
|
||||
paymentPlans.map((plan) =>
|
||||
typeof plan.plan_type === 'object'
|
||||
? plan.plan_type.id
|
||||
: plan.plan_type
|
||||
)
|
||||
if (plans.length > 0) {
|
||||
const ids = plans.map((plan) =>
|
||||
typeof plan.plan_type === 'object' ? plan.plan_type.id : plan.plan_type
|
||||
);
|
||||
setCheckedPlans([...new Set(ids)]);
|
||||
} else {
|
||||
setCheckedPlans([]);
|
||||
}
|
||||
}, [paymentPlans]);
|
||||
}, [plans]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
|
||||
@ -9,6 +9,8 @@ import SectionHeader from '@/components/SectionHeader';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import AlertMessage from '@/components/AlertMessage';
|
||||
|
||||
const DISCOUNT_TYPE_LABELS = { 0: 'Inscription', 1: 'Scolarité' };
|
||||
|
||||
const DiscountsSection = ({
|
||||
discounts,
|
||||
setDiscounts,
|
||||
@ -16,6 +18,7 @@ const DiscountsSection = ({
|
||||
handleEdit,
|
||||
handleDelete,
|
||||
type,
|
||||
unified = false,
|
||||
subscriptionMode = false,
|
||||
selectedDiscounts,
|
||||
handleDiscountSelection,
|
||||
@ -39,7 +42,7 @@ const DiscountsSection = ({
|
||||
amount: '',
|
||||
description: '',
|
||||
discount_type: 0,
|
||||
type: type,
|
||||
type: unified ? 0 : type,
|
||||
establishment: selectedEstablishmentId,
|
||||
});
|
||||
};
|
||||
@ -219,6 +222,21 @@ const DiscountsSection = ({
|
||||
handleChange,
|
||||
'Description'
|
||||
);
|
||||
case 'TYPE':
|
||||
return (
|
||||
<select
|
||||
className="border rounded px-2 py-1 text-sm"
|
||||
value={currentData.type}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value, 10);
|
||||
if (editingDiscount) setFormData((p) => ({ ...p, type: val }));
|
||||
else setNewDiscount((p) => ({ ...p, type: val }));
|
||||
}}
|
||||
>
|
||||
<option value={0}>Inscription</option>
|
||||
<option value={1}>Scolarité</option>
|
||||
</select>
|
||||
);
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
@ -259,6 +277,18 @@ const DiscountsSection = ({
|
||||
return discount.description;
|
||||
case 'MISE A JOUR':
|
||||
return discount.updated_at_formatted;
|
||||
case 'TYPE':
|
||||
return (
|
||||
<span
|
||||
className={`text-xs font-semibold px-2 py-1 rounded-full ${
|
||||
discount.type === 0
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-purple-100 text-purple-700'
|
||||
}`}
|
||||
>
|
||||
{DISCOUNT_TYPE_LABELS[discount.type]}
|
||||
</span>
|
||||
);
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
@ -335,34 +365,25 @@ const DiscountsSection = ({
|
||||
{ name: 'LIBELLE', label: 'Libellé' },
|
||||
{ name: 'DESCRIPTION', label: 'Description' },
|
||||
{ name: 'REMISE', label: 'Remise' },
|
||||
...(unified ? [{ name: 'TYPE', label: 'Type' }] : []),
|
||||
{ name: '', label: 'Sélection' },
|
||||
]
|
||||
: [
|
||||
{ name: 'LIBELLE', label: 'Libellé' },
|
||||
{ name: 'REMISE', label: 'Remise' },
|
||||
{ name: 'DESCRIPTION', label: 'Description' },
|
||||
...(unified ? [{ name: 'TYPE', label: 'Type' }] : []),
|
||||
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
|
||||
{ name: 'ACTIONS', label: 'Actions' },
|
||||
];
|
||||
|
||||
let emptyMessage;
|
||||
if (type === 0) {
|
||||
emptyMessage = (
|
||||
<AlertMessage
|
||||
type="info"
|
||||
title="Aucune réduction enregistrée"
|
||||
message="Aucune réduction sur les frais d'inscription n'a été enregistrée"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
emptyMessage = (
|
||||
<AlertMessage
|
||||
type="info"
|
||||
title="Aucune réduction enregistrée"
|
||||
message="Aucune réduction sur les frais de scolarité n'a été enregistrée"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const emptyMessage = (
|
||||
<AlertMessage
|
||||
type="info"
|
||||
title="Aucune réduction enregistrée"
|
||||
message="Aucune réduction n'a encore été enregistrée"
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@ -370,8 +391,8 @@ const DiscountsSection = ({
|
||||
<SectionHeader
|
||||
icon={Tag}
|
||||
discountStyle={true}
|
||||
title={`${type == 0 ? "Liste des réductions sur les frais d'inscription" : 'Liste des réductions sur les frais de scolarité'}`}
|
||||
description={`Gérez ${type == 0 ? " vos réductions sur les frais d'inscription" : ' vos réductions sur les frais de scolarité'}`}
|
||||
title="Liste des réductions"
|
||||
description="Gérez vos réductions sur les frais d'inscription et de scolarité"
|
||||
button={!subscriptionMode}
|
||||
onClick={handleAddDiscount}
|
||||
/>
|
||||
|
||||
@ -0,0 +1,145 @@
|
||||
import React from 'react';
|
||||
import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
||||
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
|
||||
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';
|
||||
|
||||
/**
|
||||
* Bloc complet de gestion des frais pour un type donné (inscription ou scolarité).
|
||||
* Regroupe : liste des frais, réductions, plans et modes de paiement.
|
||||
*
|
||||
* @param {string} title - Titre affiché dans le séparateur de section
|
||||
* @param {Array} fees - Liste des frais du type
|
||||
* @param {Function} setFees - Setter des frais
|
||||
* @param {Array} discounts - Liste des réductions du type
|
||||
* @param {Function} setDiscounts - Setter des réductions
|
||||
* @param {Array} paymentPlans - Plans de paiement du type
|
||||
* @param {Function} setPaymentPlans - Setter des plans de paiement
|
||||
* @param {Array} paymentModes - Modes de paiement du type
|
||||
* @param {Function} setPaymentModes - Setter des modes de paiement
|
||||
* @param {number} type - 0 = inscription, 1 = scolarité
|
||||
* @param {Function} handleCreate - (url, newData, setter) => Promise
|
||||
* @param {Function} handleEdit - (url, id, updatedData, setter) => Promise
|
||||
* @param {Function} handleDelete - (url, id, setter) => Promise
|
||||
* @param {Function} onDiscountDelete - Callback invoqué après suppression d'une réduction
|
||||
*/
|
||||
const FeeTypeSection = ({
|
||||
title,
|
||||
fees,
|
||||
setFees,
|
||||
discounts,
|
||||
setDiscounts,
|
||||
paymentPlans,
|
||||
setPaymentPlans,
|
||||
paymentModes,
|
||||
setPaymentModes,
|
||||
type,
|
||||
handleCreate,
|
||||
handleEdit,
|
||||
handleDelete,
|
||||
onDiscountDelete,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="w-4/5 mx-auto flex items-center mt-8">
|
||||
<hr className="flex-grow border-t-2 border-gray-300" />
|
||||
<span className="mx-4 text-gray-600 font-semibold">{title}</span>
|
||||
<hr className="flex-grow border-t-2 border-gray-300" />
|
||||
</div>
|
||||
|
||||
<div className="mt-8 w-4/5">
|
||||
<FeesSection
|
||||
fees={fees}
|
||||
setFees={setFees}
|
||||
discounts={discounts}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(`${BE_SCHOOL_FEES_URL}`, newData, setFees)
|
||||
}
|
||||
handleEdit={(id, updatedData) =>
|
||||
handleEdit(`${BE_SCHOOL_FEES_URL}`, id, updatedData, setFees)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setFees)
|
||||
}
|
||||
type={type}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 w-4/5">
|
||||
<DiscountsSection
|
||||
discounts={discounts}
|
||||
setDiscounts={setDiscounts}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(`${BE_SCHOOL_DISCOUNTS_URL}`, newData, setDiscounts)
|
||||
}
|
||||
handleEdit={(id, updatedData) =>
|
||||
handleEdit(
|
||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||
id,
|
||||
updatedData,
|
||||
setDiscounts
|
||||
)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(`${BE_SCHOOL_DISCOUNTS_URL}`, id, setDiscounts)
|
||||
}
|
||||
onDiscountDelete={onDiscountDelete}
|
||||
type={type}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="col-span-1 mt-4">
|
||||
<PaymentPlanSelector
|
||||
paymentPlans={paymentPlans}
|
||||
setPaymentPlans={setPaymentPlans}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(
|
||||
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
|
||||
newData,
|
||||
setPaymentPlans
|
||||
)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(
|
||||
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
|
||||
id,
|
||||
setPaymentPlans
|
||||
)
|
||||
}
|
||||
type={type}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 mt-4">
|
||||
<PaymentModeSelector
|
||||
paymentModes={paymentModes}
|
||||
setPaymentModes={setPaymentModes}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(
|
||||
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
|
||||
newData,
|
||||
setPaymentModes
|
||||
)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(
|
||||
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
|
||||
id,
|
||||
setPaymentModes
|
||||
)
|
||||
}
|
||||
type={type}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeeTypeSection;
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React from 'react';
|
||||
import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
||||
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
|
||||
import PaymentPlanSelector from '@/components/PaymentPlanSelector';
|
||||
@ -31,223 +31,141 @@ const FeesManagement = ({
|
||||
handleEdit,
|
||||
handleDelete,
|
||||
}) => {
|
||||
const handleDiscountDelete = (id, type) => {
|
||||
if (type === 0) {
|
||||
setRegistrationFees((prevFees) =>
|
||||
prevFees.map((fee) => ({
|
||||
...fee,
|
||||
discounts: fee.discounts.filter((discountId) => discountId !== id),
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
setTuitionFees((prevFees) =>
|
||||
prevFees.map((fee) => ({
|
||||
...fee,
|
||||
discounts: fee.discounts.filter((discountId) => discountId !== id),
|
||||
}))
|
||||
);
|
||||
}
|
||||
// Liste unique triée par type puis par nom
|
||||
const allFees = [...(registrationFees ?? []), ...(tuitionFees ?? [])].sort(
|
||||
(a, b) => a.type - b.type || (a.name ?? '').localeCompare(b.name ?? '')
|
||||
);
|
||||
|
||||
const setAllFees = (updater) => {
|
||||
const next = typeof updater === 'function' ? updater(allFees) : updater;
|
||||
setRegistrationFees(next.filter((f) => f.type === 0));
|
||||
setTuitionFees(next.filter((f) => f.type === 1));
|
||||
};
|
||||
|
||||
const allDiscounts = [
|
||||
...(registrationDiscounts ?? []),
|
||||
...(tuitionDiscounts ?? []),
|
||||
].sort(
|
||||
(a, b) => a.type - b.type || (a.name ?? '').localeCompare(b.name ?? '')
|
||||
);
|
||||
|
||||
const setAllDiscounts = (updater) => {
|
||||
const next =
|
||||
typeof updater === 'function' ? updater(allDiscounts) : updater;
|
||||
setRegistrationDiscounts(next.filter((d) => d.type === 0));
|
||||
setTuitionDiscounts(next.filter((d) => d.type === 1));
|
||||
};
|
||||
|
||||
const allPaymentPlans = [
|
||||
...(registrationPaymentPlans ?? []),
|
||||
...(tuitionPaymentPlans ?? []),
|
||||
];
|
||||
const allPaymentModes = [
|
||||
...(registrationPaymentModes ?? []),
|
||||
...(tuitionPaymentModes ?? []),
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="w-4/5 mx-auto flex items-center mt-8">
|
||||
<hr className="flex-grow border-t-2 border-gray-300" />
|
||||
<span className="mx-4 text-gray-600 font-semibold">
|
||||
Frais d'inscription
|
||||
</span>
|
||||
<hr className="flex-grow border-t-2 border-gray-300" />
|
||||
</div>
|
||||
<div className="w-full space-y-12">
|
||||
{/* Tableau unique des frais */}
|
||||
<FeesSection
|
||||
fees={allFees}
|
||||
setFees={setAllFees}
|
||||
unified={true}
|
||||
handleCreate={(feeData) => {
|
||||
const setter =
|
||||
feeData.type === 0 ? setRegistrationFees : setTuitionFees;
|
||||
return handleCreate(BE_SCHOOL_FEES_URL, feeData, setter);
|
||||
}}
|
||||
handleEdit={(id, data) => {
|
||||
const fee = allFees.find((f) => f.id === id);
|
||||
const feeType = data.type ?? fee?.type;
|
||||
const setter = feeType === 0 ? setRegistrationFees : setTuitionFees;
|
||||
return handleEdit(BE_SCHOOL_FEES_URL, id, data, setter);
|
||||
}}
|
||||
handleDelete={(id) => {
|
||||
const fee = allFees.find((f) => f.id === id);
|
||||
const setter = fee?.type === 0 ? setRegistrationFees : setTuitionFees;
|
||||
return handleDelete(BE_SCHOOL_FEES_URL, id, setter);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="mt-8 w-4/5">
|
||||
<FeesSection
|
||||
fees={registrationFees}
|
||||
setFees={setRegistrationFees}
|
||||
discounts={registrationDiscounts}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(`${BE_SCHOOL_FEES_URL}`, newData, setRegistrationFees)
|
||||
}
|
||||
handleEdit={(id, updatedData) =>
|
||||
handleEdit(
|
||||
`${BE_SCHOOL_FEES_URL}`,
|
||||
id,
|
||||
updatedData,
|
||||
setRegistrationFees
|
||||
)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setRegistrationFees)
|
||||
}
|
||||
type={0}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-12 w-4/5">
|
||||
<DiscountsSection
|
||||
discounts={registrationDiscounts}
|
||||
setDiscounts={setRegistrationDiscounts}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(
|
||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||
newData,
|
||||
setRegistrationDiscounts
|
||||
)
|
||||
}
|
||||
handleEdit={(id, updatedData) =>
|
||||
handleEdit(
|
||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||
id,
|
||||
updatedData,
|
||||
setRegistrationDiscounts
|
||||
)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(
|
||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||
id,
|
||||
setRegistrationDiscounts
|
||||
)
|
||||
}
|
||||
onDiscountDelete={(id) => handleDiscountDelete(id, 0)}
|
||||
type={0}
|
||||
/>
|
||||
</div>
|
||||
{/* Tableau unique des réductions */}
|
||||
<DiscountsSection
|
||||
discounts={allDiscounts}
|
||||
setDiscounts={setAllDiscounts}
|
||||
unified={true}
|
||||
handleCreate={(data) => {
|
||||
const setter =
|
||||
data.type === 0 ? setRegistrationDiscounts : setTuitionDiscounts;
|
||||
return handleCreate(BE_SCHOOL_DISCOUNTS_URL, data, setter);
|
||||
}}
|
||||
handleEdit={(id, data) => {
|
||||
const discount = allDiscounts.find((d) => d.id === id);
|
||||
const discountType = data.type ?? discount?.type;
|
||||
const setter =
|
||||
discountType === 0 ? setRegistrationDiscounts : setTuitionDiscounts;
|
||||
return handleEdit(BE_SCHOOL_DISCOUNTS_URL, id, data, setter);
|
||||
}}
|
||||
handleDelete={(id) => {
|
||||
const discount = allDiscounts.find((d) => d.id === id);
|
||||
const setter =
|
||||
discount?.type === 0
|
||||
? setRegistrationDiscounts
|
||||
: setTuitionDiscounts;
|
||||
return handleDelete(BE_SCHOOL_DISCOUNTS_URL, id, setter);
|
||||
}}
|
||||
onDiscountDelete={(id) => {
|
||||
// Retire la réduction des frais concernés
|
||||
setAllFees((prevFees) =>
|
||||
prevFees.map((fee) => ({
|
||||
...fee,
|
||||
discounts: fee.discounts.filter((dId) => dId !== id),
|
||||
}))
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Plans et modes de paiement communs */}
|
||||
<div className="mt-12 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="col-span-1 mt-4">
|
||||
<PaymentPlanSelector
|
||||
paymentPlans={registrationPaymentPlans}
|
||||
setPaymentPlans={setRegistrationPaymentPlans}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(
|
||||
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
|
||||
newData,
|
||||
setRegistrationPaymentPlans
|
||||
)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(
|
||||
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
|
||||
id,
|
||||
setRegistrationPaymentPlans
|
||||
)
|
||||
}
|
||||
type={0}
|
||||
allPaymentPlans={allPaymentPlans}
|
||||
handleCreate={(data) => {
|
||||
const setter =
|
||||
data.type === 0
|
||||
? setRegistrationPaymentPlans
|
||||
: setTuitionPaymentPlans;
|
||||
return handleCreate(BE_SCHOOL_PAYMENT_PLANS_URL, data, setter);
|
||||
}}
|
||||
handleDelete={(id) => {
|
||||
const plan = allPaymentPlans.find((p) => p.id === id);
|
||||
const setter =
|
||||
plan?.type === 0
|
||||
? setRegistrationPaymentPlans
|
||||
: setTuitionPaymentPlans;
|
||||
return handleDelete(BE_SCHOOL_PAYMENT_PLANS_URL, id, setter);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 mt-4">
|
||||
<PaymentModeSelector
|
||||
paymentModes={registrationPaymentModes}
|
||||
setPaymentModes={setRegistrationPaymentModes}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(
|
||||
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
|
||||
newData,
|
||||
setRegistrationPaymentModes
|
||||
)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(
|
||||
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
|
||||
id,
|
||||
setRegistrationPaymentModes
|
||||
)
|
||||
}
|
||||
type={0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-4/5 mx-auto flex items-center mt-16">
|
||||
<hr className="flex-grow border-t-2 border-gray-300" />
|
||||
<span className="mx-4 text-gray-600 font-semibold">
|
||||
Frais de scolarité
|
||||
</span>
|
||||
<hr className="flex-grow border-t-2 border-gray-300" />
|
||||
</div>
|
||||
|
||||
<div className="mt-8 w-4/5">
|
||||
<FeesSection
|
||||
fees={tuitionFees}
|
||||
setFees={setTuitionFees}
|
||||
discounts={tuitionDiscounts}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(`${BE_SCHOOL_FEES_URL}`, newData, setTuitionFees)
|
||||
}
|
||||
handleEdit={(id, updatedData) =>
|
||||
handleEdit(`${BE_SCHOOL_FEES_URL}`, id, updatedData, setTuitionFees)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setTuitionFees)
|
||||
}
|
||||
type={1}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-12 w-4/5">
|
||||
<DiscountsSection
|
||||
discounts={tuitionDiscounts}
|
||||
setDiscounts={setTuitionDiscounts}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(
|
||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||
newData,
|
||||
setTuitionDiscounts
|
||||
)
|
||||
}
|
||||
handleEdit={(id, updatedData) =>
|
||||
handleEdit(
|
||||
`${BE_SCHOOL_DISCOUNTS_URL}`,
|
||||
id,
|
||||
updatedData,
|
||||
setTuitionDiscounts
|
||||
)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(`${BE_SCHOOL_DISCOUNTS_URL}`, id, setTuitionDiscounts)
|
||||
}
|
||||
onDiscountDelete={(id) => handleDiscountDelete(id, 1)}
|
||||
type={1}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-12 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="col-span-1 mt-4">
|
||||
<PaymentPlanSelector
|
||||
paymentPlans={tuitionPaymentPlans}
|
||||
setPaymentPlans={setTuitionPaymentPlans}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(
|
||||
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
|
||||
newData,
|
||||
setTuitionPaymentPlans
|
||||
)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(
|
||||
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
|
||||
id,
|
||||
setTuitionPaymentPlans
|
||||
)
|
||||
}
|
||||
type={1}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 mt-4">
|
||||
<PaymentModeSelector
|
||||
paymentModes={tuitionPaymentModes}
|
||||
setPaymentModes={setTuitionPaymentModes}
|
||||
handleCreate={(newData) =>
|
||||
handleCreate(
|
||||
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
|
||||
newData,
|
||||
setTuitionPaymentModes
|
||||
)
|
||||
}
|
||||
handleDelete={(id) =>
|
||||
handleDelete(
|
||||
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
|
||||
id,
|
||||
setTuitionPaymentModes
|
||||
)
|
||||
}
|
||||
type={1}
|
||||
allPaymentModes={allPaymentModes}
|
||||
handleCreate={(data) => {
|
||||
const setter =
|
||||
data.type === 0
|
||||
? setRegistrationPaymentModes
|
||||
: setTuitionPaymentModes;
|
||||
return handleCreate(BE_SCHOOL_PAYMENT_MODES_URL, data, setter);
|
||||
}}
|
||||
handleDelete={(id) => {
|
||||
const mode = allPaymentModes.find((m) => m.id === id);
|
||||
const setter =
|
||||
mode?.type === 0
|
||||
? setRegistrationPaymentModes
|
||||
: setTuitionPaymentModes;
|
||||
return handleDelete(BE_SCHOOL_PAYMENT_MODES_URL, id, setter);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -9,6 +9,13 @@ import SectionHeader from '@/components/SectionHeader';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import AlertMessage from '@/components/AlertMessage';
|
||||
|
||||
const FEE_TYPE_LABELS = { 0: 'Inscription', 1: 'Scolarité' };
|
||||
|
||||
/**
|
||||
* @param {boolean} [unified=false] - true : tableau mixte inscription+scolarité avec colonne TYPE.
|
||||
* Dans ce cas, `fees` contient les frais des deux types et `handleCreate`/`handleEdit`/`handleDelete`
|
||||
* sont des fonctions (url, data, setter) déjà partiellement appliquées par le parent.
|
||||
*/
|
||||
const FeesSection = ({
|
||||
fees,
|
||||
setFees,
|
||||
@ -16,6 +23,7 @@ const FeesSection = ({
|
||||
handleEdit,
|
||||
handleDelete,
|
||||
type,
|
||||
unified = false,
|
||||
subscriptionMode = false,
|
||||
selectedFees,
|
||||
handleFeeSelection,
|
||||
@ -29,8 +37,9 @@ const FeesSection = ({
|
||||
const [removePopupVisible, setRemovePopupVisible] = useState(false);
|
||||
const [removePopupMessage, setRemovePopupMessage] = useState('');
|
||||
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
||||
const labelTypeFrais =
|
||||
type === 0 ? "Frais d'inscription" : 'Frais de scolarité';
|
||||
// En mode unifié, le type effectif est celui du frais ou celui du formulaire de création
|
||||
const labelTypeFrais = (feeType) =>
|
||||
feeType === 0 ? "Frais d'inscription" : 'Frais de scolarité';
|
||||
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
// Récupération des messages d'erreur
|
||||
@ -44,10 +53,8 @@ const FeesSection = ({
|
||||
name: '',
|
||||
base_amount: '',
|
||||
description: '',
|
||||
validity_start_date: '',
|
||||
validity_end_date: '',
|
||||
discounts: [],
|
||||
type: type,
|
||||
type: unified ? 0 : type,
|
||||
establishment: selectedEstablishmentId,
|
||||
});
|
||||
};
|
||||
@ -91,8 +98,8 @@ const FeesSection = ({
|
||||
const handleUpdateFee = (id, updatedFee) => {
|
||||
if (updatedFee.name && updatedFee.base_amount) {
|
||||
handleEdit(id, updatedFee)
|
||||
.then((updatedFee) => {
|
||||
setFees(fees.map((fee) => (fee.id === id ? updatedFee : fee)));
|
||||
.then((updated) => {
|
||||
setFees(fees.map((fee) => (fee.id === id ? updated : fee)));
|
||||
setEditingFee(null);
|
||||
setLocalErrors({});
|
||||
})
|
||||
@ -193,6 +200,21 @@ const FeesSection = ({
|
||||
handleChange,
|
||||
'Description'
|
||||
);
|
||||
case 'TYPE':
|
||||
return (
|
||||
<select
|
||||
className="border rounded px-2 py-1 text-sm"
|
||||
value={currentData.type}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value, 10);
|
||||
if (isEditing) setFormData((p) => ({ ...p, type: val }));
|
||||
else setNewFee((p) => ({ ...p, type: val }));
|
||||
}}
|
||||
>
|
||||
<option value={0}>Inscription</option>
|
||||
<option value={1}>Scolarité</option>
|
||||
</select>
|
||||
);
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
@ -222,6 +244,7 @@ const FeesSection = ({
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
const feeLabel = labelTypeFrais(fee.type);
|
||||
switch (column) {
|
||||
case 'NOM':
|
||||
return fee.name;
|
||||
@ -231,6 +254,18 @@ const FeesSection = ({
|
||||
return fee.updated_at_formatted;
|
||||
case 'DESCRIPTION':
|
||||
return fee.description;
|
||||
case 'TYPE':
|
||||
return (
|
||||
<span
|
||||
className={`text-xs font-semibold px-2 py-1 rounded-full ${
|
||||
fee.type === 0
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-purple-100 text-purple-700'
|
||||
}`}
|
||||
>
|
||||
{FEE_TYPE_LABELS[fee.type]}
|
||||
</span>
|
||||
);
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
@ -257,22 +292,20 @@ const FeesSection = ({
|
||||
onClick={() => {
|
||||
setRemovePopupVisible(true);
|
||||
setRemovePopupMessage(
|
||||
`Attentions ! \nVous êtes sur le point de supprimer un ${labelTypeFrais} .\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?`
|
||||
`Attentions ! \nVous êtes sur le point de supprimer un ${feeLabel}.\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?`
|
||||
);
|
||||
setRemovePopupOnConfirm(() => () => {
|
||||
handleRemoveFee(fee.id)
|
||||
.then((data) => {
|
||||
logger.debug('Success:', data);
|
||||
setPopupMessage(
|
||||
labelTypeFrais + ' correctement supprimé'
|
||||
);
|
||||
setPopupMessage(feeLabel + ' correctement supprimé');
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error archiving data:', error);
|
||||
setPopupMessage(
|
||||
'Erreur lors de la suppression du ' + labelTypeFrais
|
||||
'Erreur lors de la suppression du ' + feeLabel
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
@ -307,42 +340,33 @@ const FeesSection = ({
|
||||
{ name: 'NOM', label: 'Nom' },
|
||||
{ name: 'DESCRIPTION', label: 'Description' },
|
||||
{ name: 'MONTANT', label: 'Montant de base' },
|
||||
...(unified ? [{ name: 'TYPE', label: 'Type' }] : []),
|
||||
{ name: '', label: 'Sélection' },
|
||||
]
|
||||
: [
|
||||
{ name: 'NOM', label: 'Nom' },
|
||||
{ name: 'MONTANT', label: 'Montant de base' },
|
||||
{ name: 'DESCRIPTION', label: 'Description' },
|
||||
...(unified ? [{ name: 'TYPE', label: 'Type' }] : []),
|
||||
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
|
||||
{ name: 'ACTIONS', label: 'Actions' },
|
||||
];
|
||||
|
||||
let emptyMessage;
|
||||
if (type === 0) {
|
||||
emptyMessage = (
|
||||
<AlertMessage
|
||||
type="warning"
|
||||
title="Aucun frais d'inscription enregistré"
|
||||
message="Veuillez procéder à la création de nouveaux frais d'inscription"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
emptyMessage = (
|
||||
<AlertMessage
|
||||
type="warning"
|
||||
title="Aucun frais de scolarité enregistré"
|
||||
message="Veuillez procéder à la création de nouveaux frais de scolarité"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const emptyMessage = (
|
||||
<AlertMessage
|
||||
type="warning"
|
||||
title="Aucun frais enregistré"
|
||||
message="Veuillez procéder à la création de nouveaux frais"
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{!subscriptionMode && (
|
||||
<SectionHeader
|
||||
icon={CreditCard}
|
||||
title={`${type == 0 ? "Liste des frais d'inscription" : 'Liste des frais de scolarité'}`}
|
||||
description={`Gérez${type == 0 ? " vos frais d'inscription" : ' vos frais de scolarité'}`}
|
||||
title="Liste des frais"
|
||||
description="Gérez vos frais d'inscription et de scolarité"
|
||||
button={!subscriptionMode}
|
||||
onClick={handleAddFee}
|
||||
/>
|
||||
|
||||
211
Front-End/src/test/FeesSection.test.js
Normal file
211
Front-End/src/test/FeesSection.test.js
Normal file
@ -0,0 +1,211 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
|
||||
// Mock du contexte établissement
|
||||
jest.mock('@/context/EstablishmentContext', () => ({
|
||||
useEstablishment: () => ({ selectedEstablishmentId: 1 }),
|
||||
}));
|
||||
|
||||
// Mock des composants UI pour isoler les tests unitaires
|
||||
jest.mock(
|
||||
'@/components/Table',
|
||||
() =>
|
||||
({ data, columns, renderCell, emptyMessage }) => {
|
||||
if (!data || data.length === 0)
|
||||
return <div data-testid="empty-message">{emptyMessage}</div>;
|
||||
return (
|
||||
<table>
|
||||
<tbody>
|
||||
{data.map((row) => (
|
||||
<tr key={row.id}>
|
||||
{columns.map((col) => (
|
||||
<td key={col.name}>{renderCell(row, col.name)}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
'@/components/Popup',
|
||||
() =>
|
||||
({ isOpen, message, onConfirm, onCancel }) =>
|
||||
isOpen ? (
|
||||
<div data-testid="popup">
|
||||
<p>{message}</p>
|
||||
<button onClick={onConfirm}>Confirmer</button>
|
||||
<button onClick={onCancel}>Annuler</button>
|
||||
</div>
|
||||
) : null
|
||||
);
|
||||
|
||||
jest.mock('@/components/SectionHeader', () => ({ title, button, onClick }) => (
|
||||
<div>
|
||||
<h2>{title}</h2>
|
||||
{button && <button onClick={onClick}>Ajouter</button>}
|
||||
</div>
|
||||
));
|
||||
|
||||
jest.mock('@/components/AlertMessage', () => ({ title, message }) => (
|
||||
<div data-testid="alert-message">
|
||||
<strong>{title}</strong>
|
||||
<p>{message}</p>
|
||||
</div>
|
||||
));
|
||||
|
||||
jest.mock(
|
||||
'@/components/Form/InputText',
|
||||
() =>
|
||||
({ name, value, onChange, placeholder }) => (
|
||||
<input
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
jest.mock('@/components/Form/CheckBox', () => ({ item, handleChange }) => (
|
||||
<input type="checkbox" onChange={handleChange} />
|
||||
));
|
||||
|
||||
jest.mock('@/utils/logger', () => ({
|
||||
error: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockFee = {
|
||||
id: 1,
|
||||
name: 'Frais test',
|
||||
base_amount: '200.00',
|
||||
description: 'Description test',
|
||||
updated_at_formatted: '01-01-2026 10:00',
|
||||
is_active: true,
|
||||
discounts: [],
|
||||
type: 0,
|
||||
};
|
||||
|
||||
describe('FeesSection - type inscription (type=0)', () => {
|
||||
const defaultProps = {
|
||||
fees: [mockFee],
|
||||
setFees: jest.fn(),
|
||||
handleCreate: jest.fn(),
|
||||
handleEdit: jest.fn(),
|
||||
handleDelete: jest.fn(),
|
||||
type: 0,
|
||||
};
|
||||
|
||||
it('affiche le titre "Liste des frais"', () => {
|
||||
render(<FeesSection {...defaultProps} />);
|
||||
expect(screen.getByText('Liste des frais')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('affiche les données du frais dans le tableau', () => {
|
||||
render(<FeesSection {...defaultProps} />);
|
||||
expect(screen.getByText('Frais test')).toBeInTheDocument();
|
||||
expect(screen.getByText('200.00 €')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('affiche le bouton Ajouter en mode gestion', () => {
|
||||
render(<FeesSection {...defaultProps} />);
|
||||
expect(screen.getByText('Ajouter')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('affiche le message vide quand la liste est vide', () => {
|
||||
render(<FeesSection {...defaultProps} fees={[]} />);
|
||||
expect(screen.getByTestId('empty-message')).toBeInTheDocument();
|
||||
expect(screen.getByText('Aucun frais enregistré')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('FeesSection - type scolarité (type=1)', () => {
|
||||
const defaultProps = {
|
||||
fees: [{ ...mockFee, type: 1 }],
|
||||
setFees: jest.fn(),
|
||||
handleCreate: jest.fn(),
|
||||
handleEdit: jest.fn(),
|
||||
handleDelete: jest.fn(),
|
||||
type: 1,
|
||||
};
|
||||
|
||||
it('affiche le titre "Liste des frais" aussi pour type=1', () => {
|
||||
render(<FeesSection {...defaultProps} />);
|
||||
expect(screen.getByText('Liste des frais')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('affiche le message vide générique quand la liste est vide', () => {
|
||||
render(<FeesSection {...defaultProps} fees={[]} />);
|
||||
expect(screen.getByText('Aucun frais enregistré')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('FeesSection - mode sélection (subscriptionMode)', () => {
|
||||
const defaultProps = {
|
||||
fees: [mockFee],
|
||||
setFees: jest.fn(),
|
||||
handleCreate: jest.fn(),
|
||||
handleEdit: jest.fn(),
|
||||
handleDelete: jest.fn(),
|
||||
type: 0,
|
||||
subscriptionMode: true,
|
||||
selectedFees: [],
|
||||
handleFeeSelection: jest.fn(),
|
||||
};
|
||||
|
||||
it('cache le header section en mode subscription', () => {
|
||||
render(<FeesSection {...defaultProps} />);
|
||||
expect(screen.queryByText('Liste des frais')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("n'affiche pas le bouton Ajouter en mode subscription", () => {
|
||||
render(<FeesSection {...defaultProps} />);
|
||||
expect(screen.queryByText('Ajouter')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("FeesSection - création d'un nouveau frais", () => {
|
||||
it('initialise le nouveau frais avec le bon type', () => {
|
||||
const setFees = jest.fn();
|
||||
const handleCreate = jest.fn(() =>
|
||||
Promise.resolve({ id: 2, name: 'Nouveau', base_amount: '100' })
|
||||
);
|
||||
|
||||
render(
|
||||
<FeesSection
|
||||
fees={[]}
|
||||
setFees={setFees}
|
||||
handleCreate={handleCreate}
|
||||
handleEdit={jest.fn()}
|
||||
handleDelete={jest.fn()}
|
||||
type={0}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('Ajouter'));
|
||||
// Le nouveau frais doit apparaître dans le tableau
|
||||
expect(screen.queryByPlaceholderText('Nom des frais')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('initialise le nouveau frais avec type=1 pour les frais de scolarité', () => {
|
||||
render(
|
||||
<FeesSection
|
||||
fees={[]}
|
||||
setFees={jest.fn()}
|
||||
handleCreate={jest.fn()}
|
||||
handleEdit={jest.fn()}
|
||||
handleDelete={jest.fn()}
|
||||
type={1}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('Ajouter'));
|
||||
expect(screen.queryByPlaceholderText('Nom des frais')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user