'use client'; import React, { useState, useCallback, useEffect, useMemo, useRef, } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; import Button from '@/components/Button'; import Dropdown from '@/components/dropdown/Dropdown'; import SelectInput, { useSelect } from '@/components/input/SelectInput'; import DateInput from '@/components/input/DateInput'; import { useFormik } from 'formik'; import { ReportExpenseFilterSchema, type ReportExpenseFilterValues, } from '@/components/pages/report/expense/filter/ReportExpenseFilter'; import ExpenseStatusBadge from '@/components/pages/expense/ExpenseStatusBadge'; import RealizationStatusBadge from '@/components/pages/expense/RealizationStatusBadge'; import Table from '@/components/Table'; import { formatCurrency, formatDate } from '@/lib/helper'; import { ReportExpense } from '@/types/api/report/report-expense'; import { ReportExpenseApi } from '@/services/api/report/expense-report'; import { getErrorMessage, isResponseSuccess } from '@/lib/api-helper'; import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store'; import Modal, { useModal } from '@/components/Modal'; import Pagination from '@/components/Pagination'; import ReportExpenseSkeleton from '@/components/pages/report/expense/skeleton/ReportExpenseSkeleton'; import { generateReportExpensePDF } from '../export/ReportExpenseExportPDF'; import { generateReportExpenseExcel } from '../export/ReportExpenseExportXLSX'; import toast from 'react-hot-toast'; import { LocationApi, NonstockApi, SupplierApi, } from '@/services/api/master-data'; import { Supplier } from '@/types/api/master-data/supplier'; import { Nonstock } from '@/types/api/master-data/nonstock'; import { ColumnDef } from '@tanstack/react-table'; import { httpClient } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; import ButtonFilter from '@/components/helper/ButtonFilter'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { ProjectFlockKandangApi } from '@/services/api/production/project-flock-kandang'; interface ReportExpenseTabProps { tabId: string; } interface FilterParams { location_id?: string; supplier_id?: string; kandang_id?: string; nonstock_id?: string; realization_date?: string; category?: string; search?: string; } const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => { // ===== STATE MANAGEMENT ===== const [isPdfExportLoading, setIsPdfExportLoading] = useState(false); const [isExcelExportLoading, setIsExcelExportLoading] = useState(false); const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading; // ===== SUBMISSION STATE ===== const [filterParams, setFilterParams] = useState({}); // ===== PAGINATION STATE ===== const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); const handleFilterModalOpenRef = useRef(() => {}); const filterModal = useModal(); const categoryOptions = useMemo( () => [ { value: 'BOP', label: 'BOP' }, { value: 'NON-BOP', label: 'Non BOP' }, ], [] ); // ===== FORMIK SETUP ===== const formik = useFormik({ initialValues: { location_id: null, supplier_id: null, kandang_id: null, nonstock_id: null, realization_date: null, category: null, }, validationSchema: ReportExpenseFilterSchema, onSubmit: (values) => { setFilterParams({ location_id: values.location_id?.value ? String(values.location_id.value) : undefined, supplier_id: values.supplier_id?.value ? String(values.supplier_id.value) : undefined, kandang_id: values.kandang_id?.value ? String(values.kandang_id.value) : undefined, nonstock_id: values.nonstock_id?.value ? String(values.nonstock_id.value) : undefined, realization_date: values.realization_date || undefined, category: values.category?.value ? String(values.category.value) : undefined, }); filterModal.closeModal(); setPage(1); }, onReset: () => { setFilterParams({}); setPage(1); filterModal.closeModal(); }, }); handleFilterModalOpenRef.current = () => { const restoredLocation = filterParams.location_id ? locationOptions.find( (opt) => String(opt.value) === filterParams.location_id ) || { value: filterParams.location_id, label: filterParams.location_id, } : null; const restoredSupplier = filterParams.supplier_id ? supplierOptions.find( (opt) => String(opt.value) === filterParams.supplier_id ) || { value: filterParams.supplier_id, label: filterParams.supplier_id, } : null; const restoredKandang = filterParams.kandang_id ? projectFlockKandangOptions.find( (opt) => String(opt.value) === filterParams.kandang_id ) || { value: filterParams.kandang_id, label: filterParams.kandang_id } : null; const restoredNonstock = filterParams.nonstock_id ? nonstockOptions.find( (opt) => String(opt.value) === filterParams.nonstock_id ) || { value: filterParams.nonstock_id, label: filterParams.nonstock_id, } : null; const restoredCategory = filterParams.category ? categoryOptions.find((opt) => opt.value === filterParams.category) || null : null; formik.setValues({ location_id: restoredLocation, supplier_id: restoredSupplier, kandang_id: restoredKandang, nonstock_id: restoredNonstock, realization_date: filterParams.realization_date || null, category: restoredCategory, }); filterModal.openModal(); }; // ===== OPTIONS ===== const { setInputValue: setLocationInputValue, options: locationOptions, isLoadingOptions: isLoadingLocations, loadMore: loadMoreLocations, } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); const { setInputValue: setSupplierInputValue, options: supplierOptions, isLoadingOptions: isLoadingSuppliers, loadMore: loadMoreSuppliers, } = useSelect(SupplierApi.basePath, 'id', 'name', 'search'); const { setInputValue: setProjectFlockKandangInputValue, options: projectFlockKandangOptions, isLoadingOptions: isLoadingProjectFlockKandangs, loadMore: loadMoreProjectFlockKandangs, } = useSelect( ProjectFlockKandangApi.basePath, 'id', 'name_with_period', 'search', formik.values.location_id?.value ? { location_id: String(formik.values.location_id.value) } : undefined ); const { setInputValue: setNonstockInputValue, options: nonstockOptions, isLoadingOptions: isLoadingNonstocks, loadMore: loadMoreNonstocks, } = useSelect(NonstockApi.basePath, 'id', 'name', 'search'); // ===== FILTER VALUES ===== const locationValue = useMemo( () => formik.values.location_id, [formik.values.location_id] ); const supplierValue = useMemo( () => formik.values.supplier_id, [formik.values.supplier_id] ); const kandangValue = useMemo( () => formik.values.kandang_id, [formik.values.kandang_id] ); const nonstockValue = useMemo( () => formik.values.nonstock_id, [formik.values.nonstock_id] ); const categoryValue = useMemo( () => formik.values.category, [formik.values.category] ); const buildReportExpenseQueryString = useCallback( (extraParams?: Record) => { const params = new URLSearchParams(); if (filterParams.location_id) { params.append('location_id', filterParams.location_id); } if (filterParams.supplier_id) { params.append('supplier_id', filterParams.supplier_id); } if (filterParams.kandang_id) { params.append('project_flock_kandang_id', filterParams.kandang_id); } if (filterParams.nonstock_id) { params.append('nonstock_id', filterParams.nonstock_id); } if (filterParams.realization_date) { params.append('realization_date', filterParams.realization_date); } if (filterParams.category) { params.append('category', filterParams.category); } Object.entries(extraParams ?? {}).forEach(([key, value]) => { params.set(key, value); }); return params.toString(); }, [filterParams] ); // ===== DATA FETCHING ===== const { data: reportExpenseResponse, isLoading } = useSWR( () => { const queryString = buildReportExpenseQueryString({ page: String(page), limit: String(pageSize), }); return [`${ReportExpenseApi.basePath}?${queryString}`]; }, ([url]: string[]) => httpClient>(url) ); const data: ReportExpense[] = useMemo( () => isResponseSuccess(reportExpenseResponse) ? (reportExpenseResponse.data as ReportExpense[]) || [] : [], [reportExpenseResponse] ); const meta = useMemo( () => isResponseSuccess(reportExpenseResponse) && reportExpenseResponse.meta ? reportExpenseResponse.meta : null, [reportExpenseResponse] ); // ===== EXPORT DATA FETCHER ===== const reportExpenseExport = useCallback(async (): Promise< ReportExpense[] | null > => { const queryString = buildReportExpenseQueryString({ page: '1', limit: '100', }); const response = await httpClient>( `${ReportExpenseApi.basePath}?${queryString}` ); return isResponseSuccess(response) ? response.data : null; }, [buildReportExpenseQueryString]); // ===== EXPORT HANDLERS ===== const handleExportExcel = useCallback(async () => { setIsExcelExportLoading(true); try { await ReportExpenseApi.exportToExcel(buildReportExpenseQueryString()); } catch (error) { toast.error( await getErrorMessage(error, 'Gagal mengekspor data pengeluaran') ); } finally { setIsExcelExportLoading(false); } }, [buildReportExpenseQueryString]); const handleExportPDF = useCallback(async () => { setIsPdfExportLoading(true); try { const allData = await reportExpenseExport(); if (!allData || allData.length === 0) { toast.error('Tidak ada data untuk diekspor.'); return; } await generateReportExpensePDF(allData); toast.success('PDF berhasil dibuat dan diunduh.'); } catch { toast.error('Gagal membuat PDF. Silakan coba lagi.'); } finally { setIsPdfExportLoading(false); } }, [reportExpenseExport]); // ===== TAB ACTIONS COMPONENT ===== const TabActions = useMemo(() => { return function TabActionsComponent() { const setTabActions = useTabActionsStore((state) => state.setTabActions); const clearTabActions = useTabActionsStore( (state) => state.clearTabActions ); useEffect(() => { setTabActions( tabId,
handleFilterModalOpenRef.current()} variant='outline' className='px-3 py-2.5' />
Export
} >
); }, [setTabActions]); useEffect(() => { return () => { clearTabActions(tabId); }; }, [clearTabActions]); return null; }; }, [ tabId, filterParams, isAnyExportLoading, handleExportExcel, handleExportPDF, isExcelExportLoading, isPdfExportLoading, ]); const TabActionsElement = useMemo(() => , [TabActions]); // ===== TABLE COLUMNS DEFINITION ===== const columns = useMemo((): ColumnDef[] => { return [ { header: 'No', cell: (props) => (page - 1) * pageSize + props.row.index + 1, }, { header: 'No. PO', accessorKey: 'po_number', }, { header: 'No. Referensi', accessorKey: 'reference_number', }, { header: 'Tanggal Realisasi', accessorKey: 'realization_date', cell: ({ row }) => { return formatDate(row.original?.realization_date, 'DD MMM, YYYY'); }, }, { header: 'Tanggal Transaksi', accessorKey: 'transaction_date', cell: ({ row }) => { return formatDate(row.original?.transaction_date, 'DD MMM, YYYY'); }, }, { header: 'Kategori', accessorKey: 'category', }, { header: 'Produk', accessorFn: (row) => row.pengajuan?.nonstock?.name, }, { header: 'Supplier', accessorFn: (row) => row.supplier?.name, }, { header: 'Lokasi', accessorFn: (row) => row.kandang?.location?.name, }, { header: 'Kandang', accessorFn: (row) => row.kandang?.name, }, { header: 'Pengajuan', columns: [ { header: 'Qty', id: 'qty_pengajuan', accessorFn: (row) => row.pengajuan?.qty, cell: ({ row }) => row.original.pengajuan?.qty?.toLocaleString('id-ID') || '0', }, { header: 'Harga', id: 'harga_pengajuan', accessorFn: (row) => row.pengajuan?.price, cell: ({ row }) => formatCurrency(row.original.pengajuan?.price || 0), }, { header: 'Total', id: 'total_pengajuan', accessorFn: (row) => (row.pengajuan?.qty || 0) * (row.pengajuan?.price || 0), cell: ({ row }) => { const total = (row.original.pengajuan?.qty || 0) * (row.original.pengajuan?.price || 0); return formatCurrency(total); }, }, ], }, { header: 'Realisasi', columns: [ { header: 'Qty', id: 'qty_realisasi', accessorFn: (row) => row.realisasi?.qty, cell: ({ row }) => row.original.realisasi?.qty?.toLocaleString('id-ID') || '0', }, { header: 'Harga', id: 'harga_realisasi', accessorFn: (row) => row.realisasi?.price, cell: ({ row }) => formatCurrency(row.original.realisasi?.price || 0), }, { header: 'Total', id: 'total_realisasi', accessorFn: (row) => (row.realisasi?.qty || 0) * (row.realisasi?.price || 0), cell: ({ row }) => { const total = (row.original.realisasi?.qty || 0) * (row.original.realisasi?.price || 0); return formatCurrency(total); }, }, ], }, { header: 'Status Pencairan', cell: (props) => ( ), }, { header: 'Status BOP', cell: (props) => ( ), }, ]; }, [page, pageSize]); return ( <> {TabActionsElement}
{isLoading && (
)} {!isLoading && (!data || data.length === 0) && ( } title='Data Not Yet Available' subtitle='Please change your filters to get the data.' /> )} {!isLoading && data.length > 0 && ( <> {meta && (
setPage((currPage) => currPage > 1 ? currPage - 1 : currPage ) } onNextPage={() => setPage((currPage) => meta && meta.total_pages && currPage < meta.total_pages ? currPage + 1 : currPage ) } onPageChange={(pageNumber) => setPage(pageNumber)} rowOptions={[10, 20, 50, 100]} onRowChange={setPageSize} />
)} )} {/* Filter Modal */} {/* Modal Header */}

Filter Data

{/* Modal Body */}
{ formik.setFieldValue('location_id', val); formik.setFieldValue('kandang_id', null); }} onInputChange={setLocationInputValue} onMenuScrollToBottom={loadMoreLocations} isClearable className={{ wrapper: 'w-full' }} /> { formik.setFieldValue('kandang_id', val); }} onInputChange={setProjectFlockKandangInputValue} onMenuScrollToBottom={loadMoreProjectFlockKandangs} isClearable isDisabled={!formik.values.location_id} className={{ wrapper: 'w-full' }} /> { formik.setFieldValue('supplier_id', val); }} onInputChange={setSupplierInputValue} onMenuScrollToBottom={loadMoreSuppliers} isClearable className={{ wrapper: 'w-full' }} /> { formik.setFieldValue('nonstock_id', val); }} onInputChange={setNonstockInputValue} onMenuScrollToBottom={loadMoreNonstocks} isClearable className={{ wrapper: 'w-full' }} /> { formik.setFieldValue('category', val); }} isClearable className={{ wrapper: 'w-full' }} /> { formik.setFieldValue( 'realization_date', e.target.value || null ); }} className={{ wrapper: 'w-full' }} />
{/* Modal Footer */}
); }; export default ReportExpenseTab;