myio-js-library 0.1.28 → 0.1.30

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
@@ -1297,39 +1297,77 @@ function findValue(data, keyOrPath, legacyDataKey) {
1297
1297
  }
1298
1298
 
1299
1299
  // src/utils/deviceType.js
1300
+ function normalize(str) {
1301
+ if (typeof str !== "string") return "";
1302
+ let normalized = str.toUpperCase().trim();
1303
+ normalized = normalized.replace(/[ÀÁÂÃÄÅ]/g, "A").replace(/[ÈÉÊË]/g, "E").replace(/[ÌÍÎÏ]/g, "I").replace(/[ÒÓÔÕÖ]/g, "O").replace(/[ÙÚÛÜ]/g, "U").replace(/[Ç]/g, "C").replace(/[Ñ]/g, "N");
1304
+ normalized = normalized.replace(/\s+/g, " ");
1305
+ return normalized;
1306
+ }
1307
+ function matchCaixaDAgua(normalizedStr) {
1308
+ if (normalizedStr.includes("SCD")) return true;
1309
+ const caixaVariants = [
1310
+ "CAIXA D'AGUA",
1311
+ "CAIXA D AGUA",
1312
+ "CAIXA_D_AGUA",
1313
+ "CAIXA DAGUA"
1314
+ ];
1315
+ return caixaVariants.some((variant) => normalizedStr.includes(variant));
1316
+ }
1300
1317
  var contexts = {
1301
1318
  building: (name) => {
1302
- const upper = name.toUpperCase();
1303
- if (upper.includes("COMPRESSOR")) return "COMPRESSOR";
1304
- if (upper.includes("VENT")) return "VENTILADOR";
1305
- if (upper.includes("AUTOMATICO") || upper.includes("AUTOM\xC1TICO")) return "SELETOR_AUTO_MANUAL";
1306
- if (upper.includes("TERMOSTATO")) return "TERMOSTATO";
1307
- if (upper.includes("3F")) return "3F_MEDIDOR";
1308
- if (upper.includes("TERMO") || upper.includes("TEMP")) return "TERMOSTATO";
1309
- if (upper.includes("HIDR")) return "HIDROMETRO";
1310
- if (upper.includes("ABRE")) return "SOLENOIDE";
1311
- if (upper.includes("RECALQUE")) return "MOTOR";
1312
- if (upper.includes("AUTOMACAO") || upper.includes("AUTOMA\xC7\xC3O")) return "GLOBAL_AUTOMACAO";
1313
- if (upper.includes("AC")) return "CONTROLE REMOTO";
1314
- if (upper.includes("SCD")) return "CAIXA_D_AGUA";
1319
+ const normalized = normalize(name);
1320
+ const rules = [
1321
+ { test: (s) => s.includes("COMPRESSOR"), type: "COMPRESSOR" },
1322
+ { test: (s) => s.includes("VENT"), type: "VENTILADOR" },
1323
+ { test: (s) => s.includes("AUTOMATICO"), type: "SELETOR_AUTO_MANUAL" },
1324
+ { test: (s) => s.includes("ESRL"), type: "ESCADA_ROLANTE" },
1325
+ { test: (s) => s.includes("ESCADA"), type: "ESCADA_ROLANTE" },
1326
+ { test: (s) => s.includes("ELEV"), type: "ELEVADOR" },
1327
+ { test: (s) => s.includes("MOTR") || s.includes("RECALQUE"), type: "MOTOR" },
1328
+ { test: (s) => s.includes("TERMOSTATO"), type: "TERMOSTATO" },
1329
+ { test: (s) => s.includes("TERMO") || s.includes("TEMP"), type: "TERMOSTATO" },
1330
+ { test: (s) => s.includes("3F"), type: "3F_MEDIDOR" },
1331
+ { test: (s) => s.includes("HIDR"), type: "HIDROMETRO" },
1332
+ { test: (s) => s.includes("ABRE"), type: "SOLENOIDE" },
1333
+ { test: (s) => matchCaixaDAgua(s), type: "CAIXA_D_AGUA" },
1334
+ { test: (s) => s.includes("AUTOMACAO") || s.includes("GW_AUTO"), type: "GLOBAL_AUTOMACAO" },
1335
+ { test: (s) => s.includes("AC"), type: "CONTROLE REMOTO" }
1336
+ ];
1337
+ for (const rule of rules) {
1338
+ if (rule.test(normalized)) {
1339
+ return rule.type;
1340
+ }
1341
+ }
1315
1342
  return "default";
1316
1343
  },
1317
1344
  mall: (name) => {
1318
- const upper = name.toUpperCase();
1319
- if (upper.includes("CHILLER")) return "CHILLER";
1320
- if (upper.includes("ESCADA")) return "ESCADA_ROLANTE";
1321
- if (upper.includes("LOJA")) return "LOJA_SENSOR";
1322
- if (upper.includes("ILUMINACAO") || upper.includes("ILUMINA\xC7\xC3O")) return "ILUMINACAO";
1345
+ const normalized = normalize(name);
1346
+ const rules = [
1347
+ { test: (s) => s.includes("CHILLER"), type: "CHILLER" },
1348
+ { test: (s) => s.includes("ESCADA"), type: "ESCADA_ROLANTE" },
1349
+ { test: (s) => s.includes("LOJA"), type: "LOJA_SENSOR" },
1350
+ { test: (s) => s.includes("ILUMINACAO"), type: "ILUMINACAO" }
1351
+ ];
1352
+ for (const rule of rules) {
1353
+ if (rule.test(normalized)) {
1354
+ return rule.type;
1355
+ }
1356
+ }
1323
1357
  return "default";
1324
1358
  }
1325
1359
  };
1360
+ var warnedContexts = /* @__PURE__ */ new Set();
1326
1361
  function detectDeviceType(name, context = "building") {
1327
1362
  if (typeof name !== "string") {
1328
1363
  throw new Error("Device name must be a string.");
1329
1364
  }
1330
1365
  const detectFunction = contexts[context];
1331
1366
  if (!detectFunction) {
1332
- console.warn(`[myio-js-library] Context "${context}" not found. Using default fallback.`);
1367
+ if (!warnedContexts.has(context)) {
1368
+ console.warn(`[myio-js-library] Context "${context}" not found. Using default fallback.`);
1369
+ warnedContexts.add(context);
1370
+ }
1333
1371
  return contexts.building(name);
1334
1372
  }
1335
1373
  return detectFunction(name);
@@ -2112,6 +2150,85 @@ function renderCardComponentV2({
2112
2150
  timeVal,
2113
2151
  valType
2114
2152
  } = entityObject;
2153
+ const MyIOToast = (function() {
2154
+ let toastContainer = null;
2155
+ let toastTimeout = null;
2156
+ const TOAST_CSS = `
2157
+ #myio-global-toast-container {
2158
+ position: fixed;
2159
+ top: 25px;
2160
+ right: 25px;
2161
+ z-index: 99999;
2162
+ width: 320px;
2163
+ padding: 16px;
2164
+ border-radius: 8px;
2165
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
2166
+ font-size: 15px;
2167
+ color: #fff;
2168
+ transform: translateX(120%);
2169
+ transition: transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
2170
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
2171
+ border-left: 5px solid transparent;
2172
+ display: flex;
2173
+ align-items: center;
2174
+ }
2175
+ #myio-global-toast-container.show {
2176
+ transform: translateX(0);
2177
+ }
2178
+ #myio-global-toast-container.warning {
2179
+ background-color: #ff9800; /* Laranja para alerta */
2180
+ border-color: #f57c00;
2181
+ }
2182
+ #myio-global-toast-container.error {
2183
+ background-color: #d32f2f; /* Vermelho para erro */
2184
+ border-color: #b71c1c;
2185
+ }
2186
+ #myio-global-toast-container::before {
2187
+ content: '\u26A0\uFE0F'; /* \xCDcone de alerta */
2188
+ margin-right: 12px;
2189
+ font-size: 20px;
2190
+ }
2191
+ #myio-global-toast-container.error::before {
2192
+ content: '\u{1F6AB}'; /* \xCDcone de erro */
2193
+ }
2194
+ `;
2195
+ function createToastElement() {
2196
+ if (document.getElementById("myio-global-toast-container")) {
2197
+ toastContainer = document.getElementById("myio-global-toast-container");
2198
+ return;
2199
+ }
2200
+ const style = document.createElement("style");
2201
+ style.id = "myio-global-toast-styles";
2202
+ style.textContent = TOAST_CSS;
2203
+ document.head.appendChild(style);
2204
+ toastContainer = document.createElement("div");
2205
+ toastContainer.id = "myio-global-toast-container";
2206
+ document.body.appendChild(toastContainer);
2207
+ }
2208
+ function show(message, type = "warning", duration = 3500) {
2209
+ if (!toastContainer) {
2210
+ createToastElement();
2211
+ }
2212
+ clearTimeout(toastTimeout);
2213
+ toastContainer.textContent = message;
2214
+ toastContainer.className = "";
2215
+ toastContainer.classList.add(type);
2216
+ setTimeout(() => {
2217
+ toastContainer.classList.add("show");
2218
+ }, 10);
2219
+ toastTimeout = setTimeout(() => {
2220
+ toastContainer.classList.remove("show");
2221
+ }, duration);
2222
+ }
2223
+ if (document.readyState === "loading") {
2224
+ document.addEventListener("DOMContentLoaded", createToastElement);
2225
+ } else {
2226
+ createToastElement();
2227
+ }
2228
+ return {
2229
+ show
2230
+ };
2231
+ })();
2115
2232
  if (!useNewComponents) {
2116
2233
  return renderCardComponentLegacy({
2117
2234
  entityObject,
@@ -2184,10 +2301,31 @@ function renderCardComponentV2({
2184
2301
  const normalizedType = deviceType2?.toUpperCase() || "";
2185
2302
  return typeMap[normalizedType] || "generic";
2186
2303
  };
2187
- const determineUnit = (valType2, val2) => {
2304
+ const isEnergyDevice = (valType2, deviceType2) => {
2305
+ if (valType2 === "ENERGY") {
2306
+ return true;
2307
+ }
2308
+ const energyDeviceTypes = ["MOTOR", "3F_MEDIDOR", "RELOGIO", "ENTRADA"];
2309
+ const normalizedType = deviceType2?.toUpperCase() || "";
2310
+ return energyDeviceTypes.includes(normalizedType);
2311
+ };
2312
+ const formatCardValue = (value, valType2, deviceType2) => {
2313
+ const numValue = Number(value) || 0;
2314
+ if (isEnergyDevice(valType2, deviceType2)) {
2315
+ return formatEnergy(numValue);
2316
+ } else {
2317
+ const unit = determineUnit(valType2, numValue, deviceType2);
2318
+ const formattedValue = numValue.toLocaleString("pt-BR", {
2319
+ minimumFractionDigits: 0,
2320
+ maximumFractionDigits: 2
2321
+ });
2322
+ return `${formattedValue} ${unit}`;
2323
+ }
2324
+ };
2325
+ const determineUnit = (valType2, val2, deviceType2) => {
2188
2326
  switch (valType2) {
2189
2327
  case "ENERGY":
2190
- return "kWh";
2328
+ return "";
2191
2329
  case "WATER":
2192
2330
  return "m\xB3";
2193
2331
  case "TANK":
@@ -2485,7 +2623,7 @@ function renderCardComponentV2({
2485
2623
  <span class="flash-icon ${shouldFlashIcon ? "flash" : ""}">
2486
2624
  ${icon}
2487
2625
  </span>
2488
- <span class="consumption-value">${cardEntity.lastValue.toLocaleString("pt-BR", { minimumFractionDigits: 0, maximumFractionDigits: 2 })} ${cardEntity.unit}</span>
2626
+ <span class="consumption-value">${formatCardValue(cardEntity.lastValue, valType, deviceType)}</span>
2489
2627
  <span class="device-title-percent">(${perc.toFixed(1)}%)</span>
2490
2628
  </div>
2491
2629
  </div>
@@ -2938,6 +3076,14 @@ function renderCardComponentV2({
2938
3076
  checkbox.addEventListener("change", (e) => {
2939
3077
  e.stopPropagation();
2940
3078
  if (e.target.checked) {
3079
+ const currentCount = MyIOSelectionStore.getSelectedEntities().length;
3080
+ const isTryingToAdd = e.target.checked;
3081
+ if (isTryingToAdd && currentCount >= 6) {
3082
+ e.preventDefault();
3083
+ e.target.checked = false;
3084
+ MyIOToast.show("N\xE3o \xE9 poss\xEDvel selecionar mais de 6 itens.", "warning");
3085
+ return;
3086
+ }
2941
3087
  MyIOSelectionStore.add(entityId);
2942
3088
  } else {
2943
3089
  MyIOSelectionStore.remove(entityId);
@@ -4123,7 +4269,10 @@ var MyIOChartModalClass = class {
4123
4269
  const store = this._getSelectionStore();
4124
4270
  if (store) {
4125
4271
  store.on("comparison:open", (data) => this.open(data));
4126
- store.on("comparison:too_many", (data) => this._showTooManyEntitiesWarning(data));
4272
+ store.on(
4273
+ "comparison:too_many",
4274
+ (data) => this._showTooManyEntitiesWarning(data)
4275
+ );
4127
4276
  }
4128
4277
  }
4129
4278
  async _createModal() {
@@ -4148,74 +4297,166 @@ var MyIOChartModalClass = class {
4148
4297
  _generateModalHTML() {
4149
4298
  const { entities, totals, count } = this.currentData;
4150
4299
  return `
4151
- <div class="chart-modal-container">
4152
- <div class="chart-modal-header">
4153
- <h2 id="chart-modal-title">Comparativo de Dispositivos (${count} selecionados)</h2>
4154
- <button class="chart-modal-close" aria-label="Fechar modal">\xD7</button>
4300
+ <div class="chart-modal-container"
4301
+ style="
4302
+ background: rgba(20, 20, 20, 0.95);
4303
+ backdrop-filter: blur(10px);
4304
+ border: 1px solid rgba(255, 255, 255, 0.08);
4305
+ border-radius: 16px;
4306
+ padding: 20px;
4307
+ max-width: 900px;
4308
+ margin: auto;
4309
+ color: #fff;
4310
+ font-family: Arial, sans-serif;
4311
+ box-shadow: 0 8px 30px rgba(0,0,0,0.5);
4312
+ ">
4313
+
4314
+ <div class="chart-modal-header"
4315
+ style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
4316
+ <h2 id="chart-modal-title"
4317
+ style="font-size: 20px; font-weight: bold; margin: 0; color: #fff;">
4318
+ Comparativo de Dispositivos (${count} selecionados)
4319
+ </h2>
4320
+ <button class="chart-modal-close" aria-label="Fechar modal"
4321
+ style="background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 5px; transition: 0.3s;">
4322
+ \xD7
4323
+ </button>
4324
+ </div>
4325
+
4326
+ <div class="chart-modal-controls"
4327
+ style="display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 20px;">
4328
+
4329
+ <div class="chart-type-controls" style="flex: 1; min-width: 180px;">
4330
+ <label style="display: block; margin-bottom: 6px; color: rgba(255,255,255,0.8); font-size: 14px;">
4331
+ Tipo de Gr\xE1fico:
4332
+ </label>
4333
+ <select class="chart-type-select" aria-label="Selecionar tipo de gr\xE1fico"
4334
+ style="width: 100%; padding: 8px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.08);
4335
+ background: rgba(255,255,255,0.08); color: #fff; outline: none; cursor: pointer;">
4336
+ <option value="line" ${this.chartConfig.type === "line" ? "selected" : ""}>Linha</option>
4337
+ <option value="bar" ${this.chartConfig.type === "bar" ? "selected" : ""}>Barras</option>
4338
+ </select>
4155
4339
  </div>
4156
4340
 
4157
- <div class="chart-modal-controls">
4158
- <div class="chart-type-controls">
4159
- <label>Tipo de Gr\xE1fico:</label>
4160
- <select class="chart-type-select" aria-label="Selecionar tipo de gr\xE1fico">
4161
- <option value="line" ${this.chartConfig.type === "line" ? "selected" : ""}>Linha</option>
4162
- <option value="bar" ${this.chartConfig.type === "bar" ? "selected" : ""}>Barras</option>
4163
- </select>
4164
- </div>
4165
-
4166
- <div class="chart-range-controls">
4167
- <label>Per\xEDodo:</label>
4168
- <select class="chart-range-select" aria-label="Selecionar per\xEDodo">
4169
- <option value="7" ${this.chartConfig.timeRange === 7 ? "selected" : ""}>7 dias</option>
4170
- <option value="14" ${this.chartConfig.timeRange === 14 ? "selected" : ""}>14 dias</option>
4171
- <option value="30" ${this.chartConfig.timeRange === 30 ? "selected" : ""}>30 dias</option>
4172
- </select>
4173
- </div>
4174
-
4175
- <div class="chart-export-controls">
4176
- <button class="export-csv-btn">Exportar CSV</button>
4177
- <button class="export-png-btn">Exportar PNG</button>
4178
- <button class="export-pdf-btn">Exportar PDF</button>
4179
- </div>
4341
+ <div class="chart-range-controls" style="flex: 1; min-width: 180px;">
4342
+ <label style="display: block; margin-bottom: 6px; color: rgba(255,255,255,0.8); font-size: 14px;">
4343
+ Per\xEDodo:
4344
+ </label>
4345
+ <select class="chart-range-select" aria-label="Selecionar per\xEDodo"
4346
+ style="width: 100%; padding: 8px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.08);
4347
+ background: rgba(255,255,255,0.08); color: #fff; outline: none; cursor: pointer;">
4348
+ <option value="7" ${this.chartConfig.timeRange === 7 ? "selected" : ""}>7 dias</option>
4349
+ <option value="14" ${this.chartConfig.timeRange === 14 ? "selected" : ""}>14 dias</option>
4350
+ <option value="30" ${this.chartConfig.timeRange === 30 ? "selected" : ""}>30 dias</option>
4351
+ </select>
4180
4352
  </div>
4181
4353
 
4182
- <div class="chart-modal-body">
4183
- <div class="chart-container">
4184
- <canvas id="comparison-chart" aria-label="Gr\xE1fico comparativo de dispositivos"></canvas>
4185
- </div>
4186
-
4187
- <div class="chart-summary">
4188
- <h3>Resumo da Sele\xE7\xE3o</h3>
4189
- <div class="summary-grid">
4190
- ${this._generateSummaryHTML(totals)}
4191
- </div>
4192
- </div>
4354
+ <div class="chart-export-controls" style="display: flex; gap: 10px; align-items: end;">
4355
+ <button class="export-csv-btn"
4356
+ style="padding: 8px 14px; border-radius: 8px; border: none; cursor: pointer;
4357
+ background: rgba(255,255,255,0.08); color: #fff; transition: 0.3s;">
4358
+ Exportar CSV
4359
+ </button>
4360
+ <button class="export-png-btn"
4361
+ style="padding: 8px 14px; border-radius: 8px; border: none; cursor: pointer;
4362
+ background: rgba(255,255,255,0.08); color: #fff; transition: 0.3s;">
4363
+ Exportar PNG
4364
+ </button>
4365
+ <button class="export-pdf-btn"
4366
+ style="padding: 8px 14px; border-radius: 8px; border: none; cursor: pointer;
4367
+ background: rgba(255,255,255,0.08); color: #fff; transition: 0.3s;">
4368
+ Exportar PDF
4369
+ </button>
4370
+ </div>
4371
+ </div>
4372
+
4373
+ <div class="chart-modal-body" style="display: flex; flex-direction: column; gap: 20px;">
4374
+ <div class="chart-container"
4375
+ style="background: rgba(255,255,255,0.05); border-radius: 12px; padding: 15px;">
4376
+ <canvas id="comparison-chart" aria-label="Gr\xE1fico comparativo de dispositivos"></canvas>
4193
4377
  </div>
4194
4378
 
4195
- <div class="chart-modal-footer">
4196
- <button class="chart-modal-close-btn">Fechar</button>
4379
+ <div class="chart-summary"
4380
+ style="background: rgba(255,255,255,0.05); border-radius: 12px; padding: 15px;">
4381
+ <h3 style="margin: 0 0 12px; font-size: 16px; font-weight: bold;">Resumo da Sele\xE7\xE3o</h3>
4382
+ <div class="summary-grid"
4383
+ style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px;">
4384
+ ${this._generateSummaryHTML(totals)}
4385
+ </div>
4197
4386
  </div>
4198
4387
  </div>
4199
- `;
4388
+
4389
+ <div class="chart-modal-footer"
4390
+ style="display: flex; justify-content: flex-end; margin-top: 20px;">
4391
+ <button class="chart-modal-close-btn"
4392
+ style="padding: 10px 20px; border-radius: 8px; border: none; cursor: pointer;
4393
+ background: rgba(255,255,255,0.08); color: #fff; font-weight: bold; transition: 0.3s;">
4394
+ Fechar
4395
+ </button>
4396
+ </div>
4397
+ </div>
4398
+ `;
4399
+ }
4400
+ _generateSummaryHTML(totals) {
4401
+ const items = [];
4402
+ if (totals.energyKwh > 0) {
4403
+ items.push(`<div class="summary-item"
4404
+ style="background: rgba(255,255,255,0.08); padding: 10px; border-radius: 8px;">
4405
+ <span class="summary-label" style="display: block; font-size: 13px; color: rgba(255,255,255,0.7);">Energia Total:</span>
4406
+ <span class="summary-value" style="font-weight: bold; font-size: 14px;">${this._formatNumber(
4407
+ totals.energyKwh
4408
+ )} kWh</span>
4409
+ </div>`);
4410
+ }
4411
+ if (totals.waterM3 > 0) {
4412
+ items.push(`<div class="summary-item"
4413
+ style="background: rgba(255,255,255,0.08); padding: 10px; border-radius: 8px;">
4414
+ <span class="summary-label" style="display: block; font-size: 13px; color: rgba(255,255,255,0.7);">\xC1gua Total:</span>
4415
+ <span class="summary-value" style="font-weight: bold; font-size: 14px;">${this._formatNumber(
4416
+ totals.waterM3
4417
+ )} m\xB3</span>
4418
+ </div>`);
4419
+ }
4420
+ if (totals.tempC > 0) {
4421
+ items.push(`<div class="summary-item"
4422
+ style="background: rgba(255,255,255,0.08); padding: 10px; border-radius: 8px;">
4423
+ <span class="summary-label" style="display: block; font-size: 13px; color: rgba(255,255,255,0.7);">Temperatura M\xE9dia:</span>
4424
+ <span class="summary-value" style="font-weight: bold; font-size: 14px;">${this._formatNumber(
4425
+ totals.tempC / totals.count
4426
+ )} \xB0C</span>
4427
+ </div>`);
4428
+ }
4429
+ items.push(`<div class="summary-item"
4430
+ style="background: rgba(255,255,255,0.08); padding: 10px; border-radius: 8px;">
4431
+ <span class="summary-label" style="display: block; font-size: 13px; color: rgba(255,255,255,0.7);">Dispositivos:</span>
4432
+ <span class="summary-value" style="font-weight: bold; font-size: 14px;">${totals.count}</span>
4433
+ </div>`);
4434
+ return items.join("");
4200
4435
  }
4201
4436
  _generateSummaryHTML(totals) {
4202
4437
  const items = [];
4203
4438
  if (totals.energyKwh > 0) {
4204
4439
  items.push(`<div class="summary-item">
4205
4440
  <span class="summary-label">Energia Total:</span>
4206
- <span class="summary-value">${this._formatNumber(totals.energyKwh)} kWh</span>
4441
+ <span class="summary-value">${this._formatNumber(
4442
+ totals.energyKwh
4443
+ )} kWh</span>
4207
4444
  </div>`);
4208
4445
  }
4209
4446
  if (totals.waterM3 > 0) {
4210
4447
  items.push(`<div class="summary-item">
4211
4448
  <span class="summary-label">\xC1gua Total:</span>
4212
- <span class="summary-value">${this._formatNumber(totals.waterM3)} m\xB3</span>
4449
+ <span class="summary-value">${this._formatNumber(
4450
+ totals.waterM3
4451
+ )} m\xB3</span>
4213
4452
  </div>`);
4214
4453
  }
4215
4454
  if (totals.tempC > 0) {
4216
4455
  items.push(`<div class="summary-item">
4217
4456
  <span class="summary-label">Temperatura M\xE9dia:</span>
4218
- <span class="summary-value">${this._formatNumber(totals.tempC / totals.count)} \xB0C</span>
4457
+ <span class="summary-value">${this._formatNumber(
4458
+ totals.tempC / totals.count
4459
+ )} \xB0C</span>
4219
4460
  </div>`);
4220
4461
  }
4221
4462
  items.push(`<div class="summary-item">
@@ -4226,7 +4467,9 @@ var MyIOChartModalClass = class {
4226
4467
  }
4227
4468
  _attachModalEventListeners() {
4228
4469
  if (!this.modalElement) return;
4229
- const closeButtons = this.modalElement.querySelectorAll(".chart-modal-close, .chart-modal-close-btn");
4470
+ const closeButtons = this.modalElement.querySelectorAll(
4471
+ ".chart-modal-close, .chart-modal-close-btn"
4472
+ );
4230
4473
  closeButtons.forEach((btn) => {
4231
4474
  btn.addEventListener("click", () => this.close());
4232
4475
  });
@@ -4302,7 +4545,11 @@ var MyIOChartModalClass = class {
4302
4545
  startDate.setDate(startDate.getDate() - this.chartConfig.timeRange);
4303
4546
  let timeSeriesData = {};
4304
4547
  if (store && store.getTimeSeriesData) {
4305
- timeSeriesData = await store.getTimeSeriesData(entityIds, startDate, endDate);
4548
+ timeSeriesData = await store.getTimeSeriesData(
4549
+ entityIds,
4550
+ startDate,
4551
+ endDate
4552
+ );
4306
4553
  }
4307
4554
  const chartData = this._prepareChartData(timeSeriesData);
4308
4555
  this.chartInstance = new globalThis.Chart(canvas, {
@@ -7545,8 +7792,12 @@ var DEFAULT_STYLES = {
7545
7792
  };
7546
7793
  var CHART_JS_CDN = "https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js";
7547
7794
  var ZOOM_PLUGIN_CDN = "https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@2.0.1/dist/chartjs-plugin-zoom.min.js";
7795
+ var JSPDF_VERSION = "2.5.1";
7796
+ var JSPDF_CDN = `https://cdnjs.cloudflare.com/ajax/libs/jspdf/${JSPDF_VERSION}/jspdf.umd.min.js`;
7548
7797
  var chartJsLoaded = false;
7549
7798
  var zoomPluginLoaded = false;
7799
+ var jsPdfLoaded = false;
7800
+ var _jspdfPromise = null;
7550
7801
  var cssInjected = false;
7551
7802
  var STRINGS = {
7552
7803
  "pt-BR": {
@@ -7596,25 +7847,24 @@ async function loadScript(url, checkGlobal) {
7596
7847
  }
7597
7848
  const existingScript = document.querySelector(`script[src="${url}"]`);
7598
7849
  if (existingScript) {
7599
- setTimeout(() => {
7850
+ existingScript.addEventListener("load", () => {
7600
7851
  if (window[checkGlobal]) {
7601
7852
  resolve();
7602
7853
  } else {
7603
7854
  reject(new Error(`Library ${checkGlobal} not available after loading ${url}`));
7604
7855
  }
7605
- }, 500);
7856
+ });
7857
+ existingScript.addEventListener("error", () => reject(new Error(`Failed to load ${url}`)));
7606
7858
  return;
7607
7859
  }
7608
7860
  const script = document.createElement("script");
7609
7861
  script.src = url;
7610
7862
  script.onload = () => {
7611
- setTimeout(() => {
7612
- if (window[checkGlobal]) {
7613
- resolve();
7614
- } else {
7615
- reject(new Error(`Library ${checkGlobal} not available after loading ${url}`));
7616
- }
7617
- }, 200);
7863
+ if (window[checkGlobal]) {
7864
+ resolve();
7865
+ } else {
7866
+ reject(new Error(`Library ${checkGlobal} not available after loading ${url}`));
7867
+ }
7618
7868
  };
7619
7869
  script.onerror = () => reject(new Error(`Failed to load ${url}`));
7620
7870
  document.head.appendChild(script);
@@ -7630,10 +7880,90 @@ async function loadExternalLibraries() {
7630
7880
  await loadScript(ZOOM_PLUGIN_CDN, "ChartZoom");
7631
7881
  zoomPluginLoaded = true;
7632
7882
  }
7883
+ if (!jsPdfLoaded) {
7884
+ await ensureJsPDF();
7885
+ jsPdfLoaded = true;
7886
+ }
7633
7887
  } catch (error) {
7634
7888
  throw new Error(`Failed to load external libraries: ${error}`);
7635
7889
  }
7636
7890
  }
7891
+ function ensureJsPDF() {
7892
+ if (window.jspdf?.jsPDF) {
7893
+ console.info("jsPDF already loaded.");
7894
+ return Promise.resolve();
7895
+ }
7896
+ if (_jspdfPromise) {
7897
+ console.info("jsPDF loading already in progress.");
7898
+ return _jspdfPromise;
7899
+ }
7900
+ _jspdfPromise = new Promise((resolve, reject) => {
7901
+ const existing = document.querySelector('script[data-lib="jspdf"]');
7902
+ if (existing) {
7903
+ existing.addEventListener("load", () => {
7904
+ if (window.jspdf?.jsPDF) {
7905
+ console.info("jsPDF loaded via existing script.");
7906
+ resolve();
7907
+ } else {
7908
+ reject(new Error("jsPDF loaded but window.jspdf.jsPDF missing"));
7909
+ }
7910
+ });
7911
+ existing.addEventListener("error", () => reject(new Error("Failed to load jsPDF via existing script")));
7912
+ return;
7913
+ }
7914
+ const s = document.createElement("script");
7915
+ s.src = JSPDF_CDN;
7916
+ s.async = true;
7917
+ s.defer = true;
7918
+ s.dataset.lib = "jspdf";
7919
+ s.onload = () => {
7920
+ if (window.jspdf?.jsPDF) {
7921
+ console.info("jsPDF loaded successfully.");
7922
+ resolve();
7923
+ } else {
7924
+ reject(new Error("jsPDF loaded but window.jspdf.jsPDF missing"));
7925
+ }
7926
+ };
7927
+ s.onerror = () => reject(new Error("Failed to load jsPDF from CDN"));
7928
+ document.head.appendChild(s);
7929
+ }).finally(() => {
7930
+ _jspdfPromise = null;
7931
+ });
7932
+ return _jspdfPromise;
7933
+ }
7934
+ function getJsPDFCtor() {
7935
+ if (window.jspdf?.jsPDF) return window.jspdf.jsPDF;
7936
+ if (window.jsPDF?.jsPDF) return window.jsPDF.jsPDF;
7937
+ if (window.jsPDF) return window.jsPDF;
7938
+ throw new Error("jsPDF constructor not found on window");
7939
+ }
7940
+ function savePdfSafe(doc, filename) {
7941
+ try {
7942
+ doc.save(filename);
7943
+ } catch (e) {
7944
+ console.warn("doc.save() failed, attempting Blob URL fallback:", e);
7945
+ const blob = doc.output("blob");
7946
+ const url = URL.createObjectURL(blob);
7947
+ window.open(url, "_blank") || alert("Pop-up blocked. Allow pop-ups to download the PDF.");
7948
+ setTimeout(() => URL.revokeObjectURL(url), 3e4);
7949
+ }
7950
+ }
7951
+ function addCanvasToPdf(doc, canvas, x = 10, y = 20, maxWmm = 190) {
7952
+ const img = canvas.toDataURL("image/png", 1);
7953
+ const pageWmm = doc.internal.pageSize.getWidth();
7954
+ const mmW = Math.min(maxWmm, pageWmm - x * 2);
7955
+ const mmH = canvas.height / canvas.width * mmW;
7956
+ doc.addImage(img, "PNG", x, y, mmW, mmH, void 0, "FAST");
7957
+ return y + mmH;
7958
+ }
7959
+ function ensureRoom(doc, nextY, minRoom = 40) {
7960
+ const h = doc.internal.pageSize.getHeight();
7961
+ if (nextY + minRoom > h) {
7962
+ doc.addPage();
7963
+ return 20;
7964
+ }
7965
+ return nextY;
7966
+ }
7637
7967
  function injectCSS(styles) {
7638
7968
  if (cssInjected) return;
7639
7969
  const css = `
@@ -8120,18 +8450,72 @@ async function openDemandModal(params) {
8120
8450
  alert("Nenhum dado dispon\xEDvel para exportar");
8121
8451
  return;
8122
8452
  }
8453
+ const btn = pdfBtn;
8454
+ const originalHtml = btn.innerHTML;
8455
+ btn.disabled = true;
8456
+ btn.innerHTML = "<span>\u23F3</span> Gerando PDF...";
8123
8457
  try {
8124
- const chartImage = chartCanvas.toDataURL("image/png");
8125
- const link = document.createElement("a");
8126
- link.download = `demanda-${params.label || "dispositivo"}-${params.startDate}-${params.endDate}.png`;
8127
- link.href = chartImage;
8128
- document.body.appendChild(link);
8129
- link.click();
8130
- document.body.removeChild(link);
8131
- alert("\u{1F4CA} Gr\xE1fico exportado como imagem PNG!\n\nO export em PDF ser\xE1 implementado em uma pr\xF3xima vers\xE3o.");
8458
+ await ensureJsPDF();
8459
+ await document.fonts?.ready?.catch((e) => console.warn("Font loading interrupted or failed:", e));
8460
+ const JsPDF = getJsPDFCtor();
8461
+ const doc = new JsPDF("p", "mm", "a4");
8462
+ doc.setFontSize(20);
8463
+ doc.setTextColor(74, 20, 140);
8464
+ doc.text(strings.reportTitle, 20, 20);
8465
+ doc.setFontSize(14);
8466
+ doc.setTextColor(0, 0, 0);
8467
+ const label2 = params.label || "Dispositivo";
8468
+ doc.text(`${strings.title} - ${label2}`, 20, 35);
8469
+ const startDate = formatDate(new Date(params.startDate), params.locale || "pt-BR");
8470
+ const endDate = formatDate(new Date(params.endDate), params.locale || "pt-BR");
8471
+ doc.text(`${strings.period}: ${startDate} - ${endDate}`, 20, 45);
8472
+ let currentY = 55;
8473
+ if (chartData.globalPeak) {
8474
+ const peak = chartData.globalPeak;
8475
+ doc.setFontSize(12);
8476
+ doc.setTextColor(255, 152, 0);
8477
+ doc.text(
8478
+ `${strings.maximum}: ${peak.formattedValue} kW ${peak.key ? `(${peak.key}) ` : ""}${strings.at} ${peak.formattedTime}`,
8479
+ 20,
8480
+ currentY
8481
+ );
8482
+ currentY += 10;
8483
+ }
8484
+ currentY = ensureRoom(doc, currentY, 120);
8485
+ currentY = addCanvasToPdf(doc, chartCanvas, 20, currentY);
8486
+ currentY += 10;
8487
+ if (chartData.series.length > 0 && chartData.series[0].points.length > 0) {
8488
+ currentY = ensureRoom(doc, currentY, 60);
8489
+ doc.setFontSize(12);
8490
+ doc.setTextColor(0, 0, 0);
8491
+ doc.text("Amostra de Dados:", 20, currentY);
8492
+ currentY += 10;
8493
+ const samplePoints = chartData.series[0].points.slice(0, 10);
8494
+ doc.setFontSize(10);
8495
+ doc.text("Data/Hora", 20, currentY);
8496
+ doc.text(params.yAxisLabel || strings.demand, 100, currentY);
8497
+ currentY += 7;
8498
+ samplePoints.forEach((point) => {
8499
+ currentY = ensureRoom(doc, currentY, 10);
8500
+ const dateStr = formatDateTime(new Date(point.x), params.locale || "pt-BR");
8501
+ doc.text(dateStr, 20, currentY);
8502
+ doc.text(point.y.toFixed(2), 100, currentY);
8503
+ currentY += 7;
8504
+ });
8505
+ }
8506
+ currentY = ensureRoom(doc, currentY, 20);
8507
+ doc.setFontSize(8);
8508
+ doc.setTextColor(128, 128, 128);
8509
+ doc.text(`${strings.reportFooter}`, 20, doc.internal.pageSize.getHeight() - 15);
8510
+ doc.text(`Gerado em: ${(/* @__PURE__ */ new Date()).toLocaleString(params.locale || "pt-BR")}`, 20, doc.internal.pageSize.getHeight() - 10);
8511
+ const fileName = params.pdf?.fileName || `demanda_${label2.replace(/\s+/g, "_")}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.pdf`;
8512
+ savePdfSafe(doc, fileName);
8132
8513
  } catch (error) {
8133
- console.error("Image export failed:", error);
8134
- alert("Erro ao exportar gr\xE1fico. Tente novamente.");
8514
+ console.error("[PDF Export] Error:", error);
8515
+ alert("Erro ao gerar PDF. Por favor, tente novamente. Verifique o console para mais detalhes.");
8516
+ } finally {
8517
+ btn.disabled = false;
8518
+ btn.innerHTML = originalHtml;
8135
8519
  }
8136
8520
  }
8137
8521
  closeBtn.addEventListener("click", closeModal);