'use client'; import { useState } from 'react'; import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation'; import { usePortContext } from '@/providers/port-provider'; import { PageHeader } from '@/components/shared/page-header'; import { ActivityFeed } from './activity-feed'; import { DateRangePicker } from './date-range-picker'; 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 { WebsiteGlanceTile } from './website-glance-tile'; import { WidgetErrorBoundary } from './widget-error-boundary'; import { AlertRail } from '@/components/alerts/alert-rail'; import { isCustomRange, type DateRange } from '@/lib/analytics/range'; const PRESET_LABELS: Record<'today' | '7d' | '30d' | '90d', string> = { today: 'Today', '7d': 'Last 7 days', '30d': 'Last 30 days', '90d': 'Last 90 days', }; function rangeLabel(range: DateRange): string { if (isCustomRange(range)) { const fmt: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC', }; const from = new Date(`${range.from}T00:00:00.000Z`).toLocaleDateString('en-US', fmt); const to = new Date(`${range.to}T00:00:00.000Z`).toLocaleDateString('en-US', fmt); return `${from} – ${to}`; } return PRESET_LABELS[range]; } export function DashboardShell() { const [range, setRange] = useState('30d'); const { currentPort } = usePortContext(); const portName = currentPort?.name ?? 'this port'; // Use a partial query-key prefix (no range segment) for invalidations. // Reading: "any cached analytics result, regardless of range, please // refetch on this event." This avoids any chance that a custom-range // object literal hashes differently than the one stored in the cache, // and keeps the invalidation surface broad enough to refresh whichever // range the user is currently looking at. useRealtimeInvalidation({ 'interest:stageChanged': [ ['analytics', 'pipeline_funnel'], ['analytics', 'lead_source_attribution'], ['dashboard', 'kpis'], ], 'client:created': [['dashboard', 'kpis']], 'berth:statusChanged': [ ['analytics', 'occupancy_timeline'], ['dashboard', 'kpis'], ], }); return (
{rangeLabel(range)}} variant="gradient" actions={} /> {/* `items-start` is critical: without it, the right-column aside is stretched to match the chart column's row height, which forces MyRemindersRail (or any other child with `h-full`) to push later children out of the aside's box and into the rows below where ActivityFeed renders. */}
); }