myio-js-library 0.1.552 → 0.1.553

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -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.552",
902
+ version: "0.1.553",
903
903
  description: "A clean, standalone JS SDK for MYIO projects",
904
904
  license: "MIT",
905
905
  repository: "github:gh-myio/myio-js-library",
@@ -33574,31 +33574,52 @@ var fmtPt = (n) => new Intl.NumberFormat("pt-BR", { minimumFractionDigits: 2, ma
33574
33574
 
33575
33575
  // src/components/telemetry-grid-shopping/export.ts
33576
33576
  var import_jspdf = require("jspdf");
33577
+ function extras(d) {
33578
+ return d;
33579
+ }
33577
33580
  function makeCols(unit, colOpts) {
33578
33581
  const cols = [
33579
33582
  { key: "idx", label: "#", pdfW: 10 },
33580
33583
  { key: "nome", label: colOpts?.nameLabel || "Nome", pdfW: 100 },
33581
33584
  { key: "identificador", label: "Identificador", pdfW: 60 },
33582
33585
  { key: "consumo", label: colOpts?.valueLabel || (unit ? `Consumo (${unit})` : "Consumo"), pdfW: 50 },
33586
+ ...(colOpts?.extraColumns || []).map((c, i) => ({ key: `x${i}`, label: c.label, pdfW: c.pdfW ?? 40 })),
33583
33587
  { key: "perc", label: "%", pdfW: 20 }
33584
33588
  ];
33585
33589
  return cols.filter(
33586
33590
  (c) => !(colOpts?.hideIdentifier && c.key === "identificador") && !(colOpts?.hidePerc && c.key === "perc")
33587
33591
  );
33588
33592
  }
33589
- function buildRow(d, idx) {
33593
+ function buildRow(d, idx, colOpts) {
33594
+ const x = extras(d);
33590
33595
  const fmtVal = () => {
33591
- if (d.val === null || d.val === void 0) return "\u2014";
33596
+ if (d.val === null || d.val === void 0) return colOpts?.emptyValueText || "\u2014";
33592
33597
  return Number(d.val).toLocaleString("pt-BR", { maximumFractionDigits: 3, useGrouping: false });
33593
33598
  };
33594
33599
  const pd = resolvePercentDecimals();
33595
- return {
33600
+ const name = d.labelOrName || d.name || "\u2014";
33601
+ const row = {
33596
33602
  idx: String(idx + 1),
33597
- nome: d.labelOrName || d.name || "\u2014",
33603
+ nome: x.nameNote ? `${name} (${x.nameNote})` : name,
33598
33604
  identificador: d.deviceIdentifier || "\u2014",
33599
33605
  consumo: fmtVal(),
33600
33606
  perc: d.perc !== void 0 ? `${d.perc.toFixed(pd).replace(".", ",")}%` : "\u2014"
33601
33607
  };
33608
+ (colOpts?.extraColumns || []).forEach((_, i) => {
33609
+ row[`x${i}`] = x.extraCells?.[i] ?? "\u2014";
33610
+ });
33611
+ return row;
33612
+ }
33613
+ function forEachRow(devices, onGroup, onRow) {
33614
+ let n = 0;
33615
+ devices.forEach((d) => {
33616
+ const g = extras(d).groupHeader;
33617
+ if (g) onGroup(g);
33618
+ else onRow(d, n++);
33619
+ });
33620
+ }
33621
+ function countDataRows(devices) {
33622
+ return devices.filter((d) => !extras(d).groupHeader).length;
33602
33623
  }
33603
33624
  function fmtPeriod(period) {
33604
33625
  if (!period?.startISO) return "";
@@ -33646,10 +33667,15 @@ function exportGridCsv(devices, label, unit, period, customerName) {
33646
33667
  ].filter((v) => v !== null);
33647
33668
  const cols = makeCols(unit);
33648
33669
  const header = cols.map((c) => `"${c.label}"`).join(";");
33649
- const rows = devices.map((d, i) => {
33650
- const r = buildRow(d, i);
33651
- return cols.map((c) => `"${String(r[c.key]).replace(/"/g, '""')}"`).join(";");
33652
- });
33670
+ const rows = [];
33671
+ forEachRow(
33672
+ devices,
33673
+ (g) => rows.push(`"${g.replace(/"/g, '""')}"`),
33674
+ (d, i) => {
33675
+ const r = buildRow(d, i);
33676
+ rows.push(cols.map((c) => `"${String(r[c.key]).replace(/"/g, '""')}"`).join(";"));
33677
+ }
33678
+ );
33653
33679
  const csv = "\uFEFF" + [...metaRows, header, ...rows].join("\r\n");
33654
33680
  triggerDownload(
33655
33681
  new Blob([csv], { type: "text/csv;charset=utf-8;" }),
@@ -33665,14 +33691,23 @@ function exportGridXls(devices, label, unit, period, customerName, options) {
33665
33691
  const headerCells = cols.map(
33666
33692
  (c) => `<Cell ss:StyleID="h"><Data ss:Type="String">${escXml(c.label)}</Data></Cell>`
33667
33693
  ).join("");
33668
- const dataRows = devices.map((d, i) => {
33669
- const r = buildRow(d, i);
33670
- const cells = cols.map((c) => {
33671
- const v = escXml(String(r[c.key]));
33672
- return `<Cell><Data ss:Type="String">${v}</Data></Cell>`;
33673
- }).join("");
33674
- return `<Row>${cells}</Row>`;
33675
- }).join("\n");
33694
+ const dataRowsArr = [];
33695
+ forEachRow(
33696
+ devices,
33697
+ (g) => dataRowsArr.push(
33698
+ `<Row><Cell ss:StyleID="g" ss:MergeAcross="${cols.length - 1}"><Data ss:Type="String">${escXml(g)}</Data></Cell></Row>`
33699
+ ),
33700
+ (d, i) => {
33701
+ const r = buildRow(d, i, options?.columns);
33702
+ const cells = cols.map((c) => {
33703
+ const v = escXml(String(r[c.key]));
33704
+ return `<Cell><Data ss:Type="String">${v}</Data></Cell>`;
33705
+ }).join("");
33706
+ dataRowsArr.push(`<Row>${cells}</Row>`);
33707
+ }
33708
+ );
33709
+ const dataRows = dataRowsArr.join("\n");
33710
+ const kpiRows = (options?.kpis || []).map((k) => metaRow(k.label, k.sub ? `${k.value} (${k.sub})` : k.value)).join("\n");
33676
33711
  const xml = `<?xml version="1.0" encoding="UTF-8"?>
33677
33712
  <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
33678
33713
  xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
@@ -33685,6 +33720,10 @@ function exportGridXls(devices, label, unit, period, customerName, options) {
33685
33720
  <Font ss:Bold="1"/>
33686
33721
  <Interior ss:Color="#F0EDF9" ss:Pattern="Solid"/>
33687
33722
  </Style>
33723
+ <Style ss:ID="g">
33724
+ <Font ss:Bold="1" ss:Color="#555555"/>
33725
+ <Interior ss:Color="#EEEEEE" ss:Pattern="Solid"/>
33726
+ </Style>
33688
33727
  </Styles>
33689
33728
  <Worksheet ss:Name="${escXml(label.slice(0, 31))}">
33690
33729
  <Table>
@@ -33692,6 +33731,8 @@ function exportGridXls(devices, label, unit, period, customerName, options) {
33692
33731
  ${customerName ? metaRow("Cliente", customerName) : ""}
33693
33732
  ${periodLabel2 ? metaRow("Per\xEDodo", periodLabel2) : ""}
33694
33733
  ${metaRow("Gerado em", (/* @__PURE__ */ new Date()).toLocaleString("pt-BR"))}
33734
+ ${kpiRows ? `<Row/>
33735
+ ${kpiRows}` : ""}
33695
33736
  <Row/>
33696
33737
  <Row>${headerCells}</Row>
33697
33738
  ${dataRows}
@@ -33746,7 +33787,8 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33746
33787
  doc.setFontSize(8);
33747
33788
  const periodLabel2 = fmtPeriod(period);
33748
33789
  const periodPart = periodLabel2 ? `Per\xEDodo: ${periodLabel2} \u2022 ` : "";
33749
- const info = `${periodPart}Gerado em: ${generatedAt} \u2022 ${devices.length} dispositivo(s) \u2022 Unidade: ${unit} \u2022 P\xE1g. ${pageNo2}`;
33790
+ const countLabel = options?.columns?.countLabel || "dispositivo(s)";
33791
+ const info = `${periodPart}Gerado em: ${generatedAt} \u2022 ${countDataRows(devices)} ${countLabel} \u2022 Unidade: ${unit} \u2022 P\xE1g. ${pageNo2}`;
33750
33792
  doc.text(info, PW - MARGIN, HDR_H / 2 + 1.5, { align: "right" });
33751
33793
  }
33752
33794
  function drawColumnHeaders(y) {
@@ -33781,6 +33823,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33781
33823
  doc.setLineWidth(0.1);
33782
33824
  doc.line(MARGIN, y + ROW_H, MARGIN + TABLE_W, y + ROW_H);
33783
33825
  }
33826
+ function drawGroupRow(text2, y) {
33827
+ doc.setFillColor(...ACCENT_SOFT);
33828
+ doc.rect(MARGIN, y, TABLE_W, ROW_H, "F");
33829
+ doc.setTextColor(...ACCENT2);
33830
+ doc.setFont("helvetica", "bold");
33831
+ doc.setFontSize(6.5);
33832
+ doc.text(truncate(text2, 160), MARGIN + 1.5, y + ROW_H / 2 + 2.2);
33833
+ }
33784
33834
  function drawFooter2() {
33785
33835
  doc.setFillColor(250, 249, 255);
33786
33836
  doc.rect(0, PH - FTR_H, PW, FTR_H, "F");
@@ -33827,7 +33877,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33827
33877
  }
33828
33878
  drawColumnHeaders(currentY);
33829
33879
  currentY += HEAD_H;
33830
- devices.forEach((d, i) => {
33880
+ const ensureSpace = () => {
33831
33881
  if (currentY + ROW_H > MAX_Y) {
33832
33882
  drawFooter2();
33833
33883
  doc.addPage();
@@ -33837,9 +33887,20 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33837
33887
  drawColumnHeaders(currentY);
33838
33888
  currentY += HEAD_H;
33839
33889
  }
33840
- drawDataRow(buildRow(d, i), currentY, i % 2 === 0);
33841
- currentY += ROW_H;
33842
- });
33890
+ };
33891
+ forEachRow(
33892
+ devices,
33893
+ (g) => {
33894
+ ensureSpace();
33895
+ drawGroupRow(g, currentY);
33896
+ currentY += ROW_H;
33897
+ },
33898
+ (d, i) => {
33899
+ ensureSpace();
33900
+ drawDataRow(buildRow(d, i, options?.columns), currentY, i % 2 === 0);
33901
+ currentY += ROW_H;
33902
+ }
33903
+ );
33843
33904
  drawFooter2();
33844
33905
  const chart = options?.chartImage;
33845
33906
  if (chart?.dataUrl) {
@@ -35090,6 +35151,81 @@ function createModalFooter(params = {}) {
35090
35151
  };
35091
35152
  }
35092
35153
 
35154
+ // src/components/premium-modals/internal/temperatureBarsPng.ts
35155
+ function renderTemperatureBarsPng(opts) {
35156
+ if (typeof document === "undefined" || !opts.items.length) return null;
35157
+ const W = 1400;
35158
+ const ROW = 34;
35159
+ const TOP = 56;
35160
+ const H = TOP + opts.items.length * ROW + 20;
35161
+ const canvas = document.createElement("canvas");
35162
+ canvas.width = W;
35163
+ canvas.height = H;
35164
+ let ctx = null;
35165
+ try {
35166
+ ctx = canvas.getContext("2d");
35167
+ } catch {
35168
+ ctx = null;
35169
+ }
35170
+ if (!ctx) return null;
35171
+ const fmt2 = (v) => v.toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
35172
+ const ideal = opts.ideal && Number.isFinite(opts.ideal.min) && Number.isFinite(opts.ideal.max) ? opts.ideal : null;
35173
+ const values = opts.items.map((i) => i.value).filter((v) => v !== null && Number.isFinite(v));
35174
+ const base = values.length ? values : [20, 30];
35175
+ const lo = Math.floor(Math.min(...base, ideal ? ideal.min : Infinity) - 1);
35176
+ const hi = Math.ceil(Math.max(...base, ideal ? ideal.max : -Infinity) + 1);
35177
+ const span = Math.max(1, hi - lo);
35178
+ const LABEL_W = 460;
35179
+ const VALUE_W = 120;
35180
+ const trackX = LABEL_W + 20;
35181
+ const trackW = W - trackX - VALUE_W - 20;
35182
+ const xOf = (v) => trackX + (v - lo) / span * trackW;
35183
+ ctx.fillStyle = "#ffffff";
35184
+ ctx.fillRect(0, 0, W, H);
35185
+ ctx.fillStyle = "#6b7280";
35186
+ ctx.font = "20px Helvetica, Arial, sans-serif";
35187
+ ctx.textBaseline = "middle";
35188
+ const scale = `Escala ${fmt2(lo)}\u2013${fmt2(hi)} \xB0C`;
35189
+ ctx.fillText(ideal ? `${scale} \xB7 faixa ideal ${fmt2(ideal.min)}\u2013${fmt2(ideal.max)} \xB0C` : scale, 16, 26);
35190
+ opts.items.forEach((item, i) => {
35191
+ const y = TOP + i * ROW;
35192
+ const cy = y + ROW / 2;
35193
+ ctx.fillStyle = "#1f2937";
35194
+ ctx.font = "20px Helvetica, Arial, sans-serif";
35195
+ ctx.textAlign = "left";
35196
+ let label = item.label;
35197
+ while (ctx.measureText(label).width > LABEL_W - 10 && label.length > 4) label = label.slice(0, -2);
35198
+ if (label !== item.label) label = label.slice(0, -1) + "\u2026";
35199
+ ctx.fillText(label, 16, cy);
35200
+ if (item.value === null || !Number.isFinite(item.value)) {
35201
+ ctx.fillStyle = "#9ca3af";
35202
+ ctx.font = "italic 18px Helvetica, Arial, sans-serif";
35203
+ ctx.fillText("Sem leitura", trackX, cy);
35204
+ ctx.textAlign = "right";
35205
+ ctx.fillText("\u2014", W - 16, cy);
35206
+ return;
35207
+ }
35208
+ ctx.fillStyle = "#e5e7eb";
35209
+ ctx.fillRect(trackX, cy - 9, trackW, 18);
35210
+ if (ideal) {
35211
+ ctx.fillStyle = "rgba(34,197,94,0.18)";
35212
+ ctx.fillRect(xOf(ideal.min), cy - 9, xOf(ideal.max) - xOf(ideal.min), 18);
35213
+ }
35214
+ const v = item.value;
35215
+ ctx.fillStyle = ideal && v > ideal.max ? "#ef4444" : ideal && v < ideal.min ? "#3b82f6" : opts.accent || "#3e1a7d";
35216
+ ctx.fillRect(trackX, cy - 6, Math.max(2, xOf(v) - trackX), 12);
35217
+ ctx.fillStyle = "#111827";
35218
+ ctx.font = "bold 20px Helvetica, Arial, sans-serif";
35219
+ ctx.textAlign = "right";
35220
+ ctx.fillText(fmt2(v), W - 16, cy);
35221
+ });
35222
+ try {
35223
+ return { dataUrl: canvas.toDataURL("image/png"), width: W, height: H };
35224
+ } catch {
35225
+ return null;
35226
+ }
35227
+ }
35228
+
35093
35229
  // src/components/premium-modals/report-device/DeviceReportModal.ts
35094
35230
  var DOMAIN_CONFIG = {
35095
35231
  energy: {
@@ -35883,10 +36019,22 @@ var DeviceReportModal = class {
35883
36019
  const summaryValue = this.domainConfig.summaryType === "average" ? valid > 0 ? total / valid : 0 : total;
35884
36020
  const now = /* @__PURE__ */ new Date();
35885
36021
  const timestamp = now.toLocaleDateString("pt-BR") + " - " + now.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
36022
+ const p = this.exportPeriod;
36023
+ const fmtD = (iso) => iso ? new Date(iso).toLocaleDateString("pt-BR") : "";
36024
+ const off = Number(this.params.temperatureOffset) || 0;
36025
+ const isTemp = this.domainConfig.summaryType === "average";
36026
+ void summaryValue;
35886
36027
  const csvData = [
35887
36028
  ["Dispositivo/Loja", this.params.identifier || "N/A", this.params.label || ""],
36029
+ ...this.params.deviceName ? [["Dispositivo (TB)", this.params.deviceName, ""]] : [],
36030
+ ...isTemp && off !== 0 ? [["Offset aplicado (\xB0C)", this.domainConfig.formatter(off), ""]] : [],
36031
+ ...this.resolveCustomerName() ? [["Cliente", this.resolveCustomerName(), ""]] : [],
36032
+ ...p?.startISO ? [["Per\xEDodo", `${fmtD(p.startISO)} \u2014 ${fmtD(p.endISO)}`, ""]] : [],
36033
+ ["Granularidade", this.granularity === "1h" ? "Hora" : "Dia", ""],
35888
36034
  ["DATA EMISS\xC3O", timestamp, ""],
35889
- [this.domainConfig.summaryLabel, this.domainConfig.formatter(summaryValue), this.domainConfig.unit],
36035
+ [],
36036
+ ...this.computeKpis().map((k) => [k.label, k.value, k.sub || ""]),
36037
+ [],
35890
36038
  [this.granularity === "1h" ? "Data/Hora" : "Data", this.domainConfig.label, ""],
35891
36039
  ...this.data.map((row) => [this.formatDate(row.date), row.noData ? "Sem leitura" : this.domainConfig.formatter(row.consumption)])
35892
36040
  ];
@@ -35907,7 +36055,11 @@ var DeviceReportModal = class {
35907
36055
  }
35908
36056
  // Título dos exports — mesmo da modal (identificador + etiqueta do device).
35909
36057
  resolveExportTitle() {
35910
- return `Relat\xF3rio - ${this.params.identifier || "SEM IDENTIFICADOR"} - ${this.params.label || "SEM ETIQUETA"}`;
36058
+ const base = `Relat\xF3rio - ${this.params.identifier || "SEM IDENTIFICADOR"} - ${this.params.label || "SEM ETIQUETA"}`;
36059
+ const dev = this.params.deviceName ? ` (${this.params.deviceName})` : "";
36060
+ const off = Number(this.params.temperatureOffset) || 0;
36061
+ const offTxt = this.domainConfig.summaryType === "average" && off !== 0 ? ` \xB7 offset ${off > 0 ? "+" : "\u2212"}${Math.abs(off).toLocaleString("pt-BR", { maximumFractionDigits: 2 })} \xB0C` : "";
36062
+ return `${base}${dev}${offTxt}`;
35911
36063
  }
35912
36064
  // Mapeia as linhas do relatório (dia/hora × consumo) para o shape TelemetryDevice
35913
36065
  // dos exporters compartilhados do grid: labelOrName = data formatada, val = consumo,
@@ -35926,16 +36078,41 @@ var DeviceReportModal = class {
35926
36078
  // Opções de coluna dos exporters: relatório single-device → Data | Consumo | %
35927
36079
  // (Identificador redundante — o device já está no título/filename).
35928
36080
  exportColumnOptions() {
36081
+ const isTemp = this.domainConfig.summaryType === "average";
35929
36082
  return {
35930
36083
  nameLabel: this.granularity === "1h" ? "Data/Hora" : "Data",
35931
- hideIdentifier: true
36084
+ hideIdentifier: true,
36085
+ // Mesmo rótulo da coluna da tela (antes saía "Consumo (°C)" em temperatura)
36086
+ valueLabel: this.domainConfig.label,
36087
+ countLabel: this.granularity === "1h" ? "hora(s)" : "dia(s)",
36088
+ ...isTemp ? { hidePerc: true, emptyValueText: "Sem leitura" } : {}
35932
36089
  };
35933
36090
  }
36091
+ // Gráfico da página final do PDF = painel da direita da tela
36092
+ async buildExportChart() {
36093
+ if (this.domainConfig.summaryType === "average") {
36094
+ const isHourly = this.granularity === "1h";
36095
+ const ranked = [...this.validRows].sort((a, b) => b.consumption - a.consumption);
36096
+ const shown = isHourly ? ranked.slice(0, 24) : ranked;
36097
+ const noData = isHourly ? [] : this.data.filter((r) => r.noData);
36098
+ const png = renderTemperatureBarsPng({
36099
+ items: [
36100
+ ...shown.map((r) => ({ label: this.formatDate(r.date), value: r.consumption })),
36101
+ ...noData.map((r) => ({ label: this.formatDate(r.date), value: null }))
36102
+ ],
36103
+ ideal: this.params.temperatureIdealRange ?? null,
36104
+ accent: this.resolveAccentHex()
36105
+ });
36106
+ const title = isHourly ? `Horas mais quentes${ranked.length > 24 ? " (top 24)" : ""}` : "Dias mais quentes (m\xE9dia do dia)";
36107
+ return png ? { ...png, title } : null;
36108
+ }
36109
+ const chartPng = await this.participationChart?.toPngDataUrl?.().catch(() => null);
36110
+ return chartPng ? { ...chartPng, title: "Participa\xE7\xE3o por Dia" } : null;
36111
+ }
35934
36112
  // PDF export — layout premium do grid + paleta do dashboard + faixa de KPIs +
35935
36113
  // página dedicada com o gráfico "Participação por Dia" da modal.
35936
36114
  async exportPDF() {
35937
36115
  if (!this.data.length) return;
35938
- const chartPng = await this.participationChart?.toPngDataUrl?.().catch(() => null);
35939
36116
  exportGridPdf(
35940
36117
  this.buildExportDevices(),
35941
36118
  this.resolveExportTitle(),
@@ -35945,7 +36122,7 @@ var DeviceReportModal = class {
35945
36122
  {
35946
36123
  accentColor: this.resolveAccentHex(),
35947
36124
  kpis: this.computeKpis(),
35948
- chartImage: chartPng ? { ...chartPng, title: "Participa\xE7\xE3o por Dia" } : null,
36125
+ chartImage: await this.buildExportChart(),
35949
36126
  columns: this.exportColumnOptions()
35950
36127
  }
35951
36128
  );
@@ -35961,7 +36138,8 @@ var DeviceReportModal = class {
35961
36138
  this.resolveCustomerName() || null,
35962
36139
  {
35963
36140
  accentColor: this.resolveAccentHex(),
35964
- columns: this.exportColumnOptions()
36141
+ columns: this.exportColumnOptions(),
36142
+ kpis: this.computeKpis()
35965
36143
  }
35966
36144
  );
35967
36145
  }
@@ -38952,21 +39130,36 @@ var AllReportModal = class {
38952
39130
  }
38953
39131
  // Temperatura: valores já com offset e sem leituras inválidas (mesma base da tabela).
38954
39132
  // 1d → uma linha por sensor por DIA; 1h → uma linha por sensor por HORA.
38955
- exportTemperatureCSV(rows) {
39133
+ exportTemperatureCSV(_rows) {
39134
+ const rows = this.getFilteredData();
38956
39135
  const showId = this.showIdentifierColumn(rows);
38957
39136
  const idCol = (r) => showId ? [r.identifier] : [];
38958
39137
  const idHead = showId ? ["Identificador"] : [];
38959
39138
  const n2 = (v) => v === null || v === void 0 || !Number.isFinite(v) ? "" : v.toFixed(2);
38960
39139
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
39140
+ const gran = this.granularity;
38961
39141
  const fmtStamp = (ts) => {
38962
39142
  const { date: d, time } = this.formatSpDateTime(ts);
38963
39143
  return `${d} ${time}`;
38964
39144
  };
39145
+ const p = this.exportPeriod;
39146
+ const fmtD = (iso) => iso ? new Date(iso).toLocaleDateString("pt-BR") : "";
39147
+ const meta = [
39148
+ ["Relat\xF3rio", this.resolveTitle()],
39149
+ ...this.params.customerName ? [["Cliente", this.params.customerName]] : [],
39150
+ ...p?.startISO ? [["Per\xEDodo", `${fmtD(p.startISO)} \u2014 ${fmtD(p.endISO)}`]] : [],
39151
+ ["Granularidade", gran === "1h" ? "Hora" : "Dia"],
39152
+ ["Gerado em", (/* @__PURE__ */ new Date()).toLocaleString("pt-BR")],
39153
+ [],
39154
+ ...this.computeKpis().map((k) => [k.label, k.value, k.sub || ""]),
39155
+ []
39156
+ ];
38965
39157
  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"],
39158
+ [...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
39159
  ...rows.map((r) => [
38968
39160
  ...idCol(r),
38969
39161
  r.name,
39162
+ r.id ? n2(this.temperatureOffsetFor(r.id)) : "",
38970
39163
  r.noData ? "Sem leitura" : n2(r.consumption),
38971
39164
  r.noData ? "" : n2(r.min),
38972
39165
  r.noData || !r.minTs ? "" : fmtStamp(r.minTs),
@@ -38976,7 +39169,6 @@ var AllReportModal = class {
38976
39169
  String(r.discarded ?? "")
38977
39170
  ])
38978
39171
  ];
38979
- const gran = this.granularity;
38980
39172
  const series = [[...idHead, "Sensor", gran === "1h" ? "Data/Hora" : "Dia", "Temperatura (\xB0C)"]];
38981
39173
  const fmtTs = (ts) => gran === "1h" ? new Date(ts).toLocaleString("pt-BR", {
38982
39174
  day: "2-digit",
@@ -38989,9 +39181,9 @@ var AllReportModal = class {
38989
39181
  for (const r of rows) {
38990
39182
  const raw = r.id && this.temperatureStats.get(r.id)?.series || [];
38991
39183
  const pts = gran === "1h" ? this.aggregateHourly(raw, "avg") : this.aggregateDaily(raw, "avg");
38992
- for (const p of pts) series.push([...idCol(r), r.name, fmtTs(p.timestamp), p.value.toFixed(2)]);
39184
+ for (const p2 of pts) series.push([...idCol(r), r.name, fmtTs(p2.timestamp), p2.value.toFixed(2)]);
38993
39185
  }
38994
- const csvContent = toCsv([...summary, [], ...series]);
39186
+ const csvContent = toCsv([...meta, ...summary, [], ...series]);
38995
39187
  this.downloadCSV(csvContent, `relatorio-temperatura-${gran}-${date}.csv`);
38996
39188
  }
38997
39189
  // 1h CSV: one row per device × hour. The series comes from per-device requests
@@ -39080,28 +39272,73 @@ var AllReportModal = class {
39080
39272
  }
39081
39273
  // Maps the report rows to the TelemetryDevice shape consumed by the shared
39082
39274
  // TELEMETRY grid exporters (only labelOrName/name/deviceIdentifier/val/perc are read).
39275
+ // Linhas dos exports = a TABELA DA TELA: mesma ordem/filtros (getFilteredData), cabeçalhos
39276
+ // de grupo com o mesmo resumo, e — em temperatura — Mín/Máx, offset e "Sem leitura".
39083
39277
  buildExportDevices() {
39084
- const sorted = [...this.data].sort(
39085
- (a, b) => !!a.noData !== !!b.noData ? a.noData ? 1 : -1 : b.consumption - a.consumption
39086
- );
39087
- const total = sorted.reduce((s, r) => s + (r.consumption || 0), 0);
39088
- return sorted.map((r) => ({
39089
- labelOrName: r.name,
39090
- name: r.name,
39091
- deviceIdentifier: r.identifier,
39092
- // Temperatura: média do sensor (sem leitura → "—"); sem % de participação
39093
- val: this.isTemperature && r.noData ? null : r.consumption,
39094
- perc: this.isTemperature ? void 0 : total > 0 ? r.consumption / total * 100 : 0
39095
- }));
39278
+ const rows = this.getFilteredData();
39279
+ const grandTotal = this.calculateTotalConsumption();
39280
+ const toDevice = (r) => {
39281
+ const base = {
39282
+ labelOrName: r.name,
39283
+ name: r.name,
39284
+ deviceIdentifier: r.identifier,
39285
+ val: this.isTemperature && r.noData ? null : r.consumption,
39286
+ perc: this.isTemperature ? void 0 : grandTotal > 0 ? r.consumption / grandTotal * 100 : 0
39287
+ };
39288
+ if (!this.isTemperature) return base;
39289
+ const off = r.id && !r.noData ? this.temperatureOffsetFor(r.id) : 0;
39290
+ return {
39291
+ ...base,
39292
+ extraCells: [this.fmtTemp(r.noData ? null : r.min), this.fmtTemp(r.noData ? null : r.max)],
39293
+ ...off ? { nameNote: `offset ${off > 0 ? "+" : "\u2212"}${fmtPt(Math.abs(off))} \xB0C` } : {}
39294
+ };
39295
+ };
39296
+ const out = [];
39297
+ if (rows.some((r) => r.groupLabel)) {
39298
+ const order = [];
39299
+ const byGroup = /* @__PURE__ */ new Map();
39300
+ for (const r of rows) {
39301
+ const g = r.groupLabel || "\u2014";
39302
+ if (!byGroup.has(g)) {
39303
+ byGroup.set(g, []);
39304
+ order.push(g);
39305
+ }
39306
+ byGroup.get(g).push(r);
39307
+ }
39308
+ for (const g of order) {
39309
+ const items = byGroup.get(g);
39310
+ out.push({ groupHeader: `${g.toUpperCase()} \xB7 ${this.groupSummaryText(items)}` });
39311
+ items.forEach((r) => out.push(toDevice(r)));
39312
+ }
39313
+ } else {
39314
+ rows.forEach((r) => out.push(toDevice(r)));
39315
+ }
39316
+ return out;
39317
+ }
39318
+ // Mesmo texto do cabeçalho de grupo da tabela
39319
+ groupSummaryText(items) {
39320
+ if (this.isTemperature) {
39321
+ const withData = items.filter((r) => !r.noData);
39322
+ const avg = withData.length ? withData.reduce((s, r) => s + r.consumption, 0) / withData.length : null;
39323
+ return `${items.length} sensores \xB7 m\xE9dia ${this.fmtTemp(avg)} \xB0C`;
39324
+ }
39325
+ const total = items.reduce((s, r) => s + r.consumption, 0);
39326
+ return `${items.length} dispositivos \xB7 ${fmtPt(total)} ${this.domainConfig.unit}`;
39096
39327
  }
39097
- // Colunas do PDF/XLS: temperatura = Sensor | Temperatura média (sem %, Identificador só se útil)
39328
+ // Colunas do PDF/XLS = colunas da tela. Temperatura: Sensor | Média | Mín | Máx (sem %).
39098
39329
  exportColumns() {
39099
39330
  if (!this.isTemperature) return null;
39100
39331
  return {
39101
39332
  nameLabel: "Sensor",
39102
- valueLabel: "Temperatura m\xE9dia (\xB0C)",
39333
+ valueLabel: "M\xE9dia (\xB0C)",
39103
39334
  hidePerc: true,
39104
- hideIdentifier: !this.showIdentifierColumn()
39335
+ hideIdentifier: !this.showIdentifierColumn(),
39336
+ extraColumns: [
39337
+ { label: "M\xEDn (\xB0C)", pdfW: 35 },
39338
+ { label: "M\xE1x (\xB0C)", pdfW: 35 }
39339
+ ],
39340
+ emptyValueText: "Sem leitura",
39341
+ countLabel: "sensor(es)"
39105
39342
  };
39106
39343
  }
39107
39344
  // Accent hex da paleta do dashboard (tema efetivo) para o PDF/XLS — cai no
@@ -39115,26 +39352,38 @@ var AllReportModal = class {
39115
39352
  const vars = typeof theme.cssVars === "function" ? theme.cssVars() : theme;
39116
39353
  return vars?.["--myio-brand-700"];
39117
39354
  }
39355
+ // Gráfico da página final do PDF = o painel da direita da tela
39356
+ async buildExportChart() {
39357
+ if (this.isTemperature) {
39358
+ const png = renderTemperatureBarsPng({
39359
+ items: this.getFilteredData().map((r) => ({ label: r.name, value: r.noData ? null : r.consumption })),
39360
+ ideal: this.temperatureIdealRange,
39361
+ accent: this.resolveAccentHex()
39362
+ });
39363
+ return png ? { ...png, title: "Temperatura m\xE9dia por sensor" } : null;
39364
+ }
39365
+ const chartPng = await this.participationChart?.toPngDataUrl?.().catch(() => null);
39366
+ return chartPng ? { ...chartPng, title: "Participa\xE7\xE3o por Dispositivo" } : null;
39367
+ }
39118
39368
  // PDF export — layout premium do grid + paleta do dashboard + faixa de KPIs +
39119
- // página dedicada com o gráfico de participação da modal.
39369
+ // página dedicada com o mesmo gráfico do painel da modal.
39120
39370
  async exportPDF() {
39121
39371
  if (!this.data.length) return;
39122
- const chartPng = await this.participationChart?.toPngDataUrl?.().catch(() => null);
39123
39372
  exportGridPdf(
39124
39373
  this.buildExportDevices(),
39125
39374
  this.resolveTitle(),
39126
39375
  this.domainConfig.unit,
39127
39376
  this.exportPeriod,
39128
- null,
39377
+ this.params.customerName || null,
39129
39378
  {
39130
39379
  accentColor: this.resolveAccentHex(),
39131
39380
  kpis: this.computeKpis(),
39132
39381
  columns: this.exportColumns(),
39133
- chartImage: chartPng ? { ...chartPng, title: "Participa\xE7\xE3o por Dispositivo" } : null
39382
+ chartImage: await this.buildExportChart()
39134
39383
  }
39135
39384
  );
39136
39385
  }
39137
- // XLS export (XML Spreadsheet) — same as the TELEMETRY grid export.
39386
+ // XLS export (XML Spreadsheet) — mesma tabela + KPIs da tela.
39138
39387
  exportXLS() {
39139
39388
  if (!this.data.length) return;
39140
39389
  exportGridXls(
@@ -39142,8 +39391,8 @@ var AllReportModal = class {
39142
39391
  this.resolveTitle(),
39143
39392
  this.domainConfig.unit,
39144
39393
  this.exportPeriod,
39145
- null,
39146
- { accentColor: this.resolveAccentHex(), columns: this.exportColumns() }
39394
+ this.params.customerName || null,
39395
+ { accentColor: this.resolveAccentHex(), columns: this.exportColumns(), kpis: this.computeKpis() }
39147
39396
  );
39148
39397
  }
39149
39398
  async fetchCustomerTotals(startISO, endISO) {
@@ -79616,7 +79865,10 @@ async function openTemperatureModal(params) {
79616
79865
  document.body.appendChild(modalContainer);
79617
79866
  renderModal3(modalContainer, state6, modalId);
79618
79867
  try {
79619
- state6.data = await (state6.dataFetcher ? state6.dataFetcher(state6.startTs, state6.endTs) : fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs));
79868
+ state6.data = discardInvalid(
79869
+ await (state6.dataFetcher ? state6.dataFetcher(state6.startTs, state6.endTs) : fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs)),
79870
+ state6
79871
+ );
79620
79872
  state6.stats = calculateStats(state6.data, state6.clampRange, state6.temperatureOffset);
79621
79873
  state6.isLoading = false;
79622
79874
  renderModal3(modalContainer, state6, modalId);
@@ -79641,7 +79893,10 @@ async function openTemperatureModal(params) {
79641
79893
  state6.isLoading = true;
79642
79894
  renderModal3(modalContainer, state6, modalId);
79643
79895
  try {
79644
- state6.data = await (state6.dataFetcher ? state6.dataFetcher(state6.startTs, state6.endTs) : fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs));
79896
+ state6.data = discardInvalid(
79897
+ await (state6.dataFetcher ? state6.dataFetcher(state6.startTs, state6.endTs) : fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs)),
79898
+ state6
79899
+ );
79645
79900
  state6.stats = calculateStats(state6.data, state6.clampRange, state6.temperatureOffset);
79646
79901
  state6.isLoading = false;
79647
79902
  renderModal3(modalContainer, state6, modalId);
@@ -80269,7 +80524,10 @@ async function setupEventListeners3(container, state6, modalId, onClose) {
80269
80524
  state6.dateRangePicker = null;
80270
80525
  renderModal3(container, state6, modalId);
80271
80526
  try {
80272
- state6.data = await (state6.dataFetcher ? state6.dataFetcher(state6.startTs, state6.endTs) : fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs));
80527
+ state6.data = discardInvalid(
80528
+ await (state6.dataFetcher ? state6.dataFetcher(state6.startTs, state6.endTs) : fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs)),
80529
+ state6
80530
+ );
80273
80531
  state6.stats = calculateStats(state6.data, state6.clampRange, state6.temperatureOffset);
80274
80532
  state6.isLoading = false;
80275
80533
  renderModal3(container, state6, modalId);
@@ -80283,19 +80541,64 @@ async function setupEventListeners3(container, state6, modalId, onClose) {
80283
80541
  }
80284
80542
  });
80285
80543
  }
80544
+ function discardInvalid(data, state6) {
80545
+ const off = Number(state6.temperatureOffset) || 0;
80546
+ return data.filter((p) => {
80547
+ const v = Number(p.value) + off;
80548
+ return Number.isFinite(v) && v >= state6.clampRange.min && v <= state6.clampRange.max;
80549
+ });
80550
+ }
80286
80551
  function exportCsv(state6) {
80287
80552
  if (state6.data.length === 0) return;
80288
- const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
80289
- const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
80290
- exportTemperatureCSV(
80291
- state6.data,
80292
- state6.label,
80293
- state6.stats,
80294
- startDateStr,
80295
- endDateStr,
80296
- state6.clampRange,
80297
- state6.temperatureOffset
80298
- );
80553
+ const off = Number(state6.temperatureOffset) || 0;
80554
+ const n2 = (v) => v.toFixed(2).replace(".", ",");
80555
+ const q = (v) => `"${String(v).replace(/"/g, '""')}"`;
80556
+ const dt = (ts) => new Date(ts).toLocaleString("pt-BR", {
80557
+ day: "2-digit",
80558
+ month: "2-digit",
80559
+ year: "numeric",
80560
+ hour: "2-digit",
80561
+ minute: "2-digit",
80562
+ timeZone: state6.timezone || "America/Sao_Paulo"
80563
+ });
80564
+ const fmtD = (ts) => new Date(ts).toLocaleDateString("pt-BR", { timeZone: state6.timezone || "America/Sao_Paulo" });
80565
+ const filtered = filterByDayPeriods(state6.data, state6.selectedPeriods);
80566
+ const lines = [
80567
+ ["Relat\xF3rio", `${state6.label} - Hist\xF3rico de Temperatura`],
80568
+ ...state6.deviceName ? [["Dispositivo (TB)", state6.deviceName]] : [],
80569
+ ...state6.customerName ? [["Cliente", state6.customerName]] : [],
80570
+ ["Per\xEDodo", `${fmtD(state6.startTs)} \u2014 ${fmtD(state6.endTs)}`],
80571
+ ["Granularidade", state6.granularity === "hour" ? "Hora (m\xE9dia da hora fechada)" : "Dia (m\xE9dia do dia)"],
80572
+ ["Per\xEDodos do dia", getSelectedPeriodsLabel(state6.selectedPeriods)],
80573
+ ...off !== 0 ? [["Offset aplicado (\xB0C)", n2(off)]] : [],
80574
+ ["Gerado em", (/* @__PURE__ */ new Date()).toLocaleString("pt-BR")],
80575
+ [],
80576
+ ["Temperatura atual (\xB0C)", state6.currentTemperature !== null ? n2(state6.currentTemperature) : "N/A"],
80577
+ ["M\xE9dia do per\xEDodo (\xB0C)", state6.stats.count > 0 ? n2(state6.stats.avg) : "N/A"],
80578
+ ["M\xEDnima (\xB0C)", state6.stats.count > 0 ? n2(state6.stats.min) : "N/A"],
80579
+ ["M\xE1xima (\xB0C)", state6.stats.count > 0 ? n2(state6.stats.max) : "N/A"],
80580
+ ["Leituras", String(state6.stats.count)],
80581
+ []
80582
+ ];
80583
+ if (state6.granularity === "hour") {
80584
+ lines.push(["Data/Hora", "Temperatura (\xB0C)"]);
80585
+ aggregateByHour(filtered, state6.clampRange, off).forEach((p) => lines.push([dt(p.ts), n2(Number(p.value))]));
80586
+ } else {
80587
+ lines.push(["Dia", "M\xE9dia (\xB0C)", "M\xEDn (\xB0C)", "M\xE1x (\xB0C)", "Leituras"]);
80588
+ aggregateByDay(filtered, state6.clampRange, off).forEach(
80589
+ (d) => lines.push([fmtD(d.dateTs), n2(d.avg), n2(d.min), n2(d.max), String(d.count)])
80590
+ );
80591
+ }
80592
+ const csv = "\uFEFF" + lines.map((r) => r.map(q).join(";")).join("\r\n");
80593
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
80594
+ const url = URL.createObjectURL(blob);
80595
+ const a = document.createElement("a");
80596
+ a.href = url;
80597
+ a.download = `temperatura_${state6.label.replace(/\s+/g, "_")}_${state6.granularity === "hour" ? "1h" : "1d"}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
80598
+ document.body.appendChild(a);
80599
+ a.click();
80600
+ document.body.removeChild(a);
80601
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
80299
80602
  }
80300
80603
  function buildHeaderTitle(state6) {
80301
80604
  const base = `${escHtml3(state6.label)} - Hist\xF3rico de Temperatura`;
@@ -81202,43 +81505,63 @@ function mountCmpFooter(container, state6, modalId) {
81202
81505
  _cmpFooters.set(modalId, footer);
81203
81506
  }
81204
81507
  function exportComparisonCSV2(state6) {
81205
- const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
81206
- const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
81207
- const BOM = "\uFEFF";
81208
- let csvContent = BOM;
81209
- csvContent += `Compara\xE7\xE3o de Temperatura
81210
- `;
81211
- csvContent += `Per\xEDodo: ${startDateStr} at\xE9 ${endDateStr}
81212
- `;
81213
- csvContent += `Sensores: ${state6.devices.map((d) => d.label).join(", ")}
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
- `;
81508
+ const n2 = (v) => v.toFixed(2).replace(".", ",");
81509
+ const q = (v) => `"${String(v).replace(/"/g, '""')}"`;
81510
+ const fmtD = (ts) => new Date(ts).toLocaleDateString("pt-BR", { timeZone: "America/Sao_Paulo" });
81511
+ const dt = (ts) => new Date(ts).toLocaleString("pt-BR", {
81512
+ day: "2-digit",
81513
+ month: "2-digit",
81514
+ year: "numeric",
81515
+ hour: "2-digit",
81516
+ minute: "2-digit",
81517
+ timeZone: "America/Sao_Paulo"
81221
81518
  });
81222
- csvContent += "\n";
81223
- csvContent += "Dados Detalhados:\n";
81224
- csvContent += "Data/Hora,Sensor,Temperatura (\xB0C)\n";
81225
- state6.deviceData.forEach((dd) => {
81226
- dd.data.forEach((item) => {
81227
- const date = new Date(item.ts).toLocaleString(state6.locale);
81228
- const temp = Number(item.value).toFixed(2);
81229
- csvContent += `"${date}","${dd.device.label}",${temp}
81230
- `;
81231
- });
81519
+ const offTxt = (d) => {
81520
+ const o = Number(d.temperatureOffset) || 0;
81521
+ return o !== 0 ? n2(o) : "";
81522
+ };
81523
+ const lines = [
81524
+ ["Relat\xF3rio", `Compara\xE7\xE3o de Temperatura - ${state6.devices.length} sensores`],
81525
+ ...state6.customerName ? [["Cliente", state6.customerName]] : [],
81526
+ ["Per\xEDodo", `${fmtD(state6.startTs)} \u2014 ${fmtD(state6.endTs)}`],
81527
+ ["Granularidade", state6.granularity === "hour" ? "Hora (m\xE9dia da hora fechada)" : "Dia (m\xE9dia do dia)"],
81528
+ ["Per\xEDodos do dia", getSelectedPeriodsLabel(state6.selectedPeriods)],
81529
+ ["Gerado em", (/* @__PURE__ */ new Date()).toLocaleString("pt-BR")],
81530
+ [],
81531
+ ["Sensor", "Offset (\xB0C)", "M\xE9dia (\xB0C)", "M\xEDn (\xB0C)", "M\xE1x (\xB0C)", "Leituras"],
81532
+ ...state6.deviceData.map((dd) => [
81533
+ dd.device.label,
81534
+ offTxt(dd.device),
81535
+ dd.stats.count > 0 ? n2(dd.stats.avg) : "N/A",
81536
+ dd.stats.count > 0 ? n2(dd.stats.min) : "N/A",
81537
+ dd.stats.count > 0 ? n2(dd.stats.max) : "N/A",
81538
+ String(dd.stats.count)
81539
+ ]),
81540
+ []
81541
+ ];
81542
+ const perDevice = state6.deviceData.map((dd) => {
81543
+ const filtered = filterByDayPeriods(dd.data, state6.selectedPeriods);
81544
+ 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]);
81545
+ return new Map(pts);
81232
81546
  });
81233
- const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
81547
+ const allTs = [...new Set(perDevice.flatMap((m) => [...m.keys()]))].sort((a, b) => a - b);
81548
+ lines.push([state6.granularity === "hour" ? "Data/Hora" : "Dia", ...state6.deviceData.map((dd) => `${dd.device.label} (\xB0C)`)]);
81549
+ allTs.forEach(
81550
+ (ts) => lines.push([
81551
+ state6.granularity === "hour" ? dt(ts) : fmtD(ts),
81552
+ ...perDevice.map((m) => m.has(ts) ? n2(m.get(ts)) : "")
81553
+ ])
81554
+ );
81555
+ const csv = "\uFEFF" + lines.map((r) => r.map(q).join(";")).join("\r\n");
81556
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
81234
81557
  const url = URL.createObjectURL(blob);
81235
81558
  const link = document.createElement("a");
81236
81559
  link.href = url;
81237
- link.download = `comparacao_temperatura_${startDateStr}_${endDateStr}.csv`;
81560
+ link.download = `comparacao_temperatura_${state6.granularity === "hour" ? "1h" : "1d"}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
81238
81561
  document.body.appendChild(link);
81239
81562
  link.click();
81240
81563
  document.body.removeChild(link);
81241
- URL.revokeObjectURL(url);
81564
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
81242
81565
  }
81243
81566
 
81244
81567
  // src/components/temperature/TemperatureSettingsModal.ts