import Button from '@/components/Button'; import Card from '@/components/Card'; import Dropdown from '@/components/Dropdown'; import DateInput from '@/components/input/DateInput'; import { OptionType, useSelect } from '@/components/input/SelectInput'; import Menu from '@/components/menu/Menu'; import MenuItem from '@/components/menu/MenuItem'; import Modal, { useModal } from '@/components/Modal'; import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table'; import { isResponseSuccess } from '@/lib/api-helper'; import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper'; import { SupplierApi } from '@/services/api/master-data'; 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'; import { ColumnDef } from '@tanstack/react-table'; import { useCallback, useEffect, useMemo, useState } from 'react'; import toast from 'react-hot-toast'; import useSWR from 'swr'; import { DebtSupplierApi } from '@/services/api/report/debt-supplier'; import { useFormik } from 'formik'; import { DebtSupplierFilterSchema, DebtSupplierFilterType, } from '@/components/pages/report/finance/filter/DebtSupplierFilter'; import ButtonFilter from '@/components/helper/ButtonFilter'; import { Color } from '@/types/theme'; import { Supplier } from '@/types/api/master-data/supplier'; import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import SelectInputRadio from '@/components/input/SelectInputRadio'; import { useFinanceTabStore } from '@/stores/finance-tab/finance-tab.store'; import StatusBadge from '@/components/helper/StatusBadge'; const dueStatus: Record = { 'Sudah Jatuh Tempo': 'error', 'Belum Jatuh Tempo': 'success', 'Mendekati Jatuh Tempo': 'warning', }; const paymentStatus: Record = { 'Belum Lunas': 'warning', Lunas: 'primary', Pembayaran: 'success', }; const getPillBadge = ( statusText: string, type: 'due' | 'payment' = 'payment' ) => { // Get color based on type const color = type === 'due' ? dueStatus[statusText] || 'neutral' : paymentStatus[statusText] || 'neutral'; return ; }; interface DebtSupplierTabProps { tabId: string; } const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { // ===== STATE MANAGEMENT ===== const [isPdfExportLoading, setIsPdfExportLoading] = useState(false); const [isExcelExportLoading, setIsExcelExportLoading] = useState(false); const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading; // ===== SUBMISSION STATE ===== const [filterParams, setFilterParams] = useState({ start_date: undefined, end_date: undefined, supplier_ids: undefined, filter_by: undefined, }); const [isSubmitted, setIsSubmitted] = useState(false); const filterModal = useModal(); const { setInputValue: setSupplierInputValue, options: supplierOptions, isLoadingOptions: isLoadingSupplierOptions, loadMore: loadMoreSuppliers, } = useSelect(SupplierApi.basePath, 'id', 'name'); const dataTypeOptions = useMemo( () => [ { value: 'received_date', label: 'Tanggal Terima' }, { value: 'po_date', label: 'Tanggal PO' }, ], [] ); const handleFilterModalOpen = () => { filterModal.openModal(); }; // ===== FORMIK SETUP ===== const formik = useFormik({ 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(); 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: filterParams.supplier_ids, filter_by: filterParams.filter_by, start_date: filterParams.start_date, end_date: filterParams.end_date, }; return ['debt-supplier-report', params]; } : null, ([, params]) => DebtSupplierApi.getDebtSupplierReport( params.supplier_ids, params.filter_by, params.start_date, params.end_date ) ); const data: DebtSupplier[] = useMemo( () => isResponseSuccess(debtSupplier) ? (debtSupplier?.data as unknown as DebtSupplier[]) || [] : [], [debtSupplier] ); const meta = isResponseSuccess(debtSupplier) && debtSupplier?.meta ? debtSupplier.meta : null; // ===== EXPORT DATA FETCHER ===== const debtSupplierExport = useCallback(async (): Promise< DebtSupplier[] | null > => { const params = { supplier_ids: formik.values.supplierIds && formik.values.supplierIds.length > 0 ? formik.values.supplierIds.map((v) => String(v.value)).join(',') : 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, }; const response = await DebtSupplierApi.getDebtSupplierReport( params.supplier_ids, params.filter_by, params.start_date, params.end_date ); return isResponseSuccess(response) ? (response.data as unknown as DebtSupplier[]) : null; }, [ formik.values.supplierIds, formik.values.startDate, formik.values.endDate, formik.values.filterBy, ]); // ===== EXPORT HANDLERS ===== const handleExportExcel = useCallback(async () => { setIsExcelExportLoading(true); try { const allDataForExport = await debtSupplierExport(); if ( !allDataForExport || !Array.isArray(allDataForExport) || allDataForExport.length === 0 ) { toast.error('Tidak ada data untuk diekspor.'); return; } generateDebtSupplierExcel({ data: allDataForExport }); toast.success('Excel berhasil dibuat dan diunduh.'); } catch { toast.error('Gagal membuat Excel. Silakan coba lagi.'); } finally { setIsExcelExportLoading(false); } }, [debtSupplierExport]); const handleExportPdf = useCallback(async () => { setIsPdfExportLoading(true); try { const allDataForExport = await debtSupplierExport(); if ( !allDataForExport || !Array.isArray(allDataForExport) || allDataForExport.length === 0 ) { toast.error('Tidak ada data untuk diekspor.'); return; } await generateDebtSupplierPDF({ data: allDataForExport, params: { supplier_name: formik.values.supplierIds ?.map((v) => v.label) .join(', '), filter_by: formik.values.filterBy?.label, start_date: formik.values.startDate || undefined, end_date: formik.values.endDate || undefined, }, }); toast.success('PDF berhasil dibuat dan diunduh.'); } catch { toast.error('Gagal membuat PDF. Silakan coba lagi.'); } finally { setIsPdfExportLoading(false); } }, [debtSupplierExport]); // ===== REGISTER TAB ACTIONS TO STORE ===== const setTabActions = useFinanceTabStore((state) => state.setTabActions); const clearTabActions = useFinanceTabStore((state) => state.clearTabActions); useEffect(() => { setTabActions( tabId,
Export
} align='end' className={{ content: 'mt-1 p-0 w-full shadow-button-soft border border-base-content/10 rounded-lg', }} >
); }, [ tabId, formik.values, isAnyExportLoading, handleExportExcel, handleExportPdf, setTabActions, ]); // Cleanup on unmount useEffect(() => { return () => { clearTabActions(tabId); }; }, [tabId, clearTabActions]); const getTableColumns = (supplier: DebtSupplier): ColumnDef[] => [ { id: 'no', header: 'No', enableSorting: false, cell: (props) => props.row.index, footer: () => 'Total', }, { id: 'pr_number', header: 'Nomor PR', accessorKey: 'pr_number', enableSorting: false, cell: (props) => { const value = props.row.original.pr_number; return value || '-'; }, }, { id: 'po_number', header: 'Nomor PO', accessorKey: 'po_number', enableSorting: false, cell: (props) => { const value = props.row.original.po_number; return value || '-'; }, }, { id: 'received_date', header: 'Tanggal Terima/Bayar', accessorKey: 'received_date', enableSorting: false, cell: (props) => { const value = props.row.original.received_date; return value ? value != '-' ? formatDate(value, 'DD MMM YYYY') : '-' : '-'; }, }, { id: 'po_date', header: 'Tanggal PO', accessorKey: 'po_date', enableSorting: false, cell: (props) => { const value = props.row.original.po_date; return value ? value != '-' ? formatDate(value, 'DD MMM YYYY') : '-' : '-'; }, }, { id: 'aging', header: 'Aging', accessorKey: 'aging', enableSorting: false, cell: (props) => { const value = props.row.original.aging; return
{formatNumber(value)} Hari
; }, footer: () => { const value = supplier.total.aging; return
{formatNumber(value)} Hari
; }, }, { id: 'area', header: 'Area', accessorKey: 'area', enableSorting: false, cell: (props) => { const value = props.row.original.area?.name; return value || '-'; }, }, { id: 'warehouse', header: 'Gudang', accessorKey: 'warehouse', enableSorting: false, cell: (props) => { const value = props.row.original.warehouse?.name; return value || '-'; }, }, { id: 'due_date', header: 'Jatuh Tempo', accessorKey: 'due_date', enableSorting: false, cell: (props) => { const value = props.row.original.due_date; return value ? value != '-' ? formatDate(value, 'DD MMM YYYY') : '-' : '-'; }, }, { id: 'due_status', header: 'Status Jatuh Tempo', accessorKey: 'due_status', enableSorting: false, cell: (props) => { const value = props.row.original.due_status; return value ? (value != '-' ? getPillBadge(value, 'due') : '-') : '-'; }, }, { id: 'total_price', header: 'Nominal Pembelian', accessorKey: 'total_price', enableSorting: false, cell: (props) => { const value = props.row.original.total_price; return (
{formatCurrency(value)}
); }, footer: () => { const value = supplier.total.total_price; return (
{formatCurrency(value)}
); }, }, { id: 'payment_price', header: 'Pembayaran', accessorKey: 'payment_price', enableSorting: false, cell: (props) => { const value = props.row.original.payment_price; return (
{formatCurrency(value)}
); }, footer: () => { const value = supplier.total.payment_price; return (
{formatCurrency(value)}
); }, }, { id: 'balance', header: 'Sisa Saldo Hutang', accessorKey: 'balance', enableSorting: false, cell: (props) => { const value = props.row.original.balance; return (
{formatCurrency(value)}
); }, footer: () => { const value = supplier.total.debt_price; return (
{formatCurrency(value)}
); }, }, { id: 'status', header: 'Status', accessorKey: 'status', enableSorting: false, cell: (props) => { const value = props.row.original.status; return value ? value != '-' ? getPillBadge(value, 'payment') : '-' : '-'; }, }, { id: 'travel_number', header: 'Nomor Perjalanan', accessorKey: 'travel_number', enableSorting: false, cell: (props) => { const value = props.row.original.travel_number; return value || '-'; }, }, ]; return ( <>
{!isSubmitted ? (
Silakan klik tombol Filter untuk mengatur filter dan menampilkan data.
) : isLoading ? (
) : data.length === 0 ? (
Tidak ada data yang dapat ditampilkan...
) : ( data.map((supplierReport) => { return ( 0} className={{ containerClassName: 'w-full mb-0', tableWrapperClassName: 'overflow-x-auto rounded-tr-none rounded-tl-none', headerColumnClassName: cn( TABLE_DEFAULT_STYLING.headerColumnClassName, 'whitespace-nowrap' ), bodyColumnClassName: cn( TABLE_DEFAULT_STYLING.bodyColumnClassName, 'whitespace-nowrap' ), footerRowClassName: cn( TABLE_DEFAULT_STYLING.footerRowClassName, 'bg-white' ), footerColumnClassName: cn( TABLE_DEFAULT_STYLING.footerColumnClassName, 'whitespace-nowrap p-3' ), paginationClassName: 'hidden', }} renderCustomRow={(row) => { if (row.index == 0) { return ( ); } }} /> ); }) )} {/* Filter Modal */}
{/* Modal Header */}

Filter Data

{/* Modal Body */}
{ formik.setFieldValue('startDate', e.target.value || null); }} className={{ wrapper: 'w-full' }} isError={ formik.touched.startDate && !!formik.errors.startDate } errorMessage={formik.errors.startDate} isNestedModal />
{ formik.setFieldValue('endDate', e.target.value || null); }} className={{ wrapper: 'w-full' }} isError={formik.touched.endDate && !!formik.errors.endDate} errorMessage={formik.errors.endDate} isNestedModal />
{ formik.setFieldValue( 'supplierIds', Array.isArray(val) ? val : val ? [val] : null ); }} onInputChange={setSupplierInputValue} onMenuScrollToBottom={loadMoreSuppliers} isLoading={isLoadingSupplierOptions} isClearable className={{ wrapper: 'w-full' }} isError={ formik.touched.supplierIds && !!formik.errors.supplierIds } errorMessage={formik.errors.supplierIds as string} />
{ 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} />
{/* Action Buttons */}
); }; export default DebtSupplierTab;
{formatCurrency(row.original.balance)}