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 +1089 -7
- package/dist/index.d.cts +165 -1
- package/dist/index.js +1088 -7
- package/dist/myio-js-library.umd.js +1088 -7
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
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: #
|
|
4782
|
-
--myio-brand-600: #
|
|
4783
|
-
--myio-accent: #
|
|
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:
|
|
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:
|
|
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 = "
|
|
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 = "
|
|
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">×</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,
|