'use client'; import Button from '@/components/Button'; import CheckboxInput from '@/components/input/CheckboxInput'; import DateInput from '@/components/input/DateInput'; import TextArea from '@/components/input/TextArea'; import Modal, { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import Table from '@/components/Table'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { cn, formatCurrency, formatDate, formatTitleCase } from '@/lib/helper'; import { MarketingApi, SalesOrderApi, } from '@/services/api/marketing/marketing'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { BaseApiResponse } from '@/types/api/api-general'; import { BaseSalesOrder, Marketing, MarketingFilter, } from '@/types/api/marketing/marketing'; import { Icon } from '@iconify/react'; import { CellContext, ColumnDef, Row } from '@tanstack/react-table'; import { useRouter } from 'next/navigation'; import { ChangeEventHandler, useCallback, useMemo, useState } from 'react'; import toast from 'react-hot-toast'; import useSWR from 'swr'; import RequirePermission from '@/components/helper/RequirePermission'; import Dropdown from '@/components/Dropdown'; import PopoverButton from '@/components/popover/PopoverButton'; import PopoverContent from '@/components/popover/PopoverContent'; import StatusBadge from '@/components/helper/StatusBadge'; import MarketingFilterModal from '@/components/pages/marketing/MarketingFilter'; import ButtonFilter from '@/components/helper/ButtonFilter'; import MarketingTableSkeleton from '@/components/pages/marketing/skeleton/MarketingTableSkeleton'; const RowsOptionsMenu = ({ props, deleteClickHandler, deliveryClickHandler, popoverPosition, }: { type: 'dropdown' | 'collapse'; props: CellContext; deleteClickHandler: () => void; deliveryClickHandler?: () => void; popoverPosition?: 'top' | 'bottom'; }) => { const popoverId = `marketing#${props.row.original.id}`; const popoverAnchorName = `--anchor-marketing#${props.row.original.id}`; const isDeliveryRejected = props.row.original.latest_approval.action === 'REJECTED' && props.row.original.latest_approval.step_number === 3; return (
{props.row.original.latest_approval.step_number != 1 && !isDeliveryRejected && ( <> )} {props.row.original.latest_approval.step_number != 3 && ( <> )}
); }; const MarketingTable = () => { const [approveAction, setApproveAction] = useState<'APPROVED' | 'REJECTED'>( 'APPROVED' ); const [selectedItem, setSelectedItem] = useState(null); const [rowSelection, setRowSelection] = useState>({}); const [bulkDeliveryDate, setBulkDeliveryDate] = useState(''); const [bulkDeliveryNotes, setBulkDeliveryNotes] = useState(''); const [isSubmittingBulkDelivery, setIsSubmittingBulkDelivery] = useState(false); const router = useRouter(); const deleteModal = useModal(); const confirmationModal = useModal(); const productsModal = useModal(); const deliveryModal = useModal(); const bulkDeliveryModal = useModal(); const filterModal = useModal(); const { state: tableFilterState, updateFilter, setPage, setPageSize, toQueryString: getTableFilterToQueryString, } = useTableFilter({ initial: { search: '', product_ids: '', status: '', customer_id: '', }, paramMap: { page: 'page', pageSize: 'limit', product_ids: 'product_ids', status: 'status', customer_id: 'customer_id', }, persist: true, storeName: 'marketing-table', }); // ===== FETCH DATA ===== const { data: marketing, isLoading: isLoadingMarketing, mutate: refreshMarketing, } = useSWR( `${MarketingApi.basePath}${getTableFilterToQueryString()}`, MarketingApi.getAllFetcher ); // ===== HANDLER ===== const filterSubmitHandler = (values: MarketingFilter) => { updateFilter( 'product_ids', values.product_ids?.map((item) => item.toString()).join(','), true ); updateFilter('status', values.status ? values.status.toString() : '', true); updateFilter( 'customer_id', values.customer_id ? values.customer_id.toString() : '', true ); }; const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] = useState(false); const filterResetHandler = () => { updateFilter('product_ids', '', true); updateFilter('status', '', true); updateFilter('customer_id', '', true); }; const approveClickHandler = () => { setApproveAction('APPROVED'); if (selectedApprovalStep === 2) { bulkDeliveryModal.openModal(); return; } confirmationModal.openModal(); }; const rejectClickHandler = () => { setApproveAction('REJECTED'); confirmationModal.openModal(); }; const productsClickHandler = useCallback( (item: Marketing) => { setSelectedItem(item); productsModal.openModal(); }, [productsModal] ); const deleteMarketingHandler = async () => { const deleteMarketingRes = await MarketingApi.delete( selectedItem?.id as number ); if (isResponseSuccess(deleteMarketingRes)) { confirmationModal.closeModal(); toast.success(deleteMarketingRes?.message as string); } if (isResponseError(deleteMarketingRes)) { confirmationModal.closeModal(); toast.error(deleteMarketingRes?.message as string); } refreshMarketing(); deleteModal.closeModal(); }; const allData = isResponseSuccess(marketing) ? marketing.data : []; const selectedRowsData = allData.filter( (row) => rowSelection[row.id.toString()] ); const selectedApprovalStep = selectedRowsData.length > 0 ? selectedRowsData[0].latest_approval.step_number : null; const eligibleSelectedRows = selectedRowsData.filter((row) => { const approval = row.latest_approval; if (approval.action === 'REJECTED') { return false; } if (selectedApprovalStep === null) { return approval.step_number === 1 || approval.step_number === 2; } return approval.step_number === selectedApprovalStep; }); const hasApprovable = eligibleSelectedRows.length > 0; const hasRejectable = eligibleSelectedRows.length > 0; const disableApprove = !hasApprovable; const disableReject = !hasRejectable; const idsToProcess = eligibleSelectedRows.map((row) => row.id); const nextApprovalStatus = selectedApprovalStep === 1 ? 'SALES_ORDER' : selectedApprovalStep === 2 ? 'DELIVERY_ORDER' : null; const approveMarketingHandler = async (notes: string) => { if (idsToProcess.length === 0) { toast.error(`Tidak ada data yang valid untuk di ${approveAction}.`); confirmationModal.closeModal(); return; } if (approveAction === 'APPROVED' && selectedApprovalStep !== 1) { toast.error('Approve tahap ini harus menggunakan tanggal pengiriman.'); confirmationModal.closeModal(); return; } if (approveAction === 'APPROVED' && !nextApprovalStatus) { toast.error('Status approval berikutnya tidak valid.'); confirmationModal.closeModal(); return; } const approveMarketingRes: BaseApiResponse | undefined = approveAction === 'APPROVED' ? await MarketingApi.bulkApprovals( idsToProcess, nextApprovalStatus as 'SALES_ORDER' | 'DELIVERY_ORDER', '', notes || `APPROVED marketing ${idsToProcess.join(', ')}` ) : await SalesOrderApi.bulkApprovals(idsToProcess, approveAction, notes); if (isResponseSuccess(approveMarketingRes)) { confirmationModal.closeModal(); toast.success(approveMarketingRes?.message as string); setRowSelection({}); } refreshMarketing(); }; const bulkDeliveryDateChangeHandler: ChangeEventHandler = ( e ) => { setBulkDeliveryDate(e.target.value); }; const bulkDeliveryNotesChangeHandler: ChangeEventHandler< HTMLTextAreaElement > = (e) => { setBulkDeliveryNotes(e.target.value); }; const submitBulkDeliveryApprovalHandler = async ( selectedIds: number[], deliveryDate: string, notes: string ) => { if (selectedIds.length === 0) { toast.error('Tidak ada data yang valid untuk diproses.'); return; } if (!deliveryDate) { toast.error('Tanggal pengiriman wajib diisi.'); return; } setIsSubmittingBulkDelivery(true); try { const bulkDeliveryApprovalRes = await MarketingApi.bulkApprovals( selectedIds, 'DELIVERY_ORDER', deliveryDate, notes || `APPROVED delivery marketing ${selectedIds.join(', ')}` ); if (isResponseError(bulkDeliveryApprovalRes)) { toast.error(bulkDeliveryApprovalRes?.message as string); return; } if (!isResponseSuccess(bulkDeliveryApprovalRes)) { toast.error('Gagal memproses bulk approve delivery.'); return; } toast.success(bulkDeliveryApprovalRes?.message as string); bulkDeliveryModal.closeModal(); setBulkDeliveryDate(''); setBulkDeliveryNotes(''); setRowSelection({}); refreshMarketing(); } finally { setIsSubmittingBulkDelivery(false); } }; const confirmationModalDeliveryClickHandler = async (notes: string) => { const res = await SalesOrderApi.delivery(selectedItem?.id as number, notes); deliveryModal.closeModal(); toast.success(res?.message as string); refreshMarketing?.(); router.push( `/marketing/detail/delivery-orders/edit?id=${selectedItem?.id}` ); }; const getRowCanSelect = useCallback( (row: Row): boolean => { const approval = row.original.latest_approval; const isSelectableStep = approval?.step_number === 1 || approval?.step_number === 2; if (!isSelectableStep || approval?.action === 'REJECTED') { return false; } if (selectedApprovalStep === null) { return true; } return approval?.step_number === selectedApprovalStep; }, [selectedApprovalStep] ); const exportToExcelHandler = async () => { setIsLoadingExportingToExcel(true); await MarketingApi.exportToExcel(getTableFilterToQueryString()); setIsLoadingExportingToExcel(false); }; const columns = useMemo[]>(() => { return [ { id: 'select', size: 1, header: ({ table }) => { const allRows = table.getRowModel().rows; const stepForBulkSelection = selectedApprovalStep ?? allRows.find(getRowCanSelect)?.original.latest_approval.step_number; const selectableRows = allRows.filter((row) => { if (!getRowCanSelect(row)) { return false; } if (!stepForBulkSelection) { return false; } return ( row.original.latest_approval.step_number === stepForBulkSelection ); }); const allSelected = selectableRows.length > 0 && selectableRows.every((row) => row.getIsSelected()); const someSelected = selectableRows.some((row) => row.getIsSelected()) && !allSelected; const toggleSelectableRows = () => { const shouldSelect = !allSelected; selectableRows.forEach((row) => row.toggleSelected(shouldSelect)); }; return (
); }, cell: ({ row }) => { const canSelect = getRowCanSelect(row); return (
); }, }, { accessorKey: 'so_do_number', header: 'No. Order', cell: (props) => { return props.row.original.do_number ? props.row.original.do_number : props.row.original.so_number; }, }, { accessorKey: 'so_date', header: 'Tanggal', cell: (props) => { return formatDate(props.row.original.so_date, 'DD MMM yyyy'); }, }, { accessorKey: 'approval.step_name', header: 'Status', cell: (props) => { const approval = props.row.original.latest_approval; const isRejected = approval?.action == 'REJECTED'; const isApproved = approval?.action == 'APPROVED'; const isUpdated = approval?.action == 'UPDATED'; return ( ); }, }, { accessorKey: 'customer.name', header: 'Customer', }, { accessorFn: (row) => row.sales_order ?.map((product) => product.total_price) .reduce((a, b) => a + b, 0) ?? 0, header: 'Grand Total', cell: (props) => { return formatCurrency( props.row.original?.sales_order ?.map((product) => product.total_price) .reduce((a, b) => a + b, 0) ?? 0 ); }, }, { accessorKey: 'marketing_products.length', header: 'Product Details', cell: (props) => { if (props?.row?.original?.sales_order?.length) { if (props?.row?.original?.sales_order?.length > 1) { return ( ); } else { const product = props?.row?.original?.sales_order[0]; return <>{product?.product_warehouse?.product?.name}; } } }, }, { id: 'actions', maxSize: 80, cell: (props) => { const currentPageSize = props.table.getPaginationRowModel().rows.length; const currentPageRows = props.table.getPaginationRowModel().flatRows; const currentRowRelativeIndex = currentPageRows.findIndex((r) => r.id === props.row.id) + 1; const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2; const deleteClickHandler = () => { setSelectedItem(props.row.original); deleteModal.openModal(); }; const deliveryClickHandler = () => { setSelectedItem(props.row.original); deliveryModal.openModal(); }; return ( ); }, }, ]; }, [ deleteModal, deliveryModal, getRowCanSelect, productsClickHandler, selectedApprovalStep, ]); return ( <>
{idsToProcess.length > 0 && ( <>
)}
{ filterModal.openModal(); }} className='px-3 py-2.5' /> Export
} className={{ content: 'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden', }} >
{isLoadingMarketing ? (
) : !isResponseSuccess(marketing) || marketing.data?.length === 0 ? (
} />
) : ( )}

Bulk Approve Delivery

Pilih tanggal pengiriman untuk approve {idsToProcess.length} data penjualan tahap 2.