@vicacha-devs/strapi-plugin-bookings 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 (82) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +23 -2
  3. package/dist/admin/App-BrB9n7EO.mjs +371 -0
  4. package/dist/admin/App-ZlqOsILc.js +371 -0
  5. package/dist/admin/SettingsPage-LR6F2vyx.js +410 -0
  6. package/dist/admin/SettingsPage-Xas3gvn4.mjs +410 -0
  7. package/dist/admin/ar-9_2mxMp2.js +339 -0
  8. package/dist/admin/ar-CrM4yxqb.mjs +339 -0
  9. package/dist/admin/de-D3NbAP6M.mjs +339 -0
  10. package/dist/admin/de-DBaO-D7Z.js +339 -0
  11. package/dist/admin/en-Cu-rn_xM.mjs +339 -0
  12. package/dist/admin/en-DAoI7qkk.js +339 -0
  13. package/dist/admin/es-DUqsu82s.js +339 -0
  14. package/dist/admin/es-Y0zfya4P.mjs +339 -0
  15. package/dist/admin/fr-BkLta_o8.js +339 -0
  16. package/dist/admin/fr-BrzKRqet.mjs +339 -0
  17. package/dist/admin/getTranslation-CZAJyMSt.mjs +5 -0
  18. package/dist/admin/getTranslation-Cd0_4UtW.js +4 -0
  19. package/dist/admin/id-BZz53ViW.mjs +339 -0
  20. package/dist/admin/id-ByxSagzq.js +339 -0
  21. package/dist/admin/index.js +1497 -0
  22. package/dist/admin/index.mjs +1492 -0
  23. package/dist/admin/pt-BSCDUkdd.js +339 -0
  24. package/dist/admin/pt-DUfISuZf.mjs +339 -0
  25. package/dist/admin/src/components/Initializer.d.ts +6 -0
  26. package/dist/admin/src/components/PluginIcon.d.ts +3 -0
  27. package/dist/admin/src/components/RRuleInput.d.ts +3 -0
  28. package/dist/admin/src/components/bookings/BookingSidePanel.d.ts +10 -0
  29. package/dist/admin/src/components/bookings/BookingsGridCanvas.d.ts +20 -0
  30. package/dist/admin/src/components/bookings/QuickCreateBookingModal.d.ts +11 -0
  31. package/dist/admin/src/index.d.ts +24 -0
  32. package/dist/admin/src/pages/App.d.ts +3 -0
  33. package/dist/admin/src/pages/CalendarPage.d.ts +3 -0
  34. package/dist/admin/src/pages/SettingsPage.d.ts +3 -0
  35. package/dist/admin/src/permissions.d.ts +29 -0
  36. package/dist/admin/src/pluginId.d.ts +1 -0
  37. package/dist/admin/src/translations/cmLabels.d.ts +1 -0
  38. package/dist/admin/src/translations/cmPlaceholders.d.ts +1 -0
  39. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  40. package/dist/admin/tr-_nhc9hk6.mjs +339 -0
  41. package/dist/admin/tr-r17l5F2K.js +339 -0
  42. package/dist/admin/ur-8edBd0Qo.mjs +339 -0
  43. package/dist/admin/ur-Dw_TAOZ3.js +339 -0
  44. package/dist/admin/zh-CeR0swEm.mjs +339 -0
  45. package/dist/admin/zh-D0z5sxxN.js +339 -0
  46. package/dist/server/index.js +3547 -0
  47. package/dist/server/index.mjs +3545 -0
  48. package/dist/server/src/bootstrap.d.ts +6 -0
  49. package/dist/server/src/config/index.d.ts +6 -0
  50. package/dist/server/src/content-types/booking/schema.json.d.ts +117 -0
  51. package/dist/server/src/content-types/index.d.ts +265 -0
  52. package/dist/server/src/content-types/resource/schema.json.d.ts +147 -0
  53. package/dist/server/src/controllers/admin.d.ts +30 -0
  54. package/dist/server/src/controllers/content-api.d.ts +32 -0
  55. package/dist/server/src/controllers/index.d.ts +28 -0
  56. package/dist/server/src/destroy.d.ts +5 -0
  57. package/dist/server/src/errors.d.ts +18 -0
  58. package/dist/server/src/index.d.ts +347 -0
  59. package/dist/server/src/register.d.ts +5 -0
  60. package/dist/server/src/routes/admin.d.ts +14 -0
  61. package/dist/server/src/routes/content-api.d.ts +9 -0
  62. package/dist/server/src/routes/index.d.ts +30 -0
  63. package/dist/server/src/services/availability.d.ts +8 -0
  64. package/dist/server/src/services/index.d.ts +12 -0
  65. package/dist/server/src/services/reservation.d.ts +6 -0
  66. package/dist/server/src/services/reservationHelpers.d.ts +41 -0
  67. package/dist/server/src/services/reservationHostCapacity.d.ts +16 -0
  68. package/dist/server/src/services/reservationMutations.d.ts +32 -0
  69. package/dist/server/src/services/reservationSources.d.ts +4 -0
  70. package/dist/server/src/services/reservationValidators.d.ts +27 -0
  71. package/dist/server/src/services/settings.d.ts +8 -0
  72. package/dist/server/src/types.d.ts +99 -0
  73. package/dist/shared/calendarSystems.d.ts +3 -0
  74. package/dist/shared/icalAndCsv.d.ts +16 -0
  75. package/dist/shared/index.d.ts +6 -0
  76. package/dist/shared/index.js +752 -0
  77. package/dist/shared/index.mjs +729 -0
  78. package/dist/shared/pluginId.d.ts +2 -0
  79. package/dist/shared/slotEngine.d.ts +19 -0
  80. package/dist/shared/slotTimezone.d.ts +45 -0
  81. package/dist/shared/types.d.ts +245 -0
  82. package/package.json +111 -3
@@ -0,0 +1,371 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const reactRouterDom = require("react-router-dom");
5
+ const designSystem = require("@strapi/design-system");
6
+ const admin = require("@strapi/strapi/admin");
7
+ const React = require("react");
8
+ const reactIntl = require("react-intl");
9
+ const index = require("./index.js");
10
+ const getTranslation = require("./getTranslation-Cd0_4UtW.js");
11
+ const EMPTY_STATS = {
12
+ totalBookings: 0,
13
+ confirmedBookings: 0,
14
+ pendingBookings: 0,
15
+ cancelledBookings: 0,
16
+ completedBookings: 0,
17
+ totalGuests: 0,
18
+ activeResources: 0
19
+ };
20
+ const CalendarPage = () => {
21
+ const { formatMessage } = reactIntl.useIntl();
22
+ const navigate = reactRouterDom.useNavigate();
23
+ const { get, post, put } = admin.useFetchClient();
24
+ const { toggleNotification } = admin.useNotification();
25
+ const isMountedRef = React.useRef(true);
26
+ React.useEffect(() => {
27
+ isMountedRef.current = true;
28
+ return () => {
29
+ isMountedRef.current = false;
30
+ };
31
+ }, []);
32
+ const t = React.useCallback(
33
+ (id, defaultMessage) => formatMessage({ id: getTranslation.getTranslation(id), defaultMessage }),
34
+ [formatMessage]
35
+ );
36
+ const [bookings, setBookings] = React.useState([]);
37
+ const [resources, setResources] = React.useState([]);
38
+ const [stats, setStats] = React.useState(EMPTY_STATS);
39
+ const [selectedResourceId, setSelectedResourceId] = React.useState("");
40
+ const [selectedStatus, setSelectedStatus] = React.useState("");
41
+ const [search, setSearch] = React.useState("");
42
+ const [debouncedSearch, setDebouncedSearch] = React.useState("");
43
+ const [selectedBooking, setSelectedBooking] = React.useState(
44
+ null
45
+ );
46
+ const [quickCreateStartIso, setQuickCreateStartIso] = React.useState(
47
+ null
48
+ );
49
+ React.useEffect(() => {
50
+ const timer = setTimeout(() => {
51
+ if (isMountedRef.current) {
52
+ setDebouncedSearch(search);
53
+ }
54
+ }, 250);
55
+ return () => clearTimeout(timer);
56
+ }, [search]);
57
+ const fetchBookingsData = React.useCallback(async () => {
58
+ try {
59
+ const params = new URLSearchParams();
60
+ if (selectedResourceId) {
61
+ params.set("resourceDocumentId", selectedResourceId);
62
+ }
63
+ if (selectedStatus) {
64
+ params.set("status", selectedStatus);
65
+ }
66
+ if (debouncedSearch.trim()) {
67
+ params.set("search", debouncedSearch.trim());
68
+ }
69
+ const res = await get(`/${index.PLUGIN_ID}/calendar?${params.toString()}`);
70
+ if (!isMountedRef.current) return;
71
+ const payload = res.data?.data;
72
+ if (payload) {
73
+ const nextBookings = payload.bookings ?? [];
74
+ setBookings(nextBookings);
75
+ setResources(payload.resources ?? []);
76
+ setStats(payload.stats ?? EMPTY_STATS);
77
+ setSelectedBooking((prev) => {
78
+ if (!prev) return null;
79
+ return nextBookings.find((b) => b.documentId === prev.documentId) ?? null;
80
+ });
81
+ }
82
+ } catch {
83
+ if (!isMountedRef.current) return;
84
+ toggleNotification({
85
+ type: "danger",
86
+ message: t("notifications.loadError", "Failed to load bookings")
87
+ });
88
+ }
89
+ }, [
90
+ get,
91
+ selectedResourceId,
92
+ selectedStatus,
93
+ debouncedSearch,
94
+ toggleNotification,
95
+ t
96
+ ]);
97
+ React.useEffect(() => {
98
+ void fetchBookingsData();
99
+ }, [fetchBookingsData]);
100
+ const handleUpdateStatus = async (documentId, status, assignedHost, internalNotes) => {
101
+ try {
102
+ const res = await put(
103
+ `/${index.PLUGIN_ID}/bookings/${documentId}/status`,
104
+ {
105
+ status,
106
+ assignedHost,
107
+ internalNotes
108
+ }
109
+ );
110
+ if (isMountedRef.current && res.data?.data) {
111
+ setSelectedBooking(res.data.data);
112
+ }
113
+ toggleNotification({
114
+ type: "success",
115
+ message: t("notifications.updated", "Booking updated")
116
+ });
117
+ await fetchBookingsData();
118
+ } catch {
119
+ toggleNotification({
120
+ type: "danger",
121
+ message: t("notifications.updateError", "Failed to update booking")
122
+ });
123
+ }
124
+ };
125
+ const handleCreateBooking = async (payload) => {
126
+ try {
127
+ await post(`/${index.PLUGIN_ID}/bookings`, payload);
128
+ toggleNotification({
129
+ type: "success",
130
+ message: t("notifications.created", "Booking created")
131
+ });
132
+ await fetchBookingsData();
133
+ } catch (err) {
134
+ toggleNotification({
135
+ type: "danger",
136
+ message: t("notifications.createError", "Failed to create booking")
137
+ });
138
+ throw err;
139
+ }
140
+ };
141
+ const handleExportCsv = async () => {
142
+ try {
143
+ const params = new URLSearchParams();
144
+ if (selectedResourceId) {
145
+ params.set("resourceDocumentId", selectedResourceId);
146
+ }
147
+ if (selectedStatus) {
148
+ params.set("status", selectedStatus);
149
+ }
150
+ if (debouncedSearch.trim()) {
151
+ params.set("search", debouncedSearch.trim());
152
+ }
153
+ const qs = params.toString();
154
+ const url = qs ? `/${index.PLUGIN_ID}/export.csv?${qs}` : `/${index.PLUGIN_ID}/export.csv`;
155
+ const res = await get(url);
156
+ const csvText = typeof res.data === "string" ? res.data : String(res.data ?? "");
157
+ const blob = new Blob([csvText], { type: "text/csv;charset=utf-8;" });
158
+ const objectUrl = URL.createObjectURL(blob);
159
+ try {
160
+ const link = document.createElement("a");
161
+ link.href = objectUrl;
162
+ link.setAttribute("download", "bookings.csv");
163
+ document.body.appendChild(link);
164
+ link.click();
165
+ document.body.removeChild(link);
166
+ } finally {
167
+ URL.revokeObjectURL(objectUrl);
168
+ }
169
+ } catch {
170
+ toggleNotification({
171
+ type: "danger",
172
+ message: t("notifications.exportError", "Failed to export CSV")
173
+ });
174
+ }
175
+ };
176
+ const handleOpenInContentManager = (booking) => {
177
+ const uid = booking.sourceCollection || "plugin::bookings.booking";
178
+ navigate(`/content-manager/collection-types/${uid}/${booking.documentId}`);
179
+ };
180
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Root, { children: [
181
+ /* @__PURE__ */ jsxRuntime.jsx(
182
+ admin.Layouts.Header,
183
+ {
184
+ title: t("plugin.title", "Bookings & Slot Availability"),
185
+ subtitle: t(
186
+ "plugin.subtitle",
187
+ "Manage bookable resources, time-slot availability, staff assignment, and reservation approvals. Schedule views are unified in Calendar."
188
+ ),
189
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
190
+ /* @__PURE__ */ jsxRuntime.jsx(
191
+ designSystem.Button,
192
+ {
193
+ variant: "secondary",
194
+ onClick: () => navigate("/plugins/calendar?source=bookings"),
195
+ children: t("actions.openUnifiedCalendar", "Open in Calendar")
196
+ }
197
+ ),
198
+ /* @__PURE__ */ jsxRuntime.jsx(
199
+ designSystem.Button,
200
+ {
201
+ variant: "tertiary",
202
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}`),
203
+ children: t("actions.settings", "Settings")
204
+ }
205
+ ),
206
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", onClick: () => void handleExportCsv(), children: t("actions.exportCsv", "Export CSV") }),
207
+ /* @__PURE__ */ jsxRuntime.jsx(
208
+ designSystem.Button,
209
+ {
210
+ variant: "default",
211
+ onClick: () => setQuickCreateStartIso((/* @__PURE__ */ new Date()).toISOString().slice(0, 10)),
212
+ children: t("actions.newBooking", "New Booking")
213
+ }
214
+ )
215
+ ] })
216
+ }
217
+ ),
218
+ /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
219
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 5, children: [
220
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 4, wrap: "wrap", children: [
221
+ {
222
+ label: t("stats.total", "Total Bookings"),
223
+ value: stats.totalBookings,
224
+ badge: "neutral100"
225
+ },
226
+ {
227
+ label: t("stats.confirmed", "Confirmed"),
228
+ value: stats.confirmedBookings,
229
+ badge: "success100"
230
+ },
231
+ {
232
+ label: t("stats.pending", "Pending Approval"),
233
+ value: stats.pendingBookings,
234
+ badge: "warning100"
235
+ },
236
+ {
237
+ label: t("stats.guests", "Total Guests / Seats"),
238
+ value: stats.totalGuests,
239
+ badge: "primary100"
240
+ },
241
+ {
242
+ label: t("stats.resources", "Active Resources"),
243
+ value: stats.activeResources,
244
+ badge: "secondary100"
245
+ }
246
+ ].map((card) => /* @__PURE__ */ jsxRuntime.jsx(
247
+ designSystem.Box,
248
+ {
249
+ background: "neutral0",
250
+ hasRadius: true,
251
+ shadow: "tableShadow",
252
+ padding: 4,
253
+ style: { flex: "1 1 180px" },
254
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
255
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: card.label }),
256
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: card.badge, children: card.value })
257
+ ] })
258
+ },
259
+ card.label
260
+ )) }),
261
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(
262
+ "div",
263
+ {
264
+ style: {
265
+ display: "grid",
266
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
267
+ gap: 12
268
+ },
269
+ children: [
270
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
271
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("filters.resource", "Bookable Resource") }),
272
+ /* @__PURE__ */ jsxRuntime.jsxs(
273
+ designSystem.SingleSelect,
274
+ {
275
+ placeholder: t("placeholder.all_resources", "All Resources"),
276
+ value: selectedResourceId || "all",
277
+ onChange: (val) => setSelectedResourceId(
278
+ String(val) === "all" ? "" : String(val)
279
+ ),
280
+ children: [
281
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "all", children: t("filters.allResources", "All Resources") }),
282
+ resources.map((res) => /* @__PURE__ */ jsxRuntime.jsx(
283
+ designSystem.SingleSelectOption,
284
+ {
285
+ value: res.documentId,
286
+ children: res.name
287
+ },
288
+ res.documentId
289
+ ))
290
+ ]
291
+ }
292
+ )
293
+ ] }),
294
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
295
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("filters.status", "Booking Status") }),
296
+ /* @__PURE__ */ jsxRuntime.jsxs(
297
+ designSystem.SingleSelect,
298
+ {
299
+ placeholder: t("placeholder.all_statuses", "All Statuses"),
300
+ value: selectedStatus || "all",
301
+ onChange: (val) => setSelectedStatus(String(val) === "all" ? "" : String(val)),
302
+ children: [
303
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "all", children: t("filters.allStatuses", "All Statuses") }),
304
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "pending", children: t("page.ui.pending", "PENDING") }),
305
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "confirmed", children: t("page.ui.confirmed", "CONFIRMED") }),
306
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "completed", children: t("page.ui.completed", "COMPLETED") }),
307
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "cancelled", children: t("page.ui.cancelled", "CANCELLED") }),
308
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "no_show", children: t("page.ui.no_show", "NO SHOW") })
309
+ ]
310
+ }
311
+ )
312
+ ] }),
313
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
314
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("filters.search", "Search Reservations") }),
315
+ /* @__PURE__ */ jsxRuntime.jsx(
316
+ designSystem.TextInput,
317
+ {
318
+ placeholder: t(
319
+ "filters.searchPlaceholder",
320
+ "Code, customer, host, org..."
321
+ ),
322
+ value: search,
323
+ onChange: (e) => setSearch(e.target.value)
324
+ }
325
+ )
326
+ ] })
327
+ ]
328
+ }
329
+ ) }),
330
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "flex-start", children: [
331
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(
332
+ index.BookingsGridCanvas,
333
+ {
334
+ t,
335
+ bookings,
336
+ onSelectBooking: (b) => setSelectedBooking(b)
337
+ }
338
+ ) }),
339
+ selectedBooking && /* @__PURE__ */ jsxRuntime.jsx(
340
+ index.BookingSidePanel,
341
+ {
342
+ t,
343
+ booking: selectedBooking,
344
+ onClose: () => setSelectedBooking(null),
345
+ onUpdateStatus: handleUpdateStatus,
346
+ onOpenInContentManager: handleOpenInContentManager
347
+ }
348
+ )
349
+ ] })
350
+ ] }),
351
+ /* @__PURE__ */ jsxRuntime.jsx(
352
+ index.QuickCreateBookingModal,
353
+ {
354
+ t,
355
+ isOpen: Boolean(quickCreateStartIso),
356
+ initialStartIso: quickCreateStartIso || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
357
+ resources,
358
+ onClose: () => setQuickCreateStartIso(null),
359
+ onSubmit: handleCreateBooking
360
+ }
361
+ )
362
+ ] })
363
+ ] });
364
+ };
365
+ const App = () => {
366
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
367
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(CalendarPage, {}) }),
368
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "*", element: /* @__PURE__ */ jsxRuntime.jsx(CalendarPage, {}) })
369
+ ] });
370
+ };
371
+ exports.App = App;