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

@@ -192,6 +192,66 @@ export function CustomFieldsManager() {
data={filteredFields}
isLoading={loading}
getRowId={(row) => row.id}
cardRender={({ original }) => (
<div className="rounded-xl border border-border bg-card p-4 shadow-sm">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-foreground">
{original.fieldLabel}
</p>
<p className="mt-0.5 truncate font-mono text-xs text-muted-foreground">
{original.fieldName}
</p>
<div className="mt-2 flex flex-wrap items-center gap-1.5 text-xs">
<Badge variant="secondary">
{FIELD_TYPE_LABELS[original.fieldType] ?? original.fieldType}
</Badge>
{original.isRequired ? (
<Badge variant="destructive" className="text-xs">
Required
</Badge>
) : (
<span className="text-muted-foreground">Optional</span>
)}
<span aria-hidden className="text-muted-foreground">
·
</span>
<span className="tabular-nums text-muted-foreground">
Order {original.sortOrder}
</span>
</div>
</div>
<div className="flex shrink-0 items-center gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => handleEdit(original)}
aria-label="Edit field"
>
<Pencil className="h-4 w-4" />
</Button>
<ConfirmationDialog
trigger={
<Button
variant="ghost"
size="sm"
className="text-destructive hover:text-destructive"
disabled={deletingId === original.id}
aria-label="Delete field"
>
<Trash2 className="h-4 w-4" />
</Button>
}
title="Delete Custom Field"
description={getDeleteDescription(original)}
confirmLabel="Delete Field"
onConfirm={() => handleDelete(original.id)}
loading={deletingId === original.id}
/>
</div>
</div>
</div>
)}
emptyState={
<div className="py-8 text-center">
<p className="text-muted-foreground">