From c53f91ec3f282469a869b4d1557d3e1943da3cac Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Wed, 8 Oct 2025 15:00:30 +0700 Subject: [PATCH] feat(FE-43): create NonstocksTable component --- .../master-data/nonstock/NonstocksTable.tsx | 330 +++++++++++------- 1 file changed, 208 insertions(+), 122 deletions(-) diff --git a/src/components/pages/master-data/nonstock/NonstocksTable.tsx b/src/components/pages/master-data/nonstock/NonstocksTable.tsx index 4a2dbc5b..462b3488 100644 --- a/src/components/pages/master-data/nonstock/NonstocksTable.tsx +++ b/src/components/pages/master-data/nonstock/NonstocksTable.tsx @@ -1,20 +1,31 @@ 'use client'; -import { ChangeEventHandler, useState } from 'react'; +import { ChangeEventHandler, useCallback, useEffect, useState } from 'react'; import useSWR from 'swr'; -import { CellContext, ColumnDef } from '@tanstack/react-table'; +import { + CellContext, + ColumnDef, + ColumnSort, + SortingState, +} from '@tanstack/react-table'; +import toast from 'react-hot-toast'; import { Icon } from '@iconify/react'; import Table from '@/components/Table'; -import TextInput from '@/components/input/TextInput'; +import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import Button from '@/components/Button'; -import Collapse from '@/components/Collapse'; +import { useModal } from '@/components/Modal'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; +import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import RowDropdownOptions from '@/components/table/RowDropdownOptions'; +import RowCollapseOptions from '@/components/table/RowCollapseOptions'; -import { httpClientFetcher } from '@/services/http/client'; -import { Nonstock, NonstocksResponse } from '@/types/api/master-data/nonstock'; +import { Nonstock } from '@/types/api/master-data/nonstock'; +import { NonstockApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { deleteNonstock } from '@/services/api/master-data/nonstock'; import { isResponseSuccess } from '@/lib/api-helper'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { ROWS_OPTIONS } from '@/config/constant'; const RowOptionsMenu = ({ type = 'dropdown', @@ -23,7 +34,7 @@ const RowOptionsMenu = ({ }: { type: 'dropdown' | 'collapse'; props: CellContext; - deleteClickHandler: () => Promise; + deleteClickHandler: () => void; }) => { return (
; - isLast2Rows: boolean; - deleteClickHandler: () => Promise; -}) => { - return ( -
- - - -
- ); -}; - -const RowCollapseOptions = ({ - props, - deleteClickHandler, -}: { - props: CellContext; - deleteClickHandler: () => Promise; -}) => { - return ( - - - - } - className='w-fit' - titleClassName='p-0! justify-self-end' - > - - - ); -}; - const NonstocksTable = () => { + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { search: '', nameSort: '', locationSort: '', picSort: '' }, + paramMap: { + page: 'page', + pageSize: 'limit', + nameSort: 'sort_name', + locationSort: 'sort_location', + picSort: ' sort_pic', + }, + }); + const { data: nonstocks, isLoading, mutate: refreshNonstocks, - } = useSWR('/master-data/nonstocks', httpClientFetcher); + } = useSWR( + `${NonstockApi.basePath}${getTableFilterQueryString()}`, + NonstockApi.getAllFetcher + ); - const [searchValue, setSearchValue] = useState(''); + const deleteModal = useModal(); + + const [selectedNonstock, setSelectedNonstock] = useState< + Nonstock | undefined + >(undefined); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + const [sorting, setSorting] = useState([]); const nonstocksColumns: ColumnDef[] = [ { header: '#', - cell: (props) => props.row.index + 1, + cell: (props) => + tableFilterState.pageSize * (tableFilterState.page - 1) + + props.row.index + + 1, }, { - header: 'Nama', accessorKey: 'name', + header: 'Nama', + }, + { + accessorKey: 'uom', + header: 'UOM', + cell: (props) => props.row.original.uom.name, + }, + { + accessorKey: 'suppliers', + header: 'Supplier', + cell: (props) => { + const supplierNames = props.row.original.suppliers.map( + (supplier) => supplier.name + ); + + return supplierNames.join(', ') || '-'; + }, + }, + { + accessorKey: 'flags', + header: 'Flag', + cell: (props) => props.row.original.flags?.join(', ') || '-', }, { header: 'Aksi', @@ -157,33 +164,31 @@ const NonstocksTable = () => { const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2; - const deleteClickHandler = async () => { - const confirmation = confirm( - 'Apakah anda yakin untuk menghapus non stock ini?' - ); - - if (confirmation) { - await deleteNonstock(props.row.original.id); - refreshNonstocks(); - alert('Nonstock berhasil dihapus!'); - } + const deleteClickHandler = () => { + setSelectedNonstock(props.row.original); + deleteModal.openModal(); }; return ( <> {currentPageSize > 2 && ( - + + + )} {currentPageSize <= 2 && ( - + + + )} ); @@ -191,52 +196,133 @@ const NonstocksTable = () => { }, ]; - const searchChangeHandler: ChangeEventHandler = (e) => { - setSearchValue(e.target.value); + const confirmationModalDeleteClickHandler = async () => { + setIsDeleteLoading(true); + + await NonstockApi.delete(selectedNonstock?.id as number); + refreshNonstocks(); + + deleteModal.closeModal(); + toast.success('Successfully delete Nonstock!'); + setIsDeleteLoading(false); }; + const searchChangeHandler: ChangeEventHandler = (e) => { + updateFilter('search', e.target.value); + }; + + const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { + const newVal = val as OptionType; + + setPageSize(newVal.value as number); + }; + + const updateSortingFilter = useCallback( + ( + sortName: Exclude, + sortFilter: ColumnSort | undefined + ) => { + if (!sortFilter) { + updateFilter(sortName, ''); + } else { + updateFilter(sortName, sortFilter.desc ? 'desc' : 'asc'); + } + }, + [updateFilter] + ); + + // track sorting + useEffect(() => { + const nameSortFilter = sorting.find((sortItem) => sortItem.id === 'name'); + const locationSortFilter = sorting.find( + (sortItem) => sortItem.id === 'location' + ); + const picSortFilter = sorting.find((sortItem) => sortItem.id === 'pic'); + + updateSortingFilter('nameSort', nameSortFilter); + updateSortingFilter('locationSort', locationSortFilter); + updateSortingFilter('picSort', picSortFilter); + }, [sorting]); + return ( -
-
-
- + <> +
+
+
+
+ +
+ + +
+ +
+ +
- + data={isResponseSuccess(nonstocks) ? nonstocks?.data : []} + columns={nonstocksColumns} + pageSize={tableFilterState.pageSize} + page={isResponseSuccess(nonstocks) ? nonstocks?.meta?.page : 0} + totalItems={ + isResponseSuccess(nonstocks) ? nonstocks?.meta?.total_results : 0 + } + onPageChange={setPage} + isLoading={isLoading} + sorting={sorting} + setSorting={setSorting} + className={{ + containerClassName: cn({ + 'mb-20': + isResponseSuccess(nonstocks) && nonstocks?.data?.length === 0, + }), + tableWrapperClassName: 'overflow-x-auto min-h-full!', + tableClassName: 'font-inter w-full table-auto min-h-full!', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', + bodyRowClassName: 'border-b border-b-gray-200', + bodyColumnClassName: + 'px-6 py-3 last:flex last:flex-row last:justify-end', + }} />
- - data={isResponseSuccess(nonstocks) ? nonstocks?.data : []} - columns={nonstocksColumns} - pageSize={10} - fuzzySearchValue={searchValue} - onFuzzySearchValueChange={setSearchValue} - isLoading={isLoading} - className={{ - containerClassName: cn({ - 'mb-20': - isResponseSuccess(nonstocks) && nonstocks?.data?.length === 0, - }), - tableWrapperClassName: 'overflow-x-auto min-h-full!', - tableClassName: 'font-inter w-full table-auto min-h-full!', - headerRowClassName: 'border-b border-b-gray-200', - headerColumnClassName: - 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', - bodyRowClassName: 'border-b border-b-gray-200', - bodyColumnClassName: - 'px-6 py-3 last:flex last:flex-row last:justify-end', + -
+ ); };