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.cjs +334 -154
- package/dist/index.js +334 -154
- package/dist/myio-js-library.umd.js +334 -154
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
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
|
|
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 {
|
|
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:
|
|
14703
|
+
border-radius: 12px;
|
|
14685
14704
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
|
14686
|
-
width: ${params.ui?.width ||
|
|
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.
|
|
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
|
|
14779
|
+
* Render date range picker
|
|
14761
14780
|
*/
|
|
14762
|
-
|
|
14763
|
-
const {
|
|
14764
|
-
const
|
|
14765
|
-
const
|
|
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
|
-
|
|
14769
|
-
|
|
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
|
-
|
|
14795
|
+
flex-wrap: wrap;
|
|
14772
14796
|
">
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
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
|
|
14836
|
+
* Render tank visualization with percentage
|
|
14799
14837
|
*/
|
|
14800
|
-
|
|
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
|
-
|
|
14804
|
-
|
|
14805
|
-
|
|
14806
|
-
|
|
14807
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14812
|
-
top: 12px;
|
|
14813
|
-
right: 12px;
|
|
14814
|
-
background: ${color};
|
|
14875
|
+
background: ${levelStatus.color};
|
|
14815
14876
|
color: white;
|
|
14816
|
-
padding: 4px
|
|
14817
|
-
border-radius:
|
|
14818
|
-
font-size:
|
|
14877
|
+
padding: 4px 12px;
|
|
14878
|
+
border-radius: 20px;
|
|
14879
|
+
font-size: 12px;
|
|
14819
14880
|
font-weight: 600;
|
|
14820
14881
|
text-transform: uppercase;
|
|
14821
|
-
">${
|
|
14822
|
-
|
|
14823
|
-
|
|
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
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
|
|
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
|
-
|
|
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:
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
|
|
14879
|
-
|
|
14880
|
-
|
|
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
|
-
|
|
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 =
|
|
15068
|
+
canvas.height = 280 * window.devicePixelRatio;
|
|
15000
15069
|
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
15001
15070
|
const width = rect.width;
|
|
15002
|
-
const height =
|
|
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
|
-
|
|
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.
|
|
15008
|
-
ctx.
|
|
15009
|
-
ctx.
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
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.
|
|
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
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
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 =
|
|
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
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
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
|
*/
|