mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 07:15:44 +00:00
feat(FE-326): Add totals footer row to sales report table
This commit is contained in:
@@ -16,6 +16,10 @@ interface SalesReportTableProps {
|
|||||||
initialValues?: BaseClosingSales;
|
initialValues?: BaseClosingSales;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FooterSalesRow extends BaseSales {
|
||||||
|
_isFooter: true;
|
||||||
|
}
|
||||||
|
|
||||||
const SalesReportTable = ({
|
const SalesReportTable = ({
|
||||||
type = 'detail',
|
type = 'detail',
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -68,6 +72,29 @@ const SalesReportTable = ({
|
|||||||
};
|
};
|
||||||
}, [salesData]);
|
}, [salesData]);
|
||||||
|
|
||||||
|
const footerData = useMemo((): FooterSalesRow[] => {
|
||||||
|
if (salesData.length === 0) return [];
|
||||||
|
|
||||||
|
const footerRow: FooterSalesRow = {
|
||||||
|
id: -999,
|
||||||
|
realization_date: 'Total Penjualan',
|
||||||
|
age: 0,
|
||||||
|
do_number: '',
|
||||||
|
product: {} as Product,
|
||||||
|
customer: {} as Customer,
|
||||||
|
qty: totals.totalQuantity,
|
||||||
|
weight: totals.totalWeight,
|
||||||
|
avg_weight: totals.avgWeight,
|
||||||
|
price: totals.avgPricePartner,
|
||||||
|
total_price: totals.totalPartner,
|
||||||
|
kandang: {} as Kandang,
|
||||||
|
payment_status: '',
|
||||||
|
_isFooter: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [footerRow];
|
||||||
|
}, [salesData, totals]);
|
||||||
|
|
||||||
const salesColumns: ColumnDef<BaseSales>[] = useMemo(
|
const salesColumns: ColumnDef<BaseSales>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -75,6 +102,14 @@ const SalesReportTable = ({
|
|||||||
accessorKey: 'realization_date',
|
accessorKey: 'realization_date',
|
||||||
header: 'Tanggal Realisasi',
|
header: 'Tanggal Realisasi',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
if (isFooter) {
|
||||||
|
return (
|
||||||
|
<div className='font-semibold text-gray-900 col-span-5'>
|
||||||
|
{props.row.original.realization_date}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
const date = props.row.original.realization_date;
|
const date = props.row.original.realization_date;
|
||||||
return date ? formatDate(date, 'DD MMM YYYY') : '-';
|
return date ? formatDate(date, 'DD MMM YYYY') : '-';
|
||||||
},
|
},
|
||||||
@@ -83,19 +118,27 @@ const SalesReportTable = ({
|
|||||||
id: 'age',
|
id: 'age',
|
||||||
accessorKey: 'age',
|
accessorKey: 'age',
|
||||||
header: 'Umur',
|
header: 'Umur',
|
||||||
cell: (props) => props.getValue() || '-',
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return isFooter ? null : props.getValue() || '-';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'do_number',
|
id: 'do_number',
|
||||||
accessorKey: 'do_number',
|
accessorKey: 'do_number',
|
||||||
header: 'No. DO',
|
header: 'No. DO',
|
||||||
cell: (props) => props.getValue() || '-',
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return isFooter ? null : props.getValue() || '-';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'product',
|
id: 'product',
|
||||||
accessorKey: 'product',
|
accessorKey: 'product',
|
||||||
header: 'Produk',
|
header: 'Produk',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
if (isFooter) return null;
|
||||||
const product = props.getValue() as Product;
|
const product = props.getValue() as Product;
|
||||||
return product?.name || '-';
|
return product?.name || '-';
|
||||||
},
|
},
|
||||||
@@ -105,6 +148,8 @@ const SalesReportTable = ({
|
|||||||
accessorKey: 'customer',
|
accessorKey: 'customer',
|
||||||
header: 'Customer',
|
header: 'Customer',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
if (isFooter) return null;
|
||||||
const customer = props.getValue() as Customer;
|
const customer = props.getValue() as Customer;
|
||||||
return customer?.name || '-';
|
return customer?.name || '-';
|
||||||
},
|
},
|
||||||
@@ -115,9 +160,13 @@ const SalesReportTable = ({
|
|||||||
header: 'Kuantitas',
|
header: 'Kuantitas',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
const isSummary = props.row.id === 'summary';
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
return (
|
return (
|
||||||
<div className={isSummary ? 'text-right' : ''}>
|
<div
|
||||||
|
className={
|
||||||
|
isFooter ? 'text-left font-semibold text-gray-900' : 'text-left'
|
||||||
|
}
|
||||||
|
>
|
||||||
{formatNumber(value)}
|
{formatNumber(value)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -129,9 +178,13 @@ const SalesReportTable = ({
|
|||||||
header: 'Kg',
|
header: 'Kg',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
const isSummary = props.row.id === 'summary';
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
return (
|
return (
|
||||||
<div className={isSummary ? 'text-right' : ''}>
|
<div
|
||||||
|
className={
|
||||||
|
isFooter ? 'text-left font-semibold text-gray-900' : 'text-left'
|
||||||
|
}
|
||||||
|
>
|
||||||
{formatNumber(value)}
|
{formatNumber(value)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -143,9 +196,13 @@ const SalesReportTable = ({
|
|||||||
header: 'AVG (Kg)',
|
header: 'AVG (Kg)',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
const isSummary = props.row.id === 'summary';
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
return (
|
return (
|
||||||
<div className={isSummary ? 'text-right' : ''}>
|
<div
|
||||||
|
className={
|
||||||
|
isFooter ? 'text-left font-semibold text-gray-900' : 'text-left'
|
||||||
|
}
|
||||||
|
>
|
||||||
{formatNumber(value)}
|
{formatNumber(value)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -157,7 +214,18 @@ const SalesReportTable = ({
|
|||||||
header: 'Harga Mitra (Rp)',
|
header: 'Harga Mitra (Rp)',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
isFooter
|
||||||
|
? 'text-right font-semibold text-gray-900'
|
||||||
|
: 'text-right'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{formatCurrency(value)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -166,7 +234,18 @@ const SalesReportTable = ({
|
|||||||
header: 'Total Mitra (Rp)',
|
header: 'Total Mitra (Rp)',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
isFooter
|
||||||
|
? 'text-right font-semibold text-gray-900'
|
||||||
|
: 'text-right'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{formatCurrency(value)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -175,7 +254,18 @@ const SalesReportTable = ({
|
|||||||
header: 'Harga Act (Rp)',
|
header: 'Harga Act (Rp)',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
isFooter
|
||||||
|
? 'text-right font-semibold text-gray-900'
|
||||||
|
: 'text-right'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{formatCurrency(value)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -184,7 +274,18 @@ const SalesReportTable = ({
|
|||||||
header: 'Total Act (Rp)',
|
header: 'Total Act (Rp)',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const value = props.getValue() as number;
|
const value = props.getValue() as number;
|
||||||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
isFooter
|
||||||
|
? 'text-right font-semibold text-gray-900'
|
||||||
|
: 'text-right'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{formatCurrency(value)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -192,6 +293,8 @@ const SalesReportTable = ({
|
|||||||
accessorKey: 'kandang',
|
accessorKey: 'kandang',
|
||||||
header: 'Kandang',
|
header: 'Kandang',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
if (isFooter) return null;
|
||||||
const kandang = props.getValue() as Kandang;
|
const kandang = props.getValue() as Kandang;
|
||||||
return kandang?.name || '-';
|
return kandang?.name || '-';
|
||||||
},
|
},
|
||||||
@@ -201,6 +304,9 @@ const SalesReportTable = ({
|
|||||||
accessorKey: 'payment_status',
|
accessorKey: 'payment_status',
|
||||||
header: 'Status Pembayaran',
|
header: 'Status Pembayaran',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
|
const isFooter = '_isFooter' in props.row.original;
|
||||||
|
if (isFooter) return null;
|
||||||
|
|
||||||
const status = props.getValue() as string;
|
const status = props.getValue() as string;
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
if (!status) return 'neutral';
|
if (!status) return 'neutral';
|
||||||
@@ -239,43 +345,8 @@ const SalesReportTable = ({
|
|||||||
<Table
|
<Table
|
||||||
data={salesData}
|
data={salesData}
|
||||||
columns={salesColumns}
|
columns={salesColumns}
|
||||||
|
footerData={footerData}
|
||||||
renderFooter={salesData.length > 0}
|
renderFooter={salesData.length > 0}
|
||||||
footerContent={
|
|
||||||
<tfoot>
|
|
||||||
<tr className='bg-gray-100 font-semibold border border-gray-200'>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '>
|
|
||||||
Total Penjualan
|
|
||||||
</td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '>
|
|
||||||
{formatNumber(totals.totalQuantity)}
|
|
||||||
</td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '>
|
|
||||||
{formatNumber(totals.totalWeight)}
|
|
||||||
</td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '>
|
|
||||||
{formatNumber(totals.avgWeight)}
|
|
||||||
</td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
|
|
||||||
{formatCurrency(totals.avgPricePartner)}
|
|
||||||
</td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
|
|
||||||
{formatCurrency(totals.totalPartner)}
|
|
||||||
</td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
|
|
||||||
{formatCurrency(totals.avgPricePartner)}
|
|
||||||
</td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
|
|
||||||
{formatCurrency(totals.totalPartner)}
|
|
||||||
</td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
}
|
|
||||||
className={{
|
className={{
|
||||||
tableWrapperClassName: 'overflow-x-auto',
|
tableWrapperClassName: 'overflow-x-auto',
|
||||||
tableClassName: 'w-full table-auto text-sm',
|
tableClassName: 'w-full table-auto text-sm',
|
||||||
@@ -286,6 +357,11 @@ const SalesReportTable = ({
|
|||||||
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
|
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
|
||||||
bodyColumnClassName:
|
bodyColumnClassName:
|
||||||
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||||||
|
tableFooterClassName:
|
||||||
|
'bg-gray-100 font-semibold border border-gray-200',
|
||||||
|
footerRowClassName: 'border-t-2 border-gray-300',
|
||||||
|
footerColumnClassName:
|
||||||
|
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user