@burdenoff/microfe-eventfullymanaged 2026.704.7 → 2026.704.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"EventCharts.js","names":[],"sources":["../../../src/eventfullymanaged/components/EventCharts.tsx"],"sourcesContent":["import { useId } from 'react';\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n Cell,\n Pie,\n PieChart,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from 'recharts';\n\n/**\n * Token-driven chart primitives for EventfullyManaged analytics surfaces.\n *\n * All fills/strokes resolve to `--color-chart-*` / `--color-action-*` CSS\n * variables so the charts theme with the rest of the platform (light + dark)\n * and never hard-code a hex. Every chart renders its own empty state so callers\n * can hand it real (possibly empty) query data without branching.\n */\n\nconst CHART_TOOLTIP_STYLE = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-default)',\n borderRadius: '10px',\n fontSize: '12px',\n color: 'var(--color-text-primary)',\n boxShadow: 'var(--shadow-elevation-2)',\n} as const;\n\n/** The categorical palette, in draw order. Backed by semantic chart tokens. */\nexport const CHART_SERIES_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n] as const;\n\n/** Alias — same categorical palette (kept for call sites that import CHART_COLORS). */\nexport const CHART_COLORS = CHART_SERIES_COLORS;\n\nfunction ChartEmpty({ label, height }: { label: string; height: number }) {\n return (\n <div\n className=\"flex items-center justify-center rounded-card border border-dashed border-border-subtle text-sm text-text-muted\"\n style={{ height }}\n >\n {label}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Area chart — a single smooth series (e.g. registrations over time).\n// ---------------------------------------------------------------------------\n\nexport interface AreaPoint {\n label: string;\n value: number;\n}\n\nexport function EventAreaChart({\n data,\n height = 260,\n emptyLabel = 'Not enough data yet',\n}: {\n data: AreaPoint[];\n height?: number;\n emptyLabel?: string;\n}) {\n const gradientId = useId().replace(/:/g, '');\n if (data.length === 0) {\n return <ChartEmpty label={emptyLabel} height={height} />;\n }\n\n return (\n <ResponsiveContainer width=\"100%\" height={height}>\n <AreaChart data={data} margin={{ top: 8, right: 12, left: 4, bottom: 4 }}>\n <defs>\n <linearGradient id={`efm-area-${gradientId}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"var(--color-chart-2)\" stopOpacity={0.45} />\n <stop offset=\"100%\" stopColor=\"var(--color-chart-1)\" stopOpacity={0.04} />\n </linearGradient>\n <linearGradient id={`efm-stroke-${gradientId}`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n <stop offset=\"0%\" stopColor=\"var(--color-chart-1)\" />\n <stop offset=\"100%\" stopColor=\"var(--color-chart-2)\" />\n </linearGradient>\n </defs>\n <XAxis\n dataKey=\"label\"\n tick={{ fill: 'var(--color-text-muted)', fontSize: 11 }}\n tickLine={false}\n axisLine={{ stroke: 'var(--color-border-subtle)' }}\n minTickGap={24}\n />\n <YAxis\n tick={{ fill: 'var(--color-text-muted)', fontSize: 11 }}\n tickLine={false}\n axisLine={false}\n width={44}\n allowDecimals={false}\n />\n <Tooltip contentStyle={CHART_TOOLTIP_STYLE} cursor={{ stroke: 'var(--color-border-strong)' }} />\n <Area\n type=\"monotone\"\n dataKey=\"value\"\n name=\"Value\"\n stroke={`url(#efm-stroke-${gradientId})`}\n strokeWidth={2.5}\n fill={`url(#efm-area-${gradientId})`}\n dot={false}\n activeDot={{ r: 4, fill: 'var(--color-chart-2)', stroke: 'var(--color-bg-surface)', strokeWidth: 2 }}\n />\n </AreaChart>\n </ResponsiveContainer>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Donut chart — a categorical breakdown (e.g. registrations by status).\n// ---------------------------------------------------------------------------\n\nexport interface DonutSlice {\n name: string;\n value: number;\n color?: string;\n}\n\nexport function EventDonutChart({\n data,\n height = 240,\n centerLabel,\n centerValue,\n emptyLabel = 'No data yet',\n}: {\n data: DonutSlice[];\n height?: number;\n centerLabel?: string;\n centerValue?: string;\n emptyLabel?: string;\n}) {\n const total = data.reduce((sum, slice) => sum + slice.value, 0);\n if (total === 0) {\n return <ChartEmpty label={emptyLabel} height={height} />;\n }\n\n return (\n <div className=\"relative\" style={{ height }}>\n <ResponsiveContainer width=\"100%\" height={height}>\n <PieChart>\n <Pie\n data={data}\n cx=\"50%\"\n cy=\"50%\"\n innerRadius=\"62%\"\n outerRadius=\"88%\"\n paddingAngle={2}\n dataKey=\"value\"\n nameKey=\"name\"\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {data.map((slice, index) => (\n <Cell key={slice.name} fill={slice.color ?? CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length]} />\n ))}\n </Pie>\n <Tooltip contentStyle={CHART_TOOLTIP_STYLE} />\n </PieChart>\n </ResponsiveContainer>\n {centerValue ? (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">{centerValue}</span>\n {centerLabel ? <span className=\"text-xs text-text-muted\">{centerLabel}</span> : null}\n </div>\n ) : null}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Bar chart — discrete category counts (e.g. entity totals).\n// ---------------------------------------------------------------------------\n\nexport interface BarDatum {\n name: string;\n value: number;\n}\n\nexport function EventBarChart({\n data,\n height = 260,\n emptyLabel = 'No data yet',\n}: {\n data: BarDatum[];\n height?: number;\n emptyLabel?: string;\n}) {\n const gradientId = useId().replace(/:/g, '');\n if (data.length === 0 || data.every((datum) => datum.value === 0)) {\n return <ChartEmpty label={emptyLabel} height={height} />;\n }\n\n return (\n <ResponsiveContainer width=\"100%\" height={height}>\n <BarChart data={data} margin={{ top: 8, right: 12, left: 4, bottom: 4 }}>\n <defs>\n <linearGradient id={`efm-bar-${gradientId}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"var(--color-chart-2)\" />\n <stop offset=\"100%\" stopColor=\"var(--color-chart-1)\" />\n </linearGradient>\n </defs>\n <XAxis\n dataKey=\"name\"\n tick={{ fill: 'var(--color-text-muted)', fontSize: 11 }}\n tickLine={false}\n axisLine={{ stroke: 'var(--color-border-subtle)' }}\n interval={0}\n />\n <YAxis\n tick={{ fill: 'var(--color-text-muted)', fontSize: 11 }}\n tickLine={false}\n axisLine={false}\n width={44}\n allowDecimals={false}\n />\n <Tooltip contentStyle={CHART_TOOLTIP_STYLE} cursor={{ fill: 'var(--color-bg-sunken)', opacity: 0.6 }} />\n <Bar dataKey=\"value\" name=\"Count\" fill={`url(#efm-bar-${gradientId})`} radius={[6, 6, 0, 0]} maxBarSize={56} />\n </BarChart>\n </ResponsiveContainer>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Legend — a shared, accessible legend for the charts above.\n// ---------------------------------------------------------------------------\n\nexport interface ChartLegendItem {\n label: string;\n color: string;\n value?: string | number;\n}\n\nexport function ChartLegend({ items, className = '' }: { items: ChartLegendItem[]; className?: string }) {\n return (\n <ul className={`flex flex-col gap-2 ${className}`}>\n {items.map((item) => (\n <li key={item.label} className=\"flex items-center gap-2 text-sm\">\n <span\n aria-hidden\n className=\"h-3 w-3 shrink-0 rounded-full\"\n style={{ backgroundColor: item.color }}\n />\n <span className=\"text-text-secondary\">{item.label}</span>\n {item.value !== undefined ? (\n <span className=\"ml-auto font-semibold tabular-nums text-text-primary\">{item.value}</span>\n ) : null}\n </li>\n ))}\n </ul>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EventCharts.js","names":[],"sources":["../../../src/eventfullymanaged/components/EventCharts.tsx"],"sourcesContent":["import { useId } from 'react';\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n Cell,\n Pie,\n PieChart,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from 'recharts';\n\n/**\n * Token-driven chart primitives for EventfullyManaged analytics surfaces.\n *\n * All fills/strokes resolve to `--color-chart-*` / `--color-action-*` CSS\n * variables so the charts theme with the rest of the platform (light + dark)\n * and never hard-code a hex. Every chart renders its own empty state so callers\n * can hand it real (possibly empty) query data without branching.\n */\n\nconst CHART_TOOLTIP_STYLE = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-default)',\n borderRadius: '10px',\n fontSize: '12px',\n color: 'var(--color-text-primary)',\n boxShadow: 'var(--shadow-elevation-2)',\n} as const;\n\n/** The categorical palette, in draw order. Backed by semantic chart tokens. */\nexport const CHART_SERIES_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n] as const;\n\n/** Alias — same categorical palette (kept for call sites that import CHART_COLORS). */\nexport const CHART_COLORS = CHART_SERIES_COLORS;\n\nfunction ChartEmpty({ label, height }: { label: string; height: number }) {\n return (\n <div\n className=\"flex items-center justify-center rounded-card border border-dashed border-border-subtle text-sm text-text-muted\"\n style={{ height }}\n >\n {label}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Area chart — a single smooth series (e.g. registrations over time).\n// ---------------------------------------------------------------------------\n\nexport interface AreaPoint {\n label: string;\n value: number;\n}\n\nexport function EventAreaChart({\n data,\n height = 260,\n emptyLabel = 'Not enough data yet',\n}: {\n data: AreaPoint[];\n height?: number;\n emptyLabel?: string;\n}) {\n const gradientId = useId().replace(/:/g, '');\n if (data.length === 0) {\n return <ChartEmpty label={emptyLabel} height={height} />;\n }\n\n return (\n <ResponsiveContainer width=\"100%\" height={height}>\n <AreaChart data={data} margin={{ top: 8, right: 12, left: 4, bottom: 4 }}>\n <defs>\n <linearGradient id={`efm-area-${gradientId}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"var(--color-chart-2)\" stopOpacity={0.45} />\n <stop offset=\"100%\" stopColor=\"var(--color-chart-1)\" stopOpacity={0.04} />\n </linearGradient>\n <linearGradient id={`efm-stroke-${gradientId}`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n <stop offset=\"0%\" stopColor=\"var(--color-chart-1)\" />\n <stop offset=\"100%\" stopColor=\"var(--color-chart-2)\" />\n </linearGradient>\n </defs>\n <XAxis\n dataKey=\"label\"\n tick={{ fill: 'var(--color-text-muted)', fontSize: 11 }}\n tickLine={false}\n axisLine={{ stroke: 'var(--color-border-subtle)' }}\n minTickGap={24}\n />\n <YAxis\n tick={{ fill: 'var(--color-text-muted)', fontSize: 11 }}\n tickLine={false}\n axisLine={false}\n width={44}\n allowDecimals={false}\n />\n <Tooltip contentStyle={CHART_TOOLTIP_STYLE} cursor={{ stroke: 'var(--color-border-strong)' }} />\n <Area\n type=\"monotone\"\n dataKey=\"value\"\n name=\"Value\"\n stroke={`url(#efm-stroke-${gradientId})`}\n strokeWidth={2.5}\n fill={`url(#efm-area-${gradientId})`}\n dot={false}\n activeDot={{ r: 4, fill: 'var(--color-chart-2)', stroke: 'var(--color-bg-surface)', strokeWidth: 2 }}\n />\n </AreaChart>\n </ResponsiveContainer>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Donut chart — a categorical breakdown (e.g. registrations by status).\n// ---------------------------------------------------------------------------\n\nexport interface DonutSlice {\n name: string;\n value: number;\n color?: string;\n}\n\nexport function EventDonutChart({\n data,\n height = 240,\n centerLabel,\n centerValue,\n emptyLabel = 'No data yet',\n}: {\n data: DonutSlice[];\n height?: number;\n centerLabel?: string;\n centerValue?: string;\n emptyLabel?: string;\n}) {\n const total = data.reduce((sum, slice) => sum + slice.value, 0);\n if (total === 0) {\n return <ChartEmpty label={emptyLabel} height={height} />;\n }\n\n return (\n <div className=\"relative\" style={{ height }}>\n <ResponsiveContainer width=\"100%\" height={height}>\n <PieChart>\n <Pie\n data={data}\n cx=\"50%\"\n cy=\"50%\"\n innerRadius=\"62%\"\n outerRadius=\"88%\"\n paddingAngle={2}\n dataKey=\"value\"\n nameKey=\"name\"\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {data.map((slice, index) => (\n <Cell key={slice.name} fill={slice.color ?? CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length]} />\n ))}\n </Pie>\n <Tooltip contentStyle={CHART_TOOLTIP_STYLE} />\n </PieChart>\n </ResponsiveContainer>\n {centerValue ? (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">{centerValue}</span>\n {centerLabel ? <span className=\"text-xs text-text-muted\">{centerLabel}</span> : null}\n </div>\n ) : null}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Bar chart — discrete category counts (e.g. entity totals).\n// ---------------------------------------------------------------------------\n\nexport interface BarDatum {\n name: string;\n value: number;\n}\n\nexport function EventBarChart({\n data,\n height = 260,\n emptyLabel = 'No data yet',\n}: {\n data: BarDatum[];\n height?: number;\n emptyLabel?: string;\n}) {\n const gradientId = useId().replace(/:/g, '');\n if (data.length === 0 || data.every((datum) => datum.value === 0)) {\n return <ChartEmpty label={emptyLabel} height={height} />;\n }\n\n return (\n <ResponsiveContainer width=\"100%\" height={height}>\n <BarChart data={data} margin={{ top: 8, right: 12, left: 4, bottom: 4 }}>\n <defs>\n <linearGradient id={`efm-bar-${gradientId}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"var(--color-chart-2)\" />\n <stop offset=\"100%\" stopColor=\"var(--color-chart-1)\" />\n </linearGradient>\n </defs>\n <XAxis\n dataKey=\"name\"\n tick={{ fill: 'var(--color-text-muted)', fontSize: 11 }}\n tickLine={false}\n axisLine={{ stroke: 'var(--color-border-subtle)' }}\n interval={0}\n />\n <YAxis\n tick={{ fill: 'var(--color-text-muted)', fontSize: 11 }}\n tickLine={false}\n axisLine={false}\n width={44}\n allowDecimals={false}\n />\n <Tooltip contentStyle={CHART_TOOLTIP_STYLE} cursor={{ fill: 'var(--color-bg-sunken)', opacity: 0.6 }} />\n <Bar dataKey=\"value\" name=\"Count\" fill={`url(#efm-bar-${gradientId})`} radius={[6, 6, 0, 0]} maxBarSize={56} />\n </BarChart>\n </ResponsiveContainer>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Legend — a shared, accessible legend for the charts above.\n// ---------------------------------------------------------------------------\n\nexport interface ChartLegendItem {\n label: string;\n color: string;\n value?: string | number;\n}\n\nexport function ChartLegend({ items, className = '' }: { items: ChartLegendItem[]; className?: string }) {\n return (\n <ul className={`flex flex-col gap-2 ${className}`}>\n {items.map((item) => (\n <li key={item.label} className=\"flex items-center gap-2 text-sm\">\n <span aria-hidden className=\"h-3 w-3 shrink-0 rounded-full\" style={{ backgroundColor: item.color }} />\n <span className=\"text-text-secondary\">{item.label}</span>\n {item.value !== undefined ? (\n <span className=\"ml-auto font-semibold tabular-nums text-text-primary\">{item.value}</span>\n ) : null}\n </li>\n ))}\n </ul>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/dashboard/DashboardPage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Building2,\n CalendarClock,\n CalendarDays,\n ChevronRight,\n Handshake,\n PlayCircle,\n Ticket,\n UserPlus,\n Users,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { GlassCard, GradientText, IllustratedEmptyState, NextSteps, PagePurpose, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\n\nimport { useDashboardSummaryQuery } from '../../api/eventApi';\nimport { EventAreaChart, type AreaPoint } from '../../components/EventCharts';\nimport { PageLayout } from '../../components/PageLayout';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { formatDateTime } from '../events/eventFormUtils';\n\n// ---------------------------------------------------------------------------\n// Real supporting data for the trend chart + upcoming-sessions panel. Both are\n// dedicated operations (distinct names) so they can select the exact fields the\n// dashboard needs without touching the shared list hooks.\n// ---------------------------------------------------------------------------\n\nconst DASHBOARD_REGISTRATIONS_QUERY = gql`\n query EventfullymanagedDashboardRegistrations($limit: Int) {\n registrations: eventfullyManagedRegistrations(limit: $limit, offset: 0) {\n total\n items {\n id\n status\n registeredAt\n }\n }\n }\n`;\n\nconst DASHBOARD_UPCOMING_SESSIONS_QUERY = gql`\n query EventfullymanagedDashboardUpcomingSessions($limit: Int) {\n eventSessions: eventfullyManagedEventSessions(limit: $limit, offset: 0) {\n total\n items {\n id\n eventId\n title\n sessionType\n track\n startsAt\n endsAt\n status\n }\n }\n }\n`;\n\ntype DashboardRegistration = { id: string; status: string; registeredAt: string };\ntype DashboardRegistrationsResponse = { registrations: { total: number; items: DashboardRegistration[] } };\n\ntype DashboardSession = {\n id: string;\n eventId: string;\n title: string;\n sessionType: string;\n track?: string | null;\n startsAt: string;\n endsAt: string;\n status: string;\n};\ntype DashboardSessionsResponse = { eventSessions: { total: number; items: DashboardSession[] } };\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst TREND_DAYS = 30;\n\n/** Build a cumulative registrations-over-time series from real `registeredAt` timestamps. */\nfunction buildTrendSeries(registrations: DashboardRegistration[], nowMs: number): AreaPoint[] {\n const now = new Date(nowMs);\n const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n start.setTime(start.getTime() - (TREND_DAYS - 1) * DAY_MS);\n\n const perDay = new Array<number>(TREND_DAYS).fill(0);\n let baseline = 0; // registrations older than the window still count toward the cumulative total.\n\n for (const registration of registrations) {\n const when = new Date(registration.registeredAt);\n if (Number.isNaN(when.getTime())) {\n continue;\n }\n const dayIndex = Math.floor((when.getTime() - start.getTime()) / DAY_MS);\n if (dayIndex < 0) {\n baseline += 1;\n } else if (dayIndex < TREND_DAYS) {\n perDay[dayIndex] += 1;\n }\n }\n\n let running = baseline;\n return perDay.map((count, index) => {\n running += count;\n const day = new Date(start.getTime() + index * DAY_MS);\n return {\n label: day.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }),\n value: running,\n };\n });\n}\n\ntype StatCard = {\n label: string;\n value: number;\n sublabel: string;\n icon: React.ComponentType<{ className?: string }>;\n path: string;\n featured?: boolean;\n};\n\nexport default function DashboardPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { navigate } = useEventfullyManaged();\n const { data, loading } = useDashboardSummaryQuery();\n const { data: registrationsData, loading: registrationsLoading } = useQuery<\n DashboardRegistrationsResponse,\n { limit: number }\n >(DASHBOARD_REGISTRATIONS_QUERY, { variables: { limit: 500 } });\n const { data: sessionsData, loading: sessionsLoading } = useQuery<DashboardSessionsResponse, { limit: number }>(\n DASHBOARD_UPCOMING_SESSIONS_QUERY,\n { variables: { limit: 100 } }\n );\n\n const summary = data?.dashboardSummary;\n // Capture \"now\" once (state initialisers may run impure code) so the memoised\n // trend + upcoming filters stay pure across re-renders.\n const [nowMs] = useState(() => Date.now());\n\n const trend = useMemo(\n () => buildTrendSeries(registrationsData?.registrations?.items ?? [], nowMs),\n [registrationsData, nowMs]\n );\n\n const upcomingSessions = useMemo(() => {\n return (sessionsData?.eventSessions?.items ?? [])\n .filter((session) => {\n const startMs = new Date(session.startsAt).getTime();\n return !Number.isNaN(startMs) && startMs >= nowMs;\n })\n .sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime())\n .slice(0, 4);\n }, [sessionsData, nowMs]);\n\n const cards: StatCard[] = [\n {\n label: t('eventfullymanaged.dashboard.cards.totalEvents', 'Total Events'),\n value: summary?.totalEvents ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.totalEventsSub', 'across your workspace'),\n icon: CalendarDays,\n path: '/events',\n featured: true,\n },\n {\n label: t('eventfullymanaged.dashboard.cards.activeEvents', 'Active Events'),\n value: summary?.activeEvents ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.activeEventsSub', 'live or published now'),\n icon: PlayCircle,\n path: '/events',\n },\n {\n label: t('eventfullymanaged.dashboard.cards.registrations', 'Registrations'),\n value: summary?.totalRegistrations ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.registrationsSub', 'sign-ups received'),\n icon: UserPlus,\n path: '/registration',\n },\n {\n label: t('eventfullymanaged.dashboard.cards.attendees', 'Attendees'),\n value: summary?.totalAttendees ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.attendeesSub', 'people in your audience'),\n icon: Users,\n path: '/registration',\n },\n {\n label: t('eventfullymanaged.dashboard.cards.venues', 'Venues'),\n value: summary?.totalVenues ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.venuesSub', 'spaces mapped'),\n icon: Building2,\n path: '/venues',\n },\n {\n label: t('eventfullymanaged.dashboard.cards.sponsors', 'Sponsors'),\n value: summary?.totalSponsors ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.sponsorsSub', 'partners onboarded'),\n icon: Handshake,\n path: '/sponsors',\n },\n ];\n\n return (\n <PageLayout\n hero\n title={<GradientText as=\"span\">{t('eventfullymanaged.dashboard.title', 'Dashboard')}</GradientText>}\n description={\n loading\n ? t('eventfullymanaged.dashboard.description.loading', 'Loading real-time event operations summary…')\n : t('eventfullymanaged.dashboard.description.ready', 'Your live event operations, at a glance.')\n }\n >\n <PagePurpose className=\"mb-6\">\n {t(\n 'eventfullymanaged.dashboard.purpose',\n 'Your event control center. Each tile below is a live count from a different part of your event operations. Open any tile to manage that area, watch registrations climb in the trend chart, and jump straight to the sessions coming up next.'\n )}\n </PagePurpose>\n\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-6\">\n {cards.map((card) => {\n const Icon = card.icon;\n return (\n <GlassCard\n key={card.label}\n treatment={card.featured ? 'glass' : 'metric'}\n glow={card.featured}\n role=\"button\"\n tabIndex={0}\n aria-label={`${card.label}: ${card.value.toLocaleString()}`}\n onClick={() => navigate(card.path)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(card.path);\n }\n }}\n className=\"group cursor-pointer p-4 outline-none transition-[transform,box-shadow] duration-200 hover:shadow-elevation-1 focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <span className=\"flex h-9 w-9 items-center justify-center rounded-button bg-accent-wash text-action-primary-bg\">\n <Icon className=\"h-5 w-5\" />\n </span>\n <div className=\"mt-3 text-2xl font-semibold tabular-nums text-text-primary\">\n {loading ? <Skeleton className=\"h-7 w-14\" /> : card.value.toLocaleString()}\n </div>\n <div className=\"mt-1 text-sm font-medium text-text-primary\">{card.label}</div>\n <div className=\"mt-0.5 truncate text-xs text-text-muted\">{card.sublabel}</div>\n </GlassCard>\n );\n })}\n </div>\n\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-5\">\n <GlassCard treatment=\"glass\" className=\"p-5 lg:col-span-3\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('eventfullymanaged.dashboard.trend.title', 'Registrations over time')}\n </h2>\n <p className=\"mt-0.5 text-xs text-text-muted\">\n {t('eventfullymanaged.dashboard.trend.subtitle', 'Cumulative sign-ups, last 30 days')}\n </p>\n </div>\n <div className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {(summary?.totalRegistrations ?? 0).toLocaleString()}\n </div>\n </div>\n <div className=\"mt-4\">\n {registrationsLoading ? (\n <Skeleton className=\"h-[260px] w-full rounded-card\" />\n ) : trend.some((point) => point.value > 0) ? (\n <EventAreaChart data={trend} />\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('eventfullymanaged.dashboard.trend.empty', 'No registrations yet.')}\n description={t(\n 'eventfullymanaged.dashboard.trend.emptyHint',\n 'Once attendees start signing up, their momentum shows here.'\n )}\n className=\"py-10\"\n />\n )}\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5 lg:col-span-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('eventfullymanaged.dashboard.upcoming.title', 'Upcoming sessions')}\n </h2>\n <div className=\"mt-3\">\n {sessionsLoading ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-14 w-full rounded-card\" />\n <Skeleton className=\"h-14 w-full rounded-card\" />\n <Skeleton className=\"h-14 w-full rounded-card\" />\n </div>\n ) : upcomingSessions.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('eventfullymanaged.dashboard.upcoming.empty', 'Nothing scheduled yet.')}\n description={t(\n 'eventfullymanaged.dashboard.upcoming.emptyHint',\n 'Sessions you schedule will surface here as they approach.'\n )}\n className=\"py-8\"\n />\n ) : (\n <ul className=\"divide-y divide-border-subtle\">\n {upcomingSessions.map((session) => (\n <li key={session.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`/events/${session.eventId}`)}\n className=\"group flex w-full items-center gap-3 py-3 text-left outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-button bg-accent-wash text-action-primary-bg\">\n <CalendarClock className=\"h-4 w-4\" />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">{session.title}</span>\n <span className=\"mt-0.5 block truncate text-xs text-text-muted\">\n {[formatDateTime(session.startsAt), session.track].filter(Boolean).join(' • ')}\n </span>\n </span>\n <ChevronRight className=\"h-4 w-4 shrink-0 text-text-muted transition-transform group-hover:translate-x-0.5\" />\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n </GlassCard>\n </div>\n\n <NextSteps\n className=\"mt-6\"\n storageKey=\"eventfullymanaged-dashboard\"\n title={t('eventfullymanaged.dashboard.nextSteps.title', 'Get your first event running')}\n steps={[\n {\n id: 'create-event',\n label: t('eventfullymanaged.dashboard.nextSteps.createEvent', 'Create an event'),\n description: t(\n 'eventfullymanaged.dashboard.nextSteps.createEventDesc',\n 'Start with the basics — name, dates and capacity.'\n ),\n icon: <CalendarDays className=\"h-4 w-4\" />,\n onClick: () => navigate('/events/create'),\n },\n {\n id: 'add-venue',\n label: t('eventfullymanaged.dashboard.nextSteps.addVenue', 'Add a venue'),\n description: t(\n 'eventfullymanaged.dashboard.nextSteps.addVenueDesc',\n 'Map your event to a space with capacity and floor plans.'\n ),\n icon: <Building2 className=\"h-4 w-4\" />,\n onClick: () => navigate('/venues/create'),\n },\n {\n id: 'open-registration',\n label: t('eventfullymanaged.dashboard.nextSteps.openRegistration', 'Open registration'),\n description: t(\n 'eventfullymanaged.dashboard.nextSteps.openRegistrationDesc',\n 'Set up ticket types and track attendee sign-ups.'\n ),\n icon: <Ticket className=\"h-4 w-4\" />,\n onClick: () => navigate('/registration'),\n },\n {\n id: 'invite-sponsors',\n label: t('eventfullymanaged.dashboard.nextSteps.inviteSponsors', 'Onboard sponsors'),\n description: t(\n 'eventfullymanaged.dashboard.nextSteps.inviteSponsorsDesc',\n 'Add sponsors and start tracking value and leads.'\n ),\n icon: <Handshake className=\"h-4 w-4\" />,\n onClick: () => navigate('/sponsors'),\n },\n ]}\n />\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,IAAM,IAAgC,CAAG;;;;;;;;;;;GAanC,IAAoC,CAAG;;;;;;;;;;;;;;;;GAiCvC,IAAS,OAAU,KAAK,KACxB,IAAa;AAGnB,SAAS,EAAiB,GAAwC,GAA4B;CAC5F,IAAM,IAAM,IAAI,KAAK,EAAM,EACrB,IAAQ,IAAI,KAAK,EAAI,aAAa,EAAE,EAAI,UAAU,EAAE,EAAI,SAAS,CAAC;AACxE,GAAM,QAAQ,EAAM,SAAS,IAAI,IAAa,KAAK,EAAO;CAE1D,IAAM,IAAa,MAAc,EAAW,CAAC,KAAK,EAAE,EAChD,IAAW;AAEf,MAAK,IAAM,KAAgB,GAAe;EACxC,IAAM,IAAO,IAAI,KAAK,EAAa,aAAa;AAChD,MAAI,OAAO,MAAM,EAAK,SAAS,CAAC,CAC9B;EAEF,IAAM,IAAW,KAAK,OAAO,EAAK,SAAS,GAAG,EAAM,SAAS,IAAI,EAAO;AACxE,EAAI,IAAW,IACb,KAAY,IACH,IAAW,MACpB,EAAO,MAAa;;CAIxB,IAAI,IAAU;AACd,QAAO,EAAO,KAAK,GAAO,OACxB,KAAW,GAEJ;EACL,OAFU,IAAI,KAAK,EAAM,SAAS,GAAG,IAAQ,EAAO,CAEzC,mBAAmB,KAAA,GAAW;GAAE,OAAO;GAAS,KAAK;GAAW,CAAC;EAC5E,OAAO;EACR,EACD;;AAYJ,SAAwB,IAAgB;CACtC,IAAM,EAAK,MAAc,GAAS,EAC5B,KAAK,GAAa,MAAqB;EAC3C,IAAM,IAAQ,EAAU,EAAI;AAC5B,SAAO,MAAU,IAAM,IAAW;IAE9B,EAAE,gBAAa,GAAsB,EACrC,EAAE,SAAM,eAAY,GAA0B,EAC9C,EAAE,MAAM,GAAmB,SAAS,MAAyB,EAGjE,GAA+B,EAAE,WAAW,EAAE,OAAO,KAAK,EAAE,CAAC,EACzD,EAAE,MAAM,GAAc,SAAS,MAAoB,EACvD,GACA,EAAE,WAAW,EAAE,OAAO,KAAK,EAAE,CAC9B,EAEK,IAAU,GAAM,kBAGhB,CAAC,KAAS,QAAe,KAAK,KAAK,CAAC,EAEpC,IAAQ,QACN,EAAiB,GAAmB,eAAe,SAAS,EAAE,EAAE,EAAM,EAC5E,CAAC,GAAmB,EAAM,CAC3B,EAEK,IAAmB,SACf,GAAc,eAAe,SAAS,EAAE,EAC7C,QAAQ,MAAY;EACnB,IAAM,IAAU,IAAI,KAAK,EAAQ,SAAS,CAAC,SAAS;AACpD,SAAO,CAAC,OAAO,MAAM,EAAQ,IAAI,KAAW;GAC5C,CACD,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,CAC/E,MAAM,GAAG,EAAE,EACb,CAAC,GAAc,EAAM,CAAC,EAEnB,IAAoB;EACxB;GACE,OAAO,EAAE,iDAAiD,eAAe;GACzE,OAAO,GAAS,eAAe;GAC/B,UAAU,EAAE,oDAAoD,wBAAwB;GACxF,MAAM;GACN,MAAM;GACN,UAAU;GACX;EACD;GACE,OAAO,EAAE,kDAAkD,gBAAgB;GAC3E,OAAO,GAAS,gBAAgB;GAChC,UAAU,EAAE,qDAAqD,wBAAwB;GACzF,MAAM;GACN,MAAM;GACP;EACD;GACE,OAAO,EAAE,mDAAmD,gBAAgB;GAC5E,OAAO,GAAS,sBAAsB;GACtC,UAAU,EAAE,sDAAsD,oBAAoB;GACtF,MAAM;GACN,MAAM;GACP;EACD;GACE,OAAO,EAAE,+CAA+C,YAAY;GACpE,OAAO,GAAS,kBAAkB;GAClC,UAAU,EAAE,kDAAkD,0BAA0B;GACxF,MAAM;GACN,MAAM;GACP;EACD;GACE,OAAO,EAAE,4CAA4C,SAAS;GAC9D,OAAO,GAAS,eAAe;GAC/B,UAAU,EAAE,+CAA+C,gBAAgB;GAC3E,MAAM;GACN,MAAM;GACP;EACD;GACE,OAAO,EAAE,8CAA8C,WAAW;GAClE,OAAO,GAAS,iBAAiB;GACjC,UAAU,EAAE,iDAAiD,qBAAqB;GAClF,MAAM;GACN,MAAM;GACP;EACF;AAED,QACE,kBAAC,GAAD;EACE,MAAA;EACA,OAAO,kBAAC,GAAD;GAAc,IAAG;aAAQ,EAAE,qCAAqC,YAAY;GAAgB,CAAA;EACnG,aACE,IACI,EAAE,mDAAmD,8CAA8C,GACnG,EAAE,iDAAiD,2CAA2C;YANtG;GASE,kBAAC,GAAD;IAAa,WAAU;cACpB,EACC,uCACA,gPACD;IACW,CAAA;GAEd,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAM,KAAK,MAAS;KACnB,IAAM,IAAO,EAAK;AAClB,YACE,kBAAC,GAAD;MAEE,WAAW,EAAK,WAAW,UAAU;MACrC,MAAM,EAAK;MACX,MAAK;MACL,UAAU;MACV,cAAY,GAAG,EAAK,MAAM,IAAI,EAAK,MAAM,gBAAgB;MACzD,eAAe,EAAS,EAAK,KAAK;MAClC,YAAY,MAAU;AACpB,QAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,EAAS,EAAK,KAAK;;MAGvB,WAAU;gBAdZ;OAgBE,kBAAC,QAAD;QAAM,WAAU;kBACd,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;QACvB,CAAA;OACP,kBAAC,OAAD;QAAK,WAAU;kBACZ,IAAU,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA,GAAG,EAAK,MAAM,gBAAgB;QACtE,CAAA;OACN,kBAAC,OAAD;QAAK,WAAU;kBAA8C,EAAK;QAAY,CAAA;OAC9E,kBAAC,OAAD;QAAK,WAAU;kBAA2C,EAAK;QAAe,CAAA;OACpE;QAvBL,EAAK,MAuBA;MAEd;IACE,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAE,2CAA2C,0BAA0B;OACrE,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAE,8CAA8C,oCAAoC;OACnF,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;kBACX,GAAS,sBAAsB,GAAG,gBAAgB;OAChD,CAAA,CACF;SACN,kBAAC,OAAD;MAAK,WAAU;gBACZ,IACC,kBAAC,GAAD,EAAU,WAAU,iCAAkC,CAAA,GACpD,EAAM,MAAM,MAAU,EAAM,QAAQ,EAAE,GACxC,kBAAC,GAAD,EAAgB,MAAM,GAAS,CAAA,GAE/B,kBAAC,GAAD;OACE,cAAa;OACb,OAAO,EAAE,2CAA2C,wBAAwB;OAC5E,aAAa,EACX,+CACA,8DACD;OACD,WAAU;OACV,CAAA;MAEA,CAAA,CACI;QAEZ,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAE,8CAA8C,oBAAoB;MAClE,CAAA,EACL,kBAAC,OAAD;MAAK,WAAU;gBACZ,IACC,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD,EAAU,WAAU,4BAA6B,CAAA;QACjD,kBAAC,GAAD,EAAU,WAAU,4BAA6B,CAAA;QACjD,kBAAC,GAAD,EAAU,WAAU,4BAA6B,CAAA;QAC7C;WACJ,EAAiB,WAAW,IAC9B,kBAAC,GAAD;OACE,cAAa;OACb,OAAO,EAAE,8CAA8C,yBAAyB;OAChF,aAAa,EACX,kDACA,4DACD;OACD,WAAU;OACV,CAAA,GAEF,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAiB,KAAK,MACrB,kBAAC,MAAD,EAAA,UACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAS,WAAW,EAAQ,UAAU;QACrD,WAAU;kBAHZ;SAKE,kBAAC,QAAD;UAAM,WAAU;oBACd,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;UAChC,CAAA;SACP,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAwD,EAAQ;WAAa,CAAA,EAC7F,kBAAC,QAAD;WAAM,WAAU;qBACb,CAAC,EAAe,EAAQ,SAAS,EAAE,EAAQ,MAAM,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM;WACzE,CAAA,CACF;;SACP,kBAAC,GAAD,EAAc,WAAU,qFAAsF,CAAA;SACvG;WACN,EAjBI,EAAQ,GAiBZ,CACL;OACC,CAAA;MAEH,CAAA,CACI;OACR;;GAEN,kBAAC,GAAD;IACE,WAAU;IACV,YAAW;IACX,OAAO,EAAE,+CAA+C,+BAA+B;IACvF,OAAO;KACL;MACE,IAAI;MACJ,OAAO,EAAE,qDAAqD,kBAAkB;MAChF,aAAa,EACX,yDACA,oDACD;MACD,MAAM,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;MAC1C,eAAe,EAAS,iBAAiB;MAC1C;KACD;MACE,IAAI;MACJ,OAAO,EAAE,kDAAkD,cAAc;MACzE,aAAa,EACX,sDACA,2DACD;MACD,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MACvC,eAAe,EAAS,iBAAiB;MAC1C;KACD;MACE,IAAI;MACJ,OAAO,EAAE,0DAA0D,oBAAoB;MACvF,aAAa,EACX,8DACA,mDACD;MACD,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;MACpC,eAAe,EAAS,gBAAgB;MACzC;KACD;MACE,IAAI;MACJ,OAAO,EAAE,wDAAwD,mBAAmB;MACpF,aAAa,EACX,4DACA,mDACD;MACD,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MACvC,eAAe,EAAS,YAAY;MACrC;KACF;IACD,CAAA;GACS"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/dashboard/DashboardPage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Building2,\n CalendarClock,\n CalendarDays,\n ChevronRight,\n Handshake,\n PlayCircle,\n Ticket,\n UserPlus,\n Users,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\n\nimport { useDashboardSummaryQuery } from '../../api/eventApi';\nimport { EventAreaChart, type AreaPoint } from '../../components/EventCharts';\nimport { PageLayout } from '../../components/PageLayout';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { formatDateTime } from '../events/eventFormUtils';\n\n// ---------------------------------------------------------------------------\n// Real supporting data for the trend chart + upcoming-sessions panel. Both are\n// dedicated operations (distinct names) so they can select the exact fields the\n// dashboard needs without touching the shared list hooks.\n// ---------------------------------------------------------------------------\n\nconst DASHBOARD_REGISTRATIONS_QUERY = gql`\n query EventfullymanagedDashboardRegistrations($limit: Int) {\n registrations: eventfullyManagedRegistrations(limit: $limit, offset: 0) {\n total\n items {\n id\n status\n registeredAt\n }\n }\n }\n`;\n\nconst DASHBOARD_UPCOMING_SESSIONS_QUERY = gql`\n query EventfullymanagedDashboardUpcomingSessions($limit: Int) {\n eventSessions: eventfullyManagedEventSessions(limit: $limit, offset: 0) {\n total\n items {\n id\n eventId\n title\n sessionType\n track\n startsAt\n endsAt\n status\n }\n }\n }\n`;\n\ntype DashboardRegistration = { id: string; status: string; registeredAt: string };\ntype DashboardRegistrationsResponse = { registrations: { total: number; items: DashboardRegistration[] } };\n\ntype DashboardSession = {\n id: string;\n eventId: string;\n title: string;\n sessionType: string;\n track?: string | null;\n startsAt: string;\n endsAt: string;\n status: string;\n};\ntype DashboardSessionsResponse = { eventSessions: { total: number; items: DashboardSession[] } };\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst TREND_DAYS = 30;\n\n/** Build a cumulative registrations-over-time series from real `registeredAt` timestamps. */\nfunction buildTrendSeries(registrations: DashboardRegistration[], nowMs: number): AreaPoint[] {\n const now = new Date(nowMs);\n const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n start.setTime(start.getTime() - (TREND_DAYS - 1) * DAY_MS);\n\n const perDay = new Array<number>(TREND_DAYS).fill(0);\n let baseline = 0; // registrations older than the window still count toward the cumulative total.\n\n for (const registration of registrations) {\n const when = new Date(registration.registeredAt);\n if (Number.isNaN(when.getTime())) {\n continue;\n }\n const dayIndex = Math.floor((when.getTime() - start.getTime()) / DAY_MS);\n if (dayIndex < 0) {\n baseline += 1;\n } else if (dayIndex < TREND_DAYS) {\n perDay[dayIndex] += 1;\n }\n }\n\n let running = baseline;\n return perDay.map((count, index) => {\n running += count;\n const day = new Date(start.getTime() + index * DAY_MS);\n return {\n label: day.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }),\n value: running,\n };\n });\n}\n\ntype StatCard = {\n label: string;\n value: number;\n sublabel: string;\n icon: React.ComponentType<{ className?: string }>;\n path: string;\n featured?: boolean;\n};\n\nexport default function DashboardPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { navigate } = useEventfullyManaged();\n const { data, loading } = useDashboardSummaryQuery();\n const { data: registrationsData, loading: registrationsLoading } = useQuery<\n DashboardRegistrationsResponse,\n { limit: number }\n >(DASHBOARD_REGISTRATIONS_QUERY, { variables: { limit: 500 } });\n const { data: sessionsData, loading: sessionsLoading } = useQuery<DashboardSessionsResponse, { limit: number }>(\n DASHBOARD_UPCOMING_SESSIONS_QUERY,\n { variables: { limit: 100 } }\n );\n\n const summary = data?.dashboardSummary;\n // Capture \"now\" once (state initialisers may run impure code) so the memoised\n // trend + upcoming filters stay pure across re-renders.\n const [nowMs] = useState(() => Date.now());\n\n const trend = useMemo(\n () => buildTrendSeries(registrationsData?.registrations?.items ?? [], nowMs),\n [registrationsData, nowMs]\n );\n\n const upcomingSessions = useMemo(() => {\n return (sessionsData?.eventSessions?.items ?? [])\n .filter((session) => {\n const startMs = new Date(session.startsAt).getTime();\n return !Number.isNaN(startMs) && startMs >= nowMs;\n })\n .sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime())\n .slice(0, 4);\n }, [sessionsData, nowMs]);\n\n const cards: StatCard[] = [\n {\n label: t('eventfullymanaged.dashboard.cards.totalEvents', 'Total Events'),\n value: summary?.totalEvents ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.totalEventsSub', 'across your workspace'),\n icon: CalendarDays,\n path: '/events',\n featured: true,\n },\n {\n label: t('eventfullymanaged.dashboard.cards.activeEvents', 'Active Events'),\n value: summary?.activeEvents ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.activeEventsSub', 'live or published now'),\n icon: PlayCircle,\n path: '/events',\n },\n {\n label: t('eventfullymanaged.dashboard.cards.registrations', 'Registrations'),\n value: summary?.totalRegistrations ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.registrationsSub', 'sign-ups received'),\n icon: UserPlus,\n path: '/registration',\n },\n {\n label: t('eventfullymanaged.dashboard.cards.attendees', 'Attendees'),\n value: summary?.totalAttendees ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.attendeesSub', 'people in your audience'),\n icon: Users,\n path: '/registration',\n },\n {\n label: t('eventfullymanaged.dashboard.cards.venues', 'Venues'),\n value: summary?.totalVenues ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.venuesSub', 'spaces mapped'),\n icon: Building2,\n path: '/venues',\n },\n {\n label: t('eventfullymanaged.dashboard.cards.sponsors', 'Sponsors'),\n value: summary?.totalSponsors ?? 0,\n sublabel: t('eventfullymanaged.dashboard.cards.sponsorsSub', 'partners onboarded'),\n icon: Handshake,\n path: '/sponsors',\n },\n ];\n\n return (\n <PageLayout\n hero\n title={<GradientText as=\"span\">{t('eventfullymanaged.dashboard.title', 'Dashboard')}</GradientText>}\n description={\n loading\n ? t('eventfullymanaged.dashboard.description.loading', 'Loading real-time event operations summary…')\n : t('eventfullymanaged.dashboard.description.ready', 'Your live event operations, at a glance.')\n }\n >\n <PagePurpose className=\"mb-6\">\n {t(\n 'eventfullymanaged.dashboard.purpose',\n 'Your event control center. Each tile below is a live count from a different part of your event operations. Open any tile to manage that area, watch registrations climb in the trend chart, and jump straight to the sessions coming up next.'\n )}\n </PagePurpose>\n\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-6\">\n {cards.map((card) => {\n const Icon = card.icon;\n return (\n <GlassCard\n key={card.label}\n treatment={card.featured ? 'glass' : 'metric'}\n glow={card.featured}\n role=\"button\"\n tabIndex={0}\n aria-label={`${card.label}: ${card.value.toLocaleString()}`}\n onClick={() => navigate(card.path)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n navigate(card.path);\n }\n }}\n className=\"group cursor-pointer p-4 outline-none transition-[transform,box-shadow] duration-200 hover:shadow-elevation-1 focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <span className=\"flex h-9 w-9 items-center justify-center rounded-button bg-accent-wash text-action-primary-bg\">\n <Icon className=\"h-5 w-5\" />\n </span>\n <div className=\"mt-3 text-2xl font-semibold tabular-nums text-text-primary\">\n {loading ? <Skeleton className=\"h-7 w-14\" /> : card.value.toLocaleString()}\n </div>\n <div className=\"mt-1 text-sm font-medium text-text-primary\">{card.label}</div>\n <div className=\"mt-0.5 truncate text-xs text-text-muted\">{card.sublabel}</div>\n </GlassCard>\n );\n })}\n </div>\n\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-5\">\n <GlassCard treatment=\"glass\" className=\"p-5 lg:col-span-3\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('eventfullymanaged.dashboard.trend.title', 'Registrations over time')}\n </h2>\n <p className=\"mt-0.5 text-xs text-text-muted\">\n {t('eventfullymanaged.dashboard.trend.subtitle', 'Cumulative sign-ups, last 30 days')}\n </p>\n </div>\n <div className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {(summary?.totalRegistrations ?? 0).toLocaleString()}\n </div>\n </div>\n <div className=\"mt-4\">\n {registrationsLoading ? (\n <Skeleton className=\"h-[260px] w-full rounded-card\" />\n ) : trend.some((point) => point.value > 0) ? (\n <EventAreaChart data={trend} />\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('eventfullymanaged.dashboard.trend.empty', 'No registrations yet.')}\n description={t(\n 'eventfullymanaged.dashboard.trend.emptyHint',\n 'Once attendees start signing up, their momentum shows here.'\n )}\n className=\"py-10\"\n />\n )}\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5 lg:col-span-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('eventfullymanaged.dashboard.upcoming.title', 'Upcoming sessions')}\n </h2>\n <div className=\"mt-3\">\n {sessionsLoading ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-14 w-full rounded-card\" />\n <Skeleton className=\"h-14 w-full rounded-card\" />\n <Skeleton className=\"h-14 w-full rounded-card\" />\n </div>\n ) : upcomingSessions.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('eventfullymanaged.dashboard.upcoming.empty', 'Nothing scheduled yet.')}\n description={t(\n 'eventfullymanaged.dashboard.upcoming.emptyHint',\n 'Sessions you schedule will surface here as they approach.'\n )}\n className=\"py-8\"\n />\n ) : (\n <ul className=\"divide-y divide-border-subtle\">\n {upcomingSessions.map((session) => (\n <li key={session.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`/events/${session.eventId}`)}\n className=\"group flex w-full items-center gap-3 py-3 text-left outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-button bg-accent-wash text-action-primary-bg\">\n <CalendarClock className=\"h-4 w-4\" />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">{session.title}</span>\n <span className=\"mt-0.5 block truncate text-xs text-text-muted\">\n {[formatDateTime(session.startsAt), session.track].filter(Boolean).join(' • ')}\n </span>\n </span>\n <ChevronRight className=\"h-4 w-4 shrink-0 text-text-muted transition-transform group-hover:translate-x-0.5\" />\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n </GlassCard>\n </div>\n\n <NextSteps\n className=\"mt-6\"\n storageKey=\"eventfullymanaged-dashboard\"\n title={t('eventfullymanaged.dashboard.nextSteps.title', 'Get your first event running')}\n steps={[\n {\n id: 'create-event',\n label: t('eventfullymanaged.dashboard.nextSteps.createEvent', 'Create an event'),\n description: t(\n 'eventfullymanaged.dashboard.nextSteps.createEventDesc',\n 'Start with the basics — name, dates and capacity.'\n ),\n icon: <CalendarDays className=\"h-4 w-4\" />,\n onClick: () => navigate('/events/create'),\n },\n {\n id: 'add-venue',\n label: t('eventfullymanaged.dashboard.nextSteps.addVenue', 'Add a venue'),\n description: t(\n 'eventfullymanaged.dashboard.nextSteps.addVenueDesc',\n 'Map your event to a space with capacity and floor plans.'\n ),\n icon: <Building2 className=\"h-4 w-4\" />,\n onClick: () => navigate('/venues/create'),\n },\n {\n id: 'open-registration',\n label: t('eventfullymanaged.dashboard.nextSteps.openRegistration', 'Open registration'),\n description: t(\n 'eventfullymanaged.dashboard.nextSteps.openRegistrationDesc',\n 'Set up ticket types and track attendee sign-ups.'\n ),\n icon: <Ticket className=\"h-4 w-4\" />,\n onClick: () => navigate('/registration'),\n },\n {\n id: 'invite-sponsors',\n label: t('eventfullymanaged.dashboard.nextSteps.inviteSponsors', 'Onboard sponsors'),\n description: t(\n 'eventfullymanaged.dashboard.nextSteps.inviteSponsorsDesc',\n 'Add sponsors and start tracking value and leads.'\n ),\n icon: <Handshake className=\"h-4 w-4\" />,\n onClick: () => navigate('/sponsors'),\n },\n ]}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -446,8 +446,7 @@ function Z({ eventId: t }) {
446
446
  " ",
447
447
  "sold",
448
448
  r ? /* @__PURE__ */ x(y, { children: [
449
- " ",
450
- "/ ",
449
+ " / ",
451
450
  e.capacity.toLocaleString(),
452
451
  " capacity"
453
452
  ] }) : " • unlimited capacity"
@@ -1 +1 @@
1
- {"version":3,"file":"EventDetailPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/events/EventDetailPage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n ArrowLeft,\n CalendarClock,\n Clock,\n Edit,\n Eye,\n MapPin,\n Mic2,\n Plus,\n Ticket as TicketIcon,\n Users,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n IllustratedEmptyState,\n Skeleton,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useDeleteEventSessionMutation,\n useDeleteSpeakerMutation,\n useDeleteTicketTypeMutation,\n useEventQuery,\n useSpeakersQuery,\n useTicketTypesQuery,\n useVenuesQuery,\n type TicketTypeGql,\n} from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { CHART_COLORS } from '../../components/EventCharts';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { notify } from '../../lib/toast';\nimport { formatDateTime } from './eventFormUtils';\n\nfunction statusVariant(status: string): 'success' | 'warning' | 'secondary' | 'info' {\n const value = status.toLowerCase();\n if (['active', 'published', 'live', 'confirmed', 'completed', 'approved'].includes(value)) {\n return 'success';\n }\n if (['inactive', 'cancelled', 'archived', 'closed', 'expired'].includes(value)) {\n return 'warning';\n }\n if (['draft', 'created', 'pending', 'scheduled'].includes(value)) {\n return 'info';\n }\n return 'secondary';\n}\n\nfunction formatDuration(startsAt: string, endsAt: string): string {\n const start = new Date(startsAt).getTime();\n const end = new Date(endsAt).getTime();\n if (Number.isNaN(start) || Number.isNaN(end) || end <= start) {\n return '';\n }\n const totalMinutes = Math.round((end - start) / 60000);\n const hours = Math.floor(totalMinutes / 60);\n const minutes = totalMinutes % 60;\n if (hours && minutes) {\n return `${hours}h ${minutes}m`;\n }\n if (hours) {\n return `${hours}h`;\n }\n return `${minutes}m`;\n}\n\nfunction formatHour(hour: number): string {\n const period = hour < 12 ? 'AM' : 'PM';\n const display = hour % 12 === 0 ? 12 : hour % 12;\n return `${display}:00 ${period}`;\n}\n\nfunction formatClock(iso: string): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });\n}\n\n// ---------------------------------------------------------------------------\n// Sessions tab — multi-track schedule grid.\n//\n// The shared EVENT_SESSIONS_QUERY omits `metadata` (which carries speakerId) and\n// `description`/`capacity`, so this tab owns a fuller read that selects them; the\n// grid resolves room names from venues and speaker names from speakers.\n// ---------------------------------------------------------------------------\n\nconst SCHEDULE_SESSIONS_QUERY = gql`\n query EventfullymanagedScheduleSessions($eventId: ID, $limit: Int, $offset: Int) {\n eventSessions: eventfullyManagedEventSessions(eventId: $eventId, limit: $limit, offset: $offset) {\n total\n items {\n id\n eventId\n venueId\n title\n sessionType\n track\n startsAt\n endsAt\n status\n metadata\n }\n }\n }\n`;\n\ntype ScheduleSession = {\n id: string;\n eventId: string;\n venueId?: string | null;\n title: string;\n sessionType: string;\n track?: string | null;\n startsAt: string;\n endsAt: string;\n status: string;\n metadata?: Record<string, unknown> | null;\n};\n\ntype ScheduleSessionsResponse = { eventSessions: { total: number; items: ScheduleSession[] } };\n\nconst UNTRACKED = 'General';\n\nfunction dayKey(iso: string): string {\n const date = new Date(iso);\n return Number.isNaN(date.getTime()) ? 'unknown' : date.toISOString().slice(0, 10);\n}\n\nfunction SessionBlock({\n session,\n color,\n speakerName,\n roomName,\n onEdit,\n onDelete,\n}: {\n session: ScheduleSession;\n color: string;\n speakerName?: string;\n roomName?: string;\n onEdit: () => void;\n onDelete: () => void | Promise<void>;\n}) {\n const duration = formatDuration(session.startsAt, session.endsAt);\n return (\n <div\n className=\"relative rounded-card border border-border-subtle p-3 shadow-elevation-1\"\n style={{\n borderLeftWidth: '3px',\n borderLeftColor: color,\n background: `color-mix(in srgb, ${color} 8%, var(--color-bg-surface))`,\n }}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"min-w-0 pr-1 text-sm font-semibold leading-snug text-text-primary\">{session.title}</p>\n <div className=\"-mr-1 -mt-1 shrink-0\">\n <ActionMenu\n ariaLabel={`Session actions for ${session.title}`}\n entityName={session.title}\n deleteLabel=\"Delete session\"\n onEdit={onEdit}\n onDelete={onDelete}\n />\n </div>\n </div>\n <dl className=\"mt-2 space-y-1 text-xs text-text-secondary\">\n <div className=\"flex items-center gap-1.5\">\n <Mic2 className=\"h-3.5 w-3.5 shrink-0 text-text-muted\" />\n <span className=\"truncate\">{speakerName ?? 'Speaker TBD'}</span>\n </div>\n {roomName ? (\n <div className=\"flex items-center gap-1.5\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0 text-text-muted\" />\n <span className=\"truncate\">{roomName}</span>\n </div>\n ) : null}\n <div className=\"flex items-center gap-1.5\">\n <Clock className=\"h-3.5 w-3.5 shrink-0 text-text-muted\" />\n <span>\n {formatClock(session.startsAt)}\n {duration ? ` · ${duration}` : ''}\n </span>\n </div>\n </dl>\n </div>\n );\n}\n\nfunction SessionsTab({ eventId }: { eventId: string }) {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination();\n const { data, loading, refetch } = useQuery<\n ScheduleSessionsResponse,\n { eventId?: string; limit?: number; offset?: number }\n >(SCHEDULE_SESSIONS_QUERY, {\n variables: { eventId, limit: pagination.limit, offset: pagination.offset },\n });\n const { data: venuesData } = useVenuesQuery({ limit: 100, offset: 0 });\n const { data: speakersData } = useSpeakersQuery({ limit: 100, offset: 0 });\n const [deleteEventSession] = useDeleteEventSessionMutation();\n\n const total = data?.eventSessions?.total ?? 0;\n const rows = useMemo(() => data?.eventSessions?.items ?? [], [data]);\n\n const venueNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const venue of venuesData?.venues?.items ?? []) {\n map.set(venue.id, venue.name);\n }\n return map;\n }, [venuesData]);\n\n const speakerNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const speaker of speakersData?.speakers?.items ?? []) {\n map.set(speaker.id, speaker.fullName);\n }\n return map;\n }, [speakersData]);\n\n // Distinct tracks (ordered by first appearance) → stable colour per track.\n const trackColor = useMemo(() => {\n const map = new Map<string, string>();\n let index = 0;\n for (const session of rows) {\n const track = session.track?.trim() || UNTRACKED;\n if (!map.has(track)) {\n map.set(track, CHART_COLORS[index % CHART_COLORS.length]);\n index += 1;\n }\n }\n return map;\n }, [rows]);\n\n // Group sessions by day, then by track, keyed by the hour they start.\n const days = useMemo(() => {\n const byDay = new Map<string, ScheduleSession[]>();\n for (const session of [...rows].sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime())) {\n const key = dayKey(session.startsAt);\n const list = byDay.get(key) ?? [];\n list.push(session);\n byDay.set(key, list);\n }\n return Array.from(byDay.entries()).map(([key, sessions]) => {\n const tracks = Array.from(new Set(sessions.map((s) => s.track?.trim() || UNTRACKED)));\n const hours = sessions\n .map((s) => new Date(s.startsAt).getHours())\n .filter((h) => !Number.isNaN(h));\n const minHour = hours.length ? Math.min(...hours) : 9;\n const maxHour = hours.length ? Math.max(...hours) : 17;\n const hourRange: number[] = [];\n for (let hour = minHour; hour <= maxHour; hour += 1) {\n hourRange.push(hour);\n }\n return { key, sessions, tracks, hourRange };\n });\n }, [rows]);\n\n const onDelete = async (session: ScheduleSession) => {\n const response = await deleteEventSession({ variables: { id: session.id } });\n if (response.error) {\n notify.error('Could not delete session', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteEventSession) {\n notify.error('Could not delete session', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n notify.success('Session deleted', session.title);\n await refetch();\n };\n\n const resolveSpeaker = (session: ScheduleSession): string | undefined => {\n const metadata = (session.metadata ?? {}) as Record<string, unknown>;\n const speakerId = typeof metadata.speakerId === 'string' ? metadata.speakerId : '';\n return speakerId ? speakerNameById.get(speakerId) : undefined;\n };\n const resolveRoom = (session: ScheduleSession): string | undefined =>\n session.venueId ? venueNameById.get(session.venueId) : undefined;\n\n return (\n <div className=\"space-y-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Agenda &amp; Sessions</h2>\n <p className=\"text-sm text-text-secondary\">A multi-track schedule of everything happening on the day.</p>\n </div>\n <Button type=\"button\" size=\"sm\" onClick={() => navigate(`/events/${eventId}/sessions/create`)}>\n <Plus className=\"h-4 w-4\" />\n New Session\n </Button>\n </div>\n\n {loading ? (\n <Skeleton className=\"h-64 w-full rounded-card\" />\n ) : rows.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No sessions yet.\"\n description=\"Build the agenda by adding your first session.\"\n action={\n <Button type=\"button\" size=\"sm\" onClick={() => navigate(`/events/${eventId}/sessions/create`)}>\n <Plus className=\"h-4 w-4\" />\n New Session\n </Button>\n }\n className=\"py-10\"\n />\n ) : (\n <div className=\"space-y-8\">\n {days.map((day) => (\n <section key={day.key} className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {new Date(`${day.key}T00:00:00`).toLocaleDateString(undefined, {\n weekday: 'long',\n month: 'long',\n day: 'numeric',\n })}\n </h3>\n\n {/* Desktop / tablet: multi-track grid */}\n <div className=\"hidden overflow-x-auto rounded-card border border-border-seam md:block\">\n <div\n className=\"grid min-w-max\"\n style={{ gridTemplateColumns: `88px repeat(${day.tracks.length}, minmax(200px, 1fr))` }}\n >\n {/* Header row */}\n <div className=\"sticky left-0 z-10 border-b border-border-seam bg-bg-sunken px-3 py-2.5\" />\n {day.tracks.map((track) => (\n <div\n key={track}\n className=\"border-b border-l border-border-seam bg-bg-sunken px-3 py-2.5 text-center text-sm font-semibold text-text-primary\"\n >\n <span className=\"inline-flex items-center gap-2\">\n <span\n className=\"h-2 w-2 rounded-full\"\n style={{ background: trackColor.get(track) }}\n aria-hidden=\"true\"\n />\n {track}\n </span>\n </div>\n ))}\n\n {/* Hour rows */}\n {day.hourRange.map((hour) => (\n <div key={hour} className=\"contents\">\n <div className=\"sticky left-0 z-10 border-b border-border-subtle bg-bg-surface px-3 py-3 text-xs font-medium text-text-muted\">\n {formatHour(hour)}\n </div>\n {day.tracks.map((track) => {\n const cellSessions = day.sessions.filter(\n (session) =>\n (session.track?.trim() || UNTRACKED) === track &&\n new Date(session.startsAt).getHours() === hour\n );\n return (\n <div\n key={`${hour}-${track}`}\n className=\"space-y-2 border-b border-l border-border-subtle p-2 align-top\"\n >\n {cellSessions.map((session) => (\n <SessionBlock\n key={session.id}\n session={session}\n color={trackColor.get(track) ?? CHART_COLORS[0]}\n speakerName={resolveSpeaker(session)}\n roomName={resolveRoom(session)}\n onEdit={() => navigate(`/events/${eventId}/sessions/${session.id}/edit`)}\n onDelete={() => onDelete(session)}\n />\n ))}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n\n {/* Mobile: clean time-ordered day list */}\n <ul className=\"space-y-2.5 md:hidden\">\n {day.sessions.map((session) => {\n const track = session.track?.trim() || UNTRACKED;\n return (\n <li key={session.id}>\n <SessionBlock\n session={session}\n color={trackColor.get(track) ?? CHART_COLORS[0]}\n speakerName={resolveSpeaker(session)}\n roomName={resolveRoom(session) ?? track}\n onEdit={() => navigate(`/events/${eventId}/sessions/${session.id}/edit`)}\n onDelete={() => onDelete(session)}\n />\n </li>\n );\n })}\n </ul>\n </section>\n ))}\n </div>\n )}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"sessions\"\n loading={loading}\n />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n\ntype SpeakerRow = { id: string; fullName: string; title?: string | null; organization?: string | null };\n\nfunction SpeakersTab() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination();\n const { data, loading, refetch } = useSpeakersQuery({ limit: pagination.limit, offset: pagination.offset });\n const [deleteSpeaker] = useDeleteSpeakerMutation();\n\n const total = data?.speakers?.total ?? 0;\n const rows = (data?.speakers?.items ?? []) as unknown as SpeakerRow[];\n\n const onDelete = async (speaker: SpeakerRow) => {\n const response = await deleteSpeaker({ variables: { id: speaker.id } });\n if (response.error) {\n notify.error('Could not delete speaker', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteSpeaker) {\n notify.error('Could not delete speaker', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n notify.success('Speaker deleted', speaker.fullName);\n await refetch();\n };\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Speakers</h2>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => navigate('/speakers')}>\n Manage speakers\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={() => navigate('/speakers/create')}>\n <Plus className=\"h-4 w-4\" />\n New Speaker\n </Button>\n </div>\n </div>\n\n {loading ? (\n <Skeleton className=\"h-40 w-full rounded-card\" />\n ) : rows.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No speakers yet.\"\n description=\"Add speakers so you can assign them to sessions.\"\n className=\"py-8\"\n />\n ) : (\n <ul className=\"grid grid-cols-1 gap-2.5 sm:grid-cols-2\">\n {rows.map((speaker) => (\n <li\n key={speaker.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent-wash text-sm font-semibold text-action-primary-bg\">\n {speaker.fullName.slice(0, 1).toUpperCase()}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{speaker.fullName}</p>\n <p className=\"mt-0.5 truncate text-sm text-text-secondary\">\n {[speaker.title, speaker.organization].filter(Boolean).join(' • ') || 'Speaker'}\n </p>\n </div>\n </div>\n <ActionMenu\n ariaLabel={`Speaker actions for ${speaker.fullName}`}\n entityName={speaker.fullName}\n deleteLabel=\"Delete speaker\"\n onEdit={() => navigate(`/speakers/${speaker.id}/edit`)}\n onDelete={() => onDelete(speaker)}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"speakers\"\n loading={loading}\n />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n\nfunction TicketsTab({ eventId }: { eventId: string }) {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination();\n const { data, loading, refetch } = useTicketTypesQuery({\n eventId,\n limit: pagination.limit,\n offset: pagination.offset,\n });\n const [deleteTicketType] = useDeleteTicketTypeMutation();\n\n const total = data?.ticketTypes?.total ?? 0;\n const rows = (data?.ticketTypes?.items ?? []) as TicketTypeGql[];\n\n const onDelete = async (ticket: TicketTypeGql) => {\n const response = await deleteTicketType({ variables: { id: ticket.id } });\n if (response.error) {\n notify.error('Could not delete ticket type', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteTicketType) {\n notify.error('Could not delete ticket type', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n notify.success('Ticket type deleted');\n await refetch();\n };\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Ticket Types</h2>\n <Button type=\"button\" size=\"sm\" onClick={() => navigate(`/events/${eventId}/tickets/create`)}>\n <Plus className=\"h-4 w-4\" />\n New Ticket Type\n </Button>\n </div>\n\n {loading ? (\n <Skeleton className=\"h-40 w-full rounded-card\" />\n ) : rows.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No ticket types yet.\"\n description=\"Add a ticket type so attendees can register.\"\n className=\"py-8\"\n />\n ) : (\n <ul className=\"space-y-3\">\n {rows.map((ticket) => {\n const hasCapacity = ticket.capacity != null && ticket.capacity > 0;\n const pct = hasCapacity ? Math.min(100, Math.round((ticket.soldCount / (ticket.capacity as number)) * 100)) : 0;\n const nearlyFull = pct >= 90;\n return (\n <li\n key={ticket.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-4 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{ticket.name}</p>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {ticket.price} {ticket.currency}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Badge variant={ticket.isActive ? 'success' : 'warning'} className=\"uppercase\">\n {ticket.isActive ? 'Active' : 'Inactive'}\n </Badge>\n <ActionMenu\n ariaLabel={`Ticket actions for ${ticket.name}`}\n entityName={ticket.name}\n deleteLabel=\"Delete ticket type\"\n onEdit={() => navigate(`/events/${eventId}/tickets/${ticket.id}/edit`)}\n onDelete={() => onDelete(ticket)}\n />\n </div>\n </div>\n\n <div className=\"mt-3\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n <span className=\"font-medium tabular-nums text-text-primary\">\n {ticket.soldCount.toLocaleString()}\n </span>{' '}\n sold\n {hasCapacity ? (\n <>\n {' '}\n / {(ticket.capacity as number).toLocaleString()} capacity\n </>\n ) : (\n ' • unlimited capacity'\n )}\n </span>\n {hasCapacity ? (\n <span className={nearlyFull ? 'font-medium text-text-primary' : ''}>{pct}%</span>\n ) : null}\n </div>\n {hasCapacity ? (\n <div\n className=\"mt-1.5 h-2 overflow-hidden rounded-full bg-bg-sunken\"\n role=\"progressbar\"\n aria-valuenow={pct}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={`${ticket.name} sold`}\n >\n <div\n className=\"h-full rounded-full transition-[width] duration-500\"\n style={{\n width: `${pct}%`,\n background: 'linear-gradient(90deg, var(--color-chart-1), var(--color-chart-2))',\n }}\n />\n </div>\n ) : null}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"ticket types\"\n loading={loading}\n />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n\nfunction MetricTile({ label, value, icon: Icon }: { label: string; value: string; icon: React.ComponentType<{ className?: string }> }) {\n return (\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-button bg-accent-wash text-action-primary-bg\">\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"mt-2.5 text-xl font-semibold tabular-nums text-text-primary\">{value}</div>\n <div className=\"mt-0.5 text-xs text-text-muted\">{label}</div>\n </GlassCard>\n );\n}\n\nexport default function EventDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useEventfullyManaged();\n const { data, loading } = useEventQuery(id);\n const { data: sessionsData } = useQuery<ScheduleSessionsResponse, { eventId?: string; limit?: number }>(\n SCHEDULE_SESSIONS_QUERY,\n { variables: { eventId: id, limit: 200 }, skip: !id }\n );\n const { data: ticketsData } = useTicketTypesQuery({ eventId: id, limit: 100, offset: 0 });\n const { data: venuesData } = useVenuesQuery({ limit: 100, offset: 0 });\n const [tab, setTab] = useState('overview');\n\n const event = data?.event ?? null;\n const venueId = useMemo(() => {\n const metadata = (event?.metadata ?? {}) as Record<string, unknown>;\n return typeof metadata.venueId === 'string' ? (metadata.venueId as string) : '';\n }, [event]);\n const venueName = useMemo(\n () => (venueId ? venuesData?.venues?.items?.find((venue) => venue.id === venueId)?.name : undefined),\n [venueId, venuesData]\n );\n\n const sessionCount = sessionsData?.eventSessions?.total ?? 0;\n const ticketsSold = useMemo(\n () => (ticketsData?.ticketTypes?.items ?? []).reduce((sum, ticket) => sum + (ticket.soldCount ?? 0), 0),\n [ticketsData]\n );\n\n if (loading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-8 w-40\" />\n <Skeleton className=\"h-24 w-full rounded-card\" />\n <Skeleton className=\"h-64 w-full rounded-card\" />\n </div>\n );\n }\n\n if (!event) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/events')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Events\n </Button>\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Event not found.\"\n description=\"This event may have been removed or is outside your current scope.\"\n />\n </GlassCard>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"space-y-3\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/events')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Events\n </Button>\n <div>\n <div className=\"flex flex-wrap items-center gap-3\">\n <h1 className=\"text-2xl font-bold text-text-primary\">{event.name}</h1>\n <Badge variant={statusVariant(event.status)} className=\"uppercase\">\n {event.status}\n </Badge>\n </div>\n <p className=\"mt-1 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <CalendarClock className=\"h-4 w-4 text-text-muted\" />\n {formatDateTime(event.startAt)} — {formatDateTime(event.endAt)}\n </span>\n {venueName ? (\n <span className=\"flex items-center gap-1.5\">\n <MapPin className=\"h-4 w-4 text-text-muted\" />\n {venueName}\n </span>\n ) : null}\n </p>\n </div>\n </div>\n <Button type=\"button\" onClick={() => navigate(`/events/${id}/edit`)}>\n <Edit className=\"h-4 w-4\" />\n Edit Event\n </Button>\n </div>\n\n {/* Metrics strip */}\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <MetricTile label=\"Capacity\" value={(event.capacity ?? 0).toLocaleString()} icon={Users} />\n <MetricTile label=\"Sessions\" value={sessionCount.toLocaleString()} icon={CalendarClock} />\n <MetricTile label=\"Ticket types\" value={(ticketsData?.ticketTypes?.total ?? 0).toLocaleString()} icon={TicketIcon} />\n <MetricTile label=\"Tickets sold\" value={ticketsSold.toLocaleString()} icon={Eye} />\n </div>\n\n <Tabs value={tab} onValueChange={setTab}>\n <TabsList>\n <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n <TabsTrigger value=\"sessions\">Sessions</TabsTrigger>\n <TabsTrigger value=\"speakers\">Speakers</TabsTrigger>\n <TabsTrigger value=\"tickets\">Tickets</TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"overview\" className=\"mt-4\">\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5 lg:col-span-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Event Details</h2>\n <dl className=\"mt-4 space-y-4 text-sm\">\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Dates</dt>\n <dd className=\"text-text-primary\">\n {formatDateTime(event.startAt)} — {formatDateTime(event.endAt)}\n </dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Venue</dt>\n <dd className=\"text-text-primary\">\n {venueName ? (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n className=\"h-auto p-0\"\n onClick={() => navigate(`/venues/${venueId}`)}\n >\n {venueName}\n </Button>\n ) : (\n 'No venue assigned'\n )}\n </dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Capacity</dt>\n <dd className=\"text-text-primary\">{(event.capacity ?? 0).toLocaleString()} attendees</dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Type</dt>\n <dd className=\"capitalize text-text-primary\">{event.type.toLowerCase().replace(/_/g, ' ')}</dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Timezone</dt>\n <dd className=\"text-text-primary\">{event.timezone}</dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Visibility</dt>\n <dd className=\"capitalize text-text-primary\">{event.visibility}</dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Description</dt>\n <dd className=\"text-text-secondary\">\n {event.description?.trim() ? event.description : 'No description provided.'}\n </dd>\n </div>\n </dl>\n <div className=\"mt-5\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => navigate(`/events/${id}/edit`)}>\n <Edit className=\"h-4 w-4\" />\n Edit event details\n </Button>\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"glass\" glow className=\"p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Key Metrics</h2>\n <dl className=\"mt-4 space-y-4\">\n <div>\n <dt className=\"text-sm text-text-secondary\">Capacity</dt>\n <dd className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {(event.capacity ?? 0).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm text-text-secondary\">Sessions</dt>\n <dd className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {sessionCount.toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm text-text-secondary\">Ticket types</dt>\n <dd className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {(ticketsData?.ticketTypes?.total ?? 0).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm text-text-secondary\">Tickets sold</dt>\n <dd className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {ticketsSold.toLocaleString()}\n </dd>\n </div>\n </dl>\n </GlassCard>\n </div>\n </TabsContent>\n\n <TabsContent value=\"sessions\" className=\"mt-4\">\n <SessionsTab eventId={id} />\n </TabsContent>\n\n <TabsContent value=\"speakers\" className=\"mt-4\">\n <SpeakersTab />\n </TabsContent>\n\n <TabsContent value=\"tickets\" className=\"mt-4\">\n <TicketsTab eventId={id} />\n </TabsContent>\n </Tabs>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EventDetailPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/events/EventDetailPage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n ArrowLeft,\n CalendarClock,\n Clock,\n Edit,\n Eye,\n MapPin,\n Mic2,\n Plus,\n Ticket as TicketIcon,\n Users,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n IllustratedEmptyState,\n Skeleton,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useDeleteEventSessionMutation,\n useDeleteSpeakerMutation,\n useDeleteTicketTypeMutation,\n useEventQuery,\n useSpeakersQuery,\n useTicketTypesQuery,\n useVenuesQuery,\n type TicketTypeGql,\n} from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { CHART_COLORS } from '../../components/EventCharts';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { notify } from '../../lib/toast';\nimport { formatDateTime } from './eventFormUtils';\n\nfunction statusVariant(status: string): 'success' | 'warning' | 'secondary' | 'info' {\n const value = status.toLowerCase();\n if (['active', 'published', 'live', 'confirmed', 'completed', 'approved'].includes(value)) {\n return 'success';\n }\n if (['inactive', 'cancelled', 'archived', 'closed', 'expired'].includes(value)) {\n return 'warning';\n }\n if (['draft', 'created', 'pending', 'scheduled'].includes(value)) {\n return 'info';\n }\n return 'secondary';\n}\n\nfunction formatDuration(startsAt: string, endsAt: string): string {\n const start = new Date(startsAt).getTime();\n const end = new Date(endsAt).getTime();\n if (Number.isNaN(start) || Number.isNaN(end) || end <= start) {\n return '';\n }\n const totalMinutes = Math.round((end - start) / 60000);\n const hours = Math.floor(totalMinutes / 60);\n const minutes = totalMinutes % 60;\n if (hours && minutes) {\n return `${hours}h ${minutes}m`;\n }\n if (hours) {\n return `${hours}h`;\n }\n return `${minutes}m`;\n}\n\nfunction formatHour(hour: number): string {\n const period = hour < 12 ? 'AM' : 'PM';\n const display = hour % 12 === 0 ? 12 : hour % 12;\n return `${display}:00 ${period}`;\n}\n\nfunction formatClock(iso: string): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });\n}\n\n// ---------------------------------------------------------------------------\n// Sessions tab — multi-track schedule grid.\n//\n// The shared EVENT_SESSIONS_QUERY omits `metadata` (which carries speakerId) and\n// `description`/`capacity`, so this tab owns a fuller read that selects them; the\n// grid resolves room names from venues and speaker names from speakers.\n// ---------------------------------------------------------------------------\n\nconst SCHEDULE_SESSIONS_QUERY = gql`\n query EventfullymanagedScheduleSessions($eventId: ID, $limit: Int, $offset: Int) {\n eventSessions: eventfullyManagedEventSessions(eventId: $eventId, limit: $limit, offset: $offset) {\n total\n items {\n id\n eventId\n venueId\n title\n sessionType\n track\n startsAt\n endsAt\n status\n metadata\n }\n }\n }\n`;\n\ntype ScheduleSession = {\n id: string;\n eventId: string;\n venueId?: string | null;\n title: string;\n sessionType: string;\n track?: string | null;\n startsAt: string;\n endsAt: string;\n status: string;\n metadata?: Record<string, unknown> | null;\n};\n\ntype ScheduleSessionsResponse = { eventSessions: { total: number; items: ScheduleSession[] } };\n\nconst UNTRACKED = 'General';\n\nfunction dayKey(iso: string): string {\n const date = new Date(iso);\n return Number.isNaN(date.getTime()) ? 'unknown' : date.toISOString().slice(0, 10);\n}\n\nfunction SessionBlock({\n session,\n color,\n speakerName,\n roomName,\n onEdit,\n onDelete,\n}: {\n session: ScheduleSession;\n color: string;\n speakerName?: string;\n roomName?: string;\n onEdit: () => void;\n onDelete: () => void | Promise<void>;\n}) {\n const duration = formatDuration(session.startsAt, session.endsAt);\n return (\n <div\n className=\"relative rounded-card border border-border-subtle p-3 shadow-elevation-1\"\n style={{\n borderLeftWidth: '3px',\n borderLeftColor: color,\n background: `color-mix(in srgb, ${color} 8%, var(--color-bg-surface))`,\n }}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"min-w-0 pr-1 text-sm font-semibold leading-snug text-text-primary\">{session.title}</p>\n <div className=\"-mr-1 -mt-1 shrink-0\">\n <ActionMenu\n ariaLabel={`Session actions for ${session.title}`}\n entityName={session.title}\n deleteLabel=\"Delete session\"\n onEdit={onEdit}\n onDelete={onDelete}\n />\n </div>\n </div>\n <dl className=\"mt-2 space-y-1 text-xs text-text-secondary\">\n <div className=\"flex items-center gap-1.5\">\n <Mic2 className=\"h-3.5 w-3.5 shrink-0 text-text-muted\" />\n <span className=\"truncate\">{speakerName ?? 'Speaker TBD'}</span>\n </div>\n {roomName ? (\n <div className=\"flex items-center gap-1.5\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0 text-text-muted\" />\n <span className=\"truncate\">{roomName}</span>\n </div>\n ) : null}\n <div className=\"flex items-center gap-1.5\">\n <Clock className=\"h-3.5 w-3.5 shrink-0 text-text-muted\" />\n <span>\n {formatClock(session.startsAt)}\n {duration ? ` · ${duration}` : ''}\n </span>\n </div>\n </dl>\n </div>\n );\n}\n\nfunction SessionsTab({ eventId }: { eventId: string }) {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination();\n const { data, loading, refetch } = useQuery<\n ScheduleSessionsResponse,\n { eventId?: string; limit?: number; offset?: number }\n >(SCHEDULE_SESSIONS_QUERY, {\n variables: { eventId, limit: pagination.limit, offset: pagination.offset },\n });\n const { data: venuesData } = useVenuesQuery({ limit: 100, offset: 0 });\n const { data: speakersData } = useSpeakersQuery({ limit: 100, offset: 0 });\n const [deleteEventSession] = useDeleteEventSessionMutation();\n\n const total = data?.eventSessions?.total ?? 0;\n const rows = useMemo(() => data?.eventSessions?.items ?? [], [data]);\n\n const venueNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const venue of venuesData?.venues?.items ?? []) {\n map.set(venue.id, venue.name);\n }\n return map;\n }, [venuesData]);\n\n const speakerNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const speaker of speakersData?.speakers?.items ?? []) {\n map.set(speaker.id, speaker.fullName);\n }\n return map;\n }, [speakersData]);\n\n // Distinct tracks (ordered by first appearance) → stable colour per track.\n const trackColor = useMemo(() => {\n const map = new Map<string, string>();\n let index = 0;\n for (const session of rows) {\n const track = session.track?.trim() || UNTRACKED;\n if (!map.has(track)) {\n map.set(track, CHART_COLORS[index % CHART_COLORS.length]);\n index += 1;\n }\n }\n return map;\n }, [rows]);\n\n // Group sessions by day, then by track, keyed by the hour they start.\n const days = useMemo(() => {\n const byDay = new Map<string, ScheduleSession[]>();\n for (const session of [...rows].sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime())) {\n const key = dayKey(session.startsAt);\n const list = byDay.get(key) ?? [];\n list.push(session);\n byDay.set(key, list);\n }\n return Array.from(byDay.entries()).map(([key, sessions]) => {\n const tracks = Array.from(new Set(sessions.map((s) => s.track?.trim() || UNTRACKED)));\n const hours = sessions.map((s) => new Date(s.startsAt).getHours()).filter((h) => !Number.isNaN(h));\n const minHour = hours.length ? Math.min(...hours) : 9;\n const maxHour = hours.length ? Math.max(...hours) : 17;\n const hourRange: number[] = [];\n for (let hour = minHour; hour <= maxHour; hour += 1) {\n hourRange.push(hour);\n }\n return { key, sessions, tracks, hourRange };\n });\n }, [rows]);\n\n const onDelete = async (session: ScheduleSession) => {\n const response = await deleteEventSession({ variables: { id: session.id } });\n if (response.error) {\n notify.error('Could not delete session', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteEventSession) {\n notify.error('Could not delete session', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n notify.success('Session deleted', session.title);\n await refetch();\n };\n\n const resolveSpeaker = (session: ScheduleSession): string | undefined => {\n const metadata = (session.metadata ?? {}) as Record<string, unknown>;\n const speakerId = typeof metadata.speakerId === 'string' ? metadata.speakerId : '';\n return speakerId ? speakerNameById.get(speakerId) : undefined;\n };\n const resolveRoom = (session: ScheduleSession): string | undefined =>\n session.venueId ? venueNameById.get(session.venueId) : undefined;\n\n return (\n <div className=\"space-y-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Agenda &amp; Sessions</h2>\n <p className=\"text-sm text-text-secondary\">A multi-track schedule of everything happening on the day.</p>\n </div>\n <Button type=\"button\" size=\"sm\" onClick={() => navigate(`/events/${eventId}/sessions/create`)}>\n <Plus className=\"h-4 w-4\" />\n New Session\n </Button>\n </div>\n\n {loading ? (\n <Skeleton className=\"h-64 w-full rounded-card\" />\n ) : rows.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No sessions yet.\"\n description=\"Build the agenda by adding your first session.\"\n action={\n <Button type=\"button\" size=\"sm\" onClick={() => navigate(`/events/${eventId}/sessions/create`)}>\n <Plus className=\"h-4 w-4\" />\n New Session\n </Button>\n }\n className=\"py-10\"\n />\n ) : (\n <div className=\"space-y-8\">\n {days.map((day) => (\n <section key={day.key} className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {new Date(`${day.key}T00:00:00`).toLocaleDateString(undefined, {\n weekday: 'long',\n month: 'long',\n day: 'numeric',\n })}\n </h3>\n\n {/* Desktop / tablet: multi-track grid */}\n <div className=\"hidden overflow-x-auto rounded-card border border-border-seam md:block\">\n <div\n className=\"grid min-w-max\"\n style={{ gridTemplateColumns: `88px repeat(${day.tracks.length}, minmax(200px, 1fr))` }}\n >\n {/* Header row */}\n <div className=\"sticky left-0 z-10 border-b border-border-seam bg-bg-sunken px-3 py-2.5\" />\n {day.tracks.map((track) => (\n <div\n key={track}\n className=\"border-b border-l border-border-seam bg-bg-sunken px-3 py-2.5 text-center text-sm font-semibold text-text-primary\"\n >\n <span className=\"inline-flex items-center gap-2\">\n <span\n className=\"h-2 w-2 rounded-full\"\n style={{ background: trackColor.get(track) }}\n aria-hidden=\"true\"\n />\n {track}\n </span>\n </div>\n ))}\n\n {/* Hour rows */}\n {day.hourRange.map((hour) => (\n <div key={hour} className=\"contents\">\n <div className=\"sticky left-0 z-10 border-b border-border-subtle bg-bg-surface px-3 py-3 text-xs font-medium text-text-muted\">\n {formatHour(hour)}\n </div>\n {day.tracks.map((track) => {\n const cellSessions = day.sessions.filter(\n (session) =>\n (session.track?.trim() || UNTRACKED) === track &&\n new Date(session.startsAt).getHours() === hour\n );\n return (\n <div\n key={`${hour}-${track}`}\n className=\"space-y-2 border-b border-l border-border-subtle p-2 align-top\"\n >\n {cellSessions.map((session) => (\n <SessionBlock\n key={session.id}\n session={session}\n color={trackColor.get(track) ?? CHART_COLORS[0]}\n speakerName={resolveSpeaker(session)}\n roomName={resolveRoom(session)}\n onEdit={() => navigate(`/events/${eventId}/sessions/${session.id}/edit`)}\n onDelete={() => onDelete(session)}\n />\n ))}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n\n {/* Mobile: clean time-ordered day list */}\n <ul className=\"space-y-2.5 md:hidden\">\n {day.sessions.map((session) => {\n const track = session.track?.trim() || UNTRACKED;\n return (\n <li key={session.id}>\n <SessionBlock\n session={session}\n color={trackColor.get(track) ?? CHART_COLORS[0]}\n speakerName={resolveSpeaker(session)}\n roomName={resolveRoom(session) ?? track}\n onEdit={() => navigate(`/events/${eventId}/sessions/${session.id}/edit`)}\n onDelete={() => onDelete(session)}\n />\n </li>\n );\n })}\n </ul>\n </section>\n ))}\n </div>\n )}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"sessions\"\n loading={loading}\n />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n\ntype SpeakerRow = { id: string; fullName: string; title?: string | null; organization?: string | null };\n\nfunction SpeakersTab() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination();\n const { data, loading, refetch } = useSpeakersQuery({ limit: pagination.limit, offset: pagination.offset });\n const [deleteSpeaker] = useDeleteSpeakerMutation();\n\n const total = data?.speakers?.total ?? 0;\n const rows = (data?.speakers?.items ?? []) as unknown as SpeakerRow[];\n\n const onDelete = async (speaker: SpeakerRow) => {\n const response = await deleteSpeaker({ variables: { id: speaker.id } });\n if (response.error) {\n notify.error('Could not delete speaker', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteSpeaker) {\n notify.error('Could not delete speaker', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n notify.success('Speaker deleted', speaker.fullName);\n await refetch();\n };\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Speakers</h2>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => navigate('/speakers')}>\n Manage speakers\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={() => navigate('/speakers/create')}>\n <Plus className=\"h-4 w-4\" />\n New Speaker\n </Button>\n </div>\n </div>\n\n {loading ? (\n <Skeleton className=\"h-40 w-full rounded-card\" />\n ) : rows.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No speakers yet.\"\n description=\"Add speakers so you can assign them to sessions.\"\n className=\"py-8\"\n />\n ) : (\n <ul className=\"grid grid-cols-1 gap-2.5 sm:grid-cols-2\">\n {rows.map((speaker) => (\n <li\n key={speaker.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent-wash text-sm font-semibold text-action-primary-bg\">\n {speaker.fullName.slice(0, 1).toUpperCase()}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{speaker.fullName}</p>\n <p className=\"mt-0.5 truncate text-sm text-text-secondary\">\n {[speaker.title, speaker.organization].filter(Boolean).join(' • ') || 'Speaker'}\n </p>\n </div>\n </div>\n <ActionMenu\n ariaLabel={`Speaker actions for ${speaker.fullName}`}\n entityName={speaker.fullName}\n deleteLabel=\"Delete speaker\"\n onEdit={() => navigate(`/speakers/${speaker.id}/edit`)}\n onDelete={() => onDelete(speaker)}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"speakers\"\n loading={loading}\n />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n\nfunction TicketsTab({ eventId }: { eventId: string }) {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination();\n const { data, loading, refetch } = useTicketTypesQuery({\n eventId,\n limit: pagination.limit,\n offset: pagination.offset,\n });\n const [deleteTicketType] = useDeleteTicketTypeMutation();\n\n const total = data?.ticketTypes?.total ?? 0;\n const rows = (data?.ticketTypes?.items ?? []) as TicketTypeGql[];\n\n const onDelete = async (ticket: TicketTypeGql) => {\n const response = await deleteTicketType({ variables: { id: ticket.id } });\n if (response.error) {\n notify.error('Could not delete ticket type', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteTicketType) {\n notify.error('Could not delete ticket type', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n notify.success('Ticket type deleted');\n await refetch();\n };\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Ticket Types</h2>\n <Button type=\"button\" size=\"sm\" onClick={() => navigate(`/events/${eventId}/tickets/create`)}>\n <Plus className=\"h-4 w-4\" />\n New Ticket Type\n </Button>\n </div>\n\n {loading ? (\n <Skeleton className=\"h-40 w-full rounded-card\" />\n ) : rows.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No ticket types yet.\"\n description=\"Add a ticket type so attendees can register.\"\n className=\"py-8\"\n />\n ) : (\n <ul className=\"space-y-3\">\n {rows.map((ticket) => {\n const hasCapacity = ticket.capacity != null && ticket.capacity > 0;\n const pct = hasCapacity\n ? Math.min(100, Math.round((ticket.soldCount / (ticket.capacity as number)) * 100))\n : 0;\n const nearlyFull = pct >= 90;\n return (\n <li\n key={ticket.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-4 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{ticket.name}</p>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {ticket.price} {ticket.currency}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Badge variant={ticket.isActive ? 'success' : 'warning'} className=\"uppercase\">\n {ticket.isActive ? 'Active' : 'Inactive'}\n </Badge>\n <ActionMenu\n ariaLabel={`Ticket actions for ${ticket.name}`}\n entityName={ticket.name}\n deleteLabel=\"Delete ticket type\"\n onEdit={() => navigate(`/events/${eventId}/tickets/${ticket.id}/edit`)}\n onDelete={() => onDelete(ticket)}\n />\n </div>\n </div>\n\n <div className=\"mt-3\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n <span className=\"font-medium tabular-nums text-text-primary\">\n {ticket.soldCount.toLocaleString()}\n </span>{' '}\n sold\n {hasCapacity ? (\n <> / {(ticket.capacity as number).toLocaleString()} capacity</>\n ) : (\n ' • unlimited capacity'\n )}\n </span>\n {hasCapacity ? (\n <span className={nearlyFull ? 'font-medium text-text-primary' : ''}>{pct}%</span>\n ) : null}\n </div>\n {hasCapacity ? (\n <div\n className=\"mt-1.5 h-2 overflow-hidden rounded-full bg-bg-sunken\"\n role=\"progressbar\"\n aria-valuenow={pct}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={`${ticket.name} sold`}\n >\n <div\n className=\"h-full rounded-full transition-[width] duration-500\"\n style={{\n width: `${pct}%`,\n background: 'linear-gradient(90deg, var(--color-chart-1), var(--color-chart-2))',\n }}\n />\n </div>\n ) : null}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"ticket types\"\n loading={loading}\n />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n\nfunction MetricTile({\n label,\n value,\n icon: Icon,\n}: {\n label: string;\n value: string;\n icon: React.ComponentType<{ className?: string }>;\n}) {\n return (\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-button bg-accent-wash text-action-primary-bg\">\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"mt-2.5 text-xl font-semibold tabular-nums text-text-primary\">{value}</div>\n <div className=\"mt-0.5 text-xs text-text-muted\">{label}</div>\n </GlassCard>\n );\n}\n\nexport default function EventDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useEventfullyManaged();\n const { data, loading } = useEventQuery(id);\n const { data: sessionsData } = useQuery<ScheduleSessionsResponse, { eventId?: string; limit?: number }>(\n SCHEDULE_SESSIONS_QUERY,\n { variables: { eventId: id, limit: 200 }, skip: !id }\n );\n const { data: ticketsData } = useTicketTypesQuery({ eventId: id, limit: 100, offset: 0 });\n const { data: venuesData } = useVenuesQuery({ limit: 100, offset: 0 });\n const [tab, setTab] = useState('overview');\n\n const event = data?.event ?? null;\n const venueId = useMemo(() => {\n const metadata = (event?.metadata ?? {}) as Record<string, unknown>;\n return typeof metadata.venueId === 'string' ? (metadata.venueId as string) : '';\n }, [event]);\n const venueName = useMemo(\n () => (venueId ? venuesData?.venues?.items?.find((venue) => venue.id === venueId)?.name : undefined),\n [venueId, venuesData]\n );\n\n const sessionCount = sessionsData?.eventSessions?.total ?? 0;\n const ticketsSold = useMemo(\n () => (ticketsData?.ticketTypes?.items ?? []).reduce((sum, ticket) => sum + (ticket.soldCount ?? 0), 0),\n [ticketsData]\n );\n\n if (loading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-8 w-40\" />\n <Skeleton className=\"h-24 w-full rounded-card\" />\n <Skeleton className=\"h-64 w-full rounded-card\" />\n </div>\n );\n }\n\n if (!event) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/events')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Events\n </Button>\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Event not found.\"\n description=\"This event may have been removed or is outside your current scope.\"\n />\n </GlassCard>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"space-y-3\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/events')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Events\n </Button>\n <div>\n <div className=\"flex flex-wrap items-center gap-3\">\n <h1 className=\"text-2xl font-bold text-text-primary\">{event.name}</h1>\n <Badge variant={statusVariant(event.status)} className=\"uppercase\">\n {event.status}\n </Badge>\n </div>\n <p className=\"mt-1 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <CalendarClock className=\"h-4 w-4 text-text-muted\" />\n {formatDateTime(event.startAt)} — {formatDateTime(event.endAt)}\n </span>\n {venueName ? (\n <span className=\"flex items-center gap-1.5\">\n <MapPin className=\"h-4 w-4 text-text-muted\" />\n {venueName}\n </span>\n ) : null}\n </p>\n </div>\n </div>\n <Button type=\"button\" onClick={() => navigate(`/events/${id}/edit`)}>\n <Edit className=\"h-4 w-4\" />\n Edit Event\n </Button>\n </div>\n\n {/* Metrics strip */}\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <MetricTile label=\"Capacity\" value={(event.capacity ?? 0).toLocaleString()} icon={Users} />\n <MetricTile label=\"Sessions\" value={sessionCount.toLocaleString()} icon={CalendarClock} />\n <MetricTile\n label=\"Ticket types\"\n value={(ticketsData?.ticketTypes?.total ?? 0).toLocaleString()}\n icon={TicketIcon}\n />\n <MetricTile label=\"Tickets sold\" value={ticketsSold.toLocaleString()} icon={Eye} />\n </div>\n\n <Tabs value={tab} onValueChange={setTab}>\n <TabsList>\n <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n <TabsTrigger value=\"sessions\">Sessions</TabsTrigger>\n <TabsTrigger value=\"speakers\">Speakers</TabsTrigger>\n <TabsTrigger value=\"tickets\">Tickets</TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"overview\" className=\"mt-4\">\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5 lg:col-span-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Event Details</h2>\n <dl className=\"mt-4 space-y-4 text-sm\">\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Dates</dt>\n <dd className=\"text-text-primary\">\n {formatDateTime(event.startAt)} — {formatDateTime(event.endAt)}\n </dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Venue</dt>\n <dd className=\"text-text-primary\">\n {venueName ? (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n className=\"h-auto p-0\"\n onClick={() => navigate(`/venues/${venueId}`)}\n >\n {venueName}\n </Button>\n ) : (\n 'No venue assigned'\n )}\n </dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Capacity</dt>\n <dd className=\"text-text-primary\">{(event.capacity ?? 0).toLocaleString()} attendees</dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Type</dt>\n <dd className=\"capitalize text-text-primary\">{event.type.toLowerCase().replace(/_/g, ' ')}</dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Timezone</dt>\n <dd className=\"text-text-primary\">{event.timezone}</dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Visibility</dt>\n <dd className=\"capitalize text-text-primary\">{event.visibility}</dd>\n </div>\n <div className=\"grid grid-cols-1 gap-1 sm:grid-cols-[140px_1fr]\">\n <dt className=\"text-text-muted\">Description</dt>\n <dd className=\"text-text-secondary\">\n {event.description?.trim() ? event.description : 'No description provided.'}\n </dd>\n </div>\n </dl>\n <div className=\"mt-5\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => navigate(`/events/${id}/edit`)}>\n <Edit className=\"h-4 w-4\" />\n Edit event details\n </Button>\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"glass\" glow className=\"p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Key Metrics</h2>\n <dl className=\"mt-4 space-y-4\">\n <div>\n <dt className=\"text-sm text-text-secondary\">Capacity</dt>\n <dd className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {(event.capacity ?? 0).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm text-text-secondary\">Sessions</dt>\n <dd className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {sessionCount.toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm text-text-secondary\">Ticket types</dt>\n <dd className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {(ticketsData?.ticketTypes?.total ?? 0).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm text-text-secondary\">Tickets sold</dt>\n <dd className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {ticketsSold.toLocaleString()}\n </dd>\n </div>\n </dl>\n </GlassCard>\n </div>\n </TabsContent>\n\n <TabsContent value=\"sessions\" className=\"mt-4\">\n <SessionsTab eventId={id} />\n </TabsContent>\n\n <TabsContent value=\"speakers\" className=\"mt-4\">\n <SpeakersTab />\n </TabsContent>\n\n <TabsContent value=\"tickets\" className=\"mt-4\">\n <TicketsTab eventId={id} />\n </TabsContent>\n </Tabs>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/AnalyticsPage.tsx"],"sourcesContent":["import {\n BarChart3,\n CalendarDays,\n ClipboardList,\n MapPin,\n QrCode,\n Sparkles,\n Ticket,\n Trophy,\n Users,\n} from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\n\nimport {\n CHART_SERIES_COLORS,\n ChartLegend,\n EventAreaChart,\n EventBarChart,\n EventDonutChart,\n type AreaPoint,\n type BarDatum,\n type DonutSlice,\n} from '../../components/EventCharts';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useDashboardSummaryQuery, useRegistrationsQuery, useUpsertAnalyticsSnapshotMutation } from '../../api/eventApi';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n/**\n * Snapshot metrics are computed server-side at generation time and returned on\n * the `metrics` JSON field of an analytics snapshot. The shared\n * `useAnalyticsSnapshotsQuery` hook does not select `metrics`, so this page owns\n * a dedicated query (distinct operation name) that pulls the metric payload in\n * addition to the identity fields.\n */\nconst ANALYTICS_SNAPSHOTS_WITH_METRICS_QUERY = gql`\n query EventfullymanagedAnalyticsSnapshotsWithMetrics($limit: Int, $offset: Int) {\n analyticsSnapshots: eventfullyManagedAnalyticsSnapshots(limit: $limit, offset: $offset) {\n total\n items {\n id\n name\n title\n status\n metrics\n createdAt\n }\n }\n }\n`;\n\ntype SnapshotMetrics = {\n totalEvents?: number | null;\n activeEvents?: number | null;\n totalRegistrations?: number | null;\n totalAttendees?: number | null;\n totalVenues?: number | null;\n totalSponsors?: number | null;\n upcomingSessions?: number | null;\n generatedAt?: string | null;\n};\n\ntype SnapshotRow = {\n id: string;\n name?: string | null;\n title?: string | null;\n status?: string | null;\n createdAt?: string | null;\n metrics?: SnapshotMetrics | null;\n};\n\ntype SnapshotsResponse = {\n analyticsSnapshots: { total: number; items: SnapshotRow[] };\n};\n\nconst METRIC_FIELDS: { key: keyof SnapshotMetrics; label: string }[] = [\n { key: 'totalEvents', label: 'Events' },\n { key: 'activeEvents', label: 'Active' },\n { key: 'totalRegistrations', label: 'Registrations' },\n { key: 'totalAttendees', label: 'Attendees' },\n { key: 'totalVenues', label: 'Venues' },\n { key: 'totalSponsors', label: 'Sponsors' },\n { key: 'upcomingSessions', label: 'Upcoming Sessions' },\n];\n\nfunction statusVariant(status: string): 'success' | 'warning' | 'secondary' | 'info' {\n const value = status.toLowerCase();\n if (['active', 'published', 'completed'].includes(value)) return 'success';\n if (['inactive', 'cancelled', 'archived', 'expired'].includes(value)) return 'warning';\n if (['draft', 'created', 'pending', 'scheduled'].includes(value)) return 'info';\n return 'secondary';\n}\n\nfunction formatGeneratedAt(value?: string | null): string | null {\n if (!value) return null;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? null : parsed.toLocaleString();\n}\n\nfunction shortDate(value?: string | null): string {\n if (!value) return '—';\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) return '—';\n return parsed.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n}\n\n/** Humanise a registration status token (e.g. `checked_in` → `Checked In`). */\nfunction humanizeStatus(status: string): string {\n return status\n .replace(/[_-]+/g, ' ')\n .split(' ')\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ');\n}\n\nfunction MetricTile({ icon: Icon, label, value }: { icon: LucideIcon; label: string; value: number }) {\n return (\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Icon className=\"h-4 w-4 text-accent-primary\" />\n <p className=\"text-xs font-medium uppercase tracking-wide\">{label}</p>\n </div>\n <p className=\"mt-2 text-2xl font-bold tabular-nums text-text-primary\">{value.toLocaleString()}</p>\n </GlassCard>\n );\n}\n\nexport default function AnalyticsPage() {\n const pagination = usePagination({ pageSize: 8 });\n const { data, loading, refetch } = useQuery<SnapshotsResponse, { limit: number; offset: number }>(\n ANALYTICS_SNAPSHOTS_WITH_METRICS_QUERY,\n { variables: { limit: pagination.limit, offset: pagination.offset } }\n );\n const summaryQuery = useDashboardSummaryQuery();\n const registrationsQuery = useRegistrationsQuery({ limit: 200, offset: 0 });\n const [upsertSnapshot, { loading: creating }] = useUpsertAnalyticsSnapshotMutation();\n const [snapshotName, setSnapshotName] = useState('overview');\n\n const snapshots = useMemo(() => data?.analyticsSnapshots.items ?? [], [data]);\n const total = data?.analyticsSnapshots.total ?? 0;\n\n const summary = summaryQuery.data?.dashboardSummary;\n const registrations = useMemo(\n () => registrationsQuery.data?.registrations.items ?? [],\n [registrationsQuery.data]\n );\n\n // Check-ins are derived from the loaded registration sample (registrations\n // carrying a `checkedInAt`) — the dashboard summary does not expose them.\n const checkInCount = useMemo(\n () => registrations.filter((row) => Boolean(row.checkedInAt)).length,\n [registrations]\n );\n\n // Registrations over time: the totalRegistrations captured on each snapshot,\n // ordered oldest → newest. Purely real, snapshot-derived — never synthesised.\n const registrationsSeries = useMemo<AreaPoint[]>(() => {\n return snapshots\n .map((snapshot) => {\n const at = snapshot.metrics?.generatedAt ?? snapshot.createdAt ?? null;\n const parsed = at ? new Date(at) : null;\n const value = typeof snapshot.metrics?.totalRegistrations === 'number' ? snapshot.metrics.totalRegistrations : null;\n return parsed && !Number.isNaN(parsed.getTime()) && value !== null\n ? { time: parsed.getTime(), label: shortDate(at), value }\n : null;\n })\n .filter((point): point is { time: number; label: string; value: number } => point !== null)\n .sort((a, b) => a.time - b.time)\n .map(({ label, value }) => ({ label, value }));\n }, [snapshots]);\n\n // Registrations by status donut, computed from the real registration list.\n const statusBreakdown = useMemo<DonutSlice[]>(() => {\n const counts = new Map<string, number>();\n for (const row of registrations) {\n const key = (row.status || 'unknown').toLowerCase();\n counts.set(key, (counts.get(key) ?? 0) + 1);\n }\n return Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([key, count], index) => ({\n name: humanizeStatus(key),\n value: count,\n color: CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length],\n }));\n }, [registrations]);\n\n const registrationTotal = registrations.length;\n\n // Entity-count bar chart from the real dashboard summary + derived check-ins.\n const entityCounts = useMemo<BarDatum[]>(() => {\n if (!summary) return [];\n return [\n { name: 'Events', value: summary.totalEvents },\n { name: 'Regs', value: summary.totalRegistrations },\n { name: 'Attendees', value: summary.totalAttendees },\n { name: 'Check-ins', value: checkInCount },\n { name: 'Venues', value: summary.totalVenues },\n { name: 'Sponsors', value: summary.totalSponsors },\n { name: 'Sessions', value: summary.upcomingSessions },\n ];\n }, [summary, checkInCount]);\n\n const onCreate = async () => {\n if (!snapshotName.trim()) return;\n try {\n const response = await upsertSnapshot({\n variables: {\n input: {\n name: snapshotName,\n title: snapshotName,\n status: 'created',\n metadata: {},\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.upsertAnalyticsSnapshot?.id, 'Could not generate snapshot')) {\n return;\n }\n notify.success('Snapshot generated');\n await refetch();\n } catch (error) {\n notify.error('Could not generate snapshot', describeError(error));\n }\n };\n\n const summaryLoading = summaryQuery.loading && !summary;\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Analytics gives you the live pulse of your event operations — registrations over time, how those registrations\n break down by status, and totals across every entity you manage. Generate a snapshot to freeze today&apos;s\n numbers so you can compare trends over time and report on outcomes.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Analytics</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Real-time metrics from live event operations, plus point-in-time snapshots you control.\n </p>\n </div>\n <div className=\"flex w-full flex-col gap-2 sm:w-auto sm:flex-row\">\n <Input\n value={snapshotName}\n onChange={(e) => setSnapshotName(e.target.value)}\n placeholder=\"daily-summary\"\n aria-label=\"Snapshot name\"\n className=\"sm:w-48\"\n />\n <Button type=\"button\" disabled={creating} onClick={onCreate}>\n <Sparkles className=\"h-4 w-4\" />\n {creating ? 'Generating...' : 'Generate snapshot'}\n </Button>\n </div>\n </div>\n </EmphasisPanel>\n\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_320px]\">\n <div className=\"space-y-6\">\n {/* Live metric tiles */}\n {summaryLoading ? (\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4\">\n {Array.from({ length: 7 }).map((_, index) => (\n <Skeleton key={index} className=\"h-[92px] rounded-card\" />\n ))}\n </div>\n ) : summary ? (\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4\">\n <MetricTile icon={CalendarDays} label=\"Events\" value={summary.totalEvents} />\n <MetricTile icon={ClipboardList} label=\"Registrations\" value={summary.totalRegistrations} />\n <MetricTile icon={Users} label=\"Attendees\" value={summary.totalAttendees} />\n <MetricTile icon={QrCode} label=\"Check-ins\" value={checkInCount} />\n <MetricTile icon={MapPin} label=\"Venues\" value={summary.totalVenues} />\n <MetricTile icon={Trophy} label=\"Sponsors\" value={summary.totalSponsors} />\n <MetricTile icon={Ticket} label=\"Upcoming Sessions\" value={summary.upcomingSessions} />\n </div>\n ) : null}\n\n {/* Registrations over time + by status */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-text-primary\">Registrations over time</h2>\n <span className=\"text-xs text-text-muted\">By snapshot</span>\n </div>\n <div className=\"mt-4\">\n <EventAreaChart\n data={registrationsSeries}\n emptyLabel=\"Generate a few snapshots to chart the trend\"\n />\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Registrations by status</h2>\n <div className=\"mt-4 flex flex-col items-center gap-4 sm:flex-row\">\n <div className=\"w-full sm:w-1/2\">\n <EventDonutChart\n data={statusBreakdown}\n centerValue={registrationTotal.toLocaleString()}\n centerLabel=\"Registrations\"\n emptyLabel=\"No registrations yet\"\n />\n </div>\n <div className=\"w-full sm:w-1/2\">\n <ChartLegend\n items={statusBreakdown.map((slice) => ({\n label: slice.name,\n color: slice.color ?? 'var(--color-chart-1)',\n value: slice.value.toLocaleString(),\n }))}\n />\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* Entity totals */}\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Portfolio totals</h2>\n <p className=\"mt-0.5 text-xs text-text-muted\">Live counts across every entity you manage.</p>\n <div className=\"mt-4\">\n <EventBarChart data={entityCounts} emptyLabel=\"No operational data yet\" />\n </div>\n </GlassCard>\n </div>\n\n {/* Saved snapshots rail */}\n <aside className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n Saved snapshots {total > 0 ? <span className=\"text-text-muted\">({total})</span> : null}\n </h2>\n <BarChart3 className=\"h-4 w-4 text-text-muted\" />\n </div>\n\n {loading && snapshots.length === 0 ? (\n <div className=\"space-y-3\">\n {Array.from({ length: 3 }).map((_, index) => (\n <Skeleton key={index} className=\"h-24 rounded-card\" />\n ))}\n </div>\n ) : snapshots.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No snapshots yet\"\n description=\"Generate your first snapshot to freeze the current metrics.\"\n className=\"py-8\"\n />\n </GlassCard>\n ) : (\n <div className=\"space-y-3\">\n {snapshots.map((snapshot) => {\n const generatedAt =\n formatGeneratedAt(snapshot.metrics?.generatedAt) ?? formatGeneratedAt(snapshot.createdAt);\n const captured = METRIC_FIELDS.map((field) => {\n const raw = snapshot.metrics?.[field.key];\n return { label: field.label, value: typeof raw === 'number' ? raw : null };\n }).filter((entry) => entry.value !== null);\n return (\n <GlassCard key={snapshot.id} treatment=\"plain\" className=\"border-border-seam p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">\n {snapshot.title ?? snapshot.name ?? `Snapshot ${snapshot.id}`}\n </h3>\n {generatedAt ? <p className=\"mt-0.5 text-xs text-text-muted\">{generatedAt}</p> : null}\n </div>\n {snapshot.status ? (\n <Badge variant={statusVariant(snapshot.status)} className=\"shrink-0 uppercase\">\n {snapshot.status}\n </Badge>\n ) : null}\n </div>\n {captured.length > 0 ? (\n <dl className=\"mt-3 space-y-1.5\">\n {captured.map((entry) => (\n <div key={entry.label} className=\"flex items-center justify-between text-xs\">\n <dt className=\"text-text-secondary\">{entry.label}</dt>\n <dd className=\"font-semibold tabular-nums text-text-primary\">\n {(entry.value ?? 0).toLocaleString()}\n </dd>\n </div>\n ))}\n </dl>\n ) : (\n <p className=\"mt-3 text-xs text-text-muted\">No metrics captured.</p>\n )}\n </GlassCard>\n );\n })}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={snapshots.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"snapshots\"\n loading={loading}\n />\n </div>\n )}\n </aside>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/AnalyticsPage.tsx"],"sourcesContent":["import { BarChart3, CalendarDays, ClipboardList, MapPin, QrCode, Sparkles, Ticket, Trophy, Users } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\n\nimport {\n CHART_SERIES_COLORS,\n ChartLegend,\n EventAreaChart,\n EventBarChart,\n EventDonutChart,\n type AreaPoint,\n type BarDatum,\n type DonutSlice,\n} from '../../components/EventCharts';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport {\n useDashboardSummaryQuery,\n useRegistrationsQuery,\n useUpsertAnalyticsSnapshotMutation,\n} from '../../api/eventApi';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n/**\n * Snapshot metrics are computed server-side at generation time and returned on\n * the `metrics` JSON field of an analytics snapshot. The shared\n * `useAnalyticsSnapshotsQuery` hook does not select `metrics`, so this page owns\n * a dedicated query (distinct operation name) that pulls the metric payload in\n * addition to the identity fields.\n */\nconst ANALYTICS_SNAPSHOTS_WITH_METRICS_QUERY = gql`\n query EventfullymanagedAnalyticsSnapshotsWithMetrics($limit: Int, $offset: Int) {\n analyticsSnapshots: eventfullyManagedAnalyticsSnapshots(limit: $limit, offset: $offset) {\n total\n items {\n id\n name\n title\n status\n metrics\n createdAt\n }\n }\n }\n`;\n\ntype SnapshotMetrics = {\n totalEvents?: number | null;\n activeEvents?: number | null;\n totalRegistrations?: number | null;\n totalAttendees?: number | null;\n totalVenues?: number | null;\n totalSponsors?: number | null;\n upcomingSessions?: number | null;\n generatedAt?: string | null;\n};\n\ntype SnapshotRow = {\n id: string;\n name?: string | null;\n title?: string | null;\n status?: string | null;\n createdAt?: string | null;\n metrics?: SnapshotMetrics | null;\n};\n\ntype SnapshotsResponse = {\n analyticsSnapshots: { total: number; items: SnapshotRow[] };\n};\n\nconst METRIC_FIELDS: { key: keyof SnapshotMetrics; label: string }[] = [\n { key: 'totalEvents', label: 'Events' },\n { key: 'activeEvents', label: 'Active' },\n { key: 'totalRegistrations', label: 'Registrations' },\n { key: 'totalAttendees', label: 'Attendees' },\n { key: 'totalVenues', label: 'Venues' },\n { key: 'totalSponsors', label: 'Sponsors' },\n { key: 'upcomingSessions', label: 'Upcoming Sessions' },\n];\n\nfunction statusVariant(status: string): 'success' | 'warning' | 'secondary' | 'info' {\n const value = status.toLowerCase();\n if (['active', 'published', 'completed'].includes(value)) return 'success';\n if (['inactive', 'cancelled', 'archived', 'expired'].includes(value)) return 'warning';\n if (['draft', 'created', 'pending', 'scheduled'].includes(value)) return 'info';\n return 'secondary';\n}\n\nfunction formatGeneratedAt(value?: string | null): string | null {\n if (!value) return null;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? null : parsed.toLocaleString();\n}\n\nfunction shortDate(value?: string | null): string {\n if (!value) return '—';\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) return '—';\n return parsed.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n}\n\n/** Humanise a registration status token (e.g. `checked_in` → `Checked In`). */\nfunction humanizeStatus(status: string): string {\n return status\n .replace(/[_-]+/g, ' ')\n .split(' ')\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ');\n}\n\nfunction MetricTile({ icon: Icon, label, value }: { icon: LucideIcon; label: string; value: number }) {\n return (\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Icon className=\"h-4 w-4 text-accent-primary\" />\n <p className=\"text-xs font-medium uppercase tracking-wide\">{label}</p>\n </div>\n <p className=\"mt-2 text-2xl font-bold tabular-nums text-text-primary\">{value.toLocaleString()}</p>\n </GlassCard>\n );\n}\n\nexport default function AnalyticsPage() {\n const pagination = usePagination({ pageSize: 8 });\n const { data, loading, refetch } = useQuery<SnapshotsResponse, { limit: number; offset: number }>(\n ANALYTICS_SNAPSHOTS_WITH_METRICS_QUERY,\n { variables: { limit: pagination.limit, offset: pagination.offset } }\n );\n const summaryQuery = useDashboardSummaryQuery();\n const registrationsQuery = useRegistrationsQuery({ limit: 200, offset: 0 });\n const [upsertSnapshot, { loading: creating }] = useUpsertAnalyticsSnapshotMutation();\n const [snapshotName, setSnapshotName] = useState('overview');\n\n const snapshots = useMemo(() => data?.analyticsSnapshots.items ?? [], [data]);\n const total = data?.analyticsSnapshots.total ?? 0;\n\n const summary = summaryQuery.data?.dashboardSummary;\n const registrations = useMemo(() => registrationsQuery.data?.registrations.items ?? [], [registrationsQuery.data]);\n\n // Check-ins are derived from the loaded registration sample (registrations\n // carrying a `checkedInAt`) — the dashboard summary does not expose them.\n const checkInCount = useMemo(() => registrations.filter((row) => Boolean(row.checkedInAt)).length, [registrations]);\n\n // Registrations over time: the totalRegistrations captured on each snapshot,\n // ordered oldest → newest. Purely real, snapshot-derived — never synthesised.\n const registrationsSeries = useMemo<AreaPoint[]>(() => {\n return snapshots\n .map((snapshot) => {\n const at = snapshot.metrics?.generatedAt ?? snapshot.createdAt ?? null;\n const parsed = at ? new Date(at) : null;\n const value =\n typeof snapshot.metrics?.totalRegistrations === 'number' ? snapshot.metrics.totalRegistrations : null;\n return parsed && !Number.isNaN(parsed.getTime()) && value !== null\n ? { time: parsed.getTime(), label: shortDate(at), value }\n : null;\n })\n .filter((point): point is { time: number; label: string; value: number } => point !== null)\n .sort((a, b) => a.time - b.time)\n .map(({ label, value }) => ({ label, value }));\n }, [snapshots]);\n\n // Registrations by status donut, computed from the real registration list.\n const statusBreakdown = useMemo<DonutSlice[]>(() => {\n const counts = new Map<string, number>();\n for (const row of registrations) {\n const key = (row.status || 'unknown').toLowerCase();\n counts.set(key, (counts.get(key) ?? 0) + 1);\n }\n return Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([key, count], index) => ({\n name: humanizeStatus(key),\n value: count,\n color: CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length],\n }));\n }, [registrations]);\n\n const registrationTotal = registrations.length;\n\n // Entity-count bar chart from the real dashboard summary + derived check-ins.\n const entityCounts = useMemo<BarDatum[]>(() => {\n if (!summary) return [];\n return [\n { name: 'Events', value: summary.totalEvents },\n { name: 'Regs', value: summary.totalRegistrations },\n { name: 'Attendees', value: summary.totalAttendees },\n { name: 'Check-ins', value: checkInCount },\n { name: 'Venues', value: summary.totalVenues },\n { name: 'Sponsors', value: summary.totalSponsors },\n { name: 'Sessions', value: summary.upcomingSessions },\n ];\n }, [summary, checkInCount]);\n\n const onCreate = async () => {\n if (!snapshotName.trim()) return;\n try {\n const response = await upsertSnapshot({\n variables: {\n input: {\n name: snapshotName,\n title: snapshotName,\n status: 'created',\n metadata: {},\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.upsertAnalyticsSnapshot?.id, 'Could not generate snapshot')) {\n return;\n }\n notify.success('Snapshot generated');\n await refetch();\n } catch (error) {\n notify.error('Could not generate snapshot', describeError(error));\n }\n };\n\n const summaryLoading = summaryQuery.loading && !summary;\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Analytics gives you the live pulse of your event operations — registrations over time, how those registrations\n break down by status, and totals across every entity you manage. Generate a snapshot to freeze today&apos;s\n numbers so you can compare trends over time and report on outcomes.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Analytics</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Real-time metrics from live event operations, plus point-in-time snapshots you control.\n </p>\n </div>\n <div className=\"flex w-full flex-col gap-2 sm:w-auto sm:flex-row\">\n <Input\n value={snapshotName}\n onChange={(e) => setSnapshotName(e.target.value)}\n placeholder=\"daily-summary\"\n aria-label=\"Snapshot name\"\n className=\"sm:w-48\"\n />\n <Button type=\"button\" disabled={creating} onClick={onCreate}>\n <Sparkles className=\"h-4 w-4\" />\n {creating ? 'Generating...' : 'Generate snapshot'}\n </Button>\n </div>\n </div>\n </EmphasisPanel>\n\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_320px]\">\n <div className=\"space-y-6\">\n {/* Live metric tiles */}\n {summaryLoading ? (\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4\">\n {Array.from({ length: 7 }).map((_, index) => (\n <Skeleton key={index} className=\"h-[92px] rounded-card\" />\n ))}\n </div>\n ) : summary ? (\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4\">\n <MetricTile icon={CalendarDays} label=\"Events\" value={summary.totalEvents} />\n <MetricTile icon={ClipboardList} label=\"Registrations\" value={summary.totalRegistrations} />\n <MetricTile icon={Users} label=\"Attendees\" value={summary.totalAttendees} />\n <MetricTile icon={QrCode} label=\"Check-ins\" value={checkInCount} />\n <MetricTile icon={MapPin} label=\"Venues\" value={summary.totalVenues} />\n <MetricTile icon={Trophy} label=\"Sponsors\" value={summary.totalSponsors} />\n <MetricTile icon={Ticket} label=\"Upcoming Sessions\" value={summary.upcomingSessions} />\n </div>\n ) : null}\n\n {/* Registrations over time + by status */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-text-primary\">Registrations over time</h2>\n <span className=\"text-xs text-text-muted\">By snapshot</span>\n </div>\n <div className=\"mt-4\">\n <EventAreaChart data={registrationsSeries} emptyLabel=\"Generate a few snapshots to chart the trend\" />\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Registrations by status</h2>\n <div className=\"mt-4 flex flex-col items-center gap-4 sm:flex-row\">\n <div className=\"w-full sm:w-1/2\">\n <EventDonutChart\n data={statusBreakdown}\n centerValue={registrationTotal.toLocaleString()}\n centerLabel=\"Registrations\"\n emptyLabel=\"No registrations yet\"\n />\n </div>\n <div className=\"w-full sm:w-1/2\">\n <ChartLegend\n items={statusBreakdown.map((slice) => ({\n label: slice.name,\n color: slice.color ?? 'var(--color-chart-1)',\n value: slice.value.toLocaleString(),\n }))}\n />\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* Entity totals */}\n <GlassCard treatment=\"insight\" className=\"p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Portfolio totals</h2>\n <p className=\"mt-0.5 text-xs text-text-muted\">Live counts across every entity you manage.</p>\n <div className=\"mt-4\">\n <EventBarChart data={entityCounts} emptyLabel=\"No operational data yet\" />\n </div>\n </GlassCard>\n </div>\n\n {/* Saved snapshots rail */}\n <aside className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n Saved snapshots {total > 0 ? <span className=\"text-text-muted\">({total})</span> : null}\n </h2>\n <BarChart3 className=\"h-4 w-4 text-text-muted\" />\n </div>\n\n {loading && snapshots.length === 0 ? (\n <div className=\"space-y-3\">\n {Array.from({ length: 3 }).map((_, index) => (\n <Skeleton key={index} className=\"h-24 rounded-card\" />\n ))}\n </div>\n ) : snapshots.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No snapshots yet\"\n description=\"Generate your first snapshot to freeze the current metrics.\"\n className=\"py-8\"\n />\n </GlassCard>\n ) : (\n <div className=\"space-y-3\">\n {snapshots.map((snapshot) => {\n const generatedAt =\n formatGeneratedAt(snapshot.metrics?.generatedAt) ?? formatGeneratedAt(snapshot.createdAt);\n const captured = METRIC_FIELDS.map((field) => {\n const raw = snapshot.metrics?.[field.key];\n return { label: field.label, value: typeof raw === 'number' ? raw : null };\n }).filter((entry) => entry.value !== null);\n return (\n <GlassCard key={snapshot.id} treatment=\"plain\" className=\"border-border-seam p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">\n {snapshot.title ?? snapshot.name ?? `Snapshot ${snapshot.id}`}\n </h3>\n {generatedAt ? <p className=\"mt-0.5 text-xs text-text-muted\">{generatedAt}</p> : null}\n </div>\n {snapshot.status ? (\n <Badge variant={statusVariant(snapshot.status)} className=\"shrink-0 uppercase\">\n {snapshot.status}\n </Badge>\n ) : null}\n </div>\n {captured.length > 0 ? (\n <dl className=\"mt-3 space-y-1.5\">\n {captured.map((entry) => (\n <div key={entry.label} className=\"flex items-center justify-between text-xs\">\n <dt className=\"text-text-secondary\">{entry.label}</dt>\n <dd className=\"font-semibold tabular-nums text-text-primary\">\n {(entry.value ?? 0).toLocaleString()}\n </dd>\n </div>\n ))}\n </dl>\n ) : (\n <p className=\"mt-3 text-xs text-text-muted\">No metrics captured.</p>\n )}\n </GlassCard>\n );\n })}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={snapshots.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"snapshots\"\n loading={loading}\n />\n </div>\n )}\n </aside>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}