fix(audit-wave-9): add mobile cardRender to remaining admin lists

Five DataTable consumers were rendering as horizontally-scrolling
desktop tables on mobile because they had no cardRender prop. Now they
collapse to a vertical card list below the lg: breakpoint with the
same actions inline:

- admin/tags/tag-list
- admin/roles/role-list
- admin/ports/port-list (also: Active/Inactive badge -> StatusPill)
- admin/document-templates/template-list (also: Active/Inactive badge
  -> StatusPill)
- admin/custom-fields/custom-fields-manager

All five now share the user-list / berth-list pattern: row-card with
title, secondary meta, and trailing action buttons; same TanStack
table instance powers both the desktop table and the mobile cards.

Closes ui/ux H2 + extends M2 (status-pill coverage).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 12:00:35 +02:00
parent 153f6ac797
commit 0df761f4ad
5 changed files with 277 additions and 10 deletions

View File

@@ -8,7 +8,7 @@ import { Pencil, Plus } from 'lucide-react';
import { DataTable } from '@/components/shared/data-table';
import { PageHeader } from '@/components/shared/page-header';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { StatusPill } from '@/components/ui/status-pill';
import { apiFetch } from '@/lib/api/client';
import { PortForm } from './port-form';
@@ -85,11 +85,9 @@ export function PortList() {
header: 'Status',
cell: ({ row }) =>
row.original.isActive ? (
<Badge variant="default" className="bg-green-600">
Active
</Badge>
<StatusPill status="enabled">Active</StatusPill>
) : (
<Badge variant="destructive">Inactive</Badge>
<StatusPill status="disabled">Inactive</StatusPill>
),
},
{
@@ -124,6 +122,42 @@ export function PortList() {
data={ports}
isLoading={loading}
getRowId={(row) => row.id}
cardRender={({ original }) => (
<div className="flex items-start justify-between gap-3 rounded-xl border border-border bg-card p-4 shadow-sm">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
{original.primaryColor ? (
<span
aria-hidden
className="inline-block h-3 w-3 shrink-0 rounded-full"
style={{ backgroundColor: original.primaryColor }}
/>
) : null}
<p className="truncate text-sm font-semibold text-foreground">{original.name}</p>
{original.isActive ? (
<StatusPill status="enabled">Active</StatusPill>
) : (
<StatusPill status="disabled">Inactive</StatusPill>
)}
</div>
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
<code className="rounded bg-muted px-1.5 py-0.5">{original.slug}</code>
<span>{original.defaultCurrency}</span>
<span aria-hidden>·</span>
<span>{original.timezone}</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => handleEditPort(original)}
aria-label={`Edit ${original.name}`}
className="shrink-0"
>
<Pencil className="h-4 w-4" />
</Button>
</div>
)}
emptyState={
<div className="text-center py-8">
<p className="text-muted-foreground">No ports configured.</p>