From d85cf291932dd2e18709f841adde976af12e199f Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Sat, 6 Dec 2025 16:52:12 +0700 Subject: [PATCH] feat(FE-320): create ClosingsTable component --- .../pages/closing/ClosingsTable.tsx | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 src/components/pages/closing/ClosingsTable.tsx diff --git a/src/components/pages/closing/ClosingsTable.tsx b/src/components/pages/closing/ClosingsTable.tsx new file mode 100644 index 00000000..91e78c8c --- /dev/null +++ b/src/components/pages/closing/ClosingsTable.tsx @@ -0,0 +1,299 @@ +'use client'; + +import { ChangeEventHandler, useEffect, useState } from 'react'; +import useSWR from 'swr'; +import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; + +import { Icon } from '@iconify/react'; +import Table from '@/components/Table'; +import DebouncedTextInput from '@/components/input/DebouncedTextInput'; +import Button from '@/components/Button'; +import SelectInput, { + OptionType, + useSelect, +} from '@/components/input/SelectInput'; +import RowDropdownOptions from '@/components/table/RowDropdownOptions'; +import RowCollapseOptions from '@/components/table/RowCollapseOptions'; +import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; +import { cn, formatCurrency, 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'; + +const PROJECT_STATUS_OPTIONS = [ + { + value: 1, + label: 'Pengajuan', + }, + { + value: 2, + label: 'Aktif', + }, +]; + +const RowOptionsMenu = ({ + type = 'dropdown', + props, +}: { + type: 'dropdown' | 'collapse'; + props: CellContext; +}) => { + return ( + + {/* TODO: apply RBAC */} +
+ +
+
+ ); +}; + +const ClosingsTable = () => { + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { + search: '', + nameSort: '', + transactionDate: '', + realizationDate: '', + locationId: '', + projectStatus: '', + userId: '', + }, + paramMap: { + page: 'page', + pageSize: 'limit', + nameSort: 'sort_name', + transactionDate: 'transaction_date', + realizationDate: 'realization_date', + locationId: 'location_id', + projectStatus: 'project_status', + userId: 'user_id', + }, + }); + + const { data: closings, isLoading: isLoadingClosings } = useSWR( + `${ClosingApi.basePath}${getTableFilterQueryString()}`, + ClosingApi.getAllFetcher + ); + + const [sorting, setSorting] = useState([]); + const [rowSelection, setRowSelection] = useState>({}); + + const closingsColumns: ColumnDef[] = [ + { + header: '#', + cell: (props) => props.row.index + 1, + }, + { + 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: 'sales_paid_amount', + header: 'Jumlah Sudah Bayar', + cell: (props) => ( + + {formatCurrency(props.row.original.sales_paid_amount)} + + ), + }, + { + accessorKey: 'sales_remaining_amount', + header: 'Jumlah Sisa Bayar', + cell: (props) => ( + + {formatCurrency(props.row.original.sales_remaining_amount)} + + ), + }, + { + accessorKey: 'sales_payment_status', + header: 'Status Pembayaran', + }, + { + 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 - 3; + + return ( + <> + {currentPageSize > 3 && ( + + + + )} + + {currentPageSize <= 3 && ( + + + + )} + + ); + }, + }, + ]; + + const { + setInputValue: setLocationInputValue, + options: locationOptions, + isLoadingOptions: isLoadingLocationOptions, + } = useSelect(LocationApi.basePath, 'id', 'name'); + + const [selectedLocation, setSelectedLocation] = useState( + null + ); + + const locationChangeHandler = (val: OptionType | OptionType[] | null) => { + setSelectedLocation(val as OptionType); + updateFilter( + 'locationId', + val ? ((val as OptionType).value as string) : '' + ); + }; + + const [selectedProjectStatus, setSelectedProjectStatus] = + useState(null); + + const projectStatusChangeHandler = ( + val: OptionType | OptionType[] | null + ) => { + setSelectedProjectStatus(val as OptionType); + updateFilter( + 'projectStatus', + val ? ((val as OptionType).value as string) : '' + ); + }; + + const searchChangeHandler: ChangeEventHandler = (e) => { + updateFilter('search', e.target.value); + }; + + // track sorting + useEffect(() => { + const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name'); + + if (!isNameSorted) { + updateFilter('nameSort', ''); + } else { + updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc'); + } + }, [sorting, updateFilter]); + + return ( + <> +
+
+
+
+ +
+ +
+ + + +
+
+
+ + + 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({ + 'w-full mb-20': + isResponseSuccess(closings) && closings?.data?.length === 0, + }), + }} + /> +
+ + ); +}; + +export default ClosingsTable;