diff --git a/src/app/purchase/page.tsx b/src/app/purchase/page.tsx new file mode 100644 index 00000000..dc25a99d --- /dev/null +++ b/src/app/purchase/page.tsx @@ -0,0 +1,11 @@ +import PurchaseTable from '@/components/pages/purchase/PurchaseTable'; + +const Purchase = () => { + return ( +
+ +
+ ); +}; + +export default Purchase; diff --git a/src/components/pages/purchase/PurchaseTable.tsx b/src/components/pages/purchase/PurchaseTable.tsx new file mode 100644 index 00000000..0ff9b12c --- /dev/null +++ b/src/components/pages/purchase/PurchaseTable.tsx @@ -0,0 +1,446 @@ +'use client'; + +import { ChangeEventHandler, useState } from 'react'; +import useSWR from 'swr'; +import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; +import toast from 'react-hot-toast'; + +import { Icon } from '@iconify/react'; +import Table from '@/components/Table'; +import DebouncedTextInput from '@/components/input/DebouncedTextInput'; +import Button from '@/components/Button'; +import { useModal } from '@/components/Modal'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; +import SelectInput, { + OptionType, + useSelect, +} from '@/components/input/SelectInput'; +import RowDropdownOptions from '@/components/table/RowDropdownOptions'; +import RowCollapseOptions from '@/components/table/RowCollapseOptions'; +import TextInput from '@/components/input/TextInput'; +import CheckboxInput from '@/components/input/CheckboxInput'; +import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; + +import { cn, formatDate, formatCurrency } from '@/lib/helper'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { ROWS_OPTIONS } from '@/config/constant'; +import { Purchase } from '@/types/api/purchase/purchase'; +import { PurchaseApi } from '@/services/api/purchase'; + +const RowOptionsMenu = ({ + type = 'dropdown', + props, + deleteClickHandler, +}: { + type: 'dropdown' | 'collapse'; + props: CellContext; + deleteClickHandler: () => void; +}) => { + return ( + + + + + + + + ); +}; + +const PurchaseTable = () => { + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { + search: '', + poDate: '', + supplier: '', + }, + paramMap: { + page: 'page', + pageSize: 'limit', + poDate: 'po_date', + supplier: 'supplier', + }, + }); + + const { + data: purchaseRequests, + isLoading, + mutate: refreshPurchaseRequests, + } = useSWR( + `${PurchaseApi.basePath}${getTableFilterQueryString()}`, + PurchaseApi.getAllFetcher + ); + + // Modal hooks + const deleteModal = useModal(); + + // Supplier modal + const { + setInputValue: setSupplierInputValue, + options: supplierOptions, + isLoadingOptions: isLoadingSupplierOptions, + } = useSelect('/suppliers', 'id', 'name'); + + // Supplier value + const [selectedSupplier, setSelectedSupplier] = useState( + null + ); + + // Modal loading state + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + // Selected purchase request for operations + const [selectedPurchase, setSelectedPurchase] = useState( + null + ); + + const [sorting, setSorting] = useState([]); + const [rowSelection, setRowSelection] = useState>({}); + const selectedRowIds = Object.keys(rowSelection).map((item) => + parseInt(item) + ); + + const purchaseColumns: ColumnDef[] = [ + { + id: 'select', + header: ({ table }) => ( +
+ +
+ ), + cell: ({ row }) => ( +
+ +
+ ), + }, + { + header: 'No. PR/PO', + cell: (props) => { + const { pr_number, po_number } = props.row.original; + return po_number ? po_number : pr_number; + }, + }, + { + accessorKey: 'supplier', + header: 'Vendor', + cell: (props) => props.row.original.supplier.name, + }, + { + accessorKey: 'created_user', + header: 'Nama Pengaju', + cell: (props) => props.row.original.created_user.name, + }, + { + accessorKey: 'po_date', + header: 'Tgl. PO', + cell: (props) => + props.row.original.po_date + ? formatDate(props.row.original.po_date, 'DD-MMM-YYYY') + : '-', + }, + { + accessorKey: 'due_date', + header: 'Jatuh Tempo', + cell: (props) => + props.row.original.due_date + ? formatDate(props.row.original.due_date, 'DD-MMM-YYYY') + : '-', + }, + { + header: 'Aging', + cell: (props) => { + if (!props.row.original.po_date) return '-'; + const poDate = new Date(props.row.original.po_date); + const today = new Date(); + const diffTime = Math.abs(today.getTime() - poDate.getTime()); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + return `${diffDays} hari`; + }, + }, + { + accessorKey: 'grand_total', + header: 'Total (Rp.)', + cell: (props) => formatCurrency(props.row.original.grand_total), + }, + { + 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 = () => { + setSelectedPurchase(props.row.original); + deleteModal.openModal(); + }; + + return ( + <> + {currentPageSize > 2 && ( + + + + )} + + {currentPageSize <= 2 && ( + + + + )} + + ); + }, + }, + ]; + + // Modal confirm click handler + const confirmationModalDeleteClickHandler = async () => { + setIsDeleteLoading(true); + + try { + await PurchaseApi.delete(selectedPurchase?.id as number); + refreshPurchaseRequests(); + deleteModal.closeModal(); + toast.success('Berhasil menghapus data permintaan pembelian!'); + } catch (error) { + toast.error('Gagal menghapus data permintaan pembelian!'); + } + + 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 poDateChangeHandler: ChangeEventHandler = (e) => { + updateFilter('poDate', e.target.value); + }; + + const supplierChangeHandler = (val: OptionType | OptionType[] | null) => { + setSelectedSupplier(val as OptionType); + updateFilter('supplier', val ? ((val as OptionType).value as string) : ''); + }; + + return ( + <> +
+
+
+
+ + + {selectedRowIds.length > 0 && ( + + )} +
+ + +
+ +
+
+ + + +
+ + +
+
+ + + data={ + isResponseSuccess(purchaseRequests) ? purchaseRequests?.data : [] + } + columns={purchaseColumns} + pageSize={tableFilterState.pageSize} + page={ + isResponseSuccess(purchaseRequests) + ? purchaseRequests?.meta?.page + : 0 + } + totalItems={ + isResponseSuccess(purchaseRequests) + ? purchaseRequests?.meta?.total_results + : 0 + } + onPageChange={setPage} + isLoading={isLoading} + sorting={sorting} + setSorting={setSorting} + rowSelection={rowSelection} + setRowSelection={setRowSelection} + className={{ + containerClassName: cn({ + 'mb-20': + isResponseSuccess(purchaseRequests) && + purchaseRequests?.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', + }} + /> +
+ + + + ); +}; + +export default PurchaseTable;