feat: implement debounced input search functionality and clean up TableList component

This commit is contained in:
faisolavolut 2025-02-27 20:32:29 +07:00
parent af41495643
commit 06387116f7
2 changed files with 52 additions and 46 deletions

View File

@ -10,11 +10,10 @@ import {
SortingState, SortingState,
useReactTable, useReactTable,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import React, { useCallback, useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Button, Label, Table } from "flowbite-react"; import { Button, Label, Table } from "flowbite-react";
import { HiChevronLeft, HiChevronRight, HiPlus } from "react-icons/hi"; import { HiChevronLeft, HiChevronRight, HiPlus } from "react-icons/hi";
import { useLocal } from "@/lib/utils/use-local"; import { useLocal } from "@/lib/utils/use-local";
import { debouncedHandler } from "@/lib/utils/debounceHandler";
import { FaSort, FaSortDown, FaSortUp } from "react-icons/fa6"; import { FaSort, FaSortDown, FaSortUp } from "react-icons/fa6";
import Link from "next/link"; import Link from "next/link";
import { init_column } from "./lib/column"; import { init_column } from "./lib/column";
@ -196,7 +195,6 @@ export const TableList = <T extends object>({
{"Loading..."} {"Loading..."}
</> </>
); );
console.log(local.fieldResultFilter);
if (typeof onCount === "function") { if (typeof onCount === "function") {
const params = await events("onload-param", { const params = await events("onload-param", {
take: 1, take: 1,
@ -273,6 +271,8 @@ export const TableList = <T extends object>({
sort: local.sort, sort: local.sort,
take, take,
paging: local.paging, paging: local.paging,
...local.filter,
...local.fieldResultFilter,
}); });
if (!autoPagination) { if (!autoPagination) {
res = paginateArray(res, take, local.paging); res = paginateArray(res, take, local.paging);
@ -360,6 +360,7 @@ export const TableList = <T extends object>({
paging: 1, paging: 1,
search: local.search, search: local.search,
...local.filter, ...local.filter,
...local.fieldResultFilter,
}); });
const res = await onCount(params); const res = await onCount(params);
local.count = res; local.count = res;
@ -383,6 +384,8 @@ export const TableList = <T extends object>({
sort: local.sort, sort: local.sort,
take, take,
paging: 1, paging: 1,
...local.filter,
...local.fieldResultFilter,
}); });
if (!autoPagination) { if (!autoPagination) {
res = paginateArray(res, take, 1); res = paginateArray(res, take, 1);
@ -552,12 +555,6 @@ export const TableList = <T extends object>({
onStateChange: setState, onStateChange: setState,
debugTable: state.pagination.pageIndex > 2, debugTable: state.pagination.pageIndex > 2,
})); }));
const handleSearch = useCallback(
debouncedHandler(() => {
local.refresh();
}, 1000),
[]
);
return ( return (
<> <>
<div className="tbl-wrapper flex flex-grow flex-col"> <div className="tbl-wrapper flex flex-grow flex-col">
@ -586,7 +583,7 @@ export const TableList = <T extends object>({
<div className="ml-auto flex items-center flex-row gap-x-1"> <div className="ml-auto flex items-center flex-row gap-x-1">
<div className="tbl-search hidden items-center sm:mb-0 sm:flex sm:divide-x sm:divide-gray-100"> <div className="tbl-search hidden items-center sm:mb-0 sm:flex sm:divide-x sm:divide-gray-100">
<form <div
onSubmit={async (e) => { onSubmit={async (e) => {
e.preventDefault(); e.preventDefault();
await local.reload(); await local.reload();
@ -597,19 +594,19 @@ export const TableList = <T extends object>({
</Label> </Label>
<div className="relative lg:w-56"> <div className="relative lg:w-56">
<InputSearch <InputSearch
// className="bg-white search text-xs "
id="users-search" id="users-search"
delay={1000}
name="users-search" name="users-search"
placeholder={`Search`} placeholder={`Search`}
onChange={(e) => { onChange={(e) => {
const value = e.target.value; const value = e.target.value;
local.search = value; local.search = value;
local.render(); local.render();
handleSearch(); local.refresh();
}} }}
/> />
</div> </div>
</form> </div>
</div> </div>
{mode === "table" && filter && local?.fieldFilter?.length ? ( {mode === "table" && filter && local?.fieldFilter?.length ? (
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">

View File

@ -1,39 +1,48 @@
import * as React from "react"; import * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { HiSearch } from "react-icons/hi"; import { HiSearch } from "react-icons/hi";
import debounce from "lodash.debounce";
const InputSearch = React.forwardRef< interface InputSearchProps extends React.ComponentProps<"input"> {
HTMLInputElement, delay?: number;
React.ComponentProps<"input"> }
>(({ className, type, ...props }, ref) => {
return ( const InputSearch = React.forwardRef<HTMLInputElement, InputSearchProps>(
<div className="flex flex-row relative"> ({ className, type, onChange, delay = 100, ...props }, ref) => {
<HiSearch const debouncedLoadOptions = React.useMemo(
className={cx( () =>
"absolute", debounce((event: React.ChangeEvent<HTMLInputElement>) => {
css` if (onChange) onChange(event);
top: 50%; }, delay),
left: 17px; [delay, onChange]
transform: translate(-50%, -50%); );
`
)} return (
/> <div className="flex flex-row relative">
<input <HiSearch className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-500" />
type={type} <input
className={cn( type={type}
"px-3 py-2 flex h-9 w-full rounded-md border border-gray-300 border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className={cn(
className, "px-3 py-2 flex h-9 w-full rounded-md border border-gray-300 bg-transparent text-base shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
css` className,
padding-left: 30px; "pl-10"
` )}
)} ref={ref}
ref={ref} onChange={debouncedLoadOptions}
{...props} onKeyDown={(event) => {
/> if (event.key === "Enter" && onChange) {
</div> event.stopPropagation();
); event.preventDefault(); // Mencegah submit form default jika ada
}); onChange(event as any); // Panggil `onChange` langsung saat Enter ditekan
InputSearch.displayName = "Input"; }
}}
{...props}
/>
</div>
);
}
);
InputSearch.displayName = "InputSearch";
export { InputSearch }; export { InputSearch };