+ Paginated Table Search

This commit is contained in:
Nicolas
2025-02-17 01:41:46 +01:00
parent b8357d1d06
commit 6c67a11cab
4 changed files with 112 additions and 52 deletions

View File

@@ -1,6 +1,6 @@
import { DatabaseBackupIcon } from 'lucide-react'; import { DatabaseBackupIcon } from "lucide-react";
import { PaginatedTable } from '../../../_components/PaginatedTable'; import { PaginatedTable } from "../../../_components/PaginatedTable";
import Link from 'next/link'; import Link from "next/link";
export default () => { export default () => {
return ( return (
@@ -9,7 +9,7 @@ export default () => {
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<DatabaseBackupIcon className="w-5 h-5" /> Stationen <DatabaseBackupIcon className="w-5 h-5" /> Stationen
</span> </span>
<Link href={'/admin/station/new'}> <Link href={"/admin/station/new"}>
<button className="btn btn-sm btn-outline btn-primary"> <button className="btn btn-sm btn-outline btn-primary">
Erstellen Erstellen
</button> </button>
@@ -18,22 +18,23 @@ export default () => {
<PaginatedTable <PaginatedTable
showEditButton showEditButton
prismaModel="station" prismaModel="station"
searchFields={["bosCallsign", "bosUse", "country", "operator"]}
columns={[ columns={[
{ {
header: 'BOS Name', header: "BOS Name",
accessorKey: 'bosCallsign', accessorKey: "bosCallsign",
}, },
{ {
header: 'Bos Use', header: "Bos Use",
accessorKey: 'bosUse', accessorKey: "bosUse",
}, },
{ {
header: 'Country', header: "Country",
accessorKey: 'country', accessorKey: "country",
}, },
{ {
header: 'operator', header: "operator",
accessorKey: 'operator', accessorKey: "operator",
}, },
]} ]}
/> />

View File

@@ -1,5 +1,5 @@
import { User2 } from 'lucide-react'; import { User2 } from "lucide-react";
import { PaginatedTable } from '../../../_components/PaginatedTable'; import { PaginatedTable } from "../../../_components/PaginatedTable";
export default async () => { export default async () => {
return ( return (
@@ -10,22 +10,23 @@ export default async () => {
<PaginatedTable <PaginatedTable
showEditButton showEditButton
prismaModel="user" prismaModel="user"
searchFields={["publicId", "firstname", "lastname", "email"]}
columns={[ columns={[
{ {
header: 'ID', header: "ID",
accessorKey: 'publicId', accessorKey: "publicId",
}, },
{ {
header: 'Vorname', header: "Vorname",
accessorKey: 'firstname', accessorKey: "firstname",
}, },
{ {
header: 'Nachname', header: "Nachname",
accessorKey: 'lastname', accessorKey: "lastname",
}, },
{ {
header: 'Email', header: "Email",
accessorKey: 'email', accessorKey: "email",
}, },
]} ]}
/> />

View File

@@ -1,37 +1,76 @@
'use client'; "use client";
import { useEffect, useState } from 'react'; import { useEffect, useState, useCallback } from "react";
import SortableTable, { Pagination, SortableTableProps } from './Table'; import SortableTable, { Pagination, SortableTableProps } from "./Table";
import { PrismaClient } from '@repo/db'; import { PrismaClient } from "@repo/db";
import { getData } from './pagiantedTableActions'; import { getData } from "./pagiantedTableActions";
interface PaginatedTableProps<TData> interface PaginatedTableProps<TData>
extends Omit<SortableTableProps<TData>, 'data'> { extends Omit<SortableTableProps<TData>, "data"> {
prismaModel: keyof PrismaClient; prismaModel: keyof PrismaClient;
rowsPerPage?: number; rowsPerPage?: number;
showEditButton?: boolean; showEditButton?: boolean;
searchFields: string[];
} }
export function PaginatedTable<TData>({ export function PaginatedTable<TData>({
prismaModel, prismaModel,
rowsPerPage = 10, rowsPerPage = 10,
showEditButton = false, showEditButton = false,
searchFields,
...restProps ...restProps
}: PaginatedTableProps<TData>) { }: PaginatedTableProps<TData>) {
const [data, setData] = useState<TData[]>([]); const [data, setData] = useState<TData[]>([]);
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [searchTerm, setSearchTerm] = useState("");
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm);
const debounce = (func: Function, delay: number) => {
let timer: NodeJS.Timeout;
return (...args: any[]) => {
clearTimeout(timer);
timer = setTimeout(() => func(...args), delay);
};
};
const handleSearchChange = useCallback(
debounce((value: string) => {
setDebouncedSearchTerm(value);
}, 500),
[]
);
useEffect(() => { useEffect(() => {
getData(prismaModel, rowsPerPage, page * rowsPerPage).then((result) => { getData(
prismaModel,
rowsPerPage,
page * rowsPerPage,
debouncedSearchTerm,
searchFields
).then((result) => {
if (result) { if (result) {
setData(result.data); setData(result.data);
setTotal(result.total); setTotal(result.total);
} }
}); });
}, [page]); }, [page, debouncedSearchTerm]);
return ( return (
<div className="space-y-4"> <div className="space-y-4 m-4">
{searchFields.length > 0 && (
<div className="flex justify-end">
<input
type="text"
placeholder="Suchen..."
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
handleSearchChange(e.target.value);
}}
className="input input-bordered w-full max-w-xs justify-end"
/>
</div>
)}
<SortableTable <SortableTable
data={data} data={data}
prismaModel={prismaModel} prismaModel={prismaModel}

View File

@@ -1,24 +1,43 @@
'use server'; "use server";
import { PrismaClient } from '@repo/db'; import { PrismaClient } from "@repo/db";
export const getData = async ( const prisma = new PrismaClient();
prismaModelName: keyof PrismaClient,
take: number, export async function getData(
skip: number model: keyof PrismaClient,
) => { limit: number,
const prisma = new PrismaClient(); offset: number,
if ( searchTerm: string,
!prismaModelName || searchFields: string[]
!prisma[prismaModelName] || ) {
!('findMany' in prisma[prismaModelName]) if (!model || !prisma[model]) {
) return { data: [], total: 0 };
return; }
const model = prisma[prismaModelName] as any;
if (!model.findMany || !model.count) return; const formattedId = searchTerm.match(/^VAR(\d+)$/)?.[1];
const data = await model.findMany({
take, const where = searchTerm
skip, ? {
OR: [
formattedId ? { id: formattedId } : undefined,
...searchFields.map((field) => ({
[field]: { contains: searchTerm },
})),
].filter(Boolean),
}
: {};
if (!prisma[model]) {
return { data: [], total: 0 };
}
const data = await (prisma[model] as any).findMany({
where,
take: limit,
skip: offset,
}); });
const total = await model.count();
const total = await (prisma[model] as any).count({ where });
return { data, total }; return { data, total };
}; }