feat(FE-208): implement purchase order detail view with collapsible sections and summary table

This commit is contained in:
rstubryan
2025-11-13 16:43:08 +07:00
parent e5d9612e29
commit 7485919e52
@@ -1,18 +1,67 @@
'use client';
import { useMemo } from 'react';
import { ColumnDef } from '@tanstack/react-table';
import ApprovalSteps, {
formatGroupedApprovalsToApprovalSteps,
} from '@/components/pages/ApprovalSteps';
import Table from '@/components/Table';
import { BaseGroupedApproval } from '@/types/api/api-general';
import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line';
import Card from '@/components/Card';
interface PurchaseOrderDetailProps {
type?: 'detail' | 'edit';
}
interface PurchaseOrderItem {
id: number;
product: string;
productType: string;
quantity: number;
unit: string;
unitPrice: number;
total: number;
}
interface PurchaseOrderInfo {
businessUnit: string;
area: string;
location: string;
warehouse: string;
vendorName: string;
vendorAddress: string;
dueDate: string;
number: string;
poNumber: string;
}
const dummyPurchaseOrderInfo: PurchaseOrderInfo = {
businessUnit: 'PT MITRA BERLIAN UNGGAS',
area: 'Banten 2',
location: 'FARM PASIR TAPLOK',
warehouse: 'GUDANG PASIR TAPLOK 1',
vendorName: 'PT. CHAROEN POKPHAND JAYA FARM',
vendorAddress: '-',
dueDate: '13-Nov-2025 (1 hari)',
number: 'PR-MBU-01837',
poNumber: 'Belum dibuat',
};
const dummyPurchaseOrderItems: PurchaseOrderItem[] = [
{
id: 1,
product: 'CP Vaksin',
productType: 'DOC',
quantity: 10000,
unit: 'Ekor',
unitPrice: 6500,
total: 65000000,
},
];
const dummyGroupedApprovals: BaseGroupedApproval[] = [
{
step_number: 1,
@@ -113,6 +162,8 @@ const dummyGroupedApprovals: BaseGroupedApproval[] = [
const PurchaseOrderDetail = ({ type = 'detail' }: PurchaseOrderDetailProps) => {
// ===== STATIC DATA =====
const purchaseOrderInfo = dummyPurchaseOrderInfo;
const purchaseOrderItems = dummyPurchaseOrderItems;
const groupedApprovals = dummyGroupedApprovals;
const latestApproval =
groupedApprovals[groupedApprovals.length - 1]?.approvals[0];
@@ -133,16 +184,270 @@ const PurchaseOrderDetail = ({ type = 'detail' }: PurchaseOrderDetailProps) => {
}
}, [groupedApprovals, latestApproval]);
const totalBeforeTax = useMemo(() => {
return purchaseOrderItems.reduce((sum, item) => sum + item.total, 0);
}, [purchaseOrderItems]);
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 2,
}).format(value);
};
const formatNumber = (value: number) => {
return new Intl.NumberFormat('id-ID').format(value);
};
const purchaseOrderColumns: ColumnDef<PurchaseOrderItem>[] = [
{
accessorKey: 'product',
header: 'Produk',
},
{
accessorKey: 'productType',
header: 'Jenis Produk',
},
{
accessorKey: 'quantity',
header: 'Jumlah',
cell: (props) => formatNumber(props.getValue() as number),
},
{
accessorKey: 'unit',
header: 'Satuan',
},
{
accessorKey: 'unitPrice',
header: 'Harga Satuan',
cell: (props) => formatCurrency(props.getValue() as number),
},
{
accessorKey: 'total',
header: 'Total (Rp.)',
cell: (props) => formatCurrency(props.getValue() as number),
},
];
const summaryData = [
{
label: 'Total Sebelum Pajak',
value: totalBeforeTax,
},
{
label: 'Total Pembayaran',
value: totalBeforeTax,
},
];
const summaryColumns: ColumnDef<(typeof summaryData)[0]>[] = [
{
accessorKey: 'label',
header: '',
cell: (props) => (
<span className='font-semibold text-gray-700 text-sm'>
{props.getValue() as string}
</span>
),
},
{
accessorKey: 'value',
header: '',
cell: (props) => (
<span className='font-semibold text-gray-900 text-sm text-right'>
{formatCurrency(props.getValue() as number)}
</span>
),
},
];
return (
<section className='w-full'>
{/* Steps */}
{approvalSteps.length > 0 ? (
<div className='my-8'>
<ApprovalSteps approvals={approvalSteps} />
</div>
) : (
<div className='text-center py-8'>
<p className='text-gray-500'>Status approval tidak tersedia</p>
</div>
)}
{/* Detail Purchase Order */}
<Card
collapsible
title='Detail Purchase Order'
variant='bordered'
className={{
wrapper: 'w-full',
}}
>
{/* Order Information */}
<div className='bg-gray-50 rounded-lg p-6 mb-8'>
<h3 className='text-lg font-semibold text-gray-800 mb-6 pb-3 border-b border-gray-200'>
Informasi Pesanan
</h3>
<div className='grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-4'>
{/* Kolom 1 */}
<div className='space-y-4'>
<div className='group'>
<div className='flex items-start'>
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
Unit Bisnis
</span>
<span className='text-gray-900 font-medium ml-3 break-all'>
{purchaseOrderInfo.businessUnit}
</span>
</div>
</div>
<div className='group'>
<div className='flex items-start'>
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
Area
</span>
<span className='text-gray-900 ml-3 break-all'>
{purchaseOrderInfo.area}
</span>
</div>
</div>
<div className='group'>
<div className='flex items-start'>
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
Lokasi
</span>
<span className='text-gray-900 ml-3 break-all'>
{purchaseOrderInfo.location}
</span>
</div>
</div>
<div className='group'>
<div className='flex items-start'>
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
Gudang Penyimpanan
</span>
<span className='text-gray-900 ml-3 break-all'>
{purchaseOrderInfo.warehouse}
</span>
</div>
</div>
</div>
{/* Kolom 2 */}
<div className='space-y-4'>
<div className='group'>
<div className='flex items-start'>
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
Nama Vendor
</span>
<span className='text-gray-900 font-medium ml-3 break-all'>
{purchaseOrderInfo.vendorName}
</span>
</div>
</div>
<div className='group'>
<div className='flex items-start'>
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
Alamat Vendor
</span>
<span className='text-gray-900 ml-3 break-all'>
{purchaseOrderInfo.vendorAddress}
</span>
</div>
</div>
<div className='group'>
<div className='flex items-start'>
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
Tgl. Jatuh Tempo
</span>
<span className='text-gray-900 ml-3 break-all'>
{purchaseOrderInfo.dueDate}
</span>
</div>
</div>
<div className='group'>
<div className='flex items-start'>
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
Nomor
</span>
<span className='text-gray-900 ml-3 break-all font-mono text-sm'>
{purchaseOrderInfo.number}
</span>
</div>
</div>
<div className='group'>
<div className='flex items-start'>
<span className='font-medium text-gray-600 min-w-[140px] flex-shrink-0'>
Nomor PO
</span>
<span className='text-gray-900 ml-3 break-all font-mono text-sm'>
{purchaseOrderInfo.poNumber}
</span>
</div>
</div>
</div>
</div>
</div>
{/* Item Pembelian Section */}
<div className='mb-8'>
<h3 className='text-lg font-semibold text-gray-800 mb-4 pb-2 border-b border-gray-200'>
Item Pembelian
</h3>
<div className='rounded-lg border border-gray-200 overflow-hidden'>
{/* Product Table */}
<div className='overflow-x-auto'>
<Table<PurchaseOrderItem>
data={purchaseOrderItems}
columns={purchaseOrderColumns}
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-6 py-4 text-sm font-semibold text-gray-700 text-left',
bodyRowClassName:
'border-b border-gray-100 hover:bg-gray-50 transition-colors',
bodyColumnClassName: 'px-6 py-4 text-sm text-gray-900',
paginationClassName: 'hidden',
}}
/>
</div>
{/* Bottom Section - Catatan dan Total */}
<div className='border-t border-gray-200 grid grid-cols-1 lg:grid-cols-5 divide-x divide-gray-200'>
{/* Catatan Section */}
<div className='lg:col-span-3 p-6 border-r border-gray-200'>
<h4 className='font-medium text-gray-700 mb-3'>Catatan</h4>
<div className='text-gray-900 min-h-[60px] italic text-sm'></div>
</div>
{/* Summary Section */}
<div className='lg:col-span-2 p-6'>
<Table
data={summaryData}
columns={summaryColumns}
isLoading={false}
className={{
containerClassName: 'm-0',
tableWrapperClassName: 'overflow-x-auto',
tableClassName: 'w-full table-auto',
headerRowClassName: 'hidden',
headerColumnClassName: 'hidden',
paginationClassName: 'hidden',
bodyRowClassName:
'border-b border-gray-100 hover:bg-gray-50 transition-colors',
bodyColumnClassName: 'px-6 py-4 text-sm text-gray-900',
}}
/>
</div>
</div>
</div>
</div>
</Card>
</section>
);
};