mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 23:05:46 +00:00
Merge branch 'feat/progress-input-export' into 'development'
[FEAT/FE] Progress Input Exporet See merge request mbugroup/lti-web-client!418
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
import {
|
import {
|
||||||
ChangeEventHandler,
|
ChangeEventHandler,
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -35,6 +36,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
|
|||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
import ExpensesFilterModal from '@/components/pages/expense/filter/ExpensesFilterModal';
|
import ExpensesFilterModal from '@/components/pages/expense/filter/ExpensesFilterModal';
|
||||||
import ExpenseTableSkeleton from '@/components/pages/expense/skeleton/ExpenseTableSkeleton';
|
import ExpenseTableSkeleton from '@/components/pages/expense/skeleton/ExpenseTableSkeleton';
|
||||||
|
import Dropdown from '@/components/dropdown/Dropdown';
|
||||||
|
|
||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { ExpenseApi } from '@/services/api/expense';
|
import { ExpenseApi } from '@/services/api/expense';
|
||||||
@@ -73,6 +75,43 @@ type ApprovalStatusValue =
|
|||||||
const isApprovalDateRequired = (status?: ApprovalStatusValue) =>
|
const isApprovalDateRequired = (status?: ApprovalStatusValue) =>
|
||||||
status === 'REALISASI' || status === 'SELESAI';
|
status === 'REALISASI' || status === 'SELESAI';
|
||||||
|
|
||||||
|
const getExportErrorMessage = async (
|
||||||
|
error: unknown,
|
||||||
|
fallbackMessage: string
|
||||||
|
) => {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
const responseData = error.response?.data;
|
||||||
|
|
||||||
|
if (responseData instanceof Blob) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(await responseData.text()) as {
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
return parsed.message || fallbackMessage;
|
||||||
|
} catch {
|
||||||
|
return fallbackMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
responseData &&
|
||||||
|
typeof responseData === 'object' &&
|
||||||
|
'message' in responseData &&
|
||||||
|
typeof responseData.message === 'string'
|
||||||
|
) {
|
||||||
|
return responseData.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.message || fallbackMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackMessage;
|
||||||
|
};
|
||||||
|
|
||||||
const RowOptionsMenu = ({
|
const RowOptionsMenu = ({
|
||||||
popoverPosition = 'bottom',
|
popoverPosition = 'bottom',
|
||||||
props,
|
props,
|
||||||
@@ -236,6 +275,7 @@ const ExpensesTable = () => {
|
|||||||
const approveModal = useModal();
|
const approveModal = useModal();
|
||||||
const rejectModal = useModal();
|
const rejectModal = useModal();
|
||||||
const bulkApproveFormModal = useModal();
|
const bulkApproveFormModal = useModal();
|
||||||
|
const exportProgressInputModal = useModal();
|
||||||
|
|
||||||
// ===== FILTER MODAL STATE =====
|
// ===== FILTER MODAL STATE =====
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
@@ -246,11 +286,14 @@ const ExpensesTable = () => {
|
|||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
||||||
|
const [isExportProgressLoading, setIsExportProgressLoading] = useState(false);
|
||||||
const [, setApprovalNotes] = useState('');
|
const [, setApprovalNotes] = useState('');
|
||||||
const [bulkApprovalStatus, setBulkApprovalStatus] =
|
const [bulkApprovalStatus, setBulkApprovalStatus] =
|
||||||
useState<OptionType<ApprovalStatusValue> | null>(null);
|
useState<OptionType<ApprovalStatusValue> | null>(null);
|
||||||
const [bulkApprovalDate, setBulkApprovalDate] = useState('');
|
const [bulkApprovalDate, setBulkApprovalDate] = useState('');
|
||||||
const [bulkApprovalNotes, setBulkApprovalNotes] = useState('');
|
const [bulkApprovalNotes, setBulkApprovalNotes] = useState('');
|
||||||
|
const [exportProgressStartDate, setExportProgressStartDate] = useState('');
|
||||||
|
const [exportProgressEndDate, setExportProgressEndDate] = useState('');
|
||||||
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
||||||
@@ -492,6 +535,53 @@ const ExpensesTable = () => {
|
|||||||
setBulkApprovalNotes(e.target.value);
|
setBulkApprovalNotes(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetExportProgressForm = useCallback(() => {
|
||||||
|
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 ExpenseApi.exportInputProgressToExcel(
|
||||||
|
exportProgressStartDate,
|
||||||
|
exportProgressEndDate
|
||||||
|
);
|
||||||
|
|
||||||
|
exportProgressInputModal.closeModal();
|
||||||
|
resetExportProgressForm();
|
||||||
|
toast.success('Ekspor berhasil');
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(
|
||||||
|
await getExportErrorMessage(error, 'Gagal mengekspor input progress')
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsExportProgressLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const confirmationModalDeleteClickHandler = async () => {
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
@@ -841,6 +931,50 @@ const ExpensesTable = () => {
|
|||||||
onClick={handleFilterModalOpen}
|
onClick={handleFilterModalOpen}
|
||||||
className='px-3 py-2.5'
|
className='px-3 py-2.5'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
align='end'
|
||||||
|
direction='bottom'
|
||||||
|
className={{
|
||||||
|
content:
|
||||||
|
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||||
|
}}
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='none'
|
||||||
|
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||||
|
>
|
||||||
|
<div className='flex flex-row items-center gap-1.5'>
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:cloud-arrow-down'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span>Export</span>
|
||||||
|
|
||||||
|
<div className='w-px self-stretch bg-base-content/10' />
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:chevron-down'
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</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>
|
</div>
|
||||||
|
|
||||||
@@ -1037,6 +1171,76 @@ const ExpensesTable = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</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>
|
||||||
|
|
||||||
<ExpensesFilterModal
|
<ExpensesFilterModal
|
||||||
ref={filterModal.ref}
|
ref={filterModal.ref}
|
||||||
onSubmit={handleFilterSubmit}
|
onSubmit={handleFilterSubmit}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
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 DateInput from '@/components/input/DateInput';
|
||||||
@@ -36,6 +37,43 @@ import MarketingFilterModal from '@/components/pages/marketing/MarketingFilter';
|
|||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
import MarketingTableSkeleton from '@/components/pages/marketing/skeleton/MarketingTableSkeleton';
|
import MarketingTableSkeleton from '@/components/pages/marketing/skeleton/MarketingTableSkeleton';
|
||||||
|
|
||||||
|
const getExportErrorMessage = async (
|
||||||
|
error: unknown,
|
||||||
|
fallbackMessage: string
|
||||||
|
) => {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
const responseData = error.response?.data;
|
||||||
|
|
||||||
|
if (responseData instanceof Blob) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(await responseData.text()) as {
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
return parsed.message || fallbackMessage;
|
||||||
|
} catch {
|
||||||
|
return fallbackMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
responseData &&
|
||||||
|
typeof responseData === 'object' &&
|
||||||
|
'message' in responseData &&
|
||||||
|
typeof responseData.message === 'string'
|
||||||
|
) {
|
||||||
|
return responseData.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.message || fallbackMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackMessage;
|
||||||
|
};
|
||||||
|
|
||||||
const RowsOptionsMenu = ({
|
const RowsOptionsMenu = ({
|
||||||
props,
|
props,
|
||||||
deleteClickHandler,
|
deleteClickHandler,
|
||||||
@@ -161,6 +199,9 @@ const MarketingTable = () => {
|
|||||||
const [bulkDeliveryNotes, setBulkDeliveryNotes] = useState('');
|
const [bulkDeliveryNotes, setBulkDeliveryNotes] = useState('');
|
||||||
const [isSubmittingBulkDelivery, setIsSubmittingBulkDelivery] =
|
const [isSubmittingBulkDelivery, setIsSubmittingBulkDelivery] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const [isExportProgressLoading, setIsExportProgressLoading] = useState(false);
|
||||||
|
const [exportProgressStartDate, setExportProgressStartDate] = useState('');
|
||||||
|
const [exportProgressEndDate, setExportProgressEndDate] = useState('');
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
@@ -168,6 +209,7 @@ const MarketingTable = () => {
|
|||||||
const productsModal = useModal();
|
const productsModal = useModal();
|
||||||
const deliveryModal = useModal();
|
const deliveryModal = useModal();
|
||||||
const bulkDeliveryModal = useModal();
|
const bulkDeliveryModal = useModal();
|
||||||
|
const exportProgressInputModal = useModal();
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -439,6 +481,56 @@ const MarketingTable = () => {
|
|||||||
setIsLoadingExportingToExcel(false);
|
setIsLoadingExportingToExcel(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetExportProgressForm = useCallback(() => {
|
||||||
|
setExportProgressStartDate('');
|
||||||
|
setExportProgressEndDate('');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const exportProgressStartDateChangeHandler: ChangeEventHandler<HTMLInputElement> =
|
||||||
|
useCallback((e) => {
|
||||||
|
setExportProgressStartDate(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const exportProgressEndDateChangeHandler: ChangeEventHandler<HTMLInputElement> =
|
||||||
|
useCallback((e) => {
|
||||||
|
setExportProgressEndDate(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const exportProgressInputToExcelClickHandler = useCallback(() => {
|
||||||
|
resetExportProgressForm();
|
||||||
|
exportProgressInputModal.openModal();
|
||||||
|
}, [exportProgressInputModal, resetExportProgressForm]);
|
||||||
|
|
||||||
|
const submitExportProgressInputHandler = useCallback(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 getExportErrorMessage(error, 'Gagal mengekspor input progress')
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsExportProgressLoading(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
exportProgressEndDate,
|
||||||
|
exportProgressInputModal,
|
||||||
|
exportProgressStartDate,
|
||||||
|
resetExportProgressForm,
|
||||||
|
]);
|
||||||
|
|
||||||
const columns = useMemo<ColumnDef<Marketing>[]>(() => {
|
const columns = useMemo<ColumnDef<Marketing>[]>(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -745,6 +837,16 @@ const MarketingTable = () => {
|
|||||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||||
Export to Excel
|
Export to Excel
|
||||||
</Button>
|
</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>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -928,6 +1030,75 @@ const MarketingTable = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</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
|
<Modal
|
||||||
ref={productsModal.ref}
|
ref={productsModal.ref}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useState,
|
useState,
|
||||||
@@ -18,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
|||||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
import { OptionType } from '@/components/input/SelectInput';
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
import SelectInput, { useSelect } from '@/components/input/SelectInput';
|
import SelectInput, { useSelect } from '@/components/input/SelectInput';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||||
import PopoverButton from '@/components/popover/PopoverButton';
|
import PopoverButton from '@/components/popover/PopoverButton';
|
||||||
import PopoverContent from '@/components/popover/PopoverContent';
|
import PopoverContent from '@/components/popover/PopoverContent';
|
||||||
@@ -50,6 +52,43 @@ import { Color } from '@/types/theme';
|
|||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
import Dropdown from '@/components/Dropdown';
|
import Dropdown from '@/components/Dropdown';
|
||||||
|
|
||||||
|
const getExportErrorMessage = async (
|
||||||
|
error: unknown,
|
||||||
|
fallbackMessage: string
|
||||||
|
) => {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
const responseData = error.response?.data;
|
||||||
|
|
||||||
|
if (responseData instanceof Blob) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(await responseData.text()) as {
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
return parsed.message || fallbackMessage;
|
||||||
|
} catch {
|
||||||
|
return fallbackMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
responseData &&
|
||||||
|
typeof responseData === 'object' &&
|
||||||
|
'message' in responseData &&
|
||||||
|
typeof responseData.message === 'string'
|
||||||
|
) {
|
||||||
|
return responseData.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.message || fallbackMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackMessage;
|
||||||
|
};
|
||||||
|
|
||||||
// ===== STATUS BADGE UTILITIES =====
|
// ===== STATUS BADGE UTILITIES =====
|
||||||
const statusTextMap: Record<string, string> = {
|
const statusTextMap: Record<string, string> = {
|
||||||
APPROVED: 'Disetujui',
|
APPROVED: 'Disetujui',
|
||||||
@@ -355,10 +394,14 @@ const RecordingTable = () => {
|
|||||||
|
|
||||||
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const [isExportProgressLoading, setIsExportProgressLoading] = useState(false);
|
||||||
|
const [exportProgressStartDate, setExportProgressStartDate] = useState('');
|
||||||
|
const [exportProgressEndDate, setExportProgressEndDate] = useState('');
|
||||||
|
|
||||||
const singleDeleteModal = useModal();
|
const singleDeleteModal = useModal();
|
||||||
const approveModal = useModal();
|
const approveModal = useModal();
|
||||||
const rejectModal = useModal();
|
const rejectModal = useModal();
|
||||||
|
const exportProgressInputModal = useModal();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: recordings,
|
data: recordings,
|
||||||
@@ -698,6 +741,60 @@ const RecordingTable = () => {
|
|||||||
setIsLoadingExportingToExcel(false);
|
setIsLoadingExportingToExcel(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetExportProgressForm = useCallback(() => {
|
||||||
|
setExportProgressStartDate('');
|
||||||
|
setExportProgressEndDate('');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const exportProgressStartDateChangeHandler = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setExportProgressStartDate(e.target.value);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const exportProgressEndDateChangeHandler = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setExportProgressEndDate(e.target.value);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const exportProgressInputToExcelClickHandler = useCallback(() => {
|
||||||
|
resetExportProgressForm();
|
||||||
|
exportProgressInputModal.openModal();
|
||||||
|
}, [exportProgressInputModal, resetExportProgressForm]);
|
||||||
|
|
||||||
|
const submitExportProgressInputHandler = useCallback(async () => {
|
||||||
|
if (!exportProgressStartDate || !exportProgressEndDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsExportProgressLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await RecordingApi.exportInputProgressToExcel(
|
||||||
|
exportProgressStartDate,
|
||||||
|
exportProgressEndDate
|
||||||
|
);
|
||||||
|
|
||||||
|
exportProgressInputModal.closeModal();
|
||||||
|
resetExportProgressForm();
|
||||||
|
toast.success('Ekspor berhasil');
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(
|
||||||
|
await getExportErrorMessage(error, 'Gagal mengekspor input progress')
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsExportProgressLoading(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
exportProgressEndDate,
|
||||||
|
exportProgressInputModal,
|
||||||
|
exportProgressStartDate,
|
||||||
|
resetExportProgressForm,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isResponseSuccess(recordings) && recordings.data) {
|
if (isResponseSuccess(recordings) && recordings.data) {
|
||||||
const newSelection: Record<string, boolean> = {};
|
const newSelection: Record<string, boolean> = {};
|
||||||
@@ -1368,6 +1465,16 @@ const RecordingTable = () => {
|
|||||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||||
Export to Excel
|
Export to Excel
|
||||||
</Button>
|
</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>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1551,6 +1658,76 @@ const RecordingTable = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
<ConfirmationModalWithNotes
|
<ConfirmationModalWithNotes
|
||||||
ref={approveModal.ref}
|
ref={approveModal.ref}
|
||||||
type='success'
|
type='success'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
import {
|
import {
|
||||||
ChangeEventHandler,
|
ChangeEventHandler,
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -18,8 +19,9 @@ import Link from 'next/link';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import { useModal } from '@/components/Modal';
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import PopoverButton from '@/components/popover/PopoverButton';
|
import PopoverButton from '@/components/popover/PopoverButton';
|
||||||
import PopoverContent from '@/components/popover/PopoverContent';
|
import PopoverContent from '@/components/popover/PopoverContent';
|
||||||
@@ -28,6 +30,7 @@ import StatusBadge from '@/components/helper/StatusBadge';
|
|||||||
import PurchaseTableSkeleton from '@/components/pages/purchase/skeleton/PurchaseTableSkeleton';
|
import PurchaseTableSkeleton from '@/components/pages/purchase/skeleton/PurchaseTableSkeleton';
|
||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
import PurchaseFilterModal from '@/components/pages/purchase/PurchaseFilterModal';
|
import PurchaseFilterModal from '@/components/pages/purchase/PurchaseFilterModal';
|
||||||
|
import Dropdown from '@/components/dropdown/Dropdown';
|
||||||
|
|
||||||
import { cn, formatDate } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
@@ -40,6 +43,43 @@ import { ExpenseApi } from '@/services/api/expense';
|
|||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { Color } from '@/types/theme';
|
import { Color } from '@/types/theme';
|
||||||
|
|
||||||
|
const getExportErrorMessage = async (
|
||||||
|
error: unknown,
|
||||||
|
fallbackMessage: string
|
||||||
|
) => {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
const responseData = error.response?.data;
|
||||||
|
|
||||||
|
if (responseData instanceof Blob) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(await responseData.text()) as {
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
return parsed.message || fallbackMessage;
|
||||||
|
} catch {
|
||||||
|
return fallbackMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
responseData &&
|
||||||
|
typeof responseData === 'object' &&
|
||||||
|
'message' in responseData &&
|
||||||
|
typeof responseData.message === 'string'
|
||||||
|
) {
|
||||||
|
return responseData.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.message || fallbackMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackMessage;
|
||||||
|
};
|
||||||
|
|
||||||
// ===== STATUS BADGE UTILITIES =====
|
// ===== STATUS BADGE UTILITIES =====
|
||||||
const statusTextMap: Record<string, string> = {
|
const statusTextMap: Record<string, string> = {
|
||||||
APPROVED: 'Disetujui',
|
APPROVED: 'Disetujui',
|
||||||
@@ -152,9 +192,12 @@ const PurchaseTable = () => {
|
|||||||
|
|
||||||
// ===== STATE MANAGEMENT =====
|
// ===== STATE MANAGEMENT =====
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
const [isExportProgressLoading, setIsExportProgressLoading] = useState(false);
|
||||||
const [selectedPurchase, setSelectedPurchase] = useState<Purchase | null>(
|
const [selectedPurchase, setSelectedPurchase] = useState<Purchase | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
const [exportProgressStartDate, setExportProgressStartDate] = useState('');
|
||||||
|
const [exportProgressEndDate, setExportProgressEndDate] = useState('');
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
|
||||||
// ===== TABLE FILTER STATE =====
|
// ===== TABLE FILTER STATE =====
|
||||||
@@ -183,6 +226,7 @@ const PurchaseTable = () => {
|
|||||||
// ===== MODAL HOOKS =====
|
// ===== MODAL HOOKS =====
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
|
const exportProgressInputModal = useModal();
|
||||||
|
|
||||||
// ===== API DATA FETCHING =====
|
// ===== API DATA FETCHING =====
|
||||||
const {
|
const {
|
||||||
@@ -431,6 +475,56 @@ const PurchaseTable = () => {
|
|||||||
updateFilter('approval_status', '');
|
updateFilter('approval_status', '');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetExportProgressForm = useCallback(() => {
|
||||||
|
setExportProgressStartDate('');
|
||||||
|
setExportProgressEndDate('');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const exportProgressStartDateChangeHandler: ChangeEventHandler<HTMLInputElement> =
|
||||||
|
useCallback((e) => {
|
||||||
|
setExportProgressStartDate(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const exportProgressEndDateChangeHandler: ChangeEventHandler<HTMLInputElement> =
|
||||||
|
useCallback((e) => {
|
||||||
|
setExportProgressEndDate(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const exportProgressInputToExcelClickHandler = useCallback(() => {
|
||||||
|
resetExportProgressForm();
|
||||||
|
exportProgressInputModal.openModal();
|
||||||
|
}, [exportProgressInputModal, resetExportProgressForm]);
|
||||||
|
|
||||||
|
const submitExportProgressInputHandler = useCallback(async () => {
|
||||||
|
if (!exportProgressStartDate || !exportProgressEndDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsExportProgressLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await PurchaseApi.exportInputProgressToExcel(
|
||||||
|
exportProgressStartDate,
|
||||||
|
exportProgressEndDate
|
||||||
|
);
|
||||||
|
|
||||||
|
exportProgressInputModal.closeModal();
|
||||||
|
resetExportProgressForm();
|
||||||
|
toast.success('Ekspor berhasil');
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(
|
||||||
|
await getExportErrorMessage(error, 'Gagal mengekspor input progress')
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsExportProgressLoading(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
exportProgressEndDate,
|
||||||
|
exportProgressInputModal,
|
||||||
|
exportProgressStartDate,
|
||||||
|
resetExportProgressForm,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
@@ -482,6 +576,50 @@ const PurchaseTable = () => {
|
|||||||
onClick={filterModal.openModal}
|
onClick={filterModal.openModal}
|
||||||
className='px-3 py-2.5'
|
className='px-3 py-2.5'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
align='end'
|
||||||
|
direction='bottom'
|
||||||
|
className={{
|
||||||
|
content:
|
||||||
|
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||||
|
}}
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='none'
|
||||||
|
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||||
|
>
|
||||||
|
<div className='flex flex-row items-center gap-1.5'>
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:cloud-arrow-down'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span>Export</span>
|
||||||
|
|
||||||
|
<div className='w-px self-stretch bg-base-content/10' />
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:chevron-down'
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</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>
|
</div>
|
||||||
|
|
||||||
@@ -562,6 +700,76 @@ const PurchaseTable = () => {
|
|||||||
onClick: confirmationModalDeleteClickHandler,
|
onClick: confirmationModalDeleteClickHandler,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
UpdateExpensePayload,
|
UpdateExpensePayload,
|
||||||
} from '@/types/api/expense';
|
} from '@/types/api/expense';
|
||||||
import { httpClient } from '@/services/http/client';
|
import { httpClient } from '@/services/http/client';
|
||||||
|
import { formatDate } from '@/lib/helper';
|
||||||
|
|
||||||
export class ExpenseApiService extends BaseApiService<
|
export class ExpenseApiService extends BaseApiService<
|
||||||
Expense,
|
Expense,
|
||||||
@@ -706,6 +707,33 @@ export class ExpenseApiService extends BaseApiService<
|
|||||||
|
|
||||||
return formData;
|
return formData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async exportInputProgressToExcel(startDate: string, endDate: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
params.set('export', 'excel');
|
||||||
|
params.set('type', 'progress');
|
||||||
|
params.set('start_date', formatDate(startDate, 'YYYY-MM-DD'));
|
||||||
|
params.set('end_date', formatDate(endDate, 'YYYY-MM-DD'));
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
|
||||||
|
method: 'GET',
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
|
||||||
|
const fileName = `input-progres-BOP-${formatDate(startDate, 'DD-MM-YYYY')}-ke-${formatDate(endDate, 'DD-MM-YYYY')}.xlsx`;
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExpenseApi = new ExpenseApiService('/expenses');
|
export const ExpenseApi = new ExpenseApiService('/expenses');
|
||||||
|
|||||||
@@ -211,6 +211,33 @@ class MarketingExportService extends BaseApiService<
|
|||||||
toast.error('Gagal melakukan export marketing! Coba lagi.');
|
toast.error('Gagal melakukan export marketing! Coba lagi.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exportInputProgressToExcel(startDate: string, endDate: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
params.set('export', 'excel');
|
||||||
|
params.set('type', 'progress');
|
||||||
|
params.set('start_date', formatDate(startDate, 'YYYY-MM-DD'));
|
||||||
|
params.set('end_date', formatDate(endDate, 'YYYY-MM-DD'));
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
|
||||||
|
method: 'GET',
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
|
||||||
|
const fileName = `input-progres-penjualan-${formatDate(startDate, 'DD-MM-YYYY')}-ke-${formatDate(endDate, 'DD-MM-YYYY')}.xlsx`;
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SalesOrderApi = new SalesOrderService('/marketing/sales-orders');
|
export const SalesOrderApi = new SalesOrderService('/marketing/sales-orders');
|
||||||
|
|||||||
@@ -116,6 +116,33 @@ export class RecordingService extends BaseApiService<
|
|||||||
link.click();
|
link.click();
|
||||||
link.remove();
|
link.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exportInputProgressToExcel(startDate: string, endDate: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
params.set('export', 'excel');
|
||||||
|
params.set('type', 'progress');
|
||||||
|
params.set('start_date', formatDate(startDate, 'YYYY-MM-DD'));
|
||||||
|
params.set('end_date', formatDate(endDate, 'YYYY-MM-DD'));
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
|
||||||
|
method: 'GET',
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
|
||||||
|
const fileName = `input-progres-recording-${formatDate(startDate, 'DD-MM-YYYY')}-ke-${formatDate(endDate, 'DD-MM-YYYY')}.xlsx`;
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecordingApi = new RecordingService('/production/recordings');
|
export const RecordingApi = new RecordingService('/production/recordings');
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
} from '@/types/api/purchase/purchase';
|
} from '@/types/api/purchase/purchase';
|
||||||
import { BaseApiService } from '@/services/api/base';
|
import { BaseApiService } from '@/services/api/base';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
import { formatDate } from '@/lib/helper';
|
||||||
|
import { httpClient } from '../http/client';
|
||||||
|
|
||||||
const basePurchaseApi = new BaseApiService<
|
const basePurchaseApi = new BaseApiService<
|
||||||
Purchase,
|
Purchase,
|
||||||
@@ -112,4 +114,34 @@ export const PurchaseApi = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async exportInputProgressToExcel(startDate: string, endDate: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
params.set('export', 'excel');
|
||||||
|
params.set('type', 'progress');
|
||||||
|
params.set('start_date', formatDate(startDate, 'YYYY-MM-DD'));
|
||||||
|
params.set('end_date', formatDate(endDate, 'YYYY-MM-DD'));
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(
|
||||||
|
`${basePurchaseApi.basePath}${queryString}`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
responseType: 'blob',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
|
||||||
|
const fileName = `input-progres-pembelian-${formatDate(startDate, 'DD-MM-YYYY')}-ke-${formatDate(endDate, 'DD-MM-YYYY')}.xlsx`;
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user