From 0df761f4ad188c6a47f4ec68918f918e2b6ed3a7 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 13 May 2026 12:00:35 +0200 Subject: [PATCH] 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) --- .../custom-fields/custom-fields-manager.tsx | 60 +++++++++++++++ .../document-templates/template-list.tsx | 76 +++++++++++++++++-- src/components/admin/ports/port-list.tsx | 44 +++++++++-- src/components/admin/roles/role-list.tsx | 61 +++++++++++++++ src/components/admin/tags/tag-list.tsx | 46 +++++++++++ 5 files changed, 277 insertions(+), 10 deletions(-) diff --git a/src/components/admin/custom-fields/custom-fields-manager.tsx b/src/components/admin/custom-fields/custom-fields-manager.tsx index 6bf784f9..c778de8a 100644 --- a/src/components/admin/custom-fields/custom-fields-manager.tsx +++ b/src/components/admin/custom-fields/custom-fields-manager.tsx @@ -192,6 +192,66 @@ export function CustomFieldsManager() { data={filteredFields} isLoading={loading} getRowId={(row) => row.id} + cardRender={({ original }) => ( +
+
+
+

+ {original.fieldLabel} +

+

+ {original.fieldName} +

+
+ + {FIELD_TYPE_LABELS[original.fieldType] ?? original.fieldType} + + {original.isRequired ? ( + + Required + + ) : ( + Optional + )} + + · + + + Order {original.sortOrder} + +
+
+
+ + + + + } + title="Delete Custom Field" + description={getDeleteDescription(original)} + confirmLabel="Delete Field" + onConfirm={() => handleDelete(original.id)} + loading={deletingId === original.id} + /> +
+
+
+ )} emptyState={

diff --git a/src/components/admin/document-templates/template-list.tsx b/src/components/admin/document-templates/template-list.tsx index 585fa875..1bf7a2d2 100644 --- a/src/components/admin/document-templates/template-list.tsx +++ b/src/components/admin/document-templates/template-list.tsx @@ -10,6 +10,7 @@ import { PageHeader } from '@/components/shared/page-header'; import { ConfirmationDialog } from '@/components/shared/confirmation-dialog'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; +import { StatusPill } from '@/components/ui/status-pill'; import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'; import { apiFetch } from '@/lib/api/client'; import { TemplateForm } from './template-form'; @@ -117,11 +118,12 @@ export function TemplateList() { { accessorKey: 'isActive', header: 'Status', - cell: ({ row }) => ( - - {row.original.isActive ? 'Active' : 'Inactive'} - - ), + cell: ({ row }) => + row.original.isActive ? ( + Active + ) : ( + Inactive + ), }, { accessorKey: 'updatedAt', @@ -197,6 +199,70 @@ export function TemplateList() { columns={columns} data={templates} isLoading={loading} + getRowId={(row) => row.id} + cardRender={({ original }) => ( +

+
+
+
+ +

{original.name}

+
+
+ + {TYPE_LABELS[original.templateType] ?? original.templateType} + + v{original.version} + {original.isActive ? ( + Active + ) : ( + Inactive + )} + · + + Updated{' '} + {new Date(original.updatedAt).toLocaleDateString('en-GB', { + day: '2-digit', + month: 'short', + year: 'numeric', + })} + +
+
+
+ + + + + + + } + title="Delete Template" + description={`Are you sure you want to delete "${original.name}"? This action cannot be undone.`} + confirmLabel="Delete" + destructive + onConfirm={() => handleDeleteTemplate(original.id)} + /> +
+
+
+ )} emptyState={
No document templates yet. Create your first template to get started. diff --git a/src/components/admin/ports/port-list.tsx b/src/components/admin/ports/port-list.tsx index 1bd29cdc..e025463e 100644 --- a/src/components/admin/ports/port-list.tsx +++ b/src/components/admin/ports/port-list.tsx @@ -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 ? ( - - Active - + Active ) : ( - Inactive + Inactive ), }, { @@ -124,6 +122,42 @@ export function PortList() { data={ports} isLoading={loading} getRowId={(row) => row.id} + cardRender={({ original }) => ( +
+
+
+ {original.primaryColor ? ( + + ) : null} +

{original.name}

+ {original.isActive ? ( + Active + ) : ( + Inactive + )} +
+
+ {original.slug} + {original.defaultCurrency} + · + {original.timezone} +
+
+ +
+ )} emptyState={

No ports configured.

diff --git a/src/components/admin/roles/role-list.tsx b/src/components/admin/roles/role-list.tsx index 0ea0ba13..cac036ef 100644 --- a/src/components/admin/roles/role-list.tsx +++ b/src/components/admin/roles/role-list.tsx @@ -173,6 +173,67 @@ export function RoleList() { data={roles} isLoading={loading} getRowId={(row) => row.id} + cardRender={({ original }) => ( +
+
+
+
+

+ {formatRole(original.name)} +

+ {original.isSystem ? ( + + + System + + ) : null} +
+ {original.description ? ( +

{original.description}

+ ) : null} + +
+
+ + {!original.isSystem ? ( + + + + } + title="Delete Role" + description={`Delete "${original.name}"? Users assigned to this role must be reassigned first.`} + confirmLabel="Delete" + onConfirm={() => deleteMutation.mutate(original.id)} + loading={deleteMutation.isPending && deleteMutation.variables === original.id} + /> + ) : null} +
+
+
+ )} emptyState={

No roles defined.

diff --git a/src/components/admin/tags/tag-list.tsx b/src/components/admin/tags/tag-list.tsx index 5d48e978..c580ade5 100644 --- a/src/components/admin/tags/tag-list.tsx +++ b/src/components/admin/tags/tag-list.tsx @@ -128,6 +128,52 @@ export function TagList() { data={tags} isLoading={loading} getRowId={(row) => row.id} + cardRender={({ original }) => ( +
+
+ +
+

{original.name}

+

+ {original.color} + {' · '} + Created {new Date(original.createdAt).toLocaleDateString()} +

+
+
+
+ + + + + } + title="Delete Tag" + description={`Are you sure you want to delete "${original.name}"? This action cannot be undone.`} + confirmLabel="Delete" + onConfirm={() => deleteMutation.mutate(original.id)} + loading={deleteMutation.isPending && deleteMutation.variables === original.id} + /> +
+
+ )} emptyState={

No tags yet.