myio-js-library 0.1.561 → 0.1.562

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
@@ -454,6 +454,7 @@ __export(src_exports, {
454
454
  createFreshdeskTicket: () => createTicket,
455
455
  createGcdrAnnotationsAuthStrategy: () => createAuthStrategy,
456
456
  createGcdrAnnotationsClient: () => createGcdrAnnotationsClient,
457
+ createGcdrReportJobsApi: () => createGcdrReportJobsApi,
457
458
  createGoalsBarTooltip: () => createGoalsBarTooltip,
458
459
  createGoalsMoneyClient: () => createGoalsMoneyClient,
459
460
  createGranularitySelector: () => createGranularitySelector,
@@ -486,6 +487,7 @@ __export(src_exports, {
486
487
  createPdfButton: () => createPdfButton,
487
488
  createPremiumModalChrome: () => createPremiumModalChrome,
488
489
  createPresetupGateway: () => createPresetupGateway,
490
+ createProfileClassifier: () => createProfileClassifier,
489
491
  createRealTimeClock: () => createRealTimeClock,
490
492
  createReportMoneyColumn: () => createReportMoneyColumn,
491
493
  createScheduleCard: () => createScheduleCard,
@@ -499,6 +501,7 @@ __export(src_exports, {
499
501
  createSwitchControl: () => createSwitchControl,
500
502
  createTariffApiAdapter: () => createTariffApiAdapter,
501
503
  createTariffApiClient: () => createTariffApiClient,
504
+ createTbEnvelopeStore: () => createTbEnvelopeStore,
502
505
  createTelemetryGridComponent: () => createTelemetryGridComponent,
503
506
  createTelemetryGridShoppingComponent: () => createTelemetryGridShoppingComponent,
504
507
  createTelemetryInfoShoppingComponent: () => createTelemetryInfoShoppingComponent,
@@ -515,6 +518,8 @@ __export(src_exports, {
515
518
  decodePayload: () => decodePayload,
516
519
  decodePayloadBase64Xor: () => decodePayloadBase64Xor,
517
520
  deleteFreshdeskTicket: () => deleteTicket,
521
+ demandDevicesFromItems: () => demandDevicesFromItems,
522
+ demandReport: () => demand_exports,
518
523
  deriveCentralConnectivity: () => deriveCentralConnectivity,
519
524
  detectContext: () => detectContext,
520
525
  detectDeviceType: () => detectDeviceType,
@@ -769,6 +774,7 @@ __export(src_exports, {
769
774
  openDashboardPopupSettings: () => openDashboardPopupSettings,
770
775
  openDashboardPopupWaterTank: () => openDashboardPopupWaterTank,
771
776
  openDemandModal: () => openDemandModal,
777
+ openDemandPanelV2: () => openDemandPanelV2,
772
778
  openDeviceCategoryPanel: () => openDeviceCategoryPanel,
773
779
  openDeviceProfileModal: () => openDeviceProfileModal,
774
780
  openGCDRSyncModal: () => openGCDRSyncModal,
@@ -878,6 +884,7 @@ __export(src_exports, {
878
884
  summaryDeviceToTotalsRow: () => summaryDeviceToTotalsRow,
879
885
  summaryLastReadingTs: () => summaryLastReadingTs,
880
886
  summaryLookbackFromIso: () => summaryLookbackFromIso,
887
+ tbUserFromJwt: () => tbUserFromJwt,
881
888
  telemetryInfoFormatEnergy: () => formatEnergy2,
882
889
  telemetryInfoFormatPercentage: () => formatPercentage3,
883
890
  telemetryInfoFormatWater: () => formatWater2,
@@ -908,7 +915,7 @@ module.exports = __toCommonJS(src_exports);
908
915
  // package.json
909
916
  var package_default = {
910
917
  name: "myio-js-library",
911
- version: "0.1.561",
918
+ version: "0.1.562",
912
919
  description: "A clean, standalone JS SDK for MYIO projects",
913
920
  license: "MIT",
914
921
  repository: "github:gh-myio/myio-js-library",
@@ -29619,12 +29626,12 @@ async function openRealTimeTelemetryModal(params) {
29619
29626
  const startTs = new Date(startISO).getTime();
29620
29627
  const endTs = new Date(endISO).getTime();
29621
29628
  const isRaw = selectedAgg === "NONE";
29622
- const bucketCount = Math.ceil(Math.max(endTs - startTs, 0) / selectedIntervalMs) + 2;
29629
+ const bucketCount2 = Math.ceil(Math.max(endTs - startTs, 0) / selectedIntervalMs) + 2;
29623
29630
  const url = buildTsUrl({
29624
29631
  keys: selectedChartKeys,
29625
29632
  startTs,
29626
29633
  endTs,
29627
- limit: isRaw ? selectedLimit : Math.min(bucketCount, 5e4),
29634
+ limit: isRaw ? selectedLimit : Math.min(bucketCount2, 5e4),
29628
29635
  agg: selectedAgg,
29629
29636
  orderBy: "ASC",
29630
29637
  ...isRaw ? {} : { intervalType: "MILLISECONDS", interval: selectedIntervalMs }
@@ -31081,8 +31088,8 @@ function normalizeToSaoPauloISO(dateLike, endOfDay = false) {
31081
31088
  if (typeof dateLike === "string") {
31082
31089
  ymd = dateLike.split("T")[0];
31083
31090
  } else {
31084
- const SP_OFFSET_MS2 = 3 * 60 * 60 * 1e3;
31085
- const d = new Date(dateLike.getTime() - SP_OFFSET_MS2);
31091
+ const SP_OFFSET_MS3 = 3 * 60 * 60 * 1e3;
31092
+ const d = new Date(dateLike.getTime() - SP_OFFSET_MS3);
31086
31093
  const y = d.getUTCFullYear();
31087
31094
  const mo = String(d.getUTCMonth() + 1).padStart(2, "0");
31088
31095
  const da = String(d.getUTCDate()).padStart(2, "0");
@@ -31383,8 +31390,8 @@ var EnergyDataFetcher = class {
31383
31390
  // src/components/premium-modals/internal/engines/CsvExporter.ts
31384
31391
  var toCsv = (rows, locale = "pt-BR", sep = ";") => {
31385
31392
  const fmt2 = new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
31386
- const esc8 = (v) => (typeof v === "number" ? fmt2.format(v) : String(v)).replace(/"/g, '""');
31387
- return rows.map((r) => r.map((c) => `"${esc8(c)}"`).join(sep)).join("\r\n");
31393
+ const esc9 = (v) => (typeof v === "number" ? fmt2.format(v) : String(v)).replace(/"/g, '""');
31394
+ return rows.map((r) => r.map((c) => `"${esc9(c)}"`).join(sep)).join("\r\n");
31388
31395
  };
31389
31396
 
31390
31397
  // src/components/premium-modals/energy/types.ts
@@ -35735,14 +35742,14 @@ var DeviceReportModal = class {
35735
35742
  // Header: "Relatório - <identifier> - <label>" + nome do device (sutil, com copiar).
35736
35743
  // O customer já aparece no footer premium. Texto escapado — o shell usa innerHTML no título.
35737
35744
  buildHeaderTitleHTML() {
35738
- const esc8 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
35739
- const base = esc8(
35745
+ const esc9 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
35746
+ const base = esc9(
35740
35747
  `Relat\xF3rio - ${this.params.identifier || "SEM IDENTIFICADOR"} - ${this.params.label || "SEM ETIQUETA"}`
35741
35748
  );
35742
35749
  const deviceName = String(this.params.deviceName || "").trim();
35743
35750
  const deviceHTML = deviceName ? `<span class="myio-dr-devname" style="margin-left:10px;font-size:0.72em;font-weight:400;opacity:.75;display:inline-flex;align-items:center;gap:4px;vertical-align:middle;">
35744
- ${esc8(deviceName)}
35745
- <button type="button" class="myio-dr-copy" data-copy="${esc8(deviceName)}" title="Copiar nome do dispositivo"
35751
+ ${esc9(deviceName)}
35752
+ <button type="button" class="myio-dr-copy" data-copy="${esc9(deviceName)}" title="Copiar nome do dispositivo"
35746
35753
  aria-label="Copiar nome do dispositivo"
35747
35754
  style="border:none;background:transparent;color:inherit;cursor:pointer;padding:0 2px;line-height:1;opacity:.9;display:inline-flex;">
35748
35755
  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
@@ -40701,21 +40708,21 @@ var WaterTankModalView = class {
40701
40708
  const unit = isPct ? "%" : "m.c.a";
40702
40709
  const dec = isPct ? 1 : 2;
40703
40710
  const fmtVal = (v) => `${v.toFixed(dec)}${isPct ? "" : " "}${unit}`;
40704
- const pad = (n) => String(n).padStart(2, "0");
40711
+ const pad3 = (n) => String(n).padStart(2, "0");
40705
40712
  const fmtTs = (ts) => {
40706
40713
  const d = new Date(ts);
40707
- return `${pad(d.getDate())}/${pad(d.getMonth() + 1)} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
40714
+ return `${pad3(d.getDate())}/${pad3(d.getMonth() + 1)} ${pad3(d.getHours())}:${pad3(d.getMinutes())}`;
40708
40715
  };
40709
- const fmtDay = (ts) => {
40716
+ const fmtDay2 = (ts) => {
40710
40717
  const d = new Date(ts);
40711
- return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}`;
40718
+ return `${pad3(d.getDate())}/${pad3(d.getMonth() + 1)}`;
40712
40719
  };
40713
40720
  const avg = points.reduce((s, p) => s + p.value, 0) / points.length;
40714
40721
  const peaks = [...points].sort((a, b) => b.value - a.value).slice(0, 3);
40715
40722
  const byDay = /* @__PURE__ */ new Map();
40716
40723
  for (const p of points) {
40717
40724
  const d = new Date(p.ts);
40718
- const key = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
40725
+ const key = `${d.getFullYear()}-${pad3(d.getMonth() + 1)}-${pad3(d.getDate())}`;
40719
40726
  const e = byDay.get(key) || { ts: p.ts, sum: 0, count: 0 };
40720
40727
  e.sum += p.value;
40721
40728
  e.count++;
@@ -40743,7 +40750,7 @@ var WaterTankModalView = class {
40743
40750
  <div id="myio-water-tank-kpis" style="display: flex; gap: 12px; margin-top: 12px; flex-wrap: wrap;">
40744
40751
  ${kpiCard("M\xE9dia do per\xEDodo", `<div style="font-size: 22px; font-weight: 700; color: #2c3e50;">${fmtVal(avg)}</div>`)}
40745
40752
  ${kpiCard("Top 3 picos", peaks.map((p) => listRow(fmtTs(p.ts), fmtVal(p.value))).join(""))}
40746
- ${kpiCard("Top 3 dias mais baixos", lowestDays.map((d) => listRow(fmtDay(d.ts), fmtVal(d.avg))).join(""))}
40753
+ ${kpiCard("Top 3 dias mais baixos", lowestDays.map((d) => listRow(fmtDay2(d.ts), fmtVal(d.avg))).join(""))}
40747
40754
  </div>`;
40748
40755
  }
40749
40756
  /**
@@ -70613,7 +70620,7 @@ var WelcomeModalView = class _WelcomeModalView {
70613
70620
  if (this.metaTipEl) this.metaTipEl.style.display = "none";
70614
70621
  }
70615
70622
  buildMetaTipHtml(type, card) {
70616
- const esc8 = _WelcomeModalView.escapeMetaHtml;
70623
+ const esc9 = _WelcomeModalView.escapeMetaHtml;
70617
70624
  const chip = _WelcomeModalView.metaChip;
70618
70625
  const meta = card.metaCounts || {};
70619
70626
  const details = card.metaDetails || {};
@@ -70623,7 +70630,7 @@ var WelcomeModalView = class _WelcomeModalView {
70623
70630
  ).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
70624
70631
  const frame = (icon2, label, body) => `<div class="myio-welcome-meta-tip__header">
70625
70632
  <span class="myio-welcome-meta-tip__icon">${icon2}</span>
70626
- <span class="myio-welcome-meta-tip__title">${label} (${esc8(card.title)})</span>
70633
+ <span class="myio-welcome-meta-tip__title">${label} (${esc9(card.title)})</span>
70627
70634
  <span class="myio-welcome-meta-tip__timestamp">${stamp}</span>
70628
70635
  <button type="button" class="myio-welcome-meta-tip__close" title="Fechar">\u2715</button>
70629
70636
  </div>
@@ -70651,8 +70658,8 @@ var WelcomeModalView = class _WelcomeModalView {
70651
70658
  const chips = Object.entries(byState).map(([s, n]) => chip(`${s}: ${n}`)).join("");
70652
70659
  const rows = alarms.slice(0, 10).map(
70653
70660
  (a) => row(
70654
- esc8(a.deviceName ? `${a.deviceName} \u2014 ${a.title || "Alarme"}` : a.title || "Alarme"),
70655
- [a.severity, a.state].filter(Boolean).map(esc8).join(" \xB7 ")
70661
+ esc9(a.deviceName ? `${a.deviceName} \u2014 ${a.title || "Alarme"}` : a.title || "Alarme"),
70662
+ [a.severity, a.state].filter(Boolean).map(esc9).join(" \xB7 ")
70656
70663
  )
70657
70664
  ).join("");
70658
70665
  return frame(
@@ -70676,7 +70683,7 @@ var WelcomeModalView = class _WelcomeModalView {
70676
70683
  const rows = annotations.slice(0, 10).map((a) => {
70677
70684
  const when = a.createdAt ? new Date(a.createdAt).toLocaleDateString("pt-BR") : "";
70678
70685
  const text2 = a.text.length > 90 ? `${a.text.slice(0, 90)}\u2026` : a.text;
70679
- return row(esc8(a.deviceLabel || "Dispositivo"), `${esc8(text2)}${when ? ` \xB7 ${esc8(when)}` : ""}`);
70686
+ return row(esc9(a.deviceLabel || "Dispositivo"), `${esc9(text2)}${when ? ` \xB7 ${esc9(when)}` : ""}`);
70680
70687
  }).join("");
70681
70688
  return frame(
70682
70689
  "\u{1F4CB}",
@@ -70687,7 +70694,7 @@ var WelcomeModalView = class _WelcomeModalView {
70687
70694
  if (type === "users") {
70688
70695
  if (meta.users === null) return frame("\u{1F465}", "Usu\xE1rios", loadingBody);
70689
70696
  const users = details.users || [];
70690
- const rows = users.slice(0, 10).map((u) => row(esc8(u.name), esc8(u.email || ""))).join("");
70697
+ const rows = users.slice(0, 10).map((u) => row(esc9(u.name), esc9(u.email || ""))).join("");
70691
70698
  return frame(
70692
70699
  "\u{1F465}",
70693
70700
  "Usu\xE1rios",
@@ -75072,16 +75079,16 @@ function buildTrendSvg(values, stroke, grid) {
75072
75079
  }
75073
75080
  const width = 320;
75074
75081
  const height = 80;
75075
- const pad = 6;
75082
+ const pad3 = 6;
75076
75083
  const maxVal = Math.max(...values, 1);
75077
75084
  const points = values.map((v, i) => {
75078
- const x = pad + i / Math.max(1, values.length - 1) * (width - pad * 2);
75079
- const y = height - pad - v / maxVal * (height - pad * 2);
75085
+ const x = pad3 + i / Math.max(1, values.length - 1) * (width - pad3 * 2);
75086
+ const y = height - pad3 - v / maxVal * (height - pad3 * 2);
75080
75087
  return `${x},${y}`;
75081
75088
  });
75082
75089
  return `
75083
75090
  <svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
75084
- <line x1="0" y1="${height - pad}" x2="${width}" y2="${height - pad}" stroke="${grid}" stroke-width="1"/>
75091
+ <line x1="0" y1="${height - pad3}" x2="${width}" y2="${height - pad3}" stroke="${grid}" stroke-width="1"/>
75085
75092
  <polyline fill="none" stroke="${stroke}" stroke-width="2.5" points="${points.join(" ")}" />
75086
75093
  ${points.map((p) => {
75087
75094
  const [x, y] = p.split(",");
@@ -79536,6 +79543,2482 @@ async function openDemandModal(params) {
79536
79543
  };
79537
79544
  }
79538
79545
 
79546
+ // src/components/premium-modals/dialog/styles.ts
79547
+ var FONT_LINK_ID = "myio-dialog-font-nunito";
79548
+ var STYLE_ID6 = "myio-dialog-styles";
79549
+ function injectDialogStyles() {
79550
+ if (!document.getElementById(FONT_LINK_ID)) {
79551
+ const link = document.createElement("link");
79552
+ link.id = FONT_LINK_ID;
79553
+ link.rel = "stylesheet";
79554
+ link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
79555
+ document.head.appendChild(link);
79556
+ }
79557
+ if (document.getElementById(STYLE_ID6)) return;
79558
+ const style = document.createElement("style");
79559
+ style.id = STYLE_ID6;
79560
+ style.textContent = `
79561
+ .myio-dialog-overlay {
79562
+ position: fixed;
79563
+ inset: 0;
79564
+ display: flex;
79565
+ align-items: center;
79566
+ justify-content: center;
79567
+ background: rgba(17, 24, 39, 0.55);
79568
+ font-family: 'Nunito', system-ui, sans-serif;
79569
+ }
79570
+ .myio-dialog {
79571
+ background: #ffffff;
79572
+ color: #1f2937;
79573
+ border-radius: 12px;
79574
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
79575
+ max-width: 90vw;
79576
+ max-height: 85vh;
79577
+ display: flex;
79578
+ flex-direction: column;
79579
+ overflow: hidden;
79580
+ }
79581
+ .myio-dialog__accent {
79582
+ height: 4px;
79583
+ background: #7C3AED;
79584
+ }
79585
+ .myio-dialog__header {
79586
+ display: flex;
79587
+ align-items: flex-start;
79588
+ gap: 10px;
79589
+ padding: 16px 20px 0 20px;
79590
+ }
79591
+ .myio-dialog__icon {
79592
+ font-size: 20px;
79593
+ line-height: 1.3;
79594
+ }
79595
+ .myio-dialog__title {
79596
+ flex: 1;
79597
+ margin: 0;
79598
+ font-size: 16px;
79599
+ font-weight: 700;
79600
+ line-height: 1.35;
79601
+ }
79602
+ .myio-dialog__close {
79603
+ border: none;
79604
+ background: transparent;
79605
+ color: #9ca3af;
79606
+ font-size: 18px;
79607
+ line-height: 1;
79608
+ padding: 2px 6px;
79609
+ margin: -2px -8px 0 0;
79610
+ border-radius: 6px;
79611
+ cursor: pointer;
79612
+ }
79613
+ .myio-dialog__close:hover {
79614
+ background: rgba(0, 0, 0, 0.06);
79615
+ color: #4b5563;
79616
+ }
79617
+ .myio-dialog__message {
79618
+ margin: 0;
79619
+ padding: 10px 20px 4px 20px;
79620
+ font-size: 13.5px;
79621
+ line-height: 1.55;
79622
+ color: #4b5563;
79623
+ overflow-y: auto;
79624
+ }
79625
+ .myio-dialog__footer {
79626
+ display: flex;
79627
+ justify-content: flex-end;
79628
+ gap: 8px;
79629
+ padding: 16px 20px;
79630
+ }
79631
+ .myio-dialog__btn {
79632
+ font-family: inherit;
79633
+ font-size: 13px;
79634
+ font-weight: 600;
79635
+ padding: 8px 16px;
79636
+ border-radius: 8px;
79637
+ border: 1px solid transparent;
79638
+ cursor: pointer;
79639
+ transition: filter 0.15s ease, background 0.15s ease;
79640
+ }
79641
+ .myio-dialog__btn:hover {
79642
+ filter: brightness(0.94);
79643
+ }
79644
+ .myio-dialog__btn:focus-visible {
79645
+ outline: 2px solid #7C3AED;
79646
+ outline-offset: 2px;
79647
+ }
79648
+ .myio-dialog__btn--primary {
79649
+ background: #7C3AED;
79650
+ color: #ffffff;
79651
+ }
79652
+ .myio-dialog__btn--secondary {
79653
+ background: #ffffff;
79654
+ color: #374151;
79655
+ border-color: #d1d5db;
79656
+ }
79657
+ .myio-dialog__btn--danger {
79658
+ background: #dc2626;
79659
+ color: #ffffff;
79660
+ }
79661
+ .myio-dialog__btn--success {
79662
+ background: #16a34a;
79663
+ color: #ffffff;
79664
+ }
79665
+
79666
+ /* Severity accents (message dialog) */
79667
+ .myio-dialog--success .myio-dialog__accent { background: #16a34a; }
79668
+ .myio-dialog--warning .myio-dialog__accent { background: #d97706; }
79669
+ .myio-dialog--error .myio-dialog__accent { background: #dc2626; }
79670
+
79671
+ /* \u2500\u2500 Dark theme \u2500\u2500 */
79672
+ .myio-dialog-overlay--dark .myio-dialog {
79673
+ background: #1f2937;
79674
+ color: #f3f4f6;
79675
+ }
79676
+ .myio-dialog-overlay--dark .myio-dialog__message {
79677
+ color: #d1d5db;
79678
+ }
79679
+ .myio-dialog-overlay--dark .myio-dialog__close {
79680
+ color: #9ca3af;
79681
+ }
79682
+ .myio-dialog-overlay--dark .myio-dialog__close:hover {
79683
+ background: rgba(255, 255, 255, 0.08);
79684
+ color: #e5e7eb;
79685
+ }
79686
+ .myio-dialog-overlay--dark .myio-dialog__btn--secondary {
79687
+ background: #1f2937;
79688
+ color: #e5e7eb;
79689
+ border-color: #4b5563;
79690
+ }
79691
+ `;
79692
+ document.head.appendChild(style);
79693
+ }
79694
+
79695
+ // src/components/premium-modals/dialog/openDialog.ts
79696
+ var BASE_Z_INDEX = 10500;
79697
+ var openCount = 0;
79698
+ function escapeHtml7(value) {
79699
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
79700
+ }
79701
+ function escapeMessage(value) {
79702
+ return escapeHtml7(value).replace(/\n/g, "<br>");
79703
+ }
79704
+ function cssWidth(width) {
79705
+ if (width == null) return "420px";
79706
+ return typeof width === "number" ? `${width}px` : width;
79707
+ }
79708
+ var SEVERITY_META = {
79709
+ info: { title: "Informa\xE7\xE3o", icon: "\u2139\uFE0F" },
79710
+ success: { title: "Sucesso", icon: "\u2705" },
79711
+ warning: { title: "Aten\xE7\xE3o", icon: "\u26A0\uFE0F" },
79712
+ error: { title: "Erro", icon: "\u274C" }
79713
+ };
79714
+ function openDialog(params) {
79715
+ if (!Array.isArray(params.buttons) || params.buttons.length === 0) {
79716
+ throw new Error("[openConfirmDialog] params.buttons must contain at least one button");
79717
+ }
79718
+ injectDialogStyles();
79719
+ const dismissible = params.dismissible ?? true;
79720
+ const theme = params.theme ?? "light";
79721
+ const idBase = `myio-dialog-${++openCount}`;
79722
+ const overlay = document.createElement("div");
79723
+ overlay.className = `myio-dialog-overlay${theme === "dark" ? " myio-dialog-overlay--dark" : ""}`;
79724
+ overlay.style.zIndex = String(BASE_Z_INDEX + openCount);
79725
+ const buttonsHtml = params.buttons.map((btn, i) => {
79726
+ const variant = btn.variant ?? "secondary";
79727
+ return `<button type="button" class="myio-dialog__btn myio-dialog__btn--${variant}" data-index="${i}">${escapeHtml7(btn.label)}</button>`;
79728
+ }).join("");
79729
+ overlay.innerHTML = `
79730
+ <div class="myio-dialog${params.modifier ? ` ${params.modifier}` : ""}"
79731
+ role="dialog" aria-modal="true"
79732
+ aria-labelledby="${idBase}-title" aria-describedby="${idBase}-message"
79733
+ style="width:${cssWidth(params.width)}">
79734
+ <div class="myio-dialog__accent"></div>
79735
+ <div class="myio-dialog__header">
79736
+ ${params.icon ? `<span class="myio-dialog__icon" aria-hidden="true">${params.icon}</span>` : ""}
79737
+ <h3 class="myio-dialog__title" id="${idBase}-title">${escapeHtml7(params.title)}</h3>
79738
+ ${dismissible ? `<button type="button" class="myio-dialog__close" aria-label="Fechar">\u2715</button>` : ""}
79739
+ </div>
79740
+ <p class="myio-dialog__message" id="${idBase}-message">${escapeMessage(params.message)}</p>
79741
+ <div class="myio-dialog__footer">${buttonsHtml}</div>
79742
+ </div>
79743
+ `;
79744
+ const previousFocus = document.activeElement;
79745
+ const target = params.container ?? document.body;
79746
+ target.appendChild(overlay);
79747
+ const root = overlay.querySelector(".myio-dialog");
79748
+ const buttonEls = Array.from(root.querySelectorAll(".myio-dialog__btn"));
79749
+ return new Promise((resolve) => {
79750
+ let settled = false;
79751
+ let autoCloseTimer;
79752
+ const close = (result) => {
79753
+ if (settled) return;
79754
+ settled = true;
79755
+ if (autoCloseTimer) clearTimeout(autoCloseTimer);
79756
+ document.removeEventListener("keydown", onKeydown, true);
79757
+ overlay.remove();
79758
+ if (previousFocus && previousFocus.isConnected) previousFocus.focus();
79759
+ resolve(result);
79760
+ };
79761
+ const onKeydown = (e) => {
79762
+ if (!overlay.isConnected) return;
79763
+ if (e.key === "Escape" && dismissible) {
79764
+ e.stopPropagation();
79765
+ close(null);
79766
+ return;
79767
+ }
79768
+ if (e.key === "Tab") {
79769
+ const focusables = Array.from(
79770
+ root.querySelectorAll("button:not([disabled])")
79771
+ );
79772
+ if (focusables.length === 0) return;
79773
+ const first = focusables[0];
79774
+ const last = focusables[focusables.length - 1];
79775
+ const active = document.activeElement;
79776
+ if (!root.contains(active)) {
79777
+ e.preventDefault();
79778
+ first.focus();
79779
+ } else if (e.shiftKey && active === first) {
79780
+ e.preventDefault();
79781
+ last.focus();
79782
+ } else if (!e.shiftKey && active === last) {
79783
+ e.preventDefault();
79784
+ first.focus();
79785
+ }
79786
+ }
79787
+ };
79788
+ buttonEls.forEach((el2) => {
79789
+ el2.addEventListener("click", () => {
79790
+ const index = Number(el2.dataset.index);
79791
+ close(params.buttons[index].value);
79792
+ });
79793
+ });
79794
+ if (dismissible) {
79795
+ overlay.querySelector(".myio-dialog__close")?.addEventListener("click", () => close(null));
79796
+ overlay.addEventListener("click", (e) => {
79797
+ if (e.target === overlay) close(null);
79798
+ });
79799
+ }
79800
+ document.addEventListener("keydown", onKeydown, true);
79801
+ const autoFocusIndex = params.buttons.findIndex((b) => b.autoFocus);
79802
+ (buttonEls[autoFocusIndex >= 0 ? autoFocusIndex : buttonEls.length - 1] ?? buttonEls[0]).focus();
79803
+ if (params.autoCloseMs && params.autoCloseMs > 0) {
79804
+ autoCloseTimer = setTimeout(() => close(null), params.autoCloseMs);
79805
+ }
79806
+ });
79807
+ }
79808
+ function openConfirmDialog(params) {
79809
+ return openDialog(params);
79810
+ }
79811
+ function openMessageDialog(params) {
79812
+ const severity = params.severity ?? "info";
79813
+ const meta = SEVERITY_META[severity];
79814
+ return openDialog({
79815
+ title: params.title ?? meta.title,
79816
+ message: params.message,
79817
+ buttons: [
79818
+ { label: params.buttonLabel ?? "OK", variant: "primary", value: "ok", autoFocus: true }
79819
+ ],
79820
+ theme: params.theme,
79821
+ container: params.container,
79822
+ modifier: severity !== "info" ? `myio-dialog--${severity}` : void 0,
79823
+ icon: meta.icon,
79824
+ autoCloseMs: params.autoCloseMs
79825
+ }).then(() => void 0);
79826
+ }
79827
+
79828
+ // src/components/premium-modals/dialog/openGenericModal.ts
79829
+ var STYLE_ID7 = "myio-genmodal-styles";
79830
+ var FONT_LINK_ID2 = "myio-dialog-font-nunito";
79831
+ var BASE_Z_INDEX2 = 10600;
79832
+ var openCount2 = 0;
79833
+ function escapeHtml8(value) {
79834
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
79835
+ }
79836
+ function cssSize(v, fallback) {
79837
+ if (v == null) return fallback;
79838
+ return typeof v === "number" ? `${v}px` : v;
79839
+ }
79840
+ function injectGenericModalStyles() {
79841
+ if (!document.getElementById(FONT_LINK_ID2)) {
79842
+ const link = document.createElement("link");
79843
+ link.id = FONT_LINK_ID2;
79844
+ link.rel = "stylesheet";
79845
+ link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
79846
+ document.head.appendChild(link);
79847
+ }
79848
+ if (document.getElementById(STYLE_ID7)) return;
79849
+ const style = document.createElement("style");
79850
+ style.id = STYLE_ID7;
79851
+ style.textContent = `
79852
+ .myio-genmodal-overlay {
79853
+ position: fixed;
79854
+ inset: 0;
79855
+ display: flex;
79856
+ align-items: center;
79857
+ justify-content: center;
79858
+ background: rgba(17, 24, 39, 0.55);
79859
+ font-family: 'Nunito', system-ui, sans-serif;
79860
+ }
79861
+ .myio-genmodal {
79862
+ background: #ffffff;
79863
+ color: #1f2937;
79864
+ border-radius: 12px;
79865
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
79866
+ max-width: 90vw;
79867
+ max-height: 90vh;
79868
+ display: flex;
79869
+ flex-direction: column;
79870
+ overflow: hidden;
79871
+ }
79872
+ .myio-genmodal__accent { height: 4px; background: #7C3AED; flex-shrink: 0; }
79873
+ .myio-genmodal__header {
79874
+ display: flex;
79875
+ align-items: flex-start;
79876
+ gap: 10px;
79877
+ padding: 16px 20px;
79878
+ border-bottom: 1px solid #eef0f3;
79879
+ flex-shrink: 0;
79880
+ }
79881
+ .myio-genmodal__icon { font-size: 22px; line-height: 1.3; }
79882
+ .myio-genmodal__title {
79883
+ flex: 1;
79884
+ margin: 0;
79885
+ font-size: 18px;
79886
+ font-weight: 700;
79887
+ line-height: 1.35;
79888
+ }
79889
+ .myio-genmodal__close {
79890
+ border: none;
79891
+ background: transparent;
79892
+ color: #9ca3af;
79893
+ font-size: 18px;
79894
+ line-height: 1;
79895
+ padding: 2px 6px;
79896
+ margin: -2px -8px 0 0;
79897
+ border-radius: 6px;
79898
+ cursor: pointer;
79899
+ }
79900
+ .myio-genmodal__close:hover { background: rgba(0, 0, 0, 0.06); color: #4b5563; }
79901
+ .myio-genmodal__body {
79902
+ padding: 18px 20px;
79903
+ font-size: 15px;
79904
+ line-height: 1.6;
79905
+ color: #374151;
79906
+ overflow-y: auto;
79907
+ }
79908
+ /* Sensible defaults for arbitrary body HTML */
79909
+ .myio-genmodal__body img { max-width: 100%; height: auto; border-radius: 8px; }
79910
+ .myio-genmodal__body hr { border: none; border-top: 1px solid #e5e7eb; margin: 16px 0; }
79911
+ .myio-genmodal__body h1, .myio-genmodal__body h2, .myio-genmodal__body h3, .myio-genmodal__body h4 {
79912
+ margin: 0 0 8px; line-height: 1.3; color: #1f2937;
79913
+ }
79914
+ .myio-genmodal__body p { margin: 0 0 10px; }
79915
+ .myio-genmodal__body ul, .myio-genmodal__body ol { margin: 0 0 10px; padding-left: 22px; }
79916
+ .myio-genmodal__body table { border-collapse: collapse; width: 100%; }
79917
+ .myio-genmodal__body th, .myio-genmodal__body td {
79918
+ border: 1px solid #e5e7eb; padding: 6px 10px; text-align: left; font-size: 14px;
79919
+ }
79920
+ .myio-genmodal__body a { color: #7C3AED; }
79921
+ .myio-genmodal__footer {
79922
+ display: flex;
79923
+ justify-content: flex-end;
79924
+ gap: 8px;
79925
+ padding: 14px 20px;
79926
+ border-top: 1px solid #eef0f3;
79927
+ flex-shrink: 0;
79928
+ }
79929
+ .myio-genmodal__btn {
79930
+ font-family: inherit;
79931
+ font-size: 13px;
79932
+ font-weight: 600;
79933
+ padding: 8px 16px;
79934
+ border-radius: 8px;
79935
+ border: 1px solid transparent;
79936
+ cursor: pointer;
79937
+ transition: filter 0.15s ease, background 0.15s ease;
79938
+ }
79939
+ .myio-genmodal__btn:hover { filter: brightness(0.94); }
79940
+ .myio-genmodal__btn:focus-visible { outline: 2px solid #7C3AED; outline-offset: 2px; }
79941
+ .myio-genmodal__btn--primary { background: #7C3AED; color: #ffffff; }
79942
+ .myio-genmodal__btn--secondary { background: #ffffff; color: #374151; border-color: #d1d5db; }
79943
+ .myio-genmodal__btn--danger { background: #dc2626; color: #ffffff; }
79944
+ .myio-genmodal__btn--success { background: #16a34a; color: #ffffff; }
79945
+
79946
+ /* \u2500\u2500 Dark theme \u2500\u2500 */
79947
+ .myio-genmodal-overlay--dark .myio-genmodal { background: #1f2937; color: #f3f4f6; }
79948
+ .myio-genmodal-overlay--dark .myio-genmodal__header,
79949
+ .myio-genmodal-overlay--dark .myio-genmodal__footer { border-color: #374151; }
79950
+ .myio-genmodal-overlay--dark .myio-genmodal__title { color: #f3f4f6; }
79951
+ .myio-genmodal-overlay--dark .myio-genmodal__body { color: #d1d5db; }
79952
+ .myio-genmodal-overlay--dark .myio-genmodal__body h1,
79953
+ .myio-genmodal-overlay--dark .myio-genmodal__body h2,
79954
+ .myio-genmodal-overlay--dark .myio-genmodal__body h3,
79955
+ .myio-genmodal-overlay--dark .myio-genmodal__body h4 { color: #f3f4f6; }
79956
+ .myio-genmodal-overlay--dark .myio-genmodal__body hr { border-color: #374151; }
79957
+ .myio-genmodal-overlay--dark .myio-genmodal__body th,
79958
+ .myio-genmodal-overlay--dark .myio-genmodal__body td { border-color: #374151; }
79959
+ .myio-genmodal-overlay--dark .myio-genmodal__close { color: #9ca3af; }
79960
+ .myio-genmodal-overlay--dark .myio-genmodal__close:hover { background: rgba(255,255,255,0.08); color: #e5e7eb; }
79961
+ .myio-genmodal-overlay--dark .myio-genmodal__btn--secondary { background: #1f2937; color: #e5e7eb; border-color: #4b5563; }
79962
+ `;
79963
+ document.head.appendChild(style);
79964
+ }
79965
+ var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
79966
+ function openGenericModal(params) {
79967
+ if (typeof window === "undefined" || typeof document === "undefined") {
79968
+ throw new Error("[openGenericModal] requires a browser environment");
79969
+ }
79970
+ if (typeof params.bodyHtml !== "string") {
79971
+ throw new Error("[openGenericModal] params.bodyHtml must be a string");
79972
+ }
79973
+ injectGenericModalStyles();
79974
+ const dismissible = params.dismissible ?? true;
79975
+ const theme = params.theme ?? "light";
79976
+ const buttons = params.buttons ?? [];
79977
+ const idBase = `myio-genmodal-${++openCount2}`;
79978
+ const overlay = document.createElement("div");
79979
+ overlay.className = `myio-genmodal-overlay${theme === "dark" ? " myio-genmodal-overlay--dark" : ""}`;
79980
+ overlay.style.zIndex = String(BASE_Z_INDEX2 + openCount2);
79981
+ const buttonsHtml = buttons.map((btn, i) => {
79982
+ const variant = btn.variant ?? "secondary";
79983
+ return `<button type="button" class="myio-genmodal__btn myio-genmodal__btn--${variant}" data-index="${i}">${escapeHtml8(btn.label)}</button>`;
79984
+ }).join("");
79985
+ overlay.innerHTML = `
79986
+ <div class="myio-genmodal${params.className ? ` ${escapeHtml8(params.className)}` : ""}"
79987
+ role="dialog" aria-modal="true"
79988
+ aria-labelledby="${idBase}-title"
79989
+ style="width:${cssSize(params.width, "560px")}">
79990
+ <div class="myio-genmodal__accent"></div>
79991
+ <div class="myio-genmodal__header">
79992
+ ${params.icon ? `<span class="myio-genmodal__icon" aria-hidden="true">${params.icon}</span>` : ""}
79993
+ <h3 class="myio-genmodal__title" id="${idBase}-title">${escapeHtml8(params.title)}</h3>
79994
+ ${dismissible ? `<button type="button" class="myio-genmodal__close" aria-label="Fechar">\u2715</button>` : ""}
79995
+ </div>
79996
+ <div class="myio-genmodal__body" id="${idBase}-body" style="max-height:${cssSize(params.bodyMaxHeight, "70vh")}"></div>
79997
+ ${buttons.length ? `<div class="myio-genmodal__footer">${buttonsHtml}</div>` : ""}
79998
+ </div>
79999
+ `;
80000
+ const root = overlay.querySelector(".myio-genmodal");
80001
+ const bodyEl = root.querySelector(".myio-genmodal__body");
80002
+ const titleEl = root.querySelector(".myio-genmodal__title");
80003
+ bodyEl.innerHTML = params.bodyHtml;
80004
+ const previousFocus = document.activeElement;
80005
+ const target = params.container ?? document.body;
80006
+ target.appendChild(overlay);
80007
+ let closed = false;
80008
+ const close = () => {
80009
+ if (closed) return;
80010
+ closed = true;
80011
+ document.removeEventListener("keydown", onKeydown, true);
80012
+ overlay.remove();
80013
+ if (previousFocus && previousFocus.isConnected) previousFocus.focus();
80014
+ if (params.onClose) params.onClose();
80015
+ };
80016
+ const onKeydown = (e) => {
80017
+ if (!overlay.isConnected) return;
80018
+ if (e.key === "Escape" && dismissible) {
80019
+ e.stopPropagation();
80020
+ close();
80021
+ return;
80022
+ }
80023
+ if (e.key === "Tab") {
80024
+ const focusables = Array.from(root.querySelectorAll(FOCUSABLE));
80025
+ if (focusables.length === 0) {
80026
+ e.preventDefault();
80027
+ return;
80028
+ }
80029
+ const first = focusables[0];
80030
+ const last = focusables[focusables.length - 1];
80031
+ const active = document.activeElement;
80032
+ if (!root.contains(active)) {
80033
+ e.preventDefault();
80034
+ first.focus();
80035
+ } else if (e.shiftKey && active === first) {
80036
+ e.preventDefault();
80037
+ last.focus();
80038
+ } else if (!e.shiftKey && active === last) {
80039
+ e.preventDefault();
80040
+ first.focus();
80041
+ }
80042
+ }
80043
+ };
80044
+ root.querySelectorAll(".myio-genmodal__btn").forEach((el2) => {
80045
+ el2.addEventListener("click", () => {
80046
+ const btn = buttons[Number(el2.dataset.index)];
80047
+ if (!btn) return;
80048
+ if (params.onButton) params.onButton(btn.value);
80049
+ if (btn.closeOnClick !== false) close();
80050
+ });
80051
+ });
80052
+ if (dismissible) {
80053
+ overlay.querySelector(".myio-genmodal__close")?.addEventListener("click", () => close());
80054
+ overlay.addEventListener("click", (e) => {
80055
+ if (e.target === overlay) close();
80056
+ });
80057
+ }
80058
+ document.addEventListener("keydown", onKeydown, true);
80059
+ const autoFocusIdx = buttons.findIndex((b) => b.autoFocus);
80060
+ const footerEls = Array.from(root.querySelectorAll(".myio-genmodal__btn"));
80061
+ const initialFocus = (autoFocusIdx >= 0 ? footerEls[autoFocusIdx] : void 0) ?? footerEls[footerEls.length - 1] ?? root.querySelector(`.myio-genmodal__body ${FOCUSABLE}`) ?? overlay.querySelector(".myio-genmodal__close") ?? root;
80062
+ initialFocus.focus?.();
80063
+ return {
80064
+ close,
80065
+ setBodyHtml: (html) => {
80066
+ bodyEl.innerHTML = html;
80067
+ },
80068
+ setTitle: (title) => {
80069
+ titleEl.textContent = title;
80070
+ },
80071
+ getBodyEl: () => bodyEl,
80072
+ getRoot: () => root
80073
+ };
80074
+ }
80075
+
80076
+ // src/utils/reports/demand/index.ts
80077
+ var demand_exports = {};
80078
+ __export(demand_exports, {
80079
+ BUCKET_STATS_LABEL: () => BUCKET_STATS_LABEL,
80080
+ CATALOG_VERSION: () => CATALOG_VERSION,
80081
+ DEFAULT_DEMAND_LABEL: () => DEFAULT_DEMAND_LABEL,
80082
+ DEFAULT_RESOLUTION: () => DEFAULT_RESOLUTION,
80083
+ DEMAND_LIMITS: () => DEMAND_LIMITS,
80084
+ END_TOLERANCE_MS: () => END_TOLERANCE_MS,
80085
+ ENVELOPE_SCHEMA_VERSION: () => ENVELOPE_SCHEMA_VERSION,
80086
+ HASH_ALGORITHM: () => HASH_ALGORITHM,
80087
+ JOB_KEY_PREFIX: () => JOB_KEY_PREFIX,
80088
+ JOB_STATE_LABEL: () => JOB_STATE_LABEL,
80089
+ MIN_PERIOD_START: () => MIN_PERIOD_START,
80090
+ MIN_PERIOD_START_MS: () => MIN_PERIOD_START_MS,
80091
+ RESOLUTIONS: () => RESOLUTIONS,
80092
+ RESOLUTION_BY_ID: () => RESOLUTION_BY_ID,
80093
+ SUBMIT_SCHEMA_VERSION: () => SUBMIT_SCHEMA_VERSION,
80094
+ TELEMETRY_CATALOG: () => TELEMETRY_CATALOG,
80095
+ TELEMETRY_GROUPS: () => TELEMETRY_GROUPS,
80096
+ auditEvent: () => auditEvent,
80097
+ bucketCount: () => bucketCount,
80098
+ buildEnvelope: () => buildEnvelope,
80099
+ buildRequest: () => buildRequest,
80100
+ buildTaxonomyChips: () => buildTaxonomyChips,
80101
+ canonicalJson: () => canonicalJson,
80102
+ canonicalRequestJson: () => canonicalRequestJson,
80103
+ canonicalizeRequest: () => canonicalizeRequest,
80104
+ compareCodeUnits: () => compareCodeUnits,
80105
+ deriveJobView: () => deriveJobView,
80106
+ envelopeKey: () => envelopeKey,
80107
+ failureText: () => failureText,
80108
+ finestFittingResolution: () => finestFittingResolution,
80109
+ formatLocalDateTime: () => formatLocalDateTime,
80110
+ humanizeKey: () => humanizeKey,
80111
+ isLocalMidnight: () => isLocalMidnight,
80112
+ isUncertainStatus: () => isUncertainStatus,
80113
+ localMidnightOf: () => localMidnightOf,
80114
+ maxPeriodDays: () => maxPeriodDays,
80115
+ mergeBatch: () => mergeBatch,
80116
+ needsPolling: () => needsPolling,
80117
+ parseEnvelope: () => parseEnvelope,
80118
+ parseLocalDateTime: () => parseLocalDateTime,
80119
+ periodErrors: () => periodErrors,
80120
+ presetRange: () => presetRange,
80121
+ reconciliationEvents: () => reconciliationEvents,
80122
+ rejectionText: () => rejectionText,
80123
+ requestHash: () => requestHash,
80124
+ spEpoch: () => spEpoch,
80125
+ spParts: () => spParts,
80126
+ telemetriesInCatalogOrder: () => telemetriesInCatalogOrder,
80127
+ toLocalDateTimeInput: () => toLocalDateTimeInput,
80128
+ toSubmitCommand: () => toSubmitCommand,
80129
+ toValidateCommand: () => toValidateCommand,
80130
+ uuidv7: () => uuidv7,
80131
+ webCryptoSha256: () => webCryptoSha256
80132
+ });
80133
+
80134
+ // src/utils/reports/demand/types.ts
80135
+ var ENVELOPE_SCHEMA_VERSION = 2;
80136
+
80137
+ // src/utils/reports/demand/catalog.ts
80138
+ var CATALOG_VERSION = 1;
80139
+ var TELEMETRY_GROUPS = [
80140
+ {
80141
+ id: "power",
80142
+ label: "Pot\xEAncia",
80143
+ icon: "\u2699\uFE0F",
80144
+ items: [
80145
+ { key: "consumption", label: "Pot\xEAncia Total", unit: "W" },
80146
+ { key: "a", label: "Pot\xEAncia Fase A", unit: "W" },
80147
+ { key: "b", label: "Pot\xEAncia Fase B", unit: "W" },
80148
+ { key: "c", label: "Pot\xEAncia Fase C", unit: "W" }
80149
+ ]
80150
+ },
80151
+ {
80152
+ id: "voltage",
80153
+ label: "Tens\xE3o",
80154
+ icon: "\u26A1",
80155
+ items: [
80156
+ { key: "voltage_a", label: "Tens\xE3o Fase A", unit: "V" },
80157
+ { key: "voltage_b", label: "Tens\xE3o Fase B", unit: "V" },
80158
+ { key: "voltage_c", label: "Tens\xE3o Fase C", unit: "V" }
80159
+ ]
80160
+ },
80161
+ {
80162
+ id: "current",
80163
+ label: "Corrente",
80164
+ icon: "\u{1F50C}",
80165
+ items: [
80166
+ { key: "current_a", label: "Corrente Fase A", unit: "A" },
80167
+ { key: "current_b", label: "Corrente Fase B", unit: "A" },
80168
+ { key: "current_c", label: "Corrente Fase C", unit: "A" },
80169
+ { key: "total_current", label: "Corrente Total", unit: "A" }
80170
+ ]
80171
+ },
80172
+ {
80173
+ id: "pf",
80174
+ label: "Fator de pot\xEAncia",
80175
+ icon: "\u{1F4D0}",
80176
+ items: [
80177
+ { key: "fp_a", label: "Fator de Pot\xEAncia A", unit: "fp" },
80178
+ { key: "fp_b", label: "Fator de Pot\xEAncia B", unit: "fp" },
80179
+ { key: "fp_c", label: "Fator de Pot\xEAncia C", unit: "fp" }
80180
+ ]
80181
+ }
80182
+ ];
80183
+ var TELEMETRY_CATALOG = TELEMETRY_GROUPS.flatMap((g) => g.items);
80184
+ var RESOLUTIONS = [
80185
+ { id: "1m", label: "1 min", ms: 6e4, maxDays: 1 },
80186
+ { id: "5m", label: "5 min", ms: 3e5, maxDays: 7 },
80187
+ { id: "15m", label: "15 min", ms: 9e5, maxDays: 31 },
80188
+ { id: "1h", label: "1 hora", ms: 36e5, maxDays: 92 },
80189
+ { id: "1d", label: "1 dia", ms: 864e5, maxDays: 92 }
80190
+ ];
80191
+ var DEFAULT_RESOLUTION = "1h";
80192
+ var RESOLUTION_BY_ID = Object.fromEntries(
80193
+ RESOLUTIONS.map((r) => [r.id, r])
80194
+ );
80195
+ var BUCKET_STATS_LABEL = "m\xE9dia, m\xE1ximo, m\xEDnimo e n\xBA de amostras";
80196
+ var DEFAULT_DEMAND_LABEL = "Pot\xEAncia m\xE9dia em 15 min (demanda estimada)";
80197
+ var DEMAND_LIMITS = { devices: 50, telemetries: 15, points: 2e6, activeJobsPerUser: 10 };
80198
+ function telemetriesInCatalogOrder(keys) {
80199
+ const wanted = new Set(keys);
80200
+ return TELEMETRY_CATALOG.filter((t) => wanted.has(t.key)).map((t) => ({ ...t }));
80201
+ }
80202
+
80203
+ // src/utils/reports/demand/period.ts
80204
+ var SP_OFFSET_MS2 = -3 * 36e5;
80205
+ var DAY_MS = 864e5;
80206
+ var END_TOLERANCE_MS = 5 * 6e4;
80207
+ var MIN_PERIOD_START = "2019-03-01";
80208
+ var MIN_PERIOD_START_MS = Date.UTC(2019, 2, 1) - SP_OFFSET_MS2;
80209
+ function spParts(ms) {
80210
+ const d = new Date(ms + SP_OFFSET_MS2);
80211
+ return { y: d.getUTCFullYear(), m: d.getUTCMonth(), d: d.getUTCDate(), h: d.getUTCHours(), mi: d.getUTCMinutes() };
80212
+ }
80213
+ function spEpoch(y, m, d, h3 = 0, mi = 0) {
80214
+ return Date.UTC(y, m, d, h3, mi) - SP_OFFSET_MS2;
80215
+ }
80216
+ function parseLocalDateTime(value) {
80217
+ const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/.exec(value || "");
80218
+ return m ? spEpoch(+m[1], +m[2] - 1, +m[3], +m[4], +m[5]) : NaN;
80219
+ }
80220
+ function toLocalDateTimeInput(ms) {
80221
+ const p = spParts(ms);
80222
+ const pad3 = (n) => String(n).padStart(2, "0");
80223
+ return `${p.y}-${pad3(p.m + 1)}-${pad3(p.d)}T${pad3(p.h)}:${pad3(p.mi)}`;
80224
+ }
80225
+ function formatLocalDateTime(ms) {
80226
+ const p = spParts(ms);
80227
+ const pad3 = (n) => String(n).padStart(2, "0");
80228
+ return `${pad3(p.d)}/${pad3(p.m + 1)}/${p.y} ${pad3(p.h)}:${pad3(p.mi)}`;
80229
+ }
80230
+ function presetRange(preset, now) {
80231
+ const t = spParts(now);
80232
+ switch (preset) {
80233
+ case "today":
80234
+ return { start: spEpoch(t.y, t.m, t.d), end: now };
80235
+ case "yesterday":
80236
+ return { start: spEpoch(t.y, t.m, t.d - 1), end: spEpoch(t.y, t.m, t.d) };
80237
+ case "last7days":
80238
+ return { start: spEpoch(t.y, t.m, t.d - 7), end: spEpoch(t.y, t.m, t.d) };
80239
+ case "thisMonth":
80240
+ return { start: spEpoch(t.y, t.m, 1), end: now };
80241
+ case "lastMonth":
80242
+ return { start: spEpoch(t.y, t.m - 1, 1), end: spEpoch(t.y, t.m, 1) };
80243
+ default:
80244
+ return null;
80245
+ }
80246
+ }
80247
+ function isLocalMidnight(ms) {
80248
+ const p = spParts(ms);
80249
+ return p.h === 0 && p.mi === 0 && new Date(ms + SP_OFFSET_MS2).getUTCSeconds() === 0 && ms % 1e3 === 0;
80250
+ }
80251
+ function localMidnightOf(ms) {
80252
+ const p = spParts(ms);
80253
+ return spEpoch(p.y, p.m, p.d);
80254
+ }
80255
+ function bucketCount(start, end, resolution) {
80256
+ if (!(end > start)) return 0;
80257
+ if (resolution === "1d") return Math.ceil((localMidnightOf(end - 1) - localMidnightOf(start)) / DAY_MS) + 1;
80258
+ return Math.ceil((end - start) / RESOLUTION_BY_ID[resolution].ms);
80259
+ }
80260
+ function finestFittingResolution(range) {
80261
+ const len = range.end - range.start;
80262
+ return RESOLUTIONS.find((r) => len <= r.maxDays * DAY_MS)?.id ?? null;
80263
+ }
80264
+ function maxPeriodDays(resolution) {
80265
+ return RESOLUTION_BY_ID[resolution].maxDays;
80266
+ }
80267
+ function periodErrors(range, resolution, now) {
80268
+ if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) return ["Informe in\xEDcio e fim."];
80269
+ if (range.end <= range.start) return ["O fim precisa ser depois do in\xEDcio."];
80270
+ const errs = [];
80271
+ if (range.start < MIN_PERIOD_START_MS) errs.push("Per\xEDodos antes de 01/03/2019 n\xE3o s\xE3o suportados.");
80272
+ if (range.start > now) errs.push("O in\xEDcio est\xE1 no futuro.");
80273
+ if (range.end > now + END_TOLERANCE_MS) errs.push("O fim n\xE3o pode estar no futuro.");
80274
+ const maxDays = maxPeriodDays(resolution);
80275
+ if (range.end - range.start > maxDays * DAY_MS) {
80276
+ errs.push(`Com intervalos de ${RESOLUTION_BY_ID[resolution].label}, o per\xEDodo m\xE1ximo \xE9 ${maxDays} dia(s).`);
80277
+ }
80278
+ if (resolution === "1d" && !isLocalMidnight(range.start)) {
80279
+ errs.push(`Com intervalos de 1 dia, o in\xEDcio precisa ser \xE0 meia-noite (ex.: ${formatLocalDateTime(localMidnightOf(range.start))}).`);
80280
+ }
80281
+ return errs;
80282
+ }
80283
+
80284
+ // src/utils/reports/demand/hash.ts
80285
+ var HASH_ALGORITHM = "canonical-v2";
80286
+ function compareCodeUnits(a, b) {
80287
+ if (a === b) return 0;
80288
+ return a < b ? -1 : 1;
80289
+ }
80290
+ function isPlainObject(v) {
80291
+ return typeof v === "object" && v !== null && !Array.isArray(v);
80292
+ }
80293
+ function serialize(value) {
80294
+ if (value === null) return "null";
80295
+ switch (typeof value) {
80296
+ case "string":
80297
+ case "number":
80298
+ case "boolean":
80299
+ return JSON.stringify(value);
80300
+ case "bigint":
80301
+ throw new TypeError("canonicalJson: bigint is not serialisable");
80302
+ case "undefined":
80303
+ case "function":
80304
+ case "symbol":
80305
+ return void 0;
80306
+ default:
80307
+ break;
80308
+ }
80309
+ const withToJson = value;
80310
+ if (typeof withToJson.toJSON === "function") return serialize(withToJson.toJSON(""));
80311
+ if (Array.isArray(value)) return "[" + value.map((item) => serialize(item) ?? "null").join(",") + "]";
80312
+ if (isPlainObject(value)) {
80313
+ const parts = [];
80314
+ for (const key of Object.keys(value).sort(compareCodeUnits)) {
80315
+ const s = serialize(value[key]);
80316
+ if (s !== void 0) parts.push(JSON.stringify(key) + ":" + s);
80317
+ }
80318
+ return "{" + parts.join(",") + "}";
80319
+ }
80320
+ return JSON.stringify(value);
80321
+ }
80322
+ function canonicalJson(value) {
80323
+ const out = serialize(value);
80324
+ return out === void 0 ? "null" : out;
80325
+ }
80326
+ var toUtcIso = (value) => {
80327
+ const d = new Date(value);
80328
+ if (Number.isNaN(d.getTime())) throw new TypeError(`invalid datetime: ${String(value)}`);
80329
+ return d.toISOString();
80330
+ };
80331
+ function canonicalizeRequest(request2) {
80332
+ const devices = request2.devices.map((d) => {
80333
+ const rest = { ...d };
80334
+ delete rest.label;
80335
+ delete rest.name;
80336
+ return rest;
80337
+ }).sort((a, b) => compareCodeUnits(String(a.gcdrDeviceId), String(b.gcdrDeviceId)));
80338
+ const telemetries = request2.telemetries.map((t) => ({ ...t })).sort((a, b) => compareCodeUnits(a.key, b.key));
80339
+ const period = { ...request2.period, start: toUtcIso(request2.period.start), end: toUtcIso(request2.period.end) };
80340
+ const out = { ...request2, devices, period, telemetries };
80341
+ delete out.estimate;
80342
+ return out;
80343
+ }
80344
+ function canonicalRequestJson(request2) {
80345
+ return canonicalJson(canonicalizeRequest(request2));
80346
+ }
80347
+ var webCryptoSha256 = async (bytes) => {
80348
+ const subtle = globalThis.crypto?.subtle;
80349
+ if (!subtle) throw new Error("crypto.subtle indispon\xEDvel (contexto n\xE3o seguro?)");
80350
+ return new Uint8Array(await subtle.digest("SHA-256", bytes));
80351
+ };
80352
+ async function requestHash(request2, sha256 = webCryptoSha256) {
80353
+ const digest = await sha256(new TextEncoder().encode(canonicalRequestJson(request2)));
80354
+ return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
80355
+ }
80356
+
80357
+ // src/utils/reports/demand/envelope.ts
80358
+ var JOB_KEY_PREFIX = "demandReportJob_";
80359
+ var SUBMIT_SCHEMA_VERSION = 1;
80360
+ var envelopeKey = (jobId) => JOB_KEY_PREFIX + jobId;
80361
+ function uuidv7(ms, random = defaultRandom) {
80362
+ const r = random(10);
80363
+ const hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
80364
+ const t = Math.floor(ms).toString(16).padStart(12, "0").slice(-12);
80365
+ const rh = hex(r);
80366
+ const variant = (r[2] & 63 | 128).toString(16);
80367
+ return `${t.slice(0, 8)}-${t.slice(8, 12)}-7${rh.slice(0, 3)}-${variant}${rh.slice(6, 8)}-${rh.slice(8, 20)}`;
80368
+ }
80369
+ function defaultRandom(n) {
80370
+ const out = new Uint8Array(n);
80371
+ const c = globalThis.crypto;
80372
+ if (c?.getRandomValues) return c.getRandomValues(out);
80373
+ for (let i = 0; i < n; i++) out[i] = Math.floor(Math.random() * 256);
80374
+ return out;
80375
+ }
80376
+ var clip = (v, max) => typeof v === "string" && v.length ? v.slice(0, max) : void 0;
80377
+ function requestDevice(d) {
80378
+ const out = { gcdrDeviceId: String(d.gcdrDeviceId) };
80379
+ const tb = clip(d.tbDeviceId, 64);
80380
+ if (tb) out.tbDeviceId = tb;
80381
+ out.ingestionId = d.ingestionId || null;
80382
+ const label = clip(d.label, 200);
80383
+ if (label) out.label = label;
80384
+ const name = clip(d.name, 200);
80385
+ if (name) out.name = name;
80386
+ out.identifier = d.identifier ? d.identifier.slice(0, 100) : null;
80387
+ const profile = clip(d.deviceProfile, 100);
80388
+ if (profile) out.deviceProfile = profile;
80389
+ const group = clip(d.group ?? void 0, 40);
80390
+ if (group) out.group = group;
80391
+ return out;
80392
+ }
80393
+ function buildRequest(input) {
80394
+ const telemetries = telemetriesInCatalogOrder(input.telemetryKeys);
80395
+ const devices = input.devices.filter((d) => !!d.gcdrDeviceId).map(requestDevice);
80396
+ const buckets = bucketCount(input.range.start, input.range.end, input.resolution);
80397
+ return {
80398
+ devices,
80399
+ period: {
80400
+ start: new Date(input.range.start).toISOString(),
80401
+ end: new Date(input.range.end).toISOString(),
80402
+ timezone: "America/Sao_Paulo",
80403
+ preset: input.preset
80404
+ },
80405
+ resolution: input.resolution,
80406
+ telemetries,
80407
+ estimate: { buckets, points: buckets * devices.length * telemetries.length }
80408
+ };
80409
+ }
80410
+ async function buildEnvelope(input) {
80411
+ const jobId = `drj_${(input.idGen ?? uuidv7)(input.now)}`;
80412
+ const at = new Date(input.now).toISOString();
80413
+ return {
80414
+ schemaVersion: ENVELOPE_SCHEMA_VERSION,
80415
+ type: "demand-report",
80416
+ jobId,
80417
+ catalogVersion: CATALOG_VERSION,
80418
+ hashAlgorithm: HASH_ALGORITHM,
80419
+ requestHash: await requestHash(input.request, input.sha256),
80420
+ requestedAt: at,
80421
+ requestedBy: { ...input.user },
80422
+ customer: { ...input.customer },
80423
+ request: input.request,
80424
+ options: { ...input.options || {} },
80425
+ audit: [{ op: "submit", outcome: "requested", at, by: input.user.tbUserId }]
80426
+ };
80427
+ }
80428
+ function withOptions(cmd, o) {
80429
+ if (!o) return cmd;
80430
+ const out = cmd;
80431
+ if (o.force !== void 0) out.force = o.force;
80432
+ if (o.visibility !== void 0) out.visibility = o.visibility;
80433
+ if (o.notify !== void 0) out.notify = o.notify;
80434
+ return out;
80435
+ }
80436
+ function toSubmitCommand(e) {
80437
+ return withOptions(
80438
+ {
80439
+ schemaVersion: SUBMIT_SCHEMA_VERSION,
80440
+ type: e.type,
80441
+ catalogVersion: e.catalogVersion,
80442
+ jobId: e.jobId,
80443
+ requestHash: e.requestHash,
80444
+ requestedBy: { userId: e.requestedBy.tbUserId },
80445
+ request: e.request
80446
+ },
80447
+ e.options
80448
+ );
80449
+ }
80450
+ function toValidateCommand(request2, user, options) {
80451
+ return withOptions(
80452
+ {
80453
+ schemaVersion: SUBMIT_SCHEMA_VERSION,
80454
+ type: "demand-report",
80455
+ catalogVersion: CATALOG_VERSION,
80456
+ requestedBy: { userId: user.tbUserId },
80457
+ request: request2
80458
+ },
80459
+ options
80460
+ );
80461
+ }
80462
+ function parseEnvelope(raw) {
80463
+ let v = raw;
80464
+ if (typeof v === "string") {
80465
+ try {
80466
+ v = JSON.parse(v);
80467
+ } catch {
80468
+ return { ok: false, reason: "invalid-json" };
80469
+ }
80470
+ }
80471
+ if (!v || typeof v !== "object") return { ok: false, reason: "not-an-object" };
80472
+ const o = v;
80473
+ if (o.schemaVersion !== ENVELOPE_SCHEMA_VERSION) return { ok: false, reason: "unknown-version", schemaVersion: o.schemaVersion };
80474
+ if (o.type !== "demand-report" || typeof o.jobId !== "string" || !o.jobId.startsWith("drj_")) return { ok: false, reason: "bad-header" };
80475
+ if (!o.request || !Array.isArray(o.request.devices) || !o.request.period?.start || !Array.isArray(o.request.telemetries)) {
80476
+ return { ok: false, reason: "bad-request" };
80477
+ }
80478
+ if (!Array.isArray(o.audit) || typeof o.requestedAt !== "string" || !o.requestedBy) return { ok: false, reason: "bad-audit" };
80479
+ return { ok: true, envelope: { ...o, options: o.options || {} } };
80480
+ }
80481
+ function auditEvent(op, outcome, by, now, extra = {}) {
80482
+ return { op, outcome, at: new Date(now).toISOString(), by, ...extra };
80483
+ }
80484
+
80485
+ // src/utils/reports/demand/jobs.ts
80486
+ var JOB_STATE_LABEL = {
80487
+ loading: "Consultando\u2026",
80488
+ sending: "Enviando\u2026",
80489
+ send_uncertain: "Confirmando envio\u2026",
80490
+ not_received: "N\xE3o enviado",
80491
+ query_unavailable: "Status indispon\xEDvel",
80492
+ rejected: "Recusado",
80493
+ old_request: "Pedido antigo",
80494
+ queued: "Na fila",
80495
+ running: "Processando",
80496
+ succeeded: "Conclu\xEDdo",
80497
+ result_expired: "Expirado",
80498
+ purged: "Conte\xFAdo removido",
80499
+ failed: "Falhou",
80500
+ cancelled: "Cancelado"
80501
+ };
80502
+ function lastSendEvent(e) {
80503
+ for (let i = e.audit.length - 1; i >= 0; i--) {
80504
+ const a = e.audit[i];
80505
+ if ((a.op === "submit" || a.op === "resend") && a.outcome !== "requested") return a;
80506
+ }
80507
+ return void 0;
80508
+ }
80509
+ function deriveJobView(i) {
80510
+ const age = i.now - Date.parse(i.envelope.requestedAt);
80511
+ const inWindow = age < i.idempotencyWindowMs;
80512
+ if (i.inflight) return { state: "sending", canResend: false };
80513
+ const k = i.knowledge;
80514
+ if (k && "found" in k && k.found) {
80515
+ const job = k.job;
80516
+ if (job.resultAvailability === "purged") return { state: "purged", job, canResend: false };
80517
+ if (job.status === "expired" || job.resultAvailability === "expired") return { state: "result_expired", job, canResend: false };
80518
+ return { state: job.status, job, canResend: false };
80519
+ }
80520
+ const last = lastSendEvent(i.envelope);
80521
+ if (last && last.outcome === "rejected") {
80522
+ return {
80523
+ state: "rejected",
80524
+ code: last.code,
80525
+ reason: last.reason,
80526
+ message: last.message,
80527
+ canResend: last.code === "ACTIVE_JOBS_LIMIT" && inWindow
80528
+ };
80529
+ }
80530
+ if (k && "queryError" in k) return { state: "query_unavailable", canResend: false };
80531
+ if (!k) return { state: "loading", canResend: false };
80532
+ if (!inWindow) return { state: "old_request", canResend: false };
80533
+ if (age < i.graceMs) return { state: i.uncertain ? "send_uncertain" : "sending", canResend: i.uncertain };
80534
+ return { state: "not_received", canResend: true };
80535
+ }
80536
+ function needsPolling(state6) {
80537
+ return state6 === "queued" || state6 === "running" || state6 === "sending" || state6 === "send_uncertain" || state6 === "loading";
80538
+ }
80539
+ function mergeBatch(known, results) {
80540
+ for (const r of results) {
80541
+ const prev = known.get(r.jobId);
80542
+ if (!r.found && prev && "found" in prev && prev.found) continue;
80543
+ known.set(r.jobId, r.found ? { found: true, job: r.job } : { found: false });
80544
+ }
80545
+ }
80546
+ function reconciliationEvents(e, job) {
80547
+ const out = [];
80548
+ if (!e.audit.some((a) => (a.op === "submit" || a.op === "resend" || a.op === "reused") && a.outcome === "accepted")) {
80549
+ out.push({ op: "submit", outcome: "accepted", at: job.createdAt, by: e.requestedBy.tbUserId, source: "reconciled" });
80550
+ }
80551
+ if (job.status === "cancelled" && job.cancelledAt && !e.audit.some((a) => a.op === "cancel" && a.outcome === "accepted")) {
80552
+ out.push({ op: "cancel", outcome: "accepted", at: job.cancelledAt, by: job.cancelledBy?.declared?.userId || "desconhecido", source: "reconciled" });
80553
+ }
80554
+ return out;
80555
+ }
80556
+ function isUncertainStatus(status) {
80557
+ return status === 0 || status >= 500;
80558
+ }
80559
+ var REASON_TEXT = {
80560
+ SHAPE: "O pedido tem um formato inv\xE1lido.",
80561
+ REQUEST_HASH_MISMATCH: "O conte\xFAdo do pedido n\xE3o confere \u2014 contate o suporte.",
80562
+ CATALOG_VERSION_UNSUPPORTED: "A lista de telemetrias mudou \u2014 atualize o painel.",
80563
+ UNKNOWN_TELEMETRY: "H\xE1 uma telemetria que o servidor n\xE3o conhece.",
80564
+ TELEMETRY_DUPLICATE: "H\xE1 telemetrias repetidas.",
80565
+ PERIOD_TOO_LONG: "O per\xEDodo \xE9 longo demais para este intervalo.",
80566
+ PERIOD_NOT_ALIGNED: "Com intervalos de 1 dia, o in\xEDcio precisa ser \xE0 meia-noite.",
80567
+ PERIOD_INVALID: "O per\xEDodo \xE9 inv\xE1lido.",
80568
+ DEVICE_NOT_IN_CUSTOMER: "Um dos medidores n\xE3o pertence a este cliente.",
80569
+ DEVICE_NOT_ENERGY: "Um dos dispositivos n\xE3o \xE9 um medidor de energia.",
80570
+ DEVICE_NO_SOURCE: "Um dos medidores n\xE3o tem fonte de dados para as telemetrias escolhidas.",
80571
+ POINTS_LIMIT: "O relat\xF3rio ficaria grande demais \u2014 reduza medidores, telemetrias ou per\xEDodo.",
80572
+ NO_DATA_IN_PERIOD: "N\xE3o h\xE1 dados no per\xEDodo escolhido.",
80573
+ VISIBILITY_REQUIRES_VERIFIED_IDENTITY: "\u201CSomente eu\u201D ainda n\xE3o est\xE1 dispon\xEDvel."
80574
+ };
80575
+ function rejectionText(code, reason, message, details) {
80576
+ switch (code) {
80577
+ case "VALIDATION_FAILED":
80578
+ return reason && REASON_TEXT[reason] || message || "O pedido n\xE3o passou na valida\xE7\xE3o.";
80579
+ case "ACTIVE_JOBS_LIMIT": {
80580
+ const d = details || {};
80581
+ const who = {
80582
+ user: "seus relat\xF3rios em andamento",
80583
+ customer: "relat\xF3rios em andamento do cliente",
80584
+ tenant: "relat\xF3rios em andamento da plataforma",
80585
+ day: "relat\xF3rios gerados hoje"
80586
+ };
80587
+ return d.limit ? `Limite atingido: ${who[d.limit] || d.limit}${d.max ? ` (m\xE1x. ${d.max})` : ""}.` : "Limite de relat\xF3rios atingido.";
80588
+ }
80589
+ case "FORBIDDEN":
80590
+ return "Sem permiss\xE3o para pedir relat\xF3rios.";
80591
+ case "UNAUTHORIZED":
80592
+ return /reports:write/.test(message || "") ? "A credencial do dashboard n\xE3o tem permiss\xE3o para pedir relat\xF3rios (escopo reports:write) \u2014 contate o suporte." : "Credencial do dashboard ausente ou inv\xE1lida para relat\xF3rios \u2014 contate o suporte.";
80593
+ case "JOB_ID_CONFLICT":
80594
+ return "Este pedido j\xE1 existe com outro conte\xFAdo \u2014 use \u201CDuplicar e editar\u201D.";
80595
+ case "RATE_LIMITED":
80596
+ return "Muitas requisi\xE7\xF5es \u2014 tente de novo em instantes.";
80597
+ default:
80598
+ return message || code || "Pedido recusado.";
80599
+ }
80600
+ }
80601
+ function failureText(code) {
80602
+ switch (code) {
80603
+ case "SOURCE_UNAVAILABLE":
80604
+ return "A fonte de dados ficou indispon\xEDvel durante a gera\xE7\xE3o.";
80605
+ case "TIMEOUT":
80606
+ return "A gera\xE7\xE3o demorou al\xE9m do limite.";
80607
+ case "INTERRUPTED":
80608
+ case "WORKER_LOST":
80609
+ return "A gera\xE7\xE3o foi interrompida.";
80610
+ case "STALE":
80611
+ return "O pedido ficou tempo demais na fila.";
80612
+ case "DEVICE_GONE":
80613
+ return "Um dos medidores n\xE3o existe mais.";
80614
+ case "RESULT_TOO_LARGE":
80615
+ case "XLSX_ROW_LIMIT":
80616
+ return "O resultado ficou grande demais \u2014 reduza o per\xEDodo ou os medidores.";
80617
+ case "CONFIGURATION":
80618
+ return "Falha de configura\xE7\xE3o no servidor \u2014 contate o suporte.";
80619
+ default:
80620
+ return "N\xE3o foi poss\xEDvel gerar o relat\xF3rio.";
80621
+ }
80622
+ }
80623
+
80624
+ // src/utils/reports/demand/taxonomy.ts
80625
+ function humanizeKey(key) {
80626
+ const s = key.replace(/[_-]+/g, " ").trim();
80627
+ return s ? s.charAt(0).toUpperCase() + s.slice(1) : key;
80628
+ }
80629
+ function buildTaxonomyChips(devices, classifier) {
80630
+ const categoryOf = /* @__PURE__ */ new Map();
80631
+ const counts = /* @__PURE__ */ new Map();
80632
+ for (const d of devices) {
80633
+ const key = classifier.classify(d);
80634
+ categoryOf.set(d.tbDeviceId, key);
80635
+ if (key) counts.set(key, (counts.get(key) || 0) + 1);
80636
+ }
80637
+ const chips = [...counts.entries()].map(([key, count]) => {
80638
+ const info = classifier.describe(key);
80639
+ return { key, count, label: info?.label || humanizeKey(key), icon: info?.icon, order: info?.order ?? 999 };
80640
+ }).sort((a, b) => a.order - b.order || a.label.localeCompare(b.label, "pt-BR")).map(({ order: _order, ...chip }) => chip);
80641
+ return { chips, categoryOf };
80642
+ }
80643
+
80644
+ // src/components/demand-panel-v2/styles.ts
80645
+ var DP2_STYLE_ID = "myio-demand-panel-v2-styles";
80646
+ var DP2_CSS = `
80647
+ .dp2-root { --dp2-ac: var(--myio-brand-700, #3e1a7d); --dp2-ac-soft: color-mix(in srgb, var(--dp2-ac) 10%, transparent);
80648
+ --dp2-ok: var(--myio-success, #16a34a); --dp2-warn: var(--myio-warning, #d97706); --dp2-err: var(--myio-danger, #dc2626); --dp2-info: var(--myio-info, #2563eb);
80649
+ font-family: Nunito, var(--myio-font, system-ui, sans-serif); color: var(--myio-text, #1e293b); font-size: 13px;
80650
+ height: 100%; display: flex; flex-direction: column; min-height: 0; }
80651
+ .dp2-root * { box-sizing: border-box; }
80652
+ .dp2-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--myio-border, #e2e8f0); margin: -4px 0 0; }
80653
+ .dp2-tab { border: none; background: none; font: inherit; font-weight: 700; color: var(--myio-text-muted, #64748b); padding: 9px 14px; cursor: pointer; border-bottom: 3px solid transparent; }
80654
+ .dp2-tab[aria-selected="true"] { color: var(--dp2-ac); border-bottom-color: var(--dp2-ac); }
80655
+ .dp2-count { display: inline-block; min-width: 18px; padding: 0 5px; margin-left: 4px; border-radius: 9px; background: var(--dp2-info); color: #fff; font-size: 11px; }
80656
+ .dp2-stepper { display: flex; gap: 6px; padding: 10px 0; }
80657
+ .dp2-stepper[hidden] { display: none; }
80658
+ .dp2-step { flex: 1; display: flex; align-items: center; gap: 8px; border: none; background: none; font: inherit; color: var(--myio-text-muted, #64748b); padding: 6px 8px; border-radius: 10px; text-align: left; cursor: default; }
80659
+ .dp2-step b { width: 24px; height: 24px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; border: 2px solid var(--myio-border, #e2e8f0); font-size: 12px; flex-shrink: 0; }
80660
+ .dp2-step[data-state="current"] { color: var(--dp2-ac); font-weight: 800; background: var(--dp2-ac-soft); }
80661
+ .dp2-step[data-state="current"] b { border-color: var(--dp2-ac); background: var(--dp2-ac); color: #fff; }
80662
+ .dp2-step[data-state="done"] { color: var(--myio-text, #1e293b); cursor: pointer; }
80663
+ .dp2-step[data-state="done"] b { border-color: var(--dp2-ok); background: var(--dp2-ok); color: #fff; }
80664
+ .dp2-banner { margin: 0 0 10px; padding: 9px 12px; border-radius: 10px; background: color-mix(in srgb, var(--dp2-warn) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dp2-warn) 40%, transparent); }
80665
+ .dp2-body { flex: 1; min-height: 0; overflow: auto; padding: 4px 2px 8px; }
80666
+ .dp2-nav { display: flex; align-items: center; gap: 10px; padding: 10px 0 0; border-top: 1px solid var(--myio-border, #e2e8f0); }
80667
+ .dp2-nav .hint { flex: 1; color: var(--myio-text-muted, #64748b); }
80668
+ .dp2-btn { border: 1px solid var(--myio-border, #e2e8f0); background: var(--myio-card, #fff); color: var(--myio-text, #1e293b); border-radius: 9px; padding: 8px 14px; font: inherit; font-weight: 700; cursor: pointer; }
80669
+ .dp2-btn:hover:not([disabled]) { border-color: var(--dp2-ac); }
80670
+ .dp2-btn[disabled] { opacity: .45; cursor: not-allowed; }
80671
+ .dp2-btn.primary { background: var(--dp2-ac); color: #fff; border-color: var(--dp2-ac); }
80672
+ .dp2-btn.sm { padding: 4px 9px; font-size: 12px; }
80673
+ .dp2-btn.link { border: none; background: none; color: var(--dp2-ac); padding: 4px 6px; }
80674
+ .dp2-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
80675
+ .dp2-s1 { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 14px; height: 100%; min-height: 380px; }
80676
+ .dp2-col { display: flex; flex-direction: column; min-height: 0; background: var(--myio-card, #fff); border: 1px solid var(--myio-border, #e2e8f0); border-radius: 12px; }
80677
+ .dp2-col-head { padding: 12px; border-bottom: 1px solid var(--myio-border, #e2e8f0); display: flex; flex-direction: column; gap: 8px; }
80678
+ .dp2-col-head h3 { margin: 0; font-size: 13px; display: flex; justify-content: space-between; align-items: center; }
80679
+ .dp2-search { width: 100%; border: 1px solid var(--myio-border, #e2e8f0); background: var(--myio-bg, #f8fafc); color: var(--myio-text, #1e293b); border-radius: 9px; padding: 9px 12px; font: inherit; font-size: 14px; }
80680
+ .dp2-search:focus { outline: 2px solid var(--dp2-ac); outline-offset: -1px; }
80681
+ .dp2-chips { display: flex; flex-wrap: wrap; gap: 6px; }
80682
+ .dp2-chip { display: inline-flex; align-items: center; gap: 5px; border: 1px solid var(--myio-border, #e2e8f0); background: var(--myio-card, #fff); color: var(--myio-text, #1e293b); border-radius: 999px; padding: 3px 10px; font: inherit; font-size: 12px; cursor: pointer; }
80683
+ .dp2-chip[aria-pressed="true"] { background: var(--dp2-ac-soft); border-color: var(--dp2-ac); color: var(--dp2-ac); font-weight: 700; }
80684
+ .dp2-chip[disabled] { opacity: .4; cursor: not-allowed; }
80685
+ .dp2-list { flex: 1; overflow: auto; padding: 6px; min-height: 0; }
80686
+ .dp2-item { display: grid; grid-template-columns: 32px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 7px 8px; border-radius: 9px; }
80687
+ .dp2-item:hover { background: var(--dp2-ac-soft); }
80688
+ .dp2-item.blocked { opacity: .6; }
80689
+ .dp2-ico { width: 32px; height: 32px; object-fit: contain; }
80690
+ .dp2-name { font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
80691
+ .dp2-sub { color: var(--myio-text-muted, #64748b); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
80692
+ .dp2-tag { display: inline-block; border-radius: 999px; padding: 0 7px; font-size: 11px; background: var(--dp2-ac-soft); color: var(--dp2-ac); margin-right: 4px; }
80693
+ .dp2-tag.warn { background: color-mix(in srgb, var(--dp2-warn) 15%, transparent); color: var(--dp2-warn); }
80694
+ .dp2-tag.block { background: color-mix(in srgb, var(--dp2-err) 12%, transparent); color: var(--dp2-err); }
80695
+ .dp2-added { color: var(--dp2-ok); font-weight: 700; font-size: 12px; }
80696
+ .dp2-empty { color: var(--myio-text-muted, #64748b); text-align: center; padding: 30px 12px; }
80697
+ .dp2-mark { background: #fde68a; color: inherit; border-radius: 2px; }
80698
+ .dp2-card { background: var(--myio-card, #fff); border: 1px solid var(--myio-border, #e2e8f0); border-radius: 12px; padding: 14px 16px; margin-bottom: 14px; }
80699
+ .dp2-card h3 { margin: 0 0 10px; font-size: 14px; display: flex; justify-content: space-between; align-items: center; }
80700
+ .dp2-row { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; }
80701
+ .dp2-field { display: flex; flex-direction: column; gap: 4px; font-weight: 700; font-size: 12px; color: var(--myio-text-muted, #64748b); }
80702
+ .dp2-field input { border: 1px solid var(--myio-border, #e2e8f0); background: var(--myio-bg, #f8fafc); color: var(--myio-text, #1e293b); border-radius: 8px; padding: 7px 9px; font: inherit; }
80703
+ .dp2-info { color: var(--myio-text-muted, #64748b); }
80704
+ .dp2-err { color: var(--dp2-err); font-weight: 700; }
80705
+ .dp2-fix { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 8px; }
80706
+ .dp2-tgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 14px; }
80707
+ .dp2-check { display: flex; align-items: center; gap: 8px; padding: 6px 4px; cursor: pointer; border-radius: 7px; }
80708
+ .dp2-check:hover { background: var(--dp2-ac-soft); }
80709
+ .dp2-check input { width: 16px; height: 16px; accent-color: var(--dp2-ac); }
80710
+ .dp2-check .u { margin-left: auto; color: var(--myio-text-muted, #64748b); font-size: 11px; }
80711
+ .dp2-sum { display: grid; grid-template-columns: 170px minmax(0, 1fr); gap: 8px 14px; margin: 0; }
80712
+ .dp2-sum dt { color: var(--myio-text-muted, #64748b); font-weight: 700; }
80713
+ .dp2-sum dd { margin: 0; }
80714
+ .dp2-pills { display: flex; flex-wrap: wrap; gap: 5px; }
80715
+ .dp2-pill { border-radius: 999px; padding: 2px 9px; font-size: 12px; background: var(--dp2-ac-soft); color: var(--dp2-ac); }
80716
+ .dp2-size { display: inline-block; border-radius: 999px; padding: 2px 10px; font-weight: 800; color: #fff; background: var(--dp2-ok); }
80717
+ .dp2-size.medio { background: var(--dp2-warn); } .dp2-size.grande { background: var(--dp2-err); }
80718
+ .dp2-json { background: #0f172a; color: #e2e8f0; border-radius: 9px; padding: 10px 12px; font: 11.5px/1.45 ui-monospace, Consolas, monospace; max-height: 260px; overflow: auto; white-space: pre; margin: 8px 0 0; }
80719
+ .dp2-meter { height: 8px; background: var(--myio-border, #e2e8f0); border-radius: 4px; overflow: hidden; margin-top: 6px; }
80720
+ .dp2-meter span { display: block; height: 100%; background: var(--dp2-ok); }
80721
+ .dp2-details summary { cursor: pointer; color: var(--dp2-ac); font-weight: 700; }
80722
+ .dp2-job { background: var(--myio-card, #fff); border: 1px solid var(--myio-border, #e2e8f0); border-radius: 12px; padding: 12px 14px; margin-bottom: 10px; }
80723
+ .dp2-job.ghost { border-style: dashed; }
80724
+ .dp2-job-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
80725
+ .dp2-job-title { font-weight: 800; }
80726
+ .dp2-status { border-radius: 999px; padding: 2px 10px; font-size: 12px; font-weight: 800; color: #fff; background: #94a3b8; }
80727
+ .dp2-status.running, .dp2-status.sending, .dp2-status.send_uncertain { background: var(--dp2-info); }
80728
+ .dp2-status.succeeded { background: var(--dp2-ok); }
80729
+ .dp2-status.failed, .dp2-status.not_received, .dp2-status.rejected { background: var(--dp2-err); }
80730
+ .dp2-status.query_unavailable { background: var(--dp2-warn); }
80731
+ .dp2-status.queued { background: color-mix(in srgb, var(--dp2-info) 70%, #64748b); }
80732
+ .dp2-banner.info { background: color-mix(in srgb, var(--dp2-info) 10%, transparent); border-color: color-mix(in srgb, var(--dp2-info) 35%, transparent); }
80733
+ .dp2-bar { height: 6px; background: var(--myio-border, #e2e8f0); border-radius: 3px; overflow: hidden; margin-top: 8px; }
80734
+ .dp2-bar span { display: block; height: 100%; background: var(--dp2-info); transition: width .4s; }
80735
+ .dp2-job-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; align-items: center; }
80736
+ .dp2-skel { height: 74px; border-radius: 12px; margin-bottom: 10px; background: linear-gradient(90deg, var(--myio-border, #e2e8f0), var(--myio-card, #fff), var(--myio-border, #e2e8f0)); background-size: 200% 100%; animation: dp2sk 1.2s infinite; }
80737
+ @keyframes dp2sk { from { background-position: 200% 0; } to { background-position: -200% 0; } }
80738
+ .dp2-audit-warn { color: var(--dp2-warn); font-size: 12px; font-weight: 700; margin-top: 4px; }
80739
+ .dp2-root[data-narrow="true"] .dp2-s1 { grid-template-columns: 1fr; }
80740
+ .dp2-root[data-narrow="true"] .dp2-sum { grid-template-columns: 1fr; }
80741
+ `;
80742
+ function injectDemandPanelStyles(doc = document) {
80743
+ if (doc.getElementById(DP2_STYLE_ID)) return;
80744
+ const style = doc.createElement("style");
80745
+ style.id = DP2_STYLE_ID;
80746
+ style.textContent = DP2_CSS;
80747
+ doc.head.appendChild(style);
80748
+ }
80749
+
80750
+ // src/components/demand-panel-v2/openDemandPanelV2.ts
80751
+ var PRESETS = [
80752
+ { id: "today", label: "Hoje" },
80753
+ { id: "yesterday", label: "Ontem" },
80754
+ { id: "last7days", label: "\xDAltimos 7 dias" },
80755
+ { id: "thisMonth", label: "Este m\xEAs" },
80756
+ { id: "lastMonth", label: "M\xEAs passado" },
80757
+ { id: "custom", label: "Personalizado" }
80758
+ ];
80759
+ var STEPS = ["Dispositivos", "Per\xEDodo", "Telemetrias", "Resumo"];
80760
+ var esc4 = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
80761
+ var norm = (s) => String(s ?? "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
80762
+ var fmtN = (n) => n.toLocaleString("pt-BR");
80763
+ var pad = (n) => String(n).padStart(2, "0");
80764
+ var fmtDay = (ms) => {
80765
+ const p = spParts(ms);
80766
+ return `${pad(p.d)}/${pad(p.m + 1)}`;
80767
+ };
80768
+ var fmtDuration = (ms) => {
80769
+ const h3 = ms / 36e5;
80770
+ return h3 < 48 ? `${h3.toLocaleString("pt-BR", { maximumFractionDigits: 1 })} h` : `${(h3 / 24).toLocaleString("pt-BR", { maximumFractionDigits: 1 })} dias`;
80771
+ };
80772
+ var asPortError = (e) => {
80773
+ const x = e;
80774
+ return {
80775
+ status: typeof x?.status === "number" ? x.status : 0,
80776
+ code: x?.code || (x?.name === "AbortError" ? "ABORTED" : "ERROR"),
80777
+ message: x?.message || String(e),
80778
+ details: x?.details,
80779
+ retryAfter: x?.retryAfter
80780
+ };
80781
+ };
80782
+ function createProfileClassifier() {
80783
+ const nodes = /* @__PURE__ */ new Map();
80784
+ try {
80785
+ flattenTree(liftProfileToTree()).forEach((n, i) => nodes.set(n.key, { label: n.label, icon: n.icon, order: i }));
80786
+ } catch {
80787
+ }
80788
+ return {
80789
+ classify(d) {
80790
+ const item = { deviceProfile: d.deviceProfile, identifier: d.identifier };
80791
+ const g = resolveGroup(item).group;
80792
+ if (g === "ocultos") return null;
80793
+ if (g === "entrada") return "entrada";
80794
+ return resolveCategory(item).category || g;
80795
+ },
80796
+ describe: (key) => nodes.get(key)
80797
+ };
80798
+ }
80799
+ function deviceIconUrl(profile) {
80800
+ const url = getDeviceIcon(profile);
80801
+ return typeof url === "string" && /^(https?:|data:)/.test(url) ? url : DEFAULT_DEVICE_ICON;
80802
+ }
80803
+ var DemandPanelV2 = class {
80804
+ constructor(p) {
80805
+ this.p = p;
80806
+ this.now = p.now ?? (() => Date.now());
80807
+ this.graceMs = p.graceMs ?? 12e4;
80808
+ this.windowMs = p.idempotencyWindowMs ?? 7 * 24 * 36e5;
80809
+ this.pageSize = p.pageSize ?? 20;
80810
+ this.pollDelay = p.pollMinMs ?? 5e3;
80811
+ this.deviceById = new Map(p.devices.map((d) => [d.tbDeviceId, d]));
80812
+ p.devices.forEach((d) => this.labelCount.set(d.label, (this.labelCount.get(d.label) || 0) + 1));
80813
+ const tax = buildTaxonomyChips(p.devices, p.classifier ?? createProfileClassifier());
80814
+ this.chips = tax.chips;
80815
+ this.categoryOf = tax.categoryOf;
80816
+ const r = presetRange("last7days", this.now());
80817
+ this.s.period = { preset: "last7days", start: r.start, end: r.end };
80818
+ this.s.selected = (p.preselect ?? []).filter((id) => this.deviceById.has(id) && this.eligibility(this.deviceById.get(id)).ok);
80819
+ }
80820
+ modal;
80821
+ footer = null;
80822
+ root;
80823
+ el;
80824
+ abort = new AbortController();
80825
+ destroyed = false;
80826
+ pollTimer = null;
80827
+ pollDelay;
80828
+ paused = false;
80829
+ resizeObs = null;
80830
+ now;
80831
+ graceMs;
80832
+ windowMs;
80833
+ pageSize;
80834
+ deviceById;
80835
+ labelCount = /* @__PURE__ */ new Map();
80836
+ chips;
80837
+ categoryOf;
80838
+ s = {
80839
+ tab: "new",
80840
+ step: 1,
80841
+ maxStep: 1,
80842
+ search: "",
80843
+ category: "all",
80844
+ selected: [],
80845
+ period: { preset: "last7days", start: 0, end: 0 },
80846
+ // Resolução das linhas (GCDR): cada intervalo traz média, máximo, mínimo e nº de amostras
80847
+ resolution: DEFAULT_RESOLUTION,
80848
+ /** "Gerar novamente" quando existe um resultado idêntico reaproveitável (X-L2). */
80849
+ force: false,
80850
+ /** Validação no servidor (dry-run) do pedido do passo 4 — antes de gravar o envelope (L-10). */
80851
+ validation: { state: "idle", result: null, error: "", seq: 0 },
80852
+ config: null,
80853
+ telemetries: /* @__PURE__ */ new Set(["consumption"]),
80854
+ snapshot: null,
80855
+ submitting: false,
80856
+ envelopes: [],
80857
+ unreadable: 0,
80858
+ keys: [],
80859
+ page: 1,
80860
+ listState: "idle",
80861
+ listError: "",
80862
+ jobs: /* @__PURE__ */ new Map(),
80863
+ active: [],
80864
+ inflight: /* @__PURE__ */ new Set(),
80865
+ uncertain: /* @__PURE__ */ new Set(),
80866
+ auditPending: /* @__PURE__ */ new Set(),
80867
+ openDetails: /* @__PURE__ */ new Set()
80868
+ };
80869
+ /* ------------------------------------------------------------------ lifecycle */
80870
+ open() {
80871
+ injectDemandPanelStyles();
80872
+ this.modal = createModal({
80873
+ title: "Relat\xF3rio de Demanda e Telemetrias",
80874
+ icon: "\u26A1",
80875
+ useStandardHeader: true,
80876
+ showMaximize: true,
80877
+ width: "1120px",
80878
+ height: "88vh",
80879
+ theme: this.p.themeMode === "dark" ? "dark" : "light",
80880
+ closeOnBackdrop: false,
80881
+ closeOnEscape: false
80882
+ });
80883
+ this.root = document.createElement("div");
80884
+ this.root.className = "dp2-root";
80885
+ this.root.innerHTML = `
80886
+ <nav class="dp2-tabs" role="tablist" aria-label="Relat\xF3rio de demanda">
80887
+ <button class="dp2-tab" role="tab" type="button" data-action="tab" data-tab="new">Novo relat\xF3rio</button>
80888
+ <button class="dp2-tab" role="tab" type="button" data-action="tab" data-tab="jobs">Relat\xF3rios <span data-role="count"></span></button>
80889
+ </nav>
80890
+ <div class="dp2-stepper" data-region="stepper" aria-label="Etapas"></div>
80891
+ <div data-region="banner"></div>
80892
+ <div class="dp2-body" data-region="body" role="tabpanel"></div>
80893
+ <div class="dp2-nav" data-region="nav"></div>
80894
+ <div class="dp2-sr" aria-live="polite" data-region="live"></div>`;
80895
+ const q = (sel) => this.root.querySelector(sel);
80896
+ this.el = {
80897
+ stepper: q('[data-region="stepper"]'),
80898
+ banner: q('[data-region="banner"]'),
80899
+ body: q('[data-region="body"]'),
80900
+ nav: q('[data-region="nav"]'),
80901
+ live: q('[data-region="live"]'),
80902
+ count: q('[data-role="count"]')
80903
+ };
80904
+ this.modal.setContent(this.root);
80905
+ this.applyTheme();
80906
+ this.mountFooter();
80907
+ this.root.addEventListener("click", this.onClick);
80908
+ this.root.addEventListener("input", this.onInput);
80909
+ this.root.addEventListener("change", this.onChange);
80910
+ this.root.addEventListener("keydown", this.onKeydown);
80911
+ document.addEventListener("keydown", this.onDocKeydown);
80912
+ document.addEventListener("visibilitychange", this.onVisibility);
80913
+ if (typeof ResizeObserver !== "undefined") {
80914
+ this.resizeObs = new ResizeObserver(([e]) => {
80915
+ this.root.dataset.narrow = String(e.contentRect.width < 760);
80916
+ });
80917
+ this.resizeObs.observe(this.root);
80918
+ }
80919
+ this.modal.on("close", () => this.teardown());
80920
+ this.render();
80921
+ void this.loadPage(false);
80922
+ void this.loadConfig();
80923
+ return { close: () => this.modal.close(), refresh: () => this.loadPage(true) };
80924
+ }
80925
+ teardown() {
80926
+ if (this.destroyed) return;
80927
+ this.destroyed = true;
80928
+ this.abort.abort();
80929
+ if (this.pollTimer) clearTimeout(this.pollTimer);
80930
+ this.root.removeEventListener("click", this.onClick);
80931
+ this.root.removeEventListener("input", this.onInput);
80932
+ this.root.removeEventListener("change", this.onChange);
80933
+ this.root.removeEventListener("keydown", this.onKeydown);
80934
+ document.removeEventListener("keydown", this.onDocKeydown);
80935
+ document.removeEventListener("visibilitychange", this.onVisibility);
80936
+ this.resizeObs?.disconnect();
80937
+ this.footer?.destroy();
80938
+ this.footer = null;
80939
+ this.p.onClose?.();
80940
+ }
80941
+ mountFooter() {
80942
+ const lib = window.MyIOLibrary;
80943
+ this.footer = createModalFooter({
80944
+ customerName: this.p.customer.name,
80945
+ // unknown version (tests / showcase without the UMD) → no npm version check
80946
+ libVersion: lib?.version ? { current: lib.version } : false,
80947
+ themeMode: this.p.themeMode === "dark" ? "dark" : "light"
80948
+ });
80949
+ const modalRoot = this.modal.element.closest(".myio-modal") || this.modal.element;
80950
+ modalRoot.appendChild(this.footer.element);
80951
+ }
80952
+ applyTheme() {
80953
+ const theme = this.p.theme ?? window.MyIOUtils?.theme;
80954
+ if (!theme) return;
80955
+ const vars = typeof theme.cssVars === "function" ? theme.cssVars() : theme;
80956
+ const target = this.modal.element.closest(".myio-modal") || this.modal.element;
80957
+ Object.entries(vars || {}).forEach(([k, v]) => {
80958
+ if (k.startsWith("--") && typeof v === "string") target.style.setProperty(k, v);
80959
+ });
80960
+ }
80961
+ /* ------------------------------------------------------------------ helpers */
80962
+ announce(msg) {
80963
+ this.el.live.textContent = msg;
80964
+ }
80965
+ toast(kind, msg) {
80966
+ const t = MyIOToast;
80967
+ (t[kind] || t.info).call(MyIOToast, msg);
80968
+ }
80969
+ async confirmDialog(title, message, confirmLabel, cancelLabel = "Cancelar") {
80970
+ const v = await openConfirmDialog({
80971
+ title,
80972
+ message,
80973
+ theme: this.p.themeMode === "dark" ? "dark" : "light",
80974
+ buttons: [{ label: cancelLabel, value: "no" }, { label: confirmLabel, value: "yes", variant: "primary", autoFocus: true }]
80975
+ });
80976
+ return v === "yes";
80977
+ }
80978
+ eligibility(d) {
80979
+ if (!d.gcdrDeviceId) return { ok: false, cls: "block", text: "\u26D4 sem v\xEDnculo GCDR", title: "O fluxo atual exige o v\xEDnculo com o GCDR" };
80980
+ if (!d.ingestionId) return { ok: true, cls: "warn", text: "\u26A0 sem v\xEDnculo de dados", title: "Sem ingestionId \u2014 a valida\xE7\xE3o do passo 4 confirma se h\xE1 dados para as telemetrias escolhidas" };
80981
+ return { ok: true };
80982
+ }
80983
+ selectedDevices() {
80984
+ return this.s.selected.map((id) => this.deviceById.get(id)).filter(Boolean);
80985
+ }
80986
+ searchResults() {
80987
+ const q = norm(this.s.search).trim();
80988
+ return this.p.devices.filter((d) => {
80989
+ const cat = this.categoryOf.get(d.tbDeviceId);
80990
+ if (cat === null) return false;
80991
+ if (this.s.category !== "all" && cat !== this.s.category) return false;
80992
+ return !q || norm(`${d.label} ${d.name} ${d.identifier ?? ""} ${d.deviceProfile}`).includes(q);
80993
+ });
80994
+ }
80995
+ estimatePoints() {
80996
+ const buckets = bucketCount(this.s.period.start, this.s.period.end, this.s.resolution);
80997
+ return { buckets, points: buckets * this.s.selected.length * this.s.telemetries.size };
80998
+ }
80999
+ resolutionLabel(resolution) {
81000
+ return resolution ? `Intervalos de ${RESOLUTION_BY_ID[resolution]?.label ?? resolution}` : "\u2014";
81001
+ }
81002
+ demandLabel() {
81003
+ const l = this.s.config?.demandLabel;
81004
+ return typeof l === "string" && l ? l : DEFAULT_DEMAND_LABEL;
81005
+ }
81006
+ stepErrors(step) {
81007
+ if (step === 1) return this.s.selected.length ? [] : ["Adicione ao menos 1 medidor."];
81008
+ if (step === 2) return periodErrors(this.s.period, this.s.resolution, this.now());
81009
+ if (step === 3) return this.s.telemetries.size ? [] : ["Escolha ao menos 1 telemetria."];
81010
+ const errs = [];
81011
+ [1, 2, 3].forEach((n) => {
81012
+ if (this.stepErrors(n).length) errs.push(`Etapa ${n} incompleta.`);
81013
+ });
81014
+ const pts = this.s.snapshot?.request.estimate?.points;
81015
+ if (pts != null && pts > DEMAND_LIMITS.points) {
81016
+ errs.push(`O relat\xF3rio passa do tamanho m\xE1ximo (${fmtN(DEMAND_LIMITS.points)} pontos).`);
81017
+ }
81018
+ const v = this.s.validation;
81019
+ if (v.state === "ok" && v.result && !v.result.ok) {
81020
+ (v.result.errors || []).forEach((e) => errs.push(rejectionText(e.code || "VALIDATION_FAILED", e.reason, e.message, e.details)));
81021
+ }
81022
+ return errs;
81023
+ }
81024
+ view(e) {
81025
+ return deriveJobView({
81026
+ envelope: e,
81027
+ knowledge: this.s.jobs.get(e.jobId),
81028
+ now: this.now(),
81029
+ inflight: this.s.inflight.has(e.jobId),
81030
+ uncertain: this.s.uncertain.has(e.jobId),
81031
+ graceMs: this.graceMs,
81032
+ idempotencyWindowMs: this.windowMs
81033
+ });
81034
+ }
81035
+ freezeSnapshot() {
81036
+ const now = this.now();
81037
+ if (this.s.period.preset !== "custom") {
81038
+ const r = presetRange(this.s.period.preset, now);
81039
+ this.s.period.start = r.start;
81040
+ this.s.period.end = r.end;
81041
+ }
81042
+ this.s.snapshot = {
81043
+ takenAt: now,
81044
+ request: buildRequest({
81045
+ devices: this.selectedDevices(),
81046
+ range: this.s.period,
81047
+ preset: this.s.period.preset,
81048
+ resolution: this.s.resolution,
81049
+ telemetryKeys: this.s.telemetries
81050
+ })
81051
+ };
81052
+ void this.validateSnapshot();
81053
+ }
81054
+ /**
81055
+ * Dry-run no GCDR (reserva nada): erros, avisos, cotas, estimativa e resultado reaproveitável
81056
+ * aparecem ANTES de gravar o envelope (L-10, X-L1, X-L2). Resposta antiga nunca sobrescreve
81057
+ * a validação de um snapshot mais novo.
81058
+ */
81059
+ async validateSnapshot() {
81060
+ const snap = this.s.snapshot;
81061
+ if (!snap) return;
81062
+ const seq = ++this.s.validation.seq;
81063
+ this.s.validation = { ...this.s.validation, state: "loading", result: null, error: "", seq };
81064
+ this.render();
81065
+ try {
81066
+ const result = await this.p.api.validate(
81067
+ toValidateCommand(snap.request, this.p.user, this.s.force ? { force: true } : void 0),
81068
+ this.opts()
81069
+ );
81070
+ if (this.destroyed || seq !== this.s.validation.seq) return;
81071
+ this.s.validation = { ...this.s.validation, state: "ok", result, error: "" };
81072
+ } catch (err) {
81073
+ if (this.destroyed || seq !== this.s.validation.seq) return;
81074
+ const pe = asPortError(err);
81075
+ if (pe.code === "ABORTED") return;
81076
+ this.s.validation = {
81077
+ ...this.s.validation,
81078
+ state: "error",
81079
+ result: null,
81080
+ error: pe.status === 422 || pe.status === 401 || pe.status === 403 ? rejectionText(pe.code, pe.details?.reason, pe.message, pe.details) : pe.message
81081
+ };
81082
+ }
81083
+ this.render();
81084
+ }
81085
+ /* ------------------------------------------------------------------ rendering */
81086
+ render() {
81087
+ if (this.destroyed) return;
81088
+ this.root.querySelectorAll('[data-action="tab"]').forEach((b) => b.setAttribute("aria-selected", String(b.dataset.tab === this.s.tab)));
81089
+ this.el.count.innerHTML = this.s.active.length ? `<span class="dp2-count">${this.s.active.length}</span>` : "";
81090
+ this.el.stepper.hidden = this.s.tab !== "new";
81091
+ this.renderBanner();
81092
+ if (this.s.tab === "jobs") {
81093
+ this.renderJobs();
81094
+ return;
81095
+ }
81096
+ this.renderStepper();
81097
+ [() => this.renderStep1(), () => this.renderStep2(), () => this.renderStep3(), () => this.renderStep4()][this.s.step - 1]();
81098
+ this.renderNav();
81099
+ }
81100
+ renderBanner() {
81101
+ if (this.s.tab !== "new") {
81102
+ this.el.banner.innerHTML = "";
81103
+ return;
81104
+ }
81105
+ const q = this.s.validation.result?.quotas;
81106
+ const full = q ? ["user", "customer", "day"].filter((k) => q[k] && q[k].max > 0 && q[k].current >= q[k].max) : [];
81107
+ if (full.length && q) {
81108
+ const txt = { user: "seus relat\xF3rios em andamento", customer: "relat\xF3rios em andamento do cliente", day: "relat\xF3rios gerados hoje" };
81109
+ this.el.banner.innerHTML = `<div class="dp2-banner">\u23F3 Limite atingido: ${full.map((k) => `${txt[k]} (${q[k].current}/${q[k].max})`).join(" \xB7 ")}.
81110
+ <button class="dp2-btn link" type="button" data-action="tab" data-tab="jobs">Ver relat\xF3rios</button></div>`;
81111
+ return;
81112
+ }
81113
+ if (!this.s.active.length) {
81114
+ this.el.banner.innerHTML = "";
81115
+ return;
81116
+ }
81117
+ this.el.banner.innerHTML = `<div class="dp2-banner info">\u23F3 ${this.s.active.length} relat\xF3rio(s) em andamento.
81118
+ <button class="dp2-btn link" type="button" data-action="tab" data-tab="jobs">Ver relat\xF3rios</button></div>`;
81119
+ }
81120
+ renderStepper() {
81121
+ this.el.stepper.innerHTML = STEPS.map((label, i) => {
81122
+ const n = i + 1;
81123
+ const st = n === this.s.step ? "current" : n <= this.s.maxStep && n < 4 && !this.stepErrors(n).length ? "done" : "todo";
81124
+ return `<button class="dp2-step" type="button" data-action="goto" data-step="${n}" data-state="${st}" ${st === "current" ? 'aria-current="step"' : ""} ${st === "done" ? "" : 'tabindex="-1"'}>
81125
+ <b>${st === "done" ? "\u2713" : n}</b><span>${label}</span></button>`;
81126
+ }).join("");
81127
+ }
81128
+ highlight(text2) {
81129
+ const q = norm(this.s.search).trim();
81130
+ const i = q ? norm(text2).indexOf(q) : -1;
81131
+ if (i < 0) return esc4(text2);
81132
+ return esc4(text2.slice(0, i)) + `<mark class="dp2-mark">${esc4(text2.slice(i, i + q.length))}</mark>` + esc4(text2.slice(i + q.length));
81133
+ }
81134
+ deviceRow(d, mode) {
81135
+ const picked = this.s.selected.includes(d.tbDeviceId);
81136
+ const el2 = this.eligibility(d);
81137
+ const cat = this.categoryOf.get(d.tbDeviceId);
81138
+ const chip = this.chips.find((c) => c.key === cat);
81139
+ const dup = (this.labelCount.get(d.label) || 0) > 1;
81140
+ const action = mode === "result" ? picked ? '<span class="dp2-added">\u2713 Adicionado</span>' : `<button class="dp2-btn sm" type="button" data-action="add" data-id="${esc4(d.tbDeviceId)}" ${el2.ok ? "" : "disabled"} aria-label="Adicionar ${esc4(d.label)}">+ Adicionar</button>` : `<button class="dp2-btn sm" type="button" data-action="remove" data-id="${esc4(d.tbDeviceId)}" aria-label="Remover ${esc4(d.label)}">\u2715 Remover</button>`;
81141
+ return `<div class="dp2-item ${el2.ok ? "" : "blocked"}">
81142
+ <img class="dp2-ico" src="${esc4(deviceIconUrl(d.deviceProfile))}" alt="" loading="lazy">
81143
+ <div style="min-width:0">
81144
+ <div class="dp2-name">${mode === "result" ? this.highlight(d.label) : esc4(d.label)}${dup && d.identifier ? ` <small class="dp2-info">\xB7 ${esc4(d.identifier)}</small>` : ""}</div>
81145
+ <div class="dp2-sub">${chip ? `<span class="dp2-tag">${esc4(chip.label)}</span>` : ""}${el2.text ? `<span class="dp2-tag ${el2.cls}" title="${esc4(el2.title)}">${el2.text}</span>` : ""}
81146
+ ${mode === "result" ? this.highlight(d.name) : esc4(d.name)}${d.identifier ? " \xB7 " + esc4(d.identifier) : ""}</div>
81147
+ </div>
81148
+ <div>${action}</div>
81149
+ </div>`;
81150
+ }
81151
+ renderStep1() {
81152
+ const results = this.searchResults();
81153
+ const visible = this.p.devices.filter((d) => this.categoryOf.get(d.tbDeviceId) !== null).length;
81154
+ const addable = results.filter((d) => !this.s.selected.includes(d.tbDeviceId) && this.eligibility(d).ok).length;
81155
+ const sel = this.selectedDevices();
81156
+ const active = document.activeElement;
81157
+ const hadFocus = active?.dataset?.role === "search";
81158
+ const caret = hadFocus ? active.selectionStart : null;
81159
+ const chips = [{ key: "all", label: "Todos", icon: void 0, count: visible }, ...this.chips];
81160
+ this.el.body.innerHTML = `<div class="dp2-s1">
81161
+ <section class="dp2-col" aria-label="Buscar medidores">
81162
+ <div class="dp2-col-head">
81163
+ <input class="dp2-search" data-role="search" type="search" placeholder="\u{1F50E} Buscar por nome, identificador ou tipo\u2026" value="${esc4(this.s.search)}" aria-label="Buscar medidores">
81164
+ <div class="dp2-chips" role="group" aria-label="Filtrar por tipo">${chips.map((c) => `<button class="dp2-chip" type="button" data-action="category" data-key="${esc4(c.key)}" aria-pressed="${this.s.category === c.key}">${c.icon ? esc4(c.icon) + " " : ""}${esc4(c.label)} \xB7 ${c.count}</button>`).join("")}</div>
81165
+ <h3><span>${fmtN(results.length)} resultado(s)</span>
81166
+ <button class="dp2-btn link" type="button" data-action="add-all" ${addable ? "" : "disabled"}>+ Adicionar todos (${addable})</button></h3>
81167
+ </div>
81168
+ <div class="dp2-list" data-role="results">${results.length ? results.map((d) => this.deviceRow(d, "result")).join("") : `<div class="dp2-empty">${this.p.devices.length ? "Nenhum medidor encontrado para essa busca." : "Nenhum medidor de energia neste cliente."}</div>`}</div>
81169
+ </section>
81170
+ <section class="dp2-col" aria-label="Medidores selecionados">
81171
+ <div class="dp2-col-head"><h3><span>Selecionados \xB7 ${sel.length} de ${DEMAND_LIMITS.devices}</span>
81172
+ <button class="dp2-btn link" type="button" data-action="clear" ${sel.length ? "" : "disabled"}>Limpar lista</button></h3>
81173
+ <div class="dp2-meter"><span style="width:${Math.min(100, sel.length / DEMAND_LIMITS.devices * 100)}%"></span></div></div>
81174
+ <div class="dp2-list" data-role="selected">${sel.length ? sel.map((d) => this.deviceRow(d, "selected")).join("") : '<div class="dp2-empty">Nenhum medidor na lista.<br>Busque \xE0 esquerda e clique em <b>+ Adicionar</b>.</div>'}</div>
81175
+ </section>
81176
+ </div>`;
81177
+ if (hadFocus) {
81178
+ const s = this.el.body.querySelector('[data-role="search"]');
81179
+ s.focus();
81180
+ if (caret !== null) s.setSelectionRange(caret, caret);
81181
+ }
81182
+ }
81183
+ renderStep2() {
81184
+ const { start, end, preset } = this.s.period;
81185
+ const r = RESOLUTION_BY_ID[this.s.resolution];
81186
+ const maxDays = maxPeriodDays(this.s.resolution);
81187
+ const errs = this.stepErrors(2);
81188
+ const valid = Number.isFinite(start) && Number.isFinite(end) && end > start;
81189
+ const tooLong = valid && end - start > maxDays * 864e5;
81190
+ const fit = tooLong ? finestFittingResolution(this.s.period) : null;
81191
+ const misaligned = valid && this.s.resolution === "1d" && !isLocalMidnight(start);
81192
+ const presetTooLong = (id) => {
81193
+ const x = presetRange(id, this.now());
81194
+ return !!x && x.end - x.start > maxDays * 864e5;
81195
+ };
81196
+ const otherErrs = errs.filter((e) => !/período máximo|meia-noite/.test(e));
81197
+ this.el.body.innerHTML = `
81198
+ <div class="dp2-card"><h3 tabindex="-1" data-role="step-title">Per\xEDodo</h3>
81199
+ <div class="dp2-chips" style="margin-bottom:12px">${PRESETS.map((p) => {
81200
+ const off = p.id !== "custom" && presetTooLong(p.id);
81201
+ return `<button class="dp2-chip" type="button" data-action="preset" data-preset="${p.id}" aria-pressed="${preset === p.id}" ${off ? `disabled title="at\xE9 ${maxDays} dia(s) com intervalos de ${esc4(r.label)}"` : ""}>${p.label}</button>`;
81202
+ }).join("")}</div>
81203
+ <div class="dp2-row">
81204
+ <label class="dp2-field">In\xEDcio<input type="datetime-local" data-role="start" min="${MIN_PERIOD_START}T00:00" value="${Number.isFinite(start) ? toLocalDateTimeInput(start) : ""}"></label>
81205
+ <label class="dp2-field">Fim (exclusivo)<input type="datetime-local" data-role="end" value="${Number.isFinite(end) ? toLocalDateTimeInput(end) : ""}"></label>
81206
+ <div class="dp2-info">${valid ? `Dura\xE7\xE3o: <b>${fmtDuration(end - start)}</b> \xB7 hor\xE1rio de Bras\xEDlia` : ""}</div>
81207
+ </div></div>
81208
+ <div class="dp2-card"><h3>Resolu\xE7\xE3o</h3>
81209
+ <div class="dp2-chips" role="group" aria-label="Resolu\xE7\xE3o">${RESOLUTIONS.map((x) => `<button class="dp2-chip" type="button" data-action="resolution" data-res="${x.id}" aria-pressed="${this.s.resolution === x.id}">${x.label} <small>(at\xE9 ${x.maxDays} d)</small></button>`).join("")}</div>
81210
+ <p class="dp2-info" style="margin:10px 0 0">Cada linha do relat\xF3rio \xE9 um intervalo com <b>${esc4(BUCKET_STATS_LABEL)}</b> de cada telemetria. Os intervalos contam a partir do in\xEDcio do per\xEDodo${this.s.resolution === "1d" ? " (com 1 dia, cada linha \xE9 um dia do calend\xE1rio)" : ""}.
81211
+ ${this.s.telemetries.has("consumption") ? `Com <b>Pot\xEAncia Total</b>, o arquivo traz tamb\xE9m a <b>${esc4(this.demandLabel())}</b>, em janelas de 15 min alinhadas ao rel\xF3gio.` : ""}</p>
81212
+ ${valid && !tooLong ? `<p class="dp2-info" style="margin:6px 0 0">${fmtN(bucketCount(start, end, this.s.resolution))} intervalo(s) por medidor.</p>` : ""}
81213
+ ${tooLong ? `<div class="dp2-fix"><span class="dp2-err">O per\xEDodo \xE9 longo demais para intervalos de ${esc4(r.label)} (m\xE1ximo ${maxDays} dia(s)).</span>
81214
+ ${fit ? `<button class="dp2-btn sm primary" type="button" data-action="resolution" data-res="${fit}">Usar ${esc4(RESOLUTION_BY_ID[fit].label)}</button>` : ""}
81215
+ <button class="dp2-btn sm" type="button" data-action="shorten">Encurtar para os \xFAltimos ${maxDays} dia(s)</button></div>` : ""}
81216
+ ${misaligned ? `<div class="dp2-fix"><span class="dp2-err">Com intervalos de 1 dia, o in\xEDcio precisa ser \xE0 meia-noite.</span>
81217
+ <button class="dp2-btn sm primary" type="button" data-action="snap-midnight">Come\xE7ar em ${esc4(formatLocalDateTime(localMidnightOf(start)))}</button></div>` : ""}
81218
+ </div>
81219
+ ${otherErrs.length ? `<p class="dp2-err">${otherErrs.map(esc4).join("<br>")}</p>` : ""}`;
81220
+ }
81221
+ renderStep3() {
81222
+ this.el.body.innerHTML = `<div class="dp2-tgrid">${TELEMETRY_GROUPS.map((g) => {
81223
+ const all = g.items.every((t) => this.s.telemetries.has(t.key));
81224
+ return `<div class="dp2-card"><h3><span>${g.icon} ${esc4(g.label)}</span>
81225
+ <button class="dp2-btn link" type="button" data-action="group-all" data-gid="${g.id}">${all ? "Desmarcar" : "Marcar"} todos</button></h3>
81226
+ ${g.items.map((t) => `<label class="dp2-check"><input type="checkbox" data-role="tel" data-key="${t.key}" ${this.s.telemetries.has(t.key) ? "checked" : ""}>
81227
+ ${esc4(t.label)}<span class="u">${this.p.supportMode ? esc4(t.key) + " \xB7 " : ""}${esc4(t.unit)}</span></label>`).join("")}
81228
+ </div>`;
81229
+ }).join("")}</div>
81230
+ <p class="dp2-info">${this.s.telemetries.size} de ${TELEMETRY_CATALOG.length} telemetrias. Mesmo cat\xE1logo da modal "Telemetrias Instant\xE2neas e Pico de Demanda".</p>`;
81231
+ }
81232
+ renderStep4() {
81233
+ const snap = this.s.snapshot;
81234
+ const req = snap.request;
81235
+ const v = this.s.validation;
81236
+ const vr = v.result;
81237
+ const est = vr?.estimate || req.estimate || { buckets: 0, points: 0 };
81238
+ const errs = this.stepErrors(4);
81239
+ const pts = est.points;
81240
+ const [cls, txt] = pts < 5e4 ? ["pequeno", "pequeno"] : pts < 5e5 ? ["medio", "m\xE9dio"] : ["grande", "grande"];
81241
+ const start = Date.parse(req.period.start);
81242
+ const end = Date.parse(req.period.end);
81243
+ const labelOf = (d) => `${d.label || d.identifier || d.gcdrDeviceId}${d.label && (this.labelCount.get(d.label) || 0) > 1 && d.identifier ? " \xB7 " + d.identifier : ""}`;
81244
+ let validation = "";
81245
+ if (v.state === "loading") validation = '<div class="dp2-info">\u23F3 Validando o pedido no servidor\u2026</div>';
81246
+ else if (v.state === "error") {
81247
+ validation = `<div class="dp2-err">N\xE3o foi poss\xEDvel validar agora: ${esc4(v.error)}
81248
+ <button class="dp2-btn link" type="button" data-action="revalidate">Tentar de novo</button></div>`;
81249
+ } else if (vr) {
81250
+ const warns = (vr.warnings || []).map((w) => `<li>${esc4(w.message || w.code || "aviso")}</li>`).join("");
81251
+ const reuse = vr.reusable ? `<div class="dp2-banner info" style="margin-top:8px">\u267B J\xE1 existe um relat\xF3rio id\xEAntico${vr.reusable.generatedAt ? ` gerado em ${esc4(formatLocalDateTime(Date.parse(vr.reusable.generatedAt)))}` : ""}${vr.reusable.generatedBy?.userId ? ` por ${esc4(vr.reusable.generatedBy.userId)}` : ""}. Confirmar reaproveita o arquivo (sem custo de cota).
81252
+ <label class="dp2-check" style="margin-top:6px"><input type="checkbox" data-role="force" ${this.s.force ? "checked" : ""}> Gerar novamente mesmo assim</label></div>` : "";
81253
+ validation = `${vr.ok ? '<div class="dp2-info">\u2713 Pedido validado no servidor.</div>' : ""}
81254
+ ${warns ? `<ul class="dp2-info" style="margin:6px 0 0;padding-left:18px">${warns}</ul>` : ""}${reuse}`;
81255
+ }
81256
+ this.el.body.innerHTML = `
81257
+ <div class="dp2-card"><h3 tabindex="-1" data-role="step-title">Resumo do pedido</h3>
81258
+ <dl class="dp2-sum">
81259
+ <dt>Solicitante</dt><dd>${esc4(this.p.user.name)}</dd>
81260
+ <dt>Medidores (${req.devices.length})</dt><dd><div class="dp2-pills">${req.devices.map((d) => `<span class="dp2-pill">${esc4(labelOf(d))}</span>`).join("")}</div></dd>
81261
+ <dt>Per\xEDodo</dt><dd>${formatLocalDateTime(start)} \u2192 ${formatLocalDateTime(end)} <span class="dp2-info">(${fmtDuration(end - start)}, hor\xE1rio de Bras\xEDlia${req.period.preset === "today" || req.period.preset === "thisMonth" ? `, calculado \xE0s ${formatLocalDateTime(snap.takenAt).slice(11)}` : ""})</span></dd>
81262
+ <dt>Resolu\xE7\xE3o</dt><dd>${esc4(this.resolutionLabel(req.resolution))} <span class="dp2-info">(${esc4(BUCKET_STATS_LABEL)})</span></dd>
81263
+ <dt>Telemetrias (${req.telemetries.length})</dt><dd><div class="dp2-pills">${req.telemetries.map((t) => `<span class="dp2-pill">${esc4(t.label)} \xB7 ${esc4(t.unit)}</span>`).join("")}</div>
81264
+ ${req.telemetries.some((t) => t.key === "consumption") ? `<div class="dp2-info" style="margin-top:4px">Inclui a ${esc4(this.demandLabel())}.</div>` : ""}</dd>
81265
+ <dt>Tamanho</dt><dd><span class="dp2-size ${cls}">${txt}</span> <span class="dp2-info">\u2248 ${fmtN(pts)} pontos</span>
81266
+ <details class="dp2-details"><summary>Detalhes</summary><span class="dp2-info">${fmtN(est.buckets)} intervalos \xD7 ${req.devices.length} medidores \xD7 ${req.telemetries.length} telemetrias \xB7 limite ${fmtN(DEMAND_LIMITS.points)}</span></details></dd>
81267
+ <dt>Entrega</dt><dd>Arquivos XLSX, CSV e JSON <span class="dp2-info">(dispon\xEDveis por 3 dias; o resumo fica por 1 ano)</span></dd>
81268
+ </dl>
81269
+ <div style="margin-top:12px">${validation}</div>
81270
+ <p class="dp2-info" style="margin:12px 0 0">Seu pedido fica registrado (quem, quando, o qu\xEA) e \xE9 processado em segundo plano. <b>Voc\xEA pode fechar este painel</b> e acompanhar em Relat\xF3rios.</p>
81271
+ </div>
81272
+ ${this.p.supportMode ? `<div class="dp2-card"><h3>Pedido que ser\xE1 registrado (suporte)</h3><pre class="dp2-json">${esc4(JSON.stringify(req, null, 2))}</pre></div>` : ""}
81273
+ ${errs.length ? `<p class="dp2-err">${errs.map(esc4).join("<br>")}</p>` : ""}`;
81274
+ }
81275
+ renderNav() {
81276
+ const errs = this.stepErrors(this.s.step);
81277
+ const hints = ["", `${this.s.selected.length} medidor(es) na lista`, "Escolha o per\xEDodo e a resolu\xE7\xE3o", `${this.s.telemetries.size} telemetria(s)`, "Revise e confirme"];
81278
+ this.el.nav.innerHTML = `
81279
+ <button class="dp2-btn" type="button" data-action="back" ${this.s.step === 1 ? "disabled" : ""}>\u2190 Voltar</button>
81280
+ <span class="hint">${esc4(hints[this.s.step])}</span>
81281
+ ${this.s.step === 4 ? `<button class="dp2-btn primary" type="button" data-action="confirm" ${errs.length || this.s.submitting || this.s.validation.state !== "ok" ? "disabled" : ""}>${this.s.submitting ? "Enviando\u2026" : "\u2713 Confirmar e enviar"}</button>` : `<button class="dp2-btn primary" type="button" data-action="next" ${errs.length ? "disabled" : ""}>Pr\xF3ximo \u2192</button>`}`;
81282
+ }
81283
+ jobTitle(e) {
81284
+ const r = e.request;
81285
+ return `${r.devices.length} medidor(es) \xB7 ${fmtDay(Date.parse(r.period.start))}\u2013${fmtDay(Date.parse(r.period.end) - 1)} \xB7 ${this.resolutionLabel(r.resolution)}`;
81286
+ }
81287
+ renderJobs() {
81288
+ const s = this.s;
81289
+ this.el.nav.innerHTML = `<span class="hint">${s.envelopes.length} de ${s.keys.length} pedido(s) registrados${s.unreadable ? ` \xB7 ${s.unreadable} ileg\xEDvel(is)` : ""}</span>
81290
+ ${s.envelopes.length + s.unreadable < s.keys.length ? '<button class="dp2-btn" type="button" data-action="more">Carregar mais</button>' : ""}
81291
+ <button class="dp2-btn" type="button" data-action="reload">\u27F3 Atualizar</button>
81292
+ <button class="dp2-btn primary" type="button" data-action="tab" data-tab="new">+ Novo relat\xF3rio</button>`;
81293
+ if (s.listState === "loading" && !s.envelopes.length) {
81294
+ this.el.body.innerHTML = '<div class="dp2-skel"></div><div class="dp2-skel"></div><div class="dp2-skel"></div>';
81295
+ return;
81296
+ }
81297
+ if (s.listState === "error") {
81298
+ this.el.body.innerHTML = `<div class="dp2-empty">N\xE3o foi poss\xEDvel carregar os relat\xF3rios (${esc4(s.listError)}).<br><button class="dp2-btn" type="button" data-action="reload">Tentar de novo</button></div>`;
81299
+ return;
81300
+ }
81301
+ const orphans = this.p.supportMode ? s.active.filter((j) => !s.keys.includes(envelopeKey(j.jobId))) : [];
81302
+ if (!s.envelopes.length && !orphans.length) {
81303
+ this.el.body.innerHTML = '<div class="dp2-empty">Nenhum relat\xF3rio ainda. Crie um em <b>Novo relat\xF3rio</b>.</div>';
81304
+ return;
81305
+ }
81306
+ const when = (iso) => iso ? formatLocalDateTime(Date.parse(iso)) : "";
81307
+ const cards = s.envelopes.map((e) => {
81308
+ const v = this.view(e);
81309
+ const st = v.state;
81310
+ const j = v.job;
81311
+ const progress = Math.max(0, Math.min(100, Math.round(j?.progress ?? 0)));
81312
+ const busy = s.inflight.has(e.jobId) ? "disabled" : "";
81313
+ const note = (cls, text2) => `<div class="${cls}" style="margin-top:6px">${text2}</div>`;
81314
+ const queue = st === "queued" && j?.queue?.position ? ` (N\xBA ${j.queue.position})` : "";
81315
+ const peak = j?.totals?.largestDevicePeak;
81316
+ const peakTxt = peak && (peak.kw != null || peak.w != null) ? `Maior pico individual: <b>${esc4(fmtN(Number(peak.kw ?? peak.w / 1e3)))} kW</b>${peak.label ? ` \xB7 ${esc4(peak.label)}` : ""}${peak.at ? ` \xB7 ${esc4(when(peak.at))}` : ""}` : "";
81317
+ return `<article class="dp2-job" aria-label="${esc4(this.jobTitle(e))}">
81318
+ <div class="dp2-job-top">
81319
+ <span class="dp2-job-title">${esc4(this.jobTitle(e))}</span>
81320
+ <span class="dp2-status ${st}">${JOB_STATE_LABEL[st]}${queue}${st === "running" ? ` ${progress}%` : ""}</span>
81321
+ <span style="flex:1"></span>
81322
+ <span class="dp2-info">${esc4(e.requestedBy.name)} \xB7 ${when(e.requestedAt)}</span>
81323
+ </div>
81324
+ ${st === "queued" || st === "running" ? `<div class="dp2-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${progress}"><span style="width:${progress}%"></span></div>` : ""}
81325
+ ${st === "queued" && j?.queue?.estimatedStartAt ? note("dp2-info", `In\xEDcio estimado: ${esc4(when(j.queue.estimatedStartAt))}`) : ""}
81326
+ ${st === "failed" ? note("dp2-err", `${esc4(failureText(j?.error?.code))}${this.p.supportMode && j?.error ? ` <small>(${esc4(j.error.code)}: ${esc4(j.error.message)})</small>` : ""}`) : ""}
81327
+ ${st === "rejected" ? note("dp2-err", `${esc4(rejectionText(v.code, v.reason, v.message))}${this.p.supportMode ? ` <small>(${esc4(v.code)}${v.reason ? " \xB7 " + esc4(v.reason) : ""})</small>` : ""}`) : ""}
81328
+ ${st === "not_received" ? note("dp2-err", "O pedido est\xE1 registrado, mas o processamento n\xE3o o recebeu. Reenviar \xE9 seguro: n\xE3o gera relat\xF3rio duplicado.") : ""}
81329
+ ${st === "query_unavailable" ? note("dp2-info", "N\xE3o foi poss\xEDvel consultar o andamento agora \u2014 tentaremos de novo.") : ""}
81330
+ ${st === "old_request" ? note("dp2-info", 'Pedido antigo, sem registro de processamento. Use "Duplicar e editar".') : ""}
81331
+ ${st === "result_expired" ? note("dp2-info", "O relat\xF3rio foi conclu\xEDdo, mas os arquivos j\xE1 expiraram (ficam 3 dias).") : ""}
81332
+ ${st === "purged" ? note("dp2-info", "O conte\xFAdo deste relat\xF3rio foi removido.") : ""}
81333
+ ${st === "succeeded" && j?.reused ? note("dp2-info", `\u267B Resultado reaproveitado${j.generatedAt ? ` \u2014 gerado em ${esc4(when(j.generatedAt))}` : ""}${j.generatedBy?.userId ? ` por ${esc4(j.generatedBy.userId)}` : ""}.`) : ""}
81334
+ ${st === "succeeded" && peakTxt ? note("dp2-info", peakTxt) : ""}
81335
+ ${st === "succeeded" && j?.expiresAt ? note("dp2-info", `Arquivos dispon\xEDveis at\xE9 ${esc4(when(j.expiresAt))}.`) : ""}
81336
+ ${s.auditPending.has(e.jobId) ? '<div class="dp2-audit-warn">\u26A0 A a\xE7\xE3o foi aceita, mas o registro de auditoria falhou \u2014 ser\xE1 reconciliado.</div>' : ""}
81337
+ <div class="dp2-job-actions">
81338
+ ${st === "succeeded" ? `<button class="dp2-btn sm primary" type="button" data-action="download" data-kind="xlsx" data-job="${esc4(e.jobId)}">\u2B07 XLSX</button>
81339
+ <button class="dp2-btn sm" type="button" data-action="download" data-kind="csv" data-job="${esc4(e.jobId)}">CSV</button>
81340
+ <button class="dp2-btn sm" type="button" data-action="download" data-kind="json" data-job="${esc4(e.jobId)}">JSON</button>` : ""}
81341
+ ${st === "queued" || st === "running" ? `<button class="dp2-btn sm" type="button" data-action="cancel" data-job="${esc4(e.jobId)}" ${busy}>\u2715 Cancelar</button>` : ""}
81342
+ ${v.canResend ? `<button class="dp2-btn sm primary" type="button" data-action="resend" data-job="${esc4(e.jobId)}" ${busy}>\u21BB Reenviar</button>` : ""}
81343
+ ${["failed", "cancelled", "succeeded", "result_expired", "purged", "old_request", "rejected"].includes(st) ? `<button class="dp2-btn sm" type="button" data-action="clone" data-job="${esc4(e.jobId)}">\u29C9 Duplicar e editar</button>` : ""}
81344
+ ${this.p.supportMode ? `<button class="dp2-btn link" type="button" data-action="details" data-job="${esc4(e.jobId)}">${s.openDetails.has(e.jobId) ? "Ocultar" : "Detalhes /"} auditoria</button>` : ""}
81345
+ </div>
81346
+ ${this.p.supportMode && s.openDetails.has(e.jobId) ? `<pre class="dp2-json">${esc4(JSON.stringify(e, null, 2))}</pre>` : ""}
81347
+ </article>`;
81348
+ }).join("");
81349
+ const orphanCards = orphans.map((j) => `<article class="dp2-job ghost"><div class="dp2-job-top">
81350
+ <span class="dp2-job-title">Job sem registro no ThingsBoard (O-6)</span><span class="dp2-status ${j.status}">${esc4(JOB_STATE_LABEL[j.status] ?? j.status)}</span>
81351
+ <span style="flex:1"></span><span class="dp2-info">${esc4(j.requestedBy?.userId ?? "")} \xB7 ${when(j.createdAt)}</span></div>
81352
+ <div class="dp2-info" style="margin-top:4px"><code>${esc4(j.jobId)}</code></div></article>`).join("");
81353
+ this.el.body.innerHTML = cards + orphanCards;
81354
+ }
81355
+ /* ------------------------------------------------------------------ data flows */
81356
+ opts() {
81357
+ return { signal: this.abort.signal };
81358
+ }
81359
+ async loadConfig() {
81360
+ try {
81361
+ this.s.config = await this.p.api.config(this.opts());
81362
+ if (!this.destroyed) this.render();
81363
+ } catch {
81364
+ }
81365
+ }
81366
+ async refreshActive() {
81367
+ try {
81368
+ this.s.active = await this.p.api.active(this.opts());
81369
+ } catch {
81370
+ }
81371
+ }
81372
+ async queryJobs(ids) {
81373
+ if (!ids.length) return;
81374
+ try {
81375
+ mergeBatch(this.s.jobs, await this.p.api.batchGet(ids, this.opts()));
81376
+ } catch (err) {
81377
+ const code = asPortError(err).code;
81378
+ ids.forEach((id) => this.s.jobs.set(id, { queryError: code }));
81379
+ }
81380
+ }
81381
+ async loadPage(reset) {
81382
+ if (this.destroyed) return;
81383
+ this.s.listState = "loading";
81384
+ if (reset) {
81385
+ this.s.page = 1;
81386
+ this.render();
81387
+ }
81388
+ try {
81389
+ this.s.keys = (await this.p.store.keys(this.opts())).filter((k) => k.startsWith(JOB_KEY_PREFIX)).sort().reverse();
81390
+ const keys = this.s.keys.slice(0, this.s.page * this.pageSize);
81391
+ const rows = keys.length ? await this.p.store.get(keys, this.opts()) : [];
81392
+ const parsed = rows.map((r) => parseEnvelope(r.value));
81393
+ this.s.envelopes = parsed.filter((x) => x.ok).map((x) => x.envelope);
81394
+ this.s.unreadable = parsed.length - this.s.envelopes.length;
81395
+ this.s.listState = "ok";
81396
+ } catch (err) {
81397
+ if (this.destroyed) return;
81398
+ this.s.listState = "error";
81399
+ this.s.listError = asPortError(err).message;
81400
+ this.render();
81401
+ return;
81402
+ }
81403
+ if (this.destroyed) return;
81404
+ await Promise.all([this.queryJobs(this.s.envelopes.map((e) => e.jobId)), this.refreshActive()]);
81405
+ if (this.destroyed) return;
81406
+ await this.reconcile();
81407
+ this.render();
81408
+ this.schedulePoll(true);
81409
+ }
81410
+ async reconcile() {
81411
+ for (const e of this.s.envelopes) {
81412
+ const k = this.s.jobs.get(e.jobId);
81413
+ if (!k || !("found" in k) || !k.found) continue;
81414
+ for (const ev of reconciliationEvents(e, k.job)) {
81415
+ if (!await this.appendAudit(e, ev)) break;
81416
+ }
81417
+ }
81418
+ }
81419
+ async appendAudit(e, ev) {
81420
+ try {
81421
+ Object.assign(e, await this.p.store.appendAudit(envelopeKey(e.jobId), ev, this.opts()));
81422
+ this.s.auditPending.delete(e.jobId);
81423
+ return true;
81424
+ } catch {
81425
+ this.s.auditPending.add(e.jobId);
81426
+ return false;
81427
+ }
81428
+ }
81429
+ /** Uncertain TB write: read back, reuse when it matches; never overwrite appended audit. */
81430
+ async putEnvelope(env) {
81431
+ const key = envelopeKey(env.jobId);
81432
+ try {
81433
+ await this.p.store.put(key, env, this.opts());
81434
+ return;
81435
+ } catch (err) {
81436
+ if (asPortError(err).code === "ABORTED") throw err;
81437
+ }
81438
+ const [found] = await this.p.store.get([key], this.opts());
81439
+ const parsed = found ? parseEnvelope(found.value) : null;
81440
+ if (parsed && parsed.ok && parsed.envelope.requestHash === env.requestHash) return;
81441
+ if (found) throw new Error("chave existente com outro conte\xFAdo");
81442
+ await this.p.store.put(key, env, this.opts());
81443
+ }
81444
+ /** One automatic retry on an uncertain outcome (no response / 5xx) and one on RATE_LIMITED, same jobId. */
81445
+ async send(env) {
81446
+ const cmd = toSubmitCommand(env);
81447
+ let last = { status: 0, code: "ERROR", message: "" };
81448
+ for (let attempt = 0; attempt < 2; attempt++) {
81449
+ try {
81450
+ return { outcome: "accepted", job: await this.p.api.submit(cmd, this.opts()) };
81451
+ } catch (e) {
81452
+ last = asPortError(e);
81453
+ if (last.code === "ABORTED") return { outcome: "uncertain", err: last };
81454
+ if (last.code === "RATE_LIMITED" && attempt === 0) {
81455
+ await new Promise((r) => setTimeout(r, Math.min(10, last.retryAfter ?? 2) * 1e3));
81456
+ continue;
81457
+ }
81458
+ if (!isUncertainStatus(last.status)) return { outcome: "rejected", err: last };
81459
+ }
81460
+ }
81461
+ return { outcome: "uncertain", err: last };
81462
+ }
81463
+ async applySend(e, op, res) {
81464
+ const by = this.p.user.tbUserId;
81465
+ if (res.outcome === "accepted") {
81466
+ this.s.uncertain.delete(e.jobId);
81467
+ this.s.jobs.set(e.jobId, { found: true, job: res.job });
81468
+ this.render();
81469
+ const reused = !!res.job.reused;
81470
+ const ok = await this.appendAudit(e, reused ? auditEvent("reused", "accepted", by, this.now(), { executionId: res.job.executionId || void 0 }) : auditEvent(op, "accepted", by, this.now()));
81471
+ return ok ? reused ? "reused" : "ok" : "audit_failed";
81472
+ }
81473
+ if (res.outcome === "rejected") {
81474
+ this.s.uncertain.delete(e.jobId);
81475
+ this.s.jobs.set(e.jobId, { found: false });
81476
+ const reason = res.err.details?.reason;
81477
+ await this.appendAudit(e, auditEvent(op, "rejected", by, this.now(), { code: res.err.code, reason, httpStatus: res.err.status, message: res.err.message }));
81478
+ return "rejected";
81479
+ }
81480
+ this.s.uncertain.add(e.jobId);
81481
+ this.s.jobs.set(e.jobId, { found: false });
81482
+ return "uncertain";
81483
+ }
81484
+ rejectionOf(res) {
81485
+ if (res.outcome === "accepted") return "";
81486
+ return rejectionText(res.err.code, res.err.details?.reason, res.err.message, res.err.details);
81487
+ }
81488
+ async confirm() {
81489
+ if (this.stepErrors(4).length || this.s.submitting || !this.s.snapshot || this.s.validation.state !== "ok") return;
81490
+ this.s.submitting = true;
81491
+ this.render();
81492
+ let env;
81493
+ try {
81494
+ env = await buildEnvelope({
81495
+ request: this.s.snapshot.request,
81496
+ now: this.now(),
81497
+ user: this.p.user,
81498
+ customer: this.p.customer,
81499
+ options: this.s.force ? { force: true } : {},
81500
+ idGen: this.p.idGen,
81501
+ sha256: this.p.sha256
81502
+ });
81503
+ await this.putEnvelope(env);
81504
+ } catch (err) {
81505
+ this.s.submitting = false;
81506
+ if (!this.destroyed) {
81507
+ this.toast("error", `N\xE3o foi poss\xEDvel registrar o pedido: ${asPortError(err).message}. Nada foi enviado.`);
81508
+ this.render();
81509
+ }
81510
+ return;
81511
+ }
81512
+ this.s.inflight.add(env.jobId);
81513
+ this.s.envelopes.unshift(env);
81514
+ this.s.keys.unshift(envelopeKey(env.jobId));
81515
+ this.resetWizard();
81516
+ this.s.tab = "jobs";
81517
+ this.render();
81518
+ const res = await this.send(env);
81519
+ this.s.inflight.delete(env.jobId);
81520
+ if (this.destroyed) return;
81521
+ const outcome = await this.applySend(env, "submit", res);
81522
+ this.s.submitting = false;
81523
+ await this.refreshActive();
81524
+ const msg = {
81525
+ ok: ["success", "Pedido enviado. Acompanhe em Relat\xF3rios."],
81526
+ reused: ["success", "Relat\xF3rio id\xEAntico reaproveitado \u2014 j\xE1 pode baixar."],
81527
+ audit_failed: ["warning", "Pedido enviado. O registro de auditoria falhou e ser\xE1 reconciliado."],
81528
+ rejected: ["error", `Pedido recusado: ${this.rejectionOf(res)}`],
81529
+ uncertain: ["info", "Pedido registrado; confirmando o recebimento\u2026"]
81530
+ }[outcome];
81531
+ this.toast(msg[0], msg[1]);
81532
+ this.announce("Pedido registrado.");
81533
+ this.render();
81534
+ this.schedulePoll(true);
81535
+ }
81536
+ /** Resend = replay of the stored jobId/body/options, after re-validating (L-19). */
81537
+ async resend(jobId) {
81538
+ const e = this.s.envelopes.find((x) => x.jobId === jobId);
81539
+ if (!e) return;
81540
+ this.s.inflight.add(jobId);
81541
+ this.render();
81542
+ try {
81543
+ const v = await this.p.api.validate(toValidateCommand(e.request, this.p.user, e.options), this.opts());
81544
+ if (!v.ok) {
81545
+ this.s.inflight.delete(jobId);
81546
+ const first = v.errors?.[0];
81547
+ this.toast("error", `N\xE3o reenviado: ${rejectionText(first?.code || "VALIDATION_FAILED", first?.reason, first?.message, first?.details)}`);
81548
+ this.render();
81549
+ return;
81550
+ }
81551
+ } catch {
81552
+ }
81553
+ const res = await this.send(e);
81554
+ this.s.inflight.delete(jobId);
81555
+ if (this.destroyed) return;
81556
+ const outcome = await this.applySend(e, "resend", res);
81557
+ await this.refreshActive();
81558
+ if (outcome === "ok" || outcome === "reused") this.toast("success", "Reenviado.");
81559
+ else if (outcome === "audit_failed") this.toast("warning", "Reenviado. O registro de auditoria falhou e ser\xE1 reconciliado.");
81560
+ else if (outcome === "rejected") this.toast("error", `Recusado: ${this.rejectionOf(res)}`);
81561
+ else this.toast("info", "Ainda sem confirma\xE7\xE3o \u2014 tentaremos de novo.");
81562
+ this.render();
81563
+ this.schedulePoll(true);
81564
+ }
81565
+ async cancelJob(jobId) {
81566
+ const e = this.s.envelopes.find((x) => x.jobId === jobId);
81567
+ if (!e) return;
81568
+ if (!await this.confirmDialog("Cancelar relat\xF3rio?", `${this.jobTitle(e)}
81569
+ O cancelamento fica registrado na auditoria.`, "Sim, cancelar", "Manter")) return;
81570
+ let job;
81571
+ try {
81572
+ job = await this.p.api.cancel(jobId, { tbUserId: this.p.user.tbUserId }, this.opts());
81573
+ } catch (err) {
81574
+ const pe = asPortError(err);
81575
+ if (this.destroyed) return;
81576
+ if (pe.code === "ALREADY_FINISHED") {
81577
+ this.toast("info", pe.details?.reused ? "Resultado reaproveitado \u2014 n\xE3o pode ser cancelado." : "O relat\xF3rio j\xE1 terminou.");
81578
+ } else {
81579
+ if (pe.status !== 0) await this.appendAudit(e, auditEvent("cancel", "rejected", this.p.user.tbUserId, this.now(), { code: pe.code, httpStatus: pe.status }));
81580
+ this.toast("error", `N\xE3o foi poss\xEDvel cancelar: ${pe.message}`);
81581
+ }
81582
+ await this.queryJobs([jobId]);
81583
+ this.render();
81584
+ return;
81585
+ }
81586
+ this.s.jobs.set(jobId, { found: true, job });
81587
+ await this.refreshActive();
81588
+ this.render();
81589
+ const ok = await this.appendAudit(e, auditEvent("cancel", "accepted", this.p.user.tbUserId, this.now()));
81590
+ this.toast(ok ? "success" : "warning", ok ? "Relat\xF3rio cancelado." : "Relat\xF3rio cancelado. O registro de auditoria falhou e ser\xE1 reconciliado.");
81591
+ this.announce("Relat\xF3rio cancelado.");
81592
+ this.render();
81593
+ }
81594
+ /** Presigned URL (5 min) per file; the browser downloads directly from storage. */
81595
+ async download(jobId, kind) {
81596
+ try {
81597
+ const desc = await this.p.api.download(jobId, this.opts());
81598
+ const file = (desc.files || []).find((f) => f.kind === kind);
81599
+ if (!file?.url) {
81600
+ this.toast("error", `Arquivo ${kind.toUpperCase()} indispon\xEDvel.`);
81601
+ return;
81602
+ }
81603
+ const a = document.createElement("a");
81604
+ a.href = file.url;
81605
+ a.rel = "noopener";
81606
+ a.download = file.filename || "";
81607
+ document.body.appendChild(a);
81608
+ a.click();
81609
+ a.remove();
81610
+ } catch (err) {
81611
+ const pe = asPortError(err);
81612
+ const msg = {
81613
+ RESULT_NOT_READY: "O arquivo ainda n\xE3o est\xE1 pronto.",
81614
+ RESULT_UNAVAILABLE: "Este relat\xF3rio n\xE3o tem arquivo dispon\xEDvel.",
81615
+ RESULT_EXPIRED: "Os arquivos expiraram (ficam 3 dias).",
81616
+ RESULT_PURGED: "O conte\xFAdo deste relat\xF3rio foi removido."
81617
+ };
81618
+ this.toast(pe.code === "RESULT_NOT_READY" ? "info" : "error", msg[pe.code] || pe.message);
81619
+ await this.queryJobs([jobId]);
81620
+ this.render();
81621
+ }
81622
+ }
81623
+ /* ------------------------------------------------------------------ polling */
81624
+ pollIds() {
81625
+ return this.s.envelopes.filter((e) => needsPolling(this.view(e).state)).map((e) => e.jobId);
81626
+ }
81627
+ schedulePoll(reset) {
81628
+ if (this.destroyed) return;
81629
+ if (reset) this.pollDelay = this.p.pollMinMs ?? 5e3;
81630
+ if (this.pollTimer) clearTimeout(this.pollTimer);
81631
+ this.pollTimer = null;
81632
+ if (!this.pollIds().length && !this.s.active.length) return;
81633
+ const graceEnds = this.s.envelopes.filter((e) => {
81634
+ const st = this.view(e).state;
81635
+ return (st === "sending" || st === "send_uncertain") && !this.s.inflight.has(e.jobId);
81636
+ }).map((e) => Date.parse(e.requestedAt) + this.graceMs - this.now() + 100).filter((ms) => ms > 0);
81637
+ this.pollTimer = setTimeout(() => void this.pollTick(), Math.min(this.pollDelay, ...graceEnds));
81638
+ }
81639
+ async pollTick() {
81640
+ this.pollTimer = null;
81641
+ if (this.destroyed) return;
81642
+ if (typeof document !== "undefined" && document.hidden) {
81643
+ this.paused = true;
81644
+ return;
81645
+ }
81646
+ await Promise.all([this.queryJobs(this.pollIds()), this.refreshActive()]);
81647
+ if (this.destroyed) return;
81648
+ await this.reconcile();
81649
+ this.render();
81650
+ this.pollDelay = Math.min(this.pollDelay * 1.5, this.p.pollMaxMs ?? 3e4);
81651
+ this.schedulePoll(false);
81652
+ }
81653
+ onVisibility = () => {
81654
+ if (!document.hidden && this.paused) {
81655
+ this.paused = false;
81656
+ void this.pollTick();
81657
+ }
81658
+ };
81659
+ /* ------------------------------------------------------------------ wizard actions */
81660
+ async addDevices(ids) {
81661
+ const fresh = ids.filter((id) => !this.s.selected.includes(id) && this.deviceById.has(id) && this.eligibility(this.deviceById.get(id)).ok);
81662
+ const room = DEMAND_LIMITS.devices - this.s.selected.length;
81663
+ if (fresh.length > room) {
81664
+ if (room <= 0) {
81665
+ this.toast("warning", `A lista j\xE1 tem ${DEMAND_LIMITS.devices} medidores.`);
81666
+ return;
81667
+ }
81668
+ if (!await this.confirmDialog("Limite de medidores", `Cabem ${room} de ${fresh.length}. Adicionar os ${room} primeiros?`, `Adicionar ${room}`)) return;
81669
+ }
81670
+ const added = fresh.slice(0, Math.max(0, room));
81671
+ this.s.selected.push(...added);
81672
+ if (added.length) {
81673
+ const first = this.deviceById.get(added[0]);
81674
+ this.announce(`${added.length === 1 ? first.label + " adicionado" : added.length + " medidores adicionados"} \xB7 ${this.s.selected.length} de ${DEMAND_LIMITS.devices}`);
81675
+ }
81676
+ }
81677
+ resetWizard() {
81678
+ const r = presetRange("last7days", this.now());
81679
+ Object.assign(this.s, {
81680
+ step: 1,
81681
+ maxStep: 1,
81682
+ search: "",
81683
+ category: "all",
81684
+ selected: [],
81685
+ snapshot: null,
81686
+ resolution: DEFAULT_RESOLUTION,
81687
+ force: false,
81688
+ validation: { state: "idle", result: null, error: "", seq: this.s.validation.seq + 1 },
81689
+ period: { preset: "last7days", start: r.start, end: r.end },
81690
+ telemetries: /* @__PURE__ */ new Set(["consumption"])
81691
+ });
81692
+ }
81693
+ /** A duplicate is a NEW request: relative presets are recomputed (O-12 open). */
81694
+ cloneJob(e) {
81695
+ const req = e.request;
81696
+ this.resetWizard();
81697
+ this.s.selected = req.devices.map((d) => d.tbDeviceId || "").filter((id) => !!id && this.deviceById.has(id) && this.eligibility(this.deviceById.get(id)).ok);
81698
+ this.s.resolution = req.resolution ?? DEFAULT_RESOLUTION;
81699
+ const preset = req.period.preset && req.period.preset !== "custom" ? req.period.preset : null;
81700
+ this.s.period = preset ? { preset, ...presetRange(preset, this.now()) } : { preset: "custom", start: Date.parse(req.period.start), end: Date.parse(req.period.end) };
81701
+ this.s.telemetries = new Set(req.telemetries.map((t) => t.key));
81702
+ this.s.tab = "new";
81703
+ this.s.step = 4;
81704
+ this.s.maxStep = 4;
81705
+ this.freezeSnapshot();
81706
+ }
81707
+ focusStepTitle() {
81708
+ this.el.body.querySelector('[data-role="step-title"]')?.focus();
81709
+ }
81710
+ isDirty() {
81711
+ return this.s.tab === "new" && (this.s.selected.length > 0 || this.s.step > 1);
81712
+ }
81713
+ /* ------------------------------------------------------------------ events */
81714
+ onClick = async (ev) => {
81715
+ const t = ev.target.closest("[data-action]");
81716
+ if (!t || !this.root.contains(t)) return;
81717
+ const a = t.dataset.action;
81718
+ const job = t.dataset.job || "";
81719
+ switch (a) {
81720
+ case "tab":
81721
+ this.s.tab = t.dataset.tab;
81722
+ if (this.s.tab === "jobs") {
81723
+ void this.loadPage(true);
81724
+ return;
81725
+ }
81726
+ break;
81727
+ case "goto":
81728
+ if (t.dataset.state === "done") {
81729
+ this.s.step = Number(t.dataset.step);
81730
+ this.render();
81731
+ this.focusStepTitle();
81732
+ }
81733
+ return;
81734
+ case "next":
81735
+ if (!this.stepErrors(this.s.step).length) {
81736
+ this.s.step++;
81737
+ this.s.maxStep = Math.max(this.s.maxStep, this.s.step);
81738
+ if (this.s.step === 4) this.freezeSnapshot();
81739
+ this.render();
81740
+ this.focusStepTitle();
81741
+ }
81742
+ return;
81743
+ case "back":
81744
+ this.s.step = Math.max(1, this.s.step - 1);
81745
+ this.render();
81746
+ this.focusStepTitle();
81747
+ return;
81748
+ case "add":
81749
+ case "remove": {
81750
+ const sel = `[data-role="${a === "add" ? "results" : "selected"}"] [data-action="${a}"]`;
81751
+ const idx = Array.from(this.el.body.querySelectorAll(sel)).indexOf(t);
81752
+ if (a === "add") await this.addDevices([t.dataset.id]);
81753
+ else {
81754
+ const d = this.deviceById.get(t.dataset.id);
81755
+ this.s.selected = this.s.selected.filter((id) => id !== t.dataset.id);
81756
+ if (d) this.announce(`${d.label} removido \xB7 ${this.s.selected.length} de ${DEMAND_LIMITS.devices}`);
81757
+ }
81758
+ this.render();
81759
+ const next = Array.from(this.el.body.querySelectorAll(sel));
81760
+ (next[idx] || next[idx - 1] || this.el.body.querySelector('[data-role="search"]'))?.focus();
81761
+ return;
81762
+ }
81763
+ case "add-all":
81764
+ await this.addDevices(this.searchResults().map((d) => d.tbDeviceId));
81765
+ break;
81766
+ case "clear":
81767
+ this.s.selected = [];
81768
+ break;
81769
+ case "category":
81770
+ this.s.category = t.dataset.key || "all";
81771
+ break;
81772
+ case "preset": {
81773
+ this.s.period.preset = t.dataset.preset;
81774
+ const r = presetRange(this.s.period.preset, this.now());
81775
+ if (r) Object.assign(this.s.period, r);
81776
+ break;
81777
+ }
81778
+ case "resolution":
81779
+ this.s.resolution = t.dataset.res;
81780
+ break;
81781
+ case "snap-midnight":
81782
+ this.s.period = { preset: "custom", start: localMidnightOf(this.s.period.start), end: this.s.period.end };
81783
+ break;
81784
+ case "revalidate":
81785
+ void this.validateSnapshot();
81786
+ return;
81787
+ case "shorten": {
81788
+ const maxDays = maxPeriodDays(this.s.resolution);
81789
+ this.s.period = { preset: "custom", start: this.s.period.end - maxDays * 864e5, end: this.s.period.end };
81790
+ break;
81791
+ }
81792
+ case "group-all": {
81793
+ const g = TELEMETRY_GROUPS.find((x) => x.id === t.dataset.gid);
81794
+ if (g) {
81795
+ const all = g.items.every((x) => this.s.telemetries.has(x.key));
81796
+ g.items.forEach((x) => all ? this.s.telemetries.delete(x.key) : this.s.telemetries.add(x.key));
81797
+ }
81798
+ break;
81799
+ }
81800
+ case "confirm":
81801
+ void this.confirm();
81802
+ return;
81803
+ case "reload":
81804
+ void this.loadPage(true);
81805
+ return;
81806
+ case "more":
81807
+ this.s.page++;
81808
+ void this.loadPage(false);
81809
+ return;
81810
+ case "details":
81811
+ if (this.s.openDetails.has(job)) this.s.openDetails.delete(job);
81812
+ else this.s.openDetails.add(job);
81813
+ break;
81814
+ case "resend":
81815
+ void this.resend(job);
81816
+ return;
81817
+ case "cancel":
81818
+ void this.cancelJob(job);
81819
+ return;
81820
+ case "clone": {
81821
+ const e = this.s.envelopes.find((x) => x.jobId === job);
81822
+ if (e) this.cloneJob(e);
81823
+ break;
81824
+ }
81825
+ case "download":
81826
+ void this.download(job, t.dataset.kind || "xlsx");
81827
+ return;
81828
+ default:
81829
+ return;
81830
+ }
81831
+ this.render();
81832
+ };
81833
+ onInput = (ev) => {
81834
+ const t = ev.target;
81835
+ if (t.dataset.role === "search") {
81836
+ this.s.search = t.value;
81837
+ this.renderStep1();
81838
+ this.renderNav();
81839
+ }
81840
+ };
81841
+ onChange = (ev) => {
81842
+ const t = ev.target;
81843
+ if (t.dataset.role === "tel") {
81844
+ if (t.checked) this.s.telemetries.add(t.dataset.key);
81845
+ else this.s.telemetries.delete(t.dataset.key);
81846
+ this.render();
81847
+ }
81848
+ if (t.dataset.role === "force") {
81849
+ this.s.force = t.checked;
81850
+ void this.validateSnapshot();
81851
+ return;
81852
+ }
81853
+ if (t.dataset.role === "start" || t.dataset.role === "end") {
81854
+ this.s.period.preset = "custom";
81855
+ this.s.period[t.dataset.role] = parseLocalDateTime(t.value);
81856
+ this.render();
81857
+ }
81858
+ };
81859
+ onKeydown = async (ev) => {
81860
+ const t = ev.target;
81861
+ if (ev.key === "Enter" && t.dataset.role === "search") {
81862
+ ev.preventDefault();
81863
+ const candidates = this.searchResults().filter((d) => !this.s.selected.includes(d.tbDeviceId) && this.eligibility(d).ok);
81864
+ if (candidates.length === 1) {
81865
+ await this.addDevices([candidates[0].tbDeviceId]);
81866
+ this.renderStep1();
81867
+ this.renderNav();
81868
+ } else this.el.body.querySelector('[data-role="results"] [data-action="add"]:not([disabled])')?.focus();
81869
+ }
81870
+ if ((ev.key === "ArrowRight" || ev.key === "ArrowLeft") && t.getAttribute("role") === "tab") {
81871
+ const tabs = Array.from(this.root.querySelectorAll('[role="tab"]'));
81872
+ tabs[(tabs.indexOf(t) + (ev.key === "ArrowRight" ? 1 : tabs.length - 1)) % tabs.length].focus();
81873
+ }
81874
+ };
81875
+ /** Esc closes; with a filled wizard it asks first. */
81876
+ onDocKeydown = async (ev) => {
81877
+ if (ev.key !== "Escape" || this.destroyed) return;
81878
+ if (document.querySelector(".myio-dialog-backdrop, .myio-dialog")) return;
81879
+ if (this.isDirty() && !await this.confirmDialog("Fechar o relat\xF3rio?", "O pedido em montagem ser\xE1 descartado.", "Fechar", "Continuar")) return;
81880
+ this.modal.close();
81881
+ };
81882
+ };
81883
+ function openDemandPanelV2(params) {
81884
+ return new DemandPanelV2(params).open();
81885
+ }
81886
+
81887
+ // src/components/demand-panel-v2/adapters.ts
81888
+ function portError(status, code, message, extra = {}) {
81889
+ return Object.assign(new Error(message), { status, code, ...extra });
81890
+ }
81891
+ async function request(url, init) {
81892
+ let res;
81893
+ try {
81894
+ res = await fetch(url, init);
81895
+ } catch (e) {
81896
+ if (e?.name === "AbortError") throw portError(0, "ABORTED", "requisi\xE7\xE3o cancelada");
81897
+ throw portError(0, "NETWORK", e?.message || "falha de rede");
81898
+ }
81899
+ const text2 = await res.text();
81900
+ let body = null;
81901
+ try {
81902
+ body = text2 ? JSON.parse(text2) : null;
81903
+ } catch {
81904
+ body = text2;
81905
+ }
81906
+ if (!res.ok) {
81907
+ const err = body || {};
81908
+ const ra = Number(res.headers?.get?.("Retry-After"));
81909
+ throw portError(res.status, err.error?.code || `HTTP_${res.status}`, err.error?.message || err.message || `HTTP ${res.status}`, {
81910
+ details: err.error?.details,
81911
+ retryAfter: Number.isFinite(ra) && ra > 0 ? ra : void 0
81912
+ });
81913
+ }
81914
+ return body;
81915
+ }
81916
+ function unwrap(body) {
81917
+ const b = body;
81918
+ return b && typeof b === "object" && "data" in b ? b.data : body;
81919
+ }
81920
+ function createTbEnvelopeStore(opts) {
81921
+ const base = `${(opts.tbBaseUrl || "").replace(/\/+$/, "")}/api/plugins/telemetry/CUSTOMER/${encodeURIComponent(opts.customerId)}`;
81922
+ const token = () => opts.getToken ? opts.getToken() : typeof localStorage !== "undefined" && localStorage.getItem("jwt_token") || "";
81923
+ const headers = () => ({ "Content-Type": "application/json", "X-Authorization": `Bearer ${token()}` });
81924
+ const store = {
81925
+ async keys(o) {
81926
+ const keys = await request(`${base}/keys/attributes/SERVER_SCOPE`, { headers: headers(), signal: o?.signal });
81927
+ return Array.isArray(keys) ? keys : [];
81928
+ },
81929
+ async get(keys, o) {
81930
+ const out = [];
81931
+ for (let i = 0; i < keys.length; i += 50) {
81932
+ const chunk = keys.slice(i, i + 50).map(encodeURIComponent).join(",");
81933
+ const rows = await request(`${base}/values/attributes/SERVER_SCOPE?keys=${chunk}`, { headers: headers(), signal: o?.signal });
81934
+ (rows || []).forEach((r) => out.push({ key: r.key, value: r.value, lastUpdateTs: r.lastUpdateTs }));
81935
+ }
81936
+ return out;
81937
+ },
81938
+ async put(key, envelope, o) {
81939
+ await request(`${base}/attributes/SERVER_SCOPE`, { method: "POST", headers: headers(), body: JSON.stringify({ [key]: envelope }), signal: o?.signal });
81940
+ },
81941
+ /** Read-modify-write of ONE key — best effort, ThingsBoard has no compare-and-set. */
81942
+ async appendAudit(key, event, o) {
81943
+ const [row] = await store.get([key], o);
81944
+ const parsed = row ? parseEnvelope(row.value) : null;
81945
+ if (!parsed || !parsed.ok) throw portError(404, "ENVELOPE_NOT_FOUND", "registro do pedido n\xE3o encontrado");
81946
+ const env = { ...parsed.envelope, audit: [...parsed.envelope.audit, event] };
81947
+ await store.put(key, env, o);
81948
+ return env;
81949
+ }
81950
+ };
81951
+ return store;
81952
+ }
81953
+ function projectJob(raw) {
81954
+ const p = raw.progress;
81955
+ const percent = typeof p === "number" ? p : p && typeof p.percent === "number" ? p.percent : null;
81956
+ return {
81957
+ ...raw,
81958
+ progress: percent,
81959
+ phase: (p && typeof p === "object" ? p.phase : null) ?? null,
81960
+ createdAt: String(raw.createdAt || "")
81961
+ };
81962
+ }
81963
+ function createGcdrReportJobsApi(opts) {
81964
+ const root = `${(opts.baseUrl || "").replace(/\/+$/, "").replace(/\/api\/v1$/, "")}/api/v1/customers/${encodeURIComponent(opts.gcdrCustomerId)}/report-jobs`;
81965
+ const auth = () => Object.fromEntries(Object.entries(opts.getHeaders() || {}).filter(([, v]) => typeof v === "string" && v.trim() !== ""));
81966
+ const h3 = (json = false) => ({ Accept: "application/json", ...json ? { "Content-Type": "application/json" } : {}, ...auth() });
81967
+ const post = (url, body, signal) => request(url, { method: "POST", headers: h3(true), body: JSON.stringify(body), signal });
81968
+ return {
81969
+ submit: async (cmd, o) => projectJob(unwrap(await post(root, cmd, o?.signal))),
81970
+ validate: async (cmd, o) => unwrap(await post(`${root}/validate`, cmd, o?.signal)),
81971
+ config: async (o) => unwrap(await request(`${root}/config`, { headers: h3(), signal: o?.signal })),
81972
+ batchGet: async (jobIds, o) => {
81973
+ const data = unwrap(await post(`${root}/batch-get`, { jobIds }, o?.signal));
81974
+ const out = (data?.items || []).map((j) => ({ jobId: j.jobId, found: true, job: projectJob(j) }));
81975
+ (data?.notFound || []).forEach((jobId) => out.push({ jobId, found: false }));
81976
+ return out;
81977
+ },
81978
+ active: async (o) => {
81979
+ const data = unwrap(
81980
+ await request(`${root}?type=demand-report&status=queued,running&limit=50`, { headers: h3(), signal: o?.signal })
81981
+ );
81982
+ return (data?.items || []).map(projectJob);
81983
+ },
81984
+ cancel: async (jobId, declared, o) => projectJob(unwrap(await post(`${root}/${encodeURIComponent(jobId)}/cancel`, declared ? { cancelledBy: { userId: declared.tbUserId } } : {}, o?.signal))),
81985
+ download: async (jobId, o) => {
81986
+ const q = o?.expiresInSeconds ? `?expiresInSeconds=${Math.min(3600, Math.max(1, Math.floor(o.expiresInSeconds)))}` : "";
81987
+ return unwrap(await request(`${root}/${encodeURIComponent(jobId)}/result${q}`, { headers: h3(), signal: o?.signal }));
81988
+ },
81989
+ summary: async (jobId, o) => unwrap(await request(`${root}/${encodeURIComponent(jobId)}/summary`, { headers: h3(), signal: o?.signal }))
81990
+ };
81991
+ }
81992
+ function demandDevicesFromItems(items) {
81993
+ const seen = /* @__PURE__ */ new Set();
81994
+ const out = [];
81995
+ for (const it of items || []) {
81996
+ const tbId = it?.tbId || null;
81997
+ if (!tbId || seen.has(tbId)) continue;
81998
+ seen.add(tbId);
81999
+ out.push({
82000
+ tbDeviceId: tbId,
82001
+ gcdrDeviceId: it.gcdrDeviceId || null,
82002
+ ingestionId: it.ingestionId || null,
82003
+ label: it.label || it.entityLabel || it.identifier || tbId,
82004
+ name: it.entityName || it.name || it.label || "",
82005
+ identifier: it.identifier || null,
82006
+ deviceProfile: it.deviceProfile || ""
82007
+ });
82008
+ }
82009
+ return out;
82010
+ }
82011
+ function tbUserFromJwt(token) {
82012
+ try {
82013
+ const claims = JSON.parse(atob(String(token).split(".")[1]));
82014
+ if (!claims.userId) return null;
82015
+ const name = [claims.firstName, claims.lastName].filter(Boolean).join(" ") || claims.sub || claims.userId;
82016
+ return { tbUserId: claims.userId, name, email: claims.sub };
82017
+ } catch {
82018
+ return null;
82019
+ }
82020
+ }
82021
+
79539
82022
  // src/utils/goalsCoverage.ts
79540
82023
  var MONTHS_PT = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"];
79541
82024
  function formatCoverageRefPtBR(ref) {
@@ -79583,7 +82066,7 @@ function buildYearOptions() {
79583
82066
  for (let y = now + 2; y >= now - 2; y--) out.push(y);
79584
82067
  return out;
79585
82068
  }
79586
- function esc4(s) {
82069
+ function esc5(s) {
79587
82070
  return String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
79588
82071
  }
79589
82072
  function openGoalsPanel(params) {
@@ -79661,7 +82144,7 @@ function openGoalsPanel(params) {
79661
82144
  if (!/\/api\/v1$/.test(b)) b += "/api/v1";
79662
82145
  return b;
79663
82146
  }
79664
- async function request(method, path, opts) {
82147
+ async function request2(method, path, opts) {
79665
82148
  const o = opts || {};
79666
82149
  let url = apiBase() + path;
79667
82150
  if (o.query) {
@@ -79702,25 +82185,25 @@ function openGoalsPanel(params) {
79702
82185
  }
79703
82186
  const targetQuery = () => ({ domain: state6.domain, year: state6.year });
79704
82187
  const svc = {
79705
- getTree: (granularity, fetchHistory) => request("GET", `/customers/${customerId}/goals`, {
82188
+ getTree: (granularity, fetchHistory) => request2("GET", `/customers/${customerId}/goals`, {
79706
82189
  query: { domain: state6.domain, year: state6.year, granularity, fetchHistory }
79707
82190
  }),
79708
- replace: (body) => request("PUT", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
79709
- merge: (body) => request("PATCH", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
82191
+ replace: (body) => request2("PUT", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
82192
+ merge: (body) => request2("PATCH", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
79710
82193
  // Scoped bucket DELETE (RFC-0046 §3.6): body { bucket: { level, ref }, expectedVersion? }.
79711
82194
  // Used for cells the operator CLEARED — never a whole-year delete from this panel.
79712
- removeBucket: (body) => request("DELETE", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
79713
- importCsv: (body, dryRun) => request("POST", `/customers/${customerId}/goals/import`, {
82195
+ removeBucket: (body) => request2("DELETE", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
82196
+ importCsv: (body, dryRun) => request2("POST", `/customers/${customerId}/goals/import`, {
79714
82197
  query: { domain: state6.domain, year: state6.year, dryRun: !!dryRun },
79715
82198
  body
79716
82199
  }),
79717
82200
  // RFC-0052: margem da meta (goalMarginPct) por customer × domínio × ano.
79718
82201
  // Mesmo stream de versão dos buckets — 409 VERSION_CONFLICT no lock otimista.
79719
- setMargin: (pct, version2) => request("PUT", `/customers/${customerId}/goals/margin`, {
82202
+ setMargin: (pct, version2) => request2("PUT", `/customers/${customerId}/goals/margin`, {
79720
82203
  query: targetQuery(),
79721
82204
  body: version2 ? { goalMarginPct: pct, version: version2 } : { goalMarginPct: pct }
79722
82205
  }),
79723
- clearMargin: (version2) => request("DELETE", `/customers/${customerId}/goals/margin`, {
82206
+ clearMargin: (version2) => request2("DELETE", `/customers/${customerId}/goals/margin`, {
79724
82207
  query: targetQuery(),
79725
82208
  body: version2 ? { version: version2 } : void 0
79726
82209
  })
@@ -80379,13 +82862,13 @@ function openGoalsPanel(params) {
80379
82862
  const g = state6.goals || {};
80380
82863
  const parts = [];
80381
82864
  if (hasCoverageGaps(g.coverageGaps)) {
80382
- parts.push(`<p style="margin:0 0 8px;">${esc4(buildCoverageWarningTextPtBR(g.coverageGaps, "GERAL"))}</p>`);
82865
+ parts.push(`<p style="margin:0 0 8px;">${esc5(buildCoverageWarningTextPtBR(g.coverageGaps, "GERAL"))}</p>`);
80383
82866
  }
80384
82867
  (Array.isArray(g.devices) ? g.devices : []).forEach((d) => {
80385
82868
  if (!hasCoverageGaps(d && d.coverageGaps)) return;
80386
82869
  const label = d.label || d.code || "medidor";
80387
82870
  parts.push(
80388
- `<p style="margin:0 0 8px;">${esc4(buildCoverageWarningTextPtBR(d.coverageGaps, `do medidor ${label}`))}</p>`
82871
+ `<p style="margin:0 0 8px;">${esc5(buildCoverageWarningTextPtBR(d.coverageGaps, `do medidor ${label}`))}</p>`
80389
82872
  );
80390
82873
  });
80391
82874
  return `<div style="font-size:12px;line-height:1.55;color:#334155;">${parts.join("")}</div>`;
@@ -80425,7 +82908,7 @@ function openGoalsPanel(params) {
80425
82908
  html += `<div class="myio-goals-alert myio-goals-alert-amber">${icon2("alert")} ${i18n2.versionConflict}</div>`;
80426
82909
  }
80427
82910
  if (state6.saveError && !state6.conflict) {
80428
- html += `<div class="myio-goals-alert myio-goals-alert-red">${icon2("alert")} ${esc4(state6.saveError)}</div>`;
82911
+ html += `<div class="myio-goals-alert myio-goals-alert-red">${icon2("alert")} ${esc5(state6.saveError)}</div>`;
80429
82912
  }
80430
82913
  if (state6.saved && !state6.saveError) {
80431
82914
  html += `<div class="myio-goals-alert myio-goals-alert-green">${icon2("check")} ${i18n2.saveSuccess}</div>`;
@@ -80440,7 +82923,7 @@ function openGoalsPanel(params) {
80440
82923
  return `
80441
82924
  <div class="myio-goals-center myio-goals-center-col">
80442
82925
  ${icon2("alert-lg")}
80443
- <p>${esc4(state6.error)}</p>
82926
+ <p>${esc5(state6.error)}</p>
80444
82927
  <button class="myio-goals-btn myio-goals-btn-outline" data-action="retry">${i18n2.retry}</button>
80445
82928
  </div>`;
80446
82929
  }
@@ -80530,7 +83013,7 @@ function openGoalsPanel(params) {
80530
83013
  const cellLabel = state6.level === "month" ? i18n2.months[key] : state6.level === "day" ? i18n2.dayCell.replace("{day}", Number(key)) : `${key}:00`;
80531
83014
  const canDrill = state6.level !== "hour";
80532
83015
  const labelEl = canDrill ? `<button class="myio-goals-cell-drill" data-action="${state6.level === "month" ? "open-month" : "open-day"}" data-key="${key}" title="${i18n2.drill.openTitle}">${cellLabel} ${icon2("chevron-sm")}</button>` : `<span class="myio-goals-cell-label">${cellLabel}</span>`;
80533
- const val = state6.draft[key] !== void 0 ? esc4(state6.draft[key]) : "";
83016
+ const val = state6.draft[key] !== void 0 ? esc5(state6.draft[key]) : "";
80534
83017
  const minAttr = cfg.allowsNegative ? "" : 'min="0"';
80535
83018
  return `
80536
83019
  <div class="myio-goals-cell">
@@ -80598,7 +83081,7 @@ function openGoalsPanel(params) {
80598
83081
  const actor = ev.actor ? `${String(ev.actor).slice(0, 8)}\u2026` : i18n2.history.system;
80599
83082
  const extra = ev.bucketCount - (ev.details ? ev.details.length : 0);
80600
83083
  const chips = (ev.details || []).slice(0, DETAIL_PREVIEW).map(
80601
- (d) => `<span class="myio-goals-tl-chip"><span class="myio-goals-tl-ref">${esc4(formatRef(d.ref))}</span>${d.value === null ? "\u2014" : fmt2(d.value)}</span>`
83084
+ (d) => `<span class="myio-goals-tl-chip"><span class="myio-goals-tl-ref">${esc5(formatRef(d.ref))}</span>${d.value === null ? "\u2014" : fmt2(d.value)}</span>`
80602
83085
  ).join("");
80603
83086
  const more = extra > 0 ? `<span class="myio-goals-tl-more">${i18n2.history.moreDetails.replace("{n}", extra)}</span>` : "";
80604
83087
  return `
@@ -80608,7 +83091,7 @@ function openGoalsPanel(params) {
80608
83091
  <span class="myio-goals-tl-title">${i18n2.history.source[ev.source] || i18n2.history.source.EDIT}</span>
80609
83092
  <span class="myio-goals-tl-ver">v${ev.version}</span>
80610
83093
  </div>
80611
- <p class="myio-goals-tl-meta" title="${esc4(new Date(ev.changedAt).toLocaleString(locale))}">${esc4(actor)} \xB7 ${esc4(timeAgo(ev.changedAt))}</p>
83094
+ <p class="myio-goals-tl-meta" title="${esc5(new Date(ev.changedAt).toLocaleString(locale))}">${esc5(actor)} \xB7 ${esc5(timeAgo(ev.changedAt))}</p>
80612
83095
  <p class="myio-goals-tl-records">${i18n2.history.records.replace("{n}", ev.bucketCount)} \xB7 ${i18n2.levels[ev.actionLevel] || ev.actionLevel}</p>
80613
83096
  ${chips || more ? `<div class="myio-goals-tl-chips">${chips}${more}</div>` : ""}
80614
83097
  </li>`;
@@ -80655,7 +83138,7 @@ ${state6.year}-03-15T08,15`;
80655
83138
  let html = `
80656
83139
  <div class="myio-goals-import-hint">
80657
83140
  <p>${i18n2.import.formatHint}</p>
80658
- <code>${esc4(sample)}</code>
83141
+ <code>${esc5(sample)}</code>
80659
83142
  </div>`;
80660
83143
  if (!applied) {
80661
83144
  html += `
@@ -80669,16 +83152,16 @@ ${state6.year}-03-15T08,15`;
80669
83152
  <input type="file" id="myio-goals-csv-file" accept=".csv,.txt,text/csv,text/plain" style="display:none" />
80670
83153
  </div>
80671
83154
  </div>
80672
- <textarea id="myio-goals-csv-textarea" rows="8" spellcheck="false" placeholder="${esc4(`bucket,value
83155
+ <textarea id="myio-goals-csv-textarea" rows="8" spellcheck="false" placeholder="${esc5(`bucket,value
80673
83156
  ${state6.year},100000
80674
83157
  ${state6.year}-03,9000
80675
83158
  ${state6.year}-03-15,310
80676
- ${state6.year}-03-15T08,15`)}">${esc4(state6.csv)}</textarea>
83159
+ ${state6.year}-03-15T08,15`)}">${esc5(state6.csv)}</textarea>
80677
83160
  <p class="myio-goals-microhint">${i18n2.import.unitNote.replace("{unit}", unitLabel2())}</p>
80678
83161
  </div>`;
80679
83162
  }
80680
83163
  if (state6.saveError && state6.conflictVersion === null) {
80681
- html += `<div class="myio-goals-alert myio-goals-alert-red">${icon2("alert")} ${esc4(state6.saveError)}</div>`;
83164
+ html += `<div class="myio-goals-alert myio-goals-alert-red">${icon2("alert")} ${esc5(state6.saveError)}</div>`;
80682
83165
  }
80683
83166
  if (state6.conflictVersion !== null) {
80684
83167
  html += `<div class="myio-goals-alert myio-goals-alert-amber">${icon2("alert")} ${i18n2.import.conflict}</div>`;
@@ -80707,7 +83190,7 @@ ${state6.year}-03-15T08,15`)}">${esc4(state6.csv)}</textarea>
80707
83190
  <div class="myio-goals-import-diags">
80708
83191
  <p class="myio-goals-import-diags-title">${i18n2.import.diagnosticsTitle}</p>
80709
83192
  <ul>${result.diagnostics.map(
80710
- (d) => `<li>${icon2("alert-sm")} <span><span class="myio-goals-diag-line">${i18n2.import.line.replace("{line}", d.line)}</span>${d.bucket ? ` <span class="myio-goals-diag-bucket">[${esc4(d.bucket)}]</span>` : ""} ${esc4(d.reason)}</span></li>`
83193
+ (d) => `<li>${icon2("alert-sm")} <span><span class="myio-goals-diag-line">${i18n2.import.line.replace("{line}", d.line)}</span>${d.bucket ? ` <span class="myio-goals-diag-bucket">[${esc5(d.bucket)}]</span>` : ""} ${esc5(d.reason)}</span></li>`
80711
83194
  ).join("")}</ul>
80712
83195
  </div>`;
80713
83196
  }
@@ -80715,7 +83198,7 @@ ${state6.year}-03-15T08,15`)}">${esc4(state6.csv)}</textarea>
80715
83198
  html += `
80716
83199
  <div class="myio-goals-import-log">
80717
83200
  <p class="myio-goals-import-log-title">${i18n2.import.logTitle}</p>
80718
- <ul>${result.log.map((line) => `<li>${esc4(line)}</li>`).join("")}</ul>
83201
+ <ul>${result.log.map((line) => `<li>${esc5(line)}</li>`).join("")}</ul>
80719
83202
  </div>`;
80720
83203
  }
80721
83204
  html += `</div>`;
@@ -87753,7 +90236,7 @@ function injectCSS10() {
87753
90236
  document.head.appendChild(style);
87754
90237
  _cssInjected2 = true;
87755
90238
  }
87756
- function esc5(value) {
90239
+ function esc6(value) {
87757
90240
  return String(value == null ? "" : value).replace(
87758
90241
  /[&<>"]/g,
87759
90242
  (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]
@@ -87803,7 +90286,7 @@ function buildPieSvg(visible, total) {
87803
90286
  const val = Number(v.d.value) || 0;
87804
90287
  return `<svg class="myio-col-summary__pie" viewBox="0 0 220 220">
87805
90288
  <circle class="myio-col-summary__slice" data-idx="${v.idx}" cx="110" cy="110" r="100"
87806
- fill="${sliceColor(v.idx)}"><title>${esc5(v.d.name)} \u2014 ${esc5(fmt2(val))} (${fmtPct2(
90289
+ fill="${sliceColor(v.idx)}"><title>${esc6(v.d.name)} \u2014 ${esc6(fmt2(val))} (${fmtPct2(
87807
90290
  val,
87808
90291
  total,
87809
90292
  pd
@@ -87818,7 +90301,7 @@ function buildPieSvg(visible, total) {
87818
90301
  const a1 = acc / total * 360;
87819
90302
  return `<path class="myio-col-summary__slice" data-idx="${v.idx}"
87820
90303
  d="${arcPath(110, 110, 100, a0, a1)}" fill="${sliceColor(v.idx)}"
87821
- ><title>${esc5(v.d.name)} \u2014 ${esc5(fmt2(val))} (${fmtPct2(val, total, pd)})</title></path>`;
90304
+ ><title>${esc6(v.d.name)} \u2014 ${esc6(fmt2(val))} (${fmtPct2(val, total, pd)})</title></path>`;
87822
90305
  }).join("");
87823
90306
  return `<svg class="myio-col-summary__pie" viewBox="0 0 220 220">${paths}</svg>`;
87824
90307
  }
@@ -87832,8 +90315,8 @@ function buildLegend(total) {
87832
90315
  return `<div class="myio-col-summary__legend-row${isExcl ? " is-excluded" : ""}" data-idx="${idx}"
87833
90316
  title="Clique para ${isExcl ? "incluir" : "remover"} da pizza">
87834
90317
  <span class="myio-col-summary__legend-dot" style="background:${sliceColor(idx)};"></span>
87835
- <span class="myio-col-summary__legend-name">${esc5(d.name)}</span>
87836
- <span class="myio-col-summary__legend-val">${esc5(fmt2(val))}</span>
90318
+ <span class="myio-col-summary__legend-name">${esc6(d.name)}</span>
90319
+ <span class="myio-col-summary__legend-val">${esc6(fmt2(val))}</span>
87837
90320
  <span class="myio-col-summary__legend-pct">${pct}</span>
87838
90321
  </div>`;
87839
90322
  }).join("");
@@ -87854,25 +90337,25 @@ function buildAverageInner() {
87854
90337
  const dash = "\u2014";
87855
90338
  const periodRow = data.periodLabel ? `<div class="myio-col-summary__kpi">
87856
90339
  <span class="myio-col-summary__kpi-label">Per\xEDodo</span>
87857
- <span class="myio-col-summary__kpi-value">${esc5(data.periodLabel)}</span>
90340
+ <span class="myio-col-summary__kpi-value">${esc6(data.periodLabel)}</span>
87858
90341
  </div>` : "";
87859
90342
  const note = "M\xE9dia, m\xEDnima e m\xE1xima consideram apenas dispositivos online (com leitura).";
87860
90343
  const kpis = `<div class="myio-col-summary__kpis">
87861
90344
  ${periodRow}
87862
90345
  <div class="myio-col-summary__kpi">
87863
90346
  <span class="myio-col-summary__kpi-label">Dispositivos
87864
- <span class="myio-col-summary__info" title="${esc5(note)}" aria-label="${esc5(note)}"
90347
+ <span class="myio-col-summary__info" title="${esc6(note)}" aria-label="${esc6(note)}"
87865
90348
  style="display:inline-flex;align-items:center;justify-content:center;width:13px;height:13px;margin-left:4px;border-radius:50%;background:#3e1a7d;color:#fff;font-size:9px;font-weight:700;font-style:italic;font-family:Georgia,serif;cursor:help;vertical-align:middle;">i</span>
87866
90349
  </span>
87867
90350
  <span class="myio-col-summary__kpi-value">${online.length} online \xB7 ${offline.length} offline</span>
87868
90351
  </div>
87869
90352
  <div class="myio-col-summary__kpi">
87870
- <span class="myio-col-summary__kpi-label">${esc5(measure)} m\xE9dia</span>
87871
- <span class="myio-col-summary__kpi-value myio-col-summary__kpi-value--accent">${avg === null ? dash : esc5(fmt2(avg))}</span>
90353
+ <span class="myio-col-summary__kpi-label">${esc6(measure)} m\xE9dia</span>
90354
+ <span class="myio-col-summary__kpi-value myio-col-summary__kpi-value--accent">${avg === null ? dash : esc6(fmt2(avg))}</span>
87872
90355
  </div>
87873
90356
  <div class="myio-col-summary__kpi">
87874
90357
  <span class="myio-col-summary__kpi-label">M\xEDnima \xB7 M\xE1xima</span>
87875
- <span class="myio-col-summary__kpi-value">${minV ? esc5(fmt2(Number(minV.d.value))) : dash} \xB7 ${maxV ? esc5(fmt2(Number(maxV.d.value))) : dash}</span>
90358
+ <span class="myio-col-summary__kpi-value">${minV ? esc6(fmt2(Number(minV.d.value))) : dash} \xB7 ${maxV ? esc6(fmt2(Number(maxV.d.value))) : dash}</span>
87876
90359
  </div>
87877
90360
  </div>`;
87878
90361
  if (!data.devices.length) {
@@ -87881,12 +90364,12 @@ function buildAverageInner() {
87881
90364
  const near3 = avg === null ? [] : online.slice().sort((a, b) => Math.abs(Number(a.d.value) - avg) - Math.abs(Number(b.d.value) - avg)).slice(0, 3);
87882
90365
  const row = (v) => `
87883
90366
  <div class="myio-col-summary__row">
87884
- <span class="myio-col-summary__name" title="${esc5(v.d.name)}">${esc5(v.d.name)}</span>
87885
- <span class="myio-col-summary__val">${esc5(fmt2(Number(v.d.value) || 0))}</span>
90367
+ <span class="myio-col-summary__name" title="${esc6(v.d.name)}">${esc6(v.d.name)}</span>
90368
+ <span class="myio-col-summary__val">${esc6(fmt2(Number(v.d.value) || 0))}</span>
87886
90369
  </div>`;
87887
90370
  const offRow = (v) => `
87888
90371
  <div class="myio-col-summary__row">
87889
- <span class="myio-col-summary__name" title="${esc5(v.d.name)}">${esc5(v.d.name)}</span>
90372
+ <span class="myio-col-summary__name" title="${esc6(v.d.name)}">${esc6(v.d.name)}</span>
87890
90373
  <span class="myio-col-summary__val" style="color:#94a3b8;">sem leitura</span>
87891
90374
  </div>`;
87892
90375
  const group = (label, list, r = row) => list.length ? `<div class="myio-col-summary__group">
@@ -87903,8 +90386,8 @@ function buildAverageInner() {
87903
90386
  </div>
87904
90387
  </div>
87905
90388
  <div class="myio-col-summary__footer">
87906
- <span class="myio-col-summary__footer-label">${esc5(measure)} m\xE9dia \xB7 ${online.length} online</span>
87907
- <span class="myio-col-summary__footer-value">${avg === null ? dash : esc5(fmt2(avg))}</span>
90389
+ <span class="myio-col-summary__footer-label">${esc6(measure)} m\xE9dia \xB7 ${online.length} online</span>
90390
+ <span class="myio-col-summary__footer-value">${avg === null ? dash : esc6(fmt2(avg))}</span>
87908
90391
  </div>`;
87909
90392
  }
87910
90393
  function buildInner() {
@@ -87917,7 +90400,7 @@ function buildInner() {
87917
90400
  const avg = count ? total / count : 0;
87918
90401
  const periodRow = data.periodLabel ? `<div class="myio-col-summary__kpi">
87919
90402
  <span class="myio-col-summary__kpi-label">Per\xEDodo</span>
87920
- <span class="myio-col-summary__kpi-value">${esc5(data.periodLabel)}</span>
90403
+ <span class="myio-col-summary__kpi-value">${esc6(data.periodLabel)}</span>
87921
90404
  </div>` : "";
87922
90405
  const kpis = `<div class="myio-col-summary__kpis">
87923
90406
  ${periodRow}
@@ -87927,13 +90410,13 @@ function buildInner() {
87927
90410
  </div>
87928
90411
  <div class="myio-col-summary__kpi">
87929
90412
  <span class="myio-col-summary__kpi-label">Consumo m\xE9dio</span>
87930
- <span class="myio-col-summary__kpi-value myio-col-summary__kpi-value--accent">${esc5(
90413
+ <span class="myio-col-summary__kpi-value myio-col-summary__kpi-value--accent">${esc6(
87931
90414
  fmt2(avg)
87932
90415
  )}</span>
87933
90416
  </div>
87934
90417
  <div class="myio-col-summary__kpi">
87935
90418
  <span class="myio-col-summary__kpi-label">Consumo total</span>
87936
- <span class="myio-col-summary__kpi-value">${esc5(fmt2(total))}</span>
90419
+ <span class="myio-col-summary__kpi-value">${esc6(fmt2(total))}</span>
87937
90420
  </div>
87938
90421
  </div>`;
87939
90422
  if (!data.devices.length) {
@@ -87947,8 +90430,8 @@ function buildInner() {
87947
90430
  ).slice(0, 3);
87948
90431
  const row = (v) => `
87949
90432
  <div class="myio-col-summary__row">
87950
- <span class="myio-col-summary__name" title="${esc5(v.d.name)}">${esc5(v.d.name)}</span>
87951
- <span class="myio-col-summary__val">${esc5(fmt2(Number(v.d.value) || 0))}</span>
90433
+ <span class="myio-col-summary__name" title="${esc6(v.d.name)}">${esc6(v.d.name)}</span>
90434
+ <span class="myio-col-summary__val">${esc6(fmt2(Number(v.d.value) || 0))}</span>
87952
90435
  <span class="myio-col-summary__pct">${fmtPct2(Number(v.d.value) || 0, total, pd)}</span>
87953
90436
  </div>`;
87954
90437
  const group = (label, list) => list.length ? `<div class="myio-col-summary__group">
@@ -87971,7 +90454,7 @@ function buildInner() {
87971
90454
  </div>
87972
90455
  <div class="myio-col-summary__footer">
87973
90456
  <span class="myio-col-summary__footer-label">Consumo Total \xB7 ${count} dispositivos</span>
87974
- <span class="myio-col-summary__footer-value">${esc5(fmt2(total))}</span>
90457
+ <span class="myio-col-summary__footer-value">${esc6(fmt2(total))}</span>
87975
90458
  </div>`;
87976
90459
  }
87977
90460
  function setHighlight(idx) {
@@ -95559,12 +98042,12 @@ function createConsumptionChartWidget(config2) {
95559
98042
  }
95560
98043
 
95561
98044
  // src/components/tooltips/goals-bar-tooltip/createGoalsBarTooltip.ts
95562
- var STYLE_ID6 = "myio-goals-bar-tooltip-styles";
98045
+ var STYLE_ID8 = "myio-goals-bar-tooltip-styles";
95563
98046
  var DEFAULT_ACCENT = "#6c5ce7";
95564
98047
  function injectStyles12(doc) {
95565
- if (doc.getElementById(STYLE_ID6)) return;
98048
+ if (doc.getElementById(STYLE_ID8)) return;
95566
98049
  const s = doc.createElement("style");
95567
- s.id = STYLE_ID6;
98050
+ s.id = STYLE_ID8;
95568
98051
  s.textContent = `
95569
98052
  .myio-gbt {
95570
98053
  position: fixed;
@@ -95751,7 +98234,7 @@ function injectStyles12(doc) {
95751
98234
  `;
95752
98235
  (doc.head || doc.documentElement).appendChild(s);
95753
98236
  }
95754
- function esc6(v) {
98237
+ function esc7(v) {
95755
98238
  return String(v ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
95756
98239
  }
95757
98240
  function fmtPct3(pct) {
@@ -95772,8 +98255,8 @@ function renderDeltaChip(delta) {
95772
98255
  const tone = delta.tone === "good" ? "good" : delta.tone === "bad" ? "bad" : "neutral";
95773
98256
  const arrow = delta.arrow === "up" ? "\u2191" : delta.arrow === "down" ? "\u2193" : "\u2248";
95774
98257
  const pctTxt = fmtPct3(Math.abs(Number(delta.pct)));
95775
- const txt = delta.text ? ` ${esc6(delta.text)}` : "";
95776
- return `<span class="myio-gbt__chip myio-gbt__chip--${tone}">${arrow} ${esc6(pctTxt)}${txt}</span>`;
98258
+ const txt = delta.text ? ` ${esc7(delta.text)}` : "";
98259
+ return `<span class="myio-gbt__chip myio-gbt__chip--${tone}">${arrow} ${esc7(pctTxt)}${txt}</span>`;
95777
98260
  }
95778
98261
  function renderRow(row, level) {
95779
98262
  const hasChildren = Array.isArray(row.children) && row.children.length > 0;
@@ -95781,12 +98264,12 @@ function renderRow(row, level) {
95781
98264
  const id = `gbtr-${++_rowSeq}`;
95782
98265
  const pct = row.pct;
95783
98266
  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>`;
95784
- const swatch = row.color ? `<span class="myio-gbt__swatch" style="background:${esc6(row.color)}"></span>` : "";
95785
- const icon2 = row.icon ? `<span class="myio-gbt__icon">${esc6(row.icon)}</span>` : "";
98267
+ const swatch = row.color ? `<span class="myio-gbt__swatch" style="background:${esc7(row.color)}"></span>` : "";
98268
+ const icon2 = row.icon ? `<span class="myio-gbt__icon">${esc7(row.icon)}</span>` : "";
95786
98269
  const chipHtml = row.delta ? renderDeltaChip(row.delta) : "";
95787
- const pctHtml = !row.delta && pct != null && isFinite(pct) ? `<span class="myio-gbt__pct">${esc6(fmtPct3(pct))}</span>` : "";
98270
+ const pctHtml = !row.delta && pct != null && isFinite(pct) ? `<span class="myio-gbt__pct">${esc7(fmtPct3(pct))}</span>` : "";
95788
98271
  const rowClass = level > 0 ? "myio-gbt__row myio-gbt__row--child" : "myio-gbt__row";
95789
- let html = `<div class="${rowClass}">` + caret + swatch + icon2 + `<span class="myio-gbt__label">${esc6(row.label)}</span><span class="myio-gbt__value">${esc6(row.valueText)}</span>` + chipHtml + pctHtml + `</div>`;
98272
+ let html = `<div class="${rowClass}">` + caret + swatch + icon2 + `<span class="myio-gbt__label">${esc7(row.label)}</span><span class="myio-gbt__value">${esc7(row.valueText)}</span>` + chipHtml + pctHtml + `</div>`;
95790
98273
  if (hasChildren) {
95791
98274
  const childHtml = row.children.map((c) => renderRow(c, level + 1)).join("");
95792
98275
  html += `<div class="myio-gbt__children${open ? " is-open" : ""}" data-children="${id}">${childHtml}</div>`;
@@ -95822,15 +98305,15 @@ function createGoalsBarTooltip(opts) {
95822
98305
  const rect = el2.getBoundingClientRect();
95823
98306
  const w = rect.width || 280;
95824
98307
  const h3 = rect.height || 200;
95825
- const pad = 12;
98308
+ const pad3 = 12;
95826
98309
  const gap = 16;
95827
98310
  let left = clientX + gap;
95828
98311
  let top = clientY + gap;
95829
- if (left + w > window.innerWidth - pad) left = clientX - w - gap;
95830
- if (left < pad) left = Math.max(pad, (window.innerWidth - w) / 2);
95831
- if (top + h3 > window.innerHeight - pad) top = clientY - h3 - gap;
95832
- if (top < pad) top = pad;
95833
- if (top + h3 > window.innerHeight - pad) top = Math.max(pad, window.innerHeight - h3 - pad);
98312
+ if (left + w > window.innerWidth - pad3) left = clientX - w - gap;
98313
+ if (left < pad3) left = Math.max(pad3, (window.innerWidth - w) / 2);
98314
+ if (top + h3 > window.innerHeight - pad3) top = clientY - h3 - gap;
98315
+ if (top < pad3) top = pad3;
98316
+ if (top + h3 > window.innerHeight - pad3) top = Math.max(pad3, window.innerHeight - h3 - pad3);
95834
98317
  el2.style.left = `${Math.round(left)}px`;
95835
98318
  el2.style.top = `${Math.round(top)}px`;
95836
98319
  }
@@ -95869,8 +98352,8 @@ function createGoalsBarTooltip(opts) {
95869
98352
  <div class="myio-gbt__card">
95870
98353
  <div class="myio-gbt__header">
95871
98354
  <div class="myio-gbt__title-wrap">
95872
- <div class="myio-gbt__title">${esc6(data.title)}</div>
95873
- ${data.subtitle ? `<div class="myio-gbt__subtitle">${esc6(data.subtitle)}</div>` : ""}
98355
+ <div class="myio-gbt__title">${esc7(data.title)}</div>
98356
+ ${data.subtitle ? `<div class="myio-gbt__subtitle">${esc7(data.subtitle)}</div>` : ""}
95874
98357
  </div>
95875
98358
  <button class="myio-gbt__btn myio-gbt__btn--pin${pinned ? " is-pinned" : ""}" data-action="pin" title="${pinned ? "Desafixar" : "Fixar na tela"}">\u{1F4CC}</button>
95876
98359
  <button class="myio-gbt__btn myio-gbt__btn--close" data-action="close" title="Fechar">\u2715</button>
@@ -96673,11 +99156,11 @@ function _rerenderFromCache() {
96673
99156
  const { labels, totals, goalLine, prevTotals, domain } = _lastRender;
96674
99157
  _renderChart(canvas, labels, totals, goalLine, domain, prevTotals);
96675
99158
  }
96676
- var STYLE_ID7 = "myio-goals-modal-v2-styles";
99159
+ var STYLE_ID9 = "myio-goals-modal-v2-styles";
96677
99160
  function _injectStyles(topDoc) {
96678
- if (topDoc.getElementById(STYLE_ID7)) return;
99161
+ if (topDoc.getElementById(STYLE_ID9)) return;
96679
99162
  const s = topDoc.createElement("style");
96680
- s.id = STYLE_ID7;
99163
+ s.id = STYLE_ID9;
96681
99164
  s.textContent = `
96682
99165
  .gm-overlay{position:fixed;inset:0;z-index:1000000;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:'Nunito', system-ui, sans-serif;}
96683
99166
  .gm-overlay.show{opacity:1;}
@@ -96879,19 +99362,19 @@ function _updateCoverageHint(topDoc) {
96879
99362
  const deviceCount = data.granularity === "DEVICE" ? data.devices?.length ?? 0 : 0;
96880
99363
  hint.textContent = `\u26A0 Meta incompleta para este dom\xEDnio/ano` + (deviceCount > 0 ? ` \xB7 Por medidor (${deviceCount})` : "") + ` \u2014 passe o mouse para detalhes`;
96881
99364
  tabs.insertAdjacentElement("afterend", hint);
96882
- const esc8 = (s) => String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
99365
+ const esc9 = (s) => String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
96883
99366
  try {
96884
99367
  _coverageDetach = InfoTooltip.attach(hint, () => {
96885
99368
  const d = _getGoalsData(_currentDomain);
96886
99369
  const parts = [];
96887
99370
  if (hasCoverageGaps(d?.coverageGaps)) {
96888
- parts.push(`<p style="margin:0 0 8px;">${esc8(buildCoverageWarningTextPtBR(d?.coverageGaps, "GERAL"))}</p>`);
99371
+ parts.push(`<p style="margin:0 0 8px;">${esc9(buildCoverageWarningTextPtBR(d?.coverageGaps, "GERAL"))}</p>`);
96889
99372
  }
96890
99373
  (d?.devices || []).forEach((dev) => {
96891
99374
  if (!hasCoverageGaps(dev?.coverageGaps)) return;
96892
99375
  const label = dev.label || dev.code || "medidor";
96893
99376
  parts.push(
96894
- `<p style="margin:0 0 8px;">${esc8(buildCoverageWarningTextPtBR(dev.coverageGaps, `do medidor ${label}`))}</p>`
99377
+ `<p style="margin:0 0 8px;">${esc9(buildCoverageWarningTextPtBR(dev.coverageGaps, `do medidor ${label}`))}</p>`
96895
99378
  );
96896
99379
  });
96897
99380
  return {
@@ -97084,8 +99567,8 @@ var CSV_SEPARATORS = {
97084
99567
  "default": ";"
97085
99568
  };
97086
99569
  function formatDateForFilename(date) {
97087
- const pad = (n) => n.toString().padStart(2, "0");
97088
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
99570
+ const pad3 = (n) => n.toString().padStart(2, "0");
99571
+ return `${date.getFullYear()}-${pad3(date.getMonth() + 1)}-${pad3(date.getDate())}-${pad3(date.getHours())}-${pad3(date.getMinutes())}-${pad3(date.getSeconds())}`;
97089
99572
  }
97090
99573
  function sanitizeFilename(str) {
97091
99574
  return str.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, "_").substring(0, 50);
@@ -99528,7 +102011,7 @@ function createMyIOTheme(settings, mode) {
99528
102011
  // src/components/settings-hub/openSettingsHubModal.ts
99529
102012
  var ROUTE_DELAY_MS = 250;
99530
102013
  var MODAL_ID = "myio-conf-picker";
99531
- var STYLE_ID8 = "myio-conf-picker-styles";
102014
+ var STYLE_ID10 = "myio-conf-picker-styles";
99532
102015
  var HUB_OPTIONS = [
99533
102016
  {
99534
102017
  action: "temperature",
@@ -99719,7 +102202,7 @@ var HUB_CSS = `
99719
102202
  color: var(--myio-brand-700, #7B2FF7);
99720
102203
  }
99721
102204
  `;
99722
- function escapeHtml7(value) {
102205
+ function escapeHtml9(value) {
99723
102206
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
99724
102207
  }
99725
102208
  function resolveTopDocument() {
@@ -99739,9 +102222,9 @@ function resolveHubThemeVars(explicit) {
99739
102222
  function openSettingsHubModal(options) {
99740
102223
  const { customerName = "", isSuperAdmin: isSuperAdmin2 = false, handlers } = options;
99741
102224
  const doc = options.targetDocument || resolveTopDocument();
99742
- if (!doc.getElementById(STYLE_ID8)) {
102225
+ if (!doc.getElementById(STYLE_ID10)) {
99743
102226
  const style = doc.createElement("style");
99744
- style.id = STYLE_ID8;
102227
+ style.id = STYLE_ID10;
99745
102228
  style.textContent = HUB_CSS;
99746
102229
  doc.head.appendChild(style);
99747
102230
  }
@@ -99766,7 +102249,7 @@ function openSettingsHubModal(options) {
99766
102249
  <div class="myio-conf-picker__overlay"></div>
99767
102250
  <div class="myio-conf-picker__content">
99768
102251
  <div class="myio-conf-picker__header">
99769
- <h3>\u2699\uFE0F Configura\xE7\xF5es${customerName ? ` \u2014 ${escapeHtml7(customerName)}` : ""}</h3>
102252
+ <h3>\u2699\uFE0F Configura\xE7\xF5es${customerName ? ` \u2014 ${escapeHtml9(customerName)}` : ""}</h3>
99770
102253
  <button class="myio-conf-picker__close" aria-label="Fechar">&times;</button>
99771
102254
  </div>
99772
102255
  <div class="myio-conf-picker__body">${cardsHtml}
@@ -100961,7 +103444,7 @@ function createTariffApiAdapter(client, opts) {
100961
103444
 
100962
103445
  // src/components/pricing-panel/openPricingPanel.ts
100963
103446
  var MODAL_ID2 = "myio-pricing-panel";
100964
- function escapeHtml8(value) {
103447
+ function escapeHtml10(value) {
100965
103448
  return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
100966
103449
  }
100967
103450
  function resolveTopDocument2() {
@@ -101116,7 +103599,7 @@ function openPricingPanel(params) {
101116
103599
  }
101117
103600
  const initDomain = params.domain || "energy";
101118
103601
  const initCategory = params.category || "lojas";
101119
- const customerOptions = customers.map((c) => `<option value="${escapeHtml8(resolveCustomerId(c))}">${escapeHtml8(c.title)}</option>`).join("");
103602
+ const customerOptions = customers.map((c) => `<option value="${escapeHtml10(resolveCustomerId(c))}">${escapeHtml10(c.title)}</option>`).join("");
101120
103603
  modal.innerHTML = `
101121
103604
  <div class="myio-pricing__overlay"></div>
101122
103605
  <div class="myio-pricing__content">
@@ -101331,9 +103814,9 @@ function openPricingPanel(params) {
101331
103814
  const pushAudit = (rec) => {
101332
103815
  if (rec) auditLog = [rec, ...auditLog];
101333
103816
  };
101334
- const badge = (domain, category) => `<span class="myio-pricing__badge${domain === "water" ? " myio-pricing__badge--water" : ""}">${escapeHtml8(
103817
+ const badge = (domain, category) => `<span class="myio-pricing__badge${domain === "water" ? " myio-pricing__badge--water" : ""}">${escapeHtml10(
101335
103818
  DOMAIN_LABEL[domain]
101336
- )} \xB7 ${escapeHtml8(CATEGORY_LABEL[category])}</span>`;
103819
+ )} \xB7 ${escapeHtml10(CATEGORY_LABEL[category])}</span>`;
101337
103820
  const renderList2 = () => {
101338
103821
  const customerId = customerSel.value;
101339
103822
  const rows = entries.filter((e) => e.customerId === customerId).sort((a, b) => {
@@ -101346,15 +103829,15 @@ function openPricingPanel(params) {
101346
103829
  const boundsKey = b ? `${b.start}__${b.end}` : "";
101347
103830
  return `
101348
103831
  <li class="myio-pricing__item">
101349
- <span class="myio-pricing__item-period">${badge(e.domain, e.category)} ${escapeHtml8(
103832
+ <span class="myio-pricing__item-period">${badge(e.domain, e.category)} ${escapeHtml10(
101350
103833
  formatPeriodLabel(e)
101351
103834
  )}</span>
101352
- <span class="myio-pricing__item-price">${escapeHtml8(formatBRL2(e.pricePerKwh))} ${escapeHtml8(
103835
+ <span class="myio-pricing__item-price">${escapeHtml10(formatBRL2(e.pricePerKwh))} ${escapeHtml10(
101353
103836
  unitLabel(e.domain)
101354
103837
  )}</span>
101355
- <button class="myio-pricing__remove" data-remove="${escapeHtml8(e.domain)}|${escapeHtml8(
103838
+ <button class="myio-pricing__remove" data-remove="${escapeHtml10(e.domain)}|${escapeHtml10(
101356
103839
  e.category
101357
- )}|${escapeHtml8(boundsKey)}" aria-label="Remover" type="button">\u2715</button>
103840
+ )}|${escapeHtml10(boundsKey)}" aria-label="Remover" type="button">\u2715</button>
101358
103841
  </li>`;
101359
103842
  }).join("");
101360
103843
  emptyEl.style.display = rows.length ? "none" : "";
@@ -101453,8 +103936,8 @@ function openPricingPanel(params) {
101453
103936
  kpiEl.innerHTML = cards.map(
101454
103937
  (c) => `
101455
103938
  <div class="myio-pricing__kpi">
101456
- <div class="myio-pricing__kpi-value">${escapeHtml8(c.value)}</div>
101457
- <div class="myio-pricing__kpi-label">${escapeHtml8(c.label)}</div>
103939
+ <div class="myio-pricing__kpi-value">${escapeHtml10(c.value)}</div>
103940
+ <div class="myio-pricing__kpi-label">${escapeHtml10(c.label)}</div>
101458
103941
  </div>`
101459
103942
  ).join("");
101460
103943
  renderReport();
@@ -101473,11 +103956,11 @@ function openPricingPanel(params) {
101473
103956
  reportBody.innerHTML = rows.length ? rows.map(
101474
103957
  (e) => `
101475
103958
  <tr>
101476
- <td>${escapeHtml8(customerTitle(e.customerId))}</td>
101477
- <td>${escapeHtml8(DOMAIN_LABEL[e.domain])}</td>
101478
- <td>${escapeHtml8(CATEGORY_LABEL[e.category])}</td>
101479
- <td>${escapeHtml8(formatPeriodLabel(e))}</td>
101480
- <td>${escapeHtml8(formatBRL2(e.pricePerKwh))} ${escapeHtml8(unitLabel(e.domain))}</td>
103959
+ <td>${escapeHtml10(customerTitle(e.customerId))}</td>
103960
+ <td>${escapeHtml10(DOMAIN_LABEL[e.domain])}</td>
103961
+ <td>${escapeHtml10(CATEGORY_LABEL[e.category])}</td>
103962
+ <td>${escapeHtml10(formatPeriodLabel(e))}</td>
103963
+ <td>${escapeHtml10(formatBRL2(e.pricePerKwh))} ${escapeHtml10(unitLabel(e.domain))}</td>
101481
103964
  </tr>`
101482
103965
  ).join("") : `<tr><td colspan="5" style="text-align:center;color:var(--myio-text-muted,#6b7280);padding:24px;">Nenhum pre\xE7o para o filtro selecionado.</td></tr>`;
101483
103966
  }
@@ -101487,15 +103970,15 @@ function openPricingPanel(params) {
101487
103970
  return `
101488
103971
  <li class="myio-pricing__history-item">
101489
103972
  <div class="myio-pricing__history-head">
101490
- <span class="myio-pricing__action myio-pricing__action--${escapeHtml8(rec.action)}">${escapeHtml8(
103973
+ <span class="myio-pricing__action myio-pricing__action--${escapeHtml10(rec.action)}">${escapeHtml10(
101491
103974
  rec.action
101492
103975
  )}</span>
101493
103976
  ${badge(rec.domain, rec.category)}
101494
- <span>${escapeHtml8(rec.periodLabel)}${escapeHtml8(priceInfo)}</span>
103977
+ <span>${escapeHtml10(rec.periodLabel)}${escapeHtml10(priceInfo)}</span>
101495
103978
  </div>
101496
- <div class="myio-pricing__history-meta">${escapeHtml8(customerTitle(rec.customerId))} \xB7 ${escapeHtml8(
103979
+ <div class="myio-pricing__history-meta">${escapeHtml10(customerTitle(rec.customerId))} \xB7 ${escapeHtml10(
101497
103980
  rec.by || "\u2014"
101498
- )} \xB7 ${escapeHtml8(fmtTimestamp(rec.at))}</div>
103981
+ )} \xB7 ${escapeHtml10(fmtTimestamp(rec.at))}</div>
101499
103982
  </li>`;
101500
103983
  }).join("");
101501
103984
  historyEmpty.style.display = auditLog.length ? "none" : "";
@@ -102194,11 +104677,11 @@ function deviceCategoryToPanelTerm(cat) {
102194
104677
  function panelTermToDeviceCategory(term) {
102195
104678
  return term == null ? null : panelCategoryToWire(term);
102196
104679
  }
102197
- function esc7(s) {
104680
+ function esc8(s) {
102198
104681
  return String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
102199
104682
  }
102200
104683
  function categoryOptionsHTML(selected) {
102201
- const opt = (value, label, isSel) => `<option value="${value}"${isSel ? " selected" : ""}>${esc7(label)}</option>`;
104684
+ const opt = (value, label, isSel) => `<option value="${value}"${isSel ? " selected" : ""}>${esc8(label)}</option>`;
102202
104685
  return opt("", NONE_LABEL, selected == null) + opt("COMMON_AREA", deviceCategoryLabel("COMMON_AREA"), selected === "COMMON_AREA") + opt("SPECIFIC", deviceCategoryLabel("SPECIFIC"), selected === "SPECIFIC");
102203
104686
  }
102204
104687
  function openDeviceCategoryPanel(params) {
@@ -102235,8 +104718,8 @@ function openDeviceCategoryPanel(params) {
102235
104718
  <select class="myio-devcat__filter" data-devcat-filter="1" aria-label="Filtrar por categoria">
102236
104719
  <option value="all">Todas as categorias</option>
102237
104720
  <option value="uncategorized">Sem categoria</option>
102238
- <option value="COMMON_AREA">${esc7(deviceCategoryLabel("COMMON_AREA"))}</option>
102239
- <option value="SPECIFIC">${esc7(deviceCategoryLabel("SPECIFIC"))}</option>
104721
+ <option value="COMMON_AREA">${esc8(deviceCategoryLabel("COMMON_AREA"))}</option>
104722
+ <option value="SPECIFIC">${esc8(deviceCategoryLabel("SPECIFIC"))}</option>
102240
104723
  </select>
102241
104724
  <label class="myio-devcat__filter" style="display:flex;align-items:center;gap:6px;cursor:pointer;">
102242
104725
  <input type="checkbox" data-devcat-selectall="1" /> Selecionar vis\xEDveis
@@ -102245,9 +104728,9 @@ function openDeviceCategoryPanel(params) {
102245
104728
  <div class="myio-devcat__bulkbar" data-devcat-bulkbar="1">
102246
104729
  <span data-devcat-bulkcount="1">0 selecionados</span>
102247
104730
  <select data-devcat-bulkcat="1" aria-label="Categoria para os selecionados">
102248
- <option value="">${esc7(NONE_LABEL)}</option>
102249
- <option value="COMMON_AREA">${esc7(deviceCategoryLabel("COMMON_AREA"))}</option>
102250
- <option value="SPECIFIC">${esc7(deviceCategoryLabel("SPECIFIC"))}</option>
104731
+ <option value="">${esc8(NONE_LABEL)}</option>
104732
+ <option value="COMMON_AREA">${esc8(deviceCategoryLabel("COMMON_AREA"))}</option>
104733
+ <option value="SPECIFIC">${esc8(deviceCategoryLabel("SPECIFIC"))}</option>
102251
104734
  </select>
102252
104735
  <button type="button" class="myio-devcat__bulk-apply" data-devcat-bulkapply="1">Aplicar a selecionados</button>
102253
104736
  </div>
@@ -102277,16 +104760,16 @@ function openDeviceCategoryPanel(params) {
102277
104760
  return rows.filter((r) => matchesFilter(r) && matchesSearch(r));
102278
104761
  }
102279
104762
  function rowHTML(r) {
102280
- const name = esc7(r.label || r.code || r.deviceId);
102281
- const code = r.code ? `<span class="myio-devcat__row-code">${esc7(r.code)}</span>` : "";
104763
+ const name = esc8(r.label || r.code || r.deviceId);
104764
+ const code = r.code ? `<span class="myio-devcat__row-code">${esc8(r.code)}</span>` : "";
102282
104765
  const checked = selected.has(r.deviceId) ? " checked" : "";
102283
104766
  const focused = r.deviceId === params.focusDeviceId ? ' data-focused="1"' : "";
102284
104767
  const catAttr = r.tariffCategory == null ? "null" : r.tariffCategory;
102285
- return `<div class="myio-devcat__row" data-devcat-row="${esc7(r.deviceId)}" data-cat="${catAttr}"${focused}><input type="checkbox" class="myio-devcat__row-check" data-devcat-rowcheck="${esc7(
104768
+ return `<div class="myio-devcat__row" data-devcat-row="${esc8(r.deviceId)}" data-cat="${catAttr}"${focused}><input type="checkbox" class="myio-devcat__row-check" data-devcat-rowcheck="${esc8(
102286
104769
  r.deviceId
102287
- )}"${checked} aria-label="Selecionar ${name}" /><div class="myio-devcat__row-main"><span class="myio-devcat__row-label" title="${name}">${name}</span>` + code + `</div><select class="myio-devcat__row-select" data-devcat-rowselect="${esc7(
104770
+ )}"${checked} aria-label="Selecionar ${name}" /><div class="myio-devcat__row-main"><span class="myio-devcat__row-label" title="${name}">${name}</span>` + code + `</div><select class="myio-devcat__row-select" data-devcat-rowselect="${esc8(
102288
104771
  r.deviceId
102289
- )}" aria-label="Categoria de ${name}">${categoryOptionsHTML(r.tariffCategory)}</select><div class="myio-devcat__row-conflict" data-devcat-conflict="${esc7(r.deviceId)}"></div></div>`;
104772
+ )}" aria-label="Categoria de ${name}">${categoryOptionsHTML(r.tariffCategory)}</select><div class="myio-devcat__row-conflict" data-devcat-conflict="${esc8(r.deviceId)}"></div></div>`;
102290
104773
  }
102291
104774
  function render3() {
102292
104775
  const vis = visibleRows();
@@ -102446,7 +104929,7 @@ function openDeviceCategoryPanel(params) {
102446
104929
  rows = await port.listDevices({ customerId, domain: params.domain });
102447
104930
  } catch (err) {
102448
104931
  rows = [];
102449
- body.innerHTML = `<div class="myio-devcat__empty">N\xE3o foi poss\xEDvel carregar os dispositivos: ${esc7(
104932
+ body.innerHTML = `<div class="myio-devcat__empty">N\xE3o foi poss\xEDvel carregar os dispositivos: ${esc8(
102450
104933
  err?.message || "erro"
102451
104934
  )}</div>`;
102452
104935
  countEl.textContent = "";
@@ -109830,536 +112313,6 @@ function refreshIncidentBadges(opts) {
109830
112313
  });
109831
112314
  }
109832
112315
 
109833
- // src/components/premium-modals/dialog/styles.ts
109834
- var FONT_LINK_ID = "myio-dialog-font-nunito";
109835
- var STYLE_ID9 = "myio-dialog-styles";
109836
- function injectDialogStyles() {
109837
- if (!document.getElementById(FONT_LINK_ID)) {
109838
- const link = document.createElement("link");
109839
- link.id = FONT_LINK_ID;
109840
- link.rel = "stylesheet";
109841
- link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
109842
- document.head.appendChild(link);
109843
- }
109844
- if (document.getElementById(STYLE_ID9)) return;
109845
- const style = document.createElement("style");
109846
- style.id = STYLE_ID9;
109847
- style.textContent = `
109848
- .myio-dialog-overlay {
109849
- position: fixed;
109850
- inset: 0;
109851
- display: flex;
109852
- align-items: center;
109853
- justify-content: center;
109854
- background: rgba(17, 24, 39, 0.55);
109855
- font-family: 'Nunito', system-ui, sans-serif;
109856
- }
109857
- .myio-dialog {
109858
- background: #ffffff;
109859
- color: #1f2937;
109860
- border-radius: 12px;
109861
- box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
109862
- max-width: 90vw;
109863
- max-height: 85vh;
109864
- display: flex;
109865
- flex-direction: column;
109866
- overflow: hidden;
109867
- }
109868
- .myio-dialog__accent {
109869
- height: 4px;
109870
- background: #7C3AED;
109871
- }
109872
- .myio-dialog__header {
109873
- display: flex;
109874
- align-items: flex-start;
109875
- gap: 10px;
109876
- padding: 16px 20px 0 20px;
109877
- }
109878
- .myio-dialog__icon {
109879
- font-size: 20px;
109880
- line-height: 1.3;
109881
- }
109882
- .myio-dialog__title {
109883
- flex: 1;
109884
- margin: 0;
109885
- font-size: 16px;
109886
- font-weight: 700;
109887
- line-height: 1.35;
109888
- }
109889
- .myio-dialog__close {
109890
- border: none;
109891
- background: transparent;
109892
- color: #9ca3af;
109893
- font-size: 18px;
109894
- line-height: 1;
109895
- padding: 2px 6px;
109896
- margin: -2px -8px 0 0;
109897
- border-radius: 6px;
109898
- cursor: pointer;
109899
- }
109900
- .myio-dialog__close:hover {
109901
- background: rgba(0, 0, 0, 0.06);
109902
- color: #4b5563;
109903
- }
109904
- .myio-dialog__message {
109905
- margin: 0;
109906
- padding: 10px 20px 4px 20px;
109907
- font-size: 13.5px;
109908
- line-height: 1.55;
109909
- color: #4b5563;
109910
- overflow-y: auto;
109911
- }
109912
- .myio-dialog__footer {
109913
- display: flex;
109914
- justify-content: flex-end;
109915
- gap: 8px;
109916
- padding: 16px 20px;
109917
- }
109918
- .myio-dialog__btn {
109919
- font-family: inherit;
109920
- font-size: 13px;
109921
- font-weight: 600;
109922
- padding: 8px 16px;
109923
- border-radius: 8px;
109924
- border: 1px solid transparent;
109925
- cursor: pointer;
109926
- transition: filter 0.15s ease, background 0.15s ease;
109927
- }
109928
- .myio-dialog__btn:hover {
109929
- filter: brightness(0.94);
109930
- }
109931
- .myio-dialog__btn:focus-visible {
109932
- outline: 2px solid #7C3AED;
109933
- outline-offset: 2px;
109934
- }
109935
- .myio-dialog__btn--primary {
109936
- background: #7C3AED;
109937
- color: #ffffff;
109938
- }
109939
- .myio-dialog__btn--secondary {
109940
- background: #ffffff;
109941
- color: #374151;
109942
- border-color: #d1d5db;
109943
- }
109944
- .myio-dialog__btn--danger {
109945
- background: #dc2626;
109946
- color: #ffffff;
109947
- }
109948
- .myio-dialog__btn--success {
109949
- background: #16a34a;
109950
- color: #ffffff;
109951
- }
109952
-
109953
- /* Severity accents (message dialog) */
109954
- .myio-dialog--success .myio-dialog__accent { background: #16a34a; }
109955
- .myio-dialog--warning .myio-dialog__accent { background: #d97706; }
109956
- .myio-dialog--error .myio-dialog__accent { background: #dc2626; }
109957
-
109958
- /* \u2500\u2500 Dark theme \u2500\u2500 */
109959
- .myio-dialog-overlay--dark .myio-dialog {
109960
- background: #1f2937;
109961
- color: #f3f4f6;
109962
- }
109963
- .myio-dialog-overlay--dark .myio-dialog__message {
109964
- color: #d1d5db;
109965
- }
109966
- .myio-dialog-overlay--dark .myio-dialog__close {
109967
- color: #9ca3af;
109968
- }
109969
- .myio-dialog-overlay--dark .myio-dialog__close:hover {
109970
- background: rgba(255, 255, 255, 0.08);
109971
- color: #e5e7eb;
109972
- }
109973
- .myio-dialog-overlay--dark .myio-dialog__btn--secondary {
109974
- background: #1f2937;
109975
- color: #e5e7eb;
109976
- border-color: #4b5563;
109977
- }
109978
- `;
109979
- document.head.appendChild(style);
109980
- }
109981
-
109982
- // src/components/premium-modals/dialog/openDialog.ts
109983
- var BASE_Z_INDEX = 10500;
109984
- var openCount = 0;
109985
- function escapeHtml9(value) {
109986
- return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
109987
- }
109988
- function escapeMessage(value) {
109989
- return escapeHtml9(value).replace(/\n/g, "<br>");
109990
- }
109991
- function cssWidth(width) {
109992
- if (width == null) return "420px";
109993
- return typeof width === "number" ? `${width}px` : width;
109994
- }
109995
- var SEVERITY_META = {
109996
- info: { title: "Informa\xE7\xE3o", icon: "\u2139\uFE0F" },
109997
- success: { title: "Sucesso", icon: "\u2705" },
109998
- warning: { title: "Aten\xE7\xE3o", icon: "\u26A0\uFE0F" },
109999
- error: { title: "Erro", icon: "\u274C" }
110000
- };
110001
- function openDialog(params) {
110002
- if (!Array.isArray(params.buttons) || params.buttons.length === 0) {
110003
- throw new Error("[openConfirmDialog] params.buttons must contain at least one button");
110004
- }
110005
- injectDialogStyles();
110006
- const dismissible = params.dismissible ?? true;
110007
- const theme = params.theme ?? "light";
110008
- const idBase = `myio-dialog-${++openCount}`;
110009
- const overlay = document.createElement("div");
110010
- overlay.className = `myio-dialog-overlay${theme === "dark" ? " myio-dialog-overlay--dark" : ""}`;
110011
- overlay.style.zIndex = String(BASE_Z_INDEX + openCount);
110012
- const buttonsHtml = params.buttons.map((btn, i) => {
110013
- const variant = btn.variant ?? "secondary";
110014
- return `<button type="button" class="myio-dialog__btn myio-dialog__btn--${variant}" data-index="${i}">${escapeHtml9(btn.label)}</button>`;
110015
- }).join("");
110016
- overlay.innerHTML = `
110017
- <div class="myio-dialog${params.modifier ? ` ${params.modifier}` : ""}"
110018
- role="dialog" aria-modal="true"
110019
- aria-labelledby="${idBase}-title" aria-describedby="${idBase}-message"
110020
- style="width:${cssWidth(params.width)}">
110021
- <div class="myio-dialog__accent"></div>
110022
- <div class="myio-dialog__header">
110023
- ${params.icon ? `<span class="myio-dialog__icon" aria-hidden="true">${params.icon}</span>` : ""}
110024
- <h3 class="myio-dialog__title" id="${idBase}-title">${escapeHtml9(params.title)}</h3>
110025
- ${dismissible ? `<button type="button" class="myio-dialog__close" aria-label="Fechar">\u2715</button>` : ""}
110026
- </div>
110027
- <p class="myio-dialog__message" id="${idBase}-message">${escapeMessage(params.message)}</p>
110028
- <div class="myio-dialog__footer">${buttonsHtml}</div>
110029
- </div>
110030
- `;
110031
- const previousFocus = document.activeElement;
110032
- const target = params.container ?? document.body;
110033
- target.appendChild(overlay);
110034
- const root = overlay.querySelector(".myio-dialog");
110035
- const buttonEls = Array.from(root.querySelectorAll(".myio-dialog__btn"));
110036
- return new Promise((resolve) => {
110037
- let settled = false;
110038
- let autoCloseTimer;
110039
- const close = (result) => {
110040
- if (settled) return;
110041
- settled = true;
110042
- if (autoCloseTimer) clearTimeout(autoCloseTimer);
110043
- document.removeEventListener("keydown", onKeydown, true);
110044
- overlay.remove();
110045
- if (previousFocus && previousFocus.isConnected) previousFocus.focus();
110046
- resolve(result);
110047
- };
110048
- const onKeydown = (e) => {
110049
- if (!overlay.isConnected) return;
110050
- if (e.key === "Escape" && dismissible) {
110051
- e.stopPropagation();
110052
- close(null);
110053
- return;
110054
- }
110055
- if (e.key === "Tab") {
110056
- const focusables = Array.from(
110057
- root.querySelectorAll("button:not([disabled])")
110058
- );
110059
- if (focusables.length === 0) return;
110060
- const first = focusables[0];
110061
- const last = focusables[focusables.length - 1];
110062
- const active = document.activeElement;
110063
- if (!root.contains(active)) {
110064
- e.preventDefault();
110065
- first.focus();
110066
- } else if (e.shiftKey && active === first) {
110067
- e.preventDefault();
110068
- last.focus();
110069
- } else if (!e.shiftKey && active === last) {
110070
- e.preventDefault();
110071
- first.focus();
110072
- }
110073
- }
110074
- };
110075
- buttonEls.forEach((el2) => {
110076
- el2.addEventListener("click", () => {
110077
- const index = Number(el2.dataset.index);
110078
- close(params.buttons[index].value);
110079
- });
110080
- });
110081
- if (dismissible) {
110082
- overlay.querySelector(".myio-dialog__close")?.addEventListener("click", () => close(null));
110083
- overlay.addEventListener("click", (e) => {
110084
- if (e.target === overlay) close(null);
110085
- });
110086
- }
110087
- document.addEventListener("keydown", onKeydown, true);
110088
- const autoFocusIndex = params.buttons.findIndex((b) => b.autoFocus);
110089
- (buttonEls[autoFocusIndex >= 0 ? autoFocusIndex : buttonEls.length - 1] ?? buttonEls[0]).focus();
110090
- if (params.autoCloseMs && params.autoCloseMs > 0) {
110091
- autoCloseTimer = setTimeout(() => close(null), params.autoCloseMs);
110092
- }
110093
- });
110094
- }
110095
- function openConfirmDialog(params) {
110096
- return openDialog(params);
110097
- }
110098
- function openMessageDialog(params) {
110099
- const severity = params.severity ?? "info";
110100
- const meta = SEVERITY_META[severity];
110101
- return openDialog({
110102
- title: params.title ?? meta.title,
110103
- message: params.message,
110104
- buttons: [
110105
- { label: params.buttonLabel ?? "OK", variant: "primary", value: "ok", autoFocus: true }
110106
- ],
110107
- theme: params.theme,
110108
- container: params.container,
110109
- modifier: severity !== "info" ? `myio-dialog--${severity}` : void 0,
110110
- icon: meta.icon,
110111
- autoCloseMs: params.autoCloseMs
110112
- }).then(() => void 0);
110113
- }
110114
-
110115
- // src/components/premium-modals/dialog/openGenericModal.ts
110116
- var STYLE_ID10 = "myio-genmodal-styles";
110117
- var FONT_LINK_ID2 = "myio-dialog-font-nunito";
110118
- var BASE_Z_INDEX2 = 10600;
110119
- var openCount2 = 0;
110120
- function escapeHtml10(value) {
110121
- return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
110122
- }
110123
- function cssSize(v, fallback) {
110124
- if (v == null) return fallback;
110125
- return typeof v === "number" ? `${v}px` : v;
110126
- }
110127
- function injectGenericModalStyles() {
110128
- if (!document.getElementById(FONT_LINK_ID2)) {
110129
- const link = document.createElement("link");
110130
- link.id = FONT_LINK_ID2;
110131
- link.rel = "stylesheet";
110132
- link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
110133
- document.head.appendChild(link);
110134
- }
110135
- if (document.getElementById(STYLE_ID10)) return;
110136
- const style = document.createElement("style");
110137
- style.id = STYLE_ID10;
110138
- style.textContent = `
110139
- .myio-genmodal-overlay {
110140
- position: fixed;
110141
- inset: 0;
110142
- display: flex;
110143
- align-items: center;
110144
- justify-content: center;
110145
- background: rgba(17, 24, 39, 0.55);
110146
- font-family: 'Nunito', system-ui, sans-serif;
110147
- }
110148
- .myio-genmodal {
110149
- background: #ffffff;
110150
- color: #1f2937;
110151
- border-radius: 12px;
110152
- box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
110153
- max-width: 90vw;
110154
- max-height: 90vh;
110155
- display: flex;
110156
- flex-direction: column;
110157
- overflow: hidden;
110158
- }
110159
- .myio-genmodal__accent { height: 4px; background: #7C3AED; flex-shrink: 0; }
110160
- .myio-genmodal__header {
110161
- display: flex;
110162
- align-items: flex-start;
110163
- gap: 10px;
110164
- padding: 16px 20px;
110165
- border-bottom: 1px solid #eef0f3;
110166
- flex-shrink: 0;
110167
- }
110168
- .myio-genmodal__icon { font-size: 22px; line-height: 1.3; }
110169
- .myio-genmodal__title {
110170
- flex: 1;
110171
- margin: 0;
110172
- font-size: 18px;
110173
- font-weight: 700;
110174
- line-height: 1.35;
110175
- }
110176
- .myio-genmodal__close {
110177
- border: none;
110178
- background: transparent;
110179
- color: #9ca3af;
110180
- font-size: 18px;
110181
- line-height: 1;
110182
- padding: 2px 6px;
110183
- margin: -2px -8px 0 0;
110184
- border-radius: 6px;
110185
- cursor: pointer;
110186
- }
110187
- .myio-genmodal__close:hover { background: rgba(0, 0, 0, 0.06); color: #4b5563; }
110188
- .myio-genmodal__body {
110189
- padding: 18px 20px;
110190
- font-size: 15px;
110191
- line-height: 1.6;
110192
- color: #374151;
110193
- overflow-y: auto;
110194
- }
110195
- /* Sensible defaults for arbitrary body HTML */
110196
- .myio-genmodal__body img { max-width: 100%; height: auto; border-radius: 8px; }
110197
- .myio-genmodal__body hr { border: none; border-top: 1px solid #e5e7eb; margin: 16px 0; }
110198
- .myio-genmodal__body h1, .myio-genmodal__body h2, .myio-genmodal__body h3, .myio-genmodal__body h4 {
110199
- margin: 0 0 8px; line-height: 1.3; color: #1f2937;
110200
- }
110201
- .myio-genmodal__body p { margin: 0 0 10px; }
110202
- .myio-genmodal__body ul, .myio-genmodal__body ol { margin: 0 0 10px; padding-left: 22px; }
110203
- .myio-genmodal__body table { border-collapse: collapse; width: 100%; }
110204
- .myio-genmodal__body th, .myio-genmodal__body td {
110205
- border: 1px solid #e5e7eb; padding: 6px 10px; text-align: left; font-size: 14px;
110206
- }
110207
- .myio-genmodal__body a { color: #7C3AED; }
110208
- .myio-genmodal__footer {
110209
- display: flex;
110210
- justify-content: flex-end;
110211
- gap: 8px;
110212
- padding: 14px 20px;
110213
- border-top: 1px solid #eef0f3;
110214
- flex-shrink: 0;
110215
- }
110216
- .myio-genmodal__btn {
110217
- font-family: inherit;
110218
- font-size: 13px;
110219
- font-weight: 600;
110220
- padding: 8px 16px;
110221
- border-radius: 8px;
110222
- border: 1px solid transparent;
110223
- cursor: pointer;
110224
- transition: filter 0.15s ease, background 0.15s ease;
110225
- }
110226
- .myio-genmodal__btn:hover { filter: brightness(0.94); }
110227
- .myio-genmodal__btn:focus-visible { outline: 2px solid #7C3AED; outline-offset: 2px; }
110228
- .myio-genmodal__btn--primary { background: #7C3AED; color: #ffffff; }
110229
- .myio-genmodal__btn--secondary { background: #ffffff; color: #374151; border-color: #d1d5db; }
110230
- .myio-genmodal__btn--danger { background: #dc2626; color: #ffffff; }
110231
- .myio-genmodal__btn--success { background: #16a34a; color: #ffffff; }
110232
-
110233
- /* \u2500\u2500 Dark theme \u2500\u2500 */
110234
- .myio-genmodal-overlay--dark .myio-genmodal { background: #1f2937; color: #f3f4f6; }
110235
- .myio-genmodal-overlay--dark .myio-genmodal__header,
110236
- .myio-genmodal-overlay--dark .myio-genmodal__footer { border-color: #374151; }
110237
- .myio-genmodal-overlay--dark .myio-genmodal__title { color: #f3f4f6; }
110238
- .myio-genmodal-overlay--dark .myio-genmodal__body { color: #d1d5db; }
110239
- .myio-genmodal-overlay--dark .myio-genmodal__body h1,
110240
- .myio-genmodal-overlay--dark .myio-genmodal__body h2,
110241
- .myio-genmodal-overlay--dark .myio-genmodal__body h3,
110242
- .myio-genmodal-overlay--dark .myio-genmodal__body h4 { color: #f3f4f6; }
110243
- .myio-genmodal-overlay--dark .myio-genmodal__body hr { border-color: #374151; }
110244
- .myio-genmodal-overlay--dark .myio-genmodal__body th,
110245
- .myio-genmodal-overlay--dark .myio-genmodal__body td { border-color: #374151; }
110246
- .myio-genmodal-overlay--dark .myio-genmodal__close { color: #9ca3af; }
110247
- .myio-genmodal-overlay--dark .myio-genmodal__close:hover { background: rgba(255,255,255,0.08); color: #e5e7eb; }
110248
- .myio-genmodal-overlay--dark .myio-genmodal__btn--secondary { background: #1f2937; color: #e5e7eb; border-color: #4b5563; }
110249
- `;
110250
- document.head.appendChild(style);
110251
- }
110252
- var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
110253
- function openGenericModal(params) {
110254
- if (typeof window === "undefined" || typeof document === "undefined") {
110255
- throw new Error("[openGenericModal] requires a browser environment");
110256
- }
110257
- if (typeof params.bodyHtml !== "string") {
110258
- throw new Error("[openGenericModal] params.bodyHtml must be a string");
110259
- }
110260
- injectGenericModalStyles();
110261
- const dismissible = params.dismissible ?? true;
110262
- const theme = params.theme ?? "light";
110263
- const buttons = params.buttons ?? [];
110264
- const idBase = `myio-genmodal-${++openCount2}`;
110265
- const overlay = document.createElement("div");
110266
- overlay.className = `myio-genmodal-overlay${theme === "dark" ? " myio-genmodal-overlay--dark" : ""}`;
110267
- overlay.style.zIndex = String(BASE_Z_INDEX2 + openCount2);
110268
- const buttonsHtml = buttons.map((btn, i) => {
110269
- const variant = btn.variant ?? "secondary";
110270
- return `<button type="button" class="myio-genmodal__btn myio-genmodal__btn--${variant}" data-index="${i}">${escapeHtml10(btn.label)}</button>`;
110271
- }).join("");
110272
- overlay.innerHTML = `
110273
- <div class="myio-genmodal${params.className ? ` ${escapeHtml10(params.className)}` : ""}"
110274
- role="dialog" aria-modal="true"
110275
- aria-labelledby="${idBase}-title"
110276
- style="width:${cssSize(params.width, "560px")}">
110277
- <div class="myio-genmodal__accent"></div>
110278
- <div class="myio-genmodal__header">
110279
- ${params.icon ? `<span class="myio-genmodal__icon" aria-hidden="true">${params.icon}</span>` : ""}
110280
- <h3 class="myio-genmodal__title" id="${idBase}-title">${escapeHtml10(params.title)}</h3>
110281
- ${dismissible ? `<button type="button" class="myio-genmodal__close" aria-label="Fechar">\u2715</button>` : ""}
110282
- </div>
110283
- <div class="myio-genmodal__body" id="${idBase}-body" style="max-height:${cssSize(params.bodyMaxHeight, "70vh")}"></div>
110284
- ${buttons.length ? `<div class="myio-genmodal__footer">${buttonsHtml}</div>` : ""}
110285
- </div>
110286
- `;
110287
- const root = overlay.querySelector(".myio-genmodal");
110288
- const bodyEl = root.querySelector(".myio-genmodal__body");
110289
- const titleEl = root.querySelector(".myio-genmodal__title");
110290
- bodyEl.innerHTML = params.bodyHtml;
110291
- const previousFocus = document.activeElement;
110292
- const target = params.container ?? document.body;
110293
- target.appendChild(overlay);
110294
- let closed = false;
110295
- const close = () => {
110296
- if (closed) return;
110297
- closed = true;
110298
- document.removeEventListener("keydown", onKeydown, true);
110299
- overlay.remove();
110300
- if (previousFocus && previousFocus.isConnected) previousFocus.focus();
110301
- if (params.onClose) params.onClose();
110302
- };
110303
- const onKeydown = (e) => {
110304
- if (!overlay.isConnected) return;
110305
- if (e.key === "Escape" && dismissible) {
110306
- e.stopPropagation();
110307
- close();
110308
- return;
110309
- }
110310
- if (e.key === "Tab") {
110311
- const focusables = Array.from(root.querySelectorAll(FOCUSABLE));
110312
- if (focusables.length === 0) {
110313
- e.preventDefault();
110314
- return;
110315
- }
110316
- const first = focusables[0];
110317
- const last = focusables[focusables.length - 1];
110318
- const active = document.activeElement;
110319
- if (!root.contains(active)) {
110320
- e.preventDefault();
110321
- first.focus();
110322
- } else if (e.shiftKey && active === first) {
110323
- e.preventDefault();
110324
- last.focus();
110325
- } else if (!e.shiftKey && active === last) {
110326
- e.preventDefault();
110327
- first.focus();
110328
- }
110329
- }
110330
- };
110331
- root.querySelectorAll(".myio-genmodal__btn").forEach((el2) => {
110332
- el2.addEventListener("click", () => {
110333
- const btn = buttons[Number(el2.dataset.index)];
110334
- if (!btn) return;
110335
- if (params.onButton) params.onButton(btn.value);
110336
- if (btn.closeOnClick !== false) close();
110337
- });
110338
- });
110339
- if (dismissible) {
110340
- overlay.querySelector(".myio-genmodal__close")?.addEventListener("click", () => close());
110341
- overlay.addEventListener("click", (e) => {
110342
- if (e.target === overlay) close();
110343
- });
110344
- }
110345
- document.addEventListener("keydown", onKeydown, true);
110346
- const autoFocusIdx = buttons.findIndex((b) => b.autoFocus);
110347
- const footerEls = Array.from(root.querySelectorAll(".myio-genmodal__btn"));
110348
- const initialFocus = (autoFocusIdx >= 0 ? footerEls[autoFocusIdx] : void 0) ?? footerEls[footerEls.length - 1] ?? root.querySelector(`.myio-genmodal__body ${FOCUSABLE}`) ?? overlay.querySelector(".myio-genmodal__close") ?? root;
110349
- initialFocus.focus?.();
110350
- return {
110351
- close,
110352
- setBodyHtml: (html) => {
110353
- bodyEl.innerHTML = html;
110354
- },
110355
- setTitle: (title) => {
110356
- titleEl.textContent = title;
110357
- },
110358
- getBodyEl: () => bodyEl,
110359
- getRoot: () => root
110360
- };
110361
- }
110362
-
110363
112316
  // src/components/myio-gcdr-login/dom.ts
110364
112317
  function h2(doc, tag, attrs = {}, ...children) {
110365
112318
  const el2 = doc.createElement(tag);
@@ -131601,7 +133554,7 @@ var HeaderShoppingView = class {
131601
133554
  tip.id = "tbx-global-tooltip";
131602
133555
  document.body.appendChild(tip);
131603
133556
  }
131604
- const pad = 10;
133557
+ const pad3 = 10;
131605
133558
  const position = (ev) => {
131606
133559
  if (!tip) return;
131607
133560
  tip.textContent = text2;
@@ -131609,8 +133562,8 @@ var HeaderShoppingView = class {
131609
133562
  let y = ev.clientY - 36;
131610
133563
  const vw = window.innerWidth;
131611
133564
  const rect = tip.getBoundingClientRect();
131612
- if (x + rect.width + pad > vw) x = vw - rect.width - pad;
131613
- if (y < pad) y = ev.clientY + 18;
133565
+ if (x + rect.width + pad3 > vw) x = vw - rect.width - pad3;
133566
+ if (y < pad3) y = ev.clientY + 18;
131614
133567
  tip.style.left = x + "px";
131615
133568
  tip.style.top = y + "px";
131616
133569
  };
@@ -144755,10 +146708,10 @@ var AlarmsNotificationsPanelView = class {
144755
146708
  const renderListPanel = (group, items, sel) => {
144756
146709
  if (items.length === 0) return '<div class="afm-empty">Nenhum item encontrado</div>';
144757
146710
  return items.map((item) => {
144758
- const esc8 = this.esc(item);
146711
+ const esc9 = this.esc(item);
144759
146712
  return `<label class="afm-check-item">
144760
- <input type="checkbox" class="afm-checkbox" data-group="${group}" data-value="${esc8}"${sel.has(item) ? " checked" : ""}>
144761
- <span class="afm-check-label">${esc8}</span>
146713
+ <input type="checkbox" class="afm-checkbox" data-group="${group}" data-value="${esc9}"${sel.has(item) ? " checked" : ""}>
146714
+ <span class="afm-check-label">${esc9}</span>
144762
146715
  </label>`;
144763
146716
  }).join("");
144764
146717
  };
@@ -157636,7 +159589,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
157636
159589
  const docTitle = buildActivationReportTitle(deviceName, opts.customerName);
157637
159590
  const issuedAt = formatIssuedAt(/* @__PURE__ */ new Date(), locale);
157638
159591
  const utilization = data.totalHours > 0 ? (data.totalOnMinutes / (data.totalHours * 60) * 100).toFixed(1) : "0.0";
157639
- const esc8 = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
159592
+ const esc9 = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
157640
159593
  const htmlContent = `
157641
159594
  <!DOCTYPE html>
157642
159595
  <html lang="pt-BR">
@@ -157688,14 +159641,14 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
157688
159641
  <div class="brand">MyIO BAS</div>
157689
159642
  <h1>Relat\xF3rio de Acionamentos</h1>
157690
159643
  <div class="meta">
157691
- <div><b>Dispositivo:</b> ${esc8(deviceName)}</div>
159644
+ <div><b>Dispositivo:</b> ${esc9(deviceName)}</div>
157692
159645
  <div><b>Per\xEDodo:</b> ${periodStart} \u2014 ${periodEnd}</div>
157693
159646
  </div>
157694
159647
  </div>
157695
159648
  ${customerName ? `
157696
159649
  <div class="customer-badge">
157697
159650
  <div class="label">Cliente</div>
157698
- <div class="value">${esc8(customerName)}</div>
159651
+ <div class="value">${esc9(customerName)}</div>
157699
159652
  </div>` : ""}
157700
159653
  </div>
157701
159654
 
@@ -157706,7 +159659,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
157706
159659
  </div>
157707
159660
  <div class="summary-card accent-green">
157708
159661
  <div class="summary-value">${formatDurationMinutes(data.totalOnMinutes)}</div>
157709
- <div class="summary-label">Tempo ${esc8(labelOn)}</div>
159662
+ <div class="summary-label">Tempo ${esc9(labelOn)}</div>
157710
159663
  </div>
157711
159664
  <div class="summary-card accent-amber">
157712
159665
  <div class="summary-value">${utilization}%</div>
@@ -157734,7 +159687,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
157734
159687
  <tr>
157735
159688
  <td>${formatDateForExport(seg.startTime, locale)}</td>
157736
159689
  <td>${formatDateForExport(seg.endTime, locale)}</td>
157737
- <td><span class="pill ${seg.state === "on" ? "pill-on" : "pill-off"}">${seg.state === "on" ? esc8(labelOn) : esc8(labelOff)}</span></td>
159690
+ <td><span class="pill ${seg.state === "on" ? "pill-on" : "pill-off"}">${seg.state === "on" ? esc9(labelOn) : esc9(labelOff)}</span></td>
157738
159691
  <td>${formatDurationMinutes(seg.durationMinutes)}</td>
157739
159692
  <td>${seg.source === "manual" ? "Manual" : seg.source === "schedule" ? "Agendamento" : seg.source === "automation" ? "Automa\xE7\xE3o" : "Desconhecido"}</td>
157740
159693
  </tr>
@@ -157743,7 +159696,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
157743
159696
  </table>
157744
159697
 
157745
159698
  <div class="footer">
157746
- <span class="issued">Emitido em ${issuedAt}</span>${customerName ? ` &middot; ${esc8(customerName)}` : ""} &middot; <span class="brand">MyIO BAS</span>
159699
+ <span class="issued">Emitido em ${issuedAt}</span>${customerName ? ` &middot; ${esc9(customerName)}` : ""} &middot; <span class="brand">MyIO BAS</span>
157747
159700
  </div>
157748
159701
  </body>
157749
159702
  </html>
@@ -165771,9 +167724,9 @@ async function drawTag(doc, x, y, w, h3, device, gateway, allDevices) {
165771
167724
  doc.setDrawColor(235);
165772
167725
  doc.setLineWidth(0.15);
165773
167726
  doc.rect(x, frameY, w, frameH);
165774
- const pad = Math.max(2, h3 * 0.05);
165775
- const innerX = x + pad;
165776
- const innerW = w - pad * 2;
167727
+ const pad3 = Math.max(2, h3 * 0.05);
167728
+ const innerX = x + pad3;
167729
+ const innerW = w - pad3 * 2;
165777
167730
  const titleAreaH = h3 * 0.15;
165778
167731
  const qrAreaTop = y + titleAreaH;
165779
167732
  const qrAreaH = h3 - titleAreaH - 12;
@@ -172812,6 +174765,7 @@ var version = package_default.version || "0.0.0";
172812
174765
  createFreshdeskTicket,
172813
174766
  createGcdrAnnotationsAuthStrategy,
172814
174767
  createGcdrAnnotationsClient,
174768
+ createGcdrReportJobsApi,
172815
174769
  createGoalsBarTooltip,
172816
174770
  createGoalsMoneyClient,
172817
174771
  createGranularitySelector,
@@ -172844,6 +174798,7 @@ var version = package_default.version || "0.0.0";
172844
174798
  createPdfButton,
172845
174799
  createPremiumModalChrome,
172846
174800
  createPresetupGateway,
174801
+ createProfileClassifier,
172847
174802
  createRealTimeClock,
172848
174803
  createReportMoneyColumn,
172849
174804
  createScheduleCard,
@@ -172857,6 +174812,7 @@ var version = package_default.version || "0.0.0";
172857
174812
  createSwitchControl,
172858
174813
  createTariffApiAdapter,
172859
174814
  createTariffApiClient,
174815
+ createTbEnvelopeStore,
172860
174816
  createTelemetryGridComponent,
172861
174817
  createTelemetryGridShoppingComponent,
172862
174818
  createTelemetryInfoShoppingComponent,
@@ -172873,6 +174829,8 @@ var version = package_default.version || "0.0.0";
172873
174829
  decodePayload,
172874
174830
  decodePayloadBase64Xor,
172875
174831
  deleteFreshdeskTicket,
174832
+ demandDevicesFromItems,
174833
+ demandReport,
172876
174834
  deriveCentralConnectivity,
172877
174835
  detectContext,
172878
174836
  detectDeviceType,
@@ -173127,6 +175085,7 @@ var version = package_default.version || "0.0.0";
173127
175085
  openDashboardPopupSettings,
173128
175086
  openDashboardPopupWaterTank,
173129
175087
  openDemandModal,
175088
+ openDemandPanelV2,
173130
175089
  openDeviceCategoryPanel,
173131
175090
  openDeviceProfileModal,
173132
175091
  openGCDRSyncModal,
@@ -173236,6 +175195,7 @@ var version = package_default.version || "0.0.0";
173236
175195
  summaryDeviceToTotalsRow,
173237
175196
  summaryLastReadingTs,
173238
175197
  summaryLookbackFromIso,
175198
+ tbUserFromJwt,
173239
175199
  telemetryInfoFormatEnergy,
173240
175200
  telemetryInfoFormatPercentage,
173241
175201
  telemetryInfoFormatWater,