diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index c42f178..eb386ba 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -13,6 +13,7 @@ import { PermissionsProvider } from '@/providers/permissions-provider'; import { Sidebar } from '@/components/layout/sidebar'; import { Topbar } from '@/components/layout/topbar'; import { MobileLayout } from '@/components/layout/mobile/mobile-layout'; +import { RealtimeToasts } from '@/components/shared/realtime-toasts'; export default async function DashboardLayout({ children }: { children: React.ReactNode }) { const session = await auth.api.getSession({ headers: await headers() }); @@ -38,6 +39,7 @@ export default async function DashboardLayout({ children }: { children: React.Re + {/* Desktop shell — hidden by CSS on mobile */}
(); + const portSlug = params?.portSlug ?? ''; + + const { data, isLoading } = useQuery({ + queryKey: ['interests', { berthId, sort: 'dateLastContact', order: 'desc' }], + queryFn: () => + apiFetch( + `/api/v1/interests?berthId=${berthId}&limit=10&sort=dateLastContact&order=desc`, + ), + staleTime: 30_000, + }); + + const all = data?.data ?? []; + const active = all.filter((i) => !i.archivedAt && !i.outcome); + const preview = active.slice(0, PREVIEW_LIMIT); + const more = active.length - preview.length; + + if (isLoading) { + return ( + + + Interested parties + + +
+ {[0, 1, 2].map((i) => ( +
+ ))} +
+ + + ); + } + + if (active.length === 0) { + return ( + + + + + Interested parties + + + +

No active interests on this berth.

+
+
+ ); + } + + return ( + + + + + Interested parties + + {active.length} + + + + +
    + {preview.map((i) => { + const lastIso = i.dateLastContact ?? i.updatedAt ?? null; + const lastActivity = lastIso + ? formatDistanceToNowStrict(new Date(lastIso), { addSuffix: true }) + : null; + const urgency = computeUrgencyBadges(i); + const initials = (i.clientName ?? '?') + .split(/\s+/) + .filter(Boolean) + .slice(0, 2) + .map((p) => p[0]!.toUpperCase()) + .join(''); + return ( +
  • + + + {initials || '?'} + +
    +
    + + {i.clientName ?? 'Unknown'} + + + {stageLabel(i.pipelineStage)} + + {urgency.map((b) => ( + + {b.label} + + ))} +
    + {lastActivity ? ( +

    + Last activity {lastActivity} +

    + ) : null} +
    + + +
  • + ); + })} +
+ {more > 0 ? ( + + View all {active.length} interests → + + ) : null} +
+
+ ); +} diff --git a/src/components/berths/berth-tabs.tsx b/src/components/berths/berth-tabs.tsx index 3ea6b9e..2fb1514 100644 --- a/src/components/berths/berth-tabs.tsx +++ b/src/components/berths/berth-tabs.tsx @@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { TagBadge } from '@/components/shared/tag-badge'; import { BerthReservationsTab } from './berth-reservations-tab'; import { BerthInterestsTab } from './berth-interests-tab'; +import { BerthInterestPulse } from './berth-interest-pulse'; type BerthData = { id: string; @@ -72,93 +73,99 @@ function OverviewTab({ berth }: { berth: BerthData }) { : null; return ( -
- {/* Specifications */} - - - Specifications - - - - - - - - - - - - - - +
+ {/* Sales pulse — top-of-page so reps doing berth-level triage can see + who's interested + how warm without clicking into the Interests tab. */} + - {/* Infrastructure & Pricing */} -
+
+ {/* Specifications */} - Infrastructure - - - - - - - - - - - - - - Tenure & Pricing + Specifications + - {berth.tenureType === 'fixed_term' && ( - <> - - - - - )} - + + + + + + + + - {berth.tags.length > 0 && ( + {/* Infrastructure & Pricing */} +
- Tags + Infrastructure - -
- {berth.tags.map((tag) => ( - - ))} -
+ + + + + + +
- )} + + + + Tenure & Pricing + + + + {berth.tenureType === 'fixed_term' && ( + <> + + + + + )} + + + + + {berth.tags.length > 0 && ( + + + Tags + + +
+ {berth.tags.map((tag) => ( + + ))} +
+
+
+ )} +
); diff --git a/src/components/dashboard/dashboard-shell.tsx b/src/components/dashboard/dashboard-shell.tsx index 9c21c8e..2967a33 100644 --- a/src/components/dashboard/dashboard-shell.tsx +++ b/src/components/dashboard/dashboard-shell.tsx @@ -11,6 +11,7 @@ import { PipelineFunnelChart } from './pipeline-funnel-chart'; import { OccupancyTimelineChart } from './occupancy-timeline-chart'; import { RevenueBreakdownChart } from './revenue-breakdown-chart'; import { LeadSourceChart } from './lead-source-chart'; +import { MyRemindersRail } from './my-reminders-rail'; import { WidgetErrorBoundary } from './widget-error-boundary'; import { AlertRail } from '@/components/alerts/alert-rail'; import type { DateRange } from '@/lib/services/analytics.service'; @@ -49,7 +50,7 @@ export function DashboardShell() { actions={} /> -
+
@@ -68,7 +69,10 @@ export function DashboardShell() {
-