myio-js-library 0.1.172 → 0.1.173

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.
@@ -16456,6 +16456,1002 @@ ${rangeText}`;
16456
16456
  }
16457
16457
  }
16458
16458
 
16459
+ // src/components/premium-modals/power-limits/types.ts
16460
+ var DEVICE_TYPES = [
16461
+ { value: "ELEVADOR", label: "Elevator" },
16462
+ { value: "ESCADA_ROLANTE", label: "Escalator" },
16463
+ { value: "MOTOR", label: "Motor" },
16464
+ { value: "BOMBA", label: "Pump" },
16465
+ { value: "CHILLER", label: "Chiller" },
16466
+ { value: "AR_CONDICIONADO", label: "Air Conditioner" },
16467
+ { value: "HVAC", label: "HVAC" },
16468
+ { value: "FANCOIL", label: "Fancoil" },
16469
+ { value: "3F_MEDIDOR", label: "Three-phase Meter" }
16470
+ ];
16471
+ var TELEMETRY_TYPES2 = [
16472
+ { value: "consumption", label: "Consumption (kW)", unit: "kW" },
16473
+ { value: "voltage_a", label: "Voltage A (V)", unit: "V" },
16474
+ { value: "voltage_b", label: "Voltage B (V)", unit: "V" },
16475
+ { value: "voltage_c", label: "Voltage C (V)", unit: "V" },
16476
+ { value: "current_a", label: "Current A (A)", unit: "A" },
16477
+ { value: "current_b", label: "Current B (A)", unit: "A" },
16478
+ { value: "current_c", label: "Current C (A)", unit: "A" },
16479
+ { value: "total_current", label: "Total Current (A)", unit: "A" },
16480
+ { value: "fp_a", label: "Power Factor A", unit: "" },
16481
+ { value: "fp_b", label: "Power Factor B", unit: "" },
16482
+ { value: "fp_c", label: "Power Factor C", unit: "" }
16483
+ ];
16484
+ var STATUS_CONFIG = {
16485
+ standBy: { label: "StandBy", color: "#22c55e", bgColor: "rgba(34, 197, 94, 0.1)" },
16486
+ normal: { label: "Normal", color: "#3b82f6", bgColor: "rgba(59, 130, 246, 0.1)" },
16487
+ alert: { label: "Alert", color: "#f59e0b", bgColor: "rgba(245, 158, 11, 0.1)" },
16488
+ failure: { label: "Failure", color: "#ef4444", bgColor: "rgba(239, 68, 68, 0.1)" }
16489
+ };
16490
+
16491
+ // src/components/premium-modals/power-limits/PowerLimitsModalView.ts
16492
+ var PowerLimitsModalView = class {
16493
+ container = null;
16494
+ overlayEl = null;
16495
+ config;
16496
+ formData;
16497
+ isLoading = false;
16498
+ isSaving = false;
16499
+ constructor(config) {
16500
+ this.config = config;
16501
+ this.formData = {
16502
+ deviceType: config.deviceType,
16503
+ telemetryType: config.telemetryType,
16504
+ standby: { baseValue: null, topValue: null },
16505
+ normal: { baseValue: null, topValue: null },
16506
+ alert: { baseValue: null, topValue: null },
16507
+ failure: { baseValue: null, topValue: null }
16508
+ };
16509
+ }
16510
+ render(targetContainer) {
16511
+ this.overlayEl = document.createElement("div");
16512
+ this.overlayEl.className = "myio-power-limits-overlay";
16513
+ this.overlayEl.innerHTML = `
16514
+ <style>${this.getStyles()}</style>
16515
+ <div class="myio-power-limits-card">
16516
+ ${this.renderHeader()}
16517
+ ${this.renderSelectors()}
16518
+ ${this.renderStatusCards()}
16519
+ ${this.renderLoadingState()}
16520
+ ${this.renderErrorState()}
16521
+ ${this.renderSuccessState()}
16522
+ </div>
16523
+ `;
16524
+ const target = targetContainer || document.body;
16525
+ target.appendChild(this.overlayEl);
16526
+ this.container = this.overlayEl.querySelector(".myio-power-limits-card");
16527
+ this.setupEventListeners();
16528
+ requestAnimationFrame(() => {
16529
+ this.overlayEl?.classList.add("active");
16530
+ });
16531
+ return this.overlayEl;
16532
+ }
16533
+ renderHeader() {
16534
+ return `
16535
+ <div class="myio-power-limits-header">
16536
+ <div class="myio-power-limits-title-section">
16537
+ <span class="myio-power-limits-icon">&#x2699;</span>
16538
+ <h2 class="myio-power-limits-title">Power Limits Setup</h2>
16539
+ </div>
16540
+ <div class="myio-power-limits-actions">
16541
+ <button class="myio-btn myio-btn-primary" id="plm-save-btn" type="button">
16542
+ <span class="myio-btn-text">Save</span>
16543
+ <span class="myio-btn-spinner" style="display: none;"></span>
16544
+ </button>
16545
+ <button class="myio-btn myio-btn-secondary" id="plm-reset-btn" type="button">Reset</button>
16546
+ <button class="myio-btn myio-btn-close" id="plm-close-btn" type="button" aria-label="Close">&times;</button>
16547
+ </div>
16548
+ </div>
16549
+ `;
16550
+ }
16551
+ renderSelectors() {
16552
+ const deviceOptions = DEVICE_TYPES.map(
16553
+ (dt) => `<option value="${dt.value}" ${dt.value === this.config.deviceType ? "selected" : ""}>${dt.label}</option>`
16554
+ ).join("");
16555
+ const telemetryOptions = TELEMETRY_TYPES2.map(
16556
+ (tt) => `<option value="${tt.value}" ${tt.value === this.config.telemetryType ? "selected" : ""}>${tt.label}</option>`
16557
+ ).join("");
16558
+ return `
16559
+ <div class="myio-power-limits-selectors">
16560
+ <div class="myio-form-group">
16561
+ <label for="plm-device-type">Device Type</label>
16562
+ <select id="plm-device-type" class="myio-select">
16563
+ ${deviceOptions}
16564
+ </select>
16565
+ </div>
16566
+ <div class="myio-form-group">
16567
+ <label for="plm-telemetry-type">Telemetry Type</label>
16568
+ <select id="plm-telemetry-type" class="myio-select">
16569
+ ${telemetryOptions}
16570
+ </select>
16571
+ </div>
16572
+ </div>
16573
+ `;
16574
+ }
16575
+ renderStatusCards() {
16576
+ const statuses = ["standby", "normal", "alert", "failure"];
16577
+ const cards = statuses.map((status) => {
16578
+ const config = STATUS_CONFIG[status === "standby" ? "standBy" : status];
16579
+ const formValues = this.formData[status];
16580
+ return `
16581
+ <div class="myio-power-limits-card-item myio-status-${status}" style="--status-color: ${config.color}; --status-bg: ${config.bgColor};">
16582
+ <div class="myio-card-header">
16583
+ <span class="myio-status-indicator"></span>
16584
+ <span class="myio-status-label">${config.label}</span>
16585
+ </div>
16586
+ <div class="myio-card-inputs">
16587
+ <div class="myio-input-group">
16588
+ <label for="plm-${status}-base">Base Value</label>
16589
+ <input
16590
+ type="number"
16591
+ id="plm-${status}-base"
16592
+ class="myio-input"
16593
+ min="0"
16594
+ step="0.01"
16595
+ value="${formValues.baseValue ?? ""}"
16596
+ placeholder="0"
16597
+ >
16598
+ </div>
16599
+ <div class="myio-input-group">
16600
+ <label for="plm-${status}-top">Top Value</label>
16601
+ <input
16602
+ type="number"
16603
+ id="plm-${status}-top"
16604
+ class="myio-input"
16605
+ min="0"
16606
+ step="0.01"
16607
+ value="${formValues.topValue ?? ""}"
16608
+ placeholder="0"
16609
+ >
16610
+ </div>
16611
+ </div>
16612
+ </div>
16613
+ `;
16614
+ }).join("");
16615
+ return `
16616
+ <div class="myio-power-limits-grid" id="plm-grid">
16617
+ ${cards}
16618
+ </div>
16619
+ `;
16620
+ }
16621
+ renderLoadingState() {
16622
+ return `
16623
+ <div class="myio-power-limits-loading" id="plm-loading" style="display: none;">
16624
+ <div class="myio-spinner"></div>
16625
+ <span>Loading configuration...</span>
16626
+ </div>
16627
+ `;
16628
+ }
16629
+ renderErrorState() {
16630
+ return `
16631
+ <div class="myio-power-limits-error" id="plm-error" style="display: none;">
16632
+ <span class="myio-error-icon">&#x26A0;</span>
16633
+ <span class="myio-error-message" id="plm-error-msg"></span>
16634
+ </div>
16635
+ `;
16636
+ }
16637
+ renderSuccessState() {
16638
+ return `
16639
+ <div class="myio-power-limits-success" id="plm-success" style="display: none;">
16640
+ <span class="myio-success-icon">&#x2713;</span>
16641
+ <span class="myio-success-message">Configuration saved successfully!</span>
16642
+ </div>
16643
+ `;
16644
+ }
16645
+ setupEventListeners() {
16646
+ if (!this.overlayEl) return;
16647
+ const closeBtn = this.overlayEl.querySelector("#plm-close-btn");
16648
+ closeBtn?.addEventListener("click", () => this.close());
16649
+ this.overlayEl.addEventListener("click", (e) => {
16650
+ if (e.target === this.overlayEl) {
16651
+ this.close();
16652
+ }
16653
+ });
16654
+ document.addEventListener("keydown", this.handleKeyDown);
16655
+ const saveBtn = this.overlayEl.querySelector("#plm-save-btn");
16656
+ saveBtn?.addEventListener("click", () => this.handleSave());
16657
+ const resetBtn = this.overlayEl.querySelector("#plm-reset-btn");
16658
+ resetBtn?.addEventListener("click", () => this.handleReset());
16659
+ const deviceSelect = this.overlayEl.querySelector("#plm-device-type");
16660
+ deviceSelect?.addEventListener("change", (e) => {
16661
+ const value = e.target.value;
16662
+ this.formData.deviceType = value;
16663
+ this.config.onDeviceTypeChange(value);
16664
+ });
16665
+ const telemetrySelect = this.overlayEl.querySelector("#plm-telemetry-type");
16666
+ telemetrySelect?.addEventListener("change", (e) => {
16667
+ const value = e.target.value;
16668
+ this.formData.telemetryType = value;
16669
+ this.config.onTelemetryTypeChange(value);
16670
+ });
16671
+ const statuses = ["standby", "normal", "alert", "failure"];
16672
+ statuses.forEach((status) => {
16673
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
16674
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
16675
+ baseInput?.addEventListener("input", (e) => {
16676
+ const value = e.target.value;
16677
+ this.formData[status].baseValue = value ? parseFloat(value) : null;
16678
+ });
16679
+ topInput?.addEventListener("input", (e) => {
16680
+ const value = e.target.value;
16681
+ this.formData[status].topValue = value ? parseFloat(value) : null;
16682
+ });
16683
+ });
16684
+ }
16685
+ handleKeyDown = (e) => {
16686
+ if (e.key === "Escape") {
16687
+ this.close();
16688
+ }
16689
+ };
16690
+ async handleSave() {
16691
+ if (this.isSaving) return;
16692
+ const validationError = this.validateForm();
16693
+ if (validationError) {
16694
+ this.showError(validationError);
16695
+ return;
16696
+ }
16697
+ this.isSaving = true;
16698
+ this.showSaveLoading(true);
16699
+ this.hideError();
16700
+ this.hideSuccess();
16701
+ try {
16702
+ await this.config.onSave();
16703
+ this.showSuccess();
16704
+ setTimeout(() => this.hideSuccess(), 3e3);
16705
+ } catch (error) {
16706
+ this.showError(error.message || "Failed to save configuration");
16707
+ } finally {
16708
+ this.isSaving = false;
16709
+ this.showSaveLoading(false);
16710
+ }
16711
+ }
16712
+ handleReset() {
16713
+ const statuses = ["standby", "normal", "alert", "failure"];
16714
+ statuses.forEach((status) => {
16715
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
16716
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
16717
+ if (baseInput) baseInput.value = "";
16718
+ if (topInput) topInput.value = "";
16719
+ this.formData[status] = { baseValue: null, topValue: null };
16720
+ });
16721
+ this.hideError();
16722
+ this.hideSuccess();
16723
+ }
16724
+ validateForm() {
16725
+ const statuses = ["standby", "normal", "alert", "failure"];
16726
+ for (const status of statuses) {
16727
+ const base = this.formData[status].baseValue;
16728
+ const top = this.formData[status].topValue;
16729
+ if (base !== null && base < 0) {
16730
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Base value cannot be negative`;
16731
+ }
16732
+ if (top !== null && top < 0) {
16733
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Top value cannot be negative`;
16734
+ }
16735
+ if (base !== null && top !== null && base > top) {
16736
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Base value should not exceed top value`;
16737
+ }
16738
+ }
16739
+ return null;
16740
+ }
16741
+ close() {
16742
+ document.removeEventListener("keydown", this.handleKeyDown);
16743
+ if (this.overlayEl) {
16744
+ this.overlayEl.classList.remove("active");
16745
+ setTimeout(() => {
16746
+ this.overlayEl?.remove();
16747
+ this.overlayEl = null;
16748
+ this.container = null;
16749
+ this.config.onClose();
16750
+ }, 300);
16751
+ }
16752
+ }
16753
+ destroy() {
16754
+ document.removeEventListener("keydown", this.handleKeyDown);
16755
+ this.overlayEl?.remove();
16756
+ this.overlayEl = null;
16757
+ this.container = null;
16758
+ }
16759
+ showLoading() {
16760
+ this.isLoading = true;
16761
+ const loadingEl = this.overlayEl?.querySelector("#plm-loading");
16762
+ const gridEl = this.overlayEl?.querySelector("#plm-grid");
16763
+ if (loadingEl) loadingEl.style.display = "flex";
16764
+ if (gridEl) gridEl.style.opacity = "0.5";
16765
+ }
16766
+ hideLoading() {
16767
+ this.isLoading = false;
16768
+ const loadingEl = this.overlayEl?.querySelector("#plm-loading");
16769
+ const gridEl = this.overlayEl?.querySelector("#plm-grid");
16770
+ if (loadingEl) loadingEl.style.display = "none";
16771
+ if (gridEl) gridEl.style.opacity = "1";
16772
+ }
16773
+ showSaveLoading(show) {
16774
+ const saveBtn = this.overlayEl?.querySelector("#plm-save-btn");
16775
+ const btnText = saveBtn?.querySelector(".myio-btn-text");
16776
+ const btnSpinner = saveBtn?.querySelector(".myio-btn-spinner");
16777
+ if (saveBtn) saveBtn.disabled = show;
16778
+ if (btnText) btnText.style.display = show ? "none" : "inline";
16779
+ if (btnSpinner) btnSpinner.style.display = show ? "inline-block" : "none";
16780
+ }
16781
+ showError(message) {
16782
+ const errorEl = this.overlayEl?.querySelector("#plm-error");
16783
+ const errorMsg = this.overlayEl?.querySelector("#plm-error-msg");
16784
+ if (errorEl) errorEl.style.display = "flex";
16785
+ if (errorMsg) errorMsg.textContent = message;
16786
+ }
16787
+ hideError() {
16788
+ const errorEl = this.overlayEl?.querySelector("#plm-error");
16789
+ if (errorEl) errorEl.style.display = "none";
16790
+ }
16791
+ showSuccess() {
16792
+ const successEl = this.overlayEl?.querySelector("#plm-success");
16793
+ if (successEl) successEl.style.display = "flex";
16794
+ }
16795
+ hideSuccess() {
16796
+ const successEl = this.overlayEl?.querySelector("#plm-success");
16797
+ if (successEl) successEl.style.display = "none";
16798
+ }
16799
+ getFormData() {
16800
+ return { ...this.formData };
16801
+ }
16802
+ setFormData(data) {
16803
+ if (data.deviceType) this.formData.deviceType = data.deviceType;
16804
+ if (data.telemetryType) this.formData.telemetryType = data.telemetryType;
16805
+ if (data.standby) this.formData.standby = { ...data.standby };
16806
+ if (data.normal) this.formData.normal = { ...data.normal };
16807
+ if (data.alert) this.formData.alert = { ...data.alert };
16808
+ if (data.failure) this.formData.failure = { ...data.failure };
16809
+ this.updateInputValues();
16810
+ }
16811
+ updateInputValues() {
16812
+ const statuses = ["standby", "normal", "alert", "failure"];
16813
+ statuses.forEach((status) => {
16814
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
16815
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
16816
+ if (baseInput) {
16817
+ baseInput.value = this.formData[status].baseValue?.toString() ?? "";
16818
+ }
16819
+ if (topInput) {
16820
+ topInput.value = this.formData[status].topValue?.toString() ?? "";
16821
+ }
16822
+ });
16823
+ const deviceSelect = this.overlayEl?.querySelector("#plm-device-type");
16824
+ const telemetrySelect = this.overlayEl?.querySelector("#plm-telemetry-type");
16825
+ if (deviceSelect) deviceSelect.value = this.formData.deviceType;
16826
+ if (telemetrySelect) telemetrySelect.value = this.formData.telemetryType;
16827
+ }
16828
+ getStyles() {
16829
+ const styles = this.config.styles || {};
16830
+ const primaryColor = styles.primaryColor || "#4A148C";
16831
+ const successColor = styles.successColor || "#22c55e";
16832
+ styles.warningColor || "#f59e0b";
16833
+ const dangerColor = styles.dangerColor || "#ef4444";
16834
+ styles.infoColor || "#3b82f6";
16835
+ return `
16836
+ .myio-power-limits-overlay {
16837
+ position: fixed;
16838
+ top: 0;
16839
+ left: 0;
16840
+ right: 0;
16841
+ bottom: 0;
16842
+ background: rgba(0, 0, 0, 0.6);
16843
+ display: flex;
16844
+ align-items: center;
16845
+ justify-content: center;
16846
+ z-index: ${styles.zIndex || 1e4};
16847
+ opacity: 0;
16848
+ visibility: hidden;
16849
+ transition: all 0.3s ease;
16850
+ font-family: ${styles.fontFamily || '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'};
16851
+ }
16852
+
16853
+ .myio-power-limits-overlay.active {
16854
+ opacity: 1;
16855
+ visibility: visible;
16856
+ }
16857
+
16858
+ .myio-power-limits-card {
16859
+ background: ${styles.backgroundColor || "#ffffff"};
16860
+ border-radius: ${styles.borderRadius || "12px"};
16861
+ width: 90%;
16862
+ max-width: 700px;
16863
+ max-height: 90vh;
16864
+ overflow-y: auto;
16865
+ transform: scale(0.9);
16866
+ transition: transform 0.3s ease;
16867
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
16868
+ }
16869
+
16870
+ .myio-power-limits-overlay.active .myio-power-limits-card {
16871
+ transform: scale(1);
16872
+ }
16873
+
16874
+ .myio-power-limits-header {
16875
+ display: flex;
16876
+ align-items: center;
16877
+ justify-content: space-between;
16878
+ padding: 20px 24px;
16879
+ background: linear-gradient(135deg, ${primaryColor}, ${this.lightenColor(primaryColor, 20)});
16880
+ color: white;
16881
+ border-radius: 12px 12px 0 0;
16882
+ }
16883
+
16884
+ .myio-power-limits-title-section {
16885
+ display: flex;
16886
+ align-items: center;
16887
+ gap: 12px;
16888
+ }
16889
+
16890
+ .myio-power-limits-icon {
16891
+ font-size: 24px;
16892
+ }
16893
+
16894
+ .myio-power-limits-title {
16895
+ font-size: 1.25rem;
16896
+ font-weight: 600;
16897
+ margin: 0;
16898
+ }
16899
+
16900
+ .myio-power-limits-actions {
16901
+ display: flex;
16902
+ align-items: center;
16903
+ gap: 8px;
16904
+ }
16905
+
16906
+ .myio-btn {
16907
+ padding: 8px 16px;
16908
+ border-radius: ${styles.buttonRadius || "6px"};
16909
+ font-size: 14px;
16910
+ font-weight: 500;
16911
+ cursor: pointer;
16912
+ border: none;
16913
+ transition: all 0.2s;
16914
+ display: inline-flex;
16915
+ align-items: center;
16916
+ gap: 6px;
16917
+ }
16918
+
16919
+ .myio-btn:disabled {
16920
+ opacity: 0.6;
16921
+ cursor: not-allowed;
16922
+ }
16923
+
16924
+ .myio-btn-primary {
16925
+ background: white;
16926
+ color: ${primaryColor};
16927
+ }
16928
+
16929
+ .myio-btn-primary:hover:not(:disabled) {
16930
+ background: #f3f4f6;
16931
+ }
16932
+
16933
+ .myio-btn-secondary {
16934
+ background: rgba(255, 255, 255, 0.2);
16935
+ color: white;
16936
+ }
16937
+
16938
+ .myio-btn-secondary:hover:not(:disabled) {
16939
+ background: rgba(255, 255, 255, 0.3);
16940
+ }
16941
+
16942
+ .myio-btn-close {
16943
+ background: transparent;
16944
+ color: white;
16945
+ font-size: 24px;
16946
+ padding: 4px 8px;
16947
+ line-height: 1;
16948
+ }
16949
+
16950
+ .myio-btn-close:hover {
16951
+ background: rgba(255, 255, 255, 0.1);
16952
+ }
16953
+
16954
+ .myio-btn-spinner {
16955
+ width: 16px;
16956
+ height: 16px;
16957
+ border: 2px solid ${primaryColor};
16958
+ border-top-color: transparent;
16959
+ border-radius: 50%;
16960
+ animation: spin 0.8s linear infinite;
16961
+ }
16962
+
16963
+ @keyframes spin {
16964
+ to { transform: rotate(360deg); }
16965
+ }
16966
+
16967
+ .myio-power-limits-selectors {
16968
+ display: grid;
16969
+ grid-template-columns: 1fr 1fr;
16970
+ gap: 16px;
16971
+ padding: 20px 24px;
16972
+ background: #f9fafb;
16973
+ border-bottom: 1px solid #e5e7eb;
16974
+ }
16975
+
16976
+ .myio-form-group {
16977
+ display: flex;
16978
+ flex-direction: column;
16979
+ gap: 6px;
16980
+ }
16981
+
16982
+ .myio-form-group label {
16983
+ font-size: 13px;
16984
+ font-weight: 500;
16985
+ color: #374151;
16986
+ }
16987
+
16988
+ .myio-select, .myio-input {
16989
+ padding: 10px 12px;
16990
+ border: 1px solid #d1d5db;
16991
+ border-radius: 6px;
16992
+ font-size: 14px;
16993
+ background: white;
16994
+ color: #1f2937;
16995
+ transition: border-color 0.2s, box-shadow 0.2s;
16996
+ }
16997
+
16998
+ .myio-select:focus, .myio-input:focus {
16999
+ outline: none;
17000
+ border-color: ${primaryColor};
17001
+ box-shadow: 0 0 0 3px ${this.hexToRgba(primaryColor, 0.1)};
17002
+ }
17003
+
17004
+ .myio-power-limits-grid {
17005
+ display: grid;
17006
+ grid-template-columns: repeat(2, 1fr);
17007
+ gap: 16px;
17008
+ padding: 24px;
17009
+ transition: opacity 0.3s;
17010
+ }
17011
+
17012
+ .myio-power-limits-card-item {
17013
+ background: var(--status-bg);
17014
+ border: 1px solid var(--status-color);
17015
+ border-radius: 8px;
17016
+ padding: 16px;
17017
+ transition: transform 0.2s, box-shadow 0.2s;
17018
+ }
17019
+
17020
+ .myio-power-limits-card-item:hover {
17021
+ transform: translateY(-2px);
17022
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
17023
+ }
17024
+
17025
+ .myio-card-header {
17026
+ display: flex;
17027
+ align-items: center;
17028
+ gap: 8px;
17029
+ margin-bottom: 12px;
17030
+ }
17031
+
17032
+ .myio-status-indicator {
17033
+ width: 12px;
17034
+ height: 12px;
17035
+ border-radius: 50%;
17036
+ background: var(--status-color);
17037
+ }
17038
+
17039
+ .myio-status-label {
17040
+ font-weight: 600;
17041
+ font-size: 14px;
17042
+ color: #1f2937;
17043
+ }
17044
+
17045
+ .myio-card-inputs {
17046
+ display: grid;
17047
+ grid-template-columns: 1fr 1fr;
17048
+ gap: 12px;
17049
+ }
17050
+
17051
+ .myio-input-group {
17052
+ display: flex;
17053
+ flex-direction: column;
17054
+ gap: 4px;
17055
+ }
17056
+
17057
+ .myio-input-group label {
17058
+ font-size: 11px;
17059
+ font-weight: 500;
17060
+ color: #6b7280;
17061
+ text-transform: uppercase;
17062
+ }
17063
+
17064
+ .myio-power-limits-loading,
17065
+ .myio-power-limits-error,
17066
+ .myio-power-limits-success {
17067
+ display: flex;
17068
+ align-items: center;
17069
+ justify-content: center;
17070
+ gap: 12px;
17071
+ padding: 16px 24px;
17072
+ margin: 0 24px 24px;
17073
+ border-radius: 8px;
17074
+ }
17075
+
17076
+ .myio-power-limits-loading {
17077
+ background: #f3f4f6;
17078
+ color: #6b7280;
17079
+ }
17080
+
17081
+ .myio-power-limits-error {
17082
+ background: #fef2f2;
17083
+ color: ${dangerColor};
17084
+ border: 1px solid ${dangerColor};
17085
+ }
17086
+
17087
+ .myio-power-limits-success {
17088
+ background: #f0fdf4;
17089
+ color: ${successColor};
17090
+ border: 1px solid ${successColor};
17091
+ }
17092
+
17093
+ .myio-spinner {
17094
+ width: 24px;
17095
+ height: 24px;
17096
+ border: 3px solid #e5e7eb;
17097
+ border-top-color: ${primaryColor};
17098
+ border-radius: 50%;
17099
+ animation: spin 0.8s linear infinite;
17100
+ }
17101
+
17102
+ .myio-error-icon, .myio-success-icon {
17103
+ font-size: 20px;
17104
+ }
17105
+
17106
+ @media (max-width: 600px) {
17107
+ .myio-power-limits-selectors,
17108
+ .myio-power-limits-grid {
17109
+ grid-template-columns: 1fr;
17110
+ }
17111
+
17112
+ .myio-power-limits-header {
17113
+ flex-direction: column;
17114
+ gap: 12px;
17115
+ text-align: center;
17116
+ }
17117
+
17118
+ .myio-power-limits-actions {
17119
+ width: 100%;
17120
+ justify-content: center;
17121
+ }
17122
+ }
17123
+ `;
17124
+ }
17125
+ lightenColor(hex, percent) {
17126
+ const num = parseInt(hex.replace("#", ""), 16);
17127
+ const amt = Math.round(2.55 * percent);
17128
+ const R = (num >> 16) + amt;
17129
+ const G = (num >> 8 & 255) + amt;
17130
+ const B = (num & 255) + amt;
17131
+ 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);
17132
+ }
17133
+ hexToRgba(hex, alpha) {
17134
+ const num = parseInt(hex.replace("#", ""), 16);
17135
+ const R = num >> 16;
17136
+ const G = num >> 8 & 255;
17137
+ const B = num & 255;
17138
+ return `rgba(${R}, ${G}, ${B}, ${alpha})`;
17139
+ }
17140
+ };
17141
+
17142
+ // src/components/premium-modals/power-limits/PowerLimitsPersister.ts
17143
+ var PowerLimitsPersister = class {
17144
+ jwtToken;
17145
+ tbBaseUrl;
17146
+ constructor(jwtToken, tbBaseUrl) {
17147
+ this.jwtToken = jwtToken;
17148
+ this.tbBaseUrl = tbBaseUrl || window.location.origin;
17149
+ }
17150
+ /**
17151
+ * Load existing mapInstantaneousPower from customer server_scope attributes
17152
+ */
17153
+ async loadCustomerPowerLimits(customerId) {
17154
+ try {
17155
+ const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/values/attributes/SERVER_SCOPE?keys=mapInstantaneousPower`;
17156
+ const response = await fetch(url, {
17157
+ method: "GET",
17158
+ headers: {
17159
+ "X-Authorization": `Bearer ${this.jwtToken}`,
17160
+ "Content-Type": "application/json"
17161
+ }
17162
+ });
17163
+ if (!response.ok) {
17164
+ if (response.status === 404) {
17165
+ console.log("[PowerLimitsPersister] No existing mapInstantaneousPower found");
17166
+ return null;
17167
+ }
17168
+ throw this.createHttpError(response.status, await response.text().catch(() => ""));
17169
+ }
17170
+ const data = await response.json();
17171
+ if (!data || data.length === 0) {
17172
+ console.log("[PowerLimitsPersister] No mapInstantaneousPower attribute found");
17173
+ return null;
17174
+ }
17175
+ const attr = data.find((item) => item.key === "mapInstantaneousPower");
17176
+ if (!attr || !attr.value) {
17177
+ return null;
17178
+ }
17179
+ const parsedValue = typeof attr.value === "string" ? JSON.parse(attr.value) : attr.value;
17180
+ console.log("[PowerLimitsPersister] Loaded mapInstantaneousPower:", parsedValue);
17181
+ return parsedValue;
17182
+ } catch (error) {
17183
+ console.error("[PowerLimitsPersister] Error loading power limits:", error);
17184
+ throw this.mapError(error);
17185
+ }
17186
+ }
17187
+ /**
17188
+ * Save mapInstantaneousPower to customer server_scope attributes
17189
+ */
17190
+ async saveCustomerPowerLimits(customerId, limits) {
17191
+ try {
17192
+ const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/attributes/SERVER_SCOPE`;
17193
+ const payload = {
17194
+ mapInstantaneousPower: limits
17195
+ };
17196
+ console.log("[PowerLimitsPersister] Saving power limits:", payload);
17197
+ const response = await fetch(url, {
17198
+ method: "POST",
17199
+ headers: {
17200
+ "X-Authorization": `Bearer ${this.jwtToken}`,
17201
+ "Content-Type": "application/json"
17202
+ },
17203
+ body: JSON.stringify(payload)
17204
+ });
17205
+ if (!response.ok) {
17206
+ throw this.createHttpError(response.status, await response.text().catch(() => ""));
17207
+ }
17208
+ console.log("[PowerLimitsPersister] Successfully saved power limits");
17209
+ return { ok: true };
17210
+ } catch (error) {
17211
+ console.error("[PowerLimitsPersister] Error saving power limits:", error);
17212
+ return { ok: false, error: this.mapError(error) };
17213
+ }
17214
+ }
17215
+ /**
17216
+ * Extract form data for a specific device type and telemetry type
17217
+ */
17218
+ extractFormData(limits, deviceType, telemetryType) {
17219
+ const defaultFormData = {
17220
+ deviceType,
17221
+ telemetryType,
17222
+ standby: { baseValue: null, topValue: null },
17223
+ normal: { baseValue: null, topValue: null },
17224
+ alert: { baseValue: null, topValue: null },
17225
+ failure: { baseValue: null, topValue: null }
17226
+ };
17227
+ if (!limits || !limits.limitsByInstantaneoustPowerType) {
17228
+ return defaultFormData;
17229
+ }
17230
+ const telemetryEntry = limits.limitsByInstantaneoustPowerType.find(
17231
+ (t) => t.telemetryType === telemetryType
17232
+ );
17233
+ if (!telemetryEntry || !telemetryEntry.itemsByDeviceType) {
17234
+ return defaultFormData;
17235
+ }
17236
+ const deviceEntry = telemetryEntry.itemsByDeviceType.find(
17237
+ (d) => d.deviceType === deviceType
17238
+ );
17239
+ if (!deviceEntry || !deviceEntry.limitsByDeviceStatus) {
17240
+ return defaultFormData;
17241
+ }
17242
+ const statusMap = {
17243
+ "standBy": "standby",
17244
+ "normal": "normal",
17245
+ "alert": "alert",
17246
+ "failure": "failure"
17247
+ };
17248
+ deviceEntry.limitsByDeviceStatus.forEach((status) => {
17249
+ const formKey = statusMap[status.deviceStatusName];
17250
+ if (formKey && defaultFormData[formKey]) {
17251
+ defaultFormData[formKey] = {
17252
+ baseValue: status.limitsValues.baseValue,
17253
+ topValue: status.limitsValues.topValue
17254
+ };
17255
+ }
17256
+ });
17257
+ return defaultFormData;
17258
+ }
17259
+ /**
17260
+ * Merge form data into existing limits JSON
17261
+ * Creates new entries if they don't exist
17262
+ */
17263
+ mergeFormDataIntoLimits(existingLimits, formData) {
17264
+ const result = existingLimits ? JSON.parse(JSON.stringify(existingLimits)) : { version: "1.0.0", limitsByInstantaneoustPowerType: [] };
17265
+ const statusLimits = [
17266
+ {
17267
+ deviceStatusName: "standBy",
17268
+ limitsValues: {
17269
+ baseValue: formData.standby.baseValue ?? 0,
17270
+ topValue: formData.standby.topValue ?? 0
17271
+ }
17272
+ },
17273
+ {
17274
+ deviceStatusName: "normal",
17275
+ limitsValues: {
17276
+ baseValue: formData.normal.baseValue ?? 0,
17277
+ topValue: formData.normal.topValue ?? 0
17278
+ }
17279
+ },
17280
+ {
17281
+ deviceStatusName: "alert",
17282
+ limitsValues: {
17283
+ baseValue: formData.alert.baseValue ?? 0,
17284
+ topValue: formData.alert.topValue ?? 0
17285
+ }
17286
+ },
17287
+ {
17288
+ deviceStatusName: "failure",
17289
+ limitsValues: {
17290
+ baseValue: formData.failure.baseValue ?? 0,
17291
+ topValue: formData.failure.topValue ?? 0
17292
+ }
17293
+ }
17294
+ ];
17295
+ let telemetryEntry = result.limitsByInstantaneoustPowerType.find(
17296
+ (t) => t.telemetryType === formData.telemetryType
17297
+ );
17298
+ if (!telemetryEntry) {
17299
+ telemetryEntry = {
17300
+ telemetryType: formData.telemetryType,
17301
+ itemsByDeviceType: []
17302
+ };
17303
+ result.limitsByInstantaneoustPowerType.push(telemetryEntry);
17304
+ }
17305
+ let deviceEntry = telemetryEntry.itemsByDeviceType.find(
17306
+ (d) => d.deviceType === formData.deviceType
17307
+ );
17308
+ if (!deviceEntry) {
17309
+ deviceEntry = {
17310
+ deviceType: formData.deviceType,
17311
+ name: `mapInstantaneousPower${this.formatDeviceTypeName(formData.deviceType)}`,
17312
+ description: `Power limits for ${formData.deviceType}`,
17313
+ limitsByDeviceStatus: []
17314
+ };
17315
+ telemetryEntry.itemsByDeviceType.push(deviceEntry);
17316
+ }
17317
+ deviceEntry.limitsByDeviceStatus = statusLimits;
17318
+ deviceEntry.name = `mapInstantaneousPower${this.formatDeviceTypeName(formData.deviceType)}`;
17319
+ deviceEntry.description = `Power limits for ${formData.deviceType} - ${formData.telemetryType}`;
17320
+ return result;
17321
+ }
17322
+ /**
17323
+ * Format device type name for the JSON name field
17324
+ */
17325
+ formatDeviceTypeName(deviceType) {
17326
+ if (!deviceType) return "";
17327
+ return deviceType.toLowerCase().split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
17328
+ }
17329
+ createHttpError(status, body) {
17330
+ const error = new Error(`HTTP ${status}: ${body}`);
17331
+ error.status = status;
17332
+ error.body = body;
17333
+ return error;
17334
+ }
17335
+ mapError(error) {
17336
+ const status = error.status;
17337
+ if (status === 400) {
17338
+ return {
17339
+ code: "VALIDATION_ERROR",
17340
+ message: "Invalid input data",
17341
+ cause: error
17342
+ };
17343
+ }
17344
+ if (status === 401) {
17345
+ return {
17346
+ code: "TOKEN_EXPIRED",
17347
+ message: "Authentication token has expired",
17348
+ cause: error
17349
+ };
17350
+ }
17351
+ if (status === 403) {
17352
+ return {
17353
+ code: "AUTH_ERROR",
17354
+ message: "Insufficient permissions",
17355
+ cause: error
17356
+ };
17357
+ }
17358
+ if (status === 404) {
17359
+ return {
17360
+ code: "NETWORK_ERROR",
17361
+ message: "Customer not found",
17362
+ cause: error
17363
+ };
17364
+ }
17365
+ if (status >= 500) {
17366
+ return {
17367
+ code: "NETWORK_ERROR",
17368
+ message: "Server error occurred",
17369
+ cause: error
17370
+ };
17371
+ }
17372
+ return {
17373
+ code: "UNKNOWN_ERROR",
17374
+ message: error.message || "Unknown error occurred",
17375
+ cause: error
17376
+ };
17377
+ }
17378
+ };
17379
+
17380
+ // src/components/premium-modals/power-limits/openPowerLimitsSetupModal.ts
17381
+ async function openPowerLimitsSetupModal(params) {
17382
+ if (!params.token) {
17383
+ throw new Error("[PowerLimitsSetupModal] token is required");
17384
+ }
17385
+ if (!params.customerId) {
17386
+ throw new Error("[PowerLimitsSetupModal] customerId is required");
17387
+ }
17388
+ const persister = new PowerLimitsPersister(params.token, params.tbBaseUrl);
17389
+ let currentDeviceType = params.deviceType || "ELEVADOR";
17390
+ let currentTelemetryType = params.telemetryType || "consumption";
17391
+ let existingLimits = params.existingMapPower || null;
17392
+ const view = new PowerLimitsModalView({
17393
+ deviceType: currentDeviceType,
17394
+ telemetryType: currentTelemetryType,
17395
+ styles: params.styles,
17396
+ locale: params.locale,
17397
+ onDeviceTypeChange: async (deviceType) => {
17398
+ currentDeviceType = deviceType;
17399
+ await loadFormData();
17400
+ },
17401
+ onTelemetryTypeChange: async (telemetryType) => {
17402
+ currentTelemetryType = telemetryType;
17403
+ await loadFormData();
17404
+ },
17405
+ onSave: async () => {
17406
+ const formData = view.getFormData();
17407
+ const updatedLimits = persister.mergeFormDataIntoLimits(existingLimits, formData);
17408
+ const result = await persister.saveCustomerPowerLimits(params.customerId, updatedLimits);
17409
+ if (!result.ok) {
17410
+ throw new Error(result.error?.message || "Failed to save configuration");
17411
+ }
17412
+ existingLimits = updatedLimits;
17413
+ if (params.onSave) {
17414
+ params.onSave(updatedLimits);
17415
+ }
17416
+ },
17417
+ onClose: () => {
17418
+ if (params.onClose) {
17419
+ params.onClose();
17420
+ }
17421
+ }
17422
+ });
17423
+ async function loadFormData() {
17424
+ view.showLoading();
17425
+ try {
17426
+ if (!existingLimits) {
17427
+ existingLimits = await persister.loadCustomerPowerLimits(params.customerId);
17428
+ }
17429
+ const formData = persister.extractFormData(existingLimits, currentDeviceType, currentTelemetryType);
17430
+ view.setFormData(formData);
17431
+ } catch (error) {
17432
+ console.error("[PowerLimitsSetupModal] Error loading form data:", error);
17433
+ view.showError(error.message || "Failed to load configuration");
17434
+ } finally {
17435
+ view.hideLoading();
17436
+ }
17437
+ }
17438
+ let container;
17439
+ if (params.container) {
17440
+ if (typeof params.container === "string") {
17441
+ container = document.querySelector(params.container);
17442
+ } else {
17443
+ container = params.container;
17444
+ }
17445
+ }
17446
+ view.render(container);
17447
+ await loadFormData();
17448
+ return {
17449
+ destroy: () => view.destroy(),
17450
+ getFormData: () => view.getFormData(),
17451
+ setFormData: (data) => view.setFormData(data)
17452
+ };
17453
+ }
17454
+
16459
17455
  // src/components/premium-modals/settings/SettingsModalView.ts
16460
17456
  var SettingsModalView = class {
16461
17457
  container;
@@ -26716,6 +27712,9 @@ ${rangeText}`;
26716
27712
  exports.MyIODraggableCard = MyIODraggableCard;
26717
27713
  exports.MyIOSelectionStoreClass = MyIOSelectionStoreClass;
26718
27714
  exports.MyIOToast = MyIOToast;
27715
+ exports.POWER_LIMITS_DEVICE_TYPES = DEVICE_TYPES;
27716
+ exports.POWER_LIMITS_STATUS_CONFIG = STATUS_CONFIG;
27717
+ exports.POWER_LIMITS_TELEMETRY_TYPES = TELEMETRY_TYPES2;
26719
27718
  exports.addDetectionContext = addDetectionContext;
26720
27719
  exports.addNamespace = addNamespace;
26721
27720
  exports.aggregateByDay = aggregateByDay;
@@ -26813,6 +27812,7 @@ ${rangeText}`;
26813
27812
  exports.openDashboardPopupWaterTank = openDashboardPopupWaterTank;
26814
27813
  exports.openDemandModal = openDemandModal;
26815
27814
  exports.openGoalsPanel = openGoalsPanel;
27815
+ exports.openPowerLimitsSetupModal = openPowerLimitsSetupModal;
26816
27816
  exports.openRealTimeTelemetryModal = openRealTimeTelemetryModal;
26817
27817
  exports.openTemperatureComparisonModal = openTemperatureComparisonModal;
26818
27818
  exports.openTemperatureModal = openTemperatureModal;