fix: search function, button and pagination

This commit is contained in:
Sukma Gladys 2024-09-20 10:42:02 +07:00
parent 20553f6497
commit 78ab7ca655
2 changed files with 111 additions and 94 deletions

View File

@ -13,7 +13,7 @@ import {
keepPreviousData,
useQuery,
} from "@tanstack/react-query";
import { useDebouncedCallback } from "@mantine/hooks";
import { useDebouncedValue } from "@mantine/hooks";
import { Button } from "@/shadcn/components/ui/button";
import { useNavigate } from "@tanstack/react-router";
import { Card } from "@/shadcn/components/ui/card";
@ -23,17 +23,16 @@ import {
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/shadcn/components/ui/pagination"
} from "@/shadcn/components/ui/pagination";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/shadcn/components/ui/select"
} from "@/shadcn/components/ui/select";
type PaginatedResponse<T extends Record<string, unknown>> = {
data: Array<T>;
@ -78,32 +77,32 @@ const createCreateButton = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
property: Props<any, any, any>["createButton"] = true
) => {
const navigate = useNavigate();
const addQuery = () => {
navigate({ to: `${window.location.pathname}`, search: { create: true } });
}
if (property === true) {
const navigate = useNavigate();
return (
<Button
type="button"
className="flex bg-white w-full text-black border items-center justify-center hover:bg-muted-foreground hover:text-white"
onClick={() => navigate({ to: `${window.location.pathname}`, search: { create: true } })}
className="gap-2"
variant={"outline"}
onClick={addQuery}
>
<span className="flex items-center justify-between gap-2">
Create New
<TbPlus />
</span>
Create New
<TbPlus />
</Button>
);
} else if (typeof property === "string") {
const navigate = useNavigate();
return (
<Button
type="button"
className="flex bg-white w-full text-black border items-center justify-between hover:bg-muted-foreground hover:text-white"
onClick={() => navigate({ to: `${window.location.pathname}`, search: { create: true } })}
className="gap-2"
variant={"outline"}
onClick={addQuery}
>
<span className="flex items-center justify-between gap-2">
{property}
<TbPlus />
</span>
{property}
<TbPlus />
</Button>
);
} else {
@ -120,56 +119,87 @@ const createCreateButton = (
const CustomPagination = ({
currentPage,
totalPages,
onPageChange,
hasNextPage,
onChange,
}: {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
hasNextPage: boolean;
onChange: (page: number) => void;
}) => {
const getPaginationItems = () => {
let items = [];
// Determine start and end pages
let startPage =
currentPage == totalPages && currentPage > 3 ?
Math.max(1, currentPage - 2) :
Math.max(1, currentPage - 1);
let endPage =
currentPage == 1 ?
Math.min(totalPages, currentPage + 2) :
Math.min(totalPages, currentPage + 1);
// Add ellipsis if needed
if (startPage > 2) {
items.push(<PaginationEllipsis key="start-ellipsis" />);
}
// Add page numbers
for (let i = startPage; i <= endPage; i++) {
items.push(
<Button className='cursor-pointer' key={i} onClick={() => onChange(i)} variant={currentPage == i ? "outline" : "ghost"}>
{i}
</Button>
);
}
// Add ellipsis after
if (endPage < totalPages - 1) {
items.push(<PaginationEllipsis key="end-ellipsis" />);
}
// Add last page
if (endPage < totalPages) {
items.push(
<Button className='cursor-pointer' key={totalPages} onClick={() => onChange(totalPages)} variant={"ghost"}>
{totalPages}
</Button>
);
}
if (currentPage > 2) {
items.unshift(
<Button className='cursor-pointer' key={1} onClick={() => onChange(1)} variant={"ghost"}>
1
</Button>
);
}
return items;
};
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationContent className="flex flex-col items-center gap-4 md:flex-row">
<PaginationItem className="w-full md:w-auto">
<PaginationPrevious
onClick={() => onPageChange(currentPage - 1)}
className={`${currentPage === 1
? 'bg-white text-muted-foreground cursor-not-allowed'
: 'bg-white text-black hover:bg-muted hover:text-black'
}`}
aria-disabled={currentPage === 1}
onClick={() => onChange(Math.max(1, currentPage - 1))}
aria-disabled={currentPage - 1 == 0 ? true : false}
className="w-full gap-2 md:w-auto"
>
Previous
</PaginationPrevious>
</PaginationItem>
{Array.from({ length: totalPages }, (_, index) => index + 1).map((page) => (
<PaginationItem key={page}>
<PaginationLink
href="#"
onClick={() => onPageChange(page)}
className={page === currentPage ? 'border text-black' : ''}
>
{page}
</PaginationLink>
</PaginationItem>
))}
{totalPages > 1 && currentPage < totalPages && (
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
)}
<PaginationItem>
<div className="flex flex-wrap justify-center gap-2">
{getPaginationItems().map((item) => (
<PaginationItem key={item.key}>
{item}
</PaginationItem>
))}
</div>
<PaginationItem className="w-full md:w-auto">
<PaginationNext
onClick={() => onPageChange(currentPage + 1)}
className={`${!hasNextPage || currentPage === totalPages
? 'bg-white text-muted-foreground cursor-not-allowed'
: 'bg-white text-black hover:bg-muted hover:text-black'
}`}
aria-disabled={!hasNextPage || currentPage === totalPages}
onClick={() => onChange(Math.min(totalPages, currentPage + 1))}
aria-disabled={currentPage == totalPages ? true : false}
className="w-full gap-2 md:w-auto"
>
Next
</PaginationNext>
@ -197,14 +227,14 @@ export default function PageTemplate<
q: "",
});
// const [deboucedSearchQuery] = useDebouncedValue(filterOptions.q, 500);
const [debouncedSearchQuery] = useDebouncedValue(filterOptions.q, 500);
const query = useQuery({
...(typeof props.queryOptions === "function"
? props.queryOptions(
filterOptions.page,
filterOptions.limit,
filterOptions.q
debouncedSearchQuery
)
: props.queryOptions),
placeholderData: keepPreviousData,
@ -216,7 +246,7 @@ export default function PageTemplate<
getCoreRowModel: getCoreRowModel(),
defaultColumn: {
cell: (props) => (
<span className="text-base font-medium text-muted-foreground">
<span>
{props.getValue() as ReactNode}
</span>
),
@ -228,13 +258,13 @@ export default function PageTemplate<
*
* @param value - The new search query value.
*/
const handleSearchQueryChange = useDebouncedCallback((value: string) => {
const handleSearchQueryChange = (value: string) => {
setFilterOptions((prev) => ({
page: 0,
limit: prev.limit,
q: value,
}));
}, 500);
};
/**
* Handles the change in page number.
@ -242,42 +272,31 @@ export default function PageTemplate<
* @param page - The new page number.
*/
const handlePageChange = (page: number) => {
if (page >= 1 && page <= (query.data?._metadata.totalPages ?? 1)) {
setFilterOptions((prev) => ({
page: page - 1, // Adjust for zero-based index
limit: prev.limit,
q: prev.q,
}));
}
setFilterOptions((prev) => ({
page: page - 1, // Adjust for zero-based index
limit: prev.limit,
q: prev.q,
}));
};
// Default values when query.data is undefined
const totalPages = query.data?._metadata.totalPages ?? 1;
const hasNextPage = query.data
? filterOptions.page < totalPages - 1
: false;
return (
<div className="flex flex-col space-y-4">
<h1 className="text-2xl font-bold">{props.title}</h1>
<p className="text-2xl font-bold">{props.title}</p>
<Card className="p-4 border-hidden">
{/* Table Functionality */}
<div className="flex flex-col">
{/* Search and Create Button */}
<div className="flex flex-col md:flex-row lg:flex-row pb-4 justify-between">
<div className="flex flex-col md:flex-row lg:flex-row pb-4 justify-between gap-4">
<div className="relative w-full">
<TbSearch
className="absolute top-1/2 left-3 transform -translate-y-1/2 text-muted-foreground pointer-events-none"
/>
<TbSearch className="absolute top-1/2 left-3 transform -translate-y-1/2 text-muted-foreground pointer-events-none" />
<Input
id="search"
name="search"
className="w-full max-w-xs pl-10"
value={filterOptions.q}
onChange={(e) =>
handleSearchQueryChange(e.target.value)
}
onChange={(e) => handleSearchQueryChange(e.target.value)}
placeholder="Search..."
/>
</div>
<div className="flex">
{createCreateButton(props.createButton)}
@ -289,9 +308,9 @@ export default function PageTemplate<
{/* Pagination */}
{query.data && (
<div className="pt-4 flex flex-col md:flex-row lg:flex-row items-center justify-between">
<div className="pt-4 flex flex-col md:flex-row lg:flex-row items-center justify-between gap-2">
<div className="flex flex-row lg:flex-col items-center w-fit gap-2">
<label className="block text-sm font-medium text-muted-foreground">Per Page</label>
<span className="block text-sm font-medium text-muted-foreground">Per Page</span>
<Select
onValueChange={(value) =>
setFilterOptions((prev) => ({
@ -317,9 +336,8 @@ export default function PageTemplate<
</div>
<CustomPagination
currentPage={filterOptions.page + 1}
totalPages={totalPages}
onPageChange={handlePageChange}
hasNextPage={hasNextPage}
totalPages={query.data._metadata.totalPages}
onChange={handlePageChange}
/>
<div className="flex items-center gap-4">
<span className="text-sm text-muted-foreground whitespace-nowrap">
@ -330,7 +348,6 @@ export default function PageTemplate<
)}
</div>
{/* The Modals */}
{props.modals?.map((modal, index) => (
<React.Fragment key={index}>{modal}</React.Fragment>
))}

View File

@ -44,17 +44,17 @@ function DashboardLayout() {
};
return isAuthenticated ? (
<div className="flex flex-col h-screen">
<div className="flex flex-col w-full h-screen overflow-hidden">
{/* Header */}
<AppHeader toggle={toggle} openNavbar={openNavbar} />
{/* Main Content Area */}
<div className="flex flex-1 overflow-hidden">
<div className="flex h-full w-screen overflow-hidden">
{/* Sidebar */}
<AppNavbar />
{/* Main Content */}
<main className={"flex-1 mt-16 p-6 bg-white overflow-auto"}>
<main className="relative w-full mt-16 p-6 bg-white overflow-auto">
<Outlet />
</main>
</div>