myio-js-library 0.1.559 → 0.1.561

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.559",
911
+ version: "0.1.561",
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;
@@ -68237,7 +68307,19 @@ var WelcomeModalView = class _WelcomeModalView {
68237
68307
  shoppingCardBg: themeConfig.shoppingCardBackgroundColor,
68238
68308
  shoppingCardBorder: themeConfig.shoppingCardBorderColor
68239
68309
  };
68240
- return { ...DEFAULT_PALETTE, ...configPalette, ...this.params.palette };
68310
+ return { ...DEFAULT_PALETTE, ...configPalette, ...this.readDashboardBrand(), ...this.params.palette };
68311
+ }
68312
+ /**
68313
+ * Paleta do tema do dashboard (createMyIOTheme publica --myio-brand-700/600 no :root).
68314
+ * Quando presente, substitui o roxo padrão da modal; `params.palette` explícito ainda prevalece.
68315
+ */
68316
+ readDashboardBrand() {
68317
+ if (typeof document === "undefined" || typeof getComputedStyle !== "function") return {};
68318
+ const cs = getComputedStyle(document.documentElement);
68319
+ const brand700 = cs.getPropertyValue("--myio-brand-700").trim();
68320
+ if (!brand700) return {};
68321
+ const brand600 = cs.getPropertyValue("--myio-brand-600").trim();
68322
+ return { primary: brand700, secondary: brand600 || brand700 };
68241
68323
  }
68242
68324
  /**
68243
68325
  * Set theme mode and update UI
@@ -68614,9 +68696,9 @@ var WelcomeModalView = class _WelcomeModalView {
68614
68696
  background: linear-gradient(135deg, #0a0f1a 0%, #1a1f2e 50%, #0d1117 100%);
68615
68697
  border-radius: 14px;
68616
68698
  box-shadow:
68617
- 0 0 0 1px rgba(122, 47, 247, 0.2),
68699
+ 0 0 0 1px color-mix(in srgb, var(--wm-primary) 20%, transparent),
68618
68700
  0 25px 80px rgba(0, 0, 0, 0.5),
68619
- 0 0 60px rgba(122, 47, 247, 0.15);
68701
+ 0 0 60px color-mix(in srgb, var(--wm-primary) 15%, transparent);
68620
68702
  overflow: hidden;
68621
68703
  /* Ensure flex children can handle their own overflow */
68622
68704
  min-height: 0;
@@ -68633,7 +68715,7 @@ var WelcomeModalView = class _WelcomeModalView {
68633
68715
  padding: 16px 32px 8px 32px;
68634
68716
  background:
68635
68717
  linear-gradient(180deg,
68636
- rgba(122, 47, 247, 0.25) 0%,
68718
+ color-mix(in srgb, var(--wm-primary) 25%, transparent) 0%,
68637
68719
  var(--wm-gradient-start) 20%,
68638
68720
  var(--wm-gradient-end) 80%,
68639
68721
  rgba(13, 17, 23, 0.95) 100%
@@ -68642,7 +68724,7 @@ var WelcomeModalView = class _WelcomeModalView {
68642
68724
  background-size: cover;
68643
68725
  background-position: center;
68644
68726
  min-height: 22vh;
68645
- border-bottom: 1px solid rgba(122, 47, 247, 0.2);
68727
+ border-bottom: 1px solid color-mix(in srgb, var(--wm-primary) 20%, transparent);
68646
68728
  }
68647
68729
 
68648
68730
  /* Brand Logo - Large floating */
@@ -68836,15 +68918,15 @@ var WelcomeModalView = class _WelcomeModalView {
68836
68918
  cursor: pointer;
68837
68919
  transition: all 0.3s ease;
68838
68920
  box-shadow:
68839
- 0 4px 20px rgba(122, 47, 247, 0.4),
68840
- 0 0 0 0 rgba(122, 47, 247, 0);
68921
+ 0 4px 20px color-mix(in srgb, var(--wm-primary) 40%, transparent),
68922
+ 0 0 0 0 color-mix(in srgb, var(--wm-primary) 0%, transparent);
68841
68923
  }
68842
68924
 
68843
68925
  .myio-welcome-cta-btn:hover {
68844
68926
  transform: translateY(-3px);
68845
68927
  box-shadow:
68846
- 0 8px 30px rgba(122, 47, 247, 0.5),
68847
- 0 0 0 4px rgba(122, 47, 247, 0.2);
68928
+ 0 8px 30px color-mix(in srgb, var(--wm-primary) 50%, transparent),
68929
+ 0 0 0 4px color-mix(in srgb, var(--wm-primary) 20%, transparent);
68848
68930
  }
68849
68931
 
68850
68932
  .myio-welcome-cta-btn:active {
@@ -68902,7 +68984,7 @@ var WelcomeModalView = class _WelcomeModalView {
68902
68984
  .myio-welcome-search:focus-within {
68903
68985
  background: rgba(255,255,255,0.12);
68904
68986
  border-color: var(--wm-primary);
68905
- box-shadow: 0 0 0 2px rgba(122, 47, 247, 0.2);
68987
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--wm-primary) 20%, transparent);
68906
68988
  }
68907
68989
 
68908
68990
  .myio-welcome-search-icon {
@@ -69123,17 +69205,17 @@ var WelcomeModalView = class _WelcomeModalView {
69123
69205
  /* Hover effects only for clickable cards */
69124
69206
  .myio-welcome-card--clickable:hover {
69125
69207
  transform: scale(1.03);
69126
- border-color: rgba(122, 47, 247, 0.5);
69208
+ border-color: color-mix(in srgb, var(--wm-primary) 50%, transparent);
69127
69209
  box-shadow:
69128
69210
  0 12px 40px rgba(0,0,0,0.4),
69129
- 0 0 0 1px rgba(122, 47, 247, 0.4),
69130
- 0 0 30px rgba(122, 47, 247, 0.15);
69211
+ 0 0 0 1px color-mix(in srgb, var(--wm-primary) 40%, transparent),
69212
+ 0 0 30px color-mix(in srgb, var(--wm-primary) 15%, transparent);
69131
69213
  }
69132
69214
 
69133
69215
  .myio-welcome-card--clickable:focus {
69134
69216
  outline: none;
69135
69217
  box-shadow:
69136
- 0 0 0 3px rgba(122, 47, 247, 0.5),
69218
+ 0 0 0 3px color-mix(in srgb, var(--wm-primary) 50%, transparent),
69137
69219
  0 12px 40px rgba(0,0,0,0.4);
69138
69220
  }
69139
69221
 
@@ -69295,7 +69377,7 @@ var WelcomeModalView = class _WelcomeModalView {
69295
69377
  .myio-welcome-card-device-count.energy .count-spinner { border-top-color: #22c55e; }
69296
69378
  .myio-welcome-card-device-count.water .count-spinner { border-top-color: #3b82f6; }
69297
69379
  .myio-welcome-card-device-count.temperature .count-spinner { border-top-color: #f97316; }
69298
- .myio-welcome-card-device-count.users .count-spinner { border-top-color: #a78bfa; }
69380
+ .myio-welcome-card-device-count.users .count-spinner { border-top-color: color-mix(in srgb, var(--wm-primary) 55%, white); }
69299
69381
  .myio-welcome-card-device-count.alarms .count-spinner { border-top-color: #ef4444; }
69300
69382
  .myio-welcome-card-device-count.annotations .count-spinner { border-top-color: #6c5ce7; }
69301
69383
  .myio-welcome-card-device-count.notifications .count-spinner { border-top-color: #f59e0b; }
@@ -69320,7 +69402,7 @@ var WelcomeModalView = class _WelcomeModalView {
69320
69402
  height: 100%;
69321
69403
  width: 0%;
69322
69404
  border-radius: inherit;
69323
- background: linear-gradient(90deg, #7c3aed, #22c55e);
69405
+ background: linear-gradient(90deg, var(--wm-primary), #22c55e);
69324
69406
  background-size: 200% 100%;
69325
69407
  animation: welcome-progress-shimmer 1.6s ease infinite;
69326
69408
  transition: width 0.4s ease;
@@ -69354,7 +69436,7 @@ var WelcomeModalView = class _WelcomeModalView {
69354
69436
  height: 100%;
69355
69437
  width: 0%;
69356
69438
  border-radius: inherit;
69357
- background: linear-gradient(90deg, #7c3aed, #a78bfa, #22c55e);
69439
+ background: linear-gradient(90deg, var(--wm-primary), color-mix(in srgb, var(--wm-primary) 55%, white), #22c55e);
69358
69440
  background-size: 200% 100%;
69359
69441
  animation: welcome-progress-shimmer 1.6s ease infinite;
69360
69442
  transition: width 0.4s ease;
@@ -69612,8 +69694,9 @@ var WelcomeModalView = class _WelcomeModalView {
69612
69694
  align-items: center;
69613
69695
  justify-content: center;
69614
69696
  padding: 12px 20px;
69615
- background: #5B2EBC;
69616
- border-top: 1px solid rgba(122, 47, 247, 0.3);
69697
+ /* paleta do tema do dashboard (createMyIOTheme \u2192 --myio-brand-700); roxo MYIO sem tema */
69698
+ background: var(--myio-brand-700, #5B2EBC);
69699
+ border-top: 1px solid color-mix(in srgb, var(--myio-brand-700, #7A2FF7) 30%, transparent);
69617
69700
  }
69618
69701
 
69619
69702
  .myio-welcome-footer-text {
@@ -69690,13 +69773,13 @@ var WelcomeModalView = class _WelcomeModalView {
69690
69773
  display: flex !important;
69691
69774
  align-items: center !important;
69692
69775
  justify-content: center !important;
69693
- gap: 6px !important;
69776
+ gap: 8px !important;
69694
69777
  }
69695
69778
 
69696
69779
  /* Extend device-count styles for meta types */
69697
69780
  .myio-welcome-card-device-count.users:hover {
69698
- background: rgba(124, 58, 237, 0.3);
69699
- border-color: rgba(124, 58, 237, 0.5);
69781
+ background: color-mix(in srgb, var(--wm-primary) 30%, transparent);
69782
+ border-color: color-mix(in srgb, var(--wm-primary) 50%, transparent);
69700
69783
  }
69701
69784
 
69702
69785
  .myio-welcome-card-device-count.alarms:hover {
@@ -69709,7 +69792,7 @@ var WelcomeModalView = class _WelcomeModalView {
69709
69792
  border-color: rgba(234, 179, 8, 0.5);
69710
69793
  }
69711
69794
 
69712
- .myio-welcome-card-device-count.users .icon { color: #a78bfa; }
69795
+ .myio-welcome-card-device-count.users .icon { color: color-mix(in srgb, var(--wm-primary) 55%, white); }
69713
69796
  .myio-welcome-card-device-count.alarms .icon { color: #f87171; }
69714
69797
  .myio-welcome-card-device-count.notifications .icon { color: #fbbf24; }
69715
69798
 
@@ -69731,21 +69814,21 @@ var WelcomeModalView = class _WelcomeModalView {
69731
69814
  .myio-welcome-modal--light .myio-welcome-modal-container {
69732
69815
  background: linear-gradient(135deg, #f8f9fc 0%, #ffffff 50%, #f0f2f5 100%);
69733
69816
  box-shadow:
69734
- 0 0 0 1px rgba(122, 47, 247, 0.15),
69817
+ 0 0 0 1px color-mix(in srgb, var(--wm-primary) 15%, transparent),
69735
69818
  0 25px 80px rgba(0, 0, 0, 0.2),
69736
- 0 0 60px rgba(122, 47, 247, 0.1);
69819
+ 0 0 60px color-mix(in srgb, var(--wm-primary) 10%, transparent);
69737
69820
  }
69738
69821
 
69739
69822
  .myio-welcome-modal--light .myio-welcome-hero {
69740
69823
  background:
69741
69824
  linear-gradient(180deg,
69742
- rgba(122, 47, 247, 0.12) 0%,
69825
+ color-mix(in srgb, var(--wm-primary) 12%, transparent) 0%,
69743
69826
  rgba(255, 255, 255, 0.7) 20%,
69744
69827
  rgba(255, 255, 255, 0.85) 80%,
69745
69828
  rgba(248, 249, 252, 0.95) 100%
69746
69829
  ),
69747
69830
  var(--wm-bg-image, none);
69748
- border-bottom: 1px solid rgba(122, 47, 247, 0.15);
69831
+ border-bottom: 1px solid color-mix(in srgb, var(--wm-primary) 15%, transparent);
69749
69832
  }
69750
69833
 
69751
69834
  .myio-welcome-modal--light .myio-welcome-hero-title {
@@ -69835,11 +69918,11 @@ var WelcomeModalView = class _WelcomeModalView {
69835
69918
  }
69836
69919
 
69837
69920
  .myio-welcome-modal--light .myio-welcome-card:hover {
69838
- border-color: rgba(122, 47, 247, 0.4);
69921
+ border-color: color-mix(in srgb, var(--wm-primary) 40%, transparent);
69839
69922
  box-shadow:
69840
69923
  0 12px 40px rgba(0, 0, 0, 0.12),
69841
- 0 0 0 1px rgba(122, 47, 247, 0.3),
69842
- 0 0 30px rgba(122, 47, 247, 0.1);
69924
+ 0 0 0 1px color-mix(in srgb, var(--wm-primary) 30%, transparent),
69925
+ 0 0 30px color-mix(in srgb, var(--wm-primary) 10%, transparent);
69843
69926
  }
69844
69927
 
69845
69928
  .myio-welcome-modal--light .myio-welcome-card::before {
@@ -69906,8 +69989,8 @@ var WelcomeModalView = class _WelcomeModalView {
69906
69989
  }
69907
69990
 
69908
69991
  .myio-welcome-modal--light .myio-welcome-footer {
69909
- background: #5B2EBC;
69910
- border-top-color: rgba(122, 47, 247, 0.3);
69992
+ background: var(--myio-brand-700, #5B2EBC);
69993
+ border-top-color: color-mix(in srgb, var(--myio-brand-700, #7A2FF7) 30%, transparent);
69911
69994
  }
69912
69995
 
69913
69996
  .myio-welcome-modal--light .myio-welcome-footer-text {
@@ -69927,8 +70010,8 @@ var WelcomeModalView = class _WelcomeModalView {
69927
70010
 
69928
70011
  /* Light theme - meta counts use same device-count light styles */
69929
70012
  .myio-welcome-modal--light .myio-welcome-card-device-count.users:hover {
69930
- background: rgba(124, 58, 237, 0.15);
69931
- border-color: rgba(124, 58, 237, 0.4);
70013
+ background: color-mix(in srgb, var(--wm-primary) 15%, transparent);
70014
+ border-color: color-mix(in srgb, var(--wm-primary) 40%, transparent);
69932
70015
  }
69933
70016
 
69934
70017
  .myio-welcome-modal--light .myio-welcome-card-device-count.alarms:hover {
@@ -70122,13 +70205,13 @@ var WelcomeModalView = class _WelcomeModalView {
70122
70205
  buildCardHTML(card, index) {
70123
70206
  const bgImage = card.bgImageUrl ? `<div class="myio-welcome-card-bg" data-src="${card.bgImageUrl}"></div>` : "";
70124
70207
  const meta = card.metaCounts || {};
70125
- const renderMetaCount = (count) => count === null ? '<span class="count-spinner"></span>' : String(count ?? 0);
70208
+ const renderMetaCount = (count) => count === null ? '<span class="count-spinner"></span>' : `<span class="count">${count ?? 0}</span>`;
70126
70209
  const thirdBadgeHTML = meta.annotationsLocked ? `<span class="myio-welcome-card-device-count annotations locked"
70127
70210
  data-tooltip-type="annotations-locked"
70128
70211
  data-card-index="${index}"
70129
70212
  title="Anota\xE7\xF5es \u2014 funcionalidade n\xE3o liberada ainda (em breve)"
70130
70213
  style="opacity:.6;cursor:not-allowed;">
70131
- <span class="icon">\u{1F4CB}</span> <span class="icon" style="font-size:.85em;">\u{1F512}</span>
70214
+ <span class="icon">\u{1F4CB}</span> <span class="count">\u{1F512}</span>
70132
70215
  </span>` : "annotations" in meta ? `<span class="myio-welcome-card-device-count annotations"
70133
70216
  data-tooltip-type="annotations"
70134
70217
  data-card-index="${index}"
@@ -70416,47 +70499,107 @@ var WelcomeModalView = class _WelcomeModalView {
70416
70499
  );
70417
70500
  }
70418
70501
  static metaChip(text2) {
70419
- return `<span style="display:inline-block;padding:2px 8px;margin:0 4px 4px 0;border-radius:10px;background:#334155;font-weight:700">${_WelcomeModalView.escapeMetaHtml(text2)}</span>`;
70420
- }
70502
+ return `<span class="myio-welcome-meta-tip__chip">${_WelcomeModalView.escapeMetaHtml(text2)}</span>`;
70503
+ }
70504
+ // Mesmo padrão visual do EnergySummaryTooltip (card branco, header em degradê da cor do tipo,
70505
+ // banner de total, títulos de seção em caixa alta). Usuários segue a cor do tema do dashboard.
70506
+ static META_TIP_CSS = `
70507
+ .myio-welcome-meta-tip {
70508
+ position: fixed; z-index: 100000; width: 360px; max-width: calc(100vw - 16px);
70509
+ background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px;
70510
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 2px 10px rgba(0, 0, 0, 0.08);
70511
+ font: 12px 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
70512
+ color: #1e293b; overflow: hidden; display: none;
70513
+ }
70514
+ .myio-welcome-meta-tip__header {
70515
+ display: flex; align-items: center; gap: 8px; padding: 10px 14px;
70516
+ background: linear-gradient(90deg, var(--mt-bg1) 0%, var(--mt-bg2) 100%);
70517
+ border-bottom: 1px solid var(--mt-border);
70518
+ }
70519
+ .myio-welcome-meta-tip__icon { font-size: 18px; }
70520
+ .myio-welcome-meta-tip__title { flex: 1; font-weight: 700; font-size: 14px; color: var(--mt-accent); }
70521
+ .myio-welcome-meta-tip__timestamp { font-size: 10px; color: #6b7280; }
70522
+ .myio-welcome-meta-tip__close {
70523
+ width: 24px; height: 24px; border: none; border-radius: 4px; cursor: pointer;
70524
+ background: rgba(255, 255, 255, 0.6); color: #64748b;
70525
+ display: flex; align-items: center; justify-content: center; font-size: 14px; line-height: 1;
70526
+ }
70527
+ .myio-welcome-meta-tip__close:hover { background: rgba(255, 255, 255, 0.9); color: #1e293b; }
70528
+ .myio-welcome-meta-tip__body { padding: 14px; max-height: 60vh; overflow-y: auto; }
70529
+ .myio-welcome-meta-tip__total {
70530
+ display: flex; align-items: center; justify-content: space-between; padding: 10px 12px;
70531
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
70532
+ border: 1px solid #e2e8f0; border-radius: 8px; margin-bottom: 12px;
70533
+ }
70534
+ .myio-welcome-meta-tip__total-label { font-weight: 600; color: #475569; font-size: 12px; }
70535
+ .myio-welcome-meta-tip__total-value { font-weight: 700; font-size: 20px; color: #1e293b; }
70536
+ .myio-welcome-meta-tip__section-title {
70537
+ font-weight: 700; font-size: 10px; color: #64748b; text-transform: uppercase; letter-spacing: 0.5px;
70538
+ margin: 12px 0 6px; padding-bottom: 4px; border-bottom: 1px solid #e2e8f0;
70539
+ }
70540
+ .myio-welcome-meta-tip__chips { display: flex; flex-wrap: wrap; gap: 6px; }
70541
+ .myio-welcome-meta-tip__chip {
70542
+ display: inline-block; padding: 3px 10px; border-radius: 999px; font-weight: 700; font-size: 11px;
70543
+ color: var(--mt-accent); background: var(--mt-bg1); border: 1px solid var(--mt-border);
70544
+ }
70545
+ .myio-welcome-meta-tip__row { padding: 7px 10px; border-radius: 6px; }
70546
+ .myio-welcome-meta-tip__row:nth-child(odd) { background: #f8fafc; }
70547
+ .myio-welcome-meta-tip__row-title { font-weight: 600; color: #1e293b; }
70548
+ .myio-welcome-meta-tip__row-sub { color: #64748b; font-size: 11px; margin-top: 1px; }
70549
+ .myio-welcome-meta-tip__empty { color: #64748b; padding: 6px 2px; }
70550
+ `;
70421
70551
  ensureMetaTip() {
70422
70552
  if (this.metaTipEl && document.body.contains(this.metaTipEl)) return this.metaTipEl;
70553
+ if (!document.getElementById("myio-welcome-meta-tip-styles")) {
70554
+ const style = document.createElement("style");
70555
+ style.id = "myio-welcome-meta-tip-styles";
70556
+ style.textContent = _WelcomeModalView.META_TIP_CSS;
70557
+ document.head.appendChild(style);
70558
+ }
70423
70559
  const tip = document.createElement("div");
70424
70560
  tip.className = "myio-welcome-meta-tip";
70425
- Object.assign(tip.style, {
70426
- position: "fixed",
70427
- zIndex: "100000",
70428
- minWidth: "260px",
70429
- maxWidth: "360px",
70430
- maxHeight: "60vh",
70431
- overflow: "auto",
70432
- background: "#1e293b",
70433
- color: "#e2e8f0",
70434
- border: "1px solid #334155",
70435
- borderRadius: "10px",
70436
- boxShadow: "0 12px 40px rgba(0,0,0,.45)",
70437
- padding: "12px 14px",
70438
- font: "12px 'Nunito', system-ui, -apple-system, sans-serif",
70439
- display: "none"
70440
- });
70441
70561
  tip.addEventListener("mouseenter", () => {
70442
70562
  if (this.metaTipHideTimer) clearTimeout(this.metaTipHideTimer);
70443
70563
  });
70444
70564
  tip.addEventListener("mouseleave", () => this.startMetaTipDelayedHide());
70565
+ tip.addEventListener("click", (e) => {
70566
+ if (e.target.closest(".myio-welcome-meta-tip__close")) this.hideMetaTip();
70567
+ });
70445
70568
  document.body.appendChild(tip);
70446
70569
  this.metaTipEl = tip;
70447
70570
  return tip;
70448
70571
  }
70572
+ /** Cores do tooltip por tipo de badge (usuários = cor do tema). */
70573
+ metaTipAccent(type) {
70574
+ if (type === "alarms") return { accent: "#b91c1c", bg1: "#fef2f2", bg2: "#fee2e2", border: "#fca5a5" };
70575
+ if (type.startsWith("annotations")) return { accent: "#5b21b6", bg1: "#f5f3ff", bg2: "#ede9fe", border: "#c4b5fd" };
70576
+ if (type === "notifications") return { accent: "#b45309", bg1: "#fffbeb", bg2: "#fef3c7", border: "#fcd34d" };
70577
+ const p = this.palette.primary || "#7A2FF7";
70578
+ return {
70579
+ accent: p,
70580
+ bg1: `color-mix(in srgb, ${p} 6%, white)`,
70581
+ bg2: `color-mix(in srgb, ${p} 16%, white)`,
70582
+ border: `color-mix(in srgb, ${p} 40%, white)`
70583
+ };
70584
+ }
70449
70585
  showMetaTip(anchorEl, type, card) {
70450
70586
  const tip = this.ensureMetaTip();
70451
70587
  if (this.metaTipHideTimer) clearTimeout(this.metaTipHideTimer);
70588
+ const c = this.metaTipAccent(type);
70589
+ tip.style.setProperty("--mt-accent", c.accent);
70590
+ tip.style.setProperty("--mt-bg1", c.bg1);
70591
+ tip.style.setProperty("--mt-bg2", c.bg2);
70592
+ tip.style.setProperty("--mt-border", c.border);
70452
70593
  tip.innerHTML = this.buildMetaTipHtml(type, card);
70453
70594
  tip.style.display = "block";
70454
70595
  const r = anchorEl.getBoundingClientRect();
70455
- const tw = Math.min(360, window.innerWidth - 16);
70596
+ const tw = tip.offsetWidth || Math.min(360, window.innerWidth - 16);
70456
70597
  let left = r.left;
70457
70598
  if (left + tw > window.innerWidth - 8) left = window.innerWidth - tw - 8;
70458
70599
  tip.style.left = Math.max(8, left) + "px";
70459
- tip.style.top = r.bottom + 8 + "px";
70600
+ const th = tip.offsetHeight;
70601
+ const below = r.bottom + 8;
70602
+ tip.style.top = (below + th > window.innerHeight - 8 ? Math.max(8, r.top - th - 8) : below) + "px";
70460
70603
  }
70461
70604
  startMetaTipDelayedHide() {
70462
70605
  if (this.metaTipHideTimer) clearTimeout(this.metaTipHideTimer);
@@ -70474,56 +70617,88 @@ var WelcomeModalView = class _WelcomeModalView {
70474
70617
  const chip = _WelcomeModalView.metaChip;
70475
70618
  const meta = card.metaCounts || {};
70476
70619
  const details = card.metaDetails || {};
70477
- const loadingBody = '<div style="opacity:.7;padding:6px 0">Carregando\u2026</div>';
70478
- const header = (icon2, label, count) => `<div style="font-weight:800;font-size:13px;margin-bottom:8px">${icon2} ${label}${count === null || count === void 0 ? "" : ` (${count})`} \u2014 ${esc8(card.title)}</div>`;
70479
- const row = (titleHtml, subHtml) => `<div style="padding:6px 0;border-top:1px solid #334155">
70480
- <div style="font-weight:600">${titleHtml}</div>
70481
- ${subHtml ? `<div style="opacity:.7">${subHtml}</div>` : ""}
70620
+ const now = /* @__PURE__ */ new Date();
70621
+ const stamp = `${String(now.getDate()).padStart(2, "0")}/${String(now.getMonth() + 1).padStart(2, "0")} ${String(
70622
+ now.getHours()
70623
+ ).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
70624
+ const frame = (icon2, label, body) => `<div class="myio-welcome-meta-tip__header">
70625
+ <span class="myio-welcome-meta-tip__icon">${icon2}</span>
70626
+ <span class="myio-welcome-meta-tip__title">${label} (${esc8(card.title)})</span>
70627
+ <span class="myio-welcome-meta-tip__timestamp">${stamp}</span>
70628
+ <button type="button" class="myio-welcome-meta-tip__close" title="Fechar">\u2715</button>
70629
+ </div>
70630
+ <div class="myio-welcome-meta-tip__body">${body}</div>`;
70631
+ const total = (label, value) => `<div class="myio-welcome-meta-tip__total">
70632
+ <span class="myio-welcome-meta-tip__total-label">${label}</span>
70633
+ <span class="myio-welcome-meta-tip__total-value">${value}</span>
70482
70634
  </div>`;
70635
+ const section = (title) => `<div class="myio-welcome-meta-tip__section-title">${title}</div>`;
70636
+ const empty = (text2) => `<div class="myio-welcome-meta-tip__empty">${text2}</div>`;
70637
+ const loadingBody = empty("Carregando\u2026");
70638
+ const row = (titleHtml, subHtml) => `<div class="myio-welcome-meta-tip__row">
70639
+ <div class="myio-welcome-meta-tip__row-title">${titleHtml}</div>
70640
+ ${subHtml ? `<div class="myio-welcome-meta-tip__row-sub">${subHtml}</div>` : ""}
70641
+ </div>`;
70642
+ const more = (n) => n > 10 ? empty(`\u2026 e mais ${n - 10}`) : "";
70483
70643
  if (type === "alarms") {
70484
- if (meta.alarms === null) return header("\u{1F6A8}", "Alarmes") + loadingBody;
70644
+ if (meta.alarms === null) return frame("\u{1F6A8}", "Alarmes", loadingBody);
70485
70645
  const alarms = details.alarms || [];
70486
70646
  const byState = {};
70487
70647
  for (const a of alarms) {
70488
70648
  const s = a.state || "OPEN";
70489
70649
  byState[s] = (byState[s] || 0) + 1;
70490
70650
  }
70491
- const chips = Object.entries(byState).map(([s, n]) => chip(`${s}: ${n}`)).join("") || '<span style="opacity:.7">Sem alarmes ativos</span>';
70651
+ const chips = Object.entries(byState).map(([s, n]) => chip(`${s}: ${n}`)).join("");
70492
70652
  const rows = alarms.slice(0, 10).map(
70493
70653
  (a) => row(
70494
70654
  esc8(a.deviceName ? `${a.deviceName} \u2014 ${a.title || "Alarme"}` : a.title || "Alarme"),
70495
- `${esc8(a.severity || "")} \xB7 ${esc8(a.state || "")}`
70655
+ [a.severity, a.state].filter(Boolean).map(esc8).join(" \xB7 ")
70496
70656
  )
70497
70657
  ).join("");
70498
- return `${header("\u{1F6A8}", "Alarmes", meta.alarms ?? alarms.length)}<div style="margin-bottom:6px">${chips}</div>${rows}`;
70658
+ return frame(
70659
+ "\u{1F6A8}",
70660
+ "Alarmes",
70661
+ total("Total de Alarmes", meta.alarms ?? alarms.length) + (chips ? section("Por estado") + `<div class="myio-welcome-meta-tip__chips">${chips}</div>` : "") + section("Mais recentes") + (rows || empty("Sem alarmes ativos")) + more(alarms.length)
70662
+ );
70499
70663
  }
70500
70664
  if (type === "annotations-locked") {
70501
- return header("\u{1F4CB}", "Anota\xE7\xF5es") + '<div style="opacity:.85;padding:6px 0">\u{1F512} Funcionalidade n\xE3o liberada ainda \u2014 em breve.</div>';
70665
+ return frame("\u{1F4CB}", "Anota\xE7\xF5es", empty("\u{1F512} Funcionalidade n\xE3o liberada ainda \u2014 em breve."));
70502
70666
  }
70503
70667
  if (type === "annotations") {
70504
- if (meta.annotations === null) return header("\u{1F4CB}", "Anota\xE7\xF5es") + loadingBody;
70668
+ if (meta.annotations === null) return frame("\u{1F4CB}", "Anota\xE7\xF5es", loadingBody);
70505
70669
  const annotations = details.annotations || [];
70506
70670
  const byType = {};
70507
70671
  for (const a of annotations) {
70508
70672
  const t = a.type || "observation";
70509
70673
  byType[t] = (byType[t] || 0) + 1;
70510
70674
  }
70511
- const chips = Object.entries(byType).map(([t, n]) => chip(`${t}: ${n}`)).join("") || '<span style="opacity:.7">Sem anota\xE7\xF5es ativas</span>';
70675
+ const chips = Object.entries(byType).map(([t, n]) => chip(`${t}: ${n}`)).join("");
70512
70676
  const rows = annotations.slice(0, 10).map((a) => {
70513
70677
  const when = a.createdAt ? new Date(a.createdAt).toLocaleDateString("pt-BR") : "";
70514
70678
  const text2 = a.text.length > 90 ? `${a.text.slice(0, 90)}\u2026` : a.text;
70515
70679
  return row(esc8(a.deviceLabel || "Dispositivo"), `${esc8(text2)}${when ? ` \xB7 ${esc8(when)}` : ""}`);
70516
70680
  }).join("");
70517
- return `${header("\u{1F4CB}", "Anota\xE7\xF5es", meta.annotations ?? annotations.length)}<div style="margin-bottom:6px">${chips}</div>${rows}`;
70681
+ return frame(
70682
+ "\u{1F4CB}",
70683
+ "Anota\xE7\xF5es",
70684
+ total("Total de Anota\xE7\xF5es", meta.annotations ?? annotations.length) + (chips ? section("Por tipo") + `<div class="myio-welcome-meta-tip__chips">${chips}</div>` : "") + section("Mais recentes") + (rows || empty("Sem anota\xE7\xF5es ativas")) + more(annotations.length)
70685
+ );
70518
70686
  }
70519
70687
  if (type === "users") {
70520
- if (meta.users === null) return header("\u{1F465}", "Usu\xE1rios") + loadingBody;
70688
+ if (meta.users === null) return frame("\u{1F465}", "Usu\xE1rios", loadingBody);
70521
70689
  const users = details.users || [];
70522
- const rows = users.slice(0, 10).map((u) => row(esc8(u.name), esc8(u.email || ""))).join("") || '<div style="opacity:.7;padding:6px 0">Sem usu\xE1rios</div>';
70523
- const more = users.length > 10 ? `<div style="opacity:.7;padding:6px 0 0">\u2026 e mais ${users.length - 10}</div>` : "";
70524
- return `${header("\u{1F465}", "Usu\xE1rios", meta.users ?? users.length)}${rows}${more}`;
70690
+ const rows = users.slice(0, 10).map((u) => row(esc8(u.name), esc8(u.email || ""))).join("");
70691
+ return frame(
70692
+ "\u{1F465}",
70693
+ "Usu\xE1rios",
70694
+ total("Total de Usu\xE1rios", meta.users ?? users.length) + section("Usu\xE1rios") + (rows || empty("Sem usu\xE1rios")) + more(users.length)
70695
+ );
70525
70696
  }
70526
- return header("\u{1F514}", "Notifica\xE7\xF5es", meta.notifications ?? 0) + '<div style="opacity:.7;padding:6px 0">Sem notifica\xE7\xF5es</div>';
70697
+ return frame(
70698
+ "\u{1F514}",
70699
+ "Notifica\xE7\xF5es",
70700
+ total("Total de Notifica\xE7\xF5es", meta.notifications ?? 0) + empty("Sem notifica\xE7\xF5es")
70701
+ );
70527
70702
  }
70528
70703
  /**
70529
70704
  * Handle tooltip icon click - shows the appropriate tooltip
@@ -75035,10 +75210,12 @@ function openOperationalComparisonModal(params) {
75035
75210
  endDate: params.endDate,
75036
75211
  devices: params.devices || []
75037
75212
  };
75038
- render2(wrapper, modalId, state6);
75039
- bindEvents3(wrapper, modalId, state6, params.onClose);
75213
+ const ref = { chrome: null };
75214
+ mount2(wrapper, modalId, state6, ref, params.onClose);
75040
75215
  return {
75041
75216
  destroy: () => {
75217
+ ref.chrome?.destroy();
75218
+ ref.chrome = null;
75042
75219
  wrapper.remove();
75043
75220
  params.onClose?.();
75044
75221
  }
@@ -75441,30 +75618,39 @@ function getThemeColors3(theme) {
75441
75618
  accent: "#7c3aed"
75442
75619
  };
75443
75620
  }
75444
- function bindEvents3(container, modalId, state6, onClose) {
75445
- const closeBtn = container.querySelector(`#${modalId}-close`);
75446
- const themeBtn = container.querySelector(`#${modalId}-theme`);
75447
- const maxBtn = container.querySelector(`#${modalId}-max`);
75621
+ function mount2(container, modalId, state6, ref, onClose) {
75622
+ ref.chrome?.destroy();
75623
+ render2(container, modalId, state6);
75624
+ const content = container.querySelector(".myio-op-compare-content");
75448
75625
  const overlay = container.querySelector(".myio-op-compare-overlay");
75449
75626
  const close = () => {
75627
+ ref.chrome?.destroy();
75628
+ ref.chrome = null;
75450
75629
  container.remove();
75451
75630
  onClose?.();
75452
75631
  };
75453
- closeBtn?.addEventListener("click", close);
75454
- if (overlay) {
75455
- overlay.addEventListener("click", (e) => {
75456
- if (e.target === overlay) close();
75457
- });
75458
- }
75459
- themeBtn?.addEventListener("click", () => {
75460
- state6.theme = state6.theme === "dark" ? "light" : "dark";
75461
- render2(container, modalId, state6);
75462
- bindEvents3(container, modalId, state6, onClose);
75632
+ ref.chrome = createPremiumModalChrome({
75633
+ modalId: `${modalId}-chrome`,
75634
+ icon: "\u{1F4CA}",
75635
+ title: `Compara\xE7\xE3o Operacional (${state6.devices.length} equipamentos)`,
75636
+ modalEl: content,
75637
+ headerSlot: content.querySelector(".myio-op-compare-header"),
75638
+ theme: state6.theme,
75639
+ showThemeToggle: true,
75640
+ maximized: state6.isMaximized,
75641
+ maximizedClass: "maximized",
75642
+ headerRadius: "12px 12px 0 0",
75643
+ onClose: close,
75644
+ onThemeChange: (t) => {
75645
+ state6.theme = t;
75646
+ mount2(container, modalId, state6, ref, onClose);
75647
+ },
75648
+ onMaximizeChange: (m) => {
75649
+ state6.isMaximized = m;
75650
+ }
75463
75651
  });
75464
- maxBtn?.addEventListener("click", () => {
75465
- state6.isMaximized = !state6.isMaximized;
75466
- render2(container, modalId, state6);
75467
- bindEvents3(container, modalId, state6, onClose);
75652
+ overlay?.addEventListener("click", (e) => {
75653
+ if (e.target === overlay) close();
75468
75654
  });
75469
75655
  }
75470
75656
 
@@ -79939,7 +80125,7 @@ function openGoalsPanel(params) {
79939
80125
  state6.conflictVersion = null;
79940
80126
  render3();
79941
80127
  }
79942
- function mount2() {
80128
+ function mount3() {
79943
80129
  const existing = document.getElementById(ROOT_ID2);
79944
80130
  if (existing) existing.remove();
79945
80131
  rootEl = document.createElement("div");
@@ -81014,7 +81200,7 @@ ${state6.year}-03-15T08,15`)}">${esc4(state6.csv)}</textarea>
81014
81200
  }
81015
81201
  };
81016
81202
  }
81017
- mount2();
81203
+ mount3();
81018
81204
  return instance;
81019
81205
  }
81020
81206
 
@@ -118275,6 +118461,10 @@ var MenuView = class {
118275
118461
  // Tab being previewed in modal
118276
118462
  // Filter Modal (RFC-0121 ModalHeader controller pattern)
118277
118463
  filterModalHeaderController = null;
118464
+ /** Footer premium (holding · hora · versão) da modal Filtro de Shoppings — criado a cada abertura */
118465
+ filterModalFooter = null;
118466
+ /** Idem para a modal "Selecione a visualização" */
118467
+ unifiedModalFooter = null;
118278
118468
  // Unified Navigation Modal (RFC-0121 ModalHeader controller pattern)
118279
118469
  unifiedModalHeaderController = null;
118280
118470
  // RFC-0126: State for unified modal when controller fails (same as filter modal)
@@ -119867,7 +120057,10 @@ var MenuView = class {
119867
120057
  modalId: "menuUnified",
119868
120058
  theme: "dark",
119869
120059
  isMaximized: false,
119870
- showThemeToggle: true,
120060
+ primaryColor: MYIO_ACCENT,
120061
+ // padrão das modais premium: acento do tema do dashboard
120062
+ showThemeToggle: false,
120063
+ // corpo é sempre claro — o toggle só trocava a cor do header
119871
120064
  showMaximize: true,
119872
120065
  showClose: true,
119873
120066
  borderRadius: "16px 16px 0 0"
@@ -120167,7 +120360,10 @@ var MenuView = class {
120167
120360
  modalId: "menuFilter",
120168
120361
  theme: this.getFilterModalDefaultTheme(),
120169
120362
  isMaximized: false,
120170
- showThemeToggle: true,
120363
+ primaryColor: MYIO_ACCENT,
120364
+ // padrão das modais premium: acento do tema do dashboard
120365
+ showThemeToggle: false,
120366
+ // a modal não tem estilos escuros — o toggle só trocava a cor do header
120171
120367
  showMaximize: true,
120172
120368
  showClose: true,
120173
120369
  borderRadius: "12px 12px 0 0"
@@ -120506,6 +120702,9 @@ var MenuView = class {
120506
120702
  modal.classList.add("is-open");
120507
120703
  btn?.setAttribute("aria-expanded", "true");
120508
120704
  this.updateUnifiedModalActiveStates();
120705
+ const content = modal.querySelector(".myio-unified-modal-content");
120706
+ this.unifiedModalFooter?.destroy();
120707
+ this.unifiedModalFooter = content ? this.createPremiumFooter(content) : null;
120509
120708
  this.bindUnifiedModalButtonsFallback();
120510
120709
  if (this.configTemplate.enableDebugMode) {
120511
120710
  console.log("[MenuView] Unified modal opened");
@@ -120521,6 +120720,8 @@ var MenuView = class {
120521
120720
  if (modal) {
120522
120721
  modal.classList.remove("is-open");
120523
120722
  btn?.setAttribute("aria-expanded", "false");
120723
+ this.unifiedModalFooter?.destroy();
120724
+ this.unifiedModalFooter = null;
120524
120725
  this.unifiedModalHeaderController?.reset();
120525
120726
  if (this.unifiedModalHeaderController) {
120526
120727
  this.applyUnifiedModalTheme(this.unifiedModalHeaderController.getTheme());
@@ -120786,6 +120987,7 @@ var MenuView = class {
120786
120987
  if (modal) {
120787
120988
  modal.classList.add("is-open");
120788
120989
  this.renderFilterList();
120990
+ this.mountFilterModalFooter();
120789
120991
  requestAnimationFrame(() => {
120790
120992
  this.ensureFilterModalHeaderController();
120791
120993
  this.bindFilterModalButtonsFallback();
@@ -120804,12 +121006,32 @@ var MenuView = class {
120804
121006
  if (modal) {
120805
121007
  modal.classList.remove("is-open");
120806
121008
  }
121009
+ this.filterModalFooter?.destroy();
121010
+ this.filterModalFooter = null;
120807
121011
  this.filterModalHeaderController?.reset();
120808
121012
  if (this.filterModalHeaderController) {
120809
121013
  this.applyFilterModalTheme(this.filterModalHeaderController.getTheme());
120810
121014
  this.applyFilterModalMaximize(this.filterModalHeaderController.isMaximized());
120811
121015
  }
120812
121016
  }
121017
+ /** Footer premium padrão (holding · hora · versão | Powered by MYIO) anexado ao fim do card. */
121018
+ createPremiumFooter(card) {
121019
+ const footer = createModalFooter({
121020
+ customerName: resolveDashboardCustomerName(),
121021
+ libVersion: { current: window.MyIOLibrary?.version },
121022
+ themeMode: "light"
121023
+ });
121024
+ footer.element.style.flexShrink = "0";
121025
+ card.appendChild(footer.element);
121026
+ return footer;
121027
+ }
121028
+ /** Footer premium no fim do card (abaixo dos botões Cancelar/Aplicar). */
121029
+ mountFilterModalFooter() {
121030
+ const card = this.root.querySelector(".myio-menu-filter-modal-card");
121031
+ if (!card) return;
121032
+ this.filterModalFooter?.destroy();
121033
+ this.filterModalFooter = this.createPremiumFooter(card);
121034
+ }
120813
121035
  getFilterModalDefaultTheme() {
120814
121036
  return "dark";
120815
121037
  }
@@ -143784,7 +144006,9 @@ var AlarmsNotificationsPanelView = class {
143784
144006
  eventHandlers = /* @__PURE__ */ new Map();
143785
144007
  debug;
143786
144008
  // Bulk-selection state
143787
- selectedTitles = /* @__PURE__ */ new Set();
144009
+ // Chave = id do card (único por card em todo modo de agrupamento). Por título, cards
144010
+ // distintos com o mesmo tipo de alarme eram selecionados juntos.
144011
+ selectedIds = /* @__PURE__ */ new Set();
143788
144012
  groupedAlarms = [];
143789
144013
  // View mode: 'card' (default) or 'list' (table)
143790
144014
  viewMode = "card";
@@ -143914,7 +144138,7 @@ var AlarmsNotificationsPanelView = class {
143914
144138
  * Build compact inline list header (filters + bulk actions button)
143915
144139
  */
143916
144140
  buildListHeaderTemplate(state6) {
143917
- const sel = this.selectedTitles.size;
144141
+ const sel = this.selectedIds.size;
143918
144142
  const isCard = this.viewMode === "card";
143919
144143
  const isList = this.viewMode === "list";
143920
144144
  const isConsol = this.groupMode === "consolidado";
@@ -144027,7 +144251,7 @@ var AlarmsNotificationsPanelView = class {
144027
144251
  const mode = btn.getAttribute("data-group-mode");
144028
144252
  if (mode === this.groupMode) return;
144029
144253
  this.groupMode = mode;
144030
- this.selectedTitles.clear();
144254
+ this.selectedIds.clear();
144031
144255
  this.root?.querySelectorAll(".alarms-group-btn").forEach((b) => {
144032
144256
  b.classList.toggle("is-active", b.getAttribute("data-group-mode") === mode);
144033
144257
  });
@@ -144040,7 +144264,7 @@ var AlarmsNotificationsPanelView = class {
144040
144264
  const mode = btn.getAttribute("data-hierarchy-mode");
144041
144265
  if (mode === this.hierarchyMode) return;
144042
144266
  this.hierarchyMode = mode;
144043
- this.selectedTitles.clear();
144267
+ this.selectedIds.clear();
144044
144268
  this.root?.querySelectorAll(".alarms-hierarchy-btn").forEach((b) => {
144045
144269
  b.classList.toggle("is-active", b.getAttribute("data-hierarchy-mode") === mode);
144046
144270
  });
@@ -144091,10 +144315,10 @@ var AlarmsNotificationsPanelView = class {
144091
144315
  this.root.addEventListener("change", (e) => {
144092
144316
  const input = e.target;
144093
144317
  if (input.type !== "checkbox" || !input.classList.contains("alarm-card-select")) return;
144094
- const title = input.dataset.alarmTitle;
144095
- if (!title) return;
144096
- if (input.checked) this.selectedTitles.add(title);
144097
- else this.selectedTitles.delete(title);
144318
+ const id = input.dataset.alarmId;
144319
+ if (!id) return;
144320
+ if (input.checked) this.selectedIds.add(id);
144321
+ else this.selectedIds.delete(id);
144098
144322
  const card = input.closest(".alarm-card");
144099
144323
  if (card) card.classList.toggle("alarm-card--selected", input.checked);
144100
144324
  const row = input.closest(".atbl-row");
@@ -144105,9 +144329,9 @@ var AlarmsNotificationsPanelView = class {
144105
144329
  const input = e.target;
144106
144330
  if (input.id !== "tblSelectAll") return;
144107
144331
  if (input.checked) {
144108
- this.groupedAlarms.forEach((a) => this.selectedTitles.add(a.title));
144332
+ this.groupedAlarms.forEach((a) => this.selectedIds.add(a.id));
144109
144333
  } else {
144110
- this.groupedAlarms.forEach((a) => this.selectedTitles.delete(a.title));
144334
+ this.groupedAlarms.forEach((a) => this.selectedIds.delete(a.id));
144111
144335
  }
144112
144336
  this.root?.querySelectorAll(".alarm-card-select").forEach((cb) => {
144113
144337
  cb.checked = input.checked;
@@ -144317,7 +144541,7 @@ var AlarmsNotificationsPanelView = class {
144317
144541
  onMore: (id, e) => this.handleMore(id, e),
144318
144542
  themeMode: state6.themeMode,
144319
144543
  showCustomerName: this.params.showCustomerName ?? true,
144320
- selected: this.selectedTitles.has(alarm.title),
144544
+ selected: this.selectedIds.has(alarm.id),
144321
144545
  showDeviceBadge: isSeparado,
144322
144546
  // separado: show device badge in header
144323
144547
  alarmTypes: isPorDispositivo ? alarm._alarmTypes ?? [] : void 0,
@@ -144389,11 +144613,11 @@ var AlarmsNotificationsPanelView = class {
144389
144613
  // =====================================================================
144390
144614
  // Bulk Selection
144391
144615
  // =====================================================================
144392
- handleAlarmSelect(title, selected) {
144616
+ handleAlarmSelect(id, selected) {
144393
144617
  if (selected) {
144394
- this.selectedTitles.add(title);
144618
+ this.selectedIds.add(id);
144395
144619
  } else {
144396
- this.selectedTitles.delete(title);
144620
+ this.selectedIds.delete(id);
144397
144621
  }
144398
144622
  this.updateBulkButton();
144399
144623
  }
@@ -144402,7 +144626,7 @@ var AlarmsNotificationsPanelView = class {
144402
144626
  const btn = this.root.querySelector("#bulkActionsBtn");
144403
144627
  const count = this.root.querySelector("#bulkCount");
144404
144628
  if (!btn || !count) return;
144405
- const sel = this.selectedTitles.size;
144629
+ const sel = this.selectedIds.size;
144406
144630
  btn.disabled = sel === 0;
144407
144631
  count.textContent = String(sel);
144408
144632
  count.style.display = sel === 0 ? "none" : "";
@@ -144471,6 +144695,24 @@ var AlarmsNotificationsPanelView = class {
144471
144695
  if (v) el2.style.setProperty(n, v);
144472
144696
  });
144473
144697
  }
144698
+ /**
144699
+ * Moldura padrão das modais premium: troca o header próprio pelo header no acento
144700
+ * do tema (maximizar opcional + fechar) e anexa o footer premium (holding · hora · versão).
144701
+ */
144702
+ mountModalChrome(modal, opts) {
144703
+ return createPremiumModalChrome({
144704
+ modalId: opts.id,
144705
+ icon: opts.icon,
144706
+ title: opts.title,
144707
+ modalEl: modal,
144708
+ headerSlot: modal.firstElementChild,
144709
+ theme: this.controller.getState().themeMode === "dark" ? "dark" : "light",
144710
+ showMaximize: !!opts.maximizedClass,
144711
+ maximizedClass: opts.maximizedClass,
144712
+ headerRadius: `${opts.radius} ${opts.radius} 0 0`,
144713
+ onClose: opts.onClose
144714
+ });
144715
+ }
144474
144716
  openFilterModal() {
144475
144717
  const state6 = this.controller.getState();
144476
144718
  const filters = state6.filters;
@@ -144588,7 +144830,11 @@ var AlarmsNotificationsPanelView = class {
144588
144830
  </div>
144589
144831
  </div>
144590
144832
  `;
144591
- const close = () => overlay.remove();
144833
+ let chrome = null;
144834
+ const close = () => {
144835
+ chrome?.destroy();
144836
+ overlay.remove();
144837
+ };
144592
144838
  overlay.addEventListener("click", (e) => {
144593
144839
  const tabBtn = e.target.closest("[data-afm-tab]");
144594
144840
  if (!tabBtn) return;
@@ -144622,7 +144868,6 @@ var AlarmsNotificationsPanelView = class {
144622
144868
  item.style.display = label.includes(q) ? "" : "none";
144623
144869
  });
144624
144870
  });
144625
- overlay.querySelector(".afm-close").addEventListener("click", close);
144626
144871
  overlay.querySelector("#afmCancelBtn").addEventListener("click", close);
144627
144872
  overlay.addEventListener("click", (e) => {
144628
144873
  if (e.target === overlay) close();
@@ -144650,6 +144895,14 @@ var AlarmsNotificationsPanelView = class {
144650
144895
  close();
144651
144896
  });
144652
144897
  this._applyCssVars(overlay);
144898
+ chrome = this.mountModalChrome(overlay.querySelector(".afm-modal"), {
144899
+ id: "afmChrome",
144900
+ icon: "\u{1F50D}",
144901
+ title: "Filtrar Alarmes",
144902
+ radius: "14px",
144903
+ maximizedClass: "afm-modal--max",
144904
+ onClose: close
144905
+ });
144653
144906
  document.body.appendChild(overlay);
144654
144907
  }
144655
144908
  // =====================================================================
@@ -144701,7 +144954,7 @@ var AlarmsNotificationsPanelView = class {
144701
144954
  const sev = SEVERITY_CONFIG[alarm.severity];
144702
144955
  const st = STATE_CONFIG[alarm.state];
144703
144956
  const isActive = alarm.state !== "CLOSED";
144704
- const sel = this.selectedTitles.has(alarm.title);
144957
+ const sel = this.selectedIds.has(alarm.id);
144705
144958
  const escTitle = this.esc(alarm.title);
144706
144959
  const escCustomer = this.esc(alarm.customerName || "-");
144707
144960
  const escSource = this.esc(alarm.source || "-");
@@ -144737,7 +144990,7 @@ var AlarmsNotificationsPanelView = class {
144737
144990
  <td class="atbl-cell atbl-cell--actions">${actionBtns}</td>
144738
144991
  </tr>`;
144739
144992
  }).join("");
144740
- const allSelected = alarms.length > 0 && alarms.every((a) => this.selectedTitles.has(a.title));
144993
+ const allSelected = alarms.length > 0 && alarms.every((a) => this.selectedIds.has(a.id));
144741
144994
  return `
144742
144995
  <table class="alarms-table" aria-label="Lista de alarmes">
144743
144996
  <thead>
@@ -144801,9 +145054,17 @@ var AlarmsNotificationsPanelView = class {
144801
145054
  </div>
144802
145055
  `;
144803
145056
  this._applyCssVars(overlay);
145057
+ const chrome = this.mountModalChrome(overlay.querySelector(".aex-modal"), {
145058
+ id: "aexChrome",
145059
+ icon: "\u{1F4E4}",
145060
+ title: "Exportar Alarmes",
145061
+ radius: "14px",
145062
+ onClose: () => close()
145063
+ });
144804
145064
  document.body.appendChild(overlay);
144805
145065
  requestAnimationFrame(() => overlay.classList.add("aex-overlay--visible"));
144806
145066
  const close = () => {
145067
+ chrome.destroy();
144807
145068
  overlay.classList.remove("aex-overlay--visible");
144808
145069
  setTimeout(() => overlay.remove(), 220);
144809
145070
  document.removeEventListener("keydown", onKey);
@@ -145135,8 +145396,8 @@ var AlarmsNotificationsPanelView = class {
145135
145396
  }, 500);
145136
145397
  }
145137
145398
  openBulkActionPicker() {
145138
- if (this.selectedTitles.size === 0) return;
145139
- const selected = this.groupedAlarms.filter((a) => this.selectedTitles.has(a.title));
145399
+ if (this.selectedIds.size === 0) return;
145400
+ const selected = this.groupedAlarms.filter((a) => this.selectedIds.has(a.id));
145140
145401
  const count = selected.length;
145141
145402
  const listHtml = selected.slice(0, 5).map((a) => `<li class="abm-alarm-item">${this.esc(a.title)}</li>`).join("");
145142
145403
  const moreHtml = count > 5 ? `<li class="abm-alarm-item abm-alarm-more">+${count - 5} mais...</li>` : "";
@@ -145173,9 +145434,17 @@ var AlarmsNotificationsPanelView = class {
145173
145434
  </div>
145174
145435
  `;
145175
145436
  this._applyCssVars(overlay);
145437
+ const chrome = this.mountModalChrome(overlay.querySelector(".abm-modal"), {
145438
+ id: "abmChrome",
145439
+ icon: "\u26A1",
145440
+ title: "A\xE7\xF5es em Lote",
145441
+ radius: "14px",
145442
+ onClose: () => close()
145443
+ });
145176
145444
  document.body.appendChild(overlay);
145177
145445
  requestAnimationFrame(() => overlay.classList.add("abm-overlay--visible"));
145178
145446
  const close = () => {
145447
+ chrome.destroy();
145179
145448
  overlay.classList.remove("abm-overlay--visible");
145180
145449
  setTimeout(() => overlay.remove(), 220);
145181
145450
  document.removeEventListener("keydown", onKey);
@@ -145273,6 +145542,13 @@ var AlarmsNotificationsPanelView = class {
145273
145542
  </div>
145274
145543
  `;
145275
145544
  this._applyCssVars(overlay);
145545
+ const chrome = this.mountModalChrome(overlay.querySelector(".alarm-action-modal"), {
145546
+ id: "aamBulkChrome",
145547
+ icon: cfg.icon,
145548
+ title: cfg.title,
145549
+ radius: "16px",
145550
+ onClose: () => close()
145551
+ });
145276
145552
  document.body.appendChild(overlay);
145277
145553
  requestAnimationFrame(() => overlay.classList.add("alarm-action-modal-overlay--visible"));
145278
145554
  const textarea = overlay.querySelector("#aamText");
@@ -145286,6 +145562,7 @@ var AlarmsNotificationsPanelView = class {
145286
145562
  if (cfg.textRequired) confirmBtn.disabled = len === 0;
145287
145563
  });
145288
145564
  const close = () => {
145565
+ chrome.destroy();
145289
145566
  overlay.classList.remove("alarm-action-modal-overlay--visible");
145290
145567
  setTimeout(() => overlay.remove(), 220);
145291
145568
  document.removeEventListener("keydown", onKey);
@@ -145312,7 +145589,7 @@ var AlarmsNotificationsPanelView = class {
145312
145589
  const until = new Date(Date.now() + minutes * 60 * 1e3).toISOString();
145313
145590
  this.controller.handleSnooze(ids, until);
145314
145591
  }
145315
- this.selectedTitles.clear();
145592
+ this.selectedIds.clear();
145316
145593
  this.updateBulkButton();
145317
145594
  close();
145318
145595
  this.emit(`alarm-${type}`, selectedAlarms.map((a) => a.id));
@@ -145776,6 +146053,13 @@ var AlarmsNotificationsPanelView = class {
145776
146053
  </div>
145777
146054
  `;
145778
146055
  this._applyCssVars(overlay);
146056
+ const chrome = this.mountModalChrome(overlay.querySelector(".alarm-action-modal"), {
146057
+ id: "aamChrome",
146058
+ icon: cfg.icon,
146059
+ title: cfg.title,
146060
+ radius: "16px",
146061
+ onClose: () => closeModal2()
146062
+ });
145779
146063
  document.body.appendChild(overlay);
145780
146064
  requestAnimationFrame(() => overlay.classList.add("alarm-action-modal-overlay--visible"));
145781
146065
  const textarea = overlay.querySelector("#aamText");
@@ -145789,6 +146073,7 @@ var AlarmsNotificationsPanelView = class {
145789
146073
  if (cfg.textRequired) confirmBtn.disabled = len === 0;
145790
146074
  });
145791
146075
  const closeModal2 = () => {
146076
+ chrome.destroy();
145792
146077
  overlay.classList.remove("alarm-action-modal-overlay--visible");
145793
146078
  setTimeout(() => overlay.remove(), 220);
145794
146079
  document.removeEventListener("keydown", onKeyDown);
@@ -165622,7 +165907,7 @@ function statusStyle(status) {
165622
165907
  }
165623
165908
  function createPresetupGateway(opts) {
165624
165909
  const {
165625
- mount: mount2,
165910
+ mount: mount3,
165626
165911
  gatewayId,
165627
165912
  clientId,
165628
165913
  clientSecret,
@@ -165645,9 +165930,9 @@ function createPresetupGateway(opts) {
165645
165930
  });
165646
165931
  const ingestion = new IngestionApiClient(ingestionApiUrl, auth);
165647
165932
  const provisioning = new ProvisioningApiClient(provisioningApiUrl);
165648
- mount2.innerHTML = "";
165933
+ mount3.innerHTML = "";
165649
165934
  const root = el("div", S.root);
165650
- mount2.appendChild(root);
165935
+ mount3.appendChild(root);
165651
165936
  const header = el("div", S.header);
165652
165937
  const titleEl = el("span", "font-weight:600;font-size:15px;");
165653
165938
  titleEl.textContent = "PresetupGateway";
@@ -165957,7 +166242,7 @@ function createPresetupGateway(opts) {
165957
166242
  await load();
165958
166243
  },
165959
166244
  destroy() {
165960
- mount2.innerHTML = "";
166245
+ mount3.innerHTML = "";
165961
166246
  }
165962
166247
  };
165963
166248
  }