fix(ux): T4 polish wave — empty-contact filter, redirect-on-create, friendly stage errors
F19: client form drops empty-value contacts on submit; auto-promotes first remaining row to primary if none flagged. F20: new-interest dialog redirects to the detail page on create instead of bouncing back to the list. F21: stage-transition validation errors render with STAGE_LABELS — "Yacht is required before leaving the Enquiry stage." (was "yachtId is required before leaving stage=enquiry"). F22: blocked-stage marker swapped from the ⚑ unicode glyph to a Lucide AlertTriangle with aria-label. F25: documents-hub folder selection moves to ?folder=<id> querystring so deep-link / browser-back / refresh round-trip the current folder. F26: reopen-outcome action now toasts "Outcome cleared — interest is open again." F27: stage PATCH where target === current short-circuits to a no-op return; downstream callers don't see a phantom stage_change audit row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -333,12 +333,12 @@ export function InlineStagePicker({
|
||||
) : isCurrent ? (
|
||||
<Check className="size-3.5 text-muted-foreground" aria-hidden />
|
||||
) : isOverride && canOverride ? (
|
||||
<span
|
||||
className="text-[10px] uppercase tracking-wide text-amber-600"
|
||||
title="Override required"
|
||||
>
|
||||
⚑
|
||||
</span>
|
||||
// F22: was ⚑ unicode glyph — replaced with a Lucide
|
||||
// icon to match the rest of the visual system.
|
||||
<AlertTriangle
|
||||
className="size-3.5 text-amber-600"
|
||||
aria-label="Override required"
|
||||
/>
|
||||
) : null}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
Pencil,
|
||||
Archive,
|
||||
@@ -144,6 +145,9 @@ export function InterestDetailHeader({ portSlug, interest }: InterestDetailHeade
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['interests', interest.id] });
|
||||
queryClient.invalidateQueries({ queryKey: ['interests'] });
|
||||
// F26: confirm to the user that the action ran — pre-fix the
|
||||
// button gave no feedback and reps weren't sure if it took.
|
||||
toast.success('Outcome cleared — interest is open again.');
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Loader2, ChevronsUpDown, Check, Plus } from 'lucide-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -85,6 +86,9 @@ interface InterestFormProps {
|
||||
|
||||
export function InterestForm({ open, onOpenChange, defaultClientId, interest }: InterestFormProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const router = useRouter();
|
||||
const params = useParams<{ portSlug: string }>();
|
||||
const portSlug = params?.portSlug ?? '';
|
||||
const isEdit = !!interest;
|
||||
|
||||
const [clientOpen, setClientOpen] = useState(false);
|
||||
@@ -220,13 +224,23 @@ export function InterestForm({ open, onOpenChange, defaultClientId, interest }:
|
||||
body: { tagIds: tIds },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await apiFetch('/api/v1/interests', { method: 'POST', body: enriched });
|
||||
return { id: interest!.id, created: false };
|
||||
}
|
||||
const res = await apiFetch<{ data: { id: string } }>('/api/v1/interests', {
|
||||
method: 'POST',
|
||||
body: enriched,
|
||||
});
|
||||
return { id: res.data.id, created: true };
|
||||
},
|
||||
onSuccess: () => {
|
||||
onSuccess: (result) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['interests'] });
|
||||
onOpenChange(false);
|
||||
// F20: navigate to the new interest's detail page so the rep can
|
||||
// start the workflow immediately. Edits stay in place — no point
|
||||
// re-loading the same row's detail page they just came from.
|
||||
if (result.created && portSlug) {
|
||||
router.push(`/${portSlug}/interests/${result.id}` as never);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user