fix: search function, button and pagination
This commit is contained in:
parent
20553f6497
commit
78ab7ca655
|
|
@ -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
|
||||
) => {
|
||||
if (property === true) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const addQuery = () => {
|
||||
navigate({ to: `${window.location.pathname}`, search: { create: true } });
|
||||
}
|
||||
|
||||
if (property === true) {
|
||||
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>
|
||||
</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>
|
||||
</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>
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{getPaginationItems().map((item) => (
|
||||
<PaginationItem key={item.key}>
|
||||
{item}
|
||||
</PaginationItem>
|
||||
))}
|
||||
|
||||
{totalPages > 1 && currentPage < totalPages && (
|
||||
<PaginationItem>
|
||||
<PaginationEllipsis />
|
||||
</PaginationItem>
|
||||
)}
|
||||
|
||||
<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,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// 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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user