myio-js-library 0.1.158 → 0.1.159

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.
@@ -14429,7 +14429,7 @@ ${rangeText}`;
14429
14429
  maxLevel: "Maximum Level",
14430
14430
  dateRange: "Date Range",
14431
14431
  deviceInfo: "Device Information",
14432
- levelChart: "Level Chart",
14432
+ levelChart: "Water Level History (m.c.a)",
14433
14433
  percentUnit: "%",
14434
14434
  status: {
14435
14435
  critical: "Critical",
@@ -14460,6 +14460,20 @@ ${rangeText}`;
14460
14460
  return { status: "full", color: "#3498db", label: this.i18n.status.full };
14461
14461
  }
14462
14462
  }
14463
+ /**
14464
+ * Get tank image URL based on level percentage (same logic as device card)
14465
+ */
14466
+ getTankImageUrl(percentage) {
14467
+ if (percentage >= 70) {
14468
+ return "https://dashboard.myio-bas.com/api/images/public/3t6WVhMQJFsrKA8bSZmrngDsNPkZV7fq";
14469
+ } else if (percentage >= 40) {
14470
+ return "https://dashboard.myio-bas.com/api/images/public/4UBbShfXCVWR9wcw6IzVMNran4x1EW5n";
14471
+ } else if (percentage >= 20) {
14472
+ return "https://dashboard.myio-bas.com/api/images/public/aB9nX28F54fBBQs1Ht8jKUdYAMcq9QSm";
14473
+ } else {
14474
+ return "https://dashboard.myio-bas.com/api/images/public/qLdwhV4qw295poSCa7HinpnmXoN7dAPO";
14475
+ }
14476
+ }
14463
14477
  /**
14464
14478
  * Format timestamp to readable date
14465
14479
  */
@@ -14472,13 +14486,18 @@ ${rangeText}`;
14472
14486
  }
14473
14487
  return dateStr;
14474
14488
  }
14489
+ /**
14490
+ * Format timestamp to ISO date string for input
14491
+ */
14492
+ formatDateForInput(ts) {
14493
+ const date = new Date(ts);
14494
+ return date.toISOString().split("T")[0];
14495
+ }
14475
14496
  /**
14476
14497
  * Render the modal HTML
14477
14498
  */
14478
14499
  render() {
14479
- const { context, params, data } = this.config;
14480
- const currentLevel = data.summary.currentLevel ?? context.device.currentLevel ?? 0;
14481
- this.getLevelStatus(currentLevel);
14500
+ const { params } = this.config;
14482
14501
  this.overlay = document.createElement("div");
14483
14502
  this.overlay.className = "myio-water-tank-modal-overlay";
14484
14503
  this.overlay.style.cssText = `
@@ -14499,9 +14518,9 @@ ${rangeText}`;
14499
14518
  this.modal.className = "myio-water-tank-modal";
14500
14519
  this.modal.style.cssText = `
14501
14520
  background: white;
14502
- border-radius: 8px;
14521
+ border-radius: 12px;
14503
14522
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
14504
- width: ${params.ui?.width || 900}px;
14523
+ width: ${params.ui?.width || 700}px;
14505
14524
  max-width: 95vw;
14506
14525
  max-height: 90vh;
14507
14526
  display: flex;
@@ -14567,97 +14586,150 @@ ${rangeText}`;
14567
14586
  overflow-y: auto;
14568
14587
  flex: 1;
14569
14588
  ">
14570
- ${this.renderSummaryCards()}
14589
+ ${this.renderDateRangePicker()}
14590
+ ${this.renderTankVisualization()}
14571
14591
  ${this.renderChart()}
14572
- ${this.renderDeviceInfo()}
14573
14592
  </div>
14574
14593
  ${this.renderFooter()}
14575
14594
  `;
14576
14595
  }
14577
14596
  /**
14578
- * Render summary cards
14597
+ * Render date range picker
14579
14598
  */
14580
- renderSummaryCards() {
14581
- const { data } = this.config;
14582
- const currentLevel = data.summary.currentLevel ?? 0;
14583
- const levelStatus = this.getLevelStatus(currentLevel);
14599
+ renderDateRangePicker() {
14600
+ const { params } = this.config;
14601
+ const startDate = this.formatDateForInput(params.startTs);
14602
+ const endDate = this.formatDateForInput(params.endTs);
14584
14603
  return `
14585
14604
  <div style="
14586
- display: grid;
14587
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
14605
+ background: #f8f9fa;
14606
+ border: 1px solid #e0e0e0;
14607
+ border-radius: 8px;
14608
+ padding: 16px;
14609
+ margin-bottom: 20px;
14610
+ display: flex;
14611
+ align-items: center;
14588
14612
  gap: 16px;
14589
- margin-bottom: 24px;
14613
+ flex-wrap: wrap;
14590
14614
  ">
14591
- ${this.renderSummaryCard(
14592
- this.i18n.currentLevel,
14593
- `${currentLevel.toFixed(1)}${this.i18n.percentUnit}`,
14594
- levelStatus.color,
14595
- levelStatus.label
14596
- )}
14597
- ${this.renderSummaryCard(
14598
- this.i18n.averageLevel,
14599
- `${data.summary.avgLevel.toFixed(1)}${this.i18n.percentUnit}`,
14600
- "#3498db"
14601
- )}
14602
- ${this.renderSummaryCard(
14603
- this.i18n.minLevel,
14604
- `${data.summary.minLevel.toFixed(1)}${this.i18n.percentUnit}`,
14605
- "#e74c3c"
14606
- )}
14607
- ${this.renderSummaryCard(
14608
- this.i18n.maxLevel,
14609
- `${data.summary.maxLevel.toFixed(1)}${this.i18n.percentUnit}`,
14610
- "#27ae60"
14611
- )}
14615
+ <div style="display: flex; align-items: center; gap: 8px;">
14616
+ <label style="font-size: 14px; font-weight: 500; color: #2c3e50;">From:</label>
14617
+ <input type="date" id="myio-water-tank-start-date" value="${startDate}" style="
14618
+ padding: 8px 12px;
14619
+ border: 1px solid #ddd;
14620
+ border-radius: 6px;
14621
+ font-size: 14px;
14622
+ color: #2c3e50;
14623
+ cursor: pointer;
14624
+ "/>
14625
+ </div>
14626
+ <div style="display: flex; align-items: center; gap: 8px;">
14627
+ <label style="font-size: 14px; font-weight: 500; color: #2c3e50;">To:</label>
14628
+ <input type="date" id="myio-water-tank-end-date" value="${endDate}" style="
14629
+ padding: 8px 12px;
14630
+ border: 1px solid #ddd;
14631
+ border-radius: 6px;
14632
+ font-size: 14px;
14633
+ color: #2c3e50;
14634
+ cursor: pointer;
14635
+ "/>
14636
+ </div>
14637
+ <button id="myio-water-tank-apply-dates" style="
14638
+ background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
14639
+ color: white;
14640
+ border: none;
14641
+ padding: 8px 20px;
14642
+ border-radius: 6px;
14643
+ font-size: 14px;
14644
+ font-weight: 500;
14645
+ cursor: pointer;
14646
+ transition: all 0.2s ease;
14647
+ ">
14648
+ Apply
14649
+ </button>
14612
14650
  </div>
14613
14651
  `;
14614
14652
  }
14615
14653
  /**
14616
- * Render a single summary card
14654
+ * Render tank visualization with percentage
14617
14655
  */
14618
- renderSummaryCard(label, value, color, badge) {
14656
+ renderTankVisualization() {
14657
+ const { data, context } = this.config;
14658
+ let percentage = 0;
14659
+ const percentagePoints = data.telemetry.filter((p) => p.key === "water_percentage");
14660
+ if (percentagePoints.length > 0) {
14661
+ const latestPercentage = percentagePoints[percentagePoints.length - 1].value;
14662
+ percentage = latestPercentage <= 1 ? latestPercentage * 100 : latestPercentage;
14663
+ } else if (context.device.currentLevel !== void 0) {
14664
+ const level = context.device.currentLevel;
14665
+ percentage = level <= 1 ? level * 100 : level;
14666
+ }
14667
+ const levelStatus = this.getLevelStatus(percentage);
14668
+ const tankImageUrl = this.getTankImageUrl(percentage);
14619
14669
  return `
14620
14670
  <div style="
14621
- background: linear-gradient(135deg, ${color}15 0%, ${color}05 100%);
14622
- border: 1px solid ${color}30;
14623
- border-radius: 8px;
14624
- padding: 16px;
14625
- position: relative;
14671
+ display: flex;
14672
+ align-items: center;
14673
+ justify-content: center;
14674
+ gap: 32px;
14675
+ padding: 24px;
14676
+ background: linear-gradient(135deg, ${levelStatus.color}10 0%, ${levelStatus.color}05 100%);
14677
+ border: 1px solid ${levelStatus.color}30;
14678
+ border-radius: 12px;
14679
+ margin-bottom: 24px;
14626
14680
  ">
14627
- ${badge ? `
14681
+ <div style="
14682
+ display: flex;
14683
+ flex-direction: column;
14684
+ align-items: center;
14685
+ gap: 12px;
14686
+ ">
14687
+ <img src="${tankImageUrl}" alt="Water Tank" style="
14688
+ width: 120px;
14689
+ height: auto;
14690
+ filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1));
14691
+ "/>
14628
14692
  <div style="
14629
- position: absolute;
14630
- top: 12px;
14631
- right: 12px;
14632
- background: ${color};
14693
+ background: ${levelStatus.color};
14633
14694
  color: white;
14634
- padding: 4px 8px;
14635
- border-radius: 4px;
14636
- font-size: 10px;
14695
+ padding: 4px 12px;
14696
+ border-radius: 20px;
14697
+ font-size: 12px;
14637
14698
  font-weight: 600;
14638
14699
  text-transform: uppercase;
14639
- ">${badge}</div>
14640
- ` : ""}
14641
- <div style="
14642
- font-size: 12px;
14643
- color: #7f8c8d;
14644
- margin-bottom: 8px;
14645
- font-weight: 500;
14646
- ">${label}</div>
14700
+ ">${levelStatus.label}</div>
14701
+ </div>
14702
+
14647
14703
  <div style="
14648
- font-size: 28px;
14649
- font-weight: 700;
14650
- color: ${color};
14651
- ">${value}</div>
14704
+ display: flex;
14705
+ flex-direction: column;
14706
+ align-items: center;
14707
+ gap: 8px;
14708
+ ">
14709
+ <div style="
14710
+ font-size: 48px;
14711
+ font-weight: 700;
14712
+ color: ${levelStatus.color};
14713
+ line-height: 1;
14714
+ ">${percentage.toFixed(1)}%</div>
14715
+ <div style="
14716
+ font-size: 14px;
14717
+ color: #7f8c8d;
14718
+ font-weight: 500;
14719
+ ">${this.i18n.currentLevel}</div>
14720
+ </div>
14652
14721
  </div>
14653
14722
  `;
14654
14723
  }
14655
14724
  /**
14656
- * Render chart section
14725
+ * Render chart section - shows water_level (m.c.a) over time
14657
14726
  */
14658
14727
  renderChart() {
14659
14728
  const { data } = this.config;
14660
- if (data.telemetry.length === 0) {
14729
+ const waterLevelPoints = data.telemetry.filter(
14730
+ (p) => p.key === "water_level" || p.key === "waterLevel" || p.key === "nivel" || p.key === "level"
14731
+ );
14732
+ if (waterLevelPoints.length === 0) {
14661
14733
  return `
14662
14734
  <div style="
14663
14735
  background: #f8f9fa;
@@ -14665,20 +14737,23 @@ ${rangeText}`;
14665
14737
  border-radius: 8px;
14666
14738
  padding: 48px;
14667
14739
  text-align: center;
14668
- margin-bottom: 24px;
14669
14740
  ">
14670
14741
  <div style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;">\u{1F4CA}</div>
14671
14742
  <div style="color: #7f8c8d; font-size: 16px;">${this.i18n.noData}</div>
14743
+ <div style="color: #bdc3c7; font-size: 13px; margin-top: 8px;">
14744
+ No water_level (m.c.a) data available for this period
14745
+ </div>
14672
14746
  </div>
14673
14747
  `;
14674
14748
  }
14749
+ const firstTs = waterLevelPoints[0]?.ts;
14750
+ const lastTs = waterLevelPoints[waterLevelPoints.length - 1]?.ts;
14675
14751
  return `
14676
14752
  <div style="
14677
14753
  background: white;
14678
14754
  border: 1px solid #e0e0e0;
14679
14755
  border-radius: 8px;
14680
14756
  padding: 20px;
14681
- margin-bottom: 24px;
14682
14757
  ">
14683
14758
  <h3 style="
14684
14759
  margin: 0 0 16px 0;
@@ -14686,56 +14761,18 @@ ${rangeText}`;
14686
14761
  font-weight: 600;
14687
14762
  color: #2c3e50;
14688
14763
  ">${this.i18n.levelChart}</h3>
14689
- <canvas id="myio-water-tank-chart" style="width: 100%; height: 300px;"></canvas>
14690
- <div style="
14691
- margin-top: 16px;
14692
- font-size: 12px;
14693
- color: #7f8c8d;
14694
- text-align: center;
14695
- ">
14696
- ${this.i18n.dateRange}: ${this.formatDate(data.summary.firstReadingTs, false)} - ${this.formatDate(data.summary.lastReadingTs, false)}
14697
- </div>
14698
- </div>
14699
- `;
14700
- }
14701
- /**
14702
- * Render device info section
14703
- */
14704
- renderDeviceInfo() {
14705
- const { context, data } = this.config;
14706
- return `
14707
- <div style="
14708
- background: #f8f9fa;
14709
- border: 1px solid #e0e0e0;
14710
- border-radius: 8px;
14711
- padding: 20px;
14712
- ">
14713
- <h3 style="
14714
- margin: 0 0 16px 0;
14715
- font-size: 16px;
14716
- font-weight: 600;
14717
- color: #2c3e50;
14718
- ">${this.i18n.deviceInfo}</h3>
14719
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 12px;">
14720
- ${this.renderInfoRow("Device ID", context.device.id)}
14721
- ${this.renderInfoRow("Label", context.device.label)}
14722
- ${context.device.type ? this.renderInfoRow("Type", context.device.type) : ""}
14723
- ${context.metadata.slaveId ? this.renderInfoRow("Slave ID", String(context.metadata.slaveId)) : ""}
14724
- ${context.metadata.centralId ? this.renderInfoRow("Central ID", context.metadata.centralId) : ""}
14725
- ${this.renderInfoRow("Total Readings", String(data.summary.totalReadings))}
14726
- ${this.renderInfoRow("Data Keys", data.metadata.keys.join(", "))}
14727
- </div>
14728
- </div>
14729
- `;
14730
- }
14731
- /**
14732
- * Render an info row
14733
- */
14734
- renderInfoRow(label, value) {
14735
- return `
14736
- <div style="display: flex; justify-content: space-between; align-items: center;">
14737
- <span style="color: #7f8c8d; font-size: 13px;">${label}:</span>
14738
- <span style="color: #2c3e50; font-size: 13px; font-weight: 500; margin-left: 8px;">${value}</span>
14764
+ <canvas id="myio-water-tank-chart" style="width: 100%; height: 280px;"></canvas>
14765
+ ${firstTs && lastTs ? `
14766
+ <div style="
14767
+ margin-top: 12px;
14768
+ font-size: 12px;
14769
+ color: #7f8c8d;
14770
+ text-align: center;
14771
+ ">
14772
+ ${this.formatDate(firstTs, false)} \u2014 ${this.formatDate(lastTs, false)}
14773
+ (${waterLevelPoints.length} readings)
14774
+ </div>
14775
+ ` : ""}
14739
14776
  </div>
14740
14777
  `;
14741
14778
  }
@@ -14784,6 +14821,10 @@ ${rangeText}`;
14784
14821
  if (exportBtn) {
14785
14822
  exportBtn.addEventListener("click", () => this.config.onExport());
14786
14823
  }
14824
+ const applyDatesBtn = this.modal.querySelector("#myio-water-tank-apply-dates");
14825
+ if (applyDatesBtn) {
14826
+ applyDatesBtn.addEventListener("click", () => this.handleDateRangeChange());
14827
+ }
14787
14828
  this.overlay.addEventListener("click", (e) => {
14788
14829
  if (e.target === this.overlay) {
14789
14830
  this.config.onClose();
@@ -14797,59 +14838,122 @@ ${rangeText}`;
14797
14838
  this.renderCanvasChart();
14798
14839
  });
14799
14840
  }
14841
+ /**
14842
+ * Handle date range change
14843
+ */
14844
+ handleDateRangeChange() {
14845
+ if (!this.modal) return;
14846
+ const startInput = this.modal.querySelector("#myio-water-tank-start-date");
14847
+ const endInput = this.modal.querySelector("#myio-water-tank-end-date");
14848
+ if (startInput && endInput) {
14849
+ const startTs = new Date(startInput.value).setHours(0, 0, 0, 0);
14850
+ const endTs = new Date(endInput.value).setHours(23, 59, 59, 999);
14851
+ if (startTs >= endTs) {
14852
+ alert("Start date must be before end date");
14853
+ return;
14854
+ }
14855
+ console.log("[WaterTankModalView] Date range changed:", {
14856
+ startTs,
14857
+ endTs,
14858
+ startDate: new Date(startTs).toISOString(),
14859
+ endDate: new Date(endTs).toISOString()
14860
+ });
14861
+ if (this.config.onDateRangeChange) {
14862
+ this.config.onDateRangeChange(startTs, endTs);
14863
+ }
14864
+ }
14865
+ }
14800
14866
  handleEscapeKey(e) {
14801
14867
  if (e.key === "Escape") {
14802
14868
  this.config.onClose();
14803
14869
  }
14804
14870
  }
14805
14871
  /**
14806
- * Render chart using Canvas API
14872
+ * Render chart using Canvas API - shows water_level (m.c.a) over time
14807
14873
  */
14808
14874
  renderCanvasChart() {
14809
14875
  const canvas = document.getElementById("myio-water-tank-chart");
14810
14876
  if (!canvas) return;
14811
14877
  const { data } = this.config;
14812
- if (data.telemetry.length === 0) return;
14878
+ const points = data.telemetry.filter(
14879
+ (p) => p.key === "water_level" || p.key === "waterLevel" || p.key === "nivel" || p.key === "level"
14880
+ );
14881
+ if (points.length < 2) return;
14813
14882
  const ctx = canvas.getContext("2d");
14814
14883
  if (!ctx) return;
14815
14884
  const rect = canvas.getBoundingClientRect();
14816
14885
  canvas.width = rect.width * window.devicePixelRatio;
14817
- canvas.height = 300 * window.devicePixelRatio;
14886
+ canvas.height = 280 * window.devicePixelRatio;
14818
14887
  ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
14819
14888
  const width = rect.width;
14820
- const height = 300;
14821
- const padding = 40;
14889
+ const height = 280;
14890
+ const padding = { top: 20, right: 20, bottom: 40, left: 60 };
14822
14891
  ctx.clearRect(0, 0, width, height);
14823
- ctx.strokeStyle = "#e0e0e0";
14892
+ const values = points.map((p) => p.value);
14893
+ const minValue = Math.min(...values);
14894
+ const maxValue = Math.max(...values);
14895
+ const valueRange = maxValue - minValue || 1;
14896
+ const valuePadding = valueRange * 0.1;
14897
+ const chartMinY = minValue - valuePadding;
14898
+ const chartMaxY = maxValue + valuePadding;
14899
+ const chartRangeY = chartMaxY - chartMinY;
14900
+ ctx.fillStyle = "#fafafa";
14901
+ ctx.fillRect(padding.left, padding.top, width - padding.left - padding.right, height - padding.top - padding.bottom);
14902
+ ctx.strokeStyle = "#e8e8e8";
14824
14903
  ctx.lineWidth = 1;
14825
- ctx.beginPath();
14826
- ctx.moveTo(padding, padding);
14827
- ctx.lineTo(padding, height - padding);
14828
- ctx.lineTo(width - padding, height - padding);
14829
- ctx.stroke();
14830
- ctx.strokeStyle = "#f0f0f0";
14831
- ctx.lineWidth = 0.5;
14832
- for (let i = 0; i <= 5; i++) {
14833
- const y = padding + (height - 2 * padding) * (i / 5);
14904
+ ctx.fillStyle = "#666";
14905
+ ctx.font = "11px Arial";
14906
+ ctx.textAlign = "right";
14907
+ const ySteps = 5;
14908
+ for (let i = 0; i <= ySteps; i++) {
14909
+ const y = padding.top + (height - padding.top - padding.bottom) * (i / ySteps);
14910
+ const value = chartMaxY - chartRangeY * i / ySteps;
14834
14911
  ctx.beginPath();
14835
- ctx.moveTo(padding, y);
14836
- ctx.lineTo(width - padding, y);
14912
+ ctx.moveTo(padding.left, y);
14913
+ ctx.lineTo(width - padding.right, y);
14837
14914
  ctx.stroke();
14838
- ctx.fillStyle = "#7f8c8d";
14839
- ctx.font = "12px Arial";
14840
- ctx.textAlign = "right";
14841
- ctx.fillText(`${100 - i * 20}%`, padding - 10, y + 4);
14915
+ ctx.fillText(`${value.toFixed(2)}`, padding.left - 8, y + 4);
14842
14916
  }
14843
- const points = data.telemetry;
14844
- if (points.length < 2) return;
14845
- const xScale = (width - 2 * padding) / (points.length - 1);
14846
- const yScale = (height - 2 * padding) / 100;
14917
+ ctx.save();
14918
+ ctx.translate(15, height / 2);
14919
+ ctx.rotate(-Math.PI / 2);
14920
+ ctx.textAlign = "center";
14921
+ ctx.fillStyle = "#666";
14922
+ ctx.font = "12px Arial";
14923
+ ctx.fillText("m.c.a", 0, 0);
14924
+ ctx.restore();
14925
+ ctx.strokeStyle = "#ccc";
14926
+ ctx.lineWidth = 1;
14927
+ ctx.beginPath();
14928
+ ctx.moveTo(padding.left, padding.top);
14929
+ ctx.lineTo(padding.left, height - padding.bottom);
14930
+ ctx.lineTo(width - padding.right, height - padding.bottom);
14931
+ ctx.stroke();
14932
+ const chartWidth = width - padding.left - padding.right;
14933
+ const chartHeight = height - padding.top - padding.bottom;
14934
+ const xScale = chartWidth / (points.length - 1);
14935
+ ctx.beginPath();
14936
+ ctx.moveTo(padding.left, height - padding.bottom);
14937
+ points.forEach((point, index) => {
14938
+ const x = padding.left + index * xScale;
14939
+ const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
14940
+ ctx.lineTo(x, y);
14941
+ });
14942
+ ctx.lineTo(padding.left + (points.length - 1) * xScale, height - padding.bottom);
14943
+ ctx.closePath();
14944
+ const gradient = ctx.createLinearGradient(0, padding.top, 0, height - padding.bottom);
14945
+ gradient.addColorStop(0, "rgba(52, 152, 219, 0.3)");
14946
+ gradient.addColorStop(1, "rgba(52, 152, 219, 0.05)");
14947
+ ctx.fillStyle = gradient;
14948
+ ctx.fill();
14847
14949
  ctx.strokeStyle = "#3498db";
14848
14950
  ctx.lineWidth = 2;
14951
+ ctx.lineJoin = "round";
14952
+ ctx.lineCap = "round";
14849
14953
  ctx.beginPath();
14850
14954
  points.forEach((point, index) => {
14851
- const x = padding + index * xScale;
14852
- const y = height - padding - point.value * yScale;
14955
+ const x = padding.left + index * xScale;
14956
+ const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
14853
14957
  if (index === 0) {
14854
14958
  ctx.moveTo(x, y);
14855
14959
  } else {
@@ -14857,14 +14961,56 @@ ${rangeText}`;
14857
14961
  }
14858
14962
  });
14859
14963
  ctx.stroke();
14860
- ctx.fillStyle = "#3498db";
14861
- points.forEach((point, index) => {
14862
- const x = padding + index * xScale;
14863
- const y = height - padding - point.value * yScale;
14864
- ctx.beginPath();
14865
- ctx.arc(x, y, 3, 0, 2 * Math.PI);
14866
- ctx.fill();
14867
- });
14964
+ if (points.length <= 50) {
14965
+ ctx.fillStyle = "#3498db";
14966
+ points.forEach((point, index) => {
14967
+ const x = padding.left + index * xScale;
14968
+ const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
14969
+ ctx.beginPath();
14970
+ ctx.arc(x, y, 3, 0, 2 * Math.PI);
14971
+ ctx.fill();
14972
+ });
14973
+ }
14974
+ ctx.fillStyle = "#888";
14975
+ ctx.font = "10px Arial";
14976
+ ctx.textAlign = "center";
14977
+ const xLabelCount = Math.min(6, points.length);
14978
+ const xLabelStep = Math.floor(points.length / xLabelCount);
14979
+ for (let i = 0; i < points.length; i += xLabelStep) {
14980
+ const x = padding.left + i * xScale;
14981
+ const date = new Date(points[i].ts);
14982
+ const label = `${date.getDate()}/${date.getMonth() + 1}`;
14983
+ ctx.fillText(label, x, height - padding.bottom + 16);
14984
+ }
14985
+ if (points.length > 1) {
14986
+ const lastX = padding.left + (points.length - 1) * xScale;
14987
+ const lastDate = new Date(points[points.length - 1].ts);
14988
+ const lastLabel = `${lastDate.getDate()}/${lastDate.getMonth() + 1}`;
14989
+ ctx.fillText(lastLabel, lastX, height - padding.bottom + 16);
14990
+ }
14991
+ }
14992
+ /**
14993
+ * Update data and re-render chart
14994
+ */
14995
+ updateData(data) {
14996
+ this.config.data = data;
14997
+ if (this.modal) {
14998
+ const bodyEl = this.modal.querySelector(".myio-water-tank-modal-body");
14999
+ if (bodyEl) {
15000
+ bodyEl.innerHTML = `
15001
+ ${this.renderDateRangePicker()}
15002
+ ${this.renderTankVisualization()}
15003
+ ${this.renderChart()}
15004
+ `;
15005
+ const applyDatesBtn = this.modal.querySelector("#myio-water-tank-apply-dates");
15006
+ if (applyDatesBtn) {
15007
+ applyDatesBtn.addEventListener("click", () => this.handleDateRangeChange());
15008
+ }
15009
+ requestAnimationFrame(() => {
15010
+ this.renderCanvasChart();
15011
+ });
15012
+ }
15013
+ }
14868
15014
  }
14869
15015
  /**
14870
15016
  * Show the modal with animation
@@ -15129,8 +15275,9 @@ ${rangeText}`;
15129
15275
  data: this.data,
15130
15276
  onExport: () => this.handleExport(),
15131
15277
  onError: (error) => this.handleError(error),
15132
- onClose: () => this.close()
15278
+ onClose: () => this.close(),
15133
15279
  // Call close() to destroy view and trigger user callback
15280
+ onDateRangeChange: (startTs, endTs) => this.handleDateRangeChange(startTs, endTs)
15134
15281
  });
15135
15282
  this.view.render();
15136
15283
  this.view.show();
@@ -15162,6 +15309,39 @@ ${rangeText}`;
15162
15309
  }
15163
15310
  this.handleClose();
15164
15311
  }
15312
+ /**
15313
+ * Handle date range change from view
15314
+ */
15315
+ async handleDateRangeChange(startTs, endTs) {
15316
+ console.log("[WaterTankModal] Date range changed:", {
15317
+ startTs,
15318
+ endTs,
15319
+ startDate: new Date(startTs).toISOString(),
15320
+ endDate: new Date(endTs).toISOString()
15321
+ });
15322
+ this.options.startTs = startTs;
15323
+ this.options.endTs = endTs;
15324
+ this.context.timeRange.startTs = startTs;
15325
+ this.context.timeRange.endTs = endTs;
15326
+ try {
15327
+ console.log("[WaterTankModal] Fetching new data for date range...");
15328
+ this.data = await this.fetchTelemetryData();
15329
+ if (this.view) {
15330
+ this.view.updateData(this.data);
15331
+ }
15332
+ if (this.options.onDataLoaded) {
15333
+ try {
15334
+ this.options.onDataLoaded(this.data);
15335
+ } catch (callbackError) {
15336
+ console.warn("[WaterTankModal] onDataLoaded callback error:", callbackError);
15337
+ }
15338
+ }
15339
+ console.log("[WaterTankModal] Data refreshed successfully");
15340
+ } catch (error) {
15341
+ console.error("[WaterTankModal] Failed to fetch data for new date range:", error);
15342
+ this.handleError(error);
15343
+ }
15344
+ }
15165
15345
  /**
15166
15346
  * Handle export functionality
15167
15347
  */