feat: Mise à jour du modèle (possibilité d'associer une réduciton à un

frais d'inscription [#18]
This commit is contained in:
N3WT DE COMPET
2025-01-20 20:42:51 +01:00
parent 5a0e65bb75
commit 8d1a41e269
9 changed files with 224 additions and 151 deletions

View File

@ -4,7 +4,7 @@ import Table from '@/components/Table';
import InputTextIcon from '@/components/InputTextIcon';
import Popup from '@/components/Popup';
const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete, errors }) => {
const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete }) => {
const [editingDiscount, setEditingDiscount] = useState(null);
const [newDiscount, setNewDiscount] = useState(null);
const [formData, setFormData] = useState({});
@ -17,7 +17,7 @@ const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete, e
};
const handleRemoveDiscount = (id) => {
handleDelete(id);
handleDelete(id)
};
const handleSaveNewDiscount = () => {

View File

@ -2,41 +2,42 @@ import React, { useState } from 'react';
import FeesSection from './FeesSection';
import DiscountsSection from './DiscountsSection';
import TuitionFeesSection from './TuitionFeesSection';
import { TuitionFeesProvider } from '@/context/TuitionFeesContext';
import { BE_SCHOOL_FEE_URL, BE_SCHOOL_DISCOUNT_URL, BE_SCHOOL_TUITION_FEE_URL } from '@/utils/Url';
const FeesManagement = ({ fees, setFees, discounts, setDiscounts, setTuitionFees, handleCreate, handleEdit, handleDelete }) => {
const [errors, setErrors] = useState({});
const FeesManagement = ({ fees, setFees, discounts, setDiscounts, tuitionFees, setTuitionFees, handleCreate, handleEdit, handleDelete }) => {
return (
<TuitionFeesProvider>
<div className="max-w-8xl mx-auto p-4 mt-6 space-y-6">
<div className="p-4 bg-white rounded-lg shadow-md">
<FeesSection
fees={fees}
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_FEE_URL}`, newData, setFees, setErrors)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_FEE_URL}`, id, updatedData, setFees, setErrors)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_FEE_URL}`, id, setFees)}
errors
/>
</div>
<div className="p-4 bg-white rounded-lg shadow-md">
<DiscountsSection
discounts={discounts}
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_DISCOUNT_URL}`, newData, setDiscounts, setErrors)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_DISCOUNT_URL}`, id, updatedData, setDiscounts, setErrors)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_DISCOUNT_URL}`, id, setDiscounts)}
/>
</div>
<div className="p-4 bg-white rounded-lg shadow-md">
<TuitionFeesSection
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_TUITION_FEE_URL}`, newData, setTuitionFees, setErrors)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_TUITION_FEE_URL}`, id, updatedData, setTuitionFees, setErrors)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_TUITION_FEE_URL}`, id, setTuitionFees)}
/>
</div>
<div className="max-w-8xl mx-auto p-4 mt-6 space-y-6">
<div className="p-4 bg-white rounded-lg shadow-md">
<DiscountsSection
discounts={discounts}
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_DISCOUNT_URL}`, newData, setDiscounts)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_DISCOUNT_URL}`, id, updatedData, setDiscounts)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_DISCOUNT_URL}`, id, setDiscounts)}
/>
</div>
</TuitionFeesProvider>
<div className="p-4 bg-white rounded-lg shadow-md">
<FeesSection
fees={fees}
setFees={setFees}
discounts={discounts}
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_FEE_URL}`, newData, setFees)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_FEE_URL}`, id, updatedData, setFees)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_FEE_URL}`, id, setFees)}
/>
</div>
<div className="p-4 bg-white rounded-lg shadow-md">
<TuitionFeesSection
tuitionFees={tuitionFees}
setTuitionFees={setTuitionFees}
discounts={discounts}
fees={fees}
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_TUITION_FEE_URL}`, newData, setTuitionFees)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_TUITION_FEE_URL}`, id, updatedData, setTuitionFees)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_TUITION_FEE_URL}`, id, setTuitionFees)}
/>
</div>
</div>
);
};

View File

@ -3,8 +3,9 @@ import { Plus, Trash, Edit3, Check, X } from 'lucide-react';
import Table from '@/components/Table';
import InputTextIcon from '@/components/InputTextIcon';
import Popup from '@/components/Popup';
import SelectChoice from '@/components/SelectChoice';
const FeesSection = ({ fees, handleCreate, handleEdit, handleDelete, errors }) => {
const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handleDelete }) => {
const [editingFee, setEditingFee] = useState(null);
const [newFee, setNewFee] = useState(null);
const [formData, setFormData] = useState({});
@ -12,8 +13,14 @@ const FeesSection = ({ fees, handleCreate, handleEdit, handleDelete, errors }) =
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState("");
const paymentOptions = [
{ value: 0, label: '1 fois' },
{ value: 1, label: '4 fois' },
{ value: 2, label: '10 fois' }
];
const handleAddFee = () => {
setNewFee({ id: Date.now(), name: '', amount: '', description: '' });
setNewFee({ id: Date.now(), name: '', base_amount: '', description: '' });
};
const handleRemoveFee = (id) => {
@ -21,7 +28,7 @@ const FeesSection = ({ fees, handleCreate, handleEdit, handleDelete, errors }) =
};
const handleSaveNewFee = () => {
if (newFee.name && newFee.amount) {
if (newFee.name && newFee.base_amount) {
handleCreate(newFee)
.then(() => {
setNewFee(null);
@ -41,7 +48,7 @@ const FeesSection = ({ fees, handleCreate, handleEdit, handleDelete, errors }) =
};
const handleUpdateFee = (id, updatedFee) => {
if (updatedFee.name && updatedFee.amount) {
if (updatedFee.name && updatedFee.base_amount) {
handleEdit(id, updatedFee)
.then(() => {
setEditingFee(null);
@ -60,26 +67,59 @@ const FeesSection = ({ fees, handleCreate, handleEdit, handleDelete, errors }) =
}
};
const handleToggleActive = (id, isActive) => {
const fee = fees.find(fee => fee.id === id);
if (!fee) return;
const updatedData = {
is_active: !isActive,
discounts: fee.discounts
};
handleEdit(id, updatedData)
.then(() => {
setFees(prevFees => prevFees.map(fee => fee.id === id ? { ...fee, is_active: !isActive } : fee));
})
.catch(error => {
console.error(error);
});
};
const handleChange = (e) => {
const { name, value } = e.target;
let parsedValue = value;
if (name === 'discounts') {
parsedValue = value.split(',').map(v => parseInt(v, 10));
}
if (editingFee) {
setFormData((prevData) => ({
...prevData,
[name]: value,
[name]: parsedValue,
}));
} else if (newFee) {
setNewFee((prevData) => ({
...prevData,
[name]: value,
[name]: parsedValue,
}));
}
};
const calculateFinalAmount = (baseAmount, discountIds) => {
const totalDiscounts = discountIds.reduce((sum, discountId) => {
const discount = discounts.find(d => d.id === discountId);
return discount ? sum + parseFloat(discount.amount) : sum;
}, 0);
const finalAmount = parseFloat(baseAmount) - totalDiscounts;
return finalAmount.toFixed(2);
};
const renderInputField = (field, value, onChange, placeholder) => (
<div>
<InputTextIcon
name={field}
type={field === 'amount' ? 'number' : 'text'}
type={field === 'base_amount' ? 'number' : 'text'}
value={value}
onChange={onChange}
placeholder={placeholder}
@ -88,6 +128,19 @@ const FeesSection = ({ fees, handleCreate, handleEdit, handleDelete, errors }) =
</div>
);
const renderSelectField = (field, value, options, callback, label) => (
<div className="flex justify-center items-center h-full">
<SelectChoice
name={field}
selected={value}
options={options}
callback={callback}
placeHolder={label}
choices={options}
/>
</div>
);
const renderFeeCell = (fee, column) => {
const isEditing = editingFee === fee.id;
const isCreating = newFee && newFee.id === fee.id;
@ -97,10 +150,14 @@ const FeesSection = ({ fees, handleCreate, handleEdit, handleDelete, errors }) =
switch (column) {
case 'LIBELLE':
return renderInputField('name', currentData.name, handleChange, 'Libellé du frais');
case 'MONTANT':
return renderInputField('amount', currentData.amount, handleChange, 'Montant');
case 'MONTANT DE BASE':
return renderInputField('base_amount', currentData.base_amount, handleChange, 'Montant');
case 'DESCRIPTION':
return renderInputField('description', currentData.description, handleChange, 'Description');
case 'OPTIONS DE PAIEMENT':
return renderSelectField('payment_option', currentData.payment_option, paymentOptions, handleChange, 'Options de paiement');
case 'REMISES':
return renderSelectField('discounts', currentData.discounts, discounts.map(discount => ({ value: discount.id, label: discount.name })), handleChange, 'Remises');
case 'ACTIONS':
return (
<div className="flex space-x-2">
@ -127,10 +184,20 @@ const FeesSection = ({ fees, handleCreate, handleEdit, handleDelete, errors }) =
switch (column) {
case 'LIBELLE':
return fee.name;
case 'MONTANT':
return fee.amount + ' €';
case 'MONTANT DE BASE':
return fee.base_amount + ' €';
case 'DESCRIPTION':
return fee.description;
case 'OPTIONS DE PAIEMENT':
return paymentOptions.find(option => option.value === fee.payment_option)?.label || '';
case 'REMISES':
const discountNames = fee.discounts
.map(discountId => discounts.find(discount => discount.id === discountId)?.name)
.filter(name => name)
.join(', ');
return discountNames;
case 'MONTANT FINAL':
return calculateFinalAmount(fee.base_amount, fee.discounts) + ' €';
case 'ACTIONS':
return (
<div className="flex justify-center space-x-2">
@ -148,6 +215,13 @@ const FeesSection = ({ fees, handleCreate, handleEdit, handleDelete, errors }) =
>
<Trash className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => handleToggleActive(fee.id, fee.is_active)}
className={`text-${fee.is_active ? 'gray' : 'green'}-500 hover:text-${fee.is_active ? 'gray' : 'green'}-700`}
>
{fee.is_active ? <X className="w-5 h-5" /> : <Check className="w-5 h-5" />}
</button>
</div>
);
default:
@ -169,8 +243,11 @@ const FeesSection = ({ fees, handleCreate, handleEdit, handleDelete, errors }) =
data={newFee ? [newFee, ...fees] : fees}
columns={[
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'MONTANT', label: 'Montant' },
{ name: 'MONTANT DE BASE', label: 'Montant' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'OPTIONS DE PAIEMENT', label: 'Options de paiement' },
{ name: 'REMISES', label: 'Remises' },
{ name: 'MONTANT FINAL', label: 'Montant final' },
{ name: 'ACTIONS', label: 'Actions' }
]}
renderCell={renderFeeCell}

View File

@ -1,13 +1,11 @@
import React, { useState } from 'react';
import { Plus, Trash, Edit3, Check, X, Calendar } from 'lucide-react';
import { Plus, Trash, Edit3, Check, X } from 'lucide-react';
import Table from '@/components/Table';
import InputTextIcon from '@/components/InputTextIcon';
import Popup from '@/components/Popup';
import SelectChoice from '@/components/SelectChoice';
import { useTuitionFees } from '@/context/TuitionFeesContext';
const TuitionFeesSection = ({ handleCreate, handleEdit, handleDelete, errors }) => {
const { fees, tuitionFees, setTuitionFees, discounts } = useTuitionFees();
const TuitionFeesSection = ({ tuitionFees, setTuitionFees, discounts, fees, handleCreate, handleEdit, handleDelete }) => {
const [editingTuitionFee, setEditingTuitionFee] = useState(null);
const [newTuitionFee, setNewTuitionFee] = useState(null);
const [formData, setFormData] = useState({});
@ -26,18 +24,20 @@ const TuitionFeesSection = ({ handleCreate, handleEdit, handleDelete, errors })
};
const handleRemoveTuitionFee = (id) => {
handleDelete(id);
setTuitionFees(tuitionFees.filter(fee => fee.id !== id));
handleDelete(id)
.then(() => {
setTuitionFees(prevTuitionFees => prevTuitionFees.filter(fee => fee.id !== id));
})
.catch(error => {
console.error(error);
});
};
const handleSaveNewTuitionFee = () => {
if (
newTuitionFee.name &&
newTuitionFee.base_amount &&
newTuitionFee.payment_option >= 0 &&
newTuitionFee.validity_start_date &&
newTuitionFee.validity_end_date &&
new Date(newTuitionFee.validity_start_date) <= new Date(newTuitionFee.validity_end_date)
newTuitionFee.payment_option >= 0
) {
handleCreate(newTuitionFee)
.then((createdTuitionFee) => {
@ -62,10 +62,7 @@ const TuitionFeesSection = ({ handleCreate, handleEdit, handleDelete, errors })
if (
updatedTuitionFee.name &&
updatedTuitionFee.base_amount &&
updatedTuitionFee.payment_option >= 0 &&
updatedTuitionFee.validity_start_date &&
updatedTuitionFee.validity_end_date &&
new Date(updatedTuitionFee.validity_start_date) <= new Date(updatedTuitionFee.validity_end_date)
updatedTuitionFee.payment_option >= 0
) {
handleEdit(id, updatedTuitionFee)
.then((updatedFee) => {
@ -86,6 +83,24 @@ const TuitionFeesSection = ({ handleCreate, handleEdit, handleDelete, errors })
}
};
const handleToggleActive = (id, isActive) => {
const tuitionFee = tuitionFees.find(tuitionFee => tuitionFee.id === id);
if (!tuitionFee) return;
const updatedData = {
is_active: !isActive,
discounts: tuitionFee.discounts
};
handleEdit(id, updatedData)
.then(() => {
setFees(prevTuitionFees => prevTuitionFees.map(tuitionFee => tuitionFee.id === id ? { ...tuitionFee, is_active: !isActive } : tuitionFee));
})
.catch(error => {
console.error(error);
});
};
const handleChange = (e) => {
const { name, value } = e.target;
let parsedValue = value;
@ -120,24 +135,11 @@ const TuitionFeesSection = ({ handleCreate, handleEdit, handleDelete, errors })
</div>
);
const renderDateField = (field, value, onChange) => (
<div className="relative flex items-center justify-center h-full">
<Calendar className="w-5 h-5 text-emerald-500 absolute left-3" />
<input
type="date"
name={field}
value={value}
onChange={onChange}
className="block w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-emerald-500 focus:border-emerald-500"
/>
</div>
);
const renderSelectField = (field, value, options, callback, label) => (
<div className="flex justify-center items-center h-full">
<SelectChoice
name={field}
value={value}
selected={value}
options={options}
callback={callback}
placeHolder={label}
@ -147,14 +149,12 @@ const TuitionFeesSection = ({ handleCreate, handleEdit, handleDelete, errors })
);
const calculateFinalAmount = (baseAmount, discountIds) => {
const totalFees = fees.reduce((sum, fee) => sum + parseFloat(fee.amount), 0);
const totalDiscounts = discountIds.reduce((sum, discountId) => {
const discount = discounts.find(d => d.id === discountId);
return discount ? sum + parseFloat(discount.amount) : sum;
}, 0);
const finalAmount = parseFloat(baseAmount) + totalFees - totalDiscounts;
const finalAmount = parseFloat(baseAmount) - totalDiscounts;
return finalAmount.toFixed(2);
};
@ -172,10 +172,6 @@ const TuitionFeesSection = ({ handleCreate, handleEdit, handleDelete, errors })
return renderInputField('base_amount', currentData.base_amount, handleChange, 'Montant de base');
case 'DESCRIPTION':
return renderInputField('description', currentData.description, handleChange, 'Description');
case 'DATE DE DEBUT':
return renderDateField('validity_start_date', currentData.validity_start_date, handleChange);
case 'DATE DE FIN':
return renderDateField('validity_end_date', currentData.validity_end_date, handleChange);
case 'OPTIONS DE PAIEMENT':
return renderSelectField('payment_option', currentData.payment_option, paymentOptions, handleChange, 'Options de paiement');
case 'REMISES':
@ -210,10 +206,6 @@ const TuitionFeesSection = ({ handleCreate, handleEdit, handleDelete, errors })
return tuitionFee.base_amount + ' €';
case 'DESCRIPTION':
return tuitionFee.description;
case 'DATE DE DEBUT':
return tuitionFee.validity_start_date;
case 'DATE DE FIN':
return tuitionFee.validity_end_date;
case 'OPTIONS DE PAIEMENT':
return paymentOptions.find(option => option.value === tuitionFee.payment_option)?.label || '';
case 'REMISES':
@ -241,6 +233,13 @@ const TuitionFeesSection = ({ handleCreate, handleEdit, handleDelete, errors })
>
<Trash className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => handleToggleActive(tuitionFee.id, tuitionFee.is_active)}
className={`text-${tuitionFee.is_active ? 'gray' : 'green'}-500 hover:text-${tuitionFee.is_active ? 'gray' : 'green'}-700`}
>
{tuitionFee.is_active ? <X className="w-5 h-5" /> : <Check className="w-5 h-5" />}
</button>
</div>
);
default:
@ -263,8 +262,6 @@ const TuitionFeesSection = ({ handleCreate, handleEdit, handleDelete, errors })
{ name: 'NOM', label: 'Nom' },
{ name: 'MONTANT DE BASE', label: 'Montant de base' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'DATE DE DEBUT', label: 'Date de début' },
{ name: 'DATE DE FIN', label: 'Date de fin' },
{ name: 'OPTIONS DE PAIEMENT', label: 'Options de paiement' },
{ name: 'REMISES', label: 'Remises' },
{ name: 'MONTANT FINAL', label: 'Montant final' },