myio-js-library 0.1.561 → 0.1.562
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2638 -678
- package/dist/index.d.cts +839 -1
- package/dist/index.d.ts +839 -1
- package/dist/index.js +2610 -657
- package/dist/myio-js-library.umd.js +2608 -657
- 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
|
/**
|
|
@@ -70613,7 +70620,7 @@ var WelcomeModalView = class _WelcomeModalView {
|
|
|
70613
70620
|
if (this.metaTipEl) this.metaTipEl.style.display = "none";
|
|
70614
70621
|
}
|
|
70615
70622
|
buildMetaTipHtml(type, card) {
|
|
70616
|
-
const
|
|
70623
|
+
const esc9 = _WelcomeModalView.escapeMetaHtml;
|
|
70617
70624
|
const chip = _WelcomeModalView.metaChip;
|
|
70618
70625
|
const meta = card.metaCounts || {};
|
|
70619
70626
|
const details = card.metaDetails || {};
|
|
@@ -70623,7 +70630,7 @@ var WelcomeModalView = class _WelcomeModalView {
|
|
|
70623
70630
|
).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
70624
70631
|
const frame = (icon2, label, body) => `<div class="myio-welcome-meta-tip__header">
|
|
70625
70632
|
<span class="myio-welcome-meta-tip__icon">${icon2}</span>
|
|
70626
|
-
<span class="myio-welcome-meta-tip__title">${label} (${
|
|
70633
|
+
<span class="myio-welcome-meta-tip__title">${label} (${esc9(card.title)})</span>
|
|
70627
70634
|
<span class="myio-welcome-meta-tip__timestamp">${stamp}</span>
|
|
70628
70635
|
<button type="button" class="myio-welcome-meta-tip__close" title="Fechar">\u2715</button>
|
|
70629
70636
|
</div>
|
|
@@ -70651,8 +70658,8 @@ var WelcomeModalView = class _WelcomeModalView {
|
|
|
70651
70658
|
const chips = Object.entries(byState).map(([s, n]) => chip(`${s}: ${n}`)).join("");
|
|
70652
70659
|
const rows = alarms.slice(0, 10).map(
|
|
70653
70660
|
(a) => row(
|
|
70654
|
-
|
|
70655
|
-
[a.severity, a.state].filter(Boolean).map(
|
|
70661
|
+
esc9(a.deviceName ? `${a.deviceName} \u2014 ${a.title || "Alarme"}` : a.title || "Alarme"),
|
|
70662
|
+
[a.severity, a.state].filter(Boolean).map(esc9).join(" \xB7 ")
|
|
70656
70663
|
)
|
|
70657
70664
|
).join("");
|
|
70658
70665
|
return frame(
|
|
@@ -70676,7 +70683,7 @@ var WelcomeModalView = class _WelcomeModalView {
|
|
|
70676
70683
|
const rows = annotations.slice(0, 10).map((a) => {
|
|
70677
70684
|
const when = a.createdAt ? new Date(a.createdAt).toLocaleDateString("pt-BR") : "";
|
|
70678
70685
|
const text2 = a.text.length > 90 ? `${a.text.slice(0, 90)}\u2026` : a.text;
|
|
70679
|
-
return row(
|
|
70686
|
+
return row(esc9(a.deviceLabel || "Dispositivo"), `${esc9(text2)}${when ? ` \xB7 ${esc9(when)}` : ""}`);
|
|
70680
70687
|
}).join("");
|
|
70681
70688
|
return frame(
|
|
70682
70689
|
"\u{1F4CB}",
|
|
@@ -70687,7 +70694,7 @@ var WelcomeModalView = class _WelcomeModalView {
|
|
|
70687
70694
|
if (type === "users") {
|
|
70688
70695
|
if (meta.users === null) return frame("\u{1F465}", "Usu\xE1rios", loadingBody);
|
|
70689
70696
|
const users = details.users || [];
|
|
70690
|
-
const rows = users.slice(0, 10).map((u) => row(
|
|
70697
|
+
const rows = users.slice(0, 10).map((u) => row(esc9(u.name), esc9(u.email || ""))).join("");
|
|
70691
70698
|
return frame(
|
|
70692
70699
|
"\u{1F465}",
|
|
70693
70700
|
"Usu\xE1rios",
|
|
@@ -75072,16 +75079,16 @@ function buildTrendSvg(values, stroke, grid) {
|
|
|
75072
75079
|
}
|
|
75073
75080
|
const width = 320;
|
|
75074
75081
|
const height = 80;
|
|
75075
|
-
const
|
|
75082
|
+
const pad3 = 6;
|
|
75076
75083
|
const maxVal = Math.max(...values, 1);
|
|
75077
75084
|
const points = values.map((v, i) => {
|
|
75078
|
-
const x =
|
|
75079
|
-
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);
|
|
75080
75087
|
return `${x},${y}`;
|
|
75081
75088
|
});
|
|
75082
75089
|
return `
|
|
75083
75090
|
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
|
75084
|
-
<line x1="0" y1="${height -
|
|
75091
|
+
<line x1="0" y1="${height - pad3}" x2="${width}" y2="${height - pad3}" stroke="${grid}" stroke-width="1"/>
|
|
75085
75092
|
<polyline fill="none" stroke="${stroke}" stroke-width="2.5" points="${points.join(" ")}" />
|
|
75086
75093
|
${points.map((p) => {
|
|
75087
75094
|
const [x, y] = p.split(",");
|
|
@@ -79536,6 +79543,2482 @@ async function openDemandModal(params) {
|
|
|
79536
79543
|
};
|
|
79537
79544
|
}
|
|
79538
79545
|
|
|
79546
|
+
// src/components/premium-modals/dialog/styles.ts
|
|
79547
|
+
var FONT_LINK_ID = "myio-dialog-font-nunito";
|
|
79548
|
+
var STYLE_ID6 = "myio-dialog-styles";
|
|
79549
|
+
function injectDialogStyles() {
|
|
79550
|
+
if (!document.getElementById(FONT_LINK_ID)) {
|
|
79551
|
+
const link = document.createElement("link");
|
|
79552
|
+
link.id = FONT_LINK_ID;
|
|
79553
|
+
link.rel = "stylesheet";
|
|
79554
|
+
link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
|
|
79555
|
+
document.head.appendChild(link);
|
|
79556
|
+
}
|
|
79557
|
+
if (document.getElementById(STYLE_ID6)) return;
|
|
79558
|
+
const style = document.createElement("style");
|
|
79559
|
+
style.id = STYLE_ID6;
|
|
79560
|
+
style.textContent = `
|
|
79561
|
+
.myio-dialog-overlay {
|
|
79562
|
+
position: fixed;
|
|
79563
|
+
inset: 0;
|
|
79564
|
+
display: flex;
|
|
79565
|
+
align-items: center;
|
|
79566
|
+
justify-content: center;
|
|
79567
|
+
background: rgba(17, 24, 39, 0.55);
|
|
79568
|
+
font-family: 'Nunito', system-ui, sans-serif;
|
|
79569
|
+
}
|
|
79570
|
+
.myio-dialog {
|
|
79571
|
+
background: #ffffff;
|
|
79572
|
+
color: #1f2937;
|
|
79573
|
+
border-radius: 12px;
|
|
79574
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
|
|
79575
|
+
max-width: 90vw;
|
|
79576
|
+
max-height: 85vh;
|
|
79577
|
+
display: flex;
|
|
79578
|
+
flex-direction: column;
|
|
79579
|
+
overflow: hidden;
|
|
79580
|
+
}
|
|
79581
|
+
.myio-dialog__accent {
|
|
79582
|
+
height: 4px;
|
|
79583
|
+
background: #7C3AED;
|
|
79584
|
+
}
|
|
79585
|
+
.myio-dialog__header {
|
|
79586
|
+
display: flex;
|
|
79587
|
+
align-items: flex-start;
|
|
79588
|
+
gap: 10px;
|
|
79589
|
+
padding: 16px 20px 0 20px;
|
|
79590
|
+
}
|
|
79591
|
+
.myio-dialog__icon {
|
|
79592
|
+
font-size: 20px;
|
|
79593
|
+
line-height: 1.3;
|
|
79594
|
+
}
|
|
79595
|
+
.myio-dialog__title {
|
|
79596
|
+
flex: 1;
|
|
79597
|
+
margin: 0;
|
|
79598
|
+
font-size: 16px;
|
|
79599
|
+
font-weight: 700;
|
|
79600
|
+
line-height: 1.35;
|
|
79601
|
+
}
|
|
79602
|
+
.myio-dialog__close {
|
|
79603
|
+
border: none;
|
|
79604
|
+
background: transparent;
|
|
79605
|
+
color: #9ca3af;
|
|
79606
|
+
font-size: 18px;
|
|
79607
|
+
line-height: 1;
|
|
79608
|
+
padding: 2px 6px;
|
|
79609
|
+
margin: -2px -8px 0 0;
|
|
79610
|
+
border-radius: 6px;
|
|
79611
|
+
cursor: pointer;
|
|
79612
|
+
}
|
|
79613
|
+
.myio-dialog__close:hover {
|
|
79614
|
+
background: rgba(0, 0, 0, 0.06);
|
|
79615
|
+
color: #4b5563;
|
|
79616
|
+
}
|
|
79617
|
+
.myio-dialog__message {
|
|
79618
|
+
margin: 0;
|
|
79619
|
+
padding: 10px 20px 4px 20px;
|
|
79620
|
+
font-size: 13.5px;
|
|
79621
|
+
line-height: 1.55;
|
|
79622
|
+
color: #4b5563;
|
|
79623
|
+
overflow-y: auto;
|
|
79624
|
+
}
|
|
79625
|
+
.myio-dialog__footer {
|
|
79626
|
+
display: flex;
|
|
79627
|
+
justify-content: flex-end;
|
|
79628
|
+
gap: 8px;
|
|
79629
|
+
padding: 16px 20px;
|
|
79630
|
+
}
|
|
79631
|
+
.myio-dialog__btn {
|
|
79632
|
+
font-family: inherit;
|
|
79633
|
+
font-size: 13px;
|
|
79634
|
+
font-weight: 600;
|
|
79635
|
+
padding: 8px 16px;
|
|
79636
|
+
border-radius: 8px;
|
|
79637
|
+
border: 1px solid transparent;
|
|
79638
|
+
cursor: pointer;
|
|
79639
|
+
transition: filter 0.15s ease, background 0.15s ease;
|
|
79640
|
+
}
|
|
79641
|
+
.myio-dialog__btn:hover {
|
|
79642
|
+
filter: brightness(0.94);
|
|
79643
|
+
}
|
|
79644
|
+
.myio-dialog__btn:focus-visible {
|
|
79645
|
+
outline: 2px solid #7C3AED;
|
|
79646
|
+
outline-offset: 2px;
|
|
79647
|
+
}
|
|
79648
|
+
.myio-dialog__btn--primary {
|
|
79649
|
+
background: #7C3AED;
|
|
79650
|
+
color: #ffffff;
|
|
79651
|
+
}
|
|
79652
|
+
.myio-dialog__btn--secondary {
|
|
79653
|
+
background: #ffffff;
|
|
79654
|
+
color: #374151;
|
|
79655
|
+
border-color: #d1d5db;
|
|
79656
|
+
}
|
|
79657
|
+
.myio-dialog__btn--danger {
|
|
79658
|
+
background: #dc2626;
|
|
79659
|
+
color: #ffffff;
|
|
79660
|
+
}
|
|
79661
|
+
.myio-dialog__btn--success {
|
|
79662
|
+
background: #16a34a;
|
|
79663
|
+
color: #ffffff;
|
|
79664
|
+
}
|
|
79665
|
+
|
|
79666
|
+
/* Severity accents (message dialog) */
|
|
79667
|
+
.myio-dialog--success .myio-dialog__accent { background: #16a34a; }
|
|
79668
|
+
.myio-dialog--warning .myio-dialog__accent { background: #d97706; }
|
|
79669
|
+
.myio-dialog--error .myio-dialog__accent { background: #dc2626; }
|
|
79670
|
+
|
|
79671
|
+
/* \u2500\u2500 Dark theme \u2500\u2500 */
|
|
79672
|
+
.myio-dialog-overlay--dark .myio-dialog {
|
|
79673
|
+
background: #1f2937;
|
|
79674
|
+
color: #f3f4f6;
|
|
79675
|
+
}
|
|
79676
|
+
.myio-dialog-overlay--dark .myio-dialog__message {
|
|
79677
|
+
color: #d1d5db;
|
|
79678
|
+
}
|
|
79679
|
+
.myio-dialog-overlay--dark .myio-dialog__close {
|
|
79680
|
+
color: #9ca3af;
|
|
79681
|
+
}
|
|
79682
|
+
.myio-dialog-overlay--dark .myio-dialog__close:hover {
|
|
79683
|
+
background: rgba(255, 255, 255, 0.08);
|
|
79684
|
+
color: #e5e7eb;
|
|
79685
|
+
}
|
|
79686
|
+
.myio-dialog-overlay--dark .myio-dialog__btn--secondary {
|
|
79687
|
+
background: #1f2937;
|
|
79688
|
+
color: #e5e7eb;
|
|
79689
|
+
border-color: #4b5563;
|
|
79690
|
+
}
|
|
79691
|
+
`;
|
|
79692
|
+
document.head.appendChild(style);
|
|
79693
|
+
}
|
|
79694
|
+
|
|
79695
|
+
// src/components/premium-modals/dialog/openDialog.ts
|
|
79696
|
+
var BASE_Z_INDEX = 10500;
|
|
79697
|
+
var openCount = 0;
|
|
79698
|
+
function escapeHtml7(value) {
|
|
79699
|
+
return String(value).replace(/&/g, "&").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
|
+
|
|
79539
82022
|
// src/utils/goalsCoverage.ts
|
|
79540
82023
|
var MONTHS_PT = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"];
|
|
79541
82024
|
function formatCoverageRefPtBR(ref) {
|
|
@@ -79583,7 +82066,7 @@ function buildYearOptions() {
|
|
|
79583
82066
|
for (let y = now + 2; y >= now - 2; y--) out.push(y);
|
|
79584
82067
|
return out;
|
|
79585
82068
|
}
|
|
79586
|
-
function
|
|
82069
|
+
function esc5(s) {
|
|
79587
82070
|
return String(s == null ? "" : s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
79588
82071
|
}
|
|
79589
82072
|
function openGoalsPanel(params) {
|
|
@@ -79661,7 +82144,7 @@ function openGoalsPanel(params) {
|
|
|
79661
82144
|
if (!/\/api\/v1$/.test(b)) b += "/api/v1";
|
|
79662
82145
|
return b;
|
|
79663
82146
|
}
|
|
79664
|
-
async function
|
|
82147
|
+
async function request2(method, path, opts) {
|
|
79665
82148
|
const o = opts || {};
|
|
79666
82149
|
let url = apiBase() + path;
|
|
79667
82150
|
if (o.query) {
|
|
@@ -79702,25 +82185,25 @@ function openGoalsPanel(params) {
|
|
|
79702
82185
|
}
|
|
79703
82186
|
const targetQuery = () => ({ domain: state6.domain, year: state6.year });
|
|
79704
82187
|
const svc = {
|
|
79705
|
-
getTree: (granularity, fetchHistory) =>
|
|
82188
|
+
getTree: (granularity, fetchHistory) => request2("GET", `/customers/${customerId}/goals`, {
|
|
79706
82189
|
query: { domain: state6.domain, year: state6.year, granularity, fetchHistory }
|
|
79707
82190
|
}),
|
|
79708
|
-
replace: (body) =>
|
|
79709
|
-
merge: (body) =>
|
|
82191
|
+
replace: (body) => request2("PUT", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
|
|
82192
|
+
merge: (body) => request2("PATCH", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
|
|
79710
82193
|
// Scoped bucket DELETE (RFC-0046 §3.6): body { bucket: { level, ref }, expectedVersion? }.
|
|
79711
82194
|
// Used for cells the operator CLEARED — never a whole-year delete from this panel.
|
|
79712
|
-
removeBucket: (body) =>
|
|
79713
|
-
importCsv: (body, dryRun) =>
|
|
82195
|
+
removeBucket: (body) => request2("DELETE", `/customers/${customerId}/goals`, { query: targetQuery(), body }),
|
|
82196
|
+
importCsv: (body, dryRun) => request2("POST", `/customers/${customerId}/goals/import`, {
|
|
79714
82197
|
query: { domain: state6.domain, year: state6.year, dryRun: !!dryRun },
|
|
79715
82198
|
body
|
|
79716
82199
|
}),
|
|
79717
82200
|
// RFC-0052: margem da meta (goalMarginPct) por customer × domínio × ano.
|
|
79718
82201
|
// Mesmo stream de versão dos buckets — 409 VERSION_CONFLICT no lock otimista.
|
|
79719
|
-
setMargin: (pct, version2) =>
|
|
82202
|
+
setMargin: (pct, version2) => request2("PUT", `/customers/${customerId}/goals/margin`, {
|
|
79720
82203
|
query: targetQuery(),
|
|
79721
82204
|
body: version2 ? { goalMarginPct: pct, version: version2 } : { goalMarginPct: pct }
|
|
79722
82205
|
}),
|
|
79723
|
-
clearMargin: (version2) =>
|
|
82206
|
+
clearMargin: (version2) => request2("DELETE", `/customers/${customerId}/goals/margin`, {
|
|
79724
82207
|
query: targetQuery(),
|
|
79725
82208
|
body: version2 ? { version: version2 } : void 0
|
|
79726
82209
|
})
|
|
@@ -80379,13 +82862,13 @@ function openGoalsPanel(params) {
|
|
|
80379
82862
|
const g = state6.goals || {};
|
|
80380
82863
|
const parts = [];
|
|
80381
82864
|
if (hasCoverageGaps(g.coverageGaps)) {
|
|
80382
|
-
parts.push(`<p style="margin:0 0 8px;">${
|
|
82865
|
+
parts.push(`<p style="margin:0 0 8px;">${esc5(buildCoverageWarningTextPtBR(g.coverageGaps, "GERAL"))}</p>`);
|
|
80383
82866
|
}
|
|
80384
82867
|
(Array.isArray(g.devices) ? g.devices : []).forEach((d) => {
|
|
80385
82868
|
if (!hasCoverageGaps(d && d.coverageGaps)) return;
|
|
80386
82869
|
const label = d.label || d.code || "medidor";
|
|
80387
82870
|
parts.push(
|
|
80388
|
-
`<p style="margin:0 0 8px;">${
|
|
82871
|
+
`<p style="margin:0 0 8px;">${esc5(buildCoverageWarningTextPtBR(d.coverageGaps, `do medidor ${label}`))}</p>`
|
|
80389
82872
|
);
|
|
80390
82873
|
});
|
|
80391
82874
|
return `<div style="font-size:12px;line-height:1.55;color:#334155;">${parts.join("")}</div>`;
|
|
@@ -80425,7 +82908,7 @@ function openGoalsPanel(params) {
|
|
|
80425
82908
|
html += `<div class="myio-goals-alert myio-goals-alert-amber">${icon2("alert")} ${i18n2.versionConflict}</div>`;
|
|
80426
82909
|
}
|
|
80427
82910
|
if (state6.saveError && !state6.conflict) {
|
|
80428
|
-
html += `<div class="myio-goals-alert myio-goals-alert-red">${icon2("alert")} ${
|
|
82911
|
+
html += `<div class="myio-goals-alert myio-goals-alert-red">${icon2("alert")} ${esc5(state6.saveError)}</div>`;
|
|
80429
82912
|
}
|
|
80430
82913
|
if (state6.saved && !state6.saveError) {
|
|
80431
82914
|
html += `<div class="myio-goals-alert myio-goals-alert-green">${icon2("check")} ${i18n2.saveSuccess}</div>`;
|
|
@@ -80440,7 +82923,7 @@ function openGoalsPanel(params) {
|
|
|
80440
82923
|
return `
|
|
80441
82924
|
<div class="myio-goals-center myio-goals-center-col">
|
|
80442
82925
|
${icon2("alert-lg")}
|
|
80443
|
-
<p>${
|
|
82926
|
+
<p>${esc5(state6.error)}</p>
|
|
80444
82927
|
<button class="myio-goals-btn myio-goals-btn-outline" data-action="retry">${i18n2.retry}</button>
|
|
80445
82928
|
</div>`;
|
|
80446
82929
|
}
|
|
@@ -80530,7 +83013,7 @@ function openGoalsPanel(params) {
|
|
|
80530
83013
|
const cellLabel = state6.level === "month" ? i18n2.months[key] : state6.level === "day" ? i18n2.dayCell.replace("{day}", Number(key)) : `${key}:00`;
|
|
80531
83014
|
const canDrill = state6.level !== "hour";
|
|
80532
83015
|
const labelEl = canDrill ? `<button class="myio-goals-cell-drill" data-action="${state6.level === "month" ? "open-month" : "open-day"}" data-key="${key}" title="${i18n2.drill.openTitle}">${cellLabel} ${icon2("chevron-sm")}</button>` : `<span class="myio-goals-cell-label">${cellLabel}</span>`;
|
|
80533
|
-
const val = state6.draft[key] !== void 0 ?
|
|
83016
|
+
const val = state6.draft[key] !== void 0 ? esc5(state6.draft[key]) : "";
|
|
80534
83017
|
const minAttr = cfg.allowsNegative ? "" : 'min="0"';
|
|
80535
83018
|
return `
|
|
80536
83019
|
<div class="myio-goals-cell">
|
|
@@ -80598,7 +83081,7 @@ function openGoalsPanel(params) {
|
|
|
80598
83081
|
const actor = ev.actor ? `${String(ev.actor).slice(0, 8)}\u2026` : i18n2.history.system;
|
|
80599
83082
|
const extra = ev.bucketCount - (ev.details ? ev.details.length : 0);
|
|
80600
83083
|
const chips = (ev.details || []).slice(0, DETAIL_PREVIEW).map(
|
|
80601
|
-
(d) => `<span class="myio-goals-tl-chip"><span class="myio-goals-tl-ref">${
|
|
83084
|
+
(d) => `<span class="myio-goals-tl-chip"><span class="myio-goals-tl-ref">${esc5(formatRef(d.ref))}</span>${d.value === null ? "\u2014" : fmt2(d.value)}</span>`
|
|
80602
83085
|
).join("");
|
|
80603
83086
|
const more = extra > 0 ? `<span class="myio-goals-tl-more">${i18n2.history.moreDetails.replace("{n}", extra)}</span>` : "";
|
|
80604
83087
|
return `
|
|
@@ -80608,7 +83091,7 @@ function openGoalsPanel(params) {
|
|
|
80608
83091
|
<span class="myio-goals-tl-title">${i18n2.history.source[ev.source] || i18n2.history.source.EDIT}</span>
|
|
80609
83092
|
<span class="myio-goals-tl-ver">v${ev.version}</span>
|
|
80610
83093
|
</div>
|
|
80611
|
-
<p class="myio-goals-tl-meta" title="${
|
|
83094
|
+
<p class="myio-goals-tl-meta" title="${esc5(new Date(ev.changedAt).toLocaleString(locale))}">${esc5(actor)} \xB7 ${esc5(timeAgo(ev.changedAt))}</p>
|
|
80612
83095
|
<p class="myio-goals-tl-records">${i18n2.history.records.replace("{n}", ev.bucketCount)} \xB7 ${i18n2.levels[ev.actionLevel] || ev.actionLevel}</p>
|
|
80613
83096
|
${chips || more ? `<div class="myio-goals-tl-chips">${chips}${more}</div>` : ""}
|
|
80614
83097
|
</li>`;
|
|
@@ -80655,7 +83138,7 @@ ${state6.year}-03-15T08,15`;
|
|
|
80655
83138
|
let html = `
|
|
80656
83139
|
<div class="myio-goals-import-hint">
|
|
80657
83140
|
<p>${i18n2.import.formatHint}</p>
|
|
80658
|
-
<code>${
|
|
83141
|
+
<code>${esc5(sample)}</code>
|
|
80659
83142
|
</div>`;
|
|
80660
83143
|
if (!applied) {
|
|
80661
83144
|
html += `
|
|
@@ -80669,16 +83152,16 @@ ${state6.year}-03-15T08,15`;
|
|
|
80669
83152
|
<input type="file" id="myio-goals-csv-file" accept=".csv,.txt,text/csv,text/plain" style="display:none" />
|
|
80670
83153
|
</div>
|
|
80671
83154
|
</div>
|
|
80672
|
-
<textarea id="myio-goals-csv-textarea" rows="8" spellcheck="false" placeholder="${
|
|
83155
|
+
<textarea id="myio-goals-csv-textarea" rows="8" spellcheck="false" placeholder="${esc5(`bucket,value
|
|
80673
83156
|
${state6.year},100000
|
|
80674
83157
|
${state6.year}-03,9000
|
|
80675
83158
|
${state6.year}-03-15,310
|
|
80676
|
-
${state6.year}-03-15T08,15`)}">${
|
|
83159
|
+
${state6.year}-03-15T08,15`)}">${esc5(state6.csv)}</textarea>
|
|
80677
83160
|
<p class="myio-goals-microhint">${i18n2.import.unitNote.replace("{unit}", unitLabel2())}</p>
|
|
80678
83161
|
</div>`;
|
|
80679
83162
|
}
|
|
80680
83163
|
if (state6.saveError && state6.conflictVersion === null) {
|
|
80681
|
-
html += `<div class="myio-goals-alert myio-goals-alert-red">${icon2("alert")} ${
|
|
83164
|
+
html += `<div class="myio-goals-alert myio-goals-alert-red">${icon2("alert")} ${esc5(state6.saveError)}</div>`;
|
|
80682
83165
|
}
|
|
80683
83166
|
if (state6.conflictVersion !== null) {
|
|
80684
83167
|
html += `<div class="myio-goals-alert myio-goals-alert-amber">${icon2("alert")} ${i18n2.import.conflict}</div>`;
|
|
@@ -80707,7 +83190,7 @@ ${state6.year}-03-15T08,15`)}">${esc4(state6.csv)}</textarea>
|
|
|
80707
83190
|
<div class="myio-goals-import-diags">
|
|
80708
83191
|
<p class="myio-goals-import-diags-title">${i18n2.import.diagnosticsTitle}</p>
|
|
80709
83192
|
<ul>${result.diagnostics.map(
|
|
80710
|
-
(d) => `<li>${icon2("alert-sm")} <span><span class="myio-goals-diag-line">${i18n2.import.line.replace("{line}", d.line)}</span>${d.bucket ? ` <span class="myio-goals-diag-bucket">[${
|
|
83193
|
+
(d) => `<li>${icon2("alert-sm")} <span><span class="myio-goals-diag-line">${i18n2.import.line.replace("{line}", d.line)}</span>${d.bucket ? ` <span class="myio-goals-diag-bucket">[${esc5(d.bucket)}]</span>` : ""} ${esc5(d.reason)}</span></li>`
|
|
80711
83194
|
).join("")}</ul>
|
|
80712
83195
|
</div>`;
|
|
80713
83196
|
}
|
|
@@ -80715,7 +83198,7 @@ ${state6.year}-03-15T08,15`)}">${esc4(state6.csv)}</textarea>
|
|
|
80715
83198
|
html += `
|
|
80716
83199
|
<div class="myio-goals-import-log">
|
|
80717
83200
|
<p class="myio-goals-import-log-title">${i18n2.import.logTitle}</p>
|
|
80718
|
-
<ul>${result.log.map((line) => `<li>${
|
|
83201
|
+
<ul>${result.log.map((line) => `<li>${esc5(line)}</li>`).join("")}</ul>
|
|
80719
83202
|
</div>`;
|
|
80720
83203
|
}
|
|
80721
83204
|
html += `</div>`;
|
|
@@ -87753,7 +90236,7 @@ function injectCSS10() {
|
|
|
87753
90236
|
document.head.appendChild(style);
|
|
87754
90237
|
_cssInjected2 = true;
|
|
87755
90238
|
}
|
|
87756
|
-
function
|
|
90239
|
+
function esc6(value) {
|
|
87757
90240
|
return String(value == null ? "" : value).replace(
|
|
87758
90241
|
/[&<>"]/g,
|
|
87759
90242
|
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]
|
|
@@ -87803,7 +90286,7 @@ function buildPieSvg(visible, total) {
|
|
|
87803
90286
|
const val = Number(v.d.value) || 0;
|
|
87804
90287
|
return `<svg class="myio-col-summary__pie" viewBox="0 0 220 220">
|
|
87805
90288
|
<circle class="myio-col-summary__slice" data-idx="${v.idx}" cx="110" cy="110" r="100"
|
|
87806
|
-
fill="${sliceColor(v.idx)}"><title>${
|
|
90289
|
+
fill="${sliceColor(v.idx)}"><title>${esc6(v.d.name)} \u2014 ${esc6(fmt2(val))} (${fmtPct2(
|
|
87807
90290
|
val,
|
|
87808
90291
|
total,
|
|
87809
90292
|
pd
|
|
@@ -87818,7 +90301,7 @@ function buildPieSvg(visible, total) {
|
|
|
87818
90301
|
const a1 = acc / total * 360;
|
|
87819
90302
|
return `<path class="myio-col-summary__slice" data-idx="${v.idx}"
|
|
87820
90303
|
d="${arcPath(110, 110, 100, a0, a1)}" fill="${sliceColor(v.idx)}"
|
|
87821
|
-
><title>${
|
|
90304
|
+
><title>${esc6(v.d.name)} \u2014 ${esc6(fmt2(val))} (${fmtPct2(val, total, pd)})</title></path>`;
|
|
87822
90305
|
}).join("");
|
|
87823
90306
|
return `<svg class="myio-col-summary__pie" viewBox="0 0 220 220">${paths}</svg>`;
|
|
87824
90307
|
}
|
|
@@ -87832,8 +90315,8 @@ function buildLegend(total) {
|
|
|
87832
90315
|
return `<div class="myio-col-summary__legend-row${isExcl ? " is-excluded" : ""}" data-idx="${idx}"
|
|
87833
90316
|
title="Clique para ${isExcl ? "incluir" : "remover"} da pizza">
|
|
87834
90317
|
<span class="myio-col-summary__legend-dot" style="background:${sliceColor(idx)};"></span>
|
|
87835
|
-
<span class="myio-col-summary__legend-name">${
|
|
87836
|
-
<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>
|
|
87837
90320
|
<span class="myio-col-summary__legend-pct">${pct}</span>
|
|
87838
90321
|
</div>`;
|
|
87839
90322
|
}).join("");
|
|
@@ -87854,25 +90337,25 @@ function buildAverageInner() {
|
|
|
87854
90337
|
const dash = "\u2014";
|
|
87855
90338
|
const periodRow = data.periodLabel ? `<div class="myio-col-summary__kpi">
|
|
87856
90339
|
<span class="myio-col-summary__kpi-label">Per\xEDodo</span>
|
|
87857
|
-
<span class="myio-col-summary__kpi-value">${
|
|
90340
|
+
<span class="myio-col-summary__kpi-value">${esc6(data.periodLabel)}</span>
|
|
87858
90341
|
</div>` : "";
|
|
87859
90342
|
const note = "M\xE9dia, m\xEDnima e m\xE1xima consideram apenas dispositivos online (com leitura).";
|
|
87860
90343
|
const kpis = `<div class="myio-col-summary__kpis">
|
|
87861
90344
|
${periodRow}
|
|
87862
90345
|
<div class="myio-col-summary__kpi">
|
|
87863
90346
|
<span class="myio-col-summary__kpi-label">Dispositivos
|
|
87864
|
-
<span class="myio-col-summary__info" title="${
|
|
90347
|
+
<span class="myio-col-summary__info" title="${esc6(note)}" aria-label="${esc6(note)}"
|
|
87865
90348
|
style="display:inline-flex;align-items:center;justify-content:center;width:13px;height:13px;margin-left:4px;border-radius:50%;background:#3e1a7d;color:#fff;font-size:9px;font-weight:700;font-style:italic;font-family:Georgia,serif;cursor:help;vertical-align:middle;">i</span>
|
|
87866
90349
|
</span>
|
|
87867
90350
|
<span class="myio-col-summary__kpi-value">${online.length} online \xB7 ${offline.length} offline</span>
|
|
87868
90351
|
</div>
|
|
87869
90352
|
<div class="myio-col-summary__kpi">
|
|
87870
|
-
<span class="myio-col-summary__kpi-label">${
|
|
87871
|
-
<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>
|
|
87872
90355
|
</div>
|
|
87873
90356
|
<div class="myio-col-summary__kpi">
|
|
87874
90357
|
<span class="myio-col-summary__kpi-label">M\xEDnima \xB7 M\xE1xima</span>
|
|
87875
|
-
<span class="myio-col-summary__kpi-value">${minV ?
|
|
90358
|
+
<span class="myio-col-summary__kpi-value">${minV ? esc6(fmt2(Number(minV.d.value))) : dash} \xB7 ${maxV ? esc6(fmt2(Number(maxV.d.value))) : dash}</span>
|
|
87876
90359
|
</div>
|
|
87877
90360
|
</div>`;
|
|
87878
90361
|
if (!data.devices.length) {
|
|
@@ -87881,12 +90364,12 @@ function buildAverageInner() {
|
|
|
87881
90364
|
const near3 = avg === null ? [] : online.slice().sort((a, b) => Math.abs(Number(a.d.value) - avg) - Math.abs(Number(b.d.value) - avg)).slice(0, 3);
|
|
87882
90365
|
const row = (v) => `
|
|
87883
90366
|
<div class="myio-col-summary__row">
|
|
87884
|
-
<span class="myio-col-summary__name" title="${
|
|
87885
|
-
<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>
|
|
87886
90369
|
</div>`;
|
|
87887
90370
|
const offRow = (v) => `
|
|
87888
90371
|
<div class="myio-col-summary__row">
|
|
87889
|
-
<span class="myio-col-summary__name" title="${
|
|
90372
|
+
<span class="myio-col-summary__name" title="${esc6(v.d.name)}">${esc6(v.d.name)}</span>
|
|
87890
90373
|
<span class="myio-col-summary__val" style="color:#94a3b8;">sem leitura</span>
|
|
87891
90374
|
</div>`;
|
|
87892
90375
|
const group = (label, list, r = row) => list.length ? `<div class="myio-col-summary__group">
|
|
@@ -87903,8 +90386,8 @@ function buildAverageInner() {
|
|
|
87903
90386
|
</div>
|
|
87904
90387
|
</div>
|
|
87905
90388
|
<div class="myio-col-summary__footer">
|
|
87906
|
-
<span class="myio-col-summary__footer-label">${
|
|
87907
|
-
<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>
|
|
87908
90391
|
</div>`;
|
|
87909
90392
|
}
|
|
87910
90393
|
function buildInner() {
|
|
@@ -87917,7 +90400,7 @@ function buildInner() {
|
|
|
87917
90400
|
const avg = count ? total / count : 0;
|
|
87918
90401
|
const periodRow = data.periodLabel ? `<div class="myio-col-summary__kpi">
|
|
87919
90402
|
<span class="myio-col-summary__kpi-label">Per\xEDodo</span>
|
|
87920
|
-
<span class="myio-col-summary__kpi-value">${
|
|
90403
|
+
<span class="myio-col-summary__kpi-value">${esc6(data.periodLabel)}</span>
|
|
87921
90404
|
</div>` : "";
|
|
87922
90405
|
const kpis = `<div class="myio-col-summary__kpis">
|
|
87923
90406
|
${periodRow}
|
|
@@ -87927,13 +90410,13 @@ function buildInner() {
|
|
|
87927
90410
|
</div>
|
|
87928
90411
|
<div class="myio-col-summary__kpi">
|
|
87929
90412
|
<span class="myio-col-summary__kpi-label">Consumo m\xE9dio</span>
|
|
87930
|
-
<span class="myio-col-summary__kpi-value myio-col-summary__kpi-value--accent">${
|
|
90413
|
+
<span class="myio-col-summary__kpi-value myio-col-summary__kpi-value--accent">${esc6(
|
|
87931
90414
|
fmt2(avg)
|
|
87932
90415
|
)}</span>
|
|
87933
90416
|
</div>
|
|
87934
90417
|
<div class="myio-col-summary__kpi">
|
|
87935
90418
|
<span class="myio-col-summary__kpi-label">Consumo total</span>
|
|
87936
|
-
<span class="myio-col-summary__kpi-value">${
|
|
90419
|
+
<span class="myio-col-summary__kpi-value">${esc6(fmt2(total))}</span>
|
|
87937
90420
|
</div>
|
|
87938
90421
|
</div>`;
|
|
87939
90422
|
if (!data.devices.length) {
|
|
@@ -87947,8 +90430,8 @@ function buildInner() {
|
|
|
87947
90430
|
).slice(0, 3);
|
|
87948
90431
|
const row = (v) => `
|
|
87949
90432
|
<div class="myio-col-summary__row">
|
|
87950
|
-
<span class="myio-col-summary__name" title="${
|
|
87951
|
-
<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>
|
|
87952
90435
|
<span class="myio-col-summary__pct">${fmtPct2(Number(v.d.value) || 0, total, pd)}</span>
|
|
87953
90436
|
</div>`;
|
|
87954
90437
|
const group = (label, list) => list.length ? `<div class="myio-col-summary__group">
|
|
@@ -87971,7 +90454,7 @@ function buildInner() {
|
|
|
87971
90454
|
</div>
|
|
87972
90455
|
<div class="myio-col-summary__footer">
|
|
87973
90456
|
<span class="myio-col-summary__footer-label">Consumo Total \xB7 ${count} dispositivos</span>
|
|
87974
|
-
<span class="myio-col-summary__footer-value">${
|
|
90457
|
+
<span class="myio-col-summary__footer-value">${esc6(fmt2(total))}</span>
|
|
87975
90458
|
</div>`;
|
|
87976
90459
|
}
|
|
87977
90460
|
function setHighlight(idx) {
|
|
@@ -95559,12 +98042,12 @@ function createConsumptionChartWidget(config2) {
|
|
|
95559
98042
|
}
|
|
95560
98043
|
|
|
95561
98044
|
// src/components/tooltips/goals-bar-tooltip/createGoalsBarTooltip.ts
|
|
95562
|
-
var
|
|
98045
|
+
var STYLE_ID8 = "myio-goals-bar-tooltip-styles";
|
|
95563
98046
|
var DEFAULT_ACCENT = "#6c5ce7";
|
|
95564
98047
|
function injectStyles12(doc) {
|
|
95565
|
-
if (doc.getElementById(
|
|
98048
|
+
if (doc.getElementById(STYLE_ID8)) return;
|
|
95566
98049
|
const s = doc.createElement("style");
|
|
95567
|
-
s.id =
|
|
98050
|
+
s.id = STYLE_ID8;
|
|
95568
98051
|
s.textContent = `
|
|
95569
98052
|
.myio-gbt {
|
|
95570
98053
|
position: fixed;
|
|
@@ -95751,7 +98234,7 @@ function injectStyles12(doc) {
|
|
|
95751
98234
|
`;
|
|
95752
98235
|
(doc.head || doc.documentElement).appendChild(s);
|
|
95753
98236
|
}
|
|
95754
|
-
function
|
|
98237
|
+
function esc7(v) {
|
|
95755
98238
|
return String(v ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
95756
98239
|
}
|
|
95757
98240
|
function fmtPct3(pct) {
|
|
@@ -95772,8 +98255,8 @@ function renderDeltaChip(delta) {
|
|
|
95772
98255
|
const tone = delta.tone === "good" ? "good" : delta.tone === "bad" ? "bad" : "neutral";
|
|
95773
98256
|
const arrow = delta.arrow === "up" ? "\u2191" : delta.arrow === "down" ? "\u2193" : "\u2248";
|
|
95774
98257
|
const pctTxt = fmtPct3(Math.abs(Number(delta.pct)));
|
|
95775
|
-
const txt = delta.text ? ` ${
|
|
95776
|
-
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>`;
|
|
95777
98260
|
}
|
|
95778
98261
|
function renderRow(row, level) {
|
|
95779
98262
|
const hasChildren = Array.isArray(row.children) && row.children.length > 0;
|
|
@@ -95781,12 +98264,12 @@ function renderRow(row, level) {
|
|
|
95781
98264
|
const id = `gbtr-${++_rowSeq}`;
|
|
95782
98265
|
const pct = row.pct;
|
|
95783
98266
|
const caret = hasChildren ? `<button class="myio-gbt__caret${open ? " is-open" : ""}" data-caret="${id}" title="Expandir/recolher">\u25B8</button>` : `<span class="myio-gbt__caret-spacer"></span>`;
|
|
95784
|
-
const swatch = row.color ? `<span class="myio-gbt__swatch" style="background:${
|
|
95785
|
-
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>` : "";
|
|
95786
98269
|
const chipHtml = row.delta ? renderDeltaChip(row.delta) : "";
|
|
95787
|
-
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>` : "";
|
|
95788
98271
|
const rowClass = level > 0 ? "myio-gbt__row myio-gbt__row--child" : "myio-gbt__row";
|
|
95789
|
-
let html = `<div class="${rowClass}">` + caret + swatch + icon2 + `<span class="myio-gbt__label">${
|
|
98272
|
+
let html = `<div class="${rowClass}">` + caret + swatch + icon2 + `<span class="myio-gbt__label">${esc7(row.label)}</span><span class="myio-gbt__value">${esc7(row.valueText)}</span>` + chipHtml + pctHtml + `</div>`;
|
|
95790
98273
|
if (hasChildren) {
|
|
95791
98274
|
const childHtml = row.children.map((c) => renderRow(c, level + 1)).join("");
|
|
95792
98275
|
html += `<div class="myio-gbt__children${open ? " is-open" : ""}" data-children="${id}">${childHtml}</div>`;
|
|
@@ -95822,15 +98305,15 @@ function createGoalsBarTooltip(opts) {
|
|
|
95822
98305
|
const rect = el2.getBoundingClientRect();
|
|
95823
98306
|
const w = rect.width || 280;
|
|
95824
98307
|
const h3 = rect.height || 200;
|
|
95825
|
-
const
|
|
98308
|
+
const pad3 = 12;
|
|
95826
98309
|
const gap = 16;
|
|
95827
98310
|
let left = clientX + gap;
|
|
95828
98311
|
let top = clientY + gap;
|
|
95829
|
-
if (left + w > window.innerWidth -
|
|
95830
|
-
if (left <
|
|
95831
|
-
if (top + h3 > window.innerHeight -
|
|
95832
|
-
if (top <
|
|
95833
|
-
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);
|
|
95834
98317
|
el2.style.left = `${Math.round(left)}px`;
|
|
95835
98318
|
el2.style.top = `${Math.round(top)}px`;
|
|
95836
98319
|
}
|
|
@@ -95869,8 +98352,8 @@ function createGoalsBarTooltip(opts) {
|
|
|
95869
98352
|
<div class="myio-gbt__card">
|
|
95870
98353
|
<div class="myio-gbt__header">
|
|
95871
98354
|
<div class="myio-gbt__title-wrap">
|
|
95872
|
-
<div class="myio-gbt__title">${
|
|
95873
|
-
${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>` : ""}
|
|
95874
98357
|
</div>
|
|
95875
98358
|
<button class="myio-gbt__btn myio-gbt__btn--pin${pinned ? " is-pinned" : ""}" data-action="pin" title="${pinned ? "Desafixar" : "Fixar na tela"}">\u{1F4CC}</button>
|
|
95876
98359
|
<button class="myio-gbt__btn myio-gbt__btn--close" data-action="close" title="Fechar">\u2715</button>
|
|
@@ -96673,11 +99156,11 @@ function _rerenderFromCache() {
|
|
|
96673
99156
|
const { labels, totals, goalLine, prevTotals, domain } = _lastRender;
|
|
96674
99157
|
_renderChart(canvas, labels, totals, goalLine, domain, prevTotals);
|
|
96675
99158
|
}
|
|
96676
|
-
var
|
|
99159
|
+
var STYLE_ID9 = "myio-goals-modal-v2-styles";
|
|
96677
99160
|
function _injectStyles(topDoc) {
|
|
96678
|
-
if (topDoc.getElementById(
|
|
99161
|
+
if (topDoc.getElementById(STYLE_ID9)) return;
|
|
96679
99162
|
const s = topDoc.createElement("style");
|
|
96680
|
-
s.id =
|
|
99163
|
+
s.id = STYLE_ID9;
|
|
96681
99164
|
s.textContent = `
|
|
96682
99165
|
.gm-overlay{position:fixed;inset:0;z-index:1000000;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.5);backdrop-filter:blur(4px);opacity:0;transition:opacity .2s ease;font-family:'Nunito', system-ui, sans-serif;}
|
|
96683
99166
|
.gm-overlay.show{opacity:1;}
|
|
@@ -96879,19 +99362,19 @@ function _updateCoverageHint(topDoc) {
|
|
|
96879
99362
|
const deviceCount = data.granularity === "DEVICE" ? data.devices?.length ?? 0 : 0;
|
|
96880
99363
|
hint.textContent = `\u26A0 Meta incompleta para este dom\xEDnio/ano` + (deviceCount > 0 ? ` \xB7 Por medidor (${deviceCount})` : "") + ` \u2014 passe o mouse para detalhes`;
|
|
96881
99364
|
tabs.insertAdjacentElement("afterend", hint);
|
|
96882
|
-
const
|
|
99365
|
+
const esc9 = (s) => String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
96883
99366
|
try {
|
|
96884
99367
|
_coverageDetach = InfoTooltip.attach(hint, () => {
|
|
96885
99368
|
const d = _getGoalsData(_currentDomain);
|
|
96886
99369
|
const parts = [];
|
|
96887
99370
|
if (hasCoverageGaps(d?.coverageGaps)) {
|
|
96888
|
-
parts.push(`<p style="margin:0 0 8px;">${
|
|
99371
|
+
parts.push(`<p style="margin:0 0 8px;">${esc9(buildCoverageWarningTextPtBR(d?.coverageGaps, "GERAL"))}</p>`);
|
|
96889
99372
|
}
|
|
96890
99373
|
(d?.devices || []).forEach((dev) => {
|
|
96891
99374
|
if (!hasCoverageGaps(dev?.coverageGaps)) return;
|
|
96892
99375
|
const label = dev.label || dev.code || "medidor";
|
|
96893
99376
|
parts.push(
|
|
96894
|
-
`<p style="margin:0 0 8px;">${
|
|
99377
|
+
`<p style="margin:0 0 8px;">${esc9(buildCoverageWarningTextPtBR(dev.coverageGaps, `do medidor ${label}`))}</p>`
|
|
96895
99378
|
);
|
|
96896
99379
|
});
|
|
96897
99380
|
return {
|
|
@@ -97084,8 +99567,8 @@ var CSV_SEPARATORS = {
|
|
|
97084
99567
|
"default": ";"
|
|
97085
99568
|
};
|
|
97086
99569
|
function formatDateForFilename(date) {
|
|
97087
|
-
const
|
|
97088
|
-
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())}`;
|
|
97089
99572
|
}
|
|
97090
99573
|
function sanitizeFilename(str) {
|
|
97091
99574
|
return str.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, "_").substring(0, 50);
|
|
@@ -99528,7 +102011,7 @@ function createMyIOTheme(settings, mode) {
|
|
|
99528
102011
|
// src/components/settings-hub/openSettingsHubModal.ts
|
|
99529
102012
|
var ROUTE_DELAY_MS = 250;
|
|
99530
102013
|
var MODAL_ID = "myio-conf-picker";
|
|
99531
|
-
var
|
|
102014
|
+
var STYLE_ID10 = "myio-conf-picker-styles";
|
|
99532
102015
|
var HUB_OPTIONS = [
|
|
99533
102016
|
{
|
|
99534
102017
|
action: "temperature",
|
|
@@ -99719,7 +102202,7 @@ var HUB_CSS = `
|
|
|
99719
102202
|
color: var(--myio-brand-700, #7B2FF7);
|
|
99720
102203
|
}
|
|
99721
102204
|
`;
|
|
99722
|
-
function
|
|
102205
|
+
function escapeHtml9(value) {
|
|
99723
102206
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
99724
102207
|
}
|
|
99725
102208
|
function resolveTopDocument() {
|
|
@@ -99739,9 +102222,9 @@ function resolveHubThemeVars(explicit) {
|
|
|
99739
102222
|
function openSettingsHubModal(options) {
|
|
99740
102223
|
const { customerName = "", isSuperAdmin: isSuperAdmin2 = false, handlers } = options;
|
|
99741
102224
|
const doc = options.targetDocument || resolveTopDocument();
|
|
99742
|
-
if (!doc.getElementById(
|
|
102225
|
+
if (!doc.getElementById(STYLE_ID10)) {
|
|
99743
102226
|
const style = doc.createElement("style");
|
|
99744
|
-
style.id =
|
|
102227
|
+
style.id = STYLE_ID10;
|
|
99745
102228
|
style.textContent = HUB_CSS;
|
|
99746
102229
|
doc.head.appendChild(style);
|
|
99747
102230
|
}
|
|
@@ -99766,7 +102249,7 @@ function openSettingsHubModal(options) {
|
|
|
99766
102249
|
<div class="myio-conf-picker__overlay"></div>
|
|
99767
102250
|
<div class="myio-conf-picker__content">
|
|
99768
102251
|
<div class="myio-conf-picker__header">
|
|
99769
|
-
<h3>\u2699\uFE0F Configura\xE7\xF5es${customerName ? ` \u2014 ${
|
|
102252
|
+
<h3>\u2699\uFE0F Configura\xE7\xF5es${customerName ? ` \u2014 ${escapeHtml9(customerName)}` : ""}</h3>
|
|
99770
102253
|
<button class="myio-conf-picker__close" aria-label="Fechar">×</button>
|
|
99771
102254
|
</div>
|
|
99772
102255
|
<div class="myio-conf-picker__body">${cardsHtml}
|
|
@@ -100961,7 +103444,7 @@ function createTariffApiAdapter(client, opts) {
|
|
|
100961
103444
|
|
|
100962
103445
|
// src/components/pricing-panel/openPricingPanel.ts
|
|
100963
103446
|
var MODAL_ID2 = "myio-pricing-panel";
|
|
100964
|
-
function
|
|
103447
|
+
function escapeHtml10(value) {
|
|
100965
103448
|
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
100966
103449
|
}
|
|
100967
103450
|
function resolveTopDocument2() {
|
|
@@ -101116,7 +103599,7 @@ function openPricingPanel(params) {
|
|
|
101116
103599
|
}
|
|
101117
103600
|
const initDomain = params.domain || "energy";
|
|
101118
103601
|
const initCategory = params.category || "lojas";
|
|
101119
|
-
const customerOptions = customers.map((c) => `<option value="${
|
|
103602
|
+
const customerOptions = customers.map((c) => `<option value="${escapeHtml10(resolveCustomerId(c))}">${escapeHtml10(c.title)}</option>`).join("");
|
|
101120
103603
|
modal.innerHTML = `
|
|
101121
103604
|
<div class="myio-pricing__overlay"></div>
|
|
101122
103605
|
<div class="myio-pricing__content">
|
|
@@ -101331,9 +103814,9 @@ function openPricingPanel(params) {
|
|
|
101331
103814
|
const pushAudit = (rec) => {
|
|
101332
103815
|
if (rec) auditLog = [rec, ...auditLog];
|
|
101333
103816
|
};
|
|
101334
|
-
const badge = (domain, category) => `<span class="myio-pricing__badge${domain === "water" ? " myio-pricing__badge--water" : ""}">${
|
|
103817
|
+
const badge = (domain, category) => `<span class="myio-pricing__badge${domain === "water" ? " myio-pricing__badge--water" : ""}">${escapeHtml10(
|
|
101335
103818
|
DOMAIN_LABEL[domain]
|
|
101336
|
-
)} \xB7 ${
|
|
103819
|
+
)} \xB7 ${escapeHtml10(CATEGORY_LABEL[category])}</span>`;
|
|
101337
103820
|
const renderList2 = () => {
|
|
101338
103821
|
const customerId = customerSel.value;
|
|
101339
103822
|
const rows = entries.filter((e) => e.customerId === customerId).sort((a, b) => {
|
|
@@ -101346,15 +103829,15 @@ function openPricingPanel(params) {
|
|
|
101346
103829
|
const boundsKey = b ? `${b.start}__${b.end}` : "";
|
|
101347
103830
|
return `
|
|
101348
103831
|
<li class="myio-pricing__item">
|
|
101349
|
-
<span class="myio-pricing__item-period">${badge(e.domain, e.category)} ${
|
|
103832
|
+
<span class="myio-pricing__item-period">${badge(e.domain, e.category)} ${escapeHtml10(
|
|
101350
103833
|
formatPeriodLabel(e)
|
|
101351
103834
|
)}</span>
|
|
101352
|
-
<span class="myio-pricing__item-price">${
|
|
103835
|
+
<span class="myio-pricing__item-price">${escapeHtml10(formatBRL2(e.pricePerKwh))} ${escapeHtml10(
|
|
101353
103836
|
unitLabel(e.domain)
|
|
101354
103837
|
)}</span>
|
|
101355
|
-
<button class="myio-pricing__remove" data-remove="${
|
|
103838
|
+
<button class="myio-pricing__remove" data-remove="${escapeHtml10(e.domain)}|${escapeHtml10(
|
|
101356
103839
|
e.category
|
|
101357
|
-
)}|${
|
|
103840
|
+
)}|${escapeHtml10(boundsKey)}" aria-label="Remover" type="button">\u2715</button>
|
|
101358
103841
|
</li>`;
|
|
101359
103842
|
}).join("");
|
|
101360
103843
|
emptyEl.style.display = rows.length ? "none" : "";
|
|
@@ -101453,8 +103936,8 @@ function openPricingPanel(params) {
|
|
|
101453
103936
|
kpiEl.innerHTML = cards.map(
|
|
101454
103937
|
(c) => `
|
|
101455
103938
|
<div class="myio-pricing__kpi">
|
|
101456
|
-
<div class="myio-pricing__kpi-value">${
|
|
101457
|
-
<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>
|
|
101458
103941
|
</div>`
|
|
101459
103942
|
).join("");
|
|
101460
103943
|
renderReport();
|
|
@@ -101473,11 +103956,11 @@ function openPricingPanel(params) {
|
|
|
101473
103956
|
reportBody.innerHTML = rows.length ? rows.map(
|
|
101474
103957
|
(e) => `
|
|
101475
103958
|
<tr>
|
|
101476
|
-
<td>${
|
|
101477
|
-
<td>${
|
|
101478
|
-
<td>${
|
|
101479
|
-
<td>${
|
|
101480
|
-
<td>${
|
|
103959
|
+
<td>${escapeHtml10(customerTitle(e.customerId))}</td>
|
|
103960
|
+
<td>${escapeHtml10(DOMAIN_LABEL[e.domain])}</td>
|
|
103961
|
+
<td>${escapeHtml10(CATEGORY_LABEL[e.category])}</td>
|
|
103962
|
+
<td>${escapeHtml10(formatPeriodLabel(e))}</td>
|
|
103963
|
+
<td>${escapeHtml10(formatBRL2(e.pricePerKwh))} ${escapeHtml10(unitLabel(e.domain))}</td>
|
|
101481
103964
|
</tr>`
|
|
101482
103965
|
).join("") : `<tr><td colspan="5" style="text-align:center;color:var(--myio-text-muted,#6b7280);padding:24px;">Nenhum pre\xE7o para o filtro selecionado.</td></tr>`;
|
|
101483
103966
|
}
|
|
@@ -101487,15 +103970,15 @@ function openPricingPanel(params) {
|
|
|
101487
103970
|
return `
|
|
101488
103971
|
<li class="myio-pricing__history-item">
|
|
101489
103972
|
<div class="myio-pricing__history-head">
|
|
101490
|
-
<span class="myio-pricing__action myio-pricing__action--${
|
|
103973
|
+
<span class="myio-pricing__action myio-pricing__action--${escapeHtml10(rec.action)}">${escapeHtml10(
|
|
101491
103974
|
rec.action
|
|
101492
103975
|
)}</span>
|
|
101493
103976
|
${badge(rec.domain, rec.category)}
|
|
101494
|
-
<span>${
|
|
103977
|
+
<span>${escapeHtml10(rec.periodLabel)}${escapeHtml10(priceInfo)}</span>
|
|
101495
103978
|
</div>
|
|
101496
|
-
<div class="myio-pricing__history-meta">${
|
|
103979
|
+
<div class="myio-pricing__history-meta">${escapeHtml10(customerTitle(rec.customerId))} \xB7 ${escapeHtml10(
|
|
101497
103980
|
rec.by || "\u2014"
|
|
101498
|
-
)} \xB7 ${
|
|
103981
|
+
)} \xB7 ${escapeHtml10(fmtTimestamp(rec.at))}</div>
|
|
101499
103982
|
</li>`;
|
|
101500
103983
|
}).join("");
|
|
101501
103984
|
historyEmpty.style.display = auditLog.length ? "none" : "";
|
|
@@ -102194,11 +104677,11 @@ function deviceCategoryToPanelTerm(cat) {
|
|
|
102194
104677
|
function panelTermToDeviceCategory(term) {
|
|
102195
104678
|
return term == null ? null : panelCategoryToWire(term);
|
|
102196
104679
|
}
|
|
102197
|
-
function
|
|
104680
|
+
function esc8(s) {
|
|
102198
104681
|
return String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
102199
104682
|
}
|
|
102200
104683
|
function categoryOptionsHTML(selected) {
|
|
102201
|
-
const opt = (value, label, isSel) => `<option value="${value}"${isSel ? " selected" : ""}>${
|
|
104684
|
+
const opt = (value, label, isSel) => `<option value="${value}"${isSel ? " selected" : ""}>${esc8(label)}</option>`;
|
|
102202
104685
|
return opt("", NONE_LABEL, selected == null) + opt("COMMON_AREA", deviceCategoryLabel("COMMON_AREA"), selected === "COMMON_AREA") + opt("SPECIFIC", deviceCategoryLabel("SPECIFIC"), selected === "SPECIFIC");
|
|
102203
104686
|
}
|
|
102204
104687
|
function openDeviceCategoryPanel(params) {
|
|
@@ -102235,8 +104718,8 @@ function openDeviceCategoryPanel(params) {
|
|
|
102235
104718
|
<select class="myio-devcat__filter" data-devcat-filter="1" aria-label="Filtrar por categoria">
|
|
102236
104719
|
<option value="all">Todas as categorias</option>
|
|
102237
104720
|
<option value="uncategorized">Sem categoria</option>
|
|
102238
|
-
<option value="COMMON_AREA">${
|
|
102239
|
-
<option value="SPECIFIC">${
|
|
104721
|
+
<option value="COMMON_AREA">${esc8(deviceCategoryLabel("COMMON_AREA"))}</option>
|
|
104722
|
+
<option value="SPECIFIC">${esc8(deviceCategoryLabel("SPECIFIC"))}</option>
|
|
102240
104723
|
</select>
|
|
102241
104724
|
<label class="myio-devcat__filter" style="display:flex;align-items:center;gap:6px;cursor:pointer;">
|
|
102242
104725
|
<input type="checkbox" data-devcat-selectall="1" /> Selecionar vis\xEDveis
|
|
@@ -102245,9 +104728,9 @@ function openDeviceCategoryPanel(params) {
|
|
|
102245
104728
|
<div class="myio-devcat__bulkbar" data-devcat-bulkbar="1">
|
|
102246
104729
|
<span data-devcat-bulkcount="1">0 selecionados</span>
|
|
102247
104730
|
<select data-devcat-bulkcat="1" aria-label="Categoria para os selecionados">
|
|
102248
|
-
<option value="">${
|
|
102249
|
-
<option value="COMMON_AREA">${
|
|
102250
|
-
<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>
|
|
102251
104734
|
</select>
|
|
102252
104735
|
<button type="button" class="myio-devcat__bulk-apply" data-devcat-bulkapply="1">Aplicar a selecionados</button>
|
|
102253
104736
|
</div>
|
|
@@ -102277,16 +104760,16 @@ function openDeviceCategoryPanel(params) {
|
|
|
102277
104760
|
return rows.filter((r) => matchesFilter(r) && matchesSearch(r));
|
|
102278
104761
|
}
|
|
102279
104762
|
function rowHTML(r) {
|
|
102280
|
-
const name =
|
|
102281
|
-
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>` : "";
|
|
102282
104765
|
const checked = selected.has(r.deviceId) ? " checked" : "";
|
|
102283
104766
|
const focused = r.deviceId === params.focusDeviceId ? ' data-focused="1"' : "";
|
|
102284
104767
|
const catAttr = r.tariffCategory == null ? "null" : r.tariffCategory;
|
|
102285
|
-
return `<div class="myio-devcat__row" data-devcat-row="${
|
|
104768
|
+
return `<div class="myio-devcat__row" data-devcat-row="${esc8(r.deviceId)}" data-cat="${catAttr}"${focused}><input type="checkbox" class="myio-devcat__row-check" data-devcat-rowcheck="${esc8(
|
|
102286
104769
|
r.deviceId
|
|
102287
|
-
)}"${checked} aria-label="Selecionar ${name}" /><div class="myio-devcat__row-main"><span class="myio-devcat__row-label" title="${name}">${name}</span>` + code + `</div><select class="myio-devcat__row-select" data-devcat-rowselect="${
|
|
104770
|
+
)}"${checked} aria-label="Selecionar ${name}" /><div class="myio-devcat__row-main"><span class="myio-devcat__row-label" title="${name}">${name}</span>` + code + `</div><select class="myio-devcat__row-select" data-devcat-rowselect="${esc8(
|
|
102288
104771
|
r.deviceId
|
|
102289
|
-
)}" aria-label="Categoria de ${name}">${categoryOptionsHTML(r.tariffCategory)}</select><div class="myio-devcat__row-conflict" data-devcat-conflict="${
|
|
104772
|
+
)}" aria-label="Categoria de ${name}">${categoryOptionsHTML(r.tariffCategory)}</select><div class="myio-devcat__row-conflict" data-devcat-conflict="${esc8(r.deviceId)}"></div></div>`;
|
|
102290
104773
|
}
|
|
102291
104774
|
function render3() {
|
|
102292
104775
|
const vis = visibleRows();
|
|
@@ -102446,7 +104929,7 @@ function openDeviceCategoryPanel(params) {
|
|
|
102446
104929
|
rows = await port.listDevices({ customerId, domain: params.domain });
|
|
102447
104930
|
} catch (err) {
|
|
102448
104931
|
rows = [];
|
|
102449
|
-
body.innerHTML = `<div class="myio-devcat__empty">N\xE3o foi poss\xEDvel carregar os dispositivos: ${
|
|
104932
|
+
body.innerHTML = `<div class="myio-devcat__empty">N\xE3o foi poss\xEDvel carregar os dispositivos: ${esc8(
|
|
102450
104933
|
err?.message || "erro"
|
|
102451
104934
|
)}</div>`;
|
|
102452
104935
|
countEl.textContent = "";
|
|
@@ -109830,536 +112313,6 @@ function refreshIncidentBadges(opts) {
|
|
|
109830
112313
|
});
|
|
109831
112314
|
}
|
|
109832
112315
|
|
|
109833
|
-
// src/components/premium-modals/dialog/styles.ts
|
|
109834
|
-
var FONT_LINK_ID = "myio-dialog-font-nunito";
|
|
109835
|
-
var STYLE_ID9 = "myio-dialog-styles";
|
|
109836
|
-
function injectDialogStyles() {
|
|
109837
|
-
if (!document.getElementById(FONT_LINK_ID)) {
|
|
109838
|
-
const link = document.createElement("link");
|
|
109839
|
-
link.id = FONT_LINK_ID;
|
|
109840
|
-
link.rel = "stylesheet";
|
|
109841
|
-
link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
|
|
109842
|
-
document.head.appendChild(link);
|
|
109843
|
-
}
|
|
109844
|
-
if (document.getElementById(STYLE_ID9)) return;
|
|
109845
|
-
const style = document.createElement("style");
|
|
109846
|
-
style.id = STYLE_ID9;
|
|
109847
|
-
style.textContent = `
|
|
109848
|
-
.myio-dialog-overlay {
|
|
109849
|
-
position: fixed;
|
|
109850
|
-
inset: 0;
|
|
109851
|
-
display: flex;
|
|
109852
|
-
align-items: center;
|
|
109853
|
-
justify-content: center;
|
|
109854
|
-
background: rgba(17, 24, 39, 0.55);
|
|
109855
|
-
font-family: 'Nunito', system-ui, sans-serif;
|
|
109856
|
-
}
|
|
109857
|
-
.myio-dialog {
|
|
109858
|
-
background: #ffffff;
|
|
109859
|
-
color: #1f2937;
|
|
109860
|
-
border-radius: 12px;
|
|
109861
|
-
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
|
|
109862
|
-
max-width: 90vw;
|
|
109863
|
-
max-height: 85vh;
|
|
109864
|
-
display: flex;
|
|
109865
|
-
flex-direction: column;
|
|
109866
|
-
overflow: hidden;
|
|
109867
|
-
}
|
|
109868
|
-
.myio-dialog__accent {
|
|
109869
|
-
height: 4px;
|
|
109870
|
-
background: #7C3AED;
|
|
109871
|
-
}
|
|
109872
|
-
.myio-dialog__header {
|
|
109873
|
-
display: flex;
|
|
109874
|
-
align-items: flex-start;
|
|
109875
|
-
gap: 10px;
|
|
109876
|
-
padding: 16px 20px 0 20px;
|
|
109877
|
-
}
|
|
109878
|
-
.myio-dialog__icon {
|
|
109879
|
-
font-size: 20px;
|
|
109880
|
-
line-height: 1.3;
|
|
109881
|
-
}
|
|
109882
|
-
.myio-dialog__title {
|
|
109883
|
-
flex: 1;
|
|
109884
|
-
margin: 0;
|
|
109885
|
-
font-size: 16px;
|
|
109886
|
-
font-weight: 700;
|
|
109887
|
-
line-height: 1.35;
|
|
109888
|
-
}
|
|
109889
|
-
.myio-dialog__close {
|
|
109890
|
-
border: none;
|
|
109891
|
-
background: transparent;
|
|
109892
|
-
color: #9ca3af;
|
|
109893
|
-
font-size: 18px;
|
|
109894
|
-
line-height: 1;
|
|
109895
|
-
padding: 2px 6px;
|
|
109896
|
-
margin: -2px -8px 0 0;
|
|
109897
|
-
border-radius: 6px;
|
|
109898
|
-
cursor: pointer;
|
|
109899
|
-
}
|
|
109900
|
-
.myio-dialog__close:hover {
|
|
109901
|
-
background: rgba(0, 0, 0, 0.06);
|
|
109902
|
-
color: #4b5563;
|
|
109903
|
-
}
|
|
109904
|
-
.myio-dialog__message {
|
|
109905
|
-
margin: 0;
|
|
109906
|
-
padding: 10px 20px 4px 20px;
|
|
109907
|
-
font-size: 13.5px;
|
|
109908
|
-
line-height: 1.55;
|
|
109909
|
-
color: #4b5563;
|
|
109910
|
-
overflow-y: auto;
|
|
109911
|
-
}
|
|
109912
|
-
.myio-dialog__footer {
|
|
109913
|
-
display: flex;
|
|
109914
|
-
justify-content: flex-end;
|
|
109915
|
-
gap: 8px;
|
|
109916
|
-
padding: 16px 20px;
|
|
109917
|
-
}
|
|
109918
|
-
.myio-dialog__btn {
|
|
109919
|
-
font-family: inherit;
|
|
109920
|
-
font-size: 13px;
|
|
109921
|
-
font-weight: 600;
|
|
109922
|
-
padding: 8px 16px;
|
|
109923
|
-
border-radius: 8px;
|
|
109924
|
-
border: 1px solid transparent;
|
|
109925
|
-
cursor: pointer;
|
|
109926
|
-
transition: filter 0.15s ease, background 0.15s ease;
|
|
109927
|
-
}
|
|
109928
|
-
.myio-dialog__btn:hover {
|
|
109929
|
-
filter: brightness(0.94);
|
|
109930
|
-
}
|
|
109931
|
-
.myio-dialog__btn:focus-visible {
|
|
109932
|
-
outline: 2px solid #7C3AED;
|
|
109933
|
-
outline-offset: 2px;
|
|
109934
|
-
}
|
|
109935
|
-
.myio-dialog__btn--primary {
|
|
109936
|
-
background: #7C3AED;
|
|
109937
|
-
color: #ffffff;
|
|
109938
|
-
}
|
|
109939
|
-
.myio-dialog__btn--secondary {
|
|
109940
|
-
background: #ffffff;
|
|
109941
|
-
color: #374151;
|
|
109942
|
-
border-color: #d1d5db;
|
|
109943
|
-
}
|
|
109944
|
-
.myio-dialog__btn--danger {
|
|
109945
|
-
background: #dc2626;
|
|
109946
|
-
color: #ffffff;
|
|
109947
|
-
}
|
|
109948
|
-
.myio-dialog__btn--success {
|
|
109949
|
-
background: #16a34a;
|
|
109950
|
-
color: #ffffff;
|
|
109951
|
-
}
|
|
109952
|
-
|
|
109953
|
-
/* Severity accents (message dialog) */
|
|
109954
|
-
.myio-dialog--success .myio-dialog__accent { background: #16a34a; }
|
|
109955
|
-
.myio-dialog--warning .myio-dialog__accent { background: #d97706; }
|
|
109956
|
-
.myio-dialog--error .myio-dialog__accent { background: #dc2626; }
|
|
109957
|
-
|
|
109958
|
-
/* \u2500\u2500 Dark theme \u2500\u2500 */
|
|
109959
|
-
.myio-dialog-overlay--dark .myio-dialog {
|
|
109960
|
-
background: #1f2937;
|
|
109961
|
-
color: #f3f4f6;
|
|
109962
|
-
}
|
|
109963
|
-
.myio-dialog-overlay--dark .myio-dialog__message {
|
|
109964
|
-
color: #d1d5db;
|
|
109965
|
-
}
|
|
109966
|
-
.myio-dialog-overlay--dark .myio-dialog__close {
|
|
109967
|
-
color: #9ca3af;
|
|
109968
|
-
}
|
|
109969
|
-
.myio-dialog-overlay--dark .myio-dialog__close:hover {
|
|
109970
|
-
background: rgba(255, 255, 255, 0.08);
|
|
109971
|
-
color: #e5e7eb;
|
|
109972
|
-
}
|
|
109973
|
-
.myio-dialog-overlay--dark .myio-dialog__btn--secondary {
|
|
109974
|
-
background: #1f2937;
|
|
109975
|
-
color: #e5e7eb;
|
|
109976
|
-
border-color: #4b5563;
|
|
109977
|
-
}
|
|
109978
|
-
`;
|
|
109979
|
-
document.head.appendChild(style);
|
|
109980
|
-
}
|
|
109981
|
-
|
|
109982
|
-
// src/components/premium-modals/dialog/openDialog.ts
|
|
109983
|
-
var BASE_Z_INDEX = 10500;
|
|
109984
|
-
var openCount = 0;
|
|
109985
|
-
function escapeHtml9(value) {
|
|
109986
|
-
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
109987
|
-
}
|
|
109988
|
-
function escapeMessage(value) {
|
|
109989
|
-
return escapeHtml9(value).replace(/\n/g, "<br>");
|
|
109990
|
-
}
|
|
109991
|
-
function cssWidth(width) {
|
|
109992
|
-
if (width == null) return "420px";
|
|
109993
|
-
return typeof width === "number" ? `${width}px` : width;
|
|
109994
|
-
}
|
|
109995
|
-
var SEVERITY_META = {
|
|
109996
|
-
info: { title: "Informa\xE7\xE3o", icon: "\u2139\uFE0F" },
|
|
109997
|
-
success: { title: "Sucesso", icon: "\u2705" },
|
|
109998
|
-
warning: { title: "Aten\xE7\xE3o", icon: "\u26A0\uFE0F" },
|
|
109999
|
-
error: { title: "Erro", icon: "\u274C" }
|
|
110000
|
-
};
|
|
110001
|
-
function openDialog(params) {
|
|
110002
|
-
if (!Array.isArray(params.buttons) || params.buttons.length === 0) {
|
|
110003
|
-
throw new Error("[openConfirmDialog] params.buttons must contain at least one button");
|
|
110004
|
-
}
|
|
110005
|
-
injectDialogStyles();
|
|
110006
|
-
const dismissible = params.dismissible ?? true;
|
|
110007
|
-
const theme = params.theme ?? "light";
|
|
110008
|
-
const idBase = `myio-dialog-${++openCount}`;
|
|
110009
|
-
const overlay = document.createElement("div");
|
|
110010
|
-
overlay.className = `myio-dialog-overlay${theme === "dark" ? " myio-dialog-overlay--dark" : ""}`;
|
|
110011
|
-
overlay.style.zIndex = String(BASE_Z_INDEX + openCount);
|
|
110012
|
-
const buttonsHtml = params.buttons.map((btn, i) => {
|
|
110013
|
-
const variant = btn.variant ?? "secondary";
|
|
110014
|
-
return `<button type="button" class="myio-dialog__btn myio-dialog__btn--${variant}" data-index="${i}">${escapeHtml9(btn.label)}</button>`;
|
|
110015
|
-
}).join("");
|
|
110016
|
-
overlay.innerHTML = `
|
|
110017
|
-
<div class="myio-dialog${params.modifier ? ` ${params.modifier}` : ""}"
|
|
110018
|
-
role="dialog" aria-modal="true"
|
|
110019
|
-
aria-labelledby="${idBase}-title" aria-describedby="${idBase}-message"
|
|
110020
|
-
style="width:${cssWidth(params.width)}">
|
|
110021
|
-
<div class="myio-dialog__accent"></div>
|
|
110022
|
-
<div class="myio-dialog__header">
|
|
110023
|
-
${params.icon ? `<span class="myio-dialog__icon" aria-hidden="true">${params.icon}</span>` : ""}
|
|
110024
|
-
<h3 class="myio-dialog__title" id="${idBase}-title">${escapeHtml9(params.title)}</h3>
|
|
110025
|
-
${dismissible ? `<button type="button" class="myio-dialog__close" aria-label="Fechar">\u2715</button>` : ""}
|
|
110026
|
-
</div>
|
|
110027
|
-
<p class="myio-dialog__message" id="${idBase}-message">${escapeMessage(params.message)}</p>
|
|
110028
|
-
<div class="myio-dialog__footer">${buttonsHtml}</div>
|
|
110029
|
-
</div>
|
|
110030
|
-
`;
|
|
110031
|
-
const previousFocus = document.activeElement;
|
|
110032
|
-
const target = params.container ?? document.body;
|
|
110033
|
-
target.appendChild(overlay);
|
|
110034
|
-
const root = overlay.querySelector(".myio-dialog");
|
|
110035
|
-
const buttonEls = Array.from(root.querySelectorAll(".myio-dialog__btn"));
|
|
110036
|
-
return new Promise((resolve) => {
|
|
110037
|
-
let settled = false;
|
|
110038
|
-
let autoCloseTimer;
|
|
110039
|
-
const close = (result) => {
|
|
110040
|
-
if (settled) return;
|
|
110041
|
-
settled = true;
|
|
110042
|
-
if (autoCloseTimer) clearTimeout(autoCloseTimer);
|
|
110043
|
-
document.removeEventListener("keydown", onKeydown, true);
|
|
110044
|
-
overlay.remove();
|
|
110045
|
-
if (previousFocus && previousFocus.isConnected) previousFocus.focus();
|
|
110046
|
-
resolve(result);
|
|
110047
|
-
};
|
|
110048
|
-
const onKeydown = (e) => {
|
|
110049
|
-
if (!overlay.isConnected) return;
|
|
110050
|
-
if (e.key === "Escape" && dismissible) {
|
|
110051
|
-
e.stopPropagation();
|
|
110052
|
-
close(null);
|
|
110053
|
-
return;
|
|
110054
|
-
}
|
|
110055
|
-
if (e.key === "Tab") {
|
|
110056
|
-
const focusables = Array.from(
|
|
110057
|
-
root.querySelectorAll("button:not([disabled])")
|
|
110058
|
-
);
|
|
110059
|
-
if (focusables.length === 0) return;
|
|
110060
|
-
const first = focusables[0];
|
|
110061
|
-
const last = focusables[focusables.length - 1];
|
|
110062
|
-
const active = document.activeElement;
|
|
110063
|
-
if (!root.contains(active)) {
|
|
110064
|
-
e.preventDefault();
|
|
110065
|
-
first.focus();
|
|
110066
|
-
} else if (e.shiftKey && active === first) {
|
|
110067
|
-
e.preventDefault();
|
|
110068
|
-
last.focus();
|
|
110069
|
-
} else if (!e.shiftKey && active === last) {
|
|
110070
|
-
e.preventDefault();
|
|
110071
|
-
first.focus();
|
|
110072
|
-
}
|
|
110073
|
-
}
|
|
110074
|
-
};
|
|
110075
|
-
buttonEls.forEach((el2) => {
|
|
110076
|
-
el2.addEventListener("click", () => {
|
|
110077
|
-
const index = Number(el2.dataset.index);
|
|
110078
|
-
close(params.buttons[index].value);
|
|
110079
|
-
});
|
|
110080
|
-
});
|
|
110081
|
-
if (dismissible) {
|
|
110082
|
-
overlay.querySelector(".myio-dialog__close")?.addEventListener("click", () => close(null));
|
|
110083
|
-
overlay.addEventListener("click", (e) => {
|
|
110084
|
-
if (e.target === overlay) close(null);
|
|
110085
|
-
});
|
|
110086
|
-
}
|
|
110087
|
-
document.addEventListener("keydown", onKeydown, true);
|
|
110088
|
-
const autoFocusIndex = params.buttons.findIndex((b) => b.autoFocus);
|
|
110089
|
-
(buttonEls[autoFocusIndex >= 0 ? autoFocusIndex : buttonEls.length - 1] ?? buttonEls[0]).focus();
|
|
110090
|
-
if (params.autoCloseMs && params.autoCloseMs > 0) {
|
|
110091
|
-
autoCloseTimer = setTimeout(() => close(null), params.autoCloseMs);
|
|
110092
|
-
}
|
|
110093
|
-
});
|
|
110094
|
-
}
|
|
110095
|
-
function openConfirmDialog(params) {
|
|
110096
|
-
return openDialog(params);
|
|
110097
|
-
}
|
|
110098
|
-
function openMessageDialog(params) {
|
|
110099
|
-
const severity = params.severity ?? "info";
|
|
110100
|
-
const meta = SEVERITY_META[severity];
|
|
110101
|
-
return openDialog({
|
|
110102
|
-
title: params.title ?? meta.title,
|
|
110103
|
-
message: params.message,
|
|
110104
|
-
buttons: [
|
|
110105
|
-
{ label: params.buttonLabel ?? "OK", variant: "primary", value: "ok", autoFocus: true }
|
|
110106
|
-
],
|
|
110107
|
-
theme: params.theme,
|
|
110108
|
-
container: params.container,
|
|
110109
|
-
modifier: severity !== "info" ? `myio-dialog--${severity}` : void 0,
|
|
110110
|
-
icon: meta.icon,
|
|
110111
|
-
autoCloseMs: params.autoCloseMs
|
|
110112
|
-
}).then(() => void 0);
|
|
110113
|
-
}
|
|
110114
|
-
|
|
110115
|
-
// src/components/premium-modals/dialog/openGenericModal.ts
|
|
110116
|
-
var STYLE_ID10 = "myio-genmodal-styles";
|
|
110117
|
-
var FONT_LINK_ID2 = "myio-dialog-font-nunito";
|
|
110118
|
-
var BASE_Z_INDEX2 = 10600;
|
|
110119
|
-
var openCount2 = 0;
|
|
110120
|
-
function escapeHtml10(value) {
|
|
110121
|
-
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
110122
|
-
}
|
|
110123
|
-
function cssSize(v, fallback) {
|
|
110124
|
-
if (v == null) return fallback;
|
|
110125
|
-
return typeof v === "number" ? `${v}px` : v;
|
|
110126
|
-
}
|
|
110127
|
-
function injectGenericModalStyles() {
|
|
110128
|
-
if (!document.getElementById(FONT_LINK_ID2)) {
|
|
110129
|
-
const link = document.createElement("link");
|
|
110130
|
-
link.id = FONT_LINK_ID2;
|
|
110131
|
-
link.rel = "stylesheet";
|
|
110132
|
-
link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap";
|
|
110133
|
-
document.head.appendChild(link);
|
|
110134
|
-
}
|
|
110135
|
-
if (document.getElementById(STYLE_ID10)) return;
|
|
110136
|
-
const style = document.createElement("style");
|
|
110137
|
-
style.id = STYLE_ID10;
|
|
110138
|
-
style.textContent = `
|
|
110139
|
-
.myio-genmodal-overlay {
|
|
110140
|
-
position: fixed;
|
|
110141
|
-
inset: 0;
|
|
110142
|
-
display: flex;
|
|
110143
|
-
align-items: center;
|
|
110144
|
-
justify-content: center;
|
|
110145
|
-
background: rgba(17, 24, 39, 0.55);
|
|
110146
|
-
font-family: 'Nunito', system-ui, sans-serif;
|
|
110147
|
-
}
|
|
110148
|
-
.myio-genmodal {
|
|
110149
|
-
background: #ffffff;
|
|
110150
|
-
color: #1f2937;
|
|
110151
|
-
border-radius: 12px;
|
|
110152
|
-
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
|
|
110153
|
-
max-width: 90vw;
|
|
110154
|
-
max-height: 90vh;
|
|
110155
|
-
display: flex;
|
|
110156
|
-
flex-direction: column;
|
|
110157
|
-
overflow: hidden;
|
|
110158
|
-
}
|
|
110159
|
-
.myio-genmodal__accent { height: 4px; background: #7C3AED; flex-shrink: 0; }
|
|
110160
|
-
.myio-genmodal__header {
|
|
110161
|
-
display: flex;
|
|
110162
|
-
align-items: flex-start;
|
|
110163
|
-
gap: 10px;
|
|
110164
|
-
padding: 16px 20px;
|
|
110165
|
-
border-bottom: 1px solid #eef0f3;
|
|
110166
|
-
flex-shrink: 0;
|
|
110167
|
-
}
|
|
110168
|
-
.myio-genmodal__icon { font-size: 22px; line-height: 1.3; }
|
|
110169
|
-
.myio-genmodal__title {
|
|
110170
|
-
flex: 1;
|
|
110171
|
-
margin: 0;
|
|
110172
|
-
font-size: 18px;
|
|
110173
|
-
font-weight: 700;
|
|
110174
|
-
line-height: 1.35;
|
|
110175
|
-
}
|
|
110176
|
-
.myio-genmodal__close {
|
|
110177
|
-
border: none;
|
|
110178
|
-
background: transparent;
|
|
110179
|
-
color: #9ca3af;
|
|
110180
|
-
font-size: 18px;
|
|
110181
|
-
line-height: 1;
|
|
110182
|
-
padding: 2px 6px;
|
|
110183
|
-
margin: -2px -8px 0 0;
|
|
110184
|
-
border-radius: 6px;
|
|
110185
|
-
cursor: pointer;
|
|
110186
|
-
}
|
|
110187
|
-
.myio-genmodal__close:hover { background: rgba(0, 0, 0, 0.06); color: #4b5563; }
|
|
110188
|
-
.myio-genmodal__body {
|
|
110189
|
-
padding: 18px 20px;
|
|
110190
|
-
font-size: 15px;
|
|
110191
|
-
line-height: 1.6;
|
|
110192
|
-
color: #374151;
|
|
110193
|
-
overflow-y: auto;
|
|
110194
|
-
}
|
|
110195
|
-
/* Sensible defaults for arbitrary body HTML */
|
|
110196
|
-
.myio-genmodal__body img { max-width: 100%; height: auto; border-radius: 8px; }
|
|
110197
|
-
.myio-genmodal__body hr { border: none; border-top: 1px solid #e5e7eb; margin: 16px 0; }
|
|
110198
|
-
.myio-genmodal__body h1, .myio-genmodal__body h2, .myio-genmodal__body h3, .myio-genmodal__body h4 {
|
|
110199
|
-
margin: 0 0 8px; line-height: 1.3; color: #1f2937;
|
|
110200
|
-
}
|
|
110201
|
-
.myio-genmodal__body p { margin: 0 0 10px; }
|
|
110202
|
-
.myio-genmodal__body ul, .myio-genmodal__body ol { margin: 0 0 10px; padding-left: 22px; }
|
|
110203
|
-
.myio-genmodal__body table { border-collapse: collapse; width: 100%; }
|
|
110204
|
-
.myio-genmodal__body th, .myio-genmodal__body td {
|
|
110205
|
-
border: 1px solid #e5e7eb; padding: 6px 10px; text-align: left; font-size: 14px;
|
|
110206
|
-
}
|
|
110207
|
-
.myio-genmodal__body a { color: #7C3AED; }
|
|
110208
|
-
.myio-genmodal__footer {
|
|
110209
|
-
display: flex;
|
|
110210
|
-
justify-content: flex-end;
|
|
110211
|
-
gap: 8px;
|
|
110212
|
-
padding: 14px 20px;
|
|
110213
|
-
border-top: 1px solid #eef0f3;
|
|
110214
|
-
flex-shrink: 0;
|
|
110215
|
-
}
|
|
110216
|
-
.myio-genmodal__btn {
|
|
110217
|
-
font-family: inherit;
|
|
110218
|
-
font-size: 13px;
|
|
110219
|
-
font-weight: 600;
|
|
110220
|
-
padding: 8px 16px;
|
|
110221
|
-
border-radius: 8px;
|
|
110222
|
-
border: 1px solid transparent;
|
|
110223
|
-
cursor: pointer;
|
|
110224
|
-
transition: filter 0.15s ease, background 0.15s ease;
|
|
110225
|
-
}
|
|
110226
|
-
.myio-genmodal__btn:hover { filter: brightness(0.94); }
|
|
110227
|
-
.myio-genmodal__btn:focus-visible { outline: 2px solid #7C3AED; outline-offset: 2px; }
|
|
110228
|
-
.myio-genmodal__btn--primary { background: #7C3AED; color: #ffffff; }
|
|
110229
|
-
.myio-genmodal__btn--secondary { background: #ffffff; color: #374151; border-color: #d1d5db; }
|
|
110230
|
-
.myio-genmodal__btn--danger { background: #dc2626; color: #ffffff; }
|
|
110231
|
-
.myio-genmodal__btn--success { background: #16a34a; color: #ffffff; }
|
|
110232
|
-
|
|
110233
|
-
/* \u2500\u2500 Dark theme \u2500\u2500 */
|
|
110234
|
-
.myio-genmodal-overlay--dark .myio-genmodal { background: #1f2937; color: #f3f4f6; }
|
|
110235
|
-
.myio-genmodal-overlay--dark .myio-genmodal__header,
|
|
110236
|
-
.myio-genmodal-overlay--dark .myio-genmodal__footer { border-color: #374151; }
|
|
110237
|
-
.myio-genmodal-overlay--dark .myio-genmodal__title { color: #f3f4f6; }
|
|
110238
|
-
.myio-genmodal-overlay--dark .myio-genmodal__body { color: #d1d5db; }
|
|
110239
|
-
.myio-genmodal-overlay--dark .myio-genmodal__body h1,
|
|
110240
|
-
.myio-genmodal-overlay--dark .myio-genmodal__body h2,
|
|
110241
|
-
.myio-genmodal-overlay--dark .myio-genmodal__body h3,
|
|
110242
|
-
.myio-genmodal-overlay--dark .myio-genmodal__body h4 { color: #f3f4f6; }
|
|
110243
|
-
.myio-genmodal-overlay--dark .myio-genmodal__body hr { border-color: #374151; }
|
|
110244
|
-
.myio-genmodal-overlay--dark .myio-genmodal__body th,
|
|
110245
|
-
.myio-genmodal-overlay--dark .myio-genmodal__body td { border-color: #374151; }
|
|
110246
|
-
.myio-genmodal-overlay--dark .myio-genmodal__close { color: #9ca3af; }
|
|
110247
|
-
.myio-genmodal-overlay--dark .myio-genmodal__close:hover { background: rgba(255,255,255,0.08); color: #e5e7eb; }
|
|
110248
|
-
.myio-genmodal-overlay--dark .myio-genmodal__btn--secondary { background: #1f2937; color: #e5e7eb; border-color: #4b5563; }
|
|
110249
|
-
`;
|
|
110250
|
-
document.head.appendChild(style);
|
|
110251
|
-
}
|
|
110252
|
-
var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
110253
|
-
function openGenericModal(params) {
|
|
110254
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
110255
|
-
throw new Error("[openGenericModal] requires a browser environment");
|
|
110256
|
-
}
|
|
110257
|
-
if (typeof params.bodyHtml !== "string") {
|
|
110258
|
-
throw new Error("[openGenericModal] params.bodyHtml must be a string");
|
|
110259
|
-
}
|
|
110260
|
-
injectGenericModalStyles();
|
|
110261
|
-
const dismissible = params.dismissible ?? true;
|
|
110262
|
-
const theme = params.theme ?? "light";
|
|
110263
|
-
const buttons = params.buttons ?? [];
|
|
110264
|
-
const idBase = `myio-genmodal-${++openCount2}`;
|
|
110265
|
-
const overlay = document.createElement("div");
|
|
110266
|
-
overlay.className = `myio-genmodal-overlay${theme === "dark" ? " myio-genmodal-overlay--dark" : ""}`;
|
|
110267
|
-
overlay.style.zIndex = String(BASE_Z_INDEX2 + openCount2);
|
|
110268
|
-
const buttonsHtml = buttons.map((btn, i) => {
|
|
110269
|
-
const variant = btn.variant ?? "secondary";
|
|
110270
|
-
return `<button type="button" class="myio-genmodal__btn myio-genmodal__btn--${variant}" data-index="${i}">${escapeHtml10(btn.label)}</button>`;
|
|
110271
|
-
}).join("");
|
|
110272
|
-
overlay.innerHTML = `
|
|
110273
|
-
<div class="myio-genmodal${params.className ? ` ${escapeHtml10(params.className)}` : ""}"
|
|
110274
|
-
role="dialog" aria-modal="true"
|
|
110275
|
-
aria-labelledby="${idBase}-title"
|
|
110276
|
-
style="width:${cssSize(params.width, "560px")}">
|
|
110277
|
-
<div class="myio-genmodal__accent"></div>
|
|
110278
|
-
<div class="myio-genmodal__header">
|
|
110279
|
-
${params.icon ? `<span class="myio-genmodal__icon" aria-hidden="true">${params.icon}</span>` : ""}
|
|
110280
|
-
<h3 class="myio-genmodal__title" id="${idBase}-title">${escapeHtml10(params.title)}</h3>
|
|
110281
|
-
${dismissible ? `<button type="button" class="myio-genmodal__close" aria-label="Fechar">\u2715</button>` : ""}
|
|
110282
|
-
</div>
|
|
110283
|
-
<div class="myio-genmodal__body" id="${idBase}-body" style="max-height:${cssSize(params.bodyMaxHeight, "70vh")}"></div>
|
|
110284
|
-
${buttons.length ? `<div class="myio-genmodal__footer">${buttonsHtml}</div>` : ""}
|
|
110285
|
-
</div>
|
|
110286
|
-
`;
|
|
110287
|
-
const root = overlay.querySelector(".myio-genmodal");
|
|
110288
|
-
const bodyEl = root.querySelector(".myio-genmodal__body");
|
|
110289
|
-
const titleEl = root.querySelector(".myio-genmodal__title");
|
|
110290
|
-
bodyEl.innerHTML = params.bodyHtml;
|
|
110291
|
-
const previousFocus = document.activeElement;
|
|
110292
|
-
const target = params.container ?? document.body;
|
|
110293
|
-
target.appendChild(overlay);
|
|
110294
|
-
let closed = false;
|
|
110295
|
-
const close = () => {
|
|
110296
|
-
if (closed) return;
|
|
110297
|
-
closed = true;
|
|
110298
|
-
document.removeEventListener("keydown", onKeydown, true);
|
|
110299
|
-
overlay.remove();
|
|
110300
|
-
if (previousFocus && previousFocus.isConnected) previousFocus.focus();
|
|
110301
|
-
if (params.onClose) params.onClose();
|
|
110302
|
-
};
|
|
110303
|
-
const onKeydown = (e) => {
|
|
110304
|
-
if (!overlay.isConnected) return;
|
|
110305
|
-
if (e.key === "Escape" && dismissible) {
|
|
110306
|
-
e.stopPropagation();
|
|
110307
|
-
close();
|
|
110308
|
-
return;
|
|
110309
|
-
}
|
|
110310
|
-
if (e.key === "Tab") {
|
|
110311
|
-
const focusables = Array.from(root.querySelectorAll(FOCUSABLE));
|
|
110312
|
-
if (focusables.length === 0) {
|
|
110313
|
-
e.preventDefault();
|
|
110314
|
-
return;
|
|
110315
|
-
}
|
|
110316
|
-
const first = focusables[0];
|
|
110317
|
-
const last = focusables[focusables.length - 1];
|
|
110318
|
-
const active = document.activeElement;
|
|
110319
|
-
if (!root.contains(active)) {
|
|
110320
|
-
e.preventDefault();
|
|
110321
|
-
first.focus();
|
|
110322
|
-
} else if (e.shiftKey && active === first) {
|
|
110323
|
-
e.preventDefault();
|
|
110324
|
-
last.focus();
|
|
110325
|
-
} else if (!e.shiftKey && active === last) {
|
|
110326
|
-
e.preventDefault();
|
|
110327
|
-
first.focus();
|
|
110328
|
-
}
|
|
110329
|
-
}
|
|
110330
|
-
};
|
|
110331
|
-
root.querySelectorAll(".myio-genmodal__btn").forEach((el2) => {
|
|
110332
|
-
el2.addEventListener("click", () => {
|
|
110333
|
-
const btn = buttons[Number(el2.dataset.index)];
|
|
110334
|
-
if (!btn) return;
|
|
110335
|
-
if (params.onButton) params.onButton(btn.value);
|
|
110336
|
-
if (btn.closeOnClick !== false) close();
|
|
110337
|
-
});
|
|
110338
|
-
});
|
|
110339
|
-
if (dismissible) {
|
|
110340
|
-
overlay.querySelector(".myio-genmodal__close")?.addEventListener("click", () => close());
|
|
110341
|
-
overlay.addEventListener("click", (e) => {
|
|
110342
|
-
if (e.target === overlay) close();
|
|
110343
|
-
});
|
|
110344
|
-
}
|
|
110345
|
-
document.addEventListener("keydown", onKeydown, true);
|
|
110346
|
-
const autoFocusIdx = buttons.findIndex((b) => b.autoFocus);
|
|
110347
|
-
const footerEls = Array.from(root.querySelectorAll(".myio-genmodal__btn"));
|
|
110348
|
-
const initialFocus = (autoFocusIdx >= 0 ? footerEls[autoFocusIdx] : void 0) ?? footerEls[footerEls.length - 1] ?? root.querySelector(`.myio-genmodal__body ${FOCUSABLE}`) ?? overlay.querySelector(".myio-genmodal__close") ?? root;
|
|
110349
|
-
initialFocus.focus?.();
|
|
110350
|
-
return {
|
|
110351
|
-
close,
|
|
110352
|
-
setBodyHtml: (html) => {
|
|
110353
|
-
bodyEl.innerHTML = html;
|
|
110354
|
-
},
|
|
110355
|
-
setTitle: (title) => {
|
|
110356
|
-
titleEl.textContent = title;
|
|
110357
|
-
},
|
|
110358
|
-
getBodyEl: () => bodyEl,
|
|
110359
|
-
getRoot: () => root
|
|
110360
|
-
};
|
|
110361
|
-
}
|
|
110362
|
-
|
|
110363
112316
|
// src/components/myio-gcdr-login/dom.ts
|
|
110364
112317
|
function h2(doc, tag, attrs = {}, ...children) {
|
|
110365
112318
|
const el2 = doc.createElement(tag);
|
|
@@ -131601,7 +133554,7 @@ var HeaderShoppingView = class {
|
|
|
131601
133554
|
tip.id = "tbx-global-tooltip";
|
|
131602
133555
|
document.body.appendChild(tip);
|
|
131603
133556
|
}
|
|
131604
|
-
const
|
|
133557
|
+
const pad3 = 10;
|
|
131605
133558
|
const position = (ev) => {
|
|
131606
133559
|
if (!tip) return;
|
|
131607
133560
|
tip.textContent = text2;
|
|
@@ -131609,8 +133562,8 @@ var HeaderShoppingView = class {
|
|
|
131609
133562
|
let y = ev.clientY - 36;
|
|
131610
133563
|
const vw = window.innerWidth;
|
|
131611
133564
|
const rect = tip.getBoundingClientRect();
|
|
131612
|
-
if (x + rect.width +
|
|
131613
|
-
if (y <
|
|
133565
|
+
if (x + rect.width + pad3 > vw) x = vw - rect.width - pad3;
|
|
133566
|
+
if (y < pad3) y = ev.clientY + 18;
|
|
131614
133567
|
tip.style.left = x + "px";
|
|
131615
133568
|
tip.style.top = y + "px";
|
|
131616
133569
|
};
|
|
@@ -144755,10 +146708,10 @@ var AlarmsNotificationsPanelView = class {
|
|
|
144755
146708
|
const renderListPanel = (group, items, sel) => {
|
|
144756
146709
|
if (items.length === 0) return '<div class="afm-empty">Nenhum item encontrado</div>';
|
|
144757
146710
|
return items.map((item) => {
|
|
144758
|
-
const
|
|
146711
|
+
const esc9 = this.esc(item);
|
|
144759
146712
|
return `<label class="afm-check-item">
|
|
144760
|
-
<input type="checkbox" class="afm-checkbox" data-group="${group}" data-value="${
|
|
144761
|
-
<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>
|
|
144762
146715
|
</label>`;
|
|
144763
146716
|
}).join("");
|
|
144764
146717
|
};
|
|
@@ -157636,7 +159589,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
|
|
|
157636
159589
|
const docTitle = buildActivationReportTitle(deviceName, opts.customerName);
|
|
157637
159590
|
const issuedAt = formatIssuedAt(/* @__PURE__ */ new Date(), locale);
|
|
157638
159591
|
const utilization = data.totalHours > 0 ? (data.totalOnMinutes / (data.totalHours * 60) * 100).toFixed(1) : "0.0";
|
|
157639
|
-
const
|
|
159592
|
+
const esc9 = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
157640
159593
|
const htmlContent = `
|
|
157641
159594
|
<!DOCTYPE html>
|
|
157642
159595
|
<html lang="pt-BR">
|
|
@@ -157688,14 +159641,14 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
|
|
|
157688
159641
|
<div class="brand">MyIO BAS</div>
|
|
157689
159642
|
<h1>Relat\xF3rio de Acionamentos</h1>
|
|
157690
159643
|
<div class="meta">
|
|
157691
|
-
<div><b>Dispositivo:</b> ${
|
|
159644
|
+
<div><b>Dispositivo:</b> ${esc9(deviceName)}</div>
|
|
157692
159645
|
<div><b>Per\xEDodo:</b> ${periodStart} \u2014 ${periodEnd}</div>
|
|
157693
159646
|
</div>
|
|
157694
159647
|
</div>
|
|
157695
159648
|
${customerName ? `
|
|
157696
159649
|
<div class="customer-badge">
|
|
157697
159650
|
<div class="label">Cliente</div>
|
|
157698
|
-
<div class="value">${
|
|
159651
|
+
<div class="value">${esc9(customerName)}</div>
|
|
157699
159652
|
</div>` : ""}
|
|
157700
159653
|
</div>
|
|
157701
159654
|
|
|
@@ -157706,7 +159659,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
|
|
|
157706
159659
|
</div>
|
|
157707
159660
|
<div class="summary-card accent-green">
|
|
157708
159661
|
<div class="summary-value">${formatDurationMinutes(data.totalOnMinutes)}</div>
|
|
157709
|
-
<div class="summary-label">Tempo ${
|
|
159662
|
+
<div class="summary-label">Tempo ${esc9(labelOn)}</div>
|
|
157710
159663
|
</div>
|
|
157711
159664
|
<div class="summary-card accent-amber">
|
|
157712
159665
|
<div class="summary-value">${utilization}%</div>
|
|
@@ -157734,7 +159687,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
|
|
|
157734
159687
|
<tr>
|
|
157735
159688
|
<td>${formatDateForExport(seg.startTime, locale)}</td>
|
|
157736
159689
|
<td>${formatDateForExport(seg.endTime, locale)}</td>
|
|
157737
|
-
<td><span class="pill ${seg.state === "on" ? "pill-on" : "pill-off"}">${seg.state === "on" ?
|
|
159690
|
+
<td><span class="pill ${seg.state === "on" ? "pill-on" : "pill-off"}">${seg.state === "on" ? esc9(labelOn) : esc9(labelOff)}</span></td>
|
|
157738
159691
|
<td>${formatDurationMinutes(seg.durationMinutes)}</td>
|
|
157739
159692
|
<td>${seg.source === "manual" ? "Manual" : seg.source === "schedule" ? "Agendamento" : seg.source === "automation" ? "Automa\xE7\xE3o" : "Desconhecido"}</td>
|
|
157740
159693
|
</tr>
|
|
@@ -157743,7 +159696,7 @@ function exportTimelineToPDF(data, labels = {}, locale = "pt-BR", opts = {}) {
|
|
|
157743
159696
|
</table>
|
|
157744
159697
|
|
|
157745
159698
|
<div class="footer">
|
|
157746
|
-
<span class="issued">Emitido em ${issuedAt}</span>${customerName ? ` · ${
|
|
159699
|
+
<span class="issued">Emitido em ${issuedAt}</span>${customerName ? ` · ${esc9(customerName)}` : ""} · <span class="brand">MyIO BAS</span>
|
|
157747
159700
|
</div>
|
|
157748
159701
|
</body>
|
|
157749
159702
|
</html>
|
|
@@ -165771,9 +167724,9 @@ async function drawTag(doc, x, y, w, h3, device, gateway, allDevices) {
|
|
|
165771
167724
|
doc.setDrawColor(235);
|
|
165772
167725
|
doc.setLineWidth(0.15);
|
|
165773
167726
|
doc.rect(x, frameY, w, frameH);
|
|
165774
|
-
const
|
|
165775
|
-
const innerX = x +
|
|
165776
|
-
const innerW = w -
|
|
167727
|
+
const pad3 = Math.max(2, h3 * 0.05);
|
|
167728
|
+
const innerX = x + pad3;
|
|
167729
|
+
const innerW = w - pad3 * 2;
|
|
165777
167730
|
const titleAreaH = h3 * 0.15;
|
|
165778
167731
|
const qrAreaTop = y + titleAreaH;
|
|
165779
167732
|
const qrAreaH = h3 - titleAreaH - 12;
|
|
@@ -172812,6 +174765,7 @@ var version = package_default.version || "0.0.0";
|
|
|
172812
174765
|
createFreshdeskTicket,
|
|
172813
174766
|
createGcdrAnnotationsAuthStrategy,
|
|
172814
174767
|
createGcdrAnnotationsClient,
|
|
174768
|
+
createGcdrReportJobsApi,
|
|
172815
174769
|
createGoalsBarTooltip,
|
|
172816
174770
|
createGoalsMoneyClient,
|
|
172817
174771
|
createGranularitySelector,
|
|
@@ -172844,6 +174798,7 @@ var version = package_default.version || "0.0.0";
|
|
|
172844
174798
|
createPdfButton,
|
|
172845
174799
|
createPremiumModalChrome,
|
|
172846
174800
|
createPresetupGateway,
|
|
174801
|
+
createProfileClassifier,
|
|
172847
174802
|
createRealTimeClock,
|
|
172848
174803
|
createReportMoneyColumn,
|
|
172849
174804
|
createScheduleCard,
|
|
@@ -172857,6 +174812,7 @@ var version = package_default.version || "0.0.0";
|
|
|
172857
174812
|
createSwitchControl,
|
|
172858
174813
|
createTariffApiAdapter,
|
|
172859
174814
|
createTariffApiClient,
|
|
174815
|
+
createTbEnvelopeStore,
|
|
172860
174816
|
createTelemetryGridComponent,
|
|
172861
174817
|
createTelemetryGridShoppingComponent,
|
|
172862
174818
|
createTelemetryInfoShoppingComponent,
|
|
@@ -172873,6 +174829,8 @@ var version = package_default.version || "0.0.0";
|
|
|
172873
174829
|
decodePayload,
|
|
172874
174830
|
decodePayloadBase64Xor,
|
|
172875
174831
|
deleteFreshdeskTicket,
|
|
174832
|
+
demandDevicesFromItems,
|
|
174833
|
+
demandReport,
|
|
172876
174834
|
deriveCentralConnectivity,
|
|
172877
174835
|
detectContext,
|
|
172878
174836
|
detectDeviceType,
|
|
@@ -173127,6 +175085,7 @@ var version = package_default.version || "0.0.0";
|
|
|
173127
175085
|
openDashboardPopupSettings,
|
|
173128
175086
|
openDashboardPopupWaterTank,
|
|
173129
175087
|
openDemandModal,
|
|
175088
|
+
openDemandPanelV2,
|
|
173130
175089
|
openDeviceCategoryPanel,
|
|
173131
175090
|
openDeviceProfileModal,
|
|
173132
175091
|
openGCDRSyncModal,
|
|
@@ -173236,6 +175195,7 @@ var version = package_default.version || "0.0.0";
|
|
|
173236
175195
|
summaryDeviceToTotalsRow,
|
|
173237
175196
|
summaryLastReadingTs,
|
|
173238
175197
|
summaryLookbackFromIso,
|
|
175198
|
+
tbUserFromJwt,
|
|
173239
175199
|
telemetryInfoFormatEnergy,
|
|
173240
175200
|
telemetryInfoFormatPercentage,
|
|
173241
175201
|
telemetryInfoFormatWater,
|