@vicacha-devs/strapi-plugin-events 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +121 -2
  3. package/dist/admin/CalendarPage-CwCvPDRx.js +1215 -0
  4. package/dist/admin/CalendarPage-Dz7g19qp.mjs +1215 -0
  5. package/dist/admin/RRuleInput-BQl-siRj.js +11 -0
  6. package/dist/admin/RRuleInput-C8bR37X9.mjs +6 -0
  7. package/dist/admin/SettingsPage-CNG9AEO_.mjs +415 -0
  8. package/dist/admin/SettingsPage-DhjtKDmY.js +415 -0
  9. package/dist/admin/ar-mMKUXxch.js +216 -0
  10. package/dist/admin/ar-tWKEvJLB.mjs +216 -0
  11. package/dist/admin/de-CGk8UZ3W.js +216 -0
  12. package/dist/admin/de-OdbJEFs0.mjs +216 -0
  13. package/dist/admin/en-Dskn9ZPa.mjs +216 -0
  14. package/dist/admin/en-imdSlLhJ.js +216 -0
  15. package/dist/admin/es-DcMsMHrI.mjs +216 -0
  16. package/dist/admin/es-DkqqxUJQ.js +216 -0
  17. package/dist/admin/fr-BTEIPOFw.mjs +216 -0
  18. package/dist/admin/fr-D92B_ZTv.js +216 -0
  19. package/dist/admin/index-BTq_X4uT.js +1005 -0
  20. package/dist/admin/index-BatOiwBu.mjs +1006 -0
  21. package/dist/admin/index.js +6 -0
  22. package/dist/admin/index.mjs +6 -0
  23. package/dist/admin/it-CAxPpL_9.mjs +216 -0
  24. package/dist/admin/it-ClKaSCgv.js +216 -0
  25. package/dist/admin/nl-CS1aUfT7.mjs +216 -0
  26. package/dist/admin/nl-F6FIi7zw.js +216 -0
  27. package/dist/admin/pl-BfmBU9FW.mjs +216 -0
  28. package/dist/admin/pl-p8VLXi02.js +216 -0
  29. package/dist/admin/pt-BUdH0uSw.js +216 -0
  30. package/dist/admin/pt-DdulKcbk.mjs +216 -0
  31. package/dist/admin/src/components/EventSidePanel.d.ts +13 -0
  32. package/dist/admin/src/components/Initializer.d.ts +5 -0
  33. package/dist/admin/src/components/PluginIcon.d.ts +2 -0
  34. package/dist/admin/src/components/RRuleInput.d.ts +3 -0
  35. package/dist/admin/src/components/events/AttendeesManagerPanel.d.ts +15 -0
  36. package/dist/admin/src/components/events/EventsDirectoryPanel.d.ts +11 -0
  37. package/dist/admin/src/components/events/EventsKpiCards.d.ts +16 -0
  38. package/dist/admin/src/components/events/QuickCreateEventPanel.d.ts +21 -0
  39. package/dist/admin/src/components/events/calendarPageHelpers.d.ts +8 -0
  40. package/dist/admin/src/fields/rruleField.d.ts +41 -0
  41. package/dist/admin/src/index.d.ts +15 -0
  42. package/dist/admin/src/pages/CalendarPage.d.ts +3 -0
  43. package/dist/admin/src/pages/SettingsPage.d.ts +3 -0
  44. package/dist/admin/src/permissions.d.ts +15 -0
  45. package/dist/admin/src/translations/cmLabels.d.ts +1 -0
  46. package/dist/admin/src/translations/cmPlaceholders.d.ts +1 -0
  47. package/dist/admin/tr-Bp50qvL8.mjs +216 -0
  48. package/dist/admin/tr-C7G9mPtg.js +216 -0
  49. package/dist/server/index.js +3709 -0
  50. package/dist/server/index.mjs +3705 -0
  51. package/dist/server/src/bootstrap.d.ts +5 -0
  52. package/dist/server/src/config/index.d.ts +7 -0
  53. package/dist/server/src/content-types/event/schema.json.d.ts +279 -0
  54. package/dist/server/src/content-types/index.d.ts +366 -0
  55. package/dist/server/src/content-types/registration/schema.json.d.ts +86 -0
  56. package/dist/server/src/controllers/admin.d.ts +17 -0
  57. package/dist/server/src/controllers/content-api.d.ts +14 -0
  58. package/dist/server/src/controllers/index.d.ts +28 -0
  59. package/dist/server/src/destroy.d.ts +5 -0
  60. package/dist/server/src/index.d.ts +551 -0
  61. package/dist/server/src/middlewares/index.d.ts +2 -0
  62. package/dist/server/src/policies/index.d.ts +2 -0
  63. package/dist/server/src/register.d.ts +5 -0
  64. package/dist/server/src/routes/admin.d.ts +17 -0
  65. package/dist/server/src/routes/content-api.d.ts +12 -0
  66. package/dist/server/src/routes/index.d.ts +30 -0
  67. package/dist/server/src/services/ical.d.ts +18 -0
  68. package/dist/server/src/services/index.d.ts +110 -0
  69. package/dist/server/src/services/occurrence.d.ts +44 -0
  70. package/dist/server/src/services/occurrenceHelpers.d.ts +16 -0
  71. package/dist/server/src/services/occurrencePayloadHelpers.d.ts +30 -0
  72. package/dist/server/src/services/occurrenceRsvpBatch.d.ts +25 -0
  73. package/dist/server/src/services/rsvp.d.ts +51 -0
  74. package/dist/server/src/services/rsvpHelpers.d.ts +52 -0
  75. package/dist/server/src/services/rsvpMutations.d.ts +34 -0
  76. package/dist/server/src/services/rsvpSecurity.d.ts +22 -0
  77. package/dist/server/src/services/settings.d.ts +10 -0
  78. package/dist/server/src/types.d.ts +171 -0
  79. package/dist/shared/calendarSystems.d.ts +6 -0
  80. package/dist/shared/icalGenerator.d.ts +25 -0
  81. package/dist/shared/index.d.ts +6 -0
  82. package/dist/shared/index.js +753 -0
  83. package/dist/shared/index.mjs +727 -0
  84. package/dist/shared/pluginId.d.ts +4 -0
  85. package/dist/shared/rruleEngine.d.ts +20 -0
  86. package/dist/shared/spotlightHelpers.d.ts +17 -0
  87. package/dist/shared/types.d.ts +309 -0
  88. package/dist/shared/urlAndPosterHelpers.d.ts +18 -0
  89. package/package.json +109 -3
@@ -0,0 +1,1215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const designSystem = require("@strapi/design-system");
5
+ const admin = require("@strapi/strapi/admin");
6
+ const react = require("react");
7
+ const reactIntl = require("react-intl");
8
+ const reactRouterDom = require("react-router-dom");
9
+ const index = require("./index-BTq_X4uT.js");
10
+ require("@vicacha-devs/strapi-plugin-calendar/shared");
11
+ const STATUS_BADGE_COLORS = {
12
+ checked_in: { bg: "success100", text: "success700" },
13
+ confirmed: { bg: "primary100", text: "primary700" },
14
+ waitlisted: { bg: "warning100", text: "warning700" },
15
+ cancelled: { bg: "danger100", text: "danger700" }
16
+ };
17
+ function getStatusBadgeColors(status) {
18
+ return Object.hasOwn(STATUS_BADGE_COLORS, status) ? STATUS_BADGE_COLORS[status] : STATUS_BADGE_COLORS.cancelled;
19
+ }
20
+ const AttendeeRegistrationRow = ({
21
+ reg,
22
+ t,
23
+ onCheckIn,
24
+ onStatusChange
25
+ }) => {
26
+ const colors = getStatusBadgeColors(reg.status);
27
+ const seatWord = reg.guestsCount === 1 ? t("attendees.seatSingular", "seat") : t("attendees.seatPlural", "seats");
28
+ const eventDisplay = reg.eventTitle || reg.eventDocumentId;
29
+ const phoneSuffix = reg.attendeePhone ? ` • ${reg.attendeePhone}` : "";
30
+ const canCheckIn = reg.status === "confirmed";
31
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 3, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
32
+ designSystem.Flex,
33
+ {
34
+ justifyContent: "space-between",
35
+ alignItems: "center",
36
+ wrap: "wrap",
37
+ gap: 3,
38
+ children: [
39
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
40
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
41
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: reg.attendeeName }),
42
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
43
+ "(",
44
+ reg.attendeeEmail,
45
+ ")"
46
+ ] }),
47
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
48
+ reg.guestsCount,
49
+ " ",
50
+ seatWord
51
+ ] }),
52
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: colors.bg, textColor: colors.text, children: t(`status.${reg.status}`, reg.status.toUpperCase()) })
53
+ ] }),
54
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
55
+ eventDisplay,
56
+ " • ",
57
+ reg.occurrenceDate,
58
+ phoneSuffix
59
+ ] }) })
60
+ ] }),
61
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "flex-end", children: [
62
+ canCheckIn && /* @__PURE__ */ jsxRuntime.jsxs(
63
+ designSystem.Button,
64
+ {
65
+ size: "S",
66
+ variant: "default",
67
+ onClick: () => onCheckIn(reg.documentId),
68
+ children: [
69
+ "✓ ",
70
+ t("attendees.checkIn", "Check In")
71
+ ]
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: 155 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
75
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("attendees.rowStatus", "Registration Status") }),
76
+ /* @__PURE__ */ jsxRuntime.jsxs(
77
+ designSystem.SingleSelect,
78
+ {
79
+ placeholder: t("placeholder.select_status", "Select status"),
80
+ value: reg.status,
81
+ onChange: (nextSt) => onStatusChange(reg.documentId, nextSt),
82
+ children: [
83
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "confirmed", children: t("status.confirmed", "Confirmed") }),
84
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "checked_in", children: t("status.checked_in", "Checked In") }),
85
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "waitlisted", children: t("status.waitlisted", "Waitlisted") }),
86
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "cancelled", children: t("status.cancelled", "Cancelled") })
87
+ ]
88
+ }
89
+ )
90
+ ] }) })
91
+ ] })
92
+ ]
93
+ }
94
+ ) });
95
+ };
96
+ const AttendeesManagerPanel = ({
97
+ t,
98
+ registrations,
99
+ rsvpSearch,
100
+ onRsvpSearchChange,
101
+ rsvpStatusFilter,
102
+ onRsvpStatusFilterChange,
103
+ rsvpEventFilter,
104
+ onClearRsvpEventFilter,
105
+ onCheckIn,
106
+ onStatusChange
107
+ }) => {
108
+ const { get } = admin.useFetchClient();
109
+ const { toggleNotification } = admin.useNotification();
110
+ const [exportingCsv, setExportingCsv] = react.useState(false);
111
+ const handleExportCsv = react.useCallback(async () => {
112
+ setExportingCsv(true);
113
+ try {
114
+ const params = new URLSearchParams();
115
+ if (rsvpEventFilter.trim()) {
116
+ params.set("eventDocumentId", rsvpEventFilter.trim());
117
+ }
118
+ if (rsvpStatusFilter && rsvpStatusFilter !== "all") {
119
+ params.set("status", rsvpStatusFilter);
120
+ }
121
+ const qs = params.toString();
122
+ const suffix = qs ? `?${qs}` : "";
123
+ const res = await get(
124
+ `/${index.PLUGIN_ID}/registrations/export.csv${suffix}`
125
+ );
126
+ const csvText = typeof res?.data === "string" ? res.data : String(res?.data ?? "");
127
+ const blob = new Blob([csvText], { type: "text/csv;charset=utf-8;" });
128
+ const blobUrl = URL.createObjectURL(blob);
129
+ const anchor = document.createElement("a");
130
+ anchor.href = blobUrl;
131
+ anchor.setAttribute("download", "event-registrations.csv");
132
+ document.body.appendChild(anchor);
133
+ anchor.click();
134
+ document.body.removeChild(anchor);
135
+ URL.revokeObjectURL(blobUrl);
136
+ } catch {
137
+ toggleNotification({
138
+ type: "danger",
139
+ message: t("attendees.error.load", "Could not load registrations.")
140
+ });
141
+ } finally {
142
+ setExportingCsv(false);
143
+ }
144
+ }, [get, rsvpEventFilter, rsvpStatusFilter, t, toggleNotification]);
145
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
146
+ /* @__PURE__ */ jsxRuntime.jsxs(
147
+ designSystem.Flex,
148
+ {
149
+ justifyContent: "space-between",
150
+ alignItems: "flex-end",
151
+ wrap: "wrap",
152
+ gap: 3,
153
+ children: [
154
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, wrap: "wrap", alignItems: "flex-end", children: [
155
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: 240 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
156
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("attendees.search", "Search Attendee or Event") }),
157
+ /* @__PURE__ */ jsxRuntime.jsx(
158
+ designSystem.TextInput,
159
+ {
160
+ value: rsvpSearch,
161
+ onChange: (e) => onRsvpSearchChange(e.target.value),
162
+ placeholder: t(
163
+ "placeholder.name_email_or_event_title",
164
+ "Name, email, or event title..."
165
+ )
166
+ }
167
+ )
168
+ ] }) }),
169
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: 160 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
170
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("attendees.statusFilter", "Status") }),
171
+ /* @__PURE__ */ jsxRuntime.jsxs(
172
+ designSystem.SingleSelect,
173
+ {
174
+ placeholder: t("placeholder.all_statuses", "All Statuses"),
175
+ value: rsvpStatusFilter,
176
+ onChange: (val) => onRsvpStatusFilterChange(val),
177
+ children: [
178
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "all", children: t("placeholder.all_statuses", "All Statuses") }),
179
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "confirmed", children: t("status.confirmed", "Confirmed") }),
180
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "checked_in", children: t("status.checked_in", "Checked In") }),
181
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "waitlisted", children: t("status.waitlisted", "Waitlisted") }),
182
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "cancelled", children: t("status.cancelled", "Cancelled") })
183
+ ]
184
+ }
185
+ )
186
+ ] }) }),
187
+ rsvpEventFilter && /* @__PURE__ */ jsxRuntime.jsxs(
188
+ designSystem.Button,
189
+ {
190
+ size: "S",
191
+ variant: "tertiary",
192
+ onClick: onClearRsvpEventFilter,
193
+ children: [
194
+ t("attendees.clearEventFilter", "Clear Event Filter"),
195
+ " (",
196
+ rsvpEventFilter,
197
+ ")"
198
+ ]
199
+ }
200
+ )
201
+ ] }),
202
+ /* @__PURE__ */ jsxRuntime.jsx(
203
+ designSystem.Button,
204
+ {
205
+ size: "S",
206
+ variant: "secondary",
207
+ loading: exportingCsv,
208
+ onClick: handleExportCsv,
209
+ children: t("attendees.exportCsv", "Export CSV")
210
+ }
211
+ )
212
+ ]
213
+ }
214
+ ),
215
+ registrations.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: t(
216
+ "attendees.empty",
217
+ "No registrations found matching current filters."
218
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: registrations.map((reg) => /* @__PURE__ */ jsxRuntime.jsx(
219
+ AttendeeRegistrationRow,
220
+ {
221
+ reg,
222
+ t,
223
+ onCheckIn,
224
+ onStatusChange
225
+ },
226
+ reg.documentId
227
+ )) })
228
+ ] }) });
229
+ };
230
+ const DEFAULT_RECURRENCE_PRESETS = [
231
+ { id: "daily", label: "Daily", rrule: "FREQ=DAILY;INTERVAL=1" },
232
+ {
233
+ id: "weekly-mon",
234
+ label: "Weekly on Mondays",
235
+ rrule: "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO"
236
+ },
237
+ {
238
+ id: "weekly-tue",
239
+ label: "Weekly on Tuesdays",
240
+ rrule: "FREQ=WEEKLY;INTERVAL=1;BYDAY=TU"
241
+ },
242
+ {
243
+ id: "weekly-wed",
244
+ label: "Weekly on Wednesdays",
245
+ rrule: "FREQ=WEEKLY;INTERVAL=1;BYDAY=WE"
246
+ },
247
+ {
248
+ id: "weekly-thu",
249
+ label: "Weekly on Thursdays",
250
+ rrule: "FREQ=WEEKLY;INTERVAL=1;BYDAY=TH"
251
+ },
252
+ {
253
+ id: "weekly-fri",
254
+ label: "Weekly on Fridays",
255
+ rrule: "FREQ=WEEKLY;INTERVAL=1;BYDAY=FR"
256
+ },
257
+ {
258
+ id: "weekly-sat",
259
+ label: "Weekly on Saturdays",
260
+ rrule: "FREQ=WEEKLY;INTERVAL=1;BYDAY=SA"
261
+ },
262
+ {
263
+ id: "weekly-sun",
264
+ label: "Weekly on Sundays",
265
+ rrule: "FREQ=WEEKLY;INTERVAL=1;BYDAY=SU"
266
+ },
267
+ { id: "monthly", label: "Monthly", rrule: "FREQ=MONTHLY;INTERVAL=1" },
268
+ { id: "yearly", label: "Yearly", rrule: "FREQ=YEARLY;INTERVAL=1" }
269
+ ];
270
+ function formatCategoryLabel(category) {
271
+ const trimmed = category.trim();
272
+ if (!trimmed) return "";
273
+ return trimmed.split(/[\s_-]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
274
+ }
275
+ const EVENT_STATUS_COLORS = {
276
+ scheduled: { bg: "success100", text: "success700" },
277
+ cancelled: { bg: "danger100", text: "danger700" }
278
+ };
279
+ function getEventStatusColors(status) {
280
+ return EVENT_STATUS_COLORS[status] ?? { bg: "warning100", text: "warning700" };
281
+ }
282
+ function matchesCategoryFilter(ev, categoryFilter) {
283
+ if (categoryFilter === "all") return true;
284
+ return (ev.category ?? "").toLowerCase() === categoryFilter.toLowerCase();
285
+ }
286
+ function matchesRsvpModeFilter(ev, rsvpModeFilter) {
287
+ const enabled = Boolean(ev.rsvp?.rsvpEnabled);
288
+ if (rsvpModeFilter === "rsvp") return enabled;
289
+ if (rsvpModeFilter === "info") return !enabled;
290
+ return true;
291
+ }
292
+ function matchesSearchQuery(ev, query) {
293
+ if (!query) return true;
294
+ const haystack = [ev.title, ev.location, ev.category, ev.summary].filter(Boolean).join(" ").toLowerCase();
295
+ return haystack.includes(query);
296
+ }
297
+ function formatEventDateLabel(ev) {
298
+ const endKey = ev.endDate ? ev.endDate.slice(0, 10) : "";
299
+ return endKey && endKey !== ev.occurrenceDate ? `${ev.occurrenceDate} → ${endKey}` : ev.occurrenceDate;
300
+ }
301
+ const EventRsvpCell = ({ rsvp, t }) => {
302
+ if (!rsvp?.rsvpEnabled) {
303
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: t("events.directory.noRsvp", "Info / External Link") });
304
+ }
305
+ const badgeBg = rsvp.isFull ? "warning100" : "primary100";
306
+ const badgeText = rsvp.isFull ? "warning700" : "primary700";
307
+ const seatsLabel = rsvp.remainingSeats !== null ? `${rsvp.remainingSeats} ${t("events.directory.seatsLeft", "seats available")}` : t("sidePanel.open", "Open");
308
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
309
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: badgeBg, textColor: badgeText, children: [
310
+ rsvp.confirmedSeats,
311
+ " / ",
312
+ rsvp.capacity ?? "∞",
313
+ " ",
314
+ t("sidePanel.confirmedSeats", "Confirmed Seats")
315
+ ] }),
316
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: seatsLabel })
317
+ ] });
318
+ };
319
+ const EventDirectoryRow = ({
320
+ ev,
321
+ isSelectedForRsvp,
322
+ t,
323
+ onSelectEventDocId,
324
+ onEditInContentManager,
325
+ onOpenInCalendar
326
+ }) => {
327
+ const dateLabel = formatEventDateLabel(ev);
328
+ const statusColors = getEventStatusColors(ev.status);
329
+ const rsvpEnabled = Boolean(ev.rsvp?.rsvpEnabled);
330
+ const rsvpBtnVariant = isSelectedForRsvp ? "default" : "secondary";
331
+ const rsvpBtnLabel = isSelectedForRsvp ? t("attendees.clearEventFilter", "Clear Filter") : t("events.directory.filterRsvps", "View RSVPs");
332
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
333
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
334
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
335
+ /* @__PURE__ */ jsxRuntime.jsx(
336
+ designSystem.Box,
337
+ {
338
+ style: {
339
+ width: 10,
340
+ height: 10,
341
+ borderRadius: "50%",
342
+ background: ev.color || "#0D9488",
343
+ flexShrink: 0
344
+ }
345
+ }
346
+ ),
347
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: ev.title }),
348
+ ev.isRecurring && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: t("events.directory.recurring", "Recurring") })
349
+ ] }),
350
+ ev.summary && /* @__PURE__ */ jsxRuntime.jsx(
351
+ designSystem.Typography,
352
+ {
353
+ variant: "pi",
354
+ textColor: "neutral600",
355
+ style: {
356
+ maxWidth: 360,
357
+ whiteSpace: "nowrap",
358
+ overflow: "hidden",
359
+ textOverflow: "ellipsis"
360
+ },
361
+ children: ev.summary
362
+ }
363
+ )
364
+ ] }) }),
365
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: ev.category ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: formatCategoryLabel(ev.category) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "—" }) }),
366
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
367
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: dateLabel }),
368
+ /* @__PURE__ */ jsxRuntime.jsx(
369
+ designSystem.Badge,
370
+ {
371
+ backgroundColor: statusColors.bg,
372
+ textColor: statusColors.text,
373
+ children: t(`status.${ev.status}`, ev.status.toUpperCase())
374
+ }
375
+ )
376
+ ] }) }),
377
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral700", children: ev.location || "—" }) }),
378
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(EventRsvpCell, { rsvp: ev.rsvp, t }) }),
379
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", children: [
380
+ rsvpEnabled && /* @__PURE__ */ jsxRuntime.jsx(
381
+ designSystem.Button,
382
+ {
383
+ size: "S",
384
+ variant: rsvpBtnVariant,
385
+ onClick: () => onSelectEventDocId(isSelectedForRsvp ? "" : ev.documentId),
386
+ children: rsvpBtnLabel
387
+ }
388
+ ),
389
+ /* @__PURE__ */ jsxRuntime.jsx(
390
+ designSystem.Button,
391
+ {
392
+ size: "S",
393
+ variant: "tertiary",
394
+ onClick: () => onEditInContentManager(ev.uid, ev.documentId, ev.locale),
395
+ children: t("events.directory.editEvent", "Edit Event")
396
+ }
397
+ ),
398
+ /* @__PURE__ */ jsxRuntime.jsx(
399
+ designSystem.Button,
400
+ {
401
+ size: "S",
402
+ variant: "tertiary",
403
+ onClick: () => onOpenInCalendar(ev.documentId),
404
+ children: t("calendar.openUnifiedCalendar", "Calendar")
405
+ }
406
+ )
407
+ ] }) })
408
+ ] });
409
+ };
410
+ const EventsDirectoryPanel = ({
411
+ t,
412
+ events,
413
+ selectedEventDocId,
414
+ onSelectEventDocId,
415
+ onEditInContentManager,
416
+ onOpenInCalendar
417
+ }) => {
418
+ const [eventSearch, setEventSearch] = react.useState("");
419
+ const [categoryFilter, setCategoryFilter] = react.useState("all");
420
+ const [rsvpModeFilter, setRsvpModeFilter] = react.useState("all");
421
+ const categories = react.useMemo(() => {
422
+ const set = /* @__PURE__ */ new Set();
423
+ for (const ev of events) {
424
+ if (ev.category) set.add(ev.category);
425
+ }
426
+ return Array.from(set);
427
+ }, [events]);
428
+ const filteredEvents = react.useMemo(() => {
429
+ const q = eventSearch.trim().toLowerCase();
430
+ return events.filter(
431
+ (ev) => matchesCategoryFilter(ev, categoryFilter) && matchesRsvpModeFilter(ev, rsvpModeFilter) && matchesSearchQuery(ev, q)
432
+ );
433
+ }, [events, eventSearch, categoryFilter, rsvpModeFilter]);
434
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
435
+ /* @__PURE__ */ jsxRuntime.jsxs(
436
+ designSystem.Flex,
437
+ {
438
+ justifyContent: "space-between",
439
+ alignItems: "flex-end",
440
+ wrap: "wrap",
441
+ gap: 3,
442
+ children: [
443
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
444
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
445
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", fontWeight: "bold", children: t("events.directory.title", "Registered Events") }),
446
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: filteredEvents.length })
447
+ ] }),
448
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t(
449
+ "events.directory.subtitle",
450
+ "All scheduled and recurring events across your configured sources, including RSVP seat capacity and Content Manager links."
451
+ ) }) })
452
+ ] }),
453
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, wrap: "wrap", alignItems: "flex-end", children: [
454
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: 220 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
455
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("events.directory.search", "Search Events") }),
456
+ /* @__PURE__ */ jsxRuntime.jsx(
457
+ designSystem.TextInput,
458
+ {
459
+ value: eventSearch,
460
+ onChange: (e) => setEventSearch(e.target.value),
461
+ placeholder: t(
462
+ "events.directory.searchPlaceholder",
463
+ "Title, location, category..."
464
+ )
465
+ }
466
+ )
467
+ ] }) }),
468
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: 160 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
469
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("calendar.categoryFilter", "Category") }),
470
+ /* @__PURE__ */ jsxRuntime.jsxs(
471
+ designSystem.SingleSelect,
472
+ {
473
+ value: categoryFilter,
474
+ onChange: (val) => setCategoryFilter(String(val)),
475
+ children: [
476
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "all", children: t("calendar.allCategories", "All Categories") }),
477
+ categories.map((cat) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: cat, children: formatCategoryLabel(cat) }, cat))
478
+ ]
479
+ }
480
+ )
481
+ ] }) }),
482
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: 160 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
483
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("events.directory.rsvpFilter", "RSVP Mode") }),
484
+ /* @__PURE__ */ jsxRuntime.jsxs(
485
+ designSystem.SingleSelect,
486
+ {
487
+ value: rsvpModeFilter,
488
+ onChange: (val) => setRsvpModeFilter(String(val)),
489
+ children: [
490
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "all", children: t("events.directory.allEvents", "All Events") }),
491
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "rsvp", children: t("events.directory.rsvpOnly", "With RSVPs Enabled") }),
492
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "info", children: t("events.directory.infoOnly", "Informational Only") })
493
+ ]
494
+ }
495
+ )
496
+ ] }) })
497
+ ] })
498
+ ]
499
+ }
500
+ ),
501
+ filteredEvents.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: t(
502
+ "events.directory.empty",
503
+ "No events found matching the current filters."
504
+ ) }) }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 6, rowCount: filteredEvents.length + 1, children: [
505
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
506
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("calendar.field.title", "Event Title") }) }),
507
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("calendar.categoryFilter", "Category") }) }),
508
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("calendar.field.startDate", "Start Date") }) }),
509
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("calendar.field.location", "Location") }) }),
510
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("sidePanel.rsvpStatus", "RSVP & Capacity") }) }),
511
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("events.directory.actions", "Actions") }) })
512
+ ] }) }),
513
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: filteredEvents.map((ev) => /* @__PURE__ */ jsxRuntime.jsx(
514
+ EventDirectoryRow,
515
+ {
516
+ ev,
517
+ isSelectedForRsvp: selectedEventDocId === ev.documentId,
518
+ t,
519
+ onSelectEventDocId,
520
+ onEditInContentManager,
521
+ onOpenInCalendar
522
+ },
523
+ `${ev.uid}:${ev.documentId}`
524
+ )) })
525
+ ] })
526
+ ] }) });
527
+ };
528
+ function computeDashboardRsvpStats(uniqueEvents, registrations) {
529
+ let confirmedSeats = 0;
530
+ let checkedInSeats = 0;
531
+ let waitlistedSeats = 0;
532
+ const eventsSet = /* @__PURE__ */ new Set();
533
+ for (const ev of uniqueEvents) {
534
+ if (ev.rsvp?.rsvpEnabled) {
535
+ eventsSet.add(ev.documentId);
536
+ }
537
+ }
538
+ for (const reg of registrations) {
539
+ const seats = Number(reg.guestsCount) || 1;
540
+ if (reg.eventDocumentId) eventsSet.add(reg.eventDocumentId);
541
+ if (reg.status === "confirmed") confirmedSeats += seats;
542
+ else if (reg.status === "checked_in") checkedInSeats += seats;
543
+ else if (reg.status === "waitlisted") waitlistedSeats += seats;
544
+ }
545
+ return {
546
+ totalEvents: uniqueEvents.length,
547
+ totalRegistrations: registrations.length,
548
+ confirmedSeats,
549
+ checkedInSeats,
550
+ waitlistedSeats,
551
+ distinctEvents: eventsSet.size
552
+ };
553
+ }
554
+ const EventsKpiCards = ({ stats, t }) => {
555
+ const cards = [
556
+ {
557
+ label: t("stats.totalEvents", "Registered Events"),
558
+ value: stats.totalEvents,
559
+ badge: "primary100"
560
+ },
561
+ {
562
+ label: t("stats.eventsWithRsvps", "Events with RSVPs"),
563
+ value: stats.distinctEvents,
564
+ badge: "secondary100"
565
+ },
566
+ {
567
+ label: t("stats.registrations", "Total Registrations"),
568
+ value: stats.totalRegistrations,
569
+ badge: "neutral100"
570
+ },
571
+ {
572
+ label: t("stats.confirmedSeats", "Confirmed Seats"),
573
+ value: stats.confirmedSeats,
574
+ badge: "primary100"
575
+ },
576
+ {
577
+ label: t("stats.checkedInSeats", "Checked-In Seats"),
578
+ value: stats.checkedInSeats,
579
+ badge: "success100"
580
+ },
581
+ {
582
+ label: t("stats.waitlistedSeats", "Waitlisted Seats"),
583
+ value: stats.waitlistedSeats,
584
+ badge: "warning100"
585
+ }
586
+ ];
587
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 4, wrap: "wrap", children: cards.map((card) => /* @__PURE__ */ jsxRuntime.jsx(
588
+ designSystem.Box,
589
+ {
590
+ background: "neutral0",
591
+ hasRadius: true,
592
+ shadow: "tableShadow",
593
+ padding: 4,
594
+ style: { flex: "1 1 160px" },
595
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
596
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: card.label }),
597
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: card.badge, children: card.value })
598
+ ] })
599
+ },
600
+ card.label
601
+ )) });
602
+ };
603
+ const WEEKDAYS = [
604
+ { code: "MO", labelKey: "rrule.weekday.mo", defaultLabel: "Mon", jsDay: 1 },
605
+ { code: "TU", labelKey: "rrule.weekday.tu", defaultLabel: "Tue", jsDay: 2 },
606
+ { code: "WE", labelKey: "rrule.weekday.we", defaultLabel: "Wed", jsDay: 3 },
607
+ { code: "TH", labelKey: "rrule.weekday.th", defaultLabel: "Thu", jsDay: 4 },
608
+ { code: "FR", labelKey: "rrule.weekday.fr", defaultLabel: "Fri", jsDay: 5 },
609
+ { code: "SA", labelKey: "rrule.weekday.sa", defaultLabel: "Sat", jsDay: 6 },
610
+ { code: "SU", labelKey: "rrule.weekday.su", defaultLabel: "Sun", jsDay: 0 }
611
+ ];
612
+ function extractByDays(rrule) {
613
+ const match = rrule.match(/BYDAY=([A-Z,]+)/i);
614
+ if (!match?.[1]) return [];
615
+ const validCodes = /* @__PURE__ */ new Set([
616
+ "MO",
617
+ "TU",
618
+ "WE",
619
+ "TH",
620
+ "FR",
621
+ "SA",
622
+ "SU"
623
+ ]);
624
+ return match[1].toUpperCase().split(",").map((d) => d.trim()).filter((d) => validCodes.has(d));
625
+ }
626
+ function alignStartDateToWeekday(startStr, targetCode) {
627
+ if (!startStr || !/^\d{4}-\d{2}-\d{2}$/.test(startStr)) return startStr;
628
+ const target = WEEKDAYS.find((w) => w.code === targetCode);
629
+ if (!target) return startStr;
630
+ const dt = /* @__PURE__ */ new Date(`${startStr}T00:00:00.000Z`);
631
+ if (Number.isNaN(dt.getTime())) return startStr;
632
+ const currentDay = dt.getUTCDay();
633
+ const diff = (target.jsDay - currentDay + 7) % 7;
634
+ if (diff === 0) return startStr;
635
+ dt.setUTCDate(dt.getUTCDate() + diff);
636
+ return dt.toISOString().slice(0, 10);
637
+ }
638
+ const QuickCreateEventPanel = ({
639
+ t,
640
+ quickTitle,
641
+ onQuickTitleChange,
642
+ quickStart,
643
+ onQuickStartChange,
644
+ quickLocation,
645
+ onQuickLocationChange,
646
+ quickRRule,
647
+ onQuickRRuleChange,
648
+ quickRsvpEnabled,
649
+ onQuickRsvpEnabledChange,
650
+ quickCapacity,
651
+ onQuickCapacityChange,
652
+ recurrencePresets,
653
+ onCancel,
654
+ onSubmit
655
+ }) => {
656
+ const normalizedPresets = react.useMemo(() => {
657
+ const byRRule = /* @__PURE__ */ new Map();
658
+ for (const def of DEFAULT_RECURRENCE_PRESETS) {
659
+ byRRule.set(def.rrule, def);
660
+ }
661
+ for (const p of recurrencePresets) {
662
+ if (p?.rrule) {
663
+ byRRule.set(p.rrule, p);
664
+ }
665
+ }
666
+ return Array.from(byRRule.values());
667
+ }, [recurrencePresets]);
668
+ const isWeekly = quickRRule.toUpperCase().includes("FREQ=WEEKLY");
669
+ const selectedWeekdays = react.useMemo(
670
+ () => extractByDays(quickRRule),
671
+ [quickRRule]
672
+ );
673
+ const hasCustomWeeklyOption = isWeekly && Boolean(quickRRule) && !normalizedPresets.some((p) => p.rrule === quickRRule);
674
+ const handleRecurrenceSelect = (val) => {
675
+ const nextRRule = val === "NONE" ? "" : val;
676
+ onQuickRRuleChange(nextRRule);
677
+ if (nextRRule.toUpperCase().includes("FREQ=WEEKLY")) {
678
+ const days = extractByDays(nextRRule);
679
+ if (days.length === 1 && quickStart) {
680
+ const aligned = alignStartDateToWeekday(quickStart, days[0]);
681
+ if (aligned !== quickStart) {
682
+ onQuickStartChange(aligned);
683
+ }
684
+ }
685
+ }
686
+ };
687
+ const handleToggleWeekday = (code) => {
688
+ const exists = selectedWeekdays.includes(code);
689
+ let nextDays;
690
+ if (exists) {
691
+ nextDays = selectedWeekdays.length > 1 ? selectedWeekdays.filter((d) => d !== code) : [code];
692
+ } else {
693
+ nextDays = WEEKDAYS.map((w) => w.code).filter(
694
+ (c) => c === code || selectedWeekdays.includes(c)
695
+ );
696
+ }
697
+ const nextRRule = `FREQ=WEEKLY;INTERVAL=1;BYDAY=${nextDays.join(",")}`;
698
+ onQuickRRuleChange(nextRRule);
699
+ if (nextDays.length === 1 && quickStart) {
700
+ const aligned = alignStartDateToWeekday(quickStart, nextDays[0]);
701
+ if (aligned !== quickStart) {
702
+ onQuickStartChange(aligned);
703
+ }
704
+ }
705
+ };
706
+ const handleStartDateChange = (nextDateStr) => {
707
+ onQuickStartChange(nextDateStr);
708
+ if (isWeekly && selectedWeekdays.length === 1 && /^\d{4}-\d{2}-\d{2}$/.test(nextDateStr)) {
709
+ const dt = /* @__PURE__ */ new Date(`${nextDateStr}T00:00:00.000Z`);
710
+ if (!Number.isNaN(dt.getTime())) {
711
+ const match = WEEKDAYS.find((w) => w.jsDay === dt.getUTCDay());
712
+ if (match && match.code !== selectedWeekdays[0]) {
713
+ onQuickRRuleChange(`FREQ=WEEKLY;INTERVAL=1;BYDAY=${match.code}`);
714
+ }
715
+ }
716
+ }
717
+ };
718
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral0", hasRadius: true, shadow: "tableShadow", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 3, children: [
719
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "bold", children: t("calendar.quickCreateTitle", "Quick-Create Event") }),
720
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, wrap: "wrap", children: [
721
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 2, minWidth: 220 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { required: true, children: [
722
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("calendar.field.title", "Event Title") }),
723
+ /* @__PURE__ */ jsxRuntime.jsx(
724
+ designSystem.TextInput,
725
+ {
726
+ value: quickTitle,
727
+ onChange: (e) => onQuickTitleChange(e.target.value),
728
+ placeholder: t(
729
+ "placeholder.e_g_product_launch_webinar_weekly_workshop",
730
+ "e.g., Product Launch Webinar / Weekly Workshop"
731
+ )
732
+ }
733
+ )
734
+ ] }) }),
735
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { required: true, children: [
736
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("calendar.field.startDate", "Start Date") }),
737
+ /* @__PURE__ */ jsxRuntime.jsx(
738
+ designSystem.TextInput,
739
+ {
740
+ type: "date",
741
+ placeholder: t("placeholder.yyyy_mm_dd", "YYYY-MM-DD"),
742
+ value: quickStart,
743
+ onChange: (e) => handleStartDateChange(e.target.value)
744
+ }
745
+ )
746
+ ] }) }),
747
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
748
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("calendar.field.location", "Location") }),
749
+ /* @__PURE__ */ jsxRuntime.jsx(
750
+ designSystem.TextInput,
751
+ {
752
+ value: quickLocation,
753
+ onChange: (e) => onQuickLocationChange(e.target.value),
754
+ placeholder: t(
755
+ "placeholder.main_auditorium_online",
756
+ "Main Auditorium / Online"
757
+ )
758
+ }
759
+ )
760
+ ] }) }),
761
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 210 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
762
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("calendar.field.recurrence", "Recurrence Preset") }),
763
+ /* @__PURE__ */ jsxRuntime.jsxs(
764
+ designSystem.SingleSelect,
765
+ {
766
+ placeholder: t(
767
+ "placeholder.select_recurrence_preset",
768
+ "Select recurrence preset"
769
+ ),
770
+ value: quickRRule || "NONE",
771
+ onChange: handleRecurrenceSelect,
772
+ children: [
773
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "NONE", children: t("rrule.freq.none", "Does not repeat (Single Event)") }),
774
+ normalizedPresets.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: preset.rrule, children: preset.label }, preset.id)),
775
+ hasCustomWeeklyOption && /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: quickRRule, children: `${t("rrule.freq.weekly", "Weekly")} (${selectedWeekdays.join(", ")})` })
776
+ ]
777
+ }
778
+ )
779
+ ] }) })
780
+ ] }),
781
+ isWeekly && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 3, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, wrap: "wrap", children: [
782
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral700", children: t(
783
+ "calendar.field.repeatWeekdays",
784
+ "Repeat on Day(s) of Week:"
785
+ ) }),
786
+ WEEKDAYS.map((day) => {
787
+ const active = selectedWeekdays.includes(day.code);
788
+ return /* @__PURE__ */ jsxRuntime.jsx(
789
+ designSystem.Button,
790
+ {
791
+ size: "S",
792
+ variant: active ? "default" : "tertiary",
793
+ onClick: () => handleToggleWeekday(day.code),
794
+ children: t(day.labelKey, day.defaultLabel)
795
+ },
796
+ day.code
797
+ );
798
+ })
799
+ ] }) }),
800
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "flex-end", wrap: "wrap", children: [
801
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minHeight: 40, display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
802
+ designSystem.Checkbox,
803
+ {
804
+ checked: quickRsvpEnabled,
805
+ onCheckedChange: (checked) => onQuickRsvpEnabledChange(Boolean(checked)),
806
+ children: t("calendar.field.enableRsvp", "Enable RSVP & Seat Capacity")
807
+ }
808
+ ) }),
809
+ quickRsvpEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 180 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
810
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("calendar.field.capacity", "Max Capacity (Seats)") }),
811
+ /* @__PURE__ */ jsxRuntime.jsx(
812
+ designSystem.NumberInput,
813
+ {
814
+ placeholder: t("placeholder.50", "50"),
815
+ value: quickCapacity,
816
+ onValueChange: (val) => onQuickCapacityChange(val && val >= 1 ? val : 50)
817
+ }
818
+ )
819
+ ] }) }),
820
+ /* @__PURE__ */ jsxRuntime.jsxs(
821
+ designSystem.Flex,
822
+ {
823
+ gap: 2,
824
+ alignItems: "center",
825
+ style: { marginLeft: "auto", minHeight: 40 },
826
+ children: [
827
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: onCancel, children: t("common.cancel", "Cancel") }),
828
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "default", onClick: onSubmit, children: t("common.create", "Create Event") })
829
+ ]
830
+ }
831
+ )
832
+ ] })
833
+ ] }) });
834
+ };
835
+ function shouldReplaceRepresentativeOccurrence(existing, candidate, todayKey) {
836
+ const existingIsFuture = existing.occurrenceDate >= todayKey;
837
+ const currentIsFuture = candidate.occurrenceDate >= todayKey;
838
+ if (!existingIsFuture && currentIsFuture) return true;
839
+ if (existingIsFuture && currentIsFuture) {
840
+ return candidate.occurrenceDate < existing.occurrenceDate;
841
+ }
842
+ if (!existingIsFuture && !currentIsFuture) {
843
+ return candidate.occurrenceDate > existing.occurrenceDate;
844
+ }
845
+ return false;
846
+ }
847
+ const CalendarPage = () => {
848
+ const { formatMessage } = reactIntl.useIntl();
849
+ const navigate = reactRouterDom.useNavigate();
850
+ const { get, post } = admin.useFetchClient();
851
+ const isMountedRef = react.useRef(true);
852
+ react.useEffect(() => {
853
+ isMountedRef.current = true;
854
+ return () => {
855
+ isMountedRef.current = false;
856
+ };
857
+ }, []);
858
+ const t = react.useCallback(
859
+ (id, defaultMessage) => formatMessage({ id: `${index.PLUGIN_ID}.${id}`, defaultMessage }),
860
+ [formatMessage]
861
+ );
862
+ const [settings, setSettings] = react.useState(null);
863
+ const [occurrences, setOccurrences] = react.useState([]);
864
+ const [bannerMessage, setBannerMessage] = react.useState(null);
865
+ const [showQuickCreate, setShowQuickCreate] = react.useState(false);
866
+ const [quickTitle, setQuickTitle] = react.useState("");
867
+ const [quickStart, setQuickStart] = react.useState("");
868
+ const [quickLocation, setQuickLocation] = react.useState("");
869
+ const [quickCategory] = react.useState("general");
870
+ const [quickRRule, setQuickRRule] = react.useState("");
871
+ const [quickRsvpEnabled, setQuickRsvpEnabled] = react.useState(true);
872
+ const [quickCapacity, setQuickCapacity] = react.useState(50);
873
+ const [registrations, setRegistrations] = react.useState(
874
+ []
875
+ );
876
+ const [rsvpSearch, setRsvpSearch] = react.useState("");
877
+ const [debouncedRsvpSearch, setDebouncedRsvpSearch] = react.useState("");
878
+ const [rsvpStatusFilter, setRsvpStatusFilter] = react.useState("all");
879
+ const [rsvpEventFilter, setRsvpEventFilter] = react.useState(() => {
880
+ if (typeof window === "undefined") return "";
881
+ return new URLSearchParams(window.location.search).get("eventDocumentId") || "";
882
+ });
883
+ react.useEffect(() => {
884
+ const timer = window.setTimeout(() => {
885
+ setDebouncedRsvpSearch(rsvpSearch);
886
+ }, 250);
887
+ return () => {
888
+ window.clearTimeout(timer);
889
+ };
890
+ }, [rsvpSearch]);
891
+ const todayKey = react.useMemo(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), []);
892
+ const fetchCalendarData = react.useCallback(async () => {
893
+ try {
894
+ const now = /* @__PURE__ */ new Date();
895
+ const fromIso = `${now.getFullYear() - 1}-01-01T00:00:00.000Z`;
896
+ const toIso = `${now.getFullYear() + 2}-12-31T23:59:59.000Z`;
897
+ const res = await get(
898
+ `/${index.PLUGIN_ID}/calendar?from=${encodeURIComponent(fromIso)}&to=${encodeURIComponent(toIso)}`
899
+ );
900
+ if (!isMountedRef.current) return;
901
+ const payload = res?.data?.data;
902
+ if (payload?.settings) {
903
+ setSettings(payload.settings);
904
+ }
905
+ if (Array.isArray(payload?.occurrences)) {
906
+ setOccurrences(payload.occurrences);
907
+ }
908
+ } catch {
909
+ try {
910
+ const res = await get(`/${index.PLUGIN_ID}/settings`);
911
+ if (!isMountedRef.current) return;
912
+ const payload = res?.data?.data;
913
+ if (payload) {
914
+ setSettings(payload);
915
+ }
916
+ } catch {
917
+ }
918
+ }
919
+ }, [get]);
920
+ const fetchRegistrations = react.useCallback(async () => {
921
+ try {
922
+ const params = new URLSearchParams();
923
+ if (debouncedRsvpSearch.trim()) {
924
+ params.set("search", debouncedRsvpSearch.trim());
925
+ }
926
+ if (rsvpStatusFilter !== "all") params.set("status", rsvpStatusFilter);
927
+ if (rsvpEventFilter.trim()) {
928
+ params.set("eventDocumentId", rsvpEventFilter.trim());
929
+ }
930
+ const res = await get(`/${index.PLUGIN_ID}/registrations?${params.toString()}`);
931
+ if (!isMountedRef.current) return;
932
+ const data = res?.data?.data;
933
+ if (Array.isArray(data)) {
934
+ setRegistrations(data);
935
+ }
936
+ } catch {
937
+ if (isMountedRef.current) {
938
+ setBannerMessage(
939
+ t("attendees.error.load", "Could not load registrations.")
940
+ );
941
+ }
942
+ }
943
+ }, [get, debouncedRsvpSearch, rsvpStatusFilter, rsvpEventFilter, t]);
944
+ react.useEffect(() => {
945
+ void fetchCalendarData();
946
+ }, [fetchCalendarData]);
947
+ react.useEffect(() => {
948
+ void fetchRegistrations();
949
+ }, [fetchRegistrations]);
950
+ const uniqueEvents = react.useMemo(() => {
951
+ const byDoc = /* @__PURE__ */ new Map();
952
+ for (const occ of occurrences) {
953
+ const key = `${occ.uid}:${occ.documentId}`;
954
+ const existing = byDoc.get(key);
955
+ if (!existing || shouldReplaceRepresentativeOccurrence(existing, occ, todayKey)) {
956
+ byDoc.set(key, occ);
957
+ }
958
+ }
959
+ return Array.from(byDoc.values()).sort(
960
+ (a, b) => a.occurrenceDate.localeCompare(b.occurrenceDate)
961
+ );
962
+ }, [occurrences, todayKey]);
963
+ const recurrencePresets = react.useMemo(
964
+ () => settings?.recurrencePresets && settings.recurrencePresets.length > 0 ? settings.recurrencePresets : DEFAULT_RECURRENCE_PRESETS,
965
+ [settings?.recurrencePresets]
966
+ );
967
+ const rsvpStats = react.useMemo(
968
+ () => computeDashboardRsvpStats(uniqueEvents, registrations),
969
+ [registrations, uniqueEvents]
970
+ );
971
+ const handleQuickCreateSubmit = react.useCallback(async () => {
972
+ if (!quickTitle.trim() || !quickStart) return;
973
+ try {
974
+ const startIso = quickStart.includes("T") ? new Date(quickStart).toISOString() : `${quickStart}T10:00:00.000Z`;
975
+ const endIso = new Date(
976
+ new Date(startIso).getTime() + 36e5
977
+ ).toISOString();
978
+ await post(`/${index.PLUGIN_ID}/calendar/quick-create`, {
979
+ title: quickTitle.trim(),
980
+ startDate: startIso,
981
+ endDate: endIso,
982
+ location: quickLocation.trim() || void 0,
983
+ category: quickCategory,
984
+ rrule: quickRRule || void 0,
985
+ rsvpEnabled: quickRsvpEnabled,
986
+ capacity: quickRsvpEnabled ? quickCapacity : void 0
987
+ });
988
+ if (!isMountedRef.current) return;
989
+ setShowQuickCreate(false);
990
+ setQuickTitle("");
991
+ setQuickLocation("");
992
+ setQuickRRule("");
993
+ setBannerMessage(t("calendar.created", "Event created and published."));
994
+ await Promise.all([fetchCalendarData(), fetchRegistrations()]);
995
+ } catch {
996
+ if (isMountedRef.current) {
997
+ setBannerMessage(t("calendar.error.create", "Could not create event."));
998
+ }
999
+ }
1000
+ }, [
1001
+ quickTitle,
1002
+ quickStart,
1003
+ quickLocation,
1004
+ quickCategory,
1005
+ quickRRule,
1006
+ quickRsvpEnabled,
1007
+ quickCapacity,
1008
+ post,
1009
+ t,
1010
+ fetchCalendarData,
1011
+ fetchRegistrations
1012
+ ]);
1013
+ const handleCheckIn = react.useCallback(
1014
+ async (regDocId) => {
1015
+ try {
1016
+ await post(
1017
+ `/${index.PLUGIN_ID}/registrations/${encodeURIComponent(regDocId)}/check-in`,
1018
+ {}
1019
+ );
1020
+ if (!isMountedRef.current) return;
1021
+ setBannerMessage(
1022
+ t("attendees.checkedInSuccess", "Attendee checked in.")
1023
+ );
1024
+ await Promise.all([fetchCalendarData(), fetchRegistrations()]);
1025
+ } catch {
1026
+ if (isMountedRef.current) {
1027
+ setBannerMessage(t("attendees.error.checkIn", "Check-in failed."));
1028
+ }
1029
+ }
1030
+ },
1031
+ [post, t, fetchCalendarData, fetchRegistrations]
1032
+ );
1033
+ const handleStatusChange = react.useCallback(
1034
+ async (regDocId, status) => {
1035
+ try {
1036
+ const res = await post(
1037
+ `/${index.PLUGIN_ID}/registrations/${encodeURIComponent(regDocId)}/status`,
1038
+ { status }
1039
+ );
1040
+ if (!isMountedRef.current) return;
1041
+ const promotedCount = res?.data?.data?.promotedCount ?? 0;
1042
+ const baseMsg = t("attendees.statusUpdated", "Status updated.");
1043
+ const promoSuffix = t(
1044
+ "attendees.waitlistPromoted",
1045
+ "attendee(s) automatically promoted from waitlist!"
1046
+ );
1047
+ setBannerMessage(
1048
+ promotedCount > 0 ? `${baseMsg} ${promotedCount} ${promoSuffix}` : baseMsg
1049
+ );
1050
+ await Promise.all([fetchCalendarData(), fetchRegistrations()]);
1051
+ } catch {
1052
+ if (isMountedRef.current) {
1053
+ setBannerMessage(
1054
+ t("attendees.error.status", "Could not update status.")
1055
+ );
1056
+ }
1057
+ }
1058
+ },
1059
+ [post, t, fetchCalendarData, fetchRegistrations]
1060
+ );
1061
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, background: "neutral100", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 5, children: [
1062
+ /* @__PURE__ */ jsxRuntime.jsxs(
1063
+ designSystem.Flex,
1064
+ {
1065
+ justifyContent: "space-between",
1066
+ alignItems: "center",
1067
+ wrap: "wrap",
1068
+ gap: 3,
1069
+ children: [
1070
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
1071
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", fontWeight: "bold", children: t("calendar.title", "Events, RSVPs & Check-In") }),
1072
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", children: t(
1073
+ "calendar.subtitle",
1074
+ "Manage event registrations, waitlist auto-promotion, and attendee check-ins. Schedule views are unified in Calendar."
1075
+ ) }) })
1076
+ ] }),
1077
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", children: [
1078
+ /* @__PURE__ */ jsxRuntime.jsx(
1079
+ designSystem.Button,
1080
+ {
1081
+ variant: "secondary",
1082
+ size: "S",
1083
+ onClick: () => navigate("/plugins/calendar?source=events"),
1084
+ children: t("calendar.openUnifiedCalendar", "Open in Calendar")
1085
+ }
1086
+ ),
1087
+ /* @__PURE__ */ jsxRuntime.jsxs(
1088
+ designSystem.Button,
1089
+ {
1090
+ variant: "default",
1091
+ size: "S",
1092
+ onClick: () => {
1093
+ setQuickStart(todayKey);
1094
+ setShowQuickCreate(true);
1095
+ },
1096
+ children: [
1097
+ "+ ",
1098
+ t("calendar.quickCreate", "New Event")
1099
+ ]
1100
+ }
1101
+ ),
1102
+ /* @__PURE__ */ jsxRuntime.jsx(
1103
+ designSystem.Button,
1104
+ {
1105
+ variant: "secondary",
1106
+ size: "S",
1107
+ tag: "a",
1108
+ href: "/api/events/feed.ics",
1109
+ target: "_blank",
1110
+ rel: "noopener noreferrer",
1111
+ children: t("calendar.downloadIcsFeed", "iCal Feed (.ics)")
1112
+ }
1113
+ ),
1114
+ /* @__PURE__ */ jsxRuntime.jsx(
1115
+ designSystem.Button,
1116
+ {
1117
+ variant: "tertiary",
1118
+ size: "S",
1119
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}`),
1120
+ children: t("calendar.settings", "Settings")
1121
+ }
1122
+ )
1123
+ ] })
1124
+ ]
1125
+ }
1126
+ ),
1127
+ /* @__PURE__ */ jsxRuntime.jsx(EventsKpiCards, { stats: rsvpStats, t }),
1128
+ bannerMessage && /* @__PURE__ */ jsxRuntime.jsx(
1129
+ designSystem.Box,
1130
+ {
1131
+ padding: 3,
1132
+ background: "primary100",
1133
+ hasRadius: true,
1134
+ style: { border: "1px solid #0284C7" },
1135
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
1136
+ /* @__PURE__ */ jsxRuntime.jsx(
1137
+ designSystem.Typography,
1138
+ {
1139
+ variant: "omega",
1140
+ fontWeight: "bold",
1141
+ textColor: "primary700",
1142
+ children: bannerMessage
1143
+ }
1144
+ ),
1145
+ /* @__PURE__ */ jsxRuntime.jsx(
1146
+ designSystem.Button,
1147
+ {
1148
+ variant: "tertiary",
1149
+ size: "S",
1150
+ "aria-label": t("common.close", "Close"),
1151
+ onClick: () => setBannerMessage(null),
1152
+ children: "×"
1153
+ }
1154
+ )
1155
+ ] })
1156
+ }
1157
+ ),
1158
+ showQuickCreate && /* @__PURE__ */ jsxRuntime.jsx(
1159
+ QuickCreateEventPanel,
1160
+ {
1161
+ t,
1162
+ quickTitle,
1163
+ onQuickTitleChange: setQuickTitle,
1164
+ quickStart,
1165
+ onQuickStartChange: setQuickStart,
1166
+ quickLocation,
1167
+ onQuickLocationChange: setQuickLocation,
1168
+ quickRRule,
1169
+ onQuickRRuleChange: setQuickRRule,
1170
+ quickRsvpEnabled,
1171
+ onQuickRsvpEnabledChange: setQuickRsvpEnabled,
1172
+ quickCapacity,
1173
+ onQuickCapacityChange: setQuickCapacity,
1174
+ recurrencePresets,
1175
+ onCancel: () => setShowQuickCreate(false),
1176
+ onSubmit: handleQuickCreateSubmit
1177
+ }
1178
+ ),
1179
+ /* @__PURE__ */ jsxRuntime.jsx(
1180
+ EventsDirectoryPanel,
1181
+ {
1182
+ t,
1183
+ events: uniqueEvents,
1184
+ selectedEventDocId: rsvpEventFilter,
1185
+ onSelectEventDocId: setRsvpEventFilter,
1186
+ onEditInContentManager: (uid, docId, locale) => {
1187
+ const query = locale ? `?plugins[i18n][locale]=${encodeURIComponent(locale)}` : "";
1188
+ navigate(
1189
+ `/content-manager/collection-types/${uid}/${docId}${query}`
1190
+ );
1191
+ },
1192
+ onOpenInCalendar: (docId) => navigate(
1193
+ `/plugins/calendar?source=events${docId ? `&documentId=${encodeURIComponent(docId)}` : ""}`
1194
+ )
1195
+ }
1196
+ ),
1197
+ /* @__PURE__ */ jsxRuntime.jsx(
1198
+ AttendeesManagerPanel,
1199
+ {
1200
+ t,
1201
+ registrations,
1202
+ rsvpSearch,
1203
+ onRsvpSearchChange: setRsvpSearch,
1204
+ rsvpStatusFilter,
1205
+ onRsvpStatusFilterChange: setRsvpStatusFilter,
1206
+ rsvpEventFilter,
1207
+ onClearRsvpEventFilter: () => setRsvpEventFilter(""),
1208
+ onCheckIn: handleCheckIn,
1209
+ onStatusChange: handleStatusChange
1210
+ }
1211
+ )
1212
+ ] }) }) });
1213
+ };
1214
+ exports.CalendarPage = CalendarPage;
1215
+ exports.default = CalendarPage;