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.
package/dist/index.js CHANGED
@@ -14611,7 +14611,7 @@ var WaterTankModalView = class {
14611
14611
  maxLevel: "Maximum Level",
14612
14612
  dateRange: "Date Range",
14613
14613
  deviceInfo: "Device Information",
14614
- levelChart: "Level Chart",
14614
+ levelChart: "Water Level History (m.c.a)",
14615
14615
  percentUnit: "%",
14616
14616
  status: {
14617
14617
  critical: "Critical",
@@ -14642,6 +14642,20 @@ var WaterTankModalView = class {
14642
14642
  return { status: "full", color: "#3498db", label: this.i18n.status.full };
14643
14643
  }
14644
14644
  }
14645
+ /**
14646
+ * Get tank image URL based on level percentage (same logic as device card)
14647
+ */
14648
+ getTankImageUrl(percentage) {
14649
+ if (percentage >= 70) {
14650
+ return "https://dashboard.myio-bas.com/api/images/public/3t6WVhMQJFsrKA8bSZmrngDsNPkZV7fq";
14651
+ } else if (percentage >= 40) {
14652
+ return "https://dashboard.myio-bas.com/api/images/public/4UBbShfXCVWR9wcw6IzVMNran4x1EW5n";
14653
+ } else if (percentage >= 20) {
14654
+ return "https://dashboard.myio-bas.com/api/images/public/aB9nX28F54fBBQs1Ht8jKUdYAMcq9QSm";
14655
+ } else {
14656
+ return "https://dashboard.myio-bas.com/api/images/public/qLdwhV4qw295poSCa7HinpnmXoN7dAPO";
14657
+ }
14658
+ }
14645
14659
  /**
14646
14660
  * Format timestamp to readable date
14647
14661
  */
@@ -14654,13 +14668,18 @@ var WaterTankModalView = class {
14654
14668
  }
14655
14669
  return dateStr;
14656
14670
  }
14671
+ /**
14672
+ * Format timestamp to ISO date string for input
14673
+ */
14674
+ formatDateForInput(ts) {
14675
+ const date = new Date(ts);
14676
+ return date.toISOString().split("T")[0];
14677
+ }
14657
14678
  /**
14658
14679
  * Render the modal HTML
14659
14680
  */
14660
14681
  render() {
14661
- const { context, params, data } = this.config;
14662
- const currentLevel = data.summary.currentLevel ?? context.device.currentLevel ?? 0;
14663
- const levelStatus = this.getLevelStatus(currentLevel);
14682
+ const { params } = this.config;
14664
14683
  this.overlay = document.createElement("div");
14665
14684
  this.overlay.className = "myio-water-tank-modal-overlay";
14666
14685
  this.overlay.style.cssText = `
@@ -14681,9 +14700,9 @@ var WaterTankModalView = class {
14681
14700
  this.modal.className = "myio-water-tank-modal";
14682
14701
  this.modal.style.cssText = `
14683
14702
  background: white;
14684
- border-radius: 8px;
14703
+ border-radius: 12px;
14685
14704
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
14686
- width: ${params.ui?.width || 900}px;
14705
+ width: ${params.ui?.width || 700}px;
14687
14706
  max-width: 95vw;
14688
14707
  max-height: 90vh;
14689
14708
  display: flex;
@@ -14749,97 +14768,150 @@ var WaterTankModalView = class {
14749
14768
  overflow-y: auto;
14750
14769
  flex: 1;
14751
14770
  ">
14752
- ${this.renderSummaryCards()}
14771
+ ${this.renderDateRangePicker()}
14772
+ ${this.renderTankVisualization()}
14753
14773
  ${this.renderChart()}
14754
- ${this.renderDeviceInfo()}
14755
14774
  </div>
14756
14775
  ${this.renderFooter()}
14757
14776
  `;
14758
14777
  }
14759
14778
  /**
14760
- * Render summary cards
14779
+ * Render date range picker
14761
14780
  */
14762
- renderSummaryCards() {
14763
- const { data } = this.config;
14764
- const currentLevel = data.summary.currentLevel ?? 0;
14765
- const levelStatus = this.getLevelStatus(currentLevel);
14781
+ renderDateRangePicker() {
14782
+ const { params } = this.config;
14783
+ const startDate = this.formatDateForInput(params.startTs);
14784
+ const endDate = this.formatDateForInput(params.endTs);
14766
14785
  return `
14767
14786
  <div style="
14768
- display: grid;
14769
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
14787
+ background: #f8f9fa;
14788
+ border: 1px solid #e0e0e0;
14789
+ border-radius: 8px;
14790
+ padding: 16px;
14791
+ margin-bottom: 20px;
14792
+ display: flex;
14793
+ align-items: center;
14770
14794
  gap: 16px;
14771
- margin-bottom: 24px;
14795
+ flex-wrap: wrap;
14772
14796
  ">
14773
- ${this.renderSummaryCard(
14774
- this.i18n.currentLevel,
14775
- `${currentLevel.toFixed(1)}${this.i18n.percentUnit}`,
14776
- levelStatus.color,
14777
- levelStatus.label
14778
- )}
14779
- ${this.renderSummaryCard(
14780
- this.i18n.averageLevel,
14781
- `${data.summary.avgLevel.toFixed(1)}${this.i18n.percentUnit}`,
14782
- "#3498db"
14783
- )}
14784
- ${this.renderSummaryCard(
14785
- this.i18n.minLevel,
14786
- `${data.summary.minLevel.toFixed(1)}${this.i18n.percentUnit}`,
14787
- "#e74c3c"
14788
- )}
14789
- ${this.renderSummaryCard(
14790
- this.i18n.maxLevel,
14791
- `${data.summary.maxLevel.toFixed(1)}${this.i18n.percentUnit}`,
14792
- "#27ae60"
14793
- )}
14797
+ <div style="display: flex; align-items: center; gap: 8px;">
14798
+ <label style="font-size: 14px; font-weight: 500; color: #2c3e50;">From:</label>
14799
+ <input type="date" id="myio-water-tank-start-date" value="${startDate}" style="
14800
+ padding: 8px 12px;
14801
+ border: 1px solid #ddd;
14802
+ border-radius: 6px;
14803
+ font-size: 14px;
14804
+ color: #2c3e50;
14805
+ cursor: pointer;
14806
+ "/>
14807
+ </div>
14808
+ <div style="display: flex; align-items: center; gap: 8px;">
14809
+ <label style="font-size: 14px; font-weight: 500; color: #2c3e50;">To:</label>
14810
+ <input type="date" id="myio-water-tank-end-date" value="${endDate}" style="
14811
+ padding: 8px 12px;
14812
+ border: 1px solid #ddd;
14813
+ border-radius: 6px;
14814
+ font-size: 14px;
14815
+ color: #2c3e50;
14816
+ cursor: pointer;
14817
+ "/>
14818
+ </div>
14819
+ <button id="myio-water-tank-apply-dates" style="
14820
+ background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
14821
+ color: white;
14822
+ border: none;
14823
+ padding: 8px 20px;
14824
+ border-radius: 6px;
14825
+ font-size: 14px;
14826
+ font-weight: 500;
14827
+ cursor: pointer;
14828
+ transition: all 0.2s ease;
14829
+ ">
14830
+ Apply
14831
+ </button>
14794
14832
  </div>
14795
14833
  `;
14796
14834
  }
14797
14835
  /**
14798
- * Render a single summary card
14836
+ * Render tank visualization with percentage
14799
14837
  */
14800
- renderSummaryCard(label, value, color, badge) {
14838
+ renderTankVisualization() {
14839
+ const { data, context } = this.config;
14840
+ let percentage = 0;
14841
+ const percentagePoints = data.telemetry.filter((p) => p.key === "water_percentage");
14842
+ if (percentagePoints.length > 0) {
14843
+ const latestPercentage = percentagePoints[percentagePoints.length - 1].value;
14844
+ percentage = latestPercentage <= 1 ? latestPercentage * 100 : latestPercentage;
14845
+ } else if (context.device.currentLevel !== void 0) {
14846
+ const level = context.device.currentLevel;
14847
+ percentage = level <= 1 ? level * 100 : level;
14848
+ }
14849
+ const levelStatus = this.getLevelStatus(percentage);
14850
+ const tankImageUrl = this.getTankImageUrl(percentage);
14801
14851
  return `
14802
14852
  <div style="
14803
- background: linear-gradient(135deg, ${color}15 0%, ${color}05 100%);
14804
- border: 1px solid ${color}30;
14805
- border-radius: 8px;
14806
- padding: 16px;
14807
- position: relative;
14853
+ display: flex;
14854
+ align-items: center;
14855
+ justify-content: center;
14856
+ gap: 32px;
14857
+ padding: 24px;
14858
+ background: linear-gradient(135deg, ${levelStatus.color}10 0%, ${levelStatus.color}05 100%);
14859
+ border: 1px solid ${levelStatus.color}30;
14860
+ border-radius: 12px;
14861
+ margin-bottom: 24px;
14808
14862
  ">
14809
- ${badge ? `
14863
+ <div style="
14864
+ display: flex;
14865
+ flex-direction: column;
14866
+ align-items: center;
14867
+ gap: 12px;
14868
+ ">
14869
+ <img src="${tankImageUrl}" alt="Water Tank" style="
14870
+ width: 120px;
14871
+ height: auto;
14872
+ filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1));
14873
+ "/>
14810
14874
  <div style="
14811
- position: absolute;
14812
- top: 12px;
14813
- right: 12px;
14814
- background: ${color};
14875
+ background: ${levelStatus.color};
14815
14876
  color: white;
14816
- padding: 4px 8px;
14817
- border-radius: 4px;
14818
- font-size: 10px;
14877
+ padding: 4px 12px;
14878
+ border-radius: 20px;
14879
+ font-size: 12px;
14819
14880
  font-weight: 600;
14820
14881
  text-transform: uppercase;
14821
- ">${badge}</div>
14822
- ` : ""}
14823
- <div style="
14824
- font-size: 12px;
14825
- color: #7f8c8d;
14826
- margin-bottom: 8px;
14827
- font-weight: 500;
14828
- ">${label}</div>
14882
+ ">${levelStatus.label}</div>
14883
+ </div>
14884
+
14829
14885
  <div style="
14830
- font-size: 28px;
14831
- font-weight: 700;
14832
- color: ${color};
14833
- ">${value}</div>
14886
+ display: flex;
14887
+ flex-direction: column;
14888
+ align-items: center;
14889
+ gap: 8px;
14890
+ ">
14891
+ <div style="
14892
+ font-size: 48px;
14893
+ font-weight: 700;
14894
+ color: ${levelStatus.color};
14895
+ line-height: 1;
14896
+ ">${percentage.toFixed(1)}%</div>
14897
+ <div style="
14898
+ font-size: 14px;
14899
+ color: #7f8c8d;
14900
+ font-weight: 500;
14901
+ ">${this.i18n.currentLevel}</div>
14902
+ </div>
14834
14903
  </div>
14835
14904
  `;
14836
14905
  }
14837
14906
  /**
14838
- * Render chart section
14907
+ * Render chart section - shows water_level (m.c.a) over time
14839
14908
  */
14840
14909
  renderChart() {
14841
14910
  const { data } = this.config;
14842
- if (data.telemetry.length === 0) {
14911
+ const waterLevelPoints = data.telemetry.filter(
14912
+ (p) => p.key === "water_level" || p.key === "waterLevel" || p.key === "nivel" || p.key === "level"
14913
+ );
14914
+ if (waterLevelPoints.length === 0) {
14843
14915
  return `
14844
14916
  <div style="
14845
14917
  background: #f8f9fa;
@@ -14847,20 +14919,23 @@ var WaterTankModalView = class {
14847
14919
  border-radius: 8px;
14848
14920
  padding: 48px;
14849
14921
  text-align: center;
14850
- margin-bottom: 24px;
14851
14922
  ">
14852
14923
  <div style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;">\u{1F4CA}</div>
14853
14924
  <div style="color: #7f8c8d; font-size: 16px;">${this.i18n.noData}</div>
14925
+ <div style="color: #bdc3c7; font-size: 13px; margin-top: 8px;">
14926
+ No water_level (m.c.a) data available for this period
14927
+ </div>
14854
14928
  </div>
14855
14929
  `;
14856
14930
  }
14931
+ const firstTs = waterLevelPoints[0]?.ts;
14932
+ const lastTs = waterLevelPoints[waterLevelPoints.length - 1]?.ts;
14857
14933
  return `
14858
14934
  <div style="
14859
14935
  background: white;
14860
14936
  border: 1px solid #e0e0e0;
14861
14937
  border-radius: 8px;
14862
14938
  padding: 20px;
14863
- margin-bottom: 24px;
14864
14939
  ">
14865
14940
  <h3 style="
14866
14941
  margin: 0 0 16px 0;
@@ -14868,56 +14943,18 @@ var WaterTankModalView = class {
14868
14943
  font-weight: 600;
14869
14944
  color: #2c3e50;
14870
14945
  ">${this.i18n.levelChart}</h3>
14871
- <canvas id="myio-water-tank-chart" style="width: 100%; height: 300px;"></canvas>
14872
- <div style="
14873
- margin-top: 16px;
14874
- font-size: 12px;
14875
- color: #7f8c8d;
14876
- text-align: center;
14877
- ">
14878
- ${this.i18n.dateRange}: ${this.formatDate(data.summary.firstReadingTs, false)} - ${this.formatDate(data.summary.lastReadingTs, false)}
14879
- </div>
14880
- </div>
14881
- `;
14882
- }
14883
- /**
14884
- * Render device info section
14885
- */
14886
- renderDeviceInfo() {
14887
- const { context, data } = this.config;
14888
- return `
14889
- <div style="
14890
- background: #f8f9fa;
14891
- border: 1px solid #e0e0e0;
14892
- border-radius: 8px;
14893
- padding: 20px;
14894
- ">
14895
- <h3 style="
14896
- margin: 0 0 16px 0;
14897
- font-size: 16px;
14898
- font-weight: 600;
14899
- color: #2c3e50;
14900
- ">${this.i18n.deviceInfo}</h3>
14901
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 12px;">
14902
- ${this.renderInfoRow("Device ID", context.device.id)}
14903
- ${this.renderInfoRow("Label", context.device.label)}
14904
- ${context.device.type ? this.renderInfoRow("Type", context.device.type) : ""}
14905
- ${context.metadata.slaveId ? this.renderInfoRow("Slave ID", String(context.metadata.slaveId)) : ""}
14906
- ${context.metadata.centralId ? this.renderInfoRow("Central ID", context.metadata.centralId) : ""}
14907
- ${this.renderInfoRow("Total Readings", String(data.summary.totalReadings))}
14908
- ${this.renderInfoRow("Data Keys", data.metadata.keys.join(", "))}
14909
- </div>
14910
- </div>
14911
- `;
14912
- }
14913
- /**
14914
- * Render an info row
14915
- */
14916
- renderInfoRow(label, value) {
14917
- return `
14918
- <div style="display: flex; justify-content: space-between; align-items: center;">
14919
- <span style="color: #7f8c8d; font-size: 13px;">${label}:</span>
14920
- <span style="color: #2c3e50; font-size: 13px; font-weight: 500; margin-left: 8px;">${value}</span>
14946
+ <canvas id="myio-water-tank-chart" style="width: 100%; height: 280px;"></canvas>
14947
+ ${firstTs && lastTs ? `
14948
+ <div style="
14949
+ margin-top: 12px;
14950
+ font-size: 12px;
14951
+ color: #7f8c8d;
14952
+ text-align: center;
14953
+ ">
14954
+ ${this.formatDate(firstTs, false)} \u2014 ${this.formatDate(lastTs, false)}
14955
+ (${waterLevelPoints.length} readings)
14956
+ </div>
14957
+ ` : ""}
14921
14958
  </div>
14922
14959
  `;
14923
14960
  }
@@ -14966,6 +15003,10 @@ var WaterTankModalView = class {
14966
15003
  if (exportBtn) {
14967
15004
  exportBtn.addEventListener("click", () => this.config.onExport());
14968
15005
  }
15006
+ const applyDatesBtn = this.modal.querySelector("#myio-water-tank-apply-dates");
15007
+ if (applyDatesBtn) {
15008
+ applyDatesBtn.addEventListener("click", () => this.handleDateRangeChange());
15009
+ }
14969
15010
  this.overlay.addEventListener("click", (e) => {
14970
15011
  if (e.target === this.overlay) {
14971
15012
  this.config.onClose();
@@ -14979,59 +15020,122 @@ var WaterTankModalView = class {
14979
15020
  this.renderCanvasChart();
14980
15021
  });
14981
15022
  }
15023
+ /**
15024
+ * Handle date range change
15025
+ */
15026
+ handleDateRangeChange() {
15027
+ if (!this.modal) return;
15028
+ const startInput = this.modal.querySelector("#myio-water-tank-start-date");
15029
+ const endInput = this.modal.querySelector("#myio-water-tank-end-date");
15030
+ if (startInput && endInput) {
15031
+ const startTs = new Date(startInput.value).setHours(0, 0, 0, 0);
15032
+ const endTs = new Date(endInput.value).setHours(23, 59, 59, 999);
15033
+ if (startTs >= endTs) {
15034
+ alert("Start date must be before end date");
15035
+ return;
15036
+ }
15037
+ console.log("[WaterTankModalView] Date range changed:", {
15038
+ startTs,
15039
+ endTs,
15040
+ startDate: new Date(startTs).toISOString(),
15041
+ endDate: new Date(endTs).toISOString()
15042
+ });
15043
+ if (this.config.onDateRangeChange) {
15044
+ this.config.onDateRangeChange(startTs, endTs);
15045
+ }
15046
+ }
15047
+ }
14982
15048
  handleEscapeKey(e) {
14983
15049
  if (e.key === "Escape") {
14984
15050
  this.config.onClose();
14985
15051
  }
14986
15052
  }
14987
15053
  /**
14988
- * Render chart using Canvas API
15054
+ * Render chart using Canvas API - shows water_level (m.c.a) over time
14989
15055
  */
14990
15056
  renderCanvasChart() {
14991
15057
  const canvas = document.getElementById("myio-water-tank-chart");
14992
15058
  if (!canvas) return;
14993
15059
  const { data } = this.config;
14994
- if (data.telemetry.length === 0) return;
15060
+ const points = data.telemetry.filter(
15061
+ (p) => p.key === "water_level" || p.key === "waterLevel" || p.key === "nivel" || p.key === "level"
15062
+ );
15063
+ if (points.length < 2) return;
14995
15064
  const ctx = canvas.getContext("2d");
14996
15065
  if (!ctx) return;
14997
15066
  const rect = canvas.getBoundingClientRect();
14998
15067
  canvas.width = rect.width * window.devicePixelRatio;
14999
- canvas.height = 300 * window.devicePixelRatio;
15068
+ canvas.height = 280 * window.devicePixelRatio;
15000
15069
  ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
15001
15070
  const width = rect.width;
15002
- const height = 300;
15003
- const padding = 40;
15071
+ const height = 280;
15072
+ const padding = { top: 20, right: 20, bottom: 40, left: 60 };
15004
15073
  ctx.clearRect(0, 0, width, height);
15005
- ctx.strokeStyle = "#e0e0e0";
15074
+ const values = points.map((p) => p.value);
15075
+ const minValue = Math.min(...values);
15076
+ const maxValue = Math.max(...values);
15077
+ const valueRange = maxValue - minValue || 1;
15078
+ const valuePadding = valueRange * 0.1;
15079
+ const chartMinY = minValue - valuePadding;
15080
+ const chartMaxY = maxValue + valuePadding;
15081
+ const chartRangeY = chartMaxY - chartMinY;
15082
+ ctx.fillStyle = "#fafafa";
15083
+ ctx.fillRect(padding.left, padding.top, width - padding.left - padding.right, height - padding.top - padding.bottom);
15084
+ ctx.strokeStyle = "#e8e8e8";
15006
15085
  ctx.lineWidth = 1;
15007
- ctx.beginPath();
15008
- ctx.moveTo(padding, padding);
15009
- ctx.lineTo(padding, height - padding);
15010
- ctx.lineTo(width - padding, height - padding);
15011
- ctx.stroke();
15012
- ctx.strokeStyle = "#f0f0f0";
15013
- ctx.lineWidth = 0.5;
15014
- for (let i = 0; i <= 5; i++) {
15015
- const y = padding + (height - 2 * padding) * (i / 5);
15086
+ ctx.fillStyle = "#666";
15087
+ ctx.font = "11px Arial";
15088
+ ctx.textAlign = "right";
15089
+ const ySteps = 5;
15090
+ for (let i = 0; i <= ySteps; i++) {
15091
+ const y = padding.top + (height - padding.top - padding.bottom) * (i / ySteps);
15092
+ const value = chartMaxY - chartRangeY * i / ySteps;
15016
15093
  ctx.beginPath();
15017
- ctx.moveTo(padding, y);
15018
- ctx.lineTo(width - padding, y);
15094
+ ctx.moveTo(padding.left, y);
15095
+ ctx.lineTo(width - padding.right, y);
15019
15096
  ctx.stroke();
15020
- ctx.fillStyle = "#7f8c8d";
15021
- ctx.font = "12px Arial";
15022
- ctx.textAlign = "right";
15023
- ctx.fillText(`${100 - i * 20}%`, padding - 10, y + 4);
15097
+ ctx.fillText(`${value.toFixed(2)}`, padding.left - 8, y + 4);
15024
15098
  }
15025
- const points = data.telemetry;
15026
- if (points.length < 2) return;
15027
- const xScale = (width - 2 * padding) / (points.length - 1);
15028
- const yScale = (height - 2 * padding) / 100;
15099
+ ctx.save();
15100
+ ctx.translate(15, height / 2);
15101
+ ctx.rotate(-Math.PI / 2);
15102
+ ctx.textAlign = "center";
15103
+ ctx.fillStyle = "#666";
15104
+ ctx.font = "12px Arial";
15105
+ ctx.fillText("m.c.a", 0, 0);
15106
+ ctx.restore();
15107
+ ctx.strokeStyle = "#ccc";
15108
+ ctx.lineWidth = 1;
15109
+ ctx.beginPath();
15110
+ ctx.moveTo(padding.left, padding.top);
15111
+ ctx.lineTo(padding.left, height - padding.bottom);
15112
+ ctx.lineTo(width - padding.right, height - padding.bottom);
15113
+ ctx.stroke();
15114
+ const chartWidth = width - padding.left - padding.right;
15115
+ const chartHeight = height - padding.top - padding.bottom;
15116
+ const xScale = chartWidth / (points.length - 1);
15117
+ ctx.beginPath();
15118
+ ctx.moveTo(padding.left, height - padding.bottom);
15119
+ points.forEach((point, index) => {
15120
+ const x = padding.left + index * xScale;
15121
+ const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
15122
+ ctx.lineTo(x, y);
15123
+ });
15124
+ ctx.lineTo(padding.left + (points.length - 1) * xScale, height - padding.bottom);
15125
+ ctx.closePath();
15126
+ const gradient = ctx.createLinearGradient(0, padding.top, 0, height - padding.bottom);
15127
+ gradient.addColorStop(0, "rgba(52, 152, 219, 0.3)");
15128
+ gradient.addColorStop(1, "rgba(52, 152, 219, 0.05)");
15129
+ ctx.fillStyle = gradient;
15130
+ ctx.fill();
15029
15131
  ctx.strokeStyle = "#3498db";
15030
15132
  ctx.lineWidth = 2;
15133
+ ctx.lineJoin = "round";
15134
+ ctx.lineCap = "round";
15031
15135
  ctx.beginPath();
15032
15136
  points.forEach((point, index) => {
15033
- const x = padding + index * xScale;
15034
- const y = height - padding - point.value * yScale;
15137
+ const x = padding.left + index * xScale;
15138
+ const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
15035
15139
  if (index === 0) {
15036
15140
  ctx.moveTo(x, y);
15037
15141
  } else {
@@ -15039,14 +15143,56 @@ var WaterTankModalView = class {
15039
15143
  }
15040
15144
  });
15041
15145
  ctx.stroke();
15042
- ctx.fillStyle = "#3498db";
15043
- points.forEach((point, index) => {
15044
- const x = padding + index * xScale;
15045
- const y = height - padding - point.value * yScale;
15046
- ctx.beginPath();
15047
- ctx.arc(x, y, 3, 0, 2 * Math.PI);
15048
- ctx.fill();
15049
- });
15146
+ if (points.length <= 50) {
15147
+ ctx.fillStyle = "#3498db";
15148
+ points.forEach((point, index) => {
15149
+ const x = padding.left + index * xScale;
15150
+ const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
15151
+ ctx.beginPath();
15152
+ ctx.arc(x, y, 3, 0, 2 * Math.PI);
15153
+ ctx.fill();
15154
+ });
15155
+ }
15156
+ ctx.fillStyle = "#888";
15157
+ ctx.font = "10px Arial";
15158
+ ctx.textAlign = "center";
15159
+ const xLabelCount = Math.min(6, points.length);
15160
+ const xLabelStep = Math.floor(points.length / xLabelCount);
15161
+ for (let i = 0; i < points.length; i += xLabelStep) {
15162
+ const x = padding.left + i * xScale;
15163
+ const date = new Date(points[i].ts);
15164
+ const label = `${date.getDate()}/${date.getMonth() + 1}`;
15165
+ ctx.fillText(label, x, height - padding.bottom + 16);
15166
+ }
15167
+ if (points.length > 1) {
15168
+ const lastX = padding.left + (points.length - 1) * xScale;
15169
+ const lastDate = new Date(points[points.length - 1].ts);
15170
+ const lastLabel = `${lastDate.getDate()}/${lastDate.getMonth() + 1}`;
15171
+ ctx.fillText(lastLabel, lastX, height - padding.bottom + 16);
15172
+ }
15173
+ }
15174
+ /**
15175
+ * Update data and re-render chart
15176
+ */
15177
+ updateData(data) {
15178
+ this.config.data = data;
15179
+ if (this.modal) {
15180
+ const bodyEl = this.modal.querySelector(".myio-water-tank-modal-body");
15181
+ if (bodyEl) {
15182
+ bodyEl.innerHTML = `
15183
+ ${this.renderDateRangePicker()}
15184
+ ${this.renderTankVisualization()}
15185
+ ${this.renderChart()}
15186
+ `;
15187
+ const applyDatesBtn = this.modal.querySelector("#myio-water-tank-apply-dates");
15188
+ if (applyDatesBtn) {
15189
+ applyDatesBtn.addEventListener("click", () => this.handleDateRangeChange());
15190
+ }
15191
+ requestAnimationFrame(() => {
15192
+ this.renderCanvasChart();
15193
+ });
15194
+ }
15195
+ }
15050
15196
  }
15051
15197
  /**
15052
15198
  * Show the modal with animation
@@ -15311,8 +15457,9 @@ var WaterTankModal = class {
15311
15457
  data: this.data,
15312
15458
  onExport: () => this.handleExport(),
15313
15459
  onError: (error) => this.handleError(error),
15314
- onClose: () => this.close()
15460
+ onClose: () => this.close(),
15315
15461
  // Call close() to destroy view and trigger user callback
15462
+ onDateRangeChange: (startTs, endTs) => this.handleDateRangeChange(startTs, endTs)
15316
15463
  });
15317
15464
  this.view.render();
15318
15465
  this.view.show();
@@ -15344,6 +15491,39 @@ var WaterTankModal = class {
15344
15491
  }
15345
15492
  this.handleClose();
15346
15493
  }
15494
+ /**
15495
+ * Handle date range change from view
15496
+ */
15497
+ async handleDateRangeChange(startTs, endTs) {
15498
+ console.log("[WaterTankModal] Date range changed:", {
15499
+ startTs,
15500
+ endTs,
15501
+ startDate: new Date(startTs).toISOString(),
15502
+ endDate: new Date(endTs).toISOString()
15503
+ });
15504
+ this.options.startTs = startTs;
15505
+ this.options.endTs = endTs;
15506
+ this.context.timeRange.startTs = startTs;
15507
+ this.context.timeRange.endTs = endTs;
15508
+ try {
15509
+ console.log("[WaterTankModal] Fetching new data for date range...");
15510
+ this.data = await this.fetchTelemetryData();
15511
+ if (this.view) {
15512
+ this.view.updateData(this.data);
15513
+ }
15514
+ if (this.options.onDataLoaded) {
15515
+ try {
15516
+ this.options.onDataLoaded(this.data);
15517
+ } catch (callbackError) {
15518
+ console.warn("[WaterTankModal] onDataLoaded callback error:", callbackError);
15519
+ }
15520
+ }
15521
+ console.log("[WaterTankModal] Data refreshed successfully");
15522
+ } catch (error) {
15523
+ console.error("[WaterTankModal] Failed to fetch data for new date range:", error);
15524
+ this.handleError(error);
15525
+ }
15526
+ }
15347
15527
  /**
15348
15528
  * Handle export functionality
15349
15529
  */