feat: Harmonisation des fees / ajout de type de réduction / mise à jour

du calcul [#18]
This commit is contained in:
N3WT DE COMPET
2025-01-21 20:39:36 +01:00
parent 8d1a41e269
commit 5462306a60
11 changed files with 169 additions and 194 deletions

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { Plus, Trash, Edit3, Check, X } from 'lucide-react';
import { Plus, Trash, Edit3, Check, X, Percent, EuroIcon } from 'lucide-react';
import Table from '@/components/Table';
import InputTextIcon from '@/components/InputTextIcon';
import Popup from '@/components/Popup';
const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete }) => {
const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, handleDelete, errors }) => {
const [editingDiscount, setEditingDiscount] = useState(null);
const [newDiscount, setNewDiscount] = useState(null);
const [formData, setFormData] = useState({});
@ -13,17 +13,24 @@ const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete })
const [popupMessage, setPopupMessage] = useState("");
const handleAddDiscount = () => {
setNewDiscount({ id: Date.now(), name: '', amount: '', description: '' });
setNewDiscount({ id: Date.now(), name: '', amount: '', description: '', discountType: 'amount' });
};
const handleRemoveDiscount = (id) => {
handleDelete(id)
.then(() => {
setDiscounts(prevDiscounts => prevDiscounts.filter(discount => discount.id !== id));
})
.catch(error => {
console.error(error);
});
};
const handleSaveNewDiscount = () => {
if (newDiscount.name && newDiscount.amount) {
handleCreate(newDiscount)
.then(() => {
.then((createdDiscount) => {
setDiscounts([createdDiscount, ...discounts]);
setNewDiscount(null);
setLocalErrors({});
})
@ -60,9 +67,29 @@ const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete })
}
};
const handleToggleDiscountType = (id, newType) => {
const discount = discounts.find(discount => discount.id === id);
if (!discount) return;
const updatedData = {
...discount,
discount_type: newType
};
handleEdit(id, updatedData)
.then(() => {
setDiscounts(prevDiscounts => prevDiscounts.map(discount => discount.id === id ? { ...discount, discount_type: updatedData.discount_type } : discount));
})
.catch(error => {
console.error(error);
});
};
const handleChange = (e) => {
const { name, value } = e.target;
if (editingDiscount) {
if (name === 'discountType') {
setDiscountType(value);
} else if (editingDiscount) {
setFormData((prevData) => ({
...prevData,
[name]: value,
@ -97,13 +124,13 @@ const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete })
switch (column) {
case 'LIBELLE':
return renderInputField('name', currentData.name, handleChange, 'Libellé de la réduction');
case 'MONTANT':
return renderInputField('amount', currentData.amount, handleChange, 'Montant');
case 'VALEUR':
return renderInputField('amount', currentData.amount, handleChange, discount.discount_type === 0 ? 'Montant' : 'Pourcentage');
case 'DESCRIPTION':
return renderInputField('description', currentData.description, handleChange, 'Description');
case 'ACTIONS':
return (
<div className="flex space-x-2">
<div className="flex justify-center space-x-2">
<button
type="button"
onClick={() => (isEditing ? handleUpdateDiscount(editingDiscount, formData) : handleSaveNewDiscount())}
@ -127,9 +154,28 @@ const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete })
switch (column) {
case 'LIBELLE':
return discount.name;
case 'MONTANT':
return discount.amount + ' €';
case 'DESCRIPTION':
case 'VALEUR':
return discount.discount_type === 0 ? `${discount.amount}` : `${discount.amount} %`;
case 'TYPE DE REMISE':
return (
<div className="flex flex-col items-center space-y-2">
<button
type="button"
onClick={() => handleToggleDiscountType(discount.id, 0)}
className={`text-${discount.discount_type === 0 ? 'emerald' : 'gray'}-500 hover:text-${discount.discount_type === 0 ? 'emerald' : 'gray'}-700`}
>
<EuroIcon className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => handleToggleDiscountType(discount.id, 1)}
className={`text-${discount.discount_type === 1 ? 'emerald' : 'gray'}-500 hover:text-${discount.discount_type === 1 ? 'emerald' : 'gray'}-700`}
>
<Percent className="w-5 h-5" />
</button>
</div>
);
case 'DESCRIPTION':
return discount.description;
case 'ACTIONS':
return (
@ -168,7 +214,8 @@ const DiscountsSection = ({ discounts, handleCreate, handleEdit, handleDelete })
data={newDiscount ? [newDiscount, ...discounts] : discounts}
columns={[
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'MONTANT', label: 'Montant' },
{ name: 'VALEUR', label: 'Valeur' },
{ name: 'TYPE DE REMISE', label: 'Type de remise' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'ACTIONS', label: 'Actions' }
]}

View File

@ -1,29 +1,30 @@
import React, { useState } from 'react';
import FeesSection from './FeesSection';
import DiscountsSection from './DiscountsSection';
import TuitionFeesSection from './TuitionFeesSection';
import { BE_SCHOOL_FEE_URL, BE_SCHOOL_DISCOUNT_URL, BE_SCHOOL_TUITION_FEE_URL } from '@/utils/Url';
import RegistrationFeesSection from '@/components/Structure/Configuration/RegistrationFeesSection';
import DiscountsSection from '@/components/Structure/Configuration/DiscountsSection';
import TuitionFeesSection from '@/components/Structure/Configuration/TuitionFeesSection';
import { BE_SCHOOL_FEE_URL, BE_SCHOOL_DISCOUNT_URL } from '@/utils/Url';
const FeesManagement = ({ fees, setFees, discounts, setDiscounts, tuitionFees, setTuitionFees, handleCreate, handleEdit, handleDelete }) => {
const FeesManagement = ({ discounts, setDiscounts, registrationFees, setRegistrationFees, tuitionFees, setTuitionFees, handleCreate, handleEdit, handleDelete }) => {
return (
<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}
setDiscounts={setDiscounts}
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>
<div className="p-4 bg-white rounded-lg shadow-md">
<FeesSection
fees={fees}
setFees={setFees}
<RegistrationFeesSection
registrationFees={registrationFees}
setRegistrationFees={setRegistrationFees}
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)}
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_FEE_URL}`, newData, setRegistrationFees)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_FEE_URL}`, id, updatedData, setRegistrationFees)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_FEE_URL}`, id, setRegistrationFees)}
/>
</div>
<div className="p-4 bg-white rounded-lg shadow-md">
@ -31,10 +32,10 @@ const FeesManagement = ({ fees, setFees, discounts, setDiscounts, tuitionFees, s
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)}
registrationFees={registrationFees}
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_FEE_URL}`, newData, setTuitionFees)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_FEE_URL}`, id, updatedData, setTuitionFees)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_FEE_URL}`, id, setTuitionFees)}
/>
</div>
</div>

View File

@ -1,11 +1,11 @@
import React, { useState } from 'react';
import { Plus, Trash, Edit3, Check, X } from 'lucide-react';
import { Plus, Trash, Edit3, Check, X, EyeOff, Eye } 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, setFees, discounts, handleCreate, handleEdit, handleDelete }) => {
const RegistrationFeesSection = ({ registrationFees, setRegistrationFees, discounts, handleCreate, handleEdit, handleDelete }) => {
const [editingFee, setEditingFee] = useState(null);
const [newFee, setNewFee] = useState(null);
const [formData, setFormData] = useState({});
@ -29,8 +29,14 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
const handleSaveNewFee = () => {
if (newFee.name && newFee.base_amount) {
handleCreate(newFee)
.then(() => {
const feeData = {
...newFee,
type: 0
};
handleCreate(feeData)
.then((createdFee) => {
setRegistrationFees([createdFee, ...registrationFees]);
setNewFee(null);
setLocalErrors({});
})
@ -68,7 +74,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
};
const handleToggleActive = (id, isActive) => {
const fee = fees.find(fee => fee.id === id);
const fee = registrationFees.find(fee => fee.id === id);
if (!fee) return;
const updatedData = {
@ -78,7 +84,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
handleEdit(id, updatedData)
.then(() => {
setFees(prevFees => prevFees.map(fee => fee.id === id ? { ...fee, is_active: !isActive } : fee));
setRegistrationFees(prevFees => prevFees.map(fee => fee.id === id ? { ...fee, is_active: !isActive } : fee));
})
.catch(error => {
console.error(error);
@ -107,11 +113,18 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
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;
if (discount) {
if (discount.discount_type === 0) { // Currency
return sum + parseFloat(discount.amount);
} else if (discount.discount_type === 1) { // Percent
return sum + (parseFloat(baseAmount) * parseFloat(discount.amount) / 100);
}
}
return sum;
}, 0);
const finalAmount = parseFloat(baseAmount) - totalDiscounts;
return finalAmount.toFixed(2);
};
@ -220,7 +233,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
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" />}
{fee.is_active ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
);
@ -240,7 +253,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
</button>
</div>
<Table
data={newFee ? [newFee, ...fees] : fees}
data={newFee ? [newFee, ...registrationFees] : registrationFees}
columns={[
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'MONTANT DE BASE', label: 'Montant' },
@ -264,4 +277,4 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
);
};
export default FeesSection;
export default RegistrationFeesSection;

View File

@ -1,11 +1,11 @@
import React, { useState } from 'react';
import { Plus, Trash, Edit3, Check, X } from 'lucide-react';
import { Plus, Trash, Edit3, Check, X, EyeOff, Eye } from 'lucide-react';
import Table from '@/components/Table';
import InputTextIcon from '@/components/InputTextIcon';
import Popup from '@/components/Popup';
import SelectChoice from '@/components/SelectChoice';
const TuitionFeesSection = ({ tuitionFees, setTuitionFees, discounts, fees, handleCreate, handleEdit, handleDelete }) => {
const TuitionFeesSection = ({ tuitionFees, setTuitionFees, discounts, registrationFees, handleCreate, handleEdit, handleDelete }) => {
const [editingTuitionFee, setEditingTuitionFee] = useState(null);
const [newTuitionFee, setNewTuitionFee] = useState(null);
const [formData, setFormData] = useState({});
@ -39,7 +39,11 @@ const TuitionFeesSection = ({ tuitionFees, setTuitionFees, discounts, fees, hand
newTuitionFee.base_amount &&
newTuitionFee.payment_option >= 0
) {
handleCreate(newTuitionFee)
const tuitionFeeData = {
...newTuitionFee,
type: 1
};
handleCreate(tuitionFeeData)
.then((createdTuitionFee) => {
setTuitionFees([createdTuitionFee, ...tuitionFees]);
setNewTuitionFee(null);
@ -151,11 +155,18 @@ const TuitionFeesSection = ({ tuitionFees, setTuitionFees, discounts, fees, hand
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;
if (discount) {
if (discount.discount_type === 0) { // Currency
return sum + parseFloat(discount.amount);
} else if (discount.discount_type === 1) { // Percent
return sum + (parseFloat(baseAmount) * parseFloat(discount.amount) / 100);
}
}
return sum;
}, 0);
const finalAmount = parseFloat(baseAmount) - totalDiscounts;
return finalAmount.toFixed(2);
};
@ -238,7 +249,7 @@ const TuitionFeesSection = ({ tuitionFees, setTuitionFees, discounts, fees, hand
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" />}
{tuitionFee.is_active ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
);