mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
refactor(FE-208,212): implement grouping of goods receipt items and enhance table rendering in PurchaseOrderDetail
This commit is contained in:
@@ -106,6 +106,49 @@ const PurchaseOrderDetail = ({
|
|||||||
return purchaseOrderItems.filter((item) => item.received_date);
|
return purchaseOrderItems.filter((item) => item.received_date);
|
||||||
}, [purchaseOrderItems]);
|
}, [purchaseOrderItems]);
|
||||||
|
|
||||||
|
const groupedGoodsReceiptItems = useMemo(() => {
|
||||||
|
const uniqueProducts = Array.from(
|
||||||
|
new Map(
|
||||||
|
goodsReceiptItems.map((item) => [item.product?.id, item])
|
||||||
|
).values()
|
||||||
|
);
|
||||||
|
|
||||||
|
return uniqueProducts.map((item, index) => {
|
||||||
|
const productGroupItems = goodsReceiptItems.filter(
|
||||||
|
(groupItem) => groupItem.product?.id === item.product?.id
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalQty = productGroupItems.reduce(
|
||||||
|
(sum, item) => sum + (item.total_qty || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const receivedQty = productGroupItems.reduce(
|
||||||
|
(sum, item) => sum + (item.sub_qty || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const unreceivedQty = totalQty - receivedQty;
|
||||||
|
const nominalReceived = productGroupItems.reduce(
|
||||||
|
(sum, item) => sum + (item.sub_qty || 0) * (item.price || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const nominalUnreceived = productGroupItems.reduce(
|
||||||
|
(sum, item) => sum + unreceivedQty * (item.price || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
productIndex: index + 1,
|
||||||
|
productName: item.product?.name || 'Unknown Product',
|
||||||
|
productGroupItems,
|
||||||
|
totalQty,
|
||||||
|
receivedQty,
|
||||||
|
unreceivedQty,
|
||||||
|
nominalReceived,
|
||||||
|
nominalUnreceived,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [goodsReceiptItems]);
|
||||||
|
|
||||||
const canUpdatePurchaseItems = useMemo(() => {
|
const canUpdatePurchaseItems = useMemo(() => {
|
||||||
if (!initialValues?.approval) return false;
|
if (!initialValues?.approval) return false;
|
||||||
|
|
||||||
@@ -337,84 +380,85 @@ const PurchaseOrderDetail = ({
|
|||||||
|
|
||||||
const goodsReceiptColumns: ColumnDef<PurchaseItem>[] = [
|
const goodsReceiptColumns: ColumnDef<PurchaseItem>[] = [
|
||||||
{
|
{
|
||||||
header: 'Header Placeholder untuk tiap Produk Penerimaan Barang',
|
header: 'Tanggal Penerimaan',
|
||||||
columns: [
|
accessorKey: 'received_date',
|
||||||
{
|
cell: (props) =>
|
||||||
header: 'No',
|
props.row.original.received_date
|
||||||
cell: (props) => props.row.index + 1,
|
? formatDate(props.row.original.received_date, 'DD MMM YYYY')
|
||||||
},
|
: '-',
|
||||||
{
|
},
|
||||||
accessorKey: 'received_date',
|
{
|
||||||
header: 'Tanggal Penerimaan',
|
header: 'Gudang Tujuan',
|
||||||
cell: (props) =>
|
accessorKey: 'warehouse.name',
|
||||||
props.row.original.received_date
|
cell: (props) => {
|
||||||
? formatDate(props.row.original.received_date, 'DD MMM YYYY')
|
const warehouse = props.row.original.warehouse;
|
||||||
: '-',
|
return warehouse?.name || '-';
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
accessorKey: 'warehouse.name',
|
{
|
||||||
header: 'Gudang Tujuan',
|
header: 'No. Surat Jalan',
|
||||||
cell: (props) => {
|
accessorKey: 'travel_number',
|
||||||
const warehouse = props.row.original.warehouse;
|
cell: (props) => props.row.original.travel_number || '-',
|
||||||
return warehouse?.name || '-';
|
},
|
||||||
},
|
{
|
||||||
},
|
header: 'Dokumen Surat Jalan',
|
||||||
{
|
accessorKey: 'travel_document_path',
|
||||||
accessorKey: 'travel_number',
|
cell: (props) => {
|
||||||
header: 'No. Surat Jalan',
|
const documentPath = props.row.original.travel_document_path;
|
||||||
cell: (props) => props.row.original.travel_number || '-',
|
return documentPath ? (
|
||||||
},
|
<Button
|
||||||
{
|
color='primary'
|
||||||
accessorKey: 'travel_document_path',
|
className='w-fit min-w-32 flex items-center justify-start gap-1 px-2 py-1 text-sm'
|
||||||
header: 'Dokumen Surat Jalan',
|
href={documentPath}
|
||||||
cell: (props) => {
|
target='_blank'
|
||||||
const documentPath = props.row.original.travel_document_path;
|
rel='noopener noreferrer'
|
||||||
return documentPath ? (
|
>
|
||||||
<Button
|
<Icon
|
||||||
color='primary'
|
icon='material-symbols:file-open-outline'
|
||||||
className='w-fit min-w-32 flex items-center justify-start gap-1 px-2 py-1 text-sm'
|
width={16}
|
||||||
href={documentPath}
|
height={16}
|
||||||
target='_blank'
|
/>
|
||||||
rel='noopener noreferrer'
|
Lihat Dokumen
|
||||||
>
|
</Button>
|
||||||
<Icon
|
) : (
|
||||||
icon='material-symbols:file-open-outline'
|
'-'
|
||||||
width={16}
|
);
|
||||||
height={16}
|
},
|
||||||
/>
|
},
|
||||||
Lihat Dokumen
|
{
|
||||||
</Button>
|
header: 'No. Armada Pengangkut',
|
||||||
) : (
|
accessorKey: 'vehicle_number',
|
||||||
'-'
|
cell: (props) => props.row.original.vehicle_number || '-',
|
||||||
);
|
},
|
||||||
},
|
{
|
||||||
},
|
header: 'Jumlah Total',
|
||||||
{
|
accessorKey: 'total_qty',
|
||||||
accessorKey: 'vehicle_number',
|
cell: (props) => formatNumber(props.getValue() as number),
|
||||||
header: 'No. Armada',
|
},
|
||||||
cell: (props) => props.row.original.vehicle_number || '-',
|
{
|
||||||
},
|
header: 'Jumlah Diterima',
|
||||||
{
|
accessorKey: 'sub_qty',
|
||||||
accessorKey: 'total_qty',
|
cell: (props) => formatNumber(props.getValue() as number),
|
||||||
header: 'Jumlah Total',
|
},
|
||||||
cell: (props) => formatNumber(props.getValue() as number),
|
{
|
||||||
},
|
header: 'Jumlah Retur',
|
||||||
{
|
accessorKey: 'return_qty',
|
||||||
accessorKey: 'sub_qty',
|
cell: (props) => formatNumber((props.getValue() as number) || 0),
|
||||||
header: 'Jumlah Diterima',
|
},
|
||||||
cell: (props) => formatNumber(props.getValue() as number),
|
{
|
||||||
},
|
header: 'Ekspedisi',
|
||||||
{
|
accessorKey: 'expedition_name',
|
||||||
accessorKey: 'transport_per_item',
|
cell: (props) => '-',
|
||||||
header: 'Transport /Item',
|
},
|
||||||
cell: (props) => formatCurrency(props.getValue() as number),
|
{
|
||||||
},
|
header: 'Transport /Item',
|
||||||
{
|
accessorKey: 'transport_per_item',
|
||||||
accessorKey: 'transport_total',
|
cell: (props) => formatCurrency(props.getValue() as number),
|
||||||
header: 'Transport Total',
|
},
|
||||||
cell: (props) => formatCurrency(props.getValue() as number),
|
{
|
||||||
},
|
header: 'Transport Total',
|
||||||
],
|
accessorKey: 'transport_total',
|
||||||
|
cell: (props) => formatCurrency(props.getValue() as number),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -732,24 +776,47 @@ const PurchaseOrderDetail = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='rounded-lg border border-gray-200 overflow-hidden'>
|
<div className='rounded-lg border border-gray-200 overflow-hidden'>
|
||||||
<div className='overflow-x-auto'>
|
<div className='overflow-x-auto'>
|
||||||
<Table<PurchaseItem>
|
{groupedGoodsReceiptItems.length > 0 ? (
|
||||||
data={goodsReceiptItems}
|
<div className='space-y-8'>
|
||||||
columns={goodsReceiptColumns}
|
{groupedGoodsReceiptItems.map((productData) => (
|
||||||
isLoading={false}
|
<div
|
||||||
className={{
|
key={productData.productIndex}
|
||||||
containerClassName: 'm-0',
|
className='border border-gray-200 rounded-lg overflow-hidden'
|
||||||
tableWrapperClassName: 'overflow-x-auto',
|
>
|
||||||
tableClassName: 'w-full table-auto',
|
{/* Product Header */}
|
||||||
headerRowClassName: 'bg-gray-50 border-b border-gray-200',
|
<div className='font-semibold text-gray-900 bg-gray-100 px-6 py-4 text-lg'>
|
||||||
headerColumnClassName:
|
{productData.productIndex}.{' '}
|
||||||
'px-4 py-3 text-sm font-semibold text-gray-700 text-left whitespace-nowrap',
|
{productData.productName.toUpperCase()}
|
||||||
bodyRowClassName:
|
</div>
|
||||||
'border-b border-gray-100 hover:bg-gray-50 transition-colors',
|
|
||||||
bodyColumnClassName:
|
{/* Product Table */}
|
||||||
'px-4 py-3 text-sm text-gray-900 whitespace-nowrap',
|
<Table<PurchaseItem>
|
||||||
paginationClassName: 'hidden',
|
data={productData.productGroupItems}
|
||||||
}}
|
columns={goodsReceiptColumns}
|
||||||
/>
|
isLoading={false}
|
||||||
|
className={{
|
||||||
|
containerClassName: 'm-0',
|
||||||
|
tableWrapperClassName: 'overflow-x-auto',
|
||||||
|
tableClassName: 'w-full table-auto',
|
||||||
|
headerRowClassName:
|
||||||
|
'bg-gray-50 border-b border-gray-200',
|
||||||
|
headerColumnClassName:
|
||||||
|
'px-4 py-3 text-sm font-semibold text-gray-700 text-left whitespace-nowrap',
|
||||||
|
bodyRowClassName:
|
||||||
|
'border-b border-gray-100 hover:bg-gray-50 transition-colors',
|
||||||
|
bodyColumnClassName:
|
||||||
|
'px-4 py-3 text-sm text-gray-900 whitespace-nowrap',
|
||||||
|
paginationClassName: 'hidden',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='text-center py-8 text-gray-500'>
|
||||||
|
Tidak ada data penerimaan barang
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user