refactor(FE): Add invoice download and PO table columns

This commit is contained in:
rstubryan
2026-02-04 14:13:59 +07:00
parent 428d9f33d9
commit 760e9ccd89
3 changed files with 137 additions and 10 deletions
@@ -17,6 +17,7 @@ import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import StatusBadge from '@/components/helper/StatusBadge'; import StatusBadge from '@/components/helper/StatusBadge';
import PurchaseOrderInvoice from '@/components/pages/purchase/order/PurchaseOrderInvoice';
import { cn, formatDate } from '@/lib/helper'; import { cn, formatDate } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
@@ -158,6 +159,27 @@ const PurchaseTable = () => {
PurchaseApi.getAllFetcher PurchaseApi.getAllFetcher
); );
const [isDownloadingInvoice, setIsDownloadingInvoice] = useState(false);
const [invoicePurchaseData, setInvoicePurchaseData] =
useState<Purchase | null>(null);
const handleDownloadInvoice = async (purchaseId: number) => {
setIsDownloadingInvoice(true);
try {
const response = await PurchaseApi.getSingle(purchaseId);
if (isResponseSuccess(response) && response.data) {
setInvoicePurchaseData(response.data);
setTimeout(() => {
setInvoicePurchaseData(null);
}, 1000);
}
} catch {
toast.error('Gagal mengambil data purchase order.');
} finally {
setIsDownloadingInvoice(false);
}
};
// ===== TABLE COLUMNS DEFINITION ===== // ===== TABLE COLUMNS DEFINITION =====
const purchaseColumns: ColumnDef<Purchase>[] = [ const purchaseColumns: ColumnDef<Purchase>[] = [
{ {
@@ -168,10 +190,66 @@ const PurchaseTable = () => {
}, },
}, },
{ {
accessorKey: 'supplier', accessorKey: 'po_expedition',
header: 'PO Ekspedisi',
cell: (props) => {
const purchase = props.row.original;
if (!purchase.po_number || purchase.po_number === 'Belum dibuat') {
return <span>-</span>;
}
return (
<Button
color='primary'
className='w-fit min-w-32 flex items-center justify-start gap-1 px-2 py-1 text-sm font-mono'
onClick={() => handleDownloadInvoice(purchase.id)}
disabled={isDownloadingInvoice}
>
<Icon
icon={
isDownloadingInvoice
? 'eos-icons:loading'
: 'material-symbols:file-open-outline'
}
width={16}
height={16}
/>
{purchase.po_number}
</Button>
);
},
},
{
accessorKey: 'supplier.name',
header: 'Vendor', header: 'Vendor',
cell: (props) => props.row.original.supplier.name, cell: (props) => props.row.original.supplier.name,
}, },
{
accessorKey: 'requester_name',
header: 'Nama Pengaju',
cell: (props) => props.row.original.requester_name || '-',
},
{
accessorKey: 'products.name',
header: 'Produk',
cell: (props) => {
const products = props.row.original.products;
if (!products || products.length === 0) return '-';
return (
<ul className='list-disc pl-4'>
{products.map((product, index) => (
<li key={index}>{product.name}</li>
))}
</ul>
);
},
},
{
accessorKey: 'location.name',
header: 'Lokasi',
cell: (props) => props.row.original.location?.name || '-',
},
{ {
accessorKey: 'po_date', accessorKey: 'po_date',
header: 'Tgl. PO', header: 'Tgl. PO',
@@ -180,6 +258,14 @@ const PurchaseTable = () => {
? formatDate(props.row.original.po_date, 'DD MMM YYYY') ? formatDate(props.row.original.po_date, 'DD MMM YYYY')
: '-', : '-',
}, },
{
accessorKey: 'due_date',
header: 'Jatuh Tempo',
cell: (props) =>
props.row.original.due_date
? formatDate(props.row.original.due_date, 'DD MMM YYYY')
: '-',
},
{ {
header: 'Aging', header: 'Aging',
cell: (props) => { cell: (props) => {
@@ -231,7 +317,7 @@ const PurchaseTable = () => {
color={statusColor} color={statusColor}
text={statusText} text={statusText}
className={{ className={{
badge: 'whitespace-nowrap', badge: 'whitespace-nowrap max-w-max w-fit',
}} }}
/> />
); );
@@ -409,6 +495,15 @@ const PurchaseTable = () => {
onClick: confirmationModalDeleteClickHandler, onClick: confirmationModalDeleteClickHandler,
}} }}
/> />
{invoicePurchaseData && (
<div className='hidden'>
<PurchaseOrderInvoice
data={invoicePurchaseData}
triggerDownloadOnMount={true}
/>
</div>
)}
</> </>
); );
}; };
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useMemo, useState } from 'react'; import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
import { import {
Page, Page,
Text, Text,
@@ -235,11 +235,16 @@ const pdfStyles = StyleSheet.create({
interface PurchaseOrderInvoiceProps { interface PurchaseOrderInvoiceProps {
data?: Purchase; data?: Purchase;
className?: string; className?: string;
triggerDownloadOnMount?: boolean;
} }
const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => { const PurchaseOrderInvoice = ({
data,
triggerDownloadOnMount,
}: PurchaseOrderInvoiceProps) => {
const [, setIsGeneratingPDF] = useState(false); const [, setIsGeneratingPDF] = useState(false);
const purchaseData = data; const purchaseData = data;
const hasDownloadedRef = useRef(false);
const grandTotal = useMemo(() => { const grandTotal = useMemo(() => {
return ( return (
@@ -250,7 +255,7 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => {
); );
}, [purchaseData?.items]); }, [purchaseData?.items]);
const handleDownloadPDF = async () => { const handleDownloadPDF = useCallback(async () => {
if (!purchaseData) { if (!purchaseData) {
toast.error('No purchase order data available'); toast.error('No purchase order data available');
return; return;
@@ -510,7 +515,20 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => {
} finally { } finally {
setIsGeneratingPDF(false); setIsGeneratingPDF(false);
} }
}; }, [purchaseData]);
useEffect(() => {
if (triggerDownloadOnMount && purchaseData && !hasDownloadedRef.current) {
hasDownloadedRef.current = true;
handleDownloadPDF();
}
}, [triggerDownloadOnMount, purchaseData]);
useEffect(() => {
if (!triggerDownloadOnMount) {
hasDownloadedRef.current = false;
}
}, [triggerDownloadOnMount]);
if (!purchaseData) { if (!purchaseData) {
return ( return (
@@ -520,6 +538,10 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => {
); );
} }
if (triggerDownloadOnMount) {
return null;
}
return purchaseData?.po_number && return purchaseData?.po_number &&
purchaseData.po_number !== 'Belum dibuat' ? ( purchaseData.po_number !== 'Belum dibuat' ? (
<Button <Button
+14 -4
View File
@@ -1,10 +1,15 @@
import { BaseApproval, BaseMetadata } from '@/types/api/api-general'; import {
BaseApproval,
BaseMetadata,
CreatedUser,
} from '@/types/api/api-general';
import { Supplier } from '@/types/api/master-data/supplier'; import { Supplier } from '@/types/api/master-data/supplier';
import { Warehouse } from '@/types/api/master-data/warehouse'; import { Warehouse } from '@/types/api/master-data/warehouse';
import { Product } from '@/types/api/master-data/product'; import { Product } from '@/types/api/master-data/product';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
import { Area } from '@/types/api/master-data/area'; import { Area } from '@/types/api/master-data/area';
import { Location } from '@/types/api/master-data/location'; import { Location } from '@/types/api/master-data/location';
import { Uom } from '@/types/api/master-data/uom';
export type PurchaseItemProduct = { export type PurchaseItemProduct = {
id: number; id: number;
@@ -12,14 +17,15 @@ export type PurchaseItemProduct = {
flags?: string[]; flags?: string[];
ProductPrice?: number; ProductPrice?: number;
SellingPrice?: number; SellingPrice?: number;
uom?: { uom: Uom;
name: string;
};
product_category?: product_category?:
| { | {
id: number;
name: string; name: string;
code: string;
} }
| string; | string;
suppliers?: Supplier[];
}; };
export type PurchaseItem = { export type PurchaseItem = {
@@ -69,6 +75,10 @@ export type BasePurchase = {
warehouse?: Warehouse; warehouse?: Warehouse;
items?: PurchaseItem[]; items?: PurchaseItem[];
latest_approval?: BaseApproval; latest_approval?: BaseApproval;
requester_name?: string;
po_expedition?: string[];
created_user?: CreatedUser;
products?: PurchaseItemProduct[];
}; };
export type Purchase = BaseMetadata & BasePurchase; export type Purchase = BaseMetadata & BasePurchase;