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 +2809 -745
- package/dist/index.d.cts +839 -1
- package/dist/index.d.ts +839 -1
- package/dist/index.js +2781 -724
- package/dist/myio-js-library.umd.js +2779 -724
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
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.
|
|
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
|
|
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(
|
|
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
|
|
31085
|
-
const d = new Date(dateLike.getTime() -
|
|
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
|
|
31387
|
-
return rows.map((r) => r.map((c) => `"${
|
|
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
|
|
35739
|
-
const base =
|
|
35745
|
+
const esc9 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
-
${
|
|
35745
|
-
<button type="button" class="myio-dr-copy" data-copy="${
|
|
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
|
|
40711
|
+
const pad3 = (n) => String(n).padStart(2, "0");
|
|
40705
40712
|
const fmtTs = (ts) => {
|
|
40706
40713
|
const d = new Date(ts);
|
|
40707
|
-
return `${
|
|
40714
|
+
return `${pad3(d.getDate())}/${pad3(d.getMonth() + 1)} ${pad3(d.getHours())}:${pad3(d.getMinutes())}`;
|
|
40708
40715
|
};
|
|
40709
|
-
const
|
|
40716
|
+
const fmtDay2 = (ts) => {
|
|
40710
40717
|
const d = new Date(ts);
|
|
40711
|
-
return `${
|
|
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()}-${
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
68910
|
-
0 0 0 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
|
|
68917
|
-
0 0 0 4px
|
|
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
|
|
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:
|
|
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
|
|
69200
|
-
0 0 30px
|
|
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
|
|
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:
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
69770
|
-
border-color:
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
69913
|
-
0 0 30px
|
|
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:
|
|
70002
|
-
border-color:
|
|
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>' :
|
|
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="
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
70549
|
-
const
|
|
70550
|
-
|
|
70551
|
-
|
|
70552
|
-
|
|
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
|
|
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("")
|
|
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
|
-
|
|
70566
|
-
|
|
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
|
|
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
|
|
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
|
|
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("")
|
|
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(
|
|
70686
|
+
return row(esc9(a.deviceLabel || "Dispositivo"), `${esc9(text2)}${when ? ` \xB7 ${esc9(when)}` : ""}`);
|
|
70587
70687
|
}).join("");
|
|
70588
|
-
return
|
|
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
|
|
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(
|
|
70594
|
-
|
|
70595
|
-
|
|
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
|
|
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
|
|
75082
|
+
const pad3 = 6;
|
|
74972
75083
|
const maxVal = Math.max(...values, 1);
|
|
74973
75084
|
const points = values.map((v, i) => {
|
|
74974
|
-
const x =
|
|
74975
|
-
const y = height -
|
|
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 -
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[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
|
|
82069
|
+
function esc5(s) {
|
|
79483
82070
|
return String(s == null ? "" : s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
|
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) =>
|
|
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) =>
|
|
79605
|
-
merge: (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) =>
|
|
79609
|
-
importCsv: (body, dryRun) =>
|
|
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) =>
|
|
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) =>
|
|
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;">${
|
|
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;">${
|
|
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")} ${
|
|
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>${
|
|
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 ?
|
|
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">${
|
|
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="${
|
|
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>${
|
|
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="${
|
|
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`)}">${
|
|
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")} ${
|
|
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">[${
|
|
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>${
|
|
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
|
|
90239
|
+
function esc6(value) {
|
|
87653
90240
|
return String(value == null ? "" : value).replace(
|
|
87654
90241
|
/[&<>"]/g,
|
|
87655
90242
|
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[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>${
|
|
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>${
|
|
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">${
|
|
87732
|
-
<span class="myio-col-summary__legend-val">${
|
|
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">${
|
|
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="${
|
|
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">${
|
|
87767
|
-
<span class="myio-col-summary__kpi-value myio-col-summary__kpi-value--accent">${avg === null ? dash :
|
|
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 ?
|
|
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="${
|
|
87781
|
-
<span class="myio-col-summary__val">${
|
|
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="${
|
|
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">${
|
|
87803
|
-
<span class="myio-col-summary__footer-value">${avg === null ? dash :
|
|
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">${
|
|
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">${
|
|
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">${
|
|
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="${
|
|
87847
|
-
<span class="myio-col-summary__val">${
|
|
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">${
|
|
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
|
|
98045
|
+
var STYLE_ID8 = "myio-goals-bar-tooltip-styles";
|
|
95459
98046
|
var DEFAULT_ACCENT = "#6c5ce7";
|
|
95460
98047
|
function injectStyles12(doc) {
|
|
95461
|
-
if (doc.getElementById(
|
|
98048
|
+
if (doc.getElementById(STYLE_ID8)) return;
|
|
95462
98049
|
const s = doc.createElement("style");
|
|
95463
|
-
s.id =
|
|
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
|
|
98237
|
+
function esc7(v) {
|
|
95651
98238
|
return String(v ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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 ? ` ${
|
|
95672
|
-
return `<span class="myio-gbt__chip myio-gbt__chip--${tone}">${arrow} ${
|
|
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:${
|
|
95681
|
-
const icon2 = row.icon ? `<span class="myio-gbt__icon">${
|
|
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">${
|
|
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">${
|
|
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
|
|
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 -
|
|
95726
|
-
if (left <
|
|
95727
|
-
if (top + h3 > window.innerHeight -
|
|
95728
|
-
if (top <
|
|
95729
|
-
if (top + h3 > window.innerHeight -
|
|
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">${
|
|
95769
|
-
${data.subtitle ? `<div class="myio-gbt__subtitle">${
|
|
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
|
|
99159
|
+
var STYLE_ID9 = "myio-goals-modal-v2-styles";
|
|
96573
99160
|
function _injectStyles(topDoc) {
|
|
96574
|
-
if (topDoc.getElementById(
|
|
99161
|
+
if (topDoc.getElementById(STYLE_ID9)) return;
|
|
96575
99162
|
const s = topDoc.createElement("style");
|
|
96576
|
-
s.id =
|
|
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
|
|
99365
|
+
const esc9 = (s) => String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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;">${
|
|
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;">${
|
|
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
|
|
96984
|
-
return `${date.getFullYear()}-${
|
|
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
|
|
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
|
|
102205
|
+
function escapeHtml9(value) {
|
|
99619
102206
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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(
|
|
102225
|
+
if (!doc.getElementById(STYLE_ID10)) {
|
|
99639
102226
|
const style = doc.createElement("style");
|
|
99640
|
-
style.id =
|
|
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 ${
|
|
102252
|
+
<h3>\u2699\uFE0F Configura\xE7\xF5es${customerName ? ` \u2014 ${escapeHtml9(customerName)}` : ""}</h3>
|
|
99666
102253
|
<button class="myio-conf-picker__close" aria-label="Fechar">×</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
|
|
103447
|
+
function escapeHtml10(value) {
|
|
100861
103448
|
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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="${
|
|
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" : ""}">${
|
|
103817
|
+
const badge = (domain, category) => `<span class="myio-pricing__badge${domain === "water" ? " myio-pricing__badge--water" : ""}">${escapeHtml10(
|
|
101231
103818
|
DOMAIN_LABEL[domain]
|
|
101232
|
-
)} \xB7 ${
|
|
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)} ${
|
|
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">${
|
|
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="${
|
|
103838
|
+
<button class="myio-pricing__remove" data-remove="${escapeHtml10(e.domain)}|${escapeHtml10(
|
|
101252
103839
|
e.category
|
|
101253
|
-
)}|${
|
|
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">${
|
|
101353
|
-
<div class="myio-pricing__kpi-label">${
|
|
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>${
|
|
101373
|
-
<td>${
|
|
101374
|
-
<td>${
|
|
101375
|
-
<td>${
|
|
101376
|
-
<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--${
|
|
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>${
|
|
103977
|
+
<span>${escapeHtml10(rec.periodLabel)}${escapeHtml10(priceInfo)}</span>
|
|
101391
103978
|
</div>
|
|
101392
|
-
<div class="myio-pricing__history-meta">${
|
|
103979
|
+
<div class="myio-pricing__history-meta">${escapeHtml10(customerTitle(rec.customerId))} \xB7 ${escapeHtml10(
|
|
101393
103980
|
rec.by || "\u2014"
|
|
101394
|
-
)} \xB7 ${
|
|
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
|
|
104680
|
+
function esc8(s) {
|
|
102094
104681
|
return String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
102095
104682
|
}
|
|
102096
104683
|
function categoryOptionsHTML(selected) {
|
|
102097
|
-
const opt = (value, label, isSel) => `<option value="${value}"${isSel ? " selected" : ""}>${
|
|
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">${
|
|
102135
|
-
<option value="SPECIFIC">${
|
|
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="">${
|
|
102145
|
-
<option value="COMMON_AREA">${
|
|
102146
|
-
<option value="SPECIFIC">${
|
|
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 =
|
|
102177
|
-
const code = r.code ? `<span class="myio-devcat__row-code">${
|
|
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="${
|
|
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="${
|
|
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="${
|
|
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: ${
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
|
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 +
|
|
131509
|
-
if (y <
|
|
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
|
|
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="${
|
|
144657
|
-
<span class="afm-check-label">${
|
|
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
|
|
159592
|
+
const esc9 = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[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> ${
|
|
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">${
|
|
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 ${
|
|
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" ?
|
|
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 ? ` · ${
|
|
159699
|
+
<span class="issued">Emitido em ${issuedAt}</span>${customerName ? ` · ${esc9(customerName)}` : ""} · <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
|
|
165671
|
-
const innerX = x +
|
|
165672
|
-
const innerW = w -
|
|
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,
|