'use client'; import Button from '@/components/Button'; import CheckboxInput from '@/components/input/CheckboxInput'; 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 { 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 { 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 router = useRouter(); const deleteModal = useModal(); const confirmationModal = useModal(); const productsModal = useModal(); const deliveryModal = 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', }, }); // ===== 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(',') ); updateFilter('status', values.status ? values.status.toString() : ''); updateFilter( 'customer_id', values.customer_id ? values.customer_id.toString() : '' ); }; const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] = useState(false); const filterResetHandler = () => { updateFilter('product_ids', ''); updateFilter('status', ''); updateFilter('customer_id', ''); }; const approveClickHandler = () => { setApproveAction('APPROVED'); confirmationModal.openModal(); }; const rejectClickHandler = () => { setApproveAction('REJECTED'); confirmationModal.openModal(); }; const productsClickHandler = (item: Marketing) => { setSelectedItem(item); productsModal.openModal(); }; 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 hasApprovable = selectedRowsData.some( (row) => row.latest_approval.step_number === 1 && row.latest_approval.action !== 'REJECTED' ); const hasRejectable = selectedRowsData.some( (row) => row.latest_approval.step_number === 1 && row.latest_approval.action !== 'REJECTED' ); const disableApprove = !hasApprovable; const disableReject = !hasRejectable; const idsToProcess = approveAction === 'APPROVED' ? selectedRowsData .filter((row) => row.latest_approval.step_number === 1) .map((row) => row.id) : selectedRowsData .filter((row) => row.latest_approval.step_number === 2) .map((row) => row.id); const approveMarketingHandler = async (notes: string) => { let idsToProcess: number[] = []; idsToProcess = selectedRowsData .filter((row) => row.latest_approval.step_number === 1) .map((row) => row.id); if (idsToProcess.length === 0) { toast.error(`Tidak ada data yang valid untuk di ${approveAction}.`); confirmationModal.closeModal(); return; } const approveMarketingRes = await SalesOrderApi.bulkApprovals( idsToProcess, approveAction, notes ); if (isResponseSuccess(approveMarketingRes)) { confirmationModal.closeModal(); toast.success(approveMarketingRes?.message as string); setRowSelection({}); } if (isResponseError(approveMarketingRes)) { confirmationModal.closeModal(); toast.error(approveMarketingRes?.message as string); } refreshMarketing(); }; 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 = (row: Row): boolean => { const approval = row.original.latest_approval; return approval?.step_number === 1 && approval?.action !== 'REJECTED'; }; 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 selectableRows = allRows.filter(getRowCanSelect); 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 ( ); }, }, ]; }, []); 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 ? (
} />
) : ( )}

{' '} Daftar Produk

data={ isResponseSuccess(marketing) && selectedItem ? (selectedItem?.sales_order ?? []) : [] } columns={[ { header: 'Kandang', accessorFn(row) { return row.product_warehouse.warehouse.name; }, }, { header: 'Produk', accessorFn(row) { return row.product_warehouse.product.name; }, }, { header: 'Harga Satuan (Rp)', accessorFn(row) { return formatCurrency(row.unit_price); }, }, ]} className={{ containerClassName: 'p-4', headerColumnClassName: 'whitespace-nowrap', bodyColumnClassName: 'last:flex last:flex-row last:justify-end', paginationClassName: 'hidden', }} isLoading={isLoadingMarketing} />
); }; export default MarketingTable;