@burdenoff/microfe-eventfullymanaged 2026.828.1 → 2026.1030.1
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.
- package/dist/eventfullymanaged/EventfullyManagedRoot.d.ts.map +1 -1
- package/dist/eventfullymanaged/EventfullyManagedRoot.js +31 -19
- package/dist/eventfullymanaged/EventfullyManagedRoot.js.map +1 -1
- package/dist/eventfullymanaged/pages/dashboard/DashboardPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/dashboard/DashboardPage.js +104 -107
- package/dist/eventfullymanaged/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/CommunicationPage.js +77 -77
- package/dist/eventfullymanaged/pages/modules/EventsPage.js +180 -180
- package/dist/eventfullymanaged/pages/modules/EventsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/venues/VenueFormPage.js +56 -56
- package/dist/services/createSandboxAssistantTransport.d.ts.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js +6 -4
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/shared/hooks/index.d.ts +2 -0
- package/dist/shared/hooks/index.d.ts.map +1 -0
- package/dist/shared/hooks/useTr.d.ts +17 -0
- package/dist/shared/hooks/useTr.d.ts.map +1 -0
- package/dist/shared/hooks/useTr.js +14 -0
- package/dist/shared/hooks/useTr.js.map +1 -0
- package/dist/utils/pageContext.d.ts +66 -0
- package/dist/utils/pageContext.d.ts.map +1 -0
- package/dist/utils/pageContext.js +133 -0
- package/dist/utils/pageContext.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventfullyManagedRoot.d.ts","sourceRoot":"","sources":["../../src/eventfullymanaged/EventfullyManagedRoot.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EventfullyManagedRoot.d.ts","sourceRoot":"","sources":["../../src/eventfullymanaged/EventfullyManagedRoot.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAwE1D,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,2CAYtE"}
|
|
@@ -2,9 +2,10 @@ import { EventfullyManagedRoutes as e } from "./EventfullyManagedRoutes.js";
|
|
|
2
2
|
import { EventfullyManagedProvider as t } from "./context/EventfullyManagedContext.js";
|
|
3
3
|
import { Component as n } from "react";
|
|
4
4
|
import { FeatureFlagProvider as r } from "@burdenoff/fe-libs/shared/feature-flags";
|
|
5
|
-
import {
|
|
5
|
+
import { useI18n as i } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
6
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
7
|
//#region src/eventfullymanaged/EventfullyManagedRoot.tsx
|
|
7
|
-
var
|
|
8
|
+
var s = class extends n {
|
|
8
9
|
constructor(e) {
|
|
9
10
|
super(e), this.state = {
|
|
10
11
|
hasError: !1,
|
|
@@ -22,52 +23,63 @@ var o = class extends n {
|
|
|
22
23
|
this.setState({ errorInfo: t });
|
|
23
24
|
}
|
|
24
25
|
render() {
|
|
25
|
-
|
|
26
|
+
let { t: e } = this.props;
|
|
27
|
+
return this.state.hasError ? /* @__PURE__ */ o("div", {
|
|
26
28
|
className: "p-6 bg-red-50 border border-red-200 rounded-lg m-4",
|
|
27
29
|
children: [
|
|
28
|
-
/* @__PURE__ */
|
|
30
|
+
/* @__PURE__ */ a("h2", {
|
|
29
31
|
className: "text-lg font-semibold text-red-800 mb-2",
|
|
30
|
-
children: "Something went wrong"
|
|
32
|
+
children: e("eventfullymanaged.errors.boundary.title", "Something went wrong")
|
|
31
33
|
}),
|
|
32
|
-
/* @__PURE__ */
|
|
34
|
+
/* @__PURE__ */ a("p", {
|
|
33
35
|
className: "text-red-700 mb-4",
|
|
34
|
-
children: this.state.error?.message || "Unknown error"
|
|
36
|
+
children: this.state.error?.message || e("eventfullymanaged.errors.boundary.unknown", "Unknown error")
|
|
35
37
|
}),
|
|
36
|
-
/* @__PURE__ */
|
|
38
|
+
/* @__PURE__ */ o("details", {
|
|
37
39
|
className: "text-sm text-red-600",
|
|
38
|
-
children: [/* @__PURE__ */
|
|
40
|
+
children: [/* @__PURE__ */ a("summary", {
|
|
39
41
|
className: "cursor-pointer hover:underline",
|
|
40
|
-
children: "Error details"
|
|
41
|
-
}), /* @__PURE__ */
|
|
42
|
+
children: e("eventfullymanaged.errors.boundary.details", "Error details")
|
|
43
|
+
}), /* @__PURE__ */ o("pre", {
|
|
42
44
|
className: "mt-2 p-2 bg-red-100 rounded overflow-auto text-xs",
|
|
43
45
|
children: [this.state.error?.stack, this.state.errorInfo?.componentStack]
|
|
44
46
|
})]
|
|
45
47
|
}),
|
|
46
|
-
/* @__PURE__ */
|
|
48
|
+
/* @__PURE__ */ a("button", {
|
|
47
49
|
type: "button",
|
|
48
50
|
onClick: () => window.location.reload(),
|
|
49
51
|
className: "mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700",
|
|
50
|
-
children: "Reload Page"
|
|
52
|
+
children: e("eventfullymanaged.errors.boundary.reload", "Reload Page")
|
|
51
53
|
})
|
|
52
54
|
]
|
|
53
55
|
}) : this.props.children;
|
|
54
56
|
}
|
|
55
57
|
};
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
+
function c({ children: e }) {
|
|
59
|
+
let { t } = i();
|
|
60
|
+
return /* @__PURE__ */ a(s, {
|
|
61
|
+
t: (e, n) => {
|
|
62
|
+
let r = t(e);
|
|
63
|
+
return r === e ? n : r;
|
|
64
|
+
},
|
|
65
|
+
children: e
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function l(n) {
|
|
69
|
+
return /* @__PURE__ */ a(c, { children: /* @__PURE__ */ a(r, {
|
|
58
70
|
workspaceId: null,
|
|
59
71
|
gateway: "global",
|
|
60
72
|
defaultEnabled: !0,
|
|
61
|
-
children: /* @__PURE__ */
|
|
73
|
+
children: /* @__PURE__ */ a(t, {
|
|
62
74
|
props: n,
|
|
63
|
-
children: /* @__PURE__ */
|
|
75
|
+
children: /* @__PURE__ */ a("div", {
|
|
64
76
|
className: "eventfullymanaged-root h-full",
|
|
65
|
-
children: /* @__PURE__ */
|
|
77
|
+
children: /* @__PURE__ */ a(e, {})
|
|
66
78
|
})
|
|
67
79
|
})
|
|
68
80
|
}) });
|
|
69
81
|
}
|
|
70
82
|
//#endregion
|
|
71
|
-
export {
|
|
83
|
+
export { l as EventfullyManagedRoot };
|
|
72
84
|
|
|
73
85
|
//# sourceMappingURL=EventfullyManagedRoot.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventfullyManagedRoot.js","names":[],"sources":["../../src/eventfullymanaged/EventfullyManagedRoot.tsx"],"sourcesContent":["import { Component, type ReactNode, type ErrorInfo } from 'react';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport type { EventfullyManagedRootProps } from './types';\nimport { EventfullyManagedRoutes } from './EventfullyManagedRoutes';\nimport { EventfullyManagedProvider } from './context/EventfullyManagedContext';\n\n// Error Boundary to catch and display errors\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n errorInfo: ErrorInfo | null;\n}\n\nclass EventfullyManagedErrorBoundary extends Component
|
|
1
|
+
{"version":3,"file":"EventfullyManagedRoot.js","names":[],"sources":["../../src/eventfullymanaged/EventfullyManagedRoot.tsx"],"sourcesContent":["import { Component, type ReactNode, type ErrorInfo } from 'react';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { EventfullyManagedRootProps } from './types';\nimport { EventfullyManagedRoutes } from './EventfullyManagedRoutes';\nimport { EventfullyManagedProvider } from './context/EventfullyManagedContext';\n\n// Error Boundary to catch and display errors\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n errorInfo: ErrorInfo | null;\n}\n\nclass EventfullyManagedErrorBoundary extends Component<\n { children: ReactNode; t: (key: string, fallback: string) => string },\n ErrorBoundaryState\n> {\n constructor(props: { children: ReactNode; t: (key: string, fallback: string) => string }) {\n super(props);\n this.state = { hasError: false, error: null, errorInfo: null };\n }\n\n static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n this.setState({ errorInfo });\n }\n\n render() {\n const { t } = this.props;\n if (this.state.hasError) {\n return (\n <div className=\"p-6 bg-red-50 border border-red-200 rounded-lg m-4\">\n <h2 className=\"text-lg font-semibold text-red-800 mb-2\">\n {t('eventfullymanaged.errors.boundary.title', 'Something went wrong')}\n </h2>\n <p className=\"text-red-700 mb-4\">\n {this.state.error?.message || t('eventfullymanaged.errors.boundary.unknown', 'Unknown error')}\n </p>\n <details className=\"text-sm text-red-600\">\n <summary className=\"cursor-pointer hover:underline\">\n {t('eventfullymanaged.errors.boundary.details', 'Error details')}\n </summary>\n <pre className=\"mt-2 p-2 bg-red-100 rounded overflow-auto text-xs\">\n {this.state.error?.stack}\n {this.state.errorInfo?.componentStack}\n </pre>\n </details>\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700\"\n >\n {t('eventfullymanaged.errors.boundary.reload', 'Reload Page')}\n </button>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n\nfunction TranslatedBoundary({ children }: { children: ReactNode }) {\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 return <EventfullyManagedErrorBoundary t={t}>{children}</EventfullyManagedErrorBoundary>;\n}\n\nexport function EventfullyManagedRoot(props: EventfullyManagedRootProps) {\n return (\n <TranslatedBoundary>\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <EventfullyManagedProvider props={props}>\n <div className=\"eventfullymanaged-root h-full\">\n <EventfullyManagedRoutes />\n </div>\n </EventfullyManagedProvider>\n </FeatureFlagProvider>\n </TranslatedBoundary>\n );\n}\n"],"mappings":";;;;;;;AAcA,IAAM,IAAN,cAA6C,EAG3C;CACA,YAAY,GAA8E;AAExF,EADA,MAAM,EAAM,EACZ,KAAK,QAAQ;GAAE,UAAU;GAAO,OAAO;GAAM,WAAW;GAAM;;CAGhE,OAAO,yBAAyB,GAA2C;AACzE,SAAO;GAAE,UAAU;GAAM;GAAO;;CAGlC,kBAAkB,GAAc,GAAsB;AACpD,OAAK,SAAS,EAAE,cAAW,CAAC;;CAG9B,SAAS;EACP,IAAM,EAAE,SAAM,KAAK;AA8BnB,SA7BI,KAAK,MAAM,WAEX,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAE,2CAA2C,uBAAuB;KAClE,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,KAAK,MAAM,OAAO,WAAW,EAAE,6CAA6C,gBAAgB;KAC3F,CAAA;IACJ,kBAAC,WAAD;KAAS,WAAU;eAAnB,CACE,kBAAC,WAAD;MAAS,WAAU;gBAChB,EAAE,6CAA6C,gBAAgB;MACxD,CAAA,EACV,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,KAAK,MAAM,OAAO,OAClB,KAAK,MAAM,WAAW,eACnB;QACE;;IACV,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,OAAO,SAAS,QAAQ;KACvC,WAAU;eAET,EAAE,4CAA4C,cAAc;KACtD,CAAA;IACL;OAIH,KAAK,MAAM;;;AAItB,SAAS,EAAmB,EAAE,eAAqC;CACjE,IAAM,EAAK,MAAc,GAAS;AAKlC,QAAO,kBAAC,GAAD;EAAmC,IAJ/B,GAAa,MAAqB;GAC3C,IAAM,IAAQ,EAAU,EAAI;AAC5B,UAAO,MAAU,IAAM,IAAW;;EAEU;EAA0C,CAAA;;AAG1F,SAAgB,EAAsB,GAAmC;AACvE,QACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAqB,aAAa;EAAM,SAAQ;EAAS,gBAAgB;YACvE,kBAAC,GAAD;GAAkC;aAChC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAA2B,CAAA;IACvB,CAAA;GACoB,CAAA;EACR,CAAA,EACH,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/dashboard/DashboardPage.tsx"],"names":[],"mappings":"AA+HA,MAAM,CAAC,OAAO,UAAU,aAAa,
|
|
1
|
+
{"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/dashboard/DashboardPage.tsx"],"names":[],"mappings":"AA+HA,MAAM,CAAC,OAAO,UAAU,aAAa,4CAqQpC"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { useEventfullyManaged as e } from "../../context/EventfullyManagedContext.js";
|
|
2
2
|
import { gql as t } from "../../../node_modules/graphql-tag/lib/index.js";
|
|
3
3
|
import { useQuery as n } from "../../../node_modules/@apollo/client/react/hooks/useQuery.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
4
|
+
import { useTr as r } from "../../../shared/hooks/useTr.js";
|
|
5
|
+
import { useDashboardSummaryQuery as i } from "../../api/eventApi.js";
|
|
6
|
+
import { EventAreaChart as a } from "../../components/EventCharts.js";
|
|
7
|
+
import { PageLayout as o } from "../../components/PageLayout.js";
|
|
8
|
+
import { formatDateTime as s } from "../events/eventFormUtils.js";
|
|
9
|
+
import { useMemo as c, useState as l } from "react";
|
|
10
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
11
|
+
import { Building2 as f, CalendarClock as p, CalendarDays as m, ChevronRight as h, Handshake as g, PlayCircle as _, Ticket as v, UserPlus as y, Users as b } from "lucide-react";
|
|
12
12
|
import { GlassCard as x, GradientText as S, IllustratedEmptyState as C, NextSteps as w, PagePurpose as T, Skeleton as E } from "@burdenoff/fe-libs/ui";
|
|
13
13
|
//#region src/eventfullymanaged/pages/dashboard/DashboardPage.tsx
|
|
14
14
|
var D = t`
|
|
@@ -59,98 +59,95 @@ function j(e, t) {
|
|
|
59
59
|
}));
|
|
60
60
|
}
|
|
61
61
|
function M() {
|
|
62
|
-
let {
|
|
63
|
-
let r = t(e);
|
|
64
|
-
return r === e ? n : r;
|
|
65
|
-
}, { navigate: A } = e(), { data: M, loading: N } = r(), { data: P, loading: F } = n(D, { variables: { limit: 500 } }), { data: I, loading: L } = n(O, { variables: { limit: 100 } }), R = M?.dashboardSummary, [z] = c(() => Date.now()), B = s(() => j(P?.registrations?.items ?? [], z), [P, z]), V = s(() => (I?.eventSessions?.items ?? []).filter((e) => {
|
|
62
|
+
let t = r(), { navigate: k } = e(), { data: A, loading: M } = i(), { data: N, loading: P } = n(D, { variables: { limit: 500 } }), { data: F, loading: I } = n(O, { variables: { limit: 100 } }), L = A?.dashboardSummary, [R] = l(() => Date.now()), z = c(() => j(N?.registrations?.items ?? [], R), [N, R]), B = c(() => (F?.eventSessions?.items ?? []).filter((e) => {
|
|
66
63
|
let t = new Date(e.startsAt).getTime();
|
|
67
|
-
return !Number.isNaN(t) && t >=
|
|
68
|
-
}).sort((e, t) => new Date(e.startsAt).getTime() - new Date(t.startsAt).getTime()).slice(0, 4), [
|
|
64
|
+
return !Number.isNaN(t) && t >= R;
|
|
65
|
+
}).sort((e, t) => new Date(e.startsAt).getTime() - new Date(t.startsAt).getTime()).slice(0, 4), [F, R]), V = [
|
|
69
66
|
{
|
|
70
|
-
label:
|
|
71
|
-
value:
|
|
72
|
-
sublabel:
|
|
73
|
-
icon:
|
|
67
|
+
label: t("eventfullymanaged.dashboard.cards.totalEvents", "Total Events"),
|
|
68
|
+
value: L?.totalEvents ?? 0,
|
|
69
|
+
sublabel: t("eventfullymanaged.dashboard.cards.totalEventsSub", "across your workspace"),
|
|
70
|
+
icon: m,
|
|
74
71
|
path: "/events",
|
|
75
72
|
featured: !0
|
|
76
73
|
},
|
|
77
74
|
{
|
|
78
|
-
label:
|
|
79
|
-
value:
|
|
80
|
-
sublabel:
|
|
81
|
-
icon:
|
|
75
|
+
label: t("eventfullymanaged.dashboard.cards.activeEvents", "Active Events"),
|
|
76
|
+
value: L?.activeEvents ?? 0,
|
|
77
|
+
sublabel: t("eventfullymanaged.dashboard.cards.activeEventsSub", "live or published now"),
|
|
78
|
+
icon: _,
|
|
82
79
|
path: "/events"
|
|
83
80
|
},
|
|
84
81
|
{
|
|
85
|
-
label:
|
|
86
|
-
value:
|
|
87
|
-
sublabel:
|
|
88
|
-
icon:
|
|
82
|
+
label: t("eventfullymanaged.dashboard.cards.registrations", "Registrations"),
|
|
83
|
+
value: L?.totalRegistrations ?? 0,
|
|
84
|
+
sublabel: t("eventfullymanaged.dashboard.cards.registrationsSub", "sign-ups received"),
|
|
85
|
+
icon: y,
|
|
89
86
|
path: "/registration"
|
|
90
87
|
},
|
|
91
88
|
{
|
|
92
|
-
label:
|
|
93
|
-
value:
|
|
94
|
-
sublabel:
|
|
95
|
-
icon:
|
|
89
|
+
label: t("eventfullymanaged.dashboard.cards.attendees", "Attendees"),
|
|
90
|
+
value: L?.totalAttendees ?? 0,
|
|
91
|
+
sublabel: t("eventfullymanaged.dashboard.cards.attendeesSub", "people in your audience"),
|
|
92
|
+
icon: b,
|
|
96
93
|
path: "/registration"
|
|
97
94
|
},
|
|
98
95
|
{
|
|
99
|
-
label:
|
|
100
|
-
value:
|
|
101
|
-
sublabel:
|
|
102
|
-
icon:
|
|
96
|
+
label: t("eventfullymanaged.dashboard.cards.venues", "Venues"),
|
|
97
|
+
value: L?.totalVenues ?? 0,
|
|
98
|
+
sublabel: t("eventfullymanaged.dashboard.cards.venuesSub", "spaces mapped"),
|
|
99
|
+
icon: f,
|
|
103
100
|
path: "/venues"
|
|
104
101
|
},
|
|
105
102
|
{
|
|
106
|
-
label:
|
|
107
|
-
value:
|
|
108
|
-
sublabel:
|
|
109
|
-
icon:
|
|
103
|
+
label: t("eventfullymanaged.dashboard.cards.sponsors", "Sponsors"),
|
|
104
|
+
value: L?.totalSponsors ?? 0,
|
|
105
|
+
sublabel: t("eventfullymanaged.dashboard.cards.sponsorsSub", "partners onboarded"),
|
|
106
|
+
icon: g,
|
|
110
107
|
path: "/sponsors"
|
|
111
108
|
}
|
|
112
109
|
];
|
|
113
|
-
return /* @__PURE__ */
|
|
110
|
+
return /* @__PURE__ */ d(o, {
|
|
114
111
|
hero: !0,
|
|
115
|
-
title: /* @__PURE__ */
|
|
112
|
+
title: /* @__PURE__ */ u(S, {
|
|
116
113
|
as: "span",
|
|
117
|
-
children:
|
|
114
|
+
children: t("eventfullymanaged.dashboard.title", "Dashboard")
|
|
118
115
|
}),
|
|
119
|
-
description:
|
|
116
|
+
description: M ? t("eventfullymanaged.dashboard.description.loading", "Loading real-time event operations summary…") : t("eventfullymanaged.dashboard.description.ready", "Your live event operations, at a glance."),
|
|
120
117
|
children: [
|
|
121
|
-
/* @__PURE__ */
|
|
118
|
+
/* @__PURE__ */ u(T, {
|
|
122
119
|
className: "mb-6",
|
|
123
|
-
children:
|
|
120
|
+
children: t("eventfullymanaged.dashboard.purpose", "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.")
|
|
124
121
|
}),
|
|
125
|
-
/* @__PURE__ */
|
|
122
|
+
/* @__PURE__ */ u("div", {
|
|
126
123
|
className: "grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-6",
|
|
127
|
-
children:
|
|
124
|
+
children: V.map((e) => {
|
|
128
125
|
let t = e.icon;
|
|
129
|
-
return /* @__PURE__ */
|
|
126
|
+
return /* @__PURE__ */ d(x, {
|
|
130
127
|
treatment: e.featured ? "glass" : "metric",
|
|
131
128
|
glow: e.featured,
|
|
132
129
|
role: "button",
|
|
133
130
|
tabIndex: 0,
|
|
134
131
|
"aria-label": `${e.label}: ${e.value.toLocaleString()}`,
|
|
135
|
-
onClick: () =>
|
|
132
|
+
onClick: () => k(e.path),
|
|
136
133
|
onKeyDown: (t) => {
|
|
137
|
-
(t.key === "Enter" || t.key === " ") && (t.preventDefault(),
|
|
134
|
+
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), k(e.path));
|
|
138
135
|
},
|
|
139
136
|
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)]",
|
|
140
137
|
children: [
|
|
141
|
-
/* @__PURE__ */
|
|
138
|
+
/* @__PURE__ */ u("span", {
|
|
142
139
|
className: "flex h-9 w-9 items-center justify-center rounded-button bg-accent-wash text-action-primary-bg",
|
|
143
|
-
children: /* @__PURE__ */
|
|
140
|
+
children: /* @__PURE__ */ u(t, { className: "h-5 w-5" })
|
|
144
141
|
}),
|
|
145
|
-
/* @__PURE__ */
|
|
142
|
+
/* @__PURE__ */ u("div", {
|
|
146
143
|
className: "mt-3 text-2xl font-semibold tabular-nums text-text-primary",
|
|
147
|
-
children:
|
|
144
|
+
children: M ? /* @__PURE__ */ u(E, { className: "h-7 w-14" }) : e.value.toLocaleString()
|
|
148
145
|
}),
|
|
149
|
-
/* @__PURE__ */
|
|
146
|
+
/* @__PURE__ */ u("div", {
|
|
150
147
|
className: "mt-1 text-sm font-medium text-text-primary",
|
|
151
148
|
children: e.label
|
|
152
149
|
}),
|
|
153
|
-
/* @__PURE__ */
|
|
150
|
+
/* @__PURE__ */ u("div", {
|
|
154
151
|
className: "mt-0.5 truncate text-xs text-text-muted",
|
|
155
152
|
children: e.sublabel
|
|
156
153
|
})
|
|
@@ -158,112 +155,112 @@ function M() {
|
|
|
158
155
|
}, e.label);
|
|
159
156
|
})
|
|
160
157
|
}),
|
|
161
|
-
/* @__PURE__ */
|
|
158
|
+
/* @__PURE__ */ d("div", {
|
|
162
159
|
className: "mt-6 grid grid-cols-1 gap-4 lg:grid-cols-5",
|
|
163
|
-
children: [/* @__PURE__ */
|
|
160
|
+
children: [/* @__PURE__ */ d(x, {
|
|
164
161
|
treatment: "glass",
|
|
165
162
|
className: "p-5 lg:col-span-3",
|
|
166
|
-
children: [/* @__PURE__ */
|
|
163
|
+
children: [/* @__PURE__ */ d("div", {
|
|
167
164
|
className: "flex items-start justify-between gap-4",
|
|
168
|
-
children: [/* @__PURE__ */
|
|
165
|
+
children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("h2", {
|
|
169
166
|
className: "text-lg font-semibold text-text-primary",
|
|
170
|
-
children:
|
|
171
|
-
}), /* @__PURE__ */
|
|
167
|
+
children: t("eventfullymanaged.dashboard.trend.title", "Registrations over time")
|
|
168
|
+
}), /* @__PURE__ */ u("p", {
|
|
172
169
|
className: "mt-0.5 text-xs text-text-muted",
|
|
173
|
-
children:
|
|
174
|
-
})] }), /* @__PURE__ */
|
|
170
|
+
children: t("eventfullymanaged.dashboard.trend.subtitle", "Cumulative sign-ups, last 30 days")
|
|
171
|
+
})] }), /* @__PURE__ */ u("div", {
|
|
175
172
|
className: "text-2xl font-semibold tabular-nums text-text-primary",
|
|
176
|
-
children: (
|
|
173
|
+
children: (L?.totalRegistrations ?? 0).toLocaleString()
|
|
177
174
|
})]
|
|
178
|
-
}), /* @__PURE__ */
|
|
175
|
+
}), /* @__PURE__ */ u("div", {
|
|
179
176
|
className: "mt-4",
|
|
180
|
-
children:
|
|
177
|
+
children: P ? /* @__PURE__ */ u(E, { className: "h-[260px] w-full rounded-card" }) : z.some((e) => e.value > 0) ? /* @__PURE__ */ u(a, { data: z }) : /* @__PURE__ */ u(C, {
|
|
181
178
|
illustration: "empty-data",
|
|
182
|
-
title:
|
|
183
|
-
description:
|
|
179
|
+
title: t("eventfullymanaged.dashboard.trend.empty", "No registrations yet."),
|
|
180
|
+
description: t("eventfullymanaged.dashboard.trend.emptyHint", "Once attendees start signing up, their momentum shows here."),
|
|
184
181
|
className: "py-10"
|
|
185
182
|
})
|
|
186
183
|
})]
|
|
187
|
-
}), /* @__PURE__ */
|
|
184
|
+
}), /* @__PURE__ */ d(x, {
|
|
188
185
|
treatment: "plain",
|
|
189
186
|
className: "border-border-seam p-5 lg:col-span-2",
|
|
190
|
-
children: [/* @__PURE__ */
|
|
187
|
+
children: [/* @__PURE__ */ u("h2", {
|
|
191
188
|
className: "text-lg font-semibold text-text-primary",
|
|
192
|
-
children:
|
|
193
|
-
}), /* @__PURE__ */
|
|
189
|
+
children: t("eventfullymanaged.dashboard.upcoming.title", "Upcoming sessions")
|
|
190
|
+
}), /* @__PURE__ */ u("div", {
|
|
194
191
|
className: "mt-3",
|
|
195
|
-
children:
|
|
192
|
+
children: I ? /* @__PURE__ */ d("div", {
|
|
196
193
|
className: "space-y-3",
|
|
197
194
|
children: [
|
|
198
|
-
/* @__PURE__ */
|
|
199
|
-
/* @__PURE__ */
|
|
200
|
-
/* @__PURE__ */
|
|
195
|
+
/* @__PURE__ */ u(E, { className: "h-14 w-full rounded-card" }),
|
|
196
|
+
/* @__PURE__ */ u(E, { className: "h-14 w-full rounded-card" }),
|
|
197
|
+
/* @__PURE__ */ u(E, { className: "h-14 w-full rounded-card" })
|
|
201
198
|
]
|
|
202
|
-
}) :
|
|
199
|
+
}) : B.length === 0 ? /* @__PURE__ */ u(C, {
|
|
203
200
|
illustration: "empty-data",
|
|
204
|
-
title:
|
|
205
|
-
description:
|
|
201
|
+
title: t("eventfullymanaged.dashboard.upcoming.empty", "Nothing scheduled yet."),
|
|
202
|
+
description: t("eventfullymanaged.dashboard.upcoming.emptyHint", "Sessions you schedule will surface here as they approach."),
|
|
206
203
|
className: "py-8"
|
|
207
|
-
}) : /* @__PURE__ */
|
|
204
|
+
}) : /* @__PURE__ */ u("ul", {
|
|
208
205
|
className: "divide-y divide-border-subtle",
|
|
209
|
-
children:
|
|
206
|
+
children: B.map((e) => /* @__PURE__ */ u("li", { children: /* @__PURE__ */ d("button", {
|
|
210
207
|
type: "button",
|
|
211
|
-
onClick: () =>
|
|
208
|
+
onClick: () => k(`/events/${e.eventId}`),
|
|
212
209
|
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)]",
|
|
213
210
|
children: [
|
|
214
|
-
/* @__PURE__ */
|
|
211
|
+
/* @__PURE__ */ u("span", {
|
|
215
212
|
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-button bg-accent-wash text-action-primary-bg",
|
|
216
|
-
children: /* @__PURE__ */
|
|
213
|
+
children: /* @__PURE__ */ u(p, { className: "h-4 w-4" })
|
|
217
214
|
}),
|
|
218
|
-
/* @__PURE__ */
|
|
215
|
+
/* @__PURE__ */ d("span", {
|
|
219
216
|
className: "min-w-0 flex-1",
|
|
220
|
-
children: [/* @__PURE__ */
|
|
217
|
+
children: [/* @__PURE__ */ u("span", {
|
|
221
218
|
className: "block truncate text-sm font-medium text-text-primary",
|
|
222
219
|
children: e.title
|
|
223
|
-
}), /* @__PURE__ */
|
|
220
|
+
}), /* @__PURE__ */ u("span", {
|
|
224
221
|
className: "mt-0.5 block truncate text-xs text-text-muted",
|
|
225
|
-
children: [
|
|
222
|
+
children: [s(e.startsAt), e.track].filter(Boolean).join(" • ")
|
|
226
223
|
})]
|
|
227
224
|
}),
|
|
228
|
-
/* @__PURE__ */
|
|
225
|
+
/* @__PURE__ */ u(h, { className: "h-4 w-4 shrink-0 text-text-muted transition-transform group-hover:translate-x-0.5" })
|
|
229
226
|
]
|
|
230
227
|
}) }, e.id))
|
|
231
228
|
})
|
|
232
229
|
})]
|
|
233
230
|
})]
|
|
234
231
|
}),
|
|
235
|
-
/* @__PURE__ */
|
|
232
|
+
/* @__PURE__ */ u(w, {
|
|
236
233
|
className: "mt-6",
|
|
237
234
|
storageKey: "eventfullymanaged-dashboard",
|
|
238
|
-
title:
|
|
235
|
+
title: t("eventfullymanaged.dashboard.nextSteps.title", "Get your first event running"),
|
|
239
236
|
steps: [
|
|
240
237
|
{
|
|
241
238
|
id: "create-event",
|
|
242
|
-
label:
|
|
243
|
-
description:
|
|
244
|
-
icon: /* @__PURE__ */
|
|
245
|
-
onClick: () =>
|
|
239
|
+
label: t("eventfullymanaged.dashboard.nextSteps.createEvent", "Create an event"),
|
|
240
|
+
description: t("eventfullymanaged.dashboard.nextSteps.createEventDesc", "Start with the basics — name, dates and capacity."),
|
|
241
|
+
icon: /* @__PURE__ */ u(m, { className: "h-4 w-4" }),
|
|
242
|
+
onClick: () => k("/events/create")
|
|
246
243
|
},
|
|
247
244
|
{
|
|
248
245
|
id: "add-venue",
|
|
249
|
-
label:
|
|
250
|
-
description:
|
|
251
|
-
icon: /* @__PURE__ */
|
|
252
|
-
onClick: () =>
|
|
246
|
+
label: t("eventfullymanaged.dashboard.nextSteps.addVenue", "Add a venue"),
|
|
247
|
+
description: t("eventfullymanaged.dashboard.nextSteps.addVenueDesc", "Map your event to a space with capacity and floor plans."),
|
|
248
|
+
icon: /* @__PURE__ */ u(f, { className: "h-4 w-4" }),
|
|
249
|
+
onClick: () => k("/venues/create")
|
|
253
250
|
},
|
|
254
251
|
{
|
|
255
252
|
id: "open-registration",
|
|
256
|
-
label:
|
|
257
|
-
description:
|
|
258
|
-
icon: /* @__PURE__ */
|
|
259
|
-
onClick: () =>
|
|
253
|
+
label: t("eventfullymanaged.dashboard.nextSteps.openRegistration", "Open registration"),
|
|
254
|
+
description: t("eventfullymanaged.dashboard.nextSteps.openRegistrationDesc", "Set up ticket types and track attendee sign-ups."),
|
|
255
|
+
icon: /* @__PURE__ */ u(v, { className: "h-4 w-4" }),
|
|
256
|
+
onClick: () => k("/registration")
|
|
260
257
|
},
|
|
261
258
|
{
|
|
262
259
|
id: "invite-sponsors",
|
|
263
|
-
label:
|
|
264
|
-
description:
|
|
265
|
-
icon: /* @__PURE__ */
|
|
266
|
-
onClick: () =>
|
|
260
|
+
label: t("eventfullymanaged.dashboard.nextSteps.inviteSponsors", "Onboard sponsors"),
|
|
261
|
+
description: t("eventfullymanaged.dashboard.nextSteps.inviteSponsorsDesc", "Add sponsors and start tracking value and leads."),
|
|
262
|
+
icon: /* @__PURE__ */ u(g, { className: "h-4 w-4" }),
|
|
263
|
+
onClick: () => k("/sponsors")
|
|
267
264
|
}
|
|
268
265
|
]
|
|
269
266
|
})
|
|
@@ -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 {\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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"}
|
|
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 {\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 { useTr } from '../../../shared/hooks/useTr';\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 = useTr();\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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"}
|