@burdenoff/microfe-eventfullymanaged 2026.704.1 → 2026.704.2
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/KioskPage.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/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":"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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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"}
|
|
@@ -26,11 +26,18 @@ function O(e) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
function k() {
|
|
29
|
-
let e = l({ pageSize: 10 }), { data:
|
|
29
|
+
let e = l({ pageSize: 10 }), { data: r, loading: i } = a({
|
|
30
30
|
limit: e.limit,
|
|
31
31
|
offset: e.offset
|
|
32
|
-
}), [
|
|
33
|
-
|
|
32
|
+
}), [m] = o(), { data: h } = n({
|
|
33
|
+
limit: 200,
|
|
34
|
+
offset: 0
|
|
35
|
+
}), g = d(() => {
|
|
36
|
+
let e = /* @__PURE__ */ new Map();
|
|
37
|
+
for (let t of h?.attendees.items ?? []) t.id && t.fullName && e.set(t.id, t.fullName);
|
|
38
|
+
return e;
|
|
39
|
+
}, [h]), _ = (e) => e ? g.get(e) ?? `Attendee ${e.slice(0, 8)}…` : null, v = d(() => r?.registrations.items ?? [], [r]), b = r?.registrations.total ?? 0, x = async (e, n) => {
|
|
40
|
+
let r = await m({ variables: {
|
|
34
41
|
id: e.id,
|
|
35
42
|
input: {
|
|
36
43
|
eventId: e.eventId,
|
|
@@ -50,20 +57,20 @@ function k() {
|
|
|
50
57
|
children: [
|
|
51
58
|
/* @__PURE__ */ f("h2", {
|
|
52
59
|
className: "text-lg font-semibold text-text-primary",
|
|
53
|
-
children:
|
|
60
|
+
children: i ? "Loading registrations…" : `Registrations (${b})`
|
|
54
61
|
}),
|
|
55
|
-
|
|
62
|
+
v.length === 0 && !i ? /* @__PURE__ */ f(w, {
|
|
56
63
|
illustration: "empty-data",
|
|
57
64
|
title: "No registrations yet.",
|
|
58
65
|
description: "Register your first attendee to start tracking sign-ups.",
|
|
59
66
|
className: "mt-4"
|
|
60
67
|
}) : /* @__PURE__ */ f("ul", {
|
|
61
68
|
className: "mt-4 space-y-2",
|
|
62
|
-
children:
|
|
69
|
+
children: v.map((e) => {
|
|
63
70
|
let t = D.filter((t) => t !== (e.status ?? "").toUpperCase()).map((t, n) => ({
|
|
64
71
|
key: t,
|
|
65
72
|
label: `Set ${t.replace("_", " ").toLowerCase()}`,
|
|
66
|
-
onSelect: () => void
|
|
73
|
+
onSelect: () => void x(e, t),
|
|
67
74
|
separatorBefore: n === 0
|
|
68
75
|
}));
|
|
69
76
|
return /* @__PURE__ */ p("li", {
|
|
@@ -72,7 +79,7 @@ function k() {
|
|
|
72
79
|
className: "min-w-0",
|
|
73
80
|
children: [/* @__PURE__ */ f("p", {
|
|
74
81
|
className: "truncate text-sm font-medium text-text-primary",
|
|
75
|
-
children: e.attendeeId
|
|
82
|
+
children: _(e.attendeeId) ?? `Registration ${e.id}`
|
|
76
83
|
}), /* @__PURE__ */ f("p", {
|
|
77
84
|
className: "truncate text-xs text-text-secondary",
|
|
78
85
|
children: [`Qty ${e.quantity}`, e.totalAmount ? `${e.totalAmount} ${e.currency}` : void 0].filter(Boolean).join(" • ")
|
|
@@ -93,12 +100,12 @@ function k() {
|
|
|
93
100
|
/* @__PURE__ */ f(c, {
|
|
94
101
|
offset: e.offset,
|
|
95
102
|
limit: e.limit,
|
|
96
|
-
loadedCount:
|
|
97
|
-
total:
|
|
103
|
+
loadedCount: v.length,
|
|
104
|
+
total: b,
|
|
98
105
|
onPrev: e.prevPage,
|
|
99
106
|
onNext: e.nextPage,
|
|
100
107
|
itemLabel: "registrations",
|
|
101
|
-
loading:
|
|
108
|
+
loading: i
|
|
102
109
|
})
|
|
103
110
|
]
|
|
104
111
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RegistrationPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/RegistrationPage.tsx"],"sourcesContent":["import { Megaphone, Tag, TicketPlus, UserPlus, Users } from 'lucide-react';\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 } from 'react';\n\nimport {\n useAttendeesQuery,\n useDeletePromoCodeMutation,\n usePromoCodesQuery,\n useRegistrationsQuery,\n useUpdateRegistrationMutation,\n type AttendeeGql,\n type PromoCodeGql,\n type RegistrationGql,\n} from '../../api/eventApi';\nimport { ActionMenu, type ActionMenuItem } from '../../components/ActionMenu';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst REGISTRATION_STATUSES = ['PENDING', 'CONFIRMED', 'WAITLISTED', 'CANCELLED', 'CHECKED_IN'] as const;\n\nfunction statusVariant(status?: string | null): 'default' | 'secondary' | 'destructive' | 'outline' {\n switch ((status ?? '').toUpperCase()) {\n case 'CONFIRMED':\n case 'CHECKED_IN':\n return 'default';\n case 'CANCELLED':\n return 'destructive';\n case 'WAITLISTED':\n return 'outline';\n default:\n return 'secondary';\n }\n}\n\nfunction RegistrationsSection() {\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = useRegistrationsQuery({ limit: pagination.limit, offset: pagination.offset });\n const [updateRegistration] = useUpdateRegistrationMutation();\n\n const items = useMemo<RegistrationGql[]>(() => data?.registrations.items ?? [], [data]);\n const total = data?.registrations.total ?? 0;\n\n const changeStatus = async (row: RegistrationGql, status: string) => {\n const response = await updateRegistration({\n variables: {\n id: row.id,\n input: {\n eventId: row.eventId,\n attendeeId: row.attendeeId ?? null,\n ticketTypeId: row.ticketTypeId ?? null,\n quantity: row.quantity,\n currency: row.currency,\n totalAmount: row.totalAmount ?? null,\n status,\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.updateRegistration?.id, 'Could not update registration')) {\n return;\n }\n notify.success('Registration updated', `Status set to ${status}`);\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? 'Loading registrations…' : `Registrations (${total})`}\n </h2>\n {items.length === 0 && !loading ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No registrations yet.\"\n description=\"Register your first attendee to start tracking sign-ups.\"\n className=\"mt-4\"\n />\n ) : (\n <ul className=\"mt-4 space-y-2\">\n {items.map((row) => {\n const statusItems: ActionMenuItem[] = REGISTRATION_STATUSES.filter(\n (candidate) => candidate !== (row.status ?? '').toUpperCase()\n ).map((candidate, index) => ({\n key: candidate,\n label: `Set ${candidate.replace('_', ' ').toLowerCase()}`,\n onSelect: () => void changeStatus(row, candidate),\n separatorBefore: index === 0,\n }));\n return (\n <li\n key={row.id}\n className=\"flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {row.attendeeId ? `Attendee ${row.attendeeId}` : `Registration ${row.id}`}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[\n `Qty ${row.quantity}`,\n row.totalAmount ? `${row.totalAmount} ${row.currency}` : undefined,\n ]\n .filter(Boolean)\n .join(' • ')}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Badge variant={statusVariant(row.status)}>{row.status ?? 'UNKNOWN'}</Badge>\n <ActionMenu ariaLabel=\"Registration actions\" extraItems={statusItems} />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"registrations\"\n loading={loading}\n />\n </GlassCard>\n );\n}\n\nfunction AttendeesSection() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = useAttendeesQuery({ limit: pagination.limit, offset: pagination.offset });\n\n const items = useMemo<AttendeeGql[]>(() => data?.attendees.items ?? [], [data]);\n const total = data?.attendees.total ?? 0;\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? 'Loading attendees…' : `Attendees (${total})`}\n </h2>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => navigate('/registration/attendees/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Add attendee\n </Button>\n </div>\n {items.length === 0 && !loading ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No attendees yet.\"\n description=\"Add attendees or register one to build your guest list.\"\n className=\"mt-4\"\n />\n ) : (\n <ul className=\"mt-4 space-y-2\">\n {items.map((row) => (\n <li\n key={row.id}\n className=\"flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{row.fullName}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[row.email, row.company, row.role].filter(Boolean).join(' • ')}\n </p>\n </div>\n <ActionMenu\n ariaLabel={`Actions for ${row.fullName}`}\n onView={() => navigate(`/attendees/${row.id}/profile`)}\n onEdit={() => navigate(`/registration/attendees/${row.id}/edit`)}\n />\n </li>\n ))}\n </ul>\n )}\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"attendees\"\n loading={loading}\n />\n </GlassCard>\n );\n}\n\nfunction PromoCodesSection() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = usePromoCodesQuery({ limit: pagination.limit, offset: pagination.offset });\n const [deletePromoCode] = useDeletePromoCodeMutation();\n\n const items = useMemo<PromoCodeGql[]>(() => data?.promoCodes.items ?? [], [data]);\n const total = data?.promoCodes.total ?? 0;\n\n const onDelete = async (row: PromoCodeGql) => {\n const response = await deletePromoCode({ variables: { id: row.id } });\n if (response.error) {\n notify.error('Could not delete promo code', response.error.message);\n throw response.error;\n }\n notify.success('Promo code deleted', row.code);\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? 'Loading promo codes…' : `Promo Codes (${total})`}\n </h2>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => navigate('/registration/promo-codes/create')}\n >\n <TicketPlus className=\"h-4 w-4\" />\n New promo code\n </Button>\n </div>\n {items.length === 0 && !loading ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No promo codes yet.\"\n description=\"Create a code to offer discounts and drive registrations.\"\n className=\"mt-4\"\n />\n ) : (\n <ul className=\"mt-4 space-y-2\">\n {items.map((row) => (\n <li\n key={row.id}\n className=\"flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{row.code}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {row.discountType === 'PERCENT' ? `${row.discountValue}% off` : `${row.discountValue} off`}\n {row.maxUses != null ? ` • ${row.usedCount}/${row.maxUses} used` : ` • ${row.usedCount} used`}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Badge variant={row.isActive ? 'default' : 'secondary'}>{row.isActive ? 'Active' : 'Inactive'}</Badge>\n <ActionMenu\n ariaLabel={`Actions for ${row.code}`}\n onEdit={() => navigate(`/registration/promo-codes/${row.id}/edit`)}\n onDelete={() => onDelete(row)}\n entityName={row.code}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"promo codes\"\n loading={loading}\n />\n </GlassCard>\n );\n}\n\nexport default function RegistrationPage() {\n const { navigate } = useEventfullyManaged();\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n This is where you see who is coming. Registrations are the paid or confirmed sign-ups, attendees are the people\n behind them, and promo codes let you offer discounts. Register attendees, edit their details, change a\n registration's status, and manage discount codes — all backed by live GraphQL APIs.\n </PagePurpose>\n\n <EmphasisPanel>\n <div 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>Registration</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Track registrations, attendees, and promo codes with backend persistence.\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" onClick={() => navigate('/registration/register')}>\n <TicketPlus className=\"h-4 w-4\" />\n Register attendee\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration/attendees/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Add attendee\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration/promo-codes/create')}>\n <Tag className=\"h-4 w-4\" />\n New promo code\n </Button>\n </div>\n </div>\n </EmphasisPanel>\n\n <RegistrationsSection />\n <AttendeesSection />\n <PromoCodesSection />\n\n <NextSteps\n storageKey=\"eventfullymanaged-registration\"\n title=\"Grow your registrations\"\n steps={[\n {\n id: 'view-networking',\n label: 'Set up attendee networking',\n description: 'Turn registered attendees into connections with matchmaking.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'promote',\n label: 'Promote with a campaign',\n description: 'Send reminders and offers to drive more sign-ups.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n {\n id: 'analyze',\n label: 'Snapshot your numbers',\n description: 'Capture an analytics snapshot to track registration trends.',\n icon: <Tag className=\"h-4 w-4\" />,\n onClick: () => navigate('/analytics'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AA6BA,IAAM,IAAwB;CAAC;CAAW;CAAa;CAAc;CAAa;CAAa;AAE/F,SAAS,EAAc,GAA6E;AAClG,UAAS,KAAU,IAAI,aAAa,EAApC;EACE,KAAK;EACL,KAAK,aACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,aACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAS,IAAuB;CAC9B,IAAM,IAAa,EAAc,EAAE,UAAU,IAAI,CAAC,EAC5C,EAAE,SAAM,eAAY,EAAsB;EAAE,OAAO,EAAW;EAAO,QAAQ,EAAW;EAAQ,CAAC,EACjG,CAAC,KAAsB,GAA+B,EAEtD,IAAQ,QAAiC,GAAM,cAAc,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EACjF,IAAQ,GAAM,cAAc,SAAS,GAErC,IAAe,OAAO,GAAsB,MAAmB;EACnE,IAAM,IAAW,MAAM,EAAmB,EACxC,WAAW;GACT,IAAI,EAAI;GACR,OAAO;IACL,SAAS,EAAI;IACb,YAAY,EAAI,cAAc;IAC9B,cAAc,EAAI,gBAAgB;IAClC,UAAU,EAAI;IACd,UAAU,EAAI;IACd,aAAa,EAAI,eAAe;IAChC;IACD;GACF,EACF,CAAC;AACG,IAAkB,GAAU,EAAS,MAAM,oBAAoB,IAAI,gCAAgC,IAGxG,EAAO,QAAQ,wBAAwB,iBAAiB,IAAS;;AAGnE,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,MAAD;IAAI,WAAU;cACX,IAAU,2BAA2B,kBAAkB,EAAM;IAC3D,CAAA;GACJ,EAAM,WAAW,KAAK,CAAC,IACtB,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;IACZ,WAAU;IACV,CAAA,GAEF,kBAAC,MAAD;IAAI,WAAU;cACX,EAAM,KAAK,MAAQ;KAClB,IAAM,IAAgC,EAAsB,QACzD,MAAc,OAAe,EAAI,UAAU,IAAI,aAAa,CAC9D,CAAC,KAAK,GAAW,OAAW;MAC3B,KAAK;MACL,OAAO,OAAO,EAAU,QAAQ,KAAK,IAAI,CAAC,aAAa;MACvD,gBAAgB,KAAK,EAAa,GAAK,EAAU;MACjD,iBAAiB,MAAU;MAC5B,EAAE;AACH,YACE,kBAAC,MAAD;MAEE,WAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAI,aAAa,YAAY,EAAI,eAAe,gBAAgB,EAAI;QACnE,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,CACC,OAAO,EAAI,YACX,EAAI,cAAc,GAAG,EAAI,YAAY,GAAG,EAAI,aAAa,KAAA,EAC1D,CACE,OAAO,QAAQ,CACf,KAAK,MAAM;QACZ,CAAA,CACA;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAS,EAAc,EAAI,OAAO;kBAAG,EAAI,UAAU;QAAkB,CAAA,EAC5E,kBAAC,GAAD;QAAY,WAAU;QAAuB,YAAY;QAAe,CAAA,CACpE;SACH;QApBE,EAAI,GAoBN;MAEP;IACC,CAAA;GAEP,kBAAC,GAAD;IACE,QAAQ,EAAW;IACnB,OAAO,EAAW;IAClB,aAAa,EAAM;IACZ;IACP,QAAQ,EAAW;IACnB,QAAQ,EAAW;IACnB,WAAU;IACD;IACT,CAAA;GACQ;;;AAIhB,SAAS,IAAmB;CAC1B,IAAM,EAAE,gBAAa,GAAsB,EACrC,IAAa,EAAc,EAAE,UAAU,IAAI,CAAC,EAC5C,EAAE,SAAM,eAAY,EAAkB;EAAE,OAAO,EAAW;EAAO,QAAQ,EAAW;EAAQ,CAAC,EAE7F,IAAQ,QAA6B,GAAM,UAAU,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EACzE,IAAQ,GAAM,UAAU,SAAS;AAEvC,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,IAAU,uBAAuB,cAAc,EAAM;KACnD,CAAA,EACL,kBAAC,GAAD;KAAQ,MAAK;KAAS,MAAK;KAAK,SAAQ;KAAU,eAAe,EAAS,iCAAiC;eAA3G,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,eAEzB;OACL;;GACL,EAAM,WAAW,KAAK,CAAC,IACtB,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;IACZ,WAAU;IACV,CAAA,GAEF,kBAAC,MAAD;IAAI,WAAU;cACX,EAAM,KAAK,MACV,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAkD,EAAI;OAAa,CAAA,EAChF,kBAAC,KAAD;OAAG,WAAU;iBACV;QAAC,EAAI;QAAO,EAAI;QAAS,EAAI;QAAK,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM;OAC7D,CAAA,CACA;SACN,kBAAC,GAAD;MACE,WAAW,eAAe,EAAI;MAC9B,cAAc,EAAS,cAAc,EAAI,GAAG,UAAU;MACtD,cAAc,EAAS,2BAA2B,EAAI,GAAG,OAAO;MAChE,CAAA,CACC;OAdE,EAAI,GAcN,CACL;IACC,CAAA;GAEP,kBAAC,GAAD;IACE,QAAQ,EAAW;IACnB,OAAO,EAAW;IAClB,aAAa,EAAM;IACZ;IACP,QAAQ,EAAW;IACnB,QAAQ,EAAW;IACnB,WAAU;IACD;IACT,CAAA;GACQ;;;AAIhB,SAAS,IAAoB;CAC3B,IAAM,EAAE,gBAAa,GAAsB,EACrC,IAAa,EAAc,EAAE,UAAU,IAAI,CAAC,EAC5C,EAAE,SAAM,eAAY,EAAmB;EAAE,OAAO,EAAW;EAAO,QAAQ,EAAW;EAAQ,CAAC,EAC9F,CAAC,KAAmB,GAA4B,EAEhD,IAAQ,QAA8B,GAAM,WAAW,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EAC3E,IAAQ,GAAM,WAAW,SAAS,GAElC,IAAW,OAAO,MAAsB;EAC5C,IAAM,IAAW,MAAM,EAAgB,EAAE,WAAW,EAAE,IAAI,EAAI,IAAI,EAAE,CAAC;AACrE,MAAI,EAAS,MAEX,OADA,EAAO,MAAM,+BAA+B,EAAS,MAAM,QAAQ,EAC7D,EAAS;AAEjB,IAAO,QAAQ,sBAAsB,EAAI,KAAK;;AAGhD,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,IAAU,yBAAyB,gBAAgB,EAAM;KACvD,CAAA,EACL,kBAAC,GAAD;KACE,MAAK;KACL,MAAK;KACL,SAAQ;KACR,eAAe,EAAS,mCAAmC;eAJ7D,CAME,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,EAAA,iBAE3B;OACL;;GACL,EAAM,WAAW,KAAK,CAAC,IACtB,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;IACZ,WAAU;IACV,CAAA,GAEF,kBAAC,MAAD;IAAI,WAAU;cACX,EAAM,KAAK,MACV,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAkD,EAAI;OAAS,CAAA,EAC5E,kBAAC,KAAD;OAAG,WAAU;iBAAb,CACG,EAAI,iBAAiB,YAAY,GAAG,EAAI,cAAc,SAAS,GAAG,EAAI,cAAc,OACpF,EAAI,WAAW,OAAmD,MAAM,EAAI,UAAU,SAAhE,MAAM,EAAI,UAAU,GAAG,EAAI,QAAQ,OACxD;SACA;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAO,SAAS,EAAI,WAAW,YAAY;iBAAc,EAAI,WAAW,WAAW;OAAmB,CAAA,EACtG,kBAAC,GAAD;OACE,WAAW,eAAe,EAAI;OAC9B,cAAc,EAAS,6BAA6B,EAAI,GAAG,OAAO;OAClE,gBAAgB,EAAS,EAAI;OAC7B,YAAY,EAAI;OAChB,CAAA,CACE;QACH;OAnBE,EAAI,GAmBN,CACL;IACC,CAAA;GAEP,kBAAC,GAAD;IACE,QAAQ,EAAW;IACnB,OAAO,EAAW;IAClB,aAAa,EAAM;IACZ;IACP,QAAQ,EAAW;IACnB,QAAQ,EAAW;IACnB,WAAU;IACD;IACT,CAAA;GACQ;;;AAIhB,SAAwB,IAAmB;CACzC,IAAM,EAAE,gBAAa,GAAsB;AAE3C,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAA,UAAa,8SAIC,CAAA;GAEd,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACZ,kBAAC,GAAD,EAAA,UAAc,gBAA2B,CAAA;KACtC,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eAAmC;KAE5C,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAQ,MAAK;OAAS,eAAe,EAAS,yBAAyB;iBAAvE,CACE,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,EAAA,oBAE3B;;MACT,kBAAC,GAAD;OAAQ,MAAK;OAAS,SAAQ;OAAU,eAAe,EAAS,iCAAiC;iBAAjG,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,eAEzB;;MACT,kBAAC,GAAD;OAAQ,MAAK;OAAS,SAAQ;OAAU,eAAe,EAAS,mCAAmC;iBAAnG,CACE,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA,EAAA,iBAEpB;;MACL;OACF;OACQ,CAAA;GAEhB,kBAAC,GAAD,EAAwB,CAAA;GACxB,kBAAC,GAAD,EAAoB,CAAA;GACpB,kBAAC,GAAD,EAAqB,CAAA;GAErB,kBAAC,GAAD;IACE,YAAW;IACX,OAAM;IACN,OAAO;KACL;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,eAAe,EAAS,cAAc;MACvC;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MACvC,eAAe,EAAS,iBAAiB;MAC1C;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;MACjC,eAAe,EAAS,aAAa;MACtC;KACF;IACD,CAAA;GACE"}
|
|
1
|
+
{"version":3,"file":"RegistrationPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/RegistrationPage.tsx"],"sourcesContent":["import { Megaphone, Tag, TicketPlus, UserPlus, Users } from 'lucide-react';\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 } from 'react';\n\nimport {\n useAttendeesQuery,\n useDeletePromoCodeMutation,\n usePromoCodesQuery,\n useRegistrationsQuery,\n useUpdateRegistrationMutation,\n type AttendeeGql,\n type PromoCodeGql,\n type RegistrationGql,\n} from '../../api/eventApi';\nimport { ActionMenu, type ActionMenuItem } from '../../components/ActionMenu';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst REGISTRATION_STATUSES = ['PENDING', 'CONFIRMED', 'WAITLISTED', 'CANCELLED', 'CHECKED_IN'] as const;\n\nfunction statusVariant(status?: string | null): 'default' | 'secondary' | 'destructive' | 'outline' {\n switch ((status ?? '').toUpperCase()) {\n case 'CONFIRMED':\n case 'CHECKED_IN':\n return 'default';\n case 'CANCELLED':\n return 'destructive';\n case 'WAITLISTED':\n return 'outline';\n default:\n return 'secondary';\n }\n}\n\nfunction RegistrationsSection() {\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = useRegistrationsQuery({ limit: pagination.limit, offset: pagination.offset });\n const [updateRegistration] = useUpdateRegistrationMutation();\n\n // Resolve attendee display names (the registrations query returns only\n // attendeeId). Pull a page of attendees to build an id -> name lookup so rows\n // read \"Ada Lovelace\" instead of a raw UUID; fall back to a short id.\n const { data: attendeesData } = useAttendeesQuery({ limit: 200, offset: 0 });\n const nameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const attendee of attendeesData?.attendees.items ?? []) {\n if (attendee.id && attendee.fullName) map.set(attendee.id, attendee.fullName);\n }\n return map;\n }, [attendeesData]);\n const attendeeLabel = (attendeeId?: string | null): string | null => {\n if (!attendeeId) return null;\n return nameById.get(attendeeId) ?? `Attendee ${attendeeId.slice(0, 8)}…`;\n };\n\n const items = useMemo<RegistrationGql[]>(() => data?.registrations.items ?? [], [data]);\n const total = data?.registrations.total ?? 0;\n\n const changeStatus = async (row: RegistrationGql, status: string) => {\n const response = await updateRegistration({\n variables: {\n id: row.id,\n input: {\n eventId: row.eventId,\n attendeeId: row.attendeeId ?? null,\n ticketTypeId: row.ticketTypeId ?? null,\n quantity: row.quantity,\n currency: row.currency,\n totalAmount: row.totalAmount ?? null,\n status,\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.updateRegistration?.id, 'Could not update registration')) {\n return;\n }\n notify.success('Registration updated', `Status set to ${status}`);\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? 'Loading registrations…' : `Registrations (${total})`}\n </h2>\n {items.length === 0 && !loading ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No registrations yet.\"\n description=\"Register your first attendee to start tracking sign-ups.\"\n className=\"mt-4\"\n />\n ) : (\n <ul className=\"mt-4 space-y-2\">\n {items.map((row) => {\n const statusItems: ActionMenuItem[] = REGISTRATION_STATUSES.filter(\n (candidate) => candidate !== (row.status ?? '').toUpperCase()\n ).map((candidate, index) => ({\n key: candidate,\n label: `Set ${candidate.replace('_', ' ').toLowerCase()}`,\n onSelect: () => void changeStatus(row, candidate),\n separatorBefore: index === 0,\n }));\n return (\n <li\n key={row.id}\n className=\"flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {attendeeLabel(row.attendeeId) ?? `Registration ${row.id}`}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[`Qty ${row.quantity}`, row.totalAmount ? `${row.totalAmount} ${row.currency}` : undefined]\n .filter(Boolean)\n .join(' • ')}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Badge variant={statusVariant(row.status)}>{row.status ?? 'UNKNOWN'}</Badge>\n <ActionMenu ariaLabel=\"Registration actions\" extraItems={statusItems} />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"registrations\"\n loading={loading}\n />\n </GlassCard>\n );\n}\n\nfunction AttendeesSection() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = useAttendeesQuery({ limit: pagination.limit, offset: pagination.offset });\n\n const items = useMemo<AttendeeGql[]>(() => data?.attendees.items ?? [], [data]);\n const total = data?.attendees.total ?? 0;\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? 'Loading attendees…' : `Attendees (${total})`}\n </h2>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => navigate('/registration/attendees/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Add attendee\n </Button>\n </div>\n {items.length === 0 && !loading ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No attendees yet.\"\n description=\"Add attendees or register one to build your guest list.\"\n className=\"mt-4\"\n />\n ) : (\n <ul className=\"mt-4 space-y-2\">\n {items.map((row) => (\n <li\n key={row.id}\n className=\"flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{row.fullName}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[row.email, row.company, row.role].filter(Boolean).join(' • ')}\n </p>\n </div>\n <ActionMenu\n ariaLabel={`Actions for ${row.fullName}`}\n onView={() => navigate(`/attendees/${row.id}/profile`)}\n onEdit={() => navigate(`/registration/attendees/${row.id}/edit`)}\n />\n </li>\n ))}\n </ul>\n )}\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"attendees\"\n loading={loading}\n />\n </GlassCard>\n );\n}\n\nfunction PromoCodesSection() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = usePromoCodesQuery({ limit: pagination.limit, offset: pagination.offset });\n const [deletePromoCode] = useDeletePromoCodeMutation();\n\n const items = useMemo<PromoCodeGql[]>(() => data?.promoCodes.items ?? [], [data]);\n const total = data?.promoCodes.total ?? 0;\n\n const onDelete = async (row: PromoCodeGql) => {\n const response = await deletePromoCode({ variables: { id: row.id } });\n if (response.error) {\n notify.error('Could not delete promo code', response.error.message);\n throw response.error;\n }\n notify.success('Promo code deleted', row.code);\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? 'Loading promo codes…' : `Promo Codes (${total})`}\n </h2>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => navigate('/registration/promo-codes/create')}>\n <TicketPlus className=\"h-4 w-4\" />\n New promo code\n </Button>\n </div>\n {items.length === 0 && !loading ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No promo codes yet.\"\n description=\"Create a code to offer discounts and drive registrations.\"\n className=\"mt-4\"\n />\n ) : (\n <ul className=\"mt-4 space-y-2\">\n {items.map((row) => (\n <li\n key={row.id}\n className=\"flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{row.code}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {row.discountType === 'PERCENT' ? `${row.discountValue}% off` : `${row.discountValue} off`}\n {row.maxUses != null ? ` • ${row.usedCount}/${row.maxUses} used` : ` • ${row.usedCount} used`}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Badge variant={row.isActive ? 'default' : 'secondary'}>{row.isActive ? 'Active' : 'Inactive'}</Badge>\n <ActionMenu\n ariaLabel={`Actions for ${row.code}`}\n onEdit={() => navigate(`/registration/promo-codes/${row.id}/edit`)}\n onDelete={() => onDelete(row)}\n entityName={row.code}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"promo codes\"\n loading={loading}\n />\n </GlassCard>\n );\n}\n\nexport default function RegistrationPage() {\n const { navigate } = useEventfullyManaged();\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n This is where you see who is coming. Registrations are the paid or confirmed sign-ups, attendees are the people\n behind them, and promo codes let you offer discounts. Register attendees, edit their details, change a\n registration's status, and manage discount codes — all backed by live GraphQL APIs.\n </PagePurpose>\n\n <EmphasisPanel>\n <div 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>Registration</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Track registrations, attendees, and promo codes with backend persistence.\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" onClick={() => navigate('/registration/register')}>\n <TicketPlus className=\"h-4 w-4\" />\n Register attendee\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration/attendees/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Add attendee\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration/promo-codes/create')}>\n <Tag className=\"h-4 w-4\" />\n New promo code\n </Button>\n </div>\n </div>\n </EmphasisPanel>\n\n <RegistrationsSection />\n <AttendeesSection />\n <PromoCodesSection />\n\n <NextSteps\n storageKey=\"eventfullymanaged-registration\"\n title=\"Grow your registrations\"\n steps={[\n {\n id: 'view-networking',\n label: 'Set up attendee networking',\n description: 'Turn registered attendees into connections with matchmaking.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'promote',\n label: 'Promote with a campaign',\n description: 'Send reminders and offers to drive more sign-ups.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n {\n id: 'analyze',\n label: 'Snapshot your numbers',\n description: 'Capture an analytics snapshot to track registration trends.',\n icon: <Tag className=\"h-4 w-4\" />,\n onClick: () => navigate('/analytics'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoadmapPanel.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/RoadmapPanel.tsx"],"sourcesContent":["import { CircleDot, Clock, Compass, Sparkles } from 'lucide-react';\nimport { Badge, EmphasisPanel, GlassCard, GradientText, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nexport interface RoadmapItem {\n id: string;\n title: string;\n description: string;\n /** Delivery stage — controls the badge styling only. */\n stage: 'exploring' | 'planned' | 'in-progress';\n}\n\nexport interface RoadmapPanelProps {\n /** Area name, e.g. \"API Access\" or \"Integrations\". */\n title: string;\n /** One-line honest summary of what this area is (and is not) today. */\n summary: string;\n /** Longer honest note explaining the current state. */\n purpose: string;\n items: RoadmapItem[];\n}\n\nconst STAGE_META: Record
|
|
1
|
+
{"version":3,"file":"RoadmapPanel.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/RoadmapPanel.tsx"],"sourcesContent":["import { CircleDot, Clock, Compass, Sparkles } from 'lucide-react';\nimport { Badge, EmphasisPanel, GlassCard, GradientText, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nexport interface RoadmapItem {\n id: string;\n title: string;\n description: string;\n /** Delivery stage — controls the badge styling only. */\n stage: 'exploring' | 'planned' | 'in-progress';\n}\n\nexport interface RoadmapPanelProps {\n /** Area name, e.g. \"API Access\" or \"Integrations\". */\n title: string;\n /** One-line honest summary of what this area is (and is not) today. */\n summary: string;\n /** Longer honest note explaining the current state. */\n purpose: string;\n items: RoadmapItem[];\n}\n\nconst STAGE_META: Record<\n RoadmapItem['stage'],\n { label: string; variant: 'info' | 'warning' | 'secondary'; Icon: typeof Clock }\n> = {\n exploring: { label: 'Exploring', variant: 'secondary', Icon: Compass },\n planned: { label: 'Planned', variant: 'info', Icon: Clock },\n 'in-progress': { label: 'In progress', variant: 'warning', Icon: CircleDot },\n};\n\n/**\n * Honest \"coming soon\" / roadmap surface for EventfullyManaged areas that do not\n * yet have a real backend feature behind them. It deliberately renders NO live\n * data and makes no functional claims — it only communicates intent and stage,\n * so the app never mislabels a generic entity dashboard as a shipped capability.\n */\nexport default function RoadmapPanel({ title, summary, purpose, items }: RoadmapPanelProps) {\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>{purpose}</PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex items-center gap-2\">\n <Sparkles className=\"h-5 w-5 text-action-primary-bg\" aria-hidden=\"true\" />\n <Badge variant=\"secondary\" className=\"uppercase\">\n Coming soon\n </Badge>\n </div>\n <h1 className=\"mt-3 text-2xl font-bold text-text-primary\">\n <GradientText>{title}</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">{summary}</p>\n </EmphasisPanel>\n\n <section className=\"space-y-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">On the roadmap</h2>\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {items.map((item) => {\n const meta = STAGE_META[item.stage];\n const StageIcon = meta.Icon;\n return (\n <GlassCard key={item.id} treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <h3 className=\"text-base font-semibold text-text-primary\">{item.title}</h3>\n <Badge variant={meta.variant} className=\"shrink-0 gap-1.5 uppercase\">\n <StageIcon className=\"h-3 w-3\" aria-hidden=\"true\" />\n {meta.label}\n </Badge>\n </div>\n <p className=\"mt-2 text-sm text-text-secondary\">{item.description}</p>\n </GlassCard>\n );\n })}\n </div>\n </section>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <p className=\"text-sm text-text-secondary\">\n Nothing here is live yet. When these capabilities ship they will appear in this module with real data and\n controls — until then this page is an honest placeholder rather than a mock.\n </p>\n </GlassCard>\n </div>\n );\n}\n"],"mappings":";;;;AAqBA,IAAM,IAGF;CACF,WAAW;EAAE,OAAO;EAAa,SAAS;EAAa,MAAM;EAAS;CACtE,SAAS;EAAE,OAAO;EAAW,SAAS;EAAQ,MAAM;EAAO;CAC3D,eAAe;EAAE,OAAO;EAAe,SAAS;EAAW,MAAM;EAAW;CAC7E;AAQD,SAAwB,EAAa,EAAE,UAAO,YAAS,YAAS,YAA4B;AAC1F,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAA,UAAc,GAAsB,CAAA;GAEpC,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAU,WAAU;MAAiC,eAAY;MAAS,CAAA,EAC1E,kBAAC,GAAD;MAAO,SAAQ;MAAY,WAAU;gBAAY;MAEzC,CAAA,CACJ;;IACN,kBAAC,MAAD;KAAI,WAAU;eACZ,kBAAC,GAAD,EAAA,UAAe,GAAqB,CAAA;KACjC,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eAAoC;KAAY,CAAA;IAC/C,EAAA,CAAA;GAEhB,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,MAAD;KAAI,WAAU;eAA0C;KAAmB,CAAA,EAC3E,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAM,KAAK,MAAS;MACnB,IAAM,IAAO,EAAW,EAAK,QACvB,IAAY,EAAK;AACvB,aACE,kBAAC,GAAD;OAAyB,WAAU;OAAQ,WAAU;iBAArD,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6C,EAAK;SAAW,CAAA,EAC3E,kBAAC,GAAD;SAAO,SAAS,EAAK;SAAS,WAAU;mBAAxC,CACE,kBAAC,GAAD;UAAW,WAAU;UAAU,eAAY;UAAS,CAAA,EACnD,EAAK,MACA;WACJ;WACN,kBAAC,KAAD;QAAG,WAAU;kBAAoC,EAAK;QAAgB,CAAA,CAC5D;SATI,EAAK,GAST;OAEd;KACE,CAAA,CACE;;GAEV,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cACrC,kBAAC,KAAD;KAAG,WAAU;eAA8B;KAGvC,CAAA;IACM,CAAA;GACR"}
|