myio-js-library 0.1.32 → 0.1.34

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
@@ -617,6 +617,7 @@ __export(index_exports, {
617
617
  openDashboardPopupAllReport: () => openDashboardPopupAllReport,
618
618
  openDashboardPopupEnergy: () => openDashboardPopupEnergy,
619
619
  openDashboardPopupReport: () => openDashboardPopupReport,
620
+ openDashboardPopupSettings: () => openDashboardPopupSettings,
620
621
  openDemandModal: () => openDemandModal,
621
622
  parseInputDateToDate: () => parseInputDateToDate,
622
623
  renderCardCompenteHeadOffice: () => renderCardCompenteHeadOffice,
@@ -4778,9 +4779,9 @@ function openDashboardPopupEnergy(params) {
4778
4779
  var CSS_TOKENS = `
4779
4780
  :root {
4780
4781
  /* Brand Colors */
4781
- --myio-brand-700: #4A148C;
4782
- --myio-brand-600: #5c307d;
4783
- --myio-accent: #5c307d;
4782
+ --myio-brand-700: #3e1a7d;
4783
+ --myio-brand-600: #2d1458;
4784
+ --myio-accent: #2d1458;
4784
4785
  --myio-danger: #d32f2f;
4785
4786
  --myio-success: #388E3C;
4786
4787
  --myio-warning: #f57c00;
@@ -4895,7 +4896,7 @@ var MODAL_STYLES = `
4895
4896
  display: flex;
4896
4897
  align-items: center;
4897
4898
  justify-content: space-between;
4898
- background: #4A148C;
4899
+ background: var(--myio-brand-700);
4899
4900
  color: white;
4900
4901
  border-radius: var(--myio-radius) var(--myio-radius) 0 0;
4901
4902
  }
@@ -6952,7 +6953,7 @@ var AllReportModal = class {
6952
6953
  <button id="export-btn" class="myio-btn myio-btn-secondary" disabled>
6953
6954
  Exportar CSV
6954
6955
  </button>
6955
- <button id="filter-btn" class="myio-btn myio-btn-secondary" style="background: #4A148C; color: white;">
6956
+ <button id="filter-btn" class="myio-btn myio-btn-secondary" style="background: var(--myio-brand-700); color: white;">
6956
6957
  \u{1F50D} Filtros & Ordena\xE7\xE3o
6957
6958
  </button>
6958
6959
  <div class="myio-form-group" style="margin-bottom: 0; margin-left: auto;">
@@ -7317,10 +7318,10 @@ var AllReportModal = class {
7317
7318
  const filterBtn = document.getElementById("filter-btn");
7318
7319
  if (filterBtn && selectedIds.length > 0 && selectedIds.length < this.data.length) {
7319
7320
  filterBtn.innerHTML = `\u{1F50D} Filtros & Ordena\xE7\xE3o (${selectedIds.length})`;
7320
- filterBtn.style.background = "#3A0E5C";
7321
+ filterBtn.style.background = "var(--myio-brand-600)";
7321
7322
  } else {
7322
7323
  filterBtn.innerHTML = "\u{1F50D} Filtros & Ordena\xE7\xE3o";
7323
- filterBtn.style.background = "#4A148C";
7324
+ filterBtn.style.background = "var(--myio-brand-700)";
7324
7325
  }
7325
7326
  }
7326
7327
  exportCSV() {
@@ -7497,6 +7498,1086 @@ function openDashboardPopup(params) {
7497
7498
  };
7498
7499
  }
7499
7500
 
7501
+ // src/components/premium-modals/settings/SettingsModalView.ts
7502
+ var SettingsModalView = class {
7503
+ container;
7504
+ modal;
7505
+ form;
7506
+ config;
7507
+ focusTrapElements = [];
7508
+ originalActiveElement = null;
7509
+ constructor(config) {
7510
+ this.config = config;
7511
+ this.createModal();
7512
+ this.attachEventListeners();
7513
+ }
7514
+ render(initialData) {
7515
+ this.originalActiveElement = document.activeElement;
7516
+ document.body.appendChild(this.container);
7517
+ this.populateForm(initialData);
7518
+ this.setupAccessibility();
7519
+ this.setupFocusTrap();
7520
+ this.applyTheme();
7521
+ }
7522
+ close() {
7523
+ this.teardownFocusTrap();
7524
+ if (this.originalActiveElement && "focus" in this.originalActiveElement) {
7525
+ this.originalActiveElement.focus();
7526
+ }
7527
+ if (this.container.parentNode) {
7528
+ this.container.parentNode.removeChild(this.container);
7529
+ }
7530
+ }
7531
+ showError(message) {
7532
+ const errorEl = this.modal.querySelector(".error-message");
7533
+ if (errorEl) {
7534
+ errorEl.textContent = message;
7535
+ errorEl.style.display = "block";
7536
+ errorEl.setAttribute("role", "alert");
7537
+ errorEl.setAttribute("aria-live", "polite");
7538
+ }
7539
+ }
7540
+ hideError() {
7541
+ const errorEl = this.modal.querySelector(".error-message");
7542
+ if (errorEl) {
7543
+ errorEl.style.display = "none";
7544
+ errorEl.removeAttribute("role");
7545
+ errorEl.removeAttribute("aria-live");
7546
+ }
7547
+ }
7548
+ showLoadingState(isLoading) {
7549
+ const saveBtn = this.modal.querySelector(".btn-save");
7550
+ const cancelBtn = this.modal.querySelector(".btn-cancel");
7551
+ const formInputs = this.modal.querySelectorAll("input, select, textarea");
7552
+ if (saveBtn) {
7553
+ saveBtn.disabled = isLoading;
7554
+ saveBtn.textContent = isLoading ? "Salvando..." : "Salvar";
7555
+ }
7556
+ if (cancelBtn) {
7557
+ cancelBtn.disabled = isLoading;
7558
+ }
7559
+ formInputs.forEach((input) => {
7560
+ input.disabled = isLoading;
7561
+ });
7562
+ }
7563
+ getFormData() {
7564
+ const formData = new FormData(this.form);
7565
+ const data = {};
7566
+ for (const [key, value] of formData.entries()) {
7567
+ if (typeof value === "string") {
7568
+ if (["maxDailyKwh", "maxNightKwh", "maxBusinessKwh"].includes(key)) {
7569
+ const num = parseFloat(value);
7570
+ if (!isNaN(num) && num >= 0) {
7571
+ data[key] = num;
7572
+ }
7573
+ } else if (value.trim()) {
7574
+ data[key] = value.trim();
7575
+ }
7576
+ }
7577
+ }
7578
+ return data;
7579
+ }
7580
+ createModal() {
7581
+ this.container = document.createElement("div");
7582
+ this.container.className = "myio-settings-modal-overlay";
7583
+ this.container.innerHTML = this.getModalHTML();
7584
+ this.modal = this.container.querySelector(".myio-settings-modal");
7585
+ this.form = this.modal.querySelector("form");
7586
+ }
7587
+ getModalHTML() {
7588
+ const width = typeof this.config.width === "number" ? `${this.config.width}px` : this.config.width;
7589
+ return `
7590
+ <div class="myio-settings-modal-overlay" role="dialog" aria-modal="true" aria-labelledby="modal-title">
7591
+ <div class="myio-settings-modal" style="width: ${width}">
7592
+ <div class="modal-header">
7593
+ <h3 id="modal-title">Configura\xE7\xF5es</h3>
7594
+ <button type="button" class="close-btn" aria-label="Fechar">&times;</button>
7595
+ </div>
7596
+ <div class="modal-body">
7597
+ <div class="error-message" style="display: none;" role="alert" aria-live="polite"></div>
7598
+ <form novalidate>
7599
+ ${this.getFormHTML()}
7600
+ </form>
7601
+ </div>
7602
+ <div class="modal-footer">
7603
+ <button type="button" class="btn-cancel">Fechar</button>
7604
+ <button type="submit" class="btn-save btn-primary">Salvar</button>
7605
+ </div>
7606
+ </div>
7607
+ </div>
7608
+ ${this.getModalCSS()}
7609
+ `;
7610
+ }
7611
+ getFormHTML() {
7612
+ return `
7613
+ <div class="form-columns">
7614
+ <!-- Left Column: Outback -->
7615
+ <div class="form-column">
7616
+ <div class="form-card">
7617
+ <h4 class="section-title">${this.config.deviceLabel || "Outback"}</h4>
7618
+
7619
+ <div class="form-group">
7620
+ <label for="label">Etiqueta</label>
7621
+ <input type="text" id="label" name="label" required maxlength="255">
7622
+ </div>
7623
+
7624
+ <div class="form-group">
7625
+ <label for="floor">Andar</label>
7626
+ <input type="text" id="floor" name="floor" maxlength="50">
7627
+ </div>
7628
+
7629
+ <div class="form-group">
7630
+ <label for="identifier">N\xFAmero da Loja</label>
7631
+ <input type="text" id="identifier" name="identifier" maxlength="20" readonly>
7632
+ </div>
7633
+ </div>
7634
+ </div>
7635
+
7636
+ <!-- Right Column: Energy Alarms -->
7637
+ <div class="form-column">
7638
+ <div class="form-card">
7639
+ <h4 class="section-title">Alarmes Energia - ${this.config.deviceLabel || "Outback"}</h4>
7640
+
7641
+ <div class="form-group">
7642
+ <label for="maxDailyKwh">Consumo M\xE1ximo Di\xE1rio (kWh)</label>
7643
+ <input type="number" id="maxDailyKwh" name="maxDailyKwh" min="0" step="0.1">
7644
+ </div>
7645
+
7646
+ <div class="form-group">
7647
+ <label for="maxNightKwh">Consumo M\xE1ximo na Madrugada (0h\u201306h)</label>
7648
+ <input type="number" id="maxNightKwh" name="maxNightKwh" min="0" step="0.1">
7649
+ </div>
7650
+
7651
+ <div class="form-group">
7652
+ <label for="maxBusinessKwh">Consumo M\xE1ximo Hor\xE1rio Comercial (09h\u201322h)</label>
7653
+ <input type="number" id="maxBusinessKwh" name="maxBusinessKwh" min="0" step="0.1">
7654
+ </div>
7655
+ </div>
7656
+ </div>
7657
+ </div>
7658
+ `;
7659
+ }
7660
+ getModalCSS() {
7661
+ return `
7662
+ <style>
7663
+ .myio-settings-modal-overlay {
7664
+ position: fixed;
7665
+ top: 0;
7666
+ left: 0;
7667
+ right: 0;
7668
+ bottom: 0;
7669
+ background: rgba(0, 0, 0, 0.5);
7670
+ display: flex;
7671
+ align-items: center;
7672
+ justify-content: center;
7673
+ z-index: 10000;
7674
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
7675
+ }
7676
+
7677
+ .myio-settings-modal {
7678
+ background: white;
7679
+ border-radius: 8px;
7680
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
7681
+ max-width: 95vw;
7682
+ max-height: 90vh;
7683
+ width: 1000px;
7684
+ overflow: hidden;
7685
+ display: flex;
7686
+ flex-direction: column;
7687
+ }
7688
+
7689
+ .modal-header {
7690
+ background: #3e1a7d;
7691
+ color: white;
7692
+ padding: 20px 24px;
7693
+ display: flex;
7694
+ justify-content: space-between;
7695
+ align-items: center;
7696
+ }
7697
+
7698
+ .modal-header h3 {
7699
+ margin: 0;
7700
+ font-size: 18px;
7701
+ font-weight: 600;
7702
+ color: white;
7703
+ }
7704
+
7705
+ .close-btn {
7706
+ background: none;
7707
+ border: none;
7708
+ font-size: 24px;
7709
+ cursor: pointer;
7710
+ color: white;
7711
+ padding: 0;
7712
+ width: 32px;
7713
+ height: 32px;
7714
+ display: flex;
7715
+ align-items: center;
7716
+ justify-content: center;
7717
+ border-radius: 4px;
7718
+ }
7719
+
7720
+ .close-btn:hover {
7721
+ background: rgba(255, 255, 255, 0.1);
7722
+ }
7723
+
7724
+ .modal-body {
7725
+ padding: 24px;
7726
+ overflow-y: auto;
7727
+ flex: 1;
7728
+ background: #f8f9fa;
7729
+ }
7730
+
7731
+ .error-message {
7732
+ background: #fee;
7733
+ border: 1px solid #fcc;
7734
+ color: #c33;
7735
+ padding: 12px;
7736
+ border-radius: 4px;
7737
+ margin-bottom: 16px;
7738
+ font-size: 14px;
7739
+ }
7740
+
7741
+ .form-columns {
7742
+ display: grid;
7743
+ grid-template-columns: 1fr 1fr;
7744
+ gap: 20px;
7745
+ }
7746
+
7747
+ .form-column {
7748
+ display: flex;
7749
+ flex-direction: column;
7750
+ }
7751
+
7752
+ .form-card {
7753
+ background: white;
7754
+ border-radius: 8px;
7755
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
7756
+ padding: 20px;
7757
+ height: fit-content;
7758
+ }
7759
+
7760
+ .section-title {
7761
+ margin: 0 0 20px 0;
7762
+ font-size: 16px;
7763
+ font-weight: 600;
7764
+ color: #3e1a7d;
7765
+ }
7766
+
7767
+ .form-group {
7768
+ display: flex;
7769
+ flex-direction: column;
7770
+ margin-bottom: 16px;
7771
+ }
7772
+
7773
+ .form-group:last-child {
7774
+ margin-bottom: 0;
7775
+ }
7776
+
7777
+ .form-group label {
7778
+ font-weight: 500;
7779
+ margin-bottom: 6px;
7780
+ color: #333;
7781
+ font-size: 14px;
7782
+ }
7783
+
7784
+ .form-group input {
7785
+ width: 100%;
7786
+ padding: 10px 12px;
7787
+ border: 1px solid #ddd;
7788
+ border-radius: 6px;
7789
+ font-size: 14px;
7790
+ transition: border-color 0.2s, box-shadow 0.2s;
7791
+ box-sizing: border-box;
7792
+ }
7793
+
7794
+ .form-group input:focus {
7795
+ outline: none;
7796
+ border-color: #3e1a7d;
7797
+ box-shadow: 0 0 0 2px rgba(62, 26, 125, 0.25);
7798
+ }
7799
+
7800
+ .form-group input:invalid {
7801
+ border-color: #dc3545;
7802
+ }
7803
+
7804
+ .form-group input[readonly] {
7805
+ background-color: #f8f9fa;
7806
+ color: #6c757d;
7807
+ cursor: not-allowed;
7808
+ }
7809
+
7810
+ .modal-footer {
7811
+ padding: 16px 24px;
7812
+ border-top: 1px solid #e0e0e0;
7813
+ display: flex;
7814
+ justify-content: flex-end;
7815
+ gap: 12px;
7816
+ background: white;
7817
+ }
7818
+
7819
+ .modal-footer button {
7820
+ padding: 10px 20px;
7821
+ border: none;
7822
+ border-radius: 6px;
7823
+ font-size: 14px;
7824
+ font-weight: 500;
7825
+ cursor: pointer;
7826
+ transition: all 0.2s;
7827
+ }
7828
+
7829
+ .btn-cancel {
7830
+ background: #6c757d;
7831
+ color: white;
7832
+ }
7833
+
7834
+ .btn-cancel:hover:not(:disabled) {
7835
+ background: #545b62;
7836
+ }
7837
+
7838
+ .btn-primary {
7839
+ background: #3e1a7d;
7840
+ color: white;
7841
+ }
7842
+
7843
+ .btn-primary:hover:not(:disabled) {
7844
+ background: #2d1458;
7845
+ }
7846
+
7847
+ .modal-footer button:disabled {
7848
+ opacity: 0.6;
7849
+ cursor: not-allowed;
7850
+ }
7851
+
7852
+ /* Responsive design */
7853
+ @media (max-width: 1700px) {
7854
+ .myio-settings-modal {
7855
+ width: 95vw !important;
7856
+ }
7857
+ }
7858
+
7859
+ @media (max-width: 1024px) {
7860
+ .myio-settings-modal {
7861
+ width: 90vw !important;
7862
+ }
7863
+
7864
+ .form-columns {
7865
+ gap: 16px;
7866
+ }
7867
+
7868
+ .form-card {
7869
+ padding: 16px;
7870
+ }
7871
+ }
7872
+
7873
+ @media (max-width: 768px) {
7874
+ .myio-settings-modal {
7875
+ width: 95vw !important;
7876
+ margin: 10px;
7877
+ }
7878
+
7879
+ .form-columns {
7880
+ grid-template-columns: 1fr;
7881
+ gap: 16px;
7882
+ }
7883
+
7884
+ .modal-header, .modal-body, .modal-footer {
7885
+ padding-left: 16px;
7886
+ padding-right: 16px;
7887
+ }
7888
+
7889
+ .form-card {
7890
+ padding: 16px;
7891
+ }
7892
+ }
7893
+
7894
+ /* Scrollbar styling for modal body */
7895
+ .modal-body::-webkit-scrollbar {
7896
+ width: 6px;
7897
+ }
7898
+
7899
+ .modal-body::-webkit-scrollbar-track {
7900
+ background: #f1f1f1;
7901
+ border-radius: 3px;
7902
+ }
7903
+
7904
+ .modal-body::-webkit-scrollbar-thumb {
7905
+ background: #c1c1c1;
7906
+ border-radius: 3px;
7907
+ }
7908
+
7909
+ .modal-body::-webkit-scrollbar-thumb:hover {
7910
+ background: #a8a8a8;
7911
+ }
7912
+ </style>
7913
+ `;
7914
+ }
7915
+ populateForm(data) {
7916
+ for (const [key, value] of Object.entries(data)) {
7917
+ const input = this.form.querySelector(`[name="${key}"]`);
7918
+ if (input && value !== void 0 && value !== null) {
7919
+ input.value = String(value);
7920
+ }
7921
+ }
7922
+ }
7923
+ setupAccessibility() {
7924
+ const firstInput = this.modal.querySelector("input");
7925
+ if (firstInput) {
7926
+ setTimeout(() => firstInput.focus(), 100);
7927
+ }
7928
+ this.modal.setAttribute("aria-labelledby", "modal-title");
7929
+ }
7930
+ setupFocusTrap() {
7931
+ this.focusTrapElements = Array.from(
7932
+ this.modal.querySelectorAll(
7933
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
7934
+ )
7935
+ );
7936
+ this.modal.addEventListener("keydown", this.handleKeyDown.bind(this));
7937
+ }
7938
+ teardownFocusTrap() {
7939
+ this.modal.removeEventListener("keydown", this.handleKeyDown.bind(this));
7940
+ }
7941
+ handleKeyDown(event) {
7942
+ if (event.key === "Escape" && this.config.closeOnBackdrop !== false) {
7943
+ event.preventDefault();
7944
+ this.config.onClose();
7945
+ return;
7946
+ }
7947
+ if (event.key === "Tab") {
7948
+ const firstElement = this.focusTrapElements[0];
7949
+ const lastElement = this.focusTrapElements[this.focusTrapElements.length - 1];
7950
+ if (event.shiftKey) {
7951
+ if (document.activeElement === firstElement) {
7952
+ event.preventDefault();
7953
+ lastElement.focus();
7954
+ }
7955
+ } else {
7956
+ if (document.activeElement === lastElement) {
7957
+ event.preventDefault();
7958
+ firstElement.focus();
7959
+ }
7960
+ }
7961
+ }
7962
+ }
7963
+ attachEventListeners() {
7964
+ this.form.addEventListener("submit", (event) => {
7965
+ event.preventDefault();
7966
+ this.hideError();
7967
+ const formData = this.getFormData();
7968
+ this.config.onSave(formData);
7969
+ });
7970
+ const closeBtn = this.modal.querySelector(".close-btn");
7971
+ if (closeBtn) {
7972
+ closeBtn.addEventListener("click", (event) => {
7973
+ event.preventDefault();
7974
+ event.stopPropagation();
7975
+ this.config.onClose();
7976
+ });
7977
+ }
7978
+ const cancelBtn = this.modal.querySelector(".btn-cancel");
7979
+ if (cancelBtn) {
7980
+ cancelBtn.addEventListener("click", (event) => {
7981
+ event.preventDefault();
7982
+ event.stopPropagation();
7983
+ this.config.onClose();
7984
+ });
7985
+ }
7986
+ this.container.addEventListener("click", (event) => {
7987
+ const target = event.target;
7988
+ if (target.classList.contains("myio-settings-modal-overlay") && this.config.closeOnBackdrop !== false) {
7989
+ this.config.onClose();
7990
+ }
7991
+ });
7992
+ this.form.addEventListener("input", this.handleInputValidation.bind(this));
7993
+ }
7994
+ handleInputValidation(event) {
7995
+ const input = event.target;
7996
+ input.classList.remove("is-invalid");
7997
+ if (input.name === "guid" && input.value) {
7998
+ const guidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7999
+ if (!guidPattern.test(input.value)) {
8000
+ input.classList.add("is-invalid");
8001
+ input.setCustomValidity("Invalid GUID format");
8002
+ } else {
8003
+ input.setCustomValidity("");
8004
+ }
8005
+ }
8006
+ if (input.type === "number" && input.value) {
8007
+ const num = parseFloat(input.value);
8008
+ if (isNaN(num) || num < 0) {
8009
+ input.classList.add("is-invalid");
8010
+ input.setCustomValidity("Must be a positive number");
8011
+ } else {
8012
+ input.setCustomValidity("");
8013
+ }
8014
+ }
8015
+ }
8016
+ applyTheme() {
8017
+ if (this.config.themeTokens) {
8018
+ const style = document.createElement("style");
8019
+ let css = "";
8020
+ for (const [property, value] of Object.entries(this.config.themeTokens)) {
8021
+ css += `--myio-${property}: ${value};
8022
+ `;
8023
+ }
8024
+ style.textContent = `.myio-settings-modal { ${css} }`;
8025
+ this.container.appendChild(style);
8026
+ }
8027
+ }
8028
+ getI18nText(key, defaultText) {
8029
+ return this.config.i18n?.t(key, defaultText) || defaultText;
8030
+ }
8031
+ };
8032
+
8033
+ // src/components/premium-modals/settings/SettingsPersister.ts
8034
+ var DefaultSettingsPersister = class {
8035
+ jwtToken;
8036
+ tbBaseUrl;
8037
+ constructor(jwtToken, apiConfig) {
8038
+ this.jwtToken = jwtToken;
8039
+ this.tbBaseUrl = apiConfig?.tbBaseUrl || window.location.origin;
8040
+ }
8041
+ async saveEntityLabel(deviceId, label) {
8042
+ try {
8043
+ const getRes = await fetch(`${this.tbBaseUrl}/api/device/${deviceId}`, {
8044
+ headers: { "X-Authorization": `Bearer ${this.jwtToken}` }
8045
+ });
8046
+ if (!getRes.ok) {
8047
+ throw this.createHttpError(getRes.status, await getRes.text().catch(() => ""));
8048
+ }
8049
+ const device = await getRes.json();
8050
+ const putRes = await fetch(`${this.tbBaseUrl}/api/device`, {
8051
+ method: "PUT",
8052
+ headers: {
8053
+ "X-Authorization": `Bearer ${this.jwtToken}`,
8054
+ "Content-Type": "application/json"
8055
+ },
8056
+ body: JSON.stringify({ ...device, label: this.sanitizeLabel(label) })
8057
+ });
8058
+ if (!putRes.ok) {
8059
+ throw this.createHttpError(putRes.status, await putRes.text().catch(() => ""));
8060
+ }
8061
+ return { ok: true };
8062
+ } catch (error) {
8063
+ console.error("[SettingsPersister] Entity label save failed:", error);
8064
+ return { ok: false, error: this.mapError(error) };
8065
+ }
8066
+ }
8067
+ async saveServerScopeAttributes(deviceId, attributes) {
8068
+ try {
8069
+ const namespacedAttrs = this.addNamespaceAndVersion(attributes);
8070
+ const res = await fetch(
8071
+ `${this.tbBaseUrl}/api/plugins/telemetry/DEVICE/${deviceId}/attributes/SERVER_SCOPE`,
8072
+ {
8073
+ method: "POST",
8074
+ headers: {
8075
+ "X-Authorization": `Bearer ${this.jwtToken}`,
8076
+ "Content-Type": "application/json"
8077
+ },
8078
+ body: JSON.stringify(namespacedAttrs)
8079
+ }
8080
+ );
8081
+ if (!res.ok) {
8082
+ throw this.createHttpError(res.status, await res.text().catch(() => ""));
8083
+ }
8084
+ return {
8085
+ ok: true,
8086
+ updatedKeys: Object.keys(namespacedAttrs)
8087
+ };
8088
+ } catch (error) {
8089
+ console.error("[SettingsPersister] Attributes save failed:", error);
8090
+ return { ok: false, error: this.mapError(error) };
8091
+ }
8092
+ }
8093
+ addNamespaceAndVersion(attributes) {
8094
+ const namespaced = {
8095
+ "myio.settings.energy.__version": 1
8096
+ };
8097
+ for (const [key, value] of Object.entries(attributes)) {
8098
+ if (key !== "label") {
8099
+ namespaced[`myio.settings.energy.${key}`] = value;
8100
+ }
8101
+ }
8102
+ return namespaced;
8103
+ }
8104
+ sanitizeLabel(label) {
8105
+ return label.trim().slice(0, 255).replace(/[\x00-\x1F\x7F]/g, "");
8106
+ }
8107
+ createHttpError(status, body) {
8108
+ const error = new Error(`HTTP ${status}: ${body}`);
8109
+ error.status = status;
8110
+ error.body = body;
8111
+ return error;
8112
+ }
8113
+ mapError(error) {
8114
+ const status = error.status;
8115
+ if (status === 400) {
8116
+ return {
8117
+ code: "VALIDATION_ERROR",
8118
+ message: "Invalid input data",
8119
+ userAction: "FIX_INPUT",
8120
+ cause: error
8121
+ };
8122
+ }
8123
+ if (status === 401) {
8124
+ return {
8125
+ code: "TOKEN_EXPIRED",
8126
+ message: "Authentication token has expired",
8127
+ userAction: "RE_AUTH",
8128
+ cause: error
8129
+ };
8130
+ }
8131
+ if (status === 403) {
8132
+ return {
8133
+ code: "AUTH_ERROR",
8134
+ message: "Insufficient permissions",
8135
+ userAction: "RE_AUTH",
8136
+ cause: error
8137
+ };
8138
+ }
8139
+ if (status === 404) {
8140
+ return {
8141
+ code: "NETWORK_ERROR",
8142
+ message: "Device not found",
8143
+ userAction: "CONTACT_ADMIN",
8144
+ cause: error
8145
+ };
8146
+ }
8147
+ if (status === 409) {
8148
+ return {
8149
+ code: "VALIDATION_ERROR",
8150
+ message: "Concurrent modification detected",
8151
+ userAction: "RETRY",
8152
+ cause: error
8153
+ };
8154
+ }
8155
+ if (status >= 500) {
8156
+ return {
8157
+ code: "NETWORK_ERROR",
8158
+ message: "Server error occurred",
8159
+ userAction: "RETRY",
8160
+ cause: error
8161
+ };
8162
+ }
8163
+ return {
8164
+ code: "UNKNOWN_ERROR",
8165
+ message: error.message || "Unknown error occurred",
8166
+ userAction: "CONTACT_ADMIN",
8167
+ cause: error
8168
+ };
8169
+ }
8170
+ };
8171
+
8172
+ // src/components/premium-modals/settings/SettingsFetcher.ts
8173
+ var DefaultSettingsFetcher = class {
8174
+ jwtToken;
8175
+ tbBaseUrl;
8176
+ constructor(jwtToken, apiConfig) {
8177
+ this.jwtToken = jwtToken;
8178
+ this.tbBaseUrl = apiConfig?.tbBaseUrl || window.location.origin;
8179
+ }
8180
+ async fetchCurrentSettings(deviceId, jwtToken, scope = "SERVER_SCOPE") {
8181
+ try {
8182
+ const [entityResult, attributesResult] = await Promise.allSettled([
8183
+ this.fetchDeviceEntity(deviceId),
8184
+ this.fetchDeviceAttributes(deviceId, scope)
8185
+ ]);
8186
+ const result = {};
8187
+ if (entityResult.status === "fulfilled") {
8188
+ result.entity = entityResult.value;
8189
+ } else {
8190
+ console.warn("[SettingsFetcher] Failed to fetch device entity:", entityResult.reason);
8191
+ }
8192
+ if (attributesResult.status === "fulfilled") {
8193
+ result.attributes = attributesResult.value;
8194
+ } else {
8195
+ console.warn("[SettingsFetcher] Failed to fetch device attributes:", attributesResult.reason);
8196
+ }
8197
+ return result;
8198
+ } catch (error) {
8199
+ console.error("[SettingsFetcher] Failed to fetch current settings:", error);
8200
+ return {};
8201
+ }
8202
+ }
8203
+ async fetchDeviceEntity(deviceId) {
8204
+ const response = await fetch(`${this.tbBaseUrl}/api/device/${deviceId}`, {
8205
+ headers: { "X-Authorization": `Bearer ${this.jwtToken}` }
8206
+ });
8207
+ if (!response.ok) {
8208
+ throw new Error(`Failed to fetch device entity: ${response.status} ${response.statusText}`);
8209
+ }
8210
+ const device = await response.json();
8211
+ return {
8212
+ label: device.label || device.name || ""
8213
+ };
8214
+ }
8215
+ async fetchDeviceAttributes(deviceId, scope) {
8216
+ const response = await fetch(
8217
+ `${this.tbBaseUrl}/api/plugins/telemetry/DEVICE/${deviceId}/values/attributes/${scope}`,
8218
+ {
8219
+ headers: { "X-Authorization": `Bearer ${this.jwtToken}` }
8220
+ }
8221
+ );
8222
+ if (!response.ok) {
8223
+ throw new Error(`Failed to fetch device attributes: ${response.status} ${response.statusText}`);
8224
+ }
8225
+ const attributesArray = await response.json();
8226
+ const attributes = {};
8227
+ const settingsNamespace = "myio.settings.energy.";
8228
+ for (const attr of attributesArray) {
8229
+ if (attr.key && attr.value !== void 0 && attr.value !== null && attr.value !== "") {
8230
+ if (attr.key.startsWith(settingsNamespace)) {
8231
+ const key = attr.key.replace(settingsNamespace, "");
8232
+ if (key !== "__version") {
8233
+ attributes[key] = attr.value;
8234
+ }
8235
+ } else if (attr.key === "floor") {
8236
+ attributes.floor = attr.value;
8237
+ } else if (attr.key === "identifier") {
8238
+ attributes.identifier = attr.value;
8239
+ }
8240
+ }
8241
+ }
8242
+ return attributes;
8243
+ }
8244
+ /**
8245
+ * Utility method to merge fetched settings with seed data
8246
+ */
8247
+ static mergeWithSeed(fetchedData, seedData) {
8248
+ const merged = {};
8249
+ if (seedData) {
8250
+ Object.assign(merged, seedData);
8251
+ }
8252
+ if (fetchedData.entity?.label) {
8253
+ merged.label = fetchedData.entity.label;
8254
+ }
8255
+ if (fetchedData.attributes) {
8256
+ Object.assign(merged, fetchedData.attributes);
8257
+ }
8258
+ return merged;
8259
+ }
8260
+ /**
8261
+ * Utility method to validate and sanitize fetched data
8262
+ */
8263
+ static sanitizeFetchedData(data) {
8264
+ const sanitized = {};
8265
+ const stringFields = ["label", "floor", "identifier"];
8266
+ for (const field of stringFields) {
8267
+ if (data[field] && typeof data[field] === "string") {
8268
+ sanitized[field] = data[field].trim();
8269
+ }
8270
+ }
8271
+ const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh"];
8272
+ for (const field of numericFields) {
8273
+ if (data[field] !== void 0 && data[field] !== null) {
8274
+ const num = Number(data[field]);
8275
+ if (!isNaN(num) && num >= 0) {
8276
+ sanitized[field] = num;
8277
+ }
8278
+ }
8279
+ }
8280
+ return sanitized;
8281
+ }
8282
+ };
8283
+
8284
+ // src/components/premium-modals/settings/SettingsController.ts
8285
+ var SettingsController = class {
8286
+ view;
8287
+ persister;
8288
+ fetcher;
8289
+ params;
8290
+ constructor(params) {
8291
+ this.params = params;
8292
+ this.validateParams();
8293
+ this.persister = params.persister || new DefaultSettingsPersister(params.jwtToken, params.api);
8294
+ this.fetcher = params.fetcher || new DefaultSettingsFetcher(params.jwtToken, params.api);
8295
+ this.view = new SettingsModalView({
8296
+ title: params.ui?.title || `Settings - ${params.label || params.deviceId}`,
8297
+ width: params.ui?.width || 600,
8298
+ theme: "light",
8299
+ // Default theme
8300
+ closeOnBackdrop: params.ui?.closeOnBackdrop !== false,
8301
+ themeTokens: params.ui?.themeTokens,
8302
+ i18n: params.ui?.i18n,
8303
+ deviceLabel: params.label,
8304
+ // Pass the device label for dynamic section titles
8305
+ onSave: this.handleSave.bind(this),
8306
+ onClose: this.handleClose.bind(this)
8307
+ });
8308
+ }
8309
+ async show() {
8310
+ console.info("[SettingsModal] Opening modal", {
8311
+ deviceId: this.params.deviceId,
8312
+ ingestionId: this.params.ingestionId
8313
+ });
8314
+ this.emitEvent("modal_opened");
8315
+ let initialData = this.params.seed || {};
8316
+ if (!this.params.seed) {
8317
+ try {
8318
+ const fetchedData = await this.fetcher.fetchCurrentSettings(
8319
+ this.params.deviceId,
8320
+ this.params.jwtToken,
8321
+ this.params.scope || "SERVER_SCOPE"
8322
+ );
8323
+ initialData = DefaultSettingsFetcher.mergeWithSeed(fetchedData, this.params.seed);
8324
+ initialData = DefaultSettingsFetcher.sanitizeFetchedData(initialData);
8325
+ } catch (error) {
8326
+ console.warn("[SettingsModal] Failed to fetch current settings:", error);
8327
+ if (this.params.onError) {
8328
+ this.params.onError({
8329
+ code: "NETWORK_ERROR",
8330
+ message: "Failed to load current settings",
8331
+ cause: error
8332
+ });
8333
+ }
8334
+ }
8335
+ }
8336
+ this.view.render(initialData);
8337
+ }
8338
+ validateParams() {
8339
+ if (!this.params.jwtToken) {
8340
+ throw new Error("jwtToken is required for settings persistence");
8341
+ }
8342
+ if (!this.params.deviceId) {
8343
+ throw new Error("deviceId is required");
8344
+ }
8345
+ if (!this.params.ingestionId) {
8346
+ console.warn("[SettingsModal] ingestionId not provided - using deviceId for display");
8347
+ }
8348
+ }
8349
+ async handleSave(formData) {
8350
+ console.info("[SettingsModal] Save initiated", { deviceId: this.params.deviceId, formData });
8351
+ this.emitEvent("save_started", { formData });
8352
+ this.view.showLoadingState(true);
8353
+ try {
8354
+ const result = await this.saveSettings(formData);
8355
+ if (result.ok) {
8356
+ console.info("[SettingsModal] Settings saved successfully", result);
8357
+ this.emitEvent("save_completed", { result });
8358
+ if (this.params.onSaved) {
8359
+ this.params.onSaved(result);
8360
+ }
8361
+ setTimeout(() => {
8362
+ this.view.close();
8363
+ }, 500);
8364
+ } else {
8365
+ console.error("[SettingsModal] Save failed:", result);
8366
+ this.emitEvent("save_failed", { result });
8367
+ const errorMessage = this.getErrorMessage(result);
8368
+ this.view.showError(errorMessage);
8369
+ if (this.params.onError) {
8370
+ this.params.onError({
8371
+ code: "VALIDATION_ERROR",
8372
+ message: errorMessage,
8373
+ cause: result
8374
+ });
8375
+ }
8376
+ }
8377
+ } catch (error) {
8378
+ console.error("[SettingsModal] Save error:", error);
8379
+ this.emitEvent("save_failed", { error: error.message });
8380
+ const errorMessage = "Network error occurred while saving settings";
8381
+ this.view.showError(errorMessage);
8382
+ if (this.params.onError) {
8383
+ this.params.onError({
8384
+ code: "NETWORK_ERROR",
8385
+ message: errorMessage,
8386
+ cause: error
8387
+ });
8388
+ }
8389
+ } finally {
8390
+ this.view.showLoadingState(false);
8391
+ }
8392
+ }
8393
+ async saveSettings(formData) {
8394
+ const result = {
8395
+ ok: true,
8396
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8397
+ };
8398
+ if (formData.label) {
8399
+ try {
8400
+ const labelResult = await this.persister.saveEntityLabel(
8401
+ this.params.deviceId,
8402
+ formData.label
8403
+ );
8404
+ result.entity = {
8405
+ ok: labelResult.ok,
8406
+ updated: labelResult.ok ? ["label"] : void 0,
8407
+ error: labelResult.error ? {
8408
+ code: labelResult.error.code,
8409
+ message: labelResult.error.message,
8410
+ cause: labelResult.error.cause
8411
+ } : void 0
8412
+ };
8413
+ if (!labelResult.ok) {
8414
+ result.ok = false;
8415
+ }
8416
+ } catch (error) {
8417
+ result.entity = {
8418
+ ok: false,
8419
+ error: {
8420
+ code: "UNKNOWN_ERROR",
8421
+ message: error.message || "Failed to save device label",
8422
+ cause: error
8423
+ }
8424
+ };
8425
+ result.ok = false;
8426
+ }
8427
+ }
8428
+ const attributes = this.extractAttributes(formData);
8429
+ if (Object.keys(attributes).length > 0) {
8430
+ try {
8431
+ const attributesResult = await this.persister.saveServerScopeAttributes(
8432
+ this.params.deviceId,
8433
+ attributes
8434
+ );
8435
+ result.serverScope = {
8436
+ ok: attributesResult.ok,
8437
+ updatedKeys: attributesResult.updatedKeys,
8438
+ error: attributesResult.error ? {
8439
+ code: attributesResult.error.code,
8440
+ message: attributesResult.error.message,
8441
+ cause: attributesResult.error.cause
8442
+ } : void 0
8443
+ };
8444
+ if (!attributesResult.ok) {
8445
+ result.ok = false;
8446
+ }
8447
+ } catch (error) {
8448
+ result.serverScope = {
8449
+ ok: false,
8450
+ error: {
8451
+ code: "UNKNOWN_ERROR",
8452
+ message: error.message || "Failed to save device attributes",
8453
+ cause: error
8454
+ }
8455
+ };
8456
+ result.ok = false;
8457
+ }
8458
+ }
8459
+ return result;
8460
+ }
8461
+ extractAttributes(formData) {
8462
+ const attributes = {};
8463
+ for (const [key, value] of Object.entries(formData)) {
8464
+ if (key !== "label" && value !== void 0 && value !== null && value !== "") {
8465
+ attributes[key] = value;
8466
+ }
8467
+ }
8468
+ return attributes;
8469
+ }
8470
+ getErrorMessage(result) {
8471
+ const errors = [];
8472
+ if (result.entity?.error) {
8473
+ errors.push(`Device label: ${result.entity.error.message}`);
8474
+ }
8475
+ if (result.serverScope?.error) {
8476
+ errors.push(`Settings: ${result.serverScope.error.message}`);
8477
+ }
8478
+ return errors.length > 0 ? errors.join("; ") : "Failed to save settings";
8479
+ }
8480
+ handleClose() {
8481
+ console.info("[SettingsModal] Modal closed");
8482
+ this.emitEvent("modal_closed");
8483
+ if (this.params.onClose) {
8484
+ this.params.onClose();
8485
+ }
8486
+ }
8487
+ emitEvent(type, data) {
8488
+ if (this.params.onEvent) {
8489
+ const event = {
8490
+ type,
8491
+ deviceId: this.params.deviceId,
8492
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8493
+ data
8494
+ };
8495
+ try {
8496
+ this.params.onEvent(event);
8497
+ } catch (error) {
8498
+ console.warn("[SettingsModal] Event handler error:", error);
8499
+ }
8500
+ }
8501
+ }
8502
+ /**
8503
+ * Utility method to validate form data before save
8504
+ */
8505
+ validateFormData(formData) {
8506
+ const errors = [];
8507
+ if (formData.guid) {
8508
+ const guidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8509
+ if (!guidPattern.test(formData.guid)) {
8510
+ errors.push("GUID must be in valid UUID format");
8511
+ }
8512
+ }
8513
+ const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh"];
8514
+ for (const field of numericFields) {
8515
+ if (formData[field] !== void 0) {
8516
+ const num = Number(formData[field]);
8517
+ if (isNaN(num) || num < 0) {
8518
+ errors.push(`${field} must be a positive number`);
8519
+ }
8520
+ }
8521
+ }
8522
+ if (formData.label && formData.label.length > 255) {
8523
+ errors.push("Device label must be 255 characters or less");
8524
+ }
8525
+ if (formData.floor && formData.floor.length > 50) {
8526
+ errors.push("Floor must be 50 characters or less");
8527
+ }
8528
+ if (formData.storeNumber && formData.storeNumber.length > 20) {
8529
+ errors.push("Store number must be 20 characters or less");
8530
+ }
8531
+ return {
8532
+ valid: errors.length === 0,
8533
+ errors
8534
+ };
8535
+ }
8536
+ /**
8537
+ * Public method to programmatically close the modal
8538
+ */
8539
+ closeModal() {
8540
+ this.view.close();
8541
+ }
8542
+ /**
8543
+ * Public method to get current form data
8544
+ */
8545
+ getCurrentFormData() {
8546
+ return this.view.getFormData();
8547
+ }
8548
+ };
8549
+
8550
+ // src/components/premium-modals/settings/openDashboardPopupSettings.ts
8551
+ async function openDashboardPopupSettings(params) {
8552
+ if (!params.jwtToken) {
8553
+ throw new Error("jwtToken is required for settings persistence");
8554
+ }
8555
+ if (!params.deviceId) {
8556
+ throw new Error("deviceId is required");
8557
+ }
8558
+ console.info("[openDashboardPopupSettings] Initializing settings modal", {
8559
+ deviceId: params.deviceId,
8560
+ ingestionId: params.ingestionId,
8561
+ hasJwtToken: !!params.jwtToken,
8562
+ hasSeedData: !!params.seed,
8563
+ apiConfig: params.api ? Object.keys(params.api) : void 0
8564
+ });
8565
+ const controller = new SettingsController(params);
8566
+ try {
8567
+ await controller.show();
8568
+ } catch (error) {
8569
+ console.error("[openDashboardPopupSettings] Error:", error);
8570
+ if (params.onError) {
8571
+ params.onError({
8572
+ code: "UNKNOWN_ERROR",
8573
+ message: error.message || "Unknown error occurred while opening settings modal",
8574
+ cause: error
8575
+ });
8576
+ }
8577
+ throw error;
8578
+ }
8579
+ }
8580
+
7500
8581
  // src/components/createDateRangePicker.ts
7501
8582
  async function createDateRangePicker2(input, options = {}) {
7502
8583
  const defaultOptions = {
@@ -8722,6 +9803,7 @@ async function openDemandModal(params) {
8722
9803
  openDashboardPopupAllReport,
8723
9804
  openDashboardPopupEnergy,
8724
9805
  openDashboardPopupReport,
9806
+ openDashboardPopupSettings,
8725
9807
  openDemandModal,
8726
9808
  parseInputDateToDate,
8727
9809
  renderCardCompenteHeadOffice,