myio-js-library 0.1.560 → 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.560",
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
  /**
@@ -68307,7 +68314,19 @@ var WelcomeModalView = class _WelcomeModalView {
68307
68314
  shoppingCardBg: themeConfig.shoppingCardBackgroundColor,
68308
68315
  shoppingCardBorder: themeConfig.shoppingCardBorderColor
68309
68316
  };
68310
- return { ...DEFAULT_PALETTE, ...configPalette, ...this.params.palette };
68317
+ return { ...DEFAULT_PALETTE, ...configPalette, ...this.readDashboardBrand(), ...this.params.palette };
68318
+ }
68319
+ /**
68320
+ * Paleta do tema do dashboard (createMyIOTheme publica --myio-brand-700/600 no :root).
68321
+ * Quando presente, substitui o roxo padrão da modal; `params.palette` explícito ainda prevalece.
68322
+ */
68323
+ readDashboardBrand() {
68324
+ if (typeof document === "undefined" || typeof getComputedStyle !== "function") return {};
68325
+ const cs = getComputedStyle(document.documentElement);
68326
+ const brand700 = cs.getPropertyValue("--myio-brand-700").trim();
68327
+ if (!brand700) return {};
68328
+ const brand600 = cs.getPropertyValue("--myio-brand-600").trim();
68329
+ return { primary: brand700, secondary: brand600 || brand700 };
68311
68330
  }
68312
68331
  /**
68313
68332
  * Set theme mode and update UI
@@ -68684,9 +68703,9 @@ var WelcomeModalView = class _WelcomeModalView {
68684
68703
  background: linear-gradient(135deg, #0a0f1a 0%, #1a1f2e 50%, #0d1117 100%);
68685
68704
  border-radius: 14px;
68686
68705
  box-shadow:
68687
- 0 0 0 1px rgba(122, 47, 247, 0.2),
68706
+ 0 0 0 1px color-mix(in srgb, var(--wm-primary) 20%, transparent),
68688
68707
  0 25px 80px rgba(0, 0, 0, 0.5),
68689
- 0 0 60px rgba(122, 47, 247, 0.15);
68708
+ 0 0 60px color-mix(in srgb, var(--wm-primary) 15%, transparent);
68690
68709
  overflow: hidden;
68691
68710
  /* Ensure flex children can handle their own overflow */
68692
68711
  min-height: 0;
@@ -68703,7 +68722,7 @@ var WelcomeModalView = class _WelcomeModalView {
68703
68722
  padding: 16px 32px 8px 32px;
68704
68723
  background:
68705
68724
  linear-gradient(180deg,
68706
- rgba(122, 47, 247, 0.25) 0%,
68725
+ color-mix(in srgb, var(--wm-primary) 25%, transparent) 0%,
68707
68726
  var(--wm-gradient-start) 20%,
68708
68727
  var(--wm-gradient-end) 80%,
68709
68728
  rgba(13, 17, 23, 0.95) 100%
@@ -68712,7 +68731,7 @@ var WelcomeModalView = class _WelcomeModalView {
68712
68731
  background-size: cover;
68713
68732
  background-position: center;
68714
68733
  min-height: 22vh;
68715
- border-bottom: 1px solid rgba(122, 47, 247, 0.2);
68734
+ border-bottom: 1px solid color-mix(in srgb, var(--wm-primary) 20%, transparent);
68716
68735
  }
68717
68736
 
68718
68737
  /* Brand Logo - Large floating */
@@ -68906,15 +68925,15 @@ var WelcomeModalView = class _WelcomeModalView {
68906
68925
  cursor: pointer;
68907
68926
  transition: all 0.3s ease;
68908
68927
  box-shadow:
68909
- 0 4px 20px rgba(122, 47, 247, 0.4),
68910
- 0 0 0 0 rgba(122, 47, 247, 0);
68928
+ 0 4px 20px color-mix(in srgb, var(--wm-primary) 40%, transparent),
68929
+ 0 0 0 0 color-mix(in srgb, var(--wm-primary) 0%, transparent);
68911
68930
  }
68912
68931
 
68913
68932
  .myio-welcome-cta-btn:hover {
68914
68933
  transform: translateY(-3px);
68915
68934
  box-shadow:
68916
- 0 8px 30px rgba(122, 47, 247, 0.5),
68917
- 0 0 0 4px rgba(122, 47, 247, 0.2);
68935
+ 0 8px 30px color-mix(in srgb, var(--wm-primary) 50%, transparent),
68936
+ 0 0 0 4px color-mix(in srgb, var(--wm-primary) 20%, transparent);
68918
68937
  }
68919
68938
 
68920
68939
  .myio-welcome-cta-btn:active {
@@ -68972,7 +68991,7 @@ var WelcomeModalView = class _WelcomeModalView {
68972
68991
  .myio-welcome-search:focus-within {
68973
68992
  background: rgba(255,255,255,0.12);
68974
68993
  border-color: var(--wm-primary);
68975
- box-shadow: 0 0 0 2px rgba(122, 47, 247, 0.2);
68994
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--wm-primary) 20%, transparent);
68976
68995
  }
68977
68996
 
68978
68997
  .myio-welcome-search-icon {
@@ -69193,17 +69212,17 @@ var WelcomeModalView = class _WelcomeModalView {
69193
69212
  /* Hover effects only for clickable cards */
69194
69213
  .myio-welcome-card--clickable:hover {
69195
69214
  transform: scale(1.03);
69196
- border-color: rgba(122, 47, 247, 0.5);
69215
+ border-color: color-mix(in srgb, var(--wm-primary) 50%, transparent);
69197
69216
  box-shadow:
69198
69217
  0 12px 40px rgba(0,0,0,0.4),
69199
- 0 0 0 1px rgba(122, 47, 247, 0.4),
69200
- 0 0 30px rgba(122, 47, 247, 0.15);
69218
+ 0 0 0 1px color-mix(in srgb, var(--wm-primary) 40%, transparent),
69219
+ 0 0 30px color-mix(in srgb, var(--wm-primary) 15%, transparent);
69201
69220
  }
69202
69221
 
69203
69222
  .myio-welcome-card--clickable:focus {
69204
69223
  outline: none;
69205
69224
  box-shadow:
69206
- 0 0 0 3px rgba(122, 47, 247, 0.5),
69225
+ 0 0 0 3px color-mix(in srgb, var(--wm-primary) 50%, transparent),
69207
69226
  0 12px 40px rgba(0,0,0,0.4);
69208
69227
  }
69209
69228
 
@@ -69365,7 +69384,7 @@ var WelcomeModalView = class _WelcomeModalView {
69365
69384
  .myio-welcome-card-device-count.energy .count-spinner { border-top-color: #22c55e; }
69366
69385
  .myio-welcome-card-device-count.water .count-spinner { border-top-color: #3b82f6; }
69367
69386
  .myio-welcome-card-device-count.temperature .count-spinner { border-top-color: #f97316; }
69368
- .myio-welcome-card-device-count.users .count-spinner { border-top-color: #a78bfa; }
69387
+ .myio-welcome-card-device-count.users .count-spinner { border-top-color: color-mix(in srgb, var(--wm-primary) 55%, white); }
69369
69388
  .myio-welcome-card-device-count.alarms .count-spinner { border-top-color: #ef4444; }
69370
69389
  .myio-welcome-card-device-count.annotations .count-spinner { border-top-color: #6c5ce7; }
69371
69390
  .myio-welcome-card-device-count.notifications .count-spinner { border-top-color: #f59e0b; }
@@ -69390,7 +69409,7 @@ var WelcomeModalView = class _WelcomeModalView {
69390
69409
  height: 100%;
69391
69410
  width: 0%;
69392
69411
  border-radius: inherit;
69393
- background: linear-gradient(90deg, #7c3aed, #22c55e);
69412
+ background: linear-gradient(90deg, var(--wm-primary), #22c55e);
69394
69413
  background-size: 200% 100%;
69395
69414
  animation: welcome-progress-shimmer 1.6s ease infinite;
69396
69415
  transition: width 0.4s ease;
@@ -69424,7 +69443,7 @@ var WelcomeModalView = class _WelcomeModalView {
69424
69443
  height: 100%;
69425
69444
  width: 0%;
69426
69445
  border-radius: inherit;
69427
- background: linear-gradient(90deg, #7c3aed, #a78bfa, #22c55e);
69446
+ background: linear-gradient(90deg, var(--wm-primary), color-mix(in srgb, var(--wm-primary) 55%, white), #22c55e);
69428
69447
  background-size: 200% 100%;
69429
69448
  animation: welcome-progress-shimmer 1.6s ease infinite;
69430
69449
  transition: width 0.4s ease;
@@ -69761,13 +69780,13 @@ var WelcomeModalView = class _WelcomeModalView {
69761
69780
  display: flex !important;
69762
69781
  align-items: center !important;
69763
69782
  justify-content: center !important;
69764
- gap: 6px !important;
69783
+ gap: 8px !important;
69765
69784
  }
69766
69785
 
69767
69786
  /* Extend device-count styles for meta types */
69768
69787
  .myio-welcome-card-device-count.users:hover {
69769
- background: rgba(124, 58, 237, 0.3);
69770
- border-color: rgba(124, 58, 237, 0.5);
69788
+ background: color-mix(in srgb, var(--wm-primary) 30%, transparent);
69789
+ border-color: color-mix(in srgb, var(--wm-primary) 50%, transparent);
69771
69790
  }
69772
69791
 
69773
69792
  .myio-welcome-card-device-count.alarms:hover {
@@ -69780,7 +69799,7 @@ var WelcomeModalView = class _WelcomeModalView {
69780
69799
  border-color: rgba(234, 179, 8, 0.5);
69781
69800
  }
69782
69801
 
69783
- .myio-welcome-card-device-count.users .icon { color: #a78bfa; }
69802
+ .myio-welcome-card-device-count.users .icon { color: color-mix(in srgb, var(--wm-primary) 55%, white); }
69784
69803
  .myio-welcome-card-device-count.alarms .icon { color: #f87171; }
69785
69804
  .myio-welcome-card-device-count.notifications .icon { color: #fbbf24; }
69786
69805
 
@@ -69802,21 +69821,21 @@ var WelcomeModalView = class _WelcomeModalView {
69802
69821
  .myio-welcome-modal--light .myio-welcome-modal-container {
69803
69822
  background: linear-gradient(135deg, #f8f9fc 0%, #ffffff 50%, #f0f2f5 100%);
69804
69823
  box-shadow:
69805
- 0 0 0 1px rgba(122, 47, 247, 0.15),
69824
+ 0 0 0 1px color-mix(in srgb, var(--wm-primary) 15%, transparent),
69806
69825
  0 25px 80px rgba(0, 0, 0, 0.2),
69807
- 0 0 60px rgba(122, 47, 247, 0.1);
69826
+ 0 0 60px color-mix(in srgb, var(--wm-primary) 10%, transparent);
69808
69827
  }
69809
69828
 
69810
69829
  .myio-welcome-modal--light .myio-welcome-hero {
69811
69830
  background:
69812
69831
  linear-gradient(180deg,
69813
- rgba(122, 47, 247, 0.12) 0%,
69832
+ color-mix(in srgb, var(--wm-primary) 12%, transparent) 0%,
69814
69833
  rgba(255, 255, 255, 0.7) 20%,
69815
69834
  rgba(255, 255, 255, 0.85) 80%,
69816
69835
  rgba(248, 249, 252, 0.95) 100%
69817
69836
  ),
69818
69837
  var(--wm-bg-image, none);
69819
- border-bottom: 1px solid rgba(122, 47, 247, 0.15);
69838
+ border-bottom: 1px solid color-mix(in srgb, var(--wm-primary) 15%, transparent);
69820
69839
  }
69821
69840
 
69822
69841
  .myio-welcome-modal--light .myio-welcome-hero-title {
@@ -69906,11 +69925,11 @@ var WelcomeModalView = class _WelcomeModalView {
69906
69925
  }
69907
69926
 
69908
69927
  .myio-welcome-modal--light .myio-welcome-card:hover {
69909
- border-color: rgba(122, 47, 247, 0.4);
69928
+ border-color: color-mix(in srgb, var(--wm-primary) 40%, transparent);
69910
69929
  box-shadow:
69911
69930
  0 12px 40px rgba(0, 0, 0, 0.12),
69912
- 0 0 0 1px rgba(122, 47, 247, 0.3),
69913
- 0 0 30px rgba(122, 47, 247, 0.1);
69931
+ 0 0 0 1px color-mix(in srgb, var(--wm-primary) 30%, transparent),
69932
+ 0 0 30px color-mix(in srgb, var(--wm-primary) 10%, transparent);
69914
69933
  }
69915
69934
 
69916
69935
  .myio-welcome-modal--light .myio-welcome-card::before {
@@ -69998,8 +70017,8 @@ var WelcomeModalView = class _WelcomeModalView {
69998
70017
 
69999
70018
  /* Light theme - meta counts use same device-count light styles */
70000
70019
  .myio-welcome-modal--light .myio-welcome-card-device-count.users:hover {
70001
- background: rgba(124, 58, 237, 0.15);
70002
- border-color: rgba(124, 58, 237, 0.4);
70020
+ background: color-mix(in srgb, var(--wm-primary) 15%, transparent);
70021
+ border-color: color-mix(in srgb, var(--wm-primary) 40%, transparent);
70003
70022
  }
70004
70023
 
70005
70024
  .myio-welcome-modal--light .myio-welcome-card-device-count.alarms:hover {
@@ -70193,13 +70212,13 @@ var WelcomeModalView = class _WelcomeModalView {
70193
70212
  buildCardHTML(card, index) {
70194
70213
  const bgImage = card.bgImageUrl ? `<div class="myio-welcome-card-bg" data-src="${card.bgImageUrl}"></div>` : "";
70195
70214
  const meta = card.metaCounts || {};
70196
- const renderMetaCount = (count) => count === null ? '<span class="count-spinner"></span>' : String(count ?? 0);
70215
+ const renderMetaCount = (count) => count === null ? '<span class="count-spinner"></span>' : `<span class="count">${count ?? 0}</span>`;
70197
70216
  const thirdBadgeHTML = meta.annotationsLocked ? `<span class="myio-welcome-card-device-count annotations locked"
70198
70217
  data-tooltip-type="annotations-locked"
70199
70218
  data-card-index="${index}"
70200
70219
  title="Anota\xE7\xF5es \u2014 funcionalidade n\xE3o liberada ainda (em breve)"
70201
70220
  style="opacity:.6;cursor:not-allowed;">
70202
- <span class="icon">\u{1F4CB}</span> <span class="icon" style="font-size:.85em;">\u{1F512}</span>
70221
+ <span class="icon">\u{1F4CB}</span> <span class="count">\u{1F512}</span>
70203
70222
  </span>` : "annotations" in meta ? `<span class="myio-welcome-card-device-count annotations"
70204
70223
  data-tooltip-type="annotations"
70205
70224
  data-card-index="${index}"
@@ -70487,47 +70506,107 @@ var WelcomeModalView = class _WelcomeModalView {
70487
70506
  );
70488
70507
  }
70489
70508
  static metaChip(text2) {
70490
- return `<span style="display:inline-block;padding:2px 8px;margin:0 4px 4px 0;border-radius:10px;background:#334155;font-weight:700">${_WelcomeModalView.escapeMetaHtml(text2)}</span>`;
70491
- }
70509
+ return `<span class="myio-welcome-meta-tip__chip">${_WelcomeModalView.escapeMetaHtml(text2)}</span>`;
70510
+ }
70511
+ // Mesmo padrão visual do EnergySummaryTooltip (card branco, header em degradê da cor do tipo,
70512
+ // banner de total, títulos de seção em caixa alta). Usuários segue a cor do tema do dashboard.
70513
+ static META_TIP_CSS = `
70514
+ .myio-welcome-meta-tip {
70515
+ position: fixed; z-index: 100000; width: 360px; max-width: calc(100vw - 16px);
70516
+ background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px;
70517
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 2px 10px rgba(0, 0, 0, 0.08);
70518
+ font: 12px 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
70519
+ color: #1e293b; overflow: hidden; display: none;
70520
+ }
70521
+ .myio-welcome-meta-tip__header {
70522
+ display: flex; align-items: center; gap: 8px; padding: 10px 14px;
70523
+ background: linear-gradient(90deg, var(--mt-bg1) 0%, var(--mt-bg2) 100%);
70524
+ border-bottom: 1px solid var(--mt-border);
70525
+ }
70526
+ .myio-welcome-meta-tip__icon { font-size: 18px; }
70527
+ .myio-welcome-meta-tip__title { flex: 1; font-weight: 700; font-size: 14px; color: var(--mt-accent); }
70528
+ .myio-welcome-meta-tip__timestamp { font-size: 10px; color: #6b7280; }
70529
+ .myio-welcome-meta-tip__close {
70530
+ width: 24px; height: 24px; border: none; border-radius: 4px; cursor: pointer;
70531
+ background: rgba(255, 255, 255, 0.6); color: #64748b;
70532
+ display: flex; align-items: center; justify-content: center; font-size: 14px; line-height: 1;
70533
+ }
70534
+ .myio-welcome-meta-tip__close:hover { background: rgba(255, 255, 255, 0.9); color: #1e293b; }
70535
+ .myio-welcome-meta-tip__body { padding: 14px; max-height: 60vh; overflow-y: auto; }
70536
+ .myio-welcome-meta-tip__total {
70537
+ display: flex; align-items: center; justify-content: space-between; padding: 10px 12px;
70538
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
70539
+ border: 1px solid #e2e8f0; border-radius: 8px; margin-bottom: 12px;
70540
+ }
70541
+ .myio-welcome-meta-tip__total-label { font-weight: 600; color: #475569; font-size: 12px; }
70542
+ .myio-welcome-meta-tip__total-value { font-weight: 700; font-size: 20px; color: #1e293b; }
70543
+ .myio-welcome-meta-tip__section-title {
70544
+ font-weight: 700; font-size: 10px; color: #64748b; text-transform: uppercase; letter-spacing: 0.5px;
70545
+ margin: 12px 0 6px; padding-bottom: 4px; border-bottom: 1px solid #e2e8f0;
70546
+ }
70547
+ .myio-welcome-meta-tip__chips { display: flex; flex-wrap: wrap; gap: 6px; }
70548
+ .myio-welcome-meta-tip__chip {
70549
+ display: inline-block; padding: 3px 10px; border-radius: 999px; font-weight: 700; font-size: 11px;
70550
+ color: var(--mt-accent); background: var(--mt-bg1); border: 1px solid var(--mt-border);
70551
+ }
70552
+ .myio-welcome-meta-tip__row { padding: 7px 10px; border-radius: 6px; }
70553
+ .myio-welcome-meta-tip__row:nth-child(odd) { background: #f8fafc; }
70554
+ .myio-welcome-meta-tip__row-title { font-weight: 600; color: #1e293b; }
70555
+ .myio-welcome-meta-tip__row-sub { color: #64748b; font-size: 11px; margin-top: 1px; }
70556
+ .myio-welcome-meta-tip__empty { color: #64748b; padding: 6px 2px; }
70557
+ `;
70492
70558
  ensureMetaTip() {
70493
70559
  if (this.metaTipEl && document.body.contains(this.metaTipEl)) return this.metaTipEl;
70560
+ if (!document.getElementById("myio-welcome-meta-tip-styles")) {
70561
+ const style = document.createElement("style");
70562
+ style.id = "myio-welcome-meta-tip-styles";
70563
+ style.textContent = _WelcomeModalView.META_TIP_CSS;
70564
+ document.head.appendChild(style);
70565
+ }
70494
70566
  const tip = document.createElement("div");
70495
70567
  tip.className = "myio-welcome-meta-tip";
70496
- Object.assign(tip.style, {
70497
- position: "fixed",
70498
- zIndex: "100000",
70499
- minWidth: "260px",
70500
- maxWidth: "360px",
70501
- maxHeight: "60vh",
70502
- overflow: "auto",
70503
- background: "#1e293b",
70504
- color: "#e2e8f0",
70505
- border: "1px solid #334155",
70506
- borderRadius: "10px",
70507
- boxShadow: "0 12px 40px rgba(0,0,0,.45)",
70508
- padding: "12px 14px",
70509
- font: "12px 'Nunito', system-ui, -apple-system, sans-serif",
70510
- display: "none"
70511
- });
70512
70568
  tip.addEventListener("mouseenter", () => {
70513
70569
  if (this.metaTipHideTimer) clearTimeout(this.metaTipHideTimer);
70514
70570
  });
70515
70571
  tip.addEventListener("mouseleave", () => this.startMetaTipDelayedHide());
70572
+ tip.addEventListener("click", (e) => {
70573
+ if (e.target.closest(".myio-welcome-meta-tip__close")) this.hideMetaTip();
70574
+ });
70516
70575
  document.body.appendChild(tip);
70517
70576
  this.metaTipEl = tip;
70518
70577
  return tip;
70519
70578
  }
70579
+ /** Cores do tooltip por tipo de badge (usuários = cor do tema). */
70580
+ metaTipAccent(type) {
70581
+ if (type === "alarms") return { accent: "#b91c1c", bg1: "#fef2f2", bg2: "#fee2e2", border: "#fca5a5" };
70582
+ if (type.startsWith("annotations")) return { accent: "#5b21b6", bg1: "#f5f3ff", bg2: "#ede9fe", border: "#c4b5fd" };
70583
+ if (type === "notifications") return { accent: "#b45309", bg1: "#fffbeb", bg2: "#fef3c7", border: "#fcd34d" };
70584
+ const p = this.palette.primary || "#7A2FF7";
70585
+ return {
70586
+ accent: p,
70587
+ bg1: `color-mix(in srgb, ${p} 6%, white)`,
70588
+ bg2: `color-mix(in srgb, ${p} 16%, white)`,
70589
+ border: `color-mix(in srgb, ${p} 40%, white)`
70590
+ };
70591
+ }
70520
70592
  showMetaTip(anchorEl, type, card) {
70521
70593
  const tip = this.ensureMetaTip();
70522
70594
  if (this.metaTipHideTimer) clearTimeout(this.metaTipHideTimer);
70595
+ const c = this.metaTipAccent(type);
70596
+ tip.style.setProperty("--mt-accent", c.accent);
70597
+ tip.style.setProperty("--mt-bg1", c.bg1);
70598
+ tip.style.setProperty("--mt-bg2", c.bg2);
70599
+ tip.style.setProperty("--mt-border", c.border);
70523
70600
  tip.innerHTML = this.buildMetaTipHtml(type, card);
70524
70601
  tip.style.display = "block";
70525
70602
  const r = anchorEl.getBoundingClientRect();
70526
- const tw = Math.min(360, window.innerWidth - 16);
70603
+ const tw = tip.offsetWidth || Math.min(360, window.innerWidth - 16);
70527
70604
  let left = r.left;
70528
70605
  if (left + tw > window.innerWidth - 8) left = window.innerWidth - tw - 8;
70529
70606
  tip.style.left = Math.max(8, left) + "px";
70530
- tip.style.top = r.bottom + 8 + "px";
70607
+ const th = tip.offsetHeight;
70608
+ const below = r.bottom + 8;
70609
+ tip.style.top = (below + th > window.innerHeight - 8 ? Math.max(8, r.top - th - 8) : below) + "px";
70531
70610
  }
70532
70611
  startMetaTipDelayedHide() {
70533
70612
  if (this.metaTipHideTimer) clearTimeout(this.metaTipHideTimer);
@@ -70541,60 +70620,92 @@ var WelcomeModalView = class _WelcomeModalView {
70541
70620
  if (this.metaTipEl) this.metaTipEl.style.display = "none";
70542
70621
  }
70543
70622
  buildMetaTipHtml(type, card) {
70544
- const esc8 = _WelcomeModalView.escapeMetaHtml;
70623
+ const esc9 = _WelcomeModalView.escapeMetaHtml;
70545
70624
  const chip = _WelcomeModalView.metaChip;
70546
70625
  const meta = card.metaCounts || {};
70547
70626
  const details = card.metaDetails || {};
70548
- const loadingBody = '<div style="opacity:.7;padding:6px 0">Carregando\u2026</div>';
70549
- const header = (icon2, label, count) => `<div style="font-weight:800;font-size:13px;margin-bottom:8px">${icon2} ${label}${count === null || count === void 0 ? "" : ` (${count})`} \u2014 ${esc8(card.title)}</div>`;
70550
- const row = (titleHtml, subHtml) => `<div style="padding:6px 0;border-top:1px solid #334155">
70551
- <div style="font-weight:600">${titleHtml}</div>
70552
- ${subHtml ? `<div style="opacity:.7">${subHtml}</div>` : ""}
70627
+ const now = /* @__PURE__ */ new Date();
70628
+ const stamp = `${String(now.getDate()).padStart(2, "0")}/${String(now.getMonth() + 1).padStart(2, "0")} ${String(
70629
+ now.getHours()
70630
+ ).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
70631
+ const frame = (icon2, label, body) => `<div class="myio-welcome-meta-tip__header">
70632
+ <span class="myio-welcome-meta-tip__icon">${icon2}</span>
70633
+ <span class="myio-welcome-meta-tip__title">${label} (${esc9(card.title)})</span>
70634
+ <span class="myio-welcome-meta-tip__timestamp">${stamp}</span>
70635
+ <button type="button" class="myio-welcome-meta-tip__close" title="Fechar">\u2715</button>
70636
+ </div>
70637
+ <div class="myio-welcome-meta-tip__body">${body}</div>`;
70638
+ const total = (label, value) => `<div class="myio-welcome-meta-tip__total">
70639
+ <span class="myio-welcome-meta-tip__total-label">${label}</span>
70640
+ <span class="myio-welcome-meta-tip__total-value">${value}</span>
70553
70641
  </div>`;
70642
+ const section = (title) => `<div class="myio-welcome-meta-tip__section-title">${title}</div>`;
70643
+ const empty = (text2) => `<div class="myio-welcome-meta-tip__empty">${text2}</div>`;
70644
+ const loadingBody = empty("Carregando\u2026");
70645
+ const row = (titleHtml, subHtml) => `<div class="myio-welcome-meta-tip__row">
70646
+ <div class="myio-welcome-meta-tip__row-title">${titleHtml}</div>
70647
+ ${subHtml ? `<div class="myio-welcome-meta-tip__row-sub">${subHtml}</div>` : ""}
70648
+ </div>`;
70649
+ const more = (n) => n > 10 ? empty(`\u2026 e mais ${n - 10}`) : "";
70554
70650
  if (type === "alarms") {
70555
- if (meta.alarms === null) return header("\u{1F6A8}", "Alarmes") + loadingBody;
70651
+ if (meta.alarms === null) return frame("\u{1F6A8}", "Alarmes", loadingBody);
70556
70652
  const alarms = details.alarms || [];
70557
70653
  const byState = {};
70558
70654
  for (const a of alarms) {
70559
70655
  const s = a.state || "OPEN";
70560
70656
  byState[s] = (byState[s] || 0) + 1;
70561
70657
  }
70562
- const chips = Object.entries(byState).map(([s, n]) => chip(`${s}: ${n}`)).join("") || '<span style="opacity:.7">Sem alarmes ativos</span>';
70658
+ const chips = Object.entries(byState).map(([s, n]) => chip(`${s}: ${n}`)).join("");
70563
70659
  const rows = alarms.slice(0, 10).map(
70564
70660
  (a) => row(
70565
- esc8(a.deviceName ? `${a.deviceName} \u2014 ${a.title || "Alarme"}` : a.title || "Alarme"),
70566
- `${esc8(a.severity || "")} \xB7 ${esc8(a.state || "")}`
70661
+ esc9(a.deviceName ? `${a.deviceName} \u2014 ${a.title || "Alarme"}` : a.title || "Alarme"),
70662
+ [a.severity, a.state].filter(Boolean).map(esc9).join(" \xB7 ")
70567
70663
  )
70568
70664
  ).join("");
70569
- return `${header("\u{1F6A8}", "Alarmes", meta.alarms ?? alarms.length)}<div style="margin-bottom:6px">${chips}</div>${rows}`;
70665
+ return frame(
70666
+ "\u{1F6A8}",
70667
+ "Alarmes",
70668
+ total("Total de Alarmes", meta.alarms ?? alarms.length) + (chips ? section("Por estado") + `<div class="myio-welcome-meta-tip__chips">${chips}</div>` : "") + section("Mais recentes") + (rows || empty("Sem alarmes ativos")) + more(alarms.length)
70669
+ );
70570
70670
  }
70571
70671
  if (type === "annotations-locked") {
70572
- return header("\u{1F4CB}", "Anota\xE7\xF5es") + '<div style="opacity:.85;padding:6px 0">\u{1F512} Funcionalidade n\xE3o liberada ainda \u2014 em breve.</div>';
70672
+ return frame("\u{1F4CB}", "Anota\xE7\xF5es", empty("\u{1F512} Funcionalidade n\xE3o liberada ainda \u2014 em breve."));
70573
70673
  }
70574
70674
  if (type === "annotations") {
70575
- if (meta.annotations === null) return header("\u{1F4CB}", "Anota\xE7\xF5es") + loadingBody;
70675
+ if (meta.annotations === null) return frame("\u{1F4CB}", "Anota\xE7\xF5es", loadingBody);
70576
70676
  const annotations = details.annotations || [];
70577
70677
  const byType = {};
70578
70678
  for (const a of annotations) {
70579
70679
  const t = a.type || "observation";
70580
70680
  byType[t] = (byType[t] || 0) + 1;
70581
70681
  }
70582
- const chips = Object.entries(byType).map(([t, n]) => chip(`${t}: ${n}`)).join("") || '<span style="opacity:.7">Sem anota\xE7\xF5es ativas</span>';
70682
+ const chips = Object.entries(byType).map(([t, n]) => chip(`${t}: ${n}`)).join("");
70583
70683
  const rows = annotations.slice(0, 10).map((a) => {
70584
70684
  const when = a.createdAt ? new Date(a.createdAt).toLocaleDateString("pt-BR") : "";
70585
70685
  const text2 = a.text.length > 90 ? `${a.text.slice(0, 90)}\u2026` : a.text;
70586
- 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)}` : ""}`);
70587
70687
  }).join("");
70588
- return `${header("\u{1F4CB}", "Anota\xE7\xF5es", meta.annotations ?? annotations.length)}<div style="margin-bottom:6px">${chips}</div>${rows}`;
70688
+ return frame(
70689
+ "\u{1F4CB}",
70690
+ "Anota\xE7\xF5es",
70691
+ total("Total de Anota\xE7\xF5es", meta.annotations ?? annotations.length) + (chips ? section("Por tipo") + `<div class="myio-welcome-meta-tip__chips">${chips}</div>` : "") + section("Mais recentes") + (rows || empty("Sem anota\xE7\xF5es ativas")) + more(annotations.length)
70692
+ );
70589
70693
  }
70590
70694
  if (type === "users") {
70591
- if (meta.users === null) return header("\u{1F465}", "Usu\xE1rios") + loadingBody;
70695
+ if (meta.users === null) return frame("\u{1F465}", "Usu\xE1rios", loadingBody);
70592
70696
  const users = details.users || [];
70593
- const rows = users.slice(0, 10).map((u) => row(esc8(u.name), esc8(u.email || ""))).join("") || '<div style="opacity:.7;padding:6px 0">Sem usu\xE1rios</div>';
70594
- const more = users.length > 10 ? `<div style="opacity:.7;padding:6px 0 0">\u2026 e mais ${users.length - 10}</div>` : "";
70595
- return `${header("\u{1F465}", "Usu\xE1rios", meta.users ?? users.length)}${rows}${more}`;
70697
+ const rows = users.slice(0, 10).map((u) => row(esc9(u.name), esc9(u.email || ""))).join("");
70698
+ return frame(
70699
+ "\u{1F465}",
70700
+ "Usu\xE1rios",
70701
+ total("Total de Usu\xE1rios", meta.users ?? users.length) + section("Usu\xE1rios") + (rows || empty("Sem usu\xE1rios")) + more(users.length)
70702
+ );
70596
70703
  }
70597
- return header("\u{1F514}", "Notifica\xE7\xF5es", meta.notifications ?? 0) + '<div style="opacity:.7;padding:6px 0">Sem notifica\xE7\xF5es</div>';
70704
+ return frame(
70705
+ "\u{1F514}",
70706
+ "Notifica\xE7\xF5es",
70707
+ total("Total de Notifica\xE7\xF5es", meta.notifications ?? 0) + empty("Sem notifica\xE7\xF5es")
70708
+ );
70598
70709
  }
70599
70710
  /**
70600
70711
  * Handle tooltip icon click - shows the appropriate tooltip
@@ -74968,16 +75079,16 @@ function buildTrendSvg(values, stroke, grid) {
74968
75079
  }
74969
75080
  const width = 320;
74970
75081
  const height = 80;
74971
- const pad = 6;
75082
+ const pad3 = 6;
74972
75083
  const maxVal = Math.max(...values, 1);
74973
75084
  const points = values.map((v, i) => {
74974
- const x = pad + i / Math.max(1, values.length - 1) * (width - pad * 2);
74975
- 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);
74976
75087
  return `${x},${y}`;
74977
75088
  });
74978
75089
  return `
74979
75090
  <svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
74980
- <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"/>
74981
75092
  <polyline fill="none" stroke="${stroke}" stroke-width="2.5" points="${points.join(" ")}" />
74982
75093
  ${points.map((p) => {
74983
75094
  const [x, y] = p.split(",");
@@ -79432,6 +79543,2482 @@ async function openDemandModal(params) {
79432
79543
  };
79433
79544
  }
79434
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
+
79435
82022
  // src/utils/goalsCoverage.ts
79436
82023
  var MONTHS_PT = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"];
79437
82024
  function formatCoverageRefPtBR(ref) {
@@ -79479,7 +82066,7 @@ function buildYearOptions() {
79479
82066
  for (let y = now + 2; y >= now - 2; y--) out.push(y);
79480
82067
  return out;
79481
82068
  }
79482
- function esc4(s) {
82069
+ function esc5(s) {
79483
82070
  return String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
79484
82071
  }
79485
82072
  function openGoalsPanel(params) {
@@ -79557,7 +82144,7 @@ function openGoalsPanel(params) {
79557
82144
  if (!/\/api\/v1$/.test(b)) b += "/api/v1";
79558
82145
  return b;
79559
82146
  }
79560
- async function request(method, path, opts) {
82147
+ async function request2(method, path, opts) {
79561
82148
  const o = opts || {};
79562
82149
  let url = apiBase() + path;
79563
82150
  if (o.query) {
@@ -79598,25 +82185,25 @@ function openGoalsPanel(params) {
79598
82185
  }
79599
82186
  const targetQuery = () => ({ domain: state6.domain, year: state6.year });
79600
82187
  const svc = {
79601
- getTree: (granularity, fetchHistory) => request("GET", `/customers/${customerId}/goals`, {
82188
+ getTree: (granularity, fetchHistory) => request2("GET", `/customers/${customerId}/goals`, {
79602
82189
  query: { domain: state6.domain, year: state6.year, granularity, fetchHistory }
79603
82190
  }),
79604
- replace: (body) => request("PUT", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
79605
- 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 }),
79606
82193
  // Scoped bucket DELETE (RFC-0046 §3.6): body { bucket: { level, ref }, expectedVersion? }.
79607
82194
  // Used for cells the operator CLEARED — never a whole-year delete from this panel.
79608
- removeBucket: (body) => request("DELETE", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
79609
- 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`, {
79610
82197
  query: { domain: state6.domain, year: state6.year, dryRun: !!dryRun },
79611
82198
  body
79612
82199
  }),
79613
82200
  // RFC-0052: margem da meta (goalMarginPct) por customer × domínio × ano.
79614
82201
  // Mesmo stream de versão dos buckets — 409 VERSION_CONFLICT no lock otimista.
79615
- setMargin: (pct, version2) => request("PUT", `/customers/${customerId}/goals/margin`, {
82202
+ setMargin: (pct, version2) => request2("PUT", `/customers/${customerId}/goals/margin`, {
79616
82203
  query: targetQuery(),
79617
82204
  body: version2 ? { goalMarginPct: pct, version: version2 } : { goalMarginPct: pct }
79618
82205
  }),
79619
- clearMargin: (version2) => request("DELETE", `/customers/${customerId}/goals/margin`, {
82206
+ clearMargin: (version2) => request2("DELETE", `/customers/${customerId}/goals/margin`, {
79620
82207
  query: targetQuery(),
79621
82208
  body: version2 ? { version: version2 } : void 0
79622
82209
  })
@@ -80275,13 +82862,13 @@ function openGoalsPanel(params) {
80275
82862
  const g = state6.goals || {};
80276
82863
  const parts = [];
80277
82864
  if (hasCoverageGaps(g.coverageGaps)) {
80278
- 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>`);
80279
82866
  }
80280
82867
  (Array.isArray(g.devices) ? g.devices : []).forEach((d) => {
80281
82868
  if (!hasCoverageGaps(d && d.coverageGaps)) return;
80282
82869
  const label = d.label || d.code || "medidor";
80283
82870
  parts.push(
80284
- `<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>`
80285
82872
  );
80286
82873
  });
80287
82874
  return `<div style="font-size:12px;line-height:1.55;color:#334155;">${parts.join("")}</div>`;
@@ -80321,7 +82908,7 @@ function openGoalsPanel(params) {
80321
82908
  html += `<div class="myio-goals-alert myio-goals-alert-amber">${icon2("alert")} ${i18n2.versionConflict}</div>`;
80322
82909
  }
80323
82910
  if (state6.saveError && !state6.conflict) {
80324
- 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>`;
80325
82912
  }
80326
82913
  if (state6.saved && !state6.saveError) {
80327
82914
  html += `<div class="myio-goals-alert myio-goals-alert-green">${icon2("check")} ${i18n2.saveSuccess}</div>`;
@@ -80336,7 +82923,7 @@ function openGoalsPanel(params) {
80336
82923
  return `
80337
82924
  <div class="myio-goals-center myio-goals-center-col">
80338
82925
  ${icon2("alert-lg")}
80339
- <p>${esc4(state6.error)}</p>
82926
+ <p>${esc5(state6.error)}</p>
80340
82927
  <button class="myio-goals-btn myio-goals-btn-outline" data-action="retry">${i18n2.retry}</button>
80341
82928
  </div>`;
80342
82929
  }
@@ -80426,7 +83013,7 @@ function openGoalsPanel(params) {
80426
83013
  const cellLabel = state6.level === "month" ? i18n2.months[key] : state6.level === "day" ? i18n2.dayCell.replace("{day}", Number(key)) : `${key}:00`;
80427
83014
  const canDrill = state6.level !== "hour";
80428
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>`;
80429
- const val = state6.draft[key] !== void 0 ? esc4(state6.draft[key]) : "";
83016
+ const val = state6.draft[key] !== void 0 ? esc5(state6.draft[key]) : "";
80430
83017
  const minAttr = cfg.allowsNegative ? "" : 'min="0"';
80431
83018
  return `
80432
83019
  <div class="myio-goals-cell">
@@ -80494,7 +83081,7 @@ function openGoalsPanel(params) {
80494
83081
  const actor = ev.actor ? `${String(ev.actor).slice(0, 8)}\u2026` : i18n2.history.system;
80495
83082
  const extra = ev.bucketCount - (ev.details ? ev.details.length : 0);
80496
83083
  const chips = (ev.details || []).slice(0, DETAIL_PREVIEW).map(
80497
- (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>`
80498
83085
  ).join("");
80499
83086
  const more = extra > 0 ? `<span class="myio-goals-tl-more">${i18n2.history.moreDetails.replace("{n}", extra)}</span>` : "";
80500
83087
  return `
@@ -80504,7 +83091,7 @@ function openGoalsPanel(params) {
80504
83091
  <span class="myio-goals-tl-title">${i18n2.history.source[ev.source] || i18n2.history.source.EDIT}</span>
80505
83092
  <span class="myio-goals-tl-ver">v${ev.version}</span>
80506
83093
  </div>
80507
- <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>
80508
83095
  <p class="myio-goals-tl-records">${i18n2.history.records.replace("{n}", ev.bucketCount)} \xB7 ${i18n2.levels[ev.actionLevel] || ev.actionLevel}</p>
80509
83096
  ${chips || more ? `<div class="myio-goals-tl-chips">${chips}${more}</div>` : ""}
80510
83097
  </li>`;
@@ -80551,7 +83138,7 @@ ${state6.year}-03-15T08,15`;
80551
83138
  let html = `
80552
83139
  <div class="myio-goals-import-hint">
80553
83140
  <p>${i18n2.import.formatHint}</p>
80554
- <code>${esc4(sample)}</code>
83141
+ <code>${esc5(sample)}</code>
80555
83142
  </div>`;
80556
83143
  if (!applied) {
80557
83144
  html += `
@@ -80565,16 +83152,16 @@ ${state6.year}-03-15T08,15`;
80565
83152
  <input type="file" id="myio-goals-csv-file" accept=".csv,.txt,text/csv,text/plain" style="display:none" />
80566
83153
  </div>
80567
83154
  </div>
80568
- <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
80569
83156
  ${state6.year},100000
80570
83157
  ${state6.year}-03,9000
80571
83158
  ${state6.year}-03-15,310
80572
- ${state6.year}-03-15T08,15`)}">${esc4(state6.csv)}</textarea>
83159
+ ${state6.year}-03-15T08,15`)}">${esc5(state6.csv)}</textarea>
80573
83160
  <p class="myio-goals-microhint">${i18n2.import.unitNote.replace("{unit}", unitLabel2())}</p>
80574
83161
  </div>`;
80575
83162
  }
80576
83163
  if (state6.saveError && state6.conflictVersion === null) {
80577
- 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>`;
80578
83165
  }
80579
83166
  if (state6.conflictVersion !== null) {
80580
83167
  html += `<div class="myio-goals-alert myio-goals-alert-amber">${icon2("alert")} ${i18n2.import.conflict}</div>`;
@@ -80603,7 +83190,7 @@ ${state6.year}-03-15T08,15`)}">${esc4(state6.csv)}</textarea>
80603
83190
  <div class="myio-goals-import-diags">
80604
83191
  <p class="myio-goals-import-diags-title">${i18n2.import.diagnosticsTitle}</p>
80605
83192
  <ul>${result.diagnostics.map(
80606
- (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>`
80607
83194
  ).join("")}</ul>
80608
83195
  </div>`;
80609
83196
  }
@@ -80611,7 +83198,7 @@ ${state6.year}-03-15T08,15`)}">${esc4(state6.csv)}</textarea>
80611
83198
  html += `
80612
83199
  <div class="myio-goals-import-log">
80613
83200
  <p class="myio-goals-import-log-title">${i18n2.import.logTitle}</p>
80614
- <ul>${result.log.map((line) => `<li>${esc4(line)}</li>`).join("")}</ul>
83201
+ <ul>${result.log.map((line) => `<li>${esc5(line)}</li>`).join("")}</ul>
80615
83202
  </div>`;
80616
83203
  }
80617
83204
  html += `</div>`;
@@ -87649,7 +90236,7 @@ function injectCSS10() {
87649
90236
  document.head.appendChild(style);
87650
90237
  _cssInjected2 = true;
87651
90238
  }
87652
- function esc5(value) {
90239
+ function esc6(value) {
87653
90240
  return String(value == null ? "" : value).replace(
87654
90241
  /[&<>"]/g,
87655
90242
  (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]
@@ -87699,7 +90286,7 @@ function buildPieSvg(visible, total) {
87699
90286
  const val = Number(v.d.value) || 0;
87700
90287
  return `<svg class="myio-col-summary__pie" viewBox="0 0 220 220">
87701
90288
  <circle class="myio-col-summary__slice" data-idx="${v.idx}" cx="110" cy="110" r="100"
87702
- 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(
87703
90290
  val,
87704
90291
  total,
87705
90292
  pd
@@ -87714,7 +90301,7 @@ function buildPieSvg(visible, total) {
87714
90301
  const a1 = acc / total * 360;
87715
90302
  return `<path class="myio-col-summary__slice" data-idx="${v.idx}"
87716
90303
  d="${arcPath(110, 110, 100, a0, a1)}" fill="${sliceColor(v.idx)}"
87717
- ><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>`;
87718
90305
  }).join("");
87719
90306
  return `<svg class="myio-col-summary__pie" viewBox="0 0 220 220">${paths}</svg>`;
87720
90307
  }
@@ -87728,8 +90315,8 @@ function buildLegend(total) {
87728
90315
  return `<div class="myio-col-summary__legend-row${isExcl ? " is-excluded" : ""}" data-idx="${idx}"
87729
90316
  title="Clique para ${isExcl ? "incluir" : "remover"} da pizza">
87730
90317
  <span class="myio-col-summary__legend-dot" style="background:${sliceColor(idx)};"></span>
87731
- <span class="myio-col-summary__legend-name">${esc5(d.name)}</span>
87732
- <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>
87733
90320
  <span class="myio-col-summary__legend-pct">${pct}</span>
87734
90321
  </div>`;
87735
90322
  }).join("");
@@ -87750,25 +90337,25 @@ function buildAverageInner() {
87750
90337
  const dash = "\u2014";
87751
90338
  const periodRow = data.periodLabel ? `<div class="myio-col-summary__kpi">
87752
90339
  <span class="myio-col-summary__kpi-label">Per\xEDodo</span>
87753
- <span class="myio-col-summary__kpi-value">${esc5(data.periodLabel)}</span>
90340
+ <span class="myio-col-summary__kpi-value">${esc6(data.periodLabel)}</span>
87754
90341
  </div>` : "";
87755
90342
  const note = "M\xE9dia, m\xEDnima e m\xE1xima consideram apenas dispositivos online (com leitura).";
87756
90343
  const kpis = `<div class="myio-col-summary__kpis">
87757
90344
  ${periodRow}
87758
90345
  <div class="myio-col-summary__kpi">
87759
90346
  <span class="myio-col-summary__kpi-label">Dispositivos
87760
- <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)}"
87761
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>
87762
90349
  </span>
87763
90350
  <span class="myio-col-summary__kpi-value">${online.length} online \xB7 ${offline.length} offline</span>
87764
90351
  </div>
87765
90352
  <div class="myio-col-summary__kpi">
87766
- <span class="myio-col-summary__kpi-label">${esc5(measure)} m\xE9dia</span>
87767
- <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>
87768
90355
  </div>
87769
90356
  <div class="myio-col-summary__kpi">
87770
90357
  <span class="myio-col-summary__kpi-label">M\xEDnima \xB7 M\xE1xima</span>
87771
- <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>
87772
90359
  </div>
87773
90360
  </div>`;
87774
90361
  if (!data.devices.length) {
@@ -87777,12 +90364,12 @@ function buildAverageInner() {
87777
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);
87778
90365
  const row = (v) => `
87779
90366
  <div class="myio-col-summary__row">
87780
- <span class="myio-col-summary__name" title="${esc5(v.d.name)}">${esc5(v.d.name)}</span>
87781
- <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>
87782
90369
  </div>`;
87783
90370
  const offRow = (v) => `
87784
90371
  <div class="myio-col-summary__row">
87785
- <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>
87786
90373
  <span class="myio-col-summary__val" style="color:#94a3b8;">sem leitura</span>
87787
90374
  </div>`;
87788
90375
  const group = (label, list, r = row) => list.length ? `<div class="myio-col-summary__group">
@@ -87799,8 +90386,8 @@ function buildAverageInner() {
87799
90386
  </div>
87800
90387
  </div>
87801
90388
  <div class="myio-col-summary__footer">
87802
- <span class="myio-col-summary__footer-label">${esc5(measure)} m\xE9dia \xB7 ${online.length} online</span>
87803
- <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>
87804
90391
  </div>`;
87805
90392
  }
87806
90393
  function buildInner() {
@@ -87813,7 +90400,7 @@ function buildInner() {
87813
90400
  const avg = count ? total / count : 0;
87814
90401
  const periodRow = data.periodLabel ? `<div class="myio-col-summary__kpi">
87815
90402
  <span class="myio-col-summary__kpi-label">Per\xEDodo</span>
87816
- <span class="myio-col-summary__kpi-value">${esc5(data.periodLabel)}</span>
90403
+ <span class="myio-col-summary__kpi-value">${esc6(data.periodLabel)}</span>
87817
90404
  </div>` : "";
87818
90405
  const kpis = `<div class="myio-col-summary__kpis">
87819
90406
  ${periodRow}
@@ -87823,13 +90410,13 @@ function buildInner() {
87823
90410
  </div>
87824
90411
  <div class="myio-col-summary__kpi">
87825
90412
  <span class="myio-col-summary__kpi-label">Consumo m\xE9dio</span>
87826
- <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(
87827
90414
  fmt2(avg)
87828
90415
  )}</span>
87829
90416
  </div>
87830
90417
  <div class="myio-col-summary__kpi">
87831
90418
  <span class="myio-col-summary__kpi-label">Consumo total</span>
87832
- <span class="myio-col-summary__kpi-value">${esc5(fmt2(total))}</span>
90419
+ <span class="myio-col-summary__kpi-value">${esc6(fmt2(total))}</span>
87833
90420
  </div>
87834
90421
  </div>`;
87835
90422
  if (!data.devices.length) {
@@ -87843,8 +90430,8 @@ function buildInner() {
87843
90430
  ).slice(0, 3);
87844
90431
  const row = (v) => `
87845
90432
  <div class="myio-col-summary__row">
87846
- <span class="myio-col-summary__name" title="${esc5(v.d.name)}">${esc5(v.d.name)}</span>
87847
- <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>
87848
90435
  <span class="myio-col-summary__pct">${fmtPct2(Number(v.d.value) || 0, total, pd)}</span>
87849
90436
  </div>`;
87850
90437
  const group = (label, list) => list.length ? `<div class="myio-col-summary__group">
@@ -87867,7 +90454,7 @@ function buildInner() {
87867
90454
  </div>
87868
90455
  <div class="myio-col-summary__footer">
87869
90456
  <span class="myio-col-summary__footer-label">Consumo Total \xB7 ${count} dispositivos</span>
87870
- <span class="myio-col-summary__footer-value">${esc5(fmt2(total))}</span>
90457
+ <span class="myio-col-summary__footer-value">${esc6(fmt2(total))}</span>
87871
90458
  </div>`;
87872
90459
  }
87873
90460
  function setHighlight(idx) {
@@ -95455,12 +98042,12 @@ function createConsumptionChartWidget(config2) {
95455
98042
  }
95456
98043
 
95457
98044
  // src/components/tooltips/goals-bar-tooltip/createGoalsBarTooltip.ts
95458
- var STYLE_ID6 = "myio-goals-bar-tooltip-styles";
98045
+ var STYLE_ID8 = "myio-goals-bar-tooltip-styles";
95459
98046
  var DEFAULT_ACCENT = "#6c5ce7";
95460
98047
  function injectStyles12(doc) {
95461
- if (doc.getElementById(STYLE_ID6)) return;
98048
+ if (doc.getElementById(STYLE_ID8)) return;
95462
98049
  const s = doc.createElement("style");
95463
- s.id = STYLE_ID6;
98050
+ s.id = STYLE_ID8;
95464
98051
  s.textContent = `
95465
98052
  .myio-gbt {
95466
98053
  position: fixed;
@@ -95647,7 +98234,7 @@ function injectStyles12(doc) {
95647
98234
  `;
95648
98235
  (doc.head || doc.documentElement).appendChild(s);
95649
98236
  }
95650
- function esc6(v) {
98237
+ function esc7(v) {
95651
98238
  return String(v ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
95652
98239
  }
95653
98240
  function fmtPct3(pct) {
@@ -95668,8 +98255,8 @@ function renderDeltaChip(delta) {
95668
98255
  const tone = delta.tone === "good" ? "good" : delta.tone === "bad" ? "bad" : "neutral";
95669
98256
  const arrow = delta.arrow === "up" ? "\u2191" : delta.arrow === "down" ? "\u2193" : "\u2248";
95670
98257
  const pctTxt = fmtPct3(Math.abs(Number(delta.pct)));
95671
- const txt = delta.text ? ` ${esc6(delta.text)}` : "";
95672
- 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>`;
95673
98260
  }
95674
98261
  function renderRow(row, level) {
95675
98262
  const hasChildren = Array.isArray(row.children) && row.children.length > 0;
@@ -95677,12 +98264,12 @@ function renderRow(row, level) {
95677
98264
  const id = `gbtr-${++_rowSeq}`;
95678
98265
  const pct = row.pct;
95679
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>`;
95680
- const swatch = row.color ? `<span class="myio-gbt__swatch" style="background:${esc6(row.color)}"></span>` : "";
95681
- 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>` : "";
95682
98269
  const chipHtml = row.delta ? renderDeltaChip(row.delta) : "";
95683
- 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>` : "";
95684
98271
  const rowClass = level > 0 ? "myio-gbt__row myio-gbt__row--child" : "myio-gbt__row";
95685
- 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>`;
95686
98273
  if (hasChildren) {
95687
98274
  const childHtml = row.children.map((c) => renderRow(c, level + 1)).join("");
95688
98275
  html += `<div class="myio-gbt__children${open ? " is-open" : ""}" data-children="${id}">${childHtml}</div>`;
@@ -95718,15 +98305,15 @@ function createGoalsBarTooltip(opts) {
95718
98305
  const rect = el2.getBoundingClientRect();
95719
98306
  const w = rect.width || 280;
95720
98307
  const h3 = rect.height || 200;
95721
- const pad = 12;
98308
+ const pad3 = 12;
95722
98309
  const gap = 16;
95723
98310
  let left = clientX + gap;
95724
98311
  let top = clientY + gap;
95725
- if (left + w > window.innerWidth - pad) left = clientX - w - gap;
95726
- if (left < pad) left = Math.max(pad, (window.innerWidth - w) / 2);
95727
- if (top + h3 > window.innerHeight - pad) top = clientY - h3 - gap;
95728
- if (top < pad) top = pad;
95729
- 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);
95730
98317
  el2.style.left = `${Math.round(left)}px`;
95731
98318
  el2.style.top = `${Math.round(top)}px`;
95732
98319
  }
@@ -95765,8 +98352,8 @@ function createGoalsBarTooltip(opts) {
95765
98352
  <div class="myio-gbt__card">
95766
98353
  <div class="myio-gbt__header">
95767
98354
  <div class="myio-gbt__title-wrap">
95768
- <div class="myio-gbt__title">${esc6(data.title)}</div>
95769
- ${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>` : ""}
95770
98357
  </div>
95771
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>
95772
98359
  <button class="myio-gbt__btn myio-gbt__btn--close" data-action="close" title="Fechar">\u2715</button>
@@ -96569,11 +99156,11 @@ function _rerenderFromCache() {
96569
99156
  const { labels, totals, goalLine, prevTotals, domain } = _lastRender;
96570
99157
  _renderChart(canvas, labels, totals, goalLine, domain, prevTotals);
96571
99158
  }
96572
- var STYLE_ID7 = "myio-goals-modal-v2-styles";
99159
+ var STYLE_ID9 = "myio-goals-modal-v2-styles";
96573
99160
  function _injectStyles(topDoc) {
96574
- if (topDoc.getElementById(STYLE_ID7)) return;
99161
+ if (topDoc.getElementById(STYLE_ID9)) return;
96575
99162
  const s = topDoc.createElement("style");
96576
- s.id = STYLE_ID7;
99163
+ s.id = STYLE_ID9;
96577
99164
  s.textContent = `
96578
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;}
96579
99166
  .gm-overlay.show{opacity:1;}
@@ -96775,19 +99362,19 @@ function _updateCoverageHint(topDoc) {
96775
99362
  const deviceCount = data.granularity === "DEVICE" ? data.devices?.length ?? 0 : 0;
96776
99363
  hint.textContent = `\u26A0 Meta incompleta para este dom\xEDnio/ano` + (deviceCount > 0 ? ` \xB7 Por medidor (${deviceCount})` : "") + ` \u2014 passe o mouse para detalhes`;
96777
99364
  tabs.insertAdjacentElement("afterend", hint);
96778
- 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;");
96779
99366
  try {
96780
99367
  _coverageDetach = InfoTooltip.attach(hint, () => {
96781
99368
  const d = _getGoalsData(_currentDomain);
96782
99369
  const parts = [];
96783
99370
  if (hasCoverageGaps(d?.coverageGaps)) {
96784
- 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>`);
96785
99372
  }
96786
99373
  (d?.devices || []).forEach((dev) => {
96787
99374
  if (!hasCoverageGaps(dev?.coverageGaps)) return;
96788
99375
  const label = dev.label || dev.code || "medidor";
96789
99376
  parts.push(
96790
- `<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>`
96791
99378
  );
96792
99379
  });
96793
99380
  return {
@@ -96980,8 +99567,8 @@ var CSV_SEPARATORS = {
96980
99567
  "default": ";"
96981
99568
  };
96982
99569
  function formatDateForFilename(date) {
96983
- const pad = (n) => n.toString().padStart(2, "0");
96984
- 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())}`;
96985
99572
  }
96986
99573
  function sanitizeFilename(str) {
96987
99574
  return str.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, "_").substring(0, 50);
@@ -99424,7 +102011,7 @@ function createMyIOTheme(settings, mode) {
99424
102011
  // src/components/settings-hub/openSettingsHubModal.ts
99425
102012
  var ROUTE_DELAY_MS = 250;
99426
102013
  var MODAL_ID = "myio-conf-picker";
99427
- var STYLE_ID8 = "myio-conf-picker-styles";
102014
+ var STYLE_ID10 = "myio-conf-picker-styles";
99428
102015
  var HUB_OPTIONS = [
99429
102016
  {
99430
102017
  action: "temperature",
@@ -99615,7 +102202,7 @@ var HUB_CSS = `
99615
102202
  color: var(--myio-brand-700, #7B2FF7);
99616
102203
  }
99617
102204
  `;
99618
- function escapeHtml7(value) {
102205
+ function escapeHtml9(value) {
99619
102206
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
99620
102207
  }
99621
102208
  function resolveTopDocument() {
@@ -99635,9 +102222,9 @@ function resolveHubThemeVars(explicit) {
99635
102222
  function openSettingsHubModal(options) {
99636
102223
  const { customerName = "", isSuperAdmin: isSuperAdmin2 = false, handlers } = options;
99637
102224
  const doc = options.targetDocument || resolveTopDocument();
99638
- if (!doc.getElementById(STYLE_ID8)) {
102225
+ if (!doc.getElementById(STYLE_ID10)) {
99639
102226
  const style = doc.createElement("style");
99640
- style.id = STYLE_ID8;
102227
+ style.id = STYLE_ID10;
99641
102228
  style.textContent = HUB_CSS;
99642
102229
  doc.head.appendChild(style);
99643
102230
  }
@@ -99662,7 +102249,7 @@ function openSettingsHubModal(options) {
99662
102249
  <div class="myio-conf-picker__overlay"></div>
99663
102250
  <div class="myio-conf-picker__content">
99664
102251
  <div class="myio-conf-picker__header">
99665
- <h3>\u2699\uFE0F Configura\xE7\xF5es${customerName ? ` \u2014 ${escapeHtml7(customerName)}` : ""}</h3>
102252
+ <h3>\u2699\uFE0F Configura\xE7\xF5es${customerName ? ` \u2014 ${escapeHtml9(customerName)}` : ""}</h3>
99666
102253
  <button class="myio-conf-picker__close" aria-label="Fechar">&times;</button>
99667
102254
  </div>
99668
102255
  <div class="myio-conf-picker__body">${cardsHtml}
@@ -100857,7 +103444,7 @@ function createTariffApiAdapter(client, opts) {
100857
103444
 
100858
103445
  // src/components/pricing-panel/openPricingPanel.ts
100859
103446
  var MODAL_ID2 = "myio-pricing-panel";
100860
- function escapeHtml8(value) {
103447
+ function escapeHtml10(value) {
100861
103448
  return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
100862
103449
  }
100863
103450
  function resolveTopDocument2() {
@@ -101012,7 +103599,7 @@ function openPricingPanel(params) {
101012
103599
  }
101013
103600
  const initDomain = params.domain || "energy";
101014
103601
  const initCategory = params.category || "lojas";
101015
- 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("");
101016
103603
  modal.innerHTML = `
101017
103604
  <div class="myio-pricing__overlay"></div>
101018
103605
  <div class="myio-pricing__content">
@@ -101227,9 +103814,9 @@ function openPricingPanel(params) {
101227
103814
  const pushAudit = (rec) => {
101228
103815
  if (rec) auditLog = [rec, ...auditLog];
101229
103816
  };
101230
- 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(
101231
103818
  DOMAIN_LABEL[domain]
101232
- )} \xB7 ${escapeHtml8(CATEGORY_LABEL[category])}</span>`;
103819
+ )} \xB7 ${escapeHtml10(CATEGORY_LABEL[category])}</span>`;
101233
103820
  const renderList2 = () => {
101234
103821
  const customerId = customerSel.value;
101235
103822
  const rows = entries.filter((e) => e.customerId === customerId).sort((a, b) => {
@@ -101242,15 +103829,15 @@ function openPricingPanel(params) {
101242
103829
  const boundsKey = b ? `${b.start}__${b.end}` : "";
101243
103830
  return `
101244
103831
  <li class="myio-pricing__item">
101245
- <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(
101246
103833
  formatPeriodLabel(e)
101247
103834
  )}</span>
101248
- <span class="myio-pricing__item-price">${escapeHtml8(formatBRL2(e.pricePerKwh))} ${escapeHtml8(
103835
+ <span class="myio-pricing__item-price">${escapeHtml10(formatBRL2(e.pricePerKwh))} ${escapeHtml10(
101249
103836
  unitLabel(e.domain)
101250
103837
  )}</span>
101251
- <button class="myio-pricing__remove" data-remove="${escapeHtml8(e.domain)}|${escapeHtml8(
103838
+ <button class="myio-pricing__remove" data-remove="${escapeHtml10(e.domain)}|${escapeHtml10(
101252
103839
  e.category
101253
- )}|${escapeHtml8(boundsKey)}" aria-label="Remover" type="button">\u2715</button>
103840
+ )}|${escapeHtml10(boundsKey)}" aria-label="Remover" type="button">\u2715</button>
101254
103841
  </li>`;
101255
103842
  }).join("");
101256
103843
  emptyEl.style.display = rows.length ? "none" : "";
@@ -101349,8 +103936,8 @@ function openPricingPanel(params) {
101349
103936
  kpiEl.innerHTML = cards.map(
101350
103937
  (c) => `
101351
103938
  <div class="myio-pricing__kpi">
101352
- <div class="myio-pricing__kpi-value">${escapeHtml8(c.value)}</div>
101353
- <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>
101354
103941
  </div>`
101355
103942
  ).join("");
101356
103943
  renderReport();
@@ -101369,11 +103956,11 @@ function openPricingPanel(params) {
101369
103956
  reportBody.innerHTML = rows.length ? rows.map(
101370
103957
  (e) => `
101371
103958
  <tr>
101372
- <td>${escapeHtml8(customerTitle(e.customerId))}</td>
101373
- <td>${escapeHtml8(DOMAIN_LABEL[e.domain])}</td>
101374
- <td>${escapeHtml8(CATEGORY_LABEL[e.category])}</td>
101375
- <td>${escapeHtml8(formatPeriodLabel(e))}</td>
101376
- <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>
101377
103964
  </tr>`
101378
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>`;
101379
103966
  }
@@ -101383,15 +103970,15 @@ function openPricingPanel(params) {
101383
103970
  return `
101384
103971
  <li class="myio-pricing__history-item">
101385
103972
  <div class="myio-pricing__history-head">
101386
- <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(
101387
103974
  rec.action
101388
103975
  )}</span>
101389
103976
  ${badge(rec.domain, rec.category)}
101390
- <span>${escapeHtml8(rec.periodLabel)}${escapeHtml8(priceInfo)}</span>
103977
+ <span>${escapeHtml10(rec.periodLabel)}${escapeHtml10(priceInfo)}</span>
101391
103978
  </div>
101392
- <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(
101393
103980
  rec.by || "\u2014"
101394
- )} \xB7 ${escapeHtml8(fmtTimestamp(rec.at))}</div>
103981
+ )} \xB7 ${escapeHtml10(fmtTimestamp(rec.at))}</div>
101395
103982
  </li>`;
101396
103983
  }).join("");
101397
103984
  historyEmpty.style.display = auditLog.length ? "none" : "";
@@ -102090,11 +104677,11 @@ function deviceCategoryToPanelTerm(cat) {
102090
104677
  function panelTermToDeviceCategory(term) {
102091
104678
  return term == null ? null : panelCategoryToWire(term);
102092
104679
  }
102093
- function esc7(s) {
104680
+ function esc8(s) {
102094
104681
  return String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
102095
104682
  }
102096
104683
  function categoryOptionsHTML(selected) {
102097
- 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>`;
102098
104685
  return opt("", NONE_LABEL, selected == null) + opt("COMMON_AREA", deviceCategoryLabel("COMMON_AREA"), selected === "COMMON_AREA") + opt("SPECIFIC", deviceCategoryLabel("SPECIFIC"), selected === "SPECIFIC");
102099
104686
  }
102100
104687
  function openDeviceCategoryPanel(params) {
@@ -102131,8 +104718,8 @@ function openDeviceCategoryPanel(params) {
102131
104718
  <select class="myio-devcat__filter" data-devcat-filter="1" aria-label="Filtrar por categoria">
102132
104719
  <option value="all">Todas as categorias</option>
102133
104720
  <option value="uncategorized">Sem categoria</option>
102134
- <option value="COMMON_AREA">${esc7(deviceCategoryLabel("COMMON_AREA"))}</option>
102135
- <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>
102136
104723
  </select>
102137
104724
  <label class="myio-devcat__filter" style="display:flex;align-items:center;gap:6px;cursor:pointer;">
102138
104725
  <input type="checkbox" data-devcat-selectall="1" /> Selecionar vis\xEDveis
@@ -102141,9 +104728,9 @@ function openDeviceCategoryPanel(params) {
102141
104728
  <div class="myio-devcat__bulkbar" data-devcat-bulkbar="1">
102142
104729
  <span data-devcat-bulkcount="1">0 selecionados</span>
102143
104730
  <select data-devcat-bulkcat="1" aria-label="Categoria para os selecionados">
102144
- <option value="">${esc7(NONE_LABEL)}</option>
102145
- <option value="COMMON_AREA">${esc7(deviceCategoryLabel("COMMON_AREA"))}</option>
102146
- <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>
102147
104734
  </select>
102148
104735
  <button type="button" class="myio-devcat__bulk-apply" data-devcat-bulkapply="1">Aplicar a selecionados</button>
102149
104736
  </div>
@@ -102173,16 +104760,16 @@ function openDeviceCategoryPanel(params) {
102173
104760
  return rows.filter((r) => matchesFilter(r) && matchesSearch(r));
102174
104761
  }
102175
104762
  function rowHTML(r) {
102176
- const name = esc7(r.label || r.code || r.deviceId);
102177
- 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>` : "";
102178
104765
  const checked = selected.has(r.deviceId) ? " checked" : "";
102179
104766
  const focused = r.deviceId === params.focusDeviceId ? ' data-focused="1"' : "";
102180
104767
  const catAttr = r.tariffCategory == null ? "null" : r.tariffCategory;
102181
- 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(
102182
104769
  r.deviceId
102183
- )}"${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(
102184
104771
  r.deviceId
102185
- )}" 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>`;
102186
104773
  }
102187
104774
  function render3() {
102188
104775
  const vis = visibleRows();
@@ -102342,7 +104929,7 @@ function openDeviceCategoryPanel(params) {
102342
104929
  rows = await port.listDevices({ customerId, domain: params.domain });
102343
104930
  } catch (err) {
102344
104931
  rows = [];
102345
- 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(
102346
104933
  err?.message || "erro"
102347
104934
  )}</div>`;
102348
104935
  countEl.textContent = "";
@@ -109726,536 +112313,6 @@ function refreshIncidentBadges(opts) {
109726
112313
  });
109727
112314
  }
109728
112315
 
109729
- // src/components/premium-modals/dialog/styles.ts
109730
- var FONT_LINK_ID = "myio-dialog-font-nunito";
109731
- var STYLE_ID9 = "myio-dialog-styles";
109732
- function injectDialogStyles() {
109733
- if (!document.getElementById(FONT_LINK_ID)) {
109734
- const link = document.createElement("link");
109735
- link.id = FONT_LINK_ID;
109736
- link.rel = "stylesheet";
109737
- link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
109738
- document.head.appendChild(link);
109739
- }
109740
- if (document.getElementById(STYLE_ID9)) return;
109741
- const style = document.createElement("style");
109742
- style.id = STYLE_ID9;
109743
- style.textContent = `
109744
- .myio-dialog-overlay {
109745
- position: fixed;
109746
- inset: 0;
109747
- display: flex;
109748
- align-items: center;
109749
- justify-content: center;
109750
- background: rgba(17, 24, 39, 0.55);
109751
- font-family: 'Nunito', system-ui, sans-serif;
109752
- }
109753
- .myio-dialog {
109754
- background: #ffffff;
109755
- color: #1f2937;
109756
- border-radius: 12px;
109757
- box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
109758
- max-width: 90vw;
109759
- max-height: 85vh;
109760
- display: flex;
109761
- flex-direction: column;
109762
- overflow: hidden;
109763
- }
109764
- .myio-dialog__accent {
109765
- height: 4px;
109766
- background: #7C3AED;
109767
- }
109768
- .myio-dialog__header {
109769
- display: flex;
109770
- align-items: flex-start;
109771
- gap: 10px;
109772
- padding: 16px 20px 0 20px;
109773
- }
109774
- .myio-dialog__icon {
109775
- font-size: 20px;
109776
- line-height: 1.3;
109777
- }
109778
- .myio-dialog__title {
109779
- flex: 1;
109780
- margin: 0;
109781
- font-size: 16px;
109782
- font-weight: 700;
109783
- line-height: 1.35;
109784
- }
109785
- .myio-dialog__close {
109786
- border: none;
109787
- background: transparent;
109788
- color: #9ca3af;
109789
- font-size: 18px;
109790
- line-height: 1;
109791
- padding: 2px 6px;
109792
- margin: -2px -8px 0 0;
109793
- border-radius: 6px;
109794
- cursor: pointer;
109795
- }
109796
- .myio-dialog__close:hover {
109797
- background: rgba(0, 0, 0, 0.06);
109798
- color: #4b5563;
109799
- }
109800
- .myio-dialog__message {
109801
- margin: 0;
109802
- padding: 10px 20px 4px 20px;
109803
- font-size: 13.5px;
109804
- line-height: 1.55;
109805
- color: #4b5563;
109806
- overflow-y: auto;
109807
- }
109808
- .myio-dialog__footer {
109809
- display: flex;
109810
- justify-content: flex-end;
109811
- gap: 8px;
109812
- padding: 16px 20px;
109813
- }
109814
- .myio-dialog__btn {
109815
- font-family: inherit;
109816
- font-size: 13px;
109817
- font-weight: 600;
109818
- padding: 8px 16px;
109819
- border-radius: 8px;
109820
- border: 1px solid transparent;
109821
- cursor: pointer;
109822
- transition: filter 0.15s ease, background 0.15s ease;
109823
- }
109824
- .myio-dialog__btn:hover {
109825
- filter: brightness(0.94);
109826
- }
109827
- .myio-dialog__btn:focus-visible {
109828
- outline: 2px solid #7C3AED;
109829
- outline-offset: 2px;
109830
- }
109831
- .myio-dialog__btn--primary {
109832
- background: #7C3AED;
109833
- color: #ffffff;
109834
- }
109835
- .myio-dialog__btn--secondary {
109836
- background: #ffffff;
109837
- color: #374151;
109838
- border-color: #d1d5db;
109839
- }
109840
- .myio-dialog__btn--danger {
109841
- background: #dc2626;
109842
- color: #ffffff;
109843
- }
109844
- .myio-dialog__btn--success {
109845
- background: #16a34a;
109846
- color: #ffffff;
109847
- }
109848
-
109849
- /* Severity accents (message dialog) */
109850
- .myio-dialog--success .myio-dialog__accent { background: #16a34a; }
109851
- .myio-dialog--warning .myio-dialog__accent { background: #d97706; }
109852
- .myio-dialog--error .myio-dialog__accent { background: #dc2626; }
109853
-
109854
- /* \u2500\u2500 Dark theme \u2500\u2500 */
109855
- .myio-dialog-overlay--dark .myio-dialog {
109856
- background: #1f2937;
109857
- color: #f3f4f6;
109858
- }
109859
- .myio-dialog-overlay--dark .myio-dialog__message {
109860
- color: #d1d5db;
109861
- }
109862
- .myio-dialog-overlay--dark .myio-dialog__close {
109863
- color: #9ca3af;
109864
- }
109865
- .myio-dialog-overlay--dark .myio-dialog__close:hover {
109866
- background: rgba(255, 255, 255, 0.08);
109867
- color: #e5e7eb;
109868
- }
109869
- .myio-dialog-overlay--dark .myio-dialog__btn--secondary {
109870
- background: #1f2937;
109871
- color: #e5e7eb;
109872
- border-color: #4b5563;
109873
- }
109874
- `;
109875
- document.head.appendChild(style);
109876
- }
109877
-
109878
- // src/components/premium-modals/dialog/openDialog.ts
109879
- var BASE_Z_INDEX = 10500;
109880
- var openCount = 0;
109881
- function escapeHtml9(value) {
109882
- return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
109883
- }
109884
- function escapeMessage(value) {
109885
- return escapeHtml9(value).replace(/\n/g, "<br>");
109886
- }
109887
- function cssWidth(width) {
109888
- if (width == null) return "420px";
109889
- return typeof width === "number" ? `${width}px` : width;
109890
- }
109891
- var SEVERITY_META = {
109892
- info: { title: "Informa\xE7\xE3o", icon: "\u2139\uFE0F" },
109893
- success: { title: "Sucesso", icon: "\u2705" },
109894
- warning: { title: "Aten\xE7\xE3o", icon: "\u26A0\uFE0F" },
109895
- error: { title: "Erro", icon: "\u274C" }
109896
- };
109897
- function openDialog(params) {
109898
- if (!Array.isArray(params.buttons) || params.buttons.length === 0) {
109899
- throw new Error("[openConfirmDialog] params.buttons must contain at least one button");
109900
- }
109901
- injectDialogStyles();
109902
- const dismissible = params.dismissible ?? true;
109903
- const theme = params.theme ?? "light";
109904
- const idBase = `myio-dialog-${++openCount}`;
109905
- const overlay = document.createElement("div");
109906
- overlay.className = `myio-dialog-overlay${theme === "dark" ? " myio-dialog-overlay--dark" : ""}`;
109907
- overlay.style.zIndex = String(BASE_Z_INDEX + openCount);
109908
- const buttonsHtml = params.buttons.map((btn, i) => {
109909
- const variant = btn.variant ?? "secondary";
109910
- return `<button type="button" class="myio-dialog__btn myio-dialog__btn--${variant}" data-index="${i}">${escapeHtml9(btn.label)}</button>`;
109911
- }).join("");
109912
- overlay.innerHTML = `
109913
- <div class="myio-dialog${params.modifier ? ` ${params.modifier}` : ""}"
109914
- role="dialog" aria-modal="true"
109915
- aria-labelledby="${idBase}-title" aria-describedby="${idBase}-message"
109916
- style="width:${cssWidth(params.width)}">
109917
- <div class="myio-dialog__accent"></div>
109918
- <div class="myio-dialog__header">
109919
- ${params.icon ? `<span class="myio-dialog__icon" aria-hidden="true">${params.icon}</span>` : ""}
109920
- <h3 class="myio-dialog__title" id="${idBase}-title">${escapeHtml9(params.title)}</h3>
109921
- ${dismissible ? `<button type="button" class="myio-dialog__close" aria-label="Fechar">\u2715</button>` : ""}
109922
- </div>
109923
- <p class="myio-dialog__message" id="${idBase}-message">${escapeMessage(params.message)}</p>
109924
- <div class="myio-dialog__footer">${buttonsHtml}</div>
109925
- </div>
109926
- `;
109927
- const previousFocus = document.activeElement;
109928
- const target = params.container ?? document.body;
109929
- target.appendChild(overlay);
109930
- const root = overlay.querySelector(".myio-dialog");
109931
- const buttonEls = Array.from(root.querySelectorAll(".myio-dialog__btn"));
109932
- return new Promise((resolve) => {
109933
- let settled = false;
109934
- let autoCloseTimer;
109935
- const close = (result) => {
109936
- if (settled) return;
109937
- settled = true;
109938
- if (autoCloseTimer) clearTimeout(autoCloseTimer);
109939
- document.removeEventListener("keydown", onKeydown, true);
109940
- overlay.remove();
109941
- if (previousFocus && previousFocus.isConnected) previousFocus.focus();
109942
- resolve(result);
109943
- };
109944
- const onKeydown = (e) => {
109945
- if (!overlay.isConnected) return;
109946
- if (e.key === "Escape" && dismissible) {
109947
- e.stopPropagation();
109948
- close(null);
109949
- return;
109950
- }
109951
- if (e.key === "Tab") {
109952
- const focusables = Array.from(
109953
- root.querySelectorAll("button:not([disabled])")
109954
- );
109955
- if (focusables.length === 0) return;
109956
- const first = focusables[0];
109957
- const last = focusables[focusables.length - 1];
109958
- const active = document.activeElement;
109959
- if (!root.contains(active)) {
109960
- e.preventDefault();
109961
- first.focus();
109962
- } else if (e.shiftKey && active === first) {
109963
- e.preventDefault();
109964
- last.focus();
109965
- } else if (!e.shiftKey && active === last) {
109966
- e.preventDefault();
109967
- first.focus();
109968
- }
109969
- }
109970
- };
109971
- buttonEls.forEach((el2) => {
109972
- el2.addEventListener("click", () => {
109973
- const index = Number(el2.dataset.index);
109974
- close(params.buttons[index].value);
109975
- });
109976
- });
109977
- if (dismissible) {
109978
- overlay.querySelector(".myio-dialog__close")?.addEventListener("click", () => close(null));
109979
- overlay.addEventListener("click", (e) => {
109980
- if (e.target === overlay) close(null);
109981
- });
109982
- }
109983
- document.addEventListener("keydown", onKeydown, true);
109984
- const autoFocusIndex = params.buttons.findIndex((b) => b.autoFocus);
109985
- (buttonEls[autoFocusIndex >= 0 ? autoFocusIndex : buttonEls.length - 1] ?? buttonEls[0]).focus();
109986
- if (params.autoCloseMs && params.autoCloseMs > 0) {
109987
- autoCloseTimer = setTimeout(() => close(null), params.autoCloseMs);
109988
- }
109989
- });
109990
- }
109991
- function openConfirmDialog(params) {
109992
- return openDialog(params);
109993
- }
109994
- function openMessageDialog(params) {
109995
- const severity = params.severity ?? "info";
109996
- const meta = SEVERITY_META[severity];
109997
- return openDialog({
109998
- title: params.title ?? meta.title,
109999
- message: params.message,
110000
- buttons: [
110001
- { label: params.buttonLabel ?? "OK", variant: "primary", value: "ok", autoFocus: true }
110002
- ],
110003
- theme: params.theme,
110004
- container: params.container,
110005
- modifier: severity !== "info" ? `myio-dialog--${severity}` : void 0,
110006
- icon: meta.icon,
110007
- autoCloseMs: params.autoCloseMs
110008
- }).then(() => void 0);
110009
- }
110010
-
110011
- // src/components/premium-modals/dialog/openGenericModal.ts
110012
- var STYLE_ID10 = "myio-genmodal-styles";
110013
- var FONT_LINK_ID2 = "myio-dialog-font-nunito";
110014
- var BASE_Z_INDEX2 = 10600;
110015
- var openCount2 = 0;
110016
- function escapeHtml10(value) {
110017
- return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
110018
- }
110019
- function cssSize(v, fallback) {
110020
- if (v == null) return fallback;
110021
- return typeof v === "number" ? `${v}px` : v;
110022
- }
110023
- function injectGenericModalStyles() {
110024
- if (!document.getElementById(FONT_LINK_ID2)) {
110025
- const link = document.createElement("link");
110026
- link.id = FONT_LINK_ID2;
110027
- link.rel = "stylesheet";
110028
- link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
110029
- document.head.appendChild(link);
110030
- }
110031
- if (document.getElementById(STYLE_ID10)) return;
110032
- const style = document.createElement("style");
110033
- style.id = STYLE_ID10;
110034
- style.textContent = `
110035
- .myio-genmodal-overlay {
110036
- position: fixed;
110037
- inset: 0;
110038
- display: flex;
110039
- align-items: center;
110040
- justify-content: center;
110041
- background: rgba(17, 24, 39, 0.55);
110042
- font-family: 'Nunito', system-ui, sans-serif;
110043
- }
110044
- .myio-genmodal {
110045
- background: #ffffff;
110046
- color: #1f2937;
110047
- border-radius: 12px;
110048
- box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
110049
- max-width: 90vw;
110050
- max-height: 90vh;
110051
- display: flex;
110052
- flex-direction: column;
110053
- overflow: hidden;
110054
- }
110055
- .myio-genmodal__accent { height: 4px; background: #7C3AED; flex-shrink: 0; }
110056
- .myio-genmodal__header {
110057
- display: flex;
110058
- align-items: flex-start;
110059
- gap: 10px;
110060
- padding: 16px 20px;
110061
- border-bottom: 1px solid #eef0f3;
110062
- flex-shrink: 0;
110063
- }
110064
- .myio-genmodal__icon { font-size: 22px; line-height: 1.3; }
110065
- .myio-genmodal__title {
110066
- flex: 1;
110067
- margin: 0;
110068
- font-size: 18px;
110069
- font-weight: 700;
110070
- line-height: 1.35;
110071
- }
110072
- .myio-genmodal__close {
110073
- border: none;
110074
- background: transparent;
110075
- color: #9ca3af;
110076
- font-size: 18px;
110077
- line-height: 1;
110078
- padding: 2px 6px;
110079
- margin: -2px -8px 0 0;
110080
- border-radius: 6px;
110081
- cursor: pointer;
110082
- }
110083
- .myio-genmodal__close:hover { background: rgba(0, 0, 0, 0.06); color: #4b5563; }
110084
- .myio-genmodal__body {
110085
- padding: 18px 20px;
110086
- font-size: 15px;
110087
- line-height: 1.6;
110088
- color: #374151;
110089
- overflow-y: auto;
110090
- }
110091
- /* Sensible defaults for arbitrary body HTML */
110092
- .myio-genmodal__body img { max-width: 100%; height: auto; border-radius: 8px; }
110093
- .myio-genmodal__body hr { border: none; border-top: 1px solid #e5e7eb; margin: 16px 0; }
110094
- .myio-genmodal__body h1, .myio-genmodal__body h2, .myio-genmodal__body h3, .myio-genmodal__body h4 {
110095
- margin: 0 0 8px; line-height: 1.3; color: #1f2937;
110096
- }
110097
- .myio-genmodal__body p { margin: 0 0 10px; }
110098
- .myio-genmodal__body ul, .myio-genmodal__body ol { margin: 0 0 10px; padding-left: 22px; }
110099
- .myio-genmodal__body table { border-collapse: collapse; width: 100%; }
110100
- .myio-genmodal__body th, .myio-genmodal__body td {
110101
- border: 1px solid #e5e7eb; padding: 6px 10px; text-align: left; font-size: 14px;
110102
- }
110103
- .myio-genmodal__body a { color: #7C3AED; }
110104
- .myio-genmodal__footer {
110105
- display: flex;
110106
- justify-content: flex-end;
110107
- gap: 8px;
110108
- padding: 14px 20px;
110109
- border-top: 1px solid #eef0f3;
110110
- flex-shrink: 0;
110111
- }
110112
- .myio-genmodal__btn {
110113
- font-family: inherit;
110114
- font-size: 13px;
110115
- font-weight: 600;
110116
- padding: 8px 16px;
110117
- border-radius: 8px;
110118
- border: 1px solid transparent;
110119
- cursor: pointer;
110120
- transition: filter 0.15s ease, background 0.15s ease;
110121
- }
110122
- .myio-genmodal__btn:hover { filter: brightness(0.94); }
110123
- .myio-genmodal__btn:focus-visible { outline: 2px solid #7C3AED; outline-offset: 2px; }
110124
- .myio-genmodal__btn--primary { background: #7C3AED; color: #ffffff; }
110125
- .myio-genmodal__btn--secondary { background: #ffffff; color: #374151; border-color: #d1d5db; }
110126
- .myio-genmodal__btn--danger { background: #dc2626; color: #ffffff; }
110127
- .myio-genmodal__btn--success { background: #16a34a; color: #ffffff; }
110128
-
110129
- /* \u2500\u2500 Dark theme \u2500\u2500 */
110130
- .myio-genmodal-overlay--dark .myio-genmodal { background: #1f2937; color: #f3f4f6; }
110131
- .myio-genmodal-overlay--dark .myio-genmodal__header,
110132
- .myio-genmodal-overlay--dark .myio-genmodal__footer { border-color: #374151; }
110133
- .myio-genmodal-overlay--dark .myio-genmodal__title { color: #f3f4f6; }
110134
- .myio-genmodal-overlay--dark .myio-genmodal__body { color: #d1d5db; }
110135
- .myio-genmodal-overlay--dark .myio-genmodal__body h1,
110136
- .myio-genmodal-overlay--dark .myio-genmodal__body h2,
110137
- .myio-genmodal-overlay--dark .myio-genmodal__body h3,
110138
- .myio-genmodal-overlay--dark .myio-genmodal__body h4 { color: #f3f4f6; }
110139
- .myio-genmodal-overlay--dark .myio-genmodal__body hr { border-color: #374151; }
110140
- .myio-genmodal-overlay--dark .myio-genmodal__body th,
110141
- .myio-genmodal-overlay--dark .myio-genmodal__body td { border-color: #374151; }
110142
- .myio-genmodal-overlay--dark .myio-genmodal__close { color: #9ca3af; }
110143
- .myio-genmodal-overlay--dark .myio-genmodal__close:hover { background: rgba(255,255,255,0.08); color: #e5e7eb; }
110144
- .myio-genmodal-overlay--dark .myio-genmodal__btn--secondary { background: #1f2937; color: #e5e7eb; border-color: #4b5563; }
110145
- `;
110146
- document.head.appendChild(style);
110147
- }
110148
- var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
110149
- function openGenericModal(params) {
110150
- if (typeof window === "undefined" || typeof document === "undefined") {
110151
- throw new Error("[openGenericModal] requires a browser environment");
110152
- }
110153
- if (typeof params.bodyHtml !== "string") {
110154
- throw new Error("[openGenericModal] params.bodyHtml must be a string");
110155
- }
110156
- injectGenericModalStyles();
110157
- const dismissible = params.dismissible ?? true;
110158
- const theme = params.theme ?? "light";
110159
- const buttons = params.buttons ?? [];
110160
- const idBase = `myio-genmodal-${++openCount2}`;
110161
- const overlay = document.createElement("div");
110162
- overlay.className = `myio-genmodal-overlay${theme === "dark" ? " myio-genmodal-overlay--dark" : ""}`;
110163
- overlay.style.zIndex = String(BASE_Z_INDEX2 + openCount2);
110164
- const buttonsHtml = buttons.map((btn, i) => {
110165
- const variant = btn.variant ?? "secondary";
110166
- return `<button type="button" class="myio-genmodal__btn myio-genmodal__btn--${variant}" data-index="${i}">${escapeHtml10(btn.label)}</button>`;
110167
- }).join("");
110168
- overlay.innerHTML = `
110169
- <div class="myio-genmodal${params.className ? ` ${escapeHtml10(params.className)}` : ""}"
110170
- role="dialog" aria-modal="true"
110171
- aria-labelledby="${idBase}-title"
110172
- style="width:${cssSize(params.width, "560px")}">
110173
- <div class="myio-genmodal__accent"></div>
110174
- <div class="myio-genmodal__header">
110175
- ${params.icon ? `<span class="myio-genmodal__icon" aria-hidden="true">${params.icon}</span>` : ""}
110176
- <h3 class="myio-genmodal__title" id="${idBase}-title">${escapeHtml10(params.title)}</h3>
110177
- ${dismissible ? `<button type="button" class="myio-genmodal__close" aria-label="Fechar">\u2715</button>` : ""}
110178
- </div>
110179
- <div class="myio-genmodal__body" id="${idBase}-body" style="max-height:${cssSize(params.bodyMaxHeight, "70vh")}"></div>
110180
- ${buttons.length ? `<div class="myio-genmodal__footer">${buttonsHtml}</div>` : ""}
110181
- </div>
110182
- `;
110183
- const root = overlay.querySelector(".myio-genmodal");
110184
- const bodyEl = root.querySelector(".myio-genmodal__body");
110185
- const titleEl = root.querySelector(".myio-genmodal__title");
110186
- bodyEl.innerHTML = params.bodyHtml;
110187
- const previousFocus = document.activeElement;
110188
- const target = params.container ?? document.body;
110189
- target.appendChild(overlay);
110190
- let closed = false;
110191
- const close = () => {
110192
- if (closed) return;
110193
- closed = true;
110194
- document.removeEventListener("keydown", onKeydown, true);
110195
- overlay.remove();
110196
- if (previousFocus && previousFocus.isConnected) previousFocus.focus();
110197
- if (params.onClose) params.onClose();
110198
- };
110199
- const onKeydown = (e) => {
110200
- if (!overlay.isConnected) return;
110201
- if (e.key === "Escape" && dismissible) {
110202
- e.stopPropagation();
110203
- close();
110204
- return;
110205
- }
110206
- if (e.key === "Tab") {
110207
- const focusables = Array.from(root.querySelectorAll(FOCUSABLE));
110208
- if (focusables.length === 0) {
110209
- e.preventDefault();
110210
- return;
110211
- }
110212
- const first = focusables[0];
110213
- const last = focusables[focusables.length - 1];
110214
- const active = document.activeElement;
110215
- if (!root.contains(active)) {
110216
- e.preventDefault();
110217
- first.focus();
110218
- } else if (e.shiftKey && active === first) {
110219
- e.preventDefault();
110220
- last.focus();
110221
- } else if (!e.shiftKey && active === last) {
110222
- e.preventDefault();
110223
- first.focus();
110224
- }
110225
- }
110226
- };
110227
- root.querySelectorAll(".myio-genmodal__btn").forEach((el2) => {
110228
- el2.addEventListener("click", () => {
110229
- const btn = buttons[Number(el2.dataset.index)];
110230
- if (!btn) return;
110231
- if (params.onButton) params.onButton(btn.value);
110232
- if (btn.closeOnClick !== false) close();
110233
- });
110234
- });
110235
- if (dismissible) {
110236
- overlay.querySelector(".myio-genmodal__close")?.addEventListener("click", () => close());
110237
- overlay.addEventListener("click", (e) => {
110238
- if (e.target === overlay) close();
110239
- });
110240
- }
110241
- document.addEventListener("keydown", onKeydown, true);
110242
- const autoFocusIdx = buttons.findIndex((b) => b.autoFocus);
110243
- const footerEls = Array.from(root.querySelectorAll(".myio-genmodal__btn"));
110244
- const initialFocus = (autoFocusIdx >= 0 ? footerEls[autoFocusIdx] : void 0) ?? footerEls[footerEls.length - 1] ?? root.querySelector(`.myio-genmodal__body ${FOCUSABLE}`) ?? overlay.querySelector(".myio-genmodal__close") ?? root;
110245
- initialFocus.focus?.();
110246
- return {
110247
- close,
110248
- setBodyHtml: (html) => {
110249
- bodyEl.innerHTML = html;
110250
- },
110251
- setTitle: (title) => {
110252
- titleEl.textContent = title;
110253
- },
110254
- getBodyEl: () => bodyEl,
110255
- getRoot: () => root
110256
- };
110257
- }
110258
-
110259
112316
  // src/components/myio-gcdr-login/dom.ts
110260
112317
  function h2(doc, tag, attrs = {}, ...children) {
110261
112318
  const el2 = doc.createElement(tag);
@@ -131497,7 +133554,7 @@ var HeaderShoppingView = class {
131497
133554
  tip.id = "tbx-global-tooltip";
131498
133555
  document.body.appendChild(tip);
131499
133556
  }
131500
- const pad = 10;
133557
+ const pad3 = 10;
131501
133558
  const position = (ev) => {
131502
133559
  if (!tip) return;
131503
133560
  tip.textContent = text2;
@@ -131505,8 +133562,8 @@ var HeaderShoppingView = class {
131505
133562
  let y = ev.clientY - 36;
131506
133563
  const vw = window.innerWidth;
131507
133564
  const rect = tip.getBoundingClientRect();
131508
- if (x + rect.width + pad > vw) x = vw - rect.width - pad;
131509
- 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;
131510
133567
  tip.style.left = x + "px";
131511
133568
  tip.style.top = y + "px";
131512
133569
  };
@@ -144651,10 +146708,10 @@ var AlarmsNotificationsPanelView = class {
144651
146708
  const renderListPanel = (group, items, sel) => {
144652
146709
  if (items.length === 0) return '<div class="afm-empty">Nenhum item encontrado</div>';
144653
146710
  return items.map((item) => {
144654
- const esc8 = this.esc(item);
146711
+ const esc9 = this.esc(item);
144655
146712
  return `<label class="afm-check-item">
144656
- <input type="checkbox" class="afm-checkbox" data-group="${group}" data-value="${esc8}"${sel.has(item) ? " checked" : ""}>
144657
- <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>
144658
146715
  </label>`;
144659
146716
  }).join("");
144660
146717
  };
@@ -157532,7 +159589,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
157532
159589
  const docTitle = buildActivationReportTitle(deviceName, opts.customerName);
157533
159590
  const issuedAt = formatIssuedAt(/* @__PURE__ */ new Date(), locale);
157534
159591
  const utilization = data.totalHours > 0 ? (data.totalOnMinutes / (data.totalHours * 60) * 100).toFixed(1) : "0.0";
157535
- 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]);
157536
159593
  const htmlContent = `
157537
159594
  <!DOCTYPE html>
157538
159595
  <html lang="pt-BR">
@@ -157584,14 +159641,14 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
157584
159641
  <div class="brand">MyIO BAS</div>
157585
159642
  <h1>Relat\xF3rio de Acionamentos</h1>
157586
159643
  <div class="meta">
157587
- <div><b>Dispositivo:</b> ${esc8(deviceName)}</div>
159644
+ <div><b>Dispositivo:</b> ${esc9(deviceName)}</div>
157588
159645
  <div><b>Per\xEDodo:</b> ${periodStart} \u2014 ${periodEnd}</div>
157589
159646
  </div>
157590
159647
  </div>
157591
159648
  ${customerName ? `
157592
159649
  <div class="customer-badge">
157593
159650
  <div class="label">Cliente</div>
157594
- <div class="value">${esc8(customerName)}</div>
159651
+ <div class="value">${esc9(customerName)}</div>
157595
159652
  </div>` : ""}
157596
159653
  </div>
157597
159654
 
@@ -157602,7 +159659,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
157602
159659
  </div>
157603
159660
  <div class="summary-card accent-green">
157604
159661
  <div class="summary-value">${formatDurationMinutes(data.totalOnMinutes)}</div>
157605
- <div class="summary-label">Tempo ${esc8(labelOn)}</div>
159662
+ <div class="summary-label">Tempo ${esc9(labelOn)}</div>
157606
159663
  </div>
157607
159664
  <div class="summary-card accent-amber">
157608
159665
  <div class="summary-value">${utilization}%</div>
@@ -157630,7 +159687,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
157630
159687
  <tr>
157631
159688
  <td>${formatDateForExport(seg.startTime, locale)}</td>
157632
159689
  <td>${formatDateForExport(seg.endTime, locale)}</td>
157633
- <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>
157634
159691
  <td>${formatDurationMinutes(seg.durationMinutes)}</td>
157635
159692
  <td>${seg.source === "manual" ? "Manual" : seg.source === "schedule" ? "Agendamento" : seg.source === "automation" ? "Automa\xE7\xE3o" : "Desconhecido"}</td>
157636
159693
  </tr>
@@ -157639,7 +159696,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
157639
159696
  </table>
157640
159697
 
157641
159698
  <div class="footer">
157642
- <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>
157643
159700
  </div>
157644
159701
  </body>
157645
159702
  </html>
@@ -165667,9 +167724,9 @@ async function drawTag(doc, x, y, w, h3, device, gateway, allDevices) {
165667
167724
  doc.setDrawColor(235);
165668
167725
  doc.setLineWidth(0.15);
165669
167726
  doc.rect(x, frameY, w, frameH);
165670
- const pad = Math.max(2, h3 * 0.05);
165671
- const innerX = x + pad;
165672
- 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;
165673
167730
  const titleAreaH = h3 * 0.15;
165674
167731
  const qrAreaTop = y + titleAreaH;
165675
167732
  const qrAreaH = h3 - titleAreaH - 12;
@@ -172708,6 +174765,7 @@ var version = package_default.version || "0.0.0";
172708
174765
  createFreshdeskTicket,
172709
174766
  createGcdrAnnotationsAuthStrategy,
172710
174767
  createGcdrAnnotationsClient,
174768
+ createGcdrReportJobsApi,
172711
174769
  createGoalsBarTooltip,
172712
174770
  createGoalsMoneyClient,
172713
174771
  createGranularitySelector,
@@ -172740,6 +174798,7 @@ var version = package_default.version || "0.0.0";
172740
174798
  createPdfButton,
172741
174799
  createPremiumModalChrome,
172742
174800
  createPresetupGateway,
174801
+ createProfileClassifier,
172743
174802
  createRealTimeClock,
172744
174803
  createReportMoneyColumn,
172745
174804
  createScheduleCard,
@@ -172753,6 +174812,7 @@ var version = package_default.version || "0.0.0";
172753
174812
  createSwitchControl,
172754
174813
  createTariffApiAdapter,
172755
174814
  createTariffApiClient,
174815
+ createTbEnvelopeStore,
172756
174816
  createTelemetryGridComponent,
172757
174817
  createTelemetryGridShoppingComponent,
172758
174818
  createTelemetryInfoShoppingComponent,
@@ -172769,6 +174829,8 @@ var version = package_default.version || "0.0.0";
172769
174829
  decodePayload,
172770
174830
  decodePayloadBase64Xor,
172771
174831
  deleteFreshdeskTicket,
174832
+ demandDevicesFromItems,
174833
+ demandReport,
172772
174834
  deriveCentralConnectivity,
172773
174835
  detectContext,
172774
174836
  detectDeviceType,
@@ -173023,6 +175085,7 @@ var version = package_default.version || "0.0.0";
173023
175085
  openDashboardPopupSettings,
173024
175086
  openDashboardPopupWaterTank,
173025
175087
  openDemandModal,
175088
+ openDemandPanelV2,
173026
175089
  openDeviceCategoryPanel,
173027
175090
  openDeviceProfileModal,
173028
175091
  openGCDRSyncModal,
@@ -173132,6 +175195,7 @@ var version = package_default.version || "0.0.0";
173132
175195
  summaryDeviceToTotalsRow,
173133
175196
  summaryLastReadingTs,
173134
175197
  summaryLookbackFromIso,
175198
+ tbUserFromJwt,
173135
175199
  telemetryInfoFormatEnergy,
173136
175200
  telemetryInfoFormatPercentage,
173137
175201
  telemetryInfoFormatWater,