feat(mobile): mobile card view for clients + interests lists

Adds optional cardRender prop to <DataTable> that switches the layout
to a vertical card list below lg: while keeping the same TanStack
table instance powering both views (pagination, sort, selection).

New shared shell:
  - <ListCard>          rounded card with optional left status accent bar,
                        whole-card link to detail page, top-right actions
                        slot, and tactile hover/active states.
  - <ListCardAvatar>    40px brand-tinted circle (initials or domain icon).
  - <ListCardMeta>      inline icon + muted text segment.
  - deriveInitials()    shared helper that ignores numeric tokens (so
                        "Recovery Test 1777" -> "RT", not "R1").

Clients and interests pages now render mobile cards via cardRender
using this shell; desktop view (lg+) is unchanged. Interests cards
encode pipeline stage as a left-edge accent strip whose saturation
deepens with pipeline progression (open -> completed). Berths display
with an Anchor icon; null-berth interests fall back to a Compass +
"General interest" italic label. Hot leads get a discreet "Hot" pill.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-01 15:27:53 +02:00
parent 71da6e8fdc
commit 6009ccb7de
6 changed files with 492 additions and 1 deletions

View File

@@ -16,6 +16,7 @@ import { ArchiveConfirmDialog } from '@/components/shared/archive-confirm-dialog
import { PermissionGate } from '@/components/shared/permission-gate';
import { ClientForm } from '@/components/clients/client-form';
import { clientFilterDefinitions } from '@/components/clients/client-filters';
import { ClientCard } from '@/components/clients/client-card';
import { getClientColumns, type ClientRow } from '@/components/clients/client-columns';
import { usePaginatedQuery } from '@/hooks/use-paginated-query';
import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation';
@@ -118,6 +119,14 @@ export function ClientList() {
onSortChange={setSort}
isLoading={isFetching && !isLoading}
getRowId={(row) => row.id}
cardRender={(row) => (
<ClientCard
client={row.original}
portSlug={portSlug}
onEdit={setEditClient}
onArchive={setArchiveClient}
/>
)}
emptyState={
<EmptyState
title="No clients found"