fix(audit-wave-9): adopt StatusPill for berth + user status badges

- Extend StatusPill with berth (available/under_offer/sold) and user
  (enabled/disabled) variants so every "this thing is in state X" pill
  shares one primitive and palette.
- Swap berth-card, berth-detail-header, berth-columns from ad-hoc
  bg-green-100 / bg-yellow-100 / bg-red-100 Tailwind tuples to
  <StatusPill status="...">.
- Swap UserList Active/Disabled <Badge> and user-card Inactive pill to
  StatusPill; Super-Admin chip kept as a domain-specific accent (violet).

Closes ui/ux M1+M2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 11:54:13 +02:00
parent 4233aa3ac3
commit a49ee1c347
7 changed files with 67 additions and 70 deletions

View File

@@ -97,7 +97,7 @@ function lastActivityFor(interest: ClientInterestRow): string | null {
}
/** Full interest record returned by `/api/v1/interests/[id]`. Only the fields
* the drawer actually reads are typed here; the API returns more. */
* the preview sheet actually reads are typed here; the API returns more. */
interface InterestDetail {
id: string;
pipelineStage: string;
@@ -114,7 +114,7 @@ interface InterestDetail {
function useInterestDetail(id: string | null) {
return useQuery<{ data: InterestDetail }>({
queryKey: ['interest-detail-drawer', id],
queryKey: ['interest-detail-preview', id],
queryFn: () => apiFetch<{ data: InterestDetail }>(`/api/v1/interests/${id}`),
enabled: id !== null,
// Detail rarely changes during a single drawer-open session; stale-time
@@ -132,7 +132,7 @@ function formatDate(value: string | null | undefined): string | null {
return format(d, 'MMM d, yyyy');
}
/** A single milestone row inside the drawer's milestone summary. Filled
/** A single milestone row inside the preview sheet's milestone summary. Filled
* circle when the step is done, hollow when pending. Trailing meta line
* shows the date stamp or a "pending" hint. */
function MilestoneRow({
@@ -162,14 +162,13 @@ function MilestoneRow({
}
/**
* Bottom-sheet preview of a single interest. Designed for the mobile
* "tap an interest → see what's happening without leaving the client
* page" flow. Shows the pipeline progress, a compact milestone summary
* (EOI / Deposit / Contract), lead context, last contact, and a notes
* teaser. Tap-out / drag-down dismisses; the full edit page is one tap
* away via "Open full page →".
* Right-side sheet preview of a single interest. "Tap an interest → see
* what's happening without leaving the client page". Shows the pipeline
* progress, a compact milestone summary (EOI / Deposit / Contract),
* lead context, last contact, and a notes teaser. Tap-out / Esc
* dismisses; the full edit page is one tap away via "Open full page →".
*/
function InterestPreviewDrawer({
function InterestPreviewSheet({
interest,
portSlug,
onClose,
@@ -178,10 +177,10 @@ function InterestPreviewDrawer({
portSlug: string;
onClose: () => void;
}) {
// Pin the most recently selected interest so the drawer stays populated
// during the close-animation tail (Vaul keeps the content mounted ~250ms
// after `open=false`). Conditional setState is safe here - the guard
// ensures it only fires when the prop actually changes to a new row.
// Pin the most recently selected interest so the sheet stays populated
// during the close-animation tail (Radix keeps the content mounted
// through the slide-out). Conditional setState is safe here - the
// guard ensures it only fires when the prop actually changes.
const [pinned, setPinned] = useState<ClientInterestRow | null>(interest);
if (interest && interest !== pinned) setPinned(interest);
const showing = pinned;
@@ -448,7 +447,7 @@ export function ClientInterestsTab({ clientId }: ClientInterestsTabProps) {
</div>
) : null}
<InterestPreviewDrawer
<InterestPreviewSheet
interest={previewInterest}
portSlug={portSlug}
onClose={() => setPreviewInterest(null)}