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.js
CHANGED
|
@@ -5026,8 +5026,14 @@ function verifyOfflineStatus(entityObject, delayTimeInMins = 15) {
|
|
|
5026
5026
|
}
|
|
5027
5027
|
function paint(root, state) {
|
|
5028
5028
|
const { entityObject, i18n, delayTimeConnectionInMins } = state;
|
|
5029
|
-
if (
|
|
5030
|
-
entityObject.
|
|
5029
|
+
if (entityObject.connectionStatus) {
|
|
5030
|
+
if (entityObject.connectionStatus === "offline") {
|
|
5031
|
+
entityObject.deviceStatus = DeviceStatusType.NO_INFO;
|
|
5032
|
+
}
|
|
5033
|
+
} else {
|
|
5034
|
+
if (verifyOfflineStatus(entityObject, delayTimeConnectionInMins) === false) {
|
|
5035
|
+
entityObject.deviceStatus = DeviceStatusType.NO_INFO;
|
|
5036
|
+
}
|
|
5031
5037
|
}
|
|
5032
5038
|
const stateClass = getCardStateClass(entityObject.deviceStatus);
|
|
5033
5039
|
root.className = `myio-ho-card ${stateClass}`;
|
|
@@ -10837,11 +10843,14 @@ var EnergyModalView = class {
|
|
|
10837
10843
|
isLoading = false;
|
|
10838
10844
|
currentTheme = "dark";
|
|
10839
10845
|
currentBarMode = "stacked";
|
|
10846
|
+
// RFC-0097: Granularity selector state (only 1h and 1d supported)
|
|
10847
|
+
currentGranularity = "1d";
|
|
10840
10848
|
constructor(modal, config) {
|
|
10841
10849
|
this.modal = modal;
|
|
10842
10850
|
this.config = config;
|
|
10843
10851
|
this.initializeTheme();
|
|
10844
10852
|
this.initializeBarMode();
|
|
10853
|
+
this.initializeGranularity();
|
|
10845
10854
|
this.validateConfiguration();
|
|
10846
10855
|
this.render();
|
|
10847
10856
|
}
|
|
@@ -10859,6 +10868,59 @@ var EnergyModalView = class {
|
|
|
10859
10868
|
const savedBarMode = localStorage.getItem("myio-modal-bar-mode");
|
|
10860
10869
|
this.currentBarMode = savedBarMode || "stacked";
|
|
10861
10870
|
}
|
|
10871
|
+
/**
|
|
10872
|
+
* RFC-0097: Initializes granularity from config or localStorage
|
|
10873
|
+
*/
|
|
10874
|
+
initializeGranularity() {
|
|
10875
|
+
const savedGranularity = localStorage.getItem("myio-modal-granularity");
|
|
10876
|
+
const configGranularity = this.config.params.granularity;
|
|
10877
|
+
const candidate = savedGranularity || configGranularity || "1d";
|
|
10878
|
+
this.currentGranularity = candidate === "1h" || candidate === "1d" ? candidate : "1d";
|
|
10879
|
+
}
|
|
10880
|
+
/**
|
|
10881
|
+
* RFC-0097: Sets granularity and re-renders chart
|
|
10882
|
+
*/
|
|
10883
|
+
setGranularity(granularity) {
|
|
10884
|
+
if (this.currentGranularity === granularity) return;
|
|
10885
|
+
this.currentGranularity = granularity;
|
|
10886
|
+
const buttons = document.querySelectorAll(".myio-btn-granularity");
|
|
10887
|
+
buttons.forEach((btn) => {
|
|
10888
|
+
const btnEl = btn;
|
|
10889
|
+
if (btnEl.dataset.granularity === granularity) {
|
|
10890
|
+
btnEl.classList.add("active");
|
|
10891
|
+
} else {
|
|
10892
|
+
btnEl.classList.remove("active");
|
|
10893
|
+
}
|
|
10894
|
+
});
|
|
10895
|
+
localStorage.setItem("myio-modal-granularity", granularity);
|
|
10896
|
+
this.reRenderChart();
|
|
10897
|
+
console.log("[EnergyModalView] [RFC-0097] Granularity changed to:", granularity);
|
|
10898
|
+
}
|
|
10899
|
+
/**
|
|
10900
|
+
* RFC-0097: Calculates suggested granularity based on date range
|
|
10901
|
+
* Only supports '1h' (hour) and '1d' (day)
|
|
10902
|
+
*/
|
|
10903
|
+
calculateSuggestedGranularity(startDate, endDate) {
|
|
10904
|
+
const start = new Date(startDate);
|
|
10905
|
+
const end = new Date(endDate);
|
|
10906
|
+
const diffDays = Math.ceil((end.getTime() - start.getTime()) / (1e3 * 60 * 60 * 24));
|
|
10907
|
+
if (diffDays <= 1) return "1h";
|
|
10908
|
+
return "1d";
|
|
10909
|
+
}
|
|
10910
|
+
/**
|
|
10911
|
+
* RFC-0097: Applies granularity UI state (highlights correct button)
|
|
10912
|
+
*/
|
|
10913
|
+
applyGranularityUI() {
|
|
10914
|
+
const buttons = document.querySelectorAll(".myio-btn-granularity");
|
|
10915
|
+
buttons.forEach((btn) => {
|
|
10916
|
+
const btnEl = btn;
|
|
10917
|
+
if (btnEl.dataset.granularity === this.currentGranularity) {
|
|
10918
|
+
btnEl.classList.add("active");
|
|
10919
|
+
} else {
|
|
10920
|
+
btnEl.classList.remove("active");
|
|
10921
|
+
}
|
|
10922
|
+
});
|
|
10923
|
+
}
|
|
10862
10924
|
/**
|
|
10863
10925
|
* Toggles between dark and light theme
|
|
10864
10926
|
*/
|
|
@@ -11129,6 +11191,14 @@ var EnergyModalView = class {
|
|
|
11129
11191
|
</svg>
|
|
11130
11192
|
</button>
|
|
11131
11193
|
` : ""}
|
|
11194
|
+
${this.config.params.mode === "comparison" ? `
|
|
11195
|
+
<!-- RFC-0097: Granularity Selector (only 1h and 1d supported) -->
|
|
11196
|
+
<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;">
|
|
11197
|
+
<span style="font-size: 11px; color: #666; margin-right: 4px; white-space: nowrap;">Granularidade:</span>
|
|
11198
|
+
<button class="myio-btn myio-btn-granularity ${this.currentGranularity === "1h" ? "active" : ""}" data-granularity="1h" title="Hora">1h</button>
|
|
11199
|
+
<button class="myio-btn myio-btn-granularity ${this.currentGranularity === "1d" ? "active" : ""}" data-granularity="1d" title="Dia">1d</button>
|
|
11200
|
+
</div>
|
|
11201
|
+
` : ""}
|
|
11132
11202
|
<button id="close-btn" class="myio-btn myio-btn-secondary">
|
|
11133
11203
|
Fechar
|
|
11134
11204
|
</button>
|
|
@@ -11351,8 +11421,8 @@ var EnergyModalView = class {
|
|
|
11351
11421
|
// ← NO TIME (YYYY-MM-DD)
|
|
11352
11422
|
endDate: endDateStr,
|
|
11353
11423
|
// ← NO TIME (YYYY-MM-DD)
|
|
11354
|
-
granularity: this.
|
|
11355
|
-
//
|
|
11424
|
+
granularity: this.currentGranularity,
|
|
11425
|
+
// RFC-0097: Use current granularity from selector
|
|
11356
11426
|
theme: this.currentTheme,
|
|
11357
11427
|
// ← Use current theme (dynamic)
|
|
11358
11428
|
bar_mode: this.currentBarMode,
|
|
@@ -11424,7 +11494,8 @@ var EnergyModalView = class {
|
|
|
11424
11494
|
readingType: "temperature",
|
|
11425
11495
|
startDate: startDateStr,
|
|
11426
11496
|
endDate: endDateStr,
|
|
11427
|
-
granularity: this.
|
|
11497
|
+
granularity: this.currentGranularity,
|
|
11498
|
+
// RFC-0097: Use current granularity from selector
|
|
11428
11499
|
theme: this.currentTheme,
|
|
11429
11500
|
timezone: tzIdentifier,
|
|
11430
11501
|
iframeBaseUrl: this.config.params.chartsBaseUrl || "https://graphs.apps.myio-bas.com",
|
|
@@ -11747,6 +11818,20 @@ var EnergyModalView = class {
|
|
|
11747
11818
|
this.toggleBarMode();
|
|
11748
11819
|
});
|
|
11749
11820
|
}
|
|
11821
|
+
const granularityButtons = document.querySelectorAll(".myio-btn-granularity");
|
|
11822
|
+
if (granularityButtons.length > 0) {
|
|
11823
|
+
this.applyGranularityUI();
|
|
11824
|
+
granularityButtons.forEach((btn) => {
|
|
11825
|
+
btn.addEventListener("click", (e) => {
|
|
11826
|
+
const target = e.currentTarget;
|
|
11827
|
+
const newGranularity = target.dataset.granularity;
|
|
11828
|
+
if (newGranularity) {
|
|
11829
|
+
this.setGranularity(newGranularity);
|
|
11830
|
+
}
|
|
11831
|
+
});
|
|
11832
|
+
});
|
|
11833
|
+
console.log("[EnergyModalView] [RFC-0097] Granularity selector initialized with:", this.currentGranularity);
|
|
11834
|
+
}
|
|
11750
11835
|
try {
|
|
11751
11836
|
this.dateRangePicker = await attach(dateRangeInput, {
|
|
11752
11837
|
presetStart: this.config.params.startDate instanceof Date ? this.config.params.startDate.toISOString().split("T")[0] : this.config.params.startDate,
|
|
@@ -11849,6 +11934,37 @@ var EnergyModalView = class {
|
|
|
11849
11934
|
background: #e5e7eb;
|
|
11850
11935
|
}
|
|
11851
11936
|
|
|
11937
|
+
/* RFC-0097: Granularity selector buttons */
|
|
11938
|
+
.myio-btn-granularity {
|
|
11939
|
+
padding: 4px 10px;
|
|
11940
|
+
font-size: 12px;
|
|
11941
|
+
font-weight: 600;
|
|
11942
|
+
border-radius: 6px;
|
|
11943
|
+
border: 1px solid var(--myio-energy-border);
|
|
11944
|
+
background: var(--myio-energy-bg);
|
|
11945
|
+
color: var(--myio-energy-text);
|
|
11946
|
+
cursor: pointer;
|
|
11947
|
+
transition: all 0.2s ease;
|
|
11948
|
+
min-width: 36px;
|
|
11949
|
+
}
|
|
11950
|
+
|
|
11951
|
+
.myio-btn-granularity:hover:not(.active) {
|
|
11952
|
+
background: #f3f4f6;
|
|
11953
|
+
border-color: var(--myio-energy-primary);
|
|
11954
|
+
color: var(--myio-energy-primary);
|
|
11955
|
+
}
|
|
11956
|
+
|
|
11957
|
+
.myio-btn-granularity.active {
|
|
11958
|
+
background: var(--myio-energy-primary);
|
|
11959
|
+
color: white;
|
|
11960
|
+
border-color: var(--myio-energy-primary);
|
|
11961
|
+
box-shadow: 0 2px 4px rgba(74, 20, 140, 0.25);
|
|
11962
|
+
}
|
|
11963
|
+
|
|
11964
|
+
.myio-granularity-selector {
|
|
11965
|
+
border: 1px solid var(--myio-energy-border);
|
|
11966
|
+
}
|
|
11967
|
+
|
|
11852
11968
|
.myio-modal-scope {
|
|
11853
11969
|
height: 100% !important;
|
|
11854
11970
|
display: flex !important;
|
|
@@ -12229,7 +12345,9 @@ var EnergyModal = class {
|
|
|
12229
12345
|
return;
|
|
12230
12346
|
}
|
|
12231
12347
|
if (!this.context?.resolved.ingestionId) {
|
|
12232
|
-
const error = new Error(
|
|
12348
|
+
const error = new Error(
|
|
12349
|
+
"ingestionId not found in device attributes. Please configure the device properly."
|
|
12350
|
+
);
|
|
12233
12351
|
this.handleError(error);
|
|
12234
12352
|
return;
|
|
12235
12353
|
}
|
|
@@ -14493,7 +14611,7 @@ var WaterTankModalView = class {
|
|
|
14493
14611
|
maxLevel: "Maximum Level",
|
|
14494
14612
|
dateRange: "Date Range",
|
|
14495
14613
|
deviceInfo: "Device Information",
|
|
14496
|
-
levelChart: "Level
|
|
14614
|
+
levelChart: "Water Level History (m.c.a)",
|
|
14497
14615
|
percentUnit: "%",
|
|
14498
14616
|
status: {
|
|
14499
14617
|
critical: "Critical",
|
|
@@ -14524,6 +14642,20 @@ var WaterTankModalView = class {
|
|
|
14524
14642
|
return { status: "full", color: "#3498db", label: this.i18n.status.full };
|
|
14525
14643
|
}
|
|
14526
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
|
+
}
|
|
14527
14659
|
/**
|
|
14528
14660
|
* Format timestamp to readable date
|
|
14529
14661
|
*/
|
|
@@ -14536,13 +14668,18 @@ var WaterTankModalView = class {
|
|
|
14536
14668
|
}
|
|
14537
14669
|
return dateStr;
|
|
14538
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
|
+
}
|
|
14539
14678
|
/**
|
|
14540
14679
|
* Render the modal HTML
|
|
14541
14680
|
*/
|
|
14542
14681
|
render() {
|
|
14543
|
-
const {
|
|
14544
|
-
const currentLevel = data.summary.currentLevel ?? context.device.currentLevel ?? 0;
|
|
14545
|
-
const levelStatus = this.getLevelStatus(currentLevel);
|
|
14682
|
+
const { params } = this.config;
|
|
14546
14683
|
this.overlay = document.createElement("div");
|
|
14547
14684
|
this.overlay.className = "myio-water-tank-modal-overlay";
|
|
14548
14685
|
this.overlay.style.cssText = `
|
|
@@ -14563,9 +14700,9 @@ var WaterTankModalView = class {
|
|
|
14563
14700
|
this.modal.className = "myio-water-tank-modal";
|
|
14564
14701
|
this.modal.style.cssText = `
|
|
14565
14702
|
background: white;
|
|
14566
|
-
border-radius:
|
|
14703
|
+
border-radius: 12px;
|
|
14567
14704
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
|
14568
|
-
width: ${params.ui?.width ||
|
|
14705
|
+
width: ${params.ui?.width || 700}px;
|
|
14569
14706
|
max-width: 95vw;
|
|
14570
14707
|
max-height: 90vh;
|
|
14571
14708
|
display: flex;
|
|
@@ -14631,97 +14768,150 @@ var WaterTankModalView = class {
|
|
|
14631
14768
|
overflow-y: auto;
|
|
14632
14769
|
flex: 1;
|
|
14633
14770
|
">
|
|
14634
|
-
${this.
|
|
14771
|
+
${this.renderDateRangePicker()}
|
|
14772
|
+
${this.renderTankVisualization()}
|
|
14635
14773
|
${this.renderChart()}
|
|
14636
|
-
${this.renderDeviceInfo()}
|
|
14637
14774
|
</div>
|
|
14638
14775
|
${this.renderFooter()}
|
|
14639
14776
|
`;
|
|
14640
14777
|
}
|
|
14641
14778
|
/**
|
|
14642
|
-
* Render
|
|
14779
|
+
* Render date range picker
|
|
14643
14780
|
*/
|
|
14644
|
-
|
|
14645
|
-
const {
|
|
14646
|
-
const
|
|
14647
|
-
const
|
|
14781
|
+
renderDateRangePicker() {
|
|
14782
|
+
const { params } = this.config;
|
|
14783
|
+
const startDate = this.formatDateForInput(params.startTs);
|
|
14784
|
+
const endDate = this.formatDateForInput(params.endTs);
|
|
14648
14785
|
return `
|
|
14649
14786
|
<div style="
|
|
14650
|
-
|
|
14651
|
-
|
|
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;
|
|
14652
14794
|
gap: 16px;
|
|
14653
|
-
|
|
14795
|
+
flex-wrap: wrap;
|
|
14654
14796
|
">
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
|
|
14671
|
-
|
|
14672
|
-
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
|
|
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>
|
|
14676
14832
|
</div>
|
|
14677
14833
|
`;
|
|
14678
14834
|
}
|
|
14679
14835
|
/**
|
|
14680
|
-
* Render
|
|
14836
|
+
* Render tank visualization with percentage
|
|
14681
14837
|
*/
|
|
14682
|
-
|
|
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);
|
|
14683
14851
|
return `
|
|
14684
14852
|
<div style="
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
|
|
14688
|
-
|
|
14689
|
-
|
|
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;
|
|
14690
14862
|
">
|
|
14691
|
-
|
|
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
|
+
"/>
|
|
14692
14874
|
<div style="
|
|
14693
|
-
|
|
14694
|
-
top: 12px;
|
|
14695
|
-
right: 12px;
|
|
14696
|
-
background: ${color};
|
|
14875
|
+
background: ${levelStatus.color};
|
|
14697
14876
|
color: white;
|
|
14698
|
-
padding: 4px
|
|
14699
|
-
border-radius:
|
|
14700
|
-
font-size:
|
|
14877
|
+
padding: 4px 12px;
|
|
14878
|
+
border-radius: 20px;
|
|
14879
|
+
font-size: 12px;
|
|
14701
14880
|
font-weight: 600;
|
|
14702
14881
|
text-transform: uppercase;
|
|
14703
|
-
">${
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
font-size: 12px;
|
|
14707
|
-
color: #7f8c8d;
|
|
14708
|
-
margin-bottom: 8px;
|
|
14709
|
-
font-weight: 500;
|
|
14710
|
-
">${label}</div>
|
|
14882
|
+
">${levelStatus.label}</div>
|
|
14883
|
+
</div>
|
|
14884
|
+
|
|
14711
14885
|
<div style="
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
|
|
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>
|
|
14716
14903
|
</div>
|
|
14717
14904
|
`;
|
|
14718
14905
|
}
|
|
14719
14906
|
/**
|
|
14720
|
-
* Render chart section
|
|
14907
|
+
* Render chart section - shows water_level (m.c.a) over time
|
|
14721
14908
|
*/
|
|
14722
14909
|
renderChart() {
|
|
14723
14910
|
const { data } = this.config;
|
|
14724
|
-
|
|
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) {
|
|
14725
14915
|
return `
|
|
14726
14916
|
<div style="
|
|
14727
14917
|
background: #f8f9fa;
|
|
@@ -14729,20 +14919,23 @@ var WaterTankModalView = class {
|
|
|
14729
14919
|
border-radius: 8px;
|
|
14730
14920
|
padding: 48px;
|
|
14731
14921
|
text-align: center;
|
|
14732
|
-
margin-bottom: 24px;
|
|
14733
14922
|
">
|
|
14734
14923
|
<div style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;">\u{1F4CA}</div>
|
|
14735
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>
|
|
14736
14928
|
</div>
|
|
14737
14929
|
`;
|
|
14738
14930
|
}
|
|
14931
|
+
const firstTs = waterLevelPoints[0]?.ts;
|
|
14932
|
+
const lastTs = waterLevelPoints[waterLevelPoints.length - 1]?.ts;
|
|
14739
14933
|
return `
|
|
14740
14934
|
<div style="
|
|
14741
14935
|
background: white;
|
|
14742
14936
|
border: 1px solid #e0e0e0;
|
|
14743
14937
|
border-radius: 8px;
|
|
14744
14938
|
padding: 20px;
|
|
14745
|
-
margin-bottom: 24px;
|
|
14746
14939
|
">
|
|
14747
14940
|
<h3 style="
|
|
14748
14941
|
margin: 0 0 16px 0;
|
|
@@ -14750,56 +14943,18 @@ var WaterTankModalView = class {
|
|
|
14750
14943
|
font-weight: 600;
|
|
14751
14944
|
color: #2c3e50;
|
|
14752
14945
|
">${this.i18n.levelChart}</h3>
|
|
14753
|
-
<canvas id="myio-water-tank-chart" style="width: 100%; height:
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
/**
|
|
14766
|
-
* Render device info section
|
|
14767
|
-
*/
|
|
14768
|
-
renderDeviceInfo() {
|
|
14769
|
-
const { context, data } = this.config;
|
|
14770
|
-
return `
|
|
14771
|
-
<div style="
|
|
14772
|
-
background: #f8f9fa;
|
|
14773
|
-
border: 1px solid #e0e0e0;
|
|
14774
|
-
border-radius: 8px;
|
|
14775
|
-
padding: 20px;
|
|
14776
|
-
">
|
|
14777
|
-
<h3 style="
|
|
14778
|
-
margin: 0 0 16px 0;
|
|
14779
|
-
font-size: 16px;
|
|
14780
|
-
font-weight: 600;
|
|
14781
|
-
color: #2c3e50;
|
|
14782
|
-
">${this.i18n.deviceInfo}</h3>
|
|
14783
|
-
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 12px;">
|
|
14784
|
-
${this.renderInfoRow("Device ID", context.device.id)}
|
|
14785
|
-
${this.renderInfoRow("Label", context.device.label)}
|
|
14786
|
-
${context.device.type ? this.renderInfoRow("Type", context.device.type) : ""}
|
|
14787
|
-
${context.metadata.slaveId ? this.renderInfoRow("Slave ID", String(context.metadata.slaveId)) : ""}
|
|
14788
|
-
${context.metadata.centralId ? this.renderInfoRow("Central ID", context.metadata.centralId) : ""}
|
|
14789
|
-
${this.renderInfoRow("Total Readings", String(data.summary.totalReadings))}
|
|
14790
|
-
${this.renderInfoRow("Data Keys", data.metadata.keys.join(", "))}
|
|
14791
|
-
</div>
|
|
14792
|
-
</div>
|
|
14793
|
-
`;
|
|
14794
|
-
}
|
|
14795
|
-
/**
|
|
14796
|
-
* Render an info row
|
|
14797
|
-
*/
|
|
14798
|
-
renderInfoRow(label, value) {
|
|
14799
|
-
return `
|
|
14800
|
-
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
14801
|
-
<span style="color: #7f8c8d; font-size: 13px;">${label}:</span>
|
|
14802
|
-
<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
|
+
` : ""}
|
|
14803
14958
|
</div>
|
|
14804
14959
|
`;
|
|
14805
14960
|
}
|
|
@@ -14848,6 +15003,10 @@ var WaterTankModalView = class {
|
|
|
14848
15003
|
if (exportBtn) {
|
|
14849
15004
|
exportBtn.addEventListener("click", () => this.config.onExport());
|
|
14850
15005
|
}
|
|
15006
|
+
const applyDatesBtn = this.modal.querySelector("#myio-water-tank-apply-dates");
|
|
15007
|
+
if (applyDatesBtn) {
|
|
15008
|
+
applyDatesBtn.addEventListener("click", () => this.handleDateRangeChange());
|
|
15009
|
+
}
|
|
14851
15010
|
this.overlay.addEventListener("click", (e) => {
|
|
14852
15011
|
if (e.target === this.overlay) {
|
|
14853
15012
|
this.config.onClose();
|
|
@@ -14861,59 +15020,122 @@ var WaterTankModalView = class {
|
|
|
14861
15020
|
this.renderCanvasChart();
|
|
14862
15021
|
});
|
|
14863
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
|
+
}
|
|
14864
15048
|
handleEscapeKey(e) {
|
|
14865
15049
|
if (e.key === "Escape") {
|
|
14866
15050
|
this.config.onClose();
|
|
14867
15051
|
}
|
|
14868
15052
|
}
|
|
14869
15053
|
/**
|
|
14870
|
-
* Render chart using Canvas API
|
|
15054
|
+
* Render chart using Canvas API - shows water_level (m.c.a) over time
|
|
14871
15055
|
*/
|
|
14872
15056
|
renderCanvasChart() {
|
|
14873
15057
|
const canvas = document.getElementById("myio-water-tank-chart");
|
|
14874
15058
|
if (!canvas) return;
|
|
14875
15059
|
const { data } = this.config;
|
|
14876
|
-
|
|
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;
|
|
14877
15064
|
const ctx = canvas.getContext("2d");
|
|
14878
15065
|
if (!ctx) return;
|
|
14879
15066
|
const rect = canvas.getBoundingClientRect();
|
|
14880
15067
|
canvas.width = rect.width * window.devicePixelRatio;
|
|
14881
|
-
canvas.height =
|
|
15068
|
+
canvas.height = 280 * window.devicePixelRatio;
|
|
14882
15069
|
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
14883
15070
|
const width = rect.width;
|
|
14884
|
-
const height =
|
|
14885
|
-
const padding = 40;
|
|
15071
|
+
const height = 280;
|
|
15072
|
+
const padding = { top: 20, right: 20, bottom: 40, left: 60 };
|
|
14886
15073
|
ctx.clearRect(0, 0, width, height);
|
|
14887
|
-
|
|
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";
|
|
14888
15085
|
ctx.lineWidth = 1;
|
|
14889
|
-
ctx.
|
|
14890
|
-
ctx.
|
|
14891
|
-
ctx.
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
|
|
14895
|
-
|
|
14896
|
-
for (let i = 0; i <= 5; i++) {
|
|
14897
|
-
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;
|
|
14898
15093
|
ctx.beginPath();
|
|
14899
|
-
ctx.moveTo(padding, y);
|
|
14900
|
-
ctx.lineTo(width - padding, y);
|
|
15094
|
+
ctx.moveTo(padding.left, y);
|
|
15095
|
+
ctx.lineTo(width - padding.right, y);
|
|
14901
15096
|
ctx.stroke();
|
|
14902
|
-
ctx.
|
|
14903
|
-
ctx.font = "12px Arial";
|
|
14904
|
-
ctx.textAlign = "right";
|
|
14905
|
-
ctx.fillText(`${100 - i * 20}%`, padding - 10, y + 4);
|
|
15097
|
+
ctx.fillText(`${value.toFixed(2)}`, padding.left - 8, y + 4);
|
|
14906
15098
|
}
|
|
14907
|
-
|
|
14908
|
-
|
|
14909
|
-
|
|
14910
|
-
|
|
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();
|
|
14911
15131
|
ctx.strokeStyle = "#3498db";
|
|
14912
15132
|
ctx.lineWidth = 2;
|
|
15133
|
+
ctx.lineJoin = "round";
|
|
15134
|
+
ctx.lineCap = "round";
|
|
14913
15135
|
ctx.beginPath();
|
|
14914
15136
|
points.forEach((point, index) => {
|
|
14915
|
-
const x = padding + index * xScale;
|
|
14916
|
-
const y =
|
|
15137
|
+
const x = padding.left + index * xScale;
|
|
15138
|
+
const y = padding.top + chartHeight - (point.value - chartMinY) / chartRangeY * chartHeight;
|
|
14917
15139
|
if (index === 0) {
|
|
14918
15140
|
ctx.moveTo(x, y);
|
|
14919
15141
|
} else {
|
|
@@ -14921,14 +15143,56 @@ var WaterTankModalView = class {
|
|
|
14921
15143
|
}
|
|
14922
15144
|
});
|
|
14923
15145
|
ctx.stroke();
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
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
|
+
}
|
|
14932
15196
|
}
|
|
14933
15197
|
/**
|
|
14934
15198
|
* Show the modal with animation
|
|
@@ -15193,8 +15457,9 @@ var WaterTankModal = class {
|
|
|
15193
15457
|
data: this.data,
|
|
15194
15458
|
onExport: () => this.handleExport(),
|
|
15195
15459
|
onError: (error) => this.handleError(error),
|
|
15196
|
-
onClose: () => this.close()
|
|
15460
|
+
onClose: () => this.close(),
|
|
15197
15461
|
// Call close() to destroy view and trigger user callback
|
|
15462
|
+
onDateRangeChange: (startTs, endTs) => this.handleDateRangeChange(startTs, endTs)
|
|
15198
15463
|
});
|
|
15199
15464
|
this.view.render();
|
|
15200
15465
|
this.view.show();
|
|
@@ -15226,6 +15491,39 @@ var WaterTankModal = class {
|
|
|
15226
15491
|
}
|
|
15227
15492
|
this.handleClose();
|
|
15228
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
|
+
}
|
|
15229
15527
|
/**
|
|
15230
15528
|
* Handle export functionality
|
|
15231
15529
|
*/
|