feat: create reusable DataTable component

This commit is contained in:
Thomas Bishop 2025-07-28 18:36:48 +01:00
parent 654f6f77ea
commit 8ab2aa77a6
4 changed files with 179 additions and 157 deletions

View file

@ -0,0 +1,113 @@
import {
flexRender,
getCoreRowModel,
useReactTable,
getPaginationRowModel,
SortingState,
getSortedRowModel,
} from "@tanstack/react-table"
import { Button } from "./ui/button"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { useState } from "react"
export function DataTable({ columns, data, loading }) {
const [sorting, setSorting] = useState([])
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
state: {
sorting,
},
})
const pageCount = table.getPageCount()
const currentPage = table.getState().pagination?.pageIndex
return (
<div>
<div className="rounded-none border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : loading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading...
</TableCell>
</TableRow>
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4 gap-2">
<div>
<span className="text-sm text-muted-foreground">{`${currentPage + 1} of ${pageCount}`}</span>
</div>
<div>
<Button
className="rounded-none"
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
className="rounded-none"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
)
}

View file

@ -1,104 +0,0 @@
import {
flexRender,
getCoreRowModel,
useReactTable,
getPaginationRowModel,
} from "@tanstack/react-table"
import { Button } from "./ui/button"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
export function RecentEditsDataTable({ columns, data, loading }) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
})
const pageCount = table.getPageCount()
const currentPage = table.getState().pagination?.pageIndex
return (
<div>
<div className="rounded-none border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : loading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Loading...
</TableCell>
</TableRow>
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4 gap-2">
<div>
<span className="text-sm text-muted-foreground">{`${currentPage + 1} of ${pageCount}`}</span>
</div>
<div>
<Button
className="rounded-none"
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
className="rounded-none"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
)
}

View file

@ -2,63 +2,77 @@ import { RecentEditsDataTable } from "@/components/RecentEditsDataTable"
import { useQuery } from "@tanstack/react-query" import { useQuery } from "@tanstack/react-query"
import api from "../api/eolas-api" import api from "../api/eolas-api"
import { Link } from "react-router" import { Link } from "react-router"
import { ArrowUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import { DataTable } from "@/components/DataTable"
const columns = [ const columns = [
{ {
accessorKey: "title", accessorKey: "title",
header: "Title", header: ({ column }) => {
cell: ({ cell, row }) => { return (
return ( <Button
<Link to={`entries/${row.original.title}`}> variant="ghost"
<span className="text-foreground underline-offset-3 underline hover:text-gray-700 dark:hover:text-green-300"> onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
{row.original.title} >
</span> Title
</Link> <ArrowUpDown className="ml-2 h-4 w-4" />
) </Button>
}, )
}, },
{ // header: "Title",
accessorKey: "date", cell: ({ cell, row }) => {
header: "Date", return (
}, <Link to={`entries/${row.original.title}`}>
{ <span className="text-foreground underline-offset-3 underline hover:text-gray-700 dark:hover:text-green-300">
accessorKey: "time", {row.original.title}
header: "Time", </span>
}, </Link>
)
},
},
{
accessorKey: "date",
header: "Date",
},
{
accessorKey: "time",
header: "Time",
},
] ]
export default function RecentEdits() { export default function RecentEdits() {
const { data, isLoading } = useQuery({ const { data, isLoading } = useQuery({
queryKey: ["entries_recent"], queryKey: ["entries_recent"],
queryFn: () => api.get("/entries?limit=20&sort=date").then((res) => res.data), queryFn: () => api.get("/entries?limit=20&sort=date").then((res) => res.data),
}) })
console.log(data) console.log(data)
const parsed = data?.data?.map((entry) => { const parsed = data?.data?.map((entry) => {
const [date, time] = entry?.last_modified?.split(" ") const [date, time] = entry?.last_modified?.split(" ")
return { return {
title: entry.title.replace(/_/g, " "), title: entry.title.replace(/_/g, " "),
date: new Date(date).toLocaleString("en-GB", { date: new Date(date).toLocaleString("en-GB", {
day: "numeric", day: "numeric",
month: "long", month: "long",
year: "numeric", year: "numeric",
}), }),
time: time, time: time,
} }
}) })
return ( return (
<> <>
<div className="border w-full"> <div className="border w-full">
<div className="border-b py-2 px-4 lg:px-6 bg-sidebar"> <div className="border-b py-2 px-4 lg:px-6 bg-sidebar">
<h2 className="scroll-m-20 font-semibold">Recent edits</h2> <h2 className="scroll-m-20 font-semibold">Recent edits</h2>
</div> </div>
<div className="p-4 lg:p-6"> <div className="p-4 lg:p-6">
<div className="container mx-auto py-2"> <div className="container mx-auto py-2">
<RecentEditsDataTable columns={columns} data={parsed || []} loading={isLoading} /> <DataTable columns={columns} data={parsed || []} loading={isLoading} />
</div> </div>
</div> </div>
</div> </div>
</> </>
) )
} }

View file

@ -1,4 +1,3 @@
import Main from "@/templates/Main"
import { useParams } from "react-router" import { useParams } from "react-router"
import Page from "./Page" import Page from "./Page"
export default function Tag() { export default function Tag() {