myio-js-library 0.1.553 → 0.1.555

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.555",
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: '';
@@ -19145,19 +19190,25 @@ function renderCardComponentV5({
19145
19190
  isOffline
19146
19191
  });
19147
19192
  const calculateTempDeviationPercent = () => {
19148
- const currentTemp = Number(val) || 0;
19149
- if (temperatureMin !== void 0 && temperatureMax !== void 0) {
19150
- const avgTemp = (Number(temperatureMin) + Number(temperatureMax)) / 2;
19151
- if (avgTemp === 0) return { value: 0, sign: "" };
19152
- const deviation = (currentTemp - avgTemp) / avgTemp * 100;
19153
- return {
19154
- value: Math.abs(deviation),
19155
- sign: deviation >= 0 ? "+" : "-",
19156
- isAbove: deviation > 0,
19157
- isBelow: deviation < 0
19158
- };
19193
+ const currentTemp = Number(val);
19194
+ if (!Number.isFinite(currentTemp)) return null;
19195
+ if (temperatureMin === void 0 || temperatureMin === null || temperatureMax === void 0 || temperatureMax === null) {
19196
+ return null;
19159
19197
  }
19160
- return null;
19198
+ const min = Number(temperatureMin);
19199
+ const max = Number(temperatureMax);
19200
+ if (!Number.isFinite(min) || !Number.isFinite(max)) return null;
19201
+ const fmt1 = (n) => n.toLocaleString("pt-BR", { minimumFractionDigits: 1, maximumFractionDigits: 1 });
19202
+ const range = `Faixa ideal ${fmt1(min)}\u2013${fmt1(max)} \xB0C`;
19203
+ if (currentTemp > max) {
19204
+ const d = currentTemp - max;
19205
+ return { isAbove: true, isBelow: false, value: d, text: `+${fmt1(d)} \xB0C`, title: `${range} \xB7 ${fmt1(d)} \xB0C acima do limite` };
19206
+ }
19207
+ if (currentTemp < min) {
19208
+ const d = min - currentTemp;
19209
+ return { isAbove: false, isBelow: true, value: d, text: `\u2212${fmt1(d)} \xB0C`, title: `${range} \xB7 ${fmt1(d)} \xB0C abaixo do limite` };
19210
+ }
19211
+ return { isAbove: false, isBelow: false, value: 0, text: "na faixa", title: `${range} \xB7 dentro da faixa` };
19161
19212
  };
19162
19213
  const tempDeviationPercent = isTermostatoDevice && !showNoTempReading ? calculateTempDeviationPercent() : null;
19163
19214
  const cardHTML = `
@@ -19204,10 +19255,10 @@ function renderCardComponentV5({
19204
19255
  <span class="flash-icon ${shouldFlashIcon2 ? "flash" : ""}">
19205
19256
  ${icon2}
19206
19257
  </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>
19258
+ <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
19259
  </div>
19209
19260
  </div>
19210
- ${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>` : ""}
19261
+ ${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" : "#16a34a"}; font-weight: 600; cursor: help;" title="${tempDeviationPercent.title}">${tempDeviationPercent.text}</span>` : ""}
19211
19262
  </div>
19212
19263
  </div>
19213
19264
  </div>
@@ -19224,6 +19275,8 @@ function renderCardComponentV5({
19224
19275
  if (_isExcluded) container.classList.add("myio-card-excluded");
19225
19276
  } catch (e) {
19226
19277
  }
19278
+ if (_isTempCard && _off !== 0) container.classList.add("myio-card-excluded", "myio-card-temp-offset");
19279
+ if (showTempWarning) container.classList.add("myio-card-temp-warning");
19227
19280
  if (!document.getElementById("myio-enhanced-card-layout-styles-v5")) {
19228
19281
  const layoutStyle = document.createElement("style");
19229
19282
  layoutStyle.id = "myio-enhanced-card-layout-styles-v5";
@@ -33594,7 +33647,11 @@ function buildRow(d, idx, colOpts) {
33594
33647
  const x = extras(d);
33595
33648
  const fmtVal = () => {
33596
33649
  if (d.val === null || d.val === void 0) return colOpts?.emptyValueText || "\u2014";
33597
- return Number(d.val).toLocaleString("pt-BR", { maximumFractionDigits: 3, useGrouping: false });
33650
+ const dec = colOpts?.valueDecimals;
33651
+ return Number(d.val).toLocaleString(
33652
+ "pt-BR",
33653
+ dec !== void 0 ? { minimumFractionDigits: dec, maximumFractionDigits: dec, useGrouping: false } : { maximumFractionDigits: 3, useGrouping: false }
33654
+ );
33598
33655
  };
33599
33656
  const pd = resolvePercentDecimals();
33600
33657
  const name = d.labelOrName || d.name || "\u2014";
@@ -33656,6 +33713,9 @@ function escXml(s) {
33656
33713
  function truncate(s, max) {
33657
33714
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
33658
33715
  }
33716
+ function pdfSafe(s) {
33717
+ return String(s).replace(/−/g, "-").replace(/[‐‑]/g, "-").replace(/ /g, " ");
33718
+ }
33659
33719
  function exportGridCsv(devices, label, unit, period, customerName) {
33660
33720
  const periodLabel2 = fmtPeriod(period);
33661
33721
  const metaRows = [
@@ -33782,14 +33842,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33782
33842
  doc.setFont("helvetica", "bold");
33783
33843
  doc.setFontSize(10);
33784
33844
  const titleText = customerName ? `${customerName} \u2014 ${label}` : label;
33785
- doc.text(titleText, MARGIN, HDR_H / 2 + 1.5);
33845
+ doc.text(pdfSafe(titleText), MARGIN, HDR_H / 2 + 1.5);
33786
33846
  doc.setFont("helvetica", "normal");
33787
33847
  doc.setFontSize(8);
33788
33848
  const periodLabel2 = fmtPeriod(period);
33789
33849
  const periodPart = periodLabel2 ? `Per\xEDodo: ${periodLabel2} \u2022 ` : "";
33790
33850
  const countLabel = options?.columns?.countLabel || "dispositivo(s)";
33791
33851
  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" });
33852
+ doc.text(pdfSafe(info), PW - MARGIN, HDR_H / 2 + 1.5, { align: "right" });
33793
33853
  }
33794
33854
  function drawColumnHeaders(y) {
33795
33855
  doc.setFillColor(...ACCENT_SOFT);
@@ -33799,7 +33859,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33799
33859
  doc.setFontSize(7);
33800
33860
  cols.forEach((c, ci) => {
33801
33861
  const x = colX(ci) + 1.5;
33802
- doc.text(c.label, x, y + HEAD_H / 2 + 2.5);
33862
+ doc.text(pdfSafe(c.label), x, y + HEAD_H / 2 + 2.5);
33803
33863
  });
33804
33864
  doc.setDrawColor(200, 195, 220);
33805
33865
  doc.setLineWidth(0.2);
@@ -33816,7 +33876,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33816
33876
  cols.forEach((c, ci) => {
33817
33877
  const x = colX(ci) + 1.5;
33818
33878
  const maxChars = Math.floor(colWidths[ci] / 1.8);
33819
- const text2 = truncate(String(r[c.key]), maxChars);
33879
+ const text2 = truncate(pdfSafe(String(r[c.key])), maxChars);
33820
33880
  doc.text(text2, x, y + ROW_H / 2 + 2.2);
33821
33881
  });
33822
33882
  doc.setDrawColor(230, 228, 240);
@@ -33829,7 +33889,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33829
33889
  doc.setTextColor(...ACCENT2);
33830
33890
  doc.setFont("helvetica", "bold");
33831
33891
  doc.setFontSize(6.5);
33832
- doc.text(truncate(text2, 160), MARGIN + 1.5, y + ROW_H / 2 + 2.2);
33892
+ doc.text(truncate(pdfSafe(text2), 160), MARGIN + 1.5, y + ROW_H / 2 + 2.2);
33833
33893
  }
33834
33894
  function drawFooter2() {
33835
33895
  doc.setFillColor(250, 249, 255);
@@ -33857,14 +33917,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33857
33917
  doc.setTextColor(...ACCENT2);
33858
33918
  doc.setFont("helvetica", "bold");
33859
33919
  doc.setFontSize(11);
33860
- doc.text(truncate(kpi.value, 24), cx, y + 6.5, { align: "center" });
33920
+ doc.text(truncate(pdfSafe(kpi.value), 24), cx, y + 6.5, { align: "center" });
33861
33921
  doc.setTextColor(110, 110, 120);
33862
33922
  doc.setFont("helvetica", "normal");
33863
33923
  doc.setFontSize(6.5);
33864
- doc.text(truncate(kpi.label, 34), cx, y + 10.5, { align: "center" });
33924
+ doc.text(truncate(pdfSafe(kpi.label), 34), cx, y + 10.5, { align: "center" });
33865
33925
  if (kpi.sub) {
33866
33926
  doc.setFontSize(5.5);
33867
- doc.text(truncate(kpi.sub, 40), cx, y + 13.5, { align: "center" });
33927
+ doc.text(truncate(pdfSafe(kpi.sub), 40), cx, y + 13.5, { align: "center" });
33868
33928
  }
33869
33929
  });
33870
33930
  }
@@ -33911,7 +33971,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
33911
33971
  doc.setTextColor(...ACCENT2);
33912
33972
  doc.setFont("helvetica", "bold");
33913
33973
  doc.setFontSize(12);
33914
- doc.text(chart.title || "Participa\xE7\xE3o por Dispositivo", MARGIN, titleY);
33974
+ doc.text(pdfSafe(chart.title || "Participa\xE7\xE3o por Dispositivo"), MARGIN, titleY);
33915
33975
  const imgTop = titleY + 4;
33916
33976
  const availW = TABLE_W;
33917
33977
  const availH = MAX_Y - imgTop;
@@ -36085,7 +36145,7 @@ var DeviceReportModal = class {
36085
36145
  // Mesmo rótulo da coluna da tela (antes saía "Consumo (°C)" em temperatura)
36086
36146
  valueLabel: this.domainConfig.label,
36087
36147
  countLabel: this.granularity === "1h" ? "hora(s)" : "dia(s)",
36088
- ...isTemp ? { hidePerc: true, emptyValueText: "Sem leitura" } : {}
36148
+ ...isTemp ? { hidePerc: true, emptyValueText: "Sem leitura", valueDecimals: 2 } : {}
36089
36149
  };
36090
36150
  }
36091
36151
  // Gráfico da página final do PDF = painel da direita da tela
@@ -39123,7 +39183,7 @@ var AllReportModal = class {
39123
39183
  // Header row only
39124
39184
  ["Identificador", "Nome", `Consumo (${this.domainConfig.unit})`],
39125
39185
  // Data rows
39126
- ...sortedData.map((row) => [row.identifier, row.name, row.consumption.toFixed(2)])
39186
+ ...sortedData.map((row) => [row.identifier, row.name, row.consumption.toFixed(2).replace(".", ",")])
39127
39187
  ];
39128
39188
  const csvContent = toCsv(csvData);
39129
39189
  this.downloadCSV(csvContent, `relatorio-geral-lojas-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`);
@@ -39135,7 +39195,7 @@ var AllReportModal = class {
39135
39195
  const showId = this.showIdentifierColumn(rows);
39136
39196
  const idCol = (r) => showId ? [r.identifier] : [];
39137
39197
  const idHead = showId ? ["Identificador"] : [];
39138
- const n2 = (v) => v === null || v === void 0 || !Number.isFinite(v) ? "" : v.toFixed(2);
39198
+ const n2 = (v) => v === null || v === void 0 || !Number.isFinite(v) ? "" : v.toFixed(2).replace(".", ",");
39139
39199
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
39140
39200
  const gran = this.granularity;
39141
39201
  const fmtStamp = (ts) => {
@@ -39181,7 +39241,7 @@ var AllReportModal = class {
39181
39241
  for (const r of rows) {
39182
39242
  const raw = r.id && this.temperatureStats.get(r.id)?.series || [];
39183
39243
  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)]);
39244
+ for (const p2 of pts) series.push([...idCol(r), r.name, fmtTs(p2.timestamp), n2(p2.value)]);
39185
39245
  }
39186
39246
  const csvContent = toCsv([...meta, ...summary, [], ...series]);
39187
39247
  this.downloadCSV(csvContent, `relatorio-temperatura-${gran}-${date}.csv`);
@@ -39208,7 +39268,7 @@ var AllReportModal = class {
39208
39268
  for (const row of sortedData) {
39209
39269
  const points = row.id && series.get(row.id) || [];
39210
39270
  for (const p of points) {
39211
- csvData.push([row.identifier, row.name, fmtTs(p.timestamp), p.value.toFixed(2)]);
39271
+ csvData.push([row.identifier, row.name, fmtTs(p.timestamp), p.value.toFixed(2).replace(".", ",")]);
39212
39272
  }
39213
39273
  }
39214
39274
  if (csvData.length === 1) {
@@ -39338,7 +39398,9 @@ var AllReportModal = class {
39338
39398
  { label: "M\xE1x (\xB0C)", pdfW: 35 }
39339
39399
  ],
39340
39400
  emptyValueText: "Sem leitura",
39341
- countLabel: "sensor(es)"
39401
+ countLabel: "sensor(es)",
39402
+ valueDecimals: 2
39403
+ // "30,00" como na tela (não "30")
39342
39404
  };
39343
39405
  }
39344
39406
  // 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.555",
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: '';
@@ -17531,19 +17576,25 @@ function renderCardComponentV5({
17531
17576
  isOffline
17532
17577
  });
17533
17578
  const calculateTempDeviationPercent = () => {
17534
- const currentTemp = Number(val) || 0;
17535
- if (temperatureMin !== void 0 && temperatureMax !== void 0) {
17536
- const avgTemp = (Number(temperatureMin) + Number(temperatureMax)) / 2;
17537
- if (avgTemp === 0) return { value: 0, sign: "" };
17538
- const deviation = (currentTemp - avgTemp) / avgTemp * 100;
17539
- return {
17540
- value: Math.abs(deviation),
17541
- sign: deviation >= 0 ? "+" : "-",
17542
- isAbove: deviation > 0,
17543
- isBelow: deviation < 0
17544
- };
17579
+ const currentTemp = Number(val);
17580
+ if (!Number.isFinite(currentTemp)) return null;
17581
+ if (temperatureMin === void 0 || temperatureMin === null || temperatureMax === void 0 || temperatureMax === null) {
17582
+ return null;
17545
17583
  }
17546
- return null;
17584
+ const min = Number(temperatureMin);
17585
+ const max = Number(temperatureMax);
17586
+ if (!Number.isFinite(min) || !Number.isFinite(max)) return null;
17587
+ const fmt1 = (n) => n.toLocaleString("pt-BR", { minimumFractionDigits: 1, maximumFractionDigits: 1 });
17588
+ const range = `Faixa ideal ${fmt1(min)}\u2013${fmt1(max)} \xB0C`;
17589
+ if (currentTemp > max) {
17590
+ const d = currentTemp - max;
17591
+ return { isAbove: true, isBelow: false, value: d, text: `+${fmt1(d)} \xB0C`, title: `${range} \xB7 ${fmt1(d)} \xB0C acima do limite` };
17592
+ }
17593
+ if (currentTemp < min) {
17594
+ const d = min - currentTemp;
17595
+ return { isAbove: false, isBelow: true, value: d, text: `\u2212${fmt1(d)} \xB0C`, title: `${range} \xB7 ${fmt1(d)} \xB0C abaixo do limite` };
17596
+ }
17597
+ return { isAbove: false, isBelow: false, value: 0, text: "na faixa", title: `${range} \xB7 dentro da faixa` };
17547
17598
  };
17548
17599
  const tempDeviationPercent = isTermostatoDevice && !showNoTempReading ? calculateTempDeviationPercent() : null;
17549
17600
  const cardHTML = `
@@ -17590,10 +17641,10 @@ function renderCardComponentV5({
17590
17641
  <span class="flash-icon ${shouldFlashIcon2 ? "flash" : ""}">
17591
17642
  ${icon2}
17592
17643
  </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>
17644
+ <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
17645
  </div>
17595
17646
  </div>
17596
- ${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>` : ""}
17647
+ ${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" : "#16a34a"}; font-weight: 600; cursor: help;" title="${tempDeviationPercent.title}">${tempDeviationPercent.text}</span>` : ""}
17597
17648
  </div>
17598
17649
  </div>
17599
17650
  </div>
@@ -17610,6 +17661,8 @@ function renderCardComponentV5({
17610
17661
  if (_isExcluded) container.classList.add("myio-card-excluded");
17611
17662
  } catch (e) {
17612
17663
  }
17664
+ if (_isTempCard && _off !== 0) container.classList.add("myio-card-excluded", "myio-card-temp-offset");
17665
+ if (showTempWarning) container.classList.add("myio-card-temp-warning");
17613
17666
  if (!document.getElementById("myio-enhanced-card-layout-styles-v5")) {
17614
17667
  const layoutStyle = document.createElement("style");
17615
17668
  layoutStyle.id = "myio-enhanced-card-layout-styles-v5";
@@ -31980,7 +32033,11 @@ function buildRow(d, idx, colOpts) {
31980
32033
  const x = extras(d);
31981
32034
  const fmtVal = () => {
31982
32035
  if (d.val === null || d.val === void 0) return colOpts?.emptyValueText || "\u2014";
31983
- return Number(d.val).toLocaleString("pt-BR", { maximumFractionDigits: 3, useGrouping: false });
32036
+ const dec = colOpts?.valueDecimals;
32037
+ return Number(d.val).toLocaleString(
32038
+ "pt-BR",
32039
+ dec !== void 0 ? { minimumFractionDigits: dec, maximumFractionDigits: dec, useGrouping: false } : { maximumFractionDigits: 3, useGrouping: false }
32040
+ );
31984
32041
  };
31985
32042
  const pd = resolvePercentDecimals();
31986
32043
  const name = d.labelOrName || d.name || "\u2014";
@@ -32042,6 +32099,9 @@ function escXml(s) {
32042
32099
  function truncate(s, max) {
32043
32100
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
32044
32101
  }
32102
+ function pdfSafe(s) {
32103
+ return String(s).replace(/−/g, "-").replace(/[‐‑]/g, "-").replace(/ /g, " ");
32104
+ }
32045
32105
  function exportGridCsv(devices, label, unit, period, customerName) {
32046
32106
  const periodLabel2 = fmtPeriod(period);
32047
32107
  const metaRows = [
@@ -32168,14 +32228,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32168
32228
  doc.setFont("helvetica", "bold");
32169
32229
  doc.setFontSize(10);
32170
32230
  const titleText = customerName ? `${customerName} \u2014 ${label}` : label;
32171
- doc.text(titleText, MARGIN, HDR_H / 2 + 1.5);
32231
+ doc.text(pdfSafe(titleText), MARGIN, HDR_H / 2 + 1.5);
32172
32232
  doc.setFont("helvetica", "normal");
32173
32233
  doc.setFontSize(8);
32174
32234
  const periodLabel2 = fmtPeriod(period);
32175
32235
  const periodPart = periodLabel2 ? `Per\xEDodo: ${periodLabel2} \u2022 ` : "";
32176
32236
  const countLabel = options?.columns?.countLabel || "dispositivo(s)";
32177
32237
  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" });
32238
+ doc.text(pdfSafe(info), PW - MARGIN, HDR_H / 2 + 1.5, { align: "right" });
32179
32239
  }
32180
32240
  function drawColumnHeaders(y) {
32181
32241
  doc.setFillColor(...ACCENT_SOFT);
@@ -32185,7 +32245,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32185
32245
  doc.setFontSize(7);
32186
32246
  cols.forEach((c, ci) => {
32187
32247
  const x = colX(ci) + 1.5;
32188
- doc.text(c.label, x, y + HEAD_H / 2 + 2.5);
32248
+ doc.text(pdfSafe(c.label), x, y + HEAD_H / 2 + 2.5);
32189
32249
  });
32190
32250
  doc.setDrawColor(200, 195, 220);
32191
32251
  doc.setLineWidth(0.2);
@@ -32202,7 +32262,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32202
32262
  cols.forEach((c, ci) => {
32203
32263
  const x = colX(ci) + 1.5;
32204
32264
  const maxChars = Math.floor(colWidths[ci] / 1.8);
32205
- const text2 = truncate(String(r[c.key]), maxChars);
32265
+ const text2 = truncate(pdfSafe(String(r[c.key])), maxChars);
32206
32266
  doc.text(text2, x, y + ROW_H / 2 + 2.2);
32207
32267
  });
32208
32268
  doc.setDrawColor(230, 228, 240);
@@ -32215,7 +32275,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32215
32275
  doc.setTextColor(...ACCENT2);
32216
32276
  doc.setFont("helvetica", "bold");
32217
32277
  doc.setFontSize(6.5);
32218
- doc.text(truncate(text2, 160), MARGIN + 1.5, y + ROW_H / 2 + 2.2);
32278
+ doc.text(truncate(pdfSafe(text2), 160), MARGIN + 1.5, y + ROW_H / 2 + 2.2);
32219
32279
  }
32220
32280
  function drawFooter2() {
32221
32281
  doc.setFillColor(250, 249, 255);
@@ -32243,14 +32303,14 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32243
32303
  doc.setTextColor(...ACCENT2);
32244
32304
  doc.setFont("helvetica", "bold");
32245
32305
  doc.setFontSize(11);
32246
- doc.text(truncate(kpi.value, 24), cx, y + 6.5, { align: "center" });
32306
+ doc.text(truncate(pdfSafe(kpi.value), 24), cx, y + 6.5, { align: "center" });
32247
32307
  doc.setTextColor(110, 110, 120);
32248
32308
  doc.setFont("helvetica", "normal");
32249
32309
  doc.setFontSize(6.5);
32250
- doc.text(truncate(kpi.label, 34), cx, y + 10.5, { align: "center" });
32310
+ doc.text(truncate(pdfSafe(kpi.label), 34), cx, y + 10.5, { align: "center" });
32251
32311
  if (kpi.sub) {
32252
32312
  doc.setFontSize(5.5);
32253
- doc.text(truncate(kpi.sub, 40), cx, y + 13.5, { align: "center" });
32313
+ doc.text(truncate(pdfSafe(kpi.sub), 40), cx, y + 13.5, { align: "center" });
32254
32314
  }
32255
32315
  });
32256
32316
  }
@@ -32297,7 +32357,7 @@ function exportGridPdf(devices, label, unit, period, customerName, options) {
32297
32357
  doc.setTextColor(...ACCENT2);
32298
32358
  doc.setFont("helvetica", "bold");
32299
32359
  doc.setFontSize(12);
32300
- doc.text(chart.title || "Participa\xE7\xE3o por Dispositivo", MARGIN, titleY);
32360
+ doc.text(pdfSafe(chart.title || "Participa\xE7\xE3o por Dispositivo"), MARGIN, titleY);
32301
32361
  const imgTop = titleY + 4;
32302
32362
  const availW = TABLE_W;
32303
32363
  const availH = MAX_Y - imgTop;
@@ -34471,7 +34531,7 @@ var DeviceReportModal = class {
34471
34531
  // Mesmo rótulo da coluna da tela (antes saía "Consumo (°C)" em temperatura)
34472
34532
  valueLabel: this.domainConfig.label,
34473
34533
  countLabel: this.granularity === "1h" ? "hora(s)" : "dia(s)",
34474
- ...isTemp ? { hidePerc: true, emptyValueText: "Sem leitura" } : {}
34534
+ ...isTemp ? { hidePerc: true, emptyValueText: "Sem leitura", valueDecimals: 2 } : {}
34475
34535
  };
34476
34536
  }
34477
34537
  // Gráfico da página final do PDF = painel da direita da tela
@@ -37509,7 +37569,7 @@ var AllReportModal = class {
37509
37569
  // Header row only
37510
37570
  ["Identificador", "Nome", `Consumo (${this.domainConfig.unit})`],
37511
37571
  // Data rows
37512
- ...sortedData.map((row) => [row.identifier, row.name, row.consumption.toFixed(2)])
37572
+ ...sortedData.map((row) => [row.identifier, row.name, row.consumption.toFixed(2).replace(".", ",")])
37513
37573
  ];
37514
37574
  const csvContent = toCsv(csvData);
37515
37575
  this.downloadCSV(csvContent, `relatorio-geral-lojas-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`);
@@ -37521,7 +37581,7 @@ var AllReportModal = class {
37521
37581
  const showId = this.showIdentifierColumn(rows);
37522
37582
  const idCol = (r) => showId ? [r.identifier] : [];
37523
37583
  const idHead = showId ? ["Identificador"] : [];
37524
- const n2 = (v) => v === null || v === void 0 || !Number.isFinite(v) ? "" : v.toFixed(2);
37584
+ const n2 = (v) => v === null || v === void 0 || !Number.isFinite(v) ? "" : v.toFixed(2).replace(".", ",");
37525
37585
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
37526
37586
  const gran = this.granularity;
37527
37587
  const fmtStamp = (ts) => {
@@ -37567,7 +37627,7 @@ var AllReportModal = class {
37567
37627
  for (const r of rows) {
37568
37628
  const raw = r.id && this.temperatureStats.get(r.id)?.series || [];
37569
37629
  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)]);
37630
+ for (const p2 of pts) series.push([...idCol(r), r.name, fmtTs(p2.timestamp), n2(p2.value)]);
37571
37631
  }
37572
37632
  const csvContent = toCsv([...meta, ...summary, [], ...series]);
37573
37633
  this.downloadCSV(csvContent, `relatorio-temperatura-${gran}-${date}.csv`);
@@ -37594,7 +37654,7 @@ var AllReportModal = class {
37594
37654
  for (const row of sortedData) {
37595
37655
  const points = row.id && series.get(row.id) || [];
37596
37656
  for (const p of points) {
37597
- csvData.push([row.identifier, row.name, fmtTs(p.timestamp), p.value.toFixed(2)]);
37657
+ csvData.push([row.identifier, row.name, fmtTs(p.timestamp), p.value.toFixed(2).replace(".", ",")]);
37598
37658
  }
37599
37659
  }
37600
37660
  if (csvData.length === 1) {
@@ -37724,7 +37784,9 @@ var AllReportModal = class {
37724
37784
  { label: "M\xE1x (\xB0C)", pdfW: 35 }
37725
37785
  ],
37726
37786
  emptyValueText: "Sem leitura",
37727
- countLabel: "sensor(es)"
37787
+ countLabel: "sensor(es)",
37788
+ valueDecimals: 2
37789
+ // "30,00" como na tela (não "30")
37728
37790
  };
37729
37791
  }
37730
37792
  // Accent hex da paleta do dashboard (tema efetivo) para o PDF/XLS — cai no