feat(mobile): mobile cards for reminders, audit log, users

Three new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender.

  - ReminderCard:   Bell icon, related-entity subtitle (User/Anchor/
                    FileText icon by entity type), due-date meta with
                    past-due flag, accent bar (rose=past-due,
                    amber=pending, slate=snoozed, emerald=done).
                    Snooze/Complete/Edit/Delete in actions menu.
  - AuditLogCard:   Action icon (Plus/Pencil/Trash2/Eye), entity
                    title, "{verb} by {actor}" subtitle, timestamp
                    meta, optional changed-field chip line. Accent
                    bar by action (created=emerald, updated=blue,
                    deleted=rose). Immutable, no actions menu.
  - UserCard:       Initials avatar, displayName/email, role meta
                    (Shield icon), last-login distance, "Inactive"
                    pill when deactivated. Accent bar (violet=
                    super_admin, slate=inactive, none=active).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-01 15:39:06 +02:00
parent 722491a9dd
commit bcea28cd71
6 changed files with 584 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from 'react';
import { type ColumnDef } from '@tanstack/react-table';
import { Plus, CheckCircle2, Clock, XCircle, AlertTriangle, Bell } from 'lucide-react';
import { formatDistanceToNow } from 'date-fns';
import { useParams } from 'next/navigation';
import { DataTable } from '@/components/shared/data-table';
import { PageHeader } from '@/components/shared/page-header';
@@ -19,6 +20,7 @@ import {
} from '@/components/ui/select';
import { apiFetch } from '@/lib/api/client';
import { usePermissions } from '@/hooks/use-permissions';
import { ReminderCard } from './reminder-card';
import { ReminderForm } from './reminder-form';
import { SnoozeDialog } from './snooze-dialog';
@@ -69,6 +71,8 @@ export function ReminderList() {
const [total, setTotal] = useState(0);
const { can } = usePermissions();
const canViewAll = can('reminders', 'view_all');
const params = useParams<{ portSlug: string }>();
const portSlug = params?.portSlug ?? '';
const fetchReminders = useCallback(async () => {
setLoading(true);
@@ -290,6 +294,19 @@ export function ReminderList() {
data={reminders}
isLoading={loading}
getRowId={(row) => row.id}
cardRender={(row) => (
<ReminderCard
reminder={row.original}
portSlug={portSlug}
onComplete={handleComplete}
onSnooze={setSnoozingId}
onDismiss={handleDismiss}
onEdit={(r) => {
setEditingReminder(r);
setFormOpen(true);
}}
/>
)}
emptyState={
<div className="text-center py-8">
<Bell className="mx-auto h-8 w-8 text-muted-foreground mb-2" />