myio-js-library 0.1.133 → 0.1.135

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
@@ -643,6 +643,7 @@ __export(index_exports, {
643
643
  openDashboardPopupWaterTank: () => openDashboardPopupWaterTank,
644
644
  openDemandModal: () => openDemandModal,
645
645
  openGoalsPanel: () => openGoalsPanel,
646
+ openRealTimeTelemetryModal: () => openRealTimeTelemetryModal,
646
647
  parseInputDateToDate: () => parseInputDateToDate,
647
648
  renderCardComponent: () => renderCardComponent,
648
649
  renderCardComponentEnhanced: () => renderCardComponent2,
@@ -7070,6 +7071,632 @@ if (typeof module !== "undefined" && module.exports) {
7070
7071
  module.exports = { MyIOToast };
7071
7072
  }
7072
7073
 
7074
+ // src/components/RealTimeTelemetryModal.ts
7075
+ var TELEMETRY_CONFIG = {
7076
+ voltage: { label: "Tens\xE3o", unit: "V", icon: "\u26A1", decimals: 1 },
7077
+ current: { label: "Corrente", unit: "A", icon: "\u{1F50C}", decimals: 2 },
7078
+ power: { label: "Pot\xEAncia", unit: "kW", icon: "\u2699\uFE0F", decimals: 2 },
7079
+ energy: { label: "Energia", unit: "kWh", icon: "\u{1F4CA}", decimals: 1 },
7080
+ temperature: { label: "Temperatura", unit: "\xB0C", icon: "\u{1F321}\uFE0F", decimals: 1 },
7081
+ activePower: { label: "Pot\xEAncia Ativa", unit: "kW", icon: "\u2699\uFE0F", decimals: 2 },
7082
+ reactivePower: { label: "Pot\xEAncia Reativa", unit: "kVAr", icon: "\u{1F504}", decimals: 2 },
7083
+ apparentPower: { label: "Pot\xEAncia Aparente", unit: "kVA", icon: "\u{1F4C8}", decimals: 2 },
7084
+ powerFactor: { label: "Fator de Pot\xEAncia", unit: "", icon: "\u{1F4D0}", decimals: 3 }
7085
+ };
7086
+ var STRINGS = {
7087
+ "pt-BR": {
7088
+ title: "Telemetrias em Tempo Real",
7089
+ close: "Fechar",
7090
+ pause: "Pausar",
7091
+ resume: "Retomar",
7092
+ export: "Exportar CSV",
7093
+ autoUpdate: "Atualiza\xE7\xE3o autom\xE1tica",
7094
+ lastUpdate: "\xDAltima atualiza\xE7\xE3o",
7095
+ noData: "Sem dados",
7096
+ loading: "Carregando...",
7097
+ error: "Erro ao carregar telemetrias",
7098
+ trend_up: "Aumentando",
7099
+ trend_down: "Diminuindo",
7100
+ trend_stable: "Est\xE1vel"
7101
+ },
7102
+ "en-US": {
7103
+ title: "Real-Time Telemetry",
7104
+ close: "Close",
7105
+ pause: "Pause",
7106
+ resume: "Resume",
7107
+ export: "Export CSV",
7108
+ autoUpdate: "Auto-update",
7109
+ lastUpdate: "Last update",
7110
+ noData: "No data",
7111
+ loading: "Loading...",
7112
+ error: "Error loading telemetry",
7113
+ trend_up: "Increasing",
7114
+ trend_down: "Decreasing",
7115
+ trend_stable: "Stable"
7116
+ }
7117
+ };
7118
+ async function openRealTimeTelemetryModal(params) {
7119
+ const {
7120
+ token,
7121
+ deviceId,
7122
+ deviceLabel = "Dispositivo",
7123
+ telemetryKeys = ["voltage", "current", "power", "energy"],
7124
+ refreshInterval = 8e3,
7125
+ historyPoints = 50,
7126
+ onClose,
7127
+ locale = "pt-BR"
7128
+ } = params;
7129
+ const strings = STRINGS[locale] || STRINGS["pt-BR"];
7130
+ let refreshIntervalId = null;
7131
+ let isPaused = false;
7132
+ let telemetryHistory = /* @__PURE__ */ new Map();
7133
+ let chart = null;
7134
+ const overlay = document.createElement("div");
7135
+ overlay.className = "myio-realtime-telemetry-overlay";
7136
+ overlay.innerHTML = `
7137
+ <style>
7138
+ .myio-realtime-telemetry-overlay {
7139
+ position: fixed;
7140
+ top: 0;
7141
+ left: 0;
7142
+ right: 0;
7143
+ bottom: 0;
7144
+ background: rgba(0, 0, 0, 0.5);
7145
+ display: flex;
7146
+ align-items: center;
7147
+ justify-content: center;
7148
+ z-index: 10000;
7149
+ padding: 20px;
7150
+ animation: fadeIn 0.2s ease;
7151
+ }
7152
+
7153
+ @keyframes fadeIn {
7154
+ from { opacity: 0; }
7155
+ to { opacity: 1; }
7156
+ }
7157
+
7158
+ .myio-realtime-telemetry-container {
7159
+ background: #ffffff;
7160
+ border-radius: 12px;
7161
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
7162
+ max-width: 1200px;
7163
+ width: 100%;
7164
+ max-height: 90vh;
7165
+ display: flex;
7166
+ flex-direction: column;
7167
+ overflow: hidden;
7168
+ animation: slideUp 0.3s ease;
7169
+ }
7170
+
7171
+ @keyframes slideUp {
7172
+ from { transform: translateY(20px); opacity: 0; }
7173
+ to { transform: translateY(0); opacity: 1; }
7174
+ }
7175
+
7176
+ .myio-realtime-telemetry-header {
7177
+ padding: 20px 24px;
7178
+ border-bottom: 1px solid #e0e0e0;
7179
+ display: flex;
7180
+ justify-content: space-between;
7181
+ align-items: center;
7182
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
7183
+ color: white;
7184
+ }
7185
+
7186
+ .myio-realtime-telemetry-title {
7187
+ font-size: 20px;
7188
+ font-weight: 600;
7189
+ margin: 0;
7190
+ display: flex;
7191
+ align-items: center;
7192
+ gap: 8px;
7193
+ }
7194
+
7195
+ .myio-realtime-telemetry-close {
7196
+ background: rgba(255, 255, 255, 0.2);
7197
+ border: none;
7198
+ color: white;
7199
+ font-size: 24px;
7200
+ width: 32px;
7201
+ height: 32px;
7202
+ border-radius: 6px;
7203
+ cursor: pointer;
7204
+ display: flex;
7205
+ align-items: center;
7206
+ justify-content: center;
7207
+ transition: all 0.2s ease;
7208
+ }
7209
+
7210
+ .myio-realtime-telemetry-close:hover {
7211
+ background: rgba(255, 255, 255, 0.3);
7212
+ transform: scale(1.1);
7213
+ }
7214
+
7215
+ .myio-realtime-telemetry-body {
7216
+ padding: 24px;
7217
+ overflow-y: auto;
7218
+ flex: 1;
7219
+ }
7220
+
7221
+ .myio-telemetry-cards-grid {
7222
+ display: grid;
7223
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
7224
+ gap: 16px;
7225
+ margin-bottom: 24px;
7226
+ }
7227
+
7228
+ .myio-telemetry-card {
7229
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
7230
+ border-radius: 12px;
7231
+ padding: 16px;
7232
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
7233
+ transition: all 0.3s ease;
7234
+ }
7235
+
7236
+ .myio-telemetry-card:hover {
7237
+ transform: translateY(-4px);
7238
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
7239
+ }
7240
+
7241
+ .myio-telemetry-card-header {
7242
+ display: flex;
7243
+ align-items: center;
7244
+ gap: 8px;
7245
+ margin-bottom: 12px;
7246
+ font-size: 14px;
7247
+ color: #555;
7248
+ font-weight: 500;
7249
+ }
7250
+
7251
+ .myio-telemetry-card-icon {
7252
+ font-size: 20px;
7253
+ }
7254
+
7255
+ .myio-telemetry-card-value {
7256
+ font-size: 28px;
7257
+ font-weight: 700;
7258
+ color: #2c3e50;
7259
+ margin-bottom: 4px;
7260
+ }
7261
+
7262
+ .myio-telemetry-card-trend {
7263
+ font-size: 12px;
7264
+ color: #777;
7265
+ display: flex;
7266
+ align-items: center;
7267
+ gap: 4px;
7268
+ }
7269
+
7270
+ .myio-telemetry-card-trend.up {
7271
+ color: #27ae60;
7272
+ }
7273
+
7274
+ .myio-telemetry-card-trend.down {
7275
+ color: #e74c3c;
7276
+ }
7277
+
7278
+ .myio-telemetry-chart-container {
7279
+ background: white;
7280
+ border-radius: 12px;
7281
+ padding: 20px;
7282
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
7283
+ margin-bottom: 20px;
7284
+ }
7285
+
7286
+ .myio-telemetry-chart-title {
7287
+ font-size: 16px;
7288
+ font-weight: 600;
7289
+ margin: 0 0 16px 0;
7290
+ color: #2c3e50;
7291
+ }
7292
+
7293
+ .myio-telemetry-chart {
7294
+ height: 200px;
7295
+ }
7296
+
7297
+ .myio-realtime-telemetry-footer {
7298
+ padding: 16px 24px;
7299
+ border-top: 1px solid #e0e0e0;
7300
+ display: flex;
7301
+ justify-content: space-between;
7302
+ align-items: center;
7303
+ background: #f8f9fa;
7304
+ }
7305
+
7306
+ .myio-telemetry-status {
7307
+ display: flex;
7308
+ align-items: center;
7309
+ gap: 12px;
7310
+ font-size: 14px;
7311
+ color: #555;
7312
+ }
7313
+
7314
+ .myio-telemetry-status-indicator {
7315
+ width: 8px;
7316
+ height: 8px;
7317
+ border-radius: 50%;
7318
+ background: #27ae60;
7319
+ animation: pulse 2s infinite;
7320
+ }
7321
+
7322
+ .myio-telemetry-status-indicator.paused {
7323
+ background: #e74c3c;
7324
+ animation: none;
7325
+ }
7326
+
7327
+ @keyframes pulse {
7328
+ 0%, 100% { opacity: 1; }
7329
+ 50% { opacity: 0.5; }
7330
+ }
7331
+
7332
+ .myio-telemetry-actions {
7333
+ display: flex;
7334
+ gap: 12px;
7335
+ }
7336
+
7337
+ .myio-telemetry-btn {
7338
+ padding: 8px 16px;
7339
+ border: none;
7340
+ border-radius: 6px;
7341
+ font-size: 14px;
7342
+ font-weight: 500;
7343
+ cursor: pointer;
7344
+ transition: all 0.2s ease;
7345
+ display: flex;
7346
+ align-items: center;
7347
+ gap: 6px;
7348
+ }
7349
+
7350
+ .myio-telemetry-btn-primary {
7351
+ background: #667eea;
7352
+ color: white;
7353
+ }
7354
+
7355
+ .myio-telemetry-btn-primary:hover {
7356
+ background: #5568d3;
7357
+ transform: translateY(-1px);
7358
+ }
7359
+
7360
+ .myio-telemetry-btn-secondary {
7361
+ background: #e0e0e0;
7362
+ color: #333;
7363
+ }
7364
+
7365
+ .myio-telemetry-btn-secondary:hover {
7366
+ background: #d0d0d0;
7367
+ transform: translateY(-1px);
7368
+ }
7369
+
7370
+ .myio-telemetry-loading {
7371
+ text-align: center;
7372
+ padding: 40px;
7373
+ color: #999;
7374
+ font-size: 16px;
7375
+ }
7376
+
7377
+ .myio-telemetry-error {
7378
+ text-align: center;
7379
+ padding: 40px;
7380
+ color: #e74c3c;
7381
+ font-size: 16px;
7382
+ }
7383
+ </style>
7384
+
7385
+ <div class="myio-realtime-telemetry-container">
7386
+ <div class="myio-realtime-telemetry-header">
7387
+ <h2 class="myio-realtime-telemetry-title">
7388
+ \u26A1 ${strings.title} - ${deviceLabel}
7389
+ </h2>
7390
+ <button class="myio-realtime-telemetry-close" id="close-btn" title="${strings.close}">
7391
+ \xD7
7392
+ </button>
7393
+ </div>
7394
+
7395
+ <div class="myio-realtime-telemetry-body">
7396
+ <div class="myio-telemetry-loading" id="loading-state">
7397
+ ${strings.loading}
7398
+ </div>
7399
+
7400
+ <div id="telemetry-content" style="display: none;">
7401
+ <div class="myio-telemetry-cards-grid" id="telemetry-cards"></div>
7402
+
7403
+ <div class="myio-telemetry-chart-container" id="chart-container" style="display: none;">
7404
+ <h3 class="myio-telemetry-chart-title" id="chart-title">Pot\xEAncia (\xFAltimos 5 min)</h3>
7405
+ <canvas class="myio-telemetry-chart" id="telemetry-chart"></canvas>
7406
+ </div>
7407
+ </div>
7408
+
7409
+ <div class="myio-telemetry-error" id="error-state" style="display: none;">
7410
+ ${strings.error}
7411
+ </div>
7412
+ </div>
7413
+
7414
+ <div class="myio-realtime-telemetry-footer">
7415
+ <div class="myio-telemetry-status">
7416
+ <span class="myio-telemetry-status-indicator" id="status-indicator"></span>
7417
+ <span id="status-text">${strings.autoUpdate}: ON</span>
7418
+ <span>\u2022</span>
7419
+ <span id="last-update-text">${strings.lastUpdate}: --:--:--</span>
7420
+ </div>
7421
+
7422
+ <div class="myio-telemetry-actions">
7423
+ <button class="myio-telemetry-btn myio-telemetry-btn-secondary" id="pause-btn">
7424
+ <span id="pause-btn-icon">\u23F8\uFE0F</span>
7425
+ <span id="pause-btn-text">${strings.pause}</span>
7426
+ </button>
7427
+ <button class="myio-telemetry-btn myio-telemetry-btn-primary" id="export-btn">
7428
+ \u2B07\uFE0F ${strings.export}
7429
+ </button>
7430
+ </div>
7431
+ </div>
7432
+ </div>
7433
+ `;
7434
+ document.body.appendChild(overlay);
7435
+ const closeBtn = document.getElementById("close-btn");
7436
+ const pauseBtn = document.getElementById("pause-btn");
7437
+ const pauseBtnIcon = document.getElementById("pause-btn-icon");
7438
+ const pauseBtnText = document.getElementById("pause-btn-text");
7439
+ const exportBtn = document.getElementById("export-btn");
7440
+ const loadingState = document.getElementById("loading-state");
7441
+ const telemetryContent = document.getElementById("telemetry-content");
7442
+ const errorState = document.getElementById("error-state");
7443
+ const telemetryCards = document.getElementById("telemetry-cards");
7444
+ const chartContainer = document.getElementById("chart-container");
7445
+ const chartCanvas = document.getElementById("telemetry-chart");
7446
+ const statusIndicator = document.getElementById("status-indicator");
7447
+ const statusText = document.getElementById("status-text");
7448
+ const lastUpdateText = document.getElementById("last-update-text");
7449
+ function closeModal() {
7450
+ if (refreshIntervalId) {
7451
+ clearInterval(refreshIntervalId);
7452
+ refreshIntervalId = null;
7453
+ }
7454
+ if (chart) {
7455
+ chart.destroy();
7456
+ chart = null;
7457
+ }
7458
+ overlay.remove();
7459
+ if (onClose) {
7460
+ onClose();
7461
+ }
7462
+ }
7463
+ async function fetchLatestTelemetry() {
7464
+ const keys = telemetryKeys.join(",");
7465
+ const url = `/api/plugins/telemetry/DEVICE/${deviceId}/values/timeseries?keys=${keys}&limit=1&agg=NONE`;
7466
+ const response = await fetch(url, {
7467
+ headers: {
7468
+ "X-Authorization": `Bearer ${token}`
7469
+ }
7470
+ });
7471
+ if (!response.ok) {
7472
+ throw new Error(`Failed to fetch telemetry: ${response.statusText}`);
7473
+ }
7474
+ return await response.json();
7475
+ }
7476
+ function processTelemetryData(data) {
7477
+ const values = [];
7478
+ for (const key of telemetryKeys) {
7479
+ const telemetryData = data[key];
7480
+ if (!telemetryData || telemetryData.length === 0) continue;
7481
+ const latest = telemetryData[0];
7482
+ const config = TELEMETRY_CONFIG[key] || { label: key, unit: "", icon: "\u{1F4CA}", decimals: 2 };
7483
+ const numValue = Number(latest.value) || 0;
7484
+ const formatted = numValue.toFixed(config.decimals);
7485
+ values.push({
7486
+ key,
7487
+ value: numValue,
7488
+ timestamp: latest.ts,
7489
+ formatted: `${formatted} ${config.unit}`,
7490
+ unit: config.unit,
7491
+ icon: config.icon,
7492
+ label: config.label,
7493
+ trend: "stable"
7494
+ // Will be calculated based on history
7495
+ });
7496
+ }
7497
+ return values;
7498
+ }
7499
+ function calculateTrend(key, currentValue) {
7500
+ const history = telemetryHistory.get(key);
7501
+ if (!history || history.length < 2) return "stable";
7502
+ const previousValue = history[history.length - 2].y;
7503
+ const diff = currentValue - previousValue;
7504
+ const threshold = previousValue * 0.02;
7505
+ if (diff > threshold) return "up";
7506
+ if (diff < -threshold) return "down";
7507
+ return "stable";
7508
+ }
7509
+ function updateTelemetryCards(values) {
7510
+ telemetryCards.innerHTML = values.map((tel) => {
7511
+ const trend = calculateTrend(tel.key, tel.value);
7512
+ const trendIcon = trend === "up" ? "\u2197" : trend === "down" ? "\u2198" : "\u2192";
7513
+ const trendClass = trend;
7514
+ const trendLabel = strings[`trend_${trend}`] || "";
7515
+ return `
7516
+ <div class="myio-telemetry-card">
7517
+ <div class="myio-telemetry-card-header">
7518
+ <span class="myio-telemetry-card-icon">${tel.icon}</span>
7519
+ <span>${tel.label}</span>
7520
+ </div>
7521
+ <div class="myio-telemetry-card-value">${tel.formatted}</div>
7522
+ <div class="myio-telemetry-card-trend ${trendClass}">
7523
+ ${trendIcon} ${trendLabel}
7524
+ </div>
7525
+ </div>
7526
+ `;
7527
+ }).join("");
7528
+ }
7529
+ function updateHistory(values) {
7530
+ const now = Date.now();
7531
+ for (const tel of values) {
7532
+ if (!telemetryHistory.has(tel.key)) {
7533
+ telemetryHistory.set(tel.key, []);
7534
+ }
7535
+ const history = telemetryHistory.get(tel.key);
7536
+ history.push({ x: now, y: tel.value });
7537
+ if (history.length > historyPoints) {
7538
+ history.shift();
7539
+ }
7540
+ }
7541
+ if (telemetryHistory.has("power") && chart) {
7542
+ const powerHistory = telemetryHistory.get("power");
7543
+ chart.data.datasets[0].data = powerHistory;
7544
+ chart.update("none");
7545
+ }
7546
+ }
7547
+ function initializeChart() {
7548
+ const Chart = window.Chart;
7549
+ if (!Chart) {
7550
+ console.warn("[RealTimeTelemetry] Chart.js not loaded");
7551
+ return;
7552
+ }
7553
+ chartContainer.style.display = "block";
7554
+ chart = new Chart(chartCanvas, {
7555
+ type: "line",
7556
+ data: {
7557
+ datasets: [{
7558
+ label: "Pot\xEAncia",
7559
+ data: [],
7560
+ borderColor: "#667eea",
7561
+ backgroundColor: "rgba(102, 126, 234, 0.1)",
7562
+ borderWidth: 2,
7563
+ fill: true,
7564
+ tension: 0.4,
7565
+ pointRadius: 0
7566
+ }]
7567
+ },
7568
+ options: {
7569
+ responsive: true,
7570
+ maintainAspectRatio: false,
7571
+ plugins: {
7572
+ legend: { display: false },
7573
+ tooltip: {
7574
+ callbacks: {
7575
+ title: function(context) {
7576
+ const timestamp = context[0].parsed.x;
7577
+ const date = new Date(timestamp);
7578
+ return date.toLocaleTimeString(locale);
7579
+ },
7580
+ label: function(context) {
7581
+ return `${context.parsed.y.toFixed(2)} kW`;
7582
+ }
7583
+ }
7584
+ }
7585
+ },
7586
+ scales: {
7587
+ x: {
7588
+ type: "linear",
7589
+ ticks: {
7590
+ callback: function(value) {
7591
+ const date = new Date(value);
7592
+ return date.toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" });
7593
+ }
7594
+ }
7595
+ },
7596
+ y: {
7597
+ beginAtZero: true,
7598
+ title: {
7599
+ display: true,
7600
+ text: "kW"
7601
+ }
7602
+ }
7603
+ }
7604
+ }
7605
+ });
7606
+ }
7607
+ async function refreshData() {
7608
+ try {
7609
+ const data = await fetchLatestTelemetry();
7610
+ const values = processTelemetryData(data);
7611
+ updateHistory(values);
7612
+ updateTelemetryCards(values);
7613
+ const now = /* @__PURE__ */ new Date();
7614
+ lastUpdateText.textContent = `${strings.lastUpdate}: ${now.toLocaleTimeString(locale)}`;
7615
+ if (loadingState.style.display !== "none") {
7616
+ loadingState.style.display = "none";
7617
+ telemetryContent.style.display = "block";
7618
+ if (telemetryKeys.includes("power")) {
7619
+ initializeChart();
7620
+ }
7621
+ }
7622
+ } catch (error) {
7623
+ console.error("[RealTimeTelemetry] Error fetching data:", error);
7624
+ errorState.style.display = "block";
7625
+ loadingState.style.display = "none";
7626
+ telemetryContent.style.display = "none";
7627
+ }
7628
+ }
7629
+ function togglePause() {
7630
+ isPaused = !isPaused;
7631
+ if (isPaused) {
7632
+ if (refreshIntervalId) {
7633
+ clearInterval(refreshIntervalId);
7634
+ refreshIntervalId = null;
7635
+ }
7636
+ pauseBtnIcon.textContent = "\u25B6\uFE0F";
7637
+ pauseBtnText.textContent = strings.resume;
7638
+ statusIndicator.classList.add("paused");
7639
+ statusText.textContent = `${strings.autoUpdate}: OFF`;
7640
+ } else {
7641
+ refreshIntervalId = window.setInterval(refreshData, refreshInterval);
7642
+ pauseBtnIcon.textContent = "\u23F8\uFE0F";
7643
+ pauseBtnText.textContent = strings.pause;
7644
+ statusIndicator.classList.remove("paused");
7645
+ statusText.textContent = `${strings.autoUpdate}: ON`;
7646
+ }
7647
+ }
7648
+ function exportToCSV2() {
7649
+ const rows = [];
7650
+ rows.push("Timestamp," + telemetryKeys.map((k) => TELEMETRY_CONFIG[k]?.label || k).join(","));
7651
+ let maxLength = 0;
7652
+ for (const key of telemetryKeys) {
7653
+ const history = telemetryHistory.get(key);
7654
+ if (history && history.length > maxLength) {
7655
+ maxLength = history.length;
7656
+ }
7657
+ }
7658
+ for (let i = 0; i < maxLength; i++) {
7659
+ const row = [];
7660
+ let timestamp = "";
7661
+ for (const key of telemetryKeys) {
7662
+ const history = telemetryHistory.get(key);
7663
+ if (history && history[i]) {
7664
+ if (!timestamp) {
7665
+ timestamp = new Date(history[i].x).toISOString();
7666
+ }
7667
+ row.push(history[i].y.toFixed(2));
7668
+ } else {
7669
+ row.push("");
7670
+ }
7671
+ }
7672
+ if (timestamp) {
7673
+ rows.push(timestamp + "," + row.join(","));
7674
+ }
7675
+ }
7676
+ const csv = rows.join("\n");
7677
+ const blob = new Blob([csv], { type: "text/csv" });
7678
+ const url = URL.createObjectURL(blob);
7679
+ const a = document.createElement("a");
7680
+ a.href = url;
7681
+ a.download = `telemetry_${deviceLabel}_${(/* @__PURE__ */ new Date()).toISOString()}.csv`;
7682
+ a.click();
7683
+ URL.revokeObjectURL(url);
7684
+ }
7685
+ closeBtn.addEventListener("click", closeModal);
7686
+ pauseBtn.addEventListener("click", togglePause);
7687
+ exportBtn.addEventListener("click", exportToCSV2);
7688
+ overlay.addEventListener("click", (e) => {
7689
+ if (e.target === overlay) {
7690
+ closeModal();
7691
+ }
7692
+ });
7693
+ await refreshData();
7694
+ refreshIntervalId = window.setInterval(refreshData, refreshInterval);
7695
+ return {
7696
+ destroy: closeModal
7697
+ };
7698
+ }
7699
+
7073
7700
  // src/components/premium-modals/internal/styles/tokens.ts
7074
7701
  var CSS_TOKENS = `
7075
7702
  :root {
@@ -8511,7 +9138,7 @@ var zoomPluginLoaded = false;
8511
9138
  var jsPdfLoaded = false;
8512
9139
  var _jspdfPromise = null;
8513
9140
  var cssInjected = false;
8514
- var STRINGS = {
9141
+ var STRINGS2 = {
8515
9142
  "pt-BR": {
8516
9143
  title: "Demanda",
8517
9144
  period: "Per\xEDodo",
@@ -9098,6 +9725,32 @@ function formatDateTime(date, locale) {
9098
9725
  year: "numeric"
9099
9726
  });
9100
9727
  }
9728
+ function interpolateTemperatureData(rawPoints) {
9729
+ if (rawPoints.length === 0) return [];
9730
+ const interpolated = [];
9731
+ const interval = 30 * 60 * 1e3;
9732
+ const sorted = [...rawPoints].sort((a, b) => a.x - b.x);
9733
+ const startTime = sorted[0].x;
9734
+ const endTime = sorted[sorted.length - 1].x;
9735
+ let lastKnownValue = sorted[0].y;
9736
+ let dataIndex = 0;
9737
+ for (let time = startTime; time <= endTime; time += interval) {
9738
+ const actualPoint = sorted.find((p, idx) => {
9739
+ if (idx >= dataIndex && Math.abs(p.x - time) < 5 * 60 * 1e3) {
9740
+ dataIndex = idx + 1;
9741
+ return true;
9742
+ }
9743
+ return false;
9744
+ });
9745
+ if (actualPoint) {
9746
+ lastKnownValue = actualPoint.y;
9747
+ interpolated.push({ x: time, y: lastKnownValue });
9748
+ } else {
9749
+ interpolated.push({ x: time, y: lastKnownValue });
9750
+ }
9751
+ }
9752
+ return interpolated;
9753
+ }
9101
9754
  async function fetchTelemetryData(token, deviceId, startDate, endDate, queryParams) {
9102
9755
  const startTs = new Date(startDate).getTime();
9103
9756
  const endTs = new Date(endDate).getTime();
@@ -9236,7 +9889,7 @@ async function openDemandModal(params) {
9236
9889
  }
9237
9890
  const styles = { ...DEFAULT_STYLES, ...params.styles };
9238
9891
  const locale = params.locale || "pt-BR";
9239
- const strings = STRINGS[locale] || STRINGS["pt-BR"];
9892
+ const strings = STRINGS2[locale] || STRINGS2["pt-BR"];
9240
9893
  await loadExternalLibraries();
9241
9894
  injectCSS(styles);
9242
9895
  const container = typeof params.container === "string" ? document.querySelector(params.container) : params.container || document.body;
@@ -9810,6 +10463,12 @@ async function openDemandModal(params) {
9810
10463
  params.timezoneOffset
9811
10464
  // Pass timezone offset (default: -3)
9812
10465
  );
10466
+ if (params.readingType === "temperature" && !chartData.isEmpty) {
10467
+ chartData.series = chartData.series.map((series) => ({
10468
+ ...series,
10469
+ points: interpolateTemperatureData(series.points)
10470
+ }));
10471
+ }
9813
10472
  if (chartData.isEmpty) {
9814
10473
  errorEl.style.display = "flex";
9815
10474
  errorText.textContent = strings.noData;
@@ -9841,11 +10500,21 @@ async function openDemandModal(params) {
9841
10500
  title: function(context) {
9842
10501
  const timestamp = context[0].parsed.x;
9843
10502
  const date = new Date(timestamp);
9844
- return date.toLocaleDateString(locale, {
9845
- day: "2-digit",
9846
- month: "2-digit",
9847
- year: "numeric"
9848
- });
10503
+ if (params.readingType === "temperature") {
10504
+ return date.toLocaleString(locale, {
10505
+ day: "2-digit",
10506
+ month: "2-digit",
10507
+ year: "numeric",
10508
+ hour: "2-digit",
10509
+ minute: "2-digit"
10510
+ });
10511
+ } else {
10512
+ return date.toLocaleDateString(locale, {
10513
+ day: "2-digit",
10514
+ month: "2-digit",
10515
+ year: "numeric"
10516
+ });
10517
+ }
9849
10518
  },
9850
10519
  label: function(context) {
9851
10520
  const seriesLabel = context.dataset.label || "";
@@ -9853,6 +10522,20 @@ async function openDemandModal(params) {
9853
10522
  return `${seriesLabel}: ${value.toFixed(2)} kW`;
9854
10523
  }
9855
10524
  };
10525
+ chart.options.scales.x.ticks.callback = function(value) {
10526
+ const date = new Date(value);
10527
+ if (params.readingType === "temperature") {
10528
+ return date.toLocaleTimeString(locale, {
10529
+ hour: "2-digit",
10530
+ minute: "2-digit"
10531
+ });
10532
+ } else {
10533
+ return date.toLocaleDateString(locale, {
10534
+ day: "2-digit",
10535
+ month: "2-digit"
10536
+ });
10537
+ }
10538
+ };
9856
10539
  chart.update();
9857
10540
  } else {
9858
10541
  chart = new Chart(chartCanvas, {
@@ -9884,11 +10567,21 @@ async function openDemandModal(params) {
9884
10567
  title: function(context) {
9885
10568
  const timestamp = context[0].parsed.x;
9886
10569
  const date = new Date(timestamp);
9887
- return date.toLocaleDateString(locale, {
9888
- day: "2-digit",
9889
- month: "2-digit",
9890
- year: "numeric"
9891
- });
10570
+ if (params.readingType === "temperature") {
10571
+ return date.toLocaleString(locale, {
10572
+ day: "2-digit",
10573
+ month: "2-digit",
10574
+ year: "numeric",
10575
+ hour: "2-digit",
10576
+ minute: "2-digit"
10577
+ });
10578
+ } else {
10579
+ return date.toLocaleDateString(locale, {
10580
+ day: "2-digit",
10581
+ month: "2-digit",
10582
+ year: "numeric"
10583
+ });
10584
+ }
9892
10585
  },
9893
10586
  label: function(context) {
9894
10587
  const seriesLabel = context.dataset.label || "";
@@ -9922,10 +10615,17 @@ async function openDemandModal(params) {
9922
10615
  ticks: {
9923
10616
  callback: function(value) {
9924
10617
  const date = new Date(value);
9925
- return date.toLocaleDateString(locale, {
9926
- day: "2-digit",
9927
- month: "2-digit"
9928
- });
10618
+ if (params.readingType === "temperature") {
10619
+ return date.toLocaleTimeString(locale, {
10620
+ hour: "2-digit",
10621
+ minute: "2-digit"
10622
+ });
10623
+ } else {
10624
+ return date.toLocaleDateString(locale, {
10625
+ day: "2-digit",
10626
+ month: "2-digit"
10627
+ });
10628
+ }
9929
10629
  }
9930
10630
  }
9931
10631
  },
@@ -10190,6 +10890,16 @@ var EnergyModalView = class {
10190
10890
  Exportar CSV
10191
10891
  </button>
10192
10892
  ${this.config.params.readingType === "energy" && this.config.params.mode !== "comparison" ? `
10893
+ <button id="view-demand-btn" class="myio-btn myio-btn-secondary" style="
10894
+ background: linear-gradient(135deg, #1976D2 0%, #2196F3 100%);
10895
+ color: white;
10896
+ border: none;
10897
+ transition: all 0.3s ease;
10898
+ box-shadow: 0 2px 8px rgba(25, 118, 210, 0.3);
10899
+ ">
10900
+ <span style="font-size: 16px; margin-right: 4px;">\u{1F4CA}</span>
10901
+ Pico de Demanda
10902
+ </button>
10193
10903
  <button id="view-telemetry-btn" class="myio-btn myio-btn-secondary" style="
10194
10904
  background: linear-gradient(135deg, #4A148C 0%, #6A1B9A 100%);
10195
10905
  color: white;
@@ -10815,42 +11525,64 @@ var EnergyModalView = class {
10815
11525
  if (loadBtn) {
10816
11526
  loadBtn.addEventListener("click", () => this.loadData());
10817
11527
  }
10818
- const viewTelemetryBtn = document.getElementById("view-telemetry-btn");
10819
- if (viewTelemetryBtn) {
10820
- viewTelemetryBtn.addEventListener("click", async () => {
11528
+ const viewDemandBtn = document.getElementById("view-demand-btn");
11529
+ if (viewDemandBtn) {
11530
+ viewDemandBtn.addEventListener("click", async () => {
10821
11531
  try {
10822
- console.log("[EnergyModalView] Opening demand modal");
11532
+ console.log("[EnergyModalView] Opening demand modal (Pico de Demanda)");
10823
11533
  const jwtToken = localStorage.getItem("jwt_token");
10824
11534
  if (!jwtToken) {
10825
11535
  throw new Error("Token de autentica\xE7\xE3o n\xE3o encontrado");
10826
11536
  }
10827
- let startDate = this.config.params.startDate;
10828
- let endDate = this.config.params.endDate;
11537
+ let startDate;
11538
+ let endDate;
10829
11539
  if (this.dateRangePicker) {
10830
- const dates = this.dateRangePicker.getDates();
10831
- if (dates.startISO && dates.endISO) {
10832
- startDate = dates.startISO.split("T")[0];
10833
- endDate = dates.endISO.split("T")[0];
10834
- }
11540
+ const dates = this.dateRangePicker.getRange();
11541
+ startDate = dates.startISO;
11542
+ endDate = dates.endISO;
11543
+ } else {
11544
+ startDate = this.config.params.startDate instanceof Date ? this.config.params.startDate.toISOString() : this.config.params.startDate;
11545
+ endDate = this.config.params.endDate instanceof Date ? this.config.params.endDate.toISOString() : this.config.params.endDate;
10835
11546
  }
10836
11547
  await openDemandModal({
10837
11548
  token: jwtToken,
10838
11549
  deviceId: this.config.params.deviceId,
10839
- startDate: startDate instanceof Date ? startDate.toISOString().split("T")[0] : startDate,
10840
- endDate: endDate instanceof Date ? endDate.toISOString().split("T")[0] : endDate,
10841
- label: this.config.params.label || "Dispositivo",
10842
- telemetryQuery: {
10843
- keys: "consumption",
10844
- intervalType: "MILLISECONDS",
10845
- interval: 864e5,
10846
- // 24 hours - default, can be changed inside modal
10847
- agg: "MAX",
10848
- // default, can be changed inside modal
10849
- limit: 1e4
10850
- }
11550
+ startDate,
11551
+ endDate,
11552
+ label: this.config.params.deviceLabel || "Dispositivo",
11553
+ locale: "pt-BR",
11554
+ readingType: this.config.params.readingType || "energy",
11555
+ enableRealTimeMode: true,
11556
+ realTimeInterval: 8e3,
11557
+ realTimeAutoScroll: true
10851
11558
  });
10852
11559
  } catch (error) {
10853
11560
  console.error("[EnergyModalView] Error opening demand modal:", error);
11561
+ this.showError("Erro ao abrir pico de demanda: " + error.message);
11562
+ }
11563
+ });
11564
+ }
11565
+ const viewTelemetryBtn = document.getElementById("view-telemetry-btn");
11566
+ if (viewTelemetryBtn) {
11567
+ viewTelemetryBtn.addEventListener("click", async () => {
11568
+ try {
11569
+ console.log("[EnergyModalView] Opening real-time telemetry modal");
11570
+ const jwtToken = localStorage.getItem("jwt_token");
11571
+ if (!jwtToken) {
11572
+ throw new Error("Token de autentica\xE7\xE3o n\xE3o encontrado");
11573
+ }
11574
+ await openRealTimeTelemetryModal({
11575
+ token: jwtToken,
11576
+ deviceId: this.config.params.deviceId,
11577
+ deviceLabel: this.config.params.deviceLabel || "Dispositivo",
11578
+ telemetryKeys: ["voltage", "current", "power", "energy"],
11579
+ refreshInterval: 8e3,
11580
+ // 8 seconds
11581
+ historyPoints: 50,
11582
+ locale: "pt-BR"
11583
+ });
11584
+ } catch (error) {
11585
+ console.error("[EnergyModalView] Error opening real-time telemetry modal:", error);
10854
11586
  this.showError("Erro ao abrir telemetrias: " + error.message);
10855
11587
  }
10856
11588
  });
@@ -18845,6 +19577,7 @@ function openGoalsPanel(params) {
18845
19577
  openDashboardPopupWaterTank,
18846
19578
  openDemandModal,
18847
19579
  openGoalsPanel,
19580
+ openRealTimeTelemetryModal,
18848
19581
  parseInputDateToDate,
18849
19582
  renderCardComponent,
18850
19583
  renderCardComponentEnhanced,