From ee4a470fd22bd41e34ea5239f3c55303f800dfda Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 1 Nov 2025 09:46:06 +0700 Subject: [PATCH] refactor(FE-Storyless): add product and warehouse filters with select inputs --- .../inventory/movement/MovementTable.tsx | 414 ++++++++++-------- 1 file changed, 242 insertions(+), 172 deletions(-) diff --git a/src/components/pages/inventory/movement/MovementTable.tsx b/src/components/pages/inventory/movement/MovementTable.tsx index 61be40f8..0da23826 100644 --- a/src/components/pages/inventory/movement/MovementTable.tsx +++ b/src/components/pages/inventory/movement/MovementTable.tsx @@ -1,24 +1,56 @@ 'use client'; -import { useState } from 'react'; +import { ChangeEventHandler, useState } from 'react'; import useSWR from 'swr'; -import { SortingState } from '@tanstack/react-table'; +import { SortingState, CellContext, ColumnDef } from '@tanstack/react-table'; import Table from '@/components/Table'; -import { useModal } from '@/components/Modal'; -import ConfirmationModal from '@/components/modal/ConfirmationModal'; +import { Icon } from '@iconify/react'; import { Movement } from '@/types/api/inventory/movement'; import { MovementApi } from '@/services/api/inventory'; import { cn } from '@/lib/helper'; +import { Product } from '@/types/api/master-data/product'; +import { Warehouse } from '@/types/api/master-data/warehouse'; import { isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; -import { TableToolbar } from '@/components/table/TableToolbar'; -import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector'; -import { OptionType } from '@/components/input/SelectInput'; +import { OptionType, useSelect } from '@/components/input/SelectInput'; +import Button from '@/components/Button'; +import DebouncedTextInput from '@/components/input/DebouncedTextInput'; +import SelectInput from '@/components/input/SelectInput'; import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions'; -import { TableRowOptions } from '@/components/table/TableRowOptions'; + +const RowOptionsMenu = ({ + type = 'dropdown', + props, +}: { + type: 'dropdown' | 'collapse'; + props: CellContext; +}) => { + return ( +
+ +
+ ); +}; const MovementTable = () => { const { @@ -28,17 +60,39 @@ const MovementTable = () => { setPageSize, toQueryString: getTableFilterQueryString, } = useTableFilter({ - initial: { search: '' }, - paramMap: { page: 'page', pageSize: 'limit' }, + initial: { + search: '', + product: '', + warehouse: '', + }, + paramMap: { + page: 'page', + pageSize: 'limit', + product: 'product_id', + warehouse: 'warehouse_id', + }, }); const [sorting, setSorting] = useState([]); - const [selectedMovement, setSelectedMovement] = useState< - Movement | undefined - >(undefined); - const [isDeleteLoading, setIsDeleteLoading] = useState(false); - const deleteModal = useModal(); + const { + setInputValue: setProductInputValue, + options: productOptions, + isLoadingOptions: isLoadingProductOptions, + } = useSelect('/products', 'id', 'name'); + + const { + setInputValue: setWarehouseInputValue, + options: warehouseOptions, + isLoadingOptions: isLoadingWarehouseOptions, + } = useSelect('/warehouses', 'id', 'name'); + + const [selectedProduct, setSelectedProduct] = useState( + null + ); + const [selectedWarehouse, setSelectedWarehouse] = useState( + null + ); const { data: movements, @@ -49,9 +103,8 @@ const MovementTable = () => { MovementApi.getAllFetcher ); - const searchChangeHandler = (e: React.ChangeEvent) => { + const searchChangeHandler: ChangeEventHandler = (e) => { updateFilter('search', e.target.value); - setPage(1); }; const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { @@ -60,167 +113,184 @@ const MovementTable = () => { setPage(1); }; - const confirmationModalDeleteClickHandler = async () => { - setIsDeleteLoading(true); - try { - await MovementApi.delete(selectedMovement?.id as number); - refreshMovements(); - deleteModal.closeModal(); - } finally { - setIsDeleteLoading(false); - } + const productChangeHandler = (val: OptionType | OptionType[] | null) => { + setSelectedProduct(val as OptionType); + updateFilter('product', val ? ((val as OptionType).value as string) : ''); }; + const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => { + setSelectedWarehouse(val as OptionType); + updateFilter('warehouse', val ? ((val as OptionType).value as string) : ''); + }; + + const movementColumns: ColumnDef[] = [ + { + header: '#', + cell: (props) => + tableFilterState.pageSize * (tableFilterState.page - 1) + + props.row.index + + 1, + }, + { + accessorFn: (row) => row.source_warehouse?.name, + header: 'Gudang Asal', + }, + { + accessorFn: (row) => row.destination_warehouse?.name, + header: 'Gudang Tujuan', + }, + { + accessorKey: 'transfer_reason', + header: 'Catatan', + }, + { + accessorKey: 'transfer_date', + header: 'Tanggal', + cell: (props) => + new Date(props.row.original.transfer_date).toLocaleDateString('id-ID'), + }, + { + accessorFn: (row) => { + const totalCost = row.deliveries?.reduce( + (sum, d) => sum + (d.shipping_cost_total || 0), + 0 + ); + return totalCost?.toLocaleString('id-ID'); + }, + header: 'Biaya Pengiriman', + }, + { + 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; + + return ( + <> + {currentPageSize > 2 && ( + + + + )} + + {currentPageSize <= 2 && ( + + + + )} + + ); + }, + }, + ]; + return ( -
-
- +
+
+
+
+ +
+ + +
+ +
+ + + + + +
+
+ + + data={isResponseSuccess(movements) ? movements?.data : []} + columns={movementColumns} + pageSize={tableFilterState.pageSize} + page={isResponseSuccess(movements) ? movements?.meta?.page : 0} + totalItems={ + isResponseSuccess(movements) ? movements?.meta?.total_results : 0 + } + onPageChange={setPage} + isLoading={isLoading} + sorting={sorting} + setSorting={setSorting} + className={{ + containerClassName: cn({ + 'mb-20': + isResponseSuccess(movements) && movements?.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', }} - search={{ - value: tableFilterState.search, - onChange: searchChangeHandler, - placeholder: 'Cari Movement', - }} - /> -
- - - data={isResponseSuccess(movements) ? movements?.data : []} - columns={[ - { - header: '#', - cell: (props) => - tableFilterState.pageSize * (tableFilterState.page - 1) + - props.row.index + - 1, - }, - { - accessorFn: (row) => row.source_warehouse?.name, - header: 'Gudang Asal', - }, - { - accessorFn: (row) => row.destination_warehouse?.name, - header: 'Gudang Tujuan', - }, - { - accessorKey: 'transfer_reason', - header: 'Catatan', - }, - { - accessorKey: 'transfer_date', - header: 'Tanggal', - cell: (props) => - new Date(props.row.original.transfer_date).toLocaleDateString( - 'id-ID' - ), - }, - { - accessorFn: (row) => { - const totalCost = row.deliveries?.reduce( - (sum, d) => sum + (d.shipping_cost_total || 0), - 0 - ); - return totalCost?.toLocaleString('id-ID'); - }, - header: 'Biaya Pengiriman', - }, - { - 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 deleteClickHandler = () => { - setSelectedMovement(props.row.original); - deleteModal.openModal(); - }; - - return ( - <> - {currentPageSize > 2 && ( - - - - )} - - {currentPageSize <= 2 && ( - - - - )} - - ); - }, - }, - ]} - pageSize={tableFilterState.pageSize} - page={isResponseSuccess(movements) ? movements?.meta?.page : 0} - totalItems={ - isResponseSuccess(movements) ? movements?.meta?.total_results : 0 - } - onPageChange={setPage} - isLoading={isLoading} - sorting={sorting} - setSorting={setSorting} - className={{ - containerClassName: cn({ - 'mb-20': - isResponseSuccess(movements) && movements?.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', - }} - /> - - -
+ ); };