"use client"; import { useState, Ref, useImperativeHandle, useEffect, useCallback } from "react"; import SortableTable, { Pagination, RowsPerPage, SortableTableProps } from "./Table"; import { PrismaClient } from "@repo/db"; import { getData } from "./pagiantedTableActions"; import { cn, useDebounce } from "@repo/shared-components"; export interface PaginatedTableRef { refresh: () => void; } interface PaginatedTableProps extends Omit, "data"> { prismaModel: keyof PrismaClient; stickyHeaders?: boolean; initialRowsPerPage?: number; showSearch?: boolean; getFilter?: (searchTerm: string) => TWhere; include?: Record; strictQuery?: boolean; leftOfSearch?: React.ReactNode; rightOfSearch?: React.ReactNode; leftOfPagination?: React.ReactNode; supressQuery?: boolean; ref?: Ref; } export function PaginatedTable({ prismaModel, initialRowsPerPage = 30, getFilter, showSearch = false, include, ref, strictQuery = false, stickyHeaders = false, leftOfSearch, rightOfSearch, leftOfPagination, supressQuery, ...restProps }: PaginatedTableProps) { const [data, setData] = useState([]); const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage); const [page, setPage] = useState(0); const [total, setTotal] = useState(0); const [searchTerm, setSearchTerm] = useState(""); const [orderBy, setOrderBy] = useState>( restProps.initialOrderBy ? restProps.initialOrderBy.reduce( (acc, sort) => { acc[sort.id] = sort.desc ? "desc" : "asc"; return acc; }, {} as Record, ) : {}, ); const [loading, setLoading] = useState(false); const refreshTableData = useCallback(async () => { if (supressQuery) { setLoading(false); return; } setLoading(true); getData({ model: prismaModel, limit: rowsPerPage, offset: page * rowsPerPage, where: getFilter ? getFilter(searchTerm) : undefined, include, orderBy, select: strictQuery ? restProps.columns .filter( (col): col is { accessorKey: string } => typeof (col as { accessorKey?: unknown }).accessorKey === "string", ) .map((col) => col.accessorKey) .reduce>((acc, key) => { acc[key] = true; return acc; }, {}) : undefined, }) .then((result) => { if (result) { setData(result.data); setTotal(result.total); } }) .finally(() => { setLoading(false); }); }, [ supressQuery, prismaModel, rowsPerPage, page, searchTerm, getFilter, include, orderBy, strictQuery, restProps.columns, ]); useImperativeHandle(ref, () => ({ refresh: () => { refreshTableData(); }, })); // useEffect to show loading spinner useEffect(() => { if (supressQuery) return; setLoading(true); }, [searchTerm, page, rowsPerPage, orderBy, getFilter, setLoading, supressQuery]); useDebounce( () => { refreshTableData(); }, 500, [searchTerm, page, rowsPerPage, orderBy, getFilter], ); return (
{(rightOfSearch || leftOfSearch || showSearch) && (
{leftOfSearch}
{loading && }
{showSearch && ( { setSearchTerm(e.target.value); setPage(0); // Reset to first page on search }} className="input input-bordered w-full max-w-xs justify-end" /> )}
{rightOfSearch}
)}
{leftOfPagination} <>
); }