myio-js-library 0.1.558 → 0.1.560

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/index.cjs CHANGED
@@ -908,7 +908,7 @@ module.exports = __toCommonJS(src_exports);
908
908
  // package.json
909
909
  var package_default = {
910
910
  name: "myio-js-library",
911
- version: "0.1.558",
911
+ version: "0.1.560",
912
912
  description: "A clean, standalone JS SDK for MYIO projects",
913
913
  license: "MIT",
914
914
  repository: "github:gh-myio/myio-js-library",
@@ -53165,6 +53165,14 @@ var AlarmApiClient = class {
53165
53165
  return raw?.data ?? raw;
53166
53166
  }
53167
53167
  // -----------------------------------------------------------------------
53168
+ // Rule name (GCDR GET /rules/:id) — usado p/ alarmes cujo title veio como o UUID da regra
53169
+ // -----------------------------------------------------------------------
53170
+ async getRuleName(rulesApiBase, ruleId) {
53171
+ const raw = await this.request(`${rulesApiBase}/rules/${encodeURIComponent(ruleId)}`);
53172
+ const name = raw?.data?.name ?? raw?.name;
53173
+ return typeof name === "string" && name.trim() ? name.trim() : null;
53174
+ }
53175
+ // -----------------------------------------------------------------------
53168
53176
  // Alarm list (GET /alarms)
53169
53177
  // -----------------------------------------------------------------------
53170
53178
  async getAlarms(params = {}) {
@@ -53367,6 +53375,14 @@ function mapApiTopOffenders(items, deviceNameMap) {
53367
53375
  // computed by caller if needed
53368
53376
  }));
53369
53377
  }
53378
+ var RULE_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
53379
+ function resolveRulesApiBase(explicit, alarmsBase) {
53380
+ let base = explicit || globalThis?.MyIOUtils?.gcdrApiBaseUrl || "";
53381
+ if (!base && alarmsBase.includes("alarms-api")) base = alarmsBase.replace("alarms-api", "gcdr-api");
53382
+ if (!base) return "";
53383
+ base = String(base).replace(/\/+$/, "").replace(/\/api\/v1$/, "");
53384
+ return `${base}/api/v1`;
53385
+ }
53370
53386
  var AlarmServiceClass = class {
53371
53387
  client = new AlarmApiClient();
53372
53388
  cacheTtlMs = 3 * 60 * 1e3;
@@ -53375,6 +53391,10 @@ var AlarmServiceClass = class {
53375
53391
  alarmsCache = /* @__PURE__ */ new Map();
53376
53392
  statsCache = /* @__PURE__ */ new Map();
53377
53393
  trendCache = /* @__PURE__ */ new Map();
53394
+ // ruleId → nome da regra (null = não encontrada); sobrevive ao clearCache — nomes de regra quase não mudam
53395
+ ruleNameCache = /* @__PURE__ */ new Map();
53396
+ alarmsBaseUrl = "";
53397
+ rulesBaseUrl = "";
53378
53398
  // -----------------------------------------------------------------------
53379
53399
  // Availability (MTBF / MTTR per device)
53380
53400
  // -----------------------------------------------------------------------
@@ -53414,8 +53434,13 @@ var AlarmServiceClass = class {
53414
53434
  let summary;
53415
53435
  let totalPages = 1;
53416
53436
  let page = 1;
53437
+ const titleByRuleId = /* @__PURE__ */ new Map();
53417
53438
  while (page <= totalPages) {
53418
53439
  const response = await this.client.getAlarms({ ...params, page });
53440
+ for (const a of response.data || []) {
53441
+ const rid = a.metadata?.ruleId;
53442
+ if (rid && a.title && !RULE_ID_RE.test(a.title)) titleByRuleId.set(String(rid), a.title);
53443
+ }
53419
53444
  const pageData = (response.data || []).map((a) => mapApiAlarm(a, customerMap));
53420
53445
  allData = allData.concat(pageData);
53421
53446
  if (page === 1) {
@@ -53424,6 +53449,7 @@ var AlarmServiceClass = class {
53424
53449
  }
53425
53450
  page++;
53426
53451
  }
53452
+ await this.resolveRuleIdTitles(allData, titleByRuleId);
53427
53453
  const result = { data: allData, summary };
53428
53454
  this.alarmsCache.set(cacheKey, { data: result, timestamp: Date.now() });
53429
53455
  return result;
@@ -53534,11 +53560,47 @@ var AlarmServiceClass = class {
53534
53560
  * Set the Alarms API base URL (e.g. from MAIN_VIEW orchestrator settings).
53535
53561
  * Clears the cache so subsequent calls use the new endpoint.
53536
53562
  */
53537
- configure(baseUrl, cacheTtlMs, apiKey) {
53563
+ configure(baseUrl, cacheTtlMs, apiKey, rulesBaseUrl) {
53538
53564
  this.client.configure(baseUrl ?? "", apiKey);
53565
+ if (baseUrl) this.alarmsBaseUrl = baseUrl;
53566
+ if (rulesBaseUrl) this.rulesBaseUrl = rulesBaseUrl;
53539
53567
  if (cacheTtlMs && cacheTtlMs > 0) this.cacheTtlMs = cacheTtlMs;
53540
53568
  this.clearCache();
53541
53569
  }
53570
+ /**
53571
+ * Alarmes com title = UUID da regra recebem o nome legível: primeiro o título de
53572
+ * outro alarme da mesma regra; senão o nome da regra no GCDR (GET /rules/:id, cacheado).
53573
+ * Falha de lookup mantém o UUID (nunca quebra a listagem).
53574
+ */
53575
+ async resolveRuleIdTitles(alarms, titleByRuleId) {
53576
+ const pending = alarms.filter((a) => a.title && RULE_ID_RE.test(a.title));
53577
+ if (!pending.length) return;
53578
+ const rulesBase = resolveRulesApiBase(this.rulesBaseUrl, this.alarmsBaseUrl);
53579
+ const toFetch = [
53580
+ ...new Set(pending.map((a) => a.title).filter((rid) => !titleByRuleId.has(rid) && !this.ruleNameCache.has(rid)))
53581
+ ];
53582
+ if (rulesBase && toFetch.length) {
53583
+ const CONCURRENCY = 6;
53584
+ for (let i = 0; i < toFetch.length; i += CONCURRENCY) {
53585
+ await Promise.all(
53586
+ toFetch.slice(i, i + CONCURRENCY).map(async (rid) => {
53587
+ try {
53588
+ this.ruleNameCache.set(rid, await this.client.getRuleName(rulesBase, rid));
53589
+ } catch {
53590
+ this.ruleNameCache.set(rid, null);
53591
+ }
53592
+ })
53593
+ );
53594
+ }
53595
+ }
53596
+ for (const alarm of pending) {
53597
+ const rid = alarm.title;
53598
+ const name = titleByRuleId.get(rid) || this.ruleNameCache.get(rid);
53599
+ if (!name) continue;
53600
+ alarm.title = name;
53601
+ if (alarm.tags?.alarmType === rid) alarm.tags.alarmType = name;
53602
+ }
53603
+ }
53542
53604
  // -----------------------------------------------------------------------
53543
53605
  // Cache management
53544
53606
  // -----------------------------------------------------------------------
@@ -55150,6 +55212,14 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
55150
55212
  animation: afmSlideUp 0.2s ease;
55151
55213
  }
55152
55214
 
55215
+ .afm-modal.afm-modal--max {
55216
+ width: 100vw;
55217
+ max-width: 100vw;
55218
+ height: 100vh;
55219
+ max-height: 100vh;
55220
+ border-radius: 0;
55221
+ }
55222
+
55153
55223
  @keyframes afmSlideUp {
55154
55224
  from { transform: translateY(16px); opacity: 0.6; }
55155
55225
  to { transform: translateY(0); opacity: 1; }
@@ -58767,7 +58837,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
58767
58837
  background: #fff;
58768
58838
  border-radius: 14px;
58769
58839
  width: 92%;
58770
- max-width: 380px;
58840
+ max-width: 480px; /* espa\xE7o p/ o footer premium (holding \xB7 hora \xB7 vers\xE3o) */
58771
58841
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
58772
58842
  transform: translateY(-12px) scale(0.97);
58773
58843
  transition: transform 0.2s ease;
@@ -59251,7 +59321,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
59251
59321
  background: #fff;
59252
59322
  border-radius: 14px;
59253
59323
  width: 92%;
59254
- max-width: 360px;
59324
+ max-width: 480px; /* espa\xE7o p/ o footer premium (holding \xB7 hora \xB7 vers\xE3o) */
59255
59325
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
59256
59326
  transform: translateY(-10px) scale(0.97);
59257
59327
  transition: transform 0.2s ease;
@@ -69612,8 +69682,9 @@ var WelcomeModalView = class _WelcomeModalView {
69612
69682
  align-items: center;
69613
69683
  justify-content: center;
69614
69684
  padding: 12px 20px;
69615
- background: #5B2EBC;
69616
- border-top: 1px solid rgba(122, 47, 247, 0.3);
69685
+ /* paleta do tema do dashboard (createMyIOTheme \u2192 --myio-brand-700); roxo MYIO sem tema */
69686
+ background: var(--myio-brand-700, #5B2EBC);
69687
+ border-top: 1px solid color-mix(in srgb, var(--myio-brand-700, #7A2FF7) 30%, transparent);
69617
69688
  }
69618
69689
 
69619
69690
  .myio-welcome-footer-text {
@@ -69906,8 +69977,8 @@ var WelcomeModalView = class _WelcomeModalView {
69906
69977
  }
69907
69978
 
69908
69979
  .myio-welcome-modal--light .myio-welcome-footer {
69909
- background: #5B2EBC;
69910
- border-top-color: rgba(122, 47, 247, 0.3);
69980
+ background: var(--myio-brand-700, #5B2EBC);
69981
+ border-top-color: color-mix(in srgb, var(--myio-brand-700, #7A2FF7) 30%, transparent);
69911
69982
  }
69912
69983
 
69913
69984
  .myio-welcome-modal--light .myio-welcome-footer-text {
@@ -73752,13 +73823,13 @@ function getStyles() {
73752
73823
  color: #ffffff;
73753
73824
  cursor: pointer;
73754
73825
  overflow: hidden;
73755
- box-shadow: 0 0 0 1px rgba(62, 26, 125, 0.5), 0 4px 16px rgba(62, 26, 125, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.2);
73826
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--fc-compare-btn) 50%, transparent), 0 4px 16px color-mix(in srgb, var(--fc-compare-btn) 40%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.2);
73756
73827
  transition: var(--fc-transition);
73757
73828
  }
73758
73829
 
73759
73830
  .myio-footer-compare:hover {
73760
73831
  background: linear-gradient(135deg, var(--fc-compare-btn-hover) 0%, var(--fc-compare-btn) 100%);
73761
- box-shadow: 0 0 0 1px rgba(62, 26, 125, 0.7), 0 6px 24px rgba(62, 26, 125, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.3);
73832
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--fc-compare-btn) 70%, transparent), 0 6px 24px color-mix(in srgb, var(--fc-compare-btn) 60%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.3);
73762
73833
  transform: translateY(-2px);
73763
73834
  }
73764
73835
 
@@ -73921,6 +73992,71 @@ function getStyles() {
73921
73992
  text-shadow: none;
73922
73993
  }
73923
73994
 
73995
+ /* ==========================================
73996
+ Brand (paleta do dashboard): fundo s\xF3lido na cor do tema, conte\xFAdo branco
73997
+ ========================================== */
73998
+
73999
+ .myio-footer-component--brand {
74000
+ box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
74001
+ backdrop-filter: none;
74002
+ -webkit-backdrop-filter: none;
74003
+ }
74004
+ .myio-footer-component--brand .myio-footer-chip {
74005
+ background: var(--fc-chip-bg);
74006
+ box-shadow: none;
74007
+ }
74008
+ .myio-footer-component--brand .myio-footer-chip:hover {
74009
+ background: rgba(255, 255, 255, 0.2);
74010
+ border-color: rgba(255, 255, 255, 0.32);
74011
+ box-shadow: none;
74012
+ transform: translateY(-2px);
74013
+ }
74014
+ .myio-footer-component--brand .myio-footer-chip-remove {
74015
+ background: rgba(255, 255, 255, 0.1);
74016
+ border-color: rgba(255, 255, 255, 0.2);
74017
+ color: rgba(255, 255, 255, 0.85);
74018
+ }
74019
+ .myio-footer-component--brand .myio-footer-chip-remove:hover {
74020
+ background: rgba(255, 107, 107, 0.22);
74021
+ border-color: rgba(255, 107, 107, 0.5);
74022
+ color: #fff;
74023
+ }
74024
+ .myio-footer-component--brand .myio-footer-meta {
74025
+ background: var(--fc-meta-bg);
74026
+ box-shadow: none;
74027
+ cursor: default;
74028
+ }
74029
+ .myio-footer-component--brand .myio-footer-meta-title,
74030
+ .myio-footer-component--brand .myio-footer-totals,
74031
+ .myio-footer-component--brand .myio-footer-chip-name,
74032
+ .myio-footer-component--brand .myio-footer-chip-value {
74033
+ text-shadow: none;
74034
+ }
74035
+ .myio-footer-component--brand .myio-footer-empty {
74036
+ background: var(--fc-empty-bg);
74037
+ color: rgba(255, 255, 255, 0.85);
74038
+ text-shadow: none;
74039
+ animation: none;
74040
+ }
74041
+ .myio-footer-component--brand .myio-footer-clear-btn {
74042
+ background: var(--fc-clear-btn);
74043
+ border-color: rgba(255, 255, 255, 0.24);
74044
+ }
74045
+ .myio-footer-component--brand .myio-footer-clear-btn:hover {
74046
+ background: rgba(255, 255, 255, 0.2);
74047
+ box-shadow: none;
74048
+ }
74049
+ .myio-footer-component--brand .myio-footer-clear-btn:disabled {
74050
+ background: rgba(255, 255, 255, 0.08);
74051
+ border-color: rgba(255, 255, 255, 0.14);
74052
+ color: rgba(255, 255, 255, 0.45);
74053
+ }
74054
+ .myio-footer-component--brand .myio-footer-compare:disabled {
74055
+ background: rgba(255, 255, 255, 0.14);
74056
+ color: rgba(255, 255, 255, 0.55);
74057
+ opacity: 1;
74058
+ }
74059
+
73924
74060
  /* ==========================================
73925
74061
  Responsive
73926
74062
  ========================================== */
@@ -74001,6 +74137,7 @@ var ChipRenderer = class {
74001
74137
  const chip = document.createElement("div");
74002
74138
  chip.className = "myio-footer-chip";
74003
74139
  chip.dataset.entityId = entity.id;
74140
+ chip.dataset.fullName = entity.name || "";
74004
74141
  const content = document.createElement("div");
74005
74142
  content.className = "myio-footer-chip-content";
74006
74143
  const name = document.createElement("span");
@@ -74111,7 +74248,43 @@ var FooterView = class {
74111
74248
  getThemeConfig() {
74112
74249
  const themeConfig = this.theme === "dark" ? this.configTemplate.darkMode : this.configTemplate.lightMode;
74113
74250
  const defaults = this.theme === "dark" ? DEFAULT_DARK_THEME2 : DEFAULT_LIGHT_THEME2;
74114
- return { ...defaults, ...themeConfig };
74251
+ return { ...defaults, ...themeConfig, ...this.getBrandOverrides() };
74252
+ }
74253
+ /** Accent do tema do dashboard (createMyIOTheme → window.MyIOUtils.theme), se houver. */
74254
+ getDashboardAccent() {
74255
+ const theme = window.MyIOUtils?.theme;
74256
+ if (!theme?.accent) return null;
74257
+ return { accent: theme.accent, accentText: theme.accentText || "#ffffff" };
74258
+ }
74259
+ /**
74260
+ * Com paleta do dashboard o footer segue o padrão do dashboard de shopping: fundo sólido na cor
74261
+ * do tema e conteúdo branco (mesma identidade do header/tabs).
74262
+ */
74263
+ getBrandOverrides() {
74264
+ const brand = this.getDashboardAccent();
74265
+ if (!brand) return {};
74266
+ const a = brand.accent;
74267
+ return {
74268
+ primaryColor: a,
74269
+ primaryHoverColor: `color-mix(in srgb, ${a} 85%, #fff)`,
74270
+ primaryDarkColor: `color-mix(in srgb, ${a} 70%, #000)`,
74271
+ backgroundGradientStart: a,
74272
+ backgroundGradientEnd: a,
74273
+ borderTopColor: "rgba(255, 255, 255, 0.18)",
74274
+ textColor: brand.accentText,
74275
+ mutedTextColor: "rgba(255, 255, 255, 0.75)",
74276
+ chipBackgroundColor: "rgba(255, 255, 255, 0.12)",
74277
+ chipBorderColor: "rgba(255, 255, 255, 0.24)",
74278
+ chipTextColor: brand.accentText,
74279
+ compareButtonColor: `color-mix(in srgb, ${a} 55%, #0f172a)`,
74280
+ compareButtonHoverColor: `color-mix(in srgb, ${a} 70%, #0f172a)`,
74281
+ clearButtonColor: "rgba(255, 255, 255, 0.12)",
74282
+ clearButtonTextColor: brand.accentText,
74283
+ metaBackgroundColor: "rgba(255, 255, 255, 0.10)",
74284
+ metaBorderColor: "rgba(255, 255, 255, 0.24)",
74285
+ emptyBackgroundColor: "rgba(255, 255, 255, 0.06)",
74286
+ emptyBorderColor: "rgba(255, 255, 255, 0.35)"
74287
+ };
74115
74288
  }
74116
74289
  /**
74117
74290
  * Apply theme colors as CSS custom properties
@@ -74144,7 +74317,8 @@ var FooterView = class {
74144
74317
  */
74145
74318
  getClassName() {
74146
74319
  const base = "myio-footer-component";
74147
- return this.theme === "light" ? `${base} ${base}--light` : base;
74320
+ const brand = this.getDashboardAccent() ? ` ${base}--brand` : "";
74321
+ return (this.theme === "light" ? `${base} ${base}--light` : base) + brand;
74148
74322
  }
74149
74323
  /**
74150
74324
  * Render the footer component
@@ -74202,6 +74376,27 @@ var FooterView = class {
74202
74376
  * Bind event listeners
74203
74377
  */
74204
74378
  bindEvents() {
74379
+ this.footerEl.addEventListener("mouseover", (e) => {
74380
+ const target = e.target;
74381
+ if (target.closest(".myio-footer-chip-remove")) return hideFooterZoom();
74382
+ const chip = target.closest(".myio-footer-chip");
74383
+ if (chip) {
74384
+ const name = chip.dataset.fullName || chip.querySelector(".myio-footer-chip-name")?.textContent || "";
74385
+ const value = chip.querySelector(".myio-footer-chip-value")?.textContent || "";
74386
+ showFooterZoom(chip, name, value);
74387
+ return;
74388
+ }
74389
+ const meta = target.closest(".myio-footer-meta");
74390
+ if (meta) {
74391
+ showFooterZoom(meta, "Sele\xE7\xE3o", this.totalsEl?.textContent || "");
74392
+ }
74393
+ });
74394
+ this.footerEl.addEventListener("mouseout", (e) => {
74395
+ const to = e.relatedTarget;
74396
+ const from = e.target.closest(".myio-footer-chip, .myio-footer-meta");
74397
+ if (from && to && from.contains(to)) return;
74398
+ hideFooterZoom();
74399
+ });
74205
74400
  window.addEventListener("myio:theme-change", ((e) => {
74206
74401
  this.setTheme(e.detail.mode);
74207
74402
  }));
@@ -74283,6 +74478,7 @@ var FooterView = class {
74283
74478
  */
74284
74479
  renderDock(entities) {
74285
74480
  if (!this.dockEl) return;
74481
+ hideFooterZoom();
74286
74482
  if (entities.length === 0) {
74287
74483
  const emptyEl = this.chipRenderer.createEmptyState(this.emptyMessage);
74288
74484
  this.dockEl.replaceChildren(emptyEl);
@@ -74353,6 +74549,7 @@ var FooterView = class {
74353
74549
  */
74354
74550
  destroy() {
74355
74551
  this.eventHandlers.clear();
74552
+ hideFooterZoom();
74356
74553
  if (this.footerEl.parentNode) {
74357
74554
  this.footerEl.parentNode.removeChild(this.footerEl);
74358
74555
  }
@@ -74362,6 +74559,58 @@ var FooterView = class {
74362
74559
  this.clearBtnEl = null;
74363
74560
  }
74364
74561
  };
74562
+ var FOOTER_ZOOM_ID = "myio-footer-chip-zoom";
74563
+ function ensureFooterZoomEl() {
74564
+ if (!document.getElementById("myio-footer-chip-zoom-styles")) {
74565
+ const st = document.createElement("style");
74566
+ st.id = "myio-footer-chip-zoom-styles";
74567
+ st.textContent = `
74568
+ #${FOOTER_ZOOM_ID} {
74569
+ position: fixed; z-index: 2147483000; pointer-events: none;
74570
+ display: flex; flex-direction: column; gap: 2px;
74571
+ min-width: 140px; max-width: 360px; padding: 10px 14px;
74572
+ background: var(--myio-brand-700, #3e1a7d); color: #fff;
74573
+ border: 1px solid rgba(255, 255, 255, 0.28); border-radius: 10px;
74574
+ box-shadow: 0 10px 28px rgba(15, 23, 42, 0.35);
74575
+ font-family: 'Nunito', system-ui, sans-serif;
74576
+ transform: translate(-50%, 6px) scale(0.92); transform-origin: 50% 100%;
74577
+ opacity: 0; transition: opacity 0.12s ease, transform 0.12s ease;
74578
+ }
74579
+ #${FOOTER_ZOOM_ID}.show { opacity: 1; transform: translate(-50%, 0) scale(1); }
74580
+ #${FOOTER_ZOOM_ID} .zoom-name {
74581
+ font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.01em;
74582
+ white-space: normal; word-break: break-word; line-height: 1.2;
74583
+ }
74584
+ #${FOOTER_ZOOM_ID} .zoom-value {
74585
+ font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.15;
74586
+ }
74587
+ `;
74588
+ document.head.appendChild(st);
74589
+ }
74590
+ let el2 = document.getElementById(FOOTER_ZOOM_ID);
74591
+ if (!el2) {
74592
+ el2 = document.createElement("div");
74593
+ el2.id = FOOTER_ZOOM_ID;
74594
+ el2.setAttribute("role", "tooltip");
74595
+ el2.innerHTML = '<span class="zoom-name"></span><span class="zoom-value"></span>';
74596
+ document.body.appendChild(el2);
74597
+ }
74598
+ return el2;
74599
+ }
74600
+ function showFooterZoom(anchor, name, value) {
74601
+ const el2 = ensureFooterZoomEl();
74602
+ el2.querySelector(".zoom-name").textContent = name || "";
74603
+ el2.querySelector(".zoom-value").textContent = value || "";
74604
+ const r = anchor.getBoundingClientRect();
74605
+ const half = 75;
74606
+ const left = Math.min(Math.max(r.left + r.width / 2, half + 8), window.innerWidth - half - 8);
74607
+ el2.style.left = `${left}px`;
74608
+ el2.style.bottom = `${window.innerHeight - r.top + 8}px`;
74609
+ el2.classList.add("show");
74610
+ }
74611
+ function hideFooterZoom() {
74612
+ document.getElementById(FOOTER_ZOOM_ID)?.classList.remove("show");
74613
+ }
74365
74614
 
74366
74615
  // src/components/footer/AlertDialogs.ts
74367
74616
  var currentOverlay = null;
@@ -74857,10 +75106,12 @@ function openOperationalComparisonModal(params) {
74857
75106
  endDate: params.endDate,
74858
75107
  devices: params.devices || []
74859
75108
  };
74860
- render2(wrapper, modalId, state6);
74861
- bindEvents3(wrapper, modalId, state6, params.onClose);
75109
+ const ref = { chrome: null };
75110
+ mount2(wrapper, modalId, state6, ref, params.onClose);
74862
75111
  return {
74863
75112
  destroy: () => {
75113
+ ref.chrome?.destroy();
75114
+ ref.chrome = null;
74864
75115
  wrapper.remove();
74865
75116
  params.onClose?.();
74866
75117
  }
@@ -75263,30 +75514,39 @@ function getThemeColors3(theme) {
75263
75514
  accent: "#7c3aed"
75264
75515
  };
75265
75516
  }
75266
- function bindEvents3(container, modalId, state6, onClose) {
75267
- const closeBtn = container.querySelector(`#${modalId}-close`);
75268
- const themeBtn = container.querySelector(`#${modalId}-theme`);
75269
- const maxBtn = container.querySelector(`#${modalId}-max`);
75517
+ function mount2(container, modalId, state6, ref, onClose) {
75518
+ ref.chrome?.destroy();
75519
+ render2(container, modalId, state6);
75520
+ const content = container.querySelector(".myio-op-compare-content");
75270
75521
  const overlay = container.querySelector(".myio-op-compare-overlay");
75271
75522
  const close = () => {
75523
+ ref.chrome?.destroy();
75524
+ ref.chrome = null;
75272
75525
  container.remove();
75273
75526
  onClose?.();
75274
75527
  };
75275
- closeBtn?.addEventListener("click", close);
75276
- if (overlay) {
75277
- overlay.addEventListener("click", (e) => {
75278
- if (e.target === overlay) close();
75279
- });
75280
- }
75281
- themeBtn?.addEventListener("click", () => {
75282
- state6.theme = state6.theme === "dark" ? "light" : "dark";
75283
- render2(container, modalId, state6);
75284
- bindEvents3(container, modalId, state6, onClose);
75528
+ ref.chrome = createPremiumModalChrome({
75529
+ modalId: `${modalId}-chrome`,
75530
+ icon: "\u{1F4CA}",
75531
+ title: `Compara\xE7\xE3o Operacional (${state6.devices.length} equipamentos)`,
75532
+ modalEl: content,
75533
+ headerSlot: content.querySelector(".myio-op-compare-header"),
75534
+ theme: state6.theme,
75535
+ showThemeToggle: true,
75536
+ maximized: state6.isMaximized,
75537
+ maximizedClass: "maximized",
75538
+ headerRadius: "12px 12px 0 0",
75539
+ onClose: close,
75540
+ onThemeChange: (t) => {
75541
+ state6.theme = t;
75542
+ mount2(container, modalId, state6, ref, onClose);
75543
+ },
75544
+ onMaximizeChange: (m) => {
75545
+ state6.isMaximized = m;
75546
+ }
75285
75547
  });
75286
- maxBtn?.addEventListener("click", () => {
75287
- state6.isMaximized = !state6.isMaximized;
75288
- render2(container, modalId, state6);
75289
- bindEvents3(container, modalId, state6, onClose);
75548
+ overlay?.addEventListener("click", (e) => {
75549
+ if (e.target === overlay) close();
75290
75550
  });
75291
75551
  }
75292
75552
 
@@ -79761,7 +80021,7 @@ function openGoalsPanel(params) {
79761
80021
  state6.conflictVersion = null;
79762
80022
  render3();
79763
80023
  }
79764
- function mount2() {
80024
+ function mount3() {
79765
80025
  const existing = document.getElementById(ROOT_ID2);
79766
80026
  if (existing) existing.remove();
79767
80027
  rootEl = document.createElement("div");
@@ -80836,7 +81096,7 @@ ${state6.year}-03-15T08,15`)}">${esc4(state6.csv)}</textarea>
80836
81096
  }
80837
81097
  };
80838
81098
  }
80839
- mount2();
81099
+ mount3();
80840
81100
  return instance;
80841
81101
  }
80842
81102
 
@@ -118097,6 +118357,10 @@ var MenuView = class {
118097
118357
  // Tab being previewed in modal
118098
118358
  // Filter Modal (RFC-0121 ModalHeader controller pattern)
118099
118359
  filterModalHeaderController = null;
118360
+ /** Footer premium (holding · hora · versão) da modal Filtro de Shoppings — criado a cada abertura */
118361
+ filterModalFooter = null;
118362
+ /** Idem para a modal "Selecione a visualização" */
118363
+ unifiedModalFooter = null;
118100
118364
  // Unified Navigation Modal (RFC-0121 ModalHeader controller pattern)
118101
118365
  unifiedModalHeaderController = null;
118102
118366
  // RFC-0126: State for unified modal when controller fails (same as filter modal)
@@ -118327,14 +118591,14 @@ var MenuView = class {
118327
118591
  ========================================== */
118328
118592
 
118329
118593
  .myio-toolbar-item--nav {
118330
- background: var(--menu-nav-bg, #3e1a7d);
118594
+ background: var(--menu-nav-bg, var(--myio-brand-700, #3e1a7d));
118331
118595
  color: var(--menu-nav-color, #ffffff);
118332
118596
  font-weight: 600;
118333
118597
  padding: 8px 16px;
118334
118598
  }
118335
118599
 
118336
118600
  .myio-toolbar-item--nav:hover {
118337
- background: var(--menu-nav-hover-bg, #4c2391);
118601
+ background: var(--menu-nav-hover-bg, color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 85%, #fff));
118338
118602
  }
118339
118603
 
118340
118604
  .myio-toolbar-item--nav .nav-label {
@@ -118348,7 +118612,7 @@ var MenuView = class {
118348
118612
  ========================================== */
118349
118613
 
118350
118614
  .myio-toolbar-item--filter {
118351
- color: var(--menu-filter-color, #6366f1);
118615
+ color: var(--menu-filter-color, var(--myio-brand-700, #6366f1));
118352
118616
  }
118353
118617
 
118354
118618
  .myio-toolbar-item--filter:hover {
@@ -118371,7 +118635,7 @@ var MenuView = class {
118371
118635
  min-width: 340px;
118372
118636
  padding: 0;
118373
118637
  background: transparent;
118374
- border: 2px solid var(--menu-date-border, #3e1a7d);
118638
+ border: 2px solid var(--menu-date-border, var(--myio-brand-700, #3e1a7d));
118375
118639
  border-radius: 8px;
118376
118640
  overflow: hidden;
118377
118641
  transition: all 0.2s ease;
@@ -118379,17 +118643,17 @@ var MenuView = class {
118379
118643
  }
118380
118644
 
118381
118645
  .myio-toolbar-item--date:hover {
118382
- border-color: var(--menu-date-hover-border, #5a2d9e);
118383
- box-shadow: 0 0 0 3px rgba(62, 26, 125, 0.15);
118646
+ border-color: var(--menu-date-hover-border, color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 80%, #fff));
118647
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 15%, transparent);
118384
118648
  }
118385
118649
 
118386
118650
  .myio-toolbar-item--date:focus-within {
118387
- border-color: var(--menu-date-focus-border, #3e1a7d);
118388
- box-shadow: 0 0 0 3px rgba(62, 26, 125, 0.25);
118651
+ border-color: var(--menu-date-focus-border, var(--myio-brand-700, #3e1a7d));
118652
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 25%, transparent);
118389
118653
  }
118390
118654
 
118391
118655
  .myio-toolbar-item--date .ico {
118392
- background: var(--menu-date-icon-bg, #3e1a7d);
118656
+ background: var(--menu-date-icon-bg, var(--myio-brand-700, #3e1a7d));
118393
118657
  color: white;
118394
118658
  padding: 0 12px;
118395
118659
  margin: 0;
@@ -119689,7 +119953,10 @@ var MenuView = class {
119689
119953
  modalId: "menuUnified",
119690
119954
  theme: "dark",
119691
119955
  isMaximized: false,
119692
- showThemeToggle: true,
119956
+ primaryColor: MYIO_ACCENT,
119957
+ // padrão das modais premium: acento do tema do dashboard
119958
+ showThemeToggle: false,
119959
+ // corpo é sempre claro — o toggle só trocava a cor do header
119693
119960
  showMaximize: true,
119694
119961
  showClose: true,
119695
119962
  borderRadius: "16px 16px 0 0"
@@ -119989,7 +120256,10 @@ var MenuView = class {
119989
120256
  modalId: "menuFilter",
119990
120257
  theme: this.getFilterModalDefaultTheme(),
119991
120258
  isMaximized: false,
119992
- showThemeToggle: true,
120259
+ primaryColor: MYIO_ACCENT,
120260
+ // padrão das modais premium: acento do tema do dashboard
120261
+ showThemeToggle: false,
120262
+ // a modal não tem estilos escuros — o toggle só trocava a cor do header
119993
120263
  showMaximize: true,
119994
120264
  showClose: true,
119995
120265
  borderRadius: "12px 12px 0 0"
@@ -120328,6 +120598,9 @@ var MenuView = class {
120328
120598
  modal.classList.add("is-open");
120329
120599
  btn?.setAttribute("aria-expanded", "true");
120330
120600
  this.updateUnifiedModalActiveStates();
120601
+ const content = modal.querySelector(".myio-unified-modal-content");
120602
+ this.unifiedModalFooter?.destroy();
120603
+ this.unifiedModalFooter = content ? this.createPremiumFooter(content) : null;
120331
120604
  this.bindUnifiedModalButtonsFallback();
120332
120605
  if (this.configTemplate.enableDebugMode) {
120333
120606
  console.log("[MenuView] Unified modal opened");
@@ -120343,6 +120616,8 @@ var MenuView = class {
120343
120616
  if (modal) {
120344
120617
  modal.classList.remove("is-open");
120345
120618
  btn?.setAttribute("aria-expanded", "false");
120619
+ this.unifiedModalFooter?.destroy();
120620
+ this.unifiedModalFooter = null;
120346
120621
  this.unifiedModalHeaderController?.reset();
120347
120622
  if (this.unifiedModalHeaderController) {
120348
120623
  this.applyUnifiedModalTheme(this.unifiedModalHeaderController.getTheme());
@@ -120608,6 +120883,7 @@ var MenuView = class {
120608
120883
  if (modal) {
120609
120884
  modal.classList.add("is-open");
120610
120885
  this.renderFilterList();
120886
+ this.mountFilterModalFooter();
120611
120887
  requestAnimationFrame(() => {
120612
120888
  this.ensureFilterModalHeaderController();
120613
120889
  this.bindFilterModalButtonsFallback();
@@ -120626,12 +120902,32 @@ var MenuView = class {
120626
120902
  if (modal) {
120627
120903
  modal.classList.remove("is-open");
120628
120904
  }
120905
+ this.filterModalFooter?.destroy();
120906
+ this.filterModalFooter = null;
120629
120907
  this.filterModalHeaderController?.reset();
120630
120908
  if (this.filterModalHeaderController) {
120631
120909
  this.applyFilterModalTheme(this.filterModalHeaderController.getTheme());
120632
120910
  this.applyFilterModalMaximize(this.filterModalHeaderController.isMaximized());
120633
120911
  }
120634
120912
  }
120913
+ /** Footer premium padrão (holding · hora · versão | Powered by MYIO) anexado ao fim do card. */
120914
+ createPremiumFooter(card) {
120915
+ const footer = createModalFooter({
120916
+ customerName: resolveDashboardCustomerName(),
120917
+ libVersion: { current: window.MyIOLibrary?.version },
120918
+ themeMode: "light"
120919
+ });
120920
+ footer.element.style.flexShrink = "0";
120921
+ card.appendChild(footer.element);
120922
+ return footer;
120923
+ }
120924
+ /** Footer premium no fim do card (abaixo dos botões Cancelar/Aplicar). */
120925
+ mountFilterModalFooter() {
120926
+ const card = this.root.querySelector(".myio-menu-filter-modal-card");
120927
+ if (!card) return;
120928
+ this.filterModalFooter?.destroy();
120929
+ this.filterModalFooter = this.createPremiumFooter(card);
120930
+ }
120635
120931
  getFilterModalDefaultTheme() {
120636
120932
  return "dark";
120637
120933
  }
@@ -124412,6 +124708,16 @@ function generateFilterModalStyles(containerId, modalClass, primaryColor) {
124412
124708
  opacity: 0.5;
124413
124709
  }
124414
124710
  #${containerId} .filter-tab:hover { opacity: 0.8; transform: translateY(-1px); }
124711
+ /* Categorias (deviceProfile): r\xF3tulos longos s\xE3o encurtados (nome completo no title) */
124712
+ #${containerId} .filter-tab--category {
124713
+ max-width: 100%;
124714
+ background: color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 8%, #fff);
124715
+ color: var(--myio-brand-700, #3e1a7d);
124716
+ }
124717
+ #${containerId}[data-theme="dark"] .filter-tab--category {
124718
+ background: color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 30%, #1e293b);
124719
+ color: #e2e8f0;
124720
+ }
124415
124721
  #${containerId} .filter-tab.active { opacity: 1; box-shadow: 0 1px 3px rgba(0,0,0,0.15); }
124416
124722
  #${containerId} .filter-tab[data-filter="all"] { background: #e2e8f0; color: #475569; }
124417
124723
  #${containerId} .filter-tab[data-filter="online"],
@@ -124695,6 +125001,28 @@ function generateFilterModalStyles(containerId, modalClass, primaryColor) {
124695
125001
  }
124696
125002
 
124697
125003
  // src/components/filter-modal/FilterModalView.ts
125004
+ var CATEGORY_META = {
125005
+ ELEVADOR: { label: "Elevadores", icon: "\u{1F6D7}" },
125006
+ ESCADA_ROLANTE: { label: "Escadas Rolantes", icon: "\u{1F4F6}" },
125007
+ CHILLER: { label: "Chillers", icon: "\u2744\uFE0F" },
125008
+ FANCOIL: { label: "Fancoils", icon: "\u{1F300}" },
125009
+ BOMBA_CAG: { label: "Bombas CAG", icon: "\u{1F4A7}" },
125010
+ BOMBA_HIDRAULICA: { label: "Bombas Hidr\xE1ulicas", icon: "\u{1F4A7}" },
125011
+ BOMBA_INCENDIO: { label: "Bombas de Inc\xEAndio", icon: "\u{1F9EF}" },
125012
+ MOTOR: { label: "Motores", icon: "\u2699\uFE0F" },
125013
+ "3F_MEDIDOR": { label: "Lojas", icon: "\u{1F3EA}" },
125014
+ ENTRADA: { label: "Entrada", icon: "\u{1F50C}" },
125015
+ HIDROMETRO: { label: "Hidr\xF4metros", icon: "\u{1F4A7}" },
125016
+ HIDROMETRO_AREA_COMUM: { label: "Hidr\xF4metros \xC1rea Comum", icon: "\u{1F4A7}" },
125017
+ HIDROMETRO_SHOPPING: { label: "Hidr\xF4metros Entrada", icon: "\u{1F4A7}" },
125018
+ TERMOSTATO: { label: "Termostatos", icon: "\u{1F321}\uFE0F" }
125019
+ };
125020
+ function categoryMeta(profile) {
125021
+ const key = String(profile || "").toUpperCase();
125022
+ if (CATEGORY_META[key]) return CATEGORY_META[key];
125023
+ const label = key.split(/[_\s]+/).filter(Boolean).map((w) => w.charAt(0) + w.slice(1).toLowerCase()).join(" ");
125024
+ return { label: label || key, icon: "\u{1F3F7}\uFE0F" };
125025
+ }
124698
125026
  var FilterModalView = class {
124699
125027
  containerId;
124700
125028
  widgetName;
@@ -124711,6 +125039,13 @@ var FilterModalView = class {
124711
125039
  onApply;
124712
125040
  onClose;
124713
125041
  globalContainer = null;
125042
+ /** Abas de categoria (uma por deviceProfile presente nos itens) — recriadas a cada open() */
125043
+ dynamicTabs = [];
125044
+ dynamicIcons = {};
125045
+ dynamicTitles = {};
125046
+ groups = FILTER_GROUPS;
125047
+ /** Moldura padrão (header no acento do tema + maximizar/fechar + footer premium) */
125048
+ chrome = null;
124714
125049
  escHandler = null;
124715
125050
  currentItems = [];
124716
125051
  constructor(params) {
@@ -124732,7 +125067,7 @@ var FilterModalView = class {
124732
125067
  });
124733
125068
  }
124734
125069
  generateFilterTabsHTML(counts) {
124735
- const allTab = this.filterTabs.find((t) => t.id === "all");
125070
+ const allTab = this.allTabs().find((t) => t.id === "all");
124736
125071
  let html = "";
124737
125072
  if (allTab) {
124738
125073
  const icon2 = FILTER_TAB_ICONS["all"] || "";
@@ -124744,19 +125079,19 @@ var FilterModalView = class {
124744
125079
  </div>
124745
125080
  `;
124746
125081
  }
124747
- FILTER_GROUPS.forEach((group) => {
124748
- const groupTabs = this.filterTabs.filter((t) => group.filters.includes(t.id));
125082
+ this.groups.forEach((group) => {
125083
+ const groupTabs = this.allTabs().filter((t) => group.filters.includes(t.id));
124749
125084
  if (groupTabs.length === 0) return;
124750
125085
  html += `
124751
125086
  <div class="filter-group">
124752
125087
  <span class="filter-group-label">${group.label}</span>
124753
125088
  <div class="filter-group-tabs">
124754
125089
  ${groupTabs.map((tab) => {
124755
- const icon2 = FILTER_TAB_ICONS[tab.id] || "";
125090
+ const icon2 = FILTER_TAB_ICONS[tab.id] || this.dynamicIcons[tab.id] || "";
124756
125091
  const count = counts[tab.id] || 0;
124757
125092
  const expandBtn = count > 0 ? `<button class="filter-tab-expand" data-expand-filter="${tab.id}" title="Ver dispositivos">+</button>` : "";
124758
125093
  return `
124759
- <button class="filter-tab active" data-filter="${tab.id}">
125094
+ <button class="filter-tab active${tab.id.startsWith("cat_") ? " filter-tab--category" : ""}" data-filter="${tab.id}" title="${this.dynamicTitles[tab.id] || tab.label}">
124760
125095
  ${icon2} ${tab.label} (<span id="count${tab.id.charAt(0).toUpperCase() + tab.id.slice(1)}">${count}</span>)${expandBtn}
124761
125096
  </button>
124762
125097
  `;
@@ -124826,9 +125161,29 @@ var FilterModalView = class {
124826
125161
  </div>
124827
125162
  `;
124828
125163
  }
125164
+ allTabs() {
125165
+ return [...this.filterTabs, ...this.dynamicTabs];
125166
+ }
125167
+ /** Grupo "Categoria": uma aba por deviceProfile presente nos itens (ex.: Elevadores, Escadas Rolantes). */
125168
+ buildCategoryTabs(items) {
125169
+ const profileOf3 = (item) => String(item.deviceProfile || "").trim().toUpperCase();
125170
+ const profiles = Array.from(new Set(items.map(profileOf3).filter(Boolean)));
125171
+ profiles.sort((a, b) => categoryMeta(a).label.localeCompare(categoryMeta(b).label, "pt-BR"));
125172
+ this.dynamicIcons = {};
125173
+ this.dynamicTitles = {};
125174
+ this.dynamicTabs = profiles.map((profile) => {
125175
+ const id = `cat_${profile.replace(/[^A-Za-z0-9]/g, "_")}`;
125176
+ const meta = categoryMeta(profile);
125177
+ this.dynamicIcons[id] = meta.icon;
125178
+ this.dynamicTitles[id] = meta.label;
125179
+ const label = meta.label.length > 22 ? `${meta.label.slice(0, 21).trimEnd()}\u2026` : meta.label;
125180
+ return { id, label, filter: (item) => profileOf3(item) === profile };
125181
+ });
125182
+ this.groups = this.dynamicTabs.length ? [...FILTER_GROUPS, { id: "category", label: "Categoria", filters: this.dynamicTabs.map((t) => t.id) }] : FILTER_GROUPS;
125183
+ }
124829
125184
  calculateCounts(items) {
124830
125185
  const counts = {};
124831
- this.filterTabs.forEach((tab) => {
125186
+ this.allTabs().forEach((tab) => {
124832
125187
  counts[tab.id] = items.filter(tab.filter).length;
124833
125188
  });
124834
125189
  return counts;
@@ -124969,7 +125324,7 @@ var FilterModalView = class {
124969
125324
  bindFilterTabHandlers(modal, items) {
124970
125325
  const filterTabsEl = modal.querySelectorAll(".filter-tab");
124971
125326
  const getFilterFn = (filterId) => {
124972
- const tabConfig = this.filterTabs.find((t) => t.id === filterId);
125327
+ const tabConfig = this.allTabs().find((t) => t.id === filterId);
124973
125328
  return tabConfig ? tabConfig.filter : () => false;
124974
125329
  };
124975
125330
  const applyActiveFilters = () => {
@@ -124978,16 +125333,16 @@ var FilterModalView = class {
124978
125333
  return groupActiveFilters.some((filterId) => getFilterFn(filterId)(item));
124979
125334
  };
124980
125335
  let filteredItems = [...items];
124981
- for (let i = 0; i < FILTER_GROUPS.length; i++) {
124982
- const group = FILTER_GROUPS[i];
125336
+ for (let i = 0; i < this.groups.length; i++) {
125337
+ const group = this.groups[i];
124983
125338
  const activeInGroup = Array.from(filterTabsEl).filter(
124984
125339
  (t) => group.filters.includes(t.getAttribute("data-filter") || "") && t.classList.contains("active")
124985
125340
  ).map((t) => t.getAttribute("data-filter") || "");
124986
125341
  if (activeInGroup.length > 0) {
124987
125342
  filteredItems = filteredItems.filter((item) => itemPassesGroup(item, activeInGroup));
124988
125343
  }
124989
- for (let j = i + 1; j < FILTER_GROUPS.length; j++) {
124990
- const nextGroup = FILTER_GROUPS[j];
125344
+ for (let j = i + 1; j < this.groups.length; j++) {
125345
+ const nextGroup = this.groups[j];
124991
125346
  nextGroup.filters.forEach((filterId) => {
124992
125347
  const tab = Array.from(filterTabsEl).find((t) => t.getAttribute("data-filter") === filterId);
124993
125348
  if (!tab) return;
@@ -124999,7 +125354,7 @@ var FilterModalView = class {
124999
125354
  }
125000
125355
  const activeFilters = Array.from(filterTabsEl).filter((t) => t.classList.contains("active") && t.getAttribute("data-filter") !== "all").map((t) => t.getAttribute("data-filter") || "");
125001
125356
  const activeByGroup = {};
125002
- FILTER_GROUPS.forEach((group) => {
125357
+ this.groups.forEach((group) => {
125003
125358
  const activeInGroup = activeFilters.filter((id) => group.filters.includes(id));
125004
125359
  if (activeInGroup.length > 0) activeByGroup[group.id] = activeInGroup;
125005
125360
  });
@@ -125019,7 +125374,7 @@ var FilterModalView = class {
125019
125374
  };
125020
125375
  const getFilteredItems = () => {
125021
125376
  let filteredItems = [...items];
125022
- FILTER_GROUPS.forEach((group) => {
125377
+ this.groups.forEach((group) => {
125023
125378
  const activeInGroup = Array.from(filterTabsEl).filter(
125024
125379
  (t) => group.filters.includes(t.getAttribute("data-filter") || "") && t.classList.contains("active")
125025
125380
  ).map((t) => t.getAttribute("data-filter") || "");
@@ -125032,7 +125387,7 @@ var FilterModalView = class {
125032
125387
  return filteredItems;
125033
125388
  };
125034
125389
  const updateFilterCounts = (filteredItems) => {
125035
- this.filterTabs.forEach((tabConfig) => {
125390
+ this.allTabs().forEach((tabConfig) => {
125036
125391
  if (tabConfig.id === "all") {
125037
125392
  const countEl = modal.querySelector("#countAll");
125038
125393
  if (countEl) countEl.textContent = String(filteredItems.length);
@@ -125081,7 +125436,7 @@ var FilterModalView = class {
125081
125436
  connectivityTab.classList.add("active");
125082
125437
  }
125083
125438
  const filteredItems2 = getFilteredItems();
125084
- const typeIds = ["elevators", "escalators", "hvac", "others", "commonArea", "stores"];
125439
+ const typeIds = ["elevators", "escalators", "hvac", "others", "commonArea", "stores", ...this.dynamicTabs.map((t) => t.id)];
125085
125440
  typeIds.forEach((typeId) => {
125086
125441
  const typeTab = Array.from(filterTabsEl).find((t) => t.getAttribute("data-filter") === typeId);
125087
125442
  if (!typeTab) return;
@@ -125111,7 +125466,7 @@ var FilterModalView = class {
125111
125466
  expandBtns.forEach((btn) => {
125112
125467
  const filterId = btn.getAttribute("data-expand-filter");
125113
125468
  if (!filterId) return;
125114
- const filterTabConfig = this.filterTabs.find((t) => t.id === filterId);
125469
+ const filterTabConfig = this.allTabs().find((t) => t.id === filterId);
125115
125470
  const filterFn = filterTabConfig?.filter || (() => false);
125116
125471
  btn.addEventListener("mouseenter", (e) => {
125117
125472
  e.stopPropagation();
@@ -125132,9 +125487,9 @@ var FilterModalView = class {
125132
125487
  showDeviceTooltip(triggerEl, filterId, devices) {
125133
125488
  const InfoTooltip2 = window.MyIOLibrary;
125134
125489
  if (!InfoTooltip2?.InfoTooltip) return;
125135
- const filterTabConfig = this.filterTabs.find((t) => t.id === filterId);
125490
+ const filterTabConfig = this.allTabs().find((t) => t.id === filterId);
125136
125491
  const label = filterTabConfig?.label || filterId;
125137
- const icon2 = FILTER_TAB_ICONS[filterId] || "\u{1F4CB}";
125492
+ const icon2 = FILTER_TAB_ICONS[filterId] || this.dynamicIcons[filterId] || "\u{1F4CB}";
125138
125493
  let devicesHtml = "";
125139
125494
  if (devices.length === 0) {
125140
125495
  devicesHtml = `
@@ -125211,12 +125566,17 @@ var FilterModalView = class {
125211
125566
  this.globalContainer.innerHTML = `<style>${generateFilterModalStyles(this.containerId, this.modalClass, this.primaryColor)}</style>${this.generateModalHTML()}`;
125212
125567
  document.body.appendChild(this.globalContainer);
125213
125568
  const modal2 = this.globalContainer.querySelector("#filterModal");
125214
- if (modal2) this.setupHandlers(modal2, items);
125569
+ if (modal2) {
125570
+ this.setupHandlers(modal2, items);
125571
+ this.mountChrome(modal2);
125572
+ }
125215
125573
  }
125216
125574
  }
125217
125575
  this.globalContainer.setAttribute("data-theme", this.themeMode);
125576
+ if (this.chrome && this.chrome.getTheme() !== this.themeMode) this.chrome.setTheme(this.themeMode);
125218
125577
  const modal = this.globalContainer.querySelector("#filterModal");
125219
125578
  if (!modal) return;
125579
+ this.buildCategoryTabs(items);
125220
125580
  const counts = this.calculateCounts(items);
125221
125581
  const tabsContainer = modal.querySelector("#filterTabsContainer");
125222
125582
  if (tabsContainer) {
@@ -125231,6 +125591,28 @@ var FilterModalView = class {
125231
125591
  modal.classList.remove("hidden");
125232
125592
  document.body.classList.add("filter-modal-open");
125233
125593
  }
125594
+ /** Troca o header próprio pela moldura padrão das modais premium e adiciona o footer premium. */
125595
+ mountChrome(modal) {
125596
+ const card = modal.querySelector(`.${this.modalClass}-card`);
125597
+ if (!card || !this.globalContainer) return;
125598
+ this.chrome = createPremiumModalChrome({
125599
+ modalId: `${this.containerId}Chrome`,
125600
+ icon: "\u{1F50D}",
125601
+ title: "Filtrar e Ordenar",
125602
+ modalEl: card,
125603
+ headerSlot: modal.querySelector(`#${this.containerId}Header`),
125604
+ themeTarget: this.globalContainer,
125605
+ // estilos escuros da modal são por #container[data-theme]
125606
+ theme: this.themeMode === "dark" ? "dark" : "light",
125607
+ showThemeToggle: true,
125608
+ maximizedClass: "maximized",
125609
+ headerRadius: "16px 16px 0 0",
125610
+ onClose: () => this.close(),
125611
+ onThemeChange: (t) => {
125612
+ this.themeMode = t;
125613
+ }
125614
+ });
125615
+ }
125234
125616
  close() {
125235
125617
  if (this.globalContainer) {
125236
125618
  const modal = this.globalContainer.querySelector("#filterModal");
@@ -125245,12 +125627,15 @@ var FilterModalView = class {
125245
125627
  if (this.globalContainer) {
125246
125628
  this.globalContainer.setAttribute("data-theme", mode);
125247
125629
  }
125630
+ this.chrome?.setTheme(mode === "dark" ? "dark" : "light");
125248
125631
  }
125249
125632
  destroy() {
125250
125633
  if (this.escHandler) {
125251
125634
  document.removeEventListener("keydown", this.escHandler);
125252
125635
  this.escHandler = null;
125253
125636
  }
125637
+ this.chrome?.destroy();
125638
+ this.chrome = null;
125254
125639
  if (this.globalContainer) {
125255
125640
  this.globalContainer.remove();
125256
125641
  this.globalContainer = null;
@@ -143517,7 +143902,9 @@ var AlarmsNotificationsPanelView = class {
143517
143902
  eventHandlers = /* @__PURE__ */ new Map();
143518
143903
  debug;
143519
143904
  // Bulk-selection state
143520
- selectedTitles = /* @__PURE__ */ new Set();
143905
+ // Chave = id do card (único por card em todo modo de agrupamento). Por título, cards
143906
+ // distintos com o mesmo tipo de alarme eram selecionados juntos.
143907
+ selectedIds = /* @__PURE__ */ new Set();
143521
143908
  groupedAlarms = [];
143522
143909
  // View mode: 'card' (default) or 'list' (table)
143523
143910
  viewMode = "card";
@@ -143647,7 +144034,7 @@ var AlarmsNotificationsPanelView = class {
143647
144034
  * Build compact inline list header (filters + bulk actions button)
143648
144035
  */
143649
144036
  buildListHeaderTemplate(state6) {
143650
- const sel = this.selectedTitles.size;
144037
+ const sel = this.selectedIds.size;
143651
144038
  const isCard = this.viewMode === "card";
143652
144039
  const isList = this.viewMode === "list";
143653
144040
  const isConsol = this.groupMode === "consolidado";
@@ -143760,7 +144147,7 @@ var AlarmsNotificationsPanelView = class {
143760
144147
  const mode = btn.getAttribute("data-group-mode");
143761
144148
  if (mode === this.groupMode) return;
143762
144149
  this.groupMode = mode;
143763
- this.selectedTitles.clear();
144150
+ this.selectedIds.clear();
143764
144151
  this.root?.querySelectorAll(".alarms-group-btn").forEach((b) => {
143765
144152
  b.classList.toggle("is-active", b.getAttribute("data-group-mode") === mode);
143766
144153
  });
@@ -143773,7 +144160,7 @@ var AlarmsNotificationsPanelView = class {
143773
144160
  const mode = btn.getAttribute("data-hierarchy-mode");
143774
144161
  if (mode === this.hierarchyMode) return;
143775
144162
  this.hierarchyMode = mode;
143776
- this.selectedTitles.clear();
144163
+ this.selectedIds.clear();
143777
144164
  this.root?.querySelectorAll(".alarms-hierarchy-btn").forEach((b) => {
143778
144165
  b.classList.toggle("is-active", b.getAttribute("data-hierarchy-mode") === mode);
143779
144166
  });
@@ -143824,10 +144211,10 @@ var AlarmsNotificationsPanelView = class {
143824
144211
  this.root.addEventListener("change", (e) => {
143825
144212
  const input = e.target;
143826
144213
  if (input.type !== "checkbox" || !input.classList.contains("alarm-card-select")) return;
143827
- const title = input.dataset.alarmTitle;
143828
- if (!title) return;
143829
- if (input.checked) this.selectedTitles.add(title);
143830
- else this.selectedTitles.delete(title);
144214
+ const id = input.dataset.alarmId;
144215
+ if (!id) return;
144216
+ if (input.checked) this.selectedIds.add(id);
144217
+ else this.selectedIds.delete(id);
143831
144218
  const card = input.closest(".alarm-card");
143832
144219
  if (card) card.classList.toggle("alarm-card--selected", input.checked);
143833
144220
  const row = input.closest(".atbl-row");
@@ -143838,9 +144225,9 @@ var AlarmsNotificationsPanelView = class {
143838
144225
  const input = e.target;
143839
144226
  if (input.id !== "tblSelectAll") return;
143840
144227
  if (input.checked) {
143841
- this.groupedAlarms.forEach((a) => this.selectedTitles.add(a.title));
144228
+ this.groupedAlarms.forEach((a) => this.selectedIds.add(a.id));
143842
144229
  } else {
143843
- this.groupedAlarms.forEach((a) => this.selectedTitles.delete(a.title));
144230
+ this.groupedAlarms.forEach((a) => this.selectedIds.delete(a.id));
143844
144231
  }
143845
144232
  this.root?.querySelectorAll(".alarm-card-select").forEach((cb) => {
143846
144233
  cb.checked = input.checked;
@@ -144050,7 +144437,7 @@ var AlarmsNotificationsPanelView = class {
144050
144437
  onMore: (id, e) => this.handleMore(id, e),
144051
144438
  themeMode: state6.themeMode,
144052
144439
  showCustomerName: this.params.showCustomerName ?? true,
144053
- selected: this.selectedTitles.has(alarm.title),
144440
+ selected: this.selectedIds.has(alarm.id),
144054
144441
  showDeviceBadge: isSeparado,
144055
144442
  // separado: show device badge in header
144056
144443
  alarmTypes: isPorDispositivo ? alarm._alarmTypes ?? [] : void 0,
@@ -144122,11 +144509,11 @@ var AlarmsNotificationsPanelView = class {
144122
144509
  // =====================================================================
144123
144510
  // Bulk Selection
144124
144511
  // =====================================================================
144125
- handleAlarmSelect(title, selected) {
144512
+ handleAlarmSelect(id, selected) {
144126
144513
  if (selected) {
144127
- this.selectedTitles.add(title);
144514
+ this.selectedIds.add(id);
144128
144515
  } else {
144129
- this.selectedTitles.delete(title);
144516
+ this.selectedIds.delete(id);
144130
144517
  }
144131
144518
  this.updateBulkButton();
144132
144519
  }
@@ -144135,7 +144522,7 @@ var AlarmsNotificationsPanelView = class {
144135
144522
  const btn = this.root.querySelector("#bulkActionsBtn");
144136
144523
  const count = this.root.querySelector("#bulkCount");
144137
144524
  if (!btn || !count) return;
144138
- const sel = this.selectedTitles.size;
144525
+ const sel = this.selectedIds.size;
144139
144526
  btn.disabled = sel === 0;
144140
144527
  count.textContent = String(sel);
144141
144528
  count.style.display = sel === 0 ? "none" : "";
@@ -144204,6 +144591,24 @@ var AlarmsNotificationsPanelView = class {
144204
144591
  if (v) el2.style.setProperty(n, v);
144205
144592
  });
144206
144593
  }
144594
+ /**
144595
+ * Moldura padrão das modais premium: troca o header próprio pelo header no acento
144596
+ * do tema (maximizar opcional + fechar) e anexa o footer premium (holding · hora · versão).
144597
+ */
144598
+ mountModalChrome(modal, opts) {
144599
+ return createPremiumModalChrome({
144600
+ modalId: opts.id,
144601
+ icon: opts.icon,
144602
+ title: opts.title,
144603
+ modalEl: modal,
144604
+ headerSlot: modal.firstElementChild,
144605
+ theme: this.controller.getState().themeMode === "dark" ? "dark" : "light",
144606
+ showMaximize: !!opts.maximizedClass,
144607
+ maximizedClass: opts.maximizedClass,
144608
+ headerRadius: `${opts.radius} ${opts.radius} 0 0`,
144609
+ onClose: opts.onClose
144610
+ });
144611
+ }
144207
144612
  openFilterModal() {
144208
144613
  const state6 = this.controller.getState();
144209
144614
  const filters = state6.filters;
@@ -144321,7 +144726,11 @@ var AlarmsNotificationsPanelView = class {
144321
144726
  </div>
144322
144727
  </div>
144323
144728
  `;
144324
- const close = () => overlay.remove();
144729
+ let chrome = null;
144730
+ const close = () => {
144731
+ chrome?.destroy();
144732
+ overlay.remove();
144733
+ };
144325
144734
  overlay.addEventListener("click", (e) => {
144326
144735
  const tabBtn = e.target.closest("[data-afm-tab]");
144327
144736
  if (!tabBtn) return;
@@ -144355,7 +144764,6 @@ var AlarmsNotificationsPanelView = class {
144355
144764
  item.style.display = label.includes(q) ? "" : "none";
144356
144765
  });
144357
144766
  });
144358
- overlay.querySelector(".afm-close").addEventListener("click", close);
144359
144767
  overlay.querySelector("#afmCancelBtn").addEventListener("click", close);
144360
144768
  overlay.addEventListener("click", (e) => {
144361
144769
  if (e.target === overlay) close();
@@ -144383,6 +144791,14 @@ var AlarmsNotificationsPanelView = class {
144383
144791
  close();
144384
144792
  });
144385
144793
  this._applyCssVars(overlay);
144794
+ chrome = this.mountModalChrome(overlay.querySelector(".afm-modal"), {
144795
+ id: "afmChrome",
144796
+ icon: "\u{1F50D}",
144797
+ title: "Filtrar Alarmes",
144798
+ radius: "14px",
144799
+ maximizedClass: "afm-modal--max",
144800
+ onClose: close
144801
+ });
144386
144802
  document.body.appendChild(overlay);
144387
144803
  }
144388
144804
  // =====================================================================
@@ -144434,7 +144850,7 @@ var AlarmsNotificationsPanelView = class {
144434
144850
  const sev = SEVERITY_CONFIG[alarm.severity];
144435
144851
  const st = STATE_CONFIG[alarm.state];
144436
144852
  const isActive = alarm.state !== "CLOSED";
144437
- const sel = this.selectedTitles.has(alarm.title);
144853
+ const sel = this.selectedIds.has(alarm.id);
144438
144854
  const escTitle = this.esc(alarm.title);
144439
144855
  const escCustomer = this.esc(alarm.customerName || "-");
144440
144856
  const escSource = this.esc(alarm.source || "-");
@@ -144470,7 +144886,7 @@ var AlarmsNotificationsPanelView = class {
144470
144886
  <td class="atbl-cell atbl-cell--actions">${actionBtns}</td>
144471
144887
  </tr>`;
144472
144888
  }).join("");
144473
- const allSelected = alarms.length > 0 && alarms.every((a) => this.selectedTitles.has(a.title));
144889
+ const allSelected = alarms.length > 0 && alarms.every((a) => this.selectedIds.has(a.id));
144474
144890
  return `
144475
144891
  <table class="alarms-table" aria-label="Lista de alarmes">
144476
144892
  <thead>
@@ -144534,9 +144950,17 @@ var AlarmsNotificationsPanelView = class {
144534
144950
  </div>
144535
144951
  `;
144536
144952
  this._applyCssVars(overlay);
144953
+ const chrome = this.mountModalChrome(overlay.querySelector(".aex-modal"), {
144954
+ id: "aexChrome",
144955
+ icon: "\u{1F4E4}",
144956
+ title: "Exportar Alarmes",
144957
+ radius: "14px",
144958
+ onClose: () => close()
144959
+ });
144537
144960
  document.body.appendChild(overlay);
144538
144961
  requestAnimationFrame(() => overlay.classList.add("aex-overlay--visible"));
144539
144962
  const close = () => {
144963
+ chrome.destroy();
144540
144964
  overlay.classList.remove("aex-overlay--visible");
144541
144965
  setTimeout(() => overlay.remove(), 220);
144542
144966
  document.removeEventListener("keydown", onKey);
@@ -144868,8 +145292,8 @@ var AlarmsNotificationsPanelView = class {
144868
145292
  }, 500);
144869
145293
  }
144870
145294
  openBulkActionPicker() {
144871
- if (this.selectedTitles.size === 0) return;
144872
- const selected = this.groupedAlarms.filter((a) => this.selectedTitles.has(a.title));
145295
+ if (this.selectedIds.size === 0) return;
145296
+ const selected = this.groupedAlarms.filter((a) => this.selectedIds.has(a.id));
144873
145297
  const count = selected.length;
144874
145298
  const listHtml = selected.slice(0, 5).map((a) => `<li class="abm-alarm-item">${this.esc(a.title)}</li>`).join("");
144875
145299
  const moreHtml = count > 5 ? `<li class="abm-alarm-item abm-alarm-more">+${count - 5} mais...</li>` : "";
@@ -144906,9 +145330,17 @@ var AlarmsNotificationsPanelView = class {
144906
145330
  </div>
144907
145331
  `;
144908
145332
  this._applyCssVars(overlay);
145333
+ const chrome = this.mountModalChrome(overlay.querySelector(".abm-modal"), {
145334
+ id: "abmChrome",
145335
+ icon: "\u26A1",
145336
+ title: "A\xE7\xF5es em Lote",
145337
+ radius: "14px",
145338
+ onClose: () => close()
145339
+ });
144909
145340
  document.body.appendChild(overlay);
144910
145341
  requestAnimationFrame(() => overlay.classList.add("abm-overlay--visible"));
144911
145342
  const close = () => {
145343
+ chrome.destroy();
144912
145344
  overlay.classList.remove("abm-overlay--visible");
144913
145345
  setTimeout(() => overlay.remove(), 220);
144914
145346
  document.removeEventListener("keydown", onKey);
@@ -145006,6 +145438,13 @@ var AlarmsNotificationsPanelView = class {
145006
145438
  </div>
145007
145439
  `;
145008
145440
  this._applyCssVars(overlay);
145441
+ const chrome = this.mountModalChrome(overlay.querySelector(".alarm-action-modal"), {
145442
+ id: "aamBulkChrome",
145443
+ icon: cfg.icon,
145444
+ title: cfg.title,
145445
+ radius: "16px",
145446
+ onClose: () => close()
145447
+ });
145009
145448
  document.body.appendChild(overlay);
145010
145449
  requestAnimationFrame(() => overlay.classList.add("alarm-action-modal-overlay--visible"));
145011
145450
  const textarea = overlay.querySelector("#aamText");
@@ -145019,6 +145458,7 @@ var AlarmsNotificationsPanelView = class {
145019
145458
  if (cfg.textRequired) confirmBtn.disabled = len === 0;
145020
145459
  });
145021
145460
  const close = () => {
145461
+ chrome.destroy();
145022
145462
  overlay.classList.remove("alarm-action-modal-overlay--visible");
145023
145463
  setTimeout(() => overlay.remove(), 220);
145024
145464
  document.removeEventListener("keydown", onKey);
@@ -145045,7 +145485,7 @@ var AlarmsNotificationsPanelView = class {
145045
145485
  const until = new Date(Date.now() + minutes * 60 * 1e3).toISOString();
145046
145486
  this.controller.handleSnooze(ids, until);
145047
145487
  }
145048
- this.selectedTitles.clear();
145488
+ this.selectedIds.clear();
145049
145489
  this.updateBulkButton();
145050
145490
  close();
145051
145491
  this.emit(`alarm-${type}`, selectedAlarms.map((a) => a.id));
@@ -145509,6 +145949,13 @@ var AlarmsNotificationsPanelView = class {
145509
145949
  </div>
145510
145950
  `;
145511
145951
  this._applyCssVars(overlay);
145952
+ const chrome = this.mountModalChrome(overlay.querySelector(".alarm-action-modal"), {
145953
+ id: "aamChrome",
145954
+ icon: cfg.icon,
145955
+ title: cfg.title,
145956
+ radius: "16px",
145957
+ onClose: () => closeModal2()
145958
+ });
145512
145959
  document.body.appendChild(overlay);
145513
145960
  requestAnimationFrame(() => overlay.classList.add("alarm-action-modal-overlay--visible"));
145514
145961
  const textarea = overlay.querySelector("#aamText");
@@ -145522,6 +145969,7 @@ var AlarmsNotificationsPanelView = class {
145522
145969
  if (cfg.textRequired) confirmBtn.disabled = len === 0;
145523
145970
  });
145524
145971
  const closeModal2 = () => {
145972
+ chrome.destroy();
145525
145973
  overlay.classList.remove("alarm-action-modal-overlay--visible");
145526
145974
  setTimeout(() => overlay.remove(), 220);
145527
145975
  document.removeEventListener("keydown", onKeyDown);
@@ -165355,7 +165803,7 @@ function statusStyle(status) {
165355
165803
  }
165356
165804
  function createPresetupGateway(opts) {
165357
165805
  const {
165358
- mount: mount2,
165806
+ mount: mount3,
165359
165807
  gatewayId,
165360
165808
  clientId,
165361
165809
  clientSecret,
@@ -165378,9 +165826,9 @@ function createPresetupGateway(opts) {
165378
165826
  });
165379
165827
  const ingestion = new IngestionApiClient(ingestionApiUrl, auth);
165380
165828
  const provisioning = new ProvisioningApiClient(provisioningApiUrl);
165381
- mount2.innerHTML = "";
165829
+ mount3.innerHTML = "";
165382
165830
  const root = el("div", S.root);
165383
- mount2.appendChild(root);
165831
+ mount3.appendChild(root);
165384
165832
  const header = el("div", S.header);
165385
165833
  const titleEl = el("span", "font-weight:600;font-size:15px;");
165386
165834
  titleEl.textContent = "PresetupGateway";
@@ -165690,7 +166138,7 @@ function createPresetupGateway(opts) {
165690
166138
  await load();
165691
166139
  },
165692
166140
  destroy() {
165693
- mount2.innerHTML = "";
166141
+ mount3.innerHTML = "";
165694
166142
  }
165695
166143
  };
165696
166144
  }