myio-js-library 0.1.212 → 0.1.214

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.js CHANGED
@@ -21299,26 +21299,26 @@ var WaterTankModalView = class {
21299
21299
  */
21300
21300
  getI18n() {
21301
21301
  const defaults = {
21302
- title: "Water Tank",
21303
- loading: "Loading...",
21304
- error: "Error loading data",
21305
- noData: "No data available",
21306
- exportCsv: "Export CSV",
21307
- close: "Close",
21308
- currentLevel: "Current Level",
21309
- averageLevel: "Average Level",
21310
- minLevel: "Minimum Level",
21311
- maxLevel: "Maximum Level",
21312
- dateRange: "Date Range",
21313
- deviceInfo: "Device Information",
21314
- levelChart: "Water Level History (m.c.a)",
21302
+ title: "Caixa d'\xC1gua",
21303
+ loading: "Carregando...",
21304
+ error: "Erro ao carregar dados",
21305
+ noData: "Nenhum dado dispon\xEDvel",
21306
+ exportCsv: "Exportar CSV",
21307
+ close: "Fechar",
21308
+ currentLevel: "N\xEDvel Atual",
21309
+ averageLevel: "N\xEDvel M\xE9dio",
21310
+ minLevel: "N\xEDvel M\xEDnimo",
21311
+ maxLevel: "N\xEDvel M\xE1ximo",
21312
+ dateRange: "Per\xEDodo",
21313
+ deviceInfo: "Informa\xE7\xF5es do Dispositivo",
21314
+ levelChart: "Hist\xF3rico de N\xEDvel (m.c.a)",
21315
21315
  percentUnit: "%",
21316
21316
  status: {
21317
- critical: "Critical",
21318
- low: "Low",
21319
- medium: "Medium",
21320
- good: "Good",
21321
- full: "Full"
21317
+ critical: "Cr\xEDtico",
21318
+ low: "Baixo",
21319
+ medium: "M\xE9dio",
21320
+ good: "Bom",
21321
+ full: "Cheio"
21322
21322
  }
21323
21323
  };
21324
21324
  return {
@@ -21419,62 +21419,327 @@ var WaterTankModalView = class {
21419
21419
  this.attachEventListeners();
21420
21420
  }
21421
21421
  /**
21422
- * Render modal header
21422
+ * Render modal header - MyIO Premium Style
21423
21423
  */
21424
21424
  renderHeader() {
21425
21425
  const { context, params } = this.config;
21426
21426
  const title = params.ui?.title || `${this.i18n.title} - ${context.device.label}`;
21427
21427
  return `
21428
21428
  <div class="myio-water-tank-modal-header" style="
21429
- padding: 20px 24px;
21430
- border-bottom: 1px solid #e0e0e0;
21429
+ padding: 4px 8px;
21431
21430
  display: flex;
21432
21431
  align-items: center;
21433
21432
  justify-content: space-between;
21433
+ background: #3e1a7d;
21434
+ color: white;
21435
+ border-radius: 12px 12px 0 0;
21436
+ min-height: 20px;
21434
21437
  ">
21435
21438
  <h2 style="
21436
- margin: 0;
21437
- font-size: 20px;
21439
+ margin: 6px;
21440
+ font-size: 18px;
21438
21441
  font-weight: 600;
21439
- color: #2c3e50;
21440
- ">${title}</h2>
21441
- <button class="myio-water-tank-modal-close" style="
21442
- background: none;
21443
- border: none;
21444
- font-size: 24px;
21445
- color: #7f8c8d;
21446
- cursor: pointer;
21447
- padding: 0;
21448
- width: 32px;
21449
- height: 32px;
21450
- display: flex;
21451
- align-items: center;
21452
- justify-content: center;
21453
- border-radius: 4px;
21454
- transition: background 0.2s ease;
21455
- " title="${this.i18n.close}">
21456
- \xD7
21457
- </button>
21442
+ color: white;
21443
+ line-height: 2;
21444
+ ">\u{1F4A7} ${title}</h2>
21445
+ <div style="display: flex; gap: 4px; align-items: center;">
21446
+ <button class="myio-water-tank-modal-close" title="${this.i18n.close}" style="
21447
+ background: none;
21448
+ border: none;
21449
+ font-size: 20px;
21450
+ cursor: pointer;
21451
+ padding: 4px 8px;
21452
+ border-radius: 6px;
21453
+ color: rgba(255,255,255,0.8);
21454
+ transition: background-color 0.2s;
21455
+ ">\xD7</button>
21456
+ </div>
21458
21457
  </div>
21459
21458
  `;
21460
21459
  }
21461
21460
  /**
21462
- * Render modal body
21461
+ * RFC-0107: Render modal body with new layout
21462
+ * Left side: Tank visualization with percentage
21463
+ * Right side: Chart with controls (larger area)
21463
21464
  */
21464
21465
  renderBody() {
21465
21466
  return `
21466
21467
  <div class="myio-water-tank-modal-body" style="
21467
- padding: 24px;
21468
+ padding: 20px;
21468
21469
  overflow-y: auto;
21469
21470
  flex: 1;
21471
+ display: flex;
21472
+ flex-direction: column;
21473
+ gap: 16px;
21470
21474
  ">
21471
- ${this.renderDateRangePicker()}
21472
- ${this.renderTankVisualization()}
21473
- ${this.renderChart()}
21475
+ ${this.renderControlsBar()}
21476
+ <div style="
21477
+ display: flex;
21478
+ gap: 20px;
21479
+ flex: 1;
21480
+ min-height: 400px;
21481
+ ">
21482
+ ${this.renderTankPanel()}
21483
+ ${this.renderChartPanel()}
21484
+ </div>
21474
21485
  </div>
21475
21486
  ${this.renderFooter()}
21476
21487
  `;
21477
21488
  }
21489
+ /**
21490
+ * RFC-0107: Render controls bar with date range, aggregation, and limit
21491
+ */
21492
+ renderControlsBar() {
21493
+ const { params } = this.config;
21494
+ const startDate = this.formatDateForInput(params.startTs);
21495
+ const endDate = this.formatDateForInput(params.endTs);
21496
+ const currentAggregation = params.aggregation || "NONE";
21497
+ const currentLimit = params.limit || 1e3;
21498
+ return `
21499
+ <div style="
21500
+ background: #f8f9fa;
21501
+ border: 1px solid #e0e0e0;
21502
+ border-radius: 8px;
21503
+ padding: 12px 16px;
21504
+ display: flex;
21505
+ align-items: center;
21506
+ gap: 16px;
21507
+ flex-wrap: wrap;
21508
+ ">
21509
+ <div style="display: flex; align-items: center; gap: 8px;">
21510
+ <label style="font-size: 13px; font-weight: 500; color: #2c3e50;">De:</label>
21511
+ <input type="date" id="myio-water-tank-start-date" value="${startDate}" style="
21512
+ padding: 6px 10px;
21513
+ border: 1px solid #ddd;
21514
+ border-radius: 6px;
21515
+ font-size: 13px;
21516
+ color: #2c3e50;
21517
+ cursor: pointer;
21518
+ "/>
21519
+ </div>
21520
+ <div style="display: flex; align-items: center; gap: 8px;">
21521
+ <label style="font-size: 13px; font-weight: 500; color: #2c3e50;">At\xE9:</label>
21522
+ <input type="date" id="myio-water-tank-end-date" value="${endDate}" style="
21523
+ padding: 6px 10px;
21524
+ border: 1px solid #ddd;
21525
+ border-radius: 6px;
21526
+ font-size: 13px;
21527
+ color: #2c3e50;
21528
+ cursor: pointer;
21529
+ "/>
21530
+ </div>
21531
+ <div style="display: flex; align-items: center; gap: 8px;">
21532
+ <label style="font-size: 13px; font-weight: 500; color: #2c3e50;">Agrega\xE7\xE3o:</label>
21533
+ <select id="myio-water-tank-aggregation" style="
21534
+ padding: 6px 10px;
21535
+ border: 1px solid #ddd;
21536
+ border-radius: 6px;
21537
+ font-size: 13px;
21538
+ color: #2c3e50;
21539
+ background: white;
21540
+ cursor: pointer;
21541
+ ">
21542
+ <option value="NONE" ${currentAggregation === "NONE" ? "selected" : ""}>Nenhuma</option>
21543
+ <option value="AVG" ${currentAggregation === "AVG" ? "selected" : ""}>M\xE9dia</option>
21544
+ <option value="MIN" ${currentAggregation === "MIN" ? "selected" : ""}>M\xEDnimo</option>
21545
+ <option value="MAX" ${currentAggregation === "MAX" ? "selected" : ""}>M\xE1ximo</option>
21546
+ <option value="SUM" ${currentAggregation === "SUM" ? "selected" : ""}>Soma</option>
21547
+ <option value="COUNT" ${currentAggregation === "COUNT" ? "selected" : ""}>Contagem</option>
21548
+ </select>
21549
+ </div>
21550
+ <div style="display: flex; align-items: center; gap: 8px;">
21551
+ <label style="font-size: 13px; font-weight: 500; color: #2c3e50;">Limite:</label>
21552
+ <select id="myio-water-tank-limit" style="
21553
+ padding: 6px 10px;
21554
+ border: 1px solid #ddd;
21555
+ border-radius: 6px;
21556
+ font-size: 13px;
21557
+ color: #2c3e50;
21558
+ background: white;
21559
+ cursor: pointer;
21560
+ ">
21561
+ <option value="100" ${currentLimit === 100 ? "selected" : ""}>100</option>
21562
+ <option value="500" ${currentLimit === 500 ? "selected" : ""}>500</option>
21563
+ <option value="1000" ${currentLimit === 1e3 ? "selected" : ""}>1000</option>
21564
+ <option value="2000" ${currentLimit === 2e3 ? "selected" : ""}>2000</option>
21565
+ <option value="5000" ${currentLimit === 5e3 ? "selected" : ""}>5000</option>
21566
+ </select>
21567
+ </div>
21568
+ <button id="myio-water-tank-apply-dates" style="
21569
+ background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
21570
+ color: white;
21571
+ border: none;
21572
+ padding: 6px 16px;
21573
+ border-radius: 6px;
21574
+ font-size: 13px;
21575
+ font-weight: 500;
21576
+ cursor: pointer;
21577
+ transition: all 0.2s ease;
21578
+ ">
21579
+ Aplicar
21580
+ </button>
21581
+ </div>
21582
+ `;
21583
+ }
21584
+ /**
21585
+ * RFC-0107: Render tank panel (left side)
21586
+ */
21587
+ renderTankPanel() {
21588
+ const { data, context } = this.config;
21589
+ let percentage = 0;
21590
+ const percentagePoints = data.telemetry.filter((p) => p.key === "water_percentage");
21591
+ if (percentagePoints.length > 0) {
21592
+ const latestPercentage = percentagePoints[percentagePoints.length - 1].value;
21593
+ percentage = latestPercentage <= 1.5 ? latestPercentage * 100 : latestPercentage;
21594
+ } else if (context.device.currentLevel !== void 0) {
21595
+ const level = context.device.currentLevel;
21596
+ percentage = level <= 1.5 ? level * 100 : level;
21597
+ }
21598
+ const levelStatus = this.getLevelStatus(Math.min(percentage, 100));
21599
+ const tankImageUrl = this.getTankImageUrl(Math.min(percentage, 100));
21600
+ const displayPercentage = percentage.toFixed(1);
21601
+ return `
21602
+ <div style="
21603
+ width: 200px;
21604
+ min-width: 200px;
21605
+ background: linear-gradient(135deg, ${levelStatus.color}10 0%, ${levelStatus.color}05 100%);
21606
+ border: 1px solid ${levelStatus.color}30;
21607
+ border-radius: 12px;
21608
+ padding: 24px 16px;
21609
+ display: flex;
21610
+ flex-direction: column;
21611
+ align-items: center;
21612
+ justify-content: center;
21613
+ gap: 16px;
21614
+ ">
21615
+ <img src="${tankImageUrl}" alt="Water Tank" style="
21616
+ width: 100px;
21617
+ height: auto;
21618
+ filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1));
21619
+ "/>
21620
+ <div style="
21621
+ font-size: 42px;
21622
+ font-weight: 700;
21623
+ color: ${levelStatus.color};
21624
+ line-height: 1;
21625
+ ">${displayPercentage}%</div>
21626
+ <div style="
21627
+ background: ${levelStatus.color};
21628
+ color: white;
21629
+ padding: 4px 12px;
21630
+ border-radius: 20px;
21631
+ font-size: 11px;
21632
+ font-weight: 600;
21633
+ text-transform: uppercase;
21634
+ ">${levelStatus.label}</div>
21635
+ <div style="
21636
+ font-size: 12px;
21637
+ color: #7f8c8d;
21638
+ text-align: center;
21639
+ ">${this.i18n.currentLevel}</div>
21640
+ </div>
21641
+ `;
21642
+ }
21643
+ /**
21644
+ * RFC-0107: Render chart panel (right side) with maximize button
21645
+ */
21646
+ renderChartPanel() {
21647
+ const chartPoints = this.getChartDataPoints();
21648
+ const chartTitle = this.chartDisplayMode === "water_percentage" ? "Hist\xF3rico de N\xEDvel (%)" : this.i18n.levelChart;
21649
+ if (chartPoints.length === 0) {
21650
+ const displayLabel = this.chartDisplayMode === "water_percentage" ? "%" : "m.c.a";
21651
+ return `
21652
+ <div style="
21653
+ flex: 1;
21654
+ background: #f8f9fa;
21655
+ border: 1px solid #e0e0e0;
21656
+ border-radius: 12px;
21657
+ display: flex;
21658
+ flex-direction: column;
21659
+ align-items: center;
21660
+ justify-content: center;
21661
+ padding: 24px;
21662
+ ">
21663
+ <div style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;">\u{1F4CA}</div>
21664
+ <div style="color: #7f8c8d; font-size: 16px;">${this.i18n.noData}</div>
21665
+ <div style="color: #bdc3c7; font-size: 13px; margin-top: 8px;">
21666
+ Sem dados de ${this.chartDisplayMode === "water_percentage" ? "percentual" : "n\xEDvel"} (${displayLabel}) dispon\xEDveis
21667
+ </div>
21668
+ </div>
21669
+ `;
21670
+ }
21671
+ const firstTs = chartPoints[0]?.ts;
21672
+ const lastTs = chartPoints[chartPoints.length - 1]?.ts;
21673
+ return `
21674
+ <div id="myio-water-tank-chart-panel" style="
21675
+ flex: 1;
21676
+ background: white;
21677
+ border: 1px solid #e0e0e0;
21678
+ border-radius: 12px;
21679
+ padding: 16px;
21680
+ display: flex;
21681
+ flex-direction: column;
21682
+ position: relative;
21683
+ ">
21684
+ <div style="
21685
+ display: flex;
21686
+ align-items: center;
21687
+ justify-content: space-between;
21688
+ margin-bottom: 12px;
21689
+ ">
21690
+ <h3 style="
21691
+ margin: 0;
21692
+ font-size: 15px;
21693
+ font-weight: 600;
21694
+ color: #2c3e50;
21695
+ ">${chartTitle}</h3>
21696
+ <div style="
21697
+ display: flex;
21698
+ align-items: center;
21699
+ gap: 8px;
21700
+ ">
21701
+ <select id="myio-water-tank-display-mode" style="
21702
+ padding: 4px 8px;
21703
+ border: 1px solid #ddd;
21704
+ border-radius: 4px;
21705
+ font-size: 12px;
21706
+ color: #2c3e50;
21707
+ background: white;
21708
+ cursor: pointer;
21709
+ ">
21710
+ <option value="water_level" ${this.chartDisplayMode === "water_level" ? "selected" : ""}>N\xEDvel (m.c.a)</option>
21711
+ <option value="water_percentage" ${this.chartDisplayMode === "water_percentage" ? "selected" : ""}>Percentual (%)</option>
21712
+ </select>
21713
+ <button id="myio-water-tank-maximize" title="Maximizar gr\xE1fico" style="
21714
+ background: #f0f0f0;
21715
+ border: 1px solid #ddd;
21716
+ border-radius: 4px;
21717
+ padding: 4px 8px;
21718
+ cursor: pointer;
21719
+ font-size: 14px;
21720
+ display: flex;
21721
+ align-items: center;
21722
+ justify-content: center;
21723
+ ">\u26F6</button>
21724
+ </div>
21725
+ </div>
21726
+ <div style="flex: 1; min-height: 300px;">
21727
+ <canvas id="myio-water-tank-chart" style="width: 100%; height: 100%;"></canvas>
21728
+ </div>
21729
+ ${firstTs && lastTs ? `
21730
+ <div style="
21731
+ margin-top: 8px;
21732
+ font-size: 11px;
21733
+ color: #7f8c8d;
21734
+ text-align: center;
21735
+ ">
21736
+ ${this.formatDate(firstTs, false)} \u2014 ${this.formatDate(lastTs, false)}
21737
+ (${chartPoints.length} leituras)
21738
+ </div>
21739
+ ` : ""}
21740
+ </div>
21741
+ `;
21742
+ }
21478
21743
  /**
21479
21744
  * Render date range picker
21480
21745
  */
@@ -21495,7 +21760,7 @@ var WaterTankModalView = class {
21495
21760
  flex-wrap: wrap;
21496
21761
  ">
21497
21762
  <div style="display: flex; align-items: center; gap: 8px;">
21498
- <label style="font-size: 14px; font-weight: 500; color: #2c3e50;">From:</label>
21763
+ <label style="font-size: 14px; font-weight: 500; color: #2c3e50;">De:</label>
21499
21764
  <input type="date" id="myio-water-tank-start-date" value="${startDate}" style="
21500
21765
  padding: 8px 12px;
21501
21766
  border: 1px solid #ddd;
@@ -21506,7 +21771,7 @@ var WaterTankModalView = class {
21506
21771
  "/>
21507
21772
  </div>
21508
21773
  <div style="display: flex; align-items: center; gap: 8px;">
21509
- <label style="font-size: 14px; font-weight: 500; color: #2c3e50;">To:</label>
21774
+ <label style="font-size: 14px; font-weight: 500; color: #2c3e50;">At\xE9:</label>
21510
21775
  <input type="date" id="myio-water-tank-end-date" value="${endDate}" style="
21511
21776
  padding: 8px 12px;
21512
21777
  border: 1px solid #ddd;
@@ -21527,7 +21792,7 @@ var WaterTankModalView = class {
21527
21792
  cursor: pointer;
21528
21793
  transition: all 0.2s ease;
21529
21794
  ">
21530
- Apply
21795
+ Aplicar
21531
21796
  </button>
21532
21797
  </div>
21533
21798
  `;
@@ -21749,7 +22014,7 @@ var WaterTankModalView = class {
21749
22014
  }
21750
22015
  const applyDatesBtn = this.modal.querySelector("#myio-water-tank-apply-dates");
21751
22016
  if (applyDatesBtn) {
21752
- applyDatesBtn.addEventListener("click", () => this.handleDateRangeChange());
22017
+ applyDatesBtn.addEventListener("click", () => this.handleApplyParams());
21753
22018
  }
21754
22019
  const displayModeSelect = this.modal.querySelector("#myio-water-tank-display-mode");
21755
22020
  if (displayModeSelect) {
@@ -21758,6 +22023,10 @@ var WaterTankModalView = class {
21758
22023
  this.refreshChart();
21759
22024
  });
21760
22025
  }
22026
+ const maximizeBtn = this.modal.querySelector("#myio-water-tank-maximize");
22027
+ if (maximizeBtn) {
22028
+ maximizeBtn.addEventListener("click", () => this.handleMaximize());
22029
+ }
21761
22030
  this.overlay.addEventListener("click", (e) => {
21762
22031
  if (e.target === this.overlay) {
21763
22032
  this.config.onClose();
@@ -21772,12 +22041,20 @@ var WaterTankModalView = class {
21772
22041
  });
21773
22042
  }
21774
22043
  /**
21775
- * Handle date range change
22044
+ * Handle date range change (legacy - kept for compatibility)
21776
22045
  */
21777
22046
  handleDateRangeChange() {
22047
+ this.handleApplyParams();
22048
+ }
22049
+ /**
22050
+ * RFC-0107: Handle apply params (date range, aggregation, limit)
22051
+ */
22052
+ handleApplyParams() {
21778
22053
  if (!this.modal) return;
21779
22054
  const startInput = this.modal.querySelector("#myio-water-tank-start-date");
21780
22055
  const endInput = this.modal.querySelector("#myio-water-tank-end-date");
22056
+ const aggregationSelect = this.modal.querySelector("#myio-water-tank-aggregation");
22057
+ const limitSelect = this.modal.querySelector("#myio-water-tank-limit");
21781
22058
  if (startInput && endInput) {
21782
22059
  const startTs = new Date(startInput.value).setHours(0, 0, 0, 0);
21783
22060
  const endTs = new Date(endInput.value).setHours(23, 59, 59, 999);
@@ -21785,17 +22062,77 @@ var WaterTankModalView = class {
21785
22062
  alert("Start date must be before end date");
21786
22063
  return;
21787
22064
  }
21788
- console.log("[WaterTankModalView] Date range changed:", {
22065
+ const aggregation = aggregationSelect?.value || "NONE";
22066
+ const limit = parseInt(limitSelect?.value || "1000", 10);
22067
+ console.log("[WaterTankModalView] Params changed:", {
21789
22068
  startTs,
21790
22069
  endTs,
22070
+ aggregation,
22071
+ limit,
21791
22072
  startDate: new Date(startTs).toISOString(),
21792
22073
  endDate: new Date(endTs).toISOString()
21793
22074
  });
21794
- if (this.config.onDateRangeChange) {
22075
+ this.config.params.startTs = startTs;
22076
+ this.config.params.endTs = endTs;
22077
+ this.config.params.aggregation = aggregation;
22078
+ this.config.params.limit = limit;
22079
+ if (this.config.onParamsChange) {
22080
+ this.config.onParamsChange({ startTs, endTs, aggregation, limit });
22081
+ } else if (this.config.onDateRangeChange) {
21795
22082
  this.config.onDateRangeChange(startTs, endTs);
21796
22083
  }
21797
22084
  }
21798
22085
  }
22086
+ /**
22087
+ * RFC-0107: Handle maximize/restore chart
22088
+ */
22089
+ isMaximized = false;
22090
+ originalModalStyle = "";
22091
+ handleMaximize() {
22092
+ if (!this.modal) return;
22093
+ const chartPanel = this.modal.querySelector("#myio-water-tank-chart-panel");
22094
+ const tankPanel = chartPanel?.previousElementSibling;
22095
+ const maximizeBtn = this.modal.querySelector("#myio-water-tank-maximize");
22096
+ if (!chartPanel) return;
22097
+ if (this.isMaximized) {
22098
+ this.modal.style.cssText = this.originalModalStyle;
22099
+ if (tankPanel) tankPanel.style.display = "";
22100
+ chartPanel.style.cssText = `
22101
+ flex: 1;
22102
+ background: white;
22103
+ border: 1px solid #e0e0e0;
22104
+ border-radius: 12px;
22105
+ padding: 16px;
22106
+ display: flex;
22107
+ flex-direction: column;
22108
+ position: relative;
22109
+ `;
22110
+ if (maximizeBtn) maximizeBtn.textContent = "\u26F6";
22111
+ this.isMaximized = false;
22112
+ } else {
22113
+ this.originalModalStyle = this.modal.style.cssText;
22114
+ this.modal.style.width = "95vw";
22115
+ this.modal.style.height = "90vh";
22116
+ this.modal.style.maxWidth = "95vw";
22117
+ if (tankPanel) tankPanel.style.display = "none";
22118
+ chartPanel.style.cssText = `
22119
+ flex: 1;
22120
+ background: white;
22121
+ border: 1px solid #e0e0e0;
22122
+ border-radius: 12px;
22123
+ padding: 16px;
22124
+ display: flex;
22125
+ flex-direction: column;
22126
+ position: relative;
22127
+ min-height: 100%;
22128
+ `;
22129
+ if (maximizeBtn) maximizeBtn.textContent = "\u26F6";
22130
+ this.isMaximized = true;
22131
+ }
22132
+ requestAnimationFrame(() => {
22133
+ this.renderCanvasChart();
22134
+ });
22135
+ }
21799
22136
  handleEscapeKey(e) {
21800
22137
  if (e.key === "Escape") {
21801
22138
  this.config.onClose();
@@ -21934,7 +22271,7 @@ var WaterTankModalView = class {
21934
22271
  }
21935
22272
  }
21936
22273
  /**
21937
- * Update data and re-render chart
22274
+ * Update data and re-render chart with new layout
21938
22275
  */
21939
22276
  updateData(data) {
21940
22277
  this.config.data = data;
@@ -21942,13 +22279,20 @@ var WaterTankModalView = class {
21942
22279
  const bodyEl = this.modal.querySelector(".myio-water-tank-modal-body");
21943
22280
  if (bodyEl) {
21944
22281
  bodyEl.innerHTML = `
21945
- ${this.renderDateRangePicker()}
21946
- ${this.renderTankVisualization()}
21947
- ${this.renderChart()}
22282
+ ${this.renderControlsBar()}
22283
+ <div style="
22284
+ display: flex;
22285
+ gap: 20px;
22286
+ flex: 1;
22287
+ min-height: 400px;
22288
+ ">
22289
+ ${this.renderTankPanel()}
22290
+ ${this.renderChartPanel()}
22291
+ </div>
21948
22292
  `;
21949
22293
  const applyDatesBtn = this.modal.querySelector("#myio-water-tank-apply-dates");
21950
22294
  if (applyDatesBtn) {
21951
- applyDatesBtn.addEventListener("click", () => this.handleDateRangeChange());
22295
+ applyDatesBtn.addEventListener("click", () => this.handleApplyParams());
21952
22296
  }
21953
22297
  const displayModeSelect = this.modal.querySelector("#myio-water-tank-display-mode");
21954
22298
  if (displayModeSelect) {
@@ -21957,6 +22301,10 @@ var WaterTankModalView = class {
21957
22301
  this.refreshChart();
21958
22302
  });
21959
22303
  }
22304
+ const maximizeBtn = this.modal.querySelector("#myio-water-tank-maximize");
22305
+ if (maximizeBtn) {
22306
+ maximizeBtn.addEventListener("click", () => this.handleMaximize());
22307
+ }
21960
22308
  requestAnimationFrame(() => {
21961
22309
  this.renderCanvasChart();
21962
22310
  });
@@ -22129,13 +22477,12 @@ var WaterTankModal = class {
22129
22477
  }
22130
22478
  /**
22131
22479
  * Transform raw ThingsBoard response to our data points
22132
- * RFC-0107: Support displayKey option to choose between water_level or water_percentage
22480
+ * RFC-0107: Keep ALL data points with their original keys (no deduplication)
22481
+ * This allows the chart to switch between water_level and water_percentage dynamically
22133
22482
  */
22134
22483
  transformTelemetryData(rawData, keys) {
22135
22484
  const allPoints = [];
22136
- const displayKey = this.options.displayKey || "water_percentage";
22137
- const prioritizedKeys = displayKey && rawData[displayKey] ? [displayKey, ...keys.filter((k) => k !== displayKey)] : keys;
22138
- for (const key of prioritizedKeys) {
22485
+ for (const key of keys) {
22139
22486
  if (rawData[key] && Array.isArray(rawData[key])) {
22140
22487
  const keyPoints = rawData[key].map((point) => {
22141
22488
  let value = typeof point.value === "string" ? parseFloat(point.value) : point.value;
@@ -22152,16 +22499,12 @@ var WaterTankModal = class {
22152
22499
  }
22153
22500
  }
22154
22501
  allPoints.sort((a, b) => a.ts - b.ts);
22155
- const uniquePoints = [];
22156
- const seenTimestamps = /* @__PURE__ */ new Set();
22157
- for (const point of allPoints) {
22158
- if (!seenTimestamps.has(point.ts)) {
22159
- seenTimestamps.add(point.ts);
22160
- uniquePoints.push(point);
22161
- }
22502
+ const keyCounts = {};
22503
+ for (const p of allPoints) {
22504
+ keyCounts[p.key || "unknown"] = (keyCounts[p.key || "unknown"] || 0) + 1;
22162
22505
  }
22163
- console.log(`[WaterTankModal] Transformed ${uniquePoints.length} points, displayKey: ${displayKey}`);
22164
- return uniquePoints;
22506
+ console.log(`[WaterTankModal] Transformed ${allPoints.length} total points:`, keyCounts);
22507
+ return allPoints;
22165
22508
  }
22166
22509
  /**
22167
22510
  * Calculate summary statistics from telemetry data
@@ -22241,7 +22584,8 @@ var WaterTankModal = class {
22241
22584
  onError: (error) => this.handleError(error),
22242
22585
  onClose: () => this.close(),
22243
22586
  // Call close() to destroy view and trigger user callback
22244
- onDateRangeChange: (startTs, endTs) => this.handleDateRangeChange(startTs, endTs)
22587
+ onDateRangeChange: (startTs, endTs) => this.handleDateRangeChange(startTs, endTs),
22588
+ onParamsChange: (params) => this.handleParamsChange(params)
22245
22589
  });
22246
22590
  this.view.render();
22247
22591
  this.view.show();
@@ -22306,6 +22650,43 @@ var WaterTankModal = class {
22306
22650
  this.handleError(error);
22307
22651
  }
22308
22652
  }
22653
+ /**
22654
+ * RFC-0107: Handle params change (date range, aggregation, limit)
22655
+ */
22656
+ async handleParamsChange(params) {
22657
+ console.log("[WaterTankModal] Params changed:", {
22658
+ startTs: params.startTs,
22659
+ endTs: params.endTs,
22660
+ aggregation: params.aggregation,
22661
+ limit: params.limit,
22662
+ startDate: new Date(params.startTs).toISOString(),
22663
+ endDate: new Date(params.endTs).toISOString()
22664
+ });
22665
+ this.options.startTs = params.startTs;
22666
+ this.options.endTs = params.endTs;
22667
+ this.options.aggregation = params.aggregation;
22668
+ this.options.limit = params.limit;
22669
+ this.context.timeRange.startTs = params.startTs;
22670
+ this.context.timeRange.endTs = params.endTs;
22671
+ try {
22672
+ console.log("[WaterTankModal] Fetching data with new params...");
22673
+ this.data = await this.fetchTelemetryData();
22674
+ if (this.view) {
22675
+ this.view.updateData(this.data);
22676
+ }
22677
+ if (this.options.onDataLoaded) {
22678
+ try {
22679
+ this.options.onDataLoaded(this.data);
22680
+ } catch (callbackError) {
22681
+ console.warn("[WaterTankModal] onDataLoaded callback error:", callbackError);
22682
+ }
22683
+ }
22684
+ console.log("[WaterTankModal] Data refreshed with new params successfully");
22685
+ } catch (error) {
22686
+ console.error("[WaterTankModal] Failed to fetch data with new params:", error);
22687
+ this.handleError(error);
22688
+ }
22689
+ }
22309
22690
  /**
22310
22691
  * Handle export functionality
22311
22692
  */
@@ -22427,8 +22808,8 @@ function validateOptions2(options) {
22427
22808
  }
22428
22809
  if (options.currentLevel !== void 0) {
22429
22810
  const level = Number(options.currentLevel);
22430
- if (isNaN(level) || level < 0 || level > 100) {
22431
- errors.push("currentLevel must be a number between 0 and 100");
22811
+ if (isNaN(level) || level < 0) {
22812
+ errors.push("currentLevel must be a non-negative number");
22432
22813
  }
22433
22814
  }
22434
22815
  if (options.limit !== void 0) {