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.cjs
CHANGED
|
@@ -14719,7 +14719,7 @@ var WaterTankModalView = class {
|
|
|
14719
14719
|
maxLevel: "Maximum Level",
|
|
14720
14720
|
dateRange: "Date Range",
|
|
14721
14721
|
deviceInfo: "Device Information",
|
|
14722
|
-
levelChart: "Level
|
|
14722
|
+
levelChart: "Water Level History (m.c.a)",
|
|
14723
14723
|
percentUnit: "%",
|
|
14724
14724
|
status: {
|
|
14725
14725
|
critical: "Critical",
|
|
@@ -14750,6 +14750,20 @@ var WaterTankModalView = class {
|
|
|
14750
14750
|
return { status: "full", color: "#3498db", label: this.i18n.status.full };
|
|
14751
14751
|
}
|
|
14752
14752
|
}
|
|
14753
|
+
/**
|
|
14754
|
+
* Get tank image URL based on level percentage (same logic as device card)
|
|
14755
|
+
*/
|
|
14756
|
+
getTankImageUrl(percentage) {
|
|
14757
|
+
if (percentage >= 70) {
|
|
14758
|
+
return "https://dashboard.myio-bas.com/api/images/public/3t6WVhMQJFsrKA8bSZmrngDsNPkZV7fq";
|
|
14759
|
+
} else if (percentage >= 40) {
|
|
14760
|
+
return "https://dashboard.myio-bas.com/api/images/public/4UBbShfXCVWR9wcw6IzVMNran4x1EW5n";
|
|
14761
|
+
} else if (percentage >= 20) {
|
|
14762
|
+
return "https://dashboard.myio-bas.com/api/images/public/aB9nX28F54fBBQs1Ht8jKUdYAMcq9QSm";
|
|
14763
|
+
} else {
|
|
14764
|
+
return "https://dashboard.myio-bas.com/api/images/public/qLdwhV4qw295poSCa7HinpnmXoN7dAPO";
|
|
14765
|
+
}
|
|
14766
|
+
}
|
|
14753
14767
|
/**
|
|
14754
14768
|
* Format timestamp to readable date
|
|
14755
14769
|
*/
|
|
@@ -14762,13 +14776,18 @@ var WaterTankModalView = class {
|
|
|
14762
14776
|
}
|
|
14763
14777
|
return dateStr;
|
|
14764
14778
|
}
|
|
14779
|
+
/**
|
|
14780
|
+
* Format timestamp to ISO date string for input
|
|
14781
|
+
*/
|
|
14782
|
+
formatDateForInput(ts) {
|
|
14783
|
+
const date = new Date(ts);
|
|
14784
|
+
return date.toISOString().split("T")[0];
|
|
14785
|
+
}
|
|
14765
14786
|
/**
|
|
14766
14787
|
* Render the modal HTML
|
|
14767
14788
|
*/
|
|
14768
14789
|
render() {
|
|
14769
|
-
const {
|
|
14770
|
-
const currentLevel = data.summary.currentLevel ?? context.device.currentLevel ?? 0;
|
|
14771
|
-
const levelStatus = this.getLevelStatus(currentLevel);
|
|
14790
|
+
const { params } = this.config;
|
|
14772
14791
|
this.overlay = document.createElement("div");
|
|
14773
14792
|
this.overlay.className = "myio-water-tank-modal-overlay";
|
|
14774
14793
|
this.overlay.style.cssText = `
|
|
@@ -14789,9 +14808,9 @@ var WaterTankModalView = class {
|
|
|
14789
14808
|
this.modal.className = "myio-water-tank-modal";
|
|
14790
14809
|
this.modal.style.cssText = `
|
|
14791
14810
|
background: white;
|
|
14792
|
-
border-radius:
|
|
14811
|
+
border-radius: 12px;
|
|
14793
14812
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
|
14794
|
-
width: ${params.ui?.width ||
|
|
14813
|
+
width: ${params.ui?.width || 700}px;
|
|
14795
14814
|
max-width: 95vw;
|
|
14796
14815
|
max-height: 90vh;
|
|
14797
14816
|
display: flex;
|
|
@@ -14857,97 +14876,150 @@ var WaterTankModalView = class {
|
|
|
14857
14876
|
overflow-y: auto;
|
|
14858
14877
|
flex: 1;
|
|
14859
14878
|
">
|
|
14860
|
-
${this.
|
|
14879
|
+
${this.renderDateRangePicker()}
|
|
14880
|
+
${this.renderTankVisualization()}
|
|
14861
14881
|
${this.renderChart()}
|
|
14862
|
-
${this.renderDeviceInfo()}
|
|
14863
14882
|
</div>
|
|
14864
14883
|
${this.renderFooter()}
|
|
14865
14884
|
`;
|
|
14866
14885
|
}
|
|
14867
14886
|
/**
|
|
14868
|
-
* Render
|
|
14887
|
+
* Render date range picker
|
|
14869
14888
|
*/
|
|
14870
|
-
|
|
14871
|
-
const {
|
|
14872
|
-
const
|
|
14873
|
-
const
|
|
14889
|
+
renderDateRangePicker() {
|
|
14890
|
+
const { params } = this.config;
|
|
14891
|
+
const startDate = this.formatDateForInput(params.startTs);
|
|
14892
|
+
const endDate = this.formatDateForInput(params.endTs);
|
|
14874
14893
|
return `
|
|
14875
14894
|
<div style="
|
|
14876
|
-
|
|
14877
|
-
|
|
14895
|
+
background: #f8f9fa;
|
|
14896
|
+
border: 1px solid #e0e0e0;
|
|
14897
|
+
border-radius: 8px;
|
|
14898
|
+
padding: 16px;
|
|
14899
|
+
margin-bottom: 20px;
|
|
14900
|
+
display: flex;
|
|
14901
|
+
align-items: center;
|
|
14878
14902
|
gap: 16px;
|
|
14879
|
-
|
|
14903
|
+
flex-wrap: wrap;
|
|
14880
14904
|
">
|
|
14881
|
-
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
|
|
14888
|
-
|
|
14889
|
-
|
|
14890
|
-
|
|
14891
|
-
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
|
|
14905
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
14906
|
+
<label style="font-size: 14px; font-weight: 500; color: #2c3e50;">From:</label>
|
|
14907
|
+
<input type="date" id="myio-water-tank-start-date" value="${startDate}" style="
|
|
14908
|
+
padding: 8px 12px;
|
|
14909
|
+
border: 1px solid #ddd;
|
|
14910
|
+
border-radius: 6px;
|
|
14911
|
+
font-size: 14px;
|
|
14912
|
+
color: #2c3e50;
|
|
14913
|
+
cursor: pointer;
|
|
14914
|
+
"/>
|
|
14915
|
+
</div>
|
|
14916
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
14917
|
+
<label style="font-size: 14px; font-weight: 500; color: #2c3e50;">To:</label>
|
|
14918
|
+
<input type="date" id="myio-water-tank-end-date" value="${endDate}" style="
|
|
14919
|
+
padding: 8px 12px;
|
|
14920
|
+
border: 1px solid #ddd;
|
|
14921
|
+
border-radius: 6px;
|
|
14922
|
+
font-size: 14px;
|
|
14923
|
+
color: #2c3e50;
|
|
14924
|
+
cursor: pointer;
|
|
14925
|
+
"/>
|
|
14926
|
+
</div>
|
|
14927
|
+
<button id="myio-water-tank-apply-dates" style="
|
|
14928
|
+
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
|
|
14929
|
+
color: white;
|
|
14930
|
+
border: none;
|
|
14931
|
+
padding: 8px 20px;
|
|
14932
|
+
border-radius: 6px;
|
|
14933
|
+
font-size: 14px;
|
|
14934
|
+
font-weight: 500;
|
|
14935
|
+
cursor: pointer;
|
|
14936
|
+
transition: all 0.2s ease;
|
|
14937
|
+
">
|
|
14938
|
+
Apply
|
|
14939
|
+
</button>
|
|
14902
14940
|
</div>
|
|
14903
14941
|
`;
|
|
14904
14942
|
}
|
|
14905
14943
|
/**
|
|
14906
|
-
* Render
|
|
14944
|
+
* Render tank visualization with percentage
|
|
14907
14945
|
*/
|
|
14908
|
-
|
|
14946
|
+
renderTankVisualization() {
|
|
14947
|
+
const { data, context } = this.config;
|
|
14948
|
+
let percentage = 0;
|
|
14949
|
+
const percentagePoints = data.telemetry.filter((p) => p.key === "water_percentage");
|
|
14950
|
+
if (percentagePoints.length > 0) {
|
|
14951
|
+
const latestPercentage = percentagePoints[percentagePoints.length - 1].value;
|
|
14952
|
+
percentage = latestPercentage <= 1 ? latestPercentage * 100 : latestPercentage;
|
|
14953
|
+
} else if (context.device.currentLevel !== void 0) {
|
|
14954
|
+
const level = context.device.currentLevel;
|
|
14955
|
+
percentage = level <= 1 ? level * 100 : level;
|
|
14956
|
+
}
|
|
14957
|
+
const levelStatus = this.getLevelStatus(percentage);
|
|
14958
|
+
const tankImageUrl = this.getTankImageUrl(percentage);
|
|
14909
14959
|
return `
|
|
14910
14960
|
<div style="
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
14914
|
-
|
|
14915
|
-
|
|
14961
|
+
display: flex;
|
|
14962
|
+
align-items: center;
|
|
14963
|
+
justify-content: center;
|
|
14964
|
+
gap: 32px;
|
|
14965
|
+
padding: 24px;
|
|
14966
|
+
background: linear-gradient(135deg, ${levelStatus.color}10 0%, ${levelStatus.color}05 100%);
|
|
14967
|
+
border: 1px solid ${levelStatus.color}30;
|
|
14968
|
+
border-radius: 12px;
|
|
14969
|
+
margin-bottom: 24px;
|
|
14916
14970
|
">
|
|
14917
|
-
|
|
14971
|
+
<div style="
|
|
14972
|
+
display: flex;
|
|
14973
|
+
flex-direction: column;
|
|
14974
|
+
align-items: center;
|
|
14975
|
+
gap: 12px;
|
|
14976
|
+
">
|
|
14977
|
+
<img src="${tankImageUrl}" alt="Water Tank" style="
|
|
14978
|
+
width: 120px;
|
|
14979
|
+
height: auto;
|
|
14980
|
+
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1));
|
|
14981
|
+
"/>
|
|
14918
14982
|
<div style="
|
|
14919
|
-
|
|
14920
|
-
top: 12px;
|
|
14921
|
-
right: 12px;
|
|
14922
|
-
background: ${color};
|
|
14983
|
+
background: ${levelStatus.color};
|
|
14923
14984
|
color: white;
|
|
14924
|
-
padding: 4px
|
|
14925
|
-
border-radius:
|
|
14926
|
-
font-size:
|
|
14985
|
+
padding: 4px 12px;
|
|
14986
|
+
border-radius: 20px;
|
|
14987
|
+
font-size: 12px;
|
|
14927
14988
|
font-weight: 600;
|
|
14928
14989
|
text-transform: uppercase;
|
|
14929
|
-
">${
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
font-size: 12px;
|
|
14933
|
-
color: #7f8c8d;
|
|
14934
|
-
margin-bottom: 8px;
|
|
14935
|
-
font-weight: 500;
|
|
14936
|
-
">${label}</div>
|
|
14990
|
+
">${levelStatus.label}</div>
|
|
14991
|
+
</div>
|
|
14992
|
+
|
|
14937
14993
|
<div style="
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14994
|
+
display: flex;
|
|
14995
|
+
flex-direction: column;
|
|
14996
|
+
align-items: center;
|
|
14997
|
+
gap: 8px;
|
|
14998
|
+
">
|
|
14999
|
+
<div style="
|
|
15000
|
+
font-size: 48px;
|
|
15001
|
+
font-weight: 700;
|
|
15002
|
+
color: ${levelStatus.color};
|
|
15003
|
+
line-height: 1;
|
|
15004
|
+
">${percentage.toFixed(1)}%</div>
|
|
15005
|
+
<div style="
|
|
15006
|
+
font-size: 14px;
|
|
15007
|
+
color: #7f8c8d;
|
|
15008
|
+
font-weight: 500;
|
|
15009
|
+
">${this.i18n.currentLevel}</div>
|
|
15010
|
+
</div>
|
|
14942
15011
|
</div>
|
|
14943
15012
|
`;
|
|
14944
15013
|
}
|
|
14945
15014
|
/**
|
|
14946
|
-
* Render chart section
|
|
15015
|
+
* Render chart section - shows water_level (m.c.a) over time
|
|
14947
15016
|
*/
|
|
14948
15017
|
renderChart() {
|
|
14949
15018
|
const { data } = this.config;
|
|
14950
|
-
|
|
15019
|
+
const waterLevelPoints = data.telemetry.filter(
|
|
15020
|
+
(p) => p.key === "water_level" || p.key === "waterLevel" || p.key === "nivel" || p.key === "level"
|
|
15021
|
+
);
|
|
15022
|
+
if (waterLevelPoints.length === 0) {
|
|
14951
15023
|
return `
|
|
14952
15024
|
<div style="
|
|
14953
15025
|
background: #f8f9fa;
|
|
@@ -14955,20 +15027,23 @@ var WaterTankModalView = class {
|
|
|
14955
15027
|
border-radius: 8px;
|
|
14956
15028
|
padding: 48px;
|
|
14957
15029
|
text-align: center;
|
|
14958
|
-
margin-bottom: 24px;
|
|
14959
15030
|
">
|
|
14960
15031
|
<div style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;">\u{1F4CA}</div>
|
|
14961
15032
|
<div style="color: #7f8c8d; font-size: 16px;">${this.i18n.noData}</div>
|
|
15033
|
+
<div style="color: #bdc3c7; font-size: 13px; margin-top: 8px;">
|
|
15034
|
+
No water_level (m.c.a) data available for this period
|
|
15035
|
+
</div>
|
|
14962
15036
|
</div>
|
|
14963
15037
|
`;
|
|
14964
15038
|
}
|
|
15039
|
+
const firstTs = waterLevelPoints[0]?.ts;
|
|
15040
|
+
const lastTs = waterLevelPoints[waterLevelPoints.length - 1]?.ts;
|
|
14965
15041
|
return `
|
|
14966
15042
|
<div style="
|
|
14967
15043
|
background: white;
|
|
14968
15044
|
border: 1px solid #e0e0e0;
|
|
14969
15045
|
border-radius: 8px;
|
|
14970
15046
|
padding: 20px;
|
|
14971
|
-
margin-bottom: 24px;
|
|
14972
15047
|
">
|
|
14973
15048
|
<h3 style="
|
|
14974
15049
|
margin: 0 0 16px 0;
|
|
@@ -14976,56 +15051,18 @@ var WaterTankModalView = class {
|
|
|
14976
15051
|
font-weight: 600;
|
|
14977
15052
|
color: #2c3e50;
|
|
14978
15053
|
">${this.i18n.levelChart}</h3>
|
|
14979
|
-
<canvas id="myio-water-tank-chart" style="width: 100%; height:
|
|
14980
|
-
|
|
14981
|
-
|
|
14982
|
-
|
|
14983
|
-
|
|
14984
|
-
|
|
14985
|
-
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
/**
|
|
14992
|
-
* Render device info section
|
|
14993
|
-
*/
|
|
14994
|
-
renderDeviceInfo() {
|
|
14995
|
-
const { context, data } = this.config;
|
|
14996
|
-
return `
|
|
14997
|
-
<div style="
|
|
14998
|
-
background: #f8f9fa;
|
|
14999
|
-
border: 1px solid #e0e0e0;
|
|
15000
|
-
border-radius: 8px;
|
|
15001
|
-
padding: 20px;
|
|
15002
|
-
">
|
|
15003
|
-
<h3 style="
|
|
15004
|
-
margin: 0 0 16px 0;
|
|
15005
|
-
font-size: 16px;
|
|
15006
|
-
font-weight: 600;
|
|
15007
|
-
color: #2c3e50;
|
|
15008
|
-
">${this.i18n.deviceInfo}</h3>
|
|
15009
|
-
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 12px;">
|
|
15010
|
-
${this.renderInfoRow("Device ID", context.device.id)}
|
|
15011
|
-
${this.renderInfoRow("Label", context.device.label)}
|
|
15012
|
-
${context.device.type ? this.renderInfoRow("Type", context.device.type) : ""}
|
|
15013
|
-
${context.metadata.slaveId ? this.renderInfoRow("Slave ID", String(context.metadata.slaveId)) : ""}
|
|
15014
|
-
${context.metadata.centralId ? this.renderInfoRow("Central ID", context.metadata.centralId) : ""}
|
|
15015
|
-
${this.renderInfoRow("Total Readings", String(data.summary.totalReadings))}
|
|
15016
|
-
${this.renderInfoRow("Data Keys", data.metadata.keys.join(", "))}
|
|
15017
|
-
</div>
|
|
15018
|
-
</div>
|
|
15019
|
-
`;
|
|
15020
|
-
}
|
|
15021
|
-
/**
|
|
15022
|
-
* Render an info row
|
|
15023
|
-
*/
|
|
15024
|
-
renderInfoRow(label, value) {
|
|
15025
|
-
return `
|
|
15026
|
-
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
15027
|
-
<span style="color: #7f8c8d; font-size: 13px;">${label}:</span>
|
|
15028
|
-
<span style="color: #2c3e50; font-size: 13px; font-weight: 500; margin-left: 8px;">${value}</span>
|
|
15054
|
+
<canvas id="myio-water-tank-chart" style="width: 100%; height: 280px;"></canvas>
|
|
15055
|
+
${firstTs && lastTs ? `
|
|
15056
|
+
<div style="
|
|
15057
|
+
margin-top: 12px;
|
|
15058
|
+
font-size: 12px;
|
|
15059
|
+
color: #7f8c8d;
|
|
15060
|
+
text-align: center;
|
|
15061
|
+
">
|
|
15062
|
+
${this.formatDate(firstTs, false)} \u2014 ${this.formatDate(lastTs, false)}
|
|
15063
|
+
(${waterLevelPoints.length} readings)
|
|
15064
|
+
</div>
|
|
15065
|
+
` : ""}
|
|
15029
15066
|
</div>
|
|
15030
15067
|
`;
|
|
15031
15068
|
}
|
|
@@ -15074,6 +15111,10 @@ var WaterTankModalView = class {
|
|
|
15074
15111
|
if (exportBtn) {
|
|
15075
15112
|
exportBtn.addEventListener("click", () => this.config.onExport());
|
|
15076
15113
|
}
|
|
15114
|
+
const applyDatesBtn = this.modal.querySelector("#myio-water-tank-apply-dates");
|
|
15115
|
+
if (applyDatesBtn) {
|
|
15116
|
+
applyDatesBtn.addEventListener("click", () => this.handleDateRangeChange());
|
|
15117
|
+
}
|
|
15077
15118
|
this.overlay.addEventListener("click", (e) => {
|
|
15078
15119
|
if (e.target === this.overlay) {
|
|
15079
15120
|
this.config.onClose();
|
|
@@ -15087,59 +15128,122 @@ var WaterTankModalView = class {
|
|
|
15087
15128
|
this.renderCanvasChart();
|
|
15088
15129
|
});
|
|
15089
15130
|
}
|
|
15131
|
+
/**
|
|
15132
|
+
* Handle date range change
|
|
15133
|
+
*/
|
|
15134
|
+
handleDateRangeChange() {
|
|
15135
|
+
if (!this.modal) return;
|
|
15136
|
+
const startInput = this.modal.querySelector("#myio-water-tank-start-date");
|
|
15137
|
+
const endInput = this.modal.querySelector("#myio-water-tank-end-date");
|
|
15138
|
+
if (startInput && endInput) {
|
|
15139
|
+
const startTs = new Date(startInput.value).setHours(0, 0, 0, 0);
|
|
15140
|
+
const endTs = new Date(endInput.value).setHours(23, 59, 59, 999);
|
|
15141
|
+
if (startTs >= endTs) {
|
|
15142
|
+
alert("Start date must be before end date");
|
|
15143
|
+
return;
|
|
15144
|
+
}
|
|
15145
|
+
console.log("[WaterTankModalView] Date range changed:", {
|
|
15146
|
+
startTs,
|
|
15147
|
+
endTs,
|
|
15148
|
+
startDate: new Date(startTs).toISOString(),
|
|
15149
|
+
endDate: new Date(endTs).toISOString()
|
|
15150
|
+
});
|
|
15151
|
+
if (this.config.onDateRangeChange) {
|
|
15152
|
+
this.config.onDateRangeChange(startTs, endTs);
|
|
15153
|
+
}
|
|
15154
|
+
}
|
|
15155
|
+
}
|
|
15090
15156
|
handleEscapeKey(e) {
|
|
15091
15157
|
if (e.key === "Escape") {
|
|
15092
15158
|
this.config.onClose();
|
|
15093
15159
|
}
|
|
15094
15160
|
}
|
|
15095
15161
|
/**
|
|
15096
|
-
* Render chart using Canvas API
|
|
15162
|
+
* Render chart using Canvas API - shows water_level (m.c.a) over time
|
|
15097
15163
|
*/
|
|
15098
15164
|
renderCanvasChart() {
|
|
15099
15165
|
const canvas = document.getElementById("myio-water-tank-chart");
|
|
15100
15166
|
if (!canvas) return;
|
|
15101
15167
|
const { data } = this.config;
|
|
15102
|
-
|
|
15168
|
+
const points = data.telemetry.filter(
|
|
15169
|
+
(p) => p.key === "water_level" || p.key === "waterLevel" || p.key === "nivel" || p.key === "level"
|
|
15170
|
+
);
|
|
15171
|
+
if (points.length < 2) return;
|
|
15103
15172
|
const ctx = canvas.getContext("2d");
|
|
15104
15173
|
if (!ctx) return;
|
|
15105
15174
|
const rect = canvas.getBoundingClientRect();
|
|
15106
15175
|
canvas.width = rect.width * window.devicePixelRatio;
|
|
15107
|
-
canvas.height =
|
|
15176
|
+
canvas.height = 280 * window.devicePixelRatio;
|
|
15108
15177
|
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
15109
15178
|
const width = rect.width;
|
|
15110
|
-
const height =
|
|
15111
|
-
const padding = 40;
|
|
15179
|
+
const height = 280;
|
|
15180
|
+
const padding = { top: 20, right: 20, bottom: 40, left: 60 };
|
|
15112
15181
|
ctx.clearRect(0, 0, width, height);
|
|
15113
|
-
|
|
15182
|
+
const values = points.map((p) => p.value);
|
|
15183
|
+
const minValue = Math.min(...values);
|
|
15184
|
+
const maxValue = Math.max(...values);
|
|
15185
|
+
const valueRange = maxValue - minValue || 1;
|
|
15186
|
+
const valuePadding = valueRange * 0.1;
|
|
15187
|
+
const chartMinY = minValue - valuePadding;
|
|
15188
|
+
const chartMaxY = maxValue + valuePadding;
|
|
15189
|
+
const chartRangeY = chartMaxY - chartMinY;
|
|
15190
|
+
ctx.fillStyle = "#fafafa";
|
|
15191
|
+
ctx.fillRect(padding.left, padding.top, width - padding.left - padding.right, height - padding.top - padding.bottom);
|
|
15192
|
+
ctx.strokeStyle = "#e8e8e8";
|
|
15114
15193
|
ctx.lineWidth = 1;
|
|
15115
|
-
ctx.
|
|
15116
|
-
ctx.
|
|
15117
|
-
ctx.
|
|
15118
|
-
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
for (let i = 0; i <= 5; i++) {
|
|
15123
|
-
const y = padding + (height - 2 * padding) * (i / 5);
|
|
15194
|
+
ctx.fillStyle = "#666";
|
|
15195
|
+
ctx.font = "11px Arial";
|
|
15196
|
+
ctx.textAlign = "right";
|
|
15197
|
+
const ySteps = 5;
|
|
15198
|
+
for (let i = 0; i <= ySteps; i++) {
|
|
15199
|
+
const y = padding.top + (height - padding.top - padding.bottom) * (i / ySteps);
|
|
15200
|
+
const value = chartMaxY - chartRangeY * i / ySteps;
|
|
15124
15201
|
ctx.beginPath();
|
|
15125
|
-
ctx.moveTo(padding, y);
|
|
15126
|
-
ctx.lineTo(width - padding, y);
|
|
15202
|
+
ctx.moveTo(padding.left, y);
|
|
15203
|
+
ctx.lineTo(width - padding.right, y);
|
|
15127
15204
|
ctx.stroke();
|
|
15128
|
-
ctx.
|
|
15129
|
-
ctx.font = "12px Arial";
|
|
15130
|
-
ctx.textAlign = "right";
|
|
15131
|
-
ctx.fillText(`${100 - i * 20}%`, padding - 10, y + 4);
|
|
15205
|
+
ctx.fillText(`${value.toFixed(2)}`, padding.left - 8, y + 4);
|
|
15132
15206
|
}
|
|
15133
|
-
|
|
15134
|
-
|
|
15135
|
-
|
|
15136
|
-
|
|
15207
|
+
ctx.save();
|
|
15208
|
+
ctx.translate(15, height / 2);
|
|
15209
|
+
ctx.rotate(-Math.PI / 2);
|
|
15210
|
+
ctx.textAlign = "center";
|
|
15211
|
+
ctx.fillStyle = "#666";
|
|
15212
|
+
ctx.font = "12px Arial";
|
|
15213
|
+
ctx.fillText("m.c.a", 0, 0);
|
|
15214
|
+
ctx.restore();
|
|
15215
|
+
ctx.strokeStyle = "#ccc";
|
|
15216
|
+
ctx.lineWidth = 1;
|
|
15217
|
+
ctx.beginPath();
|
|
15218
|
+
ctx.moveTo(padding.left, padding.top);
|
|
15219
|
+
ctx.lineTo(padding.left, height - padding.bottom);
|
|
15220
|
+
ctx.lineTo(width - padding.right, height - padding.bottom);
|
|
15221
|
+
ctx.stroke();
|
|
15222
|
+
const chartWidth = width - padding.left - padding.right;
|
|
15223
|
+
const chartHeight = height - padding.top - padding.bottom;
|
|
15224
|
+
const xScale = chartWidth / (points.length - 1);
|
|
15225
|
+
ctx.beginPath();
|
|
15226
|
+
ctx.moveTo(padding.left, height - padding.bottom);
|
|
15227
|
+
points.forEach((point, index) => {
|
|
15228
|
+
const x = padding.left + index * xScale;
|
|
15229
|
+
const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
|
|
15230
|
+
ctx.lineTo(x, y);
|
|
15231
|
+
});
|
|
15232
|
+
ctx.lineTo(padding.left + (points.length - 1) * xScale, height - padding.bottom);
|
|
15233
|
+
ctx.closePath();
|
|
15234
|
+
const gradient = ctx.createLinearGradient(0, padding.top, 0, height - padding.bottom);
|
|
15235
|
+
gradient.addColorStop(0, "rgba(52, 152, 219, 0.3)");
|
|
15236
|
+
gradient.addColorStop(1, "rgba(52, 152, 219, 0.05)");
|
|
15237
|
+
ctx.fillStyle = gradient;
|
|
15238
|
+
ctx.fill();
|
|
15137
15239
|
ctx.strokeStyle = "#3498db";
|
|
15138
15240
|
ctx.lineWidth = 2;
|
|
15241
|
+
ctx.lineJoin = "round";
|
|
15242
|
+
ctx.lineCap = "round";
|
|
15139
15243
|
ctx.beginPath();
|
|
15140
15244
|
points.forEach((point, index) => {
|
|
15141
|
-
const x = padding + index * xScale;
|
|
15142
|
-
const y =
|
|
15245
|
+
const x = padding.left + index * xScale;
|
|
15246
|
+
const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
|
|
15143
15247
|
if (index === 0) {
|
|
15144
15248
|
ctx.moveTo(x, y);
|
|
15145
15249
|
} else {
|
|
@@ -15147,14 +15251,56 @@ var WaterTankModalView = class {
|
|
|
15147
15251
|
}
|
|
15148
15252
|
});
|
|
15149
15253
|
ctx.stroke();
|
|
15150
|
-
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15254
|
+
if (points.length <= 50) {
|
|
15255
|
+
ctx.fillStyle = "#3498db";
|
|
15256
|
+
points.forEach((point, index) => {
|
|
15257
|
+
const x = padding.left + index * xScale;
|
|
15258
|
+
const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
|
|
15259
|
+
ctx.beginPath();
|
|
15260
|
+
ctx.arc(x, y, 3, 0, 2 * Math.PI);
|
|
15261
|
+
ctx.fill();
|
|
15262
|
+
});
|
|
15263
|
+
}
|
|
15264
|
+
ctx.fillStyle = "#888";
|
|
15265
|
+
ctx.font = "10px Arial";
|
|
15266
|
+
ctx.textAlign = "center";
|
|
15267
|
+
const xLabelCount = Math.min(6, points.length);
|
|
15268
|
+
const xLabelStep = Math.floor(points.length / xLabelCount);
|
|
15269
|
+
for (let i = 0; i < points.length; i += xLabelStep) {
|
|
15270
|
+
const x = padding.left + i * xScale;
|
|
15271
|
+
const date = new Date(points[i].ts);
|
|
15272
|
+
const label = `${date.getDate()}/${date.getMonth() + 1}`;
|
|
15273
|
+
ctx.fillText(label, x, height - padding.bottom + 16);
|
|
15274
|
+
}
|
|
15275
|
+
if (points.length > 1) {
|
|
15276
|
+
const lastX = padding.left + (points.length - 1) * xScale;
|
|
15277
|
+
const lastDate = new Date(points[points.length - 1].ts);
|
|
15278
|
+
const lastLabel = `${lastDate.getDate()}/${lastDate.getMonth() + 1}`;
|
|
15279
|
+
ctx.fillText(lastLabel, lastX, height - padding.bottom + 16);
|
|
15280
|
+
}
|
|
15281
|
+
}
|
|
15282
|
+
/**
|
|
15283
|
+
* Update data and re-render chart
|
|
15284
|
+
*/
|
|
15285
|
+
updateData(data) {
|
|
15286
|
+
this.config.data = data;
|
|
15287
|
+
if (this.modal) {
|
|
15288
|
+
const bodyEl = this.modal.querySelector(".myio-water-tank-modal-body");
|
|
15289
|
+
if (bodyEl) {
|
|
15290
|
+
bodyEl.innerHTML = `
|
|
15291
|
+
${this.renderDateRangePicker()}
|
|
15292
|
+
${this.renderTankVisualization()}
|
|
15293
|
+
${this.renderChart()}
|
|
15294
|
+
`;
|
|
15295
|
+
const applyDatesBtn = this.modal.querySelector("#myio-water-tank-apply-dates");
|
|
15296
|
+
if (applyDatesBtn) {
|
|
15297
|
+
applyDatesBtn.addEventListener("click", () => this.handleDateRangeChange());
|
|
15298
|
+
}
|
|
15299
|
+
requestAnimationFrame(() => {
|
|
15300
|
+
this.renderCanvasChart();
|
|
15301
|
+
});
|
|
15302
|
+
}
|
|
15303
|
+
}
|
|
15158
15304
|
}
|
|
15159
15305
|
/**
|
|
15160
15306
|
* Show the modal with animation
|
|
@@ -15419,8 +15565,9 @@ var WaterTankModal = class {
|
|
|
15419
15565
|
data: this.data,
|
|
15420
15566
|
onExport: () => this.handleExport(),
|
|
15421
15567
|
onError: (error) => this.handleError(error),
|
|
15422
|
-
onClose: () => this.close()
|
|
15568
|
+
onClose: () => this.close(),
|
|
15423
15569
|
// Call close() to destroy view and trigger user callback
|
|
15570
|
+
onDateRangeChange: (startTs, endTs) => this.handleDateRangeChange(startTs, endTs)
|
|
15424
15571
|
});
|
|
15425
15572
|
this.view.render();
|
|
15426
15573
|
this.view.show();
|
|
@@ -15452,6 +15599,39 @@ var WaterTankModal = class {
|
|
|
15452
15599
|
}
|
|
15453
15600
|
this.handleClose();
|
|
15454
15601
|
}
|
|
15602
|
+
/**
|
|
15603
|
+
* Handle date range change from view
|
|
15604
|
+
*/
|
|
15605
|
+
async handleDateRangeChange(startTs, endTs) {
|
|
15606
|
+
console.log("[WaterTankModal] Date range changed:", {
|
|
15607
|
+
startTs,
|
|
15608
|
+
endTs,
|
|
15609
|
+
startDate: new Date(startTs).toISOString(),
|
|
15610
|
+
endDate: new Date(endTs).toISOString()
|
|
15611
|
+
});
|
|
15612
|
+
this.options.startTs = startTs;
|
|
15613
|
+
this.options.endTs = endTs;
|
|
15614
|
+
this.context.timeRange.startTs = startTs;
|
|
15615
|
+
this.context.timeRange.endTs = endTs;
|
|
15616
|
+
try {
|
|
15617
|
+
console.log("[WaterTankModal] Fetching new data for date range...");
|
|
15618
|
+
this.data = await this.fetchTelemetryData();
|
|
15619
|
+
if (this.view) {
|
|
15620
|
+
this.view.updateData(this.data);
|
|
15621
|
+
}
|
|
15622
|
+
if (this.options.onDataLoaded) {
|
|
15623
|
+
try {
|
|
15624
|
+
this.options.onDataLoaded(this.data);
|
|
15625
|
+
} catch (callbackError) {
|
|
15626
|
+
console.warn("[WaterTankModal] onDataLoaded callback error:", callbackError);
|
|
15627
|
+
}
|
|
15628
|
+
}
|
|
15629
|
+
console.log("[WaterTankModal] Data refreshed successfully");
|
|
15630
|
+
} catch (error) {
|
|
15631
|
+
console.error("[WaterTankModal] Failed to fetch data for new date range:", error);
|
|
15632
|
+
this.handleError(error);
|
|
15633
|
+
}
|
|
15634
|
+
}
|
|
15455
15635
|
/**
|
|
15456
15636
|
* Handle export functionality
|
|
15457
15637
|
*/
|