@datalabz_/dashboard-kit 6.0.2 → 6.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,6 +53,35 @@ A major's files are one served bundle; the repo's per-file line budget
53
53
  (AGENTS.md #2) does not apply to it — it cannot be split without changing the
54
54
  URLs already pinned by live dashboards.
55
55
 
56
+ ## Asking a dashboard for its filters
57
+
58
+ A parent may post `{ type: 'DK_GET_FILTERS_INFO' }` to a dashboard's frame; the kit answers the parent with
59
+ `{ type: 'DK_FILTERS_INFO', filters: [...] }`. It needs no token or period, so a parent can ask as soon as
60
+ the frame has loaded. The list is the header controls the dashboard can be opened with, which is
61
+ what the kit can write into a header control at its first boot, one entry each:
62
+
63
+ ```js
64
+ { id: 'playerUsername', label: 'Player username', type: 'text', // type: the input's type, 'select' or 'textarea'
65
+ required: true, value: '',
66
+ placeholder, min, max, // only when the control sets them
67
+ disabled: true, // only when it is disabled
68
+ multiple: false, optionsTotal: 2, // select only
69
+ options: [{ value: 'BRL', label: 'Real' }] } // select only: the first 100 options
70
+ ```
71
+
72
+ The list follows the fill, so it holds only what a filter can actually be given:
73
+
74
+ - Left out: a control with no `id`, a second control with an `id` already listed, and radio, file,
75
+ hidden, password and button controls, which the kit refuses to fill.
76
+ - A select lists only options with a value, since an empty value is refused, and `optionsTotal` counts
77
+ those. A select with none is left out: it takes no value.
78
+ - What exists when the dashboard boots is what counts. Once it has booted, the answer is the list taken
79
+ at that moment, so options a section loads later by query, and controls added later, are not in it, as
80
+ they were not there for the fill. Only `value` and `disabled` are read again. A filter whose options
81
+ come from a query must therefore use static `<option>`s or a plain input to be openable.
82
+
83
+ The answer always goes to the dashboard's parent, whoever asked.
84
+
56
85
  ## Rules
57
86
 
58
87
  - **Where a change lands — the current `vN` or a new major — is decided by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalabz_/dashboard-kit",
3
- "version": "6.0.2",
3
+ "version": "6.0.4",
4
4
  "description": "DashboardKit runtime + styles, served from jsDelivr",
5
5
  "license": "UNLICENSED",
6
6
  "files": [
package/v4.js CHANGED
@@ -20,7 +20,7 @@
20
20
  let _activeTabId = '';
21
21
  let _sections = []; // { id, label, load, loaded, errors }
22
22
  let _title = '';
23
- let _gitVer = 'b3d1c2d'; // replaced at deploy: sed -i "s/b3d1c2d/$(git rev-parse --short HEAD)/g" v4.js
23
+ let _gitVer = 'ce29793'; // replaced at deploy: sed -i "s/ce29793/$(git rev-parse --short HEAD)/g" v4.js
24
24
  let _calls = []; // System Info call log
25
25
  let _sectionErrors = {}; // sectionId → error count
26
26
  let _generation = 0;
package/v5.js CHANGED
@@ -20,7 +20,7 @@
20
20
  let _activeTabId = '';
21
21
  let _sections = []; // { id, label, load, loaded, errors }
22
22
  let _title = '';
23
- let _gitVer = 'b3d1c2d'; // replaced at deploy: sed -i "s/b3d1c2d/$(git rev-parse --short HEAD)/g" v4.js
23
+ let _gitVer = 'ce29793'; // replaced at deploy: sed -i "s/ce29793/$(git rev-parse --short HEAD)/g" v4.js
24
24
  let _calls = []; // System Info call log
25
25
  let _sectionErrors = {}; // sectionId → error count
26
26
  let _generation = 0;
package/v6.js CHANGED
@@ -20,7 +20,7 @@
20
20
  let _activeTabId = '';
21
21
  let _sections = []; // { id, label, load, loaded, errors }
22
22
  let _title = '';
23
- let _gitVer = 'b3d1c2d'; // replaced at deploy: sed -i "s/b3d1c2d/$(git rev-parse --short HEAD)/g" v6.js
23
+ let _gitVer = 'ce29793'; // replaced at deploy: sed -i "s/ce29793/$(git rev-parse --short HEAD)/g" v6.js
24
24
  let _calls = []; // System Info call log
25
25
  let _sectionErrors = {}; // sectionId → error count
26
26
  let _generation = 0;
@@ -931,8 +931,100 @@ details.dk-call summary::-webkit-details-marker {
931
931
  } catch { return false; }
932
932
  }
933
933
 
934
+ // ── Filters a parent can ask for ──────────────────────────────────────────────
935
+ // DK_GET_FILTERS_INFO from the parent is answered with DK_FILTERS_INFO: the header controls a
936
+ // dashboard can be opened with, which is what the kit can write into a header control at its first boot. So
937
+ // the list follows that flow and nothing else: a control it cannot fill is not listed (radio,
938
+ // file, hidden, password, buttons; no id; a second control with an id already listed), a select
939
+ // lists only options a value may name, since an empty value is refused, and a select with none
940
+ // is left out. What exists when the dashboard boots is what counts: once it has booted, the list
941
+ // is the one taken at that moment, so options a section loads later by query are not in it, as
942
+ // they were not there for the fill. Only value and disabled are read again. It needs no token or
943
+ // period, so a parent can ask as soon as the frame has loaded.
944
+ const UNFILLABLE_TYPES = ['radio', 'file', 'hidden', 'password', 'button', 'submit', 'reset', 'image'];
945
+ let _bootFilters = null; // the list as it was when the dashboard booted
946
+ const FILTER_MAX_OPTIONS = 100;
947
+ const FILTER_MAX_TEXT = 500;
948
+
949
+ function _clip(s, n) {
950
+ s = String(s == null ? '' : s);
951
+ return s.length > n ? s.slice(0, n) : s;
952
+ }
953
+
954
+ function _filterLabel(el) {
955
+ const wrap = el.labels && el.labels[0];
956
+ if (wrap) {
957
+ const c = wrap.cloneNode(true);
958
+ c.querySelectorAll('input, select, textarea, .dk-tip, [aria-hidden="true"], script, style').forEach(function (n) { n.remove(); });
959
+ const t = c.textContent.replace(/\s+/g, ' ').trim();
960
+ if (t) return _clip(t, 200);
961
+ }
962
+ return _clip(el.getAttribute('aria-label') || el.getAttribute('placeholder') || '', 200);
963
+ }
964
+
965
+ function _controlValue(el) {
966
+ if (el.tagName === 'SELECT') {
967
+ return el.multiple ? Array.from(el.selectedOptions).map(function (o) { return o.value; }) : el.value;
968
+ }
969
+ return el.type === 'checkbox' ? el.checked : _clip(el.value, FILTER_MAX_TEXT);
970
+ }
971
+
972
+ function _dashboardFilters() {
973
+ const header = document.querySelector('[data-dk-header]');
974
+ const out = [];
975
+ if (!header) return out;
976
+ const seen = {};
977
+ header.querySelectorAll('input, select, textarea').forEach(function (el) {
978
+ if (!el.id || seen[el.id] || UNFILLABLE_TYPES.indexOf(el.type) >= 0) return;
979
+ const isSelect = el.tagName === 'SELECT';
980
+ const named = isSelect ? Array.from(el.options).filter(function (o) { return o.value !== ''; }) : null;
981
+ if (isSelect && !named.length) return;
982
+ seen[el.id] = true;
983
+ const f = {
984
+ id: el.id,
985
+ label: _filterLabel(el),
986
+ type: isSelect ? 'select' : el.tagName === 'TEXTAREA' ? 'textarea' : el.type,
987
+ required: el.required,
988
+ };
989
+ if (el.disabled) f.disabled = true;
990
+ if (!isSelect) {
991
+ ['placeholder', 'min', 'max'].forEach(function (a) {
992
+ if (el.getAttribute(a) !== null) f[a] = _clip(el.getAttribute(a), 100);
993
+ });
994
+ }
995
+ if (isSelect) {
996
+ f.multiple = el.multiple;
997
+ f.optionsTotal = named.length;
998
+ f.options = named.slice(0, FILTER_MAX_OPTIONS).map(function (o) {
999
+ return { value: _clip(o.value, 200), label: _clip(o.textContent.trim(), 200) };
1000
+ });
1001
+ }
1002
+ f.value = _controlValue(el);
1003
+ out.push(f);
1004
+ });
1005
+ return out;
1006
+ }
1007
+
1008
+ // What DK_FILTERS_INFO carries: the booted list once there is one, with the values as they are now.
1009
+ function _filtersInfo() {
1010
+ const list = _bootFilters || _dashboardFilters();
1011
+ const header = document.querySelector('[data-dk-header]');
1012
+ list.forEach(function (f) {
1013
+ const el = header ? header.querySelector('#' + CSS.escape(f.id)) : null;
1014
+ if (!el) return;
1015
+ f.value = _controlValue(el);
1016
+ if (el.disabled) f.disabled = true; else delete f.disabled;
1017
+ });
1018
+ return list;
1019
+ }
1020
+
1021
+
934
1022
  function _onMessage(e) {
935
1023
  if (!e.data || typeof e.data !== 'object') return;
1024
+ if (e.data.type === 'DK_GET_FILTERS_INFO') {
1025
+ window.parent.postMessage({ type: 'DK_FILTERS_INFO', filters: _filtersInfo() }, '*');
1026
+ return;
1027
+ }
936
1028
  if (e.data.type === 'RK_SNAPSHOT') { _snapshot(); return; }
937
1029
  if (e.data.type === 'CHAT_MODE') { document.documentElement.dataset.chatMode = 'true'; return; }
938
1030
  if (e.data.type !== 'CUSTOM_DASHBOARDS_PARAMS') return;
@@ -960,6 +1052,7 @@ details.dk-call summary::-webkit-details-marker {
960
1052
  }
961
1053
 
962
1054
  if (!_hasBooted && _authToken && _dateFromIso && _dateToIso && _timezone) {
1055
+ _bootFilters = _dashboardFilters(); // before any section runs
963
1056
  _hasBooted = true;
964
1057
  const wait = document.getElementById('dk-wait');
965
1058
  if (wait) wait.hidden = true;
package/wrapper.css CHANGED
@@ -52,6 +52,15 @@ header.topbar {
52
52
  flex-wrap: wrap;
53
53
  flex-shrink: 0;
54
54
  }
55
+ .platform-badge {
56
+ font-size: 14px;
57
+ font-weight: 700;
58
+ white-space: nowrap;
59
+ color: var(--text);
60
+ }
61
+ .platform-badge b {
62
+ color: var(--brand);
63
+ }
55
64
  header .filters {
56
65
  display: flex;
57
66
  gap: 10px;
package/wrapper.js CHANGED
@@ -1,17 +1,41 @@
1
1
  (function () {
2
2
  "use strict";
3
3
 
4
- const API_BASE = "https://bcproxy.draftplaza.com";
5
- const LS_TIMEZONE = "iqg.harness.timezone";
6
- const LS_TOKEN = "iqg.harness.token";
7
- const LS_USER = "iqg.harness.user";
8
- const LS_THEME = "iqg.harness.theme";
4
+ // Where each platform's BackOffice lives; the page picks one with <script src=".../wrapper.js" data-platform="poker">.
5
+ // No data-platform means igaming, as before. An unknown name is an error, never a guess.
6
+ // apiBase — sign-in (the BackOffice's own API)
7
+ // gatewayEndpoint — the SQL gateway a dashboard's queries go to, sent as `endpoint`
8
+ // storagePrefix — localStorage key prefix, so the two keep their own sign-in on one origin
9
+ // label — the name in the top bar, before the word "Platform", that says which BackOffice this page talks to
10
+ const PLATFORMS = {
11
+ igaming: {
12
+ label: "iGaming",
13
+ apiBase: "https://bcproxy.draftplaza.com",
14
+ gatewayEndpoint: "https://aidqgateway.draftplaza.com/aidq",
15
+ storagePrefix: "iqg.harness.",
16
+ },
17
+ poker: {
18
+ label: "Poker",
19
+ apiBase: "https://pokerreporting.pokerplaza.com",
20
+ gatewayEndpoint: "https://aidqgateway.pokerplaza.com/aidq",
21
+ storagePrefix: "iqg.harness.poker.",
22
+ },
23
+ };
24
+ const platformName = (document.currentScript && document.currentScript.dataset.platform) || "igaming";
25
+ const CFG = PLATFORMS[platformName];
26
+ if (!CFG) throw new Error('wrapper: data-platform "' + platformName + '" is not one of ' + Object.keys(PLATFORMS).join(", "));
27
+ const API_BASE = CFG.apiBase;
28
+ const LS_TIMEZONE = CFG.storagePrefix + "timezone";
29
+ const LS_TOKEN = CFG.storagePrefix + "token";
30
+ const LS_USER = CFG.storagePrefix + "user";
31
+ const LS_THEME = CFG.storagePrefix + "theme";
9
32
 
10
33
  // Capture srcdoc from the inline iframe before DOM rebuild.
11
34
  const _srcdoc = document.getElementById("iframe")?.getAttribute("srcdoc") || "";
12
35
 
13
36
  document.body.innerHTML = `
14
37
  <header class="topbar">
38
+ <span class="platform-badge" id="platformBadge"></span>
15
39
  <div class="filters">
16
40
  <label>Date
17
41
  <select id="datePreset">
@@ -94,6 +118,10 @@
94
118
  <div class="toast" id="toast"></div>`;
95
119
 
96
120
  document.getElementById("iframe").setAttribute("srcdoc", _srcdoc);
121
+ const badge = document.getElementById("platformBadge");
122
+ badge.innerHTML = "<b></b> Platform";
123
+ badge.firstChild.textContent = CFG.label;
124
+ document.title = CFG.label + " · " + document.title;
97
125
 
98
126
  // ── Helpers ──────────────────────────────────────────────────────────────────
99
127
 
@@ -179,6 +207,8 @@
179
207
  const hid = State.filters.hierarchyId;
180
208
  return {
181
209
  type: "CUSTOM_DASHBOARDS_PARAMS", token: State.token, theme: State.theme,
210
+ // The SQL gateway of this BackOffice; the kit would otherwise use its own default.
211
+ endpoint: CFG.gatewayEndpoint,
182
212
  filters: {
183
213
  dateFrom: r.dateFromIso, dateTo: r.dateToIso, timezone: tz,
184
214
  hierarchyId: hid == null || Number.isNaN(hid) ? null : Number(hid)