Merge branch 'development' into fix/project-flock-form

This commit is contained in:
ValdiANS
2026-04-27 10:58:44 +07:00
6 changed files with 272 additions and 21 deletions
@@ -305,6 +305,14 @@ const PurchaseTable = () => {
? formatDate(props.row.original.po_date, 'DD MMM YYYY') ? formatDate(props.row.original.po_date, 'DD MMM YYYY')
: '-', : '-',
}, },
{
accessorKey: 'received_date',
header: 'Tgl. Terima',
cell: (props) =>
props.row.original.received_date
? formatDate(props.row.original.received_date, 'DD MMM YYYY')
: '-',
},
{ {
accessorKey: 'due_date', accessorKey: 'due_date',
header: 'Jatuh Tempo', header: 'Jatuh Tempo',
@@ -26,6 +26,8 @@ import PurchaseOrderAcceptApprovalForm from '@/components/pages/purchase/form/or
import PurchaseOrderInvoice from '@/components/pages/purchase/order/PurchaseOrderInvoice'; import PurchaseOrderInvoice from '@/components/pages/purchase/order/PurchaseOrderInvoice';
import Card from '@/components/Card'; import Card from '@/components/Card';
import DateInput from '@/components/input/DateInput';
import TextArea from '@/components/input/TextArea';
import { import {
CreateAcceptApprovalRequestPayload, CreateAcceptApprovalRequestPayload,
CreateManagerApprovalRequestPayload, CreateManagerApprovalRequestPayload,
@@ -96,6 +98,7 @@ const PurchaseOrderDetail = ({
const acceptRejectionModal = useModal(); const acceptRejectionModal = useModal();
const managerRejectionModal = useModal(); const managerRejectionModal = useModal();
const editModal = useModal(); const editModal = useModal();
const editPoDateModal = useModal();
const penerimaanBarangModal = useModal(); const penerimaanBarangModal = useModal();
const deleteModal = useModal(); const deleteModal = useModal();
@@ -105,6 +108,9 @@ const PurchaseOrderDetail = ({
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [selectedItem, setSelectedItem] = useState<PurchaseItem | null>(null); const [selectedItem, setSelectedItem] = useState<PurchaseItem | null>(null);
const [, setApprovalNotes] = useState(''); const [, setApprovalNotes] = useState('');
const [managerApprovalNotes, setManagerApprovalNotes] = useState('');
const [managerApprovalPoDate, setManagerApprovalPoDate] = useState('');
const [editPoDate, setEditPoDate] = useState('');
const selectedRowIds = Object.keys(rowSelection).map((item) => const selectedRowIds = Object.keys(rowSelection).map((item) =>
parseInt(item) parseInt(item)
@@ -212,6 +218,8 @@ const PurchaseOrderDetail = ({
break; break;
case 2: case 2:
setApprovalNotes(''); setApprovalNotes('');
setManagerApprovalNotes('');
setManagerApprovalPoDate('');
confirmationModalWithNotes.openModal(); confirmationModalWithNotes.openModal();
break; break;
case 3: case 3:
@@ -414,17 +422,44 @@ const PurchaseOrderDetail = ({
deleteModal, deleteModal,
]); ]);
const updatePoDateHandler = useCallback(async () => {
const purchaseRequestId = searchParams.get('purchaseId')
? parseInt(searchParams.get('purchaseId')!)
: initialValues?.id || 1;
if (!purchaseRequestId) {
toast.error('Purchase Request ID is required');
return;
}
const res = await PurchaseApi.updatePoDate(purchaseRequestId, {
po_date: editPoDate,
});
if (isResponseError(res)) {
toast.error(res.message || 'Gagal mengubah tanggal PO');
return;
}
toast.success('Tanggal PO berhasil diubah');
setEditPoDate('');
editPoDateModal.closeModal();
refetchData?.();
}, [initialValues?.id, searchParams, editPoDate, editPoDateModal, refetchData]);
// ===== APPROVAL/REJECTION HANDLERS ===== // ===== APPROVAL/REJECTION HANDLERS =====
const managerApprovalHandler = async (notes: string) => { const managerApprovalHandler = async () => {
const payload: CreateManagerApprovalRequestPayload = { const payload: CreateManagerApprovalRequestPayload = {
action: 'APPROVED', action: 'APPROVED',
notes: notes || null, notes: managerApprovalNotes || null,
po_date: managerApprovalPoDate || null,
}; };
await createManagerApprovalHandler(payload); await createManagerApprovalHandler(payload);
await refreshApprovals(); await refreshApprovals();
await refetchData?.(); await refetchData?.();
setApprovalNotes(''); setManagerApprovalNotes('');
setManagerApprovalPoDate('');
confirmationModalWithNotes.closeModal(); confirmationModalWithNotes.closeModal();
}; };
@@ -829,6 +864,45 @@ const PurchaseOrderDetail = ({
</div> </div>
</div> </div>
</div> </div>
{purchaseData.po_date &&
!purchaseData.po_date.startsWith('0001') && (
<div className='group'>
<div className='flex items-start'>
<span className='font-medium text-gray-600 min-w-[140px] shrink-0'>
Tanggal PO
</span>
<div className='ml-3 flex items-center gap-1'>
<span className='text-gray-900'>
:{' '}
{formatDate(purchaseData.po_date, 'DD MMM YYYY')}
</span>
<RequirePermission permissions='lti.purchase.update'>
<Button
type='button'
variant='ghost'
color='warning'
className='p-1 min-h-0 h-auto'
onClick={() => {
setEditPoDate(
formatDate(
purchaseData.po_date,
'YYYY-MM-DD'
)
);
editPoDateModal.openModal();
}}
>
<Icon
icon='material-symbols:edit-outline'
width={14}
height={14}
/>
</Button>
</RequirePermission>
</div>
</div>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -1016,28 +1090,80 @@ const PurchaseOrderDetail = ({
</div> </div>
</Card> </Card>
{/* Confirmation Modal with Notes */} {/* Manager Approval Modal */}
<ConfirmationModalWithNotes <Modal
ref={confirmationModalWithNotes.ref} ref={confirmationModalWithNotes.ref}
type='success'
text='Apakah Anda yakin ingin melanjutkan approval ini?'
placeholder='(Opsional) Tambahkan catatan untuk approval ini...'
rows={4}
closeOnBackdrop closeOnBackdrop
primaryButton={{ className={{
text: 'Ya, Lanjutkan', modalBox: 'max-w-lg rounded-lg p-0',
color: 'success',
onClick: managerApprovalHandler,
}} }}
secondaryButton={{ >
text: 'Batal', <div className='flex flex-col'>
onClick: () => { <div className='flex items-center justify-between border-b border-base-content/10 p-4'>
setApprovalNotes(''); <h4 className='text-sm font-semibold text-base-content'>
Konfirmasi Approval Manager
</h4>
<Button
variant='ghost'
color='none'
onClick={() => {
setManagerApprovalNotes('');
setManagerApprovalPoDate('');
confirmationModalWithNotes.closeModal(); confirmationModalWithNotes.closeModal();
},
}} }}
className='p-1'
>
<Icon icon='mdi:close' width={20} height={20} />
</Button>
</div>
<div className='flex flex-col gap-4 p-4'>
<p className='text-sm text-base-content/70'>
Apakah Anda yakin ingin melanjutkan approval ini?
</p>
<DateInput
name='manager_approval_po_date'
label='Tanggal PO'
value={managerApprovalPoDate}
onChange={(e) => setManagerApprovalPoDate(e.target.value)}
isNestedModal
/> />
<TextArea
name='manager_approval_notes'
label='Catatan (Opsional)'
placeholder='Tambahkan catatan untuk approval ini...'
value={managerApprovalNotes}
onChange={(e) => setManagerApprovalNotes(e.target.value)}
rows={4}
/>
</div>
<div className='flex justify-end gap-3 border-t border-base-content/10 p-4'>
<Button
variant='outline'
color='none'
onClick={() => {
setManagerApprovalNotes('');
setManagerApprovalPoDate('');
confirmationModalWithNotes.closeModal();
}}
className='px-3 py-2.5'
>
Batal
</Button>
<Button
color='success'
onClick={managerApprovalHandler}
className='px-3 py-2.5'
>
Ya, Lanjutkan
</Button>
</div>
</div>
</Modal>
{/* Staff Approval Modal */} {/* Staff Approval Modal */}
<Modal <Modal
ref={staffApprovalModal.ref} ref={staffApprovalModal.ref}
@@ -1112,6 +1238,66 @@ const PurchaseOrderDetail = ({
/> />
</Modal> </Modal>
{/* Edit PO Date Modal */}
<Modal
ref={editPoDateModal.ref}
closeOnBackdrop
className={{
modalBox: 'max-w-sm rounded-lg p-0',
}}
>
<div className='flex flex-col'>
<div className='flex items-center justify-between border-b border-base-content/10 p-4'>
<h4 className='text-sm font-semibold text-base-content'>
Edit Tanggal PO
</h4>
<Button
variant='ghost'
color='none'
onClick={() => {
setEditPoDate('');
editPoDateModal.closeModal();
}}
className='p-1'
>
<Icon icon='mdi:close' width={20} height={20} />
</Button>
</div>
<div className='flex flex-col gap-4 p-4'>
<DateInput
name='edit_po_date'
label='Tanggal PO'
value={editPoDate}
onChange={(e) => setEditPoDate(e.target.value)}
isNestedModal
/>
</div>
<div className='flex justify-end gap-3 border-t border-base-content/10 p-4'>
<Button
variant='outline'
color='none'
onClick={() => {
setEditPoDate('');
editPoDateModal.closeModal();
}}
className='px-3 py-2.5'
>
Batal
</Button>
<Button
color='primary'
onClick={updatePoDateHandler}
className='px-3 py-2.5'
disabled={!editPoDate}
>
Simpan
</Button>
</div>
</div>
</Modal>
{/* Staff Rejection Modal */} {/* Staff Rejection Modal */}
<ConfirmationModalWithNotes <ConfirmationModalWithNotes
ref={staffRejectionModal.ref} ref={staffRejectionModal.ref}
@@ -846,6 +846,27 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
</Card> </Card>
); );
})} })}
{!isLoading && data.length > 0 && meta && (
<div className='mt-5 px-3'>
<Pagination
totalItems={meta.total_results || 0}
itemsPerPage={meta.limit || 0}
currentPage={meta.page || 0}
onPrevPage={() =>
setCurrentPage((curr) => (curr > 1 ? curr - 1 : curr))
}
onNextPage={() =>
setCurrentPage((curr) =>
meta.total_pages && curr < meta.total_pages ? curr + 1 : curr
)
}
onPageChange={(pageNumber) => setCurrentPage(pageNumber)}
rowOptions={[10, 20, 50, 100]}
onRowChange={setPageSize}
/>
</div>
)}
</div> </div>
{/* Filter Modal */} {/* Filter Modal */}
@@ -771,6 +771,27 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
</Card> </Card>
); );
})} })}
{!isLoading && data.length > 0 && meta && (
<div className='mt-5 px-3'>
<Pagination
totalItems={meta.total_results || 0}
itemsPerPage={meta.limit || 0}
currentPage={meta.page || 0}
onPrevPage={() =>
setCurrentPage((curr) => (curr > 1 ? curr - 1 : curr))
}
onNextPage={() =>
setCurrentPage((curr) =>
meta.total_pages && curr < meta.total_pages ? curr + 1 : curr
)
}
onPageChange={(pageNumber) => setCurrentPage(pageNumber)}
rowOptions={[10, 20, 50, 100]}
onRowChange={setPageSize}
/>
</div>
)}
</div> </div>
{/* Filter Modal */} {/* Filter Modal */}
+13
View File
@@ -115,6 +115,19 @@ export const PurchaseApi = {
}, },
}, },
updatePoDate: async (
purchaseRequestId: number,
payload: { po_date: string }
): Promise<BaseApiResponse<Purchase> | undefined> => {
return await basePurchaseApi.customRequest<BaseApiResponse<Purchase>>(
`${purchaseRequestId}/po-date`,
{
method: 'PATCH',
payload,
}
);
},
async exportToExcel(initialQueryString: string) { async exportToExcel(initialQueryString: string) {
const params = new URLSearchParams(initialQueryString); const params = new URLSearchParams(initialQueryString);
+2
View File
@@ -68,6 +68,7 @@ export type BasePurchase = {
supplier: Supplier; supplier: Supplier;
credit_term?: number; credit_term?: number;
due_date: string; due_date: string;
received_date?: string | null;
notes?: string | null; notes?: string | null;
deleted_at?: string | null; deleted_at?: string | null;
created_by: number; created_by: number;
@@ -122,6 +123,7 @@ export type UpdateStaffApprovalRequestPayload = {
export type CreateManagerApprovalRequestPayload = { export type CreateManagerApprovalRequestPayload = {
action: 'APPROVED' | 'REJECTED'; action: 'APPROVED' | 'REJECTED';
notes?: string | null; notes?: string | null;
po_date?: string | null;
}; };
export type CreateAcceptApprovalRequestPayload = { export type CreateAcceptApprovalRequestPayload = {