'use client'; import Button from '@/components/Button'; import CheckboxInput from '@/components/input/CheckboxInput'; import SelectInput, { OptionType, useSelect, } from '@/components/input/SelectInput'; import Modal, { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import Table from '@/components/Table'; import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector'; import { TableToolbar } from '@/components/table/TableToolbar'; import { ROWS_OPTIONS } from '@/config/constant'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { cn, formatCurrency, formatDate } from '@/lib/helper'; import { MarketingApi, SalesOrderApi, } from '@/services/api/marketing/marketing'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { BaseSalesOrder, Marketing } from '@/types/api/marketing/marketing'; import { Icon } from '@iconify/react'; import { CellContext, Row } from '@tanstack/react-table'; import { useRouter } from 'next/navigation'; import { useCallback, useState } from 'react'; import toast from 'react-hot-toast'; import useSWR from 'swr'; import RequirePermission from '@/components/helper/RequirePermission'; import { useAuth } from '@/services/hooks/useAuth'; import { CustomerApi, ProductApi } from '@/services/api/master-data'; import { MARKETING_APPROVAL_LINE } from '@/config/approval-line'; const RowsOptionsMenu = ({ type = 'dropdown', props, deleteClickHandler, deliveryClickHandler, }: { type: 'dropdown' | 'collapse'; props: CellContext; deleteClickHandler: () => void; deliveryClickHandler?: () => void; }) => { return (
{props.row.original.latest_approval.step_number != 1 && ( <> )} {props.row.original.latest_approval.step_number != 3 && ( <> )}
); }; const MarketingTable = () => { const [search, setSearch] = useState(''); const [approveAction, setApproveAction] = useState<'APPROVED' | 'REJECTED'>( 'APPROVED' ); const [selectedItem, setSelectedItem] = useState(null); const [rowSelection, setRowSelection] = useState>({}); const { permissionCheck } = useAuth(); const router = useRouter(); const deleteModal = useModal(); const confirmationModal = useModal(); const productsModal = useModal(); const deliveryModal = useModal(); const { state: tableFilterState, updateFilter, setPage, setPageSize, toQueryString: getTableFilterToQueryString, } = useTableFilter({ initial: { search: '', product_ids: '', status: '', customer_id: '', page: 1, limit: 10, }, 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 ); // ===== OPTIONS ===== const { options: productsOptions, isLoadingOptions: isLoadingProductsOptions, setInputValue: setProductsInputValue, loadMore: loadMoreProducts, } = useSelect(ProductApi.basePath, 'id', 'name', '', { limit: 'limit', }); const { options: customersOptions, isLoadingOptions: isLoadingCustomersOptions, setInputValue: setCustomersInputValue, loadMore: loadMoreCustomers, } = useSelect(CustomerApi.basePath, 'id', 'name', '', { limit: 'limit', }); const statusOptions = MARKETING_APPROVAL_LINE.map((item) => ({ value: item.step_number, label: item.step_name, })); // ===== HANDLER ===== const searchChangeHandler = useCallback( (e: React.ChangeEvent) => { setSearch(e.target.value); updateFilter('page', 1); updateFilter('search', e.target.value); }, [] ); const pageSizeChangeHandler = useCallback( (val: OptionType | OptionType[] | null) => { const newVal = val as OptionType; setPageSize(newVal.value as number); updateFilter('page', 1); updateFilter('limit', newVal.value as number); }, [] ); 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?marketingId=${selectedItem?.id}` ); }; const getRowCanSelect = (row: Row): boolean => { const approval = row.original.latest_approval; return approval?.step_number === 1 && approval?.action !== 'REJECTED'; }; return ( <>
{/* select multiple product */} productsOptions.find( (option) => option.value === Number(id) ) ) .filter( (option): option is { value: number; label: string } => option !== undefined ) ?? null } onChange={(value: OptionType | OptionType[] | null) => updateFilter( 'product_ids', (value as OptionType[]) ?.map((item: OptionType) => item.value.toString()) .join(',') || '' ) } onInputChange={setProductsInputValue} onMenuScrollToBottom={loadMoreProducts} isMulti /> {/* select status */} option.value === Number(tableFilterState.status) ) : null } onChange={(value: OptionType | OptionType[] | null) => updateFilter( 'status', (value as OptionType)?.value.toString() || '' ) } /> {/* select customer */} option.value === Number(tableFilterState.customer_id) ) : null } onChange={(value: OptionType | OptionType[] | null) => updateFilter( 'customer_id', (value as OptionType)?.value.toString() || '' ) } onInputChange={setCustomersInputValue} onMenuScrollToBottom={loadMoreCustomers} />
{ 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_number', header: 'No. Order', }, { accessorKey: 'so_date', header: 'Tanggal', cell: (props) => { return formatDate(props.row.original.so_date, 'DD MMM yyyy'); }, }, { accessorKey: 'latest_approval.step_name', header: 'Status', }, { 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}; } } }, }, { header: 'Aksi', 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 ( <> {currentPageSize > 2 && ( )} {currentPageSize <= 2 && ( )} ); }, }, ]} pageSize={tableFilterState.pageSize} page={tableFilterState.page} onPageChange={setPage} className={{ tableWrapperClassName: 'overflow-x-auto min-h-full!', tableClassName: 'font-inter w-full table-auto min-h-full!', headerRowClassName: 'border-b border-b-gray-200', headerColumnClassName: 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', bodyRowClassName: 'border-b border-b-gray-200', bodyColumnClassName: 'px-6 py-3 last:flex last:flex-row last:justify-end', }} />

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-6', tableWrapperClassName: 'overflow-x-auto min-h-full!', tableClassName: 'font-inter w-full table-auto min-h-full!', headerRowClassName: 'border-b border-b-gray-200', headerColumnClassName: 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', bodyRowClassName: 'border-b border-b-gray-200', bodyColumnClassName: 'px-6 py-3 last:flex last:flex-row last:justify-end', paginationClassName: 'hidden', }} isLoading={isLoadingMarketing} />
); }; export default MarketingTable;