@vicacha-devs/strapi-plugin-calendar 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 (90) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +163 -2
  3. package/dist/admin/CalendarPage-CVqowKst.cjs +1015 -0
  4. package/dist/admin/CalendarPage-Dq6YfSXF.js +1015 -0
  5. package/dist/admin/SettingsPage-7SecVQEi.js +2088 -0
  6. package/dist/admin/SettingsPage-D0aCDb9w.cjs +2088 -0
  7. package/dist/admin/ar-O6QL_nJZ.js +331 -0
  8. package/dist/admin/ar-yKsEpX_y.cjs +331 -0
  9. package/dist/admin/de-BpDxjctz.js +331 -0
  10. package/dist/admin/de-ryVtwWOz.cjs +331 -0
  11. package/dist/admin/en-BHYSUqXv.cjs +331 -0
  12. package/dist/admin/en-BoRq-9QD.js +331 -0
  13. package/dist/admin/es-BZ7WX8Ev.js +331 -0
  14. package/dist/admin/es-T5v93hXW.cjs +331 -0
  15. package/dist/admin/fr-BnDr9DC8.js +331 -0
  16. package/dist/admin/fr-DTdTwx7e.cjs +331 -0
  17. package/dist/admin/index-DO0QbnXX.cjs +3223 -0
  18. package/dist/admin/index-USqxqyAm.js +3216 -0
  19. package/dist/admin/index.cjs +9 -0
  20. package/dist/admin/index.js +9 -0
  21. package/dist/admin/index.mjs +9 -0
  22. package/dist/admin/it-BkJNzy27.cjs +331 -0
  23. package/dist/admin/it-DkdUInap.js +331 -0
  24. package/dist/admin/nl-C2NFORLS.js +331 -0
  25. package/dist/admin/nl-CjnlbeUM.cjs +331 -0
  26. package/dist/admin/pl-BJArPgCQ.cjs +331 -0
  27. package/dist/admin/pl-mv5eXA8W.js +331 -0
  28. package/dist/admin/pt-CSYqSGnz.cjs +331 -0
  29. package/dist/admin/pt-WpvZGj3b.js +331 -0
  30. package/dist/admin/src/components/CalendarSidePanel.d.ts +13 -0
  31. package/dist/admin/src/components/Initializer.d.ts +6 -0
  32. package/dist/admin/src/components/PluginIcon.d.ts +3 -0
  33. package/dist/admin/src/components/RRuleInput.d.ts +20 -0
  34. package/dist/admin/src/components/calendar/CalendarToolbar.d.ts +32 -0
  35. package/dist/admin/src/components/calendar/FullCalendarCanvas.d.ts +21 -0
  36. package/dist/admin/src/components/calendar/FullCalendarDayCell.d.ts +11 -0
  37. package/dist/admin/src/components/calendar/OccurrenceDetailDrawer.d.ts +15 -0
  38. package/dist/admin/src/components/calendar/QuickCreatePanel.d.ts +29 -0
  39. package/dist/admin/src/components/calendar/QuickCreateSubcomponents.d.ts +40 -0
  40. package/dist/admin/src/components/calendar/calendarPageHelpers.d.ts +52 -0
  41. package/dist/admin/src/components/calendar/calendarThemeStyles.d.ts +2 -0
  42. package/dist/admin/src/components/settings/CalendarThemeSection.d.ts +9 -0
  43. package/dist/admin/src/components/settings/CalendarViewsSection.d.ts +10 -0
  44. package/dist/admin/src/components/settings/CollectionSourcesSection.d.ts +12 -0
  45. package/dist/admin/src/components/settings/CompanionSuiteSettingsSection.d.ts +29 -0
  46. package/dist/admin/src/components/settings/SecondaryCalendarsSection.d.ts +11 -0
  47. package/dist/admin/src/fields/rruleField.d.ts +19 -0
  48. package/dist/admin/src/index.d.ts +18 -0
  49. package/dist/admin/src/pages/CalendarPage.d.ts +3 -0
  50. package/dist/admin/src/pages/SettingsPage.d.ts +3 -0
  51. package/dist/admin/src/pages/calendarPageSupport.d.ts +8 -0
  52. package/dist/admin/tr-BhZKvFoK.js +331 -0
  53. package/dist/admin/tr-zUVxuw9X.cjs +331 -0
  54. package/dist/package.json +3 -0
  55. package/dist/server/index.cjs +2585 -0
  56. package/dist/server/index.js +2585 -0
  57. package/dist/server/index.mjs +2585 -0
  58. package/dist/server/src/bootstrap.d.ts +5 -0
  59. package/dist/server/src/config/index.d.ts +7 -0
  60. package/dist/server/src/controllers/admin.d.ts +15 -0
  61. package/dist/server/src/controllers/content-api.d.ts +10 -0
  62. package/dist/server/src/controllers/index.d.ts +21 -0
  63. package/dist/server/src/destroy.d.ts +5 -0
  64. package/dist/server/src/index.d.ts +85 -0
  65. package/dist/server/src/register.d.ts +5 -0
  66. package/dist/server/src/routes/admin.d.ts +17 -0
  67. package/dist/server/src/routes/content-api.d.ts +10 -0
  68. package/dist/server/src/routes/index.d.ts +28 -0
  69. package/dist/server/src/services/builtinSources.d.ts +3 -0
  70. package/dist/server/src/services/ical.d.ts +6 -0
  71. package/dist/server/src/services/index.d.ts +23 -0
  72. package/dist/server/src/services/occurrence.d.ts +6 -0
  73. package/dist/server/src/services/occurrenceDates.d.ts +7 -0
  74. package/dist/server/src/services/quickCreateHelpers.d.ts +20 -0
  75. package/dist/server/src/services/registry.d.ts +19 -0
  76. package/dist/server/src/services/settings.d.ts +6 -0
  77. package/dist/server/src/types.d.ts +98 -0
  78. package/dist/shared/calendarSystems.d.ts +41 -0
  79. package/dist/shared/icalGenerator.d.ts +18 -0
  80. package/dist/shared/index.cjs +1612 -0
  81. package/dist/shared/index.d.ts +6 -0
  82. package/dist/shared/index.js +1612 -0
  83. package/dist/shared/index.mjs +1612 -0
  84. package/dist/shared/localization.d.ts +14 -0
  85. package/dist/shared/pluginId.d.ts +2 -0
  86. package/dist/shared/rruleEngine.d.ts +22 -0
  87. package/dist/shared/themePalettes.d.ts +27 -0
  88. package/dist/shared/themePresets.d.ts +32 -0
  89. package/dist/shared/types.d.ts +311 -0
  90. package/package.json +120 -3
@@ -0,0 +1,2088 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { Box, Typography, Field, Flex, Button, Divider, Checkbox, SingleSelect, SingleSelectOption, TextInput, Badge, NumberInput, Tabs } from "@strapi/design-system";
3
+ import { useFetchClient, Layouts } from "@strapi/strapi/admin";
4
+ import { useState, useEffect, useMemo } from "react";
5
+ import { useIntl } from "react-intl";
6
+ import { w as detectEffectiveColorScheme, x as CALENDAR_COLOR_PALETTES, y as CALENDAR_THEME_PRESETS, z as resolveCalendarTheme, S as SUPPORTED_CALENDAR_LOCALES, A as SUPPORTED_INTL_CALENDARS, P as PLUGIN_ID } from "./index-USqxqyAm.js";
7
+ import { useTheme } from "styled-components";
8
+ const CalendarThemeSection = ({
9
+ t,
10
+ settings,
11
+ onPatchSettings
12
+ }) => {
13
+ const styledTheme = useTheme();
14
+ const strapiNeutral0 = styledTheme?.colors?.neutral0;
15
+ const activeTheme = settings.theme ?? "breeze";
16
+ const configuredColorScheme = settings.colorScheme ?? "auto";
17
+ const effectiveColorScheme = detectEffectiveColorScheme(
18
+ configuredColorScheme,
19
+ strapiNeutral0
20
+ );
21
+ const activePalette = settings.themePalette ?? "default";
22
+ return /* @__PURE__ */ jsxs(Box, { children: [
23
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", tag: "h2", children: t("settings.section.theme", "Calendar Theme & Visual Style") }),
24
+ /* @__PURE__ */ jsx(Box, { paddingTop: 1, paddingBottom: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
25
+ "settings.themeHint",
26
+ "Select a modern FullCalendar stock theme (Breezy, Monarch, Forma, Pulse, Classic), design system (Shadcn, MUI), or heritage palette, plus accent swatch and light/dark mode."
27
+ ) }) }),
28
+ /* @__PURE__ */ jsx(
29
+ Box,
30
+ {
31
+ padding: 4,
32
+ background: "neutral100",
33
+ hasRadius: true,
34
+ style: {
35
+ border: "1px solid rgba(128, 128, 128, 0.18)",
36
+ marginBottom: 20
37
+ },
38
+ children: /* @__PURE__ */ jsxs(
39
+ "div",
40
+ {
41
+ style: {
42
+ display: "grid",
43
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
44
+ gap: 16,
45
+ alignItems: "center"
46
+ },
47
+ children: [
48
+ /* @__PURE__ */ jsxs(
49
+ Field.Root,
50
+ {
51
+ hint: t(
52
+ "settings.paletteHint",
53
+ "Primary accent swatch applied to event pills, selected dates, and active view buttons."
54
+ ),
55
+ children: [
56
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.paletteLabel", "Accent Color Palette") }),
57
+ /* @__PURE__ */ jsx(Flex, { gap: 2, alignItems: "center", wrap: "wrap", paddingTop: 1, children: Object.values(CALENDAR_COLOR_PALETTES).map((pal) => {
58
+ const isSelected = activePalette === pal.id;
59
+ const swatchColor = pal.id === "default" ? CALENDAR_THEME_PRESETS[activeTheme]?.primaryColor ?? "#0284C7" : effectiveColorScheme === "dark" ? pal.darkPrimary : pal.lightPrimary;
60
+ return /* @__PURE__ */ jsx(
61
+ "button",
62
+ {
63
+ type: "button",
64
+ title: pal.label,
65
+ "aria-label": pal.label,
66
+ onClick: () => onPatchSettings({
67
+ themePalette: pal.id,
68
+ primaryColor: swatchColor
69
+ }),
70
+ style: {
71
+ width: 26,
72
+ height: 26,
73
+ borderRadius: "999px",
74
+ background: swatchColor,
75
+ border: isSelected ? "2px solid #FFFFFF" : "1px solid rgba(128,128,128,0.35)",
76
+ boxShadow: isSelected ? `0 0 0 2px ${swatchColor}` : "none",
77
+ cursor: "pointer",
78
+ padding: 0,
79
+ transition: "transform 0.12s ease",
80
+ transform: isSelected ? "scale(1.12)" : "scale(1)"
81
+ }
82
+ },
83
+ pal.id
84
+ );
85
+ }) }),
86
+ /* @__PURE__ */ jsx(Field.Hint, {})
87
+ ]
88
+ }
89
+ ),
90
+ /* @__PURE__ */ jsxs(
91
+ Field.Root,
92
+ {
93
+ hint: t(
94
+ "settings.colorSchemeHint",
95
+ "Choose whether the calendar canvas follows Strapi's light/dark theme or forces a specific mode."
96
+ ),
97
+ children: [
98
+ /* @__PURE__ */ jsx(Field.Label, { children: t(
99
+ "settings.colorSchemeLabel",
100
+ "Calendar Appearance (Color Scheme)"
101
+ ) }),
102
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", paddingTop: 1, children: [
103
+ /* @__PURE__ */ jsx(
104
+ Button,
105
+ {
106
+ size: "S",
107
+ variant: configuredColorScheme === "auto" ? "default" : "tertiary",
108
+ onClick: () => onPatchSettings({ colorScheme: "auto" }),
109
+ children: t("calendar.scheme.auto", "Auto (Match Strapi Theme)")
110
+ }
111
+ ),
112
+ /* @__PURE__ */ jsx(
113
+ Button,
114
+ {
115
+ size: "S",
116
+ variant: configuredColorScheme === "light" ? "default" : "tertiary",
117
+ onClick: () => onPatchSettings({ colorScheme: "light" }),
118
+ children: t("calendar.scheme.light", "Light")
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx(
122
+ Button,
123
+ {
124
+ size: "S",
125
+ variant: configuredColorScheme === "dark" ? "default" : "tertiary",
126
+ onClick: () => onPatchSettings({ colorScheme: "dark" }),
127
+ children: t("calendar.scheme.dark", "Dark")
128
+ }
129
+ )
130
+ ] }),
131
+ /* @__PURE__ */ jsx(Field.Hint, {})
132
+ ]
133
+ }
134
+ )
135
+ ]
136
+ }
137
+ )
138
+ }
139
+ ),
140
+ /* @__PURE__ */ jsx(
141
+ "div",
142
+ {
143
+ style: {
144
+ display: "grid",
145
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
146
+ gap: 14
147
+ },
148
+ children: Object.values(CALENDAR_THEME_PRESETS).map((preset) => {
149
+ const isSelected = activeTheme === preset.id;
150
+ const preview = resolveCalendarTheme(
151
+ preset.id,
152
+ effectiveColorScheme,
153
+ activePalette,
154
+ strapiNeutral0
155
+ );
156
+ return /* @__PURE__ */ jsxs(
157
+ "button",
158
+ {
159
+ type: "button",
160
+ onClick: () => onPatchSettings({
161
+ theme: preset.id,
162
+ primaryColor: preview.primaryColor
163
+ }),
164
+ style: {
165
+ textAlign: "left",
166
+ padding: "14px 16px",
167
+ borderRadius: preview.containerRadius ?? "8px",
168
+ cursor: "pointer",
169
+ background: preview.cellBg,
170
+ color: preview.cellText,
171
+ border: isSelected ? `2px solid ${preview.primaryColor}` : `1px solid ${preview.borderColor}`,
172
+ boxShadow: isSelected ? `0 0 0 2px ${preview.headerBg}` : preview.containerShadow ?? "none",
173
+ transition: "all 0.15s ease"
174
+ },
175
+ children: [
176
+ /* @__PURE__ */ jsxs(
177
+ "div",
178
+ {
179
+ style: {
180
+ display: "flex",
181
+ justifyContent: "space-between",
182
+ alignItems: "center",
183
+ marginBottom: 8
184
+ },
185
+ children: [
186
+ /* @__PURE__ */ jsx(
187
+ "span",
188
+ {
189
+ style: {
190
+ fontWeight: 700,
191
+ fontSize: 13,
192
+ color: preview.cellText
193
+ },
194
+ children: t(`settings.theme.${preset.id}.label`, preset.label)
195
+ }
196
+ ),
197
+ /* @__PURE__ */ jsx(
198
+ "span",
199
+ {
200
+ style: {
201
+ display: "inline-flex",
202
+ alignItems: "center",
203
+ gap: 4
204
+ },
205
+ children: /* @__PURE__ */ jsx(
206
+ "span",
207
+ {
208
+ style: {
209
+ width: 20,
210
+ height: 20,
211
+ borderRadius: preset.id === "forma" ? "3px" : "999px",
212
+ background: preview.todayNumberBg,
213
+ color: preview.todayNumberText,
214
+ fontSize: 10,
215
+ fontWeight: 700,
216
+ display: "inline-flex",
217
+ alignItems: "center",
218
+ justifyContent: "center"
219
+ },
220
+ children: "16"
221
+ }
222
+ )
223
+ }
224
+ )
225
+ ]
226
+ }
227
+ ),
228
+ /* @__PURE__ */ jsx(
229
+ "div",
230
+ {
231
+ style: {
232
+ marginBottom: 8,
233
+ padding: "4px 8px",
234
+ fontSize: 11,
235
+ fontWeight: 600,
236
+ borderRadius: preset.eventRadius ?? "5px",
237
+ background: preset.eventStyle === "solid" ? preview.primaryColor : `${preview.primaryColor}22`,
238
+ color: preset.eventStyle === "solid" ? "#FFFFFF" : preview.cellText,
239
+ borderLeft: preset.eventStyle === "accent-bar" ? `5px solid ${preview.primaryColor}` : `1px solid ${preview.primaryColor}55`
240
+ },
241
+ children: t(
242
+ "settings.theme.previewSampleEvent",
243
+ "09:00 · Community Event"
244
+ )
245
+ }
246
+ ),
247
+ /* @__PURE__ */ jsx(
248
+ "div",
249
+ {
250
+ style: {
251
+ fontSize: 11,
252
+ color: preview.cellMutedText,
253
+ lineHeight: 1.4
254
+ },
255
+ children: t(
256
+ `settings.theme.${preset.id}.description`,
257
+ preset.description
258
+ )
259
+ }
260
+ )
261
+ ]
262
+ },
263
+ preset.id
264
+ );
265
+ })
266
+ }
267
+ )
268
+ ] });
269
+ };
270
+ const GRID_3_COL$1 = {
271
+ display: "grid",
272
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
273
+ gap: 16
274
+ };
275
+ const GRID_2_COL$1 = {
276
+ display: "grid",
277
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
278
+ gap: 16
279
+ };
280
+ const SUB_BOX_STYLE$1 = {
281
+ border: "1px solid rgba(128, 128, 128, 0.18)"
282
+ };
283
+ const VIEW_CHECKBOXES = [
284
+ ["monthView", "view.month", "Month"],
285
+ ["weekView", "view.week", "Week"],
286
+ ["dayView", "view.day", "Day"],
287
+ ["agendaView", "view.agenda", "Agenda"],
288
+ ["yearView", "view.year", "Year"]
289
+ ];
290
+ const BEHAVIOR_CHECKBOXES = [
291
+ ["showWeekends", "settings.showWeekends", "Show Weekends"],
292
+ ["weekNumbers", "settings.weekNumbers", "Show Week Numbers"],
293
+ ["timeFormat24h", "settings.timeFormat24h", "24-Hour Time Format"],
294
+ [
295
+ "showCreateButton",
296
+ "settings.showCreateButton",
297
+ "Enable Quick-Create & Slot Selection"
298
+ ]
299
+ ];
300
+ const CalendarViewsSection = ({
301
+ t,
302
+ settings,
303
+ onPatchSettings,
304
+ mode = "all"
305
+ }) => {
306
+ const showTheme = mode === "all" || mode === "theme";
307
+ const showGeneral = mode === "all" || mode === "general";
308
+ const renderSelect = (label, hint, placeholder, value, options, onChange) => /* @__PURE__ */ jsxs(Field.Root, { hint: t(hint[0], hint[1]), children: [
309
+ /* @__PURE__ */ jsx(Field.Label, { children: t(label[0], label[1]) }),
310
+ /* @__PURE__ */ jsx(
311
+ SingleSelect,
312
+ {
313
+ placeholder: t(placeholder[0], placeholder[1]),
314
+ value,
315
+ onChange: (val) => onChange(String(val)),
316
+ children: options.map(([optVal, optIdOrLabel, optDef]) => /* @__PURE__ */ jsx(SingleSelectOption, { value: optVal, children: optDef ? t(optIdOrLabel, optDef) : optIdOrLabel }, optVal))
317
+ }
318
+ ),
319
+ /* @__PURE__ */ jsx(Field.Hint, {})
320
+ ] });
321
+ const renderText = (label, hint, placeholder, value, onChange) => /* @__PURE__ */ jsxs(Field.Root, { hint: t(hint[0], hint[1]), children: [
322
+ /* @__PURE__ */ jsx(Field.Label, { children: t(label[0], label[1]) }),
323
+ /* @__PURE__ */ jsx(
324
+ TextInput,
325
+ {
326
+ value,
327
+ onChange: (e) => onChange(e.target.value),
328
+ placeholder: t(placeholder[0], placeholder[1])
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsx(Field.Hint, {})
332
+ ] });
333
+ const extraLocales = SUPPORTED_CALENDAR_LOCALES.filter(
334
+ (loc) => !["es", "en", "ar", "tr"].includes(loc.code)
335
+ ).map((loc) => [
336
+ loc.code,
337
+ `${loc.nativeName} — ${loc.label} (${loc.code.toUpperCase()})`
338
+ ]);
339
+ return /* @__PURE__ */ jsx(
340
+ Box,
341
+ {
342
+ padding: 5,
343
+ background: "neutral0",
344
+ hasRadius: true,
345
+ shadow: "tableShadow",
346
+ style: { border: "1px solid rgba(128, 128, 128, 0.22)" },
347
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 5, children: [
348
+ showTheme && /* @__PURE__ */ jsx(
349
+ CalendarThemeSection,
350
+ {
351
+ t,
352
+ settings,
353
+ onPatchSettings
354
+ }
355
+ ),
356
+ showTheme && showGeneral && /* @__PURE__ */ jsx(Divider, {}),
357
+ showGeneral && /* @__PURE__ */ jsxs(Fragment, { children: [
358
+ /* @__PURE__ */ jsxs(Box, { children: [
359
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", tag: "h2", children: t(
360
+ "settings.section.general",
361
+ "Language, TimeGrid & Schedule Configuration"
362
+ ) }),
363
+ /* @__PURE__ */ jsx(Box, { paddingTop: 1, paddingBottom: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
364
+ "settings.generalHint",
365
+ "Configure the primary local language, optional Arabic/Hijri secondary calendar display, timezone, and daily operating hours."
366
+ ) }) }),
367
+ /* @__PURE__ */ jsxs("div", { style: GRID_3_COL$1, children: [
368
+ renderSelect(
369
+ ["settings.defaultLocale", "Primary Calendar Language"],
370
+ [
371
+ "settings.defaultLocaleHint",
372
+ "Language for month names, weekdays, and toolbar buttons."
373
+ ],
374
+ [
375
+ "settings.defaultLocale.placeholder",
376
+ "Select primary language"
377
+ ],
378
+ settings.defaultLocale || "auto",
379
+ [
380
+ ["es", "settings.locale.es", "Español — Spanish (ES)"],
381
+ ["en", "settings.locale.en", "English (EN)"],
382
+ ["ar", "settings.locale.ar", "العربية — Arabic (AR)"],
383
+ ["tr", "settings.locale.tr", "Türkçe — Turkish (TR)"],
384
+ [
385
+ "auto",
386
+ "settings.locale.auto",
387
+ "Auto (Match Strapi Admin UI)"
388
+ ],
389
+ ...extraLocales
390
+ ],
391
+ (defaultLocale) => onPatchSettings({ defaultLocale })
392
+ ),
393
+ renderSelect(
394
+ [
395
+ "settings.secondaryCalendarLanguage",
396
+ "Secondary Hijri / Arabic Date Display"
397
+ ],
398
+ [
399
+ "settings.secondaryCalendarLanguageHint",
400
+ "Choose whether Hijri dates appear in local language, Arabic script, or hidden."
401
+ ],
402
+ [
403
+ "settings.secondaryCalendarLanguage.placeholder",
404
+ "Select Hijri display mode"
405
+ ],
406
+ settings.secondaryCalendarLanguage || "local",
407
+ [
408
+ [
409
+ "local",
410
+ "settings.secondaryCalendarLanguage.local",
411
+ "Local Language (e.g. 16 Rabiʻ II)"
412
+ ],
413
+ [
414
+ "ar",
415
+ "settings.secondaryCalendarLanguage.ar",
416
+ "Arabic Script — العربية (e.g. ١٦ ربيع الآخر)"
417
+ ],
418
+ [
419
+ "off",
420
+ "settings.secondaryCalendarLanguage.off",
421
+ "Off (Gregorian Calendar Only)"
422
+ ]
423
+ ],
424
+ (val) => onPatchSettings({
425
+ secondaryCalendarLanguage: val
426
+ })
427
+ ),
428
+ renderSelect(
429
+ ["settings.direction", "Layout Direction"],
430
+ [
431
+ "settings.directionHint",
432
+ "Grid column order (LTR or RTL for Arabic)."
433
+ ],
434
+ ["settings.direction.placeholder", "Select layout direction"],
435
+ settings.direction || "auto",
436
+ [
437
+ [
438
+ "auto",
439
+ "settings.direction.auto",
440
+ "Auto (LTR / RTL by Language)"
441
+ ],
442
+ ["ltr", "settings.direction.ltr", "Left-to-Right (LTR)"],
443
+ ["rtl", "settings.direction.rtl", "Right-to-Left (RTL)"]
444
+ ],
445
+ (val) => onPatchSettings({
446
+ direction: val
447
+ })
448
+ ),
449
+ renderSelect(
450
+ ["settings.defaultView", "Default Calendar View"],
451
+ [
452
+ "settings.defaultViewHint",
453
+ "Initial view displayed when opening the calendar."
454
+ ],
455
+ ["settings.defaultView.placeholder", "Select default view"],
456
+ settings.defaultView || "dayGridMonth",
457
+ [
458
+ [
459
+ "dayGridMonth",
460
+ "settings.defaultView.month",
461
+ "Month Grid"
462
+ ],
463
+ [
464
+ "timeGridWeek",
465
+ "settings.defaultView.week",
466
+ "Week (TimeGrid)"
467
+ ],
468
+ [
469
+ "timeGridDay",
470
+ "settings.defaultView.day",
471
+ "Day (TimeGrid)"
472
+ ],
473
+ ["listMonth", "settings.defaultView.agenda", "Agenda List"],
474
+ [
475
+ "multiMonthYear",
476
+ "settings.defaultView.year",
477
+ "Full Year (3x4 Grid)"
478
+ ],
479
+ [
480
+ "multiMonthYearStacked",
481
+ "settings.defaultView.yearStacked",
482
+ "Year (Stacked Months)"
483
+ ],
484
+ [
485
+ "dayGridYear",
486
+ "settings.defaultView.dayGridYear",
487
+ "Year (Continuous Grid)"
488
+ ]
489
+ ],
490
+ (val) => onPatchSettings({ defaultView: val })
491
+ ),
492
+ renderSelect(
493
+ ["settings.firstDayOfWeek", "First Day of Week"],
494
+ [
495
+ "settings.firstDayHint",
496
+ "Starting weekday column in Month and Week views."
497
+ ],
498
+ [
499
+ "settings.firstDayOfWeek.placeholder",
500
+ "Select first day of week"
501
+ ],
502
+ String(settings.firstDayOfWeek ?? 1),
503
+ [
504
+ ["1", "settings.firstDay.monday", "Monday (1)"],
505
+ ["0", "settings.firstDay.sunday", "Sunday (0)"],
506
+ ["6", "settings.firstDay.saturday", "Saturday (6)"]
507
+ ],
508
+ (val) => onPatchSettings({
509
+ firstDayOfWeek: Number.parseInt(val, 10)
510
+ })
511
+ ),
512
+ renderSelect(
513
+ ["settings.weekNumberCalculation", "Week Calculation"],
514
+ [
515
+ "settings.weekCalcHint",
516
+ "Standard used when computing week numbers."
517
+ ],
518
+ [
519
+ "settings.weekNumberCalculation.placeholder",
520
+ "Select week calculation mode"
521
+ ],
522
+ settings.weekNumberCalculation || "ISO",
523
+ [
524
+ ["ISO", "settings.weekCalc.iso", "ISO 8601 (Monday)"],
525
+ ["local", "settings.weekCalc.local", "Locale Default"]
526
+ ],
527
+ (val) => onPatchSettings({
528
+ weekNumberCalculation: val
529
+ })
530
+ ),
531
+ renderText(
532
+ ["settings.defaultTimezone", "Default Timezone (IANA)"],
533
+ [
534
+ "settings.timezoneHint",
535
+ "IANA timezone identifier (e.g., UTC or America/New_York)."
536
+ ],
537
+ ["settings.defaultTimezone.placeholder", "e.g., UTC"],
538
+ settings.defaultTimezone || "UTC",
539
+ (defaultTimezone) => onPatchSettings({ defaultTimezone })
540
+ ),
541
+ renderText(
542
+ ["settings.startHour", "Daily Start Hour"],
543
+ [
544
+ "settings.startHourHint",
545
+ "Earliest visible time slot in Week/Day TimeGrid (HH:MM:SS)."
546
+ ],
547
+ ["settings.startHour.placeholder", "e.g., 07:00:00"],
548
+ settings.startHour || "07:00:00",
549
+ (startHour) => onPatchSettings({ startHour })
550
+ ),
551
+ renderText(
552
+ ["settings.endHour", "Daily End Hour"],
553
+ [
554
+ "settings.endHourHint",
555
+ "Latest visible time slot in Week/Day TimeGrid (HH:MM:SS)."
556
+ ],
557
+ ["settings.endHour.placeholder", "e.g., 21:00:00"],
558
+ settings.endHour || "21:00:00",
559
+ (endHour) => onPatchSettings({ endHour })
560
+ )
561
+ ] }),
562
+ /* @__PURE__ */ jsxs("div", { style: { ...GRID_2_COL$1, marginTop: 16 }, children: [
563
+ renderSelect(
564
+ ["settings.slotDuration", "TimeGrid Slot Duration"],
565
+ [
566
+ "settings.slotDurationHint",
567
+ "Time interval for rows in Week and Day TimeGrid views."
568
+ ],
569
+ ["settings.slotDuration.placeholder", "Select slot interval"],
570
+ settings.slotDuration || "00:30:00",
571
+ [
572
+ [
573
+ "00:15:00",
574
+ "settings.slotDuration.15m",
575
+ "15 min (00:15:00)"
576
+ ],
577
+ [
578
+ "00:30:00",
579
+ "settings.slotDuration.30m",
580
+ "30 min (00:30:00)"
581
+ ],
582
+ [
583
+ "01:00:00",
584
+ "settings.slotDuration.60m",
585
+ "60 min (01:00:00)"
586
+ ]
587
+ ],
588
+ (slotDuration) => onPatchSettings({ slotDuration })
589
+ ),
590
+ renderText(
591
+ ["settings.feedTitle", "Public iCal (.ics) Feed Title"],
592
+ [
593
+ "settings.feedTitleHint",
594
+ "Calendar title exported in /api/calendar/feed.ics for Google/Apple/Outlook subscriptions."
595
+ ],
596
+ [
597
+ "settings.feedTitle.placeholder",
598
+ "e.g., Organization — General Calendar"
599
+ ],
600
+ settings.feedTitle || t(
601
+ "settings.defaultFeedTitle",
602
+ "Organization — General Calendar"
603
+ ),
604
+ (feedTitle) => onPatchSettings({ feedTitle })
605
+ )
606
+ ] })
607
+ ] }),
608
+ /* @__PURE__ */ jsx(Divider, {}),
609
+ /* @__PURE__ */ jsx("div", { style: GRID_2_COL$1, children: [
610
+ [
611
+ "settings.enabledViewsGroup",
612
+ "Enabled Calendar View Tabs",
613
+ VIEW_CHECKBOXES
614
+ ],
615
+ [
616
+ "settings.gridBehaviorGroup",
617
+ "Grid Display & Interaction Options",
618
+ BEHAVIOR_CHECKBOXES
619
+ ]
620
+ ].map(([groupId, groupDef, items]) => /* @__PURE__ */ jsx(
621
+ Box,
622
+ {
623
+ padding: 4,
624
+ background: "neutral100",
625
+ hasRadius: true,
626
+ style: SUB_BOX_STYLE$1,
627
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 3, children: [
628
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral700", children: t(groupId, groupDef) }),
629
+ /* @__PURE__ */ jsx(Flex, { gap: 4, wrap: "wrap", children: items.map(([key, id, def]) => /* @__PURE__ */ jsx(
630
+ Checkbox,
631
+ {
632
+ checked: key === "timeFormat24h" ? settings.timeFormat24h !== false : Boolean(settings[key]),
633
+ onCheckedChange: (c) => onPatchSettings({ [key]: Boolean(c) }),
634
+ children: t(id, def)
635
+ },
636
+ key
637
+ )) })
638
+ ] })
639
+ },
640
+ groupId
641
+ )) })
642
+ ] })
643
+ ] })
644
+ }
645
+ );
646
+ };
647
+ const CARD_BORDER_STYLE = {
648
+ border: "1px solid rgba(128, 128, 128, 0.22)"
649
+ };
650
+ const SUB_CARD_BORDER_STYLE = {
651
+ border: "1px solid rgba(128, 128, 128, 0.18)"
652
+ };
653
+ const GRID_3_STYLE = {
654
+ display: "grid",
655
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
656
+ gap: 16
657
+ };
658
+ const GRID_2_STYLE = {
659
+ display: "grid",
660
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
661
+ gap: 16
662
+ };
663
+ const TITLE_CANDIDATES = "title,name,customerName,visitorName,headline,subject,fullName,slug".split(
664
+ ","
665
+ );
666
+ const START_CANDIDATES = "startDate,preferredDate,date,scheduledAt,releasedAt,publishedAt,createdAt,updatedAt".split(
667
+ ","
668
+ );
669
+ const CollectionSourcesSection = ({
670
+ t,
671
+ sources,
672
+ contentTypes,
673
+ onAddOrUpdateSource,
674
+ onToggleSourceEnabled,
675
+ onRemoveSource
676
+ }) => {
677
+ const [isFormOpen, setIsFormOpen] = useState(true);
678
+ const [editingSourceId, setEditingSourceId] = useState(null);
679
+ const [newSourceUid, setNewSourceUid] = useState("plugin::events.event");
680
+ const [newSourceLabel, setNewSourceLabel] = useState(
681
+ () => t("settings.defaultSourceLabel", "Events")
682
+ );
683
+ const [newSourceColor, setNewSourceColor] = useState("#0284C7");
684
+ const [newTitleField, setNewTitleField] = useState("title");
685
+ const [newStartField, setNewStartField] = useState("startDate");
686
+ const [newEndField, setNewEndField] = useState("endDate");
687
+ const [newRRuleField, setNewRRuleField] = useState("recurrence");
688
+ const selectCollectionDefaults = (ct) => {
689
+ setEditingSourceId(null);
690
+ setNewSourceUid(ct.uid);
691
+ setNewSourceLabel(ct.displayName || ct.uid);
692
+ const fieldNames = ct.fields.map((f) => f.name);
693
+ const findMatch = (candidates) => fieldNames.find((n) => candidates.includes(n));
694
+ const bestTitle = findMatch(TITLE_CANDIDATES) ?? ct.fields.find((f) => ["string", "text", "uid"].includes(f.type))?.name ?? ct.fields[0]?.name ?? "title";
695
+ const bestStart = findMatch(START_CANDIDATES) ?? ct.fields.find((f) => f.type === "datetime" || f.type === "date")?.name ?? ct.fields[0]?.name ?? "startDate";
696
+ setNewTitleField(bestTitle);
697
+ setNewStartField(bestStart);
698
+ setNewEndField(
699
+ findMatch(["endDate", "finishDate", "expiresAt"]) ?? "endDate"
700
+ );
701
+ setNewRRuleField(
702
+ findMatch(["recurrence", "rrule", "recurrenceRule"]) ?? "recurrence"
703
+ );
704
+ };
705
+ useEffect(() => {
706
+ if (contentTypes.length === 0) return;
707
+ const preferred = contentTypes.find((ct) => ct.uid === newSourceUid) ?? contentTypes.find((ct) => ct.uid === "plugin::events.event") ?? contentTypes.find((ct) => !sources.some((s) => s.uid === ct.uid)) ?? contentTypes[0];
708
+ if (preferred) selectCollectionDefaults(preferred);
709
+ }, [contentTypes]);
710
+ const selectedCtFields = useMemo(() => {
711
+ const found = contentTypes.find((ct) => ct.uid === newSourceUid);
712
+ const base = found?.fields ? [...found.fields] : [];
713
+ const ensureField = (name, fallbackType) => {
714
+ if (name && !base.some((f) => f.name === name)) {
715
+ base.push({ name, type: fallbackType });
716
+ }
717
+ };
718
+ ensureField(newTitleField || "title", "string");
719
+ ensureField(newStartField || "startDate", "datetime");
720
+ return base;
721
+ }, [contentTypes, newSourceUid, newTitleField, newStartField]);
722
+ const handleEditSource = (src) => {
723
+ setEditingSourceId(src.id);
724
+ setNewSourceUid(src.uid);
725
+ setNewSourceLabel(src.label);
726
+ setNewSourceColor(src.color || "#0284C7");
727
+ setNewTitleField(src.fields.title || "title");
728
+ setNewStartField(src.fields.startDate || "startDate");
729
+ setNewEndField(src.fields.endDate || "");
730
+ setNewRRuleField(src.fields.rrule || "");
731
+ setIsFormOpen(true);
732
+ };
733
+ const handleSaveSource = () => {
734
+ if (!newSourceUid || !newTitleField || !newStartField) return;
735
+ const existing = sources.find(
736
+ (s) => s.id === editingSourceId || s.uid === newSourceUid
737
+ );
738
+ const idSlug = (newSourceLabel || newSourceUid).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
739
+ const nextSource = {
740
+ ...existing,
741
+ id: existing?.id || idSlug || `source-${Date.now()}`,
742
+ label: newSourceLabel.trim() || newSourceUid,
743
+ uid: newSourceUid,
744
+ color: /^#[0-9a-fA-F]{3,8}$/.test(newSourceColor.trim()) ? newSourceColor.trim() : "#0284C7",
745
+ enabled: existing?.enabled ?? true,
746
+ fields: {
747
+ ...existing?.fields,
748
+ title: newTitleField,
749
+ startDate: newStartField,
750
+ endDate: newEndField.trim() || void 0,
751
+ rrule: newRRuleField.trim() || void 0
752
+ }
753
+ };
754
+ setEditingSourceId(null);
755
+ onAddOrUpdateSource(nextSource);
756
+ };
757
+ const renderTextField = (label, hint, placeholder, value, onChange) => /* @__PURE__ */ jsxs(Field.Root, { hint: t(hint[0], hint[1]), children: [
758
+ /* @__PURE__ */ jsx(Field.Label, { children: t(label[0], label[1]) }),
759
+ /* @__PURE__ */ jsx(
760
+ TextInput,
761
+ {
762
+ value,
763
+ onChange: (e) => onChange(e.target.value),
764
+ placeholder: t(placeholder[0], placeholder[1])
765
+ }
766
+ ),
767
+ /* @__PURE__ */ jsx(Field.Hint, {})
768
+ ] });
769
+ const renderFieldSelect = (label, hint, placeholder, value, onChange) => /* @__PURE__ */ jsxs(Field.Root, { required: true, hint: t(hint[0], hint[1]), children: [
770
+ /* @__PURE__ */ jsx(Field.Label, { children: t(label[0], label[1]) }),
771
+ /* @__PURE__ */ jsx(
772
+ SingleSelect,
773
+ {
774
+ placeholder: t(placeholder[0], placeholder[1]),
775
+ value,
776
+ onChange: (val) => onChange(String(val)),
777
+ children: selectedCtFields.map((f) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: f.name, children: [
778
+ f.name,
779
+ " (",
780
+ f.type,
781
+ ")"
782
+ ] }, f.name))
783
+ }
784
+ ),
785
+ /* @__PURE__ */ jsx(Field.Hint, {})
786
+ ] });
787
+ return /* @__PURE__ */ jsx(
788
+ Box,
789
+ {
790
+ padding: 5,
791
+ background: "neutral0",
792
+ hasRadius: true,
793
+ shadow: "tableShadow",
794
+ style: CARD_BORDER_STYLE,
795
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
796
+ /* @__PURE__ */ jsxs(
797
+ Flex,
798
+ {
799
+ justifyContent: "space-between",
800
+ alignItems: "flex-start",
801
+ wrap: "wrap",
802
+ gap: 3,
803
+ children: [
804
+ /* @__PURE__ */ jsxs(Box, { style: { flex: 1, minWidth: 280 }, children: [
805
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", tag: "h2", children: t("settings.section.sources", "Mapped Collection Sources") }),
806
+ /* @__PURE__ */ jsx(Box, { paddingTop: 1, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
807
+ "settings.sourcesHint",
808
+ "Connects Strapi Collection Types (such as Events or Bookings) so their records are automatically plotted on the unified calendar. Each mapping defines which fields represent the item title, start/end dates, and optional recurrence rules."
809
+ ) }) })
810
+ ] }),
811
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
812
+ /* @__PURE__ */ jsxs(Badge, { active: true, children: [
813
+ sources.length,
814
+ " ",
815
+ t("settings.mappedCount", "Mapped")
816
+ ] }),
817
+ /* @__PURE__ */ jsx(
818
+ Button,
819
+ {
820
+ variant: isFormOpen ? "tertiary" : "secondary",
821
+ size: "S",
822
+ onClick: () => setIsFormOpen((prev) => !prev),
823
+ children: isFormOpen ? t("common.hideForm", "Hide Mapping Form") : `+ ${t("settings.addSourceToggle", "Map Collection Type")}`
824
+ }
825
+ )
826
+ ] })
827
+ ]
828
+ }
829
+ ),
830
+ sources.length === 0 ? /* @__PURE__ */ jsx(
831
+ Box,
832
+ {
833
+ padding: 4,
834
+ background: "neutral100",
835
+ hasRadius: true,
836
+ style: SUB_CARD_BORDER_STYLE,
837
+ children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: t(
838
+ "settings.noSources",
839
+ "No collection types are mapped yet. Use the form below to plot Events, Bookings, or Articles on the calendar."
840
+ ) })
841
+ }
842
+ ) : /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: sources.map((src) => /* @__PURE__ */ jsx(
843
+ Box,
844
+ {
845
+ padding: 4,
846
+ background: "neutral100",
847
+ hasRadius: true,
848
+ style: {
849
+ ...SUB_CARD_BORDER_STYLE,
850
+ borderLeft: `4px solid ${src.color || "#0284C7"}`
851
+ },
852
+ children: /* @__PURE__ */ jsxs(
853
+ Flex,
854
+ {
855
+ justifyContent: "space-between",
856
+ alignItems: "center",
857
+ wrap: "wrap",
858
+ gap: 3,
859
+ children: [
860
+ /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", wrap: "wrap", children: [
861
+ /* @__PURE__ */ jsx(
862
+ "span",
863
+ {
864
+ style: {
865
+ display: "inline-block",
866
+ width: 14,
867
+ height: 14,
868
+ borderRadius: "50%",
869
+ background: src.color
870
+ }
871
+ }
872
+ ),
873
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: src.label }),
874
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: src.uid }),
875
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
876
+ t("settings.badge.title", "Title"),
877
+ ": ",
878
+ src.fields.title,
879
+ " ·",
880
+ " ",
881
+ t("settings.badge.start", "Start"),
882
+ ":",
883
+ " ",
884
+ src.fields.startDate,
885
+ src.fields.endDate ? ` · ${t("settings.badge.end", "End")}: ${src.fields.endDate}` : ""
886
+ ] })
887
+ ] }),
888
+ /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", children: [
889
+ /* @__PURE__ */ jsx(
890
+ Checkbox,
891
+ {
892
+ checked: src.enabled,
893
+ onCheckedChange: (checked) => onToggleSourceEnabled(src.id, Boolean(checked)),
894
+ children: t("settings.sourceEnabled", "Enabled")
895
+ }
896
+ ),
897
+ /* @__PURE__ */ jsx(
898
+ Button,
899
+ {
900
+ variant: "tertiary",
901
+ size: "S",
902
+ onClick: () => handleEditSource(src),
903
+ children: t("common.edit", "Edit")
904
+ }
905
+ ),
906
+ /* @__PURE__ */ jsx(
907
+ Button,
908
+ {
909
+ variant: "danger-light",
910
+ size: "S",
911
+ onClick: () => onRemoveSource(src.id),
912
+ children: t("common.remove", "Remove")
913
+ }
914
+ )
915
+ ] })
916
+ ]
917
+ }
918
+ )
919
+ },
920
+ src.id
921
+ )) }),
922
+ isFormOpen && /* @__PURE__ */ jsx(
923
+ Box,
924
+ {
925
+ padding: 5,
926
+ background: "neutral100",
927
+ hasRadius: true,
928
+ style: SUB_CARD_BORDER_STYLE,
929
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
930
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral700", children: t(
931
+ "settings.addSourceTitle",
932
+ "Configure Collection Type Mapping"
933
+ ) }),
934
+ /* @__PURE__ */ jsxs("div", { style: GRID_3_STYLE, children: [
935
+ /* @__PURE__ */ jsxs(
936
+ Field.Root,
937
+ {
938
+ hint: t(
939
+ "settings.collectionTypeHint",
940
+ "Select which Strapi Collection Type provides the dated entries."
941
+ ),
942
+ children: [
943
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.collectionType", "Strapi Collection Type") }),
944
+ contentTypes.length > 0 ? /* @__PURE__ */ jsx(
945
+ SingleSelect,
946
+ {
947
+ placeholder: t(
948
+ "settings.collectionType.placeholder",
949
+ "Select Strapi Collection Type"
950
+ ),
951
+ value: newSourceUid || contentTypes[0]?.uid || "plugin::events.event",
952
+ onChange: (val) => {
953
+ const uid = String(val);
954
+ const found = contentTypes.find((c) => c.uid === uid);
955
+ if (found) {
956
+ selectCollectionDefaults(found);
957
+ } else {
958
+ setNewSourceUid(uid);
959
+ }
960
+ },
961
+ children: contentTypes.map((ct) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: ct.uid, children: [
962
+ ct.displayName,
963
+ " (",
964
+ ct.uid,
965
+ ")"
966
+ ] }, ct.uid))
967
+ }
968
+ ) : /* @__PURE__ */ jsx(
969
+ TextInput,
970
+ {
971
+ value: newSourceUid,
972
+ onChange: (e) => setNewSourceUid(e.target.value),
973
+ placeholder: t(
974
+ "settings.collectionTypeUid.placeholder",
975
+ "e.g., plugin::events.event"
976
+ )
977
+ }
978
+ ),
979
+ /* @__PURE__ */ jsx(Field.Hint, {})
980
+ ]
981
+ }
982
+ ),
983
+ renderTextField(
984
+ ["settings.sourceLabel", "Calendar Filter Label"],
985
+ [
986
+ "settings.sourceLabelHint",
987
+ "Human-readable label shown on calendar filter chips."
988
+ ],
989
+ ["settings.sourceLabel.placeholder", "e.g., Events"],
990
+ newSourceLabel,
991
+ setNewSourceLabel
992
+ ),
993
+ renderTextField(
994
+ ["settings.sourceColor", "Event Badge Color (Hex)"],
995
+ [
996
+ "settings.sourceColorHint",
997
+ "Hex color accent applied to calendar chips for this source."
998
+ ],
999
+ ["settings.sourceColor.placeholder", "e.g., #0284C7"],
1000
+ newSourceColor,
1001
+ setNewSourceColor
1002
+ )
1003
+ ] }),
1004
+ /* @__PURE__ */ jsxs("div", { style: GRID_2_STYLE, children: [
1005
+ renderFieldSelect(
1006
+ ["settings.field.title", "Title Field"],
1007
+ [
1008
+ "settings.field.titleHint",
1009
+ "Attribute displayed as the calendar event title."
1010
+ ],
1011
+ ["settings.field.title.placeholder", "Select title field"],
1012
+ newTitleField || "title",
1013
+ setNewTitleField
1014
+ ),
1015
+ renderFieldSelect(
1016
+ ["settings.field.startDate", "Start Date Field"],
1017
+ [
1018
+ "settings.field.startDateHint",
1019
+ "Date or DateTime field used to place the entry on the grid."
1020
+ ],
1021
+ [
1022
+ "settings.field.startDate.placeholder",
1023
+ "Select start date field"
1024
+ ],
1025
+ newStartField || "startDate",
1026
+ setNewStartField
1027
+ ),
1028
+ renderTextField(
1029
+ ["settings.field.endDate", "End Date Field (Optional)"],
1030
+ [
1031
+ "settings.field.endDateHint",
1032
+ "Optional end Date/DateTime attribute for multi-hour or multi-day spans."
1033
+ ],
1034
+ ["settings.field.endDate.placeholder", "e.g., endDate"],
1035
+ newEndField,
1036
+ setNewEndField
1037
+ ),
1038
+ renderTextField(
1039
+ ["settings.field.rrule", "Recurrence Field (Optional)"],
1040
+ [
1041
+ "settings.field.rruleHint",
1042
+ "Optional RFC 5545 RRULE field for repeating schedules."
1043
+ ],
1044
+ ["settings.field.rrule.placeholder", "e.g., recurrence"],
1045
+ newRRuleField,
1046
+ setNewRRuleField
1047
+ )
1048
+ ] }),
1049
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", gap: 2, children: /* @__PURE__ */ jsxs(Button, { variant: "default", onClick: handleSaveSource, children: [
1050
+ "+ ",
1051
+ t("settings.addSourceButton", "Save Collection Source")
1052
+ ] }) })
1053
+ ] })
1054
+ }
1055
+ )
1056
+ ] })
1057
+ }
1058
+ );
1059
+ };
1060
+ const CARD_STYLE = {
1061
+ border: "1px solid rgba(128, 128, 128, 0.22)"
1062
+ };
1063
+ const SUB_BOX_STYLE = {
1064
+ border: "1px solid rgba(128, 128, 128, 0.18)"
1065
+ };
1066
+ const GRID_2_COL = {
1067
+ display: "grid",
1068
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
1069
+ gap: 16
1070
+ };
1071
+ const GRID_3_COL = {
1072
+ display: "grid",
1073
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
1074
+ gap: 16
1075
+ };
1076
+ const BOOKING_NUMBER_FIELDS = [
1077
+ {
1078
+ key: "defaultSlotDurationMinutes",
1079
+ labelId: "settings.bookingsSlotDuration",
1080
+ labelDefault: "Slot Duration (Minutes)",
1081
+ hintId: "settings.bookingsSlotDurationHint",
1082
+ hintDefault: "Standard length of each bookable appointment slot.",
1083
+ min: 5,
1084
+ fallback: 60
1085
+ },
1086
+ {
1087
+ key: "defaultCapacityPerSlot",
1088
+ labelId: "settings.bookingsCapacityPerSlot",
1089
+ labelDefault: "Default Capacity Per Slot",
1090
+ hintId: "settings.bookingsCapacityPerSlotHint",
1091
+ hintDefault: "Concurrent reservations allowed per time slot.",
1092
+ min: 1,
1093
+ fallback: 1
1094
+ },
1095
+ {
1096
+ key: "defaultBufferBeforeMinutes",
1097
+ labelId: "settings.bookingsBufferBefore",
1098
+ labelDefault: "Buffer Before (Minutes)",
1099
+ hintId: "settings.bookingsBufferBeforeHint",
1100
+ hintDefault: "Preparation gap blocked before each booking starts.",
1101
+ min: 0,
1102
+ fallback: 0
1103
+ },
1104
+ {
1105
+ key: "defaultBufferAfterMinutes",
1106
+ labelId: "settings.bookingsBufferAfter",
1107
+ labelDefault: "Buffer After (Minutes)",
1108
+ hintId: "settings.bookingsBufferAfterHint",
1109
+ hintDefault: "Cooldown gap blocked after each booking ends.",
1110
+ min: 0,
1111
+ fallback: 0
1112
+ },
1113
+ {
1114
+ key: "defaultMinLeadTimeHours",
1115
+ labelId: "settings.bookingsMinLeadHours",
1116
+ labelDefault: "Minimum Lead Time (Hours)",
1117
+ hintId: "settings.bookingsMinLeadHoursHint",
1118
+ hintDefault: "Minimum notice required before a slot can be booked.",
1119
+ min: 0,
1120
+ fallback: 2
1121
+ },
1122
+ {
1123
+ key: "defaultMaxAdvanceDays",
1124
+ labelId: "settings.bookingsMaxAdvanceDays",
1125
+ labelDefault: "Max Advance Horizon (Days)",
1126
+ hintId: "settings.bookingsMaxAdvanceDaysHint",
1127
+ hintDefault: "How far in advance visitors may schedule bookings.",
1128
+ min: 1,
1129
+ fallback: 90
1130
+ }
1131
+ ];
1132
+ const SettingCheckboxCard = ({ checked, onChange, label, hint }) => /* @__PURE__ */ jsxs(Box, { padding: 4, background: "neutral100", hasRadius: true, style: SUB_BOX_STYLE, children: [
1133
+ /* @__PURE__ */ jsx(
1134
+ Checkbox,
1135
+ {
1136
+ checked,
1137
+ onCheckedChange: (c) => onChange(Boolean(c)),
1138
+ children: label
1139
+ }
1140
+ ),
1141
+ /* @__PURE__ */ jsx(Box, { paddingTop: 2, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: hint }) })
1142
+ ] });
1143
+ const CompanionSuiteSettingsSection = ({
1144
+ t,
1145
+ mode,
1146
+ eventsSettings,
1147
+ bookingsSettings,
1148
+ onPatchEvents,
1149
+ onPatchBookings
1150
+ }) => {
1151
+ if (mode === "events") {
1152
+ if (!eventsSettings) {
1153
+ return /* @__PURE__ */ jsx(
1154
+ Box,
1155
+ {
1156
+ padding: 5,
1157
+ background: "neutral0",
1158
+ hasRadius: true,
1159
+ shadow: "tableShadow",
1160
+ style: CARD_STYLE,
1161
+ children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: t(
1162
+ "settings.eventsUnavailable",
1163
+ "Events & RSVPs plugin settings are loading or not installed."
1164
+ ) })
1165
+ }
1166
+ );
1167
+ }
1168
+ return /* @__PURE__ */ jsx(
1169
+ Box,
1170
+ {
1171
+ padding: 5,
1172
+ background: "neutral0",
1173
+ hasRadius: true,
1174
+ shadow: "tableShadow",
1175
+ style: CARD_STYLE,
1176
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 5, children: [
1177
+ /* @__PURE__ */ jsxs(
1178
+ Flex,
1179
+ {
1180
+ justifyContent: "space-between",
1181
+ alignItems: "center",
1182
+ wrap: "wrap",
1183
+ gap: 2,
1184
+ children: [
1185
+ /* @__PURE__ */ jsxs(Box, { children: [
1186
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", tag: "h2", children: t(
1187
+ "settings.eventsHeading",
1188
+ "Events, RSVP Capacity & Waitlist Automation"
1189
+ ) }),
1190
+ /* @__PURE__ */ jsx(Box, { paddingTop: 1, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
1191
+ "settings.eventsHint",
1192
+ "Configure default RSVP seat capacity, guest limits per registration, automatic FIFO waitlist promotion, and public Events iCal (.ics) feed."
1193
+ ) }) })
1194
+ ] }),
1195
+ /* @__PURE__ */ jsxs(Badge, { active: true, children: [
1196
+ t("settings.badge.timezone", "Timezone"),
1197
+ ":",
1198
+ " ",
1199
+ eventsSettings.defaultTimezone || "UTC"
1200
+ ] })
1201
+ ]
1202
+ }
1203
+ ),
1204
+ /* @__PURE__ */ jsxs("div", { style: GRID_2_COL, children: [
1205
+ /* @__PURE__ */ jsxs(
1206
+ Field.Root,
1207
+ {
1208
+ hint: t(
1209
+ "settings.eventsDefaultCapacityHint",
1210
+ "Default seat capacity for newly created RSVP events."
1211
+ ),
1212
+ children: [
1213
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.eventsDefaultCapacity", "Default Event Capacity") }),
1214
+ /* @__PURE__ */ jsx(
1215
+ NumberInput,
1216
+ {
1217
+ value: eventsSettings.defaultCapacity || 100,
1218
+ onValueChange: (val) => onPatchEvents({
1219
+ defaultCapacity: val && val >= 1 ? val : 100
1220
+ })
1221
+ }
1222
+ ),
1223
+ /* @__PURE__ */ jsx(Field.Hint, {})
1224
+ ]
1225
+ }
1226
+ ),
1227
+ /* @__PURE__ */ jsxs(
1228
+ Field.Root,
1229
+ {
1230
+ hint: t(
1231
+ "settings.eventsMaxGuestsHint",
1232
+ "Maximum seats a single RSVP submission may reserve."
1233
+ ),
1234
+ children: [
1235
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.eventsMaxGuests", "Max Guests per Registration") }),
1236
+ /* @__PURE__ */ jsx(
1237
+ NumberInput,
1238
+ {
1239
+ value: eventsSettings.defaultMaxGuestsPerRegistration || 5,
1240
+ onValueChange: (val) => onPatchEvents({
1241
+ defaultMaxGuestsPerRegistration: val && val >= 1 ? val : 5
1242
+ })
1243
+ }
1244
+ ),
1245
+ /* @__PURE__ */ jsx(Field.Hint, {})
1246
+ ]
1247
+ }
1248
+ )
1249
+ ] }),
1250
+ /* @__PURE__ */ jsxs("div", { style: GRID_2_COL, children: [
1251
+ /* @__PURE__ */ jsx(
1252
+ SettingCheckboxCard,
1253
+ {
1254
+ checked: eventsSettings.waitlistEnabledByDefault,
1255
+ onChange: (waitlistEnabledByDefault) => onPatchEvents({ waitlistEnabledByDefault }),
1256
+ label: t(
1257
+ "settings.eventsWaitlist",
1258
+ "Enable Waitlist & Automatic Promotion"
1259
+ ),
1260
+ hint: t(
1261
+ "settings.eventsWaitlistHint",
1262
+ "Automatically promotes waitlisted attendees in FIFO order when a confirmed attendee cancels."
1263
+ )
1264
+ }
1265
+ ),
1266
+ /* @__PURE__ */ jsx(
1267
+ SettingCheckboxCard,
1268
+ {
1269
+ checked: eventsSettings.sendRsvpEmails,
1270
+ onChange: (sendRsvpEmails) => onPatchEvents({ sendRsvpEmails }),
1271
+ label: t(
1272
+ "settings.eventsSendEmails",
1273
+ "Send Confirmation & Waitlist Promotion Emails"
1274
+ ),
1275
+ hint: t(
1276
+ "settings.eventsSendEmailsHint",
1277
+ "Dispatches confirmation and waitlist promotion emails via the Strapi Email plugin."
1278
+ )
1279
+ }
1280
+ )
1281
+ ] }),
1282
+ /* @__PURE__ */ jsx(Divider, {}),
1283
+ /* @__PURE__ */ jsxs("div", { style: GRID_2_COL, children: [
1284
+ /* @__PURE__ */ jsxs(
1285
+ Field.Root,
1286
+ {
1287
+ hint: t(
1288
+ "settings.defaultTimezoneHint",
1289
+ "IANA timezone identifier used for event recurrence and iCal exports."
1290
+ ),
1291
+ children: [
1292
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.defaultTimezone", "Events Timezone (IANA)") }),
1293
+ /* @__PURE__ */ jsx(
1294
+ TextInput,
1295
+ {
1296
+ value: eventsSettings.defaultTimezone || "UTC",
1297
+ onChange: (e) => onPatchEvents({ defaultTimezone: e.target.value })
1298
+ }
1299
+ ),
1300
+ /* @__PURE__ */ jsx(Field.Hint, {})
1301
+ ]
1302
+ }
1303
+ ),
1304
+ /* @__PURE__ */ jsxs(
1305
+ Field.Root,
1306
+ {
1307
+ hint: t(
1308
+ "settings.eventsFeedTitleHint",
1309
+ "Calendar name shown when users subscribe in Google, Apple, or Outlook Calendar."
1310
+ ),
1311
+ children: [
1312
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.eventsFeedTitle", "Events iCal (.ics) Feed Title") }),
1313
+ /* @__PURE__ */ jsx(
1314
+ TextInput,
1315
+ {
1316
+ value: eventsSettings.feedTitle || "Organization — Events Calendar",
1317
+ onChange: (e) => onPatchEvents({ feedTitle: e.target.value })
1318
+ }
1319
+ ),
1320
+ /* @__PURE__ */ jsx(Field.Hint, {})
1321
+ ]
1322
+ }
1323
+ )
1324
+ ] })
1325
+ ] })
1326
+ }
1327
+ );
1328
+ }
1329
+ if (!bookingsSettings) {
1330
+ return /* @__PURE__ */ jsx(
1331
+ Box,
1332
+ {
1333
+ padding: 5,
1334
+ background: "neutral0",
1335
+ hasRadius: true,
1336
+ shadow: "tableShadow",
1337
+ style: CARD_STYLE,
1338
+ children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: t(
1339
+ "settings.bookingsUnavailable",
1340
+ "Bookings & Availability plugin settings are loading or not installed."
1341
+ ) })
1342
+ }
1343
+ );
1344
+ }
1345
+ return /* @__PURE__ */ jsx(
1346
+ Box,
1347
+ {
1348
+ padding: 5,
1349
+ background: "neutral0",
1350
+ hasRadius: true,
1351
+ shadow: "tableShadow",
1352
+ style: CARD_STYLE,
1353
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 5, children: [
1354
+ /* @__PURE__ */ jsxs(Box, { children: [
1355
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", tag: "h2", children: t(
1356
+ "settings.bookingsHeading",
1357
+ "Bookings, Slot Duration & Approval Workflow"
1358
+ ) }),
1359
+ /* @__PURE__ */ jsx(Box, { paddingTop: 1, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
1360
+ "settings.bookingsHint",
1361
+ "Configure default appointment slot durations, capacity limits, preparation buffers, lead times, and manual approval rules."
1362
+ ) }) })
1363
+ ] }),
1364
+ /* @__PURE__ */ jsx("div", { style: GRID_3_COL, children: BOOKING_NUMBER_FIELDS.map((field) => /* @__PURE__ */ jsxs(
1365
+ Field.Root,
1366
+ {
1367
+ hint: t(field.hintId, field.hintDefault),
1368
+ children: [
1369
+ /* @__PURE__ */ jsx(Field.Label, { children: t(field.labelId, field.labelDefault) }),
1370
+ /* @__PURE__ */ jsx(
1371
+ NumberInput,
1372
+ {
1373
+ value: bookingsSettings[field.key],
1374
+ onValueChange: (v) => onPatchBookings({
1375
+ [field.key]: Math.max(
1376
+ field.min,
1377
+ Number(v ?? field.fallback)
1378
+ )
1379
+ })
1380
+ }
1381
+ ),
1382
+ /* @__PURE__ */ jsx(Field.Hint, {})
1383
+ ]
1384
+ },
1385
+ field.key
1386
+ )) }),
1387
+ /* @__PURE__ */ jsx(Divider, {}),
1388
+ /* @__PURE__ */ jsxs("div", { style: GRID_2_COL, children: [
1389
+ /* @__PURE__ */ jsx(
1390
+ SettingCheckboxCard,
1391
+ {
1392
+ checked: bookingsSettings.defaultRequiresApproval,
1393
+ onChange: (defaultRequiresApproval) => onPatchBookings({ defaultRequiresApproval }),
1394
+ label: t(
1395
+ "settings.bookingsRequiresApproval",
1396
+ "Require Manual Admin Approval"
1397
+ ),
1398
+ hint: t(
1399
+ "settings.bookingsRequiresApprovalHint",
1400
+ "Places new visitor reservations in pending status until confirmed by staff."
1401
+ )
1402
+ }
1403
+ ),
1404
+ /* @__PURE__ */ jsx(
1405
+ SettingCheckboxCard,
1406
+ {
1407
+ checked: bookingsSettings.sendEmailNotifications,
1408
+ onChange: (sendEmailNotifications) => onPatchBookings({ sendEmailNotifications }),
1409
+ label: t(
1410
+ "settings.bookingsSendEmails",
1411
+ "Send Confirmation & Status Update Emails"
1412
+ ),
1413
+ hint: t(
1414
+ "settings.bookingsSendEmailsHint",
1415
+ "Automatically notifies visitors when their booking is created, approved, or rescheduled."
1416
+ )
1417
+ }
1418
+ )
1419
+ ] })
1420
+ ] })
1421
+ }
1422
+ );
1423
+ };
1424
+ const SecondaryCalendarsSection = ({
1425
+ t,
1426
+ secondaryCalendars,
1427
+ onAddOrUpdateCalendar,
1428
+ onToggleCalendarEnabled,
1429
+ onRemoveCalendar
1430
+ }) => {
1431
+ const [isFormOpen, setIsFormOpen] = useState(true);
1432
+ const [newCalPreset, setNewCalPreset] = useState("islamic-umalqura");
1433
+ const [newCalId, setNewCalId] = useState("hijri");
1434
+ const [newCalLabel, setNewCalLabel] = useState("Hijri (Umm al-Qura)");
1435
+ const [newCalLocale, setNewCalLocale] = useState("local");
1436
+ const [newCalOverrideField, setNewCalOverrideField] = useState("hijriDate");
1437
+ const [newCalDayOffset, setNewCalDayOffset] = useState(0);
1438
+ const handleEditCalendarSystem = (cal) => {
1439
+ setNewCalPreset(cal.intlCalendar || "islamic-umalqura");
1440
+ setNewCalId(cal.id || "hijri");
1441
+ setNewCalLabel(cal.label || "Hijri (Umm al-Qura)");
1442
+ setNewCalLocale(cal.locale || "local");
1443
+ setNewCalOverrideField(cal.overrideField || "hijriDate");
1444
+ setNewCalDayOffset(cal.dayOffset ?? 0);
1445
+ setIsFormOpen(true);
1446
+ };
1447
+ const handleAddCalendarSystem = () => {
1448
+ if (!newCalId.trim() || !newCalPreset) return;
1449
+ const cleanId = newCalId.trim().toLowerCase().replace(/[^a-z0-9-_]/g, "");
1450
+ if (!cleanId) return;
1451
+ const nextEntry = {
1452
+ id: cleanId,
1453
+ label: newCalLabel.trim() || cleanId,
1454
+ intlCalendar: newCalPreset,
1455
+ locale: newCalLocale && newCalLocale !== "local" ? newCalLocale.trim() : void 0,
1456
+ dayOffset: newCalDayOffset || 0,
1457
+ overrideField: newCalOverrideField.trim() || void 0,
1458
+ enabled: true
1459
+ };
1460
+ onAddOrUpdateCalendar(nextEntry);
1461
+ };
1462
+ return /* @__PURE__ */ jsx(
1463
+ Box,
1464
+ {
1465
+ padding: 5,
1466
+ background: "neutral0",
1467
+ hasRadius: true,
1468
+ shadow: "tableShadow",
1469
+ style: { border: "1px solid rgba(128, 128, 128, 0.22)" },
1470
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
1471
+ /* @__PURE__ */ jsxs(
1472
+ Flex,
1473
+ {
1474
+ justifyContent: "space-between",
1475
+ alignItems: "flex-start",
1476
+ wrap: "wrap",
1477
+ gap: 3,
1478
+ children: [
1479
+ /* @__PURE__ */ jsxs(Box, { style: { flex: 1, minWidth: 280 }, children: [
1480
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", tag: "h2", children: t(
1481
+ "settings.section.secondaryCalendars",
1482
+ "Secondary & Alternative Calendar Systems (Intl / Custom)"
1483
+ ) }),
1484
+ /* @__PURE__ */ jsx(Box, { paddingTop: 1, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
1485
+ "settings.secondaryCalendarsHint",
1486
+ "Display optional secondary dates (such as Hijri Umm al-Qura) alongside Gregorian dates in day cells. Supports local language or Arabic script and moon-sighting day offsets (-2 to +2 days)."
1487
+ ) }) })
1488
+ ] }),
1489
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
1490
+ /* @__PURE__ */ jsxs(Badge, { active: true, children: [
1491
+ secondaryCalendars.length,
1492
+ " ",
1493
+ t("settings.configuredCount", "Configured")
1494
+ ] }),
1495
+ /* @__PURE__ */ jsx(
1496
+ Button,
1497
+ {
1498
+ variant: isFormOpen ? "tertiary" : "secondary",
1499
+ size: "S",
1500
+ onClick: () => setIsFormOpen((prev) => !prev),
1501
+ children: isFormOpen ? t("common.hideForm", "Hide Form") : `+ ${t("settings.addCalendar", "Add Calendar System")}`
1502
+ }
1503
+ )
1504
+ ] })
1505
+ ]
1506
+ }
1507
+ ),
1508
+ secondaryCalendars.map((cal) => /* @__PURE__ */ jsx(
1509
+ Box,
1510
+ {
1511
+ padding: 4,
1512
+ background: "neutral100",
1513
+ hasRadius: true,
1514
+ style: {
1515
+ border: "1px solid rgba(128, 128, 128, 0.18)",
1516
+ borderLeft: "4px solid #0284C7"
1517
+ },
1518
+ children: /* @__PURE__ */ jsxs(
1519
+ Flex,
1520
+ {
1521
+ justifyContent: "space-between",
1522
+ alignItems: "center",
1523
+ wrap: "wrap",
1524
+ gap: 3,
1525
+ children: [
1526
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
1527
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: cal.label }),
1528
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: cal.intlCalendar }),
1529
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: cal.locale === "ar" || cal.locale === "ar-SA" ? t(
1530
+ "settings.badge.arabicScript",
1531
+ "Arabic Script (العربية)"
1532
+ ) : t("settings.badge.localLanguage", "Local Language") }),
1533
+ cal.overrideField && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
1534
+ t("settings.badge.overrideField", "Override field"),
1535
+ ":",
1536
+ " ",
1537
+ cal.overrideField
1538
+ ] }),
1539
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
1540
+ t("settings.badge.offset", "Offset"),
1541
+ ":",
1542
+ " ",
1543
+ (cal.dayOffset ?? 0) > 0 ? `+${cal.dayOffset}` : cal.dayOffset ?? 0,
1544
+ "d"
1545
+ ] })
1546
+ ] }),
1547
+ /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", children: [
1548
+ /* @__PURE__ */ jsx(
1549
+ Checkbox,
1550
+ {
1551
+ checked: cal.enabled,
1552
+ onCheckedChange: (checked) => onToggleCalendarEnabled(cal.id, Boolean(checked)),
1553
+ children: t("settings.calendarEnabled", "Enabled")
1554
+ }
1555
+ ),
1556
+ /* @__PURE__ */ jsx(
1557
+ Button,
1558
+ {
1559
+ variant: "tertiary",
1560
+ size: "S",
1561
+ onClick: () => handleEditCalendarSystem(cal),
1562
+ children: t("common.edit", "Edit")
1563
+ }
1564
+ ),
1565
+ /* @__PURE__ */ jsx(
1566
+ Button,
1567
+ {
1568
+ variant: "danger-light",
1569
+ size: "S",
1570
+ onClick: () => onRemoveCalendar(cal.id),
1571
+ children: t("common.remove", "Remove")
1572
+ }
1573
+ )
1574
+ ] })
1575
+ ]
1576
+ }
1577
+ )
1578
+ },
1579
+ cal.id
1580
+ )),
1581
+ isFormOpen && /* @__PURE__ */ jsx(
1582
+ Box,
1583
+ {
1584
+ padding: 5,
1585
+ background: "neutral100",
1586
+ hasRadius: true,
1587
+ style: { border: "1px solid rgba(128, 128, 128, 0.18)" },
1588
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
1589
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral700", children: t(
1590
+ "settings.configureCalendarTitle",
1591
+ "Configure Secondary Calendar System"
1592
+ ) }),
1593
+ /* @__PURE__ */ jsxs(
1594
+ "div",
1595
+ {
1596
+ style: {
1597
+ display: "grid",
1598
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
1599
+ gap: 16
1600
+ },
1601
+ children: [
1602
+ /* @__PURE__ */ jsxs(
1603
+ Field.Root,
1604
+ {
1605
+ hint: t(
1606
+ "settings.calendarPresetHint",
1607
+ "Unicode CLDR calendar identifier used by Intl.DateTimeFormat."
1608
+ ),
1609
+ children: [
1610
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.calendarPreset", "Calendar System (CLDR)") }),
1611
+ /* @__PURE__ */ jsx(
1612
+ SingleSelect,
1613
+ {
1614
+ placeholder: t(
1615
+ "settings.calendarPreset.placeholder",
1616
+ "Select CLDR calendar system"
1617
+ ),
1618
+ value: newCalPreset || "islamic-umalqura",
1619
+ onChange: (val) => {
1620
+ const strVal = String(val);
1621
+ setNewCalPreset(strVal);
1622
+ const match = SUPPORTED_INTL_CALENDARS.find(
1623
+ (c) => c.intlCalendar === strVal
1624
+ );
1625
+ if (match) {
1626
+ setNewCalId(match.id.split("-")[0]);
1627
+ setNewCalLabel(match.defaultLabel);
1628
+ }
1629
+ },
1630
+ children: SUPPORTED_INTL_CALENDARS.filter(
1631
+ (c) => c.id !== "gregory"
1632
+ ).map((cal) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: cal.intlCalendar, children: [
1633
+ cal.defaultLabel,
1634
+ " (",
1635
+ cal.intlCalendar,
1636
+ ")"
1637
+ ] }, cal.id))
1638
+ }
1639
+ ),
1640
+ /* @__PURE__ */ jsx(Field.Hint, {})
1641
+ ]
1642
+ }
1643
+ ),
1644
+ /* @__PURE__ */ jsxs(
1645
+ Field.Root,
1646
+ {
1647
+ hint: t(
1648
+ "settings.calendarIdHint",
1649
+ "Unique slug key for this calendar overlay."
1650
+ ),
1651
+ children: [
1652
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.calendarId", "ID Key") }),
1653
+ /* @__PURE__ */ jsx(
1654
+ TextInput,
1655
+ {
1656
+ value: newCalId,
1657
+ onChange: (e) => setNewCalId(e.target.value),
1658
+ placeholder: t(
1659
+ "settings.calendarId.placeholder",
1660
+ "e.g., hijri"
1661
+ )
1662
+ }
1663
+ ),
1664
+ /* @__PURE__ */ jsx(Field.Hint, {})
1665
+ ]
1666
+ }
1667
+ ),
1668
+ /* @__PURE__ */ jsxs(
1669
+ Field.Root,
1670
+ {
1671
+ hint: t(
1672
+ "settings.calendarLabelHint",
1673
+ "Badge label displayed in calendar headers and tooltips."
1674
+ ),
1675
+ children: [
1676
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.calendarLabel", "Display Label") }),
1677
+ /* @__PURE__ */ jsx(
1678
+ TextInput,
1679
+ {
1680
+ value: newCalLabel,
1681
+ onChange: (e) => setNewCalLabel(e.target.value),
1682
+ placeholder: t(
1683
+ "settings.calendarLabel.placeholder",
1684
+ "e.g., Hijri (Umm al-Qura)"
1685
+ )
1686
+ }
1687
+ ),
1688
+ /* @__PURE__ */ jsx(Field.Hint, {})
1689
+ ]
1690
+ }
1691
+ ),
1692
+ /* @__PURE__ */ jsxs(
1693
+ Field.Root,
1694
+ {
1695
+ hint: t(
1696
+ "settings.calendarScriptHint",
1697
+ "Locale used to render month names and numerals."
1698
+ ),
1699
+ children: [
1700
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.calendarScript", "Language / Script Override") }),
1701
+ /* @__PURE__ */ jsxs(
1702
+ SingleSelect,
1703
+ {
1704
+ placeholder: t(
1705
+ "settings.calendarScript.placeholder",
1706
+ "Select script override"
1707
+ ),
1708
+ value: newCalLocale || "local",
1709
+ onChange: (val) => {
1710
+ setNewCalLocale(String(val));
1711
+ },
1712
+ children: [
1713
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "local", children: t(
1714
+ "settings.calendarScript.local",
1715
+ "Follow Calendar Language (Local)"
1716
+ ) }),
1717
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "ar-SA", children: t(
1718
+ "settings.calendarScript.ar",
1719
+ "Arabic Script — العربية (ar-SA)"
1720
+ ) }),
1721
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "es", children: t("settings.calendarScript.es", "Español (es)") }),
1722
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "en", children: t("settings.calendarScript.en", "English (en)") })
1723
+ ]
1724
+ }
1725
+ ),
1726
+ /* @__PURE__ */ jsx(Field.Hint, {})
1727
+ ]
1728
+ }
1729
+ ),
1730
+ /* @__PURE__ */ jsxs(
1731
+ Field.Root,
1732
+ {
1733
+ hint: t(
1734
+ "settings.overrideFieldHint",
1735
+ "Optional entry field that overrides calculated dates."
1736
+ ),
1737
+ children: [
1738
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.overrideField", "Document Override Field") }),
1739
+ /* @__PURE__ */ jsx(
1740
+ TextInput,
1741
+ {
1742
+ value: newCalOverrideField,
1743
+ onChange: (e) => setNewCalOverrideField(e.target.value),
1744
+ placeholder: t(
1745
+ "settings.overrideField.placeholder",
1746
+ "e.g., hijriDate"
1747
+ )
1748
+ }
1749
+ ),
1750
+ /* @__PURE__ */ jsx(Field.Hint, {})
1751
+ ]
1752
+ }
1753
+ ),
1754
+ /* @__PURE__ */ jsxs(
1755
+ Field.Root,
1756
+ {
1757
+ hint: t(
1758
+ "settings.dayOffsetHint",
1759
+ "Moon-sighting adjustment in days (-2 to +2)."
1760
+ ),
1761
+ children: [
1762
+ /* @__PURE__ */ jsx(Field.Label, { children: t("settings.dayOffset", "Lunar Day Offset (-2 to +2)") }),
1763
+ /* @__PURE__ */ jsx(
1764
+ NumberInput,
1765
+ {
1766
+ placeholder: t("settings.dayOffset.placeholder", "e.g., 0"),
1767
+ value: newCalDayOffset,
1768
+ onValueChange: (val) => setNewCalDayOffset(typeof val === "number" ? val : 0)
1769
+ }
1770
+ ),
1771
+ /* @__PURE__ */ jsx(Field.Hint, {})
1772
+ ]
1773
+ }
1774
+ )
1775
+ ]
1776
+ }
1777
+ ),
1778
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", gap: 2, children: /* @__PURE__ */ jsxs(Button, { variant: "default", onClick: handleAddCalendarSystem, children: [
1779
+ "+ ",
1780
+ t("settings.saveCalendar", "Save Calendar System")
1781
+ ] }) })
1782
+ ] })
1783
+ }
1784
+ )
1785
+ ] })
1786
+ }
1787
+ );
1788
+ };
1789
+ const SettingsPage = () => {
1790
+ const { formatMessage } = useIntl();
1791
+ const { get, put, post } = useFetchClient();
1792
+ const t = (id, defaultMessage) => formatMessage({ id: `${PLUGIN_ID}.${id}`, defaultMessage });
1793
+ const [settings, setSettings] = useState(null);
1794
+ const [contentTypes, setContentTypes] = useState([]);
1795
+ const [eventsSettings, setEventsSettings] = useState(null);
1796
+ const [bookingsSettings, setBookingsSettings] = useState(null);
1797
+ const [saving, setSaving] = useState(false);
1798
+ const [savedBanner, setSavedBanner] = useState(null);
1799
+ const [bannerIsError, setBannerIsError] = useState(false);
1800
+ useEffect(() => {
1801
+ Promise.all([
1802
+ get(`/${PLUGIN_ID}/settings`),
1803
+ get(`/${PLUGIN_ID}/content-types`),
1804
+ get(
1805
+ "/events/settings"
1806
+ ).catch(() => null),
1807
+ get(
1808
+ "/bookings/settings"
1809
+ ).catch(() => null)
1810
+ ]).then(([settingsRes, ctRes, eventsRes, bookingsRes]) => {
1811
+ if (settingsRes?.data?.data) {
1812
+ setSettings(settingsRes.data.data);
1813
+ }
1814
+ if (Array.isArray(ctRes?.data?.data)) {
1815
+ setContentTypes(ctRes.data.data);
1816
+ }
1817
+ if (eventsRes?.data?.data) {
1818
+ setEventsSettings(eventsRes.data.data);
1819
+ }
1820
+ if (bookingsRes?.data?.data) {
1821
+ setBookingsSettings(bookingsRes.data.data);
1822
+ }
1823
+ }).catch(() => {
1824
+ });
1825
+ }, [get]);
1826
+ const handleSave = async () => {
1827
+ if (!settings) return;
1828
+ setSaving(true);
1829
+ setSavedBanner(null);
1830
+ setBannerIsError(false);
1831
+ try {
1832
+ let companionFailed = false;
1833
+ const [res, eventsRes, bookingsRes] = await Promise.all([
1834
+ put(`/${PLUGIN_ID}/settings`, settings),
1835
+ eventsSettings ? put(
1836
+ "/events/settings",
1837
+ eventsSettings
1838
+ ).catch(() => {
1839
+ companionFailed = true;
1840
+ return null;
1841
+ }) : Promise.resolve(null),
1842
+ bookingsSettings ? put(
1843
+ "/bookings/settings",
1844
+ bookingsSettings
1845
+ ).catch(() => {
1846
+ companionFailed = true;
1847
+ return null;
1848
+ }) : Promise.resolve(null)
1849
+ ]);
1850
+ if (res?.data?.data) {
1851
+ setSettings(res.data.data);
1852
+ }
1853
+ if (eventsRes?.data?.data) {
1854
+ setEventsSettings(eventsRes.data.data);
1855
+ }
1856
+ if (bookingsRes?.data?.data) {
1857
+ setBookingsSettings(bookingsRes.data.data);
1858
+ }
1859
+ if (companionFailed) {
1860
+ setBannerIsError(true);
1861
+ setSavedBanner(
1862
+ t(
1863
+ "settings.savedPartial",
1864
+ "Calendar settings saved, but companion plugin settings could not be updated."
1865
+ )
1866
+ );
1867
+ } else {
1868
+ setSavedBanner(
1869
+ t(
1870
+ "settings.saved",
1871
+ "Calendar, Events, and Bookings settings saved successfully."
1872
+ )
1873
+ );
1874
+ }
1875
+ } catch {
1876
+ setBannerIsError(true);
1877
+ setSavedBanner(t("settings.error", "Could not save settings."));
1878
+ } finally {
1879
+ setSaving(false);
1880
+ }
1881
+ };
1882
+ const handleResetSettings = async () => {
1883
+ setSaving(true);
1884
+ setBannerIsError(false);
1885
+ try {
1886
+ const res = await post(
1887
+ `/${PLUGIN_ID}/clear-settings`,
1888
+ {}
1889
+ );
1890
+ if (res?.data?.data) {
1891
+ setSettings(res.data.data);
1892
+ }
1893
+ setSavedBanner(t("settings.resetSuccess", "Settings reset to defaults."));
1894
+ } catch {
1895
+ setBannerIsError(true);
1896
+ setSavedBanner(t("settings.error", "Could not reset settings."));
1897
+ } finally {
1898
+ setSaving(false);
1899
+ }
1900
+ };
1901
+ const handleAddOrUpdateSource = (nextSource) => {
1902
+ if (!settings) return;
1903
+ const existing = settings.sources ?? [];
1904
+ const updated = existing.some(
1905
+ (s) => s.id === nextSource.id || s.uid === nextSource.uid
1906
+ ) ? existing.map(
1907
+ (s) => s.id === nextSource.id || s.uid === nextSource.uid ? nextSource : s
1908
+ ) : [...existing, nextSource];
1909
+ setSettings({
1910
+ ...settings,
1911
+ sources: updated
1912
+ });
1913
+ };
1914
+ const toggleSourceEnabled = (sourceId, enabled) => {
1915
+ if (!settings) return;
1916
+ setSettings({
1917
+ ...settings,
1918
+ sources: settings.sources.map(
1919
+ (src) => src.id === sourceId ? { ...src, enabled } : src
1920
+ )
1921
+ });
1922
+ };
1923
+ const removeSource = (sourceId) => {
1924
+ if (!settings) return;
1925
+ setSettings({
1926
+ ...settings,
1927
+ sources: settings.sources.filter((src) => src.id !== sourceId)
1928
+ });
1929
+ };
1930
+ const handleAddOrUpdateCalendar = (nextEntry) => {
1931
+ if (!settings) return;
1932
+ const existing = settings.secondaryCalendars ?? [];
1933
+ const nextList = existing.some((c) => c.id === nextEntry.id) ? existing.map((c) => c.id === nextEntry.id ? nextEntry : c) : [...existing, nextEntry];
1934
+ setSettings({
1935
+ ...settings,
1936
+ secondaryCalendars: nextList
1937
+ });
1938
+ };
1939
+ const toggleCalendarEnabled = (calId, enabled) => {
1940
+ if (!settings) return;
1941
+ setSettings({
1942
+ ...settings,
1943
+ secondaryCalendars: (settings.secondaryCalendars ?? []).map(
1944
+ (cal) => cal.id === calId ? { ...cal, enabled } : cal
1945
+ )
1946
+ });
1947
+ };
1948
+ const removeCalendarSystem = (calId) => {
1949
+ if (!settings) return;
1950
+ setSettings({
1951
+ ...settings,
1952
+ secondaryCalendars: (settings.secondaryCalendars ?? []).filter(
1953
+ (cal) => cal.id !== calId
1954
+ )
1955
+ });
1956
+ };
1957
+ if (!settings) {
1958
+ return /* @__PURE__ */ jsx(Layouts.Root, { children: /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsx(Box, { padding: 6, children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: t("settings.loading", "Loading Calendar settings...") }) }) }) });
1959
+ }
1960
+ return /* @__PURE__ */ jsxs(Layouts.Root, { children: [
1961
+ /* @__PURE__ */ jsx(
1962
+ Layouts.Header,
1963
+ {
1964
+ title: t("settings.title", "Calendar, Events & Bookings Settings"),
1965
+ subtitle: t(
1966
+ "settings.subtitle",
1967
+ "Configure visual themes, primary local language, Arabic/Hijri calendar overlay, collection sources, RSVP event rules, and booking slot policies."
1968
+ ),
1969
+ primaryAction: /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
1970
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: handleResetSettings, children: t("settings.reset", "Reset Defaults") }),
1971
+ /* @__PURE__ */ jsx(Button, { variant: "default", loading: saving, onClick: handleSave, children: t("common.save", "Save Settings") })
1972
+ ] })
1973
+ }
1974
+ ),
1975
+ /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
1976
+ savedBanner && /* @__PURE__ */ jsx(
1977
+ Box,
1978
+ {
1979
+ padding: 3,
1980
+ background: bannerIsError ? "danger100" : "success100",
1981
+ hasRadius: true,
1982
+ children: /* @__PURE__ */ jsx(
1983
+ Typography,
1984
+ {
1985
+ variant: "omega",
1986
+ fontWeight: "bold",
1987
+ textColor: bannerIsError ? "danger700" : "success700",
1988
+ children: savedBanner
1989
+ }
1990
+ )
1991
+ }
1992
+ ),
1993
+ /* @__PURE__ */ jsxs(Tabs.Root, { defaultValue: "theme", children: [
1994
+ /* @__PURE__ */ jsxs(
1995
+ Tabs.List,
1996
+ {
1997
+ "aria-label": t(
1998
+ "settings.title",
1999
+ "Calendar, Events & Bookings Settings"
2000
+ ),
2001
+ children: [
2002
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "theme", children: t("settings.tab.theme", "Theme & Appearance") }),
2003
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "general", children: t("settings.tab.general", "General & Views") }),
2004
+ /* @__PURE__ */ jsxs(Tabs.Trigger, { value: "sources", children: [
2005
+ t("settings.tab.sources", "Collection Sources"),
2006
+ " (",
2007
+ settings.sources?.length ?? 0,
2008
+ ")"
2009
+ ] }),
2010
+ /* @__PURE__ */ jsxs(Tabs.Trigger, { value: "secondary", children: [
2011
+ t("settings.tab.secondaryCalendars", "Secondary Calendars"),
2012
+ " (",
2013
+ settings.secondaryCalendars?.length ?? 0,
2014
+ ")"
2015
+ ] }),
2016
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "events", children: t("settings.tab.events", "Events & RSVPs") }),
2017
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "bookings", children: t("settings.tab.bookings", "Bookings & Availability") })
2018
+ ]
2019
+ }
2020
+ ),
2021
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "theme", children: /* @__PURE__ */ jsx(
2022
+ CalendarViewsSection,
2023
+ {
2024
+ mode: "theme",
2025
+ t,
2026
+ settings,
2027
+ onPatchSettings: (patch) => setSettings({ ...settings, ...patch })
2028
+ }
2029
+ ) }),
2030
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "general", children: /* @__PURE__ */ jsx(
2031
+ CalendarViewsSection,
2032
+ {
2033
+ mode: "general",
2034
+ t,
2035
+ settings,
2036
+ onPatchSettings: (patch) => setSettings({ ...settings, ...patch })
2037
+ }
2038
+ ) }),
2039
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "sources", children: /* @__PURE__ */ jsx(
2040
+ CollectionSourcesSection,
2041
+ {
2042
+ t,
2043
+ sources: settings.sources,
2044
+ contentTypes,
2045
+ onAddOrUpdateSource: handleAddOrUpdateSource,
2046
+ onToggleSourceEnabled: toggleSourceEnabled,
2047
+ onRemoveSource: removeSource
2048
+ }
2049
+ ) }),
2050
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "secondary", children: /* @__PURE__ */ jsx(
2051
+ SecondaryCalendarsSection,
2052
+ {
2053
+ t,
2054
+ secondaryCalendars: settings.secondaryCalendars ?? [],
2055
+ onAddOrUpdateCalendar: handleAddOrUpdateCalendar,
2056
+ onToggleCalendarEnabled: toggleCalendarEnabled,
2057
+ onRemoveCalendar: removeCalendarSystem
2058
+ }
2059
+ ) }),
2060
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "events", children: /* @__PURE__ */ jsx(
2061
+ CompanionSuiteSettingsSection,
2062
+ {
2063
+ mode: "events",
2064
+ t,
2065
+ eventsSettings,
2066
+ bookingsSettings,
2067
+ onPatchEvents: (patch) => eventsSettings && setEventsSettings({ ...eventsSettings, ...patch }),
2068
+ onPatchBookings: (patch) => bookingsSettings && setBookingsSettings({ ...bookingsSettings, ...patch })
2069
+ }
2070
+ ) }),
2071
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "bookings", children: /* @__PURE__ */ jsx(
2072
+ CompanionSuiteSettingsSection,
2073
+ {
2074
+ mode: "bookings",
2075
+ t,
2076
+ eventsSettings,
2077
+ bookingsSettings,
2078
+ onPatchEvents: (patch) => eventsSettings && setEventsSettings({ ...eventsSettings, ...patch }),
2079
+ onPatchBookings: (patch) => bookingsSettings && setBookingsSettings({ ...bookingsSettings, ...patch })
2080
+ }
2081
+ ) })
2082
+ ] })
2083
+ ] }) })
2084
+ ] });
2085
+ };
2086
+ export {
2087
+ SettingsPage
2088
+ };