Add ID and Created columns to all admin pages with sortable columns

- Add ID column to all admin list pages for easy record identification
- Add Created column (created_at/date_joined) to pages missing it
- Implement client-side column sorting in DataTable component
- Fix sorting implementation to work without getSortingRowModel export

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-09 17:14:08 -06:00
parent 80c6b58361
commit 68a4c28d68
23 changed files with 200 additions and 7 deletions

View File

@@ -37,6 +37,15 @@ const roleColors: Record<string, 'default' | 'secondary' | 'destructive'> = {
}; };
const columns: ColumnDef<ManagedAdminUser>[] = [ const columns: ColumnDef<ManagedAdminUser>[] = [
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<span className="font-mono text-sm text-muted-foreground">
{row.original.id}
</span>
),
},
{ {
accessorKey: 'email', accessorKey: 'email',
header: 'Email', header: 'Email',

View File

@@ -170,6 +170,7 @@ export default function AppleSocialAuthPage() {
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
/> />
</TableHead> </TableHead>
<TableHead>ID</TableHead>
<TableHead>User</TableHead> <TableHead>User</TableHead>
<TableHead>Apple ID</TableHead> <TableHead>Apple ID</TableHead>
<TableHead>Apple Email</TableHead> <TableHead>Apple Email</TableHead>
@@ -202,6 +203,7 @@ export default function AppleSocialAuthPage() {
} }
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">{entry.id}</TableCell>
<TableCell> <TableCell>
<Link <Link
href={`/admin/users/${entry.user_id}`} href={`/admin/users/${entry.user_id}`}

View File

@@ -170,6 +170,7 @@ export default function AuthTokensPage() {
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
/> />
</TableHead> </TableHead>
<TableHead>ID</TableHead>
<TableHead>User</TableHead> <TableHead>User</TableHead>
<TableHead>Email</TableHead> <TableHead>Email</TableHead>
<TableHead>Token</TableHead> <TableHead>Token</TableHead>
@@ -201,6 +202,7 @@ export default function AuthTokensPage() {
} }
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">{token.user_id}</TableCell>
<TableCell> <TableCell>
<Link <Link
href={`/admin/users/${token.user_id}`} href={`/admin/users/${token.user_id}`}

View File

@@ -170,6 +170,7 @@ export default function CompletionImagesPage() {
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
/> />
</TableHead> </TableHead>
<TableHead>ID</TableHead>
<TableHead>Preview</TableHead> <TableHead>Preview</TableHead>
<TableHead>Task</TableHead> <TableHead>Task</TableHead>
<TableHead>Caption</TableHead> <TableHead>Caption</TableHead>
@@ -202,6 +203,7 @@ export default function CompletionImagesPage() {
} }
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">{img.id}</TableCell>
<TableCell> <TableCell>
{img.image_url ? ( {img.image_url ? (
<a href={img.image_url} target="_blank" rel="noopener noreferrer"> <a href={img.image_url} target="_blank" rel="noopener noreferrer">

View File

@@ -179,12 +179,14 @@ export default function CompletionsPage() {
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
/> />
</TableHead> </TableHead>
<TableHead>ID</TableHead>
<TableHead>Task</TableHead> <TableHead>Task</TableHead>
<TableHead>Residence</TableHead> <TableHead>Residence</TableHead>
<TableHead>Completed By</TableHead> <TableHead>Completed By</TableHead>
<TableHead>Completed At</TableHead> <TableHead>Completed At</TableHead>
<TableHead>Cost</TableHead> <TableHead>Cost</TableHead>
<TableHead>Photo</TableHead> <TableHead>Photo</TableHead>
<TableHead>Created</TableHead>
<TableHead className="w-24">Actions</TableHead> <TableHead className="w-24">Actions</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -212,6 +214,7 @@ export default function CompletionsPage() {
} }
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">{completion.id}</TableCell>
<TableCell> <TableCell>
<Link <Link
href={`/completions/${completion.id}`} href={`/completions/${completion.id}`}
@@ -295,6 +298,9 @@ export default function CompletionsPage() {
<span className="text-muted-foreground">-</span> <span className="text-muted-foreground">-</span>
)} )}
</TableCell> </TableCell>
<TableCell>
{new Date(completion.created_at).toLocaleDateString()}
</TableCell>
<TableCell> <TableCell>
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>

View File

@@ -175,6 +175,7 @@ export default function ConfirmationCodesPage() {
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
/> />
</TableHead> </TableHead>
<TableHead>ID</TableHead>
<TableHead>User</TableHead> <TableHead>User</TableHead>
<TableHead>Email</TableHead> <TableHead>Email</TableHead>
<TableHead>Code</TableHead> <TableHead>Code</TableHead>
@@ -208,6 +209,7 @@ export default function ConfirmationCodesPage() {
} }
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">{code.id}</TableCell>
<TableCell> <TableCell>
<Link <Link
href={`/admin/users/${code.user_id}`} href={`/admin/users/${code.user_id}`}

View File

@@ -31,6 +31,15 @@ import {
import { toast } from 'sonner'; import { toast } from 'sonner';
const columns: ColumnDef<Contractor>[] = [ const columns: ColumnDef<Contractor>[] = [
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<span className="font-mono text-sm text-muted-foreground">
{row.original.id}
</span>
),
},
{ {
accessorKey: 'name', accessorKey: 'name',
header: 'Name', header: 'Name',
@@ -82,6 +91,11 @@ const columns: ColumnDef<Contractor>[] = [
</Badge> </Badge>
), ),
}, },
{
accessorKey: 'created_at',
header: 'Created',
cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString(),
},
{ {
id: 'actions', id: 'actions',
cell: ({ row }) => { cell: ({ row }) => {

View File

@@ -269,6 +269,7 @@ export default function DevicesPage() {
}} }}
/> />
</TableHead> </TableHead>
<TableHead>ID</TableHead>
<TableHead>Name</TableHead> <TableHead>Name</TableHead>
<TableHead>User</TableHead> <TableHead>User</TableHead>
<TableHead>Device ID</TableHead> <TableHead>Device ID</TableHead>
@@ -306,6 +307,7 @@ export default function DevicesPage() {
}} }}
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">{device.id}</TableCell>
<TableCell className="font-medium">{device.name || 'Unknown'}</TableCell> <TableCell className="font-medium">{device.name || 'Unknown'}</TableCell>
<TableCell> <TableCell>
{device.user_id ? ( {device.user_id ? (

View File

@@ -170,6 +170,7 @@ export default function DocumentImagesPage() {
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
/> />
</TableHead> </TableHead>
<TableHead>ID</TableHead>
<TableHead>Preview</TableHead> <TableHead>Preview</TableHead>
<TableHead>Document</TableHead> <TableHead>Document</TableHead>
<TableHead>Residence</TableHead> <TableHead>Residence</TableHead>
@@ -202,6 +203,7 @@ export default function DocumentImagesPage() {
} }
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">{img.id}</TableCell>
<TableCell> <TableCell>
{img.image_url ? ( {img.image_url ? (
<a href={img.image_url} target="_blank" rel="noopener noreferrer"> <a href={img.image_url} target="_blank" rel="noopener noreferrer">

View File

@@ -40,6 +40,15 @@ const documentTypeColors: Record<string, 'default' | 'secondary' | 'destructive'
}; };
const columns: ColumnDef<Document>[] = [ const columns: ColumnDef<Document>[] = [
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<span className="font-mono text-sm text-muted-foreground">
{row.original.id}
</span>
),
},
{ {
accessorKey: 'title', accessorKey: 'title',
header: 'Title', header: 'Title',

View File

@@ -122,11 +122,13 @@ export default function FeatureBenefitsPage() {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>ID</TableHead>
<TableHead>Order</TableHead> <TableHead>Order</TableHead>
<TableHead>Feature</TableHead> <TableHead>Feature</TableHead>
<TableHead>Free Tier</TableHead> <TableHead>Free Tier</TableHead>
<TableHead>Pro Tier</TableHead> <TableHead>Pro Tier</TableHead>
<TableHead>Active</TableHead> <TableHead>Active</TableHead>
<TableHead>Created</TableHead>
<TableHead className="w-24">Actions</TableHead> <TableHead className="w-24">Actions</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -138,6 +140,7 @@ export default function FeatureBenefitsPage() {
) : ( ) : (
data?.data?.map((benefit) => ( data?.data?.map((benefit) => (
<TableRow key={benefit.id}> <TableRow key={benefit.id}>
<TableCell className="font-mono text-sm text-muted-foreground">{benefit.id}</TableCell>
<TableCell>{benefit.display_order}</TableCell> <TableCell>{benefit.display_order}</TableCell>
<TableCell className="font-medium">{benefit.feature_name}</TableCell> <TableCell className="font-medium">{benefit.feature_name}</TableCell>
<TableCell>{benefit.free_tier_text}</TableCell> <TableCell>{benefit.free_tier_text}</TableCell>
@@ -147,6 +150,9 @@ export default function FeatureBenefitsPage() {
{benefit.is_active ? 'Active' : 'Inactive'} {benefit.is_active ? 'Active' : 'Inactive'}
</Badge> </Badge>
</TableCell> </TableCell>
<TableCell>
{new Date(benefit.created_at).toLocaleDateString()}
</TableCell>
<TableCell> <TableCell>
<div className="flex gap-1"> <div className="flex gap-1">
<Button variant="ghost" size="icon" onClick={() => handleEdit(benefit)}> <Button variant="ghost" size="icon" onClick={() => handleEdit(benefit)}>

View File

@@ -88,6 +88,15 @@ export default function NotificationPrefsPage() {
}; };
const columns: ColumnDef<NotificationPreference>[] = [ const columns: ColumnDef<NotificationPreference>[] = [
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<span className="font-mono text-sm text-muted-foreground">
{row.original.id}
</span>
),
},
{ {
accessorKey: 'username', accessorKey: 'username',
header: 'User', header: 'User',
@@ -226,6 +235,11 @@ export default function NotificationPrefsPage() {
); );
}, },
}, },
{
accessorKey: 'created_at',
header: 'Created',
cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString(),
},
{ {
id: 'actions', id: 'actions',
cell: ({ row }) => { cell: ({ row }) => {

View File

@@ -20,6 +20,15 @@ import {
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
const columns: ColumnDef<Notification>[] = [ const columns: ColumnDef<Notification>[] = [
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<span className="font-mono text-sm text-muted-foreground">
{row.original.id}
</span>
),
},
{ {
accessorKey: 'title', accessorKey: 'title',
header: 'Title', header: 'Title',

View File

@@ -207,6 +207,7 @@ export default function PasswordResetCodesPage() {
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
/> />
</TableHead> </TableHead>
<TableHead>ID</TableHead>
<TableHead>User</TableHead> <TableHead>User</TableHead>
<TableHead>Email</TableHead> <TableHead>Email</TableHead>
<TableHead>Token</TableHead> <TableHead>Token</TableHead>
@@ -241,6 +242,7 @@ export default function PasswordResetCodesPage() {
} }
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">{code.id}</TableCell>
<TableCell> <TableCell>
<Link <Link
href={`/admin/users/${code.user_id}`} href={`/admin/users/${code.user_id}`}

View File

@@ -143,11 +143,13 @@ export default function PromotionsPage() {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>ID</TableHead> <TableHead>ID (DB)</TableHead>
<TableHead>Promotion ID</TableHead>
<TableHead>Title</TableHead> <TableHead>Title</TableHead>
<TableHead>Target</TableHead> <TableHead>Target</TableHead>
<TableHead>Date Range</TableHead> <TableHead>Date Range</TableHead>
<TableHead>Status</TableHead> <TableHead>Status</TableHead>
<TableHead>Created</TableHead>
<TableHead className="w-24">Actions</TableHead> <TableHead className="w-24">Actions</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -159,6 +161,7 @@ export default function PromotionsPage() {
) : ( ) : (
data?.data?.map((promo) => ( data?.data?.map((promo) => (
<TableRow key={promo.id}> <TableRow key={promo.id}>
<TableCell className="font-mono text-sm text-muted-foreground">{promo.id}</TableCell>
<TableCell><code className="text-xs bg-muted px-2 py-1 rounded">{promo.promotion_id}</code></TableCell> <TableCell><code className="text-xs bg-muted px-2 py-1 rounded">{promo.promotion_id}</code></TableCell>
<TableCell className="font-medium">{promo.title}</TableCell> <TableCell className="font-medium">{promo.title}</TableCell>
<TableCell> <TableCell>
@@ -178,6 +181,9 @@ export default function PromotionsPage() {
<Badge variant="destructive">Inactive</Badge> <Badge variant="destructive">Inactive</Badge>
)} )}
</TableCell> </TableCell>
<TableCell>
{new Date(promo.created_at).toLocaleDateString()}
</TableCell>
<TableCell> <TableCell>
<div className="flex gap-1"> <div className="flex gap-1">
<Button variant="ghost" size="icon" onClick={() => handleEdit(promo)}> <Button variant="ghost" size="icon" onClick={() => handleEdit(promo)}>

View File

@@ -32,6 +32,15 @@ import {
import { toast } from 'sonner'; import { toast } from 'sonner';
const columns: ColumnDef<Residence>[] = [ const columns: ColumnDef<Residence>[] = [
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<span className="font-mono text-sm text-muted-foreground">
{row.original.id}
</span>
),
},
{ {
accessorKey: 'name', accessorKey: 'name',
header: 'Name', header: 'Name',

View File

@@ -190,6 +190,7 @@ export default function ShareCodesPage() {
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
/> />
</TableHead> </TableHead>
<TableHead>ID</TableHead>
<TableHead>Code</TableHead> <TableHead>Code</TableHead>
<TableHead>Residence</TableHead> <TableHead>Residence</TableHead>
<TableHead>Created By</TableHead> <TableHead>Created By</TableHead>
@@ -223,6 +224,7 @@ export default function ShareCodesPage() {
} }
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">{code.id}</TableCell>
<TableCell> <TableCell>
<code className="text-sm bg-muted px-2 py-1 rounded font-mono"> <code className="text-sm bg-muted px-2 py-1 rounded font-mono">
{code.code} {code.code}

View File

@@ -26,6 +26,15 @@ const tierColors: Record<string, 'default' | 'secondary' | 'outline'> = {
}; };
const columns: ColumnDef<Subscription>[] = [ const columns: ColumnDef<Subscription>[] = [
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<span className="font-mono text-sm text-muted-foreground">
{row.original.id}
</span>
),
},
{ {
accessorKey: 'username', accessorKey: 'username',
header: 'User', header: 'User',

View File

@@ -239,12 +239,14 @@ export default function TaskTemplatesPage() {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>ID</TableHead>
<TableHead className="w-16">Order</TableHead> <TableHead className="w-16">Order</TableHead>
<TableHead>Title</TableHead> <TableHead>Title</TableHead>
<TableHead>Category</TableHead> <TableHead>Category</TableHead>
<TableHead>Frequency</TableHead> <TableHead>Frequency</TableHead>
<TableHead>iOS Icon</TableHead> <TableHead>iOS Icon</TableHead>
<TableHead>Active</TableHead> <TableHead>Active</TableHead>
<TableHead>Created</TableHead>
<TableHead className="w-32">Actions</TableHead> <TableHead className="w-32">Actions</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -256,6 +258,7 @@ export default function TaskTemplatesPage() {
) : ( ) : (
data?.data?.map((template) => ( data?.data?.map((template) => (
<TableRow key={template.id}> <TableRow key={template.id}>
<TableCell className="font-mono text-sm text-muted-foreground">{template.id}</TableCell>
<TableCell className="text-muted-foreground">{template.display_order}</TableCell> <TableCell className="text-muted-foreground">{template.display_order}</TableCell>
<TableCell className="font-medium max-w-xs"> <TableCell className="font-medium max-w-xs">
<div className="truncate">{template.title}</div> <div className="truncate">{template.title}</div>
@@ -275,6 +278,9 @@ export default function TaskTemplatesPage() {
{template.is_active ? 'Active' : 'Inactive'} {template.is_active ? 'Active' : 'Inactive'}
</Badge> </Badge>
</TableCell> </TableCell>
<TableCell>
{new Date(template.created_at).toLocaleDateString()}
</TableCell>
<TableCell> <TableCell>
<div className="flex gap-1"> <div className="flex gap-1">
<Button <Button

View File

@@ -31,6 +31,15 @@ import {
import { toast } from 'sonner'; import { toast } from 'sonner';
const columns: ColumnDef<Task>[] = [ const columns: ColumnDef<Task>[] = [
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<span className="font-mono text-sm text-muted-foreground">
{row.original.id}
</span>
),
},
{ {
accessorKey: 'title', accessorKey: 'title',
header: 'Title', header: 'Title',
@@ -81,6 +90,11 @@ const columns: ColumnDef<Task>[] = [
return <Badge variant="default">Active</Badge>; return <Badge variant="default">Active</Badge>;
}, },
}, },
{
accessorKey: 'created_at',
header: 'Created',
cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString(),
},
{ {
id: 'actions', id: 'actions',
cell: ({ row }) => { cell: ({ row }) => {

View File

@@ -170,6 +170,7 @@ export default function UserProfilesPage() {
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
/> />
</TableHead> </TableHead>
<TableHead>ID</TableHead>
<TableHead>User</TableHead> <TableHead>User</TableHead>
<TableHead>Email</TableHead> <TableHead>Email</TableHead>
<TableHead>Phone</TableHead> <TableHead>Phone</TableHead>
@@ -202,6 +203,7 @@ export default function UserProfilesPage() {
} }
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">{profile.id}</TableCell>
<TableCell> <TableCell>
<Link <Link
href={`/admin/users/${profile.user_id}`} href={`/admin/users/${profile.user_id}`}

View File

@@ -32,6 +32,15 @@ import {
import { toast } from 'sonner'; import { toast } from 'sonner';
const columns: ColumnDef<User>[] = [ const columns: ColumnDef<User>[] = [
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<span className="font-mono text-sm text-muted-foreground">
{row.original.id}
</span>
),
},
{ {
accessorKey: 'username', accessorKey: 'username',
header: 'Username', header: 'Username',

View File

@@ -1,12 +1,15 @@
'use client'; 'use client';
import * as React from 'react';
import { import {
ColumnDef, ColumnDef,
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
useReactTable, useReactTable,
RowSelectionState, RowSelectionState,
SortingState,
} from '@tanstack/react-table'; } from '@tanstack/react-table';
import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
import { import {
Table, Table,
@@ -17,6 +20,7 @@ import {
TableRow, TableRow,
} from '@/components/ui/table'; } from '@/components/ui/table';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { Button } from '@/components/ui/button';
import { DataTablePagination } from './data-table-pagination'; import { DataTablePagination } from './data-table-pagination';
import { DataTableToolbar } from './data-table-toolbar'; import { DataTableToolbar } from './data-table-toolbar';
@@ -52,6 +56,37 @@ export function DataTable<TData, TValue>({
onRowSelectionChange, onRowSelectionChange,
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
const [sorting, setSorting] = React.useState<SortingState>([]);
// Sort data client-side based on sorting state
const sortedData = React.useMemo(() => {
if (sorting.length === 0) return data;
const [sort] = sorting;
const { id, desc } = sort;
return [...data].sort((a, b) => {
const aValue = (a as Record<string, unknown>)[id];
const bValue = (b as Record<string, unknown>)[id];
// Handle null/undefined
if (aValue == null && bValue == null) return 0;
if (aValue == null) return desc ? -1 : 1;
if (bValue == null) return desc ? 1 : -1;
// Compare values
if (typeof aValue === 'number' && typeof bValue === 'number') {
return desc ? bValue - aValue : aValue - bValue;
}
// String comparison
const aStr = String(aValue).toLowerCase();
const bStr = String(bValue).toLowerCase();
if (aStr < bStr) return desc ? 1 : -1;
if (aStr > bStr) return desc ? -1 : 1;
return 0;
});
}, [data, sorting]);
// Add selection column if enabled // Add selection column if enabled
const tableColumns = React.useMemo(() => { const tableColumns = React.useMemo(() => {
@@ -84,19 +119,22 @@ export function DataTable<TData, TValue>({
}, [columns, enableRowSelection]); }, [columns, enableRowSelection]);
const table = useReactTable({ const table = useReactTable({
data, data: sortedData,
columns: tableColumns, columns: tableColumns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
manualPagination: true, manualPagination: true,
manualSorting: true,
pageCount: Math.ceil(totalCount / pageSize), pageCount: Math.ceil(totalCount / pageSize),
state: { state: {
rowSelection, rowSelection,
sorting,
pagination: { pagination: {
pageIndex: page - 1, pageIndex: page - 1,
pageSize, pageSize,
}, },
}, },
onRowSelectionChange: setRowSelection, onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
enableRowSelection, enableRowSelection,
}); });
@@ -127,12 +165,31 @@ export function DataTable<TData, TValue>({
<TableRow key={headerGroup.id}> <TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => ( {headerGroup.headers.map((header) => (
<TableHead key={header.id}> <TableHead key={header.id}>
{header.isPlaceholder {header.isPlaceholder ? null : header.column.getCanSort() ? (
? null <Button
: flexRender( variant="ghost"
size="sm"
className="-ml-3 h-8 data-[state=open]:bg-accent"
onClick={() => header.column.toggleSorting()}
>
{flexRender(
header.column.columnDef.header, header.column.columnDef.header,
header.getContext() header.getContext()
)} )}
{header.column.getIsSorted() === 'asc' ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : header.column.getIsSorted() === 'desc' ? (
<ArrowDown className="ml-2 h-4 w-4" />
) : (
<ArrowUpDown className="ml-2 h-4 w-4 opacity-50" />
)}
</Button>
) : (
flexRender(
header.column.columnDef.header,
header.getContext()
)
)}
</TableHead> </TableHead>
))} ))}
</TableRow> </TableRow>
@@ -188,5 +245,3 @@ export function DataTable<TData, TValue>({
</div> </div>
); );
} }
import * as React from 'react';