@seatlayer/js 0.47.2 → 0.48.1
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 +16 -1
- package/dist/{channelsMode-33ZLORVF.js → channelsMode-NCH5GYHN.js} +3 -3
- package/dist/{chunk-X2R4AQSZ.js → chunk-H4OJF6LE.js} +176 -7
- package/dist/chunk-H4OJF6LE.js.map +1 -0
- package/dist/{chunk-DMEFXZIL.js → chunk-KNMZQZXR.js} +2 -2
- package/dist/{chunk-CF5MS7UR.js → chunk-Q5QT3YLA.js} +186 -59
- package/dist/chunk-Q5QT3YLA.js.map +1 -0
- package/dist/{hostedCheckout-SHQCWN5Y.js → hostedCheckout-F6C6VCCG.js} +1 -1
- package/dist/hostedCheckout-F6C6VCCG.js.map +1 -0
- package/dist/index.cjs +612 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -5
- package/dist/index.d.ts +37 -5
- package/dist/index.js +258 -62
- package/dist/index.js.map +1 -1
- package/dist/{manager-CAIPkUYl.d.cts → manager-DEjbKiqJ.d.cts} +200 -17
- package/dist/{manager-CAIPkUYl.d.ts → manager-DEjbKiqJ.d.ts} +200 -17
- package/dist/manager.cjs +358 -62
- package/dist/manager.cjs.map +1 -1
- package/dist/manager.d.cts +1 -1
- package/dist/manager.d.ts +1 -1
- package/dist/manager.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-CF5MS7UR.js.map +0 -1
- package/dist/chunk-X2R4AQSZ.js.map +0 -1
- package/dist/hostedCheckout-SHQCWN5Y.js.map +0 -1
- /package/dist/{channelsMode-33ZLORVF.js.map → channelsMode-NCH5GYHN.js.map} +0 -0
- /package/dist/{chunk-DMEFXZIL.js.map → chunk-KNMZQZXR.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ManageApi,
|
|
3
3
|
ManageApiError
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-H4OJF6LE.js";
|
|
5
5
|
|
|
6
6
|
// src/SeatManager.ts
|
|
7
7
|
import {
|
|
@@ -12,6 +12,108 @@ import {
|
|
|
12
12
|
gaUnitLabels,
|
|
13
13
|
UNGROUPED_ID
|
|
14
14
|
} from "@seatlayer/core";
|
|
15
|
+
|
|
16
|
+
// src/manageAssets.ts
|
|
17
|
+
var SAFE_ASSET = /^[a-zA-Z0-9._-]+$/;
|
|
18
|
+
function organizerEventAssetReference(value) {
|
|
19
|
+
let url;
|
|
20
|
+
try {
|
|
21
|
+
url = new URL(value, "https://seatlayer.invalid");
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (url.search || url.hash) return null;
|
|
26
|
+
const match = /^\/v1\/events\/([^/]+)\/assets\/([^/]+)$/.exec(url.pathname);
|
|
27
|
+
if (!match) return null;
|
|
28
|
+
try {
|
|
29
|
+
const eventKey = decodeURIComponent(match[1]);
|
|
30
|
+
const asset = decodeURIComponent(match[2]);
|
|
31
|
+
if (!eventKey || !SAFE_ASSET.test(asset)) return null;
|
|
32
|
+
return { eventKey, asset };
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function looksLikeOrganizerAsset(value) {
|
|
38
|
+
try {
|
|
39
|
+
return /^\/v1\/events\/[^/]+\/assets(?:\/|$)/.test(
|
|
40
|
+
new URL(value, "https://seatlayer.invalid").pathname
|
|
41
|
+
);
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
var OrganizerAssetObjectUrls = class {
|
|
47
|
+
constructor(eventKey, load) {
|
|
48
|
+
this.eventKey = eventKey;
|
|
49
|
+
this.load = load;
|
|
50
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
51
|
+
this.created = /* @__PURE__ */ new Set();
|
|
52
|
+
this.disposed = false;
|
|
53
|
+
}
|
|
54
|
+
resolve(reference) {
|
|
55
|
+
const parsed = organizerEventAssetReference(reference);
|
|
56
|
+
if (!parsed) {
|
|
57
|
+
return Promise.resolve(looksLikeOrganizerAsset(reference) ? null : reference);
|
|
58
|
+
}
|
|
59
|
+
if (parsed.eventKey !== this.eventKey || this.disposed) return Promise.resolve(null);
|
|
60
|
+
const cacheKey = `${parsed.eventKey}/${parsed.asset}`;
|
|
61
|
+
const existing = this.pending.get(cacheKey);
|
|
62
|
+
if (existing) return existing;
|
|
63
|
+
const task = this.load(parsed.eventKey, parsed.asset).then((blob) => {
|
|
64
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
65
|
+
if (this.disposed) {
|
|
66
|
+
URL.revokeObjectURL(objectUrl);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
this.created.add(objectUrl);
|
|
70
|
+
return objectUrl;
|
|
71
|
+
}).catch((error) => {
|
|
72
|
+
this.pending.delete(cacheKey);
|
|
73
|
+
throw error;
|
|
74
|
+
});
|
|
75
|
+
this.pending.set(cacheKey, task);
|
|
76
|
+
return task;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the image fields the synchronous map renderer loads immediately.
|
|
80
|
+
* View-from-seat media stays lazy: SeatManager does not open that buyer
|
|
81
|
+
* surface, and eagerly downloading every row panorama would be unbounded.
|
|
82
|
+
*/
|
|
83
|
+
async prepareRendererChart(doc) {
|
|
84
|
+
const prepareBackground = async (background) => {
|
|
85
|
+
if (!background?.url) return;
|
|
86
|
+
const resolved = await this.resolve(background.url);
|
|
87
|
+
if (!resolved) throw new Error("organizer_event_asset_scope_mismatch");
|
|
88
|
+
background.url = resolved;
|
|
89
|
+
};
|
|
90
|
+
const prepareObjects = async (objects) => {
|
|
91
|
+
for (const object of objects) {
|
|
92
|
+
if (object.type !== "decorImage") continue;
|
|
93
|
+
const image = object;
|
|
94
|
+
const resolved = await this.resolve(image.href);
|
|
95
|
+
if (!resolved) throw new Error("organizer_event_asset_scope_mismatch");
|
|
96
|
+
image.href = resolved;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const prepareOwner = async (owner) => {
|
|
100
|
+
await prepareBackground(owner.backgroundImage);
|
|
101
|
+
await prepareObjects(owner.objects);
|
|
102
|
+
};
|
|
103
|
+
await prepareOwner(doc);
|
|
104
|
+
for (const floor of doc.floors ?? []) await prepareOwner(floor);
|
|
105
|
+
return doc;
|
|
106
|
+
}
|
|
107
|
+
dispose() {
|
|
108
|
+
if (this.disposed) return;
|
|
109
|
+
this.disposed = true;
|
|
110
|
+
for (const url of this.created) URL.revokeObjectURL(url);
|
|
111
|
+
this.created.clear();
|
|
112
|
+
this.pending.clear();
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/SeatManager.ts
|
|
15
117
|
function availabilityModeOf(rule) {
|
|
16
118
|
return rule ? rule.mode : "open";
|
|
17
119
|
}
|
|
@@ -290,8 +392,8 @@ var MANAGER_CSS = (
|
|
|
290
392
|
.slm.compact .slm-modes{min-width:0}.slm.compact .slm-mode{padding-inline:11px}
|
|
291
393
|
.slm.compact .slm-live{justify-self:end}.slm.compact .slm-bar-actions{grid-column:1/-1;justify-self:stretch}
|
|
292
394
|
.slm.compact .slm-barbtn{flex:1;padding:6px 9px}.slm.compact .slm-kpis{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}
|
|
293
|
-
.slm.compact .slm-kpi[data-kpi="
|
|
294
|
-
.slm.compact .slm-kpi[data-kpi="
|
|
395
|
+
.slm.compact .slm-kpi[data-kpi="viewing-map"],.slm.compact .slm-kpi[data-kpi="active-holds"],
|
|
396
|
+
.slm.compact .slm-kpi[data-kpi="booked-pct"],.slm.compact .slm-kpi[data-kpi="booked-value"]{display:none}
|
|
295
397
|
/* Reduced motion, as a BLANKET over the cockpit subtree rather than a list of
|
|
296
398
|
selectors. The list this replaces named four animations and two transitions,
|
|
297
399
|
and had silently fallen behind the stylesheet: the zoom hint, the toast and
|
|
@@ -501,6 +603,10 @@ var SeatManager = class {
|
|
|
501
603
|
this.currency = options.currency ?? "USD";
|
|
502
604
|
this.tokenExpiresAt = options.tokenExpiresAt ?? null;
|
|
503
605
|
this.api = new ManageApi(options.apiBase ?? DEFAULT_API_BASE, options.token);
|
|
606
|
+
this.organizerAssetUrls = new OrganizerAssetObjectUrls(
|
|
607
|
+
this.key,
|
|
608
|
+
(key, asset) => this.withAuthRetry(() => this.api.asset(key, asset))
|
|
609
|
+
);
|
|
504
610
|
this.host = resolveContainer(options.container);
|
|
505
611
|
}
|
|
506
612
|
/** Build the DOM, load the chart, subscribe to realtime, mount the board. */
|
|
@@ -508,10 +614,10 @@ var SeatManager = class {
|
|
|
508
614
|
injectStyle();
|
|
509
615
|
this.buildChrome();
|
|
510
616
|
try {
|
|
511
|
-
const res = await this.api.chart(this.key);
|
|
512
|
-
this.doc = res.doc;
|
|
617
|
+
const res = await this.withAuthRetry(() => this.api.chart(this.key));
|
|
618
|
+
this.doc = await this.organizerAssetUrls.prepareRendererChart(res.doc);
|
|
513
619
|
this.currency = res.event.currency ?? this.opts.currency ?? this.currency;
|
|
514
|
-
this.buildUnitUniverse(
|
|
620
|
+
this.buildUnitUniverse(this.doc);
|
|
515
621
|
this.buildRenderer();
|
|
516
622
|
this.buildSectionOptions();
|
|
517
623
|
const [, controlRoom] = await Promise.all([
|
|
@@ -615,7 +721,7 @@ var SeatManager = class {
|
|
|
615
721
|
if (this.channels) return Promise.resolve();
|
|
616
722
|
const load = (async () => {
|
|
617
723
|
try {
|
|
618
|
-
const mod = await import("./channelsMode-
|
|
724
|
+
const mod = await import("./channelsMode-NCH5GYHN.js");
|
|
619
725
|
if (this.closed) return;
|
|
620
726
|
if (!this.channelCaps.view) return;
|
|
621
727
|
if (!this.channels) {
|
|
@@ -858,10 +964,10 @@ var SeatManager = class {
|
|
|
858
964
|
try {
|
|
859
965
|
await this.api.unbook(this.key, targets, bookingRef);
|
|
860
966
|
this.clearSelection();
|
|
861
|
-
this.done("cancelBooking", targets, `
|
|
967
|
+
this.done("cancelBooking", targets, `Released ${targets.length} booked unit${targets.length === 1 ? "" : "s"}.`);
|
|
862
968
|
} catch (err) {
|
|
863
969
|
this.setSeatsLocal(targets, "booked");
|
|
864
|
-
this.toastErr("Couldn't
|
|
970
|
+
this.toastErr("Couldn't release that booked inventory. Check the booking reference.");
|
|
865
971
|
this.opts.onError?.(err);
|
|
866
972
|
}
|
|
867
973
|
}
|
|
@@ -914,9 +1020,9 @@ var SeatManager = class {
|
|
|
914
1020
|
async setHoldTtl(ms) {
|
|
915
1021
|
try {
|
|
916
1022
|
await this.api.setHoldTtl(this.key, ms);
|
|
917
|
-
this.done("setHoldTtl", [], ms ? `
|
|
1023
|
+
this.done("setHoldTtl", [], ms ? `Hold window set to ${Math.round(ms / 6e4)} min.` : "Hold window reset.");
|
|
918
1024
|
} catch (err) {
|
|
919
|
-
this.toastErr("Couldn't update the
|
|
1025
|
+
this.toastErr("Couldn't update the hold window.");
|
|
920
1026
|
this.opts.onError?.(err);
|
|
921
1027
|
}
|
|
922
1028
|
}
|
|
@@ -961,6 +1067,7 @@ var SeatManager = class {
|
|
|
961
1067
|
}
|
|
962
1068
|
this.renderer?.destroy();
|
|
963
1069
|
this.renderer = null;
|
|
1070
|
+
this.organizerAssetUrls.dispose();
|
|
964
1071
|
if (this.root && this.root.parentNode === this.host) this.host.removeChild(this.root);
|
|
965
1072
|
}
|
|
966
1073
|
// ---- renderer lifecycle ---------------------------------------------------
|
|
@@ -1081,24 +1188,28 @@ var SeatManager = class {
|
|
|
1081
1188
|
* projects its deltas, so any change inside a private channel allocation is
|
|
1082
1189
|
* structurally suppressed and the map silently drifts.
|
|
1083
1190
|
*
|
|
1084
|
-
* If the mint fails
|
|
1085
|
-
*
|
|
1086
|
-
*
|
|
1087
|
-
* the authenticated HTTP read.
|
|
1191
|
+
* If the mint fails, remain reconnecting. An unticketed socket is a buyer
|
|
1192
|
+
* projection, so applying it to organizer state would be worse than staying
|
|
1193
|
+
* visibly offline while the host refreshes authority or upgrades the API.
|
|
1088
1194
|
*/
|
|
1089
1195
|
async connect() {
|
|
1090
1196
|
if (this.closed) return;
|
|
1091
1197
|
let protocols;
|
|
1092
1198
|
try {
|
|
1093
|
-
protocols = (await this.api.subscribeTicket(this.key)).protocols;
|
|
1094
|
-
|
|
1095
|
-
|
|
1199
|
+
protocols = (await this.withAuthRetry(() => this.api.subscribeTicket(this.key))).protocols;
|
|
1200
|
+
if (!protocols.length) throw new Error("manage_subscribe_ticket_missing");
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
this.setLive(false);
|
|
1203
|
+
this.opts.onError?.(err);
|
|
1204
|
+
this.scheduleReconnect();
|
|
1205
|
+
return;
|
|
1096
1206
|
}
|
|
1097
1207
|
if (this.closed) return;
|
|
1098
1208
|
let ws;
|
|
1099
1209
|
try {
|
|
1100
|
-
ws =
|
|
1101
|
-
} catch {
|
|
1210
|
+
ws = new WebSocket(this.api.socketUrl(this.key), protocols);
|
|
1211
|
+
} catch (err) {
|
|
1212
|
+
this.opts.onError?.(err);
|
|
1102
1213
|
this.scheduleReconnect();
|
|
1103
1214
|
return;
|
|
1104
1215
|
}
|
|
@@ -1189,8 +1300,9 @@ var SeatManager = class {
|
|
|
1189
1300
|
this.lastSyncedAt = Date.now();
|
|
1190
1301
|
this.afterPaint();
|
|
1191
1302
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1303
|
+
const liveBookedValue = typeof m.bookedValue?.gross === "number" ? m.bookedValue.gross : m.revenue?.gross;
|
|
1304
|
+
if (typeof liveBookedValue === "number" && Number.isFinite(liveBookedValue)) {
|
|
1305
|
+
this.applyLiveGross(liveBookedValue);
|
|
1194
1306
|
}
|
|
1195
1307
|
this.recomputeTallies();
|
|
1196
1308
|
}
|
|
@@ -1207,9 +1319,12 @@ var SeatManager = class {
|
|
|
1207
1319
|
this.authoritativeGrossRevenue = gross;
|
|
1208
1320
|
this.revenueStatus = "current";
|
|
1209
1321
|
if (this.controlRoomSnapshot) {
|
|
1322
|
+
const current = this.controlRoomSnapshot.bookedValue ?? this.controlRoomSnapshot.revenue;
|
|
1323
|
+
const bookedValue = { ...current, gross };
|
|
1210
1324
|
this.controlRoomSnapshot = {
|
|
1211
1325
|
...this.controlRoomSnapshot,
|
|
1212
|
-
|
|
1326
|
+
bookedValue,
|
|
1327
|
+
revenue: bookedValue
|
|
1213
1328
|
};
|
|
1214
1329
|
this.opts.onControlRoom?.(this.controlRoomSnapshot);
|
|
1215
1330
|
}
|
|
@@ -1387,7 +1502,10 @@ var SeatManager = class {
|
|
|
1387
1502
|
// ---- tallies + feed -------------------------------------------------------
|
|
1388
1503
|
applyReportRevenue(report) {
|
|
1389
1504
|
this.authoritativeGrossRevenue = report.report.byCategory.reduce(
|
|
1390
|
-
(sum, row) =>
|
|
1505
|
+
(sum, row) => {
|
|
1506
|
+
const value = Number.isFinite(row.bookedValue) ? row.bookedValue : row.bookedRevenue;
|
|
1507
|
+
return sum + (Number.isFinite(value) ? value : 0);
|
|
1508
|
+
},
|
|
1391
1509
|
0
|
|
1392
1510
|
);
|
|
1393
1511
|
this.revenueStatus = "current";
|
|
@@ -1406,7 +1524,13 @@ var SeatManager = class {
|
|
|
1406
1524
|
const requestedAt = Date.now();
|
|
1407
1525
|
try {
|
|
1408
1526
|
const fetched = await this.api.controlRoom(this.key, this.trendWindowMinutes);
|
|
1409
|
-
|
|
1527
|
+
const incoming = fetched.bookedValue ?? fetched.revenue ?? { gross: 0, bySection: [] };
|
|
1528
|
+
const normalizedSections = (incoming.bySection ?? []).map((row) => {
|
|
1529
|
+
const value = Number.isFinite(row.bookedValue) ? row.bookedValue : row.bookedRevenue;
|
|
1530
|
+
return { ...row, bookedValue: value ?? 0, bookedRevenue: value ?? 0 };
|
|
1531
|
+
});
|
|
1532
|
+
const canonical = { ...incoming, bySection: normalizedSections };
|
|
1533
|
+
let snapshot = { ...fetched, bookedValue: canonical, revenue: canonical };
|
|
1410
1534
|
if (request === this.revenueRequest) {
|
|
1411
1535
|
if (this.livePresence && this.livePresence.at >= requestedAt) {
|
|
1412
1536
|
snapshot = { ...snapshot, presence: this.livePresence.value };
|
|
@@ -1414,14 +1538,15 @@ var SeatManager = class {
|
|
|
1414
1538
|
this.livePresence = null;
|
|
1415
1539
|
}
|
|
1416
1540
|
if (this.liveGross && this.liveGross.at >= requestedAt) {
|
|
1417
|
-
|
|
1541
|
+
const bookedValue = { ...snapshot.bookedValue, gross: this.liveGross.value };
|
|
1542
|
+
snapshot = { ...snapshot, bookedValue, revenue: bookedValue };
|
|
1418
1543
|
} else {
|
|
1419
1544
|
this.liveGross = null;
|
|
1420
1545
|
}
|
|
1421
1546
|
this.controlRoomSnapshot = snapshot;
|
|
1422
1547
|
this.rebaseServerTotals(snapshot);
|
|
1423
1548
|
this.lastSyncedAt = Date.now();
|
|
1424
|
-
this.authoritativeGrossRevenue = snapshot.
|
|
1549
|
+
this.authoritativeGrossRevenue = snapshot.bookedValue.gross;
|
|
1425
1550
|
this.currency = snapshot.currency;
|
|
1426
1551
|
this.revenueStatus = "current";
|
|
1427
1552
|
this.recomputeTallies();
|
|
@@ -1479,7 +1604,9 @@ var SeatManager = class {
|
|
|
1479
1604
|
total: Number.isFinite(seatTotal) ? seatTotal : this.unitTotal(),
|
|
1480
1605
|
capacityPct: 0,
|
|
1481
1606
|
sellThroughPct: 0,
|
|
1607
|
+
bookedValue: this.authoritativeGrossRevenue,
|
|
1482
1608
|
grossRevenue: this.authoritativeGrossRevenue,
|
|
1609
|
+
bookedValueStatus: this.revenueStatus,
|
|
1483
1610
|
revenueStatus: this.revenueStatus,
|
|
1484
1611
|
currency: this.currency
|
|
1485
1612
|
};
|
|
@@ -1634,8 +1761,8 @@ var SeatManager = class {
|
|
|
1634
1761
|
<button class="slm-barbtn follow" data-ref="follow" aria-pressed="false"
|
|
1635
1762
|
title="Stay on the current map view unless enabled">Follow live</button>
|
|
1636
1763
|
<button class="slm-barbtn" data-ref="heat" aria-pressed="false"
|
|
1637
|
-
aria-label="
|
|
1638
|
-
title="Highlight sections
|
|
1764
|
+
aria-label="Booking momentum overlay off"
|
|
1765
|
+
title="Highlight sections booking fastest in the selected time window">Booking momentum</button>
|
|
1639
1766
|
<button class="slm-barbtn" data-ref="fullscreen" title="Full screen (F)" aria-keyshortcuts="F">Full screen</button>
|
|
1640
1767
|
</div>
|
|
1641
1768
|
<div class="slm-kpis" data-ref="kpis"></div>
|
|
@@ -1738,16 +1865,16 @@ var SeatManager = class {
|
|
|
1738
1865
|
if (!button) return;
|
|
1739
1866
|
button.classList.toggle("on", this.followLive);
|
|
1740
1867
|
button.setAttribute("aria-pressed", String(this.followLive));
|
|
1741
|
-
button.setAttribute("title", this.followLive ? "Following new
|
|
1868
|
+
button.setAttribute("title", this.followLive ? "Following new holds and bookings. Turn off to keep the current view." : "Stay on the current map view. Enable to follow new holds and bookings.");
|
|
1742
1869
|
}
|
|
1743
1870
|
paintHeatButton() {
|
|
1744
1871
|
const button = this.els.heat;
|
|
1745
1872
|
if (!button) return;
|
|
1746
1873
|
button.classList.toggle("on", this.heatEnabled);
|
|
1747
1874
|
button.setAttribute("aria-pressed", String(this.heatEnabled));
|
|
1748
|
-
button.setAttribute("aria-label", `
|
|
1749
|
-
button.setAttribute("title", `${this.heatEnabled ? "Hide" : "Highlight"} sections
|
|
1750
|
-
button.textContent = "
|
|
1875
|
+
button.setAttribute("aria-label", `Booking momentum overlay ${this.heatEnabled ? "on" : "off"}`);
|
|
1876
|
+
button.setAttribute("title", `${this.heatEnabled ? "Hide" : "Highlight"} sections booking fastest in the selected time window`);
|
|
1877
|
+
button.textContent = "Booking momentum";
|
|
1751
1878
|
this.paintMomentumHelp();
|
|
1752
1879
|
}
|
|
1753
1880
|
paintMomentumHelp() {
|
|
@@ -1791,23 +1918,23 @@ var SeatManager = class {
|
|
|
1791
1918
|
formatKpiDelta(key, delta, currency) {
|
|
1792
1919
|
const sign = delta > 0 ? "+" : "\u2212";
|
|
1793
1920
|
const absolute = Math.abs(delta);
|
|
1794
|
-
if (key === "
|
|
1795
|
-
if (key === "
|
|
1921
|
+
if (key === "booked-value") return `${sign}${fmtMoney(absolute, currency)}`;
|
|
1922
|
+
if (key === "booked-pct") return `${sign}${absolute.toLocaleString()}pt`;
|
|
1796
1923
|
return `${sign}${absolute.toLocaleString()}`;
|
|
1797
1924
|
}
|
|
1798
1925
|
paintKpis(t) {
|
|
1799
1926
|
if (!this.els.kpis) return;
|
|
1800
|
-
const
|
|
1927
|
+
const bookedValue = t.bookedValueStatus === "current" ? fmtMoney(t.bookedValue, t.currency) : "\u2014";
|
|
1801
1928
|
const presence = this.presenceCounts();
|
|
1802
1929
|
const items = [
|
|
1803
|
-
{ key: "
|
|
1804
|
-
{ key: "held-seats", raw: t.held, n: t.held.toLocaleString(), l: "Held
|
|
1805
|
-
{ key: "free-seats", raw: t.free, n: t.free.toLocaleString(), l: "
|
|
1806
|
-
{ key: "blocked", raw: t.blocked, n: t.blocked.toLocaleString(), l: "Blocked", dot: "#8b94ac", title: "
|
|
1807
|
-
{ key: "
|
|
1808
|
-
{ key: "
|
|
1809
|
-
{ key: "
|
|
1810
|
-
{ key: "
|
|
1930
|
+
{ key: "booked-inventory", raw: t.booked, n: t.booked.toLocaleString(), l: "Booked inventory", dot: "#22a06b", title: "Inventory units booked" },
|
|
1931
|
+
{ key: "held-seats", raw: t.held, n: t.held.toLocaleString(), l: "Held inventory", dot: "#f4b740", title: "Inventory currently held" },
|
|
1932
|
+
{ key: "free-seats", raw: t.free, n: t.free.toLocaleString(), l: "Available", dot: "#6e7bff", title: "Inventory available to book" },
|
|
1933
|
+
{ key: "blocked", raw: t.blocked, n: t.blocked.toLocaleString(), l: "Blocked", dot: "#8b94ac", title: "Inventory withheld from booking" },
|
|
1934
|
+
{ key: "viewing-map", raw: presence?.shoppingSessions ?? null, n: presence ? presence.shoppingSessions.toLocaleString() : "\u2014", l: "Viewing map", title: "Active map sessions right now" },
|
|
1935
|
+
{ key: "active-holds", raw: presence?.activeHolds ?? null, n: presence ? presence.activeHolds.toLocaleString() : "\u2014", l: "Active holds", title: "Sessions currently holding inventory" },
|
|
1936
|
+
{ key: "booked-pct", raw: t.capacityPct, n: `${t.capacityPct}%`, l: "Booked", title: "Booked inventory as a share of the whole event" },
|
|
1937
|
+
{ key: "booked-value", raw: t.bookedValueStatus === "current" ? t.bookedValue : null, n: bookedValue, l: "Booked value", title: "Configured value attached to booked inventory" }
|
|
1811
1938
|
];
|
|
1812
1939
|
let hasChanges = false;
|
|
1813
1940
|
this.els.kpis.innerHTML = items.map((item) => {
|
|
@@ -1856,12 +1983,12 @@ var SeatManager = class {
|
|
|
1856
1983
|
renderViewRail() {
|
|
1857
1984
|
this.els.rail.innerHTML = `
|
|
1858
1985
|
<p class="slm-eyebrow">Monitor</p>
|
|
1859
|
-
<p class="slm-hint">Read-only. Inventory,
|
|
1986
|
+
<p class="slm-hint">Read-only. Inventory, map activity and booking movement update on the same live board.</p>
|
|
1860
1987
|
<div class="slm-health" data-ref="presence"></div>
|
|
1861
1988
|
<div class="slm-legend" data-ref="legend"></div>
|
|
1862
1989
|
<div class="slm-sectionhead">
|
|
1863
|
-
<div><p class="slm-eyebrow">Section
|
|
1864
|
-
<div class="slm-windows" aria-label="
|
|
1990
|
+
<div><p class="slm-eyebrow">Section inventory</p><p class="slm-note">Configured booked value \xB7 booking momentum</p></div>
|
|
1991
|
+
<div class="slm-windows" aria-label="Booking momentum window">
|
|
1865
1992
|
${[5, 15, 30, 60].map((window) => `<button class="slm-window" data-window="${window}">${window}m</button>`).join("")}
|
|
1866
1993
|
</div>
|
|
1867
1994
|
</div>
|
|
@@ -1900,8 +2027,8 @@ var SeatManager = class {
|
|
|
1900
2027
|
const sync = this.lastSyncedAt ? relTime(this.lastSyncedAt, Date.now()) : "waiting";
|
|
1901
2028
|
const presence = this.presenceCounts();
|
|
1902
2029
|
this.els.presence.innerHTML = `
|
|
1903
|
-
<div class="slm-healthitem" title="
|
|
1904
|
-
<div class="slm-healthitem" title="
|
|
2030
|
+
<div class="slm-healthitem" title="Active map sessions right now"><b>${presence ? presence.shoppingSessions.toLocaleString() : "\u2014"}</b><span>Viewing map</span></div>
|
|
2031
|
+
<div class="slm-healthitem" title="Sessions currently holding inventory"><b>${presence ? presence.activeHolds.toLocaleString() : "\u2014"}</b><span>Active holds</span></div>
|
|
1905
2032
|
<div class="slm-healthitem"><b>${connected ? "Healthy" : "Reconnecting"}</b><span>Live connection</span></div>
|
|
1906
2033
|
<div class="slm-healthitem"><b>${sync}</b><span>Last sync</span></div>`;
|
|
1907
2034
|
}
|
|
@@ -1911,10 +2038,10 @@ var SeatManager = class {
|
|
|
1911
2038
|
return;
|
|
1912
2039
|
}
|
|
1913
2040
|
const velocity = new Map(snapshot.velocity.bySection.map((row) => [row.sectionId, row]));
|
|
1914
|
-
const rows = [...snapshot.
|
|
2041
|
+
const rows = [...snapshot.bookedValue.bySection].sort((a, b) => {
|
|
1915
2042
|
const av = velocity.get(a.sectionId)?.netBooked ?? 0;
|
|
1916
2043
|
const bv = velocity.get(b.sectionId)?.netBooked ?? 0;
|
|
1917
|
-
return bv - av || b.
|
|
2044
|
+
return bv - av || b.bookedValue - a.bookedValue;
|
|
1918
2045
|
});
|
|
1919
2046
|
this.els.sections.innerHTML = rows.length ? rows.map((row) => {
|
|
1920
2047
|
const speed = velocity.get(row.sectionId);
|
|
@@ -1922,8 +2049,8 @@ var SeatManager = class {
|
|
|
1922
2049
|
const netLabel = `${net > 0 ? "+" : ""}${net}`;
|
|
1923
2050
|
const trend = speed?.trend === "rising" || speed?.trend === "cooling" ? speed.trend : "steady";
|
|
1924
2051
|
return `<button type="button" class="slm-sectionrow" data-section-focus="${esc(row.sectionId)}" title="Focus ${esc(row.sectionLabel)} on the map">
|
|
1925
|
-
<span class="slm-sectiontop"><span>${esc(row.sectionLabel)}</span><span>${fmtMoney(row.
|
|
1926
|
-
<span class="slm-sectionmeta"><span>${row.booked.toLocaleString()}/${row.total.toLocaleString()}
|
|
2052
|
+
<span class="slm-sectiontop"><span>${esc(row.sectionLabel)}</span><span>${fmtMoney(row.bookedValue, snapshot.currency)}</span></span>
|
|
2053
|
+
<span class="slm-sectionmeta"><span>${row.booked.toLocaleString()}/${row.total.toLocaleString()} booked \xB7 ${netLabel} in ${snapshot.velocity.windowMinutes}m</span><span class="slm-trend ${trend}">${trend}</span><span class="slm-sectionlocate">Locate</span></span>
|
|
1927
2054
|
</button>`;
|
|
1928
2055
|
}).join("") : '<div class="slm-empty">No section metrics are available for this chart.</div>';
|
|
1929
2056
|
this.paintTrendWindow();
|
|
@@ -1935,7 +2062,7 @@ var SeatManager = class {
|
|
|
1935
2062
|
this.renderer?.setSectionHeat(null);
|
|
1936
2063
|
return;
|
|
1937
2064
|
}
|
|
1938
|
-
const capacity = new Map(snapshot.
|
|
2065
|
+
const capacity = new Map(snapshot.bookedValue.bySection.map((row) => [row.sectionId, Math.max(1, row.total)]));
|
|
1939
2066
|
const rates = snapshot.velocity.bySection.map((row) => ({
|
|
1940
2067
|
sectionId: row.sectionId,
|
|
1941
2068
|
rate: Math.max(0, row.netBooked) / (capacity.get(row.sectionId) ?? 1) / snapshot.velocity.windowMinutes
|
|
@@ -1950,7 +2077,7 @@ var SeatManager = class {
|
|
|
1950
2077
|
if (!seat) {
|
|
1951
2078
|
this.els.rail.innerHTML = `
|
|
1952
2079
|
<p class="slm-eyebrow">Inspect seats</p>
|
|
1953
|
-
<p class="slm-hint">Select a seat to see its availability and
|
|
2080
|
+
<p class="slm-hint">Select a seat to see its availability and booking context. Nothing changes in this view.</p>
|
|
1954
2081
|
<div class="slm-empty">Select a seat on the map.</div>`;
|
|
1955
2082
|
return;
|
|
1956
2083
|
}
|
|
@@ -1959,7 +2086,7 @@ var SeatManager = class {
|
|
|
1959
2086
|
const sectionId = this.sectionByObject.get(seat.rowId) ?? UNGROUPED_ID;
|
|
1960
2087
|
const sectionLabel = this.sectionLabelById.get(sectionId) ?? "Other seats";
|
|
1961
2088
|
const category = this.doc?.categories.find((item) => item.key === seat.categoryKey);
|
|
1962
|
-
const sectionMetric = this.controlRoomSnapshot?.
|
|
2089
|
+
const sectionMetric = this.controlRoomSnapshot?.bookedValue.bySection.find((row) => row.sectionId === sectionId);
|
|
1963
2090
|
const object = this.doc?.objects.find((item) => item.id === seat.rowId);
|
|
1964
2091
|
const location = object?.type === "row" ? { label: "Row", value: object.label } : object?.type === "table" ? { label: "Table", value: object.label } : seat.kind === "booth" ? { label: "Type", value: "Booth" } : null;
|
|
1965
2092
|
const itemKind = seat.kind === "booth" ? "Booth" : "Seat";
|
|
@@ -1973,8 +2100,8 @@ var SeatManager = class {
|
|
|
1973
2100
|
<div><span>Section</span><b>${esc(sectionLabel)}</b></div>
|
|
1974
2101
|
${location ? `<div><span>${location.label}</span><b>${esc(location.value)}</b></div>` : ""}
|
|
1975
2102
|
<div><span>Category</span><b>${esc(category?.label ?? seat.categoryKey)}</b></div>
|
|
1976
|
-
<div><span>
|
|
1977
|
-
<div><span>Section
|
|
2103
|
+
<div><span>Booked in section</span><b>${sectionMetric ? `${sectionMetric.booked} of ${sectionMetric.total}` : "\u2014"}</b></div>
|
|
2104
|
+
<div><span>Section booked value</span><b>${sectionMetric && this.controlRoomSnapshot ? fmtMoney(sectionMetric.bookedValue, this.controlRoomSnapshot.currency) : "\u2014"}</b></div>
|
|
1978
2105
|
</div>
|
|
1979
2106
|
</div>`;
|
|
1980
2107
|
}
|
|
@@ -2540,4 +2667,4 @@ var SeatManager = class {
|
|
|
2540
2667
|
export {
|
|
2541
2668
|
SeatManager
|
|
2542
2669
|
};
|
|
2543
|
-
//# sourceMappingURL=chunk-
|
|
2670
|
+
//# sourceMappingURL=chunk-Q5QT3YLA.js.map
|