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

View File

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

View File

@@ -1,37 +1,76 @@
'use client';
import { useEffect, useState } from 'react';
import SortableTable, { Pagination, SortableTableProps } from './Table';
import { PrismaClient } from '@repo/db';
import { getData } from './pagiantedTableActions';
"use client";
import { useEffect, useState, useCallback } from "react";
import SortableTable, { Pagination, SortableTableProps } from "./Table";
import { PrismaClient } from "@repo/db";
import { getData } from "./pagiantedTableActions";
interface PaginatedTableProps<TData>
extends Omit<SortableTableProps<TData>, 'data'> {
extends Omit<SortableTableProps<TData>, "data"> {
prismaModel: keyof PrismaClient;
rowsPerPage?: number;
showEditButton?: boolean;
searchFields: string[];
}
export function PaginatedTable<TData>({
prismaModel,
rowsPerPage = 10,
showEditButton = false,
searchFields,
...restProps
}: PaginatedTableProps<TData>) {
const [data, setData] = useState<TData[]>([]);
const [page, setPage] = 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(() => {
getData(prismaModel, rowsPerPage, page * rowsPerPage).then((result) => {
getData(
prismaModel,
rowsPerPage,
page * rowsPerPage,
debouncedSearchTerm,
searchFields
).then((result) => {
if (result) {
setData(result.data);
setTotal(result.total);
}
});
}, [page]);
}, [page, debouncedSearchTerm]);
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
data={data}
prismaModel={prismaModel}

View File

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