myio-js-library 0.1.172 → 0.1.174

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 CHANGED
@@ -588,6 +588,9 @@ __export(index_exports, {
588
588
  MyIOSelectionStore: () => MyIOSelectionStore,
589
589
  MyIOSelectionStoreClass: () => MyIOSelectionStoreClass,
590
590
  MyIOToast: () => MyIOToast,
591
+ POWER_LIMITS_DEVICE_TYPES: () => DEVICE_TYPES,
592
+ POWER_LIMITS_STATUS_CONFIG: () => STATUS_CONFIG,
593
+ POWER_LIMITS_TELEMETRY_TYPES: () => TELEMETRY_TYPES2,
591
594
  addDetectionContext: () => addDetectionContext,
592
595
  addNamespace: () => addNamespace,
593
596
  aggregateByDay: () => aggregateByDay,
@@ -685,6 +688,7 @@ __export(index_exports, {
685
688
  openDashboardPopupWaterTank: () => openDashboardPopupWaterTank,
686
689
  openDemandModal: () => openDemandModal,
687
690
  openGoalsPanel: () => openGoalsPanel,
691
+ openPowerLimitsSetupModal: () => openPowerLimitsSetupModal,
688
692
  openRealTimeTelemetryModal: () => openRealTimeTelemetryModal,
689
693
  openTemperatureComparisonModal: () => openTemperatureComparisonModal,
690
694
  openTemperatureModal: () => openTemperatureModal,
@@ -16780,6 +16784,1002 @@ function validateOptions2(options) {
16780
16784
  }
16781
16785
  }
16782
16786
 
16787
+ // src/components/premium-modals/power-limits/types.ts
16788
+ var DEVICE_TYPES = [
16789
+ { value: "ELEVADOR", label: "Elevator" },
16790
+ { value: "ESCADA_ROLANTE", label: "Escalator" },
16791
+ { value: "MOTOR", label: "Motor" },
16792
+ { value: "BOMBA", label: "Pump" },
16793
+ { value: "CHILLER", label: "Chiller" },
16794
+ { value: "AR_CONDICIONADO", label: "Air Conditioner" },
16795
+ { value: "HVAC", label: "HVAC" },
16796
+ { value: "FANCOIL", label: "Fancoil" },
16797
+ { value: "3F_MEDIDOR", label: "Three-phase Meter" }
16798
+ ];
16799
+ var TELEMETRY_TYPES2 = [
16800
+ { value: "consumption", label: "Consumption (kW)", unit: "kW" },
16801
+ { value: "voltage_a", label: "Voltage A (V)", unit: "V" },
16802
+ { value: "voltage_b", label: "Voltage B (V)", unit: "V" },
16803
+ { value: "voltage_c", label: "Voltage C (V)", unit: "V" },
16804
+ { value: "current_a", label: "Current A (A)", unit: "A" },
16805
+ { value: "current_b", label: "Current B (A)", unit: "A" },
16806
+ { value: "current_c", label: "Current C (A)", unit: "A" },
16807
+ { value: "total_current", label: "Total Current (A)", unit: "A" },
16808
+ { value: "fp_a", label: "Power Factor A", unit: "" },
16809
+ { value: "fp_b", label: "Power Factor B", unit: "" },
16810
+ { value: "fp_c", label: "Power Factor C", unit: "" }
16811
+ ];
16812
+ var STATUS_CONFIG = {
16813
+ standBy: { label: "StandBy", color: "#22c55e", bgColor: "rgba(34, 197, 94, 0.1)" },
16814
+ normal: { label: "Normal", color: "#3b82f6", bgColor: "rgba(59, 130, 246, 0.1)" },
16815
+ alert: { label: "Alert", color: "#f59e0b", bgColor: "rgba(245, 158, 11, 0.1)" },
16816
+ failure: { label: "Failure", color: "#ef4444", bgColor: "rgba(239, 68, 68, 0.1)" }
16817
+ };
16818
+
16819
+ // src/components/premium-modals/power-limits/PowerLimitsModalView.ts
16820
+ var PowerLimitsModalView = class {
16821
+ container = null;
16822
+ overlayEl = null;
16823
+ config;
16824
+ formData;
16825
+ isLoading = false;
16826
+ isSaving = false;
16827
+ constructor(config) {
16828
+ this.config = config;
16829
+ this.formData = {
16830
+ deviceType: config.deviceType,
16831
+ telemetryType: config.telemetryType,
16832
+ standby: { baseValue: null, topValue: null },
16833
+ normal: { baseValue: null, topValue: null },
16834
+ alert: { baseValue: null, topValue: null },
16835
+ failure: { baseValue: null, topValue: null }
16836
+ };
16837
+ }
16838
+ render(targetContainer) {
16839
+ this.overlayEl = document.createElement("div");
16840
+ this.overlayEl.className = "myio-power-limits-overlay";
16841
+ this.overlayEl.innerHTML = `
16842
+ <style>${this.getStyles()}</style>
16843
+ <div class="myio-power-limits-card">
16844
+ ${this.renderHeader()}
16845
+ ${this.renderSelectors()}
16846
+ ${this.renderStatusCards()}
16847
+ ${this.renderLoadingState()}
16848
+ ${this.renderErrorState()}
16849
+ ${this.renderSuccessState()}
16850
+ </div>
16851
+ `;
16852
+ const target = targetContainer || document.body;
16853
+ target.appendChild(this.overlayEl);
16854
+ this.container = this.overlayEl.querySelector(".myio-power-limits-card");
16855
+ this.setupEventListeners();
16856
+ requestAnimationFrame(() => {
16857
+ this.overlayEl?.classList.add("active");
16858
+ });
16859
+ return this.overlayEl;
16860
+ }
16861
+ renderHeader() {
16862
+ return `
16863
+ <div class="myio-power-limits-header">
16864
+ <div class="myio-power-limits-title-section">
16865
+ <span class="myio-power-limits-icon">&#x2699;</span>
16866
+ <h2 class="myio-power-limits-title">Power Limits Setup</h2>
16867
+ </div>
16868
+ <div class="myio-power-limits-actions">
16869
+ <button class="myio-btn myio-btn-primary" id="plm-save-btn" type="button">
16870
+ <span class="myio-btn-text">Save</span>
16871
+ <span class="myio-btn-spinner" style="display: none;"></span>
16872
+ </button>
16873
+ <button class="myio-btn myio-btn-secondary" id="plm-reset-btn" type="button">Reset</button>
16874
+ <button class="myio-btn myio-btn-close" id="plm-close-btn" type="button" aria-label="Close">&times;</button>
16875
+ </div>
16876
+ </div>
16877
+ `;
16878
+ }
16879
+ renderSelectors() {
16880
+ const deviceOptions = DEVICE_TYPES.map(
16881
+ (dt) => `<option value="${dt.value}" ${dt.value === this.config.deviceType ? "selected" : ""}>${dt.label}</option>`
16882
+ ).join("");
16883
+ const telemetryOptions = TELEMETRY_TYPES2.map(
16884
+ (tt) => `<option value="${tt.value}" ${tt.value === this.config.telemetryType ? "selected" : ""}>${tt.label}</option>`
16885
+ ).join("");
16886
+ return `
16887
+ <div class="myio-power-limits-selectors">
16888
+ <div class="myio-form-group">
16889
+ <label for="plm-device-type">Device Type</label>
16890
+ <select id="plm-device-type" class="myio-select">
16891
+ ${deviceOptions}
16892
+ </select>
16893
+ </div>
16894
+ <div class="myio-form-group">
16895
+ <label for="plm-telemetry-type">Telemetry Type</label>
16896
+ <select id="plm-telemetry-type" class="myio-select">
16897
+ ${telemetryOptions}
16898
+ </select>
16899
+ </div>
16900
+ </div>
16901
+ `;
16902
+ }
16903
+ renderStatusCards() {
16904
+ const statuses = ["standby", "normal", "alert", "failure"];
16905
+ const cards = statuses.map((status) => {
16906
+ const config = STATUS_CONFIG[status === "standby" ? "standBy" : status];
16907
+ const formValues = this.formData[status];
16908
+ return `
16909
+ <div class="myio-power-limits-card-item myio-status-${status}" style="--status-color: ${config.color}; --status-bg: ${config.bgColor};">
16910
+ <div class="myio-card-header">
16911
+ <span class="myio-status-indicator"></span>
16912
+ <span class="myio-status-label">${config.label}</span>
16913
+ </div>
16914
+ <div class="myio-card-inputs">
16915
+ <div class="myio-input-group">
16916
+ <label for="plm-${status}-base">Base Value</label>
16917
+ <input
16918
+ type="number"
16919
+ id="plm-${status}-base"
16920
+ class="myio-input"
16921
+ min="0"
16922
+ step="0.01"
16923
+ value="${formValues.baseValue ?? ""}"
16924
+ placeholder="0"
16925
+ >
16926
+ </div>
16927
+ <div class="myio-input-group">
16928
+ <label for="plm-${status}-top">Top Value</label>
16929
+ <input
16930
+ type="number"
16931
+ id="plm-${status}-top"
16932
+ class="myio-input"
16933
+ min="0"
16934
+ step="0.01"
16935
+ value="${formValues.topValue ?? ""}"
16936
+ placeholder="0"
16937
+ >
16938
+ </div>
16939
+ </div>
16940
+ </div>
16941
+ `;
16942
+ }).join("");
16943
+ return `
16944
+ <div class="myio-power-limits-grid" id="plm-grid">
16945
+ ${cards}
16946
+ </div>
16947
+ `;
16948
+ }
16949
+ renderLoadingState() {
16950
+ return `
16951
+ <div class="myio-power-limits-loading" id="plm-loading" style="display: none;">
16952
+ <div class="myio-spinner"></div>
16953
+ <span>Loading configuration...</span>
16954
+ </div>
16955
+ `;
16956
+ }
16957
+ renderErrorState() {
16958
+ return `
16959
+ <div class="myio-power-limits-error" id="plm-error" style="display: none;">
16960
+ <span class="myio-error-icon">&#x26A0;</span>
16961
+ <span class="myio-error-message" id="plm-error-msg"></span>
16962
+ </div>
16963
+ `;
16964
+ }
16965
+ renderSuccessState() {
16966
+ return `
16967
+ <div class="myio-power-limits-success" id="plm-success" style="display: none;">
16968
+ <span class="myio-success-icon">&#x2713;</span>
16969
+ <span class="myio-success-message">Configuration saved successfully!</span>
16970
+ </div>
16971
+ `;
16972
+ }
16973
+ setupEventListeners() {
16974
+ if (!this.overlayEl) return;
16975
+ const closeBtn = this.overlayEl.querySelector("#plm-close-btn");
16976
+ closeBtn?.addEventListener("click", () => this.close());
16977
+ this.overlayEl.addEventListener("click", (e) => {
16978
+ if (e.target === this.overlayEl) {
16979
+ this.close();
16980
+ }
16981
+ });
16982
+ document.addEventListener("keydown", this.handleKeyDown);
16983
+ const saveBtn = this.overlayEl.querySelector("#plm-save-btn");
16984
+ saveBtn?.addEventListener("click", () => this.handleSave());
16985
+ const resetBtn = this.overlayEl.querySelector("#plm-reset-btn");
16986
+ resetBtn?.addEventListener("click", () => this.handleReset());
16987
+ const deviceSelect = this.overlayEl.querySelector("#plm-device-type");
16988
+ deviceSelect?.addEventListener("change", (e) => {
16989
+ const value = e.target.value;
16990
+ this.formData.deviceType = value;
16991
+ this.config.onDeviceTypeChange(value);
16992
+ });
16993
+ const telemetrySelect = this.overlayEl.querySelector("#plm-telemetry-type");
16994
+ telemetrySelect?.addEventListener("change", (e) => {
16995
+ const value = e.target.value;
16996
+ this.formData.telemetryType = value;
16997
+ this.config.onTelemetryTypeChange(value);
16998
+ });
16999
+ const statuses = ["standby", "normal", "alert", "failure"];
17000
+ statuses.forEach((status) => {
17001
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
17002
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
17003
+ baseInput?.addEventListener("input", (e) => {
17004
+ const value = e.target.value;
17005
+ this.formData[status].baseValue = value ? parseFloat(value) : null;
17006
+ });
17007
+ topInput?.addEventListener("input", (e) => {
17008
+ const value = e.target.value;
17009
+ this.formData[status].topValue = value ? parseFloat(value) : null;
17010
+ });
17011
+ });
17012
+ }
17013
+ handleKeyDown = (e) => {
17014
+ if (e.key === "Escape") {
17015
+ this.close();
17016
+ }
17017
+ };
17018
+ async handleSave() {
17019
+ if (this.isSaving) return;
17020
+ const validationError = this.validateForm();
17021
+ if (validationError) {
17022
+ this.showError(validationError);
17023
+ return;
17024
+ }
17025
+ this.isSaving = true;
17026
+ this.showSaveLoading(true);
17027
+ this.hideError();
17028
+ this.hideSuccess();
17029
+ try {
17030
+ await this.config.onSave();
17031
+ this.showSuccess();
17032
+ setTimeout(() => this.hideSuccess(), 3e3);
17033
+ } catch (error) {
17034
+ this.showError(error.message || "Failed to save configuration");
17035
+ } finally {
17036
+ this.isSaving = false;
17037
+ this.showSaveLoading(false);
17038
+ }
17039
+ }
17040
+ handleReset() {
17041
+ const statuses = ["standby", "normal", "alert", "failure"];
17042
+ statuses.forEach((status) => {
17043
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
17044
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
17045
+ if (baseInput) baseInput.value = "";
17046
+ if (topInput) topInput.value = "";
17047
+ this.formData[status] = { baseValue: null, topValue: null };
17048
+ });
17049
+ this.hideError();
17050
+ this.hideSuccess();
17051
+ }
17052
+ validateForm() {
17053
+ const statuses = ["standby", "normal", "alert", "failure"];
17054
+ for (const status of statuses) {
17055
+ const base = this.formData[status].baseValue;
17056
+ const top = this.formData[status].topValue;
17057
+ if (base !== null && base < 0) {
17058
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Base value cannot be negative`;
17059
+ }
17060
+ if (top !== null && top < 0) {
17061
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Top value cannot be negative`;
17062
+ }
17063
+ if (base !== null && top !== null && base > top) {
17064
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Base value should not exceed top value`;
17065
+ }
17066
+ }
17067
+ return null;
17068
+ }
17069
+ close() {
17070
+ document.removeEventListener("keydown", this.handleKeyDown);
17071
+ if (this.overlayEl) {
17072
+ this.overlayEl.classList.remove("active");
17073
+ setTimeout(() => {
17074
+ this.overlayEl?.remove();
17075
+ this.overlayEl = null;
17076
+ this.container = null;
17077
+ this.config.onClose();
17078
+ }, 300);
17079
+ }
17080
+ }
17081
+ destroy() {
17082
+ document.removeEventListener("keydown", this.handleKeyDown);
17083
+ this.overlayEl?.remove();
17084
+ this.overlayEl = null;
17085
+ this.container = null;
17086
+ }
17087
+ showLoading() {
17088
+ this.isLoading = true;
17089
+ const loadingEl = this.overlayEl?.querySelector("#plm-loading");
17090
+ const gridEl = this.overlayEl?.querySelector("#plm-grid");
17091
+ if (loadingEl) loadingEl.style.display = "flex";
17092
+ if (gridEl) gridEl.style.opacity = "0.5";
17093
+ }
17094
+ hideLoading() {
17095
+ this.isLoading = false;
17096
+ const loadingEl = this.overlayEl?.querySelector("#plm-loading");
17097
+ const gridEl = this.overlayEl?.querySelector("#plm-grid");
17098
+ if (loadingEl) loadingEl.style.display = "none";
17099
+ if (gridEl) gridEl.style.opacity = "1";
17100
+ }
17101
+ showSaveLoading(show) {
17102
+ const saveBtn = this.overlayEl?.querySelector("#plm-save-btn");
17103
+ const btnText = saveBtn?.querySelector(".myio-btn-text");
17104
+ const btnSpinner = saveBtn?.querySelector(".myio-btn-spinner");
17105
+ if (saveBtn) saveBtn.disabled = show;
17106
+ if (btnText) btnText.style.display = show ? "none" : "inline";
17107
+ if (btnSpinner) btnSpinner.style.display = show ? "inline-block" : "none";
17108
+ }
17109
+ showError(message) {
17110
+ const errorEl = this.overlayEl?.querySelector("#plm-error");
17111
+ const errorMsg = this.overlayEl?.querySelector("#plm-error-msg");
17112
+ if (errorEl) errorEl.style.display = "flex";
17113
+ if (errorMsg) errorMsg.textContent = message;
17114
+ }
17115
+ hideError() {
17116
+ const errorEl = this.overlayEl?.querySelector("#plm-error");
17117
+ if (errorEl) errorEl.style.display = "none";
17118
+ }
17119
+ showSuccess() {
17120
+ const successEl = this.overlayEl?.querySelector("#plm-success");
17121
+ if (successEl) successEl.style.display = "flex";
17122
+ }
17123
+ hideSuccess() {
17124
+ const successEl = this.overlayEl?.querySelector("#plm-success");
17125
+ if (successEl) successEl.style.display = "none";
17126
+ }
17127
+ getFormData() {
17128
+ return { ...this.formData };
17129
+ }
17130
+ setFormData(data) {
17131
+ if (data.deviceType) this.formData.deviceType = data.deviceType;
17132
+ if (data.telemetryType) this.formData.telemetryType = data.telemetryType;
17133
+ if (data.standby) this.formData.standby = { ...data.standby };
17134
+ if (data.normal) this.formData.normal = { ...data.normal };
17135
+ if (data.alert) this.formData.alert = { ...data.alert };
17136
+ if (data.failure) this.formData.failure = { ...data.failure };
17137
+ this.updateInputValues();
17138
+ }
17139
+ updateInputValues() {
17140
+ const statuses = ["standby", "normal", "alert", "failure"];
17141
+ statuses.forEach((status) => {
17142
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
17143
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
17144
+ if (baseInput) {
17145
+ baseInput.value = this.formData[status].baseValue?.toString() ?? "";
17146
+ }
17147
+ if (topInput) {
17148
+ topInput.value = this.formData[status].topValue?.toString() ?? "";
17149
+ }
17150
+ });
17151
+ const deviceSelect = this.overlayEl?.querySelector("#plm-device-type");
17152
+ const telemetrySelect = this.overlayEl?.querySelector("#plm-telemetry-type");
17153
+ if (deviceSelect) deviceSelect.value = this.formData.deviceType;
17154
+ if (telemetrySelect) telemetrySelect.value = this.formData.telemetryType;
17155
+ }
17156
+ getStyles() {
17157
+ const styles = this.config.styles || {};
17158
+ const primaryColor = styles.primaryColor || "#4A148C";
17159
+ const successColor = styles.successColor || "#22c55e";
17160
+ const warningColor = styles.warningColor || "#f59e0b";
17161
+ const dangerColor = styles.dangerColor || "#ef4444";
17162
+ const infoColor = styles.infoColor || "#3b82f6";
17163
+ return `
17164
+ .myio-power-limits-overlay {
17165
+ position: fixed;
17166
+ top: 0;
17167
+ left: 0;
17168
+ right: 0;
17169
+ bottom: 0;
17170
+ background: rgba(0, 0, 0, 0.6);
17171
+ display: flex;
17172
+ align-items: center;
17173
+ justify-content: center;
17174
+ z-index: ${styles.zIndex || 1e4};
17175
+ opacity: 0;
17176
+ visibility: hidden;
17177
+ transition: all 0.3s ease;
17178
+ font-family: ${styles.fontFamily || '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'};
17179
+ }
17180
+
17181
+ .myio-power-limits-overlay.active {
17182
+ opacity: 1;
17183
+ visibility: visible;
17184
+ }
17185
+
17186
+ .myio-power-limits-card {
17187
+ background: ${styles.backgroundColor || "#ffffff"};
17188
+ border-radius: ${styles.borderRadius || "12px"};
17189
+ width: 90%;
17190
+ max-width: 875px;
17191
+ max-height: 90vh;
17192
+ overflow-y: auto;
17193
+ transform: scale(0.9);
17194
+ transition: transform 0.3s ease;
17195
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
17196
+ }
17197
+
17198
+ .myio-power-limits-overlay.active .myio-power-limits-card {
17199
+ transform: scale(1);
17200
+ }
17201
+
17202
+ .myio-power-limits-header {
17203
+ display: flex;
17204
+ align-items: center;
17205
+ justify-content: space-between;
17206
+ padding: 20px 24px;
17207
+ background: linear-gradient(135deg, ${primaryColor}, ${this.lightenColor(primaryColor, 20)});
17208
+ color: white;
17209
+ border-radius: 12px 12px 0 0;
17210
+ }
17211
+
17212
+ .myio-power-limits-title-section {
17213
+ display: flex;
17214
+ align-items: center;
17215
+ gap: 12px;
17216
+ }
17217
+
17218
+ .myio-power-limits-icon {
17219
+ font-size: 24px;
17220
+ }
17221
+
17222
+ .myio-power-limits-title {
17223
+ font-size: 1.25rem;
17224
+ font-weight: 600;
17225
+ margin: 0;
17226
+ }
17227
+
17228
+ .myio-power-limits-actions {
17229
+ display: flex;
17230
+ align-items: center;
17231
+ gap: 8px;
17232
+ }
17233
+
17234
+ .myio-btn {
17235
+ padding: 8px 16px;
17236
+ border-radius: ${styles.buttonRadius || "6px"};
17237
+ font-size: 14px;
17238
+ font-weight: 500;
17239
+ cursor: pointer;
17240
+ border: none;
17241
+ transition: all 0.2s;
17242
+ display: inline-flex;
17243
+ align-items: center;
17244
+ gap: 6px;
17245
+ }
17246
+
17247
+ .myio-btn:disabled {
17248
+ opacity: 0.6;
17249
+ cursor: not-allowed;
17250
+ }
17251
+
17252
+ .myio-btn-primary {
17253
+ background: white;
17254
+ color: ${primaryColor};
17255
+ }
17256
+
17257
+ .myio-btn-primary:hover:not(:disabled) {
17258
+ background: #f3f4f6;
17259
+ }
17260
+
17261
+ .myio-btn-secondary {
17262
+ background: rgba(255, 255, 255, 0.2);
17263
+ color: white;
17264
+ }
17265
+
17266
+ .myio-btn-secondary:hover:not(:disabled) {
17267
+ background: rgba(255, 255, 255, 0.3);
17268
+ }
17269
+
17270
+ .myio-btn-close {
17271
+ background: transparent;
17272
+ color: white;
17273
+ font-size: 24px;
17274
+ padding: 4px 8px;
17275
+ line-height: 1;
17276
+ }
17277
+
17278
+ .myio-btn-close:hover {
17279
+ background: rgba(255, 255, 255, 0.1);
17280
+ }
17281
+
17282
+ .myio-btn-spinner {
17283
+ width: 16px;
17284
+ height: 16px;
17285
+ border: 2px solid ${primaryColor};
17286
+ border-top-color: transparent;
17287
+ border-radius: 50%;
17288
+ animation: spin 0.8s linear infinite;
17289
+ }
17290
+
17291
+ @keyframes spin {
17292
+ to { transform: rotate(360deg); }
17293
+ }
17294
+
17295
+ .myio-power-limits-selectors {
17296
+ display: grid;
17297
+ grid-template-columns: 1fr 1fr;
17298
+ gap: 16px;
17299
+ padding: 20px 24px;
17300
+ background: #f9fafb;
17301
+ border-bottom: 1px solid #e5e7eb;
17302
+ }
17303
+
17304
+ .myio-form-group {
17305
+ display: flex;
17306
+ flex-direction: column;
17307
+ gap: 6px;
17308
+ }
17309
+
17310
+ .myio-form-group label {
17311
+ font-size: 13px;
17312
+ font-weight: 500;
17313
+ color: #374151;
17314
+ }
17315
+
17316
+ .myio-select, .myio-input {
17317
+ padding: 10px 12px;
17318
+ border: 1px solid #d1d5db;
17319
+ border-radius: 6px;
17320
+ font-size: 14px;
17321
+ background: white;
17322
+ color: #1f2937;
17323
+ transition: border-color 0.2s, box-shadow 0.2s;
17324
+ }
17325
+
17326
+ .myio-select:focus, .myio-input:focus {
17327
+ outline: none;
17328
+ border-color: ${primaryColor};
17329
+ box-shadow: 0 0 0 3px ${this.hexToRgba(primaryColor, 0.1)};
17330
+ }
17331
+
17332
+ .myio-power-limits-grid {
17333
+ display: grid;
17334
+ grid-template-columns: repeat(2, 1fr);
17335
+ gap: 16px;
17336
+ padding: 24px;
17337
+ transition: opacity 0.3s;
17338
+ }
17339
+
17340
+ .myio-power-limits-card-item {
17341
+ background: var(--status-bg);
17342
+ border: 1px solid var(--status-color);
17343
+ border-radius: 8px;
17344
+ padding: 16px;
17345
+ transition: transform 0.2s, box-shadow 0.2s;
17346
+ }
17347
+
17348
+ .myio-power-limits-card-item:hover {
17349
+ transform: translateY(-2px);
17350
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
17351
+ }
17352
+
17353
+ .myio-card-header {
17354
+ display: flex;
17355
+ align-items: center;
17356
+ gap: 8px;
17357
+ margin-bottom: 12px;
17358
+ }
17359
+
17360
+ .myio-status-indicator {
17361
+ width: 12px;
17362
+ height: 12px;
17363
+ border-radius: 50%;
17364
+ background: var(--status-color);
17365
+ }
17366
+
17367
+ .myio-status-label {
17368
+ font-weight: 600;
17369
+ font-size: 14px;
17370
+ color: #1f2937;
17371
+ }
17372
+
17373
+ .myio-card-inputs {
17374
+ display: grid;
17375
+ grid-template-columns: 1fr 1fr;
17376
+ gap: 12px;
17377
+ }
17378
+
17379
+ .myio-input-group {
17380
+ display: flex;
17381
+ flex-direction: column;
17382
+ gap: 4px;
17383
+ }
17384
+
17385
+ .myio-input-group label {
17386
+ font-size: 11px;
17387
+ font-weight: 500;
17388
+ color: #6b7280;
17389
+ text-transform: uppercase;
17390
+ }
17391
+
17392
+ .myio-power-limits-loading,
17393
+ .myio-power-limits-error,
17394
+ .myio-power-limits-success {
17395
+ display: flex;
17396
+ align-items: center;
17397
+ justify-content: center;
17398
+ gap: 12px;
17399
+ padding: 16px 24px;
17400
+ margin: 0 24px 24px;
17401
+ border-radius: 8px;
17402
+ }
17403
+
17404
+ .myio-power-limits-loading {
17405
+ background: #f3f4f6;
17406
+ color: #6b7280;
17407
+ }
17408
+
17409
+ .myio-power-limits-error {
17410
+ background: #fef2f2;
17411
+ color: ${dangerColor};
17412
+ border: 1px solid ${dangerColor};
17413
+ }
17414
+
17415
+ .myio-power-limits-success {
17416
+ background: #f0fdf4;
17417
+ color: ${successColor};
17418
+ border: 1px solid ${successColor};
17419
+ }
17420
+
17421
+ .myio-spinner {
17422
+ width: 24px;
17423
+ height: 24px;
17424
+ border: 3px solid #e5e7eb;
17425
+ border-top-color: ${primaryColor};
17426
+ border-radius: 50%;
17427
+ animation: spin 0.8s linear infinite;
17428
+ }
17429
+
17430
+ .myio-error-icon, .myio-success-icon {
17431
+ font-size: 20px;
17432
+ }
17433
+
17434
+ @media (max-width: 600px) {
17435
+ .myio-power-limits-selectors,
17436
+ .myio-power-limits-grid {
17437
+ grid-template-columns: 1fr;
17438
+ }
17439
+
17440
+ .myio-power-limits-header {
17441
+ flex-direction: column;
17442
+ gap: 12px;
17443
+ text-align: center;
17444
+ }
17445
+
17446
+ .myio-power-limits-actions {
17447
+ width: 100%;
17448
+ justify-content: center;
17449
+ }
17450
+ }
17451
+ `;
17452
+ }
17453
+ lightenColor(hex, percent) {
17454
+ const num = parseInt(hex.replace("#", ""), 16);
17455
+ const amt = Math.round(2.55 * percent);
17456
+ const R = (num >> 16) + amt;
17457
+ const G = (num >> 8 & 255) + amt;
17458
+ const B = (num & 255) + amt;
17459
+ return "#" + (16777216 + (R < 255 ? R < 1 ? 0 : R : 255) * 65536 + (G < 255 ? G < 1 ? 0 : G : 255) * 256 + (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
17460
+ }
17461
+ hexToRgba(hex, alpha) {
17462
+ const num = parseInt(hex.replace("#", ""), 16);
17463
+ const R = num >> 16;
17464
+ const G = num >> 8 & 255;
17465
+ const B = num & 255;
17466
+ return `rgba(${R}, ${G}, ${B}, ${alpha})`;
17467
+ }
17468
+ };
17469
+
17470
+ // src/components/premium-modals/power-limits/PowerLimitsPersister.ts
17471
+ var PowerLimitsPersister = class {
17472
+ jwtToken;
17473
+ tbBaseUrl;
17474
+ constructor(jwtToken, tbBaseUrl) {
17475
+ this.jwtToken = jwtToken;
17476
+ this.tbBaseUrl = tbBaseUrl || window.location.origin;
17477
+ }
17478
+ /**
17479
+ * Load existing mapInstantaneousPower from customer server_scope attributes
17480
+ */
17481
+ async loadCustomerPowerLimits(customerId) {
17482
+ try {
17483
+ const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/values/attributes/SERVER_SCOPE?keys=mapInstantaneousPower`;
17484
+ const response = await fetch(url, {
17485
+ method: "GET",
17486
+ headers: {
17487
+ "X-Authorization": `Bearer ${this.jwtToken}`,
17488
+ "Content-Type": "application/json"
17489
+ }
17490
+ });
17491
+ if (!response.ok) {
17492
+ if (response.status === 404) {
17493
+ console.log("[PowerLimitsPersister] No existing mapInstantaneousPower found");
17494
+ return null;
17495
+ }
17496
+ throw this.createHttpError(response.status, await response.text().catch(() => ""));
17497
+ }
17498
+ const data = await response.json();
17499
+ if (!data || data.length === 0) {
17500
+ console.log("[PowerLimitsPersister] No mapInstantaneousPower attribute found");
17501
+ return null;
17502
+ }
17503
+ const attr = data.find((item) => item.key === "mapInstantaneousPower");
17504
+ if (!attr || !attr.value) {
17505
+ return null;
17506
+ }
17507
+ const parsedValue = typeof attr.value === "string" ? JSON.parse(attr.value) : attr.value;
17508
+ console.log("[PowerLimitsPersister] Loaded mapInstantaneousPower:", parsedValue);
17509
+ return parsedValue;
17510
+ } catch (error) {
17511
+ console.error("[PowerLimitsPersister] Error loading power limits:", error);
17512
+ throw this.mapError(error);
17513
+ }
17514
+ }
17515
+ /**
17516
+ * Save mapInstantaneousPower to customer server_scope attributes
17517
+ */
17518
+ async saveCustomerPowerLimits(customerId, limits) {
17519
+ try {
17520
+ const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/attributes/SERVER_SCOPE`;
17521
+ const payload = {
17522
+ mapInstantaneousPower: limits
17523
+ };
17524
+ console.log("[PowerLimitsPersister] Saving power limits:", payload);
17525
+ const response = await fetch(url, {
17526
+ method: "POST",
17527
+ headers: {
17528
+ "X-Authorization": `Bearer ${this.jwtToken}`,
17529
+ "Content-Type": "application/json"
17530
+ },
17531
+ body: JSON.stringify(payload)
17532
+ });
17533
+ if (!response.ok) {
17534
+ throw this.createHttpError(response.status, await response.text().catch(() => ""));
17535
+ }
17536
+ console.log("[PowerLimitsPersister] Successfully saved power limits");
17537
+ return { ok: true };
17538
+ } catch (error) {
17539
+ console.error("[PowerLimitsPersister] Error saving power limits:", error);
17540
+ return { ok: false, error: this.mapError(error) };
17541
+ }
17542
+ }
17543
+ /**
17544
+ * Extract form data for a specific device type and telemetry type
17545
+ */
17546
+ extractFormData(limits, deviceType, telemetryType) {
17547
+ const defaultFormData = {
17548
+ deviceType,
17549
+ telemetryType,
17550
+ standby: { baseValue: null, topValue: null },
17551
+ normal: { baseValue: null, topValue: null },
17552
+ alert: { baseValue: null, topValue: null },
17553
+ failure: { baseValue: null, topValue: null }
17554
+ };
17555
+ if (!limits || !limits.limitsByInstantaneoustPowerType) {
17556
+ return defaultFormData;
17557
+ }
17558
+ const telemetryEntry = limits.limitsByInstantaneoustPowerType.find(
17559
+ (t) => t.telemetryType === telemetryType
17560
+ );
17561
+ if (!telemetryEntry || !telemetryEntry.itemsByDeviceType) {
17562
+ return defaultFormData;
17563
+ }
17564
+ const deviceEntry = telemetryEntry.itemsByDeviceType.find(
17565
+ (d) => d.deviceType === deviceType
17566
+ );
17567
+ if (!deviceEntry || !deviceEntry.limitsByDeviceStatus) {
17568
+ return defaultFormData;
17569
+ }
17570
+ const statusMap = {
17571
+ "standBy": "standby",
17572
+ "normal": "normal",
17573
+ "alert": "alert",
17574
+ "failure": "failure"
17575
+ };
17576
+ deviceEntry.limitsByDeviceStatus.forEach((status) => {
17577
+ const formKey = statusMap[status.deviceStatusName];
17578
+ if (formKey && defaultFormData[formKey]) {
17579
+ defaultFormData[formKey] = {
17580
+ baseValue: status.limitsValues.baseValue,
17581
+ topValue: status.limitsValues.topValue
17582
+ };
17583
+ }
17584
+ });
17585
+ return defaultFormData;
17586
+ }
17587
+ /**
17588
+ * Merge form data into existing limits JSON
17589
+ * Creates new entries if they don't exist
17590
+ */
17591
+ mergeFormDataIntoLimits(existingLimits, formData) {
17592
+ const result = existingLimits ? JSON.parse(JSON.stringify(existingLimits)) : { version: "1.0.0", limitsByInstantaneoustPowerType: [] };
17593
+ const statusLimits = [
17594
+ {
17595
+ deviceStatusName: "standBy",
17596
+ limitsValues: {
17597
+ baseValue: formData.standby.baseValue ?? 0,
17598
+ topValue: formData.standby.topValue ?? 0
17599
+ }
17600
+ },
17601
+ {
17602
+ deviceStatusName: "normal",
17603
+ limitsValues: {
17604
+ baseValue: formData.normal.baseValue ?? 0,
17605
+ topValue: formData.normal.topValue ?? 0
17606
+ }
17607
+ },
17608
+ {
17609
+ deviceStatusName: "alert",
17610
+ limitsValues: {
17611
+ baseValue: formData.alert.baseValue ?? 0,
17612
+ topValue: formData.alert.topValue ?? 0
17613
+ }
17614
+ },
17615
+ {
17616
+ deviceStatusName: "failure",
17617
+ limitsValues: {
17618
+ baseValue: formData.failure.baseValue ?? 0,
17619
+ topValue: formData.failure.topValue ?? 0
17620
+ }
17621
+ }
17622
+ ];
17623
+ let telemetryEntry = result.limitsByInstantaneoustPowerType.find(
17624
+ (t) => t.telemetryType === formData.telemetryType
17625
+ );
17626
+ if (!telemetryEntry) {
17627
+ telemetryEntry = {
17628
+ telemetryType: formData.telemetryType,
17629
+ itemsByDeviceType: []
17630
+ };
17631
+ result.limitsByInstantaneoustPowerType.push(telemetryEntry);
17632
+ }
17633
+ let deviceEntry = telemetryEntry.itemsByDeviceType.find(
17634
+ (d) => d.deviceType === formData.deviceType
17635
+ );
17636
+ if (!deviceEntry) {
17637
+ deviceEntry = {
17638
+ deviceType: formData.deviceType,
17639
+ name: `mapInstantaneousPower${this.formatDeviceTypeName(formData.deviceType)}`,
17640
+ description: `Power limits for ${formData.deviceType}`,
17641
+ limitsByDeviceStatus: []
17642
+ };
17643
+ telemetryEntry.itemsByDeviceType.push(deviceEntry);
17644
+ }
17645
+ deviceEntry.limitsByDeviceStatus = statusLimits;
17646
+ deviceEntry.name = `mapInstantaneousPower${this.formatDeviceTypeName(formData.deviceType)}`;
17647
+ deviceEntry.description = `Power limits for ${formData.deviceType} - ${formData.telemetryType}`;
17648
+ return result;
17649
+ }
17650
+ /**
17651
+ * Format device type name for the JSON name field
17652
+ */
17653
+ formatDeviceTypeName(deviceType) {
17654
+ if (!deviceType) return "";
17655
+ return deviceType.toLowerCase().split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
17656
+ }
17657
+ createHttpError(status, body) {
17658
+ const error = new Error(`HTTP ${status}: ${body}`);
17659
+ error.status = status;
17660
+ error.body = body;
17661
+ return error;
17662
+ }
17663
+ mapError(error) {
17664
+ const status = error.status;
17665
+ if (status === 400) {
17666
+ return {
17667
+ code: "VALIDATION_ERROR",
17668
+ message: "Invalid input data",
17669
+ cause: error
17670
+ };
17671
+ }
17672
+ if (status === 401) {
17673
+ return {
17674
+ code: "TOKEN_EXPIRED",
17675
+ message: "Authentication token has expired",
17676
+ cause: error
17677
+ };
17678
+ }
17679
+ if (status === 403) {
17680
+ return {
17681
+ code: "AUTH_ERROR",
17682
+ message: "Insufficient permissions",
17683
+ cause: error
17684
+ };
17685
+ }
17686
+ if (status === 404) {
17687
+ return {
17688
+ code: "NETWORK_ERROR",
17689
+ message: "Customer not found",
17690
+ cause: error
17691
+ };
17692
+ }
17693
+ if (status >= 500) {
17694
+ return {
17695
+ code: "NETWORK_ERROR",
17696
+ message: "Server error occurred",
17697
+ cause: error
17698
+ };
17699
+ }
17700
+ return {
17701
+ code: "UNKNOWN_ERROR",
17702
+ message: error.message || "Unknown error occurred",
17703
+ cause: error
17704
+ };
17705
+ }
17706
+ };
17707
+
17708
+ // src/components/premium-modals/power-limits/openPowerLimitsSetupModal.ts
17709
+ async function openPowerLimitsSetupModal(params) {
17710
+ if (!params.token) {
17711
+ throw new Error("[PowerLimitsSetupModal] token is required");
17712
+ }
17713
+ if (!params.customerId) {
17714
+ throw new Error("[PowerLimitsSetupModal] customerId is required");
17715
+ }
17716
+ const persister = new PowerLimitsPersister(params.token, params.tbBaseUrl);
17717
+ let currentDeviceType = params.deviceType || "ELEVADOR";
17718
+ let currentTelemetryType = params.telemetryType || "consumption";
17719
+ let existingLimits = params.existingMapPower || null;
17720
+ const view = new PowerLimitsModalView({
17721
+ deviceType: currentDeviceType,
17722
+ telemetryType: currentTelemetryType,
17723
+ styles: params.styles,
17724
+ locale: params.locale,
17725
+ onDeviceTypeChange: async (deviceType) => {
17726
+ currentDeviceType = deviceType;
17727
+ await loadFormData();
17728
+ },
17729
+ onTelemetryTypeChange: async (telemetryType) => {
17730
+ currentTelemetryType = telemetryType;
17731
+ await loadFormData();
17732
+ },
17733
+ onSave: async () => {
17734
+ const formData = view.getFormData();
17735
+ const updatedLimits = persister.mergeFormDataIntoLimits(existingLimits, formData);
17736
+ const result = await persister.saveCustomerPowerLimits(params.customerId, updatedLimits);
17737
+ if (!result.ok) {
17738
+ throw new Error(result.error?.message || "Failed to save configuration");
17739
+ }
17740
+ existingLimits = updatedLimits;
17741
+ if (params.onSave) {
17742
+ params.onSave(updatedLimits);
17743
+ }
17744
+ },
17745
+ onClose: () => {
17746
+ if (params.onClose) {
17747
+ params.onClose();
17748
+ }
17749
+ }
17750
+ });
17751
+ async function loadFormData() {
17752
+ view.showLoading();
17753
+ try {
17754
+ if (!existingLimits) {
17755
+ existingLimits = await persister.loadCustomerPowerLimits(params.customerId);
17756
+ }
17757
+ const formData = persister.extractFormData(existingLimits, currentDeviceType, currentTelemetryType);
17758
+ view.setFormData(formData);
17759
+ } catch (error) {
17760
+ console.error("[PowerLimitsSetupModal] Error loading form data:", error);
17761
+ view.showError(error.message || "Failed to load configuration");
17762
+ } finally {
17763
+ view.hideLoading();
17764
+ }
17765
+ }
17766
+ let container;
17767
+ if (params.container) {
17768
+ if (typeof params.container === "string") {
17769
+ container = document.querySelector(params.container);
17770
+ } else {
17771
+ container = params.container;
17772
+ }
17773
+ }
17774
+ view.render(container);
17775
+ await loadFormData();
17776
+ return {
17777
+ destroy: () => view.destroy(),
17778
+ getFormData: () => view.getFormData(),
17779
+ setFormData: (data) => view.setFormData(data)
17780
+ };
17781
+ }
17782
+
16783
17783
  // src/components/premium-modals/settings/SettingsModalView.ts
16784
17784
  var SettingsModalView = class {
16785
17785
  container;
@@ -27047,6 +28047,9 @@ function createDistributionChartWidget(config) {
27047
28047
  MyIOSelectionStore,
27048
28048
  MyIOSelectionStoreClass,
27049
28049
  MyIOToast,
28050
+ POWER_LIMITS_DEVICE_TYPES,
28051
+ POWER_LIMITS_STATUS_CONFIG,
28052
+ POWER_LIMITS_TELEMETRY_TYPES,
27050
28053
  addDetectionContext,
27051
28054
  addNamespace,
27052
28055
  aggregateByDay,
@@ -27144,6 +28147,7 @@ function createDistributionChartWidget(config) {
27144
28147
  openDashboardPopupWaterTank,
27145
28148
  openDemandModal,
27146
28149
  openGoalsPanel,
28150
+ openPowerLimitsSetupModal,
27147
28151
  openRealTimeTelemetryModal,
27148
28152
  openTemperatureComparisonModal,
27149
28153
  openTemperatureModal,