fix(audit): frontend HIGHs — surface fetch errors, kill href=#, invalidate queries, toast over alert

R2-H10: webhook-delivery-log and audit-log-list both swallowed fetch
errors silently — failed loads showed spinner forever or stale data.
Both now set a loadError state, show an inline retry banner, and fire
a toast.error. Same applies to audit-log loadMore.

R2-H11: audit-log-card rendered as `<a href="#">` — tapping on mobile
inserted `#` in the URL and scrolled to top (back-button trap).
ListCard now treats `href` as optional and renders a non-link `<div>`
when omitted; audit-log-card no longer passes href.

R2-H12: smart-archive-dialog only invalidated ['clients'] / ['berths']
/ ['interests']. Detail header kept showing Archived=false until hard
reload. Now also invalidates ['clients', clientId] and removes the
['client-archive-dossier', clientId] cache so re-open re-fetches.

R2-H13: client-list bulk mutation used native alert() on partial
failure (blocking the page) and had no onError handler. Replaced with
toast.warning / toast.success / toast.error.

1175/1175 vitest passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-06 22:18:14 +02:00
parent a8c6c071e6
commit 5fc68a5f34
6 changed files with 79 additions and 31 deletions

View File

@@ -4,6 +4,7 @@ import { useState } from 'react';
import { useParams } from 'next/navigation';
import { Plus, Archive, Tag as TagIcon, TagsIcon, Trash2 } from 'lucide-react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { DataTable } from '@/components/shared/data-table';
@@ -102,9 +103,14 @@ export function ClientList() {
queryClient.invalidateQueries({ queryKey: ['clients'] });
const s = res.data.summary;
if (s.failed > 0) {
alert(`${s.succeeded} of ${s.total} succeeded. ${s.failed} failed.`);
toast.warning(`${s.succeeded} of ${s.total} succeeded. ${s.failed} failed.`);
} else if (s.succeeded > 0) {
toast.success(`${s.succeeded} client${s.succeeded === 1 ? '' : 's'} updated.`);
}
},
onError: (err: unknown) => {
toast.error(err instanceof Error ? err.message : 'Bulk action failed');
},
});
const columns = getClientColumns({

View File

@@ -209,6 +209,11 @@ export function SmartArchiveDialog({ open, onOpenChange, clientId, clientName, o
: `${clientName} archived.`,
);
qc.invalidateQueries({ queryKey: ['clients'] });
// Invalidate the single-client query AND the dossier so detail
// pages re-fetch (header now shows Archived badge) and a re-open
// of the dialog re-fetches a fresh dossier.
qc.invalidateQueries({ queryKey: ['clients', clientId] });
qc.removeQueries({ queryKey: ['client-archive-dossier', clientId] });
qc.invalidateQueries({ queryKey: ['berths'] });
qc.invalidateQueries({ queryKey: ['interests'] });
onOpenChange(false);