feat: add cell formatters and tables for boards, agents, and member invites
This commit is contained in:
155
frontend/src/components/board-groups/BoardGroupsTable.tsx
Normal file
155
frontend/src/components/board-groups/BoardGroupsTable.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import {
|
||||
type ColumnDef,
|
||||
type OnChangeFn,
|
||||
type SortingState,
|
||||
type Updater,
|
||||
type VisibilityState,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import { type BoardGroupRead } from "@/api/generated/model";
|
||||
import {
|
||||
DataTable,
|
||||
type DataTableEmptyState,
|
||||
} from "@/components/tables/DataTable";
|
||||
import { dateCell, linkifyCell } from "@/components/tables/cell-formatters";
|
||||
|
||||
type BoardGroupsTableProps = {
|
||||
groups: BoardGroupRead[];
|
||||
isLoading?: boolean;
|
||||
sorting?: SortingState;
|
||||
onSortingChange?: OnChangeFn<SortingState>;
|
||||
stickyHeader?: boolean;
|
||||
showActions?: boolean;
|
||||
hiddenColumns?: string[];
|
||||
columnOrder?: string[];
|
||||
disableSorting?: boolean;
|
||||
onDelete?: (group: BoardGroupRead) => void;
|
||||
emptyMessage?: string;
|
||||
emptyState?: Omit<DataTableEmptyState, "icon"> & {
|
||||
icon?: DataTableEmptyState["icon"];
|
||||
};
|
||||
};
|
||||
|
||||
const DEFAULT_EMPTY_ICON = (
|
||||
<svg
|
||||
className="h-16 w-16 text-slate-300"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M3 7h8" />
|
||||
<path d="M3 17h8" />
|
||||
<path d="M13 7h8" />
|
||||
<path d="M13 17h8" />
|
||||
<path d="M3 12h18" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export function BoardGroupsTable({
|
||||
groups,
|
||||
isLoading = false,
|
||||
sorting,
|
||||
onSortingChange,
|
||||
stickyHeader = false,
|
||||
showActions = true,
|
||||
hiddenColumns,
|
||||
columnOrder,
|
||||
disableSorting = false,
|
||||
onDelete,
|
||||
emptyMessage = "No groups found.",
|
||||
emptyState,
|
||||
}: BoardGroupsTableProps) {
|
||||
const [internalSorting, setInternalSorting] = useState<SortingState>([
|
||||
{ id: "name", desc: false },
|
||||
]);
|
||||
const resolvedSorting = sorting ?? internalSorting;
|
||||
const handleSortingChange: OnChangeFn<SortingState> =
|
||||
onSortingChange ??
|
||||
((updater: Updater<SortingState>) => {
|
||||
setInternalSorting(updater);
|
||||
});
|
||||
const columnVisibility = useMemo<VisibilityState>(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
(hiddenColumns ?? []).map((columnId) => [columnId, false]),
|
||||
),
|
||||
[hiddenColumns],
|
||||
);
|
||||
const columns = useMemo<ColumnDef<BoardGroupRead>[]>(() => {
|
||||
const baseColumns: ColumnDef<BoardGroupRead>[] = [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Group",
|
||||
cell: ({ row }) =>
|
||||
linkifyCell({
|
||||
href: `/board-groups/${row.original.id}`,
|
||||
label: row.original.name,
|
||||
subtitle: row.original.description ?? "No description",
|
||||
subtitleClassName: row.original.description
|
||||
? "mt-1 line-clamp-2 text-xs text-slate-500"
|
||||
: "mt-1 text-xs text-slate-400",
|
||||
}),
|
||||
},
|
||||
{
|
||||
accessorKey: "updated_at",
|
||||
header: "Updated",
|
||||
cell: ({ row }) => dateCell(row.original.updated_at),
|
||||
},
|
||||
];
|
||||
|
||||
return baseColumns;
|
||||
}, []);
|
||||
|
||||
// eslint-disable-next-line react-hooks/incompatible-library
|
||||
const table = useReactTable({
|
||||
data: groups,
|
||||
columns,
|
||||
enableSorting: !disableSorting,
|
||||
state: {
|
||||
...(!disableSorting ? { sorting: resolvedSorting } : {}),
|
||||
...(columnOrder ? { columnOrder } : {}),
|
||||
columnVisibility,
|
||||
},
|
||||
...(disableSorting ? {} : { onSortingChange: handleSortingChange }),
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
...(disableSorting ? {} : { getSortedRowModel: getSortedRowModel() }),
|
||||
});
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
table={table}
|
||||
isLoading={isLoading}
|
||||
stickyHeader={stickyHeader}
|
||||
emptyMessage={emptyMessage}
|
||||
rowClassName="transition hover:bg-slate-50"
|
||||
cellClassName="px-6 py-4 align-top"
|
||||
rowActions={
|
||||
showActions
|
||||
? {
|
||||
getEditHref: (group) => `/board-groups/${group.id}/edit`,
|
||||
onDelete,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
emptyState={
|
||||
emptyState
|
||||
? {
|
||||
icon: emptyState.icon ?? DEFAULT_EMPTY_ICON,
|
||||
title: emptyState.title,
|
||||
description: emptyState.description,
|
||||
actionHref: emptyState.actionHref,
|
||||
actionLabel: emptyState.actionLabel,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user