'use client'; import { ChangeEventHandler, useEffect, useState, useMemo } from 'react'; import useSWR from 'swr'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { useRouter } from 'next/navigation'; import { Icon } from '@iconify/react'; import Table from '@/components/Table'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import Button from '@/components/Button'; import SelectInput, { useSelect } from '@/components/input/SelectInput'; import PopoverButton from '@/components/popover/PopoverButton'; import PopoverContent from '@/components/popover/PopoverContent'; import RequirePermission from '@/components/helper/RequirePermission'; import Modal, { useModal } from '@/components/Modal'; import SelectInputRadio from '@/components/input/SelectInputRadio'; import { useFormik } from 'formik'; import { cn, formatDate } from '@/lib/helper'; import { isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { LocationApi } from '@/services/api/master-data'; import { Location } from '@/types/api/master-data/location'; import { ClosingApi } from '@/services/api/closing'; import { Closing } from '@/types/api/closing'; import { ClosingFilterSchema, ClosingFilterType, } from '@/components/pages/closing/filter/ClosingFilter'; import ClosingTableSkeleton from '@/components/pages/closing/skeleton/ClosingTableSkeleton'; const RowOptionsMenu = ({ props, popoverPosition = 'bottom', detailClickHandler, }: { props: CellContext; popoverPosition: 'bottom' | 'top'; detailClickHandler: (id: number) => void; }) => { const popoverId = `closing#${props.row.original.id}`; const popoverAnchorName = `--anchor-closing#${props.row.original.id}`; const closePopover = () => { document.getElementById(popoverId)?.hidePopover(); }; const detailClickHandlerWrapper = () => { detailClickHandler(props.row.original.id); closePopover(); }; return (
); }; const ClosingsTable = () => { // ===== ROUTER ===== const router = useRouter(); // ===== FILTER MODAL STATE ===== const filterModal = useModal(); const { state: tableFilterState, updateFilter, setPage, setPageSize, toQueryString: getTableFilterQueryString, } = useTableFilter({ initial: { search: '', // nameSort: '', // transactionDate: '', // realizationDate: '', location_id: '', project_status: '', // userId: '', }, paramMap: { page: 'page', pageSize: 'limit', // nameSort: 'sort_name', // transactionDate: 'transaction_date', // realizationDate: 'realization_date', // locationId: 'location_id', // projectStatus: 'project_status', // userId: 'user_id', search: 'search', location_id: 'location_id', project_status: 'project_status', }, }); // ===== FORMIK SETUP ===== const formik = useFormik({ initialValues: { location_id: null, project_status: null, }, validationSchema: ClosingFilterSchema, onSubmit: (values, { setSubmitting }) => { updateFilter('location_id', values.location_id || ''); updateFilter('project_status', values.project_status || ''); filterModal.closeModal(); setSubmitting(false); }, onReset: () => { updateFilter('location_id', ''); updateFilter('project_status', ''); }, }); // ===== DATA FETCHING ===== const { data: closings, isLoading: isLoadingClosings } = useSWR( `${ClosingApi.basePath}${getTableFilterQueryString()}`, ClosingApi.getAllFetcher ); const data = useMemo( () => isResponseSuccess(closings) ? (closings?.data as Closing[]) || [] : [], [closings] ); // ===== PAGINATION & STATE ===== const [sorting, setSorting] = useState([]); const [rowSelection, setRowSelection] = useState>({}); // ===== TABLE COLUMNS ===== const closingsColumns: ColumnDef[] = [ { header: 'No', cell: (props) => props.row.index + 1, }, { accessorKey: 'project_name', header: 'Flock', }, { accessorKey: 'location_name', header: 'Lokasi', }, { accessorKey: 'project_category', header: 'Kategori', }, { accessorKey: 'period', header: 'Periode', }, { accessorKey: 'closing_date', header: 'Periode', cell: (props) => formatDate(props.row.original.closing_date, 'DD MMM YYYY'), }, { accessorKey: 'shed_label', header: 'Jumlah Kandang', }, { accessorKey: 'project_status', header: 'Status', }, { header: 'Aksi', cell: (props) => { const currentPageSize = props.table.getPaginationRowModel().rows.length; const currentPageRows = props.table.getPaginationRowModel().flatRows; const currentRowRelativeIndex = currentPageRows.findIndex((r) => r.id === props.row.id) + 1; const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2; const detailClickHandler = (id: number) => { router.push(`/closing/detail/?closingId=${id}`); }; return ( ); }, }, ]; // ===== LOCATION OPTIONS ===== const { setInputValue: setLocationInputValue, options: locationOptions, isLoadingOptions: isLoadingLocationOptions, loadMore: loadMoreLocations, } = useSelect(LocationApi.basePath, 'id', 'name'); // ===== PROJECT STATUS OPTIONS ===== const projectStatusOptions = useMemo( () => [ { value: '1', label: 'Pengajuan' }, { value: '2', label: 'Aktif' }, ], [] ); // ===== FILTER HELPERS ===== const locationIdValue = useMemo(() => { if (!formik.values.location_id) return null; return ( locationOptions.find( (opt) => String(opt.value) === formik.values.location_id ) || null ); }, [formik.values.location_id, locationOptions]); const projectStatusValue = useMemo(() => { if (!formik.values.project_status) return null; return ( projectStatusOptions.find( (opt) => opt.value === formik.values.project_status ) || null ); }, [formik.values.project_status, projectStatusOptions]); // ===== ACTIVE FILTERS COUNT ===== const activeFiltersCount = useMemo(() => { let count = 0; if (tableFilterState.location_id) { count += 1; } if (tableFilterState.project_status) { count += 1; } return count; }, [tableFilterState.location_id, tableFilterState.project_status]); const hasFilters = activeFiltersCount > 0; // ===== SEARCH CHANGE HANDLER ===== const searchChangeHandler: ChangeEventHandler = (e) => { updateFilter('search', e.target.value); }; // ===== HANDLE FILTER MODAL OPEN ===== const handleFilterModalOpen = () => { filterModal.openModal(); formik.validateForm(); }; // track sorting useEffect(() => { const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name'); if (!isNameSorted) { // updateFilter('nameSort', ''); } else { // updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc'); } }, [sorting]); return ( <>
{/* Space for action buttons if needed in the future */}
} className={{ wrapper: 'w-full min-w-24 max-w-3xs', inputWrapper: 'rounded-xl! shadow-button-soft', input: 'placeholder:font-semibold placeholder:text-base-content/50', }} />
{isLoadingClosings ? (
) : data.length === 0 ? ( } title='Data Closing Belum Tersedia' subtitle='Tidak ada data closing untuk saat ini.' /> ) : ( data={isResponseSuccess(closings) ? closings?.data : []} columns={closingsColumns} pageSize={tableFilterState.pageSize} onPageSizeChange={setPageSize} rowOptions={[10, 20, 50, 100]} page={isResponseSuccess(closings) ? closings?.meta?.page : 0} totalItems={ isResponseSuccess(closings) ? closings?.meta?.total_results : 0 } onPageChange={setPage} isLoading={isLoadingClosings} sorting={sorting} setSorting={setSorting} rowSelection={rowSelection} setRowSelection={setRowSelection} className={{ containerClassName: cn('mt-3', { 'w-full mb-0': isResponseSuccess(closings) && closings?.data?.length === 0, }), headerColumnClassName: 'text-nowrap', }} /> )}
{/* Filter Modal */} {/* Modal Header */}

Filter Data

{ if (!Array.isArray(val)) { formik.setFieldValue( 'location_id', val?.value ? String(val.value) : null ); } }} onInputChange={setLocationInputValue} isLoading={isLoadingLocationOptions} isClearable onMenuScrollToBottom={loadMoreLocations} className={{ wrapper: 'w-full' }} /> { if (!Array.isArray(val)) { formik.setFieldValue('project_status', val?.value || null); } }} className={{ wrapper: 'w-full' }} isClearable={true} />
{/* Modal Footer */}
); }; export default ClosingsTable;