Files
lti-web-client/src/components/pages/marketing/MarketingTable.tsx
T
2026-05-13 15:29:03 +07:00

1303 lines
41 KiB
TypeScript

'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 {
getErrorMessage,
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,
SortingState,
Updater,
} 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<Marketing, unknown>;
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 (
<div className='relative'>
<PopoverButton
tabIndex={0}
variant='ghost'
color='none'
popoverTarget={popoverId}
anchorName={popoverAnchorName}
>
<Icon icon='material-symbols:more-vert' width={16} height={16} />
</PopoverButton>
<PopoverContent
id={popoverId}
anchorName={popoverAnchorName}
position={popoverPosition === 'bottom' ? 'bottom-start' : 'left'}
className='w-full max-w-40 rounded-xl border border-base-content/5 shadow-sm'
>
<div className='flex flex-col bg-base-100 rounded-xl'>
<RequirePermission permissions='lti.marketing.delivery_order.detail'>
<Button
href={`/marketing?action=detail&id=${props.row.original.id}`}
variant='ghost'
color='none'
className='p-3 justify-start text-sm font-semibold w-full'
>
<Icon icon='heroicons:eye' width={20} height={20} />
View Details
</Button>
</RequirePermission>
{props.row.original.latest_approval.step_number != 1 &&
!isDeliveryRejected && (
<>
<RequirePermission
permissions={
props.row.original.latest_approval.step_number == 3
? 'lti.marketing.delivery_order.update'
: 'lti.marketing.delivery_order.create'
}
>
<Button
href={
props.row.original.latest_approval.step_number == 3
? `/marketing?action=edit_delivery&id=${props.row.original.id}`
: props.row.original.latest_approval.step_number == 2
? `/marketing?action=add_delivery&id=${props.row.original.id}`
: undefined
}
onClick={() => {
if (props.row.original.latest_approval.step_number == 2) {
deliveryClickHandler?.();
}
}}
variant='ghost'
color='none'
className='p-3 justify-start text-sm font-semibold w-full'
>
<Icon icon='heroicons:truck' width={20} height={20} />
{props.row.original.latest_approval.step_number == 2
? 'Deliver Item'
: 'Edit Delivery'}
</Button>
</RequirePermission>
</>
)}
{props.row.original.latest_approval.step_number != 3 && (
<>
<RequirePermission permissions='lti.marketing.sales_order.update'>
<Button
href={`/marketing?action=edit&id=${props.row.original.id}`}
variant='ghost'
color='none'
className='p-3 justify-start text-sm font-semibold w-full'
>
<Icon icon='heroicons:pencil-square' width={20} height={20} />
Edit Item
</Button>
</RequirePermission>
</>
)}
<RequirePermission permissions='lti.marketing.sales_order.delete'>
<Button
onClick={deleteClickHandler}
variant='ghost'
color='none'
className='relative p-3 overflow-hidden justify-start text-sm font-semibold w-full text-error before:content-[""] before:absolute before:h-px before:p-3 before:top-0 before:left-0 before:right-0 before:border-t before:border-base-content/5'
>
<Icon icon='heroicons:trash' width={20} height={20} />
Delete Item
</Button>
</RequirePermission>
</div>
</PopoverContent>
</div>
);
};
const MarketingTable = () => {
const [approveAction, setApproveAction] = useState<'APPROVED' | 'REJECTED'>(
'APPROVED'
);
const [selectedItem, setSelectedItem] = useState<Marketing | null>(null);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const [bulkDeliveryDate, setBulkDeliveryDate] = useState('');
const [bulkDeliveryNotes, setBulkDeliveryNotes] = useState('');
const [isSubmittingBulkDelivery, setIsSubmittingBulkDelivery] =
useState(false);
const [isExportProgressLoading, setIsExportProgressLoading] = useState(false);
const [exportProgressStartDate, setExportProgressStartDate] = useState('');
const [exportProgressEndDate, setExportProgressEndDate] = useState('');
const router = useRouter();
const deleteModal = useModal();
const confirmationModal = useModal();
const productsModal = useModal();
const deliveryModal = useModal();
const bulkDeliveryModal = useModal();
const exportProgressInputModal = useModal();
const filterModal = useModal();
const {
state: tableFilterState,
updateFilter,
setPage,
setPageSize,
toQueryString: getTableFilterToQueryString,
} = useTableFilter({
initial: {
search: '',
product_ids: '',
product_names: '',
status: '',
status_name: '',
customer_id: '',
customer_name: '',
project_flock_id: '',
project_flock_name: '',
project_flock_kandang_id: '',
project_flock_kandang_name: '',
sort_by: '',
order_by: '',
},
paramMap: {
page: 'page',
pageSize: 'limit',
product_ids: 'product_ids',
status: 'status',
customer_id: 'customer_id',
project_flock_id: 'project_flock_id',
project_flock_kandang_id: 'project_flock_kandang_id',
sort_by: 'sort_by',
order_by: 'sort_order',
},
excludeKeysFromUrl: [
'product_names',
'status_name',
'customer_name',
'project_flock_name',
'project_flock_kandang_name',
],
persist: true,
storeName: 'marketing-table',
});
const sorting: SortingState = tableFilterState.sort_by
? [
{
id: tableFilterState.sort_by,
desc: tableFilterState.order_by === 'desc',
},
]
: [];
const handleSortingChange = (updater: Updater<SortingState>) => {
const next = typeof updater === 'function' ? updater(sorting) : updater;
if (next.length > 0) {
updateFilter('sort_by', next[0].id, true);
updateFilter('order_by', next[0].desc ? 'desc' : 'asc', true);
} else {
updateFilter('sort_by', '', true);
updateFilter('order_by', '', true);
}
};
// ===== 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('product_names', values.product_names?.join(','));
updateFilter('status', values.status ? values.status.toString() : '', true);
updateFilter('status_name', values.status_name, true);
updateFilter(
'customer_id',
values.customer_id ? values.customer_id.toString() : '',
true
);
updateFilter('customer_name', values.customer_name, true);
updateFilter(
'project_flock_id',
values.project_flock_id ? values.project_flock_id.toString() : '',
true
);
updateFilter('project_flock_name', values.project_flock_name ?? '', true);
updateFilter(
'project_flock_kandang_id',
values.project_flock_kandang_id
? values.project_flock_kandang_id.toString()
: '',
true
);
updateFilter(
'project_flock_kandang_name',
values.project_flock_kandang_name ?? '',
true
);
};
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
useState(false);
const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isDeliveryLoading, setIsDeliveryLoading] = useState(false);
const filterResetHandler = () => {
updateFilter('product_ids', '', true);
updateFilter('product_names', '', true);
updateFilter('status', '', true);
updateFilter('status_name', '', true);
updateFilter('customer_id', '', true);
updateFilter('customer_name', '', true);
updateFilter('project_flock_id', '', true);
updateFilter('project_flock_name', '', true);
updateFilter('project_flock_kandang_id', '', true);
updateFilter('project_flock_kandang_name', '', 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 productIds = tableFilterState.product_ids
? tableFilterState.product_ids
.split(',')
.map((item) => item.trim())
.filter(Boolean)
: [];
const productLabels = tableFilterState.product_names
? tableFilterState.product_names
.split(',')
.map((item) => item.trim())
.filter(Boolean)
: [];
const marketingFilterInitialValues = {
product_ids: productIds.map((value, idx) => ({
value: Number(value),
label: productLabels[idx] || '-',
})),
status: tableFilterState.status
? {
value: tableFilterState.status,
label: tableFilterState.status_name,
}
: null,
customer: tableFilterState.customer_id
? {
value: Number(tableFilterState.customer_id),
label: tableFilterState.customer_name,
}
: null,
project_flock: tableFilterState.project_flock_id
? {
value: Number(tableFilterState.project_flock_id),
label: tableFilterState.project_flock_name,
}
: null,
project_flock_kandang: tableFilterState.project_flock_kandang_id
? {
value: Number(tableFilterState.project_flock_kandang_id),
label: tableFilterState.project_flock_kandang_name,
}
: 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;
}
setIsApproveLoading(true);
try {
const approveMarketingRes: BaseApiResponse<unknown> | 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();
} finally {
setIsApproveLoading(false);
}
};
const bulkDeliveryDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (
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) => {
setIsDeliveryLoading(true);
try {
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}`
);
} finally {
setIsDeliveryLoading(false);
}
};
const getRowCanSelect = useCallback(
(row: Row<Marketing>): 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 resetExportProgressForm = () => {
setExportProgressStartDate('');
setExportProgressEndDate('');
};
const exportProgressStartDateChangeHandler: ChangeEventHandler<
HTMLInputElement
> = (e) => {
setExportProgressStartDate(e.target.value);
};
const exportProgressEndDateChangeHandler: ChangeEventHandler<
HTMLInputElement
> = (e) => {
setExportProgressEndDate(e.target.value);
};
const exportProgressInputToExcelClickHandler = () => {
resetExportProgressForm();
exportProgressInputModal.openModal();
};
const submitExportProgressInputHandler = async () => {
if (!exportProgressStartDate || !exportProgressEndDate) {
return;
}
setIsExportProgressLoading(true);
try {
await MarketingApi.exportInputProgressToExcel(
exportProgressStartDate,
exportProgressEndDate
);
exportProgressInputModal.closeModal();
resetExportProgressForm();
toast.success('Ekspor berhasil');
} catch (error) {
toast.error(
await getErrorMessage(error, 'Gagal mengekspor input progress')
);
} finally {
setIsExportProgressLoading(false);
}
};
const columns = useMemo<ColumnDef<Marketing>[]>(() => {
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 (
<div className='w-fit flex flex-row justify-start'>
<CheckboxInput
name='allRow'
checked={allSelected}
indeterminate={someSelected}
onChange={toggleSelectableRows}
disabled={selectableRows.length === 0}
/>
</div>
);
},
cell: ({ row }) => {
const canSelect = getRowCanSelect(row);
return (
<div className='w-fit flex flex-row justify-start'>
<CheckboxInput
name='row'
checked={row.getIsSelected()}
disabled={!canSelect}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
</div>
);
},
},
{
accessorKey: 'so_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: 'status',
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 (
<StatusBadge
color={
isRejected
? 'error'
: isApproved || isUpdated
? approval?.step_number == 1
? 'neutral'
: approval?.step_number == 2
? 'info'
: approval?.step_number == 3
? 'success'
: 'neutral'
: 'neutral'
}
text={
isRejected
? 'Ditolak'
: formatTitleCase(approval?.step_name || '')
}
className={{
badge: 'whitespace-nowrap',
}}
/>
);
},
},
{
accessorKey: 'customer',
header: 'Customer',
cell: (props) => props.row.original.customer.name,
},
{
accessorKey: 'grand_total',
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',
enableSorting: false,
cell: (props) => {
if (props?.row?.original?.sales_order?.length) {
if (props?.row?.original?.sales_order?.length > 1) {
return (
<Button
variant='link'
className='p-0 text-none text-sm'
onClick={() => {
productsClickHandler(props?.row?.original);
}}
>
Lihat {props?.row?.original?.sales_order?.length} Produk
</Button>
);
} else {
const product = props?.row?.original?.sales_order[0];
return <>{product?.product_warehouse?.product?.name}</>;
}
}
},
},
{
accessorKey: 'created_at',
header: 'Tanggal Dibuat',
cell: (props) =>
props.row.original.created_at
? formatDate(props.row.original.created_at, 'DD MMM yyyy')
: '-',
},
{
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 (
<RowsOptionsMenu
type='dropdown'
props={props}
deleteClickHandler={deleteClickHandler}
deliveryClickHandler={deliveryClickHandler}
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
/>
);
},
},
];
}, [
deleteModal,
deliveryModal,
getRowCanSelect,
productsClickHandler,
selectedApprovalStep,
]);
return (
<>
<div className='flex flex-col'>
<div className='flex flex-row justify-between p-3 border-b border-base-content/10'>
<div className='flex flex-row gap-3'>
<RequirePermission permissions='lti.marketing.sales_order.create'>
<Button
href={{
pathname: '/marketing',
query: {
action: 'add',
},
}}
className='font-semibold text-base-100 text-sm rounded-lg shadow-button-soft px-3 py-2.5'
>
<Icon icon='heroicons:plus' width={20} height={20} />
Add Sales Order
</Button>
</RequirePermission>
{idsToProcess.length > 0 && (
<>
<div className='divider divider-horizontal w-px p-0 m-0 bg-base-content/10 text-base-content/10 before:bg-base-content/10 before:w-px after:bg-base-content/10 after:w-px' />
<RequirePermission permissions='lti.marketing.sales_order.approve'>
<Button
color='error'
onClick={rejectClickHandler}
className='justify-start text-sm shadow-button-soft rounded-lg bg-base-100 text-base-content/50 px-3 py-2.5 border border-base-content/10'
disabled={disableReject}
>
<Icon
icon='heroicons:x-mark'
className='text-error'
width={20}
height={20}
/>
Reject ({idsToProcess.length} Item)
</Button>
</RequirePermission>
<RequirePermission permissions='lti.marketing.sales_order.approve'>
<Button
color='none'
onClick={approveClickHandler}
className='justify-start text-sm shadow-button-soft rounded-lg bg-base-100 text-base-content/50 px-3 py-2.5 border border-base-content/10'
disabled={disableApprove}
>
<Icon
icon='heroicons:check'
className='text-success'
width={20}
height={20}
/>
Approve ({idsToProcess.length} Item)
</Button>
</RequirePermission>
</>
)}
</div>
<div className='flex flex-row gap-3'>
<ButtonFilter
values={tableFilterState}
excludeFields={[
'page',
'pageSize',
'search',
'product_names',
'status_name',
'customer_name',
'project_flock_name',
'project_flock_kandang_name',
'sort_by',
'order_by',
]}
onClick={() => {
filterModal.openModal();
}}
className='px-3 py-2.5'
/>
<Dropdown
align='end'
direction='bottom'
trigger={
<Button
variant='outline'
color='none'
className={cn(
'px-3 py-2.5 rounded-lg font-semibold text-sm gap-1.5',
'text-sm text-base-content/50 border border-base-content/10 shadow-button-soft'
)}
>
<Icon
width={20}
height={20}
icon='heroicons:cloud-arrow-down'
/>
Export
<div className='w-6.5 h-5 flex items-center justify-center border-l border-base-content/10'>
<Icon
width={14}
height={14}
icon='heroicons:chevron-down'
/>
</div>
</Button>
}
className={{
content:
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
}}
>
<Button
variant='ghost'
color='none'
onClick={exportToExcelHandler}
isLoading={isLoadingExportingToExcel}
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
>
<Icon icon='heroicons:table-cells' width={20} height={20} />
Ekspor ke Excel
</Button>
<Button
variant='ghost'
color='none'
onClick={exportProgressInputToExcelClickHandler}
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
>
<Icon icon='heroicons:table-cells' width={20} height={20} />
Ekspor Input Progress (Excel)
</Button>
</Dropdown>
</div>
</div>
<div className='flex flex-col mb-4'>
{isLoadingMarketing ? (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
) : !isResponseSuccess(marketing) || marketing.data?.length === 0 ? (
<div className='p-3'>
<MarketingTableSkeleton
columns={columns}
icon={
<Icon
icon='heroicons:document-text'
className='text-white'
width={20}
height={20}
/>
}
/>
</div>
) : (
<Table
rowSelection={rowSelection}
setRowSelection={setRowSelection}
onPageChange={setPage}
onPageSizeChange={setPageSize}
data={allData}
columns={columns}
pageSize={tableFilterState.pageSize}
page={isResponseSuccess(marketing) ? marketing?.meta?.page : 1}
sorting={sorting}
setSorting={handleSortingChange}
manualSorting
totalItems={
isResponseSuccess(marketing)
? marketing?.meta?.total_results
: 0
}
isLoading={isLoadingMarketing}
className={{
containerClassName: cn('p-3 mb-0'),
bodyColumnClassName:
'last:text-end last:w-17 first:text-start first:w-5',
}}
/>
)}
</div>
</div>
<ConfirmationModal
ref={deleteModal.ref}
type='error'
text={`Apakah anda yakin ingin menghapus data Project Flock ini?`}
secondaryButton={{
text: 'Tidak',
}}
primaryButton={{
text: 'Ya',
color: 'error',
}}
/>
<ConfirmationModalWithNotes
ref={confirmationModal.ref}
type={approveAction === 'APPROVED' ? 'success' : 'error'}
text={`Apakah anda yakin ingin ${approveAction == 'APPROVED' ? 'approve' : 'reject'} data penjualan tahap ${selectedApprovalStep ?? '-'} (${idsToProcess.length} data)?`}
secondaryButton={{
text: 'Tidak',
isLoading: isApproveLoading,
onClick: confirmationModal.closeModal,
}}
primaryButton={{
text: 'Ya',
color: approveAction === 'APPROVED' ? 'success' : 'error',
isLoading: isApproveLoading,
onClick: approveMarketingHandler,
}}
/>
<ConfirmationModal
ref={deleteModal.ref}
type='error'
text={`Apakah anda yakin ingin menghapus data penjualan ini?`}
secondaryButton={{
text: 'Tidak',
onClick: deleteModal.closeModal,
}}
primaryButton={{
text: 'Ya',
color: 'error',
onClick: deleteMarketingHandler,
}}
/>
<ConfirmationModalWithNotes
ref={deliveryModal.ref}
type={'success'}
text={`Apakah anda yakin ingin deliver penjualan ${selectedItem?.so_number}?`}
secondaryButton={{
text: 'Tidak',
isLoading: isDeliveryLoading,
}}
primaryButton={{
text: 'Ya',
color: 'success',
isLoading: isDeliveryLoading,
onClick: confirmationModalDeliveryClickHandler,
}}
/>
<Modal
ref={bulkDeliveryModal.ref}
className={{
modalBox: 'max-w-lg rounded-lg p-0',
}}
closeOnBackdrop
>
<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'>
Bulk Approve Delivery
</h4>
<Button
variant='ghost'
color='none'
onClick={() => {
bulkDeliveryModal.closeModal();
setBulkDeliveryDate('');
setBulkDeliveryNotes('');
}}
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'>
Pilih tanggal pengiriman untuk approve {idsToProcess.length} data
penjualan tahap 2.
</p>
<DateInput
name='bulk_delivery_date'
label='Tanggal Pengiriman'
value={bulkDeliveryDate}
onChange={bulkDeliveryDateChangeHandler}
isNestedModal
required
/>
<TextArea
name='bulk_delivery_notes'
label='Catatan'
placeholder='Masukkan catatan approval...'
value={bulkDeliveryNotes}
onChange={bulkDeliveryNotesChangeHandler}
rows={4}
/>
</div>
<div className='flex justify-end gap-3 border-t border-base-content/10 p-4'>
<Button
variant='outline'
color='none'
disabled={isSubmittingBulkDelivery}
onClick={() => {
bulkDeliveryModal.closeModal();
setBulkDeliveryDate('');
setBulkDeliveryNotes('');
}}
className='px-3 py-2.5'
>
Batal
</Button>
<Button
color='success'
isLoading={isSubmittingBulkDelivery}
disabled={isSubmittingBulkDelivery}
onClick={() =>
submitBulkDeliveryApprovalHandler(
idsToProcess,
bulkDeliveryDate,
bulkDeliveryNotes
)
}
className='px-3 py-2.5'
>
Submit
</Button>
</div>
</div>
</Modal>
<Modal
ref={exportProgressInputModal.ref}
className={{
modalBox: 'max-w-lg rounded-lg p-0',
}}
closeOnBackdrop
>
<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'>
Ekspor Input Progress
</h4>
<Button
variant='ghost'
color='none'
onClick={() => {
exportProgressInputModal.closeModal();
resetExportProgressForm();
}}
className='p-1'
>
<Icon icon='mdi:close' width={20} height={20} />
</Button>
</div>
<div className='flex flex-col gap-4 p-4'>
<DateInput
name='export_progress_start_date'
label='Tanggal Mulai'
value={exportProgressStartDate}
onChange={exportProgressStartDateChangeHandler}
isNestedModal
required
/>
<DateInput
name='export_progress_end_date'
label='Tanggal Selesai'
value={exportProgressEndDate}
onChange={exportProgressEndDateChangeHandler}
isNestedModal
required
/>
</div>
<div className='flex justify-end gap-3 border-t border-base-content/10 p-4'>
<Button
variant='outline'
color='none'
onClick={() => {
exportProgressInputModal.closeModal();
resetExportProgressForm();
}}
className='px-3 py-2.5'
>
Batal
</Button>
<Button
color='success'
onClick={submitExportProgressInputHandler}
isLoading={isExportProgressLoading}
disabled={!exportProgressStartDate || !exportProgressEndDate}
className='px-3 py-2.5'
>
Submit
</Button>
</div>
</div>
</Modal>
<Modal
ref={productsModal.ref}
className={{
modalBox: 'xs:max-w-2/5 z-100 rounded-lg p-0 flex flex-col',
modal: 'rounded-lg',
}}
closeOnBackdrop
>
<div className='flex flex-row justify-between items-center border-b border-base-content/10 p-4'>
<h4 className='flex items-center justify-center gap-2 text-sm font-medium text-base-content'>
<Icon icon='heroicons:information-circle' width={20} height={20} />{' '}
Daftar Produk
</h4>
<Button
variant='ghost'
color='none'
onClick={productsModal.closeModal}
className='justify-start text-sm p-1'
>
<Icon icon='mdi:close' width={20} height={20} />
</Button>
</div>
<Table<BaseSalesOrder>
data={
isResponseSuccess(marketing) && selectedItem
? (selectedItem?.sales_order ?? [])
: []
}
columns={[
{
header: 'Gudang Fisik',
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}
/>
</Modal>
<MarketingFilterModal
ref={filterModal.ref}
onSubmit={filterSubmitHandler}
onReset={filterResetHandler}
initialValues={marketingFilterInitialValues}
/>
</>
);
};
export default MarketingTable;