feat(tracker-web): adopt @bytelyst/data-table for items list (Wave 9.C.9)

Replaces the bespoke <table> in /dashboard/items with <DataTable> (TanStack-
powered sorting + client pagination), keeping the existing badge cells, title
link, labels and delete action via ColumnDef cell renderers. Server-side
type/status/priority/search filters retained (enableFilter=false on the table).

Verified: tsc --noEmit clean; vitest 31/31; next build --webpack succeeds
(/dashboard/items compiles).
This commit is contained in:
saravanakumardb1 2026-05-28 18:38:08 -07:00
parent 7e1a2ad660
commit 0985969377
3 changed files with 117 additions and 89 deletions

View File

@ -27,6 +27,7 @@
"@bytelyst/api-client": "workspace:*",
"@bytelyst/config": "workspace:*",
"@bytelyst/dashboard-components": "workspace:*",
"@bytelyst/data-table": "workspace:*",
"@bytelyst/errors": "workspace:*",
"@bytelyst/telemetry-client": "workspace:*",
"@bytelyst/logger": "workspace:*",

View File

@ -1,7 +1,8 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
import { useEffect, useState, useCallback, useMemo } from 'react';
import Link from 'next/link';
import { DataTable, type ColumnDef } from '@bytelyst/data-table';
import { useAuth } from '@/lib/auth-context';
import { listItems, createItem, deleteItem, type TrackerItem } from '@/lib/tracker-client';
@ -87,15 +88,105 @@ export default function ItemsListPage() {
}
};
const handleDelete = async (id: string) => {
if (!confirm('Delete this item?')) return;
try {
await deleteItem(id);
fetchItems();
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Failed to delete');
}
};
const handleDelete = useCallback(
async (id: string) => {
if (!confirm('Delete this item?')) return;
try {
await deleteItem(id);
fetchItems();
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Failed to delete');
}
},
[fetchItems]
);
const columns = useMemo<ColumnDef<TrackerItem, unknown>[]>(
() => [
{
id: 'title',
accessorKey: 'title',
header: 'Title',
cell: ({ row }) => {
const item = row.original;
return (
<div>
<Link
href={`/dashboard/items/${item.id}`}
className="font-medium text-foreground hover:text-primary hover:underline"
>
{item.title}
</Link>
{item.labels.length > 0 && (
<div className="mt-1 flex gap-1">
{item.labels.map(l => (
<span
key={l}
className="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground"
>
{l}
</span>
))}
</div>
)}
</div>
);
},
},
{
id: 'type',
accessorKey: 'type',
header: 'Type',
cell: ({ row }) => (
<span
className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${TYPE_BADGE[row.original.type] || ''}`}
>
{row.original.type}
</span>
),
},
{
id: 'status',
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => (
<span
className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${STATUS_BADGE[row.original.status] || ''}`}
>
{row.original.status.replace(/_/g, ' ')}
</span>
),
},
{
id: 'priority',
accessorKey: 'priority',
header: 'Priority',
cell: ({ row }) => (
<span
className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${PRIORITY_BADGE[row.original.priority] || ''}`}
>
{row.original.priority}
</span>
),
},
{ id: 'voteCount', accessorKey: 'voteCount', header: 'Votes' },
{ id: 'commentCount', accessorKey: 'commentCount', header: 'Comments' },
{
id: 'actions',
header: 'Actions',
enableSorting: false,
cell: ({ row }) => (
<button
onClick={() => handleDelete(row.original.id)}
className="text-xs text-muted-foreground hover:text-destructive"
>
Delete
</button>
),
},
],
[handleDelete]
);
return (
<div className="space-y-6">
@ -163,88 +254,21 @@ export default function ItemsListPage() {
</select>
</div>
{/* Items table */}
{/* Items table — @bytelyst/data-table (Wave 9.C.9) */}
{loading ? (
<div className="text-muted-foreground">Loading...</div>
) : items.length === 0 ? (
<div className="rounded-xl border border-border bg-card p-12 text-center text-muted-foreground">
No items found. Create one to get started.
</div>
) : (
<div className="overflow-hidden rounded-xl border border-border">
<table className="w-full text-sm">
<thead className="bg-muted/50">
<tr>
<th className="px-4 py-3 text-left font-medium">Title</th>
<th className="px-4 py-3 text-left font-medium">Type</th>
<th className="px-4 py-3 text-left font-medium">Status</th>
<th className="px-4 py-3 text-left font-medium">Priority</th>
<th className="px-4 py-3 text-center font-medium">Votes</th>
<th className="px-4 py-3 text-center font-medium">Comments</th>
<th className="px-4 py-3 text-right font-medium">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{items.map(item => (
<tr key={item.id} className="hover:bg-muted/30 transition-colors">
<td className="px-4 py-3">
<Link
href={`/dashboard/items/${item.id}`}
className="font-medium text-foreground hover:text-primary hover:underline"
>
{item.title}
</Link>
{item.labels.length > 0 && (
<div className="mt-1 flex gap-1">
{item.labels.map(l => (
<span
key={l}
className="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground"
>
{l}
</span>
))}
</div>
)}
</td>
<td className="px-4 py-3">
<span
className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${TYPE_BADGE[item.type] || ''}`}
>
{item.type}
</span>
</td>
<td className="px-4 py-3">
<span
className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${STATUS_BADGE[item.status] || ''}`}
>
{item.status.replace(/_/g, ' ')}
</span>
</td>
<td className="px-4 py-3">
<span
className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${PRIORITY_BADGE[item.priority] || ''}`}
>
{item.priority}
</span>
</td>
<td className="px-4 py-3 text-center text-muted-foreground">{item.voteCount}</td>
<td className="px-4 py-3 text-center text-muted-foreground">
{item.commentCount}
</td>
<td className="px-4 py-3 text-right">
<button
onClick={() => handleDelete(item.id)}
className="text-xs text-muted-foreground hover:text-destructive"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
<DataTable
ariaLabel="Tracker items"
columns={columns}
data={items}
getRowId={item => item.id}
enableFilter={false}
enableSorting
enablePagination
pageSize={15}
emptyState="No items found. Create one to get started."
/>
)}
{/* Create modal */}

3
pnpm-lock.yaml generated
View File

@ -249,6 +249,9 @@ importers:
'@bytelyst/dashboard-components':
specifier: workspace:*
version: link:../../packages/dashboard-components
'@bytelyst/data-table':
specifier: workspace:*
version: link:../../packages/data-table
'@bytelyst/errors':
specifier: workspace:*
version: link:../../packages/errors