@burdenoff/microfe-eventfullymanaged 2026.703.2 → 2026.704.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.
Files changed (74) hide show
  1. package/dist/eventfullymanaged/EventfullyManagedRoutes.js +154 -66
  2. package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
  3. package/dist/eventfullymanaged/api/eventApi.js +546 -67
  4. package/dist/eventfullymanaged/api/eventApi.js.map +1 -1
  5. package/dist/eventfullymanaged/components/ActionMenu.js +113 -0
  6. package/dist/eventfullymanaged/components/ActionMenu.js.map +1 -0
  7. package/dist/eventfullymanaged/components/EntityFormShell.js +51 -0
  8. package/dist/eventfullymanaged/components/EntityFormShell.js.map +1 -0
  9. package/dist/eventfullymanaged/components/EntityTagsField.js +146 -0
  10. package/dist/eventfullymanaged/components/EntityTagsField.js.map +1 -0
  11. package/dist/eventfullymanaged/components/Pagination.js +84 -0
  12. package/dist/eventfullymanaged/components/Pagination.js.map +1 -0
  13. package/dist/eventfullymanaged/components/SafeLink.js +32 -0
  14. package/dist/eventfullymanaged/components/SafeLink.js.map +1 -0
  15. package/dist/eventfullymanaged/components/index.js +7 -0
  16. package/dist/eventfullymanaged/pages/events/EventDetailPage.js +458 -0
  17. package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -0
  18. package/dist/eventfullymanaged/pages/events/EventFormPage.js +267 -0
  19. package/dist/eventfullymanaged/pages/events/EventFormPage.js.map +1 -0
  20. package/dist/eventfullymanaged/pages/events/SessionFormPage.js +308 -0
  21. package/dist/eventfullymanaged/pages/events/SessionFormPage.js.map +1 -0
  22. package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js +214 -0
  23. package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js.map +1 -0
  24. package/dist/eventfullymanaged/pages/events/eventFormUtils.js +128 -0
  25. package/dist/eventfullymanaged/pages/events/eventFormUtils.js.map +1 -0
  26. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js +233 -0
  27. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js.map +1 -0
  28. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorsPage.js +165 -0
  29. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorsPage.js.map +1 -0
  30. package/dist/eventfullymanaged/pages/leads/LeadCaptureDialog.js +141 -0
  31. package/dist/eventfullymanaged/pages/leads/LeadCaptureDialog.js.map +1 -0
  32. package/dist/eventfullymanaged/pages/modules/AnalyticsPage.js +178 -45
  33. package/dist/eventfullymanaged/pages/modules/AnalyticsPage.js.map +1 -1
  34. package/dist/eventfullymanaged/pages/modules/CommunicationPage.js +240 -70
  35. package/dist/eventfullymanaged/pages/modules/CommunicationPage.js.map +1 -1
  36. package/dist/eventfullymanaged/pages/modules/EventsPage.js +133 -97
  37. package/dist/eventfullymanaged/pages/modules/EventsPage.js.map +1 -1
  38. package/dist/eventfullymanaged/pages/modules/KioskPage.js +746 -36
  39. package/dist/eventfullymanaged/pages/modules/KioskPage.js.map +1 -1
  40. package/dist/eventfullymanaged/pages/modules/NetworkingPage.js +210 -69
  41. package/dist/eventfullymanaged/pages/modules/NetworkingPage.js.map +1 -1
  42. package/dist/eventfullymanaged/pages/modules/RegistrationPage.js +289 -77
  43. package/dist/eventfullymanaged/pages/modules/RegistrationPage.js.map +1 -1
  44. package/dist/eventfullymanaged/pages/modules/RoadmapPanel.js +95 -0
  45. package/dist/eventfullymanaged/pages/modules/RoadmapPanel.js.map +1 -0
  46. package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js +77 -56
  47. package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js.map +1 -1
  48. package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js +70 -36
  49. package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js.map +1 -1
  50. package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js +343 -0
  51. package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js.map +1 -0
  52. package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js +236 -0
  53. package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js.map +1 -0
  54. package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js +181 -0
  55. package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js.map +1 -0
  56. package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js +262 -0
  57. package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js.map +1 -0
  58. package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js +414 -0
  59. package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js.map +1 -0
  60. package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js +224 -0
  61. package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js.map +1 -0
  62. package/dist/eventfullymanaged/pages/speakers/SpeakersPage.js +96 -0
  63. package/dist/eventfullymanaged/pages/speakers/SpeakersPage.js.map +1 -0
  64. package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js +149 -140
  65. package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js.map +1 -1
  66. package/dist/eventfullymanaged/pages/sponsors/SponsorsListPage.js +97 -67
  67. package/dist/eventfullymanaged/pages/sponsors/SponsorsListPage.js.map +1 -1
  68. package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js +93 -72
  69. package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js.map +1 -1
  70. package/dist/eventfullymanaged/pages/venues/VenueFormPage.js +91 -89
  71. package/dist/eventfullymanaged/pages/venues/VenueFormPage.js.map +1 -1
  72. package/dist/eventfullymanaged/pages/venues/VenueListPage.js +83 -53
  73. package/dist/eventfullymanaged/pages/venues/VenueListPage.js.map +1 -1
  74. package/package.json +1 -1
@@ -0,0 +1,141 @@
1
+ import { notify as e } from "../../lib/toast.js";
2
+ import { useCreateLeadMutation as t } from "../../api/eventApi.js";
3
+ import { describeError as n, mutationSucceeded as r } from "../../lib/mutationResult.js";
4
+ import { useState as i } from "react";
5
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
6
+ import { UserPlus as s } from "lucide-react";
7
+ import { Button as c, Input as l, ResponsiveDialog as u, ResponsiveDialogContent as d, ResponsiveDialogDescription as f, ResponsiveDialogFooter as p, ResponsiveDialogHeader as m, ResponsiveDialogTitle as h, Textarea as g } from "@burdenoff/fe-libs/ui";
8
+ //#region src/eventfullymanaged/pages/leads/LeadCaptureDialog.tsx
9
+ function _() {
10
+ let e = /* @__PURE__ */ new Date(), t = e.getTimezoneOffset() * 60 * 1e3;
11
+ return new Date(e.getTime() - t).toISOString().slice(0, 16);
12
+ }
13
+ function v() {
14
+ return {
15
+ attendeeName: "",
16
+ attendeeEmail: "",
17
+ company: "",
18
+ notes: "",
19
+ scannedAt: _()
20
+ };
21
+ }
22
+ function y({ open: _, onOpenChange: y, eventId: b, exhibitorId: x, sponsorId: S, contextLabel: C, onCreated: w }) {
23
+ let [T, E] = i(v), [D, { loading: O }] = t(), [k, A] = i(_);
24
+ _ !== k && (A(_), _ && E(v()));
25
+ let j = (e, t) => {
26
+ E((n) => ({
27
+ ...n,
28
+ [e]: t
29
+ }));
30
+ };
31
+ return /* @__PURE__ */ a(u, {
32
+ open: _,
33
+ onOpenChange: y,
34
+ children: /* @__PURE__ */ o(d, { children: [/* @__PURE__ */ o(m, { children: [/* @__PURE__ */ a(h, { children: "Capture a lead" }), /* @__PURE__ */ o(f, { children: [
35
+ "Record a contact scanned by ",
36
+ C,
37
+ ". They are added to the event lead list for follow-up."
38
+ ] })] }), /* @__PURE__ */ o("form", {
39
+ onSubmit: async (t) => {
40
+ if (t.preventDefault(), !b) {
41
+ e.error("Event required", "This record is not linked to an event yet.");
42
+ return;
43
+ }
44
+ let i = T.scannedAt ? new Date(T.scannedAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString(), a = {
45
+ eventId: b,
46
+ attendeeName: T.attendeeName,
47
+ attendeeEmail: T.attendeeEmail,
48
+ notes: T.notes,
49
+ status: "NEW",
50
+ capturedAt: i,
51
+ metadata: T.company.trim() ? { company: T.company.trim() } : {}
52
+ };
53
+ x && (a.exhibitorId = x), S && (a.sponsorId = S);
54
+ try {
55
+ let t = await D({ variables: { input: a } });
56
+ if (!r(t, t.data?.createLead?.id, "Could not capture lead")) return;
57
+ e.success("Lead captured", `${T.attendeeName} was added to the lead list.`), y(!1), w?.();
58
+ } catch (t) {
59
+ e.error("Could not capture lead", n(t));
60
+ }
61
+ },
62
+ className: "space-y-4",
63
+ children: [
64
+ /* @__PURE__ */ o("div", {
65
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
66
+ children: [/* @__PURE__ */ o("label", {
67
+ className: "space-y-1 text-sm",
68
+ children: [/* @__PURE__ */ a("span", {
69
+ className: "font-medium text-text-primary",
70
+ children: "Name"
71
+ }), /* @__PURE__ */ a(l, {
72
+ value: T.attendeeName,
73
+ onChange: (e) => j("attendeeName", e.target.value),
74
+ required: !0
75
+ })]
76
+ }), /* @__PURE__ */ o("label", {
77
+ className: "space-y-1 text-sm",
78
+ children: [/* @__PURE__ */ a("span", {
79
+ className: "font-medium text-text-primary",
80
+ children: "Email"
81
+ }), /* @__PURE__ */ a(l, {
82
+ type: "email",
83
+ value: T.attendeeEmail,
84
+ onChange: (e) => j("attendeeEmail", e.target.value),
85
+ required: !0
86
+ })]
87
+ })]
88
+ }),
89
+ /* @__PURE__ */ o("div", {
90
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
91
+ children: [/* @__PURE__ */ o("label", {
92
+ className: "space-y-1 text-sm",
93
+ children: [/* @__PURE__ */ a("span", {
94
+ className: "font-medium text-text-primary",
95
+ children: "Company"
96
+ }), /* @__PURE__ */ a(l, {
97
+ value: T.company,
98
+ onChange: (e) => j("company", e.target.value)
99
+ })]
100
+ }), /* @__PURE__ */ o("label", {
101
+ className: "space-y-1 text-sm",
102
+ children: [/* @__PURE__ */ a("span", {
103
+ className: "font-medium text-text-primary",
104
+ children: "Scanned at"
105
+ }), /* @__PURE__ */ a(l, {
106
+ type: "datetime-local",
107
+ value: T.scannedAt,
108
+ onChange: (e) => j("scannedAt", e.target.value)
109
+ })]
110
+ })]
111
+ }),
112
+ /* @__PURE__ */ o("label", {
113
+ className: "block space-y-1 text-sm",
114
+ children: [/* @__PURE__ */ a("span", {
115
+ className: "font-medium text-text-primary",
116
+ children: "Notes"
117
+ }), /* @__PURE__ */ a(g, {
118
+ value: T.notes,
119
+ onChange: (e) => j("notes", e.target.value),
120
+ rows: 3
121
+ })]
122
+ }),
123
+ /* @__PURE__ */ o(p, { children: [/* @__PURE__ */ a(c, {
124
+ type: "button",
125
+ variant: "outline",
126
+ onClick: () => y(!1),
127
+ disabled: O,
128
+ children: "Cancel"
129
+ }), /* @__PURE__ */ o(c, {
130
+ type: "submit",
131
+ disabled: O,
132
+ children: [/* @__PURE__ */ a(s, { className: "h-4 w-4" }), O ? "Saving…" : "Capture lead"]
133
+ })] })
134
+ ]
135
+ })] })
136
+ });
137
+ }
138
+ //#endregion
139
+ export { y as LeadCaptureDialog };
140
+
141
+ //# sourceMappingURL=LeadCaptureDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeadCaptureDialog.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/leads/LeadCaptureDialog.tsx"],"sourcesContent":["import { UserPlus } from 'lucide-react';\nimport {\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useState } from 'react';\n\nimport { useCreateLeadMutation } from '../../api/eventApi';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n/** Local YYYY-MM-DDTHH:mm string for a `datetime-local` input default. */\nfunction nowLocalInput(): string {\n const now = new Date();\n const offsetMs = now.getTimezoneOffset() * 60 * 1000;\n return new Date(now.getTime() - offsetMs).toISOString().slice(0, 16);\n}\n\nexport interface LeadCaptureDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Event the lead belongs to. */\n eventId: string;\n /** Booth/exhibitor that scanned the lead (mutually exclusive with sponsorId). */\n exhibitorId?: string;\n /** Sponsor that captured the lead. */\n sponsorId?: string;\n /** Friendly name shown in the dialog title, e.g. the exhibitor or sponsor name. */\n contextLabel: string;\n /** Fired after a lead is successfully persisted so the caller can refetch. */\n onCreated?: () => void;\n}\n\ntype LeadFormState = {\n attendeeName: string;\n attendeeEmail: string;\n company: string;\n notes: string;\n scannedAt: string;\n};\n\nfunction makeInitialState(): LeadFormState {\n return {\n attendeeName: '',\n attendeeEmail: '',\n company: '',\n notes: '',\n scannedAt: nowLocalInput(),\n };\n}\n\n/**\n * Booth/sponsor lead-capture dialog. Records the person a booth just scanned as\n * an EventfullyManaged lead via `useCreateLeadMutation`, attaching either the\n * `exhibitorId` or `sponsorId` context. The captured company is preserved in\n * `metadata.company` (no first-class column) and `scannedAt` maps to the\n * backend `capturedAt` timestamp.\n */\nexport function LeadCaptureDialog({\n open,\n onOpenChange,\n eventId,\n exhibitorId,\n sponsorId,\n contextLabel,\n onCreated,\n}: LeadCaptureDialogProps) {\n const [form, setForm] = useState<LeadFormState>(makeInitialState);\n const [createLead, { loading }] = useCreateLeadMutation();\n\n // Reset to a clean slate (and fresh timestamp) each time the dialog opens.\n // Deriving from the `open` transition during render is React's documented\n // alternative to an effect + setState (avoids a cascading render).\n const [wasOpen, setWasOpen] = useState(open);\n if (open !== wasOpen) {\n setWasOpen(open);\n if (open) {\n setForm(makeInitialState());\n }\n }\n\n const onChange = (field: keyof LeadFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!eventId) {\n notify.error('Event required', 'This record is not linked to an event yet.');\n return;\n }\n\n const capturedAt = form.scannedAt ? new Date(form.scannedAt).toISOString() : new Date().toISOString();\n const input: Record<string, unknown> = {\n eventId,\n attendeeName: form.attendeeName,\n attendeeEmail: form.attendeeEmail,\n notes: form.notes,\n status: 'NEW',\n capturedAt,\n metadata: form.company.trim() ? { company: form.company.trim() } : {},\n };\n if (exhibitorId) {\n input.exhibitorId = exhibitorId;\n }\n if (sponsorId) {\n input.sponsorId = sponsorId;\n }\n\n try {\n const response = await createLead({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createLead?.id, 'Could not capture lead')) {\n return;\n }\n notify.success('Lead captured', `${form.attendeeName} was added to the lead list.`);\n onOpenChange(false);\n onCreated?.();\n } catch (error) {\n notify.error('Could not capture lead', describeError(error));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Capture a lead</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Record a contact scanned by {contextLabel}. They are added to the event lead list for follow-up.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <form onSubmit={onSubmit} className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Name</span>\n <Input value={form.attendeeName} onChange={(e) => onChange('attendeeName', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Email</span>\n <Input\n type=\"email\"\n value={form.attendeeEmail}\n onChange={(e) => onChange('attendeeEmail', e.target.value)}\n required\n />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Company</span>\n <Input value={form.company} onChange={(e) => onChange('company', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Scanned at</span>\n <Input\n type=\"datetime-local\"\n value={form.scannedAt}\n onChange={(e) => onChange('scannedAt', e.target.value)}\n />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Notes</span>\n <Textarea value={form.notes} onChange={(e) => onChange('notes', e.target.value)} rows={3} />\n </label>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)} disabled={loading}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={loading}>\n <UserPlus className=\"h-4 w-4\" />\n {loading ? 'Saving…' : 'Capture lead'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -1,73 +1,206 @@
1
- import { notify as e } from "../../lib/toast.js";
2
- import { useAnalyticsSnapshotsQuery as t, useUpsertAnalyticsSnapshotMutation as n } from "../../api/eventApi.js";
3
- import { describeError as r, mutationSucceeded as i } from "../../lib/mutationResult.js";
4
- import { EntityListSection as a } from "./EntityListSection.js";
5
- import { useMemo as o, useState as s } from "react";
6
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
7
- import { BarChart3 as u } from "lucide-react";
8
- import { Button as d, EmphasisPanel as f, GradientText as p, Input as m, PagePurpose as h } from "@burdenoff/fe-libs/ui";
1
+ import { gql as e } from "../../../node_modules/graphql-tag/lib/index.js";
2
+ import { useQuery as t } from "../../../node_modules/@apollo/client/react/hooks/useQuery.js";
3
+ import { notify as n } from "../../lib/toast.js";
4
+ import { useUpsertAnalyticsSnapshotMutation as r } from "../../api/eventApi.js";
5
+ import { Pagination as i, usePagination as a } from "../../components/Pagination.js";
6
+ import { describeError as o, mutationSucceeded as s } from "../../lib/mutationResult.js";
7
+ import { useMemo as c, useState as l } from "react";
8
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
9
+ import { BarChart3 as f } from "lucide-react";
10
+ import { Badge as p, Button as m, EmphasisPanel as h, GlassCard as g, GradientText as _, IllustratedEmptyState as v, Input as y, PagePurpose as b } from "@burdenoff/fe-libs/ui";
9
11
  //#region src/eventfullymanaged/pages/modules/AnalyticsPage.tsx
10
- function g() {
11
- let g = t({
12
- limit: 100,
13
- offset: 0
14
- }), [_, { loading: v }] = n(), [y, b] = s("overview"), x = o(() => g.data?.analyticsSnapshots.items ?? [], [g.data]);
15
- return /* @__PURE__ */ l("div", {
12
+ var x = e`
13
+ query EventfullymanagedAnalyticsSnapshotsWithMetrics($limit: Int, $offset: Int) {
14
+ analyticsSnapshots: eventfullyManagedAnalyticsSnapshots(limit: $limit, offset: $offset) {
15
+ total
16
+ items {
17
+ id
18
+ name
19
+ title
20
+ status
21
+ metrics
22
+ createdAt
23
+ }
24
+ }
25
+ }
26
+ `, S = [
27
+ {
28
+ key: "totalEvents",
29
+ label: "Events"
30
+ },
31
+ {
32
+ key: "activeEvents",
33
+ label: "Active"
34
+ },
35
+ {
36
+ key: "totalRegistrations",
37
+ label: "Registrations"
38
+ },
39
+ {
40
+ key: "totalAttendees",
41
+ label: "Attendees"
42
+ },
43
+ {
44
+ key: "totalVenues",
45
+ label: "Venues"
46
+ },
47
+ {
48
+ key: "totalSponsors",
49
+ label: "Sponsors"
50
+ },
51
+ {
52
+ key: "upcomingSessions",
53
+ label: "Upcoming Sessions"
54
+ }
55
+ ];
56
+ function C(e) {
57
+ let t = e.toLowerCase();
58
+ return [
59
+ "active",
60
+ "published",
61
+ "completed"
62
+ ].includes(t) ? "success" : [
63
+ "inactive",
64
+ "cancelled",
65
+ "archived",
66
+ "expired"
67
+ ].includes(t) ? "warning" : [
68
+ "draft",
69
+ "created",
70
+ "pending",
71
+ "scheduled"
72
+ ].includes(t) ? "info" : "secondary";
73
+ }
74
+ function w(e) {
75
+ if (!e) return null;
76
+ let t = new Date(e);
77
+ return Number.isNaN(t.getTime()) ? null : t.toLocaleString();
78
+ }
79
+ function T() {
80
+ let e = a({ pageSize: 10 }), { data: T, loading: E, refetch: D } = t(x, { variables: {
81
+ limit: e.limit,
82
+ offset: e.offset
83
+ } }), [O, { loading: k }] = r(), [A, j] = l("overview"), M = c(() => T?.analyticsSnapshots.items ?? [], [T]), N = T?.analyticsSnapshots.total ?? 0;
84
+ return /* @__PURE__ */ d("div", {
16
85
  className: "space-y-6 p-6",
17
86
  children: [
18
- /* @__PURE__ */ c(h, { children: "Snapshots capture a point-in-time picture of how your events are performing — registrations, attendance, revenue and engagement frozen at the moment you generate them. Use this page to take a daily or post-event snapshot so you can compare trends over time and report on outcomes." }),
19
- /* @__PURE__ */ l(f, { children: [
20
- /* @__PURE__ */ c("h1", {
87
+ /* @__PURE__ */ u(b, { children: "Snapshots capture a point-in-time picture of how your events are performing — total events, registrations, attendees, venues, sponsors and upcoming sessions frozen at the moment you generate them. Use this page to take a daily or post-event snapshot so you can compare trends over time and report on outcomes." }),
88
+ /* @__PURE__ */ d(h, { children: [
89
+ /* @__PURE__ */ u("h1", {
21
90
  className: "text-2xl font-bold text-text-primary",
22
- children: /* @__PURE__ */ c(p, { children: "Analytics" })
91
+ children: /* @__PURE__ */ u(_, { children: "Analytics" })
23
92
  }),
24
- /* @__PURE__ */ c("p", {
93
+ /* @__PURE__ */ u("p", {
25
94
  className: "mt-1 text-sm text-text-secondary",
26
- children: "Generate and track analytics snapshots from event operations data."
95
+ children: "Generate and track analytics snapshots from live event operations data."
27
96
  }),
28
- /* @__PURE__ */ l("div", {
97
+ /* @__PURE__ */ d("div", {
29
98
  className: "mt-4 flex flex-col gap-3 sm:flex-row",
30
- children: [/* @__PURE__ */ c(m, {
31
- value: y,
32
- onChange: (e) => b(e.target.value),
99
+ children: [/* @__PURE__ */ u(y, {
100
+ value: A,
101
+ onChange: (e) => j(e.target.value),
33
102
  placeholder: "daily-summary",
34
103
  "aria-label": "Snapshot name",
35
104
  className: "flex-1"
36
- }), /* @__PURE__ */ l(d, {
105
+ }), /* @__PURE__ */ d(m, {
37
106
  type: "button",
38
- disabled: v,
107
+ disabled: k,
39
108
  onClick: async () => {
40
- if (y.trim()) try {
41
- let t = await _({ variables: { input: {
42
- name: y,
43
- title: y,
109
+ if (A.trim()) try {
110
+ let e = await O({ variables: { input: {
111
+ name: A,
112
+ title: A,
44
113
  status: "created",
45
114
  metadata: {}
46
115
  } } });
47
- if (!i(t, t.data?.upsertAnalyticsSnapshot?.id, "Could not generate snapshot")) return;
48
- e.success("Snapshot generated");
49
- } catch (t) {
50
- e.error("Could not generate snapshot", r(t));
116
+ if (!s(e, e.data?.upsertAnalyticsSnapshot?.id, "Could not generate snapshot")) return;
117
+ n.success("Snapshot generated"), await D();
118
+ } catch (e) {
119
+ n.error("Could not generate snapshot", o(e));
51
120
  }
52
121
  },
53
- children: [/* @__PURE__ */ c(u, { className: "h-4 w-4" }), v ? "Generating..." : "Generate Snapshot"]
122
+ children: [/* @__PURE__ */ u(f, { className: "h-4 w-4" }), k ? "Generating..." : "Generate Snapshot"]
54
123
  })]
55
124
  })
56
125
  ] }),
57
- /* @__PURE__ */ c(a, {
58
- heading: g.loading ? "Loading snapshots..." : `Snapshots (${x.length})`,
59
- emptyLabel: "No analytics snapshots available yet.",
60
- emptyIllustration: "empty-data",
61
- rows: x.map((e) => ({
62
- id: e.id,
63
- title: e.title ?? e.name ?? `Snapshot ${e.id}`,
64
- status: e.status ?? void 0
65
- }))
126
+ /* @__PURE__ */ d("section", {
127
+ className: "space-y-4",
128
+ children: [
129
+ /* @__PURE__ */ u("h2", {
130
+ className: "text-lg font-semibold text-text-primary",
131
+ children: E ? "Loading snapshots..." : `Snapshots (${N})`
132
+ }),
133
+ M.length === 0 ? /* @__PURE__ */ u(g, {
134
+ treatment: "plain",
135
+ className: "border-border-seam",
136
+ children: /* @__PURE__ */ u(v, {
137
+ illustration: "empty-data",
138
+ title: "No analytics snapshots available yet.",
139
+ description: "Generate your first snapshot to freeze the current event metrics.",
140
+ className: "py-8"
141
+ })
142
+ }) : /* @__PURE__ */ u("div", {
143
+ className: "space-y-4",
144
+ children: M.map((e) => {
145
+ let t = w(e.metrics?.generatedAt) ?? w(e.createdAt);
146
+ return /* @__PURE__ */ d(g, {
147
+ treatment: "plain",
148
+ className: "border-border-seam p-5",
149
+ children: [/* @__PURE__ */ d("div", {
150
+ className: "flex flex-wrap items-start justify-between gap-3",
151
+ children: [/* @__PURE__ */ d("div", {
152
+ className: "min-w-0",
153
+ children: [/* @__PURE__ */ u("h3", {
154
+ className: "truncate text-base font-semibold text-text-primary",
155
+ children: e.title ?? e.name ?? `Snapshot ${e.id}`
156
+ }), t ? /* @__PURE__ */ d("p", {
157
+ className: "mt-0.5 text-xs text-text-secondary",
158
+ children: ["Generated ", t]
159
+ }) : null]
160
+ }), e.status ? /* @__PURE__ */ u(p, {
161
+ variant: C(e.status),
162
+ className: "shrink-0 uppercase",
163
+ children: e.status
164
+ }) : null]
165
+ }), e.metrics ? /* @__PURE__ */ u("div", {
166
+ className: "mt-4 grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-7",
167
+ children: S.map((t) => {
168
+ let n = e.metrics?.[t.key], r = typeof n == "number" ? n : 0;
169
+ return /* @__PURE__ */ d(g, {
170
+ treatment: "metric",
171
+ className: "p-3",
172
+ children: [/* @__PURE__ */ u("p", {
173
+ className: "text-xs uppercase tracking-wide text-text-secondary",
174
+ children: t.label
175
+ }), /* @__PURE__ */ u("p", {
176
+ className: "mt-1 text-xl font-semibold tabular-nums text-text-primary",
177
+ children: r.toLocaleString()
178
+ })]
179
+ }, t.key);
180
+ })
181
+ }) : /* @__PURE__ */ u("p", {
182
+ className: "mt-3 text-sm text-text-secondary",
183
+ children: "No metrics were captured for this snapshot."
184
+ })]
185
+ }, e.id);
186
+ })
187
+ }),
188
+ /* @__PURE__ */ u(i, {
189
+ offset: e.offset,
190
+ limit: e.limit,
191
+ loadedCount: M.length,
192
+ total: N,
193
+ onPrev: e.prevPage,
194
+ onNext: e.nextPage,
195
+ itemLabel: "snapshots",
196
+ loading: E
197
+ })
198
+ ]
66
199
  })
67
200
  ]
68
201
  });
69
202
  }
70
203
  //#endregion
71
- export { g as default };
204
+ export { T as default };
72
205
 
73
206
  //# sourceMappingURL=AnalyticsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/AnalyticsPage.tsx"],"sourcesContent":["import { BarChart3 } from 'lucide-react';\nimport { Button, EmphasisPanel, GradientText, Input, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\n\nimport { useAnalyticsSnapshotsQuery, useUpsertAnalyticsSnapshotMutation } from '../../api/eventApi';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\nimport { EntityListSection } from './EntityListSection';\n\ntype SnapshotRow = {\n id: string;\n name?: string;\n title?: string;\n status?: string;\n};\n\nexport default function AnalyticsPage() {\n const snapshotsQuery = useAnalyticsSnapshotsQuery({ limit: 100, offset: 0 });\n const [upsertSnapshot, { loading: creating }] = useUpsertAnalyticsSnapshotMutation();\n const [snapshotName, setSnapshotName] = useState('overview');\n\n const snapshots = useMemo(\n () => (snapshotsQuery.data?.analyticsSnapshots.items ?? []) as SnapshotRow[],\n [snapshotsQuery.data]\n );\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 } catch (error) {\n notify.error('Could not generate snapshot', describeError(error));\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Snapshots capture a point-in-time picture of how your events are performing — registrations, attendance, revenue\n and engagement frozen at the moment you generate them. Use this page to take a daily or post-event snapshot so\n you can compare trends over time and report on outcomes.\n </PagePurpose>\n\n <EmphasisPanel>\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 Generate and track analytics snapshots from event operations data.\n </p>\n <div className=\"mt-4 flex flex-col gap-3 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=\"flex-1\"\n />\n <Button type=\"button\" disabled={creating} onClick={onCreate}>\n <BarChart3 className=\"h-4 w-4\" />\n {creating ? 'Generating...' : 'Generate Snapshot'}\n </Button>\n </div>\n </EmphasisPanel>\n\n <EntityListSection\n heading={snapshotsQuery.loading ? 'Loading snapshots...' : `Snapshots (${snapshots.length})`}\n emptyLabel=\"No analytics snapshots available yet.\"\n emptyIllustration=\"empty-data\"\n rows={snapshots.map((row) => ({\n id: row.id,\n title: row.title ?? row.name ?? `Snapshot ${row.id}`,\n status: row.status ?? undefined,\n }))}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAgBA,SAAwB,IAAgB;CACtC,IAAM,IAAiB,EAA2B;EAAE,OAAO;EAAK,QAAQ;EAAG,CAAC,EACtE,CAAC,GAAgB,EAAE,SAAS,OAAc,GAAoC,EAC9E,CAAC,GAAc,KAAmB,EAAS,WAAW,EAEtD,IAAY,QACT,EAAe,MAAM,mBAAmB,SAAS,EAAE,EAC1D,CAAC,EAAe,KAAK,CACtB;AAwBD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAA,UAAa,4RAIC,CAAA;GAEd,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,MAAD;KAAI,WAAU;eACZ,kBAAC,GAAD,EAAA,UAAc,aAAwB,CAAA;KACnC,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eAAmC;KAE5C,CAAA;IACJ,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,OAAO;MACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAM;MAChD,aAAY;MACZ,cAAW;MACX,WAAU;MACV,CAAA,EACF,kBAAC,GAAD;MAAQ,MAAK;MAAS,UAAU;MAAU,SA7CjC,YAAY;AACtB,aAAa,MAAM,CACxB,KAAI;QACF,IAAM,IAAW,MAAM,EAAe,EACpC,WAAW,EACT,OAAO;SACL,MAAM;SACN,OAAO;SACP,QAAQ;SACR,UAAU,EAAE;SACb,EACF,EACF,CAAC;AACF,YAAI,CAAC,EAAkB,GAAU,EAAS,MAAM,yBAAyB,IAAI,8BAA8B,CACzG;AAEF,UAAO,QAAQ,qBAAqB;gBAC7B,GAAO;AACd,UAAO,MAAM,+BAA+B,EAAc,EAAM,CAAC;;;gBA2B7D,CACE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAChC,IAAW,kBAAkB,oBACvB;QACL;;IACQ,EAAA,CAAA;GAEhB,kBAAC,GAAD;IACE,SAAS,EAAe,UAAU,yBAAyB,cAAc,EAAU,OAAO;IAC1F,YAAW;IACX,mBAAkB;IAClB,MAAM,EAAU,KAAK,OAAS;KAC5B,IAAI,EAAI;KACR,OAAO,EAAI,SAAS,EAAI,QAAQ,YAAY,EAAI;KAChD,QAAQ,EAAI,UAAU,KAAA;KACvB,EAAE;IACH,CAAA;GACE"}
1
+ {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/AnalyticsPage.tsx"],"sourcesContent":["import { BarChart3 } 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} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\n\nimport { useUpsertAnalyticsSnapshotMutation } from '../../api/eventApi';\nimport { Pagination, usePagination } from '../../components/Pagination';\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\nexport default function AnalyticsPage() {\n const pagination = usePagination({ pageSize: 10 });\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 [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 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 return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Snapshots capture a point-in-time picture of how your events are performing — total events, registrations,\n attendees, venues, sponsors and upcoming sessions frozen at the moment you generate them. Use this page to take a\n daily or post-event snapshot so you can compare trends over time and report on outcomes.\n </PagePurpose>\n\n <EmphasisPanel>\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 Generate and track analytics snapshots from live event operations data.\n </p>\n <div className=\"mt-4 flex flex-col gap-3 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=\"flex-1\"\n />\n <Button type=\"button\" disabled={creating} onClick={onCreate}>\n <BarChart3 className=\"h-4 w-4\" />\n {creating ? 'Generating...' : 'Generate Snapshot'}\n </Button>\n </div>\n </EmphasisPanel>\n\n <section className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? 'Loading snapshots...' : `Snapshots (${total})`}\n </h2>\n\n {snapshots.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No analytics snapshots available yet.\"\n description=\"Generate your first snapshot to freeze the current event metrics.\"\n className=\"py-8\"\n />\n </GlassCard>\n ) : (\n <div className=\"space-y-4\">\n {snapshots.map((snapshot) => {\n const generatedAt =\n formatGeneratedAt(snapshot.metrics?.generatedAt) ?? formatGeneratedAt(snapshot.createdAt);\n return (\n <GlassCard key={snapshot.id} treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">\n {snapshot.title ?? snapshot.name ?? `Snapshot ${snapshot.id}`}\n </h3>\n {generatedAt ? (\n <p className=\"mt-0.5 text-xs text-text-secondary\">Generated {generatedAt}</p>\n ) : 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\n {snapshot.metrics ? (\n <div className=\"mt-4 grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-7\">\n {METRIC_FIELDS.map((field) => {\n const raw = snapshot.metrics?.[field.key];\n const value = typeof raw === 'number' ? raw : 0;\n return (\n <GlassCard key={field.key} treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">{field.label}</p>\n <p className=\"mt-1 text-xl font-semibold tabular-nums text-text-primary\">\n {value.toLocaleString()}\n </p>\n </GlassCard>\n );\n })}\n </div>\n ) : (\n <p className=\"mt-3 text-sm text-text-secondary\">\n No metrics were captured for this snapshot.\n </p>\n )}\n </GlassCard>\n );\n })}\n </div>\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 </section>\n </div>\n );\n}\n"],"mappings":"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"}