myio-js-library 0.1.157 → 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 +458 -160
- package/dist/index.js +458 -160
- package/dist/myio-js-library.umd.js +458 -160
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5134,8 +5134,14 @@ function verifyOfflineStatus(entityObject, delayTimeInMins = 15) {
|
|
|
5134
5134
|
}
|
|
5135
5135
|
function paint(root, state) {
|
|
5136
5136
|
const { entityObject, i18n, delayTimeConnectionInMins } = state;
|
|
5137
|
-
if (
|
|
5138
|
-
entityObject.
|
|
5137
|
+
if (entityObject.connectionStatus) {
|
|
5138
|
+
if (entityObject.connectionStatus === "offline") {
|
|
5139
|
+
entityObject.deviceStatus = DeviceStatusType.NO_INFO;
|
|
5140
|
+
}
|
|
5141
|
+
} else {
|
|
5142
|
+
if (verifyOfflineStatus(entityObject, delayTimeConnectionInMins) === false) {
|
|
5143
|
+
entityObject.deviceStatus = DeviceStatusType.NO_INFO;
|
|
5144
|
+
}
|
|
5139
5145
|
}
|
|
5140
5146
|
const stateClass = getCardStateClass(entityObject.deviceStatus);
|
|
5141
5147
|
root.className = `myio-ho-card ${stateClass}`;
|
|
@@ -10945,11 +10951,14 @@ var EnergyModalView = class {
|
|
|
10945
10951
|
isLoading = false;
|
|
10946
10952
|
currentTheme = "dark";
|
|
10947
10953
|
currentBarMode = "stacked";
|
|
10954
|
+
// RFC-0097: Granularity selector state (only 1h and 1d supported)
|
|
10955
|
+
currentGranularity = "1d";
|
|
10948
10956
|
constructor(modal, config) {
|
|
10949
10957
|
this.modal = modal;
|
|
10950
10958
|
this.config = config;
|
|
10951
10959
|
this.initializeTheme();
|
|
10952
10960
|
this.initializeBarMode();
|
|
10961
|
+
this.initializeGranularity();
|
|
10953
10962
|
this.validateConfiguration();
|
|
10954
10963
|
this.render();
|
|
10955
10964
|
}
|
|
@@ -10967,6 +10976,59 @@ var EnergyModalView = class {
|
|
|
10967
10976
|
const savedBarMode = localStorage.getItem("myio-modal-bar-mode");
|
|
10968
10977
|
this.currentBarMode = savedBarMode || "stacked";
|
|
10969
10978
|
}
|
|
10979
|
+
/**
|
|
10980
|
+
* RFC-0097: Initializes granularity from config or localStorage
|
|
10981
|
+
*/
|
|
10982
|
+
initializeGranularity() {
|
|
10983
|
+
const savedGranularity = localStorage.getItem("myio-modal-granularity");
|
|
10984
|
+
const configGranularity = this.config.params.granularity;
|
|
10985
|
+
const candidate = savedGranularity || configGranularity || "1d";
|
|
10986
|
+
this.currentGranularity = candidate === "1h" || candidate === "1d" ? candidate : "1d";
|
|
10987
|
+
}
|
|
10988
|
+
/**
|
|
10989
|
+
* RFC-0097: Sets granularity and re-renders chart
|
|
10990
|
+
*/
|
|
10991
|
+
setGranularity(granularity) {
|
|
10992
|
+
if (this.currentGranularity === granularity) return;
|
|
10993
|
+
this.currentGranularity = granularity;
|
|
10994
|
+
const buttons = document.querySelectorAll(".myio-btn-granularity");
|
|
10995
|
+
buttons.forEach((btn) => {
|
|
10996
|
+
const btnEl = btn;
|
|
10997
|
+
if (btnEl.dataset.granularity === granularity) {
|
|
10998
|
+
btnEl.classList.add("active");
|
|
10999
|
+
} else {
|
|
11000
|
+
btnEl.classList.remove("active");
|
|
11001
|
+
}
|
|
11002
|
+
});
|
|
11003
|
+
localStorage.setItem("myio-modal-granularity", granularity);
|
|
11004
|
+
this.reRenderChart();
|
|
11005
|
+
console.log("[EnergyModalView] [RFC-0097] Granularity changed to:", granularity);
|
|
11006
|
+
}
|
|
11007
|
+
/**
|
|
11008
|
+
* RFC-0097: Calculates suggested granularity based on date range
|
|
11009
|
+
* Only supports '1h' (hour) and '1d' (day)
|
|
11010
|
+
*/
|
|
11011
|
+
calculateSuggestedGranularity(startDate, endDate) {
|
|
11012
|
+
const start = new Date(startDate);
|
|
11013
|
+
const end = new Date(endDate);
|
|
11014
|
+
const diffDays = Math.ceil((end.getTime() - start.getTime()) / (1e3 * 60 * 60 * 24));
|
|
11015
|
+
if (diffDays <= 1) return "1h";
|
|
11016
|
+
return "1d";
|
|
11017
|
+
}
|
|
11018
|
+
/**
|
|
11019
|
+
* RFC-0097: Applies granularity UI state (highlights correct button)
|
|
11020
|
+
*/
|
|
11021
|
+
applyGranularityUI() {
|
|
11022
|
+
const buttons = document.querySelectorAll(".myio-btn-granularity");
|
|
11023
|
+
buttons.forEach((btn) => {
|
|
11024
|
+
const btnEl = btn;
|
|
11025
|
+
if (btnEl.dataset.granularity === this.currentGranularity) {
|
|
11026
|
+
btnEl.classList.add("active");
|
|
11027
|
+
} else {
|
|
11028
|
+
btnEl.classList.remove("active");
|
|
11029
|
+
}
|
|
11030
|
+
});
|
|
11031
|
+
}
|
|
10970
11032
|
/**
|
|
10971
11033
|
* Toggles between dark and light theme
|
|
10972
11034
|
*/
|
|
@@ -11237,6 +11299,14 @@ var EnergyModalView = class {
|
|
|
11237
11299
|
</svg>
|
|
11238
11300
|
</button>
|
|
11239
11301
|
` : ""}
|
|
11302
|
+
${this.config.params.mode === "comparison" ? `
|
|
11303
|
+
<!-- RFC-0097: Granularity Selector (only 1h and 1d supported) -->
|
|
11304
|
+
<div class="myio-granularity-selector" style="display: flex; align-items: center; gap: 4px; margin-left: 8px; padding: 4px 8px; background: rgba(0,0,0,0.05); border-radius: 8px;">
|
|
11305
|
+
<span style="font-size: 11px; color: #666; margin-right: 4px; white-space: nowrap;">Granularidade:</span>
|
|
11306
|
+
<button class="myio-btn myio-btn-granularity ${this.currentGranularity === "1h" ? "active" : ""}" data-granularity="1h" title="Hora">1h</button>
|
|
11307
|
+
<button class="myio-btn myio-btn-granularity ${this.currentGranularity === "1d" ? "active" : ""}" data-granularity="1d" title="Dia">1d</button>
|
|
11308
|
+
</div>
|
|
11309
|
+
` : ""}
|
|
11240
11310
|
<button id="close-btn" class="myio-btn myio-btn-secondary">
|
|
11241
11311
|
Fechar
|
|
11242
11312
|
</button>
|
|
@@ -11459,8 +11529,8 @@ var EnergyModalView = class {
|
|
|
11459
11529
|
// ← NO TIME (YYYY-MM-DD)
|
|
11460
11530
|
endDate: endDateStr,
|
|
11461
11531
|
// ← NO TIME (YYYY-MM-DD)
|
|
11462
|
-
granularity: this.
|
|
11463
|
-
//
|
|
11532
|
+
granularity: this.currentGranularity,
|
|
11533
|
+
// RFC-0097: Use current granularity from selector
|
|
11464
11534
|
theme: this.currentTheme,
|
|
11465
11535
|
// ← Use current theme (dynamic)
|
|
11466
11536
|
bar_mode: this.currentBarMode,
|
|
@@ -11532,7 +11602,8 @@ var EnergyModalView = class {
|
|
|
11532
11602
|
readingType: "temperature",
|
|
11533
11603
|
startDate: startDateStr,
|
|
11534
11604
|
endDate: endDateStr,
|
|
11535
|
-
granularity: this.
|
|
11605
|
+
granularity: this.currentGranularity,
|
|
11606
|
+
// RFC-0097: Use current granularity from selector
|
|
11536
11607
|
theme: this.currentTheme,
|
|
11537
11608
|
timezone: tzIdentifier,
|
|
11538
11609
|
iframeBaseUrl: this.config.params.chartsBaseUrl || "https://graphs.apps.myio-bas.com",
|
|
@@ -11855,6 +11926,20 @@ var EnergyModalView = class {
|
|
|
11855
11926
|
this.toggleBarMode();
|
|
11856
11927
|
});
|
|
11857
11928
|
}
|
|
11929
|
+
const granularityButtons = document.querySelectorAll(".myio-btn-granularity");
|
|
11930
|
+
if (granularityButtons.length > 0) {
|
|
11931
|
+
this.applyGranularityUI();
|
|
11932
|
+
granularityButtons.forEach((btn) => {
|
|
11933
|
+
btn.addEventListener("click", (e) => {
|
|
11934
|
+
const target = e.currentTarget;
|
|
11935
|
+
const newGranularity = target.dataset.granularity;
|
|
11936
|
+
if (newGranularity) {
|
|
11937
|
+
this.setGranularity(newGranularity);
|
|
11938
|
+
}
|
|
11939
|
+
});
|
|
11940
|
+
});
|
|
11941
|
+
console.log("[EnergyModalView] [RFC-0097] Granularity selector initialized with:", this.currentGranularity);
|
|
11942
|
+
}
|
|
11858
11943
|
try {
|
|
11859
11944
|
this.dateRangePicker = await attach(dateRangeInput, {
|
|
11860
11945
|
presetStart: this.config.params.startDate instanceof Date ? this.config.params.startDate.toISOString().split("T")[0] : this.config.params.startDate,
|
|
@@ -11957,6 +12042,37 @@ var EnergyModalView = class {
|
|
|
11957
12042
|
background: #e5e7eb;
|
|
11958
12043
|
}
|
|
11959
12044
|
|
|
12045
|
+
/* RFC-0097: Granularity selector buttons */
|
|
12046
|
+
.myio-btn-granularity {
|
|
12047
|
+
padding: 4px 10px;
|
|
12048
|
+
font-size: 12px;
|
|
12049
|
+
font-weight: 600;
|
|
12050
|
+
border-radius: 6px;
|
|
12051
|
+
border: 1px solid var(--myio-energy-border);
|
|
12052
|
+
background: var(--myio-energy-bg);
|
|
12053
|
+
color: var(--myio-energy-text);
|
|
12054
|
+
cursor: pointer;
|
|
12055
|
+
transition: all 0.2s ease;
|
|
12056
|
+
min-width: 36px;
|
|
12057
|
+
}
|
|
12058
|
+
|
|
12059
|
+
.myio-btn-granularity:hover:not(.active) {
|
|
12060
|
+
background: #f3f4f6;
|
|
12061
|
+
border-color: var(--myio-energy-primary);
|
|
12062
|
+
color: var(--myio-energy-primary);
|
|
12063
|
+
}
|
|
12064
|
+
|
|
12065
|
+
.myio-btn-granularity.active {
|
|
12066
|
+
background: var(--myio-energy-primary);
|
|
12067
|
+
color: white;
|
|
12068
|
+
border-color: var(--myio-energy-primary);
|
|
12069
|
+
box-shadow: 0 2px 4px rgba(74, 20, 140, 0.25);
|
|
12070
|
+
}
|
|
12071
|
+
|
|
12072
|
+
.myio-granularity-selector {
|
|
12073
|
+
border: 1px solid var(--myio-energy-border);
|
|
12074
|
+
}
|
|
12075
|
+
|
|
11960
12076
|
.myio-modal-scope {
|
|
11961
12077
|
height: 100% !important;
|
|
11962
12078
|
display: flex !important;
|
|
@@ -12337,7 +12453,9 @@ var EnergyModal = class {
|
|
|
12337
12453
|
return;
|
|
12338
12454
|
}
|
|
12339
12455
|
if (!this.context?.resolved.ingestionId) {
|
|
12340
|
-
const error = new Error(
|
|
12456
|
+
const error = new Error(
|
|
12457
|
+
"ingestionId not found in device attributes. Please configure the device properly."
|
|
12458
|
+
);
|
|
12341
12459
|
this.handleError(error);
|
|
12342
12460
|
return;
|
|
12343
12461
|
}
|
|
@@ -14601,7 +14719,7 @@ var WaterTankModalView = class {
|
|
|
14601
14719
|
maxLevel: "Maximum Level",
|
|
14602
14720
|
dateRange: "Date Range",
|
|
14603
14721
|
deviceInfo: "Device Information",
|
|
14604
|
-
levelChart: "Level
|
|
14722
|
+
levelChart: "Water Level History (m.c.a)",
|
|
14605
14723
|
percentUnit: "%",
|
|
14606
14724
|
status: {
|
|
14607
14725
|
critical: "Critical",
|
|
@@ -14632,6 +14750,20 @@ var WaterTankModalView = class {
|
|
|
14632
14750
|
return { status: "full", color: "#3498db", label: this.i18n.status.full };
|
|
14633
14751
|
}
|
|
14634
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
|
+
}
|
|
14635
14767
|
/**
|
|
14636
14768
|
* Format timestamp to readable date
|
|
14637
14769
|
*/
|
|
@@ -14644,13 +14776,18 @@ var WaterTankModalView = class {
|
|
|
14644
14776
|
}
|
|
14645
14777
|
return dateStr;
|
|
14646
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
|
+
}
|
|
14647
14786
|
/**
|
|
14648
14787
|
* Render the modal HTML
|
|
14649
14788
|
*/
|
|
14650
14789
|
render() {
|
|
14651
|
-
const {
|
|
14652
|
-
const currentLevel = data.summary.currentLevel ?? context.device.currentLevel ?? 0;
|
|
14653
|
-
const levelStatus = this.getLevelStatus(currentLevel);
|
|
14790
|
+
const { params } = this.config;
|
|
14654
14791
|
this.overlay = document.createElement("div");
|
|
14655
14792
|
this.overlay.className = "myio-water-tank-modal-overlay";
|
|
14656
14793
|
this.overlay.style.cssText = `
|
|
@@ -14671,9 +14808,9 @@ var WaterTankModalView = class {
|
|
|
14671
14808
|
this.modal.className = "myio-water-tank-modal";
|
|
14672
14809
|
this.modal.style.cssText = `
|
|
14673
14810
|
background: white;
|
|
14674
|
-
border-radius:
|
|
14811
|
+
border-radius: 12px;
|
|
14675
14812
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
|
14676
|
-
width: ${params.ui?.width ||
|
|
14813
|
+
width: ${params.ui?.width || 700}px;
|
|
14677
14814
|
max-width: 95vw;
|
|
14678
14815
|
max-height: 90vh;
|
|
14679
14816
|
display: flex;
|
|
@@ -14739,97 +14876,150 @@ var WaterTankModalView = class {
|
|
|
14739
14876
|
overflow-y: auto;
|
|
14740
14877
|
flex: 1;
|
|
14741
14878
|
">
|
|
14742
|
-
${this.
|
|
14879
|
+
${this.renderDateRangePicker()}
|
|
14880
|
+
${this.renderTankVisualization()}
|
|
14743
14881
|
${this.renderChart()}
|
|
14744
|
-
${this.renderDeviceInfo()}
|
|
14745
14882
|
</div>
|
|
14746
14883
|
${this.renderFooter()}
|
|
14747
14884
|
`;
|
|
14748
14885
|
}
|
|
14749
14886
|
/**
|
|
14750
|
-
* Render
|
|
14887
|
+
* Render date range picker
|
|
14751
14888
|
*/
|
|
14752
|
-
|
|
14753
|
-
const {
|
|
14754
|
-
const
|
|
14755
|
-
const
|
|
14889
|
+
renderDateRangePicker() {
|
|
14890
|
+
const { params } = this.config;
|
|
14891
|
+
const startDate = this.formatDateForInput(params.startTs);
|
|
14892
|
+
const endDate = this.formatDateForInput(params.endTs);
|
|
14756
14893
|
return `
|
|
14757
14894
|
<div style="
|
|
14758
|
-
|
|
14759
|
-
|
|
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;
|
|
14760
14902
|
gap: 16px;
|
|
14761
|
-
|
|
14903
|
+
flex-wrap: wrap;
|
|
14762
14904
|
">
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
|
|
14766
|
-
|
|
14767
|
-
|
|
14768
|
-
|
|
14769
|
-
|
|
14770
|
-
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
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>
|
|
14784
14940
|
</div>
|
|
14785
14941
|
`;
|
|
14786
14942
|
}
|
|
14787
14943
|
/**
|
|
14788
|
-
* Render
|
|
14944
|
+
* Render tank visualization with percentage
|
|
14789
14945
|
*/
|
|
14790
|
-
|
|
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);
|
|
14791
14959
|
return `
|
|
14792
14960
|
<div style="
|
|
14793
|
-
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
|
|
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;
|
|
14798
14970
|
">
|
|
14799
|
-
|
|
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
|
+
"/>
|
|
14800
14982
|
<div style="
|
|
14801
|
-
|
|
14802
|
-
top: 12px;
|
|
14803
|
-
right: 12px;
|
|
14804
|
-
background: ${color};
|
|
14983
|
+
background: ${levelStatus.color};
|
|
14805
14984
|
color: white;
|
|
14806
|
-
padding: 4px
|
|
14807
|
-
border-radius:
|
|
14808
|
-
font-size:
|
|
14985
|
+
padding: 4px 12px;
|
|
14986
|
+
border-radius: 20px;
|
|
14987
|
+
font-size: 12px;
|
|
14809
14988
|
font-weight: 600;
|
|
14810
14989
|
text-transform: uppercase;
|
|
14811
|
-
">${
|
|
14812
|
-
|
|
14813
|
-
|
|
14814
|
-
font-size: 12px;
|
|
14815
|
-
color: #7f8c8d;
|
|
14816
|
-
margin-bottom: 8px;
|
|
14817
|
-
font-weight: 500;
|
|
14818
|
-
">${label}</div>
|
|
14990
|
+
">${levelStatus.label}</div>
|
|
14991
|
+
</div>
|
|
14992
|
+
|
|
14819
14993
|
<div style="
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
|
|
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>
|
|
14824
15011
|
</div>
|
|
14825
15012
|
`;
|
|
14826
15013
|
}
|
|
14827
15014
|
/**
|
|
14828
|
-
* Render chart section
|
|
15015
|
+
* Render chart section - shows water_level (m.c.a) over time
|
|
14829
15016
|
*/
|
|
14830
15017
|
renderChart() {
|
|
14831
15018
|
const { data } = this.config;
|
|
14832
|
-
|
|
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) {
|
|
14833
15023
|
return `
|
|
14834
15024
|
<div style="
|
|
14835
15025
|
background: #f8f9fa;
|
|
@@ -14837,20 +15027,23 @@ var WaterTankModalView = class {
|
|
|
14837
15027
|
border-radius: 8px;
|
|
14838
15028
|
padding: 48px;
|
|
14839
15029
|
text-align: center;
|
|
14840
|
-
margin-bottom: 24px;
|
|
14841
15030
|
">
|
|
14842
15031
|
<div style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;">\u{1F4CA}</div>
|
|
14843
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>
|
|
14844
15036
|
</div>
|
|
14845
15037
|
`;
|
|
14846
15038
|
}
|
|
15039
|
+
const firstTs = waterLevelPoints[0]?.ts;
|
|
15040
|
+
const lastTs = waterLevelPoints[waterLevelPoints.length - 1]?.ts;
|
|
14847
15041
|
return `
|
|
14848
15042
|
<div style="
|
|
14849
15043
|
background: white;
|
|
14850
15044
|
border: 1px solid #e0e0e0;
|
|
14851
15045
|
border-radius: 8px;
|
|
14852
15046
|
padding: 20px;
|
|
14853
|
-
margin-bottom: 24px;
|
|
14854
15047
|
">
|
|
14855
15048
|
<h3 style="
|
|
14856
15049
|
margin: 0 0 16px 0;
|
|
@@ -14858,56 +15051,18 @@ var WaterTankModalView = class {
|
|
|
14858
15051
|
font-weight: 600;
|
|
14859
15052
|
color: #2c3e50;
|
|
14860
15053
|
">${this.i18n.levelChart}</h3>
|
|
14861
|
-
<canvas id="myio-water-tank-chart" style="width: 100%; height:
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
|
|
14868
|
-
|
|
14869
|
-
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
/**
|
|
14874
|
-
* Render device info section
|
|
14875
|
-
*/
|
|
14876
|
-
renderDeviceInfo() {
|
|
14877
|
-
const { context, data } = this.config;
|
|
14878
|
-
return `
|
|
14879
|
-
<div style="
|
|
14880
|
-
background: #f8f9fa;
|
|
14881
|
-
border: 1px solid #e0e0e0;
|
|
14882
|
-
border-radius: 8px;
|
|
14883
|
-
padding: 20px;
|
|
14884
|
-
">
|
|
14885
|
-
<h3 style="
|
|
14886
|
-
margin: 0 0 16px 0;
|
|
14887
|
-
font-size: 16px;
|
|
14888
|
-
font-weight: 600;
|
|
14889
|
-
color: #2c3e50;
|
|
14890
|
-
">${this.i18n.deviceInfo}</h3>
|
|
14891
|
-
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 12px;">
|
|
14892
|
-
${this.renderInfoRow("Device ID", context.device.id)}
|
|
14893
|
-
${this.renderInfoRow("Label", context.device.label)}
|
|
14894
|
-
${context.device.type ? this.renderInfoRow("Type", context.device.type) : ""}
|
|
14895
|
-
${context.metadata.slaveId ? this.renderInfoRow("Slave ID", String(context.metadata.slaveId)) : ""}
|
|
14896
|
-
${context.metadata.centralId ? this.renderInfoRow("Central ID", context.metadata.centralId) : ""}
|
|
14897
|
-
${this.renderInfoRow("Total Readings", String(data.summary.totalReadings))}
|
|
14898
|
-
${this.renderInfoRow("Data Keys", data.metadata.keys.join(", "))}
|
|
14899
|
-
</div>
|
|
14900
|
-
</div>
|
|
14901
|
-
`;
|
|
14902
|
-
}
|
|
14903
|
-
/**
|
|
14904
|
-
* Render an info row
|
|
14905
|
-
*/
|
|
14906
|
-
renderInfoRow(label, value) {
|
|
14907
|
-
return `
|
|
14908
|
-
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
14909
|
-
<span style="color: #7f8c8d; font-size: 13px;">${label}:</span>
|
|
14910
|
-
<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
|
+
` : ""}
|
|
14911
15066
|
</div>
|
|
14912
15067
|
`;
|
|
14913
15068
|
}
|
|
@@ -14956,6 +15111,10 @@ var WaterTankModalView = class {
|
|
|
14956
15111
|
if (exportBtn) {
|
|
14957
15112
|
exportBtn.addEventListener("click", () => this.config.onExport());
|
|
14958
15113
|
}
|
|
15114
|
+
const applyDatesBtn = this.modal.querySelector("#myio-water-tank-apply-dates");
|
|
15115
|
+
if (applyDatesBtn) {
|
|
15116
|
+
applyDatesBtn.addEventListener("click", () => this.handleDateRangeChange());
|
|
15117
|
+
}
|
|
14959
15118
|
this.overlay.addEventListener("click", (e) => {
|
|
14960
15119
|
if (e.target === this.overlay) {
|
|
14961
15120
|
this.config.onClose();
|
|
@@ -14969,59 +15128,122 @@ var WaterTankModalView = class {
|
|
|
14969
15128
|
this.renderCanvasChart();
|
|
14970
15129
|
});
|
|
14971
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
|
+
}
|
|
14972
15156
|
handleEscapeKey(e) {
|
|
14973
15157
|
if (e.key === "Escape") {
|
|
14974
15158
|
this.config.onClose();
|
|
14975
15159
|
}
|
|
14976
15160
|
}
|
|
14977
15161
|
/**
|
|
14978
|
-
* Render chart using Canvas API
|
|
15162
|
+
* Render chart using Canvas API - shows water_level (m.c.a) over time
|
|
14979
15163
|
*/
|
|
14980
15164
|
renderCanvasChart() {
|
|
14981
15165
|
const canvas = document.getElementById("myio-water-tank-chart");
|
|
14982
15166
|
if (!canvas) return;
|
|
14983
15167
|
const { data } = this.config;
|
|
14984
|
-
|
|
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;
|
|
14985
15172
|
const ctx = canvas.getContext("2d");
|
|
14986
15173
|
if (!ctx) return;
|
|
14987
15174
|
const rect = canvas.getBoundingClientRect();
|
|
14988
15175
|
canvas.width = rect.width * window.devicePixelRatio;
|
|
14989
|
-
canvas.height =
|
|
15176
|
+
canvas.height = 280 * window.devicePixelRatio;
|
|
14990
15177
|
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
14991
15178
|
const width = rect.width;
|
|
14992
|
-
const height =
|
|
14993
|
-
const padding = 40;
|
|
15179
|
+
const height = 280;
|
|
15180
|
+
const padding = { top: 20, right: 20, bottom: 40, left: 60 };
|
|
14994
15181
|
ctx.clearRect(0, 0, width, height);
|
|
14995
|
-
|
|
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";
|
|
14996
15193
|
ctx.lineWidth = 1;
|
|
14997
|
-
ctx.
|
|
14998
|
-
ctx.
|
|
14999
|
-
ctx.
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
|
|
15004
|
-
for (let i = 0; i <= 5; i++) {
|
|
15005
|
-
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;
|
|
15006
15201
|
ctx.beginPath();
|
|
15007
|
-
ctx.moveTo(padding, y);
|
|
15008
|
-
ctx.lineTo(width - padding, y);
|
|
15202
|
+
ctx.moveTo(padding.left, y);
|
|
15203
|
+
ctx.lineTo(width - padding.right, y);
|
|
15009
15204
|
ctx.stroke();
|
|
15010
|
-
ctx.
|
|
15011
|
-
ctx.font = "12px Arial";
|
|
15012
|
-
ctx.textAlign = "right";
|
|
15013
|
-
ctx.fillText(`${100 - i * 20}%`, padding - 10, y + 4);
|
|
15205
|
+
ctx.fillText(`${value.toFixed(2)}`, padding.left - 8, y + 4);
|
|
15014
15206
|
}
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
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();
|
|
15019
15239
|
ctx.strokeStyle = "#3498db";
|
|
15020
15240
|
ctx.lineWidth = 2;
|
|
15241
|
+
ctx.lineJoin = "round";
|
|
15242
|
+
ctx.lineCap = "round";
|
|
15021
15243
|
ctx.beginPath();
|
|
15022
15244
|
points.forEach((point, index) => {
|
|
15023
|
-
const x = padding + index * xScale;
|
|
15024
|
-
const y =
|
|
15245
|
+
const x = padding.left + index * xScale;
|
|
15246
|
+
const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
|
|
15025
15247
|
if (index === 0) {
|
|
15026
15248
|
ctx.moveTo(x, y);
|
|
15027
15249
|
} else {
|
|
@@ -15029,14 +15251,56 @@ var WaterTankModalView = class {
|
|
|
15029
15251
|
}
|
|
15030
15252
|
});
|
|
15031
15253
|
ctx.stroke();
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
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
|
+
}
|
|
15040
15304
|
}
|
|
15041
15305
|
/**
|
|
15042
15306
|
* Show the modal with animation
|
|
@@ -15301,8 +15565,9 @@ var WaterTankModal = class {
|
|
|
15301
15565
|
data: this.data,
|
|
15302
15566
|
onExport: () => this.handleExport(),
|
|
15303
15567
|
onError: (error) => this.handleError(error),
|
|
15304
|
-
onClose: () => this.close()
|
|
15568
|
+
onClose: () => this.close(),
|
|
15305
15569
|
// Call close() to destroy view and trigger user callback
|
|
15570
|
+
onDateRangeChange: (startTs, endTs) => this.handleDateRangeChange(startTs, endTs)
|
|
15306
15571
|
});
|
|
15307
15572
|
this.view.render();
|
|
15308
15573
|
this.view.show();
|
|
@@ -15334,6 +15599,39 @@ var WaterTankModal = class {
|
|
|
15334
15599
|
}
|
|
15335
15600
|
this.handleClose();
|
|
15336
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
|
+
}
|
|
15337
15635
|
/**
|
|
15338
15636
|
* Handle export functionality
|
|
15339
15637
|
*/
|