diff --git a/components/tablelist/TableBetter.tsx b/components/tablelist/TableBetter.tsx new file mode 100644 index 0000000..1ae1114 --- /dev/null +++ b/components/tablelist/TableBetter.tsx @@ -0,0 +1,959 @@ +"use client"; +import { + ColumnDef, + ColumnResizeDirection, + ColumnResizeMode, + flexRender, + getCoreRowModel, + getPaginationRowModel, + getSortedRowModel, + SortingState, + useReactTable, +} from "@tanstack/react-table"; +import React, { useCallback, useEffect, useState } from "react"; +import { Button, Label, Table } from "flowbite-react"; +import { HiChevronLeft, HiChevronRight, HiPlus } from "react-icons/hi"; +import { useLocal } from "@/lib/utils/use-local"; +import { debouncedHandler } from "@/lib/utils/debounceHandler"; +import { FaChevronUp } from "react-icons/fa6"; +import Link from "next/link"; +import { init_column } from "./lib/column"; +import { toast } from "sonner"; +import { Loader2, Sticker } from "lucide-react"; +import { InputSearch } from "../ui/input-search"; +import { FaChevronDown } from "react-icons/fa"; +import get from "lodash.get"; +import { Checkbox } from "../ui/checkbox"; +import { getNumber } from "@/lib/utils/getNumber"; +import { formatMoney } from "../form/field/TypeInput"; +import { cloneFM } from "@/lib/utils/cloneFm"; +import { ResizableBox } from "react-resizable"; +export const TableEditBetter: React.FC = ({ + name, + column, + align = "center", + onLoad, + take = 20, + header, + disabledPagination, + disabledHeader, + disabledHeadTable, + hiddenNoRow, + disabledHoverRow, + onInit, + onCount, + fm, + mode, + feature, + onChange, + delete_name, +}) => { + const [data, setData] = useState([]); + const [columns, setColumns] = useState([] as any[]); + const sideLeft = + typeof header?.sideLeft === "function" ? header.sideLeft : null; + const sideRight = + typeof header?.sideRight === "function" ? header.sideRight : null; + const checkbox = + Array.isArray(feature) && feature?.length + ? feature.includes("checkbox") + : false; + const local = useLocal({ + table: null as any, + data: [] as any[], + dataForm: [] as any[], + listData: [] as any[], + sort: {} as any, + search: null as any, + count: 0 as any, + addRow: (row: any) => { + const data = fm.data?.[name] || []; + data.push(row); + fm.data[name] = data; + fm.render(); + local.data = fm.data[name]; + local.render(); + }, + selection: { + all: false, + partial: [] as any[], + }, + renderRow: (row: any) => { + setData((prev) => [...prev, row]); + local.data = data; + local.render(); + }, + removeRow: (row: any) => { + // setData((prev) => prev.filter((item) => item !== row)); // Update state lokal + // local.data = local.data.filter((item: any) => item !== row); // Hapus row dari local.data + // local.render(); // Panggil render untuk memperbarui UI + console.log("HALOO"); + const data = fm.data?.[name] || []; + // data.push(row); + if (delete_name) { + const ids: any[] = Array.isArray(fm.data?.[delete_name]) + ? fm.data?.deleted_line_ids + : []; + if (row?.id) { + ids.push(row.id); + } + fm.data[delete_name] = ids; + } + fm.data[name] = data.filter((item: any) => item !== row); + fm.render(); + local.data = fm.data[name]; + local.render(); + console.log({ fm }); + // local.data = fm.data[name]; + // local.render(); + }, + reload: async () => { + toast.info( + <> + + {"Loading..."} + + ); + if (Array.isArray(onLoad)) { + local.data = onLoad; + local.render(); + setData(onLoad); + } else { + const res: any = onLoad({ + search: local.search, + sort: local.sort, + take, + paging: 1, + }); + if (res instanceof Promise) { + res.then((e) => { + local.data = e; + cloneListFM(e); + local.render(); + setData(e); + setTimeout(() => { + toast.dismiss(); + }, 2000); + }); + } else { + local.data = res; + cloneListFM(res); + local.render(); + setData(res); + setTimeout(() => { + toast.dismiss(); + }, 2000); + } + } + }, + }); + // const cloneListFM = (data: any[]) => { + // if (mode === "form") { + // local.dataForm = data.map((e: any) => cloneFM(fm, e)); + // local.render(); + // } + // }; + useEffect(() => { + const defaultColumns: any[] = init_column(column); + const col = defaultColumns?.length + ? defaultColumns.map((e: any) => { + return { + ...e, + width: e?.width || "auto", + }; + }) + : ([] as any[]); + setColumns(col); + local.data = fm?.data[name] || []; + local.render(); + console.log(columns); + }, []); + + const handleResize = (index: any, width: any) => { + setColumns((prevColumns: any) => { + const updatedColumns = [...prevColumns]; + updatedColumns[index].width = width; + return updatedColumns; + }); + }; + return ( + <> +
+ {!disabledHeader ? ( +
+
+
+
{sideLeft ? sideLeft(local) : <>}
+
+
+
+
{sideRight ? sideRight(local) : <>}
+
+
+ ) : ( + <> + )} + +
+
+
+
+ th:first-child { + width: 20px !important; /* Atur lebar sesuai kebutuhan */ + text-align: center; + } + .table-row-element > td:first-child { + width: 20px !important; /* Atur lebar sesuai kebutuhan */ + text-align: center; + } + ` + )} + > + {!disabledHeadTable ? ( + + + {columns.map((col, idx) => { + return ( + + ); + })} + + + ) : ( + <> + )} + + {local.data.map((row: any, index: any) => { + const fm_row = cloneFM(fm, row); + return ( + + {columns.map((col, idx) => { + const param = { + row: row, + name: col?.name, + idx, + tbl: local, + fm_row: fm_row, + onChange, + }; + const renderData = + typeof col?.renderCell === "function" ? ( + col.renderCell(param) + ) : ( + <>No Column + ); + return ( + + ); + })} + + ); + })} + +
+
+ {col?.name} +
+
+ {renderData} +
+
+
+ {!local?.data?.length && ( +
+
+ +
No Data
+
+
+ )} +
+
+
+ + ); + return ( + <> +
+ {!disabledHeader ? ( +
+
+
+
+ {sideLeft ? ( + sideLeft(local) + ) : ( + <> + + + + + )} +
+
+
+ +
+
+
{ + e.preventDefault(); + await local.reload(); + }} + > + +
+ { + const value = e.target.value; + local.search = value; + local.render(); + handleSearch(); + }} + /> +
+
+
+
{sideRight ? sideRight(local) : <>}
+
+
+ ) : ( + <> + )} + +
+
+
+
+ th:first-child { + width: 20px !important; /* Atur lebar sesuai kebutuhan */ + text-align: center; + } + .table-row-element > td:first-child { + width: 20px !important; /* Atur lebar sesuai kebutuhan */ + text-align: center; + } + ` + )} + > + {!disabledHeadTable ? ( + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header, index) => { + const name = header.column.id; + const col = column.find( + (e: any) => e?.name === name + ); + const isSort = + name === "select" + ? false + : typeof col?.sortable === "boolean" + ? col.sortable + : true; + const resize = + name === "select" + ? false + : typeof col?.resize === "boolean" + ? col.resize + : true; + return ( + + ); + })} + + ))} + + ) : ( + <> + )} + + + {table.getRowModel().rows.map((row, idx) => { + const fm_row = + mode === "form" ? local.dataForm?.[idx] : null; + return ( + td { + vertical-align: ${align}; + } + `, + "border-none" + )} + > + {row.getVisibleCells().map((cell: any) => { + const ctx = cell.getContext(); + const param = { + row: row.original, + name: get(ctx, "column.columnDef.accessorKey"), + cell, + idx, + tbl: local, + fm_row: fm_row, + onChange, + }; + const head = column.find( + (e: any) => + e?.name === + get(ctx, "column.columnDef.accessorKey") + ); + const renderData = + typeof head?.renderCell === "function" + ? head.renderCell(param) + : flexRender( + cell.column.columnDef.cell, + cell.getContext() + ); + return ( + + {renderData} + + ); + })} + + ); + })} + +
+
{ + if (isSort) { + const sort = local?.sort?.[name]; + const mode = + sort === "desc" + ? null + : sort === "asc" + ? "desc" + : "asc"; + local.sort = mode + ? { + [name]: mode, + } + : {}; + local.render(); + + local.reload(); + } + }} + className={cx( + "flex flex-grow flex-row flex-grow select-none items-center flex-row text-base text-nowrap", + isSort ? " cursor-pointer" : "" + )} + > +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} +
+ {isSort ? ( +
+ + +
+ ) : ( + <> + )} +
+ + {headerGroup.headers.length !== index + 1 ? ( +
+ header.column.resetSize(), + onMouseDown: header.getResizeHandler(), + onTouchStart: header.getResizeHandler(), + className: cx( + `resizer bg-[#b3c9fe] cursor-e-resize ${ + table.options.columnResizeDirection + } ${ + header.column.getIsResizing() + ? "isResizing" + : "" + }`, + css` + width: 1px; + cursor: e-resize !important; + ` + ), + style: { + transform: + columnResizeMode === "onEnd" && + header.column.getIsResizing() + ? `translateX(${ + (table.options + .columnResizeDirection === + "rtl" + ? -1 + : 1) * + (table.getState() + .columnSizingInfo + .deltaOffset ?? 0) + }px)` + : "", + }, + }} + >
+ ) : null} +
+
+
+ {!hiddenNoRow && !table.getRowModel().rows?.length && ( +
+
+ +
No Data
+
+
+ )} +
+
+ table.nextPage()} + onPrevPage={() => table.previousPage()} + disabledNextPage={!table.getCanNextPage()} + disabledPrevPage={!table.getCanPreviousPage()} + page={table.getState().pagination.pageIndex + 1} + setPage={(page: any) => { + setPagination({ + pageIndex: page, + pageSize: 20, + }); + }} + countPage={table.getPageCount()} + countData={local.data.length} + take={take} + onChangePage={(page: number) => { + table.setPageIndex(page); + }} + /> +
+ + ); +}; + +export const Pagination: React.FC = ({ + onNextPage, + onPrevPage, + disabledNextPage, + disabledPrevPage, + page, + count, + list, + setPage, + onChangePage, +}) => { + const local = useLocal({ + page: 1 as any, + pagination: [] as any, + }); + useEffect(() => { + local.page = page; + local.pagination = getPagination(page, Math.ceil(count / 20)); + local.render(); + }, [page, count]); + return ( +
+
+ Showing {local.page * 20 - 19} to{" "} + {list.data?.length >= 20 + ? local.page * 20 + : local.page === 1 && Math.ceil(count / 20) === 1 + ? list.data?.length + : local.page * 20 - 19 + list.data?.length}{" "} + of {formatMoney(getNumber(count))} results +
+
+
+ +
+
+
+
+
{ + if (!disabledPrevPage) { + onPrevPage(); + } + }} + className={cx( + "flex flex-row items-center gap-x-2 justify-center rounded p-1 ", + disabledPrevPage + ? "text-gray-200 border-gray-200 border px-2" + : "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border px-2" + )} + > + + Previous +
+
{ + if (!disabledNextPage) { + onNextPage(); + } + }} + className={cx( + "flex flex-row items-center gap-x-2 justify-center rounded p-1 ", + disabledNextPage + ? "text-gray-200 border-gray-200 border px-2" + : "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border px-2" + )} + > + Next + +
+
+
+
+ ); +}; +export const PaginationPage: React.FC = ({ + onNextPage, + onPrevPage, + disabledNextPage, + disabledPrevPage, + page, + count, + list, + take, + setPage, + onChangePage, +}) => { + const local = useLocal({ + page: 1 as any, + pagination: [] as any, + }); + useEffect(() => { + local.page = page; + local.pagination = getPagination(page, Math.ceil(count / take)); + local.render(); + }, [page, count]); + return ( +
+
+
+
{ + if (!disabledPrevPage) { + onPrevPage(); + } + }} + className={cx( + "flex flex-row items-center gap-x-2 justify-center rounded-full p-2 text-md", + disabledPrevPage + ? "text-gray-200 border-gray-200 border " + : "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border " + )} + > + +
+
+
+ +
+
+
{ + if (!disabledNextPage) { + onNextPage(); + } + }} + className={cx( + "flex flex-row items-center gap-x-2 justify-center rounded-full p-2 ", + disabledNextPage + ? "text-gray-200 border-gray-200 border" + : "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border " + )} + > + +
+
+
+
+ ); +}; + +const getPagination = (currentPage: number, totalPages: number) => { + const pagination: { label: string; active: boolean }[] = []; + const maxVisible = 5; // Jumlah maksimal elemen yang ditampilkan + const halfRange = Math.floor((maxVisible - 3) / 2); + + if (totalPages <= maxVisible) { + // Jika total halaman lebih kecil dari batas, tampilkan semua halaman + for (let i = 1; i <= totalPages; i++) { + pagination.push({ label: i.toString(), active: i === currentPage }); + } + } else { + pagination.push({ label: "1", active: currentPage === 1 }); // Halaman pertama selalu ada + + if (currentPage > halfRange + 2) { + pagination.push({ label: "...", active: false }); // Awal titik-titik + } + + const startPage = Math.max(2, currentPage - halfRange); + const endPage = Math.min(totalPages - 1, currentPage + halfRange); + + for (let i = startPage; i <= endPage; i++) { + pagination.push({ label: i.toString(), active: i === currentPage }); + } + + if (currentPage < totalPages - halfRange - 1) { + pagination.push({ label: "...", active: false }); // Akhir titik-titik + } + + pagination.push({ + label: totalPages.toString(), + active: currentPage === totalPages, + }); // Halaman terakhir selalu ada + } + + return pagination; +}; diff --git a/components/tablelist/lib/column.ts b/components/tablelist/lib/column.ts index 32c0c97..3e73496 100644 --- a/components/tablelist/lib/column.ts +++ b/components/tablelist/lib/column.ts @@ -1,4 +1,4 @@ -export const init_column = (data: any[]) => { +export const init_column = (data: any[]): any[] => { return data.length ? data.map((e) => { return { diff --git a/package.json b/package.json index f13d63e..0ea7944 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "next": "15.0.3", "react-colorful": "^5.6.1", "react-icons": "^5.3.0", + "react-resizable": "^3.0.5", "react-resizable-panels": "^2.1.7", "react-slick": "^0.30.2", "sonner": "^1.7.0", @@ -61,6 +62,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/react-resizable": "^3.0.8", "@types/react-slick": "^0.23.13", "@types/tinycolor2": "^1.4.6", "eslint": "^8",