feat(uat-p5): long-tail polish - tag chips, notes counts, hub context, tenancies toggle
- StageStepper renders now carry tag chips next to the progress bar (client interest cards, pipeline summary, preview sheet). - Notes tab badge on the interest detail aggregates note counts across the interest, the linked client, the linked yacht, and any companies the client is an active member of - reps see the full surface area at a glance. - Admin Settings: Tenancies Module toggle wired into the Feature Flags card. Disabling hides nav/tabs without deleting any rows; re-enabling brings them back. Service layer was already complete; this surfaces the control on the operations page. - HubRoot recent-files rows now show folder breadcrumb + entity badge (Interest/Client/Yacht/Company) so reps can tell at a glance where a file lives. Backed by listFiles enrichment (5 batched lookups per page; no per-row queries). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ import {
|
||||
type ClientInterestRow,
|
||||
} from '@/components/clients/client-pipeline-summary';
|
||||
import { InterestForm } from '@/components/interests/interest-form';
|
||||
import { TagBadge } from '@/components/shared/tag-badge';
|
||||
|
||||
const LEAD_CATEGORY_LABELS: Record<string, string> = {
|
||||
general_interest: 'General interest',
|
||||
@@ -87,6 +88,16 @@ function InterestRowItem({
|
||||
<div className="mt-3">
|
||||
<StageStepper current={stage} />
|
||||
</div>
|
||||
{interest.tags && interest.tags.length > 0 ? (
|
||||
<div className="mt-2 flex flex-wrap gap-1">
|
||||
{interest.tags.slice(0, 4).map((t) => (
|
||||
<TagBadge key={t.id} name={t.name} color={t.color} />
|
||||
))}
|
||||
{interest.tags.length > 4 ? (
|
||||
<span className="text-xs text-muted-foreground">+{interest.tags.length - 4} more</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -117,6 +128,7 @@ interface InterestDetail {
|
||||
eoiDocStatus: string | null;
|
||||
reservationDocStatus: string | null;
|
||||
contractDocStatus: string | null;
|
||||
tags?: Array<{ id: string; name: string; color: string }>;
|
||||
}
|
||||
|
||||
function useInterestDetail(id: string | null) {
|
||||
@@ -261,6 +273,13 @@ function InterestPreviewSheet({
|
||||
Pipeline progress
|
||||
</p>
|
||||
<StageStepper current={stage} />
|
||||
{detail.data?.data.tags && detail.data.data.tags.length > 0 ? (
|
||||
<div className="mt-2 flex flex-wrap gap-1">
|
||||
{detail.data.data.tags.map((t) => (
|
||||
<TagBadge key={t.id} name={t.name} color={t.color} />
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { formatDistanceToNowStrict } from 'date-fns';
|
||||
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { TagBadge } from '@/components/shared/tag-badge';
|
||||
import { deriveInterestBerthLabel } from '@/lib/templates/interest-berth-label';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
@@ -38,6 +39,9 @@ export interface ClientInterestRow {
|
||||
desiredWidthFt?: string | null;
|
||||
desiredDraftFt?: string | null;
|
||||
source?: string | null;
|
||||
/** Tag chips surfaced alongside the StageStepper. Ship by `getInterests`
|
||||
* (list endpoint resolves the join on every row in a single batch). */
|
||||
tags?: Array<{ id: string; name: string; color: string }>;
|
||||
}
|
||||
|
||||
interface InterestsResponse {
|
||||
@@ -223,6 +227,16 @@ function HeroVariant({ clientId, portSlug }: { clientId: string; portSlug: strin
|
||||
<div className="mt-1.5">
|
||||
<StageStepper current={stage} size="xs" />
|
||||
</div>
|
||||
{top.tags && top.tags.length > 0 ? (
|
||||
<div className="mt-1.5 flex flex-wrap gap-1">
|
||||
{top.tags.slice(0, 4).map((t) => (
|
||||
<TagBadge key={t.id} name={t.name} color={t.color} />
|
||||
))}
|
||||
{top.tags.length > 4 ? (
|
||||
<span className="text-xs text-muted-foreground">+{top.tags.length - 4} more</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
@@ -339,6 +353,16 @@ function PanelVariant({ clientId, portSlug }: { clientId: string; portSlug: stri
|
||||
<div className="mt-1">
|
||||
<StageStepper current={stage} size="xs" />
|
||||
</div>
|
||||
{i.tags && i.tags.length > 0 ? (
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{i.tags.slice(0, 3).map((t) => (
|
||||
<TagBadge key={t.id} name={t.name} color={t.color} />
|
||||
))}
|
||||
{i.tags.length > 3 ? (
|
||||
<span className="text-xs text-muted-foreground">+{i.tags.length - 3}</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<ChevronRight
|
||||
className="size-3.5 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5"
|
||||
|
||||
Reference in New Issue
Block a user