@pisell/private-materials 6.11.125 → 6.11.126

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.
@@ -0,0 +1,632 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/plus/pisellReservation/components/bookingChangeModal/index.tsx
30
+ var bookingChangeModal_exports = {};
31
+ __export(bookingChangeModal_exports, {
32
+ closeBookingChangeModal: () => close,
33
+ default: () => bookingChangeModal_default,
34
+ openBookingChangeModal: () => open
35
+ });
36
+ module.exports = __toCommonJS(bookingChangeModal_exports);
37
+ var import_react = __toESM(require("react"));
38
+ var import_react_dom = __toESM(require("react-dom"));
39
+ var import_index = require("./index.less");
40
+ var CLOSE_ANIMATION_MS = 180;
41
+ var activeInstance = null;
42
+ function defaultFormatHourLabel(hour) {
43
+ const normalizedMinutes = Math.round(hour * 60);
44
+ const wholeHours = Math.floor(normalizedMinutes / 60);
45
+ const minutes = normalizedMinutes % 60;
46
+ return `${String(wholeHours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
47
+ }
48
+ function getHourStep(hourSlots) {
49
+ if (hourSlots.length < 2) {
50
+ return 1;
51
+ }
52
+ const sorted = [...hourSlots].sort((a, b) => a - b);
53
+ let minStep = Number.POSITIVE_INFINITY;
54
+ for (let index = 1; index < sorted.length; index += 1) {
55
+ const step = sorted[index] - sorted[index - 1];
56
+ if (step > 0 && step < minStep) {
57
+ minStep = step;
58
+ }
59
+ }
60
+ return Number.isFinite(minStep) ? minStep : 1;
61
+ }
62
+ function getFallbackHourSlots() {
63
+ return Array.from({ length: 24 }, (_, index) => index);
64
+ }
65
+ function getCourtName(courts, courtId) {
66
+ var _a;
67
+ if (!courtId) {
68
+ return "Court";
69
+ }
70
+ return ((_a = courts.find((court) => court.id === courtId)) == null ? void 0 : _a.name) || "Court";
71
+ }
72
+ function pickFirstDefined(...values) {
73
+ return values.find((value) => value !== void 0 && value !== null && value !== "");
74
+ }
75
+ function asNumber(value, fallback = 0) {
76
+ const next = Number(value);
77
+ return Number.isFinite(next) ? next : fallback;
78
+ }
79
+ function formatDateKey(date) {
80
+ const year = date.getFullYear();
81
+ const month = `${date.getMonth() + 1}`.padStart(2, "0");
82
+ const day = `${date.getDate()}`.padStart(2, "0");
83
+ return `${year}-${month}-${day}`;
84
+ }
85
+ function getNormalizedDateTime(dateKey, hour) {
86
+ const date = /* @__PURE__ */ new Date(`${dateKey}T00:00:00`);
87
+ if (Number.isNaN(date.getTime())) {
88
+ return {
89
+ date: dateKey,
90
+ time: defaultFormatHourLabel(hour)
91
+ };
92
+ }
93
+ const minutes = Math.round(hour * 60);
94
+ date.setMinutes(date.getMinutes() + minutes);
95
+ return {
96
+ date: formatDateKey(date),
97
+ time: `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(
98
+ 2,
99
+ "0"
100
+ )}`
101
+ };
102
+ }
103
+ function buildSubmitPayload(args) {
104
+ var _a, _b, _c;
105
+ const { item, draft, duration, nextEndHour, targetCourt } = args;
106
+ const itemRaw = item.raw || {};
107
+ const itemMetadata = item.metadata || {};
108
+ const targetCourtMetadata = (targetCourt == null ? void 0 : targetCourt.metadata) || {};
109
+ const firstResource = Array.isArray(item.resources) && item.resources[0] || {};
110
+ const firstResourceMetadata = firstResource.metadata || {};
111
+ const start = getNormalizedDateTime(draft.targetDate, draft.targetHour);
112
+ const end = getNormalizedDateTime(draft.targetDate, nextEndHour);
113
+ const resourceRelationId = pickFirstDefined(
114
+ targetCourt == null ? void 0 : targetCourt.relation_id,
115
+ targetCourt == null ? void 0 : targetCourt.relationId,
116
+ targetCourt == null ? void 0 : targetCourt.resource_id,
117
+ targetCourt == null ? void 0 : targetCourt.resourceId,
118
+ targetCourt == null ? void 0 : targetCourt.id,
119
+ firstResource.relation_id,
120
+ item.courtId
121
+ ) || item.courtId;
122
+ return {
123
+ id: pickFirstDefined(item.id, itemRaw.id) || "",
124
+ relation_id: pickFirstDefined(item.relation_id, itemRaw.relation_id, 0) || 0,
125
+ like_status: pickFirstDefined(item.like_status, itemRaw.like_status, "common") || "common",
126
+ relation_type: pickFirstDefined(item.relation_type, itemRaw.relation_type, "") || "",
127
+ is_all: asNumber(pickFirstDefined(item.is_all, itemRaw.is_all, 0), 0),
128
+ schedule_id: pickFirstDefined(item.schedule_id, itemRaw.schedule_id, 0) || 0,
129
+ sub_type: pickFirstDefined(item.sub_type, itemRaw.sub_type, "minutes") || "minutes",
130
+ select_date: draft.targetDate,
131
+ number: Math.max(1, asNumber(pickFirstDefined(item.number, itemRaw.number, 1), 1)),
132
+ metadata: {
133
+ ...itemRaw.metadata,
134
+ ...itemMetadata,
135
+ slot_count: Math.max(
136
+ 1,
137
+ asNumber(
138
+ pickFirstDefined(
139
+ itemMetadata.slot_count,
140
+ (_a = itemRaw.metadata) == null ? void 0 : _a.slot_count,
141
+ Math.ceil(Math.max(0, duration * 60) / 30)
142
+ ),
143
+ 1
144
+ )
145
+ ),
146
+ resource_id: pickFirstDefined(
147
+ targetCourt == null ? void 0 : targetCourt.resource_id,
148
+ targetCourt == null ? void 0 : targetCourt.resourceId,
149
+ resourceRelationId,
150
+ item.courtId
151
+ ),
152
+ venue_booking: pickFirstDefined(
153
+ itemMetadata.venue_booking,
154
+ (_b = itemRaw.metadata) == null ? void 0 : _b.venue_booking,
155
+ true
156
+ ) ?? true,
157
+ unique_identification_number: pickFirstDefined(
158
+ itemMetadata.unique_identification_number,
159
+ (_c = itemRaw.metadata) == null ? void 0 : _c.unique_identification_number
160
+ )
161
+ },
162
+ resources: [
163
+ {
164
+ form_id: pickFirstDefined(
165
+ targetCourt == null ? void 0 : targetCourt.form_id,
166
+ targetCourt == null ? void 0 : targetCourt.formId,
167
+ firstResource.form_id,
168
+ 0
169
+ ) || 0,
170
+ relation_type: pickFirstDefined(
171
+ targetCourt == null ? void 0 : targetCourt.relation_type,
172
+ targetCourt == null ? void 0 : targetCourt.relationType,
173
+ firstResource.relation_type,
174
+ "form"
175
+ ) || "form",
176
+ relation_id: resourceRelationId,
177
+ capacity: asNumber(pickFirstDefined(targetCourt == null ? void 0 : targetCourt.capacity, firstResource.capacity, 0), 0),
178
+ like_status: pickFirstDefined(
179
+ targetCourt == null ? void 0 : targetCourt.like_status,
180
+ targetCourt == null ? void 0 : targetCourt.likeStatus,
181
+ firstResource.like_status,
182
+ "common"
183
+ ) || "common",
184
+ metadata: {
185
+ ...firstResourceMetadata,
186
+ ...targetCourtMetadata,
187
+ form_name: pickFirstDefined(
188
+ targetCourt == null ? void 0 : targetCourt.form_name,
189
+ targetCourt == null ? void 0 : targetCourt.formName,
190
+ targetCourtMetadata.form_name,
191
+ firstResourceMetadata.form_name
192
+ ) || "",
193
+ resource_name: pickFirstDefined(
194
+ targetCourt == null ? void 0 : targetCourt.resource_name,
195
+ targetCourt == null ? void 0 : targetCourt.resourceName,
196
+ targetCourtMetadata.resource_name,
197
+ firstResourceMetadata.resource_name,
198
+ targetCourt == null ? void 0 : targetCourt.name
199
+ ) || (targetCourt == null ? void 0 : targetCourt.name) || ""
200
+ }
201
+ }
202
+ ],
203
+ start_date: start.date,
204
+ start_time: start.time,
205
+ end_date: end.date,
206
+ end_time: end.time,
207
+ duration: Math.max(0, Math.round(duration * 60))
208
+ };
209
+ }
210
+ function resolveDetail(options, courts) {
211
+ var _a;
212
+ try {
213
+ return ((_a = options.getBookingDetailData) == null ? void 0 : _a.call(options, options.item, courts)) || {};
214
+ } catch (error) {
215
+ return {};
216
+ }
217
+ }
218
+ function buildCustomer(options, detail, selectedCustomer) {
219
+ var _a, _b, _c, _d;
220
+ if (selectedCustomer) {
221
+ return selectedCustomer;
222
+ }
223
+ return {
224
+ name: ((_a = detail.customer) == null ? void 0 : _a.name) || ((_b = detail.headerSummary) == null ? void 0 : _b.customerName) || options.item.name || "Customer",
225
+ phone: ((_c = detail.customer) == null ? void 0 : _c.phone) || options.item.phone || "",
226
+ email: ((_d = detail.customer) == null ? void 0 : _d.email) || ""
227
+ };
228
+ }
229
+ function destroyActiveInstance() {
230
+ if (!activeInstance) {
231
+ return;
232
+ }
233
+ const { container } = activeInstance;
234
+ activeInstance = null;
235
+ import_react_dom.default.unmountComponentAtNode(container);
236
+ if (document.body.contains(container)) {
237
+ document.body.removeChild(container);
238
+ }
239
+ }
240
+ function closeActiveInstance(reason) {
241
+ if (!activeInstance) {
242
+ return;
243
+ }
244
+ const { reject } = activeInstance;
245
+ destroyActiveInstance();
246
+ reject(reason);
247
+ }
248
+ var BookingChangeModalRenderer = ({
249
+ options,
250
+ onResolve,
251
+ onReject
252
+ }) => {
253
+ var _a, _b;
254
+ const {
255
+ item,
256
+ courts = [],
257
+ visibleCourts,
258
+ bookings = [],
259
+ selectedCustomer: initialCustomer = null,
260
+ title = "Booking information",
261
+ description = "Update customer, court, date and time for this booking.",
262
+ confirmText = "Confirm",
263
+ cancelText = "Cancel",
264
+ closeOnBackdrop = true,
265
+ zIndex = 1072,
266
+ theme = "dark"
267
+ } = options;
268
+ const panelRef = (0, import_react.useRef)(null);
269
+ const closedRef = (0, import_react.useRef)(false);
270
+ const timerRef = (0, import_react.useRef)(null);
271
+ const [visible, setVisible] = (0, import_react.useState)(false);
272
+ const [customerLoading, setCustomerLoading] = (0, import_react.useState)(false);
273
+ const [submitLoading, setSubmitLoading] = (0, import_react.useState)(false);
274
+ const [submitError, setSubmitError] = (0, import_react.useState)("");
275
+ const [selectedCustomer, setSelectedCustomer] = (0, import_react.useState)(initialCustomer);
276
+ const [draft, setDraft] = (0, import_react.useState)({
277
+ targetCourtId: options.targetCourtId,
278
+ targetDate: options.targetDate,
279
+ targetHour: options.targetHour
280
+ });
281
+ const mergedCourts = (0, import_react.useMemo)(() => {
282
+ const source = (visibleCourts && visibleCourts.length > 0 ? visibleCourts : courts) || [];
283
+ if (source.length > 0) {
284
+ return source;
285
+ }
286
+ const fallbackIds = [item.courtId, draft.targetCourtId].filter(Boolean);
287
+ const uniqueIds = Array.from(new Set(fallbackIds));
288
+ return uniqueIds.map((courtId) => ({
289
+ id: courtId,
290
+ name: courtId
291
+ }));
292
+ }, [courts, draft.targetCourtId, item.courtId, visibleCourts]);
293
+ const allCourts = (0, import_react.useMemo)(() => {
294
+ if (courts.length > 0) {
295
+ return courts;
296
+ }
297
+ return mergedCourts;
298
+ }, [courts, mergedCourts]);
299
+ const targetCourt = (0, import_react.useMemo)(
300
+ () => allCourts.find((court) => court.id === draft.targetCourtId),
301
+ [allCourts, draft.targetCourtId]
302
+ );
303
+ const detail = (0, import_react.useMemo)(() => resolveDetail(options, allCourts), [allCourts, options]);
304
+ const currentCustomer = (0, import_react.useMemo)(
305
+ () => buildCustomer(options, detail, selectedCustomer),
306
+ [detail, options, selectedCustomer]
307
+ );
308
+ const hourSlots = (0, import_react.useMemo)(
309
+ () => options.hourSlots && options.hourSlots.length > 0 ? options.hourSlots : getFallbackHourSlots(),
310
+ [options.hourSlots]
311
+ );
312
+ const formatHourLabel = options.formatHourLabel || defaultFormatHourLabel;
313
+ const isBusinessHour = options.isBusinessHour || (() => true);
314
+ const availableHours = (0, import_react.useMemo)(() => {
315
+ const filtered = hourSlots.filter((hour) => isBusinessHour(hour));
316
+ return filtered.length > 0 ? filtered : hourSlots;
317
+ }, [hourSlots, isBusinessHour]);
318
+ const hourStep = (0, import_react.useMemo)(() => getHourStep(hourSlots), [hourSlots]);
319
+ const firstTimelineHour = options.firstTimelineHour ?? Math.min(...hourSlots) ?? 0;
320
+ const endTimelineExclusive = options.endTimelineExclusive ?? Math.max(...hourSlots) + hourStep;
321
+ const duration = item.endHour - item.startHour;
322
+ const nextStartHour = draft.targetHour;
323
+ const nextEndHour = nextStartHour + duration;
324
+ const originalDate = item.date;
325
+ const originalStart = formatHourLabel(item.startHour);
326
+ const originalEnd = formatHourLabel(item.endHour);
327
+ const originalCourtName = getCourtName(allCourts, item.courtId) || ((_a = detail.headerSummary) == null ? void 0 : _a.resourceLabel) || "Court";
328
+ const nextCourtName = getCourtName(allCourts, draft.targetCourtId) || ((_b = detail.headerSummary) == null ? void 0 : _b.resourceLabel) || "Court";
329
+ const changed = originalDate !== draft.targetDate || item.courtId !== draft.targetCourtId || item.startHour !== nextStartHour;
330
+ const isDragScenario = item.date !== options.targetDate || item.startHour !== options.targetHour || item.courtId !== options.targetCourtId;
331
+ const hasConflict = options.hasConflict || (() => {
332
+ return false;
333
+ });
334
+ const conflict = hasConflict(
335
+ bookings,
336
+ draft.targetCourtId,
337
+ draft.targetDate,
338
+ nextStartHour,
339
+ nextEndHour,
340
+ item.id
341
+ );
342
+ const isLight = theme === "light";
343
+ const finishClose = (type, payload) => {
344
+ if (closedRef.current) {
345
+ return;
346
+ }
347
+ closedRef.current = true;
348
+ setVisible(false);
349
+ timerRef.current = window.setTimeout(() => {
350
+ if (type === "resolve") {
351
+ onResolve(payload);
352
+ } else {
353
+ onReject(payload);
354
+ }
355
+ }, CLOSE_ANIMATION_MS);
356
+ };
357
+ const handleCancel = (reasonType) => {
358
+ if (submitLoading) {
359
+ return;
360
+ }
361
+ finishClose("reject", { type: reasonType });
362
+ };
363
+ (0, import_react.useEffect)(() => {
364
+ const rafId = window.requestAnimationFrame(() => {
365
+ setVisible(true);
366
+ });
367
+ const previousOverflow = document.body.style.overflow;
368
+ document.body.style.overflow = "hidden";
369
+ const onKeyDown = (event) => {
370
+ if (event.key === "Escape") {
371
+ handleCancel("escape");
372
+ }
373
+ };
374
+ window.addEventListener("keydown", onKeyDown);
375
+ return () => {
376
+ window.cancelAnimationFrame(rafId);
377
+ window.removeEventListener("keydown", onKeyDown);
378
+ document.body.style.overflow = previousOverflow;
379
+ if (timerRef.current) {
380
+ window.clearTimeout(timerRef.current);
381
+ }
382
+ };
383
+ }, []);
384
+ const handleCustomerChange = async () => {
385
+ if (!options.onCustomerChange || customerLoading) {
386
+ return;
387
+ }
388
+ setCustomerLoading(true);
389
+ try {
390
+ const result = await options.onCustomerChange({
391
+ item,
392
+ customer: currentCustomer,
393
+ draft,
394
+ options
395
+ });
396
+ if (result) {
397
+ setSelectedCustomer(result);
398
+ }
399
+ } finally {
400
+ setCustomerLoading(false);
401
+ }
402
+ };
403
+ const handleConfirm = async () => {
404
+ if (conflict || submitLoading) {
405
+ return;
406
+ }
407
+ setSubmitError("");
408
+ const resultBase = {
409
+ item,
410
+ original: {
411
+ targetCourtId: options.targetCourtId,
412
+ targetDate: options.targetDate,
413
+ targetHour: options.targetHour
414
+ },
415
+ draft,
416
+ customer: currentCustomer,
417
+ duration,
418
+ nextEndHour,
419
+ changed,
420
+ submitPayload: buildSubmitPayload({
421
+ item,
422
+ draft,
423
+ duration,
424
+ nextEndHour,
425
+ targetCourt
426
+ })
427
+ };
428
+ if (!options.onSubmit) {
429
+ finishClose("resolve", resultBase);
430
+ return;
431
+ }
432
+ setSubmitLoading(true);
433
+ try {
434
+ const submitResult = await options.onSubmit({
435
+ ...resultBase,
436
+ options
437
+ });
438
+ finishClose("resolve", {
439
+ ...resultBase,
440
+ submitResult
441
+ });
442
+ } catch (error) {
443
+ const message = (error == null ? void 0 : error.message) || options.submitErrorMessage || "Submit failed. Please try again.";
444
+ setSubmitError(message);
445
+ } finally {
446
+ setSubmitLoading(false);
447
+ }
448
+ };
449
+ return /* @__PURE__ */ import_react.default.createElement(
450
+ "div",
451
+ {
452
+ className: [
453
+ "pisell-booking-change-modal",
454
+ visible ? "is-visible" : "",
455
+ isLight ? "is-light" : "is-dark"
456
+ ].filter(Boolean).join(" "),
457
+ style: { zIndex },
458
+ onMouseDown: (event) => {
459
+ if (event.target === event.currentTarget && closeOnBackdrop) {
460
+ handleCancel("backdrop");
461
+ }
462
+ }
463
+ },
464
+ /* @__PURE__ */ import_react.default.createElement(
465
+ "div",
466
+ {
467
+ ref: panelRef,
468
+ className: "pisell-booking-change-modal__panel",
469
+ onMouseDown: (event) => event.stopPropagation()
470
+ },
471
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__title" }, title),
472
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__description" }, description),
473
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__body" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__card" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__customer" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__customer-info" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__section-label" }, "Customer"), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__customer-name" }, currentCustomer.name), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__customer-meta" }, currentCustomer.phone || "No phone"), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__customer-meta" }, currentCustomer.email || "No email")), /* @__PURE__ */ import_react.default.createElement(
474
+ "button",
475
+ {
476
+ type: "button",
477
+ onClick: handleCustomerChange,
478
+ disabled: !options.onCustomerChange || customerLoading,
479
+ className: "pisell-booking-change-modal__ghost-button"
480
+ },
481
+ customerLoading ? "Loading..." : "Change"
482
+ ))), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__grid" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "pisell-booking-change-modal__field-label" }, "Court"), /* @__PURE__ */ import_react.default.createElement(
483
+ "select",
484
+ {
485
+ value: draft.targetCourtId,
486
+ onChange: (event) => setDraft((prev) => ({
487
+ ...prev,
488
+ targetCourtId: event.target.value
489
+ })),
490
+ className: "pisell-booking-change-modal__control"
491
+ },
492
+ mergedCourts.map((court) => /* @__PURE__ */ import_react.default.createElement("option", { key: court.id, value: court.id }, court.name))
493
+ )), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "pisell-booking-change-modal__field-label" }, "New date"), /* @__PURE__ */ import_react.default.createElement(
494
+ "input",
495
+ {
496
+ type: "date",
497
+ value: draft.targetDate,
498
+ onChange: (event) => setDraft((prev) => ({
499
+ ...prev,
500
+ targetDate: event.target.value
501
+ })),
502
+ className: "pisell-booking-change-modal__control"
503
+ }
504
+ ))), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__grid" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "pisell-booking-change-modal__field-label" }, "New start time"), /* @__PURE__ */ import_react.default.createElement(
505
+ "select",
506
+ {
507
+ value: nextStartHour,
508
+ onChange: (event) => setDraft((prev) => ({
509
+ ...prev,
510
+ targetHour: Number(event.target.value)
511
+ })),
512
+ className: "pisell-booking-change-modal__control"
513
+ },
514
+ availableHours.map((hour) => {
515
+ const disabled = hour + duration > endTimelineExclusive || hasConflict(
516
+ bookings,
517
+ draft.targetCourtId,
518
+ draft.targetDate,
519
+ hour,
520
+ hour + duration,
521
+ item.id
522
+ );
523
+ return /* @__PURE__ */ import_react.default.createElement("option", { key: hour, value: hour, disabled }, disabled ? `Unavailable ${formatHourLabel(hour)}` : formatHourLabel(hour));
524
+ })
525
+ )), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "pisell-booking-change-modal__field-label" }, "New end time"), /* @__PURE__ */ import_react.default.createElement(
526
+ "select",
527
+ {
528
+ value: nextEndHour,
529
+ onChange: (event) => {
530
+ const targetEndHour = Number(event.target.value);
531
+ setDraft((prev) => ({
532
+ ...prev,
533
+ targetHour: targetEndHour - duration
534
+ }));
535
+ },
536
+ className: "pisell-booking-change-modal__control"
537
+ },
538
+ availableHours.map((hour) => {
539
+ const startHour = hour - duration;
540
+ const disabled = startHour < firstTimelineHour || hasConflict(
541
+ bookings,
542
+ draft.targetCourtId,
543
+ draft.targetDate,
544
+ startHour,
545
+ hour,
546
+ item.id
547
+ );
548
+ return /* @__PURE__ */ import_react.default.createElement("option", { key: `end-${hour}`, value: hour, disabled }, disabled ? `Unavailable ${formatHourLabel(hour)}` : formatHourLabel(hour));
549
+ })
550
+ ))), changed && /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__card" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__section-label" }, "Previous booking info"), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-grid" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-card" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-title" }, "Original"), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-value" }, originalCourtName), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-value" }, originalDate), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-value" }, originalStart, " - ", originalEnd)), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-card is-highlighted" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-title" }, isDragScenario ? "Drop target" : "Updated"), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-value" }, nextCourtName), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-value" }, draft.targetDate), /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__compare-value" }, formatHourLabel(nextStartHour), " - ", formatHourLabel(nextEndHour)))))),
551
+ conflict && /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__error" }, "The selected date, court, or time range is unavailable. Please choose an available option."),
552
+ !conflict && submitError && /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__error" }, submitError),
553
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "pisell-booking-change-modal__footer" }, /* @__PURE__ */ import_react.default.createElement(
554
+ "button",
555
+ {
556
+ type: "button",
557
+ onClick: () => handleCancel("cancel"),
558
+ disabled: submitLoading,
559
+ className: "pisell-booking-change-modal__ghost-button"
560
+ },
561
+ cancelText
562
+ ), /* @__PURE__ */ import_react.default.createElement(
563
+ "button",
564
+ {
565
+ type: "button",
566
+ disabled: conflict || submitLoading,
567
+ onClick: handleConfirm,
568
+ className: "pisell-booking-change-modal__primary-button"
569
+ },
570
+ submitLoading ? "Submitting..." : confirmText
571
+ ))
572
+ )
573
+ );
574
+ };
575
+ function open(options) {
576
+ if (typeof window === "undefined" || typeof document === "undefined") {
577
+ return Promise.reject({
578
+ type: "programmatic-close",
579
+ message: "bookingChangeModal can only open in a browser environment."
580
+ });
581
+ }
582
+ if (activeInstance) {
583
+ closeActiveInstance({
584
+ type: "replaced",
585
+ message: "A new booking change modal replaced the current one."
586
+ });
587
+ }
588
+ const container = document.createElement("div");
589
+ document.body.appendChild(container);
590
+ return new Promise((resolve, reject) => {
591
+ activeInstance = {
592
+ container,
593
+ reject
594
+ };
595
+ const handleResolve = (result) => {
596
+ destroyActiveInstance();
597
+ resolve(result);
598
+ };
599
+ const handleReject = (reason) => {
600
+ destroyActiveInstance();
601
+ reject(reason);
602
+ };
603
+ import_react_dom.default.render(
604
+ /* @__PURE__ */ import_react.default.createElement(
605
+ BookingChangeModalRenderer,
606
+ {
607
+ options,
608
+ onResolve: handleResolve,
609
+ onReject: handleReject
610
+ }
611
+ ),
612
+ container
613
+ );
614
+ });
615
+ }
616
+ function close(reason) {
617
+ closeActiveInstance(
618
+ reason || {
619
+ type: "programmatic-close"
620
+ }
621
+ );
622
+ }
623
+ var bookingChangeModal = {
624
+ open,
625
+ close
626
+ };
627
+ var bookingChangeModal_default = bookingChangeModal;
628
+ // Annotate the CommonJS export names for ESM import in node:
629
+ 0 && (module.exports = {
630
+ closeBookingChangeModal,
631
+ openBookingChangeModal
632
+ });