myio-js-library 0.1.553 → 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 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.553",
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 (!isTemperatureDevice2(deviceType) || temperatureSource !== "ingestion-avg-2h") return "";
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";
@@ -33594,7 +33641,11 @@ function buildRow(d, idx, colOpts) {
33594
33641
  const x = extras(d);
33595
33642
  const fmtVal = () => {
33596
33643
  if (d.val === null || d.val === void 0) return colOpts?.emptyValueText || "\u2014";
33597
- return Number(d.val).toLocaleString("pt-BR", { maximumFractionDigits: 3, useGrouping: false });
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
+ );
33598
33649
  };
33599
33650
  const pd = resolvePercentDecimals();
33600
33651
  const name = d.labelOrName || d.name || "\u2014";
@@ -33656,6 +33707,9 @@ function escXml(s) {
33656
33707
  function truncate(s, max) {
33657
33708
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
33658
33709
  }
33710
+ function pdfSafe(s) {
33711
+ return String(s).replace(/−/g, "-").replace(/[‐‑]/g, "-").replace(/ /g, " ");
33712
+ }
33659
33713
  function exportGridCsv(devices, label, unit, period, customerName) {
33660
33714
  const periodLabel2 = fmtPeriod(period);
33661
33715
  const metaRows = [
@@ -33782,14 +33836,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33782
33836
  doc.setFont("helvetica", "bold");
33783
33837
  doc.setFontSize(10);
33784
33838
  const titleText = customerName ? `${customerName} \u2014 ${label}` : label;
33785
- doc.text(titleText, MARGIN, HDR_H / 2 + 1.5);
33839
+ doc.text(pdfSafe(titleText), MARGIN, HDR_H / 2 + 1.5);
33786
33840
  doc.setFont("helvetica", "normal");
33787
33841
  doc.setFontSize(8);
33788
33842
  const periodLabel2 = fmtPeriod(period);
33789
33843
  const periodPart = periodLabel2 ? `Per\xEDodo: ${periodLabel2} \u2022 ` : "";
33790
33844
  const countLabel = options?.columns?.countLabel || "dispositivo(s)";
33791
33845
  const info = `${periodPart}Gerado em: ${generatedAt} \u2022 ${countDataRows(devices)} ${countLabel} \u2022 Unidade: ${unit} \u2022 P\xE1g. ${pageNo2}`;
33792
- doc.text(info, PW - MARGIN, HDR_H / 2 + 1.5, { align: "right" });
33846
+ doc.text(pdfSafe(info), PW - MARGIN, HDR_H / 2 + 1.5, { align: "right" });
33793
33847
  }
33794
33848
  function drawColumnHeaders(y) {
33795
33849
  doc.setFillColor(...ACCENT_SOFT);
@@ -33799,7 +33853,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33799
33853
  doc.setFontSize(7);
33800
33854
  cols.forEach((c, ci) => {
33801
33855
  const x = colX(ci) + 1.5;
33802
- doc.text(c.label, x, y + HEAD_H / 2 + 2.5);
33856
+ doc.text(pdfSafe(c.label), x, y + HEAD_H / 2 + 2.5);
33803
33857
  });
33804
33858
  doc.setDrawColor(200, 195, 220);
33805
33859
  doc.setLineWidth(0.2);
@@ -33816,7 +33870,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33816
33870
  cols.forEach((c, ci) => {
33817
33871
  const x = colX(ci) + 1.5;
33818
33872
  const maxChars = Math.floor(colWidths[ci] / 1.8);
33819
- const text2 = truncate(String(r[c.key]), maxChars);
33873
+ const text2 = truncate(pdfSafe(String(r[c.key])), maxChars);
33820
33874
  doc.text(text2, x, y + ROW_H / 2 + 2.2);
33821
33875
  });
33822
33876
  doc.setDrawColor(230, 228, 240);
@@ -33829,7 +33883,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33829
33883
  doc.setTextColor(...ACCENT2);
33830
33884
  doc.setFont("helvetica", "bold");
33831
33885
  doc.setFontSize(6.5);
33832
- doc.text(truncate(text2, 160), MARGIN + 1.5, y + ROW_H / 2 + 2.2);
33886
+ doc.text(truncate(pdfSafe(text2), 160), MARGIN + 1.5, y + ROW_H / 2 + 2.2);
33833
33887
  }
33834
33888
  function drawFooter2() {
33835
33889
  doc.setFillColor(250, 249, 255);
@@ -33857,14 +33911,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33857
33911
  doc.setTextColor(...ACCENT2);
33858
33912
  doc.setFont("helvetica", "bold");
33859
33913
  doc.setFontSize(11);
33860
- 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" });
33861
33915
  doc.setTextColor(110, 110, 120);
33862
33916
  doc.setFont("helvetica", "normal");
33863
33917
  doc.setFontSize(6.5);
33864
- 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" });
33865
33919
  if (kpi.sub) {
33866
33920
  doc.setFontSize(5.5);
33867
- 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" });
33868
33922
  }
33869
33923
  });
33870
33924
  }
@@ -33911,7 +33965,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33911
33965
  doc.setTextColor(...ACCENT2);
33912
33966
  doc.setFont("helvetica", "bold");
33913
33967
  doc.setFontSize(12);
33914
- doc.text(chart.title || "Participa\xE7\xE3o por Dispositivo", MARGIN, titleY);
33968
+ doc.text(pdfSafe(chart.title || "Participa\xE7\xE3o por Dispositivo"), MARGIN, titleY);
33915
33969
  const imgTop = titleY + 4;
33916
33970
  const availW = TABLE_W;
33917
33971
  const availH = MAX_Y - imgTop;
@@ -36085,7 +36139,7 @@ var DeviceReportModal = class {
36085
36139
  // Mesmo rótulo da coluna da tela (antes saía "Consumo (°C)" em temperatura)
36086
36140
  valueLabel: this.domainConfig.label,
36087
36141
  countLabel: this.granularity === "1h" ? "hora(s)" : "dia(s)",
36088
- ...isTemp ? { hidePerc: true, emptyValueText: "Sem leitura" } : {}
36142
+ ...isTemp ? { hidePerc: true, emptyValueText: "Sem leitura", valueDecimals: 2 } : {}
36089
36143
  };
36090
36144
  }
36091
36145
  // Gráfico da página final do PDF = painel da direita da tela
@@ -39123,7 +39177,7 @@ var AllReportModal = class {
39123
39177
  // Header row only
39124
39178
  ["Identificador", "Nome", `Consumo (${this.domainConfig.unit})`],
39125
39179
  // Data rows
39126
- ...sortedData.map((row) => [row.identifier, row.name, row.consumption.toFixed(2)])
39180
+ ...sortedData.map((row) => [row.identifier, row.name, row.consumption.toFixed(2).replace(".", ",")])
39127
39181
  ];
39128
39182
  const csvContent = toCsv(csvData);
39129
39183
  this.downloadCSV(csvContent, `relatorio-geral-lojas-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`);
@@ -39135,7 +39189,7 @@ var AllReportModal = class {
39135
39189
  const showId = this.showIdentifierColumn(rows);
39136
39190
  const idCol = (r) => showId ? [r.identifier] : [];
39137
39191
  const idHead = showId ? ["Identificador"] : [];
39138
- 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(".", ",");
39139
39193
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
39140
39194
  const gran = this.granularity;
39141
39195
  const fmtStamp = (ts) => {
@@ -39181,7 +39235,7 @@ var AllReportModal = class {
39181
39235
  for (const r of rows) {
39182
39236
  const raw = r.id && this.temperatureStats.get(r.id)?.series || [];
39183
39237
  const pts = gran === "1h" ? this.aggregateHourly(raw, "avg") : this.aggregateDaily(raw, "avg");
39184
- for (const p2 of pts) series.push([...idCol(r), r.name, fmtTs(p2.timestamp), p2.value.toFixed(2)]);
39238
+ for (const p2 of pts) series.push([...idCol(r), r.name, fmtTs(p2.timestamp), n2(p2.value)]);
39185
39239
  }
39186
39240
  const csvContent = toCsv([...meta, ...summary, [], ...series]);
39187
39241
  this.downloadCSV(csvContent, `relatorio-temperatura-${gran}-${date}.csv`);
@@ -39208,7 +39262,7 @@ var AllReportModal = class {
39208
39262
  for (const row of sortedData) {
39209
39263
  const points = row.id && series.get(row.id) || [];
39210
39264
  for (const p of points) {
39211
- 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(".", ",")]);
39212
39266
  }
39213
39267
  }
39214
39268
  if (csvData.length === 1) {
@@ -39338,7 +39392,9 @@ var AllReportModal = class {
39338
39392
  { label: "M\xE1x (\xB0C)", pdfW: 35 }
39339
39393
  ],
39340
39394
  emptyValueText: "Sem leitura",
39341
- countLabel: "sensor(es)"
39395
+ countLabel: "sensor(es)",
39396
+ valueDecimals: 2
39397
+ // "30,00" como na tela (não "30")
39342
39398
  };
39343
39399
  }
39344
39400
  // Accent hex da paleta do dashboard (tema efetivo) para o PDF/XLS — cai no
package/dist/index.d.cts CHANGED
@@ -19449,6 +19449,8 @@ interface GridColumnsOptions {
19449
19449
  emptyValueText?: string;
19450
19450
  /** Rótulo da contagem no header do PDF (default 'dispositivo(s)'; ex.: 'sensor(es)', 'dia(s)'). */
19451
19451
  countLabel?: string;
19452
+ /** Casas decimais FIXAS do valor (ex.: 2 → "30,00" como na tela). Default: até 3, sem mínimo. */
19453
+ valueDecimals?: number;
19452
19454
  }
19453
19455
  declare function exportGridCsv(devices: TelemetryDevice[], label: string, unit: string, period?: ExportPeriod | null, customerName?: string | null): void;
19454
19456
  declare function exportGridXls(devices: TelemetryDevice[], label: string, unit: string, period?: ExportPeriod | null, customerName?: string | null, options?: {
package/dist/index.d.ts CHANGED
@@ -19449,6 +19449,8 @@ interface GridColumnsOptions {
19449
19449
  emptyValueText?: string;
19450
19450
  /** Rótulo da contagem no header do PDF (default 'dispositivo(s)'; ex.: 'sensor(es)', 'dia(s)'). */
19451
19451
  countLabel?: string;
19452
+ /** Casas decimais FIXAS do valor (ex.: 2 → "30,00" como na tela). Default: até 3, sem mínimo. */
19453
+ valueDecimals?: number;
19452
19454
  }
19453
19455
  declare function exportGridCsv(devices: TelemetryDevice[], label: string, unit: string, period?: ExportPeriod | null, customerName?: string | null): void;
19454
19456
  declare function exportGridXls(devices: TelemetryDevice[], label: string, unit: string, period?: ExportPeriod | null, customerName?: string | null, options?: {
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  // package.json
11
11
  var package_default = {
12
12
  name: "myio-js-library",
13
- version: "0.1.553",
13
+ version: "0.1.554",
14
14
  description: "A clean, standalone JS SDK for MYIO projects",
15
15
  license: "MIT",
16
16
  repository: "github:gh-myio/myio-js-library",
@@ -17053,9 +17053,17 @@ function renderCardComponentV5({
17053
17053
  // Temperatura atual vinda do Ingestion = média das últimas 2 h; true = nenhuma leitura na janela
17054
17054
  temperatureNoRecentReading = false,
17055
17055
  temperatureSource,
17056
- // 'ingestion-avg-2h' | 'thingsboard' | undefined
17056
+ // 'ingestion-hourly' | 'ingestion-avg-2h' | 'thingsboard' | undefined
17057
17057
  temperatureFetchedAt,
17058
17058
  // ms da consulta (rótulo "Atualizado às HH:MM")
17059
+ // Idade da última leitura: 'ok' | 'warning' (10–12 h) | 'stale' (12–24 h) | 'offline' (> 24 h)
17060
+ temperatureFreshness,
17061
+ temperatureHourTs,
17062
+ // início da hora fechada exibida (ms)
17063
+ temperatureLastTs,
17064
+ // instante da última leitura válida (ms)
17065
+ temperatureOffset = 0,
17066
+ // offset aplicado (°C) — marcador igual ao da exclusão de totais
17059
17067
  // Per-device exclude_groups_totals attribute (SERVER_SCOPE) — drives the orange marker
17060
17068
  excludeGroupsTotals
17061
17069
  } = entityObject;
@@ -17216,8 +17224,29 @@ function renderCardComponentV5({
17216
17224
  const tankLevelCm = isTankDevice2 ? Number(waterLevel ?? val) : null;
17217
17225
  const _pctDecimals = resolvePercentDecimals(percentDecimals);
17218
17226
  const showNoTempReading = !!temperatureNoRecentReading && isTemperatureDevice2(deviceType);
17227
+ const _isTempCard = isTemperatureDevice2(deviceType);
17228
+ const showTempWarning = _isTempCard && temperatureFreshness === "warning";
17229
+ const _off = Number(temperatureOffset) || 0;
17219
17230
  const tempValueTitle = (() => {
17220
- if (!isTemperatureDevice2(deviceType) || temperatureSource !== "ingestion-avg-2h") return "";
17231
+ if (_isTempCard && temperatureSource === "ingestion-hourly") {
17232
+ const tz = { timeZone: "America/Sao_Paulo" };
17233
+ const hm = (ts) => new Date(ts).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit", ...tz });
17234
+ const dm = (ts) => new Date(ts).toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", ...tz });
17235
+ const ago = (ts) => {
17236
+ const h3 = Math.floor((Date.now() - ts) / 36e5);
17237
+ const m = Math.floor((Date.now() - ts) % 36e5 / 6e4);
17238
+ return h3 > 0 ? `${h3} h ${m} min` : `${m} min`;
17239
+ };
17240
+ const offTxt = _off ? ` \xB7 offset ${_off > 0 ? "+" : "\u2212"}${String(Math.abs(_off)).replace(".", ",")} \xB0C` : "";
17241
+ if (temperatureFreshness === "offline") return "Nenhuma leitura nas \xFAltimas 24 h";
17242
+ if (!temperatureLastTs) return "";
17243
+ const last = `\xFAltima leitura ${dm(temperatureLastTs)} ${hm(temperatureLastTs)} (h\xE1 ${ago(temperatureLastTs)})`;
17244
+ if (temperatureFreshness === "stale") return `Sem leitura nas \xFAltimas 12 h \u2014 ${last}`;
17245
+ const hour = temperatureHourTs ? `M\xE9dia da hora ${dm(temperatureHourTs)} ${hm(temperatureHourTs)}` : "Temperatura";
17246
+ const warn = temperatureFreshness === "warning" ? "\u26A0 Leitura atrasada (10\u201312 h) \u2014 " : "";
17247
+ return `${warn}${hour} \xB7 ${last}${offTxt}`;
17248
+ }
17249
+ if (!_isTempCard || temperatureSource !== "ingestion-avg-2h") return "";
17221
17250
  const at = temperatureFetchedAt ? ` \xB7 Atualizado \xE0s ${new Date(temperatureFetchedAt).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" })}` : "";
17222
17251
  return showNoTempReading ? `Nenhuma leitura nas \xFAltimas 2 h${at}` : `Temperatura (m\xE9dia das \xFAltimas 2 h)${at}`;
17223
17252
  })();
@@ -17301,6 +17330,22 @@ function renderCardComponentV5({
17301
17330
  height: 100%;
17302
17331
  }
17303
17332
 
17333
+ /* Temperatura: leitura atrasada (10\u201312 h) \u2014 borda laranja + \u26A0 piscando */
17334
+ .myio-enhanced-card-container-v5.myio-card-temp-warning .device-card-centered {
17335
+ border: 2px solid #f59e0b !important;
17336
+ box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.18) !important;
17337
+ }
17338
+ .myio-temp-warn-icon {
17339
+ margin-left: 4px;
17340
+ font-size: 0.9em;
17341
+ cursor: help;
17342
+ animation: myio-temp-warn-blink 1.2s ease-in-out infinite;
17343
+ }
17344
+ @keyframes myio-temp-warn-blink {
17345
+ 0%, 100% { opacity: 1; }
17346
+ 50% { opacity: 0.2; }
17347
+ }
17348
+
17304
17349
  /* Subtle orange marker \u2014 device flagged in exclude_groups_totals */
17305
17350
  .myio-enhanced-card-container-v5.myio-card-excluded::after {
17306
17351
  content: '';
@@ -17590,7 +17635,7 @@ function renderCardComponentV5({
17590
17635
  <span class="flash-icon ${shouldFlashIcon2 ? "flash" : ""}">
17591
17636
  ${icon2}
17592
17637
  </span>
17593
- <span class="consumption-value"${showNoTempReading ? ' style="color:#9ca3af;font-weight:500;font-size:0.85em;"' : ""}${tempValueTitle ? ` title="${tempValueTitle}"` : ""}>${formatCardValue(cardEntity.lastValue, deviceType)}</span>
17638
+ <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>` : ""}
17594
17639
  </div>
17595
17640
  </div>
17596
17641
  ${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>` : ""}
@@ -17610,6 +17655,8 @@ function renderCardComponentV5({
17610
17655
  if (_isExcluded) container.classList.add("myio-card-excluded");
17611
17656
  } catch (e) {
17612
17657
  }
17658
+ if (_isTempCard && _off !== 0) container.classList.add("myio-card-excluded", "myio-card-temp-offset");
17659
+ if (showTempWarning) container.classList.add("myio-card-temp-warning");
17613
17660
  if (!document.getElementById("myio-enhanced-card-layout-styles-v5")) {
17614
17661
  const layoutStyle = document.createElement("style");
17615
17662
  layoutStyle.id = "myio-enhanced-card-layout-styles-v5";
@@ -31980,7 +32027,11 @@ function buildRow(d, idx, colOpts) {
31980
32027
  const x = extras(d);
31981
32028
  const fmtVal = () => {
31982
32029
  if (d.val === null || d.val === void 0) return colOpts?.emptyValueText || "\u2014";
31983
- return Number(d.val).toLocaleString("pt-BR", { maximumFractionDigits: 3, useGrouping: false });
32030
+ const dec = colOpts?.valueDecimals;
32031
+ return Number(d.val).toLocaleString(
32032
+ "pt-BR",
32033
+ dec !== void 0 ? { minimumFractionDigits: dec, maximumFractionDigits: dec, useGrouping: false } : { maximumFractionDigits: 3, useGrouping: false }
32034
+ );
31984
32035
  };
31985
32036
  const pd = resolvePercentDecimals();
31986
32037
  const name = d.labelOrName || d.name || "\u2014";
@@ -32042,6 +32093,9 @@ function escXml(s) {
32042
32093
  function truncate(s, max) {
32043
32094
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
32044
32095
  }
32096
+ function pdfSafe(s) {
32097
+ return String(s).replace(/−/g, "-").replace(/[‐‑]/g, "-").replace(/ /g, " ");
32098
+ }
32045
32099
  function exportGridCsv(devices, label, unit, period, customerName) {
32046
32100
  const periodLabel2 = fmtPeriod(period);
32047
32101
  const metaRows = [
@@ -32168,14 +32222,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32168
32222
  doc.setFont("helvetica", "bold");
32169
32223
  doc.setFontSize(10);
32170
32224
  const titleText = customerName ? `${customerName} \u2014 ${label}` : label;
32171
- doc.text(titleText, MARGIN, HDR_H / 2 + 1.5);
32225
+ doc.text(pdfSafe(titleText), MARGIN, HDR_H / 2 + 1.5);
32172
32226
  doc.setFont("helvetica", "normal");
32173
32227
  doc.setFontSize(8);
32174
32228
  const periodLabel2 = fmtPeriod(period);
32175
32229
  const periodPart = periodLabel2 ? `Per\xEDodo: ${periodLabel2} \u2022 ` : "";
32176
32230
  const countLabel = options?.columns?.countLabel || "dispositivo(s)";
32177
32231
  const info = `${periodPart}Gerado em: ${generatedAt} \u2022 ${countDataRows(devices)} ${countLabel} \u2022 Unidade: ${unit} \u2022 P\xE1g. ${pageNo2}`;
32178
- doc.text(info, PW - MARGIN, HDR_H / 2 + 1.5, { align: "right" });
32232
+ doc.text(pdfSafe(info), PW - MARGIN, HDR_H / 2 + 1.5, { align: "right" });
32179
32233
  }
32180
32234
  function drawColumnHeaders(y) {
32181
32235
  doc.setFillColor(...ACCENT_SOFT);
@@ -32185,7 +32239,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32185
32239
  doc.setFontSize(7);
32186
32240
  cols.forEach((c, ci) => {
32187
32241
  const x = colX(ci) + 1.5;
32188
- doc.text(c.label, x, y + HEAD_H / 2 + 2.5);
32242
+ doc.text(pdfSafe(c.label), x, y + HEAD_H / 2 + 2.5);
32189
32243
  });
32190
32244
  doc.setDrawColor(200, 195, 220);
32191
32245
  doc.setLineWidth(0.2);
@@ -32202,7 +32256,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32202
32256
  cols.forEach((c, ci) => {
32203
32257
  const x = colX(ci) + 1.5;
32204
32258
  const maxChars = Math.floor(colWidths[ci] / 1.8);
32205
- const text2 = truncate(String(r[c.key]), maxChars);
32259
+ const text2 = truncate(pdfSafe(String(r[c.key])), maxChars);
32206
32260
  doc.text(text2, x, y + ROW_H / 2 + 2.2);
32207
32261
  });
32208
32262
  doc.setDrawColor(230, 228, 240);
@@ -32215,7 +32269,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32215
32269
  doc.setTextColor(...ACCENT2);
32216
32270
  doc.setFont("helvetica", "bold");
32217
32271
  doc.setFontSize(6.5);
32218
- doc.text(truncate(text2, 160), MARGIN + 1.5, y + ROW_H / 2 + 2.2);
32272
+ doc.text(truncate(pdfSafe(text2), 160), MARGIN + 1.5, y + ROW_H / 2 + 2.2);
32219
32273
  }
32220
32274
  function drawFooter2() {
32221
32275
  doc.setFillColor(250, 249, 255);
@@ -32243,14 +32297,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32243
32297
  doc.setTextColor(...ACCENT2);
32244
32298
  doc.setFont("helvetica", "bold");
32245
32299
  doc.setFontSize(11);
32246
- doc.text(truncate(kpi.value, 24), cx, y + 6.5, { align: "center" });
32300
+ doc.text(truncate(pdfSafe(kpi.value), 24), cx, y + 6.5, { align: "center" });
32247
32301
  doc.setTextColor(110, 110, 120);
32248
32302
  doc.setFont("helvetica", "normal");
32249
32303
  doc.setFontSize(6.5);
32250
- doc.text(truncate(kpi.label, 34), cx, y + 10.5, { align: "center" });
32304
+ doc.text(truncate(pdfSafe(kpi.label), 34), cx, y + 10.5, { align: "center" });
32251
32305
  if (kpi.sub) {
32252
32306
  doc.setFontSize(5.5);
32253
- doc.text(truncate(kpi.sub, 40), cx, y + 13.5, { align: "center" });
32307
+ doc.text(truncate(pdfSafe(kpi.sub), 40), cx, y + 13.5, { align: "center" });
32254
32308
  }
32255
32309
  });
32256
32310
  }
@@ -32297,7 +32351,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32297
32351
  doc.setTextColor(...ACCENT2);
32298
32352
  doc.setFont("helvetica", "bold");
32299
32353
  doc.setFontSize(12);
32300
- doc.text(chart.title || "Participa\xE7\xE3o por Dispositivo", MARGIN, titleY);
32354
+ doc.text(pdfSafe(chart.title || "Participa\xE7\xE3o por Dispositivo"), MARGIN, titleY);
32301
32355
  const imgTop = titleY + 4;
32302
32356
  const availW = TABLE_W;
32303
32357
  const availH = MAX_Y - imgTop;
@@ -34471,7 +34525,7 @@ var DeviceReportModal = class {
34471
34525
  // Mesmo rótulo da coluna da tela (antes saía "Consumo (°C)" em temperatura)
34472
34526
  valueLabel: this.domainConfig.label,
34473
34527
  countLabel: this.granularity === "1h" ? "hora(s)" : "dia(s)",
34474
- ...isTemp ? { hidePerc: true, emptyValueText: "Sem leitura" } : {}
34528
+ ...isTemp ? { hidePerc: true, emptyValueText: "Sem leitura", valueDecimals: 2 } : {}
34475
34529
  };
34476
34530
  }
34477
34531
  // Gráfico da página final do PDF = painel da direita da tela
@@ -37509,7 +37563,7 @@ var AllReportModal = class {
37509
37563
  // Header row only
37510
37564
  ["Identificador", "Nome", `Consumo (${this.domainConfig.unit})`],
37511
37565
  // Data rows
37512
- ...sortedData.map((row) => [row.identifier, row.name, row.consumption.toFixed(2)])
37566
+ ...sortedData.map((row) => [row.identifier, row.name, row.consumption.toFixed(2).replace(".", ",")])
37513
37567
  ];
37514
37568
  const csvContent = toCsv(csvData);
37515
37569
  this.downloadCSV(csvContent, `relatorio-geral-lojas-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`);
@@ -37521,7 +37575,7 @@ var AllReportModal = class {
37521
37575
  const showId = this.showIdentifierColumn(rows);
37522
37576
  const idCol = (r) => showId ? [r.identifier] : [];
37523
37577
  const idHead = showId ? ["Identificador"] : [];
37524
- const n2 = (v) => v === null || v === void 0 || !Number.isFinite(v) ? "" : v.toFixed(2);
37578
+ const n2 = (v) => v === null || v === void 0 || !Number.isFinite(v) ? "" : v.toFixed(2).replace(".", ",");
37525
37579
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
37526
37580
  const gran = this.granularity;
37527
37581
  const fmtStamp = (ts) => {
@@ -37567,7 +37621,7 @@ var AllReportModal = class {
37567
37621
  for (const r of rows) {
37568
37622
  const raw = r.id && this.temperatureStats.get(r.id)?.series || [];
37569
37623
  const pts = gran === "1h" ? this.aggregateHourly(raw, "avg") : this.aggregateDaily(raw, "avg");
37570
- for (const p2 of pts) series.push([...idCol(r), r.name, fmtTs(p2.timestamp), p2.value.toFixed(2)]);
37624
+ for (const p2 of pts) series.push([...idCol(r), r.name, fmtTs(p2.timestamp), n2(p2.value)]);
37571
37625
  }
37572
37626
  const csvContent = toCsv([...meta, ...summary, [], ...series]);
37573
37627
  this.downloadCSV(csvContent, `relatorio-temperatura-${gran}-${date}.csv`);
@@ -37594,7 +37648,7 @@ var AllReportModal = class {
37594
37648
  for (const row of sortedData) {
37595
37649
  const points = row.id && series.get(row.id) || [];
37596
37650
  for (const p of points) {
37597
- csvData.push([row.identifier, row.name, fmtTs(p.timestamp), p.value.toFixed(2)]);
37651
+ csvData.push([row.identifier, row.name, fmtTs(p.timestamp), p.value.toFixed(2).replace(".", ",")]);
37598
37652
  }
37599
37653
  }
37600
37654
  if (csvData.length === 1) {
@@ -37724,7 +37778,9 @@ var AllReportModal = class {
37724
37778
  { label: "M\xE1x (\xB0C)", pdfW: 35 }
37725
37779
  ],
37726
37780
  emptyValueText: "Sem leitura",
37727
- countLabel: "sensor(es)"
37781
+ countLabel: "sensor(es)",
37782
+ valueDecimals: 2
37783
+ // "30,00" como na tela (não "30")
37728
37784
  };
37729
37785
  }
37730
37786
  // Accent hex da paleta do dashboard (tema efetivo) para o PDF/XLS — cai no