@seatlayer/js 0.12.3 → 0.13.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.
- package/README.md +3 -0
- package/dist/index.cjs +254 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +254 -26
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -933,6 +933,10 @@ declare class SeatManager {
|
|
|
933
933
|
private sectionByObject;
|
|
934
934
|
private sectionLabelById;
|
|
935
935
|
private lastSyncedAt;
|
|
936
|
+
private blockedQuery;
|
|
937
|
+
private blockedSection;
|
|
938
|
+
private blockedResultLimit;
|
|
939
|
+
private unblockAllConfirmTimer;
|
|
936
940
|
private readonly onFullscreenChange;
|
|
937
941
|
private readonly onKeyDown;
|
|
938
942
|
constructor(options: SeatManagerOptions);
|
|
@@ -988,8 +992,9 @@ declare class SeatManager {
|
|
|
988
992
|
private onMessage;
|
|
989
993
|
private resnapshot;
|
|
990
994
|
private applySnapshot;
|
|
991
|
-
/** Optimistic local write shared by
|
|
992
|
-
|
|
995
|
+
/** Optimistic local write shared by organizer actions. Paint and tally once,
|
|
996
|
+
* even when an arena-sized operation changes hundreds of seats. */
|
|
997
|
+
private setSeatsLocal;
|
|
993
998
|
/** Keep the canvas painting on hidden/occluded tabs (war-room second monitor). */
|
|
994
999
|
private afterPaint;
|
|
995
1000
|
private flash;
|
|
@@ -1023,8 +1028,17 @@ declare class SeatManager {
|
|
|
1023
1028
|
private paintLegend;
|
|
1024
1029
|
private paintFeed;
|
|
1025
1030
|
private renderBlockRail;
|
|
1026
|
-
private
|
|
1031
|
+
private toggleCategory;
|
|
1032
|
+
/** A category/filter is a real toggle: add the missing seats, or remove the
|
|
1033
|
+
* whole group when every eligible seat in it is already selected. */
|
|
1034
|
+
private toggleLabels;
|
|
1035
|
+
private isBlockSelectable;
|
|
1027
1036
|
private paintSelBar;
|
|
1037
|
+
private paintCategoryControls;
|
|
1038
|
+
private filteredBlockedSeats;
|
|
1039
|
+
private paintBlockedInventory;
|
|
1040
|
+
private confirmUnblockAll;
|
|
1041
|
+
private resetUnblockAllConfirm;
|
|
1028
1042
|
private done;
|
|
1029
1043
|
private toastOk;
|
|
1030
1044
|
private toastErr;
|
package/dist/index.d.ts
CHANGED
|
@@ -933,6 +933,10 @@ declare class SeatManager {
|
|
|
933
933
|
private sectionByObject;
|
|
934
934
|
private sectionLabelById;
|
|
935
935
|
private lastSyncedAt;
|
|
936
|
+
private blockedQuery;
|
|
937
|
+
private blockedSection;
|
|
938
|
+
private blockedResultLimit;
|
|
939
|
+
private unblockAllConfirmTimer;
|
|
936
940
|
private readonly onFullscreenChange;
|
|
937
941
|
private readonly onKeyDown;
|
|
938
942
|
constructor(options: SeatManagerOptions);
|
|
@@ -988,8 +992,9 @@ declare class SeatManager {
|
|
|
988
992
|
private onMessage;
|
|
989
993
|
private resnapshot;
|
|
990
994
|
private applySnapshot;
|
|
991
|
-
/** Optimistic local write shared by
|
|
992
|
-
|
|
995
|
+
/** Optimistic local write shared by organizer actions. Paint and tally once,
|
|
996
|
+
* even when an arena-sized operation changes hundreds of seats. */
|
|
997
|
+
private setSeatsLocal;
|
|
993
998
|
/** Keep the canvas painting on hidden/occluded tabs (war-room second monitor). */
|
|
994
999
|
private afterPaint;
|
|
995
1000
|
private flash;
|
|
@@ -1023,8 +1028,17 @@ declare class SeatManager {
|
|
|
1023
1028
|
private paintLegend;
|
|
1024
1029
|
private paintFeed;
|
|
1025
1030
|
private renderBlockRail;
|
|
1026
|
-
private
|
|
1031
|
+
private toggleCategory;
|
|
1032
|
+
/** A category/filter is a real toggle: add the missing seats, or remove the
|
|
1033
|
+
* whole group when every eligible seat in it is already selected. */
|
|
1034
|
+
private toggleLabels;
|
|
1035
|
+
private isBlockSelectable;
|
|
1027
1036
|
private paintSelBar;
|
|
1037
|
+
private paintCategoryControls;
|
|
1038
|
+
private filteredBlockedSeats;
|
|
1039
|
+
private paintBlockedInventory;
|
|
1040
|
+
private confirmUnblockAll;
|
|
1041
|
+
private resetUnblockAllConfirm;
|
|
1028
1042
|
private done;
|
|
1029
1043
|
private toastOk;
|
|
1030
1044
|
private toastErr;
|
package/dist/index.js
CHANGED
|
@@ -2342,11 +2342,44 @@ var CSS2 = `
|
|
|
2342
2342
|
font-size:12px;font-weight:700;color:var(--slm-text);display:inline-flex;align-items:center;gap:6px}
|
|
2343
2343
|
.slm-chip:hover{border-color:var(--slm-muted)}
|
|
2344
2344
|
.slm-chip .dot{width:8px;height:8px;border-radius:50%}
|
|
2345
|
+
.slm-chip .slm-chipcount{min-width:18px;padding:1px 5px;border-radius:999px;background:rgba(255,255,255,.07);
|
|
2346
|
+
color:var(--slm-muted);font-size:10px;font-variant-numeric:tabular-nums;text-align:center}
|
|
2347
|
+
.slm-chip .slm-chipcheck{display:none;font-size:11px;line-height:1}
|
|
2348
|
+
.slm-chip.on{border-color:var(--slm-accent);background:color-mix(in srgb,var(--slm-accent) 20%,var(--slm-surface));
|
|
2349
|
+
box-shadow:0 0 0 1px color-mix(in srgb,var(--slm-accent) 45%,transparent)}
|
|
2350
|
+
.slm-chip.on .slm-chipcount{background:var(--slm-accent);color:var(--slm-accent-ink)}
|
|
2351
|
+
.slm-chip.on .slm-chipcheck{display:inline}
|
|
2352
|
+
.slm-chip.partial{border-style:dashed;border-color:var(--slm-accent)}
|
|
2353
|
+
.slm-chip:disabled{opacity:.42;cursor:not-allowed}
|
|
2354
|
+
.slm-selecthelp{margin:-1px 0 9px;color:var(--slm-muted);font-size:11px;line-height:1.4}
|
|
2345
2355
|
.slm-field{margin:14px 0}
|
|
2346
2356
|
.slm-field label{display:block;font-size:11px;font-weight:700;color:var(--slm-muted);margin-bottom:5px}
|
|
2347
2357
|
.slm-input,.slm-select{width:100%;padding:8px 10px;border-radius:9px;border:1px solid var(--slm-line);
|
|
2348
2358
|
background:var(--slm-surface);color:var(--slm-text)}
|
|
2349
2359
|
.slm-note{font-size:11.5px;color:var(--slm-muted);margin-top:5px}
|
|
2360
|
+
.slm-blocked{margin-top:17px;padding-top:15px;border-top:1px solid var(--slm-line)}
|
|
2361
|
+
.slm-blockedhead{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:8px}
|
|
2362
|
+
.slm-blockedhead .slm-eyebrow{margin-bottom:0}.slm-blockedtotal{font-size:11px;color:var(--slm-muted)}
|
|
2363
|
+
.slm-blockedtotal b{color:var(--slm-text);font-variant-numeric:tabular-nums}
|
|
2364
|
+
.slm-blockedtools{display:grid;grid-template-columns:minmax(0,1fr);gap:7px}
|
|
2365
|
+
.slm-blockedsummary{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:9px 0 6px;
|
|
2366
|
+
color:var(--slm-muted);font-size:10.5px}
|
|
2367
|
+
.slm-linkbtn{font-size:11px!important;font-weight:800!important;color:var(--slm-accent)!important;text-align:right}
|
|
2368
|
+
.slm-linkbtn:disabled{opacity:.45;cursor:not-allowed}
|
|
2369
|
+
.slm-blockedlist{max-height:246px;overflow:auto;border:1px solid var(--slm-line);border-radius:10px;background:var(--slm-surface)}
|
|
2370
|
+
.slm-blockeditem{display:grid!important;grid-template-columns:18px minmax(0,1fr);width:100%;gap:8px;padding:8px 9px!important;
|
|
2371
|
+
border-bottom:1px solid var(--slm-line)!important;text-align:left!important}
|
|
2372
|
+
.slm-blockeditem:last-child{border-bottom:0!important}.slm-blockeditem:hover{background:rgba(255,255,255,.035)!important}
|
|
2373
|
+
.slm-blockeditem.on{background:color-mix(in srgb,var(--slm-accent) 13%,var(--slm-surface))!important}
|
|
2374
|
+
.slm-blockedcheck{display:flex;align-items:center;justify-content:center;width:16px;height:16px;margin-top:1px;border-radius:4px;
|
|
2375
|
+
border:1px solid var(--slm-muted);color:transparent;font-size:10px;font-weight:900}
|
|
2376
|
+
.slm-blockeditem.on .slm-blockedcheck{border-color:var(--slm-accent);background:var(--slm-accent);color:var(--slm-accent-ink)}
|
|
2377
|
+
.slm-blockedcopy{min-width:0}.slm-blockedlabel{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
2378
|
+
font-size:12px;font-weight:800}.slm-blockedmeta{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
2379
|
+
margin-top:2px;color:var(--slm-muted);font-size:10px}
|
|
2380
|
+
.slm-blockedmore{width:100%;padding:9px!important;color:var(--slm-accent)!important;font-size:11px!important;font-weight:800!important}
|
|
2381
|
+
.slm-blockedempty{padding:12px;color:var(--slm-muted);font-size:11.5px;line-height:1.45}
|
|
2382
|
+
.slm-allnote{margin-top:-4px;margin-bottom:10px}
|
|
2350
2383
|
|
|
2351
2384
|
/* toast */
|
|
2352
2385
|
.slm-toast{position:absolute;left:50%;bottom:16px;transform:translateX(-50%);padding:10px 16px;border-radius:10px;
|
|
@@ -2466,6 +2499,10 @@ var SeatManager = class {
|
|
|
2466
2499
|
this.sectionByObject = /* @__PURE__ */ new Map();
|
|
2467
2500
|
this.sectionLabelById = /* @__PURE__ */ new Map();
|
|
2468
2501
|
this.lastSyncedAt = null;
|
|
2502
|
+
this.blockedQuery = "";
|
|
2503
|
+
this.blockedSection = "";
|
|
2504
|
+
this.blockedResultLimit = 100;
|
|
2505
|
+
this.unblockAllConfirmTimer = null;
|
|
2469
2506
|
this.onFullscreenChange = () => {
|
|
2470
2507
|
this.paintFullscreenButton();
|
|
2471
2508
|
this.updateContainerLayout();
|
|
@@ -2611,13 +2648,13 @@ var SeatManager = class {
|
|
|
2611
2648
|
const targets = (labels ?? this.selectionLabels()).filter((l) => this.status.get(l) === "free");
|
|
2612
2649
|
if (!targets.length) return;
|
|
2613
2650
|
const releaseAt = opts.releaseAt ?? this.releaseAt ?? void 0;
|
|
2614
|
-
|
|
2651
|
+
this.setSeatsLocal(targets, "blocked");
|
|
2615
2652
|
try {
|
|
2616
2653
|
await this.api.block(this.key, targets, { ...opts, releaseAt });
|
|
2617
2654
|
this.clearSelection();
|
|
2618
2655
|
this.done("block", targets, releaseAt ? `Blocked ${targets.length} \u2014 auto-release ${new Date(releaseAt).toLocaleString()}.` : `Blocked ${targets.length} seat${targets.length === 1 ? "" : "s"}.`);
|
|
2619
2656
|
} catch (err) {
|
|
2620
|
-
|
|
2657
|
+
this.setSeatsLocal(targets, "free");
|
|
2621
2658
|
this.toastErr(err instanceof ManageApiError && err.status === 409 ? "Some seats were just taken. Try again." : "Couldn't block those seats.");
|
|
2622
2659
|
this.opts.onError?.(err);
|
|
2623
2660
|
}
|
|
@@ -2625,13 +2662,13 @@ var SeatManager = class {
|
|
|
2625
2662
|
async unblock(labels) {
|
|
2626
2663
|
const targets = (labels ?? this.selectionLabels()).filter((l) => this.status.get(l) === "blocked");
|
|
2627
2664
|
if (!targets.length) return;
|
|
2628
|
-
|
|
2665
|
+
this.setSeatsLocal(targets, "free");
|
|
2629
2666
|
try {
|
|
2630
2667
|
await this.api.unblock(this.key, targets);
|
|
2631
2668
|
this.clearSelection();
|
|
2632
2669
|
this.done("unblock", targets, `Unblocked ${targets.length} seat${targets.length === 1 ? "" : "s"}.`);
|
|
2633
2670
|
} catch (err) {
|
|
2634
|
-
|
|
2671
|
+
this.setSeatsLocal(targets, "blocked");
|
|
2635
2672
|
this.toastErr("Couldn't unblock those seats.");
|
|
2636
2673
|
this.opts.onError?.(err);
|
|
2637
2674
|
}
|
|
@@ -2639,9 +2676,10 @@ var SeatManager = class {
|
|
|
2639
2676
|
async unblockAll() {
|
|
2640
2677
|
const blocked = [...this.status.entries()].filter(([, s]) => s === "blocked").map(([l]) => l);
|
|
2641
2678
|
if (!blocked.length) return;
|
|
2642
|
-
|
|
2679
|
+
this.setSeatsLocal(blocked, "free");
|
|
2643
2680
|
try {
|
|
2644
2681
|
const res = await this.api.unblockAll(this.key);
|
|
2682
|
+
this.clearSelection();
|
|
2645
2683
|
this.done("unblockAll", blocked, `Unblocked ${res.freed} seat${res.freed === 1 ? "" : "s"}.`);
|
|
2646
2684
|
} catch (err) {
|
|
2647
2685
|
await this.resnapshot();
|
|
@@ -2653,13 +2691,13 @@ var SeatManager = class {
|
|
|
2653
2691
|
async cancelBooking(labels, bookingRef) {
|
|
2654
2692
|
const targets = labels.filter((l) => this.status.get(l) === "booked");
|
|
2655
2693
|
if (!targets.length || !bookingRef) return;
|
|
2656
|
-
|
|
2694
|
+
this.setSeatsLocal(targets, "free");
|
|
2657
2695
|
try {
|
|
2658
2696
|
await this.api.unbook(this.key, targets, bookingRef);
|
|
2659
2697
|
this.clearSelection();
|
|
2660
2698
|
this.done("cancelBooking", targets, `Cancelled ${targets.length} booking${targets.length === 1 ? "" : "s"}.`);
|
|
2661
2699
|
} catch (err) {
|
|
2662
|
-
|
|
2700
|
+
this.setSeatsLocal(targets, "booked");
|
|
2663
2701
|
this.toastErr("Couldn't cancel that booking. Check the reference.");
|
|
2664
2702
|
this.opts.onError?.(err);
|
|
2665
2703
|
}
|
|
@@ -2722,6 +2760,7 @@ var SeatManager = class {
|
|
|
2722
2760
|
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
2723
2761
|
if (this.feedTimer) clearInterval(this.feedTimer);
|
|
2724
2762
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
2763
|
+
if (this.unblockAllConfirmTimer) clearTimeout(this.unblockAllConfirmTimer);
|
|
2725
2764
|
if (this.revenueRefreshTimer) clearTimeout(this.revenueRefreshTimer);
|
|
2726
2765
|
if (this.tokenRefreshTimer) clearTimeout(this.tokenRefreshTimer);
|
|
2727
2766
|
this.layoutObserver?.disconnect();
|
|
@@ -2900,11 +2939,16 @@ var SeatManager = class {
|
|
|
2900
2939
|
this.afterPaint();
|
|
2901
2940
|
this.recomputeTallies();
|
|
2902
2941
|
}
|
|
2903
|
-
/** Optimistic local write shared by
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
const
|
|
2907
|
-
|
|
2942
|
+
/** Optimistic local write shared by organizer actions. Paint and tally once,
|
|
2943
|
+
* even when an arena-sized operation changes hundreds of seats. */
|
|
2944
|
+
setSeatsLocal(labels, st) {
|
|
2945
|
+
const ids = [];
|
|
2946
|
+
for (const label of labels) {
|
|
2947
|
+
this.status.set(label, st);
|
|
2948
|
+
const id = this.labelToId.get(label);
|
|
2949
|
+
if (id) ids.push(id);
|
|
2950
|
+
}
|
|
2951
|
+
if (ids.length) this.renderer?.setStatus(ids, toRenderStatus(st));
|
|
2908
2952
|
this.afterPaint();
|
|
2909
2953
|
this.recomputeTallies();
|
|
2910
2954
|
}
|
|
@@ -2987,6 +3031,7 @@ var SeatManager = class {
|
|
|
2987
3031
|
this.paintLegend(t3);
|
|
2988
3032
|
this.paintMonitorInsights();
|
|
2989
3033
|
} else if (this.mode === "inspect") this.renderInspectRail(this.getSelection());
|
|
3034
|
+
else if (this.mode === "block") this.paintSelBar(this.getSelection());
|
|
2990
3035
|
this.opts.onTallies?.(t3);
|
|
2991
3036
|
}
|
|
2992
3037
|
verbFor(prev, next) {
|
|
@@ -3366,23 +3411,30 @@ var SeatManager = class {
|
|
|
3366
3411
|
}
|
|
3367
3412
|
renderBlockRail() {
|
|
3368
3413
|
const cats = this.doc?.categories ?? [];
|
|
3369
|
-
const catChips = cats.map((c) => `<button class="slm-chip" data-cat="${esc(c.key)}"
|
|
3414
|
+
const catChips = cats.map((c) => `<button class="slm-chip" type="button" data-cat="${esc(c.key)}" aria-pressed="false">
|
|
3415
|
+
<span class="dot" style="background:${esc(c.color ?? "#6e7bff")}"></span>
|
|
3416
|
+
<span>${esc(c.label ?? c.key)}</span>
|
|
3417
|
+
<span class="slm-chipcount" data-cat-count>0</span>
|
|
3418
|
+
<span class="slm-chipcheck" aria-hidden="true">\u2713</span>
|
|
3419
|
+
</button>`).join("");
|
|
3370
3420
|
const sectionField = this.sectionOptions.length ? `<div class="slm-field"><label>Select a whole section</label>
|
|
3371
3421
|
<select class="slm-select" data-ref="section"><option value="">Choose a section\u2026</option>
|
|
3372
3422
|
${this.sectionOptions.map((s) => `<option value="${esc(s.id)}">${esc(s.label)}</option>`).join("")}</select></div>` : "";
|
|
3423
|
+
const blockedSectionOptions = this.sectionOptions.map((s) => `<option value="${esc(s.id)}">${esc(s.label)}</option>`).join("");
|
|
3373
3424
|
this.els.rail.innerHTML = `
|
|
3374
3425
|
<p class="slm-eyebrow">Block & unblock</p>
|
|
3375
3426
|
<p class="slm-hint">Drag a box on the map to marquee-select, \u2318A for all, or pick a category/section. Booked and held inventory is never actionable here.</p>
|
|
3376
|
-
<div class="slm-selbar"><span class="slm-selnum" data-ref="selnum">0</span><span class="slm-sellabel">selected</span></div>
|
|
3427
|
+
<div class="slm-selbar" aria-live="polite"><span class="slm-selnum" data-ref="selnum">0</span><span class="slm-sellabel" data-ref="selmeta">selected</span></div>
|
|
3377
3428
|
<div class="slm-row">
|
|
3378
3429
|
<button class="slm-btn" data-ref="doblock" disabled>Block</button>
|
|
3379
|
-
<button class="slm-btn ghost" data-ref="dounblock" disabled>
|
|
3430
|
+
<button class="slm-btn ghost" data-ref="dounblock" disabled>Put back on sale</button>
|
|
3380
3431
|
</div>
|
|
3381
3432
|
<div class="slm-row">
|
|
3382
3433
|
<button class="slm-btn ghost" data-ref="selall">Select all</button>
|
|
3383
3434
|
<button class="slm-btn ghost" data-ref="clearsel">Clear</button>
|
|
3384
3435
|
</div>
|
|
3385
|
-
<p class="slm-eyebrow" style="margin-top:8px">
|
|
3436
|
+
<p class="slm-eyebrow" style="margin-top:8px">Select by category</p>
|
|
3437
|
+
<p class="slm-selecthelp">Choose one or more. A checked category is selected; click it again to remove it.</p>
|
|
3386
3438
|
<div class="slm-chiprow">${catChips || '<span class="slm-empty">No categories.</span>'}</div>
|
|
3387
3439
|
${sectionField}
|
|
3388
3440
|
<div class="slm-field">
|
|
@@ -3390,18 +3442,46 @@ var SeatManager = class {
|
|
|
3390
3442
|
<input type="datetime-local" class="slm-input" data-ref="release" />
|
|
3391
3443
|
<p class="slm-note" data-ref="releasenote">Leave empty to block permanently.</p>
|
|
3392
3444
|
</div>
|
|
3393
|
-
<
|
|
3445
|
+
<section class="slm-blocked" aria-labelledby="slm-blocked-title">
|
|
3446
|
+
<div class="slm-blockedhead">
|
|
3447
|
+
<p class="slm-eyebrow" id="slm-blocked-title">Blocked inventory</p>
|
|
3448
|
+
<span class="slm-blockedtotal"><b data-ref="blockedcount">0</b> out of sale</span>
|
|
3449
|
+
</div>
|
|
3450
|
+
<p class="slm-selecthelp">Find blocked seats, select only the ones you need, then use \u201CPut back on sale\u201D.</p>
|
|
3451
|
+
<div class="slm-blockedtools">
|
|
3452
|
+
<input type="search" class="slm-input" data-ref="blockedsearch" placeholder="Find seat, row or category" aria-label="Search blocked seats" />
|
|
3453
|
+
<select class="slm-select" data-ref="blockedsection" aria-label="Filter blocked seats by section">
|
|
3454
|
+
<option value="">All sections</option>${blockedSectionOptions}
|
|
3455
|
+
</select>
|
|
3456
|
+
</div>
|
|
3457
|
+
<div class="slm-blockedsummary">
|
|
3458
|
+
<span data-ref="blockedshowing">No blocked seats</span>
|
|
3459
|
+
<button type="button" class="slm-linkbtn" data-ref="selblocked" disabled>Select results</button>
|
|
3460
|
+
</div>
|
|
3461
|
+
<div class="slm-blockedlist" data-ref="blockedlist"></div>
|
|
3462
|
+
</section>
|
|
3463
|
+
<div class="slm-field">
|
|
3464
|
+
<button class="slm-btn ghost" data-ref="markall" style="width:100%" disabled>Put all blocked seats on sale</button>
|
|
3465
|
+
<p class="slm-note slm-allnote" data-ref="markallnote">For a full reset only. You will be asked to confirm.</p>
|
|
3466
|
+
</div>
|
|
3394
3467
|
`;
|
|
3395
3468
|
const r = (n) => this.els.rail.querySelector(`[data-ref="${n}"]`);
|
|
3396
3469
|
this.els.selnum = r("selnum");
|
|
3397
3470
|
this.els.doblock = r("doblock");
|
|
3398
3471
|
this.els.dounblock = r("dounblock");
|
|
3472
|
+
this.els.selmeta = r("selmeta");
|
|
3473
|
+
this.els.blockedcount = r("blockedcount");
|
|
3474
|
+
this.els.blockedshowing = r("blockedshowing");
|
|
3475
|
+
this.els.blockedlist = r("blockedlist");
|
|
3476
|
+
this.els.selblocked = r("selblocked");
|
|
3477
|
+
this.els.markall = r("markall");
|
|
3478
|
+
this.els.markallnote = r("markallnote");
|
|
3399
3479
|
r("doblock").addEventListener("click", () => void this.block());
|
|
3400
3480
|
r("dounblock").addEventListener("click", () => void this.unblock());
|
|
3401
3481
|
r("selall").addEventListener("click", () => this.selectAll());
|
|
3402
3482
|
r("clearsel").addEventListener("click", () => this.clearSelection());
|
|
3403
|
-
r("markall").addEventListener("click", () =>
|
|
3404
|
-
this.els.rail.querySelectorAll("[data-cat]").forEach((b) => b.addEventListener("click", () => this.
|
|
3483
|
+
r("markall").addEventListener("click", () => this.confirmUnblockAll());
|
|
3484
|
+
this.els.rail.querySelectorAll("[data-cat]").forEach((b) => b.addEventListener("click", () => this.toggleCategory(b.dataset.cat)));
|
|
3405
3485
|
const sectionSel = this.els.rail.querySelector('[data-ref="section"]');
|
|
3406
3486
|
sectionSel?.addEventListener("change", () => {
|
|
3407
3487
|
if (sectionSel.value) {
|
|
@@ -3409,6 +3489,32 @@ var SeatManager = class {
|
|
|
3409
3489
|
sectionSel.value = "";
|
|
3410
3490
|
}
|
|
3411
3491
|
});
|
|
3492
|
+
const blockedSearch = r("blockedsearch");
|
|
3493
|
+
const blockedSection = r("blockedsection");
|
|
3494
|
+
blockedSearch.value = this.blockedQuery;
|
|
3495
|
+
blockedSection.value = this.blockedSection;
|
|
3496
|
+
blockedSearch.addEventListener("input", () => {
|
|
3497
|
+
this.blockedQuery = blockedSearch.value;
|
|
3498
|
+
this.blockedResultLimit = 100;
|
|
3499
|
+
this.paintBlockedInventory();
|
|
3500
|
+
});
|
|
3501
|
+
blockedSection.addEventListener("change", () => {
|
|
3502
|
+
this.blockedSection = blockedSection.value;
|
|
3503
|
+
this.blockedResultLimit = 100;
|
|
3504
|
+
this.paintBlockedInventory();
|
|
3505
|
+
});
|
|
3506
|
+
r("selblocked").addEventListener("click", () => {
|
|
3507
|
+
this.toggleLabels(this.filteredBlockedSeats().map((seat) => seat.label));
|
|
3508
|
+
});
|
|
3509
|
+
r("blockedlist").addEventListener("click", (event) => {
|
|
3510
|
+
const target = event.target;
|
|
3511
|
+
const seatButton = target.closest("[data-blocked-label]");
|
|
3512
|
+
if (seatButton?.dataset.blockedLabel) this.toggleLabels([seatButton.dataset.blockedLabel]);
|
|
3513
|
+
else if (target.closest("[data-blocked-more]")) {
|
|
3514
|
+
this.blockedResultLimit += 100;
|
|
3515
|
+
this.paintBlockedInventory();
|
|
3516
|
+
}
|
|
3517
|
+
});
|
|
3412
3518
|
const rel = r("release");
|
|
3413
3519
|
rel.addEventListener("change", () => {
|
|
3414
3520
|
const ms = rel.value ? new Date(rel.value).getTime() : NaN;
|
|
@@ -3418,18 +3524,140 @@ var SeatManager = class {
|
|
|
3418
3524
|
});
|
|
3419
3525
|
this.paintSelBar(this.getSelection());
|
|
3420
3526
|
}
|
|
3421
|
-
|
|
3527
|
+
toggleCategory(catKey) {
|
|
3422
3528
|
const labels = [];
|
|
3423
|
-
for (const [label, seat] of this.labelToSeat.entries())
|
|
3424
|
-
|
|
3529
|
+
for (const [label, seat] of this.labelToSeat.entries()) {
|
|
3530
|
+
if (seat.categoryKey === catKey && this.isBlockSelectable(label)) labels.push(label);
|
|
3531
|
+
}
|
|
3532
|
+
this.toggleLabels(labels);
|
|
3533
|
+
}
|
|
3534
|
+
/** A category/filter is a real toggle: add the missing seats, or remove the
|
|
3535
|
+
* whole group when every eligible seat in it is already selected. */
|
|
3536
|
+
toggleLabels(labels) {
|
|
3537
|
+
if (!this.renderer) return;
|
|
3538
|
+
const eligible = labels.filter((label) => this.labelToSeat.has(label) && this.isBlockSelectable(label));
|
|
3539
|
+
if (!eligible.length) return;
|
|
3540
|
+
const selected = new Set(this.selectionLabels());
|
|
3541
|
+
const allSelected = eligible.every((label) => selected.has(label));
|
|
3542
|
+
if (allSelected) {
|
|
3543
|
+
const ids = eligible.map((label) => this.labelToId.get(label)).filter((id) => Boolean(id));
|
|
3544
|
+
this.renderer.deselect(ids);
|
|
3545
|
+
} else {
|
|
3546
|
+
this.renderer.selectByLabels(eligible);
|
|
3547
|
+
}
|
|
3548
|
+
this.syncSelection();
|
|
3549
|
+
}
|
|
3550
|
+
isBlockSelectable(label) {
|
|
3551
|
+
const status = this.status.get(label) ?? "free";
|
|
3552
|
+
return status === "free" || status === "blocked";
|
|
3425
3553
|
}
|
|
3426
3554
|
paintSelBar(seats) {
|
|
3427
3555
|
if (!this.els.selnum) return;
|
|
3428
3556
|
this.els.selnum.textContent = seats.length.toLocaleString();
|
|
3429
|
-
const
|
|
3430
|
-
const
|
|
3431
|
-
this.els.
|
|
3432
|
-
this.els.
|
|
3557
|
+
const freeCount = seats.filter((s) => (this.status.get(s.label) ?? "free") === "free").length;
|
|
3558
|
+
const blockedCount = seats.filter((s) => this.status.get(s.label) === "blocked").length;
|
|
3559
|
+
this.els.selmeta.textContent = seats.length ? `${freeCount.toLocaleString()} available \xB7 ${blockedCount.toLocaleString()} blocked` : "selected";
|
|
3560
|
+
const blockButton = this.els.doblock;
|
|
3561
|
+
const unblockButton = this.els.dounblock;
|
|
3562
|
+
blockButton.disabled = freeCount === 0;
|
|
3563
|
+
unblockButton.disabled = blockedCount === 0;
|
|
3564
|
+
blockButton.textContent = freeCount ? `Block ${freeCount.toLocaleString()}` : "Block selected";
|
|
3565
|
+
unblockButton.textContent = blockedCount ? `Put ${blockedCount.toLocaleString()} on sale` : "Put back on sale";
|
|
3566
|
+
this.paintCategoryControls(seats);
|
|
3567
|
+
this.paintBlockedInventory();
|
|
3568
|
+
}
|
|
3569
|
+
paintCategoryControls(seats) {
|
|
3570
|
+
const selected = new Set(seats.map((seat) => seat.label));
|
|
3571
|
+
this.els.rail?.querySelectorAll("[data-cat]").forEach((button) => {
|
|
3572
|
+
const catKey = button.dataset.cat;
|
|
3573
|
+
const labels = [];
|
|
3574
|
+
for (const [label, seat] of this.labelToSeat.entries()) {
|
|
3575
|
+
if (seat.categoryKey === catKey && this.isBlockSelectable(label)) labels.push(label);
|
|
3576
|
+
}
|
|
3577
|
+
const picked = labels.filter((label) => selected.has(label)).length;
|
|
3578
|
+
const full = labels.length > 0 && picked === labels.length;
|
|
3579
|
+
const partial = picked > 0 && !full;
|
|
3580
|
+
button.disabled = labels.length === 0;
|
|
3581
|
+
button.classList.toggle("on", full);
|
|
3582
|
+
button.classList.toggle("partial", partial);
|
|
3583
|
+
button.setAttribute("aria-pressed", full ? "true" : partial ? "mixed" : "false");
|
|
3584
|
+
button.setAttribute("title", full ? `Remove all ${labels.length.toLocaleString()} seats in this category from the selection` : partial ? `Select the remaining ${(labels.length - picked).toLocaleString()} seats in this category` : `Select all ${labels.length.toLocaleString()} seats in this category`);
|
|
3585
|
+
const count = button.querySelector("[data-cat-count]");
|
|
3586
|
+
if (count) count.textContent = picked ? `${picked.toLocaleString()}/${labels.length.toLocaleString()}` : labels.length.toLocaleString();
|
|
3587
|
+
});
|
|
3588
|
+
}
|
|
3589
|
+
filteredBlockedSeats() {
|
|
3590
|
+
const query = this.blockedQuery.trim().toLocaleLowerCase();
|
|
3591
|
+
const seats = [];
|
|
3592
|
+
for (const [label, seat] of this.labelToSeat.entries()) {
|
|
3593
|
+
if (this.status.get(label) !== "blocked") continue;
|
|
3594
|
+
const sectionId = this.sectionByObject.get(seat.rowId) ?? UNGROUPED_ID;
|
|
3595
|
+
if (this.blockedSection && sectionId !== this.blockedSection) continue;
|
|
3596
|
+
if (query) {
|
|
3597
|
+
const category = this.doc?.categories.find((item) => item.key === seat.categoryKey)?.label ?? seat.categoryKey;
|
|
3598
|
+
const section = this.sectionLabelById.get(sectionId) ?? "Other seats";
|
|
3599
|
+
const object = this.doc?.objects.find((item) => item.id === seat.rowId);
|
|
3600
|
+
const objectLabel = object?.type === "row" || object?.type === "table" ? object.label : "";
|
|
3601
|
+
const haystack = `${label} ${category} ${section} ${objectLabel}`.toLocaleLowerCase();
|
|
3602
|
+
if (!haystack.includes(query)) continue;
|
|
3603
|
+
}
|
|
3604
|
+
seats.push(seat);
|
|
3605
|
+
}
|
|
3606
|
+
return seats.sort((a, b) => a.label.localeCompare(b.label, void 0, { numeric: true, sensitivity: "base" }));
|
|
3607
|
+
}
|
|
3608
|
+
paintBlockedInventory() {
|
|
3609
|
+
if (!this.els.blockedlist) return;
|
|
3610
|
+
const allBlocked = [...this.status.entries()].filter(([, status]) => status === "blocked").length;
|
|
3611
|
+
const filtered = this.filteredBlockedSeats();
|
|
3612
|
+
const visible = filtered.slice(0, this.blockedResultLimit);
|
|
3613
|
+
const selected = new Set(this.selectionLabels());
|
|
3614
|
+
const selectedResults = filtered.filter((seat) => selected.has(seat.label)).length;
|
|
3615
|
+
const allResultsSelected = filtered.length > 0 && selectedResults === filtered.length;
|
|
3616
|
+
this.els.blockedcount.textContent = allBlocked.toLocaleString();
|
|
3617
|
+
this.els.blockedshowing.textContent = filtered.length ? `Showing ${visible.length.toLocaleString()} of ${filtered.length.toLocaleString()}` : allBlocked ? "No matches" : "No blocked seats";
|
|
3618
|
+
const selectResults = this.els.selblocked;
|
|
3619
|
+
selectResults.disabled = filtered.length === 0;
|
|
3620
|
+
selectResults.textContent = allResultsSelected ? `Remove ${filtered.length.toLocaleString()} results` : `Select ${filtered.length.toLocaleString()} results`;
|
|
3621
|
+
this.els.blockedlist.innerHTML = visible.length ? visible.map((seat) => {
|
|
3622
|
+
const sectionId = this.sectionByObject.get(seat.rowId) ?? UNGROUPED_ID;
|
|
3623
|
+
const section = this.sectionLabelById.get(sectionId) ?? "Other seats";
|
|
3624
|
+
const category = this.doc?.categories.find((item) => item.key === seat.categoryKey)?.label ?? seat.categoryKey;
|
|
3625
|
+
const isSelected = selected.has(seat.label);
|
|
3626
|
+
return `<button type="button" class="slm-blockeditem${isSelected ? " on" : ""}" data-blocked-label="${esc(seat.label)}" aria-pressed="${isSelected}">
|
|
3627
|
+
<span class="slm-blockedcheck" aria-hidden="true">\u2713</span>
|
|
3628
|
+
<span class="slm-blockedcopy"><span class="slm-blockedlabel">${esc(seat.label)}</span>
|
|
3629
|
+
<span class="slm-blockedmeta">${esc(section)} \xB7 ${esc(category)}</span></span>
|
|
3630
|
+
</button>`;
|
|
3631
|
+
}).join("") + (filtered.length > visible.length ? `<button type="button" class="slm-blockedmore" data-blocked-more>Show 100 more</button>` : "") : `<div class="slm-blockedempty">${allBlocked ? "No blocked seats match this search or section." : "No seats are blocked. Newly blocked seats will appear here."}</div>`;
|
|
3632
|
+
const markAll = this.els.markall;
|
|
3633
|
+
const armed = markAll.dataset.confirm === "true";
|
|
3634
|
+
markAll.disabled = allBlocked === 0;
|
|
3635
|
+
markAll.textContent = armed ? `Confirm: put all ${allBlocked.toLocaleString()} on sale` : `Put all ${allBlocked.toLocaleString()} blocked seats on sale`;
|
|
3636
|
+
}
|
|
3637
|
+
confirmUnblockAll() {
|
|
3638
|
+
const button = this.els.markall;
|
|
3639
|
+
if (!button || button.disabled) return;
|
|
3640
|
+
if (button.dataset.confirm === "true") {
|
|
3641
|
+
this.resetUnblockAllConfirm();
|
|
3642
|
+
void this.unblockAll();
|
|
3643
|
+
return;
|
|
3644
|
+
}
|
|
3645
|
+
button.dataset.confirm = "true";
|
|
3646
|
+
button.classList.add("danger");
|
|
3647
|
+
this.els.markallnote.textContent = "This changes every blocked seat. Click the red button again to confirm.";
|
|
3648
|
+
this.paintBlockedInventory();
|
|
3649
|
+
if (this.unblockAllConfirmTimer) clearTimeout(this.unblockAllConfirmTimer);
|
|
3650
|
+
this.unblockAllConfirmTimer = setTimeout(() => this.resetUnblockAllConfirm(), 6e3);
|
|
3651
|
+
}
|
|
3652
|
+
resetUnblockAllConfirm() {
|
|
3653
|
+
if (this.unblockAllConfirmTimer) clearTimeout(this.unblockAllConfirmTimer);
|
|
3654
|
+
this.unblockAllConfirmTimer = null;
|
|
3655
|
+
const button = this.els.markall;
|
|
3656
|
+
if (!button) return;
|
|
3657
|
+
delete button.dataset.confirm;
|
|
3658
|
+
button.classList.remove("danger");
|
|
3659
|
+
if (this.els.markallnote) this.els.markallnote.textContent = "For a full reset only. You will be asked to confirm.";
|
|
3660
|
+
this.paintBlockedInventory();
|
|
3433
3661
|
}
|
|
3434
3662
|
// ---- toast / done / fail --------------------------------------------------
|
|
3435
3663
|
done(action, labels, msg) {
|