/* eslint-disable no-mixed-spaces-and-tabs */ import { Button, Card, Flex, Pagination, Select, Stack, Text, TextInput, Title, } from "@mantine/core"; import { Link } from "@tanstack/react-router"; import React, { ReactNode, useState } from "react"; import { TbPlus, TbSearch } from "react-icons/tb"; import DashboardTable from "./DashboardTable"; import { ColumnDef, getCoreRowModel, useReactTable, } from "@tanstack/react-table"; import { QueryKey, UseQueryOptions, keepPreviousData, useQuery, } from "@tanstack/react-query"; import { useDebouncedCallback } from "@mantine/hooks"; type PaginatedResponse> = { data: Array; _metadata: { currentPage: number; totalPages: number; perPage: number; totalItems: number; }; }; //ref: https://x.com/TkDodo/status/1491451513264574501 type Props< TQueryKey extends QueryKey, TQueryFnData extends Record, TError, TData extends Record = TQueryFnData, > = { title: string; createButton?: string | true | React.ReactNode; modals?: React.ReactNode[]; queryOptions: ( page: number, limit: number, q?: string ) => UseQueryOptions< PaginatedResponse, TError, PaginatedResponse, TQueryKey >; columnDefs: ColumnDef[]; }; /** * Creates a "Create New" button or returns the provided React node. * * @param property - The property that determines the type of button to create. It can be a boolean, string, or React node. * @returns The create button element. */ const createCreateButton = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any property: Props["createButton"] = true ) => { if (property === true) { return ( ); } else if (typeof property === "string") { return ( ); } else { return property; } }; /** * PageTemplate component for displaying a paginated table with search and filter functionality. * @param props - The properties object. * @returns The rendered PageTemplate component. */ export default function PageTemplate< TQueryKey extends QueryKey, TQueryFnData extends Record, TError, TData extends Record = TQueryFnData, >(props: Props) { const [filterOptions, setFilterOptions] = useState({ page: 0, limit: 10, q: "", }); // const [deboucedSearchQuery] = useDebouncedValue(filterOptions.q, 500); const query = useQuery({ ...(typeof props.queryOptions === "function" ? props.queryOptions( filterOptions.page, filterOptions.limit, filterOptions.q ) : props.queryOptions), placeholderData: keepPreviousData, }); const table = useReactTable({ data: query.data?.data ?? [], columns: props.columnDefs, getCoreRowModel: getCoreRowModel(), defaultColumn: { cell: (props) => {props.getValue() as ReactNode}, }, }); /** * Handles the change in search query input with debounce. * * @param value - The new search query value. */ const handleSearchQueryChange = useDebouncedCallback((value: string) => { setFilterOptions((prev) => ({ page: 0, limit: prev.limit, q: value, })); }, 500); /** * Handles the change in page number. * * @param page - The new page number. */ const handlePageChange = (page: number) => { setFilterOptions((prev) => ({ page: page - 1, limit: prev.limit, q: prev.q, })); }; return ( {props.title} {/* Top Section */} {createCreateButton(props.createButton)} {/* Table Functionality */}
{/* Search */}
} value={filterOptions.q} onChange={(e) => handleSearchQueryChange(e.target.value) } placeholder="Search..." />
{/* Table */} {/* Pagination */} {query.data && (