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

View File

@ -44,17 +44,17 @@ function DashboardLayout() {
}; };
return isAuthenticated ? ( return isAuthenticated ? (
<div className="flex flex-col h-screen"> <div className="flex flex-col w-full h-screen overflow-hidden">
{/* Header */} {/* Header */}
<AppHeader toggle={toggle} openNavbar={openNavbar} /> <AppHeader toggle={toggle} openNavbar={openNavbar} />
{/* Main Content Area */} {/* Main Content Area */}
<div className="flex flex-1 overflow-hidden"> <div className="flex h-full w-screen overflow-hidden">
{/* Sidebar */} {/* Sidebar */}
<AppNavbar /> <AppNavbar />
{/* Main Content */} {/* 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 /> <Outlet />
</main> </main>
</div> </div>