@seatlayer/js 0.57.0 → 0.58.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/dist/{channelsMode-CFxx1jp3.d.cts → channelsMode-2z9HBOAX.d.cts} +35 -0
- package/dist/{channelsMode-CFxx1jp3.d.ts → channelsMode-2z9HBOAX.d.ts} +35 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/manager.cjs +138 -108
- package/dist/manager.d.cts +2 -2
- package/dist/manager.d.ts +2 -2
- package/dist/manager.js +141 -111
- package/package.json +2 -2
package/dist/manager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{A as
|
|
1
|
+
import{A as be,C as fe,a as v,b as T,c as U,d as K,e as W,f as G,g as Z,h as Y,i as Q,j as X,k as J,l as ee,m as te,n as se,o as ie,p as oe,q as ae,r as le,s as ne,t as re,u as ce,v as de,w as he,x as pe,y as me,z as ue}from"./chunk-VCVUAOOK.js";import"./chunk-OSZ7DOEH.js";import{SeatmapRenderer as xe,expandChart as Le,computeSections as Te,floorsOf as Ce,gaAreasOf as Me,gaUnitLabels as Re,UNGROUPED_ID as g}from"@seatlayer/core";var ge=/^[a-zA-Z0-9._-]+$/;function ve(d){let e;try{e=new URL(d,"https://seatlayer.invalid")}catch{return null}if(e.search||e.hash)return null;let t=/^\/v1\/events\/([^/]+)\/assets\/([^/]+)$/.exec(e.pathname);if(!t)return null;try{let s=decodeURIComponent(t[1]),i=decodeURIComponent(t[2]);return!s||!ge.test(i)?null:{eventKey:s,asset:i}}catch{return null}}function ye(d){try{return/^\/v1\/events\/[^/]+\/assets(?:\/|$)/.test(new URL(d,"https://seatlayer.invalid").pathname)}catch{return!1}}var S=class{constructor(e,t){this.eventKey=e;this.load=t;this.pending=new Map;this.created=new Set;this.disposed=!1}resolve(e){let t=ve(e);if(!t)return Promise.resolve(ye(e)?null:e);if(t.eventKey!==this.eventKey||this.disposed)return Promise.resolve(null);let s=`${t.eventKey}/${t.asset}`,i=this.pending.get(s);if(i)return i;let o=this.load(t.eventKey,t.asset).then(a=>{let l=URL.createObjectURL(a);return this.disposed?(URL.revokeObjectURL(l),null):(this.created.add(l),l)}).catch(a=>{throw this.pending.delete(s),a});return this.pending.set(s,o),o}async prepareRendererChart(e){let t=async o=>{if(!o?.url)return;let a=await this.resolve(o.url);if(!a)throw new Error("organizer_event_asset_scope_mismatch");o.url=a},s=async o=>{for(let a of o){if(a.type!=="decorImage")continue;let l=a,r=await this.resolve(l.href);if(!r)throw new Error("organizer_event_asset_scope_mismatch");l.href=r}},i=async o=>{await t(o.backgroundImage),await s(o.objects)};await i(e);for(let o of e.floors??[])await i(o);return e}dispose(){if(!this.disposed){this.disposed=!0;for(let e of this.created)URL.revokeObjectURL(e);this.created.clear(),this.pending.clear()}}};var $="seatlayer-manager-style",H="seatlayer-manager-channels-style",Se=`
|
|
2
2
|
/* The floor was 480px, which trapped the cockpit in any host shorter than that:
|
|
3
3
|
every ancestor here is overflow:hidden, so the bottom of the rail (Create
|
|
4
4
|
channel, Show archived) was rendered but clipped away by the host and could
|
|
@@ -32,9 +32,14 @@ import{A as he,C as pe,a as g,b as L,c as V,d as N,e as q,f as _,g as U,h as K,i
|
|
|
32
32
|
broken layout had one designed cause and one accidental one. */
|
|
33
33
|
.slm-bar{display:grid;grid-template-columns:auto auto auto minmax(0,1fr);align-items:center;column-gap:14px;row-gap:10px;
|
|
34
34
|
padding:10px 16px;border-bottom:1px solid var(--slm-line);flex:none}
|
|
35
|
-
.slm-modes{display:inline-flex;background:var(--slm-surface);border:1px solid var(--slm-line);border-radius:999px;padding:3px}
|
|
35
|
+
.slm-modes{display:inline-flex;flex-wrap:wrap;row-gap:4px;background:var(--slm-surface);border:1px solid var(--slm-line);border-radius:999px;padding:3px}
|
|
36
|
+
.slm-modegroup{display:inline-flex;align-items:center;gap:1px;padding:0 4px 0 6px}
|
|
37
|
+
.slm-modegroup[hidden]{display:none}
|
|
38
|
+
.slm-modegroup+.slm-modegroup{border-left:1px solid var(--slm-line);margin-left:2px}
|
|
39
|
+
.slm-modegroup-label{padding:0 8px 0 4px;font-size:9.5px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--slm-muted);opacity:.8;white-space:nowrap}
|
|
36
40
|
.slm-tools{display:none}
|
|
37
|
-
.slm-mode{padding:6px
|
|
41
|
+
.slm-mode{padding:6px 13px;border-radius:999px;font-weight:700;font-size:13px;color:var(--slm-muted);white-space:nowrap}
|
|
42
|
+
.slm-mode[hidden]{display:none}
|
|
38
43
|
.slm-mode.on{background:var(--slm-accent);color:var(--slm-accent-ink)}
|
|
39
44
|
.slm-live{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.12em;font-weight:800;color:var(--slm-muted)}
|
|
40
45
|
.slm-live-dot{width:8px;height:8px;border-radius:50%;background:#8b94ac}
|
|
@@ -87,6 +92,15 @@ import{A as he,C as pe,a as g,b as L,c as V,d as N,e as q,f as _,g as U,h as K,i
|
|
|
87
92
|
.slm-railscroll{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px}
|
|
88
93
|
.slm-eyebrow{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--slm-muted);font-weight:800;margin-bottom:6px}
|
|
89
94
|
.slm-hint{font-size:12.5px;color:var(--slm-muted);line-height:1.5;margin-bottom:14px}
|
|
95
|
+
/* rail header: title + scope badge, then one plain sentence \u2014 every tool opens the same way */
|
|
96
|
+
.slm-railhead{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:6px}
|
|
97
|
+
.slm-railhead .slm-eyebrow{margin-bottom:0;font-size:11px;color:var(--slm-text)}
|
|
98
|
+
.slm-scope{flex:none;padding:2px 8px;border-radius:999px;font-size:9.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap}
|
|
99
|
+
.slm-scope.read{background:rgba(139,148,172,.16);color:#c2c9d8}
|
|
100
|
+
.slm-scope.event{background:rgba(244,183,64,.16);color:#f7ca6b}
|
|
101
|
+
.slm-scope.host{background:color-mix(in srgb,var(--slm-accent) 18%,transparent);color:var(--slm-accent)}
|
|
102
|
+
.slm-step{display:flex;align-items:center;gap:7px}
|
|
103
|
+
.slm-stepnum{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:var(--slm-accent);color:var(--slm-accent-ink);font-size:9.5px;letter-spacing:0}
|
|
90
104
|
|
|
91
105
|
/* legend rows */
|
|
92
106
|
.slm-legend{display:flex;flex-direction:column;gap:2px;margin-bottom:16px}
|
|
@@ -230,17 +244,31 @@ import{A as he,C as pe,a as g,b as L,c as V,d as N,e as q,f as _,g as U,h as K,i
|
|
|
230
244
|
.slm-inspect-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px 20px;margin-top:18px}
|
|
231
245
|
.slm-inspect-grid>div{min-width:0}.slm-inspect-grid span{display:block;color:var(--slm-muted);font-size:10px;
|
|
232
246
|
text-transform:uppercase;letter-spacing:.08em}.slm-inspect-grid b{display:block;margin-top:4px;font-size:13px;line-height:1.35;overflow-wrap:anywhere}
|
|
233
|
-
.slm-categoryassign{display:flex;flex-direction:column;gap:
|
|
234
|
-
.slm-categoryrow{display:grid;grid-template-columns:auto minmax(0,1fr) auto
|
|
235
|
-
padding:9px;border:1px solid var(--slm-line);border-radius:10px;background:var(--slm-surface)
|
|
236
|
-
|
|
237
|
-
.slm-
|
|
238
|
-
.slm-
|
|
239
|
-
.slm-
|
|
240
|
-
.slm-
|
|
247
|
+
.slm-categoryassign{display:flex;flex-direction:column;gap:6px}
|
|
248
|
+
.slm-categoryrow{display:grid!important;width:100%;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:10px;
|
|
249
|
+
padding:9px 10px!important;border:1px solid var(--slm-line)!important;border-radius:10px;background:var(--slm-surface)!important;
|
|
250
|
+
text-align:left!important;transition:border-color var(--slm-mo-quick) var(--slm-mo-out),background var(--slm-mo-quick) var(--slm-mo-out)}
|
|
251
|
+
.slm-categoryrow:not(:disabled):hover{border-color:var(--slm-accent)!important;background:color-mix(in srgb,var(--slm-accent) 10%,var(--slm-surface))!important}
|
|
252
|
+
.slm-categoryrow:disabled{cursor:not-allowed}
|
|
253
|
+
.slm-categoryrow:disabled:not(.current){opacity:.55}
|
|
254
|
+
.slm-categoryrow.current{border-style:dashed!important}
|
|
255
|
+
.slm-categoryrow>.dot{width:10px;height:10px;border-radius:50%;flex:none}
|
|
256
|
+
.slm-categorycopy{display:flex;min-width:0;flex-direction:column;gap:2px}
|
|
257
|
+
.slm-categorycopy b{font-size:12.5px;font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
258
|
+
.slm-categorycopy small{font-size:10px;color:var(--slm-muted);font-variant-numeric:tabular-nums}
|
|
259
|
+
.slm-categoryact{flex:none;font-size:11px;font-weight:800;color:var(--slm-accent);white-space:nowrap}
|
|
260
|
+
.slm-categoryrow:disabled .slm-categoryact{color:var(--slm-muted)}
|
|
261
|
+
.slm-categoryrow.current .slm-categoryact{color:var(--slm-muted)}
|
|
262
|
+
.slm-tablesummary{display:flex;align-items:baseline;gap:8px;padding:9px 12px;border:1px solid var(--slm-line);border-radius:9px;background:var(--slm-surface);font-size:12px;color:var(--slm-muted)}
|
|
263
|
+
.slm-tablesummary b{color:var(--slm-text);font-variant-numeric:tabular-nums}
|
|
264
|
+
.slm-tablelist{display:flex;flex-direction:column;gap:8px;margin-top:4px}
|
|
265
|
+
.slm-tablerow{display:grid;grid-template-columns:minmax(0,1fr) minmax(128px,auto);align-items:center;gap:10px;
|
|
241
266
|
padding:10px;border:1px solid var(--slm-line);border-radius:10px;background:var(--slm-surface)}
|
|
267
|
+
.slm-tablerow.locked{border-style:dashed}
|
|
242
268
|
.slm-tablecopy{display:flex;min-width:0;flex-direction:column;gap:2px}.slm-tablecopy b{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
243
|
-
.slm-tablecopy small{font-size:10px;color:var(--slm-muted)}.slm-tablerow .slm-select{min-width:
|
|
269
|
+
.slm-tablecopy small{font-size:10px;color:var(--slm-muted)}.slm-tablerow .slm-select{min-width:128px}
|
|
270
|
+
.slm-tablelock{color:#f7ca6b;font-weight:700}
|
|
271
|
+
.slm-select:disabled{opacity:.55;cursor:not-allowed}
|
|
244
272
|
.slm:fullscreen{border-radius:0;min-height:100vh;background:var(--slm-bg)}
|
|
245
273
|
.slm:fullscreen .slm-bar{padding:14px 22px}.slm:fullscreen .slm-kpi b{font-size:21px}.slm:fullscreen .slm-rail{width:360px}
|
|
246
274
|
|
|
@@ -271,44 +299,40 @@ import{A as he,C as pe,a as g,b as L,c as V,d as N,e as q,f as _,g as U,h as K,i
|
|
|
271
299
|
transition:none!important;
|
|
272
300
|
scroll-behavior:auto!important}
|
|
273
301
|
}
|
|
274
|
-
`;function
|
|
275
|
-
<select class="slm-select slm-availmode${t!=="open"?" on":""}" data-avail-id="${
|
|
302
|
+
`;function I(){if(typeof document>"u"||document.getElementById($))return;let d=document.createElement("style");d.id=$,d.textContent=Se,document.head.appendChild(d)}function B(d){if(typeof document>"u"||document.getElementById(H))return;let e=document.createElement("style");e.id=H,e.textContent=d,document.head.appendChild(e)}import{formatMoney as ke}from"@seatlayer/core";function O(d){return d?d.mode:"open"}function z(d,e,t){switch(d){case"open":return null;case"hidden":return{mode:"hidden",labels:e};case"closed":return{mode:"closed",labels:e};case"timed":return{mode:"timed",revealAt:t?.revealAt??Date.now()+36e5,labels:e};case"threshold":return{mode:"threshold",thresholdPct:t?.thresholdPct??80,labels:e}}}function F(d){return new Date(d-new Date().getTimezoneOffset()*6e4).toISOString().slice(0,16)}function j(d){if(typeof d=="string"){let e=document.querySelector(d);if(!e)throw new Error(`seatmanager: container "${d}" not found`);return e}if(!(d instanceof HTMLElement))throw new Error("seatmanager: container must be a CSS selector or an HTMLElement");return d}function C(d){if(!d.startsWith("mse_"))throw new Error("seatmanager: token must be a short-lived event-scoped mse_ grant minted by your backend; tenant secret keys are unsupported in browsers")}function k(d){return d==="blocked"?"not_for_sale":d}var P=[{key:"free",label:"Free",color:"#6e7bff"},{key:"held",label:"Held",color:"#f4b740"},{key:"booked",label:"Booked",color:"#22a06b"},{key:"blocked",label:"Blocked",color:"#8b94ac"}];function M(d){let e=d??{};return{"--slm-bg":e.background??"#0e1017","--slm-surface":"#181b24","--slm-text":"#eef1f7","--slm-muted":"#8b93a7","--slm-line":"rgba(255,255,255,.09)","--slm-accent":e.accent??"#6e7bff","--slm-accent-ink":e.accentInk??"#ffffff","--slm-font":"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif","--slm-radius":"14px"}}function R(d,e){let t=Math.max(0,Math.round((e-d)/1e3));if(t<5)return"just now";if(t<60)return`${t}s ago`;let s=Math.round(t/60);return s<60?`${s}m ago`:`${Math.round(s/60)}h ago`}function y(d,e){return ke(d,e,{maximumFractionDigits:0,fallback:(t,s)=>`${s} ${Math.round(t).toLocaleString()}`})}function p(d){return String(d??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}import{UNGROUPED_ID as we}from"@seatlayer/core";function E(d){return Object.entries(d).filter(([,e])=>e.mode==="closed").map(([e])=>e)}function w(d){let e=d.sectionsBase;if(!e)return{rows:[],hiddenSections:0,closedSections:0};let t=d.doc?.zones??[],s=new Map,i=[];for(let c of e.sections)if(c.zone&&t.some(n=>n.id===c.zone)){let n=s.get(c.zone)??[];n.push(c),s.set(c.zone,n)}else i.push(c);let o=[],a=0,l=0,r=(c,n,h,m=!1)=>{let u=d.availabilityRules[n.id]??null,b=d.effectiveClosed.has(n.id)||m,f=d.effectiveHidden.has(n.id)||h&&!b;c==="section"&&f&&(a+=1),c==="section"&&b&&(l+=1),o.push({kind:c,id:n.id,label:n.label,seatCount:n.seatCount,seatLabels:n.seatLabels,rule:u,hidden:f,closed:b,followsZone:c==="section"&&h})};for(let c of t){let n=s.get(c.id);if(!n||!n.length)continue;let h={id:c.id,label:c.label||"Zone",seatCount:n.reduce((b,f)=>b+f.seatCount,0),seatLabels:n.flatMap(b=>b.seatLabels)},m=!!d.availabilityRules[c.id],u=d.availabilityRules[c.id]?.mode==="closed";r("zone",h,!1);for(let b of n)r("section",b,m,u)}for(let c of i)r("section",c,!1);if(e.ungrouped){let c=e.ungrouped;r("section",{id:we,label:c.label,seatCount:c.seatCount,seatLabels:c.seatLabels},!1)}return{rows:o,hiddenSections:a,closedSections:l}}function D(d,e){let t=O(d.rule),s=`slm-availrow${d.kind==="zone"?" zone":""}${d.hidden?" hidden":""}${d.closed?" closed":""}`,i=e?" disabled":"",o=(n,h)=>`<option value="${n}"${t===n?" selected":""}>${h}</option>`,a=d.followsZone?'<span class="slm-availfollows">Follows zone</span>':`<span class="slm-availselwrap">
|
|
303
|
+
<select class="slm-select slm-availmode${t!=="open"?" on":""}" data-avail-id="${p(d.id)}"${i} aria-label="Availability for ${p(d.label)}">
|
|
276
304
|
${o("open","Open \u2014 on sale")}
|
|
277
305
|
${o("closed","Closed \u2014 visible, not on sale")}
|
|
278
306
|
${o("hidden","Hidden \u2014 off the buyer map")}
|
|
279
307
|
${o("timed","Reveal at a time")}
|
|
280
308
|
${o("threshold","Auto-reveal at % sold")}
|
|
281
309
|
</select>
|
|
282
|
-
</span>`,l="";if(!
|
|
283
|
-
<input type="datetime-local" class="slm-input" data-avail-reveal="${
|
|
284
|
-
</div>`}else if(!
|
|
310
|
+
</span>`,l="";if(!d.followsZone&&t==="timed"){let n=d.rule?.revealAt?p(F(d.rule.revealAt)):"";l=`<div class="slm-availdetail">
|
|
311
|
+
<input type="datetime-local" class="slm-input" data-avail-reveal="${p(d.id)}" value="${n}"${i} aria-label="Reveal time for ${p(d.label)}" />
|
|
312
|
+
</div>`}else if(!d.followsZone&&t==="threshold"){let n=d.rule?.thresholdPct??80;l=`<div class="slm-availdetail">
|
|
285
313
|
<span class="slm-availpctlabel">Reveal at</span>
|
|
286
|
-
<input type="number" min="1" max="100" class="slm-input slm-availpct" data-avail-pct="${
|
|
314
|
+
<input type="number" min="1" max="100" class="slm-input slm-availpct" data-avail-pct="${p(d.id)}" value="${p(n)}"${i} aria-label="Percent sold to reveal ${p(d.label)}" />
|
|
287
315
|
<span class="slm-availpctlabel">% sold</span>
|
|
288
|
-
</div>`}let r=
|
|
316
|
+
</div>`}let r=d.closed?'<span class="slm-availbadge closed">Closed</span>':d.hidden?'<span class="slm-availbadge hidden">Hidden</span>':"",c=d.kind==="zone"?`<span class="slm-availcaret" aria-hidden="true">${d.hidden?"\u25B8":"\u25BE"}</span>`:"";return`<div class="${s}">
|
|
289
317
|
<div class="slm-availhead">
|
|
290
|
-
<span class="slm-availlabel">${
|
|
318
|
+
<span class="slm-availlabel">${c}${p(d.label)}</span>
|
|
291
319
|
${r}
|
|
292
|
-
<span class="slm-availcount">${
|
|
320
|
+
<span class="slm-availcount">${d.seatCount.toLocaleString()}</span>
|
|
293
321
|
${a}
|
|
294
322
|
</div>
|
|
295
323
|
${l}
|
|
296
|
-
</div>`}var Te="https://api.seatlayer.io",x=80,Ce=16,D=4,E=class{constructor(e){this.els={};this.renderer=null;this.doc=null;this.inventoryModelVersion=1;this.selectableObjectLabels=new Set;this.lastSelectionLabels=new Set;this.selectionWasValid=null;this.initialSelectionApplied=!1;this.labelToId=new Map;this.labelToSeat=new Map;this.allIds=[];this.gaUnitLabelSet=new Set;this.status=new Map;this.counts={held:0,booked:0,blocked:0};this.modelVersion=0;this.currency="USD";this.authoritativeCurrency=null;this.authoritativeGrossRevenue=0;this.revenueStatus="loading";this.revenueRequest=0;this.controlRoomSnapshot=null;this.serverBaseline=null;this.livePresence=null;this.liveGross=null;this.paintHandle=null;this.trendWindowMinutes=15;this.heatEnabled=!1;this.lastKpiValues=new Map;this.activeKpiDeltas=new Map;this.ws=null;this.reconnectTimer=null;this.attempt=0;this.closed=!1;this.connectionStatus="reconnecting";this.lastMessageAt=null;this.ready=!1;this.feed=[];this.feedTimer=null;this.toastTimer=null;this.liveEventTimer=null;this.kpiCleanupTimer=null;this.followLiveTimer=null;this.followSeatTimer=null;this.releaseAt=null;this.layoutObserver=null;this.tokenExpiresAt=null;this.tokenRefreshTimer=null;this.tokenRefreshInFlight=!1;this.sectionByObject=new Map;this.sectionLabelById=new Map;this.sectionsBase=null;this.filteredSectionLabel=null;this.availabilityRules={};this.effectiveHidden=new Set;this.effectiveClosed=new Set;this.availabilitySaving=!1;this.lastSyncedAt=null;this.blockedQuery="";this.blockedSection="";this.blockedResultLimit=100;this.unblockAllConfirmTimer=null;this.channels=null;this.channelCaps={view:!1,manage:!1};this.categoryCanManage=!1;this.tableCanManage=!1;this.tableBookingSaving=!1;this.channelCapabilityResolution=0;this.channelsLoading=null;this.onFullscreenChange=()=>{this.paintFullscreenButton(),this.updateContainerLayout(),this.renderer?.forceDraw()};this.onKeyDown=e=>{if(e.metaKey||e.ctrlKey||e.altKey||e.target?.matches('input,select,textarea,[contenteditable="true"]'))return;let s=e.key.toLowerCase();if(s==="m")this.setMode("view");else if(s==="i")this.setMode("inspect");else if(s==="x")this.setMode("select");else if(s==="l")this.setMode("filterSections");else if(s==="b")this.setMode("block");else if(s==="s")this.setMode("sections");else if(s==="g")if(this.categoryCanManage)this.setMode("categories");else return;else if(s==="t")if(this.tableCanManage)this.setMode("tables");else return;else if(s==="c"){if(!this.channelCaps.view)return;this.setMode("channels")}else if(s==="f")this.toggleFullscreen();else if(s==="escape"){if(!this.channels?.handleBack())return}else return;e.preventDefault()};this.onRailClick=e=>{let t=e.target,s=t?.closest("[data-section-focus]");if(s?.dataset.sectionFocus){this.locateSection(s.dataset.sectionFocus);return}let i=t?.closest("[data-feed-id]");i?.dataset.feedId&&this.locateActivity(i.dataset.feedId)};this.sectionOptions=[];T(e.token),this.assertSelectionOptions(e.maxSelectedObjects,e.numberOfPlacesToSelect),this.opts=e,this.key=e.eventKey,this.mode=e.mode??"view",this.selectableObjectLabels=new Set(e.selectableObjects??[]),this.categoryCanManage=new Set(e.capabilities??[]).has("event:categories:manage"),this.tableCanManage=new Set(e.capabilities??[]).has("event:tables:manage"),this.keepLive=e.keepLiveWhileHidden??!0,this.followLive=e.followLive??!1,this.currency=e.currency??"USD",this.tokenExpiresAt=e.tokenExpiresAt??null,this.api=new L(e.apiBase??Te,e.token),this.organizerAssetUrls=new S(this.key,(t,s)=>this.withAuthRetry(()=>this.api.asset(t,s))),this.host=z(e.container)}assertSelectionOptions(e,t){let s=i=>i===void 0||Number.isInteger(i)&&i>0;if(!s(e))throw new TypeError("maxSelectedObjects must be a positive integer");if(!s(t))throw new TypeError("numberOfPlacesToSelect must be a positive integer");if(e!==void 0&&t!==void 0&&e<t)throw new TypeError("maxSelectedObjects cannot be lower than numberOfPlacesToSelect")}async render(){B(),this.buildChrome();try{let e=await this.withAuthRetry(()=>this.api.chart(this.key));this.inventoryModelVersion=e.event.inventoryModelVersion===2?2:1,this.doc=await this.organizerAssetUrls.prepareRendererChart(e.doc),this.authoritativeCurrency=e.event.currency??null,this.currency=this.authoritativeCurrency??this.opts.currency??this.currency,this.buildUnitUniverse(this.doc),this.buildRenderer(),this.buildSectionOptions();let[,t]=await Promise.all([this.resnapshot(),this.refreshControlRoom().catch(s=>this.opts.onError?.(s)),this.refreshAvailability()]);t?.activity?this.seedFeed(t.activity):this.api.log(this.key,{limit:24}).then(s=>this.seedFeed(s.entries)).catch(()=>{}),this.connect(),this.startFeedClock(),this.ready=!0,await this.resolveChannelCapabilities(),this.setMode(this.mode),this.applyInitialSelection(),this.scheduleTokenRefresh(),this.opts.onReady?.()}catch(e){this.fail(e)}return this}setMode(e){e==="channels"&&!this.channelCaps.view&&(e="view"),e==="categories"&&!this.categoryCanManage&&(e="view"),e==="tables"&&!this.tableCanManage&&(e="view"),e==="channels"&&!this.channels&&this.ensureChannels();let t=e!==this.mode,s=this.mode==="channels",i=this.mode==="filterSections";this.mode=e,!this.renderer&&this.doc?this.buildRenderer():this.updateRendererInteraction(),t&&(this.renderer?.clearSelection(),this.syncSelection()),s&&e!=="channels"&&this.channels?.leave(),this.paintModeTabs(),this.paintRail(),this.applySectionCanvasTreatment(),i&&e!=="filterSections"&&this.renderer?.zoomToFit(),e==="channels"&&this.channels?.enter(),e==="select"&&this.applyInitialSelection(),t&&this.opts.onModeChange?.(e)}async resolveChannelCapabilities(){let e=++this.channelCapabilityResolution,t=this.opts.capabilities,s;if(t){let i=new Set(t);s={view:i.has("event:channels:view"),manage:i.has("event:channels:view")&&i.has("event:channels:manage")}}else s={view:!1,manage:!1};if(!s.view&&!t)try{await this.api.channels(this.key),s={view:!0,manage:!1}}catch{s={view:!1,manage:!1}}if(e===this.channelCapabilityResolution){if(this.channelCaps=s,!this.channelCaps.view){this.channels?.destroy(),this.channels=null,this.mode==="channels"?this.setMode("view"):this.paintModeTabs();return}this.channels?.setCapabilities(this.channelCaps),this.paintModeTabs()}}ensureChannels(){if(this.channelsLoading)return this.channelsLoading;if(this.channels)return Promise.resolve();let e=(async()=>{try{let t=await import("./channelsMode-XA5FM2LX.js");if(this.closed||!this.channelCaps.view||(this.channels||(I(t.CHANNELS_CSS),this.channels=new t.ChannelsMode(this.buildChannelsHost(),this.channelCaps),this.channels.onInteractionChange=()=>this.updateRendererInteraction()),this.mode!=="channels"))return;this.updateRendererInteraction(),this.paintRail(),this.channels.enter()}catch(t){if(this.closed)return;this.channelsLoading=null,this.mode==="channels"&&this.paintRail(),this.opts.onError?.(t)}})();return this.channelsLoading=e,e}buildChannelsHost(){return{eventKey:this.key,api:this.api,rail:this.els.rail,mapLayer:this.root.querySelector(".slm-map"),root:this.root,seats:()=>[...this.labelToSeat.values()].map(e=>({id:e.id,label:e.label,x:e.x,y:e.y})),statusOf:e=>this.status.get(e)??(this.labelToSeat.has(e)?"free":void 0),selectionLabels:()=>this.selectionLabels(),selectByLabels:e=>{this.selectByLabels(e)},clearSelection:()=>this.clearSelection(),selectSection:e=>{this.selectSection(e)},sections:()=>this.sectionOptions,labelsInSection:e=>this.renderer?.getSelectableInSection(e).map(t=>t.label)??[],rows:()=>{let e=new Map((this.doc?.objects??[]).map(s=>[s.id,s])),t=new Map;for(let s of this.labelToSeat.values()){let i=e.get(s.rowId);if(!i||i.type!=="row"&&i.type!=="table")continue;let o=s.logicalRowId??s.rowId,a=this.sectionByObject.get(s.rowId)??f,l=t.get(o)??{id:o,label:i.type==="row"?i.segmentedRow?.displayLabel??i.displayLabel??i.label:i.displayLabel??i.label,sectionId:a,sectionLabel:this.sectionLabelById.get(a)??"Other seats",labels:[]};l.labels.push(s.label),t.set(o,l)}return[...t.values()].sort((s,i)=>s.sectionLabel.localeCompare(i.sectionLabel,void 0,{numeric:!0,sensitivity:"base"})||s.label.localeCompare(i.label,void 0,{numeric:!0,sensitivity:"base"}))},categories:()=>(this.doc?.categories??[]).map(e=>({key:e.key,label:e.label??e.key,color:e.color,price:e.price,tiers:e.tiers?.map(t=>({id:t.id,name:t.name,price:t.price}))})),labelsInCategory:e=>[...this.labelToSeat.entries()].filter(([,t])=>t.categoryKey===e).map(([t])=>t),sectionOfLabel:e=>{let t=this.labelToSeat.get(e);if(!t)return null;let s=this.sectionByObject.get(t.rowId)??f;return{id:s,label:this.sectionLabelById.get(s)??"Other seats"}},worldToScreen:e=>this.renderer?.worldToScreen(e)??null,seatPixelSize:()=>this.seatPixelSize(),isSeatDetail:()=>this.renderer?.getRung?.()==="seats",showSectionOverview:()=>{this.renderer?.clearSectionFocus(),this.renderer?.setRung?.("sections")},focusSection:e=>this.renderer?.focusSection(e),isCompact:()=>!!this.root?.classList.contains("compact"),setMapInert:e=>{this.mapHost.toggleAttribute("inert",e),this.mapHost.setAttribute("aria-hidden",String(e))},toast:(e,t)=>this.toast(e,t),onError:e=>this.opts.onError?.(e)}}seatPixelSize(){let e=this.renderer?.getVisibleWorldRect?.(),t=this.mapHost?.clientWidth??0;return!e?.width||!t?6:Math.max(3,t/e.width*18)}setHeatOverlay(e){this.heatEnabled=e,this.applyHeatOverlay(),this.paintHeatButton()}setFollowLive(e){let t=this.followLive!==e;this.followLive=e,e||(this.followLiveTimer&&clearTimeout(this.followLiveTimer),this.followSeatTimer&&clearTimeout(this.followSeatTimer),this.followLiveTimer=null,this.followSeatTimer=null),this.paintFollowLiveButton(),t&&this.opts.onFollowLiveChange?.(e)}setKeepLiveWhileHidden(e){this.keepLive=e??!0,this.opts.keepLiveWhileHidden=e}setCurrency(e){this.opts.currency=e,!this.authoritativeCurrency&&(this.currency=e??"USD",this.ready&&this.recomputeTallies())}setTheme(e){if(this.opts.theme=e,!!this.root)for(let[t,s]of Object.entries(C(e)))this.root.style.setProperty(t,s)}setCapabilities(e){this.opts.capabilities=e,this.categoryCanManage=new Set(e??[]).has("event:categories:manage"),this.tableCanManage=new Set(e??[]).has("event:tables:manage"),!this.categoryCanManage&&this.mode==="categories"||!this.tableCanManage&&this.mode==="tables"?this.setMode("view"):this.paintModeTabs(),this.ready&&this.resolveChannelCapabilities()}setTokenRefresh(e){this.opts.onTokenRefresh=e,this.scheduleTokenRefresh()}setTrendWindow(e){let t=Number.isFinite(e)?Math.floor(e):15;return this.trendWindowMinutes=Math.max(5,Math.min(60,t)),this.paintTrendWindow(),this.refreshControlRoom()}async enterFullscreen(){!this.root?.requestFullscreen||this.isFullscreen()||(await this.root.requestFullscreen(),this.root.focus({preventScroll:!0}))}async exitFullscreen(){typeof document>"u"||!this.isFullscreen()||await document.exitFullscreen()}isFullscreen(){return typeof document<"u"&&document.fullscreenElement===this.root}toggleFullscreen(){(this.isFullscreen()?this.exitFullscreen():this.enterFullscreen()).catch(t=>this.opts.onError?.(t))}setToken(e,t){T(e),this.api.setToken(e),this.opts.token=e,this.opts.tokenExpiresAt=t,this.tokenExpiresAt=t??null,this.scheduleTokenRefresh(),this.ready&&this.resolveChannelCapabilities()}scheduleTokenRefresh(){this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.tokenRefreshTimer=null;let e=this.opts.onTokenRefresh,t=this.tokenExpiresAt;if(this.closed||!e||!t||!Number.isFinite(t))return;let s=t-Date.now(),i=Math.min(12e4,Math.max(3e4,s*.2)),o=Math.max(0,s-i);this.tokenRefreshTimer=setTimeout(()=>{this.tokenRefreshTimer=null,this.rotateToken()},o)}async rotateToken(){if(!(this.closed||this.tokenRefreshInFlight||!this.opts.onTokenRefresh)){this.tokenRefreshInFlight=!0;try{let e=await this.opts.onTokenRefresh();if(!e?.token||!Number.isFinite(e.expiresAt))throw new Error("invalid_token_refresh_result");this.setToken(e.token,e.expiresAt)}catch(e){this.opts.onError?.(e),this.closed||(this.tokenRefreshTimer=setTimeout(()=>{this.tokenRefreshTimer=null,this.rotateToken()},3e4))}finally{this.tokenRefreshInFlight=!1}}}async block(e,t={}){let s=(e??this.selectionLabels()).filter(o=>this.status.get(o)==="free");if(!s.length)return;let i=t.releaseAt??this.releaseAt??void 0;this.setSeatsLocal(s,"blocked");try{await this.api.block(this.key,s,{...t,releaseAt:i}),this.clearSelection(),this.done("block",s,i?`Blocked ${s.length} \u2014 auto-release ${new Date(i).toLocaleString()}.`:`Blocked ${s.length} seat${s.length===1?"":"s"}.`)}catch(o){this.setSeatsLocal(s,"free"),this.toastErr(o instanceof g&&o.status===409?"Some seats were just taken. Try again.":"Couldn't block those seats."),this.opts.onError?.(o)}}async unblock(e){let t=(e??this.selectionLabels()).filter(s=>this.status.get(s)==="blocked");if(t.length){this.setSeatsLocal(t,"free");try{await this.api.unblock(this.key,t),this.clearSelection(),this.done("unblock",t,`Unblocked ${t.length} seat${t.length===1?"":"s"}.`)}catch(s){this.setSeatsLocal(t,"blocked"),this.toastErr("Couldn't unblock those seats."),this.opts.onError?.(s)}}}async unblockAll(){let e=[...this.status.entries()].filter(([,t])=>t==="blocked").map(([t])=>t);if(e.length){this.setSeatsLocal(e,"free");try{let t=await this.api.unblockAll(this.key);this.clearSelection(),this.done("unblockAll",e,`Unblocked ${t.freed} seat${t.freed===1?"":"s"}.`)}catch(t){await this.resnapshot(),this.toastErr("Couldn't mark everything for sale."),this.opts.onError?.(t)}}}async cancelBooking(e,t){let s=e.filter(i=>this.status.get(i)==="booked");if(!(!s.length||!t)){this.setSeatsLocal(s,"free");try{await this.api.unbook(this.key,s,t),this.clearSelection(),this.done("cancelBooking",s,`Released ${s.length} booked unit${s.length===1?"":"s"}.`)}catch(i){this.setSeatsLocal(s,"booked"),this.toastErr("Couldn't release that booked inventory. Check the booking reference."),this.opts.onError?.(i)}}}async setCategory(e,t){let s=this.doc?.categories.find(o=>o.key===e),i=[...new Set(t??this.selectionLabels())].filter(o=>this.labelToSeat.has(o));if(!(!s||!i.length))try{let o=await this.withAuthRetry(()=>this.api.setCategory(this.key,i,e)),a={};for(let l of o.labels){let r=this.labelToSeat.get(l),d=this.labelToId.get(l);r&&(r.categoryKey=e),d&&(a[d]=e)}this.renderer?.setSeatCategories?.(a),this.clearSelection(),this.done("setCategory",o.labels,`Assigned ${o.labels.length.toLocaleString()} object${o.labels.length===1?"":"s"} to ${s.label??s.key}.`)}catch(o){this.toastErr("Couldn't update those event categories."),this.opts.onError?.(o)}}async setTableBooking(e,t,s={}){let i=[...new Set(e)].filter(Boolean);if(!(!i.length||this.tableBookingSaving)){if(this.inventoryModelVersion!==2){this.toastErr("Table booking controls require inventory model 2 for this event.");return}this.tableBookingSaving=!0,this.mode==="tables"&&this.renderTablesRail();try{let o=await this.withAuthRetry(()=>this.api.setTableBooking(this.key,i,t,s));await this.reloadEventChart(),this.done("setTableBooking",o.tableIds,`Updated ${o.tableIds.length.toLocaleString()} table${o.tableIds.length===1?"":"s"} to ${t==="individual"?"per-chair":t==="whole"?"whole-table":"flexible-group"} booking.`)}catch(o){let a=o instanceof g&&o.code==="table_inventory_in_use";this.toastErr(a?"Release held, booked, or blocked table inventory before changing its booking mode.":"Couldn't update table booking. No table mode was changed."),this.opts.onError?.(o)}finally{this.tableBookingSaving=!1,this.mode==="tables"&&this.renderTablesRail()}}}async reloadEventChart(){let e=await this.withAuthRetry(()=>this.api.chart(this.key));this.inventoryModelVersion=e.event.inventoryModelVersion===2?2:1;let t=await this.organizerAssetUrls.prepareRendererChart(e.doc);this.renderer?.destroy(),this.renderer=null,this.doc=t,this.labelToId.clear(),this.labelToSeat.clear(),this.allIds=[],this.gaUnitLabelSet.clear(),this.status.clear(),this.counts={held:0,booked:0,blocked:0},this.serverBaseline=null,this.lastSelectionLabels.clear(),this.buildUnitUniverse(t),this.buildRenderer(),this.buildSectionOptions(),await Promise.all([this.resnapshot(),this.refreshAvailability()]),this.paintRail()}selectAll(){if(this.mode==="select")return this.selectByLabels([...this.labelToSeat.values()].filter(t=>this.canSelectObject(t)).map(t=>t.label));let e=this.renderer?.selectAllSelectable()??[];return this.syncSelection(),e}selectSection(e){if(!this.renderer)return[];let t=this.renderer.getSelectableInSection(e);return this.renderer.selectByLabels(t.map(s=>s.label)),this.syncSelection(),this.renderer.getSelection()}setFilteredSection(e){let t=e.trim(),s=this.filterableSections().filter(a=>a.label===t);if(!s.length)throw new Error(`Unknown event section: ${e}`);let i=t!==this.filteredSectionLabel;this.filteredSectionLabel=t,this.mode==="filterSections"&&(this.applyFilteredSectionCanvas(s),this.renderFilterSectionsRail());let o=s.map(({id:a,label:l,seatCount:r,zone:d})=>({id:a,label:l,seatCount:r,...d?{zone:d}:{}}));return i&&this.opts.onFilteredSectionChange?.(o),o}clearFilteredSection(){let e=this.filteredSectionLabel!==null;this.filteredSectionLabel=null,this.renderer?.setDimmedSections(null),this.renderer?.clearSectionFocus(),this.renderer?.zoomToFit(),this.mode==="filterSections"&&this.renderFilterSectionsRail(),e&&this.opts.onFilteredSectionChange?.([])}getFilteredSections(){return this.filteredSectionLabel?this.filterableSections().filter(e=>e.label===this.filteredSectionLabel).map(({id:e,label:t,seatCount:s,zone:i})=>({id:e,label:t,seatCount:s,...i?{zone:i}:{}})):[]}selectByLabels(e){let t=e;if(this.mode==="select"){let s=new Set(this.selectionLabels()),i=this.selectionCap(),o=[...new Set(e)].filter(l=>!s.has(l)).map(l=>this.labelToSeat.get(l)).filter(l=>!!l&&this.canSelectObject(l)),a=Math.max(0,i-s.size);o.length>a&&this.opts.onSelectionLimit?.(i),t=o.slice(0,a).map(l=>l.label)}return this.renderer?.selectByLabels(t),this.syncSelection(),this.getSelection()}selectObjects(e){return this.selectByLabels(e)}deselectObjects(e){let t=e.map(s=>this.labelToId.get(s)).filter(s=>!!s);return this.renderer?.deselect(t),this.syncSelection(),this.getSelection()}selectCategories(e){let t=new Set(e);return this.selectByLabels([...this.labelToSeat.values()].filter(s=>t.has(s.categoryKey)).map(s=>s.label))}deselectCategories(e){let t=new Set(e);return this.deselectObjects(this.getSelection().filter(s=>t.has(s.categoryKey)).map(s=>s.label))}setSelectableObjects(e){this.opts.selectableObjects=[...e],this.selectableObjectLabels=new Set(e),this.reconcileSelectPolicy()}setUnavailableObjectsSelectable(e){this.opts.unavailableObjectsSelectable=e,this.reconcileSelectPolicy()}setObjectSelectable(e){this.opts.isObjectSelectable=e,this.reconcileSelectPolicy()}setMaxSelectedObjects(e){this.assertSelectionOptions(e,this.opts.numberOfPlacesToSelect),this.opts.maxSelectedObjects=e,this.trimSelectionToCap(),this.updateRendererInteraction(),this.syncSelection()}setNumberOfPlacesToSelect(e){this.assertSelectionOptions(this.opts.maxSelectedObjects,e),this.opts.numberOfPlacesToSelect=e,this.selectionWasValid=null,this.trimSelectionToCap(),this.updateRendererInteraction(),this.syncSelection()}getSelectionValidity(){let e=this.opts.numberOfPlacesToSelect;if(e===void 0)return null;let t=this.getSelection(),s=t.length;return{isValid:s===e,count:s,required:e,remaining:Math.max(0,e-s),objects:t}}clearSelection(){this.renderer?.clearSelection(),this.syncSelection()}getSelection(){return this.renderer?.getSelection()??[]}getReport(){return this.api.report(this.key).then(e=>(this.applyReportRevenue(e),e))}getControlRoomSnapshot(e=this.trendWindowMinutes){return this.setTrendWindow(e)}getConnection(){return{status:this.connectionStatus,lastMessageAt:this.lastMessageAt}}getLog(e={}){return this.api.log(this.key,e)}async setHoldTtl(e){try{await this.api.setHoldTtl(this.key,e),this.done("setHoldTtl",[],e?`Hold window set to ${Math.round(e/6e4)} min.`:"Hold window reset.")}catch(t){this.toastErr("Couldn't update the hold window."),this.opts.onError?.(t)}}zoomToFit(){if(this.mode==="filterSections"&&this.filteredSectionLabel){this.clearFilteredSection();return}this.renderer?.clearSectionFocus(),this.renderer?.zoomToFit()}destroy(){if(this.closed=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.feedTimer&&clearInterval(this.feedTimer),this.toastTimer&&clearTimeout(this.toastTimer),this.liveEventTimer&&clearTimeout(this.liveEventTimer),this.kpiCleanupTimer&&clearTimeout(this.kpiCleanupTimer),this.followLiveTimer&&clearTimeout(this.followLiveTimer),this.followSeatTimer&&clearTimeout(this.followSeatTimer),this.unblockAllConfirmTimer&&clearTimeout(this.unblockAllConfirmTimer),this.paintHandle!==null&&typeof cancelAnimationFrame=="function"&&cancelAnimationFrame(this.paintHandle),this.paintHandle=null,this.channels?.destroy(),this.channels=null,this.channelsLoading=null,this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.layoutObserver?.disconnect(),this.layoutObserver=null,this.root?.removeEventListener("keydown",this.onKeyDown),this.els.rail?.removeEventListener("click",this.onRailClick),typeof document<"u"&&document.removeEventListener("fullscreenchange",this.onFullscreenChange),this.ws){try{this.ws.close()}catch{}this.ws=null}this.renderer?.destroy(),this.renderer=null,this.organizerAssetUrls.dispose(),this.root&&this.root.parentNode===this.host&&this.host.removeChild(this.root)}buildRenderer(){if(!this.doc)return;let e=this.isBulkSelectMode();this.renderer=new ye(this.mapHost,{manageMode:!0,marqueeSelect:e,maxSelection:this.selectionCap(),selectableStatuses:this.selectableStatuses(),currency:this.currency,onSelect:t=>this.handleSeatSelect(t),onDeselect:()=>this.syncSelection(),onSelectionLimit:t=>this.opts.onSelectionLimit?.(t),onMarquee:()=>this.syncSelection(),onSectionTap:t=>{if(this.mode==="filterSections"){let s=this.filterableSections().find(i=>i.id===t);s&&this.setFilteredSection(s.label);return}this.renderer?.focusSection(t),this.channels?.handleSectionFocus(t)},onViewChange:()=>{this.updateZoomHint(),this.channels?.handleViewChange()}}),this.renderer.setChart(this.doc),this.repaintAll(),this.applyHeatOverlay(),this.updateZoomHint()}isBulkSelectMode(){return this.mode==="block"||this.mode==="categories"||this.mode==="channels"&&this.channels?.usesMarqueeSelection()===!0}selectableStatuses(){return this.mode==="block"?["free","not_for_sale"]:this.mode==="select"||this.mode==="categories"||this.mode==="inspect"||this.isBulkSelectMode()||this.mode==="channels"&&this.channels?.canSelect()===!0?["free","held","booked","not_for_sale"]:[]}updateRendererInteraction(){let e=this.isBulkSelectMode();this.renderer?.setManageInteraction({manageMode:!0,marqueeSelect:e,maxSelection:this.selectionCap(),selectableStatuses:this.selectableStatuses()}),this.updateZoomHint()}handleSeatSelect(e){if(this.mode==="select"&&!this.canSelectObject(e)){this.renderer?.deselect([e.id]),this.syncSelection();return}if(this.mode==="inspect"){let t=this.getSelection().filter(s=>s.id!==e.id).map(s=>s.id);t.length&&this.renderer?.deselect(t)}this.syncSelection()}buildUnitUniverse(e){for(let t of Se(e))this.labelToId.set(t.label,t.id),this.labelToSeat.set(t.label,t),this.allIds.push(t.id);for(let t of xe(e))for(let s of Le(t))this.labelToId.has(s)||this.gaUnitLabelSet.add(s)}unitTotal(){return this.allIds.length+this.gaUnitLabelSet.size}knownLabels(){return[...this.labelToId.keys(),...this.gaUnitLabelSet]}repaintAll(){let e=this.renderer;if(!e)return;this.allIds.length&&e.setStatus(this.allIds,"free");let t={free:[],held:[],booked:[],not_for_sale:[]};for(let[s,i]of this.status.entries()){let o=this.labelToId.get(s);o&&t[k(i)].push(o)}["held","booked","not_for_sale"].forEach(s=>{t[s].length&&e.setStatus(t[s],s)})}async connect(){if(this.closed)return;let e;try{if(e=(await this.withAuthRetry(()=>this.api.subscribeTicket(this.key))).protocols,!e.length)throw new Error("manage_subscribe_ticket_missing")}catch(s){this.setLive(!1),this.opts.onError?.(s),this.scheduleReconnect();return}if(this.closed)return;let t;try{t=new WebSocket(this.api.socketUrl(this.key),e)}catch(s){this.opts.onError?.(s),this.scheduleReconnect();return}this.ws=t,t.onopen=()=>{this.attempt=0,this.setLive(!0),this.resnapshot().then(()=>this.refreshControlRoom()).catch(s=>this.opts.onError?.(s)),this.refreshAvailability()},t.onmessage=s=>this.onMessage(s),t.onclose=()=>{this.ws===t&&(this.ws=null),this.setLive(!1),this.scheduleReconnect()},t.onerror=()=>{try{t.close()}catch{}}}scheduleReconnect(){if(this.closed||this.reconnectTimer)return;let e=Math.min(1e3*2**Math.min(this.attempt++,5),15e3);this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},e)}onMessage(e){let t;try{t=JSON.parse(typeof e.data=="string"?e.data:"")}catch{return}if(!t||typeof t!="object")return;this.lastMessageAt=Date.now();let s=t;if((Array.isArray(s.hidden)||Array.isArray(s.closed))&&this.updateEffectiveAvailability(s.hidden,s.closed),s.type==="presence"){typeof s.shoppingSessions=="number"&&typeof s.activeHolds=="number"&&(this.livePresence={at:Date.now(),value:{shoppingSessions:s.shoppingSessions,activeHolds:s.activeHolds}},this.controlRoomSnapshot&&(this.controlRoomSnapshot={...this.controlRoomSnapshot,presence:this.livePresence.value},this.opts.onControlRoom?.(this.controlRoomSnapshot)),this.lastSyncedAt=Date.now(),this.recomputeTallies(),this.paintMonitorInsights());return}if(s.type!=="hidden"){if(s.seats&&typeof s.seats=="object")this.applySnapshot(s.seats,typeof s.default=="string"?s.default:void 0);else if(Array.isArray(s.changes)){let i=[],o=new Map;for(let l of s.changes){let r=["free","held","booked","blocked"].includes(l.status)?l.status:"free",d=this.status.get(l.label)??"free";if(d===r)continue;this.setStatusLabel(l.label,r,d);let n=this.labelToId.get(l.label);n&&(this.renderer?.setStatus([n],k(r)),i.push(n));let p=this.verbFor(d,r),m=`${p}:${r}`,b=o.get(m)??{labels:[],verb:p,status:r};b.labels.push(l.label),o.set(m,b)}for(let l of o.values()){let r=this.pushActivity(l.labels,l.verb,l.status);r&&this.paintSpatialActivity(r)}i.length&&(this.lastSyncedAt=Date.now(),this.afterPaint());let a=typeof s.bookedValue?.gross=="number"?s.bookedValue.gross:s.revenue?.gross;typeof a=="number"&&Number.isFinite(a)&&this.applyLiveGross(a),this.recomputeTallies()}}}applyLiveGross(e){if(this.liveGross={at:Date.now(),value:e},this.authoritativeGrossRevenue=e,this.revenueStatus="current",this.controlRoomSnapshot){let s={...this.controlRoomSnapshot.bookedValue??this.controlRoomSnapshot.revenue,gross:e};this.controlRoomSnapshot={...this.controlRoomSnapshot,bookedValue:s,revenue:s},this.opts.onControlRoom?.(this.controlRoomSnapshot)}}setStatusLabel(e,t,s=this.status.get(e)??"free"){this.status.set(e,t),s!==t&&(s!=="free"&&(this.counts[s]-=1),t!=="free"&&(this.counts[t]+=1))}async resnapshot(){try{let e=await this.api.objects(this.key);this.applySnapshot(e.seats),this.updateEffectiveAvailability(e.hidden,e.closed),this.lastMessageAt=Date.now()}catch{}}applySnapshot(e,t){let s=o=>["free","held","booked","blocked"].includes(o)?o:"free",i=new Map;if(t!==void 0){let o=s(t);for(let a of this.knownLabels())i.set(a,o)}for(let[o,a]of Object.entries(e))i.set(o,s(a));this.status=i,this.modelVersion+=1,this.recountAll(),this.lastSyncedAt=Date.now(),this.repaintAll(),this.afterPaint(),this.recomputeTallies()}recountAll(){let e={held:0,booked:0,blocked:0};for(let t of this.status.values())t!=="free"&&(e[t]+=1);this.counts=e}setSeatsLocal(e,t){let s=[];for(let i of e){this.setStatusLabel(i,t);let o=this.labelToId.get(i);o&&s.push(o)}s.length&&this.renderer?.setStatus(s,k(t)),this.afterPaint(),this.recomputeTallies()}afterPaint(){this.keepLive&&typeof document<"u"&&document.hidden&&this.renderer?.forceDraw()}activityColor(e){return e==="held"?"#f4b740":e==="booked"?"#22a06b":e==="blocked"?"#8b94ac":"#6e7bff"}sectionsForLabels(e){let t=new Set;for(let i of e){let o=this.labelToSeat.get(i);if(!o)continue;let a=this.sectionByObject.get(o.rowId);a&&a!==f&&t.add(a)}let s=[...t];return{ids:s,labels:s.map(i=>this.sectionLabelById.get(i)??i)}}pulseSeatLabels(e,t){let s=this.activityColor(t);for(let i of e.slice(0,Ce)){let o=this.labelToId.get(i);o&&this.renderer?.flashSeat(o,s)}}paintSpatialActivity(e){let t=e.sectionIds??this.sectionsForLabels(e.labels).ids,s=this.renderer?.getFocusedSection()??null,i=this.followLive&&t.length===1&&(e.status==="held"||e.status==="booked");if(i&&s===t[0]){this.pulseSeatLabels(e.labels,e.status);return}if(i){this.followLiveTimer&&clearTimeout(this.followLiveTimer),this.followSeatTimer&&clearTimeout(this.followSeatTimer),this.followLiveTimer=setTimeout(()=>{this.followLiveTimer=null,this.renderer?.focusSection(t[0]),this.followSeatTimer=setTimeout(()=>{this.followSeatTimer=null,this.pulseSeatLabels(e.labels,e.status)},520)},220);return}if(!s&&t.length){let o=this.activityColor(e.status);for(let a of t.slice(0,D))this.renderer?.flashSection(a,o);return}(!t.length||s&&t.includes(s))&&this.pulseSeatLabels(e.labels,e.status)}locateSection(e){this.renderer?.focusSection(e)}locateActivity(e){let t=this.feed.find(i=>i.id===e);if(!t)return;let s=t.sectionIds??this.sectionsForLabels(t.labels).ids;if(this.followSeatTimer&&clearTimeout(this.followSeatTimer),s.length===1){this.locateSection(s[0]),this.followSeatTimer=setTimeout(()=>{this.followSeatTimer=null,this.pulseSeatLabels(t.labels,t.status)},520);return}this.zoomToFit(),this.followSeatTimer=setTimeout(()=>{if(this.followSeatTimer=null,s.length){let i=this.activityColor(t.status);for(let o of s.slice(0,D))this.renderer?.flashSection(o,i)}else this.pulseSeatLabels(t.labels,t.status)},280)}showLiveEvent(e){let t=this.els.liveevent;if(!t)return;let s=e.sectionLabels??[],i=s.length===1?s[0]:s.length>1?`${s.length} sections`:e.label,o=e.count===1?"seat":"seats";t.innerHTML=`<span class="slm-liveeventdot" style="background:${this.activityColor(e.status)}"></span>
|
|
297
|
-
<span class="slm-liveeventcopy">${
|
|
298
|
-
<span class="slm-liveeventhint">Live</span>`,t.classList.add("on"),this.liveEventTimer&&clearTimeout(this.liveEventTimer),this.liveEventTimer=setTimeout(()=>{this.liveEventTimer=null,t.classList.remove("on"),t.innerHTML=""},2800)}applyReportRevenue(e){this.authoritativeGrossRevenue=e.report.byCategory.reduce((t,s)=>{let i=Number.isFinite(s.bookedValue)?s.bookedValue:s.bookedRevenue;return t+(Number.isFinite(i)?i:0)},0),this.revenueStatus="current",this.recomputeTallies()}async refreshControlRoom(){let e=++this.revenueRequest,t=Date.now();try{let s=await this.api.controlRoom(this.key,this.trendWindowMinutes),i=s.bookedValue??s.revenue??{gross:0,bySection:[]},o=(i.bySection??[]).map(r=>{let
|
|
324
|
+
</div>`}var Ee="https://api.seatlayer.io",x=80,Ae=16,V=4,N=[{key:"watch",label:"Watch",tools:[{mode:"view",label:"Monitor",shortcut:"M",title:"Live board \u2014 inventory, activity and booking momentum. Read-only."},{mode:"inspect",label:"Inspect",shortcut:"I",title:"Click one seat to read its live status and booking context. Read-only."}]},{key:"manage",label:"Manage",tools:[{mode:"block",label:"Block",shortcut:"B",title:"Take seats off sale for this event, or put them back."},{mode:"sections",label:"Sections",shortcut:"S",title:"Open, close or hide sections for this event, on a schedule or on demand."},{mode:"categories",label:"Categories",shortcut:"G",title:"Move seats to a different price category for this event only."},{mode:"tables",label:"Tables",shortcut:"T",title:"Sell each table per chair or as one whole-table booking."},{mode:"channels",label:"Channels",shortcut:"C",title:"Allocate inventory to sales channels and share their links."}]},{key:"host",label:"Host tools",tools:[{mode:"select",label:"Select",shortcut:"X",title:"Select seats for your own back-office operation. The selection is handed to your app; nothing changes here."},{mode:"filterSections",label:"Filter",shortcut:"L",title:"Filter the map to one section for your own reporting."}]}],$e=Object.fromEntries(N.flatMap(d=>d.tools.map(e=>[e.shortcut.toLowerCase(),e.mode]))),A=class{constructor(e){this.els={};this.renderer=null;this.doc=null;this.inventoryModelVersion=1;this.selectableObjectLabels=new Set;this.lastSelectionLabels=new Set;this.selectionWasValid=null;this.initialSelectionApplied=!1;this.labelToId=new Map;this.labelToSeat=new Map;this.allIds=[];this.gaUnitLabelSet=new Set;this.status=new Map;this.counts={held:0,booked:0,blocked:0};this.modelVersion=0;this.currency="USD";this.authoritativeCurrency=null;this.authoritativeGrossRevenue=0;this.revenueStatus="loading";this.revenueRequest=0;this.controlRoomSnapshot=null;this.serverBaseline=null;this.livePresence=null;this.liveGross=null;this.paintHandle=null;this.trendWindowMinutes=15;this.heatEnabled=!1;this.lastKpiValues=new Map;this.activeKpiDeltas=new Map;this.ws=null;this.reconnectTimer=null;this.attempt=0;this.closed=!1;this.connectionStatus="reconnecting";this.lastMessageAt=null;this.ready=!1;this.feed=[];this.feedTimer=null;this.toastTimer=null;this.liveEventTimer=null;this.kpiCleanupTimer=null;this.followLiveTimer=null;this.followSeatTimer=null;this.releaseAt=null;this.layoutObserver=null;this.tokenExpiresAt=null;this.tokenRefreshTimer=null;this.tokenRefreshInFlight=!1;this.sectionByObject=new Map;this.sectionLabelById=new Map;this.sectionsBase=null;this.filteredSectionLabel=null;this.availabilityRules={};this.effectiveHidden=new Set;this.effectiveClosed=new Set;this.availabilitySaving=!1;this.lastSyncedAt=null;this.blockedQuery="";this.blockedSection="";this.blockedResultLimit=100;this.unblockAllConfirmTimer=null;this.channels=null;this.channelCaps={view:!1,manage:!1};this.categoryCanManage=!1;this.tableCanManage=!1;this.tableBookingSaving=!1;this.channelCapabilityResolution=0;this.channelsLoading=null;this.onFullscreenChange=()=>{this.paintFullscreenButton(),this.updateContainerLayout(),this.renderer?.forceDraw()};this.onKeyDown=e=>{if(e.metaKey||e.ctrlKey||e.altKey||e.target?.matches('input,select,textarea,[contenteditable="true"]'))return;let s=e.key.toLowerCase(),i=$e[s];if(i){if(!this.toolAvailable(i))return;this.setMode(i)}else if(s==="f")this.toggleFullscreen();else if(s==="escape"){if(!this.channels?.handleBack())return}else return;e.preventDefault()};this.onRailClick=e=>{let t=e.target,s=t?.closest("[data-section-focus]");if(s?.dataset.sectionFocus){this.locateSection(s.dataset.sectionFocus);return}let i=t?.closest("[data-feed-id]");i?.dataset.feedId&&this.locateActivity(i.dataset.feedId)};this.sectionOptions=[];C(e.token),this.assertSelectionOptions(e.maxSelectedObjects,e.numberOfPlacesToSelect),this.opts=e,this.key=e.eventKey,this.mode=e.mode??"view",this.selectableObjectLabels=new Set(e.selectableObjects??[]),this.categoryCanManage=new Set(e.capabilities??[]).has("event:categories:manage"),this.tableCanManage=new Set(e.capabilities??[]).has("event:tables:manage"),this.keepLive=e.keepLiveWhileHidden??!0,this.followLive=e.followLive??!1,this.currency=e.currency??"USD",this.tokenExpiresAt=e.tokenExpiresAt??null,this.api=new T(e.apiBase??Ee,e.token),this.organizerAssetUrls=new S(this.key,(t,s)=>this.withAuthRetry(()=>this.api.asset(t,s))),this.host=j(e.container)}assertSelectionOptions(e,t){let s=i=>i===void 0||Number.isInteger(i)&&i>0;if(!s(e))throw new TypeError("maxSelectedObjects must be a positive integer");if(!s(t))throw new TypeError("numberOfPlacesToSelect must be a positive integer");if(e!==void 0&&t!==void 0&&e<t)throw new TypeError("maxSelectedObjects cannot be lower than numberOfPlacesToSelect")}async render(){I(),this.buildChrome();try{let e=await this.withAuthRetry(()=>this.api.chart(this.key));this.inventoryModelVersion=e.event.inventoryModelVersion===2?2:1,this.doc=await this.organizerAssetUrls.prepareRendererChart(e.doc),this.authoritativeCurrency=e.event.currency??null,this.currency=this.authoritativeCurrency??this.opts.currency??this.currency,this.buildUnitUniverse(this.doc),this.buildRenderer(),this.buildSectionOptions(),this.settleMode();let[,t]=await Promise.all([this.resnapshot(),this.refreshControlRoom().catch(s=>this.opts.onError?.(s)),this.refreshAvailability()]);t?.activity?this.seedFeed(t.activity):this.api.log(this.key,{limit:24}).then(s=>this.seedFeed(s.entries)).catch(()=>{}),this.connect(),this.startFeedClock(),this.ready=!0,await this.resolveChannelCapabilities(),this.setMode(this.mode),this.applyInitialSelection(),this.scheduleTokenRefresh(),this.opts.onReady?.()}catch(e){this.fail(e)}return this}setMode(e){this.toolAvailable(e)||(e="view"),e==="channels"&&!this.channels&&this.ensureChannels();let t=e!==this.mode,s=this.mode==="channels",i=this.mode==="filterSections";this.mode=e,!this.renderer&&this.doc?this.buildRenderer():this.updateRendererInteraction(),t&&(this.renderer?.clearSelection(),this.syncSelection()),s&&e!=="channels"&&this.channels?.leave(),this.paintModeTabs(),this.paintRail(),this.applySectionCanvasTreatment(),i&&e!=="filterSections"&&this.renderer?.zoomToFit(),e==="channels"&&this.channels?.enter(),e==="select"&&this.applyInitialSelection(),t&&this.opts.onModeChange?.(e)}async resolveChannelCapabilities(){let e=++this.channelCapabilityResolution,t=this.opts.capabilities,s;if(t){let i=new Set(t);s={view:i.has("event:channels:view"),manage:i.has("event:channels:view")&&i.has("event:channels:manage")}}else s={view:!1,manage:!1};if(!s.view&&!t)try{await this.api.channels(this.key),s={view:!0,manage:!1}}catch{s={view:!1,manage:!1}}if(e===this.channelCapabilityResolution){if(this.channelCaps=s,!this.channelCaps.view){this.channels?.destroy(),this.channels=null,this.mode==="channels"?this.setMode("view"):this.paintModeTabs();return}this.channels?.setCapabilities(this.channelCaps),this.paintModeTabs()}}ensureChannels(){if(this.channelsLoading)return this.channelsLoading;if(this.channels)return Promise.resolve();let e=(async()=>{try{let t=await import("./channelsMode-XA5FM2LX.js");if(this.closed||!this.channelCaps.view||(this.channels||(B(t.CHANNELS_CSS),this.channels=new t.ChannelsMode(this.buildChannelsHost(),this.channelCaps),this.channels.onInteractionChange=()=>this.updateRendererInteraction()),this.mode!=="channels"))return;this.updateRendererInteraction(),this.paintRail(),this.channels.enter()}catch(t){if(this.closed)return;this.channelsLoading=null,this.mode==="channels"&&this.paintRail(),this.opts.onError?.(t)}})();return this.channelsLoading=e,e}buildChannelsHost(){return{eventKey:this.key,api:this.api,rail:this.els.rail,mapLayer:this.root.querySelector(".slm-map"),root:this.root,seats:()=>[...this.labelToSeat.values()].map(e=>({id:e.id,label:e.label,x:e.x,y:e.y})),statusOf:e=>this.status.get(e)??(this.labelToSeat.has(e)?"free":void 0),selectionLabels:()=>this.selectionLabels(),selectByLabels:e=>{this.selectByLabels(e)},clearSelection:()=>this.clearSelection(),selectSection:e=>{this.selectSection(e)},sections:()=>this.sectionOptions,labelsInSection:e=>this.renderer?.getSelectableInSection(e).map(t=>t.label)??[],rows:()=>{let e=new Map((this.doc?.objects??[]).map(s=>[s.id,s])),t=new Map;for(let s of this.labelToSeat.values()){let i=e.get(s.rowId);if(!i||i.type!=="row"&&i.type!=="table")continue;let o=s.logicalRowId??s.rowId,a=this.sectionByObject.get(s.rowId)??g,l=t.get(o)??{id:o,label:i.type==="row"?i.segmentedRow?.displayLabel??i.displayLabel??i.label:i.displayLabel??i.label,sectionId:a,sectionLabel:this.sectionLabelById.get(a)??"Other seats",labels:[]};l.labels.push(s.label),t.set(o,l)}return[...t.values()].sort((s,i)=>s.sectionLabel.localeCompare(i.sectionLabel,void 0,{numeric:!0,sensitivity:"base"})||s.label.localeCompare(i.label,void 0,{numeric:!0,sensitivity:"base"}))},categories:()=>(this.doc?.categories??[]).map(e=>({key:e.key,label:e.label??e.key,color:e.color,price:e.price,tiers:e.tiers?.map(t=>({id:t.id,name:t.name,price:t.price}))})),labelsInCategory:e=>[...this.labelToSeat.entries()].filter(([,t])=>t.categoryKey===e).map(([t])=>t),sectionOfLabel:e=>{let t=this.labelToSeat.get(e);if(!t)return null;let s=this.sectionByObject.get(t.rowId)??g;return{id:s,label:this.sectionLabelById.get(s)??"Other seats"}},worldToScreen:e=>this.renderer?.worldToScreen(e)??null,seatPixelSize:()=>this.seatPixelSize(),isSeatDetail:()=>this.renderer?.getRung?.()==="seats",showSectionOverview:()=>{this.renderer?.clearSectionFocus(),this.renderer?.setRung?.("sections")},focusSection:e=>this.renderer?.focusSection(e),isCompact:()=>!!this.root?.classList.contains("compact"),setMapInert:e=>{this.mapHost.toggleAttribute("inert",e),this.mapHost.setAttribute("aria-hidden",String(e))},toast:(e,t)=>this.toast(e,t),onError:e=>this.opts.onError?.(e)}}seatPixelSize(){let e=this.renderer?.getVisibleWorldRect?.(),t=this.mapHost?.clientWidth??0;return!e?.width||!t?6:Math.max(3,t/e.width*18)}setHeatOverlay(e){this.heatEnabled=e,this.applyHeatOverlay(),this.paintHeatButton()}setFollowLive(e){let t=this.followLive!==e;this.followLive=e,e||(this.followLiveTimer&&clearTimeout(this.followLiveTimer),this.followSeatTimer&&clearTimeout(this.followSeatTimer),this.followLiveTimer=null,this.followSeatTimer=null),this.paintFollowLiveButton(),t&&this.opts.onFollowLiveChange?.(e)}setKeepLiveWhileHidden(e){this.keepLive=e??!0,this.opts.keepLiveWhileHidden=e}setCurrency(e){this.opts.currency=e,!this.authoritativeCurrency&&(this.currency=e??"USD",this.ready&&this.recomputeTallies())}setTheme(e){if(this.opts.theme=e,!!this.root)for(let[t,s]of Object.entries(M(e)))this.root.style.setProperty(t,s)}setCapabilities(e){this.opts.capabilities=e,this.categoryCanManage=new Set(e??[]).has("event:categories:manage"),this.tableCanManage=new Set(e??[]).has("event:tables:manage"),!this.categoryCanManage&&this.mode==="categories"||!this.tableCanManage&&this.mode==="tables"?this.setMode("view"):this.paintModeTabs(),this.ready&&this.resolveChannelCapabilities()}setTokenRefresh(e){this.opts.onTokenRefresh=e,this.scheduleTokenRefresh()}setTrendWindow(e){let t=Number.isFinite(e)?Math.floor(e):15;return this.trendWindowMinutes=Math.max(5,Math.min(60,t)),this.paintTrendWindow(),this.refreshControlRoom()}async enterFullscreen(){!this.root?.requestFullscreen||this.isFullscreen()||(await this.root.requestFullscreen(),this.root.focus({preventScroll:!0}))}async exitFullscreen(){typeof document>"u"||!this.isFullscreen()||await document.exitFullscreen()}isFullscreen(){return typeof document<"u"&&document.fullscreenElement===this.root}toggleFullscreen(){(this.isFullscreen()?this.exitFullscreen():this.enterFullscreen()).catch(t=>this.opts.onError?.(t))}setToken(e,t){C(e),this.api.setToken(e),this.opts.token=e,this.opts.tokenExpiresAt=t,this.tokenExpiresAt=t??null,this.scheduleTokenRefresh(),this.ready&&this.resolveChannelCapabilities()}scheduleTokenRefresh(){this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.tokenRefreshTimer=null;let e=this.opts.onTokenRefresh,t=this.tokenExpiresAt;if(this.closed||!e||!t||!Number.isFinite(t))return;let s=t-Date.now(),i=Math.min(12e4,Math.max(3e4,s*.2)),o=Math.max(0,s-i);this.tokenRefreshTimer=setTimeout(()=>{this.tokenRefreshTimer=null,this.rotateToken()},o)}async rotateToken(){if(!(this.closed||this.tokenRefreshInFlight||!this.opts.onTokenRefresh)){this.tokenRefreshInFlight=!0;try{let e=await this.opts.onTokenRefresh();if(!e?.token||!Number.isFinite(e.expiresAt))throw new Error("invalid_token_refresh_result");this.setToken(e.token,e.expiresAt)}catch(e){this.opts.onError?.(e),this.closed||(this.tokenRefreshTimer=setTimeout(()=>{this.tokenRefreshTimer=null,this.rotateToken()},3e4))}finally{this.tokenRefreshInFlight=!1}}}async block(e,t={}){let s=(e??this.selectionLabels()).filter(o=>this.status.get(o)==="free");if(!s.length)return;let i=t.releaseAt??this.releaseAt??void 0;this.setSeatsLocal(s,"blocked");try{await this.api.block(this.key,s,{...t,releaseAt:i}),this.clearSelection(),this.done("block",s,i?`Blocked ${s.length} \u2014 auto-release ${new Date(i).toLocaleString()}.`:`Blocked ${s.length} seat${s.length===1?"":"s"}.`)}catch(o){this.setSeatsLocal(s,"free"),this.toastErr(o instanceof v&&o.status===409?"Some seats were just taken. Try again.":"Couldn't block those seats."),this.opts.onError?.(o)}}async unblock(e){let t=(e??this.selectionLabels()).filter(s=>this.status.get(s)==="blocked");if(t.length){this.setSeatsLocal(t,"free");try{await this.api.unblock(this.key,t),this.clearSelection(),this.done("unblock",t,`Unblocked ${t.length} seat${t.length===1?"":"s"}.`)}catch(s){this.setSeatsLocal(t,"blocked"),this.toastErr("Couldn't unblock those seats."),this.opts.onError?.(s)}}}async unblockAll(){let e=[...this.status.entries()].filter(([,t])=>t==="blocked").map(([t])=>t);if(e.length){this.setSeatsLocal(e,"free");try{let t=await this.api.unblockAll(this.key);this.clearSelection(),this.done("unblockAll",e,`Unblocked ${t.freed} seat${t.freed===1?"":"s"}.`)}catch(t){await this.resnapshot(),this.toastErr("Couldn't mark everything for sale."),this.opts.onError?.(t)}}}async cancelBooking(e,t){let s=e.filter(i=>this.status.get(i)==="booked");if(!(!s.length||!t)){this.setSeatsLocal(s,"free");try{await this.api.unbook(this.key,s,t),this.clearSelection(),this.done("cancelBooking",s,`Released ${s.length} booked unit${s.length===1?"":"s"}.`)}catch(i){this.setSeatsLocal(s,"booked"),this.toastErr("Couldn't release that booked inventory. Check the booking reference."),this.opts.onError?.(i)}}}async setCategory(e,t){let s=this.doc?.categories.find(o=>o.key===e),i=[...new Set(t??this.selectionLabels())].filter(o=>this.labelToSeat.has(o));if(!(!s||!i.length))try{let o=await this.withAuthRetry(()=>this.api.setCategory(this.key,i,e)),a={};for(let l of o.labels){let r=this.labelToSeat.get(l),c=this.labelToId.get(l);r&&(r.categoryKey=e),c&&(a[c]=e)}this.renderer?.setSeatCategories?.(a),this.clearSelection(),this.done("setCategory",o.labels,`Assigned ${o.labels.length.toLocaleString()} object${o.labels.length===1?"":"s"} to ${s.label??s.key}.`)}catch(o){this.toastErr("Couldn't update those event categories."),this.opts.onError?.(o)}}async setTableBooking(e,t,s={}){let i=[...new Set(e)].filter(Boolean);if(!(!i.length||this.tableBookingSaving)){if(this.inventoryModelVersion!==2){this.toastErr("Table booking controls require inventory model 2 for this event.");return}this.tableBookingSaving=!0,this.mode==="tables"&&this.renderTablesRail();try{let o=await this.withAuthRetry(()=>this.api.setTableBooking(this.key,i,t,s));await this.reloadEventChart(),this.done("setTableBooking",o.tableIds,`Updated ${o.tableIds.length.toLocaleString()} table${o.tableIds.length===1?"":"s"} to ${t==="individual"?"per-chair":t==="whole"?"whole-table":"flexible-group"} booking.`)}catch(o){let a=o instanceof v&&o.code==="table_inventory_in_use";this.toastErr(a?"Release held, booked, or blocked table inventory before changing its booking mode.":"Couldn't update table booking. No table mode was changed."),this.opts.onError?.(o)}finally{this.tableBookingSaving=!1,this.mode==="tables"&&this.renderTablesRail()}}}async reloadEventChart(){let e=await this.withAuthRetry(()=>this.api.chart(this.key));this.inventoryModelVersion=e.event.inventoryModelVersion===2?2:1;let t=await this.organizerAssetUrls.prepareRendererChart(e.doc);this.renderer?.destroy(),this.renderer=null,this.doc=t,this.labelToId.clear(),this.labelToSeat.clear(),this.allIds=[],this.gaUnitLabelSet.clear(),this.status.clear(),this.counts={held:0,booked:0,blocked:0},this.serverBaseline=null,this.lastSelectionLabels.clear(),this.buildUnitUniverse(t),this.buildRenderer(),this.buildSectionOptions(),this.settleMode(),await Promise.all([this.resnapshot(),this.refreshAvailability()]),this.paintRail()}selectAll(){if(this.mode==="select")return this.selectByLabels([...this.labelToSeat.values()].filter(t=>this.canSelectObject(t)).map(t=>t.label));let e=this.renderer?.selectAllSelectable()??[];return this.syncSelection(),e}selectSection(e){if(!this.renderer)return[];let t=this.renderer.getSelectableInSection(e);return this.renderer.selectByLabels(t.map(s=>s.label)),this.syncSelection(),this.renderer.getSelection()}setFilteredSection(e){let t=e.trim(),s=this.filterableSections().filter(a=>a.label===t);if(!s.length)throw new Error(`Unknown event section: ${e}`);let i=t!==this.filteredSectionLabel;this.filteredSectionLabel=t,this.mode==="filterSections"&&(this.applyFilteredSectionCanvas(s),this.renderFilterSectionsRail());let o=s.map(({id:a,label:l,seatCount:r,zone:c})=>({id:a,label:l,seatCount:r,...c?{zone:c}:{}}));return i&&this.opts.onFilteredSectionChange?.(o),o}clearFilteredSection(){let e=this.filteredSectionLabel!==null;this.filteredSectionLabel=null,this.renderer?.setDimmedSections(null),this.renderer?.clearSectionFocus(),this.renderer?.zoomToFit(),this.mode==="filterSections"&&this.renderFilterSectionsRail(),e&&this.opts.onFilteredSectionChange?.([])}getFilteredSections(){return this.filteredSectionLabel?this.filterableSections().filter(e=>e.label===this.filteredSectionLabel).map(({id:e,label:t,seatCount:s,zone:i})=>({id:e,label:t,seatCount:s,...i?{zone:i}:{}})):[]}selectByLabels(e){let t=e;if(this.mode==="select"){let s=new Set(this.selectionLabels()),i=this.selectionCap(),o=[...new Set(e)].filter(l=>!s.has(l)).map(l=>this.labelToSeat.get(l)).filter(l=>!!l&&this.canSelectObject(l)),a=Math.max(0,i-s.size);o.length>a&&this.opts.onSelectionLimit?.(i),t=o.slice(0,a).map(l=>l.label)}return this.renderer?.selectByLabels(t),this.syncSelection(),this.getSelection()}selectObjects(e){return this.selectByLabels(e)}deselectObjects(e){let t=e.map(s=>this.labelToId.get(s)).filter(s=>!!s);return this.renderer?.deselect(t),this.syncSelection(),this.getSelection()}selectCategories(e){let t=new Set(e);return this.selectByLabels([...this.labelToSeat.values()].filter(s=>t.has(s.categoryKey)).map(s=>s.label))}deselectCategories(e){let t=new Set(e);return this.deselectObjects(this.getSelection().filter(s=>t.has(s.categoryKey)).map(s=>s.label))}setSelectableObjects(e){this.opts.selectableObjects=[...e],this.selectableObjectLabels=new Set(e),this.reconcileSelectPolicy()}setUnavailableObjectsSelectable(e){this.opts.unavailableObjectsSelectable=e,this.reconcileSelectPolicy()}setObjectSelectable(e){this.opts.isObjectSelectable=e,this.reconcileSelectPolicy()}setMaxSelectedObjects(e){this.assertSelectionOptions(e,this.opts.numberOfPlacesToSelect),this.opts.maxSelectedObjects=e,this.trimSelectionToCap(),this.updateRendererInteraction(),this.syncSelection()}setNumberOfPlacesToSelect(e){this.assertSelectionOptions(this.opts.maxSelectedObjects,e),this.opts.numberOfPlacesToSelect=e,this.selectionWasValid=null,this.trimSelectionToCap(),this.updateRendererInteraction(),this.syncSelection()}getSelectionValidity(){let e=this.opts.numberOfPlacesToSelect;if(e===void 0)return null;let t=this.getSelection(),s=t.length;return{isValid:s===e,count:s,required:e,remaining:Math.max(0,e-s),objects:t}}clearSelection(){this.renderer?.clearSelection(),this.syncSelection()}getSelection(){return this.renderer?.getSelection()??[]}getReport(){return this.api.report(this.key).then(e=>(this.applyReportRevenue(e),e))}getControlRoomSnapshot(e=this.trendWindowMinutes){return this.setTrendWindow(e)}getConnection(){return{status:this.connectionStatus,lastMessageAt:this.lastMessageAt}}getLog(e={}){return this.api.log(this.key,e)}async setHoldTtl(e){try{await this.api.setHoldTtl(this.key,e),this.done("setHoldTtl",[],e?`Hold window set to ${Math.round(e/6e4)} min.`:"Hold window reset.")}catch(t){this.toastErr("Couldn't update the hold window."),this.opts.onError?.(t)}}zoomToFit(){if(this.mode==="filterSections"&&this.filteredSectionLabel){this.clearFilteredSection();return}this.renderer?.clearSectionFocus(),this.renderer?.zoomToFit()}destroy(){if(this.closed=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.feedTimer&&clearInterval(this.feedTimer),this.toastTimer&&clearTimeout(this.toastTimer),this.liveEventTimer&&clearTimeout(this.liveEventTimer),this.kpiCleanupTimer&&clearTimeout(this.kpiCleanupTimer),this.followLiveTimer&&clearTimeout(this.followLiveTimer),this.followSeatTimer&&clearTimeout(this.followSeatTimer),this.unblockAllConfirmTimer&&clearTimeout(this.unblockAllConfirmTimer),this.paintHandle!==null&&typeof cancelAnimationFrame=="function"&&cancelAnimationFrame(this.paintHandle),this.paintHandle=null,this.channels?.destroy(),this.channels=null,this.channelsLoading=null,this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.layoutObserver?.disconnect(),this.layoutObserver=null,this.root?.removeEventListener("keydown",this.onKeyDown),this.els.rail?.removeEventListener("click",this.onRailClick),typeof document<"u"&&document.removeEventListener("fullscreenchange",this.onFullscreenChange),this.ws){try{this.ws.close()}catch{}this.ws=null}this.renderer?.destroy(),this.renderer=null,this.organizerAssetUrls.dispose(),this.root&&this.root.parentNode===this.host&&this.host.removeChild(this.root)}buildRenderer(){if(!this.doc)return;let e=this.isBulkSelectMode();this.renderer=new xe(this.mapHost,{manageMode:!0,marqueeSelect:e,maxSelection:this.selectionCap(),selectableStatuses:this.selectableStatuses(),currency:this.currency,onSelect:t=>this.handleSeatSelect(t),onDeselect:()=>this.syncSelection(),onSelectionLimit:t=>this.opts.onSelectionLimit?.(t),onMarquee:()=>this.syncSelection(),onSectionTap:t=>{if(this.mode==="filterSections"){let s=this.filterableSections().find(i=>i.id===t);s&&this.setFilteredSection(s.label);return}this.renderer?.focusSection(t),this.channels?.handleSectionFocus(t)},onViewChange:()=>{this.updateZoomHint(),this.channels?.handleViewChange()}}),this.renderer.setChart(this.doc),this.repaintAll(),this.applyHeatOverlay(),this.updateZoomHint()}isBulkSelectMode(){return this.mode==="block"||this.mode==="categories"||this.mode==="channels"&&this.channels?.usesMarqueeSelection()===!0}selectableStatuses(){return this.mode==="block"?["free","not_for_sale"]:this.mode==="select"||this.mode==="categories"||this.mode==="inspect"||this.isBulkSelectMode()||this.mode==="channels"&&this.channels?.canSelect()===!0?["free","held","booked","not_for_sale"]:[]}updateRendererInteraction(){let e=this.isBulkSelectMode();this.renderer?.setManageInteraction({manageMode:!0,marqueeSelect:e,maxSelection:this.selectionCap(),selectableStatuses:this.selectableStatuses()}),this.updateZoomHint()}handleSeatSelect(e){if(this.mode==="select"&&!this.canSelectObject(e)){this.renderer?.deselect([e.id]),this.syncSelection();return}if(this.mode==="inspect"){let t=this.getSelection().filter(s=>s.id!==e.id).map(s=>s.id);t.length&&this.renderer?.deselect(t)}this.syncSelection()}buildUnitUniverse(e){for(let t of Le(e))this.labelToId.set(t.label,t.id),this.labelToSeat.set(t.label,t),this.allIds.push(t.id);for(let t of Me(e))for(let s of Re(t))this.labelToId.has(s)||this.gaUnitLabelSet.add(s)}unitTotal(){return this.allIds.length+this.gaUnitLabelSet.size}knownLabels(){return[...this.labelToId.keys(),...this.gaUnitLabelSet]}repaintAll(){let e=this.renderer;if(!e)return;this.allIds.length&&e.setStatus(this.allIds,"free");let t={free:[],held:[],booked:[],not_for_sale:[]};for(let[s,i]of this.status.entries()){let o=this.labelToId.get(s);o&&t[k(i)].push(o)}["held","booked","not_for_sale"].forEach(s=>{t[s].length&&e.setStatus(t[s],s)})}async connect(){if(this.closed)return;let e;try{if(e=(await this.withAuthRetry(()=>this.api.subscribeTicket(this.key))).protocols,!e.length)throw new Error("manage_subscribe_ticket_missing")}catch(s){this.setLive(!1),this.opts.onError?.(s),this.scheduleReconnect();return}if(this.closed)return;let t;try{t=new WebSocket(this.api.socketUrl(this.key),e)}catch(s){this.opts.onError?.(s),this.scheduleReconnect();return}this.ws=t,t.onopen=()=>{this.attempt=0,this.setLive(!0),this.resnapshot().then(()=>this.refreshControlRoom()).catch(s=>this.opts.onError?.(s)),this.refreshAvailability()},t.onmessage=s=>this.onMessage(s),t.onclose=()=>{this.ws===t&&(this.ws=null),this.setLive(!1),this.scheduleReconnect()},t.onerror=()=>{try{t.close()}catch{}}}scheduleReconnect(){if(this.closed||this.reconnectTimer)return;let e=Math.min(1e3*2**Math.min(this.attempt++,5),15e3);this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},e)}onMessage(e){let t;try{t=JSON.parse(typeof e.data=="string"?e.data:"")}catch{return}if(!t||typeof t!="object")return;this.lastMessageAt=Date.now();let s=t;if((Array.isArray(s.hidden)||Array.isArray(s.closed))&&this.updateEffectiveAvailability(s.hidden,s.closed),s.type==="presence"){typeof s.shoppingSessions=="number"&&typeof s.activeHolds=="number"&&(this.livePresence={at:Date.now(),value:{shoppingSessions:s.shoppingSessions,activeHolds:s.activeHolds}},this.controlRoomSnapshot&&(this.controlRoomSnapshot={...this.controlRoomSnapshot,presence:this.livePresence.value},this.opts.onControlRoom?.(this.controlRoomSnapshot)),this.lastSyncedAt=Date.now(),this.recomputeTallies(),this.paintMonitorInsights());return}if(s.type!=="hidden"){if(s.seats&&typeof s.seats=="object")this.applySnapshot(s.seats,typeof s.default=="string"?s.default:void 0);else if(Array.isArray(s.changes)){let i=[],o=new Map;for(let l of s.changes){let r=["free","held","booked","blocked"].includes(l.status)?l.status:"free",c=this.status.get(l.label)??"free";if(c===r)continue;this.setStatusLabel(l.label,r,c);let n=this.labelToId.get(l.label);n&&(this.renderer?.setStatus([n],k(r)),i.push(n));let h=this.verbFor(c,r),m=`${h}:${r}`,u=o.get(m)??{labels:[],verb:h,status:r};u.labels.push(l.label),o.set(m,u)}for(let l of o.values()){let r=this.pushActivity(l.labels,l.verb,l.status);r&&this.paintSpatialActivity(r)}i.length&&(this.lastSyncedAt=Date.now(),this.afterPaint());let a=typeof s.bookedValue?.gross=="number"?s.bookedValue.gross:s.revenue?.gross;typeof a=="number"&&Number.isFinite(a)&&this.applyLiveGross(a),this.recomputeTallies()}}}applyLiveGross(e){if(this.liveGross={at:Date.now(),value:e},this.authoritativeGrossRevenue=e,this.revenueStatus="current",this.controlRoomSnapshot){let s={...this.controlRoomSnapshot.bookedValue??this.controlRoomSnapshot.revenue,gross:e};this.controlRoomSnapshot={...this.controlRoomSnapshot,bookedValue:s,revenue:s},this.opts.onControlRoom?.(this.controlRoomSnapshot)}}setStatusLabel(e,t,s=this.status.get(e)??"free"){this.status.set(e,t),s!==t&&(s!=="free"&&(this.counts[s]-=1),t!=="free"&&(this.counts[t]+=1))}async resnapshot(){try{let e=await this.api.objects(this.key);this.applySnapshot(e.seats),this.updateEffectiveAvailability(e.hidden,e.closed),this.lastMessageAt=Date.now()}catch{}}applySnapshot(e,t){let s=o=>["free","held","booked","blocked"].includes(o)?o:"free",i=new Map;if(t!==void 0){let o=s(t);for(let a of this.knownLabels())i.set(a,o)}for(let[o,a]of Object.entries(e))i.set(o,s(a));this.status=i,this.modelVersion+=1,this.recountAll(),this.lastSyncedAt=Date.now(),this.repaintAll(),this.afterPaint(),this.recomputeTallies()}recountAll(){let e={held:0,booked:0,blocked:0};for(let t of this.status.values())t!=="free"&&(e[t]+=1);this.counts=e}setSeatsLocal(e,t){let s=[];for(let i of e){this.setStatusLabel(i,t);let o=this.labelToId.get(i);o&&s.push(o)}s.length&&this.renderer?.setStatus(s,k(t)),this.afterPaint(),this.recomputeTallies()}afterPaint(){this.keepLive&&typeof document<"u"&&document.hidden&&this.renderer?.forceDraw()}activityColor(e){return e==="held"?"#f4b740":e==="booked"?"#22a06b":e==="blocked"?"#8b94ac":"#6e7bff"}sectionsForLabels(e){let t=new Set;for(let i of e){let o=this.labelToSeat.get(i);if(!o)continue;let a=this.sectionByObject.get(o.rowId);a&&a!==g&&t.add(a)}let s=[...t];return{ids:s,labels:s.map(i=>this.sectionLabelById.get(i)??i)}}pulseSeatLabels(e,t){let s=this.activityColor(t);for(let i of e.slice(0,Ae)){let o=this.labelToId.get(i);o&&this.renderer?.flashSeat(o,s)}}paintSpatialActivity(e){let t=e.sectionIds??this.sectionsForLabels(e.labels).ids,s=this.renderer?.getFocusedSection()??null,i=this.followLive&&t.length===1&&(e.status==="held"||e.status==="booked");if(i&&s===t[0]){this.pulseSeatLabels(e.labels,e.status);return}if(i){this.followLiveTimer&&clearTimeout(this.followLiveTimer),this.followSeatTimer&&clearTimeout(this.followSeatTimer),this.followLiveTimer=setTimeout(()=>{this.followLiveTimer=null,this.renderer?.focusSection(t[0]),this.followSeatTimer=setTimeout(()=>{this.followSeatTimer=null,this.pulseSeatLabels(e.labels,e.status)},520)},220);return}if(!s&&t.length){let o=this.activityColor(e.status);for(let a of t.slice(0,V))this.renderer?.flashSection(a,o);return}(!t.length||s&&t.includes(s))&&this.pulseSeatLabels(e.labels,e.status)}locateSection(e){this.renderer?.focusSection(e)}locateActivity(e){let t=this.feed.find(i=>i.id===e);if(!t)return;let s=t.sectionIds??this.sectionsForLabels(t.labels).ids;if(this.followSeatTimer&&clearTimeout(this.followSeatTimer),s.length===1){this.locateSection(s[0]),this.followSeatTimer=setTimeout(()=>{this.followSeatTimer=null,this.pulseSeatLabels(t.labels,t.status)},520);return}this.zoomToFit(),this.followSeatTimer=setTimeout(()=>{if(this.followSeatTimer=null,s.length){let i=this.activityColor(t.status);for(let o of s.slice(0,V))this.renderer?.flashSection(o,i)}else this.pulseSeatLabels(t.labels,t.status)},280)}showLiveEvent(e){let t=this.els.liveevent;if(!t)return;let s=e.sectionLabels??[],i=s.length===1?s[0]:s.length>1?`${s.length} sections`:e.label,o=e.count===1?"seat":"seats";t.innerHTML=`<span class="slm-liveeventdot" style="background:${this.activityColor(e.status)}"></span>
|
|
325
|
+
<span class="slm-liveeventcopy">${p(i)} \xB7 ${e.count.toLocaleString()} ${o} ${p(e.verb)}</span>
|
|
326
|
+
<span class="slm-liveeventhint">Live</span>`,t.classList.add("on"),this.liveEventTimer&&clearTimeout(this.liveEventTimer),this.liveEventTimer=setTimeout(()=>{this.liveEventTimer=null,t.classList.remove("on"),t.innerHTML=""},2800)}applyReportRevenue(e){this.authoritativeGrossRevenue=e.report.byCategory.reduce((t,s)=>{let i=Number.isFinite(s.bookedValue)?s.bookedValue:s.bookedRevenue;return t+(Number.isFinite(i)?i:0)},0),this.revenueStatus="current",this.recomputeTallies()}async refreshControlRoom(){let e=++this.revenueRequest,t=Date.now();try{let s=await this.api.controlRoom(this.key,this.trendWindowMinutes),i=s.bookedValue??s.revenue??{gross:0,bySection:[]},o=(i.bySection??[]).map(r=>{let c=Number.isFinite(r.bookedValue)?r.bookedValue:r.bookedRevenue;return{...r,bookedValue:c??0,bookedRevenue:c??0}}),a={...i,bySection:o},l={...s,bookedValue:a,revenue:a};if(e===this.revenueRequest){if(this.livePresence&&this.livePresence.at>=t?l={...l,presence:this.livePresence.value}:this.livePresence=null,this.liveGross&&this.liveGross.at>=t){let r={...l.bookedValue,gross:this.liveGross.value};l={...l,bookedValue:r,revenue:r}}else this.liveGross=null;this.controlRoomSnapshot=l,this.rebaseServerTotals(l),this.lastSyncedAt=Date.now(),this.authoritativeGrossRevenue=l.bookedValue.gross,this.authoritativeCurrency=l.currency,this.currency=l.currency,this.revenueStatus="current",this.recomputeTallies(),this.applyHeatOverlay(),this.paintMonitorInsights(),this.opts.onControlRoom?.(l)}return l}catch(s){throw e===this.revenueRequest&&(this.revenueStatus="stale",this.recomputeTallies()),s}}rebaseServerTotals(e){let t=e.totals;if(!t||["free","held","booked","blocked"].some(s=>!Number.isFinite(t[s]))){this.serverBaseline=null;return}this.serverBaseline={model:this.modelVersion,server:{free:t.free,held:t.held,booked:t.booked,blocked:t.blocked},client:this.clientTallies()}}clientTallies(){let{held:e,booked:t,blocked:s}=this.counts;return{held:e,booked:t,blocked:s,free:Math.max(0,this.unitTotal()-e-t-s)}}buildTallies(){let e=this.clientTallies(),t=this.serverBaseline?.model===this.modelVersion?this.serverBaseline:null,s=l=>t?Math.max(0,t.server[l]+(e[l]-t.client[l])):e[l],i=this.controlRoomSnapshot?.event?.seatTotal,o={free:s("free"),held:s("held"),booked:s("booked"),blocked:s("blocked"),total:Number.isFinite(i)?i:this.unitTotal(),capacityPct:0,sellThroughPct:0,bookedValue:this.authoritativeGrossRevenue,grossRevenue:this.authoritativeGrossRevenue,bookedValueStatus:this.revenueStatus,revenueStatus:this.revenueStatus,currency:this.currency};o.capacityPct=o.total?Math.round(o.booked/o.total*100):0;let a=o.total-o.blocked;return o.sellThroughPct=a>0?Math.round(o.booked/a*100):0,o}recomputeTallies(){if(!this.closed){if(typeof requestAnimationFrame!="function"){this.flushTallies();return}this.paintHandle===null&&(this.paintHandle=requestAnimationFrame(()=>{this.paintHandle=null,this.flushTallies()}))}}flushTallies(){if(this.closed)return;let e=this.buildTallies();this.paintKpis(e),this.mode==="view"?(this.paintLegend(e),this.paintMonitorInsights()):this.mode==="inspect"?this.renderInspectRail(this.getSelection()):this.mode==="block"?this.paintSelBar(this.getSelection()):this.mode==="channels"&&this.channels?.handleSelectionChange(),this.opts.onTallies?.(e)}verbFor(e,t){return t==="held"?"held":t==="booked"?"booked":t==="blocked"?"blocked":t==="free"?e==="blocked"?"unblocked":e==="booked"?"cancelled":"released":t}pushActivity(e,t,s,i=Date.now()){let o=e[0];if(!o)return null;let a=this.sectionsForLabels(e),l={id:`${o}:${i}:${Math.random().toString(36).slice(2,6)}`,at:i,label:o,labels:[...e],count:e.length,verb:t,status:s,sectionIds:a.ids,sectionLabels:a.labels};return this.feed.unshift(l),this.feed.length>x&&(this.feed.length=x),this.mode==="view"&&this.paintFeed(),this.showLiveEvent(l),this.opts.onActivity?.(l),l}seedFeed(e){let t={hold:"held",book:"booked",release:"released",expire:"expired",block:"blocked",unblock:"unblocked",unbook:"cancelled"},s={hold:"held",book:"booked",release:"free",expire:"free",block:"blocked",unblock:"free",unbook:"free"};for(let i of e){let o=i.labels[0];if(!o)continue;let a=this.sectionsForLabels(i.labels),l={id:`log:${i.id}`,at:i.at,label:o,labels:[...i.labels],count:i.labels.length,verb:t[i.action]??i.action,status:s[i.action]??"free",sectionIds:a.ids,sectionLabels:a.labels};this.feed.push(l),this.opts.onActivity?.(l)}this.feed.sort((i,o)=>o.at-i.at),this.feed.length>x&&(this.feed.length=x),this.mode==="view"&&this.paintFeed()}startFeedClock(){this.feedTimer=setInterval(()=>{this.mode==="view"&&(this.paintFeed(),this.paintMonitorInsights())},1e4)}selectionCap(){return this.mode!=="select"?1e6:this.opts.numberOfPlacesToSelect??this.opts.maxSelectedObjects??1e6}canSelectObject(e){let t=this.status.get(e.label)??"free",s=this.opts.unavailableObjectsSelectable??!0,i=t==="free"||s||this.selectableObjectLabels.has(e.label);if(!this.opts.isObjectSelectable)return i;try{return this.opts.isObjectSelectable(e,i)===!0}catch(o){return this.opts.onError?.(o),!1}}applyInitialSelection(){this.initialSelectionApplied||this.mode!=="select"||!this.renderer||(this.initialSelectionApplied=!0,this.selectByLabels(this.opts.selectedObjects??[]))}trimSelectionToCap(){if(this.mode!=="select")return;let e=this.getSelection(),t=this.selectionCap();e.length<=t||this.renderer?.deselect(e.slice(t).map(s=>s.id))}reconcileSelectPolicy(){if(this.mode!=="select")return;let e=this.getSelection().filter(t=>!this.canSelectObject(t));e.length&&this.renderer?.deselect(e.map(t=>t.id)),this.trimSelectionToCap(),this.syncSelection()}selectionLabels(){return this.getSelection().map(e=>e.label)}syncSelection(){let e=this.getSelection();this.mode==="block"?this.paintSelBar(e):this.mode==="inspect"?this.renderInspectRail(e):this.mode==="select"?this.paintSelectSelection(e):this.mode==="categories"?this.paintCategorySelection(e):this.mode==="channels"&&this.channels?.handleSelectionChange();let t=new Map(e.map(s=>[s.label,s]));if(this.mode==="select"){for(let[i,o]of t)this.lastSelectionLabels.has(i)||this.opts.onObjectSelected?.(o);for(let i of this.lastSelectionLabels)if(!t.has(i)){let o=this.labelToSeat.get(i);o&&this.opts.onObjectDeselected?.(o)}let s=this.getSelectionValidity();s&&(this.opts.onSelectionValidityChange?.(s),this.selectionWasValid!==s.isValid&&(s.isValid?this.opts.onSelectionValid?.(s):this.opts.onSelectionInvalid?.(s)),this.selectionWasValid=s.isValid)}this.lastSelectionLabels=new Set(t.keys()),this.opts.onSelectionChange?.(e)}buildChrome(){let e=document.createElement("div");e.className="slm",e.tabIndex=0,e.setAttribute("role","region"),e.setAttribute("aria-label","SeatLayer live control room");let t=M(this.opts.theme);for(let[i,o]of Object.entries(t))e.style.setProperty(i,o);e.innerHTML=`
|
|
299
327
|
<div class="slm-bar">
|
|
300
|
-
<div class="slm-modes" data-ref="modes" role="tablist" aria-label="
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<button class="slm-mode" role="tab" data-mode="sections" title="Sections (S)" aria-keyshortcuts="S">Sections</button>
|
|
307
|
-
<button class="slm-mode" role="tab" data-mode="categories" title="Categories (G)" aria-keyshortcuts="G" hidden>Categories</button>
|
|
308
|
-
<button class="slm-mode" role="tab" data-mode="tables" title="Tables (T)" aria-keyshortcuts="T" hidden>Tables</button>
|
|
309
|
-
<button class="slm-mode" role="tab" data-mode="channels" title="Channels (C)" aria-keyshortcuts="C" hidden>Channels</button>
|
|
328
|
+
<div class="slm-modes" data-ref="modes" role="tablist" aria-label="Control room tools">
|
|
329
|
+
${N.map(i=>`<div class="slm-modegroup" data-group="${i.key}" role="presentation" hidden>
|
|
330
|
+
<span class="slm-modegroup-label" aria-hidden="true">${p(i.label)}</span>
|
|
331
|
+
${i.tools.map(o=>`<button class="slm-mode" role="tab" data-mode="${o.mode}"
|
|
332
|
+
title="${p(o.title)} (${o.shortcut})" aria-keyshortcuts="${o.shortcut}" hidden>${p(o.label)}</button>`).join("")}
|
|
333
|
+
</div>`).join("")}
|
|
310
334
|
</div>
|
|
311
|
-
<select class="slm-tools" data-ref="tools" aria-label="
|
|
335
|
+
<select class="slm-tools" data-ref="tools" aria-label="Control room tools"></select>
|
|
312
336
|
<span class="slm-live"><span class="slm-live-dot"></span><span data-ref="livetext">CONNECTING</span></span>
|
|
313
337
|
<div class="slm-bar-actions">
|
|
314
338
|
<button class="slm-barbtn follow" data-ref="follow" aria-pressed="false"
|
|
@@ -330,12 +354,12 @@ import{A as he,C as pe,a as g,b as L,c as V,d as N,e as q,f as _,g as U,h as K,i
|
|
|
330
354
|
<aside class="slm-rail"><div class="slm-railscroll" data-ref="rail"></div></aside>
|
|
331
355
|
</div>
|
|
332
356
|
<div class="slm-toast" data-ref="toast"></div>
|
|
333
|
-
`,this.host.appendChild(e),this.root=e,this.updateContainerLayout(),typeof ResizeObserver<"u"&&(this.layoutObserver=new ResizeObserver(()=>this.updateContainerLayout()),this.layoutObserver.observe(e));let s=i=>e.querySelector(`[data-ref="${i}"]`);this.mapHost=s("maphost"),this.els={modes:s("modes"),tools:s("tools"),livetext:s("livetext"),kpis:s("kpis"),follow:s("follow"),heat:s("heat"),fullscreen:s("fullscreen"),zoomhint:s("zoomhint"),liveevent:s("liveevent"),rail:s("rail"),toast:s("toast"),zfit:s("zfit")},this.els.modes.querySelectorAll("[data-mode]").forEach(i=>i.addEventListener("click",()=>this.setMode(i.dataset.mode))),this.els.tools.addEventListener("change",()=>this.setMode(this.els.tools.value)),this.els.zfit.addEventListener("click",()=>this.zoomToFit()),this.els.follow.addEventListener("click",()=>this.setFollowLive(!this.followLive)),this.els.heat.addEventListener("click",()=>this.setHeatOverlay(!this.heatEnabled)),this.els.fullscreen.addEventListener("click",()=>this.toggleFullscreen()),e.addEventListener("keydown",this.onKeyDown),this.els.rail.addEventListener("click",this.onRailClick),document.addEventListener("fullscreenchange",this.onFullscreenChange),this.paintModeTabs(),this.paintFollowLiveButton(),this.paintHeatButton(),this.paintFullscreenButton()}updateContainerLayout(){let e=this.root?.getBoundingClientRect().width||this.host.clientWidth;this.root?.classList.toggle("compact",e>0&&e<800),this.channels?.handleLayoutChange()}filterableSections(){return this.sectionsBase?[...this.sectionsBase.sections,...this.sectionsBase.ungrouped?[this.sectionsBase.ungrouped]:[]]:[]}applyFilteredSectionCanvas(e=this.filterableSections().filter(t=>t.label===this.filteredSectionLabel)){if(!this.renderer||this.mode!=="filterSections"||!e.length)return;let t=new Set(e.map(n=>n.id)),s=this.filterableSections().filter(n=>!t.has(n.id)).map(n=>n.id);this.renderer.clearSectionFocus(),this.renderer.setClosedSections(null),this.renderer.setDimmedSections(s);let i=e.flatMap(n=>n.seatLabels).map(n=>this.labelToSeat.get(n)).filter(n=>!!n&&Number.isFinite(n.x)&&Number.isFinite(n.y));if(!i.length){this.renderer.focusRegion(e[0].id);return}let o=Math.min(...i.map(n=>n.x)),a=Math.min(...i.map(n=>n.y)),l=Math.max(...i.map(n=>n.x)),r=Math.max(...i.map(n=>n.y)),
|
|
357
|
+
`,this.host.appendChild(e),this.root=e,this.updateContainerLayout(),typeof ResizeObserver<"u"&&(this.layoutObserver=new ResizeObserver(()=>this.updateContainerLayout()),this.layoutObserver.observe(e));let s=i=>e.querySelector(`[data-ref="${i}"]`);this.mapHost=s("maphost"),this.els={modes:s("modes"),tools:s("tools"),livetext:s("livetext"),kpis:s("kpis"),follow:s("follow"),heat:s("heat"),fullscreen:s("fullscreen"),zoomhint:s("zoomhint"),liveevent:s("liveevent"),rail:s("rail"),toast:s("toast"),zfit:s("zfit")},this.els.modes.querySelectorAll("[data-mode]").forEach(i=>i.addEventListener("click",()=>this.setMode(i.dataset.mode))),this.els.tools.addEventListener("change",()=>this.setMode(this.els.tools.value)),this.els.zfit.addEventListener("click",()=>this.zoomToFit()),this.els.follow.addEventListener("click",()=>this.setFollowLive(!this.followLive)),this.els.heat.addEventListener("click",()=>this.setHeatOverlay(!this.heatEnabled)),this.els.fullscreen.addEventListener("click",()=>this.toggleFullscreen()),e.addEventListener("keydown",this.onKeyDown),this.els.rail.addEventListener("click",this.onRailClick),document.addEventListener("fullscreenchange",this.onFullscreenChange),this.paintModeTabs(),this.paintFollowLiveButton(),this.paintHeatButton(),this.paintFullscreenButton()}updateContainerLayout(){let e=this.root?.getBoundingClientRect().width||this.host.clientWidth;this.root?.classList.toggle("compact",e>0&&e<800),this.channels?.handleLayoutChange()}filterableSections(){return this.sectionsBase?[...this.sectionsBase.sections,...this.sectionsBase.ungrouped?[this.sectionsBase.ungrouped]:[]]:[]}applyFilteredSectionCanvas(e=this.filterableSections().filter(t=>t.label===this.filteredSectionLabel)){if(!this.renderer||this.mode!=="filterSections"||!e.length)return;let t=new Set(e.map(n=>n.id)),s=this.filterableSections().filter(n=>!t.has(n.id)).map(n=>n.id);this.renderer.clearSectionFocus(),this.renderer.setClosedSections(null),this.renderer.setDimmedSections(s);let i=e.flatMap(n=>n.seatLabels).map(n=>this.labelToSeat.get(n)).filter(n=>!!n&&Number.isFinite(n.x)&&Number.isFinite(n.y));if(!i.length){this.renderer.focusRegion(e[0].id);return}let o=Math.min(...i.map(n=>n.x)),a=Math.min(...i.map(n=>n.y)),l=Math.max(...i.map(n=>n.x)),r=Math.max(...i.map(n=>n.y)),c=Math.max(40,Math.max(l-o,r-a)*.08);this.renderer.focusRegion({x:o-c,y:a-c,width:Math.max(1,l-o)+c*2,height:Math.max(1,r-a)+c*2})}buildSectionOptions(){if(this.doc)try{let e=Te(this.doc);this.sectionsBase=e,this.sectionOptions=[],this.sectionByObject=new Map(e.objectToSection),this.sectionLabelById.clear();for(let t of e.sections)this.sectionOptions.push({id:t.id,label:t.label}),this.sectionLabelById.set(t.id,t.label);e.ungrouped&&(this.sectionOptions.push({id:g,label:e.ungrouped.label}),this.sectionLabelById.set(g,e.ungrouped.label))}catch{}}toolAvailable(e){return e==="view"?!0:e==="channels"&&!this.channelCaps.view||e==="categories"&&!this.categoryCanManage||e==="tables"&&(!this.tableCanManage||!this.tables().length)?!1:!this.opts.tools||this.opts.tools.includes(e)}settleMode(){this.toolAvailable(this.mode)?this.paintModeTabs():this.setMode("view")}tables(){return(this.doc?Ce(this.doc):[]).flatMap(e=>e.objects).filter(e=>e.type==="table")}paintModeTabs(){let e=[];this.els.modes?.querySelectorAll(".slm-modegroup").forEach(s=>{let i=[];s.querySelectorAll("[data-mode]").forEach(o=>{let a=o.dataset.mode,l=this.toolAvailable(a);if(o.hidden=!l,!l)return;i.push({mode:a,label:o.textContent??a});let r=a===this.mode;o.classList.toggle("on",r),o.setAttribute("aria-selected",String(r)),o.tabIndex=r?0:-1}),s.hidden=i.length===0,i.length&&e.push({label:s.querySelector(".slm-modegroup-label")?.textContent??"",tools:i})});let t=this.els.tools;t&&(t.innerHTML=e.map(s=>`<optgroup label="${p(s.label)}">${s.tools.map(i=>`<option value="${i.mode}"${i.mode===this.mode?" selected":""}>${p(i.label)}</option>`).join("")}</optgroup>`).join(""),t.value=this.mode),this.root?.classList.toggle("block-mode",this.mode==="block")}railHeader(e,t,s){let i=t==="read"?"Read-only":t==="event"?"This event only":"Your app";return`<div class="slm-railhead"><p class="slm-eyebrow">${p(e)}</p><span class="slm-scope ${t}">${i}</span></div>
|
|
358
|
+
<p class="slm-hint">${s}</p>`}paintFollowLiveButton(){let e=this.els.follow;e&&(e.classList.toggle("on",this.followLive),e.setAttribute("aria-pressed",String(this.followLive)),e.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."))}paintHeatButton(){let e=this.els.heat;e&&(e.classList.toggle("on",this.heatEnabled),e.setAttribute("aria-pressed",String(this.heatEnabled)),e.setAttribute("aria-label",`Booking momentum overlay ${this.heatEnabled?"on":"off"}`),e.setAttribute("title",`${this.heatEnabled?"Hide":"Highlight"} sections booking fastest in the selected time window`),e.textContent="Booking momentum",this.paintMomentumHelp())}paintMomentumHelp(){let e=this.els.rail?.querySelector('[data-ref="momentumhelp"]');if(!e)return;e.hidden=!this.heatEnabled;let t=e.querySelector('[data-ref="momentumcopy"]');if(!t)return;let s=this.controlRoomSnapshot?.velocity.bySection.some(i=>i.netBooked>0);t.textContent=s?"Warmer sections have more completed bookings, adjusted for section size. Holds and viewers are not counted.":`No completed bookings in the last ${this.trendWindowMinutes} minutes.`}paintFullscreenButton(){this.els.fullscreen&&(this.els.fullscreen.textContent=this.isFullscreen()?"Exit full screen":"Full screen")}paintTrendWindow(){this.els.rail?.querySelectorAll("[data-window]").forEach(e=>{let t=Number(e.dataset.window);e.classList.toggle("on",t===this.trendWindowMinutes)})}setLive(e){this.root?.classList.toggle("live",e),this.els.livetext&&(this.els.livetext.textContent=e?"LIVE":"RECONNECTING"),this.paintMonitorInsights();let t=e?"live":"reconnecting";if(t!==this.connectionStatus){this.connectionStatus=t;try{this.opts.onConnectionChange?.(this.getConnection())}catch(s){this.opts.onError?.(s)}}}updateZoomHint(){let e=this.els.zoomhint;if(!e)return;let t=this.mode==="block"&&this.renderer?.getRung?.()!=="seats";e.classList.toggle("on",!!t)}formatKpiDelta(e,t,s){let i=t>0?"+":"\u2212",o=Math.abs(t);return e==="booked-value"?`${i}${y(o,s)}`:e==="booked-pct"?`${i}${o.toLocaleString()}pt`:`${i}${o.toLocaleString()}`}paintKpis(e){if(!this.els.kpis)return;let t=e.bookedValueStatus==="current"?y(e.bookedValue,e.currency):"\u2014",s=this.presenceCounts(),i=[{key:"booked-inventory",raw:e.booked,n:e.booked.toLocaleString(),l:"Booked inventory",dot:"#22a06b",title:"Inventory units booked"},{key:"held-seats",raw:e.held,n:e.held.toLocaleString(),l:"Held inventory",dot:"#f4b740",title:"Inventory currently held"},{key:"free-seats",raw:e.free,n:e.free.toLocaleString(),l:"Available",dot:"#6e7bff",title:"Inventory available to book"},{key:"blocked",raw:e.blocked,n:e.blocked.toLocaleString(),l:"Blocked",dot:"#8b94ac",title:"Inventory withheld from booking"},{key:"viewing-map",raw:s?.shoppingSessions??null,n:s?s.shoppingSessions.toLocaleString():"\u2014",l:"Viewing map",title:"Active map sessions right now"},{key:"active-holds",raw:s?.activeHolds??null,n:s?s.activeHolds.toLocaleString():"\u2014",l:"Active holds",title:"Sessions currently holding inventory"},{key:"booked-pct",raw:e.capacityPct,n:`${e.capacityPct}%`,l:"Booked",title:"Booked inventory as a share of the whole event"},{key:"booked-value",raw:e.bookedValueStatus==="current"?e.bookedValue:null,n:t,l:"Booked value",title:"Configured value attached to booked inventory"}],o=!1;this.els.kpis.innerHTML=i.map(a=>{let l=this.lastKpiValues.get(a.key),r=a.raw!=null&&l!=null&&a.raw!==l,c=r?a.raw-l:0;r&&(o=!0,this.activeKpiDeltas.set(a.key,{text:this.formatKpiDelta(a.key,c,e.currency),down:c<0})),a.raw!=null&&this.lastKpiValues.set(a.key,a.raw);let n=this.activeKpiDeltas.get(a.key);return`<div class="slm-kpi${n?" changed":""}" data-kpi="${a.key}" title="${p(a.title)}">
|
|
334
359
|
<b>${a.dot?`<span class="dot" style="background:${a.dot}"></span>`:""}${a.n}</b><span>${a.l}</span>
|
|
335
360
|
${n?`<span class="slm-kpidelta${n.down?" down":""}">${n.text}</span>`:""}
|
|
336
361
|
</div>`}).join(""),o&&(this.kpiCleanupTimer&&clearTimeout(this.kpiCleanupTimer),this.kpiCleanupTimer=setTimeout(()=>{this.kpiCleanupTimer=null,this.activeKpiDeltas.clear(),this.els.kpis?.querySelectorAll(".slm-kpidelta").forEach(a=>a.remove()),this.els.kpis?.querySelectorAll(".slm-kpi.changed").forEach(a=>a.classList.remove("changed"))},1500))}paintRail(){this.mode==="view"?this.renderViewRail():this.mode==="inspect"?this.renderInspectRail(this.getSelection()):this.mode==="select"?this.renderSelectRail():this.mode==="filterSections"?this.renderFilterSectionsRail():this.mode==="sections"?this.renderSectionsRail():this.mode==="categories"?this.renderCategoriesRail():this.mode==="tables"?this.renderTablesRail():this.mode==="channels"?this.channels?this.channels.paintRail():this.channelsLoading?this.els.rail.innerHTML='<div class="slm-empty" role="status">Loading sales channels\u2026</div>':this.els.rail.innerHTML='<div class="slm-empty" role="alert">Sales channels could not be loaded. Switch away and back to try again.</div>':this.renderBlockRail(),this.updateZoomHint()}renderViewRail(){this.els.rail.innerHTML=`
|
|
337
|
-
|
|
338
|
-
<p class="slm-hint">Read-only. Inventory, map activity and booking movement update on the same live board.</p>
|
|
362
|
+
${this.railHeader("Monitor","read","Watch the event live. Inventory, buyers on the map and booking movement update on this board as they happen \u2014 nothing here changes the event.")}
|
|
339
363
|
<div class="slm-health" data-ref="presence"></div>
|
|
340
364
|
<div class="slm-legend" data-ref="legend"></div>
|
|
341
365
|
<div class="slm-sectionhead">
|
|
@@ -351,16 +375,15 @@ import{A as he,C as pe,a as g,b as L,c as V,d as N,e as q,f as _,g as U,h as K,i
|
|
|
351
375
|
<div class="slm-sectionlist" data-ref="sections"></div>
|
|
352
376
|
<p class="slm-eyebrow">Activity</p>
|
|
353
377
|
<div class="slm-feed" data-ref="feed"></div>
|
|
354
|
-
`,this.els.presence=this.els.rail.querySelector('[data-ref="presence"]'),this.els.legend=this.els.rail.querySelector('[data-ref="legend"]'),this.els.sections=this.els.rail.querySelector('[data-ref="sections"]'),this.els.feed=this.els.rail.querySelector('[data-ref="feed"]'),this.els.rail.querySelectorAll("[data-window]").forEach(e=>e.addEventListener("click",()=>{let t=Number(e.dataset.window);this.setTrendWindow(t).catch(s=>this.opts.onError?.(s))})),this.recomputeTallies(),this.paintMonitorInsights(),this.paintTrendWindow(),this.paintMomentumHelp(),this.paintFeed()}presenceCounts(){return this.livePresence?.value??this.controlRoomSnapshot?.presence??null}paintMonitorInsights(){if(this.mode!=="view")return;let e=this.controlRoomSnapshot;if(this.els.presence){let i=this.root?.classList.contains("live"),o=this.lastSyncedAt?
|
|
378
|
+
`,this.els.presence=this.els.rail.querySelector('[data-ref="presence"]'),this.els.legend=this.els.rail.querySelector('[data-ref="legend"]'),this.els.sections=this.els.rail.querySelector('[data-ref="sections"]'),this.els.feed=this.els.rail.querySelector('[data-ref="feed"]'),this.els.rail.querySelectorAll("[data-window]").forEach(e=>e.addEventListener("click",()=>{let t=Number(e.dataset.window);this.setTrendWindow(t).catch(s=>this.opts.onError?.(s))})),this.recomputeTallies(),this.paintMonitorInsights(),this.paintTrendWindow(),this.paintMomentumHelp(),this.paintFeed()}presenceCounts(){return this.livePresence?.value??this.controlRoomSnapshot?.presence??null}paintMonitorInsights(){if(this.mode!=="view")return;let e=this.controlRoomSnapshot;if(this.els.presence){let i=this.root?.classList.contains("live"),o=this.lastSyncedAt?R(this.lastSyncedAt,Date.now()):"waiting",a=this.presenceCounts();this.els.presence.innerHTML=`
|
|
355
379
|
<div class="slm-healthitem" title="Active map sessions right now"><b>${a?a.shoppingSessions.toLocaleString():"\u2014"}</b><span>Viewing map</span></div>
|
|
356
380
|
<div class="slm-healthitem" title="Sessions currently holding inventory"><b>${a?a.activeHolds.toLocaleString():"\u2014"}</b><span>Active holds</span></div>
|
|
357
381
|
<div class="slm-healthitem"><b>${i?"Healthy":"Reconnecting"}</b><span>Live connection</span></div>
|
|
358
|
-
<div class="slm-healthitem"><b>${o}</b><span>Last sync</span></div>`}if(!this.els.sections)return;if(!e){this.els.sections.innerHTML='<div class="slm-empty">Loading authoritative section metrics\u2026</div>';return}let t=new Map(e.velocity.bySection.map(i=>[i.sectionId,i])),s=[...e.bookedValue.bySection].sort((i,o)=>{let a=t.get(i.sectionId)?.netBooked??0;return(t.get(o.sectionId)?.netBooked??0)-a||o.bookedValue-i.bookedValue});this.els.sections.innerHTML=s.length?s.map(i=>{let o=t.get(i.sectionId),a=o?.netBooked??0,l=`${a>0?"+":""}${a}`,r=o?.trend==="rising"||o?.trend==="cooling"?o.trend:"steady";return`<button type="button" class="slm-sectionrow" data-section-focus="${
|
|
359
|
-
<span class="slm-sectiontop"><span>${
|
|
382
|
+
<div class="slm-healthitem"><b>${o}</b><span>Last sync</span></div>`}if(!this.els.sections)return;if(!e){this.els.sections.innerHTML='<div class="slm-empty">Loading authoritative section metrics\u2026</div>';return}let t=new Map(e.velocity.bySection.map(i=>[i.sectionId,i])),s=[...e.bookedValue.bySection].sort((i,o)=>{let a=t.get(i.sectionId)?.netBooked??0;return(t.get(o.sectionId)?.netBooked??0)-a||o.bookedValue-i.bookedValue});this.els.sections.innerHTML=s.length?s.map(i=>{let o=t.get(i.sectionId),a=o?.netBooked??0,l=`${a>0?"+":""}${a}`,r=o?.trend==="rising"||o?.trend==="cooling"?o.trend:"steady";return`<button type="button" class="slm-sectionrow" data-section-focus="${p(i.sectionId)}" title="Focus ${p(i.sectionLabel)} on the map">
|
|
383
|
+
<span class="slm-sectiontop"><span>${p(i.sectionLabel)}</span><span>${y(i.bookedValue,e.currency)}</span></span>
|
|
360
384
|
<span class="slm-sectionmeta"><span>${i.booked.toLocaleString()}/${i.total.toLocaleString()} booked \xB7 ${l} in ${e.velocity.windowMinutes}m</span><span class="slm-trend ${r}">${r}</span><span class="slm-sectionlocate">Locate</span></span>
|
|
361
385
|
</button>`}).join(""):'<div class="slm-empty">No section metrics are available for this chart.</div>',this.paintTrendWindow(),this.paintMomentumHelp()}applyHeatOverlay(){let e=this.controlRoomSnapshot;if(!this.heatEnabled||!e){this.renderer?.setSectionHeat(null);return}let t=new Map(e.bookedValue.bySection.map(a=>[a.sectionId,Math.max(1,a.total)])),s=e.velocity.bySection.map(a=>({sectionId:a.sectionId,rate:Math.max(0,a.netBooked)/(t.get(a.sectionId)??1)/e.velocity.windowMinutes})),i=Math.max(0,...s.map(a=>a.rate)),o={};for(let a of s)o[a.sectionId]=i>0?Math.sqrt(a.rate/i):0;this.renderer?.setSectionHeat(o)}renderSelectRail(){let e=this.doc?.categories??[],t=this.opts.numberOfPlacesToSelect,s=this.selectionCap();this.els.rail.innerHTML=`
|
|
362
|
-
|
|
363
|
-
<p class="slm-hint">Select inventory for a host-defined back-office operation. This tool is read-only and never books, releases, or blocks seats by itself.</p>
|
|
386
|
+
${this.railHeader("Select","host","Pick seats for your own back-office operation \u2014 the selection is handed to your app. This tool is read-only inside the control room: it never books, releases or blocks seats by itself.")}
|
|
364
387
|
<div class="slm-selbar" aria-live="polite"><span class="slm-selnum" data-ref="selectnum">0</span><span class="slm-sellabel" data-ref="selectmeta">selected</span></div>
|
|
365
388
|
<p class="slm-note" data-ref="selectvalidity">${t?`Select exactly ${t.toLocaleString()} object${t===1?"":"s"}.`:`Up to ${s.toLocaleString()} objects may be selected.`}</p>
|
|
366
389
|
<div class="slm-row">
|
|
@@ -369,98 +392,105 @@ import{A as he,C as pe,a as g,b as L,c as V,d as N,e as q,f as _,g as U,h as K,i
|
|
|
369
392
|
</div>
|
|
370
393
|
${e.length?`<p class="slm-eyebrow" style="margin-top:8px">Select by category</p>
|
|
371
394
|
<div class="slm-chipgrid">${e.map(i=>`
|
|
372
|
-
<button class="slm-chip" type="button" data-select-cat="${
|
|
373
|
-
<span class="dot" style="background:${
|
|
374
|
-
<span>${
|
|
395
|
+
<button class="slm-chip" type="button" data-select-cat="${p(i.key)}" aria-pressed="false">
|
|
396
|
+
<span class="dot" style="background:${p(i.color??"#6e7bff")}"></span>
|
|
397
|
+
<span>${p(i.label??i.key)}</span>
|
|
375
398
|
<span class="slm-chipcount" data-cat-count>0</span>
|
|
376
399
|
<span class="slm-chipcheck" aria-hidden="true">\u2713</span>
|
|
377
|
-
</button>`).join("")}</div>`:""}`,this.els.selectnum=this.els.rail.querySelector('[data-ref="selectnum"]'),this.els.selectmeta=this.els.rail.querySelector('[data-ref="selectmeta"]'),this.els.selectvalidity=this.els.rail.querySelector('[data-ref="selectvalidity"]'),this.els.selectclear=this.els.rail.querySelector('[data-ref="selectclear"]'),this.els.rail.querySelector('[data-ref="selectall"]')?.addEventListener("click",()=>this.selectAll()),this.els.selectclear.addEventListener("click",()=>this.clearSelection()),this.els.rail.querySelectorAll("[data-select-cat]").forEach(i=>{i.addEventListener("click",()=>{let o=i.dataset.selectCat,a=[...this.labelToSeat.values()].filter(r=>r.categoryKey===o&&this.canSelectObject(r)),l=new Set(this.selectionLabels());a.length>0&&a.every(r=>l.has(r.label))?this.deselectCategories([o]):this.selectCategories([o])})}),this.paintSelectSelection(this.getSelection())}paintSelectSelection(e){if(!this.els.selectnum||!this.els.selectmeta)return;let t=e.length;this.els.selectnum.textContent=t.toLocaleString(),this.els.selectmeta.textContent=t===1?"object selected":"objects selected",this.els.selectclear.disabled=t===0;let s=this.getSelectionValidity();this.els.selectvalidity&&(this.els.selectvalidity.textContent=s?s.isValid?`Ready \u2014 exactly ${s.required.toLocaleString()} selected.`:`Select ${s.remaining.toLocaleString()} more to continue.`:`${Math.max(0,this.selectionCap()-t).toLocaleString()} selections remaining.`);let i=new Set(e.map(o=>o.label));this.els.rail?.querySelectorAll("[data-select-cat]").forEach(o=>{let a=o.dataset.selectCat,l=[...this.labelToSeat.values()].filter(
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
<span class="slm-sectiontop"><span>${h(t)}</span><span>${o.toLocaleString()}</span></span>
|
|
400
|
+
</button>`).join("")}</div>`:""}`,this.els.selectnum=this.els.rail.querySelector('[data-ref="selectnum"]'),this.els.selectmeta=this.els.rail.querySelector('[data-ref="selectmeta"]'),this.els.selectvalidity=this.els.rail.querySelector('[data-ref="selectvalidity"]'),this.els.selectclear=this.els.rail.querySelector('[data-ref="selectclear"]'),this.els.rail.querySelector('[data-ref="selectall"]')?.addEventListener("click",()=>this.selectAll()),this.els.selectclear.addEventListener("click",()=>this.clearSelection()),this.els.rail.querySelectorAll("[data-select-cat]").forEach(i=>{i.addEventListener("click",()=>{let o=i.dataset.selectCat,a=[...this.labelToSeat.values()].filter(r=>r.categoryKey===o&&this.canSelectObject(r)),l=new Set(this.selectionLabels());a.length>0&&a.every(r=>l.has(r.label))?this.deselectCategories([o]):this.selectCategories([o])})}),this.paintSelectSelection(this.getSelection())}paintSelectSelection(e){if(!this.els.selectnum||!this.els.selectmeta)return;let t=e.length;this.els.selectnum.textContent=t.toLocaleString(),this.els.selectmeta.textContent=t===1?"object selected":"objects selected",this.els.selectclear.disabled=t===0;let s=this.getSelectionValidity();this.els.selectvalidity&&(this.els.selectvalidity.textContent=s?s.isValid?`Ready \u2014 exactly ${s.required.toLocaleString()} selected.`:`Select ${s.remaining.toLocaleString()} more to continue.`:`${Math.max(0,this.selectionCap()-t).toLocaleString()} selections remaining.`);let i=new Set(e.map(o=>o.label));this.els.rail?.querySelectorAll("[data-select-cat]").forEach(o=>{let a=o.dataset.selectCat,l=[...this.labelToSeat.values()].filter(h=>h.categoryKey===a&&this.canSelectObject(h)),r=l.filter(h=>i.has(h.label)).length,c=l.length>0&&r===l.length;o.classList.toggle("on",c),o.setAttribute("aria-pressed",String(c));let n=o.querySelector("[data-cat-count]");n&&(n.textContent=r?`${r}/${l.length}`:l.length.toLocaleString()),o.disabled=l.length===0})}renderFilterSectionsRail(){let e=new Map;for(let t of this.filterableSections())e.set(t.label,[...e.get(t.label)??[],t]);this.els.rail.innerHTML=`
|
|
401
|
+
${this.railHeader("Filter sections","host","Narrow the map to one section for your own reporting. Sections sharing a label stay together and the map frames their combined inventory. Nothing changes for buyers.")}
|
|
402
|
+
${e.size?`<div class="slm-sectionlist">${[...e].map(([t,s])=>{let i=t===this.filteredSectionLabel,o=s.reduce((a,l)=>a+l.seatCount,0);return`<button type="button" class="slm-sectionrow${i?" on":""}" data-filter-section="${p(t)}" aria-pressed="${i}">
|
|
403
|
+
<span class="slm-sectiontop"><span>${p(t)}</span><span>${o.toLocaleString()}</span></span>
|
|
382
404
|
<span class="slm-sectionmeta"><span>${s.length===1?"One section":`${s.length.toLocaleString()} matching sections`}</span><span class="slm-sectionlocate">${i?"Filtered":"Filter"}</span></span>
|
|
383
405
|
</button>`}).join("")}</div>
|
|
384
|
-
<button type="button" class="slm-btn ghost" data-clear-filter ${this.filteredSectionLabel?"":"disabled"}>Clear filter</button>`:'<div class="slm-empty">This chart has no filterable sections.</div>'}`,this.els.rail.querySelectorAll("[data-filter-section]").forEach(t=>{t.addEventListener("click",()=>this.setFilteredSection(t.dataset.filterSection))}),this.els.rail.querySelector("[data-clear-filter]")?.addEventListener("click",()=>this.clearFilteredSection())}renderCategoriesRail(){let e=this.doc?.categories??[];this.els.rail.innerHTML=`
|
|
385
|
-
|
|
386
|
-
<p class="slm-
|
|
387
|
-
<
|
|
406
|
+
<button type="button" class="slm-btn ghost" data-clear-filter ${this.filteredSectionLabel?"":"disabled"}>Clear filter</button>`:'<div class="slm-empty">This chart has no filterable sections.</div>'}`,this.els.rail.querySelectorAll("[data-filter-section]").forEach(t=>{t.addEventListener("click",()=>this.setFilteredSection(t.dataset.filterSection))}),this.els.rail.querySelector("[data-clear-filter]")?.addEventListener("click",()=>this.clearFilteredSection())}categorySeats(e){return[...this.labelToSeat.values()].filter(t=>t.categoryKey===e&&this.canSelectObject(t))}renderCategoriesRail(){let e=this.doc?.categories??[];this.els.rail.innerHTML=`
|
|
407
|
+
${this.railHeader("Categories","event","Move seats to a different price category for this event only. The reusable chart keeps its design, and seats already held or booked keep the price they were sold at.")}
|
|
408
|
+
<p class="slm-eyebrow slm-step"><span class="slm-stepnum">1</span>Choose seats</p>
|
|
409
|
+
<p class="slm-selecthelp">Click or drag seats on the map, press \u2318A for all, or tick a whole category. A ticked category is selected; tick it again to remove it.</p>
|
|
410
|
+
<div class="slm-chiprow">${e.length?e.map(s=>`
|
|
411
|
+
<button class="slm-chip" type="button" data-pick-category="${p(s.key)}" aria-pressed="false">
|
|
412
|
+
<span class="dot" style="background:${p(s.color??"#6e7bff")}"></span>
|
|
413
|
+
<span>${p(s.label??s.key)}</span>
|
|
414
|
+
<span class="slm-chipcount" data-cat-count>0</span>
|
|
415
|
+
<span class="slm-chipcheck" aria-hidden="true">\u2713</span>
|
|
416
|
+
</button>`).join(""):'<span class="slm-empty">This chart has no categories.</span>'}</div>
|
|
417
|
+
<div class="slm-selbar" aria-live="polite">
|
|
418
|
+
<span class="slm-selnum" data-ref="categorynum">0</span>
|
|
419
|
+
<span class="slm-sellabel" data-ref="categorymeta">selected</span>
|
|
420
|
+
</div>
|
|
388
421
|
<div class="slm-row">
|
|
389
422
|
<button class="slm-btn ghost" data-ref="categoryall">Select all</button>
|
|
390
423
|
<button class="slm-btn ghost" data-ref="categoryclear" disabled>Clear</button>
|
|
391
424
|
</div>
|
|
392
|
-
<p class="slm-eyebrow" style="margin-top:
|
|
393
|
-
<
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
<
|
|
397
|
-
<
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
<div class="slm-empty">Upgrade
|
|
402
|
-
|
|
403
|
-
<
|
|
404
|
-
|
|
405
|
-
<
|
|
406
|
-
<p class="slm-
|
|
425
|
+
<p class="slm-eyebrow slm-step" style="margin-top:14px"><span class="slm-stepnum">2</span>Move them to</p>
|
|
426
|
+
<p class="slm-selecthelp" data-ref="categoryhelp">Select seats first \u2014 then pick the category they should sell under.</p>
|
|
427
|
+
<div class="slm-categoryassign">${e.map(s=>`
|
|
428
|
+
<button type="button" class="slm-categoryrow" data-assign-category="${p(s.key)}" disabled>
|
|
429
|
+
<span class="dot" style="background:${p(s.color??"#6e7bff")}"></span>
|
|
430
|
+
<span class="slm-categorycopy"><b>${p(s.label??s.key)}</b><small data-cat-current>0 seats now</small></span>
|
|
431
|
+
<span class="slm-categoryact" data-cat-action>Move here</span>
|
|
432
|
+
</button>`).join("")}</div>`;let t=s=>this.els.rail.querySelector(`[data-ref="${s}"]`);this.els.categorynum=t("categorynum"),this.els.categorymeta=t("categorymeta"),this.els.categoryhelp=t("categoryhelp"),this.els.categoryclear=t("categoryclear"),t("categoryall").addEventListener("click",()=>this.selectAll()),this.els.categoryclear.addEventListener("click",()=>this.clearSelection()),this.els.rail.querySelectorAll("[data-pick-category]").forEach(s=>{s.addEventListener("click",()=>{let i=s.dataset.pickCategory,o=this.categorySeats(i),a=new Set(this.selectionLabels());o.length>0&&o.every(l=>a.has(l.label))?this.deselectCategories([i]):this.selectCategories([i])})}),this.els.rail.querySelectorAll("[data-assign-category]").forEach(s=>{s.addEventListener("click",()=>{this.setCategory(s.dataset.assignCategory)})}),this.paintCategorySelection(this.getSelection())}paintCategorySelection(e){if(!this.els.categorynum)return;let t=e.length;this.els.categorynum.textContent=t.toLocaleString(),this.els.categoryclear.disabled=t===0;let s=new Map;for(let a of e)s.set(a.categoryKey,(s.get(a.categoryKey)??0)+1);let i=a=>this.doc?.categories.find(l=>l.key===a)?.label??a;this.els.categorymeta&&(this.els.categorymeta.textContent=t===0?"selected":`${t===1?"seat":"seats"} selected \xB7 ${[...s].map(([a,l])=>`${l.toLocaleString()} ${i(a)}`).join(", ")}`),this.els.categoryhelp&&(this.els.categoryhelp.textContent=t===0?"Select seats first \u2014 then pick the category they should sell under.":`Choose the category the ${t===1?"selected seat":`${t.toLocaleString()} selected seats`} should sell under. Applies immediately.`);let o=new Set(e.map(a=>a.label));this.els.rail?.querySelectorAll("[data-pick-category]").forEach(a=>{let l=a.dataset.pickCategory,r=this.categorySeats(l),c=r.filter(m=>o.has(m.label)).length,n=r.length>0&&c===r.length;a.classList.toggle("on",n),a.classList.toggle("partial",c>0&&!n),a.setAttribute("aria-pressed",String(n));let h=a.querySelector("[data-cat-count]");h&&(h.textContent=c?`${c.toLocaleString()}/${r.length.toLocaleString()}`:r.length.toLocaleString()),a.disabled=r.length===0}),this.els.rail?.querySelectorAll("[data-assign-category]").forEach(a=>{let l=a.dataset.assignCategory,r=[...this.labelToSeat.values()].filter(u=>u.categoryKey===l).length,c=s.get(l)??0,n=t>0&&c===t;a.disabled=t===0||n,a.classList.toggle("current",n);let h=a.querySelector("[data-cat-current]");h&&(h.textContent=`${r.toLocaleString()} ${r===1?"seat":"seats"} now`);let m=a.querySelector("[data-cat-action]");m&&(m.textContent=t===0?"Move here":n?"Already here":`Move ${(t-c).toLocaleString()} here`)})}renderTablesRail(){let e=this.tables(),t="Decide how each table sells for this event: every chair on its own, or the whole table as one booking. A table with held, booked, or blocked chairs is locked until that inventory is released, and channel allocations are kept.";if(this.inventoryModelVersion!==2){this.els.rail.innerHTML=`
|
|
433
|
+
${this.railHeader("Tables","event",t)}
|
|
434
|
+
<div class="slm-empty">This event still uses the older inventory model, which cannot switch tables between per-chair and whole-table selling. Upgrade the event's inventory model to unlock this tool.</div>`;return}if(!e.length){this.els.rail.innerHTML=`
|
|
435
|
+
${this.railHeader("Tables","event",t)}
|
|
436
|
+
<div class="slm-empty">This chart has no tables. Add tables in the designer and they will appear here.</div>`;return}let s=h=>h.variableOccupancy?"variable":h.bookAsWhole?"whole":"individual",i=h=>{let m=0,u=0,b=0;for(let[q,_]of this.labelToSeat){if(_.rowId!==h.id)continue;let L=this.status.get(q);L==="held"?m++:L==="booked"?u++:L==="blocked"&&b++}let f=[u&&`${u} booked`,m&&`${m} held`,b&&`${b} blocked`].filter(Boolean);return f.length?f.join(" \xB7 "):null},o=e.map(h=>({table:h,current:s(h),locked:i(h)})),a=o.filter(h=>h.current==="individual").length,l=o.filter(h=>h.current==="whole").length,r=o.length-a-l,c=[`${a.toLocaleString()} per chair`,`${l.toLocaleString()} whole table`,r?`${r.toLocaleString()} flexible group`:""].filter(Boolean).join(" \xB7 "),n=this.tableBookingSaving;this.els.rail.innerHTML=`
|
|
437
|
+
${this.railHeader("Tables","event",t)}
|
|
438
|
+
<div class="slm-tablesummary"><b>${o.length.toLocaleString()} ${o.length===1?"table":"tables"}</b><span>${p(c)}</span></div>
|
|
439
|
+
<p class="slm-eyebrow" style="margin-top:12px">Set every table at once</p>
|
|
407
440
|
<div class="slm-row">
|
|
408
|
-
<button class="slm-btn ghost" data-table-all="individual"${
|
|
409
|
-
<button class="slm-btn ghost" data-table-all="whole"${
|
|
441
|
+
<button class="slm-btn ghost" data-table-all="individual"${n?" disabled":""} title="Sell every chair on its own">All per chair</button>
|
|
442
|
+
<button class="slm-btn ghost" data-table-all="whole"${n?" disabled":""} title="Sell every table as one booking">All whole table</button>
|
|
410
443
|
</div>
|
|
411
|
-
<
|
|
412
|
-
|
|
413
|
-
<
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
${
|
|
444
|
+
<p class="slm-eyebrow" style="margin-top:12px">Or one table at a time</p>
|
|
445
|
+
<div class="slm-tablelist">${o.map(({table:h,current:m,locked:u})=>`<div class="slm-tablerow${u?" locked":""}">
|
|
446
|
+
<span class="slm-tablecopy"><b>${p(h.displayLabel??h.label)}</b><small>${h.seatCount.toLocaleString()} chairs${u?` \xB7 <span class="slm-tablelock">Locked: ${p(u)}</span>`:""}</small></span>
|
|
447
|
+
<select class="slm-select" data-table-mode="${p(h.id)}" aria-label="Booking mode for ${p(h.displayLabel??h.label)}"
|
|
448
|
+
${n||u?"disabled":""} ${u?`title="Release the ${p(u)} chairs before changing how this table sells"`:""}>
|
|
449
|
+
<option value="individual"${m==="individual"?" selected":""}>Per chair</option>
|
|
450
|
+
<option value="whole"${m==="whole"?" selected":""}>Whole table</option>
|
|
451
|
+
${m==="variable"?'<option value="variable" selected>Flexible group</option>':""}
|
|
417
452
|
</select>
|
|
418
|
-
</div>`
|
|
419
|
-
<p class="slm-note">Flexible
|
|
420
|
-
|
|
421
|
-
<
|
|
422
|
-
|
|
423
|
-
<p class="slm-eyebrow">${p} details</p>
|
|
424
|
-
<p class="slm-hint">Live availability and section performance.</p>
|
|
453
|
+
</div>`).join("")}</div>
|
|
454
|
+
<p class="slm-note">Changes apply immediately to this event. Flexible group sizes (minimum and maximum guests) are set through the API, never guessed here.</p>`,this.els.rail.querySelectorAll("[data-table-all]").forEach(h=>{h.addEventListener("click",()=>{this.setTableBooking(o.filter(m=>!m.locked).map(m=>m.table.id),h.dataset.tableAll)})}),this.els.rail.querySelectorAll("[data-table-mode]").forEach(h=>{h.addEventListener("change",()=>{this.setTableBooking([h.dataset.tableMode],h.value)})})}renderInspectRail(e){let t=e[e.length-1];if(!t){this.els.rail.innerHTML=`
|
|
455
|
+
${this.railHeader("Inspect","read","Click one seat on the map to read its live status, category, section and booking context. Nothing changes in this view.")}
|
|
456
|
+
<div class="slm-empty">Select a seat on the map.</div>`;return}let s=this.status.get(t.label)??"free",i={free:"Free",held:"Held",booked:"Booked",blocked:"Blocked"},o=this.sectionByObject.get(t.rowId)??g,a=this.sectionLabelById.get(o)??"Other seats",l=this.doc?.categories.find(m=>m.key===t.categoryKey),r=this.controlRoomSnapshot?.bookedValue.bySection.find(m=>m.sectionId===o),c=this.doc?.objects.find(m=>m.id===t.rowId),n=c?.type==="row"?{label:"Row",value:c.label}:c?.type==="table"?{label:"Table",value:c.label}:t.kind==="booth"?{label:"Type",value:"Booth"}:null,h=t.kind==="booth"?"Booth":"Seat";this.els.rail.innerHTML=`
|
|
457
|
+
${this.railHeader("Inspect","read",`Live status and section performance for this ${h.toLowerCase()}. Click another seat to switch.`)}
|
|
425
458
|
<div class="slm-inspect-card">
|
|
426
|
-
<div class="slm-inspect-label">${
|
|
459
|
+
<div class="slm-inspect-label">${p(t.label)}</div>
|
|
427
460
|
<div class="slm-inspect-grid">
|
|
428
461
|
<div><span>Status</span><b>${i[s]}</b></div>
|
|
429
|
-
<div><span>Section</span><b>${
|
|
430
|
-
${n?`<div><span>${n.label}</span><b>${
|
|
431
|
-
<div><span>Category</span><b>${
|
|
462
|
+
<div><span>Section</span><b>${p(a)}</b></div>
|
|
463
|
+
${n?`<div><span>${n.label}</span><b>${p(n.value)}</b></div>`:""}
|
|
464
|
+
<div><span>Category</span><b>${p(l?.label??t.categoryKey)}</b></div>
|
|
432
465
|
<div><span>Booked in section</span><b>${r?`${r.booked} of ${r.total}`:"\u2014"}</b></div>
|
|
433
466
|
<div><span>Section booked value</span><b>${r&&this.controlRoomSnapshot?y(r.bookedValue,this.controlRoomSnapshot.currency):"\u2014"}</b></div>
|
|
434
467
|
</div>
|
|
435
|
-
</div>`}async refreshAvailability(){try{let e=await this.withAuthRetry(()=>this.api.availability(this.key));this.availabilityRules=e.rules??{},this.effectiveClosed=new Set(
|
|
436
|
-
|
|
437
|
-
<p class="slm-hint">Draw sections or zones in the designer to schedule availability per area. This chart has none yet.</p>
|
|
468
|
+
</div>`}async refreshAvailability(){try{let e=await this.withAuthRetry(()=>this.api.availability(this.key));this.availabilityRules=e.rules??{},this.effectiveClosed=new Set(E(this.availabilityRules)),this.mode==="sections"&&this.renderSectionsRail(),this.applySectionCanvasTreatment()}catch(e){this.opts.onError?.(e)}}async withAuthRetry(e){try{return await e()}catch(t){if(t instanceof v&&t.status===401&&this.opts.onTokenRefresh&&!this.tokenRefreshInFlight)return await this.rotateToken(),e();throw t}}updateEffectiveAvailability(e,t){let s=!1;Array.isArray(e)&&(this.effectiveHidden=new Set(e.filter(i=>typeof i=="string")),s=!0),Array.isArray(t)&&(this.effectiveClosed=new Set(t.filter(i=>typeof i=="string")),s=!0),s&&(this.mode==="sections"&&this.renderSectionsRail(),this.applySectionCanvasTreatment())}applySectionCanvasTreatment(){this.renderer&&(this.mode==="sections"?(this.renderer.setDimmedSections([...this.effectiveHidden]),this.renderer.setClosedSections([...this.effectiveClosed])):this.mode==="filterSections"&&this.filteredSectionLabel?this.applyFilteredSectionCanvas():(this.renderer.setDimmedSections(null),this.renderer.setClosedSections(null)))}sectionRowsInput(){return{sectionsBase:this.sectionsBase,doc:this.doc,availabilityRules:this.availabilityRules,effectiveHidden:this.effectiveHidden,effectiveClosed:this.effectiveClosed}}renderSectionsRail(){let{rows:e,hiddenSections:t,closedSections:s}=w(this.sectionRowsInput());if(!e.length){this.els.rail.innerHTML=`
|
|
469
|
+
${this.railHeader("Sections","event","Draw sections or zones in the designer to schedule availability per area. This chart has none yet.")}
|
|
438
470
|
<div class="slm-empty">No sections on this chart.</div>`;return}let i=[];t&&i.push(`${t} hidden`),s&&i.push(`${s} closed`);let o=i.length?i.join(" \xB7 "):"All sections open and on sale",a=t>0||s>0;this.els.rail.innerHTML=`
|
|
439
|
-
|
|
440
|
-
<
|
|
441
|
-
<div class="slm-availlist" data-ref="availlist">${e.map(l=>P(l,this.availabilitySaving)).join("")}</div>
|
|
471
|
+
${this.railHeader("Sections","event","Decide when each zone or section goes on sale for this event. Keep it hidden, reveal it at a set time, or <b>auto-reveal once the rest sells past a threshold</b>. Hidden seats vanish for buyers; closed seats stay on the map (flat grey) but can't be bought.")}
|
|
472
|
+
<div class="slm-availlist" data-ref="availlist">${e.map(l=>D(l,this.availabilitySaving)).join("")}</div>
|
|
442
473
|
<div class="slm-availsummary">
|
|
443
474
|
<span class="slm-availdot${a?" warn":""}"></span>
|
|
444
|
-
<span>${
|
|
475
|
+
<span>${p(o)}</span>
|
|
445
476
|
</div>
|
|
446
477
|
<div class="slm-availcallout">
|
|
447
478
|
<span class="slm-availstar" aria-hidden="true">\u2726</span>
|
|
448
|
-
<p><b>Auto-reveal at % sold</b> is
|
|
449
|
-
</div>`,this.wireSectionRail(),this.applySectionCanvasTreatment()}wireSectionRail(){let e=this.els.rail;e&&(e.querySelectorAll("[data-avail-id]").forEach(t=>{t.addEventListener("change",()=>this.setSectionMode(t.dataset.availId,t.value))}),e.querySelectorAll("[data-avail-reveal]").forEach(t=>{t.addEventListener("change",()=>{let s=new Date(t.value).getTime();Number.isFinite(s)&&this.setSectionRulePatch(t.dataset.availReveal,{revealAt:s})})}),e.querySelectorAll("[data-avail-pct]").forEach(t=>{t.addEventListener("change",()=>{let s=Math.max(1,Math.min(100,Number(t.value)||0));this.setSectionRulePatch(t.dataset.availPct,{thresholdPct:s})})}))}setSectionMode(e,t){let s=w(this.sectionRowsInput()).rows.find(l=>l.id===e),i=s?.seatLabels??this.availabilityRules[e]?.labels??[],o={...this.availabilityRules},a=
|
|
450
|
-
<span class="slm-leglabel">${t.label}</span><span class="slm-legcount">${e[t.key].toLocaleString()}</span></div>`).join(""))}paintFeed(){if(!this.els.feed)return;if(!this.feed.length){this.els.feed.innerHTML=`<div class="slm-empty">No activity yet \u2014 it'll stream in live.</div>`;return}let e=Date.now(),t={free:"#6e7bff",held:"#f4b740",booked:"#22a06b",blocked:"#8b94ac"};this.els.feed.innerHTML=this.feed.map(s=>{let i=s.count>1?` +${s.count-1}`:"",o=s.sectionLabels??[],a=o.length===1?o[0]:o.length>1?`${o.length} sections`:"";return`<button type="button" class="slm-feedrow" data-feed-id="${
|
|
479
|
+
<p><b>Auto-reveal at % sold</b> is demand-triggered release: the balcony opens itself the moment the stalls cross the threshold \u2014 no one has to be watching.</p>
|
|
480
|
+
</div>`,this.wireSectionRail(),this.applySectionCanvasTreatment()}wireSectionRail(){let e=this.els.rail;e&&(e.querySelectorAll("[data-avail-id]").forEach(t=>{t.addEventListener("change",()=>this.setSectionMode(t.dataset.availId,t.value))}),e.querySelectorAll("[data-avail-reveal]").forEach(t=>{t.addEventListener("change",()=>{let s=new Date(t.value).getTime();Number.isFinite(s)&&this.setSectionRulePatch(t.dataset.availReveal,{revealAt:s})})}),e.querySelectorAll("[data-avail-pct]").forEach(t=>{t.addEventListener("change",()=>{let s=Math.max(1,Math.min(100,Number(t.value)||0));this.setSectionRulePatch(t.dataset.availPct,{thresholdPct:s})})}))}setSectionMode(e,t){let s=w(this.sectionRowsInput()).rows.find(l=>l.id===e),i=s?.seatLabels??this.availabilityRules[e]?.labels??[],o={...this.availabilityRules},a=z(t,i,this.availabilityRules[e]);if(a?o[e]=a:delete o[e],s?.kind==="zone"&&this.sectionsBase)for(let l of this.sectionsBase.sections)l.zone===e&&delete o[l.id];this.persistAvailability(o)}setSectionRulePatch(e,t){let s=this.availabilityRules[e];if(!s)return;let o=w(this.sectionRowsInput()).rows.find(a=>a.id===e)?.seatLabels??s.labels??[];this.persistAvailability({...this.availabilityRules,[e]:{...s,...t,labels:o}})}async persistAvailability(e){let t=this.availabilityRules;this.availabilityRules=e,this.availabilitySaving=!0,this.mode==="sections"&&this.renderSectionsRail();try{let s=await this.withAuthRetry(()=>this.api.setAvailability(this.key,e));this.availabilityRules=s.rules,this.effectiveHidden=new Set(s.hidden),this.effectiveClosed=new Set(E(s.rules)),this.availabilitySaving=!1,this.mode==="sections"&&this.renderSectionsRail(),this.applySectionCanvasTreatment()}catch(s){this.availabilityRules=t,this.availabilitySaving=!1,this.mode==="sections"&&this.renderSectionsRail(),this.toastErr("Couldn't update availability. Try again."),this.opts.onError?.(s)}}paintLegend(e){this.els.legend&&(this.els.legend.innerHTML=P.map(t=>`<div class="slm-legrow"><span class="slm-legdot" style="background:${t.color}"></span>
|
|
481
|
+
<span class="slm-leglabel">${t.label}</span><span class="slm-legcount">${e[t.key].toLocaleString()}</span></div>`).join(""))}paintFeed(){if(!this.els.feed)return;if(!this.feed.length){this.els.feed.innerHTML=`<div class="slm-empty">No activity yet \u2014 it'll stream in live.</div>`;return}let e=Date.now(),t={free:"#6e7bff",held:"#f4b740",booked:"#22a06b",blocked:"#8b94ac"};this.els.feed.innerHTML=this.feed.map(s=>{let i=s.count>1?` +${s.count-1}`:"",o=s.sectionLabels??[],a=o.length===1?o[0]:o.length>1?`${o.length} sections`:"";return`<button type="button" class="slm-feedrow" data-feed-id="${p(s.id)}" title="Locate this activity on the map">
|
|
451
482
|
<span class="slm-feeddot" style="background:${t[s.status]}"></span>
|
|
452
|
-
<span class="slm-feedtext">${a?`<span class="slm-feedsection">${
|
|
453
|
-
<span class="slm-feedmeta"><span class="slm-feedtime">${
|
|
454
|
-
</button>`}).join("")}renderBlockRail(){let t=(this.doc?.categories??[]).map(n=>`<button class="slm-chip" type="button" data-cat="${
|
|
455
|
-
<span class="dot" style="background:${
|
|
456
|
-
<span>${
|
|
483
|
+
<span class="slm-feedtext">${a?`<span class="slm-feedsection">${p(a)}</span>`:""}${s.count===1?"Seat":"Seats"} <b>${p(s.label)}${i}</b> ${p(s.verb)}</span>
|
|
484
|
+
<span class="slm-feedmeta"><span class="slm-feedtime">${R(s.at,e)}</span><span class="slm-feedlocate">Locate</span></span>
|
|
485
|
+
</button>`}).join("")}renderBlockRail(){let t=(this.doc?.categories??[]).map(n=>`<button class="slm-chip" type="button" data-cat="${p(n.key)}" aria-pressed="false">
|
|
486
|
+
<span class="dot" style="background:${p(n.color??"#6e7bff")}"></span>
|
|
487
|
+
<span>${p(n.label??n.key)}</span>
|
|
457
488
|
<span class="slm-chipcount" data-cat-count>0</span>
|
|
458
489
|
<span class="slm-chipcheck" aria-hidden="true">\u2713</span>
|
|
459
490
|
</button>`).join(""),s=this.sectionOptions.length?`<div class="slm-field"><label>Select a whole section</label>
|
|
460
491
|
<select class="slm-select" data-ref="section"><option value="">Choose a section\u2026</option>
|
|
461
|
-
${this.sectionOptions.map(n=>`<option value="${
|
|
462
|
-
|
|
463
|
-
<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>
|
|
492
|
+
${this.sectionOptions.map(n=>`<option value="${p(n.id)}">${p(n.label)}</option>`).join("")}</select></div>`:"",i=this.sectionOptions.map(n=>`<option value="${p(n.id)}">${p(n.label)}</option>`).join("");this.els.rail.innerHTML=`
|
|
493
|
+
${this.railHeader("Block","event","Take seats off sale for this event, or put blocked seats back. Drag a box on the map, press \u2318A for all, or pick a whole category or section below. Booked and held seats are never touched.")}
|
|
464
494
|
<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>
|
|
465
495
|
<div class="slm-row">
|
|
466
496
|
<button class="slm-btn" data-ref="doblock" disabled>Block</button>
|
|
@@ -501,8 +531,8 @@ import{A as he,C as pe,a as g,b as L,c as V,d as N,e as q,f as _,g as U,h as K,i
|
|
|
501
531
|
<button class="slm-btn ghost" data-ref="markall" style="width:100%" disabled>Put all blocked seats on sale</button>
|
|
502
532
|
<p class="slm-note slm-allnote" data-ref="markallnote">For a full reset only. You will be asked to confirm.</p>
|
|
503
533
|
</div>
|
|
504
|
-
`;let o=n=>this.els.rail.querySelector(`[data-ref="${n}"]`);this.els.selnum=o("selnum"),this.els.doblock=o("doblock"),this.els.dounblock=o("dounblock"),this.els.selmeta=o("selmeta"),this.els.blockedcount=o("blockedcount"),this.els.blockedshowing=o("blockedshowing"),this.els.blockedlist=o("blockedlist"),this.els.selblocked=o("selblocked"),this.els.markall=o("markall"),this.els.markallnote=o("markallnote"),o("doblock").addEventListener("click",()=>{this.block()}),o("dounblock").addEventListener("click",()=>{this.unblock()}),o("selall").addEventListener("click",()=>this.selectAll()),o("clearsel").addEventListener("click",()=>this.clearSelection()),o("markall").addEventListener("click",()=>this.confirmUnblockAll()),this.els.rail.querySelectorAll("[data-cat]").forEach(n=>n.addEventListener("click",()=>this.toggleCategory(n.dataset.cat)));let a=this.els.rail.querySelector('[data-ref="section"]');a?.addEventListener("change",()=>{a.value&&(this.selectSection(a.value),a.value="")});let l=o("blockedsearch"),r=o("blockedsection");l.value=this.blockedQuery,r.value=this.blockedSection,l.addEventListener("input",()=>{this.blockedQuery=l.value,this.blockedResultLimit=100,this.paintBlockedInventory()}),r.addEventListener("change",()=>{this.blockedSection=r.value,this.blockedResultLimit=100,this.paintBlockedInventory()}),o("selblocked").addEventListener("click",()=>{this.toggleLabels(this.filteredBlockedSeats().map(n=>n.label))}),o("blockedlist").addEventListener("click",n=>{let
|
|
534
|
+
`;let o=n=>this.els.rail.querySelector(`[data-ref="${n}"]`);this.els.selnum=o("selnum"),this.els.doblock=o("doblock"),this.els.dounblock=o("dounblock"),this.els.selmeta=o("selmeta"),this.els.blockedcount=o("blockedcount"),this.els.blockedshowing=o("blockedshowing"),this.els.blockedlist=o("blockedlist"),this.els.selblocked=o("selblocked"),this.els.markall=o("markall"),this.els.markallnote=o("markallnote"),o("doblock").addEventListener("click",()=>{this.block()}),o("dounblock").addEventListener("click",()=>{this.unblock()}),o("selall").addEventListener("click",()=>this.selectAll()),o("clearsel").addEventListener("click",()=>this.clearSelection()),o("markall").addEventListener("click",()=>this.confirmUnblockAll()),this.els.rail.querySelectorAll("[data-cat]").forEach(n=>n.addEventListener("click",()=>this.toggleCategory(n.dataset.cat)));let a=this.els.rail.querySelector('[data-ref="section"]');a?.addEventListener("change",()=>{a.value&&(this.selectSection(a.value),a.value="")});let l=o("blockedsearch"),r=o("blockedsection");l.value=this.blockedQuery,r.value=this.blockedSection,l.addEventListener("input",()=>{this.blockedQuery=l.value,this.blockedResultLimit=100,this.paintBlockedInventory()}),r.addEventListener("change",()=>{this.blockedSection=r.value,this.blockedResultLimit=100,this.paintBlockedInventory()}),o("selblocked").addEventListener("click",()=>{this.toggleLabels(this.filteredBlockedSeats().map(n=>n.label))}),o("blockedlist").addEventListener("click",n=>{let h=n.target,m=h.closest("[data-blocked-label]");m?.dataset.blockedLabel?this.toggleLabels([m.dataset.blockedLabel]):h.closest("[data-blocked-more]")&&(this.blockedResultLimit+=100,this.paintBlockedInventory())});let c=o("release");c.addEventListener("change",()=>{let n=c.value?new Date(c.value).getTime():NaN;this.releaseAt=Number.isFinite(n)&&n>Date.now()?n:null;let h=o("releasenote");h.textContent=this.releaseAt?`New blocks auto-release ${new Date(this.releaseAt).toLocaleString()}.`:c.value?"Pick a time in the future.":"Leave empty to block permanently."}),this.paintSelBar(this.getSelection())}toggleCategory(e){let t=[];for(let[s,i]of this.labelToSeat.entries())i.categoryKey===e&&this.isBlockSelectable(s)&&t.push(s);this.toggleLabels(t)}toggleLabels(e){if(!this.renderer)return;let t=e.filter(o=>this.labelToSeat.has(o)&&this.isBlockSelectable(o));if(!t.length)return;let s=new Set(this.selectionLabels());if(t.every(o=>s.has(o))){let o=t.map(a=>this.labelToId.get(a)).filter(a=>!!a);this.renderer.deselect(o)}else this.renderer.selectByLabels(t);this.syncSelection()}isBlockSelectable(e){let t=this.status.get(e)??"free";return t==="free"||t==="blocked"}paintSelBar(e){if(!this.els.selnum)return;this.els.selnum.textContent=e.length.toLocaleString();let t=e.filter(a=>(this.status.get(a.label)??"free")==="free").length,s=e.filter(a=>this.status.get(a.label)==="blocked").length;this.els.selmeta.textContent=e.length?`${t.toLocaleString()} available \xB7 ${s.toLocaleString()} blocked`:"selected";let i=this.els.doblock,o=this.els.dounblock;i.disabled=t===0,o.disabled=s===0,i.textContent=t?`Block ${t.toLocaleString()}`:"Block selected",o.textContent=s?`Put ${s.toLocaleString()} on sale`:"Put back on sale",this.paintCategoryControls(e),this.paintBlockedInventory()}paintCategoryControls(e){let t=new Set(e.map(s=>s.label));this.els.rail?.querySelectorAll("[data-cat]").forEach(s=>{let i=s.dataset.cat,o=[];for(let[n,h]of this.labelToSeat.entries())h.categoryKey===i&&this.isBlockSelectable(n)&&o.push(n);let a=o.filter(n=>t.has(n)).length,l=o.length>0&&a===o.length,r=a>0&&!l;s.disabled=o.length===0,s.classList.toggle("on",l),s.classList.toggle("partial",r),s.setAttribute("aria-pressed",l?"true":r?"mixed":"false"),s.setAttribute("title",l?`Remove all ${o.length.toLocaleString()} seats in this category from the selection`:r?`Select the remaining ${(o.length-a).toLocaleString()} seats in this category`:`Select all ${o.length.toLocaleString()} seats in this category`);let c=s.querySelector("[data-cat-count]");c&&(c.textContent=a?`${a.toLocaleString()}/${o.length.toLocaleString()}`:o.length.toLocaleString())})}filteredBlockedSeats(){let e=this.blockedQuery.trim().toLocaleLowerCase(),t=[];for(let[s,i]of this.labelToSeat.entries()){if(this.status.get(s)!=="blocked")continue;let o=this.sectionByObject.get(i.rowId)??g;if(!(this.blockedSection&&o!==this.blockedSection)){if(e){let a=this.doc?.categories.find(h=>h.key===i.categoryKey)?.label??i.categoryKey,l=this.sectionLabelById.get(o)??"Other seats",r=this.doc?.objects.find(h=>h.id===i.rowId),c=r?.type==="row"||r?.type==="table"?r.label:"";if(!`${s} ${a} ${l} ${c}`.toLocaleLowerCase().includes(e))continue}t.push(i)}}return t.sort((s,i)=>s.label.localeCompare(i.label,void 0,{numeric:!0,sensitivity:"base"}))}paintBlockedInventory(){if(!this.els.blockedlist)return;let e=[...this.status.entries()].filter(([,n])=>n==="blocked").length,t=this.filteredBlockedSeats(),s=t.slice(0,this.blockedResultLimit),i=new Set(this.selectionLabels()),o=t.filter(n=>i.has(n.label)).length,a=t.length>0&&o===t.length;this.els.blockedcount.textContent=e.toLocaleString(),this.els.blockedshowing.textContent=t.length?`Showing ${s.length.toLocaleString()} of ${t.length.toLocaleString()}`:e?"No matches":"No blocked seats";let l=this.els.selblocked;l.disabled=t.length===0,l.textContent=a?`Remove ${t.length.toLocaleString()} results`:`Select ${t.length.toLocaleString()} results`,this.els.blockedlist.innerHTML=s.length?s.map(n=>{let h=this.sectionByObject.get(n.rowId)??g,m=this.sectionLabelById.get(h)??"Other seats",u=this.doc?.categories.find(f=>f.key===n.categoryKey)?.label??n.categoryKey,b=i.has(n.label);return`<button type="button" class="slm-blockeditem${b?" on":""}" data-blocked-label="${p(n.label)}" aria-pressed="${b}">
|
|
505
535
|
<span class="slm-blockedcheck" aria-hidden="true">\u2713</span>
|
|
506
|
-
<span class="slm-blockedcopy"><span class="slm-blockedlabel">${
|
|
507
|
-
<span class="slm-blockedmeta">${
|
|
508
|
-
</button>`}).join("")+(t.length>s.length?'<button type="button" class="slm-blockedmore" data-blocked-more>Show 100 more</button>':""):`<div class="slm-blockedempty">${e?"No blocked seats match this search or section.":"No seats are blocked. Newly blocked seats will appear here."}</div>`;let r=this.els.markall,
|
|
536
|
+
<span class="slm-blockedcopy"><span class="slm-blockedlabel">${p(n.label)}</span>
|
|
537
|
+
<span class="slm-blockedmeta">${p(m)} \xB7 ${p(u)}</span></span>
|
|
538
|
+
</button>`}).join("")+(t.length>s.length?'<button type="button" class="slm-blockedmore" data-blocked-more>Show 100 more</button>':""):`<div class="slm-blockedempty">${e?"No blocked seats match this search or section.":"No seats are blocked. Newly blocked seats will appear here."}</div>`;let r=this.els.markall,c=r.dataset.confirm==="true";r.disabled=e===0,r.textContent=c?`Confirm: put all ${e.toLocaleString()} on sale`:`Put all ${e.toLocaleString()} blocked seats on sale`}confirmUnblockAll(){let e=this.els.markall;if(!(!e||e.disabled)){if(e.dataset.confirm==="true"){this.resetUnblockAllConfirm(),this.unblockAll();return}e.dataset.confirm="true",e.classList.add("danger"),this.els.markallnote.textContent="This changes every blocked seat. Click the red button again to confirm.",this.paintBlockedInventory(),this.unblockAllConfirmTimer&&clearTimeout(this.unblockAllConfirmTimer),this.unblockAllConfirmTimer=setTimeout(()=>this.resetUnblockAllConfirm(),6e3)}}resetUnblockAllConfirm(){this.unblockAllConfirmTimer&&clearTimeout(this.unblockAllConfirmTimer),this.unblockAllConfirmTimer=null;let e=this.els.markall;e&&(delete e.dataset.confirm,e.classList.remove("danger"),this.els.markallnote&&(this.els.markallnote.textContent="For a full reset only. You will be asked to confirm."),this.paintBlockedInventory())}done(e,t,s){if(this.toastOk(s),t.length){let i=e==="block"?this.pushActivity(t,"blocked","blocked"):e==="unblock"||e==="unblockAll"?this.pushActivity(t,"unblocked","free"):e==="cancelBooking"?this.pushActivity(t,"cancelled","free"):null;i&&this.paintSpatialActivity(i)}e!=="setHoldTtl"&&this.refreshControlRoom().catch(i=>this.opts.onError?.(i)),this.opts.onActionComplete?.({action:e,labels:t,count:t.length})}toastOk(e){this.toast(e,"ok")}toastErr(e){this.toast(e,"err")}toast(e,t){let s=this.els.toast;s&&(s.textContent=e,s.className=`slm-toast on ${t}`,this.toastTimer&&clearTimeout(this.toastTimer),this.toastTimer=setTimeout(()=>{s.className="slm-toast"},3200))}fail(e){this.opts.onError?.(e),this.els.rail&&(this.els.rail.innerHTML=`<div class="slm-empty">Couldn't load this event. Check the event key and token.</div>`)}};export{re as ACCESS_LINK_DEFAULTS,fe as ChannelsMode,T as ManageApi,v as ManageApiError,U as PUBLIC_CHANNEL_ID,K as PUBLIC_CHANNEL_NAME,A as SeatManager,ae as accessIntentDescription,oe as accessIntentLabel,ie as accessLine,ce as accessLinkBadge,pe as accessLinkErrorCopy,de as accessLinkIsLive,he as accessLinkPolicyLines,te as bucketRows,be as bucketRowsHtml,me as dropReviewRows,le as intentForbidsCopy,ne as intentSwitchBlockedCopy,W as isPublicChannelId,G as markerLetter,Y as markerOf,J as mutationCount,ee as needsMoveConfirmation,X as planAssignment,se as retryAfterCopy,Q as selectionSources,ue as stateBadge,Z as suggestMarker};
|