mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 23:35:45 +00:00
904 lines
27 KiB
TypeScript
904 lines
27 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback, useMemo } from 'react';
|
|
import { ColumnDef } from '@tanstack/react-table';
|
|
|
|
import ApprovalSteps, {
|
|
formatGroupedApprovalsToApprovalSteps,
|
|
} from '@/components/pages/ApprovalSteps';
|
|
import Table from '@/components/Table';
|
|
import Button from '@/components/Button';
|
|
import { Icon } from '@iconify/react';
|
|
import { useModal } from '@/components/Modal';
|
|
import Modal from '@/components/Modal';
|
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
|
import PurchaseOrderStaffApprovalForm from '@/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm';
|
|
import PurchaseOrderAcceptApprovalForm from '@/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm';
|
|
import PurchaseOrderInvoice from '@/components/pages/purchase/order/PurchaseOrderInvoice';
|
|
|
|
import { BaseGroupedApproval } from '@/types/api/api-general';
|
|
import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line';
|
|
import Card from '@/components/Card';
|
|
import {
|
|
CreateManagerApprovalRequestPayload,
|
|
Purchase,
|
|
PurchaseItem,
|
|
} from '@/types/api/purchase/purchase';
|
|
import {
|
|
createdUser,
|
|
dummyAreas,
|
|
dummyLocations,
|
|
dummyProductWarehouses,
|
|
dummyWarehouses,
|
|
} from '@/dummy/marketing.dummy';
|
|
import { ManagerApprovalApi } from '@/services/api/purchase';
|
|
import { isResponseError } from '@/lib/api-helper';
|
|
import { toast } from 'react-hot-toast';
|
|
import { useSearchParams } from 'next/navigation';
|
|
|
|
const ItemPembelianDropdown = ({ onEdit }: { onEdit: () => void }) => {
|
|
return (
|
|
<RowOptionsMenuWrapper type='dropdown'>
|
|
<Button
|
|
onClick={onEdit}
|
|
variant='ghost'
|
|
color='warning'
|
|
className='justify-start text-sm'
|
|
>
|
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
Edit
|
|
</Button>
|
|
</RowOptionsMenuWrapper>
|
|
);
|
|
};
|
|
|
|
interface PurchaseOrderDetailProps {
|
|
type?: 'detail' | 'edit';
|
|
data?: Purchase;
|
|
}
|
|
|
|
const dummyPurchaseData: Purchase = {
|
|
id: 1,
|
|
pr_number: 'PR-MBU-01837',
|
|
po_number: 'PO-MBU-01837',
|
|
po_document_path: '/documents/po-mbu-01837.pdf',
|
|
po_date: '2025-01-10T00:00:00Z',
|
|
supplier: {
|
|
id: 1,
|
|
name: 'PT. CHAROEN POKPHAND JAYA FARM',
|
|
alias: 'CP JAYA FARM',
|
|
pic: 'Budi Santoso',
|
|
type: 'Supplier',
|
|
category: 'Feed',
|
|
hatchery: 'Jawa Barat',
|
|
phone: '+62-22-7563850',
|
|
email: 'info@cp.co.id',
|
|
address:
|
|
'Jl. Raya Bandung - Sumedang Km. 28, Desa Cisantana, Kec. Cigendel, Kabupaten Sumedang, Jawa Barat 45363',
|
|
npwp: '01.938.451.6-433.000',
|
|
account_number: '123-456-7890',
|
|
due_date: 30,
|
|
balance: 0,
|
|
created_at: '2025-01-01T00:00:00Z',
|
|
updated_at: '2025-01-01T00:00:00Z',
|
|
created_user: createdUser,
|
|
},
|
|
credit_term: 30,
|
|
due_date: '2025-11-13T00:00:00Z',
|
|
grand_total: 65000000,
|
|
notes: null,
|
|
area: dummyAreas[0],
|
|
location: dummyLocations[0],
|
|
items: [
|
|
{
|
|
id: 1,
|
|
purchase_id: 1,
|
|
product: {
|
|
id: 1,
|
|
name: 'CP Vaksin',
|
|
brand: '',
|
|
sku: '',
|
|
product_price: 0,
|
|
selling_price: 0,
|
|
tax: 0,
|
|
expiry_period: 0,
|
|
uom: {
|
|
id: 1,
|
|
name: 'Ekor',
|
|
created_user: {
|
|
id: 1,
|
|
id_user: 1,
|
|
email: 'hello@gmail.com',
|
|
name: 'Admin',
|
|
},
|
|
created_at: '2025-01-01T00:00:00Z',
|
|
updated_at: '2025-01-01T00:00:00Z',
|
|
},
|
|
product_category: {
|
|
id: 1,
|
|
code: 'DOC',
|
|
name: 'DOC',
|
|
created_user: {
|
|
id: 1,
|
|
id_user: 1,
|
|
email: 'hello@gmail.com',
|
|
name: 'Admin',
|
|
},
|
|
created_at: '2025-01-01T00:00:00Z',
|
|
updated_at: '2025-01-01T00:00:00Z',
|
|
},
|
|
suppliers: [],
|
|
flags: [],
|
|
created_user: {
|
|
id: 1,
|
|
id_user: 1,
|
|
email: 'hello@gmail.com',
|
|
name: 'Admin',
|
|
},
|
|
created_at: '2025-01-01T00:00:00Z',
|
|
updated_at: '2025-01-01T00:00:00Z',
|
|
},
|
|
product_warehouse: dummyProductWarehouses[0],
|
|
quantity: 10000,
|
|
sub_qty: 10000,
|
|
total_qty: 10000,
|
|
total_used: 0,
|
|
price: 6500,
|
|
total_price: 65000000,
|
|
received_date: null,
|
|
travel_number: null,
|
|
travel_number_docs: null,
|
|
vehicle_number: null,
|
|
warehouse: dummyWarehouses[0],
|
|
},
|
|
],
|
|
created_at: '2025-01-10T00:00:00Z',
|
|
updated_at: '2025-01-10T00:00:00Z',
|
|
created_by: 1,
|
|
deleted_at: null,
|
|
created_user: createdUser,
|
|
warehouse: dummyWarehouses[0],
|
|
};
|
|
|
|
// Goods Receipt data - using items from PurchaseItem with received data
|
|
const dummyGoodsReceiptItems: PurchaseItem[] = [
|
|
{
|
|
id: 1,
|
|
purchase_id: 1,
|
|
product: {
|
|
id: 1,
|
|
product_category: {
|
|
id: 1,
|
|
code: 'DOC',
|
|
name: 'DOC',
|
|
created_user: createdUser,
|
|
created_at: '2025-01-01T00:00:00Z',
|
|
updated_at: '2025-01-01T00:00:00Z',
|
|
},
|
|
name: 'CP Vaksin',
|
|
brand: '',
|
|
sku: '',
|
|
product_price: 0,
|
|
selling_price: 0,
|
|
tax: 0,
|
|
expiry_period: 0,
|
|
uom: {
|
|
id: 1,
|
|
name: 'Ekor',
|
|
created_user: createdUser,
|
|
created_at: '2025-01-01T00:00:00Z',
|
|
updated_at: '2025-01-01T00:00:00Z',
|
|
},
|
|
suppliers: [],
|
|
flags: [],
|
|
created_user: {
|
|
id: 1,
|
|
id_user: 1,
|
|
email: 'hello@gmail.com',
|
|
name: 'Admin',
|
|
},
|
|
created_at: '2025-01-01T00:00:00Z',
|
|
updated_at: '2025-01-01T00:00:00Z',
|
|
},
|
|
product_warehouse: dummyProductWarehouses[0],
|
|
quantity: 10000,
|
|
sub_qty: 10000,
|
|
total_qty: 10000,
|
|
total_used: 0,
|
|
price: 6500,
|
|
total_price: 65000000,
|
|
received_date: '2025-01-15T00:00:00Z',
|
|
travel_number: 'NSJ-1',
|
|
travel_number_docs: '/documents/nsj-1.pdf',
|
|
vehicle_number: 'NAP-1',
|
|
warehouse: dummyWarehouses[0],
|
|
},
|
|
];
|
|
|
|
const dummyGroupedApprovals: BaseGroupedApproval[] = [
|
|
{
|
|
step_number: 1,
|
|
step_name: 'Pengajuan',
|
|
approvals: [
|
|
{
|
|
step_number: 1,
|
|
step_name: 'Pengajuan',
|
|
action: 'submit',
|
|
notes: 'Pengajuan purchase order dibuat',
|
|
action_by: {
|
|
id: 1,
|
|
id_user: 1,
|
|
email: 'user@company.com',
|
|
name: 'User Pengajuan',
|
|
},
|
|
action_at: '2025-01-10T08:00:00Z',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
step_number: 2,
|
|
step_name: 'Approval Purchasing',
|
|
approvals: [
|
|
{
|
|
step_number: 2,
|
|
step_name: 'Approval Purchasing',
|
|
action: 'approve',
|
|
notes: 'Purchase order disetujui oleh purchasing',
|
|
action_by: {
|
|
id: 2,
|
|
id_user: 2,
|
|
email: 'purchasing@company.com',
|
|
name: 'Staff Purchasing',
|
|
},
|
|
action_at: '2025-01-10T10:30:00Z',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
step_number: 3,
|
|
step_name: 'Approval Manager Purchasing',
|
|
approvals: [
|
|
{
|
|
step_number: 3,
|
|
step_name: 'Approval Manager Purchasing',
|
|
action: 'approve',
|
|
notes: 'Purchase order disetujui oleh manager purchasing',
|
|
action_by: {
|
|
id: 3,
|
|
id_user: 3,
|
|
email: 'manager.purchasing@company.com',
|
|
name: 'Manager Purchasing',
|
|
},
|
|
action_at: '2025-01-10T14:15:00Z',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
step_number: 4,
|
|
step_name: 'Produk Diterima',
|
|
approvals: [
|
|
{
|
|
step_number: 4,
|
|
step_name: 'Produk Diterima',
|
|
action: 'receive',
|
|
notes: 'Produk telah diterima sesuai pesanan',
|
|
action_by: {
|
|
id: 4,
|
|
id_user: 4,
|
|
email: 'user@company.com',
|
|
name: 'User Pengajuan',
|
|
},
|
|
action_at: '2025-01-12T09:00:00Z',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
step_number: 5,
|
|
step_name: 'Selesai',
|
|
approvals: [
|
|
{
|
|
step_number: 5,
|
|
step_name: 'Selesai',
|
|
action: 'complete',
|
|
notes: 'Purchase order telah selesai diproses',
|
|
action_by: {
|
|
id: 5,
|
|
id_user: 5,
|
|
email: 'user@company.com',
|
|
name: 'User Pengajuan',
|
|
},
|
|
action_at: '2025-01-12T16:00:00Z',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const PurchaseOrderDetail = ({
|
|
type = 'detail',
|
|
data,
|
|
}: PurchaseOrderDetailProps) => {
|
|
// ===== MODAL HOOKS =====
|
|
const searchParams = useSearchParams();
|
|
const confirmationModalWithNotes = useModal();
|
|
const staffApprovalModal = useModal();
|
|
const acceptApprovalModal = useModal();
|
|
const editModal = useModal();
|
|
|
|
// ===== STATIC DATA =====
|
|
const purchaseData = data || dummyPurchaseData;
|
|
const purchaseOrderItems = purchaseData.items || [];
|
|
const goodsReceiptItems = dummyGoodsReceiptItems;
|
|
const groupedApprovals = dummyGroupedApprovals;
|
|
const latestApproval =
|
|
groupedApprovals[groupedApprovals.length - 1]?.approvals[0];
|
|
|
|
// ===== SUBMISSION HANDLER =====
|
|
const createManagerApprovalHandler = useCallback(
|
|
async (payload: CreateManagerApprovalRequestPayload) => {
|
|
const purchaseRequestId = searchParams.get('purchaseId')
|
|
? parseInt(searchParams.get('purchaseId')!)
|
|
: purchaseData?.id || 1;
|
|
|
|
if (!purchaseRequestId) {
|
|
toast.error('Purchase Request ID is required');
|
|
return;
|
|
}
|
|
|
|
const res = await ManagerApprovalApi.createManagerApproval(
|
|
purchaseRequestId,
|
|
payload
|
|
);
|
|
|
|
if (isResponseError(res)) {
|
|
toast.error(res.message);
|
|
return;
|
|
}
|
|
toast.success(res?.message as string);
|
|
},
|
|
[purchaseData?.id, searchParams]
|
|
);
|
|
|
|
// ===== COMPUTED VALUES =====
|
|
const approvalSteps = useMemo(() => {
|
|
if (!groupedApprovals.length || !latestApproval) return [];
|
|
|
|
try {
|
|
return formatGroupedApprovalsToApprovalSteps(
|
|
PURCHASE_ORDER_APPROVAL_LINE,
|
|
groupedApprovals,
|
|
latestApproval
|
|
);
|
|
} catch (error) {
|
|
console.error('Error formatting approval steps:', error);
|
|
return [];
|
|
}
|
|
}, [groupedApprovals, latestApproval]);
|
|
|
|
const totalBeforeTax = useMemo(() => {
|
|
return purchaseOrderItems.reduce(
|
|
(sum, item) => sum + (item.total_price || 0),
|
|
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<PurchaseItem>[] = [
|
|
{
|
|
header: 'No',
|
|
cell: (props) => props.row.index + 1,
|
|
},
|
|
{
|
|
accessorKey: 'product.name',
|
|
header: 'Produk',
|
|
cell: (props) => props.row.original.product?.name || '-',
|
|
},
|
|
{
|
|
accessorKey: 'product.product_category.name',
|
|
header: 'Jenis Produk',
|
|
cell: (props) =>
|
|
props.row.original.product?.product_category?.name || '-',
|
|
},
|
|
{
|
|
accessorKey: 'quantity',
|
|
header: 'Jumlah',
|
|
cell: (props) => formatNumber(props.getValue() as number),
|
|
},
|
|
{
|
|
accessorKey: 'product.uom.name',
|
|
header: 'Satuan',
|
|
cell: (props) => props.row.original.product?.uom?.name || '-',
|
|
},
|
|
{
|
|
accessorKey: 'price',
|
|
header: 'Harga Satuan',
|
|
cell: (props) => formatCurrency(props.getValue() as number),
|
|
},
|
|
{
|
|
accessorKey: 'total_price',
|
|
header: 'Total (Rp.)',
|
|
cell: (props) => formatCurrency(props.getValue() as number),
|
|
},
|
|
];
|
|
|
|
const goodsReceiptColumns: ColumnDef<PurchaseItem>[] = [
|
|
{
|
|
accessorKey: 'received_date',
|
|
header: 'Tanggal Penerimaan',
|
|
cell: (props) =>
|
|
props.row.original.received_date
|
|
? new Date(props.row.original.received_date).toLocaleDateString(
|
|
'id-ID'
|
|
)
|
|
: '-',
|
|
},
|
|
{
|
|
accessorKey: 'product_warehouse.warehouse.name',
|
|
header: 'Gudang Tujuan',
|
|
cell: (props) =>
|
|
props.row.original.product_warehouse?.warehouse?.name || '-',
|
|
},
|
|
{
|
|
accessorKey: 'travel_number',
|
|
header: 'No. Surat Jalan',
|
|
cell: (props) => props.row.original.travel_number || '-',
|
|
},
|
|
{
|
|
accessorKey: 'travel_number_docs',
|
|
header: 'Dokumen Surat Jalan',
|
|
cell: (props) => {
|
|
const documentPath = props.row.original.travel_number_docs;
|
|
return documentPath ? (
|
|
<Button
|
|
color='primary'
|
|
className='w-fit min-w-32 flex items-center justify-start gap-1 px-2 py-1 text-sm'
|
|
href={documentPath}
|
|
target='_blank'
|
|
rel='noopener noreferrer'
|
|
>
|
|
<Icon
|
|
icon='material-symbols:file-open-outline'
|
|
width={16}
|
|
height={16}
|
|
/>
|
|
Lihat Dokumen
|
|
</Button>
|
|
) : (
|
|
'-'
|
|
);
|
|
},
|
|
},
|
|
{
|
|
accessorKey: 'vehicle_number',
|
|
header: 'No. Armada',
|
|
cell: (props) => props.row.original.vehicle_number || '-',
|
|
},
|
|
{
|
|
accessorKey: 'pengangkut',
|
|
header: 'Pengangkut',
|
|
cell: (props) => props.row.original.product?.name || '-',
|
|
},
|
|
{
|
|
accessorKey: 'quantity',
|
|
header: 'Jumlah Total',
|
|
cell: (props) => formatNumber(props.getValue() as number),
|
|
},
|
|
{
|
|
accessorKey: 'sub_qty',
|
|
header: 'Jumlah Diterima',
|
|
cell: (props) => formatNumber(props.getValue() as number),
|
|
},
|
|
{
|
|
accessorKey: 'ekspedisi',
|
|
header: 'Ekspedisi',
|
|
cell: (props) => 'Ekspedisi 1',
|
|
},
|
|
{
|
|
accessorKey: 'price',
|
|
header: 'Transport /Item',
|
|
cell: (props) => formatCurrency(props.getValue() as number),
|
|
},
|
|
{
|
|
accessorKey: 'total_price',
|
|
header: 'Transport Total',
|
|
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'>
|
|
{/* Approval Action Buttons */}
|
|
<div className='flex flex-wrap gap-3 my-6'>
|
|
<Button
|
|
onClick={() => staffApprovalModal.openModal()}
|
|
variant='outline'
|
|
color='info'
|
|
className='w-full sm:w-fit'
|
|
>
|
|
<Icon icon='mdi:account-check-outline' width={20} height={20} />
|
|
Staff Approval
|
|
</Button>
|
|
|
|
<Button
|
|
onClick={() => confirmationModalWithNotes.openModal()}
|
|
variant='outline'
|
|
color='warning'
|
|
className='w-full sm:w-fit'
|
|
>
|
|
<Icon icon='mdi:note-edit-outline' width={20} height={20} />
|
|
Manager Approval
|
|
</Button>
|
|
|
|
<Button
|
|
onClick={() => acceptApprovalModal.openModal()}
|
|
variant='outline'
|
|
color='success'
|
|
className='w-full sm:w-fit'
|
|
>
|
|
<Icon
|
|
icon='mdi:package-variant-closed-check'
|
|
width={20}
|
|
height={20}
|
|
/>
|
|
Accept Approval
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 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='my-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'>
|
|
Area
|
|
</span>
|
|
<span className='text-gray-900 ml-3 break-all'>
|
|
: {purchaseData.area?.name || '-'}
|
|
</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'>
|
|
: {purchaseData.location?.name || '-'}
|
|
</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
|
|
</span>
|
|
<span className='text-gray-900 ml-3 break-all'>
|
|
: {purchaseData.warehouse?.name || '-'}
|
|
</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 Supplier
|
|
</span>
|
|
<span className='text-gray-900 font-medium ml-3 break-all'>
|
|
: {purchaseData.supplier?.name || '-'}
|
|
</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 Supplier
|
|
</span>
|
|
<span className='text-gray-900 ml-3 break-all'>
|
|
: {purchaseData.supplier?.address || '-'}
|
|
</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'>
|
|
:{' '}
|
|
{new Date(purchaseData.due_date).toLocaleDateString(
|
|
'id-ID',
|
|
{
|
|
day: 'numeric',
|
|
month: 'short',
|
|
year: 'numeric',
|
|
}
|
|
)}{' '}
|
|
({purchaseData.credit_term} hari)
|
|
</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'>
|
|
: {purchaseData.pr_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>
|
|
<div className='ml-3'>
|
|
{purchaseData.po_number &&
|
|
purchaseData.po_number !== 'Belum dibuat' ? (
|
|
<PurchaseOrderInvoice data={purchaseData} />
|
|
) : (
|
|
<Button
|
|
color='primary'
|
|
className='w-fit min-w-32 flex items-center justify-start gap-1 px-2 py-1 text-sm font-mono'
|
|
disabled
|
|
>
|
|
PO-MBU-01837
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Item Pembelian Section */}
|
|
<div className='mb-8'>
|
|
<div className='flex items-center justify-between mb-4 pb-2 border-b border-gray-200'>
|
|
<h3 className='text-lg font-semibold text-gray-800'>
|
|
Item Pembelian
|
|
</h3>
|
|
<RowDropdownOptions isLast2Rows>
|
|
<ItemPembelianDropdown onEdit={editModal.openModal} />
|
|
</RowDropdownOptions>
|
|
</div>
|
|
<div className='rounded-lg border border-gray-200 overflow-hidden'>
|
|
{/* Product Table */}
|
|
<div className='overflow-x-auto'>
|
|
<Table<PurchaseItem>
|
|
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'>
|
|
{purchaseData.notes || 'Tidak ada catatan'}
|
|
</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>
|
|
|
|
{/* Penerimaan Barang */}
|
|
<Card
|
|
collapsible
|
|
title='Penerimaan Barang'
|
|
variant='bordered'
|
|
className={{
|
|
wrapper: 'w-full mt-8',
|
|
}}
|
|
>
|
|
{/* Detail Penerimaan Barang Section */}
|
|
<div className='my-8'>
|
|
<h3 className='text-lg font-semibold text-gray-800 mb-4 pb-2 border-b border-gray-200'>
|
|
Informasi Penerimaan Barang
|
|
</h3>
|
|
<div className='rounded-lg border border-gray-200 overflow-hidden'>
|
|
<div className='overflow-x-auto'>
|
|
<Table<PurchaseItem>
|
|
data={goodsReceiptItems}
|
|
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>
|
|
</Card>
|
|
|
|
{/* Confirmation Modal with Notes */}
|
|
<ConfirmationModalWithNotes
|
|
ref={confirmationModalWithNotes.ref}
|
|
type='success'
|
|
text='Apakah Anda yakin ingin melanjutkan approval ini?'
|
|
placeholder='(Opsional) Tambahkan catatan untuk approval ini...'
|
|
rows={4}
|
|
closeOnBackdrop
|
|
primaryButton={{
|
|
text: 'Ya, Lanjutkan',
|
|
color: 'success',
|
|
onClick: async (notes) => {
|
|
const payload: CreateManagerApprovalRequestPayload = {
|
|
notes: notes || null,
|
|
};
|
|
|
|
await createManagerApprovalHandler(payload);
|
|
confirmationModalWithNotes.closeModal();
|
|
},
|
|
}}
|
|
secondaryButton={{
|
|
text: 'Batal',
|
|
}}
|
|
/>
|
|
|
|
{/* Staff Approval Modal */}
|
|
<Modal
|
|
ref={staffApprovalModal.ref}
|
|
closeOnBackdrop
|
|
className={{
|
|
modalBox: 'w-full max-w-screen-2xl max-h-[90vh] overflow-y-auto',
|
|
}}
|
|
>
|
|
<PurchaseOrderStaffApprovalForm
|
|
type='add'
|
|
onCancel={staffApprovalModal.closeModal}
|
|
/>
|
|
</Modal>
|
|
|
|
{/* Accept Approval Modal */}
|
|
<Modal
|
|
ref={acceptApprovalModal.ref}
|
|
closeOnBackdrop
|
|
className={{
|
|
modalBox: 'w-full max-w-screen-2xl max-h-[90vh] overflow-y-auto',
|
|
}}
|
|
>
|
|
<PurchaseOrderAcceptApprovalForm
|
|
type='add'
|
|
onCancel={acceptApprovalModal.closeModal}
|
|
/>
|
|
</Modal>
|
|
|
|
{/* Edit Modal */}
|
|
<Modal
|
|
ref={editModal.ref}
|
|
closeOnBackdrop
|
|
className={{
|
|
modalBox: 'w-full max-w-screen-2xl max-h-[90vh] overflow-y-auto',
|
|
}}
|
|
>
|
|
<PurchaseOrderStaffApprovalForm
|
|
type='edit'
|
|
initialValues={purchaseData}
|
|
onCancel={editModal.closeModal}
|
|
/>
|
|
</Modal>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default PurchaseOrderDetail;
|