Files
lti-web-client/src/components/pages/closing/table/SalesClosingTable.tsx
T

376 lines
11 KiB
TypeScript

'use client';
import React, { useMemo } from 'react';
import { ColumnDef } from '@tanstack/react-table';
import Table from '@/components/Table';
import Card from '@/components/Card';
import { formatCurrency, formatNumber, formatDate } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper';
import { BaseSales, ClosingSalesSummary } from '@/types/api/closing';
import { Product } from '@/types/api/master-data/product';
import { Customer } from '@/types/api/master-data/customer';
import { Kandang } from '@/types/api/master-data/kandang';
import { ClosingApi } from '@/services/api/closing';
import { useSearchParams } from 'next/navigation';
import useSWR from 'swr';
import SalesClosingSkeleton from '@/components/pages/closing/skeleton/SalesClosingSkeleton';
interface SalesClosingTableProps {
projectFlockId: number;
}
const SalesClosingTable = ({ projectFlockId }: SalesClosingTableProps) => {
const searchParams = useSearchParams();
const kandangId = searchParams.get('kandangId');
const { data: sales, isLoading } = useSWR(
kandangId
? `/closing/sales/${projectFlockId}/${kandangId}`
: `/closing/sales/${projectFlockId}`,
() =>
kandangId
? ClosingApi.getPenjualanByKandang(projectFlockId, Number(kandangId))
: ClosingApi.getPenjualan(projectFlockId)
);
const salesData: BaseSales[] = useMemo(() => {
if (isResponseSuccess(sales)) {
return sales.data.sales || [];
}
return [];
}, [sales]);
const summary: ClosingSalesSummary | undefined = useMemo(() => {
if (isResponseSuccess(sales)) {
return sales.data.summary;
}
return undefined;
}, [sales]);
const totals = useMemo(() => {
if (salesData.length === 0) {
return {
totalQuantity: 0,
totalWeight: 0,
avgWeight: 0,
avgSalesPrice: 0,
totalSalesPrice: 0,
avgActualPrice: 0,
totalActualPrice: 0,
};
}
const totalQuantity = salesData.reduce(
(sum, item) => sum + (item.qty || 0),
0
);
const totalWeight = salesData.reduce(
(sum, item) => sum + (item.weight || 0),
0
);
const avgWeight = totalQuantity > 0 ? totalWeight / totalQuantity : 0;
const totalSalesPrice = salesData.reduce(
(sum, item) => sum + (item.total_sales_price || 0),
0
);
const validSalesPriceItems = salesData.filter(
(item) => item.sales_price != null && item.sales_price > 0
);
const avgSalesPrice =
validSalesPriceItems.length > 0
? validSalesPriceItems.reduce(
(sum, item) => sum + item.sales_price,
0
) / validSalesPriceItems.length
: 0;
const totalActualPrice = salesData.reduce(
(sum, item) => sum + (item.total_actual_price || 0),
0
);
const validActualPriceItems = salesData.filter(
(item) => item.actual_price != null && item.actual_price > 0
);
const avgActualPrice =
validActualPriceItems.length > 0
? validActualPriceItems.reduce(
(sum, item) => sum + item.actual_price,
0
) / validActualPriceItems.length
: 0;
return {
totalQuantity,
totalWeight,
avgWeight,
avgSalesPrice,
totalSalesPrice,
avgActualPrice,
totalActualPrice,
};
}, [salesData]);
const salesColumns: ColumnDef<BaseSales>[] = useMemo(
() => [
{
id: 'realization_date',
accessorKey: 'realization_date',
header: 'Tanggal Realisasi',
cell: (props) => {
const date = props.row.original.realization_date;
return date ? formatDate(date, 'DD MMM YYYY') : '-';
},
footer: () => (
<div className='font-semibold text-gray-900'>Total Penjualan</div>
),
},
{
id: 'age',
accessorKey: 'age',
header: 'Umur',
cell: (props) => {
const age = props.row.original.age;
const week = props.row.original.week;
return age && week ? `${age} hari (${week} minggu)` : '-';
},
},
{
id: 'do_number',
accessorKey: 'do_number',
header: 'No. DO',
cell: (props) => props.getValue() || '-',
},
{
id: 'product',
accessorKey: 'product',
header: 'Produk',
cell: (props) => {
const product = props.getValue() as Product;
return product?.name || '-';
},
},
{
id: 'customer',
accessorKey: 'customer',
header: 'Customer',
cell: (props) => {
const customer = props.getValue() as Customer;
return customer?.name || '-';
},
},
{
id: 'jumlah',
header: 'Jumlah',
columns: [
{
id: 'qty',
accessorKey: 'qty',
header: 'Kuantitas',
cell: (props) => {
const value = props.getValue() as number;
return <div className='text-left'>{formatNumber(value)}</div>;
},
footer: () => (
<div className='text-left font-semibold text-gray-900'>
{formatNumber(totals.totalQuantity)}
</div>
),
},
{
id: 'weight',
accessorKey: 'weight',
header: 'Kg',
cell: (props) => {
const value = props.getValue() as number;
return <div className='text-left'>{formatNumber(value)}</div>;
},
footer: () => (
<div className='text-left font-semibold text-gray-900'>
{formatNumber(totals.totalWeight)}
</div>
),
},
],
},
{
id: 'avg_weight',
accessorKey: 'avg_weight',
header: 'AVG (Kg)',
cell: (props) => {
const value = props.getValue() as number;
return <div className='text-left'>{formatNumber(value)}</div>;
},
footer: () => (
<div className='text-left font-semibold text-gray-900'>
{formatNumber(totals.avgWeight)}
</div>
),
},
{
id: 'sales_price',
accessorKey: 'sales_price',
header: 'Harga Sales (Rp)',
cell: (props) => {
const value = props.getValue() as number;
return <div className='text-right'>{formatCurrency(value)}</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>
{summary
? formatCurrency(summary.avg_sales_price)
: formatCurrency(totals.avgSalesPrice)}
</div>
),
},
{
id: 'total_sales_price',
accessorKey: 'total_sales_price',
header: 'Total Sales (Rp)',
cell: (props) => {
const value = props.getValue() as number;
return <div className='text-right'>{formatCurrency(value)}</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>
{summary
? formatCurrency(summary.total_sales_price)
: formatCurrency(totals.totalSalesPrice)}
</div>
),
},
{
id: 'actual_price',
accessorKey: 'actual_price',
header: 'Harga Act (Rp)',
cell: (props) => {
const value = props.getValue() as number;
return <div className='text-right'>{formatCurrency(value)}</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>
{summary
? formatCurrency(summary.avg_actual_price)
: formatCurrency(totals.avgActualPrice)}
</div>
),
},
{
id: 'total_actual_price',
accessorKey: 'total_actual_price',
header: 'Total Act (Rp)',
cell: (props) => {
const value = props.getValue() as number;
return <div className='text-right'>{formatCurrency(value)}</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>
{summary
? formatCurrency(summary.total_actual_price)
: formatCurrency(totals.totalActualPrice)}
</div>
),
},
{
id: 'kandang',
accessorKey: 'kandang',
header: 'Kandang',
cell: (props) => {
const kandang = props.getValue() as Kandang;
return kandang?.name || '-';
},
},
// {
// id: 'payment_status',
// accessorKey: 'payment_status',
// header: 'Status Pembayaran',
// cell: (props) => {
// const status = props.getValue() as string;
// const getStatusColor = (status: string) => {
// if (!status) return 'neutral';
// switch (status.toLowerCase()) {
// case 'paid':
// return 'success';
// case 'tempo':
// return 'warning';
// default:
// return 'neutral';
// }
// };
// return (
// <Badge variant='soft' size='sm' color={getStatusColor(status)}>
// {status || '-'}
// </Badge>
// );
// },
// },
],
[
summary,
totals.avgActualPrice,
totals.avgSalesPrice,
totals.avgWeight,
totals.totalActualPrice,
totals.totalQuantity,
totals.totalSalesPrice,
totals.totalWeight,
]
);
return (
<div className='w-full pt-3'>
<Card
className={{
wrapper: 'w-full rounded-lg border-none',
body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}}
variant='bordered'
title='Penjualan'
collapsible
defaultCollapsed={false}
>
{isLoading ? (
<SalesClosingSkeleton columns={salesColumns} />
) : salesData.length === 0 ? (
<SalesClosingSkeleton
columns={salesColumns}
iconName='heroicons:chart-bar'
/>
) : (
<Table
data={salesData}
columns={salesColumns}
isLoading={isLoading}
renderFooter={salesData.length > 0}
className={{
containerClassName: 'w-full mb-0!',
tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'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:
'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>
</div>
);
};
export default SalesClosingTable;