mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'fix/debt-supplier' into 'development'
[FIX/FE] Fixing Debt Supplier Report Ledger See merge request mbugroup/lti-web-client!172
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import Button, { ButtonProps } from '@/components/Button';
|
||||
import { getFilledFormikValuesCount } from '@/lib/formik-helper';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { FormikValues } from 'formik';
|
||||
|
||||
export type ButtonFilterProps = ButtonProps & {
|
||||
values: FormikValues;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
const ButtonFilter = ({ values, onClick, ...props }: ButtonFilterProps) => {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
onClick={onClick}
|
||||
className={
|
||||
getFilledFormikValuesCount(values) > 0
|
||||
? 'bg-gradient-to-t from-blue-50 to-blue-100 border-blue-500 text-blue-600 hover:from-blue-100 hover:to-blue-200'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:funnel'
|
||||
width={20}
|
||||
height={20}
|
||||
className={
|
||||
getFilledFormikValuesCount(values) > 0 ? 'text-blue-600' : ''
|
||||
}
|
||||
/>
|
||||
Filter
|
||||
{getFilledFormikValuesCount(values) > 0 && (
|
||||
<span className='w-6 h-6 text-white bg-red-500 rounded-lg flex items-center justify-center text-xs'>
|
||||
{getFilledFormikValuesCount(values)}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ButtonFilter;
|
||||
@@ -8,16 +8,16 @@ const FinanceTabs = () => {
|
||||
const tabs = [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Kontrol Pembayaran Customer',
|
||||
|
||||
content: <CustomerPaymentTab />,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Rekapitulasi Hutang Ke Supplier',
|
||||
|
||||
content: <DebtSupplierTab />,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Kontrol Pembayaran Customer',
|
||||
|
||||
content: <CustomerPaymentTab />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -176,7 +176,7 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
|
||||
<Text>No. PO</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>Tgl Terima</Text>
|
||||
<Text>Tgl Terima/Bayar</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>Tgl PO</Text>
|
||||
@@ -191,19 +191,19 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
|
||||
<Text>Gudang</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>Tgl Jatuh Tempo</Text>
|
||||
<Text>Jatuh Tempo</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>Status Jatuh Tempo</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.5 }]}>
|
||||
<Text>Total Harga</Text>
|
||||
<Text>Nominal Pembelian (Rp)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.5 }]}>
|
||||
<Text>Pembayaran</Text>
|
||||
<Text>Pembayaran (Rp)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.5 }]}>
|
||||
<Text>Hutang</Text>
|
||||
<Text>Sisa Saldo Hutang (Rp)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>Status</Text>
|
||||
@@ -213,6 +213,65 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Initial Balance Row */}
|
||||
<View style={[pdfStyles.tableRow, pdfStyles.tableBorderBottom]}>
|
||||
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.6 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.5 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.5 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{
|
||||
flex: 1.5,
|
||||
color: supplierReport.initial_balance < 0 ? 'red' : 'black',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{formatCurrency(supplierReport.initial_balance || 0)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Table Body */}
|
||||
{supplierReport.rows.map((item, index) => (
|
||||
<View
|
||||
@@ -297,10 +356,10 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{ flex: 1.5, color: item.debt_price < 0 ? 'red' : 'black' },
|
||||
{ flex: 1.5, color: item.balance < 0 ? 'red' : 'black' },
|
||||
]}
|
||||
>
|
||||
<Text>{formatCurrency(item.debt_price)}</Text>
|
||||
<Text>{formatCurrency(item.balance)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text>{item.status || '-'}</Text>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as XLSX from 'xlsx';
|
||||
import { formatDate } from '@/lib/helper';
|
||||
import { DebtSupplier } from '@/types/api/report/debt-supplier';
|
||||
import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier';
|
||||
|
||||
interface DebtSupplierExportExcelParams {
|
||||
data: DebtSupplier[];
|
||||
@@ -21,12 +21,29 @@ export const generateDebtSupplierExcel = (
|
||||
const supplierData = supplierReport.rows;
|
||||
const supplierName = supplierReport.supplier.name || 'Unknown Supplier';
|
||||
|
||||
const excelData: { [key: string]: string | number }[] = supplierData.map(
|
||||
(item, index) => ({
|
||||
const excelData: { [key: string]: string | number }[] = [
|
||||
{
|
||||
No: '',
|
||||
'Nomor PR': '',
|
||||
'Nomor PO': '',
|
||||
'Tanggal Terima/Bayar': '',
|
||||
'Tanggal PO': '',
|
||||
'Aging (Hari)': '',
|
||||
Area: '',
|
||||
Gudang: '',
|
||||
'Jatuh Tempo': '',
|
||||
'Status Jatuh Tempo': '',
|
||||
'Nominal Pembelian (Rp)': '',
|
||||
'Pembayaran (Rp)': '',
|
||||
'Sisa Saldo Hutang (Rp)': supplierReport.initial_balance || 0,
|
||||
Status: '',
|
||||
'Nomor Perjalanan': '',
|
||||
},
|
||||
...supplierData.map((item, index) => ({
|
||||
No: index + 1,
|
||||
'Nomor PR': item.pr_number || '',
|
||||
'Nomor PO': item.po_number || '',
|
||||
'Tanggal Terima': item.received_date
|
||||
'Tanggal Terima/Bayar': item.received_date
|
||||
? item.received_date != '-'
|
||||
? formatDate(item.received_date, 'MM/DD/YYYY')
|
||||
: '-'
|
||||
@@ -39,35 +56,35 @@ export const generateDebtSupplierExcel = (
|
||||
'Aging (Hari)': item.aging || 0,
|
||||
Area: item.area?.name || '',
|
||||
Gudang: item.warehouse?.name || '',
|
||||
'Tanggal Jatuh Tempo': item.due_date
|
||||
'Jatuh Tempo': item.due_date
|
||||
? item.due_date != '-'
|
||||
? formatDate(item.due_date, 'MM/DD/YYYY')
|
||||
: '-'
|
||||
: '-',
|
||||
'Status Jatuh Tempo': item.due_status || '',
|
||||
'Total Harga': item.total_price || 0,
|
||||
'Harga Pembayaran': item.payment_price || 0,
|
||||
'Harga Hutang': item.debt_price || 0,
|
||||
'Nominal Pembelian (Rp)': item.total_price || 0,
|
||||
'Pembayaran (Rp)': item.payment_price || 0,
|
||||
'Sisa Saldo Hutang (Rp)': item.debt_price || 0,
|
||||
Status: item.status || '',
|
||||
'Nomor Perjalanan': item.travel_number || '',
|
||||
})
|
||||
);
|
||||
})),
|
||||
];
|
||||
|
||||
if (supplierReport.total) {
|
||||
excelData.push({
|
||||
No: 'Total',
|
||||
'Nomor PR': '',
|
||||
'Nomor PO': '',
|
||||
'Tanggal Terima': '',
|
||||
'Tanggal Terima/Bayar': '',
|
||||
'Tanggal PO': '',
|
||||
'Aging (Hari)': supplierReport.total.aging || 0,
|
||||
Area: '',
|
||||
Gudang: '',
|
||||
'Tanggal Jatuh Tempo': '',
|
||||
'Jatuh Tempo': '',
|
||||
'Status Jatuh Tempo': '',
|
||||
'Total Harga': supplierReport.total.total_price || 0,
|
||||
'Harga Pembayaran': supplierReport.total.payment_price || 0,
|
||||
'Harga Hutang': supplierReport.total.debt_price || 0,
|
||||
'Nominal Pembelian (Rp)': supplierReport.total.total_price || 0,
|
||||
'Pembayaran (Rp)': supplierReport.total.payment_price || 0,
|
||||
'Sisa Saldo Hutang (Rp)': supplierReport.total.debt_price || 0,
|
||||
Status: '',
|
||||
'Nomor Perjalanan': '',
|
||||
});
|
||||
@@ -79,16 +96,16 @@ export const generateDebtSupplierExcel = (
|
||||
{ wch: 5 }, // No
|
||||
{ wch: 15 }, // Nomor PR
|
||||
{ wch: 15 }, // Nomor PO
|
||||
{ wch: 15 }, // Tanggal PR
|
||||
{ wch: 15 }, // Tanggal Terima/Bayar
|
||||
{ wch: 15 }, // Tanggal PO
|
||||
{ wch: 12 }, // Aging
|
||||
{ wch: 15 }, // Area
|
||||
{ wch: 15 }, // Gudang
|
||||
{ wch: 18 }, // Tanggal Jatuh Tempo
|
||||
{ wch: 18 }, // Jatuh Tempo
|
||||
{ wch: 18 }, // Status Jatuh Tempo
|
||||
{ wch: 15 }, // Total Harga
|
||||
{ wch: 15 }, // Harga Pembayaran
|
||||
{ wch: 15 }, // Harga Hutang
|
||||
{ wch: 15 }, // Nominal Pembelian (Rp)
|
||||
{ wch: 15 }, // Pembayaran (Rp)
|
||||
{ wch: 15 }, // Sisa Saldo Hutang (Rp)
|
||||
{ wch: 12 }, // Status
|
||||
{ wch: 15 }, // Nomor Perjalanan
|
||||
];
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { OptionType } from '@/components/input/SelectInput';
|
||||
import * as yup from 'yup';
|
||||
|
||||
export type DebtSupplierFilterType = {
|
||||
startDate: string | null | undefined;
|
||||
endDate: string | null | undefined;
|
||||
supplierIds: OptionType[] | null | undefined;
|
||||
filterBy: OptionType | null | undefined;
|
||||
};
|
||||
|
||||
export const DebtSupplierFilterSchema: yup.ObjectSchema<DebtSupplierFilterType> =
|
||||
yup.object({
|
||||
startDate: yup.string().optional().notRequired(),
|
||||
endDate: yup.string().optional().notRequired(),
|
||||
supplierIds: yup
|
||||
.array()
|
||||
.of(
|
||||
yup.object({
|
||||
value: yup.mixed<string | number>().required(),
|
||||
label: yup.string().required(),
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.notRequired(),
|
||||
filterBy: yup
|
||||
.object({
|
||||
value: yup.mixed<string | number>().required(),
|
||||
label: yup.string().required(),
|
||||
})
|
||||
.optional()
|
||||
.notRequired(),
|
||||
});
|
||||
|
||||
export type DebtSupplierFilterValues = yup.InferType<
|
||||
typeof DebtSupplierFilterSchema
|
||||
>;
|
||||
@@ -13,7 +13,11 @@ import Table from '@/components/Table';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||
import { SupplierApi } from '@/services/api/master-data';
|
||||
import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier';
|
||||
import {
|
||||
DebtRow,
|
||||
DebtSupplier,
|
||||
DebtSupplierFilter,
|
||||
} from '@/types/api/report/debt-supplier';
|
||||
import { generateDebtSupplierExcel } from '@/components/pages/report/finance/export/DebtSupplierExportXLSX';
|
||||
import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF';
|
||||
import { Icon } from '@iconify/react';
|
||||
@@ -21,8 +25,14 @@ import { ColumnDef } from '@tanstack/react-table';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import useSWR from 'swr';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import { DebtSupplierApi } from '@/services/api/report/debt-supplier';
|
||||
import { useFormik } from 'formik';
|
||||
import {
|
||||
DebtSupplierFilterSchema,
|
||||
DebtSupplierFilterType,
|
||||
} from '@/components/pages/report/finance/filter/DebtSupplierFilter';
|
||||
import { getFilledFormikValuesCount } from '@/lib/formik-helper';
|
||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||
|
||||
const DebtSupplierTab = () => {
|
||||
// ===== STATE MANAGEMENT =====
|
||||
@@ -30,20 +40,15 @@ const DebtSupplierTab = () => {
|
||||
const [isExcelExportLoading, setIsExcelExportLoading] = useState(false);
|
||||
const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading;
|
||||
|
||||
// ===== PAGINATION STATE =====
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
// ===== SUBMISSION STATE =====
|
||||
const [filterParams, setFilterParams] = useState<DebtSupplierFilter>({
|
||||
start_date: undefined,
|
||||
end_date: undefined,
|
||||
supplier_ids: undefined,
|
||||
filter_by: undefined,
|
||||
});
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
|
||||
// ===== FILTER STATE =====
|
||||
const [filterSupplier, setFilterSupplier] = useState<OptionType[]>([]);
|
||||
const [filterStartDate, setFilterStartDate] = useState('');
|
||||
const [filterEndDate, setFilterEndDate] = useState('');
|
||||
const [filterDateType, setFilterDateType] = useState<OptionType>();
|
||||
const [filterErrors, setFilterErrors] = useState<Record<string, string>>({});
|
||||
|
||||
const filterModal = useModal();
|
||||
|
||||
const { options: supplierOptions, isLoadingOptions: isLoadingSuppliers } =
|
||||
@@ -59,48 +64,51 @@ const DebtSupplierTab = () => {
|
||||
[]
|
||||
);
|
||||
|
||||
// ===== FILTER HANDLERS =====
|
||||
const handleResetFilters = useCallback(() => {
|
||||
setIsSubmitted(false);
|
||||
setFilterSupplier([]);
|
||||
setFilterStartDate('');
|
||||
setFilterEndDate('');
|
||||
setFilterErrors({});
|
||||
}, []);
|
||||
const handleFilterModalOpen = () => {
|
||||
filterModal.openModal();
|
||||
};
|
||||
|
||||
const handleApplyFilters = useCallback(() => {
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
if (!filterStartDate) {
|
||||
errors.start_date = 'Tanggal mulai wajib diisi';
|
||||
}
|
||||
if (!filterEndDate) {
|
||||
errors.end_date = 'Tanggal akhir wajib diisi';
|
||||
}
|
||||
|
||||
setFilterErrors(errors);
|
||||
|
||||
if (Object.keys(errors).length === 0) {
|
||||
setIsSubmitted(true);
|
||||
setCurrentPage(1);
|
||||
// ===== FORMIK SETUP =====
|
||||
const formik = useFormik<DebtSupplierFilterType>({
|
||||
initialValues: {
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
supplierIds: null,
|
||||
filterBy: null,
|
||||
},
|
||||
validationSchema: DebtSupplierFilterSchema,
|
||||
onSubmit: (values) => {
|
||||
setFilterParams({
|
||||
start_date: values.startDate?.toString() || undefined,
|
||||
end_date: values.endDate?.toString() || undefined,
|
||||
supplier_ids:
|
||||
values.supplierIds?.map((v) => String(v.value)).join(',') ||
|
||||
undefined,
|
||||
filter_by: values.filterBy?.value?.toString() || undefined,
|
||||
});
|
||||
filterModal.closeModal();
|
||||
}
|
||||
}, [filterModal, filterStartDate, filterEndDate]);
|
||||
setIsSubmitted(true);
|
||||
},
|
||||
onReset: (values) => {
|
||||
setFilterParams({
|
||||
start_date: undefined,
|
||||
end_date: undefined,
|
||||
supplier_ids: undefined,
|
||||
filter_by: undefined,
|
||||
});
|
||||
setIsSubmitted(false);
|
||||
},
|
||||
});
|
||||
|
||||
// ===== DATA FETCHING =====
|
||||
const { data: debtSupplier, isLoading } = useSWR(
|
||||
isSubmitted
|
||||
? () => {
|
||||
const params = {
|
||||
supplier_ids:
|
||||
filterSupplier.length > 0
|
||||
? filterSupplier.map((v) => String(v.value)).join(',')
|
||||
: undefined,
|
||||
filter_by: filterDateType?.value,
|
||||
start_date: filterStartDate || undefined,
|
||||
end_date: filterEndDate || undefined,
|
||||
page: currentPage,
|
||||
limit: pageSize,
|
||||
supplier_ids: filterParams.supplier_ids,
|
||||
filter_by: filterParams.filter_by,
|
||||
start_date: filterParams.start_date,
|
||||
end_date: filterParams.end_date,
|
||||
};
|
||||
|
||||
return ['debt-supplier-report', params];
|
||||
@@ -109,11 +117,9 @@ const DebtSupplierTab = () => {
|
||||
([, params]) =>
|
||||
DebtSupplierApi.getDebtSupplierReport(
|
||||
params.supplier_ids,
|
||||
params.filter_by?.toString(),
|
||||
params.filter_by,
|
||||
params.start_date,
|
||||
params.end_date,
|
||||
params.page,
|
||||
params.limit
|
||||
params.end_date
|
||||
)
|
||||
);
|
||||
|
||||
@@ -135,13 +141,15 @@ const DebtSupplierTab = () => {
|
||||
> => {
|
||||
const params = {
|
||||
supplier_ids:
|
||||
filterSupplier.length > 0
|
||||
? filterSupplier.map((v) => String(v.value)).join(',')
|
||||
formik.values.supplierIds && formik.values.supplierIds.length > 0
|
||||
? formik.values.supplierIds.map((v) => String(v.value)).join(',')
|
||||
: undefined,
|
||||
filter_by: filterDateType?.value?.toString(),
|
||||
start_date: filterStartDate || undefined,
|
||||
end_date: filterEndDate || undefined,
|
||||
date_type: filterDateType ? filterDateType.value : undefined,
|
||||
filter_by: formik.values.filterBy?.value?.toString() || undefined,
|
||||
start_date: formik.values.startDate || undefined,
|
||||
end_date: formik.values.endDate || undefined,
|
||||
date_type: formik.values.filterBy
|
||||
? formik.values.filterBy.value
|
||||
: undefined,
|
||||
limit: 100,
|
||||
page: 1,
|
||||
};
|
||||
@@ -150,15 +158,18 @@ const DebtSupplierTab = () => {
|
||||
params.supplier_ids,
|
||||
params.filter_by,
|
||||
params.start_date,
|
||||
params.end_date,
|
||||
params.page,
|
||||
params.limit
|
||||
params.end_date
|
||||
);
|
||||
|
||||
return isResponseSuccess(response)
|
||||
? (response.data as unknown as DebtSupplier[])
|
||||
: null;
|
||||
}, [filterSupplier, filterStartDate, filterEndDate]);
|
||||
}, [
|
||||
formik.values.supplierIds,
|
||||
formik.values.startDate,
|
||||
formik.values.endDate,
|
||||
formik.values.filterBy,
|
||||
]);
|
||||
|
||||
// ===== EXPORT HANDLERS =====
|
||||
const handleExportExcel = useCallback(async () => {
|
||||
@@ -207,37 +218,18 @@ const DebtSupplierTab = () => {
|
||||
}
|
||||
}, [debtSupplierExport]);
|
||||
|
||||
// ===== PAGINATION HANDLERS =====
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleRowChange = (pageSize: number) => {
|
||||
setPageSize(pageSize);
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
if (meta && currentPage < meta.total_pages) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrevPage = () => {
|
||||
if (currentPage > 1) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const getTableColumns = (supplier: DebtSupplier): ColumnDef<DebtRow>[] => [
|
||||
{
|
||||
id: 'no',
|
||||
header: 'No',
|
||||
cell: (props) => props.row.index + 1,
|
||||
enableSorting: false,
|
||||
cell: (props) => props.row.index,
|
||||
},
|
||||
{
|
||||
id: 'pr_number',
|
||||
header: 'Nomor PR',
|
||||
accessorKey: 'pr_number',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.pr_number;
|
||||
return value || '-';
|
||||
@@ -247,6 +239,7 @@ const DebtSupplierTab = () => {
|
||||
id: 'po_number',
|
||||
header: 'Nomor PO',
|
||||
accessorKey: 'po_number',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.po_number;
|
||||
return value || '-';
|
||||
@@ -254,8 +247,9 @@ const DebtSupplierTab = () => {
|
||||
},
|
||||
{
|
||||
id: 'received_date',
|
||||
header: 'Tanggal Terima',
|
||||
header: 'Tanggal Terima/Bayar',
|
||||
accessorKey: 'received_date',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.received_date;
|
||||
return value
|
||||
@@ -269,6 +263,7 @@ const DebtSupplierTab = () => {
|
||||
id: 'po_date',
|
||||
header: 'Tanggal PO',
|
||||
accessorKey: 'po_date',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.po_date;
|
||||
return value
|
||||
@@ -282,6 +277,7 @@ const DebtSupplierTab = () => {
|
||||
id: 'aging',
|
||||
header: 'Aging',
|
||||
accessorKey: 'aging',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.aging;
|
||||
return <div className='text-center'>{formatNumber(value)} Hari</div>;
|
||||
@@ -295,6 +291,7 @@ const DebtSupplierTab = () => {
|
||||
id: 'area',
|
||||
header: 'Area',
|
||||
accessorKey: 'area',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.area?.name;
|
||||
return value || '-';
|
||||
@@ -304,6 +301,7 @@ const DebtSupplierTab = () => {
|
||||
id: 'warehouse',
|
||||
header: 'Gudang',
|
||||
accessorKey: 'warehouse',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.warehouse?.name;
|
||||
return value || '-';
|
||||
@@ -311,8 +309,9 @@ const DebtSupplierTab = () => {
|
||||
},
|
||||
{
|
||||
id: 'due_date',
|
||||
header: 'Tanggal Jatuh Tempo',
|
||||
header: 'Jatuh Tempo',
|
||||
accessorKey: 'due_date',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.due_date;
|
||||
return value
|
||||
@@ -326,6 +325,7 @@ const DebtSupplierTab = () => {
|
||||
id: 'due_status',
|
||||
header: 'Status Jatuh Tempo',
|
||||
accessorKey: 'due_status',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.due_status;
|
||||
return value || '-';
|
||||
@@ -333,8 +333,9 @@ const DebtSupplierTab = () => {
|
||||
},
|
||||
{
|
||||
id: 'total_price',
|
||||
header: 'Total Harga',
|
||||
header: 'Nominal Pembelian',
|
||||
accessorKey: 'total_price',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.total_price;
|
||||
return (
|
||||
@@ -354,8 +355,9 @@ const DebtSupplierTab = () => {
|
||||
},
|
||||
{
|
||||
id: 'payment_price',
|
||||
header: 'Harga Pembayaran',
|
||||
header: 'Pembayaran',
|
||||
accessorKey: 'payment_price',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.payment_price;
|
||||
return (
|
||||
@@ -374,11 +376,12 @@ const DebtSupplierTab = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'debt_price',
|
||||
header: 'Harga Hutang',
|
||||
accessorKey: 'debt_price',
|
||||
id: 'balance',
|
||||
header: 'Sisa Saldo Hutang',
|
||||
accessorKey: 'balance',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.debt_price;
|
||||
const value = props.row.original.balance;
|
||||
return (
|
||||
<div className={`text-right ${value < 0 ? 'text-red-500' : ''}`}>
|
||||
{formatCurrency(value)}
|
||||
@@ -398,6 +401,7 @@ const DebtSupplierTab = () => {
|
||||
id: 'status',
|
||||
header: 'Status',
|
||||
accessorKey: 'status',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.status;
|
||||
return value || '-';
|
||||
@@ -407,6 +411,7 @@ const DebtSupplierTab = () => {
|
||||
id: 'travel_number',
|
||||
header: 'Nomor Perjalanan',
|
||||
accessorKey: 'travel_number',
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.travel_number;
|
||||
return value || '-';
|
||||
@@ -421,10 +426,11 @@ const DebtSupplierTab = () => {
|
||||
className={{ wrapper: 'w-full', body: 'p-1!' }}
|
||||
>
|
||||
<div className='mb-4 flex justify-end gap-2 [&_button]:px-4'>
|
||||
<Button variant='outline' onClick={filterModal.openModal}>
|
||||
<Icon icon='heroicons:funnel' width={18} height={18} />
|
||||
Filter
|
||||
</Button>
|
||||
<ButtonFilter
|
||||
values={formik.values}
|
||||
onClick={handleFilterModalOpen}
|
||||
variant='outline'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
trigger={
|
||||
@@ -471,9 +477,14 @@ const DebtSupplierTab = () => {
|
||||
collapsible={true}
|
||||
>
|
||||
<Table
|
||||
data={supplierReport.rows}
|
||||
data={[
|
||||
{
|
||||
balance: supplierReport.initial_balance,
|
||||
} as DebtRow,
|
||||
...supplierReport.rows,
|
||||
]}
|
||||
columns={getTableColumns(supplierReport)}
|
||||
pageSize={supplierReport.rows.length}
|
||||
pageSize={supplierReport.rows.length + 1}
|
||||
renderFooter={supplierReport.rows.length > 0}
|
||||
className={{
|
||||
containerClassName: 'w-full',
|
||||
@@ -493,26 +504,38 @@ const DebtSupplierTab = () => {
|
||||
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||||
paginationClassName: 'hidden',
|
||||
}}
|
||||
renderCustomRow={(row) => {
|
||||
if (row.index == 0) {
|
||||
return (
|
||||
<tr
|
||||
className='hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200'
|
||||
key={row.index}
|
||||
>
|
||||
<td
|
||||
className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap'
|
||||
colSpan={12}
|
||||
></td>
|
||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap'>
|
||||
<div
|
||||
className={`text-right ${row.original.balance < 0 ? 'text-red-500' : ''}`}
|
||||
>
|
||||
{formatCurrency(row.original.balance)}
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap'
|
||||
colSpan={2}
|
||||
></td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
{meta && data.length > 0 && (
|
||||
<div className='mt-6'>
|
||||
<Pagination
|
||||
currentPage={meta.page}
|
||||
totalItems={meta.total_results}
|
||||
onPageChange={handlePageChange}
|
||||
onRowChange={handleRowChange}
|
||||
onNextPage={handleNextPage}
|
||||
onPrevPage={handlePrevPage}
|
||||
rowOptions={[10, 25, 50, 100]}
|
||||
itemsPerPage={meta.limit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filter Modal */}
|
||||
<Modal
|
||||
@@ -522,7 +545,11 @@ const DebtSupplierTab = () => {
|
||||
modalBox: 'p-0 rounded-2xl xl:max-w-4/12 max-w-sm',
|
||||
}}
|
||||
>
|
||||
<div className='space-y-6'>
|
||||
<form
|
||||
className='space-y-6'
|
||||
onSubmit={formik.handleSubmit}
|
||||
onReset={formik.handleReset}
|
||||
>
|
||||
{/* Modal Header */}
|
||||
<div className='flex items-center justify-between gap-2 py-3 border-b border-gray-300 px-4'>
|
||||
<div className='flex items-center gap-2 text-primary'>
|
||||
@@ -542,37 +569,31 @@ const DebtSupplierTab = () => {
|
||||
<div>
|
||||
<DateInput
|
||||
label='Tanggal'
|
||||
name='start_date'
|
||||
value={filterStartDate}
|
||||
name='startDate'
|
||||
value={formik.values.startDate || ''}
|
||||
onChange={(e) => {
|
||||
setFilterStartDate(e.target.value);
|
||||
setFilterErrors((prev) => ({ ...prev, start_date: '' }));
|
||||
formik.setFieldValue('startDate', e.target.value || null);
|
||||
}}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
isError={
|
||||
formik.touched.startDate && !!formik.errors.startDate
|
||||
}
|
||||
errorMessage={formik.errors.startDate}
|
||||
/>
|
||||
{filterErrors.start_date && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.start_date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='mt-auto'>
|
||||
<DateInput
|
||||
label=' '
|
||||
name='end_date'
|
||||
value={filterEndDate}
|
||||
name='endDate'
|
||||
value={formik.values.endDate || ''}
|
||||
onChange={(e) => {
|
||||
setFilterEndDate(e.target.value);
|
||||
setFilterErrors((prev) => ({ ...prev, end_date: '' }));
|
||||
formik.setFieldValue('endDate', e.target.value || null);
|
||||
}}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
isError={formik.touched.endDate && !!formik.errors.endDate}
|
||||
errorMessage={formik.errors.endDate}
|
||||
/>
|
||||
{filterErrors.end_date && (
|
||||
<p className='text-red-500 text-sm mt-1'>
|
||||
{filterErrors.end_date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -582,15 +603,20 @@ const DebtSupplierTab = () => {
|
||||
placeholder='Pilih Supplier'
|
||||
isMulti
|
||||
options={supplierOptions}
|
||||
value={filterSupplier}
|
||||
value={formik.values.supplierIds || []}
|
||||
onChange={(val) => {
|
||||
setFilterSupplier(
|
||||
Array.isArray(val) ? val : val ? [val] : []
|
||||
formik.setFieldValue(
|
||||
'supplierIds',
|
||||
Array.isArray(val) ? val : val ? [val] : null
|
||||
);
|
||||
}}
|
||||
isLoading={isLoadingSuppliers}
|
||||
isClearable
|
||||
className={{ wrapper: 'w-full' }}
|
||||
isError={
|
||||
formik.touched.supplierIds && !!formik.errors.supplierIds
|
||||
}
|
||||
errorMessage={formik.errors.supplierIds as string}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -599,12 +625,17 @@ const DebtSupplierTab = () => {
|
||||
label='Filter Berdasarkan'
|
||||
placeholder='Pilih Filter Berdasarkan'
|
||||
options={dataTypeOptions}
|
||||
value={filterDateType}
|
||||
value={formik.values.filterBy || null}
|
||||
onChange={(val) => {
|
||||
setFilterDateType(val ? (val as OptionType) : undefined);
|
||||
formik.setFieldValue(
|
||||
'filterBy',
|
||||
val ? (val as OptionType) : null
|
||||
);
|
||||
}}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
isClearable
|
||||
isError={formik.touched.filterBy && !!formik.errors.filterBy}
|
||||
errorMessage={formik.errors.filterBy as string}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -614,18 +645,15 @@ const DebtSupplierTab = () => {
|
||||
<Button
|
||||
variant='soft'
|
||||
className='ms-4 min-w-36 rounded-lg'
|
||||
onClick={handleResetFilters}
|
||||
type='reset'
|
||||
>
|
||||
Reset Filter
|
||||
</Button>
|
||||
<Button
|
||||
className='me-4 min-w-36 rounded-lg'
|
||||
onClick={handleApplyFilters}
|
||||
>
|
||||
<Button className='me-4 min-w-36 rounded-lg' type='submit'>
|
||||
Apply Filter
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FormikErrors } from 'formik';
|
||||
import { FormikErrors, FormikValues } from 'formik';
|
||||
|
||||
export type ErrorMessage = {
|
||||
key: string;
|
||||
@@ -69,3 +69,66 @@ export function getUniqueFormikErrors<T>(errors: FormikErrors<T>): string[] {
|
||||
export function getAllFormikErrors<T>(errors: FormikErrors<T>): ErrorMessage[] {
|
||||
return parseFormikErrors(errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is considered "filled" (not empty)
|
||||
* @param value - Value to check
|
||||
* @returns True if value is filled, false otherwise
|
||||
*/
|
||||
function isValueFilled(value: unknown): boolean {
|
||||
// Check for null or undefined
|
||||
if (value === null || value === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for empty string
|
||||
if (typeof value === 'string' && value.trim() === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for empty array
|
||||
if (Array.isArray(value) && value.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for empty object (but not Date or other special objects)
|
||||
if (
|
||||
typeof value === 'object' &&
|
||||
!Array.isArray(value) &&
|
||||
!(value instanceof Date) &&
|
||||
Object.keys(value).length === 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of filled (non-empty) values in Formik values object
|
||||
* @param values - Formik values object
|
||||
* @returns Number of filled values
|
||||
* @example
|
||||
* const values = {
|
||||
* name: 'John',
|
||||
* email: '',
|
||||
* age: null,
|
||||
* tags: ['tag1', 'tag2'],
|
||||
* emptyArray: [],
|
||||
* };
|
||||
* getFilledFormikValuesCount(values); // Returns 2 (name and tags)
|
||||
*/
|
||||
export function getFilledFormikValuesCount<T extends FormikValues>(
|
||||
values: T
|
||||
): number {
|
||||
let count = 0;
|
||||
|
||||
Object.keys(values).forEach((key) => {
|
||||
const value = values[key];
|
||||
if (isValueFilled(value)) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ export class DebtSupplierApiService extends BaseApiService<
|
||||
supplier_ids?: string,
|
||||
filter_by?: string,
|
||||
start_date?: string,
|
||||
end_date?: string,
|
||||
page?: number,
|
||||
limit?: number
|
||||
end_date?: string
|
||||
): Promise<BaseApiResponse<DebtSupplier[]> | undefined> {
|
||||
return await this.customRequest<BaseApiResponse<DebtSupplier[]>>(
|
||||
`debt-supplier`,
|
||||
@@ -28,8 +26,6 @@ export class DebtSupplierApiService extends BaseApiService<
|
||||
filter_by: filter_by,
|
||||
start_date: start_date,
|
||||
end_date: end_date,
|
||||
page: page,
|
||||
limit: limit,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
+8
@@ -33,3 +33,11 @@ export interface DebtRow {
|
||||
travel_number: string;
|
||||
balance: number;
|
||||
}
|
||||
|
||||
// Filter Param
|
||||
export interface DebtSupplierFilter {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
supplier_ids?: string;
|
||||
filter_by?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user