myio-js-library 0.1.552 → 0.1.554
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 +492 -113
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +492 -113
- package/dist/myio-js-library.umd.js +492 -114
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -899,7 +899,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
899
899
|
// package.json
|
|
900
900
|
var package_default = {
|
|
901
901
|
name: "myio-js-library",
|
|
902
|
-
version: "0.1.
|
|
902
|
+
version: "0.1.554",
|
|
903
903
|
description: "A clean, standalone JS SDK for MYIO projects",
|
|
904
904
|
license: "MIT",
|
|
905
905
|
repository: "github:gh-myio/myio-js-library",
|
|
@@ -18667,9 +18667,17 @@ function renderCardComponentV5({
|
|
|
18667
18667
|
// Temperatura atual vinda do Ingestion = média das últimas 2 h; true = nenhuma leitura na janela
|
|
18668
18668
|
temperatureNoRecentReading = false,
|
|
18669
18669
|
temperatureSource,
|
|
18670
|
-
// 'ingestion-avg-2h' | 'thingsboard' | undefined
|
|
18670
|
+
// 'ingestion-hourly' | 'ingestion-avg-2h' | 'thingsboard' | undefined
|
|
18671
18671
|
temperatureFetchedAt,
|
|
18672
18672
|
// ms da consulta (rótulo "Atualizado às HH:MM")
|
|
18673
|
+
// Idade da última leitura: 'ok' | 'warning' (10–12 h) | 'stale' (12–24 h) | 'offline' (> 24 h)
|
|
18674
|
+
temperatureFreshness,
|
|
18675
|
+
temperatureHourTs,
|
|
18676
|
+
// início da hora fechada exibida (ms)
|
|
18677
|
+
temperatureLastTs,
|
|
18678
|
+
// instante da última leitura válida (ms)
|
|
18679
|
+
temperatureOffset = 0,
|
|
18680
|
+
// offset aplicado (°C) — marcador igual ao da exclusão de totais
|
|
18673
18681
|
// Per-device exclude_groups_totals attribute (SERVER_SCOPE) — drives the orange marker
|
|
18674
18682
|
excludeGroupsTotals
|
|
18675
18683
|
} = entityObject;
|
|
@@ -18830,8 +18838,29 @@ function renderCardComponentV5({
|
|
|
18830
18838
|
const tankLevelCm = isTankDevice2 ? Number(waterLevel ?? val) : null;
|
|
18831
18839
|
const _pctDecimals = resolvePercentDecimals(percentDecimals);
|
|
18832
18840
|
const showNoTempReading = !!temperatureNoRecentReading && isTemperatureDevice2(deviceType);
|
|
18841
|
+
const _isTempCard = isTemperatureDevice2(deviceType);
|
|
18842
|
+
const showTempWarning = _isTempCard && temperatureFreshness === "warning";
|
|
18843
|
+
const _off = Number(temperatureOffset) || 0;
|
|
18833
18844
|
const tempValueTitle = (() => {
|
|
18834
|
-
if (
|
|
18845
|
+
if (_isTempCard && temperatureSource === "ingestion-hourly") {
|
|
18846
|
+
const tz = { timeZone: "America/Sao_Paulo" };
|
|
18847
|
+
const hm = (ts) => new Date(ts).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit", ...tz });
|
|
18848
|
+
const dm = (ts) => new Date(ts).toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", ...tz });
|
|
18849
|
+
const ago = (ts) => {
|
|
18850
|
+
const h3 = Math.floor((Date.now() - ts) / 36e5);
|
|
18851
|
+
const m = Math.floor((Date.now() - ts) % 36e5 / 6e4);
|
|
18852
|
+
return h3 > 0 ? `${h3} h ${m} min` : `${m} min`;
|
|
18853
|
+
};
|
|
18854
|
+
const offTxt = _off ? ` \xB7 offset ${_off > 0 ? "+" : "\u2212"}${String(Math.abs(_off)).replace(".", ",")} \xB0C` : "";
|
|
18855
|
+
if (temperatureFreshness === "offline") return "Nenhuma leitura nas \xFAltimas 24 h";
|
|
18856
|
+
if (!temperatureLastTs) return "";
|
|
18857
|
+
const last = `\xFAltima leitura ${dm(temperatureLastTs)} ${hm(temperatureLastTs)} (h\xE1 ${ago(temperatureLastTs)})`;
|
|
18858
|
+
if (temperatureFreshness === "stale") return `Sem leitura nas \xFAltimas 12 h \u2014 ${last}`;
|
|
18859
|
+
const hour = temperatureHourTs ? `M\xE9dia da hora ${dm(temperatureHourTs)} ${hm(temperatureHourTs)}` : "Temperatura";
|
|
18860
|
+
const warn = temperatureFreshness === "warning" ? "\u26A0 Leitura atrasada (10\u201312 h) \u2014 " : "";
|
|
18861
|
+
return `${warn}${hour} \xB7 ${last}${offTxt}`;
|
|
18862
|
+
}
|
|
18863
|
+
if (!_isTempCard || temperatureSource !== "ingestion-avg-2h") return "";
|
|
18835
18864
|
const at = temperatureFetchedAt ? ` \xB7 Atualizado \xE0s ${new Date(temperatureFetchedAt).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" })}` : "";
|
|
18836
18865
|
return showNoTempReading ? `Nenhuma leitura nas \xFAltimas 2 h${at}` : `Temperatura (m\xE9dia das \xFAltimas 2 h)${at}`;
|
|
18837
18866
|
})();
|
|
@@ -18915,6 +18944,22 @@ function renderCardComponentV5({
|
|
|
18915
18944
|
height: 100%;
|
|
18916
18945
|
}
|
|
18917
18946
|
|
|
18947
|
+
/* Temperatura: leitura atrasada (10\u201312 h) \u2014 borda laranja + \u26A0 piscando */
|
|
18948
|
+
.myio-enhanced-card-container-v5.myio-card-temp-warning .device-card-centered {
|
|
18949
|
+
border: 2px solid #f59e0b !important;
|
|
18950
|
+
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.18) !important;
|
|
18951
|
+
}
|
|
18952
|
+
.myio-temp-warn-icon {
|
|
18953
|
+
margin-left: 4px;
|
|
18954
|
+
font-size: 0.9em;
|
|
18955
|
+
cursor: help;
|
|
18956
|
+
animation: myio-temp-warn-blink 1.2s ease-in-out infinite;
|
|
18957
|
+
}
|
|
18958
|
+
@keyframes myio-temp-warn-blink {
|
|
18959
|
+
0%, 100% { opacity: 1; }
|
|
18960
|
+
50% { opacity: 0.2; }
|
|
18961
|
+
}
|
|
18962
|
+
|
|
18918
18963
|
/* Subtle orange marker \u2014 device flagged in exclude_groups_totals */
|
|
18919
18964
|
.myio-enhanced-card-container-v5.myio-card-excluded::after {
|
|
18920
18965
|
content: '';
|
|
@@ -19204,7 +19249,7 @@ function renderCardComponentV5({
|
|
|
19204
19249
|
<span class="flash-icon ${shouldFlashIcon2 ? "flash" : ""}">
|
|
19205
19250
|
${icon2}
|
|
19206
19251
|
</span>
|
|
19207
|
-
<span class="consumption-value"${showNoTempReading ? ' style="color:#9ca3af;font-weight:500;font-size:0.85em;"' : ""}${tempValueTitle ? ` title="${tempValueTitle}"` : ""}>${formatCardValue(cardEntity.lastValue, deviceType)}</span
|
|
19252
|
+
<span class="consumption-value"${showNoTempReading ? ' style="color:#9ca3af;font-weight:500;font-size:0.85em;"' : ""}${tempValueTitle ? ` title="${tempValueTitle}"` : ""}>${formatCardValue(cardEntity.lastValue, deviceType)}</span>${showTempWarning ? `<span class="myio-temp-warn-icon" title="${tempValueTitle}" aria-label="Leitura atrasada">\u26A0\uFE0F</span>` : ""}
|
|
19208
19253
|
</div>
|
|
19209
19254
|
</div>
|
|
19210
19255
|
${isTankDevice2 ? `<span class="device-percentage-badge device-tank-level-badge" style="position: absolute; bottom: 12px; right: 12px; z-index: 20; background: none !important;">${formatTankHeadFromCm(tankLevelCm)}</span>` : !isTermostatoDevice ? `<span class="device-percentage-badge percentage-tooltip-trigger" style="position: absolute; bottom: 12px; right: 12px; z-index: 20; background: none !important; cursor: help;">${percentageForDisplay.toFixed(_pctDecimals).replace(".", ",")}%</span>` : tempDeviationPercent ? `<span class="device-percentage-badge temp-deviation-badge temp-comparison-tooltip-trigger" style="position: absolute; bottom: 12px; right: 12px; z-index: 20; background: none !important; color: ${tempDeviationPercent.isAbove ? "#ef4444" : tempDeviationPercent.isBelow ? "#3b82f6" : "#6b7280"}; font-weight: 600; cursor: help;">${tempDeviationPercent.sign}${tempDeviationPercent.value.toFixed(1)}%</span>` : ""}
|
|
@@ -19224,6 +19269,8 @@ function renderCardComponentV5({
|
|
|
19224
19269
|
if (_isExcluded) container.classList.add("myio-card-excluded");
|
|
19225
19270
|
} catch (e) {
|
|
19226
19271
|
}
|
|
19272
|
+
if (_isTempCard && _off !== 0) container.classList.add("myio-card-excluded", "myio-card-temp-offset");
|
|
19273
|
+
if (showTempWarning) container.classList.add("myio-card-temp-warning");
|
|
19227
19274
|
if (!document.getElementById("myio-enhanced-card-layout-styles-v5")) {
|
|
19228
19275
|
const layoutStyle = document.createElement("style");
|
|
19229
19276
|
layoutStyle.id = "myio-enhanced-card-layout-styles-v5";
|
|
@@ -33574,31 +33621,56 @@ var fmtPt = (n) => new Intl.NumberFormat("pt-BR", { minimumFractionDigits: 2, ma
|
|
|
33574
33621
|
|
|
33575
33622
|
// src/components/telemetry-grid-shopping/export.ts
|
|
33576
33623
|
var import_jspdf = require("jspdf");
|
|
33624
|
+
function extras(d) {
|
|
33625
|
+
return d;
|
|
33626
|
+
}
|
|
33577
33627
|
function makeCols(unit, colOpts) {
|
|
33578
33628
|
const cols = [
|
|
33579
33629
|
{ key: "idx", label: "#", pdfW: 10 },
|
|
33580
33630
|
{ key: "nome", label: colOpts?.nameLabel || "Nome", pdfW: 100 },
|
|
33581
33631
|
{ key: "identificador", label: "Identificador", pdfW: 60 },
|
|
33582
33632
|
{ key: "consumo", label: colOpts?.valueLabel || (unit ? `Consumo (${unit})` : "Consumo"), pdfW: 50 },
|
|
33633
|
+
...(colOpts?.extraColumns || []).map((c, i) => ({ key: `x${i}`, label: c.label, pdfW: c.pdfW ?? 40 })),
|
|
33583
33634
|
{ key: "perc", label: "%", pdfW: 20 }
|
|
33584
33635
|
];
|
|
33585
33636
|
return cols.filter(
|
|
33586
33637
|
(c) => !(colOpts?.hideIdentifier && c.key === "identificador") && !(colOpts?.hidePerc && c.key === "perc")
|
|
33587
33638
|
);
|
|
33588
33639
|
}
|
|
33589
|
-
function buildRow(d, idx) {
|
|
33640
|
+
function buildRow(d, idx, colOpts) {
|
|
33641
|
+
const x = extras(d);
|
|
33590
33642
|
const fmtVal = () => {
|
|
33591
|
-
if (d.val === null || d.val === void 0) return "\u2014";
|
|
33592
|
-
|
|
33643
|
+
if (d.val === null || d.val === void 0) return colOpts?.emptyValueText || "\u2014";
|
|
33644
|
+
const dec = colOpts?.valueDecimals;
|
|
33645
|
+
return Number(d.val).toLocaleString(
|
|
33646
|
+
"pt-BR",
|
|
33647
|
+
dec !== void 0 ? { minimumFractionDigits: dec, maximumFractionDigits: dec, useGrouping: false } : { maximumFractionDigits: 3, useGrouping: false }
|
|
33648
|
+
);
|
|
33593
33649
|
};
|
|
33594
33650
|
const pd = resolvePercentDecimals();
|
|
33595
|
-
|
|
33651
|
+
const name = d.labelOrName || d.name || "\u2014";
|
|
33652
|
+
const row = {
|
|
33596
33653
|
idx: String(idx + 1),
|
|
33597
|
-
nome:
|
|
33654
|
+
nome: x.nameNote ? `${name} (${x.nameNote})` : name,
|
|
33598
33655
|
identificador: d.deviceIdentifier || "\u2014",
|
|
33599
33656
|
consumo: fmtVal(),
|
|
33600
33657
|
perc: d.perc !== void 0 ? `${d.perc.toFixed(pd).replace(".", ",")}%` : "\u2014"
|
|
33601
33658
|
};
|
|
33659
|
+
(colOpts?.extraColumns || []).forEach((_, i) => {
|
|
33660
|
+
row[`x${i}`] = x.extraCells?.[i] ?? "\u2014";
|
|
33661
|
+
});
|
|
33662
|
+
return row;
|
|
33663
|
+
}
|
|
33664
|
+
function forEachRow(devices, onGroup, onRow) {
|
|
33665
|
+
let n = 0;
|
|
33666
|
+
devices.forEach((d) => {
|
|
33667
|
+
const g = extras(d).groupHeader;
|
|
33668
|
+
if (g) onGroup(g);
|
|
33669
|
+
else onRow(d, n++);
|
|
33670
|
+
});
|
|
33671
|
+
}
|
|
33672
|
+
function countDataRows(devices) {
|
|
33673
|
+
return devices.filter((d) => !extras(d).groupHeader).length;
|
|
33602
33674
|
}
|
|
33603
33675
|
function fmtPeriod(period) {
|
|
33604
33676
|
if (!period?.startISO) return "";
|
|
@@ -33635,6 +33707,9 @@ function escXml(s) {
|
|
|
33635
33707
|
function truncate(s, max) {
|
|
33636
33708
|
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
33637
33709
|
}
|
|
33710
|
+
function pdfSafe(s) {
|
|
33711
|
+
return String(s).replace(/−/g, "-").replace(/[‐‑]/g, "-").replace(/ /g, " ");
|
|
33712
|
+
}
|
|
33638
33713
|
function exportGridCsv(devices, label, unit, period, customerName) {
|
|
33639
33714
|
const periodLabel2 = fmtPeriod(period);
|
|
33640
33715
|
const metaRows = [
|
|
@@ -33646,10 +33721,15 @@ function exportGridCsv(devices, label, unit, period, customerName) {
|
|
|
33646
33721
|
].filter((v) => v !== null);
|
|
33647
33722
|
const cols = makeCols(unit);
|
|
33648
33723
|
const header = cols.map((c) => `"${c.label}"`).join(";");
|
|
33649
|
-
const rows =
|
|
33650
|
-
|
|
33651
|
-
|
|
33652
|
-
|
|
33724
|
+
const rows = [];
|
|
33725
|
+
forEachRow(
|
|
33726
|
+
devices,
|
|
33727
|
+
(g) => rows.push(`"${g.replace(/"/g, '""')}"`),
|
|
33728
|
+
(d, i) => {
|
|
33729
|
+
const r = buildRow(d, i);
|
|
33730
|
+
rows.push(cols.map((c) => `"${String(r[c.key]).replace(/"/g, '""')}"`).join(";"));
|
|
33731
|
+
}
|
|
33732
|
+
);
|
|
33653
33733
|
const csv = "\uFEFF" + [...metaRows, header, ...rows].join("\r\n");
|
|
33654
33734
|
triggerDownload(
|
|
33655
33735
|
new Blob([csv], { type: "text/csv;charset=utf-8;" }),
|
|
@@ -33665,14 +33745,23 @@ function exportGridXls(devices, label, unit, period, customerName, options) {
|
|
|
33665
33745
|
const headerCells = cols.map(
|
|
33666
33746
|
(c) => `<Cell ss:StyleID="h"><Data ss:Type="String">${escXml(c.label)}</Data></Cell>`
|
|
33667
33747
|
).join("");
|
|
33668
|
-
const
|
|
33669
|
-
|
|
33670
|
-
|
|
33671
|
-
|
|
33672
|
-
|
|
33673
|
-
|
|
33674
|
-
|
|
33675
|
-
|
|
33748
|
+
const dataRowsArr = [];
|
|
33749
|
+
forEachRow(
|
|
33750
|
+
devices,
|
|
33751
|
+
(g) => dataRowsArr.push(
|
|
33752
|
+
`<Row><Cell ss:StyleID="g" ss:MergeAcross="${cols.length - 1}"><Data ss:Type="String">${escXml(g)}</Data></Cell></Row>`
|
|
33753
|
+
),
|
|
33754
|
+
(d, i) => {
|
|
33755
|
+
const r = buildRow(d, i, options?.columns);
|
|
33756
|
+
const cells = cols.map((c) => {
|
|
33757
|
+
const v = escXml(String(r[c.key]));
|
|
33758
|
+
return `<Cell><Data ss:Type="String">${v}</Data></Cell>`;
|
|
33759
|
+
}).join("");
|
|
33760
|
+
dataRowsArr.push(`<Row>${cells}</Row>`);
|
|
33761
|
+
}
|
|
33762
|
+
);
|
|
33763
|
+
const dataRows = dataRowsArr.join("\n");
|
|
33764
|
+
const kpiRows = (options?.kpis || []).map((k) => metaRow(k.label, k.sub ? `${k.value} (${k.sub})` : k.value)).join("\n");
|
|
33676
33765
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
33677
33766
|
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
|
33678
33767
|
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
|
|
@@ -33685,6 +33774,10 @@ function exportGridXls(devices, label, unit, period, customerName, options) {
|
|
|
33685
33774
|
<Font ss:Bold="1"/>
|
|
33686
33775
|
<Interior ss:Color="#F0EDF9" ss:Pattern="Solid"/>
|
|
33687
33776
|
</Style>
|
|
33777
|
+
<Style ss:ID="g">
|
|
33778
|
+
<Font ss:Bold="1" ss:Color="#555555"/>
|
|
33779
|
+
<Interior ss:Color="#EEEEEE" ss:Pattern="Solid"/>
|
|
33780
|
+
</Style>
|
|
33688
33781
|
</Styles>
|
|
33689
33782
|
<Worksheet ss:Name="${escXml(label.slice(0, 31))}">
|
|
33690
33783
|
<Table>
|
|
@@ -33692,6 +33785,8 @@ function exportGridXls(devices, label, unit, period, customerName, options) {
|
|
|
33692
33785
|
${customerName ? metaRow("Cliente", customerName) : ""}
|
|
33693
33786
|
${periodLabel2 ? metaRow("Per\xEDodo", periodLabel2) : ""}
|
|
33694
33787
|
${metaRow("Gerado em", (/* @__PURE__ */ new Date()).toLocaleString("pt-BR"))}
|
|
33788
|
+
${kpiRows ? `<Row/>
|
|
33789
|
+
${kpiRows}` : ""}
|
|
33695
33790
|
<Row/>
|
|
33696
33791
|
<Row>${headerCells}</Row>
|
|
33697
33792
|
${dataRows}
|
|
@@ -33741,13 +33836,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
|
|
|
33741
33836
|
doc.setFont("helvetica", "bold");
|
|
33742
33837
|
doc.setFontSize(10);
|
|
33743
33838
|
const titleText = customerName ? `${customerName} \u2014 ${label}` : label;
|
|
33744
|
-
doc.text(titleText, MARGIN, HDR_H / 2 + 1.5);
|
|
33839
|
+
doc.text(pdfSafe(titleText), MARGIN, HDR_H / 2 + 1.5);
|
|
33745
33840
|
doc.setFont("helvetica", "normal");
|
|
33746
33841
|
doc.setFontSize(8);
|
|
33747
33842
|
const periodLabel2 = fmtPeriod(period);
|
|
33748
33843
|
const periodPart = periodLabel2 ? `Per\xEDodo: ${periodLabel2} \u2022 ` : "";
|
|
33749
|
-
const
|
|
33750
|
-
|
|
33844
|
+
const countLabel = options?.columns?.countLabel || "dispositivo(s)";
|
|
33845
|
+
const info = `${periodPart}Gerado em: ${generatedAt} \u2022 ${countDataRows(devices)} ${countLabel} \u2022 Unidade: ${unit} \u2022 P\xE1g. ${pageNo2}`;
|
|
33846
|
+
doc.text(pdfSafe(info), PW - MARGIN, HDR_H / 2 + 1.5, { align: "right" });
|
|
33751
33847
|
}
|
|
33752
33848
|
function drawColumnHeaders(y) {
|
|
33753
33849
|
doc.setFillColor(...ACCENT_SOFT);
|
|
@@ -33757,7 +33853,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
|
|
|
33757
33853
|
doc.setFontSize(7);
|
|
33758
33854
|
cols.forEach((c, ci) => {
|
|
33759
33855
|
const x = colX(ci) + 1.5;
|
|
33760
|
-
doc.text(c.label, x, y + HEAD_H / 2 + 2.5);
|
|
33856
|
+
doc.text(pdfSafe(c.label), x, y + HEAD_H / 2 + 2.5);
|
|
33761
33857
|
});
|
|
33762
33858
|
doc.setDrawColor(200, 195, 220);
|
|
33763
33859
|
doc.setLineWidth(0.2);
|
|
@@ -33774,13 +33870,21 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
|
|
|
33774
33870
|
cols.forEach((c, ci) => {
|
|
33775
33871
|
const x = colX(ci) + 1.5;
|
|
33776
33872
|
const maxChars = Math.floor(colWidths[ci] / 1.8);
|
|
33777
|
-
const text2 = truncate(String(r[c.key]), maxChars);
|
|
33873
|
+
const text2 = truncate(pdfSafe(String(r[c.key])), maxChars);
|
|
33778
33874
|
doc.text(text2, x, y + ROW_H / 2 + 2.2);
|
|
33779
33875
|
});
|
|
33780
33876
|
doc.setDrawColor(230, 228, 240);
|
|
33781
33877
|
doc.setLineWidth(0.1);
|
|
33782
33878
|
doc.line(MARGIN, y + ROW_H, MARGIN + TABLE_W, y + ROW_H);
|
|
33783
33879
|
}
|
|
33880
|
+
function drawGroupRow(text2, y) {
|
|
33881
|
+
doc.setFillColor(...ACCENT_SOFT);
|
|
33882
|
+
doc.rect(MARGIN, y, TABLE_W, ROW_H, "F");
|
|
33883
|
+
doc.setTextColor(...ACCENT2);
|
|
33884
|
+
doc.setFont("helvetica", "bold");
|
|
33885
|
+
doc.setFontSize(6.5);
|
|
33886
|
+
doc.text(truncate(pdfSafe(text2), 160), MARGIN + 1.5, y + ROW_H / 2 + 2.2);
|
|
33887
|
+
}
|
|
33784
33888
|
function drawFooter2() {
|
|
33785
33889
|
doc.setFillColor(250, 249, 255);
|
|
33786
33890
|
doc.rect(0, PH - FTR_H, PW, FTR_H, "F");
|
|
@@ -33807,14 +33911,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
|
|
|
33807
33911
|
doc.setTextColor(...ACCENT2);
|
|
33808
33912
|
doc.setFont("helvetica", "bold");
|
|
33809
33913
|
doc.setFontSize(11);
|
|
33810
|
-
doc.text(truncate(kpi.value, 24), cx, y + 6.5, { align: "center" });
|
|
33914
|
+
doc.text(truncate(pdfSafe(kpi.value), 24), cx, y + 6.5, { align: "center" });
|
|
33811
33915
|
doc.setTextColor(110, 110, 120);
|
|
33812
33916
|
doc.setFont("helvetica", "normal");
|
|
33813
33917
|
doc.setFontSize(6.5);
|
|
33814
|
-
doc.text(truncate(kpi.label, 34), cx, y + 10.5, { align: "center" });
|
|
33918
|
+
doc.text(truncate(pdfSafe(kpi.label), 34), cx, y + 10.5, { align: "center" });
|
|
33815
33919
|
if (kpi.sub) {
|
|
33816
33920
|
doc.setFontSize(5.5);
|
|
33817
|
-
doc.text(truncate(kpi.sub, 40), cx, y + 13.5, { align: "center" });
|
|
33921
|
+
doc.text(truncate(pdfSafe(kpi.sub), 40), cx, y + 13.5, { align: "center" });
|
|
33818
33922
|
}
|
|
33819
33923
|
});
|
|
33820
33924
|
}
|
|
@@ -33827,7 +33931,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
|
|
|
33827
33931
|
}
|
|
33828
33932
|
drawColumnHeaders(currentY);
|
|
33829
33933
|
currentY += HEAD_H;
|
|
33830
|
-
|
|
33934
|
+
const ensureSpace = () => {
|
|
33831
33935
|
if (currentY + ROW_H > MAX_Y) {
|
|
33832
33936
|
drawFooter2();
|
|
33833
33937
|
doc.addPage();
|
|
@@ -33837,9 +33941,20 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
|
|
|
33837
33941
|
drawColumnHeaders(currentY);
|
|
33838
33942
|
currentY += HEAD_H;
|
|
33839
33943
|
}
|
|
33840
|
-
|
|
33841
|
-
|
|
33842
|
-
|
|
33944
|
+
};
|
|
33945
|
+
forEachRow(
|
|
33946
|
+
devices,
|
|
33947
|
+
(g) => {
|
|
33948
|
+
ensureSpace();
|
|
33949
|
+
drawGroupRow(g, currentY);
|
|
33950
|
+
currentY += ROW_H;
|
|
33951
|
+
},
|
|
33952
|
+
(d, i) => {
|
|
33953
|
+
ensureSpace();
|
|
33954
|
+
drawDataRow(buildRow(d, i, options?.columns), currentY, i % 2 === 0);
|
|
33955
|
+
currentY += ROW_H;
|
|
33956
|
+
}
|
|
33957
|
+
);
|
|
33843
33958
|
drawFooter2();
|
|
33844
33959
|
const chart = options?.chartImage;
|
|
33845
33960
|
if (chart?.dataUrl) {
|
|
@@ -33850,7 +33965,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
|
|
|
33850
33965
|
doc.setTextColor(...ACCENT2);
|
|
33851
33966
|
doc.setFont("helvetica", "bold");
|
|
33852
33967
|
doc.setFontSize(12);
|
|
33853
|
-
doc.text(chart.title || "Participa\xE7\xE3o por Dispositivo", MARGIN, titleY);
|
|
33968
|
+
doc.text(pdfSafe(chart.title || "Participa\xE7\xE3o por Dispositivo"), MARGIN, titleY);
|
|
33854
33969
|
const imgTop = titleY + 4;
|
|
33855
33970
|
const availW = TABLE_W;
|
|
33856
33971
|
const availH = MAX_Y - imgTop;
|
|
@@ -35090,6 +35205,81 @@ function createModalFooter(params = {}) {
|
|
|
35090
35205
|
};
|
|
35091
35206
|
}
|
|
35092
35207
|
|
|
35208
|
+
// src/components/premium-modals/internal/temperatureBarsPng.ts
|
|
35209
|
+
function renderTemperatureBarsPng(opts) {
|
|
35210
|
+
if (typeof document === "undefined" || !opts.items.length) return null;
|
|
35211
|
+
const W = 1400;
|
|
35212
|
+
const ROW = 34;
|
|
35213
|
+
const TOP = 56;
|
|
35214
|
+
const H = TOP + opts.items.length * ROW + 20;
|
|
35215
|
+
const canvas = document.createElement("canvas");
|
|
35216
|
+
canvas.width = W;
|
|
35217
|
+
canvas.height = H;
|
|
35218
|
+
let ctx = null;
|
|
35219
|
+
try {
|
|
35220
|
+
ctx = canvas.getContext("2d");
|
|
35221
|
+
} catch {
|
|
35222
|
+
ctx = null;
|
|
35223
|
+
}
|
|
35224
|
+
if (!ctx) return null;
|
|
35225
|
+
const fmt2 = (v) => v.toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
35226
|
+
const ideal = opts.ideal && Number.isFinite(opts.ideal.min) && Number.isFinite(opts.ideal.max) ? opts.ideal : null;
|
|
35227
|
+
const values = opts.items.map((i) => i.value).filter((v) => v !== null && Number.isFinite(v));
|
|
35228
|
+
const base = values.length ? values : [20, 30];
|
|
35229
|
+
const lo = Math.floor(Math.min(...base, ideal ? ideal.min : Infinity) - 1);
|
|
35230
|
+
const hi = Math.ceil(Math.max(...base, ideal ? ideal.max : -Infinity) + 1);
|
|
35231
|
+
const span = Math.max(1, hi - lo);
|
|
35232
|
+
const LABEL_W = 460;
|
|
35233
|
+
const VALUE_W = 120;
|
|
35234
|
+
const trackX = LABEL_W + 20;
|
|
35235
|
+
const trackW = W - trackX - VALUE_W - 20;
|
|
35236
|
+
const xOf = (v) => trackX + (v - lo) / span * trackW;
|
|
35237
|
+
ctx.fillStyle = "#ffffff";
|
|
35238
|
+
ctx.fillRect(0, 0, W, H);
|
|
35239
|
+
ctx.fillStyle = "#6b7280";
|
|
35240
|
+
ctx.font = "20px Helvetica, Arial, sans-serif";
|
|
35241
|
+
ctx.textBaseline = "middle";
|
|
35242
|
+
const scale = `Escala ${fmt2(lo)}\u2013${fmt2(hi)} \xB0C`;
|
|
35243
|
+
ctx.fillText(ideal ? `${scale} \xB7 faixa ideal ${fmt2(ideal.min)}\u2013${fmt2(ideal.max)} \xB0C` : scale, 16, 26);
|
|
35244
|
+
opts.items.forEach((item, i) => {
|
|
35245
|
+
const y = TOP + i * ROW;
|
|
35246
|
+
const cy = y + ROW / 2;
|
|
35247
|
+
ctx.fillStyle = "#1f2937";
|
|
35248
|
+
ctx.font = "20px Helvetica, Arial, sans-serif";
|
|
35249
|
+
ctx.textAlign = "left";
|
|
35250
|
+
let label = item.label;
|
|
35251
|
+
while (ctx.measureText(label).width > LABEL_W - 10 && label.length > 4) label = label.slice(0, -2);
|
|
35252
|
+
if (label !== item.label) label = label.slice(0, -1) + "\u2026";
|
|
35253
|
+
ctx.fillText(label, 16, cy);
|
|
35254
|
+
if (item.value === null || !Number.isFinite(item.value)) {
|
|
35255
|
+
ctx.fillStyle = "#9ca3af";
|
|
35256
|
+
ctx.font = "italic 18px Helvetica, Arial, sans-serif";
|
|
35257
|
+
ctx.fillText("Sem leitura", trackX, cy);
|
|
35258
|
+
ctx.textAlign = "right";
|
|
35259
|
+
ctx.fillText("\u2014", W - 16, cy);
|
|
35260
|
+
return;
|
|
35261
|
+
}
|
|
35262
|
+
ctx.fillStyle = "#e5e7eb";
|
|
35263
|
+
ctx.fillRect(trackX, cy - 9, trackW, 18);
|
|
35264
|
+
if (ideal) {
|
|
35265
|
+
ctx.fillStyle = "rgba(34,197,94,0.18)";
|
|
35266
|
+
ctx.fillRect(xOf(ideal.min), cy - 9, xOf(ideal.max) - xOf(ideal.min), 18);
|
|
35267
|
+
}
|
|
35268
|
+
const v = item.value;
|
|
35269
|
+
ctx.fillStyle = ideal && v > ideal.max ? "#ef4444" : ideal && v < ideal.min ? "#3b82f6" : opts.accent || "#3e1a7d";
|
|
35270
|
+
ctx.fillRect(trackX, cy - 6, Math.max(2, xOf(v) - trackX), 12);
|
|
35271
|
+
ctx.fillStyle = "#111827";
|
|
35272
|
+
ctx.font = "bold 20px Helvetica, Arial, sans-serif";
|
|
35273
|
+
ctx.textAlign = "right";
|
|
35274
|
+
ctx.fillText(fmt2(v), W - 16, cy);
|
|
35275
|
+
});
|
|
35276
|
+
try {
|
|
35277
|
+
return { dataUrl: canvas.toDataURL("image/png"), width: W, height: H };
|
|
35278
|
+
} catch {
|
|
35279
|
+
return null;
|
|
35280
|
+
}
|
|
35281
|
+
}
|
|
35282
|
+
|
|
35093
35283
|
// src/components/premium-modals/report-device/DeviceReportModal.ts
|
|
35094
35284
|
var DOMAIN_CONFIG = {
|
|
35095
35285
|
energy: {
|
|
@@ -35883,10 +36073,22 @@ var DeviceReportModal = class {
|
|
|
35883
36073
|
const summaryValue = this.domainConfig.summaryType === "average" ? valid > 0 ? total / valid : 0 : total;
|
|
35884
36074
|
const now = /* @__PURE__ */ new Date();
|
|
35885
36075
|
const timestamp = now.toLocaleDateString("pt-BR") + " - " + now.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
|
|
36076
|
+
const p = this.exportPeriod;
|
|
36077
|
+
const fmtD = (iso) => iso ? new Date(iso).toLocaleDateString("pt-BR") : "";
|
|
36078
|
+
const off = Number(this.params.temperatureOffset) || 0;
|
|
36079
|
+
const isTemp = this.domainConfig.summaryType === "average";
|
|
36080
|
+
void summaryValue;
|
|
35886
36081
|
const csvData = [
|
|
35887
36082
|
["Dispositivo/Loja", this.params.identifier || "N/A", this.params.label || ""],
|
|
36083
|
+
...this.params.deviceName ? [["Dispositivo (TB)", this.params.deviceName, ""]] : [],
|
|
36084
|
+
...isTemp && off !== 0 ? [["Offset aplicado (\xB0C)", this.domainConfig.formatter(off), ""]] : [],
|
|
36085
|
+
...this.resolveCustomerName() ? [["Cliente", this.resolveCustomerName(), ""]] : [],
|
|
36086
|
+
...p?.startISO ? [["Per\xEDodo", `${fmtD(p.startISO)} \u2014 ${fmtD(p.endISO)}`, ""]] : [],
|
|
36087
|
+
["Granularidade", this.granularity === "1h" ? "Hora" : "Dia", ""],
|
|
35888
36088
|
["DATA EMISS\xC3O", timestamp, ""],
|
|
35889
|
-
[
|
|
36089
|
+
[],
|
|
36090
|
+
...this.computeKpis().map((k) => [k.label, k.value, k.sub || ""]),
|
|
36091
|
+
[],
|
|
35890
36092
|
[this.granularity === "1h" ? "Data/Hora" : "Data", this.domainConfig.label, ""],
|
|
35891
36093
|
...this.data.map((row) => [this.formatDate(row.date), row.noData ? "Sem leitura" : this.domainConfig.formatter(row.consumption)])
|
|
35892
36094
|
];
|
|
@@ -35907,7 +36109,11 @@ var DeviceReportModal = class {
|
|
|
35907
36109
|
}
|
|
35908
36110
|
// Título dos exports — mesmo da modal (identificador + etiqueta do device).
|
|
35909
36111
|
resolveExportTitle() {
|
|
35910
|
-
|
|
36112
|
+
const base = `Relat\xF3rio - ${this.params.identifier || "SEM IDENTIFICADOR"} - ${this.params.label || "SEM ETIQUETA"}`;
|
|
36113
|
+
const dev = this.params.deviceName ? ` (${this.params.deviceName})` : "";
|
|
36114
|
+
const off = Number(this.params.temperatureOffset) || 0;
|
|
36115
|
+
const offTxt = this.domainConfig.summaryType === "average" && off !== 0 ? ` \xB7 offset ${off > 0 ? "+" : "\u2212"}${Math.abs(off).toLocaleString("pt-BR", { maximumFractionDigits: 2 })} \xB0C` : "";
|
|
36116
|
+
return `${base}${dev}${offTxt}`;
|
|
35911
36117
|
}
|
|
35912
36118
|
// Mapeia as linhas do relatório (dia/hora × consumo) para o shape TelemetryDevice
|
|
35913
36119
|
// dos exporters compartilhados do grid: labelOrName = data formatada, val = consumo,
|
|
@@ -35926,16 +36132,41 @@ var DeviceReportModal = class {
|
|
|
35926
36132
|
// Opções de coluna dos exporters: relatório single-device → Data | Consumo | %
|
|
35927
36133
|
// (Identificador redundante — o device já está no título/filename).
|
|
35928
36134
|
exportColumnOptions() {
|
|
36135
|
+
const isTemp = this.domainConfig.summaryType === "average";
|
|
35929
36136
|
return {
|
|
35930
36137
|
nameLabel: this.granularity === "1h" ? "Data/Hora" : "Data",
|
|
35931
|
-
hideIdentifier: true
|
|
36138
|
+
hideIdentifier: true,
|
|
36139
|
+
// Mesmo rótulo da coluna da tela (antes saía "Consumo (°C)" em temperatura)
|
|
36140
|
+
valueLabel: this.domainConfig.label,
|
|
36141
|
+
countLabel: this.granularity === "1h" ? "hora(s)" : "dia(s)",
|
|
36142
|
+
...isTemp ? { hidePerc: true, emptyValueText: "Sem leitura", valueDecimals: 2 } : {}
|
|
35932
36143
|
};
|
|
35933
36144
|
}
|
|
36145
|
+
// Gráfico da página final do PDF = painel da direita da tela
|
|
36146
|
+
async buildExportChart() {
|
|
36147
|
+
if (this.domainConfig.summaryType === "average") {
|
|
36148
|
+
const isHourly = this.granularity === "1h";
|
|
36149
|
+
const ranked = [...this.validRows].sort((a, b) => b.consumption - a.consumption);
|
|
36150
|
+
const shown = isHourly ? ranked.slice(0, 24) : ranked;
|
|
36151
|
+
const noData = isHourly ? [] : this.data.filter((r) => r.noData);
|
|
36152
|
+
const png = renderTemperatureBarsPng({
|
|
36153
|
+
items: [
|
|
36154
|
+
...shown.map((r) => ({ label: this.formatDate(r.date), value: r.consumption })),
|
|
36155
|
+
...noData.map((r) => ({ label: this.formatDate(r.date), value: null }))
|
|
36156
|
+
],
|
|
36157
|
+
ideal: this.params.temperatureIdealRange ?? null,
|
|
36158
|
+
accent: this.resolveAccentHex()
|
|
36159
|
+
});
|
|
36160
|
+
const title = isHourly ? `Horas mais quentes${ranked.length > 24 ? " (top 24)" : ""}` : "Dias mais quentes (m\xE9dia do dia)";
|
|
36161
|
+
return png ? { ...png, title } : null;
|
|
36162
|
+
}
|
|
36163
|
+
const chartPng = await this.participationChart?.toPngDataUrl?.().catch(() => null);
|
|
36164
|
+
return chartPng ? { ...chartPng, title: "Participa\xE7\xE3o por Dia" } : null;
|
|
36165
|
+
}
|
|
35934
36166
|
// PDF export — layout premium do grid + paleta do dashboard + faixa de KPIs +
|
|
35935
36167
|
// página dedicada com o gráfico "Participação por Dia" da modal.
|
|
35936
36168
|
async exportPDF() {
|
|
35937
36169
|
if (!this.data.length) return;
|
|
35938
|
-
const chartPng = await this.participationChart?.toPngDataUrl?.().catch(() => null);
|
|
35939
36170
|
exportGridPdf(
|
|
35940
36171
|
this.buildExportDevices(),
|
|
35941
36172
|
this.resolveExportTitle(),
|
|
@@ -35945,7 +36176,7 @@ var DeviceReportModal = class {
|
|
|
35945
36176
|
{
|
|
35946
36177
|
accentColor: this.resolveAccentHex(),
|
|
35947
36178
|
kpis: this.computeKpis(),
|
|
35948
|
-
chartImage:
|
|
36179
|
+
chartImage: await this.buildExportChart(),
|
|
35949
36180
|
columns: this.exportColumnOptions()
|
|
35950
36181
|
}
|
|
35951
36182
|
);
|
|
@@ -35961,7 +36192,8 @@ var DeviceReportModal = class {
|
|
|
35961
36192
|
this.resolveCustomerName() || null,
|
|
35962
36193
|
{
|
|
35963
36194
|
accentColor: this.resolveAccentHex(),
|
|
35964
|
-
columns: this.exportColumnOptions()
|
|
36195
|
+
columns: this.exportColumnOptions(),
|
|
36196
|
+
kpis: this.computeKpis()
|
|
35965
36197
|
}
|
|
35966
36198
|
);
|
|
35967
36199
|
}
|
|
@@ -38945,28 +39177,43 @@ var AllReportModal = class {
|
|
|
38945
39177
|
// Header row only
|
|
38946
39178
|
["Identificador", "Nome", `Consumo (${this.domainConfig.unit})`],
|
|
38947
39179
|
// Data rows
|
|
38948
|
-
...sortedData.map((row) => [row.identifier, row.name, row.consumption.toFixed(2)])
|
|
39180
|
+
...sortedData.map((row) => [row.identifier, row.name, row.consumption.toFixed(2).replace(".", ",")])
|
|
38949
39181
|
];
|
|
38950
39182
|
const csvContent = toCsv(csvData);
|
|
38951
39183
|
this.downloadCSV(csvContent, `relatorio-geral-lojas-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`);
|
|
38952
39184
|
}
|
|
38953
39185
|
// Temperatura: valores já com offset e sem leituras inválidas (mesma base da tabela).
|
|
38954
39186
|
// 1d → uma linha por sensor por DIA; 1h → uma linha por sensor por HORA.
|
|
38955
|
-
exportTemperatureCSV(
|
|
39187
|
+
exportTemperatureCSV(_rows) {
|
|
39188
|
+
const rows = this.getFilteredData();
|
|
38956
39189
|
const showId = this.showIdentifierColumn(rows);
|
|
38957
39190
|
const idCol = (r) => showId ? [r.identifier] : [];
|
|
38958
39191
|
const idHead = showId ? ["Identificador"] : [];
|
|
38959
|
-
const n2 = (v) => v === null || v === void 0 || !Number.isFinite(v) ? "" : v.toFixed(2);
|
|
39192
|
+
const n2 = (v) => v === null || v === void 0 || !Number.isFinite(v) ? "" : v.toFixed(2).replace(".", ",");
|
|
38960
39193
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
39194
|
+
const gran = this.granularity;
|
|
38961
39195
|
const fmtStamp = (ts) => {
|
|
38962
39196
|
const { date: d, time } = this.formatSpDateTime(ts);
|
|
38963
39197
|
return `${d} ${time}`;
|
|
38964
39198
|
};
|
|
39199
|
+
const p = this.exportPeriod;
|
|
39200
|
+
const fmtD = (iso) => iso ? new Date(iso).toLocaleDateString("pt-BR") : "";
|
|
39201
|
+
const meta = [
|
|
39202
|
+
["Relat\xF3rio", this.resolveTitle()],
|
|
39203
|
+
...this.params.customerName ? [["Cliente", this.params.customerName]] : [],
|
|
39204
|
+
...p?.startISO ? [["Per\xEDodo", `${fmtD(p.startISO)} \u2014 ${fmtD(p.endISO)}`]] : [],
|
|
39205
|
+
["Granularidade", gran === "1h" ? "Hora" : "Dia"],
|
|
39206
|
+
["Gerado em", (/* @__PURE__ */ new Date()).toLocaleString("pt-BR")],
|
|
39207
|
+
[],
|
|
39208
|
+
...this.computeKpis().map((k) => [k.label, k.value, k.sub || ""]),
|
|
39209
|
+
[]
|
|
39210
|
+
];
|
|
38965
39211
|
const summary = [
|
|
38966
|
-
[...idHead, "Sensor", "M\xE9dia (\xB0C)", "M\xEDn (\xB0C)", "Data/Hora M\xEDn", "M\xE1x (\xB0C)", "Data/Hora M\xE1x", "Leituras", "Leituras descartadas"],
|
|
39212
|
+
[...idHead, "Sensor", "Offset (\xB0C)", "M\xE9dia (\xB0C)", "M\xEDn (\xB0C)", "Data/Hora M\xEDn", "M\xE1x (\xB0C)", "Data/Hora M\xE1x", "Leituras", "Leituras descartadas"],
|
|
38967
39213
|
...rows.map((r) => [
|
|
38968
39214
|
...idCol(r),
|
|
38969
39215
|
r.name,
|
|
39216
|
+
r.id ? n2(this.temperatureOffsetFor(r.id)) : "",
|
|
38970
39217
|
r.noData ? "Sem leitura" : n2(r.consumption),
|
|
38971
39218
|
r.noData ? "" : n2(r.min),
|
|
38972
39219
|
r.noData || !r.minTs ? "" : fmtStamp(r.minTs),
|
|
@@ -38976,7 +39223,6 @@ var AllReportModal = class {
|
|
|
38976
39223
|
String(r.discarded ?? "")
|
|
38977
39224
|
])
|
|
38978
39225
|
];
|
|
38979
|
-
const gran = this.granularity;
|
|
38980
39226
|
const series = [[...idHead, "Sensor", gran === "1h" ? "Data/Hora" : "Dia", "Temperatura (\xB0C)"]];
|
|
38981
39227
|
const fmtTs = (ts) => gran === "1h" ? new Date(ts).toLocaleString("pt-BR", {
|
|
38982
39228
|
day: "2-digit",
|
|
@@ -38989,9 +39235,9 @@ var AllReportModal = class {
|
|
|
38989
39235
|
for (const r of rows) {
|
|
38990
39236
|
const raw = r.id && this.temperatureStats.get(r.id)?.series || [];
|
|
38991
39237
|
const pts = gran === "1h" ? this.aggregateHourly(raw, "avg") : this.aggregateDaily(raw, "avg");
|
|
38992
|
-
for (const
|
|
39238
|
+
for (const p2 of pts) series.push([...idCol(r), r.name, fmtTs(p2.timestamp), n2(p2.value)]);
|
|
38993
39239
|
}
|
|
38994
|
-
const csvContent = toCsv([...summary, [], ...series]);
|
|
39240
|
+
const csvContent = toCsv([...meta, ...summary, [], ...series]);
|
|
38995
39241
|
this.downloadCSV(csvContent, `relatorio-temperatura-${gran}-${date}.csv`);
|
|
38996
39242
|
}
|
|
38997
39243
|
// 1h CSV: one row per device × hour. The series comes from per-device requests
|
|
@@ -39016,7 +39262,7 @@ var AllReportModal = class {
|
|
|
39016
39262
|
for (const row of sortedData) {
|
|
39017
39263
|
const points = row.id && series.get(row.id) || [];
|
|
39018
39264
|
for (const p of points) {
|
|
39019
|
-
csvData.push([row.identifier, row.name, fmtTs(p.timestamp), p.value.toFixed(2)]);
|
|
39265
|
+
csvData.push([row.identifier, row.name, fmtTs(p.timestamp), p.value.toFixed(2).replace(".", ",")]);
|
|
39020
39266
|
}
|
|
39021
39267
|
}
|
|
39022
39268
|
if (csvData.length === 1) {
|
|
@@ -39080,28 +39326,75 @@ var AllReportModal = class {
|
|
|
39080
39326
|
}
|
|
39081
39327
|
// Maps the report rows to the TelemetryDevice shape consumed by the shared
|
|
39082
39328
|
// TELEMETRY grid exporters (only labelOrName/name/deviceIdentifier/val/perc are read).
|
|
39329
|
+
// Linhas dos exports = a TABELA DA TELA: mesma ordem/filtros (getFilteredData), cabeçalhos
|
|
39330
|
+
// de grupo com o mesmo resumo, e — em temperatura — Mín/Máx, offset e "Sem leitura".
|
|
39083
39331
|
buildExportDevices() {
|
|
39084
|
-
const
|
|
39085
|
-
|
|
39086
|
-
)
|
|
39087
|
-
|
|
39088
|
-
|
|
39089
|
-
|
|
39090
|
-
|
|
39091
|
-
|
|
39092
|
-
|
|
39093
|
-
|
|
39094
|
-
|
|
39095
|
-
|
|
39332
|
+
const rows = this.getFilteredData();
|
|
39333
|
+
const grandTotal = this.calculateTotalConsumption();
|
|
39334
|
+
const toDevice = (r) => {
|
|
39335
|
+
const base = {
|
|
39336
|
+
labelOrName: r.name,
|
|
39337
|
+
name: r.name,
|
|
39338
|
+
deviceIdentifier: r.identifier,
|
|
39339
|
+
val: this.isTemperature && r.noData ? null : r.consumption,
|
|
39340
|
+
perc: this.isTemperature ? void 0 : grandTotal > 0 ? r.consumption / grandTotal * 100 : 0
|
|
39341
|
+
};
|
|
39342
|
+
if (!this.isTemperature) return base;
|
|
39343
|
+
const off = r.id && !r.noData ? this.temperatureOffsetFor(r.id) : 0;
|
|
39344
|
+
return {
|
|
39345
|
+
...base,
|
|
39346
|
+
extraCells: [this.fmtTemp(r.noData ? null : r.min), this.fmtTemp(r.noData ? null : r.max)],
|
|
39347
|
+
...off ? { nameNote: `offset ${off > 0 ? "+" : "\u2212"}${fmtPt(Math.abs(off))} \xB0C` } : {}
|
|
39348
|
+
};
|
|
39349
|
+
};
|
|
39350
|
+
const out = [];
|
|
39351
|
+
if (rows.some((r) => r.groupLabel)) {
|
|
39352
|
+
const order = [];
|
|
39353
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
39354
|
+
for (const r of rows) {
|
|
39355
|
+
const g = r.groupLabel || "\u2014";
|
|
39356
|
+
if (!byGroup.has(g)) {
|
|
39357
|
+
byGroup.set(g, []);
|
|
39358
|
+
order.push(g);
|
|
39359
|
+
}
|
|
39360
|
+
byGroup.get(g).push(r);
|
|
39361
|
+
}
|
|
39362
|
+
for (const g of order) {
|
|
39363
|
+
const items = byGroup.get(g);
|
|
39364
|
+
out.push({ groupHeader: `${g.toUpperCase()} \xB7 ${this.groupSummaryText(items)}` });
|
|
39365
|
+
items.forEach((r) => out.push(toDevice(r)));
|
|
39366
|
+
}
|
|
39367
|
+
} else {
|
|
39368
|
+
rows.forEach((r) => out.push(toDevice(r)));
|
|
39369
|
+
}
|
|
39370
|
+
return out;
|
|
39371
|
+
}
|
|
39372
|
+
// Mesmo texto do cabeçalho de grupo da tabela
|
|
39373
|
+
groupSummaryText(items) {
|
|
39374
|
+
if (this.isTemperature) {
|
|
39375
|
+
const withData = items.filter((r) => !r.noData);
|
|
39376
|
+
const avg = withData.length ? withData.reduce((s, r) => s + r.consumption, 0) / withData.length : null;
|
|
39377
|
+
return `${items.length} sensores \xB7 m\xE9dia ${this.fmtTemp(avg)} \xB0C`;
|
|
39378
|
+
}
|
|
39379
|
+
const total = items.reduce((s, r) => s + r.consumption, 0);
|
|
39380
|
+
return `${items.length} dispositivos \xB7 ${fmtPt(total)} ${this.domainConfig.unit}`;
|
|
39096
39381
|
}
|
|
39097
|
-
// Colunas do PDF/XLS
|
|
39382
|
+
// Colunas do PDF/XLS = colunas da tela. Temperatura: Sensor | Média | Mín | Máx (sem %).
|
|
39098
39383
|
exportColumns() {
|
|
39099
39384
|
if (!this.isTemperature) return null;
|
|
39100
39385
|
return {
|
|
39101
39386
|
nameLabel: "Sensor",
|
|
39102
|
-
valueLabel: "
|
|
39387
|
+
valueLabel: "M\xE9dia (\xB0C)",
|
|
39103
39388
|
hidePerc: true,
|
|
39104
|
-
hideIdentifier: !this.showIdentifierColumn()
|
|
39389
|
+
hideIdentifier: !this.showIdentifierColumn(),
|
|
39390
|
+
extraColumns: [
|
|
39391
|
+
{ label: "M\xEDn (\xB0C)", pdfW: 35 },
|
|
39392
|
+
{ label: "M\xE1x (\xB0C)", pdfW: 35 }
|
|
39393
|
+
],
|
|
39394
|
+
emptyValueText: "Sem leitura",
|
|
39395
|
+
countLabel: "sensor(es)",
|
|
39396
|
+
valueDecimals: 2
|
|
39397
|
+
// "30,00" como na tela (não "30")
|
|
39105
39398
|
};
|
|
39106
39399
|
}
|
|
39107
39400
|
// Accent hex da paleta do dashboard (tema efetivo) para o PDF/XLS — cai no
|
|
@@ -39115,26 +39408,38 @@ var AllReportModal = class {
|
|
|
39115
39408
|
const vars = typeof theme.cssVars === "function" ? theme.cssVars() : theme;
|
|
39116
39409
|
return vars?.["--myio-brand-700"];
|
|
39117
39410
|
}
|
|
39411
|
+
// Gráfico da página final do PDF = o painel da direita da tela
|
|
39412
|
+
async buildExportChart() {
|
|
39413
|
+
if (this.isTemperature) {
|
|
39414
|
+
const png = renderTemperatureBarsPng({
|
|
39415
|
+
items: this.getFilteredData().map((r) => ({ label: r.name, value: r.noData ? null : r.consumption })),
|
|
39416
|
+
ideal: this.temperatureIdealRange,
|
|
39417
|
+
accent: this.resolveAccentHex()
|
|
39418
|
+
});
|
|
39419
|
+
return png ? { ...png, title: "Temperatura m\xE9dia por sensor" } : null;
|
|
39420
|
+
}
|
|
39421
|
+
const chartPng = await this.participationChart?.toPngDataUrl?.().catch(() => null);
|
|
39422
|
+
return chartPng ? { ...chartPng, title: "Participa\xE7\xE3o por Dispositivo" } : null;
|
|
39423
|
+
}
|
|
39118
39424
|
// PDF export — layout premium do grid + paleta do dashboard + faixa de KPIs +
|
|
39119
|
-
// página dedicada com o gráfico
|
|
39425
|
+
// página dedicada com o mesmo gráfico do painel da modal.
|
|
39120
39426
|
async exportPDF() {
|
|
39121
39427
|
if (!this.data.length) return;
|
|
39122
|
-
const chartPng = await this.participationChart?.toPngDataUrl?.().catch(() => null);
|
|
39123
39428
|
exportGridPdf(
|
|
39124
39429
|
this.buildExportDevices(),
|
|
39125
39430
|
this.resolveTitle(),
|
|
39126
39431
|
this.domainConfig.unit,
|
|
39127
39432
|
this.exportPeriod,
|
|
39128
|
-
null,
|
|
39433
|
+
this.params.customerName || null,
|
|
39129
39434
|
{
|
|
39130
39435
|
accentColor: this.resolveAccentHex(),
|
|
39131
39436
|
kpis: this.computeKpis(),
|
|
39132
39437
|
columns: this.exportColumns(),
|
|
39133
|
-
chartImage:
|
|
39438
|
+
chartImage: await this.buildExportChart()
|
|
39134
39439
|
}
|
|
39135
39440
|
);
|
|
39136
39441
|
}
|
|
39137
|
-
// XLS export (XML Spreadsheet) —
|
|
39442
|
+
// XLS export (XML Spreadsheet) — mesma tabela + KPIs da tela.
|
|
39138
39443
|
exportXLS() {
|
|
39139
39444
|
if (!this.data.length) return;
|
|
39140
39445
|
exportGridXls(
|
|
@@ -39142,8 +39447,8 @@ var AllReportModal = class {
|
|
|
39142
39447
|
this.resolveTitle(),
|
|
39143
39448
|
this.domainConfig.unit,
|
|
39144
39449
|
this.exportPeriod,
|
|
39145
|
-
null,
|
|
39146
|
-
{ accentColor: this.resolveAccentHex(), columns: this.exportColumns() }
|
|
39450
|
+
this.params.customerName || null,
|
|
39451
|
+
{ accentColor: this.resolveAccentHex(), columns: this.exportColumns(), kpis: this.computeKpis() }
|
|
39147
39452
|
);
|
|
39148
39453
|
}
|
|
39149
39454
|
async fetchCustomerTotals(startISO, endISO) {
|
|
@@ -79616,7 +79921,10 @@ async function openTemperatureModal(params) {
|
|
|
79616
79921
|
document.body.appendChild(modalContainer);
|
|
79617
79922
|
renderModal3(modalContainer, state6, modalId);
|
|
79618
79923
|
try {
|
|
79619
|
-
state6.data =
|
|
79924
|
+
state6.data = discardInvalid(
|
|
79925
|
+
await (state6.dataFetcher ? state6.dataFetcher(state6.startTs, state6.endTs) : fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs)),
|
|
79926
|
+
state6
|
|
79927
|
+
);
|
|
79620
79928
|
state6.stats = calculateStats(state6.data, state6.clampRange, state6.temperatureOffset);
|
|
79621
79929
|
state6.isLoading = false;
|
|
79622
79930
|
renderModal3(modalContainer, state6, modalId);
|
|
@@ -79641,7 +79949,10 @@ async function openTemperatureModal(params) {
|
|
|
79641
79949
|
state6.isLoading = true;
|
|
79642
79950
|
renderModal3(modalContainer, state6, modalId);
|
|
79643
79951
|
try {
|
|
79644
|
-
state6.data =
|
|
79952
|
+
state6.data = discardInvalid(
|
|
79953
|
+
await (state6.dataFetcher ? state6.dataFetcher(state6.startTs, state6.endTs) : fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs)),
|
|
79954
|
+
state6
|
|
79955
|
+
);
|
|
79645
79956
|
state6.stats = calculateStats(state6.data, state6.clampRange, state6.temperatureOffset);
|
|
79646
79957
|
state6.isLoading = false;
|
|
79647
79958
|
renderModal3(modalContainer, state6, modalId);
|
|
@@ -80269,7 +80580,10 @@ async function setupEventListeners3(container, state6, modalId, onClose) {
|
|
|
80269
80580
|
state6.dateRangePicker = null;
|
|
80270
80581
|
renderModal3(container, state6, modalId);
|
|
80271
80582
|
try {
|
|
80272
|
-
state6.data =
|
|
80583
|
+
state6.data = discardInvalid(
|
|
80584
|
+
await (state6.dataFetcher ? state6.dataFetcher(state6.startTs, state6.endTs) : fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs)),
|
|
80585
|
+
state6
|
|
80586
|
+
);
|
|
80273
80587
|
state6.stats = calculateStats(state6.data, state6.clampRange, state6.temperatureOffset);
|
|
80274
80588
|
state6.isLoading = false;
|
|
80275
80589
|
renderModal3(container, state6, modalId);
|
|
@@ -80283,19 +80597,64 @@ async function setupEventListeners3(container, state6, modalId, onClose) {
|
|
|
80283
80597
|
}
|
|
80284
80598
|
});
|
|
80285
80599
|
}
|
|
80600
|
+
function discardInvalid(data, state6) {
|
|
80601
|
+
const off = Number(state6.temperatureOffset) || 0;
|
|
80602
|
+
return data.filter((p) => {
|
|
80603
|
+
const v = Number(p.value) + off;
|
|
80604
|
+
return Number.isFinite(v) && v >= state6.clampRange.min && v <= state6.clampRange.max;
|
|
80605
|
+
});
|
|
80606
|
+
}
|
|
80286
80607
|
function exportCsv(state6) {
|
|
80287
80608
|
if (state6.data.length === 0) return;
|
|
80288
|
-
const
|
|
80289
|
-
const
|
|
80290
|
-
|
|
80291
|
-
|
|
80292
|
-
|
|
80293
|
-
|
|
80294
|
-
|
|
80295
|
-
|
|
80296
|
-
|
|
80297
|
-
state6.
|
|
80298
|
-
);
|
|
80609
|
+
const off = Number(state6.temperatureOffset) || 0;
|
|
80610
|
+
const n2 = (v) => v.toFixed(2).replace(".", ",");
|
|
80611
|
+
const q = (v) => `"${String(v).replace(/"/g, '""')}"`;
|
|
80612
|
+
const dt = (ts) => new Date(ts).toLocaleString("pt-BR", {
|
|
80613
|
+
day: "2-digit",
|
|
80614
|
+
month: "2-digit",
|
|
80615
|
+
year: "numeric",
|
|
80616
|
+
hour: "2-digit",
|
|
80617
|
+
minute: "2-digit",
|
|
80618
|
+
timeZone: state6.timezone || "America/Sao_Paulo"
|
|
80619
|
+
});
|
|
80620
|
+
const fmtD = (ts) => new Date(ts).toLocaleDateString("pt-BR", { timeZone: state6.timezone || "America/Sao_Paulo" });
|
|
80621
|
+
const filtered = filterByDayPeriods(state6.data, state6.selectedPeriods);
|
|
80622
|
+
const lines = [
|
|
80623
|
+
["Relat\xF3rio", `${state6.label} - Hist\xF3rico de Temperatura`],
|
|
80624
|
+
...state6.deviceName ? [["Dispositivo (TB)", state6.deviceName]] : [],
|
|
80625
|
+
...state6.customerName ? [["Cliente", state6.customerName]] : [],
|
|
80626
|
+
["Per\xEDodo", `${fmtD(state6.startTs)} \u2014 ${fmtD(state6.endTs)}`],
|
|
80627
|
+
["Granularidade", state6.granularity === "hour" ? "Hora (m\xE9dia da hora fechada)" : "Dia (m\xE9dia do dia)"],
|
|
80628
|
+
["Per\xEDodos do dia", getSelectedPeriodsLabel(state6.selectedPeriods)],
|
|
80629
|
+
...off !== 0 ? [["Offset aplicado (\xB0C)", n2(off)]] : [],
|
|
80630
|
+
["Gerado em", (/* @__PURE__ */ new Date()).toLocaleString("pt-BR")],
|
|
80631
|
+
[],
|
|
80632
|
+
["Temperatura atual (\xB0C)", state6.currentTemperature !== null ? n2(state6.currentTemperature) : "N/A"],
|
|
80633
|
+
["M\xE9dia do per\xEDodo (\xB0C)", state6.stats.count > 0 ? n2(state6.stats.avg) : "N/A"],
|
|
80634
|
+
["M\xEDnima (\xB0C)", state6.stats.count > 0 ? n2(state6.stats.min) : "N/A"],
|
|
80635
|
+
["M\xE1xima (\xB0C)", state6.stats.count > 0 ? n2(state6.stats.max) : "N/A"],
|
|
80636
|
+
["Leituras", String(state6.stats.count)],
|
|
80637
|
+
[]
|
|
80638
|
+
];
|
|
80639
|
+
if (state6.granularity === "hour") {
|
|
80640
|
+
lines.push(["Data/Hora", "Temperatura (\xB0C)"]);
|
|
80641
|
+
aggregateByHour(filtered, state6.clampRange, off).forEach((p) => lines.push([dt(p.ts), n2(Number(p.value))]));
|
|
80642
|
+
} else {
|
|
80643
|
+
lines.push(["Dia", "M\xE9dia (\xB0C)", "M\xEDn (\xB0C)", "M\xE1x (\xB0C)", "Leituras"]);
|
|
80644
|
+
aggregateByDay(filtered, state6.clampRange, off).forEach(
|
|
80645
|
+
(d) => lines.push([fmtD(d.dateTs), n2(d.avg), n2(d.min), n2(d.max), String(d.count)])
|
|
80646
|
+
);
|
|
80647
|
+
}
|
|
80648
|
+
const csv = "\uFEFF" + lines.map((r) => r.map(q).join(";")).join("\r\n");
|
|
80649
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
80650
|
+
const url = URL.createObjectURL(blob);
|
|
80651
|
+
const a = document.createElement("a");
|
|
80652
|
+
a.href = url;
|
|
80653
|
+
a.download = `temperatura_${state6.label.replace(/\s+/g, "_")}_${state6.granularity === "hour" ? "1h" : "1d"}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
80654
|
+
document.body.appendChild(a);
|
|
80655
|
+
a.click();
|
|
80656
|
+
document.body.removeChild(a);
|
|
80657
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
80299
80658
|
}
|
|
80300
80659
|
function buildHeaderTitle(state6) {
|
|
80301
80660
|
const base = `${escHtml3(state6.label)} - Hist\xF3rico de Temperatura`;
|
|
@@ -81202,43 +81561,63 @@ function mountCmpFooter(container, state6, modalId) {
|
|
|
81202
81561
|
_cmpFooters.set(modalId, footer);
|
|
81203
81562
|
}
|
|
81204
81563
|
function exportComparisonCSV2(state6) {
|
|
81205
|
-
const
|
|
81206
|
-
const
|
|
81207
|
-
const
|
|
81208
|
-
|
|
81209
|
-
|
|
81210
|
-
|
|
81211
|
-
|
|
81212
|
-
|
|
81213
|
-
|
|
81214
|
-
|
|
81215
|
-
csvContent += "\n";
|
|
81216
|
-
csvContent += "Estat\xEDsticas por Sensor:\n";
|
|
81217
|
-
csvContent += "Sensor,M\xE9dia (\xB0C),Min (\xB0C),Max (\xB0C),Leituras\n";
|
|
81218
|
-
state6.deviceData.forEach((dd) => {
|
|
81219
|
-
csvContent += `"${dd.device.label}",${dd.stats.avg.toFixed(2)},${dd.stats.min.toFixed(2)},${dd.stats.max.toFixed(2)},${dd.stats.count}
|
|
81220
|
-
`;
|
|
81564
|
+
const n2 = (v) => v.toFixed(2).replace(".", ",");
|
|
81565
|
+
const q = (v) => `"${String(v).replace(/"/g, '""')}"`;
|
|
81566
|
+
const fmtD = (ts) => new Date(ts).toLocaleDateString("pt-BR", { timeZone: "America/Sao_Paulo" });
|
|
81567
|
+
const dt = (ts) => new Date(ts).toLocaleString("pt-BR", {
|
|
81568
|
+
day: "2-digit",
|
|
81569
|
+
month: "2-digit",
|
|
81570
|
+
year: "numeric",
|
|
81571
|
+
hour: "2-digit",
|
|
81572
|
+
minute: "2-digit",
|
|
81573
|
+
timeZone: "America/Sao_Paulo"
|
|
81221
81574
|
});
|
|
81222
|
-
|
|
81223
|
-
|
|
81224
|
-
|
|
81225
|
-
|
|
81226
|
-
|
|
81227
|
-
|
|
81228
|
-
|
|
81229
|
-
|
|
81230
|
-
|
|
81231
|
-
|
|
81575
|
+
const offTxt = (d) => {
|
|
81576
|
+
const o = Number(d.temperatureOffset) || 0;
|
|
81577
|
+
return o !== 0 ? n2(o) : "";
|
|
81578
|
+
};
|
|
81579
|
+
const lines = [
|
|
81580
|
+
["Relat\xF3rio", `Compara\xE7\xE3o de Temperatura - ${state6.devices.length} sensores`],
|
|
81581
|
+
...state6.customerName ? [["Cliente", state6.customerName]] : [],
|
|
81582
|
+
["Per\xEDodo", `${fmtD(state6.startTs)} \u2014 ${fmtD(state6.endTs)}`],
|
|
81583
|
+
["Granularidade", state6.granularity === "hour" ? "Hora (m\xE9dia da hora fechada)" : "Dia (m\xE9dia do dia)"],
|
|
81584
|
+
["Per\xEDodos do dia", getSelectedPeriodsLabel(state6.selectedPeriods)],
|
|
81585
|
+
["Gerado em", (/* @__PURE__ */ new Date()).toLocaleString("pt-BR")],
|
|
81586
|
+
[],
|
|
81587
|
+
["Sensor", "Offset (\xB0C)", "M\xE9dia (\xB0C)", "M\xEDn (\xB0C)", "M\xE1x (\xB0C)", "Leituras"],
|
|
81588
|
+
...state6.deviceData.map((dd) => [
|
|
81589
|
+
dd.device.label,
|
|
81590
|
+
offTxt(dd.device),
|
|
81591
|
+
dd.stats.count > 0 ? n2(dd.stats.avg) : "N/A",
|
|
81592
|
+
dd.stats.count > 0 ? n2(dd.stats.min) : "N/A",
|
|
81593
|
+
dd.stats.count > 0 ? n2(dd.stats.max) : "N/A",
|
|
81594
|
+
String(dd.stats.count)
|
|
81595
|
+
]),
|
|
81596
|
+
[]
|
|
81597
|
+
];
|
|
81598
|
+
const perDevice = state6.deviceData.map((dd) => {
|
|
81599
|
+
const filtered = filterByDayPeriods(dd.data, state6.selectedPeriods);
|
|
81600
|
+
const pts = state6.granularity === "hour" ? aggregateByHour(filtered, state6.clampRange).map((p) => [p.ts, Number(p.value)]) : aggregateByDay(filtered, state6.clampRange).map((d) => [d.dateTs, d.avg]);
|
|
81601
|
+
return new Map(pts);
|
|
81232
81602
|
});
|
|
81233
|
-
const
|
|
81603
|
+
const allTs = [...new Set(perDevice.flatMap((m) => [...m.keys()]))].sort((a, b) => a - b);
|
|
81604
|
+
lines.push([state6.granularity === "hour" ? "Data/Hora" : "Dia", ...state6.deviceData.map((dd) => `${dd.device.label} (\xB0C)`)]);
|
|
81605
|
+
allTs.forEach(
|
|
81606
|
+
(ts) => lines.push([
|
|
81607
|
+
state6.granularity === "hour" ? dt(ts) : fmtD(ts),
|
|
81608
|
+
...perDevice.map((m) => m.has(ts) ? n2(m.get(ts)) : "")
|
|
81609
|
+
])
|
|
81610
|
+
);
|
|
81611
|
+
const csv = "\uFEFF" + lines.map((r) => r.map(q).join(";")).join("\r\n");
|
|
81612
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
81234
81613
|
const url = URL.createObjectURL(blob);
|
|
81235
81614
|
const link = document.createElement("a");
|
|
81236
81615
|
link.href = url;
|
|
81237
|
-
link.download = `comparacao_temperatura_${
|
|
81616
|
+
link.download = `comparacao_temperatura_${state6.granularity === "hour" ? "1h" : "1d"}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
81238
81617
|
document.body.appendChild(link);
|
|
81239
81618
|
link.click();
|
|
81240
81619
|
document.body.removeChild(link);
|
|
81241
|
-
URL.revokeObjectURL(url);
|
|
81620
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
81242
81621
|
}
|
|
81243
81622
|
|
|
81244
81623
|
// src/components/temperature/TemperatureSettingsModal.ts
|