mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
Merge branch 'feat/bulk-approve-sales-order' into 'development'
[FEAT/FE] Bulk Approve Sales Order See merge request mbugroup/lti-web-client!413
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
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 Modal, { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
@@ -13,6 +15,7 @@ import {
|
|||||||
SalesOrderApi,
|
SalesOrderApi,
|
||||||
} from '@/services/api/marketing/marketing';
|
} from '@/services/api/marketing/marketing';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import {
|
import {
|
||||||
BaseSalesOrder,
|
BaseSalesOrder,
|
||||||
Marketing,
|
Marketing,
|
||||||
@@ -21,7 +24,7 @@ import {
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { CellContext, ColumnDef, Row } from '@tanstack/react-table';
|
import { CellContext, ColumnDef, Row } from '@tanstack/react-table';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useMemo, useState } from 'react';
|
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
@@ -154,12 +157,17 @@ const MarketingTable = () => {
|
|||||||
);
|
);
|
||||||
const [selectedItem, setSelectedItem] = useState<Marketing | null>(null);
|
const [selectedItem, setSelectedItem] = useState<Marketing | null>(null);
|
||||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
||||||
|
const [bulkDeliveryDate, setBulkDeliveryDate] = useState('');
|
||||||
|
const [bulkDeliveryNotes, setBulkDeliveryNotes] = useState('');
|
||||||
|
const [isSubmittingBulkDelivery, setIsSubmittingBulkDelivery] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
const confirmationModal = useModal();
|
const confirmationModal = useModal();
|
||||||
const productsModal = useModal();
|
const productsModal = useModal();
|
||||||
const deliveryModal = useModal();
|
const deliveryModal = useModal();
|
||||||
|
const bulkDeliveryModal = useModal();
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -182,6 +190,9 @@ const MarketingTable = () => {
|
|||||||
status: 'status',
|
status: 'status',
|
||||||
customer_id: 'customer_id',
|
customer_id: 'customer_id',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
persist: true,
|
||||||
|
storeName: 'marketing-table',
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== FETCH DATA =====
|
// ===== FETCH DATA =====
|
||||||
@@ -198,12 +209,14 @@ const MarketingTable = () => {
|
|||||||
const filterSubmitHandler = (values: MarketingFilter) => {
|
const filterSubmitHandler = (values: MarketingFilter) => {
|
||||||
updateFilter(
|
updateFilter(
|
||||||
'product_ids',
|
'product_ids',
|
||||||
values.product_ids?.map((item) => item.toString()).join(',')
|
values.product_ids?.map((item) => item.toString()).join(','),
|
||||||
|
true
|
||||||
);
|
);
|
||||||
updateFilter('status', values.status ? values.status.toString() : '');
|
updateFilter('status', values.status ? values.status.toString() : '', true);
|
||||||
updateFilter(
|
updateFilter(
|
||||||
'customer_id',
|
'customer_id',
|
||||||
values.customer_id ? values.customer_id.toString() : ''
|
values.customer_id ? values.customer_id.toString() : '',
|
||||||
|
true
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -211,13 +224,19 @@ const MarketingTable = () => {
|
|||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
const filterResetHandler = () => {
|
const filterResetHandler = () => {
|
||||||
updateFilter('product_ids', '');
|
updateFilter('product_ids', '', true);
|
||||||
updateFilter('status', '');
|
updateFilter('status', '', true);
|
||||||
updateFilter('customer_id', '');
|
updateFilter('customer_id', '', true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const approveClickHandler = () => {
|
const approveClickHandler = () => {
|
||||||
setApproveAction('APPROVED');
|
setApproveAction('APPROVED');
|
||||||
|
|
||||||
|
if (selectedApprovalStep === 2) {
|
||||||
|
bulkDeliveryModal.openModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
confirmationModal.openModal();
|
confirmationModal.openModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -226,10 +245,13 @@ const MarketingTable = () => {
|
|||||||
confirmationModal.openModal();
|
confirmationModal.openModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const productsClickHandler = (item: Marketing) => {
|
const productsClickHandler = useCallback(
|
||||||
setSelectedItem(item);
|
(item: Marketing) => {
|
||||||
productsModal.openModal();
|
setSelectedItem(item);
|
||||||
};
|
productsModal.openModal();
|
||||||
|
},
|
||||||
|
[productsModal]
|
||||||
|
);
|
||||||
|
|
||||||
const deleteMarketingHandler = async () => {
|
const deleteMarketingHandler = async () => {
|
||||||
const deleteMarketingRes = await MarketingApi.delete(
|
const deleteMarketingRes = await MarketingApi.delete(
|
||||||
@@ -251,61 +273,135 @@ const MarketingTable = () => {
|
|||||||
const selectedRowsData = allData.filter(
|
const selectedRowsData = allData.filter(
|
||||||
(row) => rowSelection[row.id.toString()]
|
(row) => rowSelection[row.id.toString()]
|
||||||
);
|
);
|
||||||
|
const selectedApprovalStep =
|
||||||
|
selectedRowsData.length > 0
|
||||||
|
? selectedRowsData[0].latest_approval.step_number
|
||||||
|
: null;
|
||||||
|
|
||||||
const hasApprovable = selectedRowsData.some(
|
const eligibleSelectedRows = selectedRowsData.filter((row) => {
|
||||||
(row) =>
|
const approval = row.latest_approval;
|
||||||
row.latest_approval.step_number === 1 &&
|
|
||||||
row.latest_approval.action !== 'REJECTED'
|
if (approval.action === 'REJECTED') {
|
||||||
);
|
return false;
|
||||||
const hasRejectable = selectedRowsData.some(
|
}
|
||||||
(row) =>
|
|
||||||
row.latest_approval.step_number === 1 &&
|
if (selectedApprovalStep === null) {
|
||||||
row.latest_approval.action !== 'REJECTED'
|
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 disableApprove = !hasApprovable;
|
||||||
const disableReject = !hasRejectable;
|
const disableReject = !hasRejectable;
|
||||||
|
|
||||||
const idsToProcess =
|
const idsToProcess = eligibleSelectedRows.map((row) => row.id);
|
||||||
approveAction === 'APPROVED'
|
const nextApprovalStatus =
|
||||||
? selectedRowsData
|
selectedApprovalStep === 1
|
||||||
.filter((row) => row.latest_approval.step_number === 1)
|
? 'SALES_ORDER'
|
||||||
.map((row) => row.id)
|
: selectedApprovalStep === 2
|
||||||
: selectedRowsData
|
? 'DELIVERY_ORDER'
|
||||||
.filter((row) => row.latest_approval.step_number === 2)
|
: null;
|
||||||
.map((row) => row.id);
|
|
||||||
|
|
||||||
const approveMarketingHandler = async (notes: string) => {
|
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) {
|
if (idsToProcess.length === 0) {
|
||||||
toast.error(`Tidak ada data yang valid untuk di ${approveAction}.`);
|
toast.error(`Tidak ada data yang valid untuk di ${approveAction}.`);
|
||||||
confirmationModal.closeModal();
|
confirmationModal.closeModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const approveMarketingRes = await SalesOrderApi.bulkApprovals(
|
if (approveAction === 'APPROVED' && selectedApprovalStep !== 1) {
|
||||||
idsToProcess,
|
toast.error('Approve tahap ini harus menggunakan tanggal pengiriman.');
|
||||||
approveAction,
|
confirmationModal.closeModal();
|
||||||
notes
|
return;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (approveAction === 'APPROVED' && !nextApprovalStatus) {
|
||||||
|
toast.error('Status approval berikutnya tidak valid.');
|
||||||
|
confirmationModal.closeModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)) {
|
if (isResponseSuccess(approveMarketingRes)) {
|
||||||
confirmationModal.closeModal();
|
confirmationModal.closeModal();
|
||||||
toast.success(approveMarketingRes?.message as string);
|
toast.success(approveMarketingRes?.message as string);
|
||||||
setRowSelection({});
|
setRowSelection({});
|
||||||
}
|
}
|
||||||
if (isResponseError(approveMarketingRes)) {
|
|
||||||
confirmationModal.closeModal();
|
|
||||||
toast.error(approveMarketingRes?.message as string);
|
|
||||||
}
|
|
||||||
refreshMarketing();
|
refreshMarketing();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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) => {
|
const confirmationModalDeliveryClickHandler = async (notes: string) => {
|
||||||
const res = await SalesOrderApi.delivery(selectedItem?.id as number, notes);
|
const res = await SalesOrderApi.delivery(selectedItem?.id as number, notes);
|
||||||
deliveryModal.closeModal();
|
deliveryModal.closeModal();
|
||||||
@@ -316,10 +412,24 @@ const MarketingTable = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRowCanSelect = (row: Row<Marketing>): boolean => {
|
const getRowCanSelect = useCallback(
|
||||||
const approval = row.original.latest_approval;
|
(row: Row<Marketing>): boolean => {
|
||||||
return approval?.step_number === 1 && approval?.action !== 'REJECTED';
|
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 () => {
|
const exportToExcelHandler = async () => {
|
||||||
setIsLoadingExportingToExcel(true);
|
setIsLoadingExportingToExcel(true);
|
||||||
@@ -336,7 +446,22 @@ const MarketingTable = () => {
|
|||||||
size: 1,
|
size: 1,
|
||||||
header: ({ table }) => {
|
header: ({ table }) => {
|
||||||
const allRows = table.getRowModel().rows;
|
const allRows = table.getRowModel().rows;
|
||||||
const selectableRows = allRows.filter(getRowCanSelect);
|
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 =
|
const allSelected =
|
||||||
selectableRows.length > 0 &&
|
selectableRows.length > 0 &&
|
||||||
@@ -504,7 +629,13 @@ const MarketingTable = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, []);
|
}, [
|
||||||
|
deleteModal,
|
||||||
|
deliveryModal,
|
||||||
|
getRowCanSelect,
|
||||||
|
productsClickHandler,
|
||||||
|
selectedApprovalStep,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -677,7 +808,7 @@ const MarketingTable = () => {
|
|||||||
<ConfirmationModalWithNotes
|
<ConfirmationModalWithNotes
|
||||||
ref={confirmationModal.ref}
|
ref={confirmationModal.ref}
|
||||||
type={approveAction === 'APPROVED' ? 'success' : 'error'}
|
type={approveAction === 'APPROVED' ? 'success' : 'error'}
|
||||||
text={`Apakah anda yakin ingin ${approveAction == 'APPROVED' ? 'approve' : 'reject'} data penjualan (${idsToProcess.length} data)?`}
|
text={`Apakah anda yakin ingin ${approveAction == 'APPROVED' ? 'approve' : 'reject'} data penjualan tahap ${selectedApprovalStep ?? '-'} (${idsToProcess.length} data)?`}
|
||||||
secondaryButton={{
|
secondaryButton={{
|
||||||
text: 'Tidak',
|
text: 'Tidak',
|
||||||
onClick: confirmationModal.closeModal,
|
onClick: confirmationModal.closeModal,
|
||||||
@@ -716,6 +847,88 @@ const MarketingTable = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<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'
|
||||||
|
onClick={() => {
|
||||||
|
bulkDeliveryModal.closeModal();
|
||||||
|
setBulkDeliveryDate('');
|
||||||
|
setBulkDeliveryNotes('');
|
||||||
|
}}
|
||||||
|
className='px-3 py-2.5'
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color='success'
|
||||||
|
isLoading={isSubmittingBulkDelivery}
|
||||||
|
onClick={() =>
|
||||||
|
submitBulkDeliveryApprovalHandler(
|
||||||
|
idsToProcess,
|
||||||
|
bulkDeliveryDate,
|
||||||
|
bulkDeliveryNotes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className='px-3 py-2.5'
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
ref={productsModal.ref}
|
ref={productsModal.ref}
|
||||||
className={{
|
className={{
|
||||||
|
|||||||
@@ -104,6 +104,29 @@ class MarketingExportService extends BaseApiService<
|
|||||||
super(basePath);
|
super(basePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async bulkApprovals(
|
||||||
|
ids: number[],
|
||||||
|
status: 'SALES_ORDER' | 'DELIVERY_ORDER',
|
||||||
|
date: string, // YYYY-MM-DD
|
||||||
|
notes: string
|
||||||
|
): Promise<BaseApiResponse<Marketing[] | Marketing> | undefined> {
|
||||||
|
try {
|
||||||
|
const path = `${this.basePath}/approvals/bulk`;
|
||||||
|
|
||||||
|
return await httpClient<BaseApiResponse<Marketing[] | Marketing>>(path, {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
approvable_ids: ids,
|
||||||
|
status: status,
|
||||||
|
date: date,
|
||||||
|
notes: notes,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export to Excel
|
* Export to Excel
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user