myio-js-library 0.1.524 → 0.1.525

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
@@ -402,6 +402,7 @@ __export(src_exports, {
402
402
  createFilterModalComponent: () => createFilterModalComponent,
403
403
  createFooterComponent: () => createFooterComponent,
404
404
  createFreshdeskTicket: () => createTicket,
405
+ createGoalsBarTooltip: () => createGoalsBarTooltip,
405
406
  createGranularitySelector: () => createGranularitySelector,
406
407
  createGroupScheduleCard: () => createGroupScheduleCard,
407
408
  createHeaderComponent: () => createHeaderComponent,
@@ -591,7 +592,7 @@ __export(src_exports, {
591
592
  injectBASDashboardStyles: () => injectBASDashboardStyles,
592
593
  injectCustomerCardV1Styles: () => injectCustomerCardV1Styles,
593
594
  injectCustomerCardV2Styles: () => injectCustomerCardV2Styles,
594
- injectDeviceGridV6Styles: () => injectStyles16,
595
+ injectDeviceGridV6Styles: () => injectStyles17,
595
596
  injectDeviceOperationalCardGridStyles: () => injectDeviceOperationalCardGridStyles,
596
597
  injectDeviceOperationalCardStyles: () => injectDeviceOperationalCardStyles,
597
598
  injectExportButtonStyles: () => injectExportButtonStyles,
@@ -767,7 +768,7 @@ module.exports = __toCommonJS(src_exports);
767
768
  // package.json
768
769
  var package_default = {
769
770
  name: "myio-js-library",
770
- version: "0.1.524",
771
+ version: "0.1.525",
771
772
  description: "A clean, standalone JS SDK for MYIO projects",
772
773
  license: "MIT",
773
774
  repository: "github:gh-myio/myio-js-library",
@@ -23851,7 +23852,8 @@ var DEFAULT_LOADING_CONFIG = {
23851
23852
  message: "Carregando dados...",
23852
23853
  spinnerType: "double",
23853
23854
  theme: "dark",
23854
- showTimer: false
23855
+ showTimer: false,
23856
+ showProgress: false
23855
23857
  };
23856
23858
 
23857
23859
  // src/components/loading-spinner/LoadingSpinner.ts
@@ -23866,11 +23868,15 @@ var LoadingSpinner = class {
23866
23868
  minDisplayTimeoutId = null;
23867
23869
  hidePending = false;
23868
23870
  theme;
23871
+ progressElement = null;
23872
+ progressFillElement = null;
23873
+ progressLabelElement = null;
23869
23874
  styleElement = null;
23870
23875
  constructor(config2 = {}) {
23871
23876
  this.config = { ...DEFAULT_LOADING_CONFIG, ...config2 };
23872
23877
  this.theme = this.config.theme || "dark";
23873
23878
  this.container = this.ensureDOM();
23879
+ this.applyAccent();
23874
23880
  this.updateTheme(this.theme);
23875
23881
  this.injectStyles();
23876
23882
  if (this.config.showTimer) {
@@ -23879,6 +23885,9 @@ var LoadingSpinner = class {
23879
23885
  if (this.config.message) {
23880
23886
  this.updateMessage(this.config.message);
23881
23887
  }
23888
+ if (this.config.showProgress || this.config.progress != null) {
23889
+ this.setProgress(this.config.progress ?? null);
23890
+ }
23882
23891
  }
23883
23892
  /**
23884
23893
  * Ensures the main DOM element for the spinner exists in the body.
@@ -23886,27 +23895,57 @@ var LoadingSpinner = class {
23886
23895
  ensureDOM() {
23887
23896
  const BUSY_OVERLAY_ID = "myio-loading-spinner-overlay";
23888
23897
  let el2 = document.getElementById(BUSY_OVERLAY_ID);
23889
- if (el2) {
23890
- return el2;
23898
+ if (!el2) {
23899
+ el2 = document.createElement("div");
23900
+ el2.id = BUSY_OVERLAY_ID;
23901
+ el2.className = "myio-loading-spinner-overlay";
23902
+ el2.style.display = "none";
23903
+ const contentContainer = document.createElement("div");
23904
+ contentContainer.className = "myio-loading-spinner-content";
23905
+ contentContainer.innerHTML = `
23906
+ <div class="myio-spinner-box">
23907
+ <div class="myio-spinner-outer"></div>
23908
+ <div class="myio-spinner-inner"></div>
23909
+ </div>
23910
+ <div class="myio-spinner-message">${this.config.message}</div>
23911
+ <div class="myio-spinner-progress" style="display:none;">
23912
+ <div class="myio-spinner-progress-track"><div class="myio-spinner-progress-fill"></div></div>
23913
+ <div class="myio-spinner-progress-label"></div>
23914
+ </div>
23915
+ `;
23916
+ el2.appendChild(contentContainer);
23917
+ document.body.appendChild(el2);
23891
23918
  }
23892
- el2 = document.createElement("div");
23893
- el2.id = BUSY_OVERLAY_ID;
23894
- el2.className = "myio-loading-spinner-overlay";
23895
- el2.style.display = "none";
23896
- const contentContainer = document.createElement("div");
23897
- contentContainer.className = "myio-loading-spinner-content";
23898
- contentContainer.innerHTML = `
23899
- <div class="myio-spinner-box">
23900
- <div class="myio-spinner-outer"></div>
23901
- <div class="myio-spinner-inner"></div>
23902
- </div>
23903
- <div class="myio-spinner-message">${this.config.message}</div>
23904
- `;
23905
- el2.appendChild(contentContainer);
23906
- document.body.appendChild(el2);
23907
23919
  this.messageElement = el2.querySelector(".myio-spinner-message");
23920
+ let progress = el2.querySelector(".myio-spinner-progress");
23921
+ if (!progress) {
23922
+ const content = el2.querySelector(".myio-loading-spinner-content");
23923
+ if (content) {
23924
+ progress = document.createElement("div");
23925
+ progress.className = "myio-spinner-progress";
23926
+ progress.style.display = "none";
23927
+ progress.innerHTML = '<div class="myio-spinner-progress-track"><div class="myio-spinner-progress-fill"></div></div><div class="myio-spinner-progress-label"></div>';
23928
+ content.appendChild(progress);
23929
+ }
23930
+ }
23931
+ this.progressElement = progress;
23932
+ this.progressFillElement = el2.querySelector(".myio-spinner-progress-fill");
23933
+ this.progressLabelElement = el2.querySelector(".myio-spinner-progress-label");
23908
23934
  return el2;
23909
23935
  }
23936
+ /**
23937
+ * Resolves the accent color (config.accentColor → inherited `--myio-brand-700`
23938
+ * → default purple) and stashes it on the overlay as `--myio-ls-accent`, which
23939
+ * the spinner ring, progress fill and dark panel tint read from.
23940
+ */
23941
+ applyAccent() {
23942
+ let accent = (this.config.accentColor || "").trim();
23943
+ if (!accent && typeof document !== "undefined") {
23944
+ accent = getComputedStyle(document.documentElement).getPropertyValue("--myio-brand-700").trim();
23945
+ }
23946
+ if (!accent) accent = "#7a2ff7";
23947
+ this.container.style.setProperty("--myio-ls-accent", accent);
23948
+ }
23910
23949
  /**
23911
23950
  * Inject necessary CSS styles for the spinner and overlay.
23912
23951
  */
@@ -23934,7 +23973,7 @@ var LoadingSpinner = class {
23934
23973
  .myio-spinner-inner {
23935
23974
  width: 32px; height: 32px; border-width: 3px;
23936
23975
  border-style: solid; border-radius: 50%;
23937
- border-color: rgba(122, 47, 247, 1) transparent rgba(122, 47, 247, 1) transparent;
23976
+ border-color: var(--myio-ls-accent, #7a2ff7) transparent var(--myio-ls-accent, #7a2ff7) transparent;
23938
23977
  position: absolute; top: 8px; left: 8px; /* Position inside outer */
23939
23978
  animation: myio-spin-reverse 1.8s linear infinite;
23940
23979
  }
@@ -24032,7 +24071,7 @@ var LoadingSpinner = class {
24032
24071
  border-color: rgba(45, 20, 88, 0.2) transparent rgba(45, 20, 88, 0.2) transparent;
24033
24072
  }
24034
24073
  .myio-loading-spinner-overlay.light .myio-spinner-inner {
24035
- border-color: rgba(122, 47, 247, 1) transparent rgba(122, 47, 247, 1) transparent;
24074
+ border-color: var(--myio-ls-accent, #7a2ff7) transparent var(--myio-ls-accent, #7a2ff7) transparent;
24036
24075
  }
24037
24076
 
24038
24077
  /* Timer element styling */
@@ -24043,6 +24082,29 @@ var LoadingSpinner = class {
24043
24082
  text-align: center;
24044
24083
  }
24045
24084
  .myio-loading-spinner-overlay.light .myio-spinner-timer { color: #6b7280; }
24085
+
24086
+ /* Progress bar (determinate via setProgress(pct) / indeterminate when null) */
24087
+ .myio-spinner-progress { width: 100%; min-width: 200px; margin-top: 16px; }
24088
+ .myio-spinner-progress-track {
24089
+ width: 100%; height: 6px; border-radius: 999px;
24090
+ background: rgba(255, 255, 255, 0.18); overflow: hidden;
24091
+ }
24092
+ .myio-loading-spinner-overlay.light .myio-spinner-progress-track { background: rgba(0, 0, 0, 0.10); }
24093
+ .myio-spinner-progress-fill {
24094
+ height: 100%; width: 0%; border-radius: 999px;
24095
+ background: var(--myio-ls-accent, #7a2ff7);
24096
+ transition: width 0.3s ease;
24097
+ }
24098
+ .myio-spinner-progress.indeterminate .myio-spinner-progress-fill {
24099
+ width: 40% !important; animation: myio-ls-indeterminate 1.2s ease-in-out infinite;
24100
+ }
24101
+ @keyframes myio-ls-indeterminate {
24102
+ 0% { margin-left: -40%; }
24103
+ 100% { margin-left: 100%; }
24104
+ }
24105
+ .myio-spinner-progress-label {
24106
+ margin-top: 6px; font-size: 11px; font-weight: 600; opacity: 0.85; text-align: center;
24107
+ }
24046
24108
  `;
24047
24109
  }
24048
24110
  /**
@@ -24116,7 +24178,7 @@ var LoadingSpinner = class {
24116
24178
  this.container.classList.add(theme);
24117
24179
  const content = this.container.querySelector(".myio-loading-spinner-content");
24118
24180
  if (content) {
24119
- content.style.background = theme === "dark" ? "#2d1458" : "#ffffff";
24181
+ content.style.background = theme === "dark" ? "color-mix(in srgb, var(--myio-ls-accent, #7a2ff7) 22%, #14121c)" : "#ffffff";
24120
24182
  content.style.color = theme === "dark" ? "#ffffff" : "#1a1a2e";
24121
24183
  }
24122
24184
  }
@@ -24152,6 +24214,7 @@ var LoadingSpinner = class {
24152
24214
  this.isCurrentlyShowing = true;
24153
24215
  this.hidePending = false;
24154
24216
  this.startTime = Date.now();
24217
+ this.applyAccent();
24155
24218
  this.container.style.display = "flex";
24156
24219
  requestAnimationFrame(() => {
24157
24220
  this.container.classList.add("show");
@@ -24219,6 +24282,26 @@ var LoadingSpinner = class {
24219
24282
  this.messageElement.textContent = message;
24220
24283
  }
24221
24284
  }
24285
+ /**
24286
+ * Updates the progress bar. `pct` 0–100 → determinate fill; `null` →
24287
+ * indeterminate (animated) bar. `label` overrides the percentage text.
24288
+ */
24289
+ setProgress(pct, label) {
24290
+ if (!this.progressElement || !this.progressFillElement) return;
24291
+ this.progressElement.style.display = "block";
24292
+ if (pct == null || Number.isNaN(Number(pct))) {
24293
+ this.progressElement.classList.add("indeterminate");
24294
+ this.progressFillElement.style.width = "";
24295
+ if (this.progressLabelElement) this.progressLabelElement.textContent = label || "";
24296
+ } else {
24297
+ const clamped = Math.max(0, Math.min(100, Number(pct)));
24298
+ this.progressElement.classList.remove("indeterminate");
24299
+ this.progressFillElement.style.width = `${clamped}%`;
24300
+ if (this.progressLabelElement) {
24301
+ this.progressLabelElement.textContent = label != null ? label : `${Math.round(clamped)}%`;
24302
+ }
24303
+ }
24304
+ }
24222
24305
  /**
24223
24306
  * Checks if spinner is currently visible
24224
24307
  * @returns true if spinner is showing
@@ -24236,6 +24319,11 @@ var LoadingSpinner = class {
24236
24319
  this.isCurrentlyShowing = false;
24237
24320
  this.hidePending = false;
24238
24321
  this.startTime = 0;
24322
+ if (this.progressElement) {
24323
+ this.progressElement.style.display = "none";
24324
+ this.progressElement.classList.remove("indeterminate");
24325
+ }
24326
+ if (this.progressFillElement) this.progressFillElement.style.width = "0%";
24239
24327
  console.log("[LoadingSpinner] Instance destroyed (DOM kept for re-use)");
24240
24328
  }
24241
24329
  };
@@ -28410,8 +28498,8 @@ var EnergyDataFetcher = class {
28410
28498
  // src/components/premium-modals/internal/engines/CsvExporter.ts
28411
28499
  var toCsv = (rows, locale = "pt-BR", sep = ";") => {
28412
28500
  const fmt2 = new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
28413
- const esc5 = (v) => (typeof v === "number" ? fmt2.format(v) : String(v)).replace(/"/g, '""');
28414
- return rows.map((r) => r.map((c) => `"${esc5(c)}"`).join(sep)).join("\r\n");
28501
+ const esc6 = (v) => (typeof v === "number" ? fmt2.format(v) : String(v)).replace(/"/g, '""');
28502
+ return rows.map((r) => r.map((c) => `"${esc6(c)}"`).join(sep)).join("\r\n");
28415
28503
  };
28416
28504
 
28417
28505
  // src/utils/telemetryUtils.ts
@@ -37597,10 +37685,10 @@ var WaterTankModalView = class {
37597
37685
  */
37598
37686
  render() {
37599
37687
  const { params } = this.config;
37600
- const STYLE_ID13 = "myio-water-tank-modal-styles";
37601
- if (!document.getElementById(STYLE_ID13)) {
37688
+ const STYLE_ID14 = "myio-water-tank-modal-styles";
37689
+ if (!document.getElementById(STYLE_ID14)) {
37602
37690
  const style = document.createElement("style");
37603
- style.id = STYLE_ID13;
37691
+ style.id = STYLE_ID14;
37604
37692
  style.textContent = `
37605
37693
  @media (max-width: 640px) {
37606
37694
  .myio-water-tank-modal { width: 100vw !important; max-width: 100vw !important; max-height: 100vh !important; border-radius: 0 !important; }
@@ -43242,12 +43330,12 @@ var WelcomeModalView = class _WelcomeModalView {
43242
43330
  if (this.metaTipEl) this.metaTipEl.style.display = "none";
43243
43331
  }
43244
43332
  buildMetaTipHtml(type, card) {
43245
- const esc5 = _WelcomeModalView.escapeMetaHtml;
43333
+ const esc6 = _WelcomeModalView.escapeMetaHtml;
43246
43334
  const chip = _WelcomeModalView.metaChip;
43247
43335
  const meta = card.metaCounts || {};
43248
43336
  const details = card.metaDetails || {};
43249
43337
  const loadingBody = '<div style="opacity:.7;padding:6px 0">Carregando\u2026</div>';
43250
- const header = (icon, label, count) => `<div style="font-weight:800;font-size:13px;margin-bottom:8px">${icon} ${label}${count === null || count === void 0 ? "" : ` (${count})`} \u2014 ${esc5(card.title)}</div>`;
43338
+ const header = (icon, label, count) => `<div style="font-weight:800;font-size:13px;margin-bottom:8px">${icon} ${label}${count === null || count === void 0 ? "" : ` (${count})`} \u2014 ${esc6(card.title)}</div>`;
43251
43339
  const row = (titleHtml, subHtml) => `<div style="padding:6px 0;border-top:1px solid #334155">
43252
43340
  <div style="font-weight:600">${titleHtml}</div>
43253
43341
  ${subHtml ? `<div style="opacity:.7">${subHtml}</div>` : ""}
@@ -43263,8 +43351,8 @@ var WelcomeModalView = class _WelcomeModalView {
43263
43351
  const chips = Object.entries(byState).map(([s, n]) => chip(`${s}: ${n}`)).join("") || '<span style="opacity:.7">Sem alarmes ativos</span>';
43264
43352
  const rows = alarms.slice(0, 10).map(
43265
43353
  (a) => row(
43266
- esc5(a.deviceName ? `${a.deviceName} \u2014 ${a.title || "Alarme"}` : a.title || "Alarme"),
43267
- `${esc5(a.severity || "")} \xB7 ${esc5(a.state || "")}`
43354
+ esc6(a.deviceName ? `${a.deviceName} \u2014 ${a.title || "Alarme"}` : a.title || "Alarme"),
43355
+ `${esc6(a.severity || "")} \xB7 ${esc6(a.state || "")}`
43268
43356
  )
43269
43357
  ).join("");
43270
43358
  return `${header("\u{1F6A8}", "Alarmes", meta.alarms ?? alarms.length)}<div style="margin-bottom:6px">${chips}</div>${rows}`;
@@ -43284,14 +43372,14 @@ var WelcomeModalView = class _WelcomeModalView {
43284
43372
  const rows = annotations.slice(0, 10).map((a) => {
43285
43373
  const when = a.createdAt ? new Date(a.createdAt).toLocaleDateString("pt-BR") : "";
43286
43374
  const text2 = a.text.length > 90 ? `${a.text.slice(0, 90)}\u2026` : a.text;
43287
- return row(esc5(a.deviceLabel || "Dispositivo"), `${esc5(text2)}${when ? ` \xB7 ${esc5(when)}` : ""}`);
43375
+ return row(esc6(a.deviceLabel || "Dispositivo"), `${esc6(text2)}${when ? ` \xB7 ${esc6(when)}` : ""}`);
43288
43376
  }).join("");
43289
43377
  return `${header("\u{1F4CB}", "Anota\xE7\xF5es", meta.annotations ?? annotations.length)}<div style="margin-bottom:6px">${chips}</div>${rows}`;
43290
43378
  }
43291
43379
  if (type === "users") {
43292
43380
  if (meta.users === null) return header("\u{1F465}", "Usu\xE1rios") + loadingBody;
43293
43381
  const users = details.users || [];
43294
- const rows = users.slice(0, 10).map((u) => row(esc5(u.name), esc5(u.email || ""))).join("") || '<div style="opacity:.7;padding:6px 0">Sem usu\xE1rios</div>';
43382
+ const rows = users.slice(0, 10).map((u) => row(esc6(u.name), esc6(u.email || ""))).join("") || '<div style="opacity:.7;padding:6px 0">Sem usu\xE1rios</div>';
43295
43383
  const more = users.length > 10 ? `<div style="opacity:.7;padding:6px 0 0">\u2026 e mais ${users.length - 10}</div>` : "";
43296
43384
  return `${header("\u{1F465}", "Usu\xE1rios", meta.users ?? users.length)}${rows}${more}`;
43297
43385
  }
@@ -65187,10 +65275,10 @@ ${this._formatFileSize(f.size)} \xB7 ${f.type || "arquivo"}`;
65187
65275
  // Styles
65188
65276
  // ==========================================================================
65189
65277
  injectStyles() {
65190
- const STYLE_ID13 = "myio-tickets-tab-styles";
65191
- if (document.getElementById(STYLE_ID13)) return;
65278
+ const STYLE_ID14 = "myio-tickets-tab-styles";
65279
+ if (document.getElementById(STYLE_ID14)) return;
65192
65280
  const style = document.createElement("style");
65193
- style.id = STYLE_ID13;
65281
+ style.id = STYLE_ID14;
65194
65282
  style.textContent = `
65195
65283
  /* ===== TicketsTab layout ===== */
65196
65284
  .ct-tab {
@@ -69638,7 +69726,7 @@ async function createInputDateRangePickerInsideDIV(params) {
69638
69726
  placeholder = "Clique para selecionar per\xEDodo",
69639
69727
  pickerOptions = {},
69640
69728
  classNames = {},
69641
- injectStyles: injectStyles20 = true,
69729
+ injectStyles: injectStyles21 = true,
69642
69730
  showHelper = true
69643
69731
  } = params;
69644
69732
  validateId(containerId, "containerId");
@@ -69647,7 +69735,7 @@ async function createInputDateRangePickerInsideDIV(params) {
69647
69735
  if (!container) {
69648
69736
  throw new Error(`[createInputDateRangePickerInsideDIV] Container '#${containerId}' not found`);
69649
69737
  }
69650
- if (injectStyles20) {
69738
+ if (injectStyles21) {
69651
69739
  injectPremiumStyles();
69652
69740
  }
69653
69741
  let inputEl = document.getElementById(inputId);
@@ -70366,7 +70454,7 @@ function openGoalsPanel(params) {
70366
70454
  rootEl.setAttribute("role", "dialog");
70367
70455
  rootEl.setAttribute("aria-modal", "true");
70368
70456
  container.appendChild(rootEl);
70369
- injectStyles20();
70457
+ injectStyles21();
70370
70458
  rootEl.addEventListener("click", onClick);
70371
70459
  rootEl.addEventListener("change", onChange);
70372
70460
  rootEl.addEventListener("input", onInput);
@@ -71021,7 +71109,7 @@ ${state6.year}-03-15T08,15`)}">${esc3(state6.csv)}</textarea>
71021
71109
  return "";
71022
71110
  }
71023
71111
  }
71024
- function injectStyles20() {
71112
+ function injectStyles21() {
71025
71113
  const id = "myio-goals-gcdr-styles";
71026
71114
  const prev = document.getElementById(id);
71027
71115
  if (prev) prev.remove();
@@ -77757,13 +77845,13 @@ var COLUMN_SUMMARY_CSS = `
77757
77845
  var _cssInjected2 = false;
77758
77846
  function injectCSS10() {
77759
77847
  if (_cssInjected2 || typeof document === "undefined") return;
77760
- const STYLE_ID13 = "myio-column-summary-tooltip-css";
77761
- if (document.getElementById(STYLE_ID13)) {
77848
+ const STYLE_ID14 = "myio-column-summary-tooltip-css";
77849
+ if (document.getElementById(STYLE_ID14)) {
77762
77850
  _cssInjected2 = true;
77763
77851
  return;
77764
77852
  }
77765
77853
  const style = document.createElement("style");
77766
- style.id = STYLE_ID13;
77854
+ style.id = STYLE_ID14;
77767
77855
  style.textContent = COLUMN_SUMMARY_CSS;
77768
77856
  document.head.appendChild(style);
77769
77857
  _cssInjected2 = true;
@@ -84857,7 +84945,7 @@ function createConsumptionChartWidget(config2) {
84857
84945
  </div>
84858
84946
  `;
84859
84947
  }
84860
- function injectStyles20() {
84948
+ function injectStyles21() {
84861
84949
  if (styleElement) return;
84862
84950
  styleElement = document.createElement("style");
84863
84951
  styleElement.id = `${widgetId}-styles`;
@@ -85371,7 +85459,7 @@ function createConsumptionChartWidget(config2) {
85371
85459
  console.error(`[ConsumptionWidget] Container #${config2.containerId} not found`);
85372
85460
  return;
85373
85461
  }
85374
- injectStyles20();
85462
+ injectStyles21();
85375
85463
  containerElement.innerHTML = renderHTML();
85376
85464
  setupListeners();
85377
85465
  applyCollapseState();
@@ -85504,6 +85592,340 @@ function createConsumptionChartWidget(config2) {
85504
85592
  return instance;
85505
85593
  }
85506
85594
 
85595
+ // src/components/tooltips/goals-bar-tooltip/createGoalsBarTooltip.ts
85596
+ var STYLE_ID5 = "myio-goals-bar-tooltip-styles";
85597
+ var DEFAULT_ACCENT = "#6c5ce7";
85598
+ function injectStyles11(doc) {
85599
+ if (doc.getElementById(STYLE_ID5)) return;
85600
+ const s = doc.createElement("style");
85601
+ s.id = STYLE_ID5;
85602
+ s.textContent = `
85603
+ .myio-gbt {
85604
+ position: fixed;
85605
+ z-index: 100000;
85606
+ top: 0;
85607
+ left: 0;
85608
+ opacity: 0;
85609
+ pointer-events: none; /* unpinned: never blocks the chart hover */
85610
+ transition: opacity .15s ease;
85611
+ font-family: Nunito, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
85612
+ }
85613
+ .myio-gbt.myio-gbt--visible { opacity: 1; }
85614
+ /* Header stays clickable even while unpinned, so the \u{1F4CC} pin is reachable as the
85615
+ cursor travels off the chart onto the tooltip (a short grace hide covers the gap). */
85616
+ .myio-gbt .myio-gbt__header { pointer-events: auto; }
85617
+ .myio-gbt.myio-gbt--pinned { pointer-events: auto; } /* fully interactive when pinned */
85618
+
85619
+ .myio-gbt__card {
85620
+ background: #ffffff;
85621
+ border: 1px solid #e6e8ef;
85622
+ border-radius: 12px;
85623
+ box-shadow: 0 12px 40px rgba(2,6,23,.18), 0 2px 8px rgba(2,6,23,.08);
85624
+ min-width: 250px;
85625
+ max-width: 360px;
85626
+ overflow: hidden;
85627
+ color: #1e293b;
85628
+ font-size: 12px;
85629
+ }
85630
+
85631
+ .myio-gbt__header {
85632
+ display: flex;
85633
+ align-items: center;
85634
+ gap: 8px;
85635
+ padding: 9px 12px;
85636
+ background: linear-gradient(90deg,
85637
+ color-mix(in srgb, var(--myio-gbt-accent) 14%, #fff) 0%,
85638
+ color-mix(in srgb, var(--myio-gbt-accent) 6%, #fff) 100%);
85639
+ border-bottom: 1px solid color-mix(in srgb, var(--myio-gbt-accent) 30%, #fff);
85640
+ }
85641
+ .myio-gbt__title-wrap { flex: 1; min-width: 0; }
85642
+ .myio-gbt__title {
85643
+ font-weight: 800;
85644
+ font-size: 13px;
85645
+ color: var(--myio-gbt-accent);
85646
+ white-space: nowrap;
85647
+ overflow: hidden;
85648
+ text-overflow: ellipsis;
85649
+ }
85650
+ .myio-gbt__subtitle {
85651
+ font-size: 10.5px;
85652
+ color: #64748b;
85653
+ margin-top: 1px;
85654
+ white-space: nowrap;
85655
+ overflow: hidden;
85656
+ text-overflow: ellipsis;
85657
+ }
85658
+ .myio-gbt__btn {
85659
+ width: 22px;
85660
+ height: 22px;
85661
+ border: none;
85662
+ background: rgba(255,255,255,.55);
85663
+ border-radius: 6px;
85664
+ cursor: pointer;
85665
+ display: flex;
85666
+ align-items: center;
85667
+ justify-content: center;
85668
+ font-size: 13px;
85669
+ line-height: 1;
85670
+ color: #64748b;
85671
+ transition: background .12s ease, color .12s ease, transform .12s ease;
85672
+ flex-shrink: 0;
85673
+ }
85674
+ .myio-gbt__btn:hover { background: rgba(255,255,255,.95); color: #1e293b; }
85675
+ .myio-gbt__btn--pin.is-pinned {
85676
+ background: var(--myio-gbt-accent);
85677
+ color: #fff;
85678
+ transform: rotate(-8deg);
85679
+ }
85680
+ .myio-gbt__btn--close { display: none; }
85681
+ .myio-gbt.myio-gbt--pinned .myio-gbt__btn--close { display: flex; }
85682
+
85683
+ .myio-gbt__body {
85684
+ padding: 6px 8px 8px;
85685
+ max-height: 60vh;
85686
+ overflow-y: auto;
85687
+ }
85688
+
85689
+ .myio-gbt__row {
85690
+ display: flex;
85691
+ align-items: center;
85692
+ gap: 6px;
85693
+ padding: 5px 6px;
85694
+ border-radius: 7px;
85695
+ }
85696
+ .myio-gbt__row:hover { background: #f5f6fb; }
85697
+ .myio-gbt__row--child { padding-top: 4px; padding-bottom: 4px; }
85698
+ .myio-gbt__row--child .myio-gbt__label { font-weight: 500; color: #475569; }
85699
+ .myio-gbt__row--child .myio-gbt__value { font-weight: 600; }
85700
+
85701
+ .myio-gbt__caret {
85702
+ width: 16px;
85703
+ height: 16px;
85704
+ border: none;
85705
+ background: transparent;
85706
+ color: var(--myio-gbt-accent);
85707
+ cursor: pointer;
85708
+ font-size: 11px;
85709
+ line-height: 1;
85710
+ display: flex;
85711
+ align-items: center;
85712
+ justify-content: center;
85713
+ padding: 0;
85714
+ flex-shrink: 0;
85715
+ transition: transform .12s ease;
85716
+ user-select: none;
85717
+ }
85718
+ .myio-gbt__caret.is-open { transform: rotate(90deg); }
85719
+ .myio-gbt__caret-spacer { width: 16px; flex-shrink: 0; }
85720
+
85721
+ .myio-gbt__swatch {
85722
+ width: 9px;
85723
+ height: 9px;
85724
+ border-radius: 3px;
85725
+ flex-shrink: 0;
85726
+ }
85727
+ .myio-gbt__icon { font-size: 12px; flex-shrink: 0; }
85728
+ .myio-gbt__label {
85729
+ flex: 1;
85730
+ min-width: 0;
85731
+ font-weight: 700;
85732
+ white-space: nowrap;
85733
+ overflow: hidden;
85734
+ text-overflow: ellipsis;
85735
+ }
85736
+ .myio-gbt__value {
85737
+ font-weight: 800;
85738
+ color: #0f172a;
85739
+ white-space: nowrap;
85740
+ font-variant-numeric: tabular-nums;
85741
+ }
85742
+ .myio-gbt__pct {
85743
+ min-width: 46px;
85744
+ text-align: right;
85745
+ font-size: 11px;
85746
+ font-weight: 700;
85747
+ color: var(--myio-gbt-accent);
85748
+ font-variant-numeric: tabular-nums;
85749
+ flex-shrink: 0;
85750
+ }
85751
+ .myio-gbt__children {
85752
+ display: none;
85753
+ margin-left: 10px;
85754
+ padding-left: 6px;
85755
+ border-left: 2px solid color-mix(in srgb, var(--myio-gbt-accent) 22%, #fff);
85756
+ }
85757
+ .myio-gbt__children.is-open { display: block; }
85758
+
85759
+ .myio-gbt__empty {
85760
+ padding: 10px;
85761
+ text-align: center;
85762
+ color: #94a3b8;
85763
+ font-style: italic;
85764
+ }
85765
+ `;
85766
+ (doc.head || doc.documentElement).appendChild(s);
85767
+ }
85768
+ function esc5(v) {
85769
+ return String(v ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
85770
+ }
85771
+ function fmtPct3(pct) {
85772
+ if (pct == null || !isFinite(pct)) return "";
85773
+ return `${pct.toLocaleString("pt-BR", { minimumFractionDigits: 1, maximumFractionDigits: 1 })}%`;
85774
+ }
85775
+ function resolveAccent(explicit, doc) {
85776
+ if (explicit) return explicit;
85777
+ try {
85778
+ const v = getComputedStyle(doc.documentElement).getPropertyValue("--myio-brand-700").trim();
85779
+ if (v) return v;
85780
+ } catch {
85781
+ }
85782
+ return DEFAULT_ACCENT;
85783
+ }
85784
+ var _rowSeq = 0;
85785
+ function renderRow(row, level) {
85786
+ const hasChildren = Array.isArray(row.children) && row.children.length > 0;
85787
+ const open = !!row.defaultExpanded;
85788
+ const id = `gbtr-${++_rowSeq}`;
85789
+ const pct = row.pct;
85790
+ const caret = hasChildren ? `<button class="myio-gbt__caret${open ? " is-open" : ""}" data-caret="${id}" title="Expandir/recolher">\u25B8</button>` : `<span class="myio-gbt__caret-spacer"></span>`;
85791
+ const swatch = row.color ? `<span class="myio-gbt__swatch" style="background:${esc5(row.color)}"></span>` : "";
85792
+ const icon = row.icon ? `<span class="myio-gbt__icon">${esc5(row.icon)}</span>` : "";
85793
+ const pctHtml = pct != null && isFinite(pct) ? `<span class="myio-gbt__pct">${esc5(fmtPct3(pct))}</span>` : "";
85794
+ const rowClass = level > 0 ? "myio-gbt__row myio-gbt__row--child" : "myio-gbt__row";
85795
+ let html = `<div class="${rowClass}">` + caret + swatch + icon + `<span class="myio-gbt__label">${esc5(row.label)}</span><span class="myio-gbt__value">${esc5(row.valueText)}</span>` + pctHtml + `</div>`;
85796
+ if (hasChildren) {
85797
+ const childHtml = row.children.map((c) => renderRow(c, level + 1)).join("");
85798
+ html += `<div class="myio-gbt__children${open ? " is-open" : ""}" data-children="${id}">${childHtml}</div>`;
85799
+ }
85800
+ return html;
85801
+ }
85802
+ function createGoalsBarTooltip(opts) {
85803
+ const doc = (() => {
85804
+ try {
85805
+ return (window.top || window).document;
85806
+ } catch {
85807
+ return document;
85808
+ }
85809
+ })();
85810
+ injectStyles11(doc);
85811
+ const el2 = doc.createElement("div");
85812
+ el2.className = "myio-gbt";
85813
+ el2.style.setProperty("--myio-gbt-accent", resolveAccent(opts?.accentColor, doc));
85814
+ doc.body.appendChild(el2);
85815
+ let pinned = false;
85816
+ let hideTimer = null;
85817
+ const clearHideTimer = () => {
85818
+ if (hideTimer) {
85819
+ clearTimeout(hideTimer);
85820
+ hideTimer = null;
85821
+ }
85822
+ };
85823
+ el2.addEventListener("mouseenter", clearHideTimer);
85824
+ el2.addEventListener("mouseleave", () => {
85825
+ if (!pinned) hide();
85826
+ });
85827
+ function positionAt(clientX, clientY) {
85828
+ const rect = el2.getBoundingClientRect();
85829
+ const w = rect.width || 280;
85830
+ const h2 = rect.height || 200;
85831
+ const pad = 12;
85832
+ const gap = 16;
85833
+ let left = clientX + gap;
85834
+ let top = clientY + gap;
85835
+ if (left + w > window.innerWidth - pad) left = clientX - w - gap;
85836
+ if (left < pad) left = Math.max(pad, (window.innerWidth - w) / 2);
85837
+ if (top + h2 > window.innerHeight - pad) top = clientY - h2 - gap;
85838
+ if (top < pad) top = pad;
85839
+ if (top + h2 > window.innerHeight - pad) top = Math.max(pad, window.innerHeight - h2 - pad);
85840
+ el2.style.left = `${Math.round(left)}px`;
85841
+ el2.style.top = `${Math.round(top)}px`;
85842
+ }
85843
+ function wireInteractions() {
85844
+ const pinBtn = el2.querySelector('[data-action="pin"]');
85845
+ pinBtn?.addEventListener("click", (e) => {
85846
+ e.stopPropagation();
85847
+ pinned = !pinned;
85848
+ el2.classList.toggle("myio-gbt--pinned", pinned);
85849
+ pinBtn.classList.toggle("is-pinned", pinned);
85850
+ pinBtn.title = pinned ? "Desafixar" : "Fixar na tela";
85851
+ });
85852
+ const closeBtn = el2.querySelector('[data-action="close"]');
85853
+ closeBtn?.addEventListener("click", (e) => {
85854
+ e.stopPropagation();
85855
+ pinned = false;
85856
+ el2.classList.remove("myio-gbt--pinned");
85857
+ hide(true);
85858
+ });
85859
+ el2.querySelectorAll("[data-caret]").forEach((btn) => {
85860
+ btn.addEventListener("click", (e) => {
85861
+ e.stopPropagation();
85862
+ const id = btn.dataset.caret;
85863
+ const kids = el2.querySelector(`[data-children="${id}"]`);
85864
+ if (!kids) return;
85865
+ const isOpen = kids.classList.toggle("is-open");
85866
+ btn.classList.toggle("is-open", isOpen);
85867
+ });
85868
+ });
85869
+ }
85870
+ function render3(data) {
85871
+ const accent = resolveAccent(data.accentColor ?? opts?.accentColor, doc);
85872
+ el2.style.setProperty("--myio-gbt-accent", accent);
85873
+ const rowsHtml = data.rows && data.rows.length > 0 ? data.rows.map((r) => renderRow(r, 0)).join("") : `<div class="myio-gbt__empty">Sem dados para este per\xEDodo</div>`;
85874
+ el2.innerHTML = `
85875
+ <div class="myio-gbt__card">
85876
+ <div class="myio-gbt__header">
85877
+ <div class="myio-gbt__title-wrap">
85878
+ <div class="myio-gbt__title">${esc5(data.title)}</div>
85879
+ ${data.subtitle ? `<div class="myio-gbt__subtitle">${esc5(data.subtitle)}</div>` : ""}
85880
+ </div>
85881
+ <button class="myio-gbt__btn myio-gbt__btn--pin${pinned ? " is-pinned" : ""}" data-action="pin" title="${pinned ? "Desafixar" : "Fixar na tela"}">\u{1F4CC}</button>
85882
+ <button class="myio-gbt__btn myio-gbt__btn--close" data-action="close" title="Fechar">\u2715</button>
85883
+ </div>
85884
+ <div class="myio-gbt__body">${rowsHtml}</div>
85885
+ </div>`;
85886
+ if (pinned) el2.classList.add("myio-gbt--pinned");
85887
+ wireInteractions();
85888
+ }
85889
+ function show(data, evt) {
85890
+ clearHideTimer();
85891
+ if (pinned) return;
85892
+ render3(data);
85893
+ el2.classList.add("myio-gbt--visible");
85894
+ const x = evt && typeof evt.clientX === "number" ? evt.clientX : window.innerWidth / 2;
85895
+ const y = evt && typeof evt.clientY === "number" ? evt.clientY : window.innerHeight / 2;
85896
+ positionAt(x, y);
85897
+ requestAnimationFrame(() => {
85898
+ if (!pinned) positionAt(x, y);
85899
+ });
85900
+ }
85901
+ function hide(force) {
85902
+ if (pinned && !force) return;
85903
+ if (force) {
85904
+ clearHideTimer();
85905
+ el2.classList.remove("myio-gbt--visible");
85906
+ return;
85907
+ }
85908
+ clearHideTimer();
85909
+ hideTimer = setTimeout(() => {
85910
+ if (!pinned) el2.classList.remove("myio-gbt--visible");
85911
+ hideTimer = null;
85912
+ }, 260);
85913
+ }
85914
+ function destroy() {
85915
+ clearHideTimer();
85916
+ try {
85917
+ el2.remove();
85918
+ } catch {
85919
+ }
85920
+ }
85921
+ return {
85922
+ show,
85923
+ hide,
85924
+ isPinned: () => pinned,
85925
+ destroy
85926
+ };
85927
+ }
85928
+
85507
85929
  // src/components/goals-modal/GoalsModal.ts
85508
85930
  var DOMAIN_CFG = {
85509
85931
  energy: {
@@ -85513,20 +85935,22 @@ var DOMAIN_CFG = {
85513
85935
  unitLarge: "MWh",
85514
85936
  threshold: 1e3,
85515
85937
  primaryColor: "#3e1a7d",
85516
- barColor: "#6c5ce7",
85517
- barColorAlpha: "rgba(108,92,231,0.15)",
85518
- goalColor: "#f97316",
85519
- goalColorAlpha: "rgba(249,115,22,0.12)"
85938
+ // Esquema padrão das Metas: Realizado AZUL fixo #2563eb, Meta ROXO #7c3aed.
85939
+ barColor: "#2563eb",
85940
+ barColorAlpha: "rgba(37,99,235,0.15)",
85941
+ goalColor: "#7c3aed",
85942
+ goalColorAlpha: "rgba(124,58,237,0.12)"
85520
85943
  },
85521
85944
  water: {
85522
85945
  label: "\xC1gua",
85523
85946
  icon: "\u{1F4A7}",
85524
85947
  unit: "m\xB3",
85525
85948
  primaryColor: "#0288d1",
85526
- barColor: "#0891b2",
85527
- barColorAlpha: "rgba(8,145,178,0.15)",
85528
- goalColor: "#f59e0b",
85529
- goalColorAlpha: "rgba(245,158,11,0.12)"
85949
+ // Esquema padrão das Metas: Realizado AZUL fixo #2563eb, Meta ROXO #7c3aed.
85950
+ barColor: "#2563eb",
85951
+ barColorAlpha: "rgba(37,99,235,0.15)",
85952
+ goalColor: "#7c3aed",
85953
+ goalColorAlpha: "rgba(124,58,237,0.12)"
85530
85954
  },
85531
85955
  temperature: {
85532
85956
  label: "Temperatura",
@@ -85553,6 +85977,7 @@ var _chartType = "bar";
85553
85977
  var _lastRender = null;
85554
85978
  var _renderSeq = 0;
85555
85979
  var _datePickerControl = null;
85980
+ var _barTip = null;
85556
85981
  function _todayISO() {
85557
85982
  const d = /* @__PURE__ */ new Date();
85558
85983
  const mm = String(d.getMonth() + 1).padStart(2, "0");
@@ -85589,10 +86014,12 @@ function _applyTheme(root) {
85589
86014
  }
85590
86015
  function _formatValue(value, domain) {
85591
86016
  const cfg = DOMAIN_CFG[domain] || DOMAIN_CFG.energy;
86017
+ const dec = domain === "temperature" ? 1 : 2;
86018
+ const ptBR = (v, d) => v.toLocaleString("pt-BR", { minimumFractionDigits: d, maximumFractionDigits: d });
85592
86019
  if (cfg.threshold && cfg.unitLarge && Math.abs(value) >= cfg.threshold) {
85593
- return `${(value / cfg.threshold).toFixed(2)} ${cfg.unitLarge}`;
86020
+ return `${ptBR(value / cfg.threshold, 2)} ${cfg.unitLarge}`;
85594
86021
  }
85595
- return `${value.toFixed(domain === "temperature" ? 1 : 2)} ${cfg.unit}`;
86022
+ return `${ptBR(value, dec)} ${cfg.unit}`;
85596
86023
  }
85597
86024
  function _getGoalsData(domain) {
85598
86025
  const cached = window.MyIOUtils?.goalsData?.[domain] ?? null;
@@ -85699,8 +86126,10 @@ function _seriesToTotals(series, boundaries, viewGran) {
85699
86126
  const byKey = /* @__PURE__ */ new Map();
85700
86127
  for (const pt of series) {
85701
86128
  if (!pt) continue;
85702
- const iso = typeof pt.timestamp === "number" ? new Date(pt.timestamp).toISOString() : String(pt.timestamp);
85703
- const key = viewGran === "1M" ? iso.slice(5, 7) : iso.slice(5, 10);
86129
+ const dt = new Date(pt.timestamp);
86130
+ const mm = String(dt.getMonth() + 1).padStart(2, "0");
86131
+ const dd = String(dt.getDate()).padStart(2, "0");
86132
+ const key = viewGran === "1M" ? mm : `${mm}-${dd}`;
85704
86133
  byKey.set(key, (byKey.get(key) || 0) + (Number(pt.value) || 0));
85705
86134
  }
85706
86135
  return boundaries.map(
@@ -85808,6 +86237,34 @@ function _goalNodeValue(node) {
85808
86237
  const v = node.adjustedValue ?? node.value;
85809
86238
  return v == null ? null : v;
85810
86239
  }
86240
+ function _budgetNodeValue(node) {
86241
+ if (!node) return null;
86242
+ return node.value == null ? null : node.value;
86243
+ }
86244
+ function _buildBudgetLine(domain, labels, gran, dateISO) {
86245
+ const tree = _getGoalsTree(domain);
86246
+ if (!tree) return labels.map(() => null);
86247
+ if (gran === "1h") {
86248
+ const ref = (dateISO || _selectedDate).split("-");
86249
+ const mm = ref[1] ?? "";
86250
+ const dd = ref[2] ?? "";
86251
+ if (tree.hourly && Object.keys(tree.hourly).length > 0) {
86252
+ return labels.map((lbl) => {
86253
+ const hh = lbl.replace("h", "").padStart(2, "0");
86254
+ return _budgetNodeValue(tree.hourly[`${mm}-${dd}T${hh}`]);
86255
+ });
86256
+ }
86257
+ return labels.map(() => null);
86258
+ }
86259
+ if (gran === "1M") {
86260
+ return labels.map((lbl) => {
86261
+ const m = MONTH_LABELS_PT.indexOf(lbl);
86262
+ if (m < 0) return null;
86263
+ return _budgetNodeValue(tree.monthly?.[String(m + 1).padStart(2, "0")]);
86264
+ });
86265
+ }
86266
+ return labels.map((lbl) => _budgetNodeValue(tree.daily?.[_labelToDailyKey(lbl)]));
86267
+ }
85811
86268
  function _buildGoalLine(domain, labels, gran, dateISO) {
85812
86269
  const tree = _getGoalsTree(domain);
85813
86270
  if (!tree) return labels.map(() => null);
@@ -85908,6 +86365,101 @@ function _renderChart(canvas, labels, totals, goalLine, domain, prevTotals) {
85908
86365
  const useLarge = !!(cfg.unitLarge && cfg.threshold && yMax && yMax >= cfg.threshold);
85909
86366
  const axisDivisor = useLarge ? cfg.threshold : 1;
85910
86367
  const axisUnit = useLarge ? cfg.unitLarge : cfg.unit;
86368
+ const budgetLine = _buildBudgetLine(domain, labels, _currentGran, _selectedDate);
86369
+ let accent = cfg.primaryColor;
86370
+ try {
86371
+ const root = _overlay || _getTopDoc().documentElement;
86372
+ const v = getComputedStyle(root).getPropertyValue("--myio-brand-700").trim();
86373
+ if (v) accent = v;
86374
+ } catch {
86375
+ }
86376
+ const buildBarTipData = (idx) => {
86377
+ const realizado = totals[idx];
86378
+ const meta = goalLine[idx];
86379
+ const prev = prevTotals ? prevTotals[idx] : null;
86380
+ const budget = budgetLine[idx];
86381
+ const ref = meta != null && meta > 0 ? meta : realizado || null;
86382
+ const pctOf = (v) => v != null && ref ? v / ref * 100 : null;
86383
+ const rows = [];
86384
+ rows.push({
86385
+ icon: "\u{1F4CA}",
86386
+ label: "Realizado",
86387
+ color: cfg.barColor,
86388
+ valueText: _formatValue(realizado, domain),
86389
+ pct: meta != null && meta > 0 ? pctOf(realizado) : null
86390
+ });
86391
+ if (prev != null && prev > 0) {
86392
+ rows.push({
86393
+ icon: "\u{1F553}",
86394
+ label: "A-1 (ano anterior)",
86395
+ color: "#94a3b8",
86396
+ valueText: _formatValue(prev, domain),
86397
+ pct: pctOf(prev)
86398
+ });
86399
+ }
86400
+ if (meta != null) {
86401
+ const metaRow = {
86402
+ icon: "\u{1F3AF}",
86403
+ label: "Meta",
86404
+ color: cfg.goalColor,
86405
+ valueText: _formatValue(meta, domain)
86406
+ };
86407
+ const gdata = _getGoalsData(domain);
86408
+ if (gdata?.granularity === "DEVICE" && Array.isArray(gdata.devices) && gdata.devices.length && meta > 0) {
86409
+ const devs = gdata.devices.map((d) => ({
86410
+ label: d.label || d.code || "Medidor",
86411
+ annual: Number(d.annualAdjusted ?? d.annual ?? 0) || 0
86412
+ }));
86413
+ const totalAnnual = devs.reduce((s, d) => s + d.annual, 0);
86414
+ if (totalAnnual > 0) {
86415
+ metaRow.children = devs.map((d) => {
86416
+ const w = d.annual / totalAnnual;
86417
+ return {
86418
+ icon: cfg.icon,
86419
+ label: d.label,
86420
+ valueText: _formatValue(meta * w, domain),
86421
+ pct: w * 100
86422
+ };
86423
+ });
86424
+ metaRow.defaultExpanded = false;
86425
+ }
86426
+ }
86427
+ rows.push(metaRow);
86428
+ }
86429
+ if (budget != null && (meta == null || Math.abs(budget - meta) > 1e-6)) {
86430
+ rows.push({
86431
+ icon: "\u{1F4CB}",
86432
+ label: "Or\xE7ado",
86433
+ color: "#f59e0b",
86434
+ valueText: _formatValue(budget, domain),
86435
+ pct: pctOf(budget)
86436
+ });
86437
+ }
86438
+ let subtitle;
86439
+ if (meta != null && meta > 0) {
86440
+ const dev = (realizado - meta) / meta * 100;
86441
+ subtitle = Math.abs(dev) < 0.05 ? "Na Meta (0,0%)" : `${Math.abs(dev).toLocaleString("pt-BR", { minimumFractionDigits: 1, maximumFractionDigits: 1 })}% ${dev < 0 ? "abaixo" : "acima"} da Meta`;
86442
+ }
86443
+ return { title: labels[idx] ?? "", subtitle, rows, accentColor: accent };
86444
+ };
86445
+ const externalTip = _barTip ? (context) => {
86446
+ try {
86447
+ const tt = context?.tooltip;
86448
+ if (!tt || tt.opacity === 0) {
86449
+ _barTip.hide();
86450
+ return;
86451
+ }
86452
+ const dp = tt.dataPoints && tt.dataPoints[0];
86453
+ if (!dp) return;
86454
+ const idx = dp.dataIndex;
86455
+ const rect = context.chart.canvas.getBoundingClientRect();
86456
+ _barTip.show(buildBarTipData(idx), {
86457
+ clientX: rect.left + tt.caretX,
86458
+ clientY: rect.top + tt.caretY
86459
+ });
86460
+ } catch {
86461
+ }
86462
+ } : void 0;
85911
86463
  _chartInstance = new Chart(canvas, {
85912
86464
  type: "bar",
85913
86465
  data: { labels, datasets },
@@ -85924,6 +86476,9 @@ function _renderChart(canvas, labels, totals, goalLine, domain, prevTotals) {
85924
86476
  labels: { color: "#374151", font: { size: 11 } }
85925
86477
  },
85926
86478
  tooltip: {
86479
+ // Premium tooltip via external handler; se indisponível, cai no built-in.
86480
+ enabled: !externalTip,
86481
+ external: externalTip,
85927
86482
  callbacks: {
85928
86483
  label(ctx) {
85929
86484
  const v = ctx.parsed.y;
@@ -86074,11 +86629,11 @@ function _rerenderFromCache() {
86074
86629
  const { labels, totals, goalLine, prevTotals, domain } = _lastRender;
86075
86630
  _renderChart(canvas, labels, totals, goalLine, domain, prevTotals);
86076
86631
  }
86077
- var STYLE_ID5 = "myio-goals-modal-v2-styles";
86632
+ var STYLE_ID6 = "myio-goals-modal-v2-styles";
86078
86633
  function _injectStyles(topDoc) {
86079
- if (topDoc.getElementById(STYLE_ID5)) return;
86634
+ if (topDoc.getElementById(STYLE_ID6)) return;
86080
86635
  const s = topDoc.createElement("style");
86081
- s.id = STYLE_ID5;
86636
+ s.id = STYLE_ID6;
86082
86637
  s.textContent = `
86083
86638
  .gm-overlay{position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.5);backdrop-filter:blur(4px);opacity:0;transition:opacity .2s ease;font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
86084
86639
  .gm-overlay.show{opacity:1;}
@@ -86262,19 +86817,19 @@ function _updateCoverageHint(topDoc) {
86262
86817
  const deviceCount = data.granularity === "DEVICE" ? data.devices?.length ?? 0 : 0;
86263
86818
  hint.textContent = `\u26A0 Meta incompleta para este dom\xEDnio/ano` + (deviceCount > 0 ? ` \xB7 Por medidor (${deviceCount})` : "") + ` \u2014 passe o mouse para detalhes`;
86264
86819
  tabs.insertAdjacentElement("afterend", hint);
86265
- const esc5 = (s) => String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
86820
+ const esc6 = (s) => String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
86266
86821
  try {
86267
86822
  _coverageDetach = InfoTooltip.attach(hint, () => {
86268
86823
  const d = _getGoalsData(_currentDomain);
86269
86824
  const parts = [];
86270
86825
  if (hasCoverageGaps(d?.coverageGaps)) {
86271
- parts.push(`<p style="margin:0 0 8px;">${esc5(buildCoverageWarningTextPtBR(d?.coverageGaps, "GERAL"))}</p>`);
86826
+ parts.push(`<p style="margin:0 0 8px;">${esc6(buildCoverageWarningTextPtBR(d?.coverageGaps, "GERAL"))}</p>`);
86272
86827
  }
86273
86828
  (d?.devices || []).forEach((dev) => {
86274
86829
  if (!hasCoverageGaps(dev?.coverageGaps)) return;
86275
86830
  const label = dev.label || dev.code || "medidor";
86276
86831
  parts.push(
86277
- `<p style="margin:0 0 8px;">${esc5(buildCoverageWarningTextPtBR(dev.coverageGaps, `do medidor ${label}`))}</p>`
86832
+ `<p style="margin:0 0 8px;">${esc6(buildCoverageWarningTextPtBR(dev.coverageGaps, `do medidor ${label}`))}</p>`
86278
86833
  );
86279
86834
  });
86280
86835
  return {
@@ -86313,6 +86868,16 @@ var GoalsModal = {
86313
86868
  overlay.appendChild(modal);
86314
86869
  topDoc.body.appendChild(overlay);
86315
86870
  _overlay = overlay;
86871
+ try {
86872
+ _barTip?.destroy();
86873
+ } catch {
86874
+ }
86875
+ _barTip = null;
86876
+ try {
86877
+ _barTip = createGoalsBarTooltip();
86878
+ } catch {
86879
+ _barTip = null;
86880
+ }
86316
86881
  _applyTheme(overlay);
86317
86882
  requestAnimationFrame(() => overlay.classList.add("show"));
86318
86883
  _wireEvents(overlay, topDoc);
@@ -86326,6 +86891,11 @@ var GoalsModal = {
86326
86891
  if (!_overlay) return;
86327
86892
  _overlay.classList.remove("show");
86328
86893
  _destroyChart();
86894
+ try {
86895
+ _barTip?.destroy();
86896
+ } catch {
86897
+ }
86898
+ _barTip = null;
86329
86899
  _datePickerControl?.destroy();
86330
86900
  _datePickerControl = null;
86331
86901
  if (_coverageDetach) {
@@ -88883,7 +89453,7 @@ function createMyIOTheme(settings, mode) {
88883
89453
  // src/components/settings-hub/openSettingsHubModal.ts
88884
89454
  var ROUTE_DELAY_MS = 250;
88885
89455
  var MODAL_ID = "myio-conf-picker";
88886
- var STYLE_ID6 = "myio-conf-picker-styles";
89456
+ var STYLE_ID7 = "myio-conf-picker-styles";
88887
89457
  var HUB_OPTIONS = [
88888
89458
  {
88889
89459
  action: "temperature",
@@ -89094,9 +89664,9 @@ function resolveHubThemeVars(explicit) {
89094
89664
  function openSettingsHubModal(options) {
89095
89665
  const { customerName = "", isSuperAdmin: isSuperAdmin2 = false, handlers } = options;
89096
89666
  const doc = options.targetDocument || resolveTopDocument();
89097
- if (!doc.getElementById(STYLE_ID6)) {
89667
+ if (!doc.getElementById(STYLE_ID7)) {
89098
89668
  const style = doc.createElement("style");
89099
- style.id = STYLE_ID6;
89669
+ style.id = STYLE_ID7;
89100
89670
  style.textContent = HUB_CSS;
89101
89671
  doc.head.appendChild(style);
89102
89672
  }
@@ -97329,7 +97899,7 @@ function refreshTicketBadges(opts) {
97329
97899
 
97330
97900
  // src/components/premium-modals/dialog/styles.ts
97331
97901
  var FONT_LINK_ID = "myio-dialog-font-nunito";
97332
- var STYLE_ID7 = "myio-dialog-styles";
97902
+ var STYLE_ID8 = "myio-dialog-styles";
97333
97903
  function injectDialogStyles() {
97334
97904
  if (!document.getElementById(FONT_LINK_ID)) {
97335
97905
  const link = document.createElement("link");
@@ -97338,9 +97908,9 @@ function injectDialogStyles() {
97338
97908
  link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
97339
97909
  document.head.appendChild(link);
97340
97910
  }
97341
- if (document.getElementById(STYLE_ID7)) return;
97911
+ if (document.getElementById(STYLE_ID8)) return;
97342
97912
  const style = document.createElement("style");
97343
- style.id = STYLE_ID7;
97913
+ style.id = STYLE_ID8;
97344
97914
  style.textContent = `
97345
97915
  .myio-dialog-overlay {
97346
97916
  position: fixed;
@@ -97610,7 +98180,7 @@ function openMessageDialog(params) {
97610
98180
  }
97611
98181
 
97612
98182
  // src/components/premium-modals/dialog/openGenericModal.ts
97613
- var STYLE_ID8 = "myio-genmodal-styles";
98183
+ var STYLE_ID9 = "myio-genmodal-styles";
97614
98184
  var FONT_LINK_ID2 = "myio-dialog-font-nunito";
97615
98185
  var BASE_Z_INDEX2 = 10600;
97616
98186
  var openCount2 = 0;
@@ -97629,9 +98199,9 @@ function injectGenericModalStyles() {
97629
98199
  link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
97630
98200
  document.head.appendChild(link);
97631
98201
  }
97632
- if (document.getElementById(STYLE_ID8)) return;
98202
+ if (document.getElementById(STYLE_ID9)) return;
97633
98203
  const style = document.createElement("style");
97634
- style.id = STYLE_ID8;
98204
+ style.id = STYLE_ID9;
97635
98205
  style.textContent = `
97636
98206
  .myio-genmodal-overlay {
97637
98207
  position: fixed;
@@ -97858,12 +98428,12 @@ function openGenericModal(params) {
97858
98428
  }
97859
98429
 
97860
98430
  // src/components/img-gallery/ScrollableTabs.ts
97861
- var STYLE_ID9 = "myio-tabs-styles";
98431
+ var STYLE_ID10 = "myio-tabs-styles";
97862
98432
  var FONT_LINK_ID3 = "myio-dialog-font-nunito";
97863
98433
  function escapeHtml11(value) {
97864
98434
  return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
97865
98435
  }
97866
- function injectStyles11() {
98436
+ function injectStyles12() {
97867
98437
  if (!document.getElementById(FONT_LINK_ID3)) {
97868
98438
  const link = document.createElement("link");
97869
98439
  link.id = FONT_LINK_ID3;
@@ -97871,9 +98441,9 @@ function injectStyles11() {
97871
98441
  link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
97872
98442
  document.head.appendChild(link);
97873
98443
  }
97874
- if (document.getElementById(STYLE_ID9)) return;
98444
+ if (document.getElementById(STYLE_ID10)) return;
97875
98445
  const style = document.createElement("style");
97876
- style.id = STYLE_ID9;
98446
+ style.id = STYLE_ID10;
97877
98447
  style.textContent = `
97878
98448
  .myio-tabs {
97879
98449
  display: flex;
@@ -97948,7 +98518,7 @@ function createScrollableTabs(options) {
97948
98518
  if (typeof document === "undefined") {
97949
98519
  throw new Error("[createScrollableTabs] requires a browser environment");
97950
98520
  }
97951
- injectStyles11();
98521
+ injectStyles12();
97952
98522
  const theme = options.theme ?? "light";
97953
98523
  const accent = options.accent ?? "#7C3AED";
97954
98524
  const showArrows = options.showArrows ?? "auto";
@@ -98081,13 +98651,13 @@ function createScrollableTabs(options) {
98081
98651
  }
98082
98652
 
98083
98653
  // src/components/img-gallery/ImgGallery.ts
98084
- var STYLE_ID10 = "myio-gallery-styles";
98654
+ var STYLE_ID11 = "myio-gallery-styles";
98085
98655
  var FONT_LINK_ID4 = "myio-dialog-font-nunito";
98086
98656
  function escapeHtml12(value) {
98087
98657
  return String(value == null ? "" : value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
98088
98658
  }
98089
98659
  var PRM = () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
98090
- function injectStyles12() {
98660
+ function injectStyles13() {
98091
98661
  if (!document.getElementById(FONT_LINK_ID4)) {
98092
98662
  const link = document.createElement("link");
98093
98663
  link.id = FONT_LINK_ID4;
@@ -98095,9 +98665,9 @@ function injectStyles12() {
98095
98665
  link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
98096
98666
  document.head.appendChild(link);
98097
98667
  }
98098
- if (document.getElementById(STYLE_ID10)) return;
98668
+ if (document.getElementById(STYLE_ID11)) return;
98099
98669
  const style = document.createElement("style");
98100
- style.id = STYLE_ID10;
98670
+ style.id = STYLE_ID11;
98101
98671
  style.textContent = `
98102
98672
  .myio-gallery {
98103
98673
  font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -98256,7 +98826,7 @@ function createImgGallery(options) {
98256
98826
  if (typeof document === "undefined") {
98257
98827
  throw new Error("[createImgGallery] requires a browser environment");
98258
98828
  }
98259
- injectStyles12();
98829
+ injectStyles13();
98260
98830
  const locale = options.locale === "en-US" ? "en-US" : "pt-BR";
98261
98831
  const theme = options.theme ?? "light";
98262
98832
  const accent = options.accent ?? "#7C3AED";
@@ -110972,7 +111542,7 @@ var MODAL_CSS2 = `
110972
111542
  background: rgba(255, 255, 255, 0.2);
110973
111543
  }
110974
111544
  `;
110975
- function injectStyles13() {
111545
+ function injectStyles14() {
110976
111546
  if (document.getElementById(CSS_ID5)) return;
110977
111547
  const style = document.createElement("style");
110978
111548
  style.id = CSS_ID5;
@@ -110989,7 +111559,7 @@ var FilterModalComponent = class {
110989
111559
  showDeviceGrid;
110990
111560
  deviceSearchQuery = "";
110991
111561
  constructor(options) {
110992
- injectStyles13();
111562
+ injectStyles14();
110993
111563
  this.options = options;
110994
111564
  this.themeMode = options.themeMode || "light";
110995
111565
  this.selectedCategories = new Set(
@@ -111893,7 +112463,7 @@ function createCustomerCardV1(params) {
111893
112463
  }
111894
112464
 
111895
112465
  // src/components/cards/customer-goals/v1.0.0/styles.ts
111896
- var STYLE_ID11 = "myio-customer-goals-card-css";
112466
+ var STYLE_ID12 = "myio-customer-goals-card-css";
111897
112467
  var CSS3 = `
111898
112468
  .myio-cgc{
111899
112469
  --cgc-surface:#ffffff;
@@ -111901,9 +112471,10 @@ var CSS3 = `
111901
112471
  --cgc-text:#1e293b;
111902
112472
  --cgc-muted:#64748b;
111903
112473
  --cgc-grid:rgba(100,116,139,.18);
111904
- --cgc-realized:#6c5ce7;
112474
+ --cgc-realized:#2563eb;
111905
112475
  --cgc-prev:#94a3b8;
111906
- --cgc-budget:#f59e0b;
112476
+ --cgc-budget:#7c3aed;
112477
+ --cgc-orcado:#f59e0b;
111907
112478
  --cgc-good:#16a34a;
111908
112479
  --cgc-bad:#ef4444;
111909
112480
  display:flex;flex-direction:column;
@@ -111953,53 +112524,54 @@ var CSS3 = `
111953
112524
  font:600 11px 'Nunito',sans-serif;color:var(--cgc-muted);
111954
112525
  }
111955
112526
  .myio-cgc__totals{
111956
- display:grid;grid-template-columns:repeat(3,1fr);
112527
+ display:grid;grid-template-columns:repeat(4,1fr);
111957
112528
  border-top:1px solid var(--cgc-border);margin-top:6px;
111958
112529
  }
111959
- .myio-cgc__totals--two{grid-template-columns:repeat(2,1fr);}
111960
- .myio-cgc__totals--one{grid-template-columns:1fr;}
111961
- .myio-cgc__total{padding:6px 4px;text-align:center;border-right:1px solid var(--cgc-border);min-width:0;}
112530
+ .myio-cgc__total{padding:6px 3px;text-align:center;border-right:1px solid var(--cgc-border);min-width:0;}
111962
112531
  .myio-cgc__total:last-child{border-right:none;}
111963
- .myio-cgc__total-label{display:block;font:700 10.5px 'Nunito',sans-serif;line-height:1.4;}
112532
+ .myio-cgc__total-label{display:block;font:700 9.5px 'Nunito',sans-serif;line-height:1.35;white-space:nowrap;}
111964
112533
  .myio-cgc__total-label--realized{color:var(--cgc-realized);}
111965
112534
  .myio-cgc__total-label--prev{color:var(--cgc-prev);}
111966
112535
  .myio-cgc__total-label--budget{color:var(--cgc-budget);}
111967
112536
  .myio-cgc__total-label--orcado{color:var(--cgc-orcado);}
111968
112537
  .myio-cgc__total-value{
111969
- display:block;font:700 12px 'Nunito',sans-serif;color:var(--cgc-text);
112538
+ display:block;font:700 11px 'Nunito',sans-serif;color:var(--cgc-text);
111970
112539
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
111971
112540
  }
111972
112541
  .myio-cgc__deltas{
111973
- display:grid;grid-template-columns:repeat(2,1fr);
112542
+ display:grid;grid-template-columns:repeat(4,1fr);
111974
112543
  border-top:1px solid var(--cgc-border);
111975
112544
  }
111976
- .myio-cgc__deltas--one{grid-template-columns:1fr;}
111977
- .myio-cgc__deltas--three{grid-template-columns:repeat(3,1fr);}
111978
- .myio-cgc__delta{padding:6px 4px 8px;text-align:center;border-right:1px solid var(--cgc-border);}
112545
+ .myio-cgc__delta{padding:5px 3px 8px;text-align:center;border-right:1px solid var(--cgc-border);min-width:0;}
111979
112546
  .myio-cgc__delta:last-child{border-right:none;}
111980
- .myio-cgc__delta-label{display:block;font:600 10.5px 'Nunito',sans-serif;color:var(--cgc-muted);line-height:1.5;}
111981
- /* "Meta <valor>" na coluna 3 dos deltas segue o azul da s\xE9rie de Meta. */
111982
- .myio-cgc__delta-label[data-total="budget"]{color:var(--cgc-budget);font-weight:700;}
111983
- .myio-cgc__delta-value{display:block;font:800 13px 'Nunito',sans-serif;}
112547
+ .myio-cgc__delta-label{display:block;font:600 9px 'Nunito',sans-serif;color:var(--cgc-muted);line-height:1.4;margin-bottom:2px;}
112548
+ .myio-cgc__delta-value{display:block;font:800 11.5px 'Nunito',sans-serif;}
112549
+ /* Coluna A-1: dois chips pequenos empilhados (Or\xE7ado vs A-1, Meta vs A-1). */
112550
+ .myio-cgc__delta-dual{display:flex;flex-direction:column;gap:2px;align-items:center;}
112551
+ .myio-cgc__delta-mini{display:inline-flex;align-items:center;gap:3px;line-height:1.2;}
112552
+ .myio-cgc__delta-mini-label{font:700 8px 'Nunito',sans-serif;color:var(--cgc-muted);}
112553
+ .myio-cgc__delta-mini .myio-cgc__delta-value{display:inline;font-size:9.5px;}
111984
112554
  .myio-cgc__delta-value--good{color:var(--cgc-good);}
111985
112555
  .myio-cgc__delta-value--bad{color:var(--cgc-bad);}
111986
112556
  .myio-cgc__delta-value--neutral{color:var(--cgc-muted);}
111987
112557
  `;
111988
112558
  function injectCustomerGoalsCardStyles() {
111989
112559
  if (typeof document === "undefined") return;
111990
- if (document.getElementById(STYLE_ID11)) return;
112560
+ if (document.getElementById(STYLE_ID12)) return;
111991
112561
  const tag = document.createElement("style");
111992
- tag.id = STYLE_ID11;
112562
+ tag.id = STYLE_ID12;
111993
112563
  tag.textContent = CSS3;
111994
112564
  document.head.appendChild(tag);
111995
112565
  }
111996
112566
 
111997
112567
  // src/components/cards/customer-goals/v1.0.0/CustomerGoalsCard.ts
111998
112568
  var COLORS = {
111999
- realized: "#6c5ce7",
112569
+ realized: "#2563eb",
112570
+ // Realizado (ano corrente) — AZUL fixo (não segue mais o accent do host)
112000
112571
  prev: "#94a3b8",
112001
- budget: "#0ea5e9",
112002
- // Meta (adjustedValue) — AZUL tracejado
112572
+ // A-1 (ano anterior) — CINZA
112573
+ budget: "#7c3aed",
112574
+ // Meta (adjustedValue) — ROXO tracejado
112003
112575
  orcado: "#f59e0b"
112004
112576
  // Orçado (value cru) — LARANJA tracejado (também a linha "Orçado" legada)
112005
112577
  };
@@ -112171,7 +112743,7 @@ var CustomerGoalsCard = class {
112171
112743
  u = "MWh";
112172
112744
  }
112173
112745
  }
112174
- const txt = n.toLocaleString(this.locale, { maximumFractionDigits: Math.abs(n) >= 100 ? 0 : 2 });
112746
+ const txt = n.toLocaleString(this.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
112175
112747
  return `${txt} ${u}`;
112176
112748
  }
112177
112749
  /** Compact axis ticks like the reference ("1M", "2M", "500k") */
@@ -112183,16 +112755,22 @@ var CustomerGoalsCard = class {
112183
112755
  if (abs >= 1e3) return `${num(v / 1e3)}k`;
112184
112756
  return num(v);
112185
112757
  }
112186
- /** Variance badge: consumption ABOVE the reference is bad (↑ red); below is good (↓ green). */
112758
+ fmtPct(pct) {
112759
+ return `${Math.abs(pct).toLocaleString(this.locale, {
112760
+ minimumFractionDigits: 1,
112761
+ maximumFractionDigits: 1
112762
+ })}%`;
112763
+ }
112764
+ /**
112765
+ * Variance vs TARGET (Orçado/Meta): consumption BELOW the reference is GOOD
112766
+ * (↓ green — under budget); ABOVE is bad (↑ red — over budget).
112767
+ */
112187
112768
  deltaBadge(realized, reference) {
112188
112769
  if (realized == null || reference == null || reference === 0) {
112189
112770
  return '<span class="myio-cgc__delta-value myio-cgc__delta-value--neutral">\u2014</span>';
112190
112771
  }
112191
112772
  const pct = (realized - reference) / reference * 100;
112192
- const txt = `${Math.abs(pct).toLocaleString(this.locale, {
112193
- minimumFractionDigits: 2,
112194
- maximumFractionDigits: 2
112195
- })}%`;
112773
+ const txt = this.fmtPct(pct);
112196
112774
  if (pct > 0) {
112197
112775
  return `<span class="myio-cgc__delta-value myio-cgc__delta-value--bad">&#8593; ${txt}</span>`;
112198
112776
  }
@@ -112201,6 +112779,25 @@ var CustomerGoalsCard = class {
112201
112779
  }
112202
112780
  return `<span class="myio-cgc__delta-value myio-cgc__delta-value--neutral">${txt}</span>`;
112203
112781
  }
112782
+ /**
112783
+ * Growth vs LAST YEAR (A-1): a value ABOVE the reference GREW (↑ green — bom);
112784
+ * below SHRANK (↓ red). Inverted semantics of deltaBadge — used pelas colunas
112785
+ * A-1/Realizado onde "subir vs ano anterior" é positivo.
112786
+ */
112787
+ growthBadge(current, reference) {
112788
+ if (current == null || reference == null || reference === 0) {
112789
+ return '<span class="myio-cgc__delta-value myio-cgc__delta-value--neutral">\u2014</span>';
112790
+ }
112791
+ const pct = (current - reference) / reference * 100;
112792
+ const txt = this.fmtPct(pct);
112793
+ if (pct > 0) {
112794
+ return `<span class="myio-cgc__delta-value myio-cgc__delta-value--good">&#8593; ${txt}</span>`;
112795
+ }
112796
+ if (pct < 0) {
112797
+ return `<span class="myio-cgc__delta-value myio-cgc__delta-value--bad">&#8595; ${txt}</span>`;
112798
+ }
112799
+ return `<span class="myio-cgc__delta-value myio-cgc__delta-value--neutral">${txt}</span>`;
112800
+ }
112204
112801
  // ── Rendering ─────────────────────────────────────────────────────────────
112205
112802
  esc(s) {
112206
112803
  return String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -112211,62 +112808,62 @@ var CustomerGoalsCard = class {
112211
112808
  const hasPrev = !!s.previousYear;
112212
112809
  const hasBudget = !!s.budget || !!(s.budgetBreakdown && s.budgetBreakdown.length);
112213
112810
  const hasOrcado = this.hasRawOrcado();
112214
- const totalCells = [
112215
- `<div class="myio-cgc__total">
112216
- <span class="myio-cgc__total-label myio-cgc__total-label--realized">Realizado</span>
112217
- <span class="myio-cgc__total-value" data-total="realized">${this.fmtQty(totals.realized)}</span>
112218
- </div>`
112219
- ];
112811
+ const t = totals;
112812
+ const yl = this.params.yearLabels;
112813
+ const curLbl = yl?.current || "Atual";
112814
+ const prevLbl = yl?.previous || "A-1";
112815
+ const orcadoVal = hasOrcado ? t.orcado : hasBudget ? t.budget : null;
112816
+ const hasMetaCol = hasOrcado && hasBudget;
112817
+ const valueCell = (cls, label, total, val) => `<div class="myio-cgc__total"><span class="myio-cgc__total-label myio-cgc__total-label--${cls}">${label}</span><span class="myio-cgc__total-value" data-total="${total}">${this.fmtQty(val)}</span></div>`;
112818
+ const deltaCell = (header, chipsHtml) => `<div class="myio-cgc__delta">${header ? `<span class="myio-cgc__delta-label">${header}</span>` : ""}${chipsHtml}</div>`;
112819
+ const miniChip = (label, badgeHtml2) => `<span class="myio-cgc__delta-mini"><span class="myio-cgc__delta-mini-label">${label}</span>${badgeHtml2}</span>`;
112820
+ const valueCells = [];
112821
+ const deltaCells = [];
112822
+ let anyDelta = false;
112220
112823
  if (hasPrev) {
112221
- totalCells.push(`<div class="myio-cgc__total">
112222
- <span class="myio-cgc__total-label myio-cgc__total-label--prev">A-1</span>
112223
- <span class="myio-cgc__total-value" data-total="prev">${this.fmtQty(totals.previousYear)}</span>
112224
- </div>`);
112225
- }
112226
- if (hasOrcado) {
112227
- totalCells.push(`<div class="myio-cgc__total">
112228
- <span class="myio-cgc__total-label myio-cgc__total-label--orcado">Or&ccedil;ado</span>
112229
- <span class="myio-cgc__total-value" data-total="orcado">${this.fmtQty(totals.orcado)}</span>
112230
- </div>`);
112231
- } else if (hasBudget) {
112232
- totalCells.push(`<div class="myio-cgc__total">
112233
- <span class="myio-cgc__total-label myio-cgc__total-label--budget">Or&ccedil;ado</span>
112234
- <span class="myio-cgc__total-value" data-total="budget">${this.fmtQty(totals.budget)}</span>
112235
- </div>`);
112236
- }
112237
- const totalsMod = totalCells.length === 1 ? " myio-cgc__totals--one" : totalCells.length === 2 ? " myio-cgc__totals--two" : "";
112238
- let deltasHtml = "";
112239
- const dcell = (labelHtml, badgeHtml2) => `<div class="myio-cgc__delta"><span class="myio-cgc__delta-label">${labelHtml}</span>${badgeHtml2}</div>`;
112240
- if (hasOrcado) {
112241
- const cells = [];
112242
- if (hasPrev) {
112243
- cells.push(dcell("vs A-1", this.deltaBadge(totals.realized, totals.previousYear)));
112244
- cells.push(dcell("vs Or&ccedil;ado", this.deltaBadge(totals.realized, totals.orcado)));
112824
+ valueCells.push(valueCell("prev", "A-1", "prev", t.previousYear));
112825
+ const grow = [];
112826
+ if (hasOrcado) grow.push(miniChip("Or&ccedil;.", this.growthBadge(t.orcado, t.previousYear)));
112827
+ if (hasMetaCol) grow.push(miniChip("Meta", this.growthBadge(t.budget, t.previousYear)));
112828
+ if (!hasOrcado && hasBudget) grow.push(miniChip("Or&ccedil;.", this.growthBadge(t.budget, t.previousYear)));
112829
+ if (grow.length) {
112830
+ anyDelta = true;
112831
+ const header = grow.length > 1 ? "vs Or&ccedil;ado &amp; vs Meta" : "vs Or&ccedil;ado";
112832
+ deltaCells.push(deltaCell(header, `<div class="myio-cgc__delta-dual">${grow.join("")}</div>`));
112245
112833
  } else {
112246
- cells.push(dcell("vs Or&ccedil;ado", this.deltaBadge(totals.realized, totals.orcado)));
112834
+ deltaCells.push(deltaCell("", ""));
112247
112835
  }
112248
- cells.push(
112249
- `<div class="myio-cgc__delta"><span class="myio-cgc__delta-label" data-total="budget">${hasBudget ? `Meta ${this.fmtQty(totals.budget)}` : "Meta"}</span>${hasBudget ? this.deltaBadge(totals.realized, totals.budget) : ""}</div>`
112836
+ }
112837
+ valueCells.push(valueCell("realized", "Realizado", "realized", t.realized));
112838
+ if (hasPrev) {
112839
+ anyDelta = true;
112840
+ deltaCells.push(
112841
+ deltaCell(`${this.esc(curLbl)} &times; ${this.esc(prevLbl)}`, this.growthBadge(t.realized, t.previousYear))
112250
112842
  );
112251
- const mod = cells.length === 3 ? " myio-cgc__deltas--three" : cells.length === 1 ? " myio-cgc__deltas--one" : "";
112252
- deltasHtml = `<div class="myio-cgc__deltas${mod}">${cells.join("")}</div>`;
112253
112843
  } else {
112254
- const deltaCells = [];
112255
- if (hasPrev) {
112256
- deltaCells.push(dcell("vs A-1", this.deltaBadge(totals.realized, totals.previousYear)));
112257
- }
112258
- if (hasBudget) {
112259
- deltaCells.push(dcell("vs Or&ccedil;ado", this.deltaBadge(totals.realized, totals.budget)));
112260
- }
112261
- deltasHtml = deltaCells.length ? `<div class="myio-cgc__deltas${deltaCells.length === 1 ? " myio-cgc__deltas--one" : ""}">${deltaCells.join("")}</div>` : "";
112262
- }
112844
+ deltaCells.push(deltaCell("", ""));
112845
+ }
112846
+ if (hasOrcado || hasBudget) {
112847
+ anyDelta = true;
112848
+ valueCells.push(valueCell("orcado", "Or&ccedil;ado", hasOrcado ? "orcado" : "budget", orcadoVal));
112849
+ deltaCells.push(deltaCell("vs Or&ccedil;ado", this.deltaBadge(t.realized, orcadoVal)));
112850
+ }
112851
+ if (hasMetaCol) {
112852
+ anyDelta = true;
112853
+ valueCells.push(valueCell("budget", "Meta", "budget", t.budget));
112854
+ deltaCells.push(deltaCell("vs Meta", this.deltaBadge(t.realized, t.budget)));
112855
+ }
112856
+ const nCols = valueCells.length;
112857
+ const gridStyle = `grid-template-columns:repeat(${nCols},1fr);`;
112858
+ const totalsHtml = `<div class="myio-cgc__totals" style="${gridStyle}">${valueCells.join("")}</div>`;
112859
+ const deltasHtml = anyDelta ? `<div class="myio-cgc__deltas" style="${gridStyle}">${deltaCells.join("")}</div>` : "";
112263
112860
  const clickable = typeof this.params.onClick === "function";
112264
112861
  const expandable = this.params.expandable !== false;
112265
112862
  this.el.innerHTML = `
112266
112863
  ${expandable ? `<button type="button" class="myio-cgc__expand" title="${this.expanded ? "Recolher" : "Expandir"}">${this.expanded ? "\u2715" : "\u26F6"}</button>` : ""}
112267
112864
  <h4 class="myio-cgc__title${clickable ? " myio-cgc__title--clickable" : ""}" title="${this.esc(this.params.title)}">${this.esc(this.params.title)}</h4>
112268
112865
  <div class="myio-cgc__chart"><canvas></canvas></div>
112269
- <div class="myio-cgc__totals${totalsMod}">${totalCells.join("")}</div>
112866
+ ${totalsHtml}
112270
112867
  ${deltasHtml}
112271
112868
  `;
112272
112869
  if (clickable) {
@@ -118365,7 +118962,7 @@ var TELEMETRY_GRID_SHOPPING_STYLES = `
118365
118962
  text-overflow: ellipsis;
118366
118963
  }
118367
118964
  `;
118368
- function injectStyles14(_container) {
118965
+ function injectStyles15(_container) {
118369
118966
  const styleId = "telemetry-grid-shopping-styles";
118370
118967
  if (document.getElementById(styleId)) {
118371
118968
  return;
@@ -118413,7 +119010,7 @@ var TelemetryGridShoppingView = class {
118413
119010
  // Render
118414
119011
  // =========================================================================
118415
119012
  render() {
118416
- injectStyles14(this.root);
119013
+ injectStyles15(this.root);
118417
119014
  this.root.innerHTML = this.buildHTML();
118418
119015
  this.cacheElements();
118419
119016
  this.bindEvents();
@@ -120084,7 +120681,7 @@ var TELEMETRY_INFO_SHOPPING_STYLES = `
120084
120681
  }
120085
120682
  }
120086
120683
  `;
120087
- function injectStyles15(_container) {
120684
+ function injectStyles16(_container) {
120088
120685
  const styleId = "telemetry-info-shopping-styles-v2";
120089
120686
  if (document.getElementById(styleId)) {
120090
120687
  return;
@@ -120144,7 +120741,7 @@ var TelemetryInfoShoppingView = class _TelemetryInfoShoppingView {
120144
120741
  // Render
120145
120742
  // =========================================================================
120146
120743
  render() {
120147
- injectStyles15(this.root);
120744
+ injectStyles16(this.root);
120148
120745
  this.root.innerHTML = this.buildHTML();
120149
120746
  this.cacheElements();
120150
120747
  this.bindEvents();
@@ -128891,10 +129488,10 @@ var AlarmsNotificationsPanelView = class {
128891
129488
  const renderListPanel = (group, items, sel) => {
128892
129489
  if (items.length === 0) return '<div class="afm-empty">Nenhum item encontrado</div>';
128893
129490
  return items.map((item) => {
128894
- const esc5 = this.esc(item);
129491
+ const esc6 = this.esc(item);
128895
129492
  return `<label class="afm-check-item">
128896
- <input type="checkbox" class="afm-checkbox" data-group="${group}" data-value="${esc5}"${sel.has(item) ? " checked" : ""}>
128897
- <span class="afm-check-label">${esc5}</span>
129493
+ <input type="checkbox" class="afm-checkbox" data-group="${group}" data-value="${esc6}"${sel.has(item) ? " checked" : ""}>
129494
+ <span class="afm-check-label">${esc6}</span>
128898
129495
  </label>`;
128899
129496
  }).join("");
128900
129497
  };
@@ -139320,7 +139917,7 @@ var STYLES = `
139320
139917
  color: var(--dgv6-ink-2);
139321
139918
  }
139322
139919
  `;
139323
- function injectStyles16() {
139920
+ function injectStyles17() {
139324
139921
  if (document.getElementById(CSS_ID6)) return;
139325
139922
  const style = document.createElement("style");
139326
139923
  style.id = CSS_ID6;
@@ -139359,7 +139956,7 @@ var DeviceGridV6View = class {
139359
139956
  // Render
139360
139957
  // =========================================================================
139361
139958
  render() {
139362
- injectStyles16();
139959
+ injectStyles17();
139363
139960
  this.root.innerHTML = this.buildHTML();
139364
139961
  this.cacheElements();
139365
139962
  this.bindEvents();
@@ -141729,7 +142326,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
141729
142326
  const docTitle = buildActivationReportTitle(deviceName, opts.customerName);
141730
142327
  const issuedAt = formatIssuedAt(/* @__PURE__ */ new Date(), locale);
141731
142328
  const utilization = data.totalHours > 0 ? (data.totalOnMinutes / (data.totalHours * 60) * 100).toFixed(1) : "0.0";
141732
- const esc5 = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
142329
+ const esc6 = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
141733
142330
  const htmlContent = `
141734
142331
  <!DOCTYPE html>
141735
142332
  <html lang="pt-BR">
@@ -141781,14 +142378,14 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
141781
142378
  <div class="brand">MyIO BAS</div>
141782
142379
  <h1>Relat\xF3rio de Acionamentos</h1>
141783
142380
  <div class="meta">
141784
- <div><b>Dispositivo:</b> ${esc5(deviceName)}</div>
142381
+ <div><b>Dispositivo:</b> ${esc6(deviceName)}</div>
141785
142382
  <div><b>Per\xEDodo:</b> ${periodStart} \u2014 ${periodEnd}</div>
141786
142383
  </div>
141787
142384
  </div>
141788
142385
  ${customerName ? `
141789
142386
  <div class="customer-badge">
141790
142387
  <div class="label">Cliente</div>
141791
- <div class="value">${esc5(customerName)}</div>
142388
+ <div class="value">${esc6(customerName)}</div>
141792
142389
  </div>` : ""}
141793
142390
  </div>
141794
142391
 
@@ -141799,7 +142396,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
141799
142396
  </div>
141800
142397
  <div class="summary-card accent-green">
141801
142398
  <div class="summary-value">${formatDurationMinutes(data.totalOnMinutes)}</div>
141802
- <div class="summary-label">Tempo ${esc5(labelOn)}</div>
142399
+ <div class="summary-label">Tempo ${esc6(labelOn)}</div>
141803
142400
  </div>
141804
142401
  <div class="summary-card accent-amber">
141805
142402
  <div class="summary-value">${utilization}%</div>
@@ -141827,7 +142424,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
141827
142424
  <tr>
141828
142425
  <td>${formatDateForExport(seg.startTime, locale)}</td>
141829
142426
  <td>${formatDateForExport(seg.endTime, locale)}</td>
141830
- <td><span class="pill ${seg.state === "on" ? "pill-on" : "pill-off"}">${seg.state === "on" ? esc5(labelOn) : esc5(labelOff)}</span></td>
142427
+ <td><span class="pill ${seg.state === "on" ? "pill-on" : "pill-off"}">${seg.state === "on" ? esc6(labelOn) : esc6(labelOff)}</span></td>
141831
142428
  <td>${formatDurationMinutes(seg.durationMinutes)}</td>
141832
142429
  <td>${seg.source === "manual" ? "Manual" : seg.source === "schedule" ? "Agendamento" : seg.source === "automation" ? "Automa\xE7\xE3o" : "Desconhecido"}</td>
141833
142430
  </tr>
@@ -141836,7 +142433,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
141836
142433
  </table>
141837
142434
 
141838
142435
  <div class="footer">
141839
- <span class="issued">Emitido em ${issuedAt}</span>${customerName ? ` &middot; ${esc5(customerName)}` : ""} &middot; <span class="brand">MyIO BAS</span>
142436
+ <span class="issued">Emitido em ${issuedAt}</span>${customerName ? ` &middot; ${esc6(customerName)}` : ""} &middot; <span class="brand">MyIO BAS</span>
141840
142437
  </div>
141841
142438
  </body>
141842
142439
  </html>
@@ -147826,7 +148423,7 @@ var STYLES2 = `
147826
148423
  }
147827
148424
  #${MODAL_ID4} .gcdr-fail-item:last-child { border-bottom: none; }
147828
148425
  `;
147829
- function injectStyles17() {
148426
+ function injectStyles18() {
147830
148427
  if (document.getElementById(`${MODAL_ID4}-styles`)) return;
147831
148428
  const style = document.createElement("style");
147832
148429
  style.id = `${MODAL_ID4}-styles`;
@@ -147935,7 +148532,7 @@ function escHtml3(str) {
147935
148532
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
147936
148533
  }
147937
148534
  async function openGCDRSyncModal(params) {
147938
- injectStyles17();
148535
+ injectStyles18();
147939
148536
  removeExistingModal();
147940
148537
  if (!params.thingsboardToken) {
147941
148538
  alert("Token ThingsBoard n\xE3o encontrado. Fa\xE7a login novamente.");
@@ -148503,7 +149100,7 @@ var STYLES3 = `
148503
149100
  .abm-confirm-btn--confirm { background: #e67e22; color: #fff; }
148504
149101
  .abm-confirm-btn--confirm:hover { background: #cf6d17; }
148505
149102
  `;
148506
- function injectStyles18() {
149103
+ function injectStyles19() {
148507
149104
  if (document.getElementById(`${MODAL_ID5}-styles`)) return;
148508
149105
  const style = document.createElement("style");
148509
149106
  style.id = `${MODAL_ID5}-styles`;
@@ -149181,7 +149778,7 @@ async function fetchBundle(customerTB_ID, gcdrTenantId, baseUrl) {
149181
149778
  return json.data;
149182
149779
  }
149183
149780
  async function openAlarmBundleMapModal(params) {
149184
- injectStyles18();
149781
+ injectStyles19();
149185
149782
  removeExistingModal2();
149186
149783
  if (!params.customerTB_ID) {
149187
149784
  alert("Customer TB ID n\xE3o informado. N\xE3o \xE9 poss\xEDvel carregar o mapa de alarmes.");
@@ -150329,11 +150926,11 @@ function _esc(s) {
150329
150926
  const str = s == null ? "" : String(s);
150330
150927
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
150331
150928
  }
150332
- var STYLE_ID12 = "ntw-styles";
150929
+ var STYLE_ID13 = "ntw-styles";
150333
150930
  function _injectStyles2() {
150334
- if (document.getElementById(STYLE_ID12)) return;
150931
+ if (document.getElementById(STYLE_ID13)) return;
150335
150932
  const s = document.createElement("style");
150336
- s.id = STYLE_ID12;
150933
+ s.id = STYLE_ID13;
150337
150934
  s.textContent = NTW_CSS;
150338
150935
  document.head.appendChild(s);
150339
150936
  }
@@ -153296,7 +153893,7 @@ var STYLES4 = `
153296
153893
  background: #cbd5e1; border-color: #cbd5e1; cursor: not-allowed;
153297
153894
  }
153298
153895
  `;
153299
- function injectStyles19() {
153896
+ function injectStyles20() {
153300
153897
  if (typeof document === "undefined") return;
153301
153898
  if (document.getElementById(STYLES_ID3)) return;
153302
153899
  const el2 = document.createElement("style");
@@ -153306,7 +153903,7 @@ function injectStyles19() {
153306
153903
  }
153307
153904
  var _activeBackdrop = null;
153308
153905
  function openExportModal(options) {
153309
- injectStyles19();
153906
+ injectStyles20();
153310
153907
  closeExportModal();
153311
153908
  const backdrop = document.createElement("div");
153312
153909
  backdrop.className = `${MODAL_DOM_ID}-backdrop`;
@@ -155075,6 +155672,7 @@ var version = package_default.version || "0.0.0";
155075
155672
  createFilterModalComponent,
155076
155673
  createFooterComponent,
155077
155674
  createFreshdeskTicket,
155675
+ createGoalsBarTooltip,
155078
155676
  createGranularitySelector,
155079
155677
  createGroupScheduleCard,
155080
155678
  createHeaderComponent,