feat(client-archive): bulk-archive wizard with per-high-stakes confirmation

Replaces the single window.confirm() with a 3-stage wizard:
- preflight: counts auto/needs-reason/blocked (POST /bulk-archive-preflight)
- reasons: carousel through high-stakes clients capturing per-client
  reason (≥5 chars) — bulk endpoint accepts reasonsByClientId map
- confirm: shows the final archivable count and submits

Low-stakes still auto-archives with safe defaults; blocked clients
are skipped with a per-row reason in the preflight summary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-06 19:29:17 +02:00
parent 70105715a7
commit 7274baf1e1
4 changed files with 404 additions and 14 deletions

View File

@@ -16,6 +16,7 @@ import { ArchiveConfirmDialog } from '@/components/shared/archive-confirm-dialog
import { PermissionGate } from '@/components/shared/permission-gate';
import { TagPicker } from '@/components/shared/tag-picker';
import { BulkHardDeleteDialog } from '@/components/clients/bulk-hard-delete-dialog';
import { BulkArchiveWizard } from '@/components/clients/bulk-archive-wizard';
import { usePermissions } from '@/hooks/use-permissions';
import {
Dialog,
@@ -46,6 +47,7 @@ export function ClientList() {
);
const [tagChoice, setTagChoice] = useState<string[]>([]);
const [bulkDeleteIds, setBulkDeleteIds] = useState<string[]>([]);
const [bulkArchiveIds, setBulkArchiveIds] = useState<string[]>([]);
const { can } = usePermissions();
const canHardDelete = can('admin', 'permanently_delete_clients');
@@ -183,14 +185,7 @@ export function ClientList() {
variant: 'destructive',
onClick: (ids) => {
if (ids.length === 0) return;
if (
!window.confirm(
`Archive ${ids.length} client${ids.length === 1 ? '' : 's'}? This can be undone from the archived list.`,
)
) {
return;
}
bulkMutation.mutate({ action: 'archive', ids });
setBulkArchiveIds(ids);
},
},
...(canHardDelete
@@ -298,6 +293,13 @@ export function ClientList() {
clientIds={bulkDeleteIds}
onDeleted={() => setBulkDeleteIds([])}
/>
<BulkArchiveWizard
open={bulkArchiveIds.length > 0}
onOpenChange={(open) => !open && setBulkArchiveIds([])}
clientIds={bulkArchiveIds}
onSuccess={() => setBulkArchiveIds([])}
/>
</div>
);
}