fix(audit-wave-9): standardize on Sheet for previews; doctrine in CLAUDE.md
Swap the one outlier (client-interests-tab.tsx) from Vaul Drawer to Sheet side=right so every detail-preview surface uses the same primitive. Document the doctrine: Sheet for side panels on both desktop and mobile; Vaul Drawer reserved for mobile-only bottom-sheet UX (currently just MoreSheet). Closes ui/ux M11. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
Bell,
|
||||
@@ -48,6 +48,7 @@ import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
import { toastError } from '@/lib/api/toast-error';
|
||||
import { useConfirmation } from '@/hooks/use-confirmation';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface InterestContactLogTabProps {
|
||||
@@ -158,6 +159,7 @@ function ContactLogRow({
|
||||
onEdit: (e: ContactLogEntry) => void;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const { confirm, dialog: confirmDialog } = useConfirmation();
|
||||
const channelMeta = CHANNEL_META[entry.channel];
|
||||
const Icon = channelMeta.icon;
|
||||
|
||||
@@ -218,10 +220,13 @@ function ContactLogRow({
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
disabled={deleteMutation.isPending}
|
||||
onClick={() => {
|
||||
if (window.confirm('Delete this contact log entry?')) {
|
||||
deleteMutation.mutate();
|
||||
}
|
||||
onClick={async () => {
|
||||
const ok = await confirm({
|
||||
title: 'Delete contact log entry',
|
||||
description: 'This cannot be undone.',
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
if (ok) deleteMutation.mutate();
|
||||
}}
|
||||
>
|
||||
<Trash2 className="mr-2 size-3.5" />
|
||||
@@ -230,6 +235,7 @@ function ContactLogRow({
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{confirmDialog}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@@ -257,7 +263,24 @@ function EmptyState({ onAdd }: { onAdd: () => void }) {
|
||||
|
||||
// ─── Compose / edit dialog ───────────────────────────────────────────────────
|
||||
|
||||
function ComposeDialog({
|
||||
function ComposeDialog(props: {
|
||||
interestId: string;
|
||||
existing?: ContactLogEntry;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}) {
|
||||
// Key-based remount: body keyed on open + existing.id so useState
|
||||
// initializers re-run each time the dialog opens with a new row.
|
||||
// Replaces the prior useEffect(setState, [open, existing]) sync.
|
||||
return (
|
||||
<ComposeDialogBody
|
||||
key={props.open ? `open:${props.existing?.id ?? 'new'}` : 'closed'}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ComposeDialogBody({
|
||||
interestId,
|
||||
existing,
|
||||
open,
|
||||
@@ -284,21 +307,6 @@ function ComposeDialog({
|
||||
existing?.followUpAt ? localIsoString(existing.followUpAt) : '',
|
||||
);
|
||||
|
||||
// Re-sync local state when the existing entry changes (e.g. opening
|
||||
// the edit dialog for a different row). useEffect, not useMemo —
|
||||
// setState in render is a Compiler red flag.
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setOccurredAt(
|
||||
existing ? localIsoString(existing.occurredAt) : localIsoString(new Date().toISOString()),
|
||||
);
|
||||
setChannel(existing?.channel ?? 'phone');
|
||||
setDirection(existing?.direction ?? 'outbound');
|
||||
setSummary(existing?.summary ?? '');
|
||||
setFollowUpAt(existing?.followUpAt ? localIsoString(existing.followUpAt) : '');
|
||||
}
|
||||
}, [open, existing]);
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const body = {
|
||||
|
||||
Reference in New Issue
Block a user