diff --git a/src/app/purchase/detail/page.tsx b/src/app/purchase/detail/page.tsx index 5a497f14..29675d89 100644 --- a/src/app/purchase/detail/page.tsx +++ b/src/app/purchase/detail/page.tsx @@ -2,7 +2,7 @@ import { useRouter, useSearchParams } from 'next/navigation'; import useSWR from 'swr'; -import PurchaseRequestForm from '@/components/pages/purchase/form/request/PurchaseRequestForm'; +import PurchaseOrderDetail from '@/components/pages/purchase/order/PurchaseOrderDetail'; import { PurchaseRequestApi } from '@/services/api/purchase'; import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; @@ -33,12 +33,14 @@ const PurchaseDetail = () => { } return ( -
+
{isLoadingPurchase && ( - +
+ +
)} {!isLoadingPurchase && isResponseSuccess(purchase) && ( - + )}
); diff --git a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx index a1475c18..256ffed3 100644 --- a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx +++ b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx @@ -206,15 +206,13 @@ const PurchaseOrderAcceptApprovalForm = ({ if (initialValues?.items) { return initialValues.items.map((item) => ({ value: item.id, - label: `${item.product.name} (${item.quantity} ${item.product.uom.name})`, + label: `${item.product.name} (${item.quantity} ${item.product.uom?.name || 'unit'})`, id: item.id, quantity: item.quantity, product: { name: item.product.name, product_category: item.product.product_category, - uom: { - name: item.product.uom.name, - }, + uom: item.product.uom, }, warehouse: { name: item.warehouse?.name || '', diff --git a/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx b/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx index 137b9ff7..2517b337 100644 --- a/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx +++ b/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx @@ -44,7 +44,7 @@ const PurchaseOrderStaffApprovalForm = ({ product: { name: string; type?: string; - uom: { + uom?: { name: string; }; }; @@ -170,15 +170,13 @@ const PurchaseOrderStaffApprovalForm = ({ if (initialValues?.items) { return initialValues.items.map((item) => ({ value: item.id, - label: `${item.product.name} (${item.quantity} ${item.product.uom.name})`, + label: `${item.product.name} (${item.quantity} ${item.product.uom?.name || 'unit'})`, id: item.id, quantity: item.quantity, product: { name: item.product.name, product_category: item.product.product_category, - uom: { - name: item.product.uom.name, - }, + uom: item.product.uom, }, warehouse: { name: item.warehouse?.name || '', diff --git a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx index ec18ed15..98940a13 100644 --- a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx +++ b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx @@ -3,9 +3,7 @@ import { useCallback, useMemo, useState } from 'react'; import { ColumnDef, SortingState } from '@tanstack/react-table'; -import ApprovalSteps, { - formatGroupedApprovalsToApprovalSteps, -} from '@/components/pages/ApprovalSteps'; +import ApprovalSteps from '@/components/pages/ApprovalSteps'; import Table from '@/components/Table'; import Button from '@/components/Button'; import { Icon } from '@iconify/react'; @@ -20,21 +18,13 @@ import PurchaseOrderStaffApprovalForm from '@/components/pages/purchase/form/ord 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 { ApprovalStepStatus } from '@/components/pages/ApprovalSteps'; 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, PurchaseDeleteItemsApi, @@ -81,321 +71,8 @@ interface PurchaseOrderDetailProps { 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], - }, - { - id: 2, - purchase_id: 2, - 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], -}; -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', @@ -420,23 +97,49 @@ const PurchaseOrderDetail = ({ parseInt(item) ); - // ===== STATIC DATA ===== - const purchaseData = data || dummyPurchaseData; + // ===== COMPUTED VALUES ===== const purchaseOrderItems = useMemo( - () => purchaseData.items || [], - [purchaseData.items] + () => data?.items || [], + [data?.items] ); - const goodsReceiptItems = dummyGoodsReceiptItems; - const groupedApprovals = dummyGroupedApprovals; - const latestApproval = - groupedApprovals[groupedApprovals.length - 1]?.approvals[0]; + + // Create goods receipt items from received items + const goodsReceiptItems = useMemo(() => { + return purchaseOrderItems.filter(item => item.received_date); + }, [purchaseOrderItems]); + + // Create simple approval steps from single approval data + const approvalSteps = useMemo(() => { + if (!data?.approval) return []; + + // Create a simple approval step based on the single approval data + const status = data.approval.action === 'APPROVED' ? 'APPROVED' : + data.approval.action === 'REJECTED' ? 'REJECTED' : 'WAITING'; + + return [{ + name: data.approval.step_name, + status: status as ApprovalStepStatus, + logs: [{ + action_by: data.approval.action_by?.name, + date: data.approval.action_at, + notes: data.approval.notes, + }], + }]; + }, [data?.approval]); + + const totalBeforeTax = useMemo(() => { + return purchaseOrderItems.reduce( + (sum, item) => sum + (item.total_price || 0), + 0 + ); + }, [purchaseOrderItems]); // ===== SUBMISSION HANDLER ===== const createManagerApprovalHandler = useCallback( async (payload: CreateManagerApprovalRequestPayload) => { const purchaseRequestId = searchParams.get('purchaseId') ? parseInt(searchParams.get('purchaseId')!) - : purchaseData?.id || 1; + : data?.id || 1; if (!purchaseRequestId) { toast.error('Purchase Request ID is required'); @@ -454,14 +157,14 @@ const PurchaseOrderDetail = ({ } toast.success(res?.message as string); }, - [purchaseData?.id, searchParams] + [data?.id, searchParams] ); // ===== DELETE HANDLER ===== const deleteItemsHandler = useCallback(async () => { const purchaseRequestId = searchParams.get('purchaseId') ? parseInt(searchParams.get('purchaseId')!) - : purchaseData?.id || 1; + : data?.id || 1; if (!purchaseRequestId) { toast.error('Purchase Request ID is required'); @@ -501,30 +204,14 @@ const PurchaseOrderDetail = ({ } finally { setIsDeleteLoading(false); } - }, [purchaseData?.id, searchParams, selectedItem, selectedRowIds]); + }, [data?.id, searchParams, selectedItem, selectedRowIds]); - // ===== COMPUTED VALUES ===== - const approvalSteps = useMemo(() => { - if (!groupedApprovals.length || !latestApproval) return []; + // Return null if no data provided + if (!data) { + return null; + } - 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 purchaseData = data; const purchaseOrderColumns: ColumnDef[] = [ { @@ -563,10 +250,15 @@ const PurchaseOrderDetail = ({ cell: (props) => props.row.original.product?.name || '-', }, { - accessorKey: 'product.product_category.name', + accessorKey: 'product.product_category', header: 'Jenis Produk', - cell: (props) => - props.row.original.product?.product_category?.name || '-', + cell: (props) => { + const category = props.row.original.product?.product_category; + if (typeof category === 'string') { + return category; + } + return category?.name || '-'; + }, }, { accessorKey: 'quantity', @@ -576,7 +268,13 @@ const PurchaseOrderDetail = ({ { accessorKey: 'product.uom.name', header: 'Satuan', - cell: (props) => props.row.original.product?.uom?.name || '-', + cell: (props) => { + const uom = props.row.original.product?.uom; + if (uom && typeof uom === 'object' && uom.name) { + return uom.name; + } + return uom || '-'; + }, }, { accessorKey: 'price', @@ -628,10 +326,12 @@ const PurchaseOrderDetail = ({ : '-', }, { - accessorKey: 'product_warehouse.warehouse.name', + accessorKey: 'warehouse.name', header: 'Gudang Tujuan', - cell: (props) => - props.row.original.product_warehouse?.warehouse?.name || '-', + cell: (props) => { + const warehouse = props.row.original.warehouse; + return warehouse?.name || '-'; + }, }, { accessorKey: 'travel_number', @@ -639,10 +339,10 @@ const PurchaseOrderDetail = ({ cell: (props) => props.row.original.travel_number || '-', }, { - accessorKey: 'travel_number_docs', + accessorKey: 'travel_document_path', header: 'Dokumen Surat Jalan', cell: (props) => { - const documentPath = props.row.original.travel_number_docs; + const documentPath = props.row.original.travel_document_path; return documentPath ? (
@@ -817,7 +517,7 @@ const PurchaseOrderDetail = ({ Lokasi - : {purchaseData.location?.name || '-'} + : {purchaseData.items?.[0]?.warehouse?.type !== 'AREA' && purchaseData.items?.[0]?.warehouse?.location?.name ? purchaseData.items[0].warehouse.location.name : '-'} @@ -827,7 +527,7 @@ const PurchaseOrderDetail = ({ Gudang - : {purchaseData.warehouse?.name || '-'} + : {purchaseData.items?.[0]?.warehouse?.name || '-'} @@ -848,10 +548,10 @@ const PurchaseOrderDetail = ({
- Alamat Supplier + Kategori Supplier - : {purchaseData.supplier?.address || '-'} + : {purchaseData.supplier?.category || '-'}
diff --git a/src/types/api/purchase/purchase.d.ts b/src/types/api/purchase/purchase.d.ts index ec3d88cc..f20c4549 100644 --- a/src/types/api/purchase/purchase.d.ts +++ b/src/types/api/purchase/purchase.d.ts @@ -1,4 +1,4 @@ -import { BaseMetadata } from '@/types/api/api-general'; +import { BaseApproval, BaseMetadata } from '@/types/api/api-general'; import { Supplier } from '@/types/api/master-data/supplier'; import { Warehouse } from '@/types/api/master-data/warehouse'; import { Product } from '@/types/api/master-data/product'; @@ -6,11 +6,25 @@ import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; import { Area } from '@/types/api/master-data/area'; import { Location } from '@/types/api/master-data/location'; +export type PurchaseItemProduct = { + id: number; + name: string; + flags?: string[]; + uom?: { + name: string; + }; + product_category?: + | { + name: string; + } + | string; +}; + export type PurchaseItem = { id: number; - purchase_id?: number; + product_id: number; warehouse: Warehouse; - product: Product; + product: PurchaseItemProduct | Product; product_warehouse: ProductWarehouse; quantity: number; qty: number; @@ -22,6 +36,7 @@ export type PurchaseItem = { received_date?: string | null; travel_number?: string | null; travel_number_docs?: string | null; + travel_document_path?: string | null; vehicle_number?: string | null; }; @@ -42,6 +57,7 @@ export type BasePurchase = { location?: Location; warehouse?: Warehouse; items?: PurchaseItem[]; + approval?: BaseApproval; }; export type Purchase = BaseMetadata & BasePurchase;