refactor(FE-208,212): implement delete functionality for purchase order items with confirmation modal

This commit is contained in:
rstubryan
2025-11-17 13:09:47 +07:00
parent d0ba9eadbd
commit 7ec4105454
@@ -1,7 +1,7 @@
'use client';
import { useCallback, useMemo } from 'react';
import { ColumnDef } from '@tanstack/react-table';
import { useCallback, useMemo, useState } from 'react';
import { ColumnDef, SortingState } from '@tanstack/react-table';
import ApprovalSteps, {
formatGroupedApprovalsToApprovalSteps,
@@ -10,8 +10,10 @@ import Table from '@/components/Table';
import Button from '@/components/Button';
import { Icon } from '@iconify/react';
import { useModal } from '@/components/Modal';
import CheckboxInput from '@/components/input/CheckboxInput';
import Modal from '@/components/Modal';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import PurchaseOrderStaffApprovalForm from '@/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm';
@@ -33,10 +35,14 @@ import {
dummyProductWarehouses,
dummyWarehouses,
} from '@/dummy/marketing.dummy';
import { ManagerApprovalApi } from '@/services/api/purchase';
import {
ManagerApprovalApi,
PurchaseDeleteItemsApi,
} from '@/services/api/purchase';
import { isResponseError } from '@/lib/api-helper';
import { toast } from 'react-hot-toast';
import { useSearchParams } from 'next/navigation';
import { formatCurrency, formatNumber, formatDate } from '@/lib/helper';
const ItemPembelianDropdown = ({ onEdit }: { onEdit: () => void }) => {
return (
@@ -342,10 +348,20 @@ const PurchaseOrderDetail = ({
const acceptApprovalModal = useModal();
const editModal = useModal();
const penerimaanBarangModal = useModal();
const deleteModal = useModal();
// ===== STATE MANAGEMENT =====
const [sorting, setSorting] = useState<SortingState>([]);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [selectedItem, setSelectedItem] = useState<PurchaseItem | null>(null);
// ===== STATIC DATA =====
const purchaseData = data || dummyPurchaseData;
const purchaseOrderItems = purchaseData.items || [];
const purchaseOrderItems = useMemo(
() => purchaseData.items || [],
[purchaseData.items]
);
const goodsReceiptItems = dummyGoodsReceiptItems;
const groupedApprovals = dummyGroupedApprovals;
const latestApproval =
@@ -377,6 +393,45 @@ const PurchaseOrderDetail = ({
[purchaseData?.id, searchParams]
);
// ===== DELETE HANDLER =====
const deleteItemHandler = useCallback(async () => {
const purchaseRequestId = searchParams.get('purchaseId')
? parseInt(searchParams.get('purchaseId')!)
: purchaseData?.id || 1;
if (!purchaseRequestId) {
toast.error('Purchase Request ID is required');
return;
}
if (!selectedItem) {
toast.error('Item tidak valid');
return;
}
setIsDeleteLoading(true);
try {
const res = await PurchaseDeleteItemsApi.deleteItems(purchaseRequestId, {
item_ids: [selectedItem.id],
});
if (isResponseError(res)) {
toast.error(res.message || 'Gagal menghapus item pembelian');
return;
}
toast.success('Berhasil menghapus item pembelian');
deleteModal.closeModal();
setSelectedItem(null);
} catch (error) {
toast.error('Terjadi kesalahan saat menghapus item pembelian');
} finally {
setIsDeleteLoading(false);
}
}, [purchaseData?.id, searchParams, selectedItem]);
// ===== COMPUTED VALUES =====
const approvalSteps = useMemo(() => {
if (!groupedApprovals.length || !latestApproval) return [];
@@ -400,19 +455,33 @@ const PurchaseOrderDetail = ({
);
}, [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>[] = [
{
id: 'select',
header: ({ table }) => (
<div className='w-full flex flex-row justify-center'>
<CheckboxInput
name='allRow'
checked={table.getIsAllRowsSelected()}
indeterminate={table.getIsSomeRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>
</div>
),
cell: ({ row }) => {
return (
<div>
<CheckboxInput
name='row'
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
</div>
);
},
},
{
header: 'No',
cell: (props) => props.row.index + 1,
@@ -448,6 +517,26 @@ const PurchaseOrderDetail = ({
header: 'Total (Rp.)',
cell: (props) => formatCurrency(props.getValue() as number),
},
{
header: 'Aksi',
cell: (props) => {
const deleteClickHandler = () => {
setSelectedItem(props.row.original);
deleteModal.openModal();
};
return (
<Button
type='button'
color='error'
className='text-sm'
onClick={deleteClickHandler}
>
<Icon icon='mdi:trash-can' width={16} height={16} />
</Button>
);
},
},
];
const goodsReceiptColumns: ColumnDef<PurchaseItem>[] = [
@@ -456,9 +545,7 @@ const PurchaseOrderDetail = ({
header: 'Tanggal Penerimaan',
cell: (props) =>
props.row.original.received_date
? new Date(props.row.original.received_date).toLocaleDateString(
'id-ID'
)
? formatDate(props.row.original.received_date, 'DD MMM YYYY')
: '-',
},
{
@@ -568,7 +655,7 @@ const PurchaseOrderDetail = ({
return (
<section className='w-full'>
{/* Approval Action Buttons */}
{/* Approval and Action Buttons */}
<div className='flex flex-wrap gap-3 my-6'>
<Button
onClick={() => staffApprovalModal.openModal()}
@@ -693,16 +780,8 @@ const PurchaseOrderDetail = ({
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)
: {formatDate(purchaseData.due_date, 'D MMM YYYY')} (
{purchaseData.credit_term} hari)
</span>
</div>
</div>
@@ -758,6 +837,11 @@ const PurchaseOrderDetail = ({
data={purchaseOrderItems}
columns={purchaseOrderColumns}
isLoading={false}
sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
enableRowSelection={() => true}
className={{
containerClassName: 'm-0',
tableWrapperClassName: 'overflow-x-auto',
@@ -823,7 +907,9 @@ const PurchaseOrderDetail = ({
Informasi Penerimaan Barang
</h3>
<RowDropdownOptions isLast2Rows>
<PenerimaanBarangDropdown onEdit={penerimaanBarangModal.openModal} />
<PenerimaanBarangDropdown
onEdit={penerimaanBarangModal.openModal}
/>
</RowDropdownOptions>
</div>
<div className='rounded-lg border border-gray-200 overflow-hidden'>
@@ -933,6 +1019,25 @@ const PurchaseOrderDetail = ({
onCancel={penerimaanBarangModal.closeModal}
/>
</Modal>
{/* Delete Confirmation Modal */}
<ConfirmationModal
ref={deleteModal.ref}
type='error'
text='Apakah Anda yakin ingin menghapus item pembelian ini?'
closeOnBackdrop
primaryButton={{
text: 'Ya, Hapus',
color: 'error',
isLoading: isDeleteLoading,
onClick: async () => {
await deleteItemHandler();
},
}}
secondaryButton={{
text: 'Batal',
}}
/>
</section>
);
};