@burdenoff/microfe-eventfullymanaged 2026.704.1 → 2026.704.3
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/EventfullyManagedRoutes.js +0 -4
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
- package/dist/eventfullymanaged/components/EntityTagsField.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/SessionFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/AnalyticsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js +2 -2
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/KioskPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.js +13 -12
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js +18 -11
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RoadmapPanel.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js +2 -2
- package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js +2 -1
- package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js.map +1 -1
- package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
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\n a 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\">No metrics were captured for this snapshot.</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":";;;;;;;;;;;AA2BA,IAAM,IAAyC,CAAG;;;;;;;;;;;;;;GAwC5C,IAAiE;CACrE;EAAE,KAAK;EAAe,OAAO;EAAU;CACvC;EAAE,KAAK;EAAgB,OAAO;EAAU;CACxC;EAAE,KAAK;EAAsB,OAAO;EAAiB;CACrD;EAAE,KAAK;EAAkB,OAAO;EAAa;CAC7C;EAAE,KAAK;EAAe,OAAO;EAAU;CACvC;EAAE,KAAK;EAAiB,OAAO;EAAY;CAC3C;EAAE,KAAK;EAAoB,OAAO;EAAqB;CACxD;AAED,SAAS,EAAc,GAA8D;CACnF,IAAM,IAAQ,EAAO,aAAa;AAIlC,QAHI;EAAC;EAAU;EAAa;EAAY,CAAC,SAAS,EAAM,GAAS,YAC7D;EAAC;EAAY;EAAa;EAAY;EAAU,CAAC,SAAS,EAAM,GAAS,YACzE;EAAC;EAAS;EAAW;EAAW;EAAY,CAAC,SAAS,EAAM,GAAS,SAClE;;AAGT,SAAS,EAAkB,GAAsC;AAC/D,KAAI,CAAC,EAAO,QAAO;CACnB,IAAM,IAAS,IAAI,KAAK,EAAM;AAC9B,QAAO,OAAO,MAAM,EAAO,SAAS,CAAC,GAAG,OAAO,EAAO,gBAAgB;;AAGxE,SAAwB,IAAgB;CACtC,IAAM,IAAa,EAAc,EAAE,UAAU,IAAI,CAAC,EAC5C,EAAE,SAAM,YAAS,eAAY,EACjC,GACA,EAAE,WAAW;EAAE,OAAO,EAAW;EAAO,QAAQ,EAAW;EAAQ,EAAE,CACtE,EACK,CAAC,GAAgB,EAAE,SAAS,OAAc,GAAoC,EAC9E,CAAC,GAAc,KAAmB,EAAS,WAAW,EAEtD,IAAY,QAAc,GAAM,mBAAmB,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EACvE,IAAQ,GAAM,mBAAmB,SAAS;AAyBhD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAA,UAAa,yTAIC,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,SA9CjC,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;AAGF,QADA,EAAO,QAAQ,qBAAqB,EACpC,MAAM,GAAS;gBACR,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,WAAD;IAAS,WAAU;cAAnB;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,IAAU,yBAAyB,cAAc,EAAM;MACrD,CAAA;KAEJ,EAAU,WAAW,IACpB,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBACrC,kBAAC,GAAD;OACE,cAAa;OACb,OAAM;OACN,aAAY;OACZ,WAAU;OACV,CAAA;MACQ,CAAA,GAEZ,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAU,KAAK,MAAa;OAC3B,IAAM,IACJ,EAAkB,EAAS,SAAS,YAAY,IAAI,EAAkB,EAAS,UAAU;AAC3F,cACE,kBAAC,GAAD;QAA6B,WAAU;QAAQ,WAAU;kBAAzD,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAS,SAAS,EAAS,QAAQ,YAAY,EAAS;WACtD,CAAA,EACJ,IACC,kBAAC,KAAD;WAAG,WAAU;qBAAb,CAAkD,cAAW,EAAgB;eAC3E,KACA;aACL,EAAS,SACR,kBAAC,GAAD;UAAO,SAAS,EAAc,EAAS,OAAO;UAAE,WAAU;oBACvD,EAAS;UACJ,CAAA,GACN,KACA;YAEL,EAAS,UACR,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAc,KAAK,MAAU;UAC5B,IAAM,IAAM,EAAS,UAAU,EAAM,MAC/B,IAAQ,OAAO,KAAQ,WAAW,IAAM;AAC9C,iBACE,kBAAC,GAAD;WAA2B,WAAU;WAAS,WAAU;qBAAxD,CACE,kBAAC,KAAD;YAAG,WAAU;sBAAuD,EAAM;YAAU,CAAA,EACpF,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAM,gBAAgB;YACrB,CAAA,CACM;aALI,EAAM,IAKV;WAEd;SACE,CAAA,GAEN,kBAAC,KAAD;SAAG,WAAU;mBAAmC;SAA+C,CAAA,CAEvF;UAnCI,EAAS,GAmCb;QAEd;MACE,CAAA;KAGR,kBAAC,GAAD;MACE,QAAQ,EAAW;MACnB,OAAO,EAAW;MAClB,aAAa,EAAU;MAChB;MACP,QAAQ,EAAW;MACnB,QAAQ,EAAW;MACnB,WAAU;MACD;MACT,CAAA;KACM;;GACN"}
|
|
@@ -10,11 +10,11 @@ import { ContextConversationWidget as _ } from "@burdenoff/fe-libs/shared";
|
|
|
10
10
|
import { useRegisterPageConversationContext as v } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
11
11
|
//#region src/eventfullymanaged/pages/modules/AttendeeProfilePage.tsx
|
|
12
12
|
function y({ attendeeId: e, attendeeName: t }) {
|
|
13
|
-
return v({
|
|
13
|
+
return v(i(() => ({
|
|
14
14
|
entityType: "EVENTFULLYMANAGED_ATTENDEE",
|
|
15
15
|
entityId: e,
|
|
16
16
|
contextTitle: `Attendee · ${t}`
|
|
17
|
-
}), /* @__PURE__ */ s(_, { widgetKey: "attendee-message" });
|
|
17
|
+
}), [e, t])), /* @__PURE__ */ s(_, { widgetKey: "attendee-message" });
|
|
18
18
|
}
|
|
19
19
|
function b() {
|
|
20
20
|
let { id: _ = "" } = o(), { navigate: v } = e(), b = t({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttendeeProfilePage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/AttendeeProfilePage.tsx"],"sourcesContent":["import { MessageSquare, Ticket, Users } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo, useRef } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport { useAttendeesQuery, useRegistrationsQuery } from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { EntityListSection } from './EntityListSection';\n\n/**\n * Direct message thread with a single attendee, backed by wspace-conversations-svc.\n * Kept as a dedicated component so `useRegisterPageConversationContext` scopes the\n * thread to this attendee (entityType + entityId) and unregisters on unmount.\n */\nfunction AttendeeConversation({ attendeeId, attendeeName }: { attendeeId: string; attendeeName: string }) {\n useRegisterPageConversationContext({\n entityType: 'EVENTFULLYMANAGED_ATTENDEE',\n entityId: attendeeId,\n contextTitle: `Attendee · ${attendeeName}`,\n });\n return <ContextConversationWidget widgetKey=\"attendee-message\" />;\n}\n\ntype AttendeeRow = {\n id: string;\n fullName?: string;\n email?: string;\n company?: string;\n role?: string;\n};\n\ntype RegistrationRow = {\n id: string;\n attendeeId?: string;\n status?: string;\n quantity?: number;\n currency?: string;\n totalAmount?: string;\n};\n\nexport default function AttendeeProfilePage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const attendeesQuery = useAttendeesQuery({ limit: 100, offset: 0 });\n const registrationsQuery = useRegistrationsQuery({ limit: 100, offset: 0 });\n const conversationRef = useRef<HTMLDivElement | null>(null);\n\n const attendee = useMemo(\n () => ((attendeesQuery.data?.attendees.items ?? []) as AttendeeRow[]).find((item) => item.id === id),\n [id, attendeesQuery.data]\n );\n const registrations = useMemo(\n () =>\n ((registrationsQuery.data?.registrations.items ?? []) as RegistrationRow[]).filter(\n (registration) => registration.attendeeId === id\n ),\n [id, registrationsQuery.data]\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n A single view of one attendee — who they are and everything they have registered for. Use this when you need to\n look someone up: verify their ticket, answer a support question, or understand their history with your events\n before reaching out.\n </PagePurpose>\n\n <EmphasisPanel>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Attendee</GradientText> Profile\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {attendeesQuery.loading\n ? 'Loading attendee profile...'\n : attendee\n ? `${attendee.fullName ?? attendee.id} profile from registration and attendee APIs.`\n : 'Attendee not found in current query scope.'}\n </p>\n {attendee ? (\n <div className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Name</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.fullName ?? attendee.id}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Email</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.email ?? 'Unavailable'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Company</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.company ?? 'Unavailable'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Role</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.role ?? 'Unavailable'}</p>\n </GlassCard>\n </div>\n ) : null}\n </EmphasisPanel>\n\n <EntityListSection\n heading={\n registrationsQuery.loading ? 'Loading attendee registrations...' : `Registrations (${registrations.length})`\n }\n emptyLabel=\"No registrations linked to this attendee.\"\n rows={registrations.map((registration) => ({\n id: registration.id,\n title: `Registration ${registration.id}`,\n subtitle: [\n registration.quantity ? `Qty ${registration.quantity}` : undefined,\n registration.totalAmount ? `${registration.totalAmount} ${registration.currency ?? ''}` : undefined,\n ]\n .filter(Boolean)\n .join(' • '),\n status: registration.status,\n }))}\n />\n\n {id ? (\n <div ref={conversationRef} className=\"space-y-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Messages</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Start a direct conversation with this attendee. Threads and attachments are handled by the shared\n conversations service.\n </p>\n </div>\n <AttendeeConversation attendeeId={id} attendeeName={attendee?.fullName ?? attendee?.id ?? id} />\n </div>\n ) : null}\n\n <NextSteps\n storageKey=\"eventfullymanaged-attendee-profile\"\n title=\"Act on this attendee\"\n steps={[\n {\n id: 'view-registrations',\n label: 'Review all registrations',\n description: 'See the full registration list to manage tickets and payments.',\n icon: <Ticket className=\"h-4 w-4\" />,\n onClick: () => navigate('/registration'),\n },\n {\n id: 'networking',\n label: 'Find networking matches',\n description: 'Connect this attendee with relevant people and meetings.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'reach-out',\n label: 'Send a message',\n description: 'Open a direct conversation thread with this attendee.',\n icon: <MessageSquare className=\"h-4 w-4\" />,\n onClick: () => conversationRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AttendeeProfilePage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/AttendeeProfilePage.tsx"],"sourcesContent":["import { MessageSquare, Ticket, Users } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo, useRef } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport { useAttendeesQuery, useRegistrationsQuery } from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { EntityListSection } from './EntityListSection';\n\n/**\n * Direct message thread with a single attendee, backed by wspace-conversations-svc.\n * Kept as a dedicated component so `useRegisterPageConversationContext` scopes the\n * thread to this attendee (entityType + entityId) and unregisters on unmount.\n */\nfunction AttendeeConversation({ attendeeId, attendeeName }: { attendeeId: string; attendeeName: string }) {\n // Stable reference: `useRegisterPageConversationContext` keys its effect on this\n // object's identity, so a fresh inline object every render would loop the shared\n // PageConversationContextProvider (see NetworkingPage for the full rationale).\n const conversationContext = useMemo(\n () => ({\n entityType: 'EVENTFULLYMANAGED_ATTENDEE',\n entityId: attendeeId,\n contextTitle: `Attendee · ${attendeeName}`,\n }),\n [attendeeId, attendeeName]\n );\n useRegisterPageConversationContext(conversationContext);\n return <ContextConversationWidget widgetKey=\"attendee-message\" />;\n}\n\ntype AttendeeRow = {\n id: string;\n fullName?: string;\n email?: string;\n company?: string;\n role?: string;\n};\n\ntype RegistrationRow = {\n id: string;\n attendeeId?: string;\n status?: string;\n quantity?: number;\n currency?: string;\n totalAmount?: string;\n};\n\nexport default function AttendeeProfilePage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const attendeesQuery = useAttendeesQuery({ limit: 100, offset: 0 });\n const registrationsQuery = useRegistrationsQuery({ limit: 100, offset: 0 });\n const conversationRef = useRef<HTMLDivElement | null>(null);\n\n const attendee = useMemo(\n () => ((attendeesQuery.data?.attendees.items ?? []) as AttendeeRow[]).find((item) => item.id === id),\n [id, attendeesQuery.data]\n );\n const registrations = useMemo(\n () =>\n ((registrationsQuery.data?.registrations.items ?? []) as RegistrationRow[]).filter(\n (registration) => registration.attendeeId === id\n ),\n [id, registrationsQuery.data]\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n A single view of one attendee — who they are and everything they have registered for. Use this when you need to\n look someone up: verify their ticket, answer a support question, or understand their history with your events\n before reaching out.\n </PagePurpose>\n\n <EmphasisPanel>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Attendee</GradientText> Profile\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {attendeesQuery.loading\n ? 'Loading attendee profile...'\n : attendee\n ? `${attendee.fullName ?? attendee.id} profile from registration and attendee APIs.`\n : 'Attendee not found in current query scope.'}\n </p>\n {attendee ? (\n <div className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Name</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.fullName ?? attendee.id}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Email</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.email ?? 'Unavailable'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Company</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.company ?? 'Unavailable'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Role</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.role ?? 'Unavailable'}</p>\n </GlassCard>\n </div>\n ) : null}\n </EmphasisPanel>\n\n <EntityListSection\n heading={\n registrationsQuery.loading ? 'Loading attendee registrations...' : `Registrations (${registrations.length})`\n }\n emptyLabel=\"No registrations linked to this attendee.\"\n rows={registrations.map((registration) => ({\n id: registration.id,\n title: `Registration ${registration.id}`,\n subtitle: [\n registration.quantity ? `Qty ${registration.quantity}` : undefined,\n registration.totalAmount ? `${registration.totalAmount} ${registration.currency ?? ''}` : undefined,\n ]\n .filter(Boolean)\n .join(' • '),\n status: registration.status,\n }))}\n />\n\n {id ? (\n <div ref={conversationRef} className=\"space-y-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Messages</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Start a direct conversation with this attendee. Threads and attachments are handled by the shared\n conversations service.\n </p>\n </div>\n <AttendeeConversation attendeeId={id} attendeeName={attendee?.fullName ?? attendee?.id ?? id} />\n </div>\n ) : null}\n\n <NextSteps\n storageKey=\"eventfullymanaged-attendee-profile\"\n title=\"Act on this attendee\"\n steps={[\n {\n id: 'view-registrations',\n label: 'Review all registrations',\n description: 'See the full registration list to manage tickets and payments.',\n icon: <Ticket className=\"h-4 w-4\" />,\n onClick: () => navigate('/registration'),\n },\n {\n id: 'networking',\n label: 'Find networking matches',\n description: 'Connect this attendee with relevant people and meetings.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'reach-out',\n label: 'Send a message',\n description: 'Open a direct conversation thread with this attendee.',\n icon: <MessageSquare className=\"h-4 w-4\" />,\n onClick: () => conversationRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KioskPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/KioskPage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n Label,\n PagePurpose,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n} from '@burdenoff/fe-libs/ui';\nimport { LogIn, MonitorSmartphone, Plus, ScanLine } from 'lucide-react';\nimport { useMemo, useState } from 'react';\n\nimport {\n useAttendeesQuery,\n useCheckInRecordsQuery,\n useCheckInStationsQuery,\n useCreateCheckInRecordMutation,\n useCreateCheckInStationMutation,\n useDeleteCheckInRecordMutation,\n useDeleteCheckInStationMutation,\n useEventSessionsQuery,\n useEventsQuery,\n useUpdateCheckInRecordMutation,\n useUpdateCheckInStationMutation,\n} from '../../api/eventApi';\nimport { ActionMenu, Pagination, usePagination } from '../../components';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\ntype StationRow = {\n id: string;\n eventId?: string | null;\n name?: string | null;\n metadata?: Record<string, unknown> | null;\n};\n\ntype RecordRow = {\n id: string;\n eventId: string;\n stationId?: string | null;\n attendeeId?: string | null;\n registrationId?: string | null;\n method: string;\n status: string;\n direction: string;\n recordedAt: string;\n metadata?: Record<string, unknown> | null;\n};\n\nconst STATION_TYPES = ['DESK', 'GATE', 'SELF_SERVICE', 'MOBILE'] as const;\nconst CHECKIN_METHODS = ['QR', 'MANUAL'] as const;\nconst CHECKIN_DIRECTIONS = ['IN', 'OUT'] as const;\n\n/**\n * The stations list resolver projects a CheckInStation onto the generic\n * SimpleEntity shape (id/name/metadata) — it does NOT expose the `code`,\n * `location` or `isActive` columns. To keep create → list → edit lossless we\n * mirror those attributes into `metadata` on every write, so an edit opened from\n * a list row can rebuild the full required input without a by-id resolver.\n */\ntype StationMeta = {\n code?: string;\n location?: string;\n type?: string;\n isActive?: boolean;\n};\n\nfunction readStationMeta(metadata: Record<string, unknown> | null | undefined): StationMeta {\n if (!metadata || typeof metadata !== 'object') {\n return {};\n }\n const m = metadata as Record<string, unknown>;\n return {\n code: typeof m.code === 'string' ? m.code : undefined,\n location: typeof m.location === 'string' ? m.location : undefined,\n type: typeof m.type === 'string' ? m.type : undefined,\n isActive: typeof m.isActive === 'boolean' ? m.isActive : undefined,\n };\n}\n\nfunction slugCode(name: string): string {\n return (\n name\n .trim()\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 24) || 'STATION'\n );\n}\n\n// ===========================================================================\n// Station create / edit dialog\n// ===========================================================================\n\ntype StationFormState = {\n name: string;\n code: string;\n location: string;\n type: string;\n isActive: boolean;\n};\n\ninterface StationDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n eventId: string;\n eventName: string;\n /** Present when editing an existing station. */\n station?: StationRow;\n}\n\nfunction StationDialog({ open, onOpenChange, eventId, eventName, station }: StationDialogProps) {\n const isEdit = Boolean(station);\n const [createStation, { loading: creating }] = useCreateCheckInStationMutation();\n const [updateStation, { loading: updating }] = useUpdateCheckInStationMutation();\n\n const initial = useMemo<StationFormState>(() => {\n if (!station) {\n return { name: '', code: '', location: '', type: 'DESK', isActive: true };\n }\n const meta = readStationMeta(station.metadata);\n return {\n name: station.name ?? '',\n code: meta.code ?? slugCode(station.name ?? ''),\n location: meta.location ?? '',\n type: meta.type ?? 'DESK',\n isActive: meta.isActive ?? true,\n };\n }, [station]);\n\n const [form, setForm] = useState<StationFormState>(initial);\n // Reset the form to the active station whenever the dialog is (re)opened.\n const [seededFor, setSeededFor] = useState<string | null>(null);\n const seedKey = `${open ? 'open' : 'closed'}:${station?.id ?? 'new'}`;\n if (open && seededFor !== seedKey) {\n setSeededFor(seedKey);\n setForm(initial);\n }\n if (!open && seededFor !== null) {\n setSeededFor(null);\n }\n\n const onChange = <K extends keyof StationFormState>(field: K, value: StationFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const code = form.code.trim() || slugCode(form.name);\n const input = {\n eventId,\n name: form.name.trim(),\n code,\n location: form.location.trim() || null,\n isActive: form.isActive,\n kioskConfig: { type: form.type },\n // Mirror the write-only columns so the SimpleEntity list can rebuild them.\n metadata: { code, location: form.location.trim(), type: form.type, isActive: form.isActive },\n };\n\n try {\n if (isEdit && station) {\n const res = await updateStation({ variables: { id: station.id, input } });\n if (!mutationSucceeded(res, res.data?.updateCheckInStation?.id, 'Could not update station')) {\n return;\n }\n notify.success('Station updated', `“${input.name}” is now ${form.isActive ? 'active' : 'disabled'}.`);\n } else {\n const res = await createStation({ variables: { input } });\n if (!mutationSucceeded(res, res.data?.createCheckInStation?.id, 'Could not create station')) {\n return;\n }\n notify.success('Station created', `“${input.name}” is ready at ${eventName}.`);\n }\n onOpenChange(false);\n } catch (error) {\n notify.error('Could not save station', describeError(error));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{isEdit ? 'Edit check-in station' : 'New check-in station'}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n A station is a desk, gate or device where attendees scan in at {eventName}.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form id=\"station-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\n value={form.name}\n onChange={(e) => onChange('name', e.target.value)}\n placeholder=\"Main entrance\"\n required\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Code</span>\n <Input\n value={form.code}\n onChange={(e) => onChange('code', e.target.value)}\n placeholder=\"Auto from name\"\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\">Location</span>\n <Input\n value={form.location}\n onChange={(e) => onChange('location', e.target.value)}\n placeholder=\"North lobby\"\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Type</span>\n <Select value={form.type} onValueChange={(value) => onChange('type', value)}>\n <SelectTrigger aria-label=\"Station type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STATION_TYPES.map((type) => (\n <SelectItem key={type} value={type}>\n {type.replace('_', ' ')}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <div className=\"flex items-center justify-between rounded-card border border-border-seam bg-bg-surface p-3\">\n <div>\n <Label htmlFor=\"station-active\" className=\"text-text-primary\">\n Active\n </Label>\n <p className=\"text-xs text-text-secondary\">Disabled stations stay configured but cannot accept scans.</p>\n </div>\n <Switch\n id=\"station-active\"\n checked={form.isActive}\n onCheckedChange={(checked) => onChange('isActive', checked)}\n aria-label=\"Station active\"\n />\n </div>\n </form>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)} disabled={saving}>\n Cancel\n </Button>\n <Button type=\"submit\" form=\"station-form\" disabled={saving}>\n {saving ? 'Saving…' : isEdit ? 'Save station' : 'Create station'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ===========================================================================\n// Check-in record edit dialog\n// ===========================================================================\n\ntype RecordFormState = {\n method: string;\n direction: string;\n status: string;\n stationId: string;\n attendeeId: string;\n};\n\ninterface RecordDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n record: RecordRow | null;\n stations: StationRow[];\n}\n\nfunction RecordDialog({ open, onOpenChange, record, stations }: RecordDialogProps) {\n const [updateRecord, { loading }] = useUpdateCheckInRecordMutation();\n\n const initial = useMemo<RecordFormState>(\n () => ({\n method: record?.method ?? 'MANUAL',\n direction: record?.direction ?? 'IN',\n status: record?.status ?? 'checked_in',\n stationId: record?.stationId ?? '',\n attendeeId: record?.attendeeId ?? '',\n }),\n [record]\n );\n const [form, setForm] = useState<RecordFormState>(initial);\n const [seededFor, setSeededFor] = useState<string | null>(null);\n const seedKey = `${open ? 'open' : 'closed'}:${record?.id ?? 'none'}`;\n if (open && seededFor !== seedKey) {\n setSeededFor(seedKey);\n setForm(initial);\n }\n if (!open && seededFor !== null) {\n setSeededFor(null);\n }\n\n const onChange = <K extends keyof RecordFormState>(field: K, value: RecordFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!record) {\n return;\n }\n const input = {\n eventId: record.eventId,\n attendeeId: form.attendeeId.trim() || null,\n registrationId: record.registrationId ?? null,\n stationId: form.stationId || null,\n method: form.method,\n direction: form.direction,\n status: form.status.trim() || 'checked_in',\n metadata: record.metadata ?? {},\n };\n try {\n const res = await updateRecord({ variables: { id: record.id, input } });\n if (!mutationSucceeded(res, res.data?.updateCheckInRecord?.id, 'Could not update record')) {\n return;\n }\n notify.success('Check-in updated');\n onOpenChange(false);\n } catch (error) {\n notify.error('Could not update record', describeError(error));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Edit check-in record</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>Correct the direction, method or station for this scan.</ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form id=\"record-form\" onSubmit={onSubmit} className=\"space-y-4\">\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Attendee ID</span>\n <Input value={form.attendeeId} onChange={(e) => onChange('attendeeId', e.target.value)} />\n </label>\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\">Method</span>\n <Select value={form.method} onValueChange={(value) => onChange('method', value)}>\n <SelectTrigger aria-label=\"Method\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CHECKIN_METHODS.map((method) => (\n <SelectItem key={method} value={method}>\n {method}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Direction</span>\n <Select value={form.direction} onValueChange={(value) => onChange('direction', value)}>\n <SelectTrigger aria-label=\"Direction\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CHECKIN_DIRECTIONS.map((direction) => (\n <SelectItem key={direction} value={direction}>\n {direction}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\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\">Status</span>\n <Input value={form.status} onChange={(e) => onChange('status', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Station</span>\n <Select\n value={form.stationId || 'none'}\n onValueChange={(value) => onChange('stationId', value === 'none' ? '' : value)}\n >\n <SelectTrigger aria-label=\"Station\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"none\">No station</SelectItem>\n {stations.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name ?? s.id}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n </form>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)} disabled={loading}>\n Cancel\n </Button>\n <Button type=\"submit\" form=\"record-form\" disabled={loading}>\n {loading ? 'Saving…' : 'Save record'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ===========================================================================\n// Check-in action panel (the hero workflow)\n// ===========================================================================\n\ninterface CheckInPanelProps {\n eventId: string;\n stations: StationRow[];\n onCheckedIn: () => void;\n}\n\nfunction CheckInPanel({ eventId, stations, onCheckedIn }: CheckInPanelProps) {\n const [scanValue, setScanValue] = useState('');\n const [method, setMethod] = useState<string>('QR');\n const [direction, setDirection] = useState<string>('IN');\n const [stationId, setStationId] = useState('');\n const [sessionId, setSessionId] = useState('');\n\n const [createRecord, { loading }] = useCreateCheckInRecordMutation();\n const attendeesQuery = useAttendeesQuery({ eventId, limit: 100, offset: 0 });\n const sessionsQuery = useEventSessionsQuery({ eventId, limit: 100, offset: 0 });\n\n const attendees = attendeesQuery.data?.attendees.items ?? [];\n const sessions = sessionsQuery.data?.eventSessions.items ?? [];\n\n const resolveAttendee = (raw: string): { attendeeId: string | null; label: string } => {\n const value = raw.trim();\n if (!value) {\n return { attendeeId: null, label: 'attendee' };\n }\n if (value.includes('@')) {\n const match = attendees.find((a) => a.email?.toLowerCase() === value.toLowerCase());\n return { attendeeId: match?.id ?? null, label: match?.fullName ?? value };\n }\n const byId = attendees.find((a) => a.id === value);\n return { attendeeId: value, label: byId?.fullName ?? `attendee ${value}` };\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const { attendeeId, label } = resolveAttendee(scanValue);\n if (scanValue.trim().includes('@') && !attendeeId) {\n notify.error('Attendee not found', `No attendee at this event matches “${scanValue.trim()}”.`);\n return;\n }\n const input = {\n eventId,\n attendeeId,\n stationId: stationId || null,\n method,\n direction,\n status: 'checked_in',\n recordedAt: new Date().toISOString(),\n metadata: sessionId ? { sessionId } : {},\n };\n try {\n const res = await createRecord({ variables: { input } });\n if (!mutationSucceeded(res, res.data?.createCheckInRecord?.id, 'Could not record check-in')) {\n return;\n }\n notify.success(\n `${label} checked ${direction === 'OUT' ? 'out' : 'in'}`,\n `Recorded via ${method}${stationId ? '' : ' • no station'}.`\n );\n setScanValue('');\n onCheckedIn();\n } catch (error) {\n notify.error('Could not record check-in', describeError(error));\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center gap-2\">\n <ScanLine className=\"h-5 w-5 text-text-secondary\" aria-hidden=\"true\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">Check in an attendee</h2>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Scan a QR badge or type an attendee ID or email, then record the arrival.\n </p>\n\n <form onSubmit={onSubmit} className=\"mt-4 space-y-4\">\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Attendee ID / email / QR</span>\n <Input\n value={scanValue}\n onChange={(e) => setScanValue(e.target.value)}\n placeholder=\"Scan badge or paste attendee ID / email\"\n autoComplete=\"off\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Method</span>\n <Select value={method} onValueChange={setMethod}>\n <SelectTrigger aria-label=\"Check-in method\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CHECKIN_METHODS.map((m) => (\n <SelectItem key={m} value={m}>\n {m}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Direction</span>\n <Select value={direction} onValueChange={setDirection}>\n <SelectTrigger aria-label=\"Direction\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CHECKIN_DIRECTIONS.map((d) => (\n <SelectItem key={d} value={d}>\n {d}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Station</span>\n <Select value={stationId || 'none'} onValueChange={(value) => setStationId(value === 'none' ? '' : value)}>\n <SelectTrigger aria-label=\"Station\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"none\">No station</SelectItem>\n {stations.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name ?? s.id}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Session</span>\n <Select value={sessionId || 'none'} onValueChange={(value) => setSessionId(value === 'none' ? '' : value)}>\n <SelectTrigger aria-label=\"Session\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"none\">Whole event</SelectItem>\n {sessions.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <div className=\"flex justify-end\">\n <Button type=\"submit\" disabled={loading || !scanValue.trim()}>\n <LogIn className=\"h-4 w-4\" />\n {loading ? 'Recording…' : 'Record check-in'}\n </Button>\n </div>\n </form>\n </GlassCard>\n );\n}\n\n// ===========================================================================\n// Page\n// ===========================================================================\n\nconst PAGE_SIZE = 10;\n\nexport default function KioskPage() {\n const eventsQuery = useEventsQuery({ limit: 100, offset: 0 });\n const events = eventsQuery.data?.events.items ?? [];\n const [selectedEventId, setSelectedEventId] = useState('');\n const scopeEventId = selectedEventId || undefined;\n const selectedEvent = events.find((e) => e.id === selectedEventId);\n\n const stationsPagination = usePagination({ pageSize: PAGE_SIZE, resetKey: selectedEventId });\n const recordsPagination = usePagination({ pageSize: PAGE_SIZE, resetKey: selectedEventId });\n\n const stationsQuery = useCheckInStationsQuery({\n eventId: scopeEventId,\n limit: stationsPagination.limit,\n offset: stationsPagination.offset,\n });\n const recordsQuery = useCheckInRecordsQuery({\n eventId: scopeEventId,\n limit: recordsPagination.limit,\n offset: recordsPagination.offset,\n });\n\n const stations = (stationsQuery.data?.checkInStations.items ?? []) as StationRow[];\n const stationsTotal = stationsQuery.data?.checkInStations.total ?? 0;\n const records = (recordsQuery.data?.checkInRecords.items ?? []) as RecordRow[];\n const recordsTotal = recordsQuery.data?.checkInRecords.total ?? 0;\n\n const [deleteStation] = useDeleteCheckInStationMutation();\n const [deleteRecord] = useDeleteCheckInRecordMutation();\n\n const [stationDialogOpen, setStationDialogOpen] = useState(false);\n const [editingStation, setEditingStation] = useState<StationRow | undefined>(undefined);\n const [recordDialogOpen, setRecordDialogOpen] = useState(false);\n const [editingRecord, setEditingRecord] = useState<RecordRow | null>(null);\n\n const openNewStation = () => {\n setEditingStation(undefined);\n setStationDialogOpen(true);\n };\n const openEditStation = (station: StationRow) => {\n setEditingStation(station);\n setStationDialogOpen(true);\n };\n const openEditRecord = (record: RecordRow) => {\n setEditingRecord(record);\n setRecordDialogOpen(true);\n };\n\n const onDeleteStation = async (station: StationRow) => {\n const res = await deleteStation({ variables: { id: station.id } });\n if (!mutationSucceeded(res, res.data?.deleteCheckInStation ? station.id : null, 'Could not delete station')) {\n return;\n }\n notify.success('Station deleted', `“${station.name ?? station.id}” was removed.`);\n };\n\n const onDeleteRecord = async (record: RecordRow) => {\n const res = await deleteRecord({ variables: { id: record.id } });\n if (!mutationSucceeded(res, res.data?.deleteCheckInRecord ? record.id : null, 'Could not delete record')) {\n return;\n }\n notify.success('Check-in record deleted');\n };\n\n const hasEvent = Boolean(selectedEventId);\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Kiosks are the front desk of your event. Each check-in station is a device or counter where attendees scan in,\n and check-in records are the live log of who arrived and when. Configure your stations before doors open, then\n check attendees in from this page and watch arrivals flow in on the day.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Kiosk</GradientText> & Check-In\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Manage check-in stations and record attendee arrivals against a live backend.\n </p>\n </div>\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={selectedEventId || 'all'} onValueChange={(v) => setSelectedEventId(v === 'all' ? '' : v)}>\n <SelectTrigger aria-label=\"Event\" className=\"min-w-52\">\n <SelectValue placeholder=\"All events\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All events</SelectItem>\n {events.map((event) => (\n <SelectItem key={event.id} value={event.id}>\n {event.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <Button type=\"button\" onClick={openNewStation} disabled={!hasEvent}>\n <Plus className=\"h-4 w-4\" />\n New station\n </Button>\n </div>\n </div>\n </EmphasisPanel>\n\n {hasEvent ? (\n <CheckInPanel\n eventId={selectedEventId}\n stations={stations}\n onCheckedIn={() => {\n recordsPagination.reset();\n void recordsQuery.refetch();\n }}\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"Select an event to check attendees in\"\n description=\"Check-in stations and records belong to a specific event. Pick one above to configure stations and record arrivals.\"\n className=\"py-8\"\n />\n </GlassCard>\n )}\n\n {/* Stations */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center gap-2\">\n <MonitorSmartphone className=\"h-5 w-5 text-text-secondary\" aria-hidden=\"true\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {stationsQuery.loading ? 'Loading stations…' : `Stations (${stationsTotal})`}\n </h2>\n </div>\n <div className=\"mt-4\">\n {stations.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No check-in stations yet\"\n description={hasEvent ? 'Create a station to start scanning attendees in.' : 'No stations configured.'}\n className=\"py-8\"\n />\n ) : (\n <ul className=\"space-y-2.5\">\n {stations.map((station) => {\n const meta = readStationMeta(station.metadata);\n const active = meta.isActive ?? true;\n const subtitle = [meta.type?.replace('_', ' '), meta.location, meta.code]\n .filter(Boolean)\n .join(' • ');\n return (\n <li\n key={station.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{station.name ?? station.id}</p>\n {subtitle ? <p className=\"mt-0.5 truncate text-sm text-text-secondary\">{subtitle}</p> : null}\n </div>\n <div className=\"flex items-center gap-2\">\n <Badge variant={active ? 'success' : 'warning'} className=\"shrink-0 uppercase\">\n {active ? 'active' : 'disabled'}\n </Badge>\n <ActionMenu\n ariaLabel={`Station ${station.name ?? station.id} actions`}\n entityName={station.name ?? undefined}\n onEdit={() => openEditStation(station)}\n onDelete={() => onDeleteStation(station)}\n />\n </div>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n <Pagination\n offset={stationsPagination.offset}\n limit={stationsPagination.limit}\n loadedCount={stations.length}\n total={stationsTotal}\n onPrev={stationsPagination.prevPage}\n onNext={stationsPagination.nextPage}\n itemLabel=\"stations\"\n loading={stationsQuery.loading}\n />\n </GlassCard>\n\n {/* Records */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {recordsQuery.loading ? 'Loading check-in records…' : `Check-In Records (${recordsTotal})`}\n </h2>\n <div className=\"mt-4\">\n {records.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No check-in records yet\"\n description=\"Arrivals recorded from this page show up here, newest first.\"\n className=\"py-8\"\n />\n ) : (\n <ul className=\"space-y-2.5\">\n {records.map((record) => (\n <li\n key={record.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">\n {record.attendeeId ? `Attendee ${record.attendeeId}` : `Record ${record.id}`}\n </p>\n <p className=\"mt-0.5 truncate text-sm text-text-secondary\">\n {record.method} • {record.direction} • {new Date(record.recordedAt).toLocaleString()}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\" className=\"shrink-0 uppercase\">\n {record.status}\n </Badge>\n <ActionMenu\n ariaLabel={`Check-in record ${record.id} actions`}\n onEdit={() => openEditRecord(record)}\n onDelete={() => onDeleteRecord(record)}\n />\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n <Pagination\n offset={recordsPagination.offset}\n limit={recordsPagination.limit}\n loadedCount={records.length}\n total={recordsTotal}\n onPrev={recordsPagination.prevPage}\n onNext={recordsPagination.nextPage}\n itemLabel=\"records\"\n loading={recordsQuery.loading}\n />\n </GlassCard>\n\n {selectedEventId ? (\n <StationDialog\n open={stationDialogOpen}\n onOpenChange={setStationDialogOpen}\n eventId={selectedEventId}\n eventName={selectedEvent?.name ?? 'this event'}\n station={editingStation}\n />\n ) : null}\n\n <RecordDialog\n open={recordDialogOpen}\n onOpenChange={setRecordDialogOpen}\n record={editingRecord}\n stations={stations}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"KioskPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/KioskPage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n Label,\n PagePurpose,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n} from '@burdenoff/fe-libs/ui';\nimport { LogIn, MonitorSmartphone, Plus, ScanLine } from 'lucide-react';\nimport { useMemo, useState } from 'react';\n\nimport {\n useAttendeesQuery,\n useCheckInRecordsQuery,\n useCheckInStationsQuery,\n useCreateCheckInRecordMutation,\n useCreateCheckInStationMutation,\n useDeleteCheckInRecordMutation,\n useDeleteCheckInStationMutation,\n useEventSessionsQuery,\n useEventsQuery,\n useUpdateCheckInRecordMutation,\n useUpdateCheckInStationMutation,\n} from '../../api/eventApi';\nimport { ActionMenu, Pagination, usePagination } from '../../components';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\ntype StationRow = {\n id: string;\n eventId?: string | null;\n name?: string | null;\n metadata?: Record<string, unknown> | null;\n};\n\ntype RecordRow = {\n id: string;\n eventId: string;\n stationId?: string | null;\n attendeeId?: string | null;\n registrationId?: string | null;\n method: string;\n status: string;\n direction: string;\n recordedAt: string;\n metadata?: Record<string, unknown> | null;\n};\n\nconst STATION_TYPES = ['DESK', 'GATE', 'SELF_SERVICE', 'MOBILE'] as const;\nconst CHECKIN_METHODS = ['QR', 'MANUAL'] as const;\nconst CHECKIN_DIRECTIONS = ['IN', 'OUT'] as const;\n\n/**\n * The stations list resolver projects a CheckInStation onto the generic\n * SimpleEntity shape (id/name/metadata) — it does NOT expose the `code`,\n * `location` or `isActive` columns. To keep create → list → edit lossless we\n * mirror those attributes into `metadata` on every write, so an edit opened from\n * a list row can rebuild the full required input without a by-id resolver.\n */\ntype StationMeta = {\n code?: string;\n location?: string;\n type?: string;\n isActive?: boolean;\n};\n\nfunction readStationMeta(metadata: Record<string, unknown> | null | undefined): StationMeta {\n if (!metadata || typeof metadata !== 'object') {\n return {};\n }\n const m = metadata as Record<string, unknown>;\n return {\n code: typeof m.code === 'string' ? m.code : undefined,\n location: typeof m.location === 'string' ? m.location : undefined,\n type: typeof m.type === 'string' ? m.type : undefined,\n isActive: typeof m.isActive === 'boolean' ? m.isActive : undefined,\n };\n}\n\nfunction slugCode(name: string): string {\n return (\n name\n .trim()\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 24) || 'STATION'\n );\n}\n\n// ===========================================================================\n// Station create / edit dialog\n// ===========================================================================\n\ntype StationFormState = {\n name: string;\n code: string;\n location: string;\n type: string;\n isActive: boolean;\n};\n\ninterface StationDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n eventId: string;\n eventName: string;\n /** Present when editing an existing station. */\n station?: StationRow;\n}\n\nfunction StationDialog({ open, onOpenChange, eventId, eventName, station }: StationDialogProps) {\n const isEdit = Boolean(station);\n const [createStation, { loading: creating }] = useCreateCheckInStationMutation();\n const [updateStation, { loading: updating }] = useUpdateCheckInStationMutation();\n\n const initial = useMemo<StationFormState>(() => {\n if (!station) {\n return { name: '', code: '', location: '', type: 'DESK', isActive: true };\n }\n const meta = readStationMeta(station.metadata);\n return {\n name: station.name ?? '',\n code: meta.code ?? slugCode(station.name ?? ''),\n location: meta.location ?? '',\n type: meta.type ?? 'DESK',\n isActive: meta.isActive ?? true,\n };\n }, [station]);\n\n const [form, setForm] = useState<StationFormState>(initial);\n // Reset the form to the active station whenever the dialog is (re)opened.\n const [seededFor, setSeededFor] = useState<string | null>(null);\n const seedKey = `${open ? 'open' : 'closed'}:${station?.id ?? 'new'}`;\n if (open && seededFor !== seedKey) {\n setSeededFor(seedKey);\n setForm(initial);\n }\n if (!open && seededFor !== null) {\n setSeededFor(null);\n }\n\n const onChange = <K extends keyof StationFormState>(field: K, value: StationFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const code = form.code.trim() || slugCode(form.name);\n const input = {\n eventId,\n name: form.name.trim(),\n code,\n location: form.location.trim() || null,\n isActive: form.isActive,\n kioskConfig: { type: form.type },\n // Mirror the write-only columns so the SimpleEntity list can rebuild them.\n metadata: { code, location: form.location.trim(), type: form.type, isActive: form.isActive },\n };\n\n try {\n if (isEdit && station) {\n const res = await updateStation({ variables: { id: station.id, input } });\n if (!mutationSucceeded(res, res.data?.updateCheckInStation?.id, 'Could not update station')) {\n return;\n }\n notify.success('Station updated', `“${input.name}” is now ${form.isActive ? 'active' : 'disabled'}.`);\n } else {\n const res = await createStation({ variables: { input } });\n if (!mutationSucceeded(res, res.data?.createCheckInStation?.id, 'Could not create station')) {\n return;\n }\n notify.success('Station created', `“${input.name}” is ready at ${eventName}.`);\n }\n onOpenChange(false);\n } catch (error) {\n notify.error('Could not save station', describeError(error));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{isEdit ? 'Edit check-in station' : 'New check-in station'}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n A station is a desk, gate or device where attendees scan in at {eventName}.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form id=\"station-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\n value={form.name}\n onChange={(e) => onChange('name', e.target.value)}\n placeholder=\"Main entrance\"\n required\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Code</span>\n <Input\n value={form.code}\n onChange={(e) => onChange('code', e.target.value)}\n placeholder=\"Auto from name\"\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\">Location</span>\n <Input\n value={form.location}\n onChange={(e) => onChange('location', e.target.value)}\n placeholder=\"North lobby\"\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Type</span>\n <Select value={form.type} onValueChange={(value) => onChange('type', value)}>\n <SelectTrigger aria-label=\"Station type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STATION_TYPES.map((type) => (\n <SelectItem key={type} value={type}>\n {type.replace('_', ' ')}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <div className=\"flex items-center justify-between rounded-card border border-border-seam bg-bg-surface p-3\">\n <div>\n <Label htmlFor=\"station-active\" className=\"text-text-primary\">\n Active\n </Label>\n <p className=\"text-xs text-text-secondary\">Disabled stations stay configured but cannot accept scans.</p>\n </div>\n <Switch\n id=\"station-active\"\n checked={form.isActive}\n onCheckedChange={(checked) => onChange('isActive', checked)}\n aria-label=\"Station active\"\n />\n </div>\n </form>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)} disabled={saving}>\n Cancel\n </Button>\n <Button type=\"submit\" form=\"station-form\" disabled={saving}>\n {saving ? 'Saving…' : isEdit ? 'Save station' : 'Create station'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ===========================================================================\n// Check-in record edit dialog\n// ===========================================================================\n\ntype RecordFormState = {\n method: string;\n direction: string;\n status: string;\n stationId: string;\n attendeeId: string;\n};\n\ninterface RecordDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n record: RecordRow | null;\n stations: StationRow[];\n}\n\nfunction RecordDialog({ open, onOpenChange, record, stations }: RecordDialogProps) {\n const [updateRecord, { loading }] = useUpdateCheckInRecordMutation();\n\n const initial = useMemo<RecordFormState>(\n () => ({\n method: record?.method ?? 'MANUAL',\n direction: record?.direction ?? 'IN',\n status: record?.status ?? 'checked_in',\n stationId: record?.stationId ?? '',\n attendeeId: record?.attendeeId ?? '',\n }),\n [record]\n );\n const [form, setForm] = useState<RecordFormState>(initial);\n const [seededFor, setSeededFor] = useState<string | null>(null);\n const seedKey = `${open ? 'open' : 'closed'}:${record?.id ?? 'none'}`;\n if (open && seededFor !== seedKey) {\n setSeededFor(seedKey);\n setForm(initial);\n }\n if (!open && seededFor !== null) {\n setSeededFor(null);\n }\n\n const onChange = <K extends keyof RecordFormState>(field: K, value: RecordFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!record) {\n return;\n }\n const input = {\n eventId: record.eventId,\n attendeeId: form.attendeeId.trim() || null,\n registrationId: record.registrationId ?? null,\n stationId: form.stationId || null,\n method: form.method,\n direction: form.direction,\n status: form.status.trim() || 'checked_in',\n metadata: record.metadata ?? {},\n };\n try {\n const res = await updateRecord({ variables: { id: record.id, input } });\n if (!mutationSucceeded(res, res.data?.updateCheckInRecord?.id, 'Could not update record')) {\n return;\n }\n notify.success('Check-in updated');\n onOpenChange(false);\n } catch (error) {\n notify.error('Could not update record', describeError(error));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Edit check-in record</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Correct the direction, method or station for this scan.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form id=\"record-form\" onSubmit={onSubmit} className=\"space-y-4\">\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Attendee ID</span>\n <Input value={form.attendeeId} onChange={(e) => onChange('attendeeId', e.target.value)} />\n </label>\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\">Method</span>\n <Select value={form.method} onValueChange={(value) => onChange('method', value)}>\n <SelectTrigger aria-label=\"Method\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CHECKIN_METHODS.map((method) => (\n <SelectItem key={method} value={method}>\n {method}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Direction</span>\n <Select value={form.direction} onValueChange={(value) => onChange('direction', value)}>\n <SelectTrigger aria-label=\"Direction\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CHECKIN_DIRECTIONS.map((direction) => (\n <SelectItem key={direction} value={direction}>\n {direction}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\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\">Status</span>\n <Input value={form.status} onChange={(e) => onChange('status', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Station</span>\n <Select\n value={form.stationId || 'none'}\n onValueChange={(value) => onChange('stationId', value === 'none' ? '' : value)}\n >\n <SelectTrigger aria-label=\"Station\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"none\">No station</SelectItem>\n {stations.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name ?? s.id}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n </form>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)} disabled={loading}>\n Cancel\n </Button>\n <Button type=\"submit\" form=\"record-form\" disabled={loading}>\n {loading ? 'Saving…' : 'Save record'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ===========================================================================\n// Check-in action panel (the hero workflow)\n// ===========================================================================\n\ninterface CheckInPanelProps {\n eventId: string;\n stations: StationRow[];\n onCheckedIn: () => void;\n}\n\nfunction CheckInPanel({ eventId, stations, onCheckedIn }: CheckInPanelProps) {\n const [scanValue, setScanValue] = useState('');\n const [method, setMethod] = useState<string>('QR');\n const [direction, setDirection] = useState<string>('IN');\n const [stationId, setStationId] = useState('');\n const [sessionId, setSessionId] = useState('');\n\n const [createRecord, { loading }] = useCreateCheckInRecordMutation();\n const attendeesQuery = useAttendeesQuery({ eventId, limit: 100, offset: 0 });\n const sessionsQuery = useEventSessionsQuery({ eventId, limit: 100, offset: 0 });\n\n const attendees = attendeesQuery.data?.attendees.items ?? [];\n const sessions = sessionsQuery.data?.eventSessions.items ?? [];\n\n const resolveAttendee = (raw: string): { attendeeId: string | null; label: string } => {\n const value = raw.trim();\n if (!value) {\n return { attendeeId: null, label: 'attendee' };\n }\n if (value.includes('@')) {\n const match = attendees.find((a) => a.email?.toLowerCase() === value.toLowerCase());\n return { attendeeId: match?.id ?? null, label: match?.fullName ?? value };\n }\n const byId = attendees.find((a) => a.id === value);\n return { attendeeId: value, label: byId?.fullName ?? `attendee ${value}` };\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const { attendeeId, label } = resolveAttendee(scanValue);\n if (scanValue.trim().includes('@') && !attendeeId) {\n notify.error('Attendee not found', `No attendee at this event matches “${scanValue.trim()}”.`);\n return;\n }\n const input = {\n eventId,\n attendeeId,\n stationId: stationId || null,\n method,\n direction,\n status: 'checked_in',\n recordedAt: new Date().toISOString(),\n metadata: sessionId ? { sessionId } : {},\n };\n try {\n const res = await createRecord({ variables: { input } });\n if (!mutationSucceeded(res, res.data?.createCheckInRecord?.id, 'Could not record check-in')) {\n return;\n }\n notify.success(\n `${label} checked ${direction === 'OUT' ? 'out' : 'in'}`,\n `Recorded via ${method}${stationId ? '' : ' • no station'}.`\n );\n setScanValue('');\n onCheckedIn();\n } catch (error) {\n notify.error('Could not record check-in', describeError(error));\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center gap-2\">\n <ScanLine className=\"h-5 w-5 text-text-secondary\" aria-hidden=\"true\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">Check in an attendee</h2>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Scan a QR badge or type an attendee ID or email, then record the arrival.\n </p>\n\n <form onSubmit={onSubmit} className=\"mt-4 space-y-4\">\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Attendee ID / email / QR</span>\n <Input\n value={scanValue}\n onChange={(e) => setScanValue(e.target.value)}\n placeholder=\"Scan badge or paste attendee ID / email\"\n autoComplete=\"off\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Method</span>\n <Select value={method} onValueChange={setMethod}>\n <SelectTrigger aria-label=\"Check-in method\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CHECKIN_METHODS.map((m) => (\n <SelectItem key={m} value={m}>\n {m}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Direction</span>\n <Select value={direction} onValueChange={setDirection}>\n <SelectTrigger aria-label=\"Direction\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CHECKIN_DIRECTIONS.map((d) => (\n <SelectItem key={d} value={d}>\n {d}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Station</span>\n <Select value={stationId || 'none'} onValueChange={(value) => setStationId(value === 'none' ? '' : value)}>\n <SelectTrigger aria-label=\"Station\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"none\">No station</SelectItem>\n {stations.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name ?? s.id}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Session</span>\n <Select value={sessionId || 'none'} onValueChange={(value) => setSessionId(value === 'none' ? '' : value)}>\n <SelectTrigger aria-label=\"Session\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"none\">Whole event</SelectItem>\n {sessions.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <div className=\"flex justify-end\">\n <Button type=\"submit\" disabled={loading || !scanValue.trim()}>\n <LogIn className=\"h-4 w-4\" />\n {loading ? 'Recording…' : 'Record check-in'}\n </Button>\n </div>\n </form>\n </GlassCard>\n );\n}\n\n// ===========================================================================\n// Page\n// ===========================================================================\n\nconst PAGE_SIZE = 10;\n\nexport default function KioskPage() {\n const eventsQuery = useEventsQuery({ limit: 100, offset: 0 });\n const events = eventsQuery.data?.events.items ?? [];\n const [selectedEventId, setSelectedEventId] = useState('');\n const scopeEventId = selectedEventId || undefined;\n const selectedEvent = events.find((e) => e.id === selectedEventId);\n\n const stationsPagination = usePagination({ pageSize: PAGE_SIZE, resetKey: selectedEventId });\n const recordsPagination = usePagination({ pageSize: PAGE_SIZE, resetKey: selectedEventId });\n\n const stationsQuery = useCheckInStationsQuery({\n eventId: scopeEventId,\n limit: stationsPagination.limit,\n offset: stationsPagination.offset,\n });\n const recordsQuery = useCheckInRecordsQuery({\n eventId: scopeEventId,\n limit: recordsPagination.limit,\n offset: recordsPagination.offset,\n });\n\n const stations = (stationsQuery.data?.checkInStations.items ?? []) as StationRow[];\n const stationsTotal = stationsQuery.data?.checkInStations.total ?? 0;\n const records = (recordsQuery.data?.checkInRecords.items ?? []) as RecordRow[];\n const recordsTotal = recordsQuery.data?.checkInRecords.total ?? 0;\n\n const [deleteStation] = useDeleteCheckInStationMutation();\n const [deleteRecord] = useDeleteCheckInRecordMutation();\n\n const [stationDialogOpen, setStationDialogOpen] = useState(false);\n const [editingStation, setEditingStation] = useState<StationRow | undefined>(undefined);\n const [recordDialogOpen, setRecordDialogOpen] = useState(false);\n const [editingRecord, setEditingRecord] = useState<RecordRow | null>(null);\n\n const openNewStation = () => {\n setEditingStation(undefined);\n setStationDialogOpen(true);\n };\n const openEditStation = (station: StationRow) => {\n setEditingStation(station);\n setStationDialogOpen(true);\n };\n const openEditRecord = (record: RecordRow) => {\n setEditingRecord(record);\n setRecordDialogOpen(true);\n };\n\n const onDeleteStation = async (station: StationRow) => {\n const res = await deleteStation({ variables: { id: station.id } });\n if (!mutationSucceeded(res, res.data?.deleteCheckInStation ? station.id : null, 'Could not delete station')) {\n return;\n }\n notify.success('Station deleted', `“${station.name ?? station.id}” was removed.`);\n };\n\n const onDeleteRecord = async (record: RecordRow) => {\n const res = await deleteRecord({ variables: { id: record.id } });\n if (!mutationSucceeded(res, res.data?.deleteCheckInRecord ? record.id : null, 'Could not delete record')) {\n return;\n }\n notify.success('Check-in record deleted');\n };\n\n const hasEvent = Boolean(selectedEventId);\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Kiosks are the front desk of your event. Each check-in station is a device or counter where attendees scan in,\n and check-in records are the live log of who arrived and when. Configure your stations before doors open, then\n check attendees in from this page and watch arrivals flow in on the day.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Kiosk</GradientText> & Check-In\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Manage check-in stations and record attendee arrivals against a live backend.\n </p>\n </div>\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={selectedEventId || 'all'} onValueChange={(v) => setSelectedEventId(v === 'all' ? '' : v)}>\n <SelectTrigger aria-label=\"Event\" className=\"min-w-52\">\n <SelectValue placeholder=\"All events\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All events</SelectItem>\n {events.map((event) => (\n <SelectItem key={event.id} value={event.id}>\n {event.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <Button type=\"button\" onClick={openNewStation} disabled={!hasEvent}>\n <Plus className=\"h-4 w-4\" />\n New station\n </Button>\n </div>\n </div>\n </EmphasisPanel>\n\n {hasEvent ? (\n <CheckInPanel\n eventId={selectedEventId}\n stations={stations}\n onCheckedIn={() => {\n recordsPagination.reset();\n void recordsQuery.refetch();\n }}\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"Select an event to check attendees in\"\n description=\"Check-in stations and records belong to a specific event. Pick one above to configure stations and record arrivals.\"\n className=\"py-8\"\n />\n </GlassCard>\n )}\n\n {/* Stations */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center gap-2\">\n <MonitorSmartphone className=\"h-5 w-5 text-text-secondary\" aria-hidden=\"true\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {stationsQuery.loading ? 'Loading stations…' : `Stations (${stationsTotal})`}\n </h2>\n </div>\n <div className=\"mt-4\">\n {stations.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No check-in stations yet\"\n description={hasEvent ? 'Create a station to start scanning attendees in.' : 'No stations configured.'}\n className=\"py-8\"\n />\n ) : (\n <ul className=\"space-y-2.5\">\n {stations.map((station) => {\n const meta = readStationMeta(station.metadata);\n const active = meta.isActive ?? true;\n const subtitle = [meta.type?.replace('_', ' '), meta.location, meta.code].filter(Boolean).join(' • ');\n return (\n <li\n key={station.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{station.name ?? station.id}</p>\n {subtitle ? <p className=\"mt-0.5 truncate text-sm text-text-secondary\">{subtitle}</p> : null}\n </div>\n <div className=\"flex items-center gap-2\">\n <Badge variant={active ? 'success' : 'warning'} className=\"shrink-0 uppercase\">\n {active ? 'active' : 'disabled'}\n </Badge>\n <ActionMenu\n ariaLabel={`Station ${station.name ?? station.id} actions`}\n entityName={station.name ?? undefined}\n onEdit={() => openEditStation(station)}\n onDelete={() => onDeleteStation(station)}\n />\n </div>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n <Pagination\n offset={stationsPagination.offset}\n limit={stationsPagination.limit}\n loadedCount={stations.length}\n total={stationsTotal}\n onPrev={stationsPagination.prevPage}\n onNext={stationsPagination.nextPage}\n itemLabel=\"stations\"\n loading={stationsQuery.loading}\n />\n </GlassCard>\n\n {/* Records */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {recordsQuery.loading ? 'Loading check-in records…' : `Check-In Records (${recordsTotal})`}\n </h2>\n <div className=\"mt-4\">\n {records.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No check-in records yet\"\n description=\"Arrivals recorded from this page show up here, newest first.\"\n className=\"py-8\"\n />\n ) : (\n <ul className=\"space-y-2.5\">\n {records.map((record) => (\n <li\n key={record.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">\n {record.attendeeId ? `Attendee ${record.attendeeId}` : `Record ${record.id}`}\n </p>\n <p className=\"mt-0.5 truncate text-sm text-text-secondary\">\n {record.method} • {record.direction} • {new Date(record.recordedAt).toLocaleString()}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\" className=\"shrink-0 uppercase\">\n {record.status}\n </Badge>\n <ActionMenu\n ariaLabel={`Check-in record ${record.id} actions`}\n onEdit={() => openEditRecord(record)}\n onDelete={() => onDeleteRecord(record)}\n />\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n <Pagination\n offset={recordsPagination.offset}\n limit={recordsPagination.limit}\n loadedCount={records.length}\n total={recordsTotal}\n onPrev={recordsPagination.prevPage}\n onNext={recordsPagination.nextPage}\n itemLabel=\"records\"\n loading={recordsQuery.loading}\n />\n </GlassCard>\n\n {selectedEventId ? (\n <StationDialog\n open={stationDialogOpen}\n onOpenChange={setStationDialogOpen}\n eventId={selectedEventId}\n eventName={selectedEvent?.name ?? 'this event'}\n station={editingStation}\n />\n ) : null}\n\n <RecordDialog\n open={recordDialogOpen}\n onOpenChange={setRecordDialogOpen}\n record={editingRecord}\n stations={stations}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -90,14 +90,15 @@ function j({ heading: e, loading: t, rows: n, total: r, emptyLabel: i, itemLabel
|
|
|
90
90
|
]
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}), /* @__PURE__ */ p(O, { widgetKey: "networking-lounge" });
|
|
99
|
-
}
|
|
93
|
+
var M = {
|
|
94
|
+
entityType: "EVENTFULLYMANAGED_NETWORKING_LOUNGE",
|
|
95
|
+
entityId: "general",
|
|
96
|
+
contextTitle: "Networking Lounge"
|
|
97
|
+
};
|
|
100
98
|
function N() {
|
|
99
|
+
return k(M), /* @__PURE__ */ p(O, { widgetKey: "networking-lounge" });
|
|
100
|
+
}
|
|
101
|
+
function P() {
|
|
101
102
|
let { navigate: c } = e(), b = l(), C = l(), T = l(), O = o({
|
|
102
103
|
limit: b.limit,
|
|
103
104
|
offset: b.offset
|
|
@@ -107,14 +108,14 @@ function N() {
|
|
|
107
108
|
}), A = i({
|
|
108
109
|
limit: T.limit,
|
|
109
110
|
offset: T.offset
|
|
110
|
-
}), { data:
|
|
111
|
+
}), { data: M } = n({
|
|
111
112
|
limit: 100,
|
|
112
113
|
offset: 0
|
|
113
114
|
}), [P] = r(), F = f(() => {
|
|
114
115
|
let e = /* @__PURE__ */ new Map();
|
|
115
|
-
for (let t of
|
|
116
|
+
for (let t of M?.attendees?.items ?? []) e.set(t.id, t.fullName);
|
|
116
117
|
return e;
|
|
117
|
-
}, [
|
|
118
|
+
}, [M]), I = f(() => O.data?.networkingProfiles.items ?? [], [O.data]), L = f(() => k.data?.networkingMatches.items ?? [], [k.data]), R = f(() => A.data?.meetings.items ?? [], [A.data]), z = I.map((e) => ({
|
|
118
119
|
id: e.id,
|
|
119
120
|
title: F.get(e.attendeeId) ?? (e.attendeeId ? `Attendee ${e.attendeeId}` : `Profile ${e.id}`),
|
|
120
121
|
subtitle: [e.role, e.industry].filter(Boolean).join(" • ") || void 0,
|
|
@@ -217,7 +218,7 @@ function N() {
|
|
|
217
218
|
}), /* @__PURE__ */ p("p", {
|
|
218
219
|
className: "mt-1 text-sm text-text-secondary",
|
|
219
220
|
children: "A shared space for attendees and organizers to connect, backed by the conversations service."
|
|
220
|
-
})] }), /* @__PURE__ */ p(
|
|
221
|
+
})] }), /* @__PURE__ */ p(N, {})]
|
|
221
222
|
}),
|
|
222
223
|
/* @__PURE__ */ p(E, {
|
|
223
224
|
storageKey: "eventfullymanaged-networking",
|
|
@@ -250,6 +251,6 @@ function N() {
|
|
|
250
251
|
});
|
|
251
252
|
}
|
|
252
253
|
//#endregion
|
|
253
|
-
export {
|
|
254
|
+
export { P as default };
|
|
254
255
|
|
|
255
256
|
//# sourceMappingURL=NetworkingPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkingPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/NetworkingPage.tsx"],"sourcesContent":["import { CalendarDays, CalendarPlus, Megaphone, UserPlus, Users } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, type ReactNode } from 'react';\n\nimport {\n useAttendeesQuery,\n useDeleteMeetingMutation,\n useMeetingsQuery,\n useNetworkingMatchesQuery,\n useNetworkingProfilesQuery,\n} from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { Pagination, usePagination, type UsePaginationResult } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype SimpleRow = {\n id: string;\n name?: string;\n title?: string;\n status?: string;\n};\n\ntype ListRow = {\n id: string;\n title: string;\n subtitle?: string;\n status?: string;\n};\n\nfunction statusVariant(status: string): 'success' | 'warning' | 'secondary' | 'info' {\n const value = status.toLowerCase();\n if (['active', 'published', 'live', 'confirmed', 'completed', 'approved', 'public'].includes(value)) {\n return 'success';\n }\n if (['inactive', 'cancelled', 'archived', 'closed', 'expired', 'private'].includes(value)) {\n return 'warning';\n }\n if (['draft', 'created', 'pending', 'scheduled'].includes(value)) {\n return 'info';\n }\n return 'secondary';\n}\n\ninterface NetworkingListProps {\n heading: string;\n loading: boolean;\n rows: ListRow[];\n total: number;\n emptyLabel: string;\n itemLabel: string;\n pagination: UsePaginationResult;\n renderActions?: (row: ListRow) => ReactNode;\n}\n\nfunction NetworkingList({\n heading,\n loading,\n rows,\n total,\n emptyLabel,\n itemLabel,\n pagination,\n renderActions,\n}: NetworkingListProps) {\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{loading ? `${heading}…` : `${heading} (${total})`}</h2>\n <div className=\"mt-4\">\n {rows.length === 0 ? (\n <IllustratedEmptyState illustration=\"empty-people\" title={emptyLabel} className=\"py-8\" />\n ) : (\n <ul className=\"space-y-2.5\">\n {rows.map((row) => (\n <li\n key={row.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{row.title}</p>\n {row.subtitle ? (\n <p className=\"mt-0.5 truncate text-sm text-text-secondary\">{row.subtitle}</p>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {row.status ? (\n <Badge variant={statusVariant(row.status)} className=\"uppercase\">\n {row.status}\n </Badge>\n ) : null}\n {renderActions?.(row)}\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel={itemLabel}\n loading={loading}\n />\n </GlassCard>\n );\n}\n\n/**\n * Shared networking lounge chat, backed by wspace-conversations-svc. Scoped by a\n * stable networking entity so every organizer/attendee on this workspace lands in\n * the same thread.\n */\nfunction NetworkingLounge() {\n useRegisterPageConversationContext({\n entityType: 'EVENTFULLYMANAGED_NETWORKING_LOUNGE',\n entityId: 'general',\n contextTitle: 'Networking Lounge',\n });\n return <ContextConversationWidget widgetKey=\"networking-lounge\" />;\n}\n\nexport default function NetworkingPage() {\n const { navigate } = useEventfullyManaged();\n\n const profilesPagination = usePagination();\n const matchesPagination = usePagination();\n const meetingsPagination = usePagination();\n\n const profilesQuery = useNetworkingProfilesQuery({\n limit: profilesPagination.limit,\n offset: profilesPagination.offset,\n });\n const matchesQuery = useNetworkingMatchesQuery({\n limit: matchesPagination.limit,\n offset: matchesPagination.offset,\n });\n const meetingsQuery = useMeetingsQuery({\n limit: meetingsPagination.limit,\n offset: meetingsPagination.offset,\n });\n const { data: attendeesData } = useAttendeesQuery({ limit: 100, offset: 0 });\n\n const [deleteMeeting] = useDeleteMeetingMutation();\n\n const attendeeNames = useMemo(() => {\n const map = new Map<string, string>();\n for (const attendee of attendeesData?.attendees?.items ?? []) {\n map.set(attendee.id, attendee.fullName);\n }\n return map;\n }, [attendeesData]);\n\n const profiles = useMemo(() => profilesQuery.data?.networkingProfiles.items ?? [], [profilesQuery.data]);\n const matches = useMemo(() => (matchesQuery.data?.networkingMatches.items ?? []) as SimpleRow[], [matchesQuery.data]);\n const meetings = useMemo(() => (meetingsQuery.data?.meetings.items ?? []) as SimpleRow[], [meetingsQuery.data]);\n\n const profileRows: ListRow[] = profiles.map((row) => ({\n id: row.id,\n title: attendeeNames.get(row.attendeeId) ?? (row.attendeeId ? `Attendee ${row.attendeeId}` : `Profile ${row.id}`),\n subtitle: [row.role, row.industry].filter(Boolean).join(' • ') || undefined,\n status: row.visibility,\n }));\n\n const matchRows: ListRow[] = matches.map((row) => ({\n id: row.id,\n title: row.name ?? row.title ?? `Match ${row.id}`,\n subtitle: 'Suggested by networking engine',\n status: row.status ?? undefined,\n }));\n\n const meetingRows: ListRow[] = meetings.map((row) => ({\n id: row.id,\n title: row.title ?? row.name ?? `Meeting ${row.id}`,\n status: row.status ?? undefined,\n }));\n\n const onDeleteMeeting = async (row: ListRow) => {\n try {\n const response = await deleteMeeting({ variables: { id: row.id } });\n const deleted = response.data?.deleteMeeting === true;\n if (!mutationSucceeded(response, deleted ? row.id : undefined, 'Could not delete meeting')) {\n return;\n }\n notify.success('Meeting removed', `${row.title} was deleted.`);\n await meetingsQuery.refetch();\n } catch (error) {\n notify.error('Could not delete meeting', describeError(error));\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Networking turns attendees into connections. Profiles describe who each attendee is and what they are looking\n for, matches are the suggestions the engine makes between them, and meetings are the 1:1s they book. Opt an\n attendee in, review strong matches, and book meetings that mirror straight into the calendar.\n </PagePurpose>\n\n <EmphasisPanel className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Networking</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Manage profiles, matches and meeting schedules from live backend data.\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/networking/profiles/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Opt in a profile\n </Button>\n <Button type=\"button\" onClick={() => navigate('/networking/meetings/create')}>\n <CalendarPlus className=\"h-4 w-4\" />\n Book a meeting\n </Button>\n </div>\n </EmphasisPanel>\n\n <NetworkingList\n heading=\"Profiles\"\n loading={profilesQuery.loading}\n rows={profileRows}\n total={profilesQuery.data?.networkingProfiles.total ?? 0}\n emptyLabel=\"No networking profiles yet. Opt an attendee in to get started.\"\n itemLabel=\"profiles\"\n pagination={profilesPagination}\n renderActions={(row) => (\n <ActionMenu\n ariaLabel={`${row.title} actions`}\n onEdit={() => navigate(`/networking/profiles/${row.id}/edit`)}\n />\n )}\n />\n\n <NetworkingList\n heading=\"Matches\"\n loading={matchesQuery.loading}\n rows={matchRows}\n total={matchesQuery.data?.networkingMatches.total ?? 0}\n emptyLabel=\"No networking matches yet.\"\n itemLabel=\"matches\"\n pagination={matchesPagination}\n renderActions={(row) => (\n <ActionMenu\n ariaLabel={`${row.title} actions`}\n extraItems={[\n {\n key: 'book',\n label: 'Book meeting',\n icon: CalendarPlus,\n onSelect: () => navigate('/networking/meetings/create'),\n },\n ]}\n />\n )}\n />\n\n <NetworkingList\n heading=\"Meetings\"\n loading={meetingsQuery.loading}\n rows={meetingRows}\n total={meetingsQuery.data?.meetings.total ?? 0}\n emptyLabel=\"No meetings scheduled yet.\"\n itemLabel=\"meetings\"\n pagination={meetingsPagination}\n renderActions={(row) => (\n <ActionMenu\n ariaLabel={`${row.title} actions`}\n entityName={row.title}\n onEdit={() => navigate(`/networking/meetings/${row.id}/edit`)}\n onDelete={() => onDeleteMeeting(row)}\n />\n )}\n />\n\n <div className=\"space-y-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Networking chat</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n A shared space for attendees and organizers to connect, backed by the conversations service.\n </p>\n </div>\n <NetworkingLounge />\n </div>\n\n <NextSteps\n storageKey=\"eventfullymanaged-networking\"\n title=\"Make networking land\"\n steps={[\n {\n id: 'grow-attendees',\n label: 'Bring in more attendees',\n description: 'More registered attendees means richer matches and meetings.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/registration'),\n },\n {\n id: 'announce-networking',\n label: 'Announce networking sessions',\n description: 'Let attendees know how and when to connect.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n {\n id: 'review-event',\n label: 'Review the event schedule',\n description: 'Make sure networking slots fit around your sessions.',\n icon: <CalendarDays className=\"h-4 w-4\" />,\n onClick: () => navigate('/events'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NetworkingPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/NetworkingPage.tsx"],"sourcesContent":["import { CalendarDays, CalendarPlus, Megaphone, UserPlus, Users } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, type ReactNode } from 'react';\n\nimport {\n useAttendeesQuery,\n useDeleteMeetingMutation,\n useMeetingsQuery,\n useNetworkingMatchesQuery,\n useNetworkingProfilesQuery,\n} from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { Pagination, usePagination, type UsePaginationResult } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype SimpleRow = {\n id: string;\n name?: string;\n title?: string;\n status?: string;\n};\n\ntype ListRow = {\n id: string;\n title: string;\n subtitle?: string;\n status?: string;\n};\n\nfunction statusVariant(status: string): 'success' | 'warning' | 'secondary' | 'info' {\n const value = status.toLowerCase();\n if (['active', 'published', 'live', 'confirmed', 'completed', 'approved', 'public'].includes(value)) {\n return 'success';\n }\n if (['inactive', 'cancelled', 'archived', 'closed', 'expired', 'private'].includes(value)) {\n return 'warning';\n }\n if (['draft', 'created', 'pending', 'scheduled'].includes(value)) {\n return 'info';\n }\n return 'secondary';\n}\n\ninterface NetworkingListProps {\n heading: string;\n loading: boolean;\n rows: ListRow[];\n total: number;\n emptyLabel: string;\n itemLabel: string;\n pagination: UsePaginationResult;\n renderActions?: (row: ListRow) => ReactNode;\n}\n\nfunction NetworkingList({\n heading,\n loading,\n rows,\n total,\n emptyLabel,\n itemLabel,\n pagination,\n renderActions,\n}: NetworkingListProps) {\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{loading ? `${heading}…` : `${heading} (${total})`}</h2>\n <div className=\"mt-4\">\n {rows.length === 0 ? (\n <IllustratedEmptyState illustration=\"empty-people\" title={emptyLabel} className=\"py-8\" />\n ) : (\n <ul className=\"space-y-2.5\">\n {rows.map((row) => (\n <li\n key={row.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{row.title}</p>\n {row.subtitle ? (\n <p className=\"mt-0.5 truncate text-sm text-text-secondary\">{row.subtitle}</p>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {row.status ? (\n <Badge variant={statusVariant(row.status)} className=\"uppercase\">\n {row.status}\n </Badge>\n ) : null}\n {renderActions?.(row)}\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel={itemLabel}\n loading={loading}\n />\n </GlassCard>\n );\n}\n\n/**\n * Registration payload for the networking lounge conversation context.\n *\n * MUST be a stable (module-level) reference. `useRegisterPageConversationContext`\n * keys its effect on this object's identity; passing a fresh inline object on\n * every render makes the effect re-run each render, which toggles state on the\n * shared PageConversationContextProvider (an ancestor of this MFE's <Routes> in\n * the app shell). That perpetual re-render loop races client-side navigations\n * dispatched from this page, so links like \"Opt in a profile\" / \"Book a meeting\"\n * changed the URL but never re-matched the create route. Keeping the reference\n * constant lets the effect settle after the first run.\n */\nconst NETWORKING_LOUNGE_CONTEXT = {\n entityType: 'EVENTFULLYMANAGED_NETWORKING_LOUNGE',\n entityId: 'general',\n contextTitle: 'Networking Lounge',\n};\n\n/**\n * Shared networking lounge chat, backed by wspace-conversations-svc. Scoped by a\n * stable networking entity so every organizer/attendee on this workspace lands in\n * the same thread.\n */\nfunction NetworkingLounge() {\n useRegisterPageConversationContext(NETWORKING_LOUNGE_CONTEXT);\n return <ContextConversationWidget widgetKey=\"networking-lounge\" />;\n}\n\nexport default function NetworkingPage() {\n const { navigate } = useEventfullyManaged();\n\n const profilesPagination = usePagination();\n const matchesPagination = usePagination();\n const meetingsPagination = usePagination();\n\n const profilesQuery = useNetworkingProfilesQuery({\n limit: profilesPagination.limit,\n offset: profilesPagination.offset,\n });\n const matchesQuery = useNetworkingMatchesQuery({\n limit: matchesPagination.limit,\n offset: matchesPagination.offset,\n });\n const meetingsQuery = useMeetingsQuery({\n limit: meetingsPagination.limit,\n offset: meetingsPagination.offset,\n });\n const { data: attendeesData } = useAttendeesQuery({ limit: 100, offset: 0 });\n\n const [deleteMeeting] = useDeleteMeetingMutation();\n\n const attendeeNames = useMemo(() => {\n const map = new Map<string, string>();\n for (const attendee of attendeesData?.attendees?.items ?? []) {\n map.set(attendee.id, attendee.fullName);\n }\n return map;\n }, [attendeesData]);\n\n const profiles = useMemo(() => profilesQuery.data?.networkingProfiles.items ?? [], [profilesQuery.data]);\n const matches = useMemo(() => (matchesQuery.data?.networkingMatches.items ?? []) as SimpleRow[], [matchesQuery.data]);\n const meetings = useMemo(() => (meetingsQuery.data?.meetings.items ?? []) as SimpleRow[], [meetingsQuery.data]);\n\n const profileRows: ListRow[] = profiles.map((row) => ({\n id: row.id,\n title: attendeeNames.get(row.attendeeId) ?? (row.attendeeId ? `Attendee ${row.attendeeId}` : `Profile ${row.id}`),\n subtitle: [row.role, row.industry].filter(Boolean).join(' • ') || undefined,\n status: row.visibility,\n }));\n\n const matchRows: ListRow[] = matches.map((row) => ({\n id: row.id,\n title: row.name ?? row.title ?? `Match ${row.id}`,\n subtitle: 'Suggested by networking engine',\n status: row.status ?? undefined,\n }));\n\n const meetingRows: ListRow[] = meetings.map((row) => ({\n id: row.id,\n title: row.title ?? row.name ?? `Meeting ${row.id}`,\n status: row.status ?? undefined,\n }));\n\n const onDeleteMeeting = async (row: ListRow) => {\n try {\n const response = await deleteMeeting({ variables: { id: row.id } });\n const deleted = response.data?.deleteMeeting === true;\n if (!mutationSucceeded(response, deleted ? row.id : undefined, 'Could not delete meeting')) {\n return;\n }\n notify.success('Meeting removed', `${row.title} was deleted.`);\n await meetingsQuery.refetch();\n } catch (error) {\n notify.error('Could not delete meeting', describeError(error));\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Networking turns attendees into connections. Profiles describe who each attendee is and what they are looking\n for, matches are the suggestions the engine makes between them, and meetings are the 1:1s they book. Opt an\n attendee in, review strong matches, and book meetings that mirror straight into the calendar.\n </PagePurpose>\n\n <EmphasisPanel className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Networking</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Manage profiles, matches and meeting schedules from live backend data.\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/networking/profiles/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Opt in a profile\n </Button>\n <Button type=\"button\" onClick={() => navigate('/networking/meetings/create')}>\n <CalendarPlus className=\"h-4 w-4\" />\n Book a meeting\n </Button>\n </div>\n </EmphasisPanel>\n\n <NetworkingList\n heading=\"Profiles\"\n loading={profilesQuery.loading}\n rows={profileRows}\n total={profilesQuery.data?.networkingProfiles.total ?? 0}\n emptyLabel=\"No networking profiles yet. Opt an attendee in to get started.\"\n itemLabel=\"profiles\"\n pagination={profilesPagination}\n renderActions={(row) => (\n <ActionMenu\n ariaLabel={`${row.title} actions`}\n onEdit={() => navigate(`/networking/profiles/${row.id}/edit`)}\n />\n )}\n />\n\n <NetworkingList\n heading=\"Matches\"\n loading={matchesQuery.loading}\n rows={matchRows}\n total={matchesQuery.data?.networkingMatches.total ?? 0}\n emptyLabel=\"No networking matches yet.\"\n itemLabel=\"matches\"\n pagination={matchesPagination}\n renderActions={(row) => (\n <ActionMenu\n ariaLabel={`${row.title} actions`}\n extraItems={[\n {\n key: 'book',\n label: 'Book meeting',\n icon: CalendarPlus,\n onSelect: () => navigate('/networking/meetings/create'),\n },\n ]}\n />\n )}\n />\n\n <NetworkingList\n heading=\"Meetings\"\n loading={meetingsQuery.loading}\n rows={meetingRows}\n total={meetingsQuery.data?.meetings.total ?? 0}\n emptyLabel=\"No meetings scheduled yet.\"\n itemLabel=\"meetings\"\n pagination={meetingsPagination}\n renderActions={(row) => (\n <ActionMenu\n ariaLabel={`${row.title} actions`}\n entityName={row.title}\n onEdit={() => navigate(`/networking/meetings/${row.id}/edit`)}\n onDelete={() => onDeleteMeeting(row)}\n />\n )}\n />\n\n <div className=\"space-y-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Networking chat</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n A shared space for attendees and organizers to connect, backed by the conversations service.\n </p>\n </div>\n <NetworkingLounge />\n </div>\n\n <NextSteps\n storageKey=\"eventfullymanaged-networking\"\n title=\"Make networking land\"\n steps={[\n {\n id: 'grow-attendees',\n label: 'Bring in more attendees',\n description: 'More registered attendees means richer matches and meetings.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/registration'),\n },\n {\n id: 'announce-networking',\n label: 'Announce networking sessions',\n description: 'Let attendees know how and when to connect.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n {\n id: 'review-event',\n label: 'Review the event schedule',\n description: 'Make sure networking slots fit around your sessions.',\n icon: <CalendarDays className=\"h-4 w-4\" />,\n onClick: () => navigate('/events'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|