myio-js-library 0.1.169 → 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.
@@ -1324,7 +1324,8 @@
1324
1324
  FAILURE: "failure",
1325
1325
  MAINTENANCE: "maintenance",
1326
1326
  NO_INFO: "no_info",
1327
- NOT_INSTALLED: "not_installed"
1327
+ NOT_INSTALLED: "not_installed",
1328
+ OFFLINE: "offline"
1328
1329
  };
1329
1330
  var ConnectionStatusType = {
1330
1331
  CONNECTED: "connected",
@@ -1333,39 +1334,42 @@
1333
1334
  var deviceStatusIcons = {
1334
1335
  [DeviceStatusType.POWER_ON]: "\u26A1",
1335
1336
  [DeviceStatusType.STANDBY]: "\u{1F50C}",
1336
- [DeviceStatusType.POWER_OFF]: "\u{1F534}",
1337
+ [DeviceStatusType.POWER_OFF]: "\u26AB",
1337
1338
  [DeviceStatusType.WARNING]: "\u26A0\uFE0F",
1338
1339
  [DeviceStatusType.FAILURE]: "\u{1F6A8}",
1339
1340
  [DeviceStatusType.MAINTENANCE]: "\u{1F6E0}\uFE0F",
1340
1341
  [DeviceStatusType.NO_INFO]: "\u2753\uFE0F",
1341
- [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}"
1342
+ [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}",
1343
+ [DeviceStatusType.OFFLINE]: "\u{1F534}"
1342
1344
  };
1343
1345
  var waterDeviceStatusIcons = {
1344
1346
  [DeviceStatusType.POWER_ON]: "\u{1F4A7}",
1345
1347
  [DeviceStatusType.STANDBY]: "\u{1F6B0}",
1346
- [DeviceStatusType.POWER_OFF]: "\u{1F534}",
1348
+ [DeviceStatusType.POWER_OFF]: "\u26AB",
1347
1349
  [DeviceStatusType.WARNING]: "\u26A0\uFE0F",
1348
1350
  [DeviceStatusType.FAILURE]: "\u{1F6A8}",
1349
1351
  [DeviceStatusType.MAINTENANCE]: "\u{1F6E0}\uFE0F",
1350
1352
  [DeviceStatusType.NO_INFO]: "\u2753\uFE0F",
1351
- [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}"
1353
+ [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}",
1354
+ [DeviceStatusType.OFFLINE]: "\u{1F534}"
1352
1355
  };
1353
1356
  var temperatureDeviceStatusIcons = {
1354
1357
  [DeviceStatusType.POWER_ON]: "\u{1F321}\uFE0F",
1355
1358
  [DeviceStatusType.STANDBY]: "\u{1F321}\uFE0F",
1356
- [DeviceStatusType.POWER_OFF]: "\u{1F534}",
1359
+ [DeviceStatusType.POWER_OFF]: "\u26AB",
1357
1360
  [DeviceStatusType.WARNING]: "\u26A0\uFE0F",
1358
1361
  [DeviceStatusType.FAILURE]: "\u{1F6A8}",
1359
1362
  [DeviceStatusType.MAINTENANCE]: "\u{1F6E0}\uFE0F",
1360
1363
  [DeviceStatusType.NO_INFO]: "\u2753\uFE0F",
1361
- [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}"
1364
+ [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}",
1365
+ [DeviceStatusType.OFFLINE]: "\u{1F534}"
1362
1366
  };
1363
1367
  var connectionStatusIcons = {
1364
1368
  [ConnectionStatusType.CONNECTED]: "\u{1F7E2}",
1365
1369
  [ConnectionStatusType.OFFLINE]: "\u{1F6AB}"
1366
1370
  };
1367
1371
  function mapDeviceToConnectionStatus(deviceStatus) {
1368
- if (deviceStatus === DeviceStatusType.NO_INFO) {
1372
+ if (deviceStatus === DeviceStatusType.NO_INFO || deviceStatus === DeviceStatusType.OFFLINE) {
1369
1373
  return ConnectionStatusType.OFFLINE;
1370
1374
  }
1371
1375
  return ConnectionStatusType.CONNECTED;
@@ -1389,15 +1393,16 @@
1389
1393
  [DeviceStatusType.FAILURE]: "fail",
1390
1394
  [DeviceStatusType.MAINTENANCE]: "alert",
1391
1395
  [DeviceStatusType.NO_INFO]: "unknown",
1392
- [DeviceStatusType.NOT_INSTALLED]: "not_installed"
1396
+ [DeviceStatusType.NOT_INSTALLED]: "not_installed",
1397
+ [DeviceStatusType.OFFLINE]: "offline"
1393
1398
  };
1394
1399
  return statusMap[deviceStatus] || "unknown";
1395
1400
  }
1396
1401
  function shouldFlashIcon(deviceStatus) {
1397
- return deviceStatus === DeviceStatusType.POWER_OFF || deviceStatus === DeviceStatusType.WARNING || deviceStatus === DeviceStatusType.FAILURE || deviceStatus === DeviceStatusType.MAINTENANCE;
1402
+ return deviceStatus === DeviceStatusType.POWER_OFF || deviceStatus === DeviceStatusType.WARNING || deviceStatus === DeviceStatusType.FAILURE || deviceStatus === DeviceStatusType.MAINTENANCE || deviceStatus === DeviceStatusType.OFFLINE;
1398
1403
  }
1399
1404
  function isDeviceOffline(deviceStatus) {
1400
- return deviceStatus === DeviceStatusType.NO_INFO;
1405
+ return deviceStatus === DeviceStatusType.NO_INFO || deviceStatus === DeviceStatusType.OFFLINE;
1401
1406
  }
1402
1407
  function getDeviceStatusIcon(deviceStatus, deviceType = null) {
1403
1408
  const normalizedType = deviceType?.toUpperCase() || "";
@@ -3953,15 +3958,30 @@
3953
3958
  --myio-chip-alert-fg: #b45309;
3954
3959
  --myio-border-alert: rgba(245, 158, 11, 0.5);
3955
3960
 
3956
- /* Status colors - Failure (red) */
3961
+ /* Status colors - Failure (dark red) */
3957
3962
  --myio-chip-failure-bg: #fee2e2;
3958
3963
  --myio-chip-failure-fg: #b91c1c;
3959
- --myio-border-failure: rgba(239, 68, 68, 0.5);
3964
+ --myio-border-failure: rgba(153, 27, 27, 0.6);
3965
+
3966
+ /* Status colors - Power Off (light red) */
3967
+ --myio-chip-power-off-bg: #fecaca;
3968
+ --myio-chip-power-off-fg: #dc2626;
3969
+ --myio-border-power-off: rgba(239, 68, 68, 0.5);
3970
+
3971
+ /* Status colors - Offline (dark gray) */
3972
+ --myio-chip-offline-bg: #e2e8f0;
3973
+ --myio-chip-offline-fg: #475569;
3974
+ --myio-border-offline: rgba(71, 85, 105, 0.6);
3975
+
3976
+ /* Status colors - No Info (dark orange) */
3977
+ --myio-chip-no-info-bg: #fed7aa;
3978
+ --myio-chip-no-info-fg: #c2410c;
3979
+ --myio-border-no-info: rgba(194, 65, 12, 0.5);
3960
3980
 
3961
- /* Status colors - Offline (gray) */
3962
- --myio-chip-offline-bg: #f1f5f9;
3963
- --myio-chip-offline-fg: #64748b;
3964
- --myio-border-offline: rgba(100, 116, 139, 0.4);
3981
+ /* Status colors - Not Installed (purple) */
3982
+ --myio-chip-not-installed-bg: #e9d5ff;
3983
+ --myio-chip-not-installed-fg: #7c3aed;
3984
+ --myio-border-not-installed: rgba(124, 58, 237, 0.5);
3965
3985
 
3966
3986
  --myio-text-1: #0f172a;
3967
3987
  --myio-text-2: #4b5563;
@@ -4017,26 +4037,55 @@
4017
4037
  }
4018
4038
 
4019
4039
  /* Card border states based on device status */
4020
- .myio-ho-card.is-ok {
4040
+
4041
+ /* POWER_ON - Blue */
4042
+ .myio-ho-card.is-power-on {
4021
4043
  border-color: var(--myio-border-ok);
4022
4044
  box-shadow: 0 0 0 2px var(--myio-border-ok), var(--myio-card-shadow);
4023
4045
  }
4024
4046
 
4047
+ /* STANDBY - Green */
4025
4048
  .myio-ho-card.is-standby {
4026
4049
  border-color: var(--myio-border-standby);
4027
4050
  box-shadow: 0 0 0 2px var(--myio-border-standby), var(--myio-card-shadow);
4028
4051
  }
4029
4052
 
4030
- .myio-ho-card.is-alert {
4053
+ /* WARNING - Yellow */
4054
+ .myio-ho-card.is-warning {
4055
+ border-color: var(--myio-border-alert);
4056
+ box-shadow: 0 0 0 2px var(--myio-border-alert), var(--myio-card-shadow);
4057
+ }
4058
+
4059
+ /* MAINTENANCE - Yellow */
4060
+ .myio-ho-card.is-maintenance {
4031
4061
  border-color: var(--myio-border-alert);
4032
4062
  box-shadow: 0 0 0 2px var(--myio-border-alert), var(--myio-card-shadow);
4033
4063
  }
4034
4064
 
4065
+ /* FAILURE - Dark Red */
4035
4066
  .myio-ho-card.is-failure {
4036
4067
  border-color: var(--myio-border-failure);
4037
4068
  box-shadow: 0 0 0 2px var(--myio-border-failure), var(--myio-card-shadow);
4038
4069
  }
4039
4070
 
4071
+ /* POWER_OFF - Light Red */
4072
+ .myio-ho-card.is-power-off {
4073
+ border-color: var(--myio-border-power-off);
4074
+ box-shadow: 0 0 0 2px var(--myio-border-power-off), var(--myio-card-shadow);
4075
+ }
4076
+
4077
+ /* NO_INFO - Dark Orange */
4078
+ .myio-ho-card.is-no-info {
4079
+ border-color: var(--myio-border-no-info);
4080
+ box-shadow: 0 0 0 2px var(--myio-border-no-info), var(--myio-card-shadow);
4081
+ }
4082
+
4083
+ /* NOT_INSTALLED - Purple */
4084
+ .myio-ho-card.is-not-installed {
4085
+ border-color: var(--myio-border-not-installed);
4086
+ box-shadow: 0 0 0 2px var(--myio-border-not-installed), var(--myio-card-shadow);
4087
+ }
4088
+
4040
4089
  /* Header section */
4041
4090
  .myio-ho-card__header {
4042
4091
  display: flex;
@@ -4054,7 +4103,9 @@
4054
4103
  margin-top: 2px;
4055
4104
  }
4056
4105
 
4057
- .myio-ho-card.is-alert .myio-ho-card__icon {
4106
+ /* Icon colors based on status */
4107
+ .myio-ho-card.is-warning .myio-ho-card__icon,
4108
+ .myio-ho-card.is-maintenance .myio-ho-card__icon {
4058
4109
  color: var(--myio-chip-alert-fg);
4059
4110
  }
4060
4111
 
@@ -4062,14 +4113,28 @@
4062
4113
  color: var(--myio-chip-failure-fg);
4063
4114
  }
4064
4115
 
4116
+ .myio-ho-card.is-power-off .myio-ho-card__icon {
4117
+ color: var(--myio-chip-power-off-fg);
4118
+ }
4119
+
4120
+ .myio-ho-card.is-offline .myio-ho-card__icon {
4121
+ color: var(--myio-chip-offline-fg);
4122
+ }
4123
+
4124
+ .myio-ho-card.is-no-info .myio-ho-card__icon {
4125
+ color: var(--myio-chip-no-info-fg);
4126
+ }
4127
+
4128
+ .myio-ho-card.is-not-installed .myio-ho-card__icon {
4129
+ color: var(--myio-chip-not-installed-fg);
4130
+ }
4131
+
4065
4132
  .myio-ho-card__title {
4066
4133
  flex: 1;
4067
4134
  min-width: 0;
4068
4135
  }
4069
4136
 
4070
- /* Adicione estas duas novas regras ao seu CSS_STRING */
4071
-
4072
- /* Estado Offline - borda cinza */
4137
+ /* OFFLINE - Dark Gray */
4073
4138
  .myio-ho-card.is-offline {
4074
4139
  border-color: var(--myio-border-offline);
4075
4140
  box-shadow: 0 0 0 2px var(--myio-border-offline), var(--myio-card-shadow);
@@ -4464,6 +4529,37 @@
4464
4529
  color: var(--myio-chip-offline-fg);
4465
4530
  }
4466
4531
 
4532
+ /* New chip classes aligned with getCardStateClass */
4533
+ .chip--power-on {
4534
+ background: var(--myio-chip-ok-bg);
4535
+ color: var(--myio-chip-ok-fg);
4536
+ }
4537
+
4538
+ .chip--warning {
4539
+ background: var(--myio-chip-alert-bg);
4540
+ color: var(--myio-chip-alert-fg);
4541
+ }
4542
+
4543
+ .chip--maintenance {
4544
+ background: var(--myio-chip-alert-bg);
4545
+ color: var(--myio-chip-alert-fg);
4546
+ }
4547
+
4548
+ .chip--power-off {
4549
+ background: var(--myio-chip-power-off-bg);
4550
+ color: var(--myio-chip-power-off-fg);
4551
+ }
4552
+
4553
+ .chip--no-info {
4554
+ background: var(--myio-chip-no-info-bg);
4555
+ color: var(--myio-chip-no-info-fg);
4556
+ }
4557
+
4558
+ .chip--not-installed {
4559
+ background: var(--myio-chip-not-installed-bg);
4560
+ color: var(--myio-chip-not-installed-fg);
4561
+ }
4562
+
4467
4563
  /* Status indicator dot for power metric */
4468
4564
  .status-dot {
4469
4565
  width: 8px;
@@ -4497,6 +4593,31 @@
4497
4593
  background: var(--myio-muted);
4498
4594
  }
4499
4595
 
4596
+ /* New dot classes aligned with getCardStateClass */
4597
+ .status-dot.dot--power-on {
4598
+ background: var(--myio-chip-ok-fg);
4599
+ }
4600
+
4601
+ .status-dot.dot--warning {
4602
+ background: var(--myio-chip-alert-fg);
4603
+ }
4604
+
4605
+ .status-dot.dot--maintenance {
4606
+ background: var(--myio-chip-alert-fg);
4607
+ }
4608
+
4609
+ .status-dot.dot--power-off {
4610
+ background: var(--myio-chip-power-off-fg);
4611
+ }
4612
+
4613
+ .status-dot.dot--no-info {
4614
+ background: var(--myio-chip-no-info-fg);
4615
+ }
4616
+
4617
+ .status-dot.dot--not-installed {
4618
+ background: var(--myio-chip-not-installed-fg);
4619
+ }
4620
+
4500
4621
  /* Primary metric */
4501
4622
  .myio-ho-card__primary {
4502
4623
  margin-bottom: 14px;
@@ -4679,6 +4800,215 @@
4679
4800
  transition: none;
4680
4801
  }
4681
4802
  }
4803
+
4804
+ /* ============================================
4805
+ DEBUG TOOLTIP STYLES (Premium)
4806
+ ============================================ */
4807
+
4808
+ .has-debug-tooltip {
4809
+ cursor: help !important;
4810
+ }
4811
+
4812
+ .has-debug-tooltip::after {
4813
+ content: '\u{1F41B}';
4814
+ position: absolute;
4815
+ top: -4px;
4816
+ right: -4px;
4817
+ font-size: 10px;
4818
+ z-index: 1;
4819
+ }
4820
+
4821
+ .debug-tooltip-container {
4822
+ position: absolute;
4823
+ bottom: calc(100% + 8px);
4824
+ left: 50%;
4825
+ transform: translateX(-50%);
4826
+ z-index: 10000;
4827
+ pointer-events: none;
4828
+ opacity: 0;
4829
+ transition: opacity 0.2s ease, transform 0.2s ease;
4830
+ }
4831
+
4832
+ .has-debug-tooltip:hover .debug-tooltip-container {
4833
+ opacity: 1;
4834
+ pointer-events: auto;
4835
+ }
4836
+
4837
+ .debug-tooltip {
4838
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
4839
+ border: 1px solid rgba(99, 102, 241, 0.3);
4840
+ border-radius: 12px;
4841
+ box-shadow:
4842
+ 0 20px 40px rgba(0, 0, 0, 0.4),
4843
+ 0 0 0 1px rgba(255, 255, 255, 0.05),
4844
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
4845
+ min-width: 340px;
4846
+ max-width: 420px;
4847
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4848
+ font-size: 12px;
4849
+ color: #e2e8f0;
4850
+ overflow: hidden;
4851
+ }
4852
+
4853
+ .debug-tooltip__header {
4854
+ display: flex;
4855
+ align-items: center;
4856
+ gap: 8px;
4857
+ padding: 12px 16px;
4858
+ background: linear-gradient(90deg, rgba(99, 102, 241, 0.2) 0%, rgba(139, 92, 246, 0.1) 100%);
4859
+ border-bottom: 1px solid rgba(99, 102, 241, 0.2);
4860
+ }
4861
+
4862
+ .debug-tooltip__icon {
4863
+ font-size: 16px;
4864
+ }
4865
+
4866
+ .debug-tooltip__title {
4867
+ font-weight: 700;
4868
+ font-size: 13px;
4869
+ color: #f1f5f9;
4870
+ letter-spacing: 0.5px;
4871
+ text-transform: uppercase;
4872
+ }
4873
+
4874
+ .debug-tooltip__content {
4875
+ padding: 12px 16px;
4876
+ max-height: 400px;
4877
+ overflow-y: auto;
4878
+ }
4879
+
4880
+ .debug-tooltip__section {
4881
+ margin-bottom: 14px;
4882
+ padding-bottom: 12px;
4883
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
4884
+ }
4885
+
4886
+ .debug-tooltip__section:last-child {
4887
+ margin-bottom: 0;
4888
+ padding-bottom: 0;
4889
+ border-bottom: none;
4890
+ }
4891
+
4892
+ .debug-tooltip__section-title {
4893
+ font-size: 11px;
4894
+ font-weight: 600;
4895
+ color: #94a3b8;
4896
+ text-transform: uppercase;
4897
+ letter-spacing: 0.8px;
4898
+ margin-bottom: 10px;
4899
+ display: flex;
4900
+ align-items: center;
4901
+ gap: 6px;
4902
+ }
4903
+
4904
+ .debug-tooltip__row {
4905
+ display: flex;
4906
+ justify-content: space-between;
4907
+ align-items: flex-start;
4908
+ padding: 4px 0;
4909
+ gap: 12px;
4910
+ }
4911
+
4912
+ .debug-tooltip__row--full {
4913
+ flex-direction: column;
4914
+ gap: 4px;
4915
+ }
4916
+
4917
+ .debug-tooltip__label {
4918
+ color: #94a3b8;
4919
+ font-size: 11px;
4920
+ flex-shrink: 0;
4921
+ }
4922
+
4923
+ .debug-tooltip__value {
4924
+ color: #f1f5f9;
4925
+ font-weight: 500;
4926
+ text-align: right;
4927
+ word-break: break-all;
4928
+ }
4929
+
4930
+ .debug-tooltip__row--full .debug-tooltip__value {
4931
+ text-align: left;
4932
+ background: rgba(0, 0, 0, 0.3);
4933
+ padding: 6px 10px;
4934
+ border-radius: 6px;
4935
+ font-size: 11px;
4936
+ width: 100%;
4937
+ box-sizing: border-box;
4938
+ }
4939
+
4940
+ .debug-tooltip__value--mono {
4941
+ font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
4942
+ font-size: 11px;
4943
+ background: rgba(0, 0, 0, 0.3);
4944
+ padding: 2px 6px;
4945
+ border-radius: 4px;
4946
+ }
4947
+
4948
+ .debug-tooltip__value--highlight {
4949
+ color: #a5b4fc;
4950
+ font-style: italic;
4951
+ }
4952
+
4953
+ .debug-tooltip__badge {
4954
+ display: inline-block;
4955
+ padding: 2px 8px;
4956
+ border-radius: 4px;
4957
+ font-size: 10px;
4958
+ font-weight: 600;
4959
+ text-transform: uppercase;
4960
+ letter-spacing: 0.5px;
4961
+ background: rgba(99, 102, 241, 0.3);
4962
+ color: #a5b4fc;
4963
+ }
4964
+
4965
+ .debug-tooltip__badge--energy {
4966
+ background: rgba(59, 130, 246, 0.3);
4967
+ color: #93c5fd;
4968
+ }
4969
+
4970
+ .debug-tooltip__badge--water {
4971
+ background: rgba(6, 182, 212, 0.3);
4972
+ color: #67e8f9;
4973
+ }
4974
+
4975
+ .debug-tooltip__badge--temperature {
4976
+ background: rgba(249, 115, 22, 0.3);
4977
+ color: #fdba74;
4978
+ }
4979
+
4980
+ /* Tooltip arrow */
4981
+ .debug-tooltip::after {
4982
+ content: '';
4983
+ position: absolute;
4984
+ bottom: -6px;
4985
+ left: 50%;
4986
+ transform: translateX(-50%) rotate(45deg);
4987
+ width: 12px;
4988
+ height: 12px;
4989
+ background: #0f172a;
4990
+ border-right: 1px solid rgba(99, 102, 241, 0.3);
4991
+ border-bottom: 1px solid rgba(99, 102, 241, 0.3);
4992
+ }
4993
+
4994
+ /* Scrollbar styling for tooltip content */
4995
+ .debug-tooltip__content::-webkit-scrollbar {
4996
+ width: 6px;
4997
+ }
4998
+
4999
+ .debug-tooltip__content::-webkit-scrollbar-track {
5000
+ background: rgba(0, 0, 0, 0.2);
5001
+ border-radius: 3px;
5002
+ }
5003
+
5004
+ .debug-tooltip__content::-webkit-scrollbar-thumb {
5005
+ background: rgba(99, 102, 241, 0.4);
5006
+ border-radius: 3px;
5007
+ }
5008
+
5009
+ .debug-tooltip__content::-webkit-scrollbar-thumb:hover {
5010
+ background: rgba(99, 102, 241, 0.6);
5011
+ }
4682
5012
  `;
4683
5013
 
4684
5014
  // src/thingsboard/main-dashboard-shopping/v-4.0.0/head-office/card-head-office.icons.ts
@@ -4819,46 +5149,295 @@
4819
5149
  menu_settings: "Configura\xE7\xF5es"
4820
5150
  };
4821
5151
 
4822
- // src/thingsboard/main-dashboard-shopping/v-4.0.0/card/head-office/card-head-office.js
4823
- var CSS_TAG = "head-office-card-v1";
4824
- function ensureCss() {
4825
- if (!document.querySelector(`style[data-myio-css="${CSS_TAG}"]`)) {
4826
- const style = document.createElement("style");
4827
- style.setAttribute("data-myio-css", CSS_TAG);
4828
- style.textContent = CSS_STRING;
4829
- document.head.appendChild(style);
4830
- }
5152
+ // src/components/temperature/utils.ts
5153
+ var DAY_PERIODS = [
5154
+ { id: "madrugada", label: "Madrugada (00h-06h)", startHour: 0, endHour: 6 },
5155
+ { id: "manha", label: "Manh\xE3 (06h-12h)", startHour: 6, endHour: 12 },
5156
+ { id: "tarde", label: "Tarde (12h-18h)", startHour: 12, endHour: 18 },
5157
+ { id: "noite", label: "Noite (18h-24h)", startHour: 18, endHour: 24 }
5158
+ ];
5159
+ var DEFAULT_CLAMP_RANGE = { min: 15, max: 40 };
5160
+ function getTodaySoFar() {
5161
+ const now = /* @__PURE__ */ new Date();
5162
+ const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
5163
+ return {
5164
+ startTs: startOfDay.getTime(),
5165
+ endTs: now.getTime()
5166
+ };
4831
5167
  }
4832
- function normalizeParams(params) {
4833
- if (!params || !params.entityObject) {
4834
- throw new Error("renderCardCompenteHeadOffice: entityObject is required");
5168
+ var CHART_COLORS = [
5169
+ "#1976d2",
5170
+ // Blue
5171
+ "#FF6B6B",
5172
+ // Red
5173
+ "#4CAF50",
5174
+ // Green
5175
+ "#FF9800",
5176
+ // Orange
5177
+ "#9C27B0",
5178
+ // Purple
5179
+ "#00BCD4",
5180
+ // Cyan
5181
+ "#E91E63",
5182
+ // Pink
5183
+ "#795548"
5184
+ // Brown
5185
+ ];
5186
+ async function fetchTemperatureData(token, deviceId, startTs, endTs) {
5187
+ const url = `/api/plugins/telemetry/DEVICE/${deviceId}/values/timeseries?keys=temperature&startTs=${encodeURIComponent(startTs)}&endTs=${encodeURIComponent(endTs)}&limit=50000&agg=NONE`;
5188
+ const response = await fetch(url, {
5189
+ headers: {
5190
+ "X-Authorization": `Bearer ${token}`,
5191
+ "Content-Type": "application/json"
5192
+ }
5193
+ });
5194
+ if (!response.ok) {
5195
+ throw new Error(`Failed to fetch temperature data: ${response.status}`);
4835
5196
  }
4836
- const entityObject = params.entityObject;
4837
- if (!entityObject.entityId) {
4838
- console.warn("renderCardCompenteHeadOffice: entityId is missing, generating temporary ID");
4839
- entityObject.entityId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
5197
+ const data = await response.json();
5198
+ return data?.temperature || [];
5199
+ }
5200
+ function clampTemperature(value, range = DEFAULT_CLAMP_RANGE) {
5201
+ const num = Number(value || 0);
5202
+ if (num < range.min) return range.min;
5203
+ if (num > range.max) return range.max;
5204
+ return num;
5205
+ }
5206
+ function calculateStats(data, clampRange = DEFAULT_CLAMP_RANGE) {
5207
+ if (data.length === 0) {
5208
+ return { avg: 0, min: 0, max: 0, count: 0 };
4840
5209
  }
5210
+ const values = data.map((item) => clampTemperature(item.value, clampRange));
5211
+ const sum = values.reduce((acc, v) => acc + v, 0);
4841
5212
  return {
4842
- entityObject,
4843
- i18n: { ...DEFAULT_I18N, ...params.i18n || {} },
4844
- enableSelection: Boolean(params.enableSelection),
4845
- enableDragDrop: Boolean(params.enableDragDrop),
4846
- useNewComponents: Boolean(params.useNewComponents),
4847
- // RFC-0091: Configurable delay time for connection status check (default 15 minutes)
4848
- delayTimeConnectionInMins: params.delayTimeConnectionInMins ?? 15,
4849
- callbacks: {
4850
- handleActionDashboard: params.handleActionDashboard,
4851
- handleActionReport: params.handleActionReport,
4852
- handleActionSettings: params.handleActionSettings,
4853
- handleSelect: params.handleSelect,
4854
- handInfo: params.handInfo,
4855
- handleClickCard: params.handleClickCard
4856
- }
5213
+ avg: sum / values.length,
5214
+ min: Math.min(...values),
5215
+ max: Math.max(...values),
5216
+ count: values.length
4857
5217
  };
4858
5218
  }
4859
- function getIconSvg(deviceType, domain) {
4860
- if (domain === "water") {
4861
- return Icons.waterDrop;
5219
+ function interpolateTemperature(data, options) {
5220
+ const { intervalMinutes, startTs, endTs, clampRange = DEFAULT_CLAMP_RANGE } = options;
5221
+ const intervalMs = intervalMinutes * 60 * 1e3;
5222
+ if (data.length === 0) {
5223
+ return [];
5224
+ }
5225
+ const sortedData = [...data].sort((a, b) => a.ts - b.ts);
5226
+ const result = [];
5227
+ let lastKnownValue = clampTemperature(sortedData[0].value, clampRange);
5228
+ let dataIndex = 0;
5229
+ for (let ts = startTs; ts <= endTs; ts += intervalMs) {
5230
+ while (dataIndex < sortedData.length - 1 && sortedData[dataIndex + 1].ts <= ts) {
5231
+ dataIndex++;
5232
+ }
5233
+ const currentData = sortedData[dataIndex];
5234
+ if (currentData && Math.abs(currentData.ts - ts) < intervalMs) {
5235
+ lastKnownValue = clampTemperature(currentData.value, clampRange);
5236
+ }
5237
+ result.push({
5238
+ ts,
5239
+ value: lastKnownValue
5240
+ });
5241
+ }
5242
+ return result;
5243
+ }
5244
+ function aggregateByDay(data, clampRange = DEFAULT_CLAMP_RANGE) {
5245
+ if (data.length === 0) {
5246
+ return [];
5247
+ }
5248
+ const dayMap = /* @__PURE__ */ new Map();
5249
+ data.forEach((item) => {
5250
+ const date = new Date(item.ts);
5251
+ const dateKey = date.toISOString().split("T")[0];
5252
+ if (!dayMap.has(dateKey)) {
5253
+ dayMap.set(dateKey, []);
5254
+ }
5255
+ dayMap.get(dateKey).push(item);
5256
+ });
5257
+ const result = [];
5258
+ dayMap.forEach((dayData, dateKey) => {
5259
+ const values = dayData.map((item) => clampTemperature(item.value, clampRange));
5260
+ const sum = values.reduce((acc, v) => acc + v, 0);
5261
+ result.push({
5262
+ date: dateKey,
5263
+ dateTs: new Date(dateKey).getTime(),
5264
+ avg: sum / values.length,
5265
+ min: Math.min(...values),
5266
+ max: Math.max(...values),
5267
+ count: values.length
5268
+ });
5269
+ });
5270
+ return result.sort((a, b) => a.dateTs - b.dateTs);
5271
+ }
5272
+ function filterByDayPeriods(data, selectedPeriods) {
5273
+ if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
5274
+ return data;
5275
+ }
5276
+ return data.filter((item) => {
5277
+ const date = new Date(item.ts);
5278
+ const hour = date.getHours();
5279
+ return selectedPeriods.some((periodId) => {
5280
+ const period = DAY_PERIODS.find((p) => p.id === periodId);
5281
+ if (!period) return false;
5282
+ return hour >= period.startHour && hour < period.endHour;
5283
+ });
5284
+ });
5285
+ }
5286
+ function getSelectedPeriodsLabel(selectedPeriods) {
5287
+ if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
5288
+ return "Todos os per\xEDodos";
5289
+ }
5290
+ if (selectedPeriods.length === 1) {
5291
+ const period = DAY_PERIODS.find((p) => p.id === selectedPeriods[0]);
5292
+ return period?.label || "";
5293
+ }
5294
+ return `${selectedPeriods.length} per\xEDodos selecionados`;
5295
+ }
5296
+ function formatTemperature(value, decimals = 1) {
5297
+ if (value === null || value === void 0 || isNaN(value)) {
5298
+ return "\u2014";
5299
+ }
5300
+ return `${value.toFixed(decimals)}\xB0C`;
5301
+ }
5302
+ function exportTemperatureCSV(data, deviceLabel, stats, startDate, endDate) {
5303
+ if (data.length === 0) {
5304
+ console.warn("No data to export");
5305
+ return;
5306
+ }
5307
+ const BOM = "\uFEFF";
5308
+ let csvContent = BOM;
5309
+ csvContent += `Relat\xF3rio de Temperatura - ${deviceLabel}
5310
+ `;
5311
+ csvContent += `Per\xEDodo: ${startDate} at\xE9 ${endDate}
5312
+ `;
5313
+ csvContent += `M\xE9dia: ${formatTemperature(stats.avg)}
5314
+ `;
5315
+ csvContent += `M\xEDnima: ${formatTemperature(stats.min)}
5316
+ `;
5317
+ csvContent += `M\xE1xima: ${formatTemperature(stats.max)}
5318
+ `;
5319
+ csvContent += `Total de leituras: ${stats.count}
5320
+ `;
5321
+ csvContent += "\n";
5322
+ csvContent += "Data/Hora,Temperatura (\xB0C)\n";
5323
+ data.forEach((item) => {
5324
+ const date = new Date(item.ts).toLocaleString("pt-BR");
5325
+ const temp = Number(item.value).toFixed(2);
5326
+ csvContent += `"${date}",${temp}
5327
+ `;
5328
+ });
5329
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
5330
+ const url = URL.createObjectURL(blob);
5331
+ const link = document.createElement("a");
5332
+ link.href = url;
5333
+ link.download = `temperatura_${deviceLabel.replace(/\s+/g, "_")}_${startDate}_${endDate}.csv`;
5334
+ document.body.appendChild(link);
5335
+ link.click();
5336
+ document.body.removeChild(link);
5337
+ URL.revokeObjectURL(url);
5338
+ }
5339
+ var DARK_THEME = {
5340
+ background: "rgba(0, 0, 0, 0.85)",
5341
+ surface: "#1a1f28",
5342
+ text: "#ffffff",
5343
+ textMuted: "rgba(255, 255, 255, 0.7)",
5344
+ border: "rgba(255, 255, 255, 0.1)",
5345
+ primary: "#1976d2",
5346
+ success: "#4CAF50",
5347
+ warning: "#FF9800",
5348
+ danger: "#f44336",
5349
+ chartLine: "#1976d2",
5350
+ chartGrid: "rgba(255, 255, 255, 0.1)"
5351
+ };
5352
+ var LIGHT_THEME = {
5353
+ background: "rgba(0, 0, 0, 0.6)",
5354
+ surface: "#ffffff",
5355
+ text: "#333333",
5356
+ textMuted: "#666666",
5357
+ border: "#e0e0e0",
5358
+ primary: "#1976d2",
5359
+ success: "#4CAF50",
5360
+ warning: "#FF9800",
5361
+ danger: "#f44336",
5362
+ chartLine: "#1976d2",
5363
+ chartGrid: "#e0e0e0"
5364
+ };
5365
+ function getThemeColors(theme) {
5366
+ return theme === "dark" ? DARK_THEME : LIGHT_THEME;
5367
+ }
5368
+
5369
+ // src/utils/logHelper.js
5370
+ function createLogHelper(debugActive = false) {
5371
+ return {
5372
+ log: function(...args) {
5373
+ if (debugActive) {
5374
+ console.log(...args);
5375
+ }
5376
+ },
5377
+ warn: function(...args) {
5378
+ if (debugActive) {
5379
+ console.warn(...args);
5380
+ }
5381
+ },
5382
+ error: function(...args) {
5383
+ console.error(...args);
5384
+ }
5385
+ };
5386
+ }
5387
+
5388
+ // src/thingsboard/main-dashboard-shopping/v-4.0.0/card/head-office/card-head-office.js
5389
+ var LABEL_CHAR_LIMIT = 18;
5390
+ var DEFAUL_DELAY_TIME_CONNECTION_IN_MINS = 1440;
5391
+ var CSS_TAG = "head-office-card-v1";
5392
+ function ensureCss() {
5393
+ if (!document.querySelector(`style[data-myio-css="${CSS_TAG}"]`)) {
5394
+ const style = document.createElement("style");
5395
+ style.setAttribute("data-myio-css", CSS_TAG);
5396
+ style.textContent = CSS_STRING;
5397
+ document.head.appendChild(style);
5398
+ }
5399
+ }
5400
+ function normalizeParams(params) {
5401
+ if (!params || !params.entityObject) {
5402
+ throw new Error("renderCardCompenteHeadOffice: entityObject is required");
5403
+ }
5404
+ const LogHelper2 = createLogHelper(params.debugActive ?? false);
5405
+ const entityObject = params.entityObject;
5406
+ if (!entityObject.entityId) {
5407
+ LogHelper2.warn("[CardHeadOffice] entityId is missing, generating temporary ID");
5408
+ entityObject.entityId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
5409
+ }
5410
+ if (!params.delayTimeConnectionInMins) {
5411
+ LogHelper2.warn(
5412
+ `[CardHeadOffice] delayTimeConnectionInMins is missing, defaulting to ${DEFAUL_DELAY_TIME_CONNECTION_IN_MINS} mins`
5413
+ );
5414
+ }
5415
+ return {
5416
+ entityObject,
5417
+ i18n: { ...DEFAULT_I18N, ...params.i18n || {} },
5418
+ enableSelection: Boolean(params.enableSelection),
5419
+ enableDragDrop: Boolean(params.enableDragDrop),
5420
+ useNewComponents: Boolean(params.useNewComponents),
5421
+ // RFC-0091: Configurable delay time for connection status check (default 15 minutes)
5422
+ delayTimeConnectionInMins: params.delayTimeConnectionInMins ?? DEFAUL_DELAY_TIME_CONNECTION_IN_MINS,
5423
+ // Debug options
5424
+ debugActive: params.debugActive ?? false,
5425
+ activeTooltipDebug: params.activeTooltipDebug ?? false,
5426
+ // LogHelper instance for this card
5427
+ LogHelper: LogHelper2,
5428
+ callbacks: {
5429
+ handleActionDashboard: params.handleActionDashboard,
5430
+ handleActionReport: params.handleActionReport,
5431
+ handleActionSettings: params.handleActionSettings,
5432
+ handleSelect: params.handleSelect,
5433
+ handInfo: params.handInfo,
5434
+ handleClickCard: params.handleClickCard
5435
+ }
5436
+ };
5437
+ }
5438
+ function getIconSvg(deviceType, domain) {
5439
+ if (domain === "water") {
5440
+ return Icons.waterDrop;
4862
5441
  }
4863
5442
  if (domain === "temperature") {
4864
5443
  return Icons.thermometer;
@@ -4883,16 +5462,10 @@
4883
5462
  return formatWaterVolumeM3(value);
4884
5463
  }
4885
5464
  if (domain === "temperature") {
4886
- return formatTemperature(value);
5465
+ return formatTemperature(value, 0);
4887
5466
  }
4888
5467
  return formatEnergy(value);
4889
5468
  }
4890
- function formatTemperature(temp) {
4891
- if (temp === null || temp === void 0 || isNaN(temp)) {
4892
- return "\u2014";
4893
- }
4894
- return `${temp.toFixed(0)}\xB0C`;
4895
- }
4896
5469
  function calculateConsumptionPercentage(target, consumption) {
4897
5470
  const numericTarget = Number(target);
4898
5471
  const numericConsumption = Number(consumption);
@@ -4907,32 +5480,32 @@
4907
5480
  // --- Novos Status de Temperatura ---
4908
5481
  case "normal":
4909
5482
  return { chipClass: "chip--ok", label: "Normal" };
4910
- // Verde/Azul
4911
5483
  case "cold":
4912
5484
  return { chipClass: "chip--standby", label: "Frio" };
4913
- // Azul claro/Ciano
4914
5485
  case "hot":
4915
5486
  return { chipClass: "chip--alert", label: "Quente" };
4916
- // Laranja/Amarelo
4917
- // --- Status Existentes ---
5487
+ // --- Status Existentes (aligned with getCardStateClass) ---
4918
5488
  case DeviceStatusType.POWER_ON:
4919
5489
  if (domain === "water") {
4920
- return { chipClass: "chip--ok", label: i18n.in_operation_water };
5490
+ return { chipClass: "chip--power-on", label: i18n.in_operation_water };
4921
5491
  }
4922
- return { chipClass: "chip--ok", label: i18n.in_operation };
5492
+ return { chipClass: "chip--power-on", label: i18n.in_operation };
4923
5493
  case DeviceStatusType.STANDBY:
4924
5494
  return { chipClass: "chip--standby", label: i18n.standby };
4925
5495
  case DeviceStatusType.WARNING:
4926
- return { chipClass: "chip--alert", label: i18n.alert };
5496
+ return { chipClass: "chip--warning", label: i18n.alert };
5497
+ case DeviceStatusType.MAINTENANCE:
5498
+ return { chipClass: "chip--maintenance", label: i18n.maintenance };
4927
5499
  case DeviceStatusType.FAILURE:
4928
- case DeviceStatusType.POWER_OFF:
4929
5500
  return { chipClass: "chip--failure", label: i18n.failure };
4930
- case DeviceStatusType.MAINTENANCE:
4931
- return { chipClass: "chip--alert", label: i18n.maintenance };
4932
- case DeviceStatusType.NOT_INSTALLED:
4933
- return { chipClass: "chip--offline", label: i18n.not_installed };
4934
- // Default (Cai aqui se não achar 'normal', 'hot' etc)
5501
+ case DeviceStatusType.POWER_OFF:
5502
+ return { chipClass: "chip--power-off", label: i18n.power_off || i18n.failure };
5503
+ case DeviceStatusType.OFFLINE:
5504
+ return { chipClass: "chip--offline", label: i18n.offline };
4935
5505
  case DeviceStatusType.NO_INFO:
5506
+ return { chipClass: "chip--no-info", label: i18n.no_info || i18n.offline };
5507
+ case DeviceStatusType.NOT_INSTALLED:
5508
+ return { chipClass: "chip--not-installed", label: i18n.not_installed };
4936
5509
  default:
4937
5510
  return { chipClass: "chip--offline", label: i18n.offline };
4938
5511
  }
@@ -4940,23 +5513,32 @@
4940
5513
  function getCardStateClass(deviceStatus) {
4941
5514
  switch (deviceStatus) {
4942
5515
  case DeviceStatusType.POWER_ON:
4943
- return "is-ok";
5516
+ return "is-power-on";
4944
5517
  // Blue border
4945
5518
  case DeviceStatusType.STANDBY:
4946
5519
  return "is-standby";
4947
5520
  // Green border
4948
5521
  case DeviceStatusType.WARNING:
5522
+ return "is-warning";
5523
+ // Yellow border
4949
5524
  case DeviceStatusType.MAINTENANCE:
4950
- return "is-alert";
5525
+ return "is-maintenance";
4951
5526
  // Yellow border
4952
5527
  case DeviceStatusType.FAILURE:
4953
- case DeviceStatusType.POWER_OFF:
4954
5528
  return "is-failure";
4955
- // Red border
5529
+ // Dark Red border
5530
+ case DeviceStatusType.POWER_OFF:
5531
+ return "is-power-off";
5532
+ // Light Red border
5533
+ case DeviceStatusType.OFFLINE:
5534
+ return "is-offline";
5535
+ // Dark Gray border
4956
5536
  case DeviceStatusType.NO_INFO:
5537
+ return "is-no-info";
5538
+ // Dark Orange border
4957
5539
  case DeviceStatusType.NOT_INSTALLED:
4958
- return "is-offline";
4959
- // Gray border
5540
+ return "is-not-installed";
5541
+ // Purple border
4960
5542
  default:
4961
5543
  return "";
4962
5544
  }
@@ -4970,17 +5552,25 @@
4970
5552
  return "dot--standby";
4971
5553
  case "hot":
4972
5554
  return "dot--alert";
4973
- // --- Status Existentes ---
5555
+ // --- Status Existentes (aligned with getCardStateClass) ---
4974
5556
  case DeviceStatusType.POWER_ON:
4975
- return "dot--ok";
5557
+ return "dot--power-on";
4976
5558
  case DeviceStatusType.STANDBY:
4977
5559
  return "dot--standby";
4978
5560
  case DeviceStatusType.WARNING:
5561
+ return "dot--warning";
4979
5562
  case DeviceStatusType.MAINTENANCE:
4980
- return "dot--alert";
5563
+ return "dot--maintenance";
4981
5564
  case DeviceStatusType.FAILURE:
4982
- case DeviceStatusType.POWER_OFF:
4983
5565
  return "dot--failure";
5566
+ case DeviceStatusType.POWER_OFF:
5567
+ return "dot--power-off";
5568
+ case DeviceStatusType.OFFLINE:
5569
+ return "dot--offline";
5570
+ case DeviceStatusType.NO_INFO:
5571
+ return "dot--no-info";
5572
+ case DeviceStatusType.NOT_INSTALLED:
5573
+ return "dot--not-installed";
4984
5574
  default:
4985
5575
  return "dot--offline";
4986
5576
  }
@@ -5004,7 +5594,13 @@
5004
5594
  titleSection.className = "myio-ho-card__title";
5005
5595
  const nameEl = document.createElement("div");
5006
5596
  nameEl.className = "myio-ho-card__name";
5007
- nameEl.textContent = entityObject.labelOrName || "Unknown Device";
5597
+ const fullName = entityObject.labelOrName || "Unknown Device";
5598
+ if (fullName.length > LABEL_CHAR_LIMIT) {
5599
+ nameEl.textContent = fullName.slice(0, LABEL_CHAR_LIMIT) + "\u2026";
5600
+ nameEl.title = fullName;
5601
+ } else {
5602
+ nameEl.textContent = fullName;
5603
+ }
5008
5604
  titleSection.appendChild(nameEl);
5009
5605
  if (entityObject.deviceIdentifier) {
5010
5606
  const codeEl = document.createElement("div");
@@ -5133,29 +5729,207 @@
5133
5729
  root.appendChild(footer);
5134
5730
  return root;
5135
5731
  }
5136
- function verifyOfflineStatus(entityObject, delayTimeInMins = 15) {
5732
+ function buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins) {
5733
+ const formatTimestamp = (ts) => {
5734
+ if (!ts) return "N/A";
5735
+ const d = new Date(ts);
5736
+ return d.toLocaleString("pt-BR", {
5737
+ day: "2-digit",
5738
+ month: "2-digit",
5739
+ year: "numeric",
5740
+ hour: "2-digit",
5741
+ minute: "2-digit",
5742
+ second: "2-digit"
5743
+ });
5744
+ };
5745
+ return {
5746
+ // Entity identification
5747
+ entityId: entityObject.entityId || "N/A",
5748
+ name: entityObject.name || entityObject.nameEl || "N/A",
5749
+ domain: entityObject.domain || "energy",
5750
+ // Status decision chain
5751
+ originalDeviceStatus: entityObject._originalDeviceStatus || entityObject.deviceStatus,
5752
+ finalDeviceStatus: entityObject.deviceStatus,
5753
+ connectionStatus: entityObject.connectionStatus || "N/A",
5754
+ statusDecisionSource,
5755
+ // Visual output
5756
+ stateClass,
5757
+ chipClass: statusInfo.chipClass,
5758
+ chipLabel: statusInfo.label,
5759
+ // Connection timestamps
5760
+ lastConnectTime: formatTimestamp(entityObject.lastConnectTime),
5761
+ lastDisconnectTime: formatTimestamp(entityObject.lastDisconnectTime),
5762
+ delayTimeConnectionInMins,
5763
+ // Raw values
5764
+ val: entityObject.val,
5765
+ consumptionTargetValue: entityObject.consumptionTargetValue,
5766
+ deviceType: entityObject.deviceType || "N/A"
5767
+ };
5768
+ }
5769
+ function attachDebugTooltip(element, debugInfo) {
5770
+ const existingTooltip = element.querySelector(".debug-tooltip-container");
5771
+ if (existingTooltip) {
5772
+ existingTooltip.remove();
5773
+ }
5774
+ const tooltipContainer = document.createElement("div");
5775
+ tooltipContainer.className = "debug-tooltip-container";
5776
+ const tooltip = document.createElement("div");
5777
+ tooltip.className = "debug-tooltip";
5778
+ tooltip.innerHTML = `
5779
+ <div class="debug-tooltip__header">
5780
+ <span class="debug-tooltip__icon">\u{1F50D}</span>
5781
+ <span class="debug-tooltip__title">Debug Info</span>
5782
+ </div>
5783
+ <div class="debug-tooltip__content">
5784
+ <div class="debug-tooltip__section">
5785
+ <div class="debug-tooltip__section-title">\u{1F4CB} Identifica\xE7\xE3o</div>
5786
+ <div class="debug-tooltip__row">
5787
+ <span class="debug-tooltip__label">Entity ID:</span>
5788
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.entityId}</span>
5789
+ </div>
5790
+ <div class="debug-tooltip__row">
5791
+ <span class="debug-tooltip__label">Nome:</span>
5792
+ <span class="debug-tooltip__value">${debugInfo.name}</span>
5793
+ </div>
5794
+ <div class="debug-tooltip__row">
5795
+ <span class="debug-tooltip__label">Dom\xEDnio:</span>
5796
+ <span class="debug-tooltip__value debug-tooltip__badge debug-tooltip__badge--${debugInfo.domain}">${debugInfo.domain}</span>
5797
+ </div>
5798
+ </div>
5799
+
5800
+ <div class="debug-tooltip__section">
5801
+ <div class="debug-tooltip__section-title">\u26A1 Decis\xE3o de Status</div>
5802
+ <div class="debug-tooltip__row">
5803
+ <span class="debug-tooltip__label">connectionStatus:</span>
5804
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.connectionStatus}</span>
5805
+ </div>
5806
+ <div class="debug-tooltip__row">
5807
+ <span class="debug-tooltip__label">deviceStatus (final):</span>
5808
+ <span class="debug-tooltip__value debug-tooltip__badge">${debugInfo.finalDeviceStatus}</span>
5809
+ </div>
5810
+ <div class="debug-tooltip__row debug-tooltip__row--full">
5811
+ <span class="debug-tooltip__label">Fonte da decis\xE3o:</span>
5812
+ <span class="debug-tooltip__value debug-tooltip__value--highlight">${debugInfo.statusDecisionSource}</span>
5813
+ </div>
5814
+ </div>
5815
+
5816
+ <div class="debug-tooltip__section">
5817
+ <div class="debug-tooltip__section-title">\u{1F3A8} Output Visual</div>
5818
+ <div class="debug-tooltip__row">
5819
+ <span class="debug-tooltip__label">stateClass:</span>
5820
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.stateClass}</span>
5821
+ </div>
5822
+ <div class="debug-tooltip__row">
5823
+ <span class="debug-tooltip__label">chipClass:</span>
5824
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.chipClass}</span>
5825
+ </div>
5826
+ <div class="debug-tooltip__row">
5827
+ <span class="debug-tooltip__label">chipLabel:</span>
5828
+ <span class="debug-tooltip__value">${debugInfo.chipLabel}</span>
5829
+ </div>
5830
+ </div>
5831
+
5832
+ <div class="debug-tooltip__section">
5833
+ <div class="debug-tooltip__section-title">\u{1F550} Timestamps de Conex\xE3o</div>
5834
+ <div class="debug-tooltip__row">
5835
+ <span class="debug-tooltip__label">lastConnectTime:</span>
5836
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.lastConnectTime}</span>
5837
+ </div>
5838
+ <div class="debug-tooltip__row">
5839
+ <span class="debug-tooltip__label">lastDisconnectTime:</span>
5840
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.lastDisconnectTime}</span>
5841
+ </div>
5842
+ <div class="debug-tooltip__row">
5843
+ <span class="debug-tooltip__label">delayTime:</span>
5844
+ <span class="debug-tooltip__value">${debugInfo.delayTimeConnectionInMins} mins</span>
5845
+ </div>
5846
+ </div>
5847
+
5848
+ <div class="debug-tooltip__section">
5849
+ <div class="debug-tooltip__section-title">\u{1F4CA} Valores</div>
5850
+ <div class="debug-tooltip__row">
5851
+ <span class="debug-tooltip__label">val (consumo):</span>
5852
+ <span class="debug-tooltip__value">${debugInfo.val ?? "N/A"}</span>
5853
+ </div>
5854
+ <div class="debug-tooltip__row">
5855
+ <span class="debug-tooltip__label">target (meta):</span>
5856
+ <span class="debug-tooltip__value">${debugInfo.consumptionTargetValue ?? "N/A"}</span>
5857
+ </div>
5858
+ <div class="debug-tooltip__row">
5859
+ <span class="debug-tooltip__label">deviceType:</span>
5860
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.deviceType}</span>
5861
+ </div>
5862
+ </div>
5863
+ </div>
5864
+ `;
5865
+ tooltipContainer.appendChild(tooltip);
5866
+ element.style.position = "relative";
5867
+ element.style.cursor = "help";
5868
+ element.appendChild(tooltipContainer);
5869
+ element.classList.add("has-debug-tooltip");
5870
+ }
5871
+ function verifyOfflineStatus(entityObject, delayTimeInMins = 15, LogHelper2) {
5137
5872
  const lastConnectionTime = new Date(entityObject.lastConnectTime || 0);
5138
5873
  const lastDisconnectTime = new Date(entityObject.lastDisconnectTime || 0);
5139
5874
  const now = /* @__PURE__ */ new Date();
5140
5875
  const delayTimeInMs = delayTimeInMins * 60 * 1e3;
5141
5876
  const timeSinceConnection = now.getTime() - lastConnectionTime.getTime();
5877
+ let isOffline = false;
5142
5878
  if (lastDisconnectTime.getTime() > lastConnectionTime.getTime()) {
5143
- return false;
5144
- }
5145
- if (timeSinceConnection < delayTimeInMs) {
5146
- return false;
5879
+ isOffline = true;
5880
+ LogHelper2.log(
5881
+ "[CardHeadOffice][ConnectionStatus Verify] Device is OFFLINE because lastDisconnectTime is more recent than lastConnectTime",
5882
+ entityObject.nameEl
5883
+ );
5884
+ } else if (timeSinceConnection > delayTimeInMs) {
5885
+ isOffline = true;
5886
+ LogHelper2.log(
5887
+ "[CardHeadOffice][ConnectionStatus Verify] Device is OFFLINE because lastConnectTime is older than configured delayTimeConnectionInMins:",
5888
+ delayTimeInMins,
5889
+ "for device",
5890
+ entityObject.nameEl
5891
+ );
5892
+ } else {
5893
+ isOffline = false;
5894
+ LogHelper2.log(
5895
+ "[CardHeadOffice][ConnectionStatus Verify] Device is ONLINE because lastConnectTime is within configured delayTimeConnectionInMins:",
5896
+ delayTimeInMins,
5897
+ "for device",
5898
+ entityObject.nameEl
5899
+ );
5147
5900
  }
5148
- return true;
5901
+ return isOffline;
5149
5902
  }
5150
5903
  function paint(root, state) {
5151
- const { entityObject, i18n, delayTimeConnectionInMins, isSelected } = state;
5904
+ const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state;
5905
+ let statusDecisionSource = "unknown";
5152
5906
  if (entityObject.connectionStatus) {
5153
5907
  if (entityObject.connectionStatus === "offline") {
5154
- entityObject.deviceStatus = DeviceStatusType.NO_INFO;
5908
+ LogHelper2.log(
5909
+ "[CardHeadOffice][ConnectionStatus Verify 01] Setting deviceStatus to OFFLINE based on connectionStatus"
5910
+ );
5911
+ entityObject.deviceStatus = DeviceStatusType.OFFLINE;
5912
+ statusDecisionSource = 'connectionStatus === "offline"';
5913
+ } else {
5914
+ LogHelper2.log(
5915
+ "[CardHeadOffice] Device is ONLINE or WAITING based on connectionStatus for device",
5916
+ entityObject.nameEl
5917
+ );
5918
+ statusDecisionSource = `connectionStatus === "${entityObject.connectionStatus}" (kept original deviceStatus)`;
5155
5919
  }
5156
5920
  } else {
5157
- if (verifyOfflineStatus(entityObject, delayTimeConnectionInMins) === false) {
5158
- entityObject.deviceStatus = DeviceStatusType.NO_INFO;
5921
+ if (verifyOfflineStatus(entityObject, delayTimeConnectionInMins, LogHelper2) === false) {
5922
+ LogHelper2.log(
5923
+ "[CardHeadOffice][ConnectionStatus Verify 02] Setting deviceStatus to OFFLINE based on timestamp verification by verifyOfflineStatus METHOD with delayTimeConnectionInMins:",
5924
+ delayTimeConnectionInMins
5925
+ );
5926
+ entityObject.deviceStatus = DeviceStatusType.OFFLINE;
5927
+ statusDecisionSource = `verifyOfflineStatus() returned false (delay: ${delayTimeConnectionInMins} mins)`;
5928
+ } else {
5929
+ LogHelper2.log(
5930
+ `[CardHeadOffice][ConnectionStatus Verify 03] Device is ONLINE with deviceStatus = ${entityObject.deviceStatus} based on timestamp verification for device ${entityObject.nameEl}`
5931
+ );
5932
+ statusDecisionSource = `verifyOfflineStatus() returned true (delay: ${delayTimeConnectionInMins} mins)`;
5159
5933
  }
5160
5934
  }
5161
5935
  const stateClass = getCardStateClass(entityObject.deviceStatus);
@@ -5164,6 +5938,10 @@
5164
5938
  const chip = root.querySelector(".chip");
5165
5939
  chip.className = `chip ${statusInfo.chipClass}`;
5166
5940
  chip.innerHTML = statusInfo.label;
5941
+ if (activeTooltipDebug) {
5942
+ const debugInfo = buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins);
5943
+ attachDebugTooltip(chip, debugInfo);
5944
+ }
5167
5945
  const primaryValue = formatValueByDomain(entityObject.val, entityObject.domain);
5168
5946
  const numSpan = root.querySelector(".myio-ho-card__value .num");
5169
5947
  root.querySelector(".myio-ho-card__value .unit");
@@ -5376,6 +6154,7 @@
5376
6154
  }
5377
6155
 
5378
6156
  // src/thingsboard/main-dashboard-shopping/v-5.2.0/card/template-card-v5.js
6157
+ var LABEL_CHAR_LIMIT2 = 18;
5379
6158
  function renderCardComponentV5({
5380
6159
  entityObject,
5381
6160
  handleActionDashboard,
@@ -5892,7 +6671,7 @@ ${rangeText}`;
5892
6671
 
5893
6672
  <div class="device-title-row" style="flex-direction: column; min-height: 38px; text-align: center; width: 100%;">
5894
6673
  <span class="device-title" title="${cardEntity.name}">
5895
- ${cardEntity.name.length > 18 ? cardEntity.name.slice(0, 18) + "\u2026" : cardEntity.name}
6674
+ ${cardEntity.name.length > LABEL_CHAR_LIMIT2 ? cardEntity.name.slice(0, LABEL_CHAR_LIMIT2) + "\u2026" : cardEntity.name}
5896
6675
  </span>
5897
6676
  ${deviceIdentifier ? `
5898
6677
  <span class="device-subtitle" title="${deviceIdentifier}">
@@ -15658,23 +16437,1019 @@ ${rangeText}`;
15658
16437
  if (isNaN(level) || level < 0 || level > 100) {
15659
16438
  errors.push("currentLevel must be a number between 0 and 100");
15660
16439
  }
15661
- }
15662
- if (options.limit !== void 0) {
15663
- const limit = Number(options.limit);
15664
- if (isNaN(limit) || limit < 1 || limit > 1e4) {
15665
- errors.push("limit must be a number between 1 and 10000");
16440
+ }
16441
+ if (options.limit !== void 0) {
16442
+ const limit = Number(options.limit);
16443
+ if (isNaN(limit) || limit < 1 || limit > 1e4) {
16444
+ errors.push("limit must be a number between 1 and 10000");
16445
+ }
16446
+ }
16447
+ if (options.aggregation !== void 0) {
16448
+ const validAggregations = ["NONE", "MIN", "MAX", "AVG", "SUM", "COUNT"];
16449
+ if (!validAggregations.includes(options.aggregation)) {
16450
+ errors.push(`aggregation must be one of: ${validAggregations.join(", ")}`);
16451
+ }
16452
+ }
16453
+ if (errors.length > 0) {
16454
+ throw new Error(`Validation failed:
16455
+ - ${errors.join("\n- ")}`);
16456
+ }
16457
+ }
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
+ };
15666
17371
  }
17372
+ return {
17373
+ code: "UNKNOWN_ERROR",
17374
+ message: error.message || "Unknown error occurred",
17375
+ cause: error
17376
+ };
15667
17377
  }
15668
- if (options.aggregation !== void 0) {
15669
- const validAggregations = ["NONE", "MIN", "MAX", "AVG", "SUM", "COUNT"];
15670
- if (!validAggregations.includes(options.aggregation)) {
15671
- errors.push(`aggregation must be one of: ${validAggregations.join(", ")}`);
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();
15672
17436
  }
15673
17437
  }
15674
- if (errors.length > 0) {
15675
- throw new Error(`Validation failed:
15676
- - ${errors.join("\n- ")}`);
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
+ }
15677
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
+ };
15678
17453
  }
15679
17454
 
15680
17455
  // src/components/premium-modals/settings/SettingsModalView.ts
@@ -20045,220 +21820,6 @@ ${rangeText}`;
20045
21820
  }
20046
21821
  }
20047
21822
 
20048
- // src/components/temperature/utils.ts
20049
- var DAY_PERIODS = [
20050
- { id: "madrugada", label: "Madrugada (00h-06h)", startHour: 0, endHour: 6 },
20051
- { id: "manha", label: "Manh\xE3 (06h-12h)", startHour: 6, endHour: 12 },
20052
- { id: "tarde", label: "Tarde (12h-18h)", startHour: 12, endHour: 18 },
20053
- { id: "noite", label: "Noite (18h-24h)", startHour: 18, endHour: 24 }
20054
- ];
20055
- var DEFAULT_CLAMP_RANGE = { min: 15, max: 40 };
20056
- function getTodaySoFar() {
20057
- const now = /* @__PURE__ */ new Date();
20058
- const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
20059
- return {
20060
- startTs: startOfDay.getTime(),
20061
- endTs: now.getTime()
20062
- };
20063
- }
20064
- var CHART_COLORS = [
20065
- "#1976d2",
20066
- // Blue
20067
- "#FF6B6B",
20068
- // Red
20069
- "#4CAF50",
20070
- // Green
20071
- "#FF9800",
20072
- // Orange
20073
- "#9C27B0",
20074
- // Purple
20075
- "#00BCD4",
20076
- // Cyan
20077
- "#E91E63",
20078
- // Pink
20079
- "#795548"
20080
- // Brown
20081
- ];
20082
- async function fetchTemperatureData(token, deviceId, startTs, endTs) {
20083
- const url = `/api/plugins/telemetry/DEVICE/${deviceId}/values/timeseries?keys=temperature&startTs=${encodeURIComponent(startTs)}&endTs=${encodeURIComponent(endTs)}&limit=50000&agg=NONE`;
20084
- const response = await fetch(url, {
20085
- headers: {
20086
- "X-Authorization": `Bearer ${token}`,
20087
- "Content-Type": "application/json"
20088
- }
20089
- });
20090
- if (!response.ok) {
20091
- throw new Error(`Failed to fetch temperature data: ${response.status}`);
20092
- }
20093
- const data = await response.json();
20094
- return data?.temperature || [];
20095
- }
20096
- function clampTemperature(value, range = DEFAULT_CLAMP_RANGE) {
20097
- const num = Number(value || 0);
20098
- if (num < range.min) return range.min;
20099
- if (num > range.max) return range.max;
20100
- return num;
20101
- }
20102
- function calculateStats(data, clampRange = DEFAULT_CLAMP_RANGE) {
20103
- if (data.length === 0) {
20104
- return { avg: 0, min: 0, max: 0, count: 0 };
20105
- }
20106
- const values = data.map((item) => clampTemperature(item.value, clampRange));
20107
- const sum = values.reduce((acc, v) => acc + v, 0);
20108
- return {
20109
- avg: sum / values.length,
20110
- min: Math.min(...values),
20111
- max: Math.max(...values),
20112
- count: values.length
20113
- };
20114
- }
20115
- function interpolateTemperature(data, options) {
20116
- const { intervalMinutes, startTs, endTs, clampRange = DEFAULT_CLAMP_RANGE } = options;
20117
- const intervalMs = intervalMinutes * 60 * 1e3;
20118
- if (data.length === 0) {
20119
- return [];
20120
- }
20121
- const sortedData = [...data].sort((a, b) => a.ts - b.ts);
20122
- const result = [];
20123
- let lastKnownValue = clampTemperature(sortedData[0].value, clampRange);
20124
- let dataIndex = 0;
20125
- for (let ts = startTs; ts <= endTs; ts += intervalMs) {
20126
- while (dataIndex < sortedData.length - 1 && sortedData[dataIndex + 1].ts <= ts) {
20127
- dataIndex++;
20128
- }
20129
- const currentData = sortedData[dataIndex];
20130
- if (currentData && Math.abs(currentData.ts - ts) < intervalMs) {
20131
- lastKnownValue = clampTemperature(currentData.value, clampRange);
20132
- }
20133
- result.push({
20134
- ts,
20135
- value: lastKnownValue
20136
- });
20137
- }
20138
- return result;
20139
- }
20140
- function aggregateByDay(data, clampRange = DEFAULT_CLAMP_RANGE) {
20141
- if (data.length === 0) {
20142
- return [];
20143
- }
20144
- const dayMap = /* @__PURE__ */ new Map();
20145
- data.forEach((item) => {
20146
- const date = new Date(item.ts);
20147
- const dateKey = date.toISOString().split("T")[0];
20148
- if (!dayMap.has(dateKey)) {
20149
- dayMap.set(dateKey, []);
20150
- }
20151
- dayMap.get(dateKey).push(item);
20152
- });
20153
- const result = [];
20154
- dayMap.forEach((dayData, dateKey) => {
20155
- const values = dayData.map((item) => clampTemperature(item.value, clampRange));
20156
- const sum = values.reduce((acc, v) => acc + v, 0);
20157
- result.push({
20158
- date: dateKey,
20159
- dateTs: new Date(dateKey).getTime(),
20160
- avg: sum / values.length,
20161
- min: Math.min(...values),
20162
- max: Math.max(...values),
20163
- count: values.length
20164
- });
20165
- });
20166
- return result.sort((a, b) => a.dateTs - b.dateTs);
20167
- }
20168
- function filterByDayPeriods(data, selectedPeriods) {
20169
- if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
20170
- return data;
20171
- }
20172
- return data.filter((item) => {
20173
- const date = new Date(item.ts);
20174
- const hour = date.getHours();
20175
- return selectedPeriods.some((periodId) => {
20176
- const period = DAY_PERIODS.find((p) => p.id === periodId);
20177
- if (!period) return false;
20178
- return hour >= period.startHour && hour < period.endHour;
20179
- });
20180
- });
20181
- }
20182
- function getSelectedPeriodsLabel(selectedPeriods) {
20183
- if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
20184
- return "Todos os per\xEDodos";
20185
- }
20186
- if (selectedPeriods.length === 1) {
20187
- const period = DAY_PERIODS.find((p) => p.id === selectedPeriods[0]);
20188
- return period?.label || "";
20189
- }
20190
- return `${selectedPeriods.length} per\xEDodos selecionados`;
20191
- }
20192
- function formatTemperature2(value, decimals = 1) {
20193
- return `${value.toFixed(decimals)}\xB0C`;
20194
- }
20195
- function exportTemperatureCSV(data, deviceLabel, stats, startDate, endDate) {
20196
- if (data.length === 0) {
20197
- console.warn("No data to export");
20198
- return;
20199
- }
20200
- const BOM = "\uFEFF";
20201
- let csvContent = BOM;
20202
- csvContent += `Relat\xF3rio de Temperatura - ${deviceLabel}
20203
- `;
20204
- csvContent += `Per\xEDodo: ${startDate} at\xE9 ${endDate}
20205
- `;
20206
- csvContent += `M\xE9dia: ${formatTemperature2(stats.avg)}
20207
- `;
20208
- csvContent += `M\xEDnima: ${formatTemperature2(stats.min)}
20209
- `;
20210
- csvContent += `M\xE1xima: ${formatTemperature2(stats.max)}
20211
- `;
20212
- csvContent += `Total de leituras: ${stats.count}
20213
- `;
20214
- csvContent += "\n";
20215
- csvContent += "Data/Hora,Temperatura (\xB0C)\n";
20216
- data.forEach((item) => {
20217
- const date = new Date(item.ts).toLocaleString("pt-BR");
20218
- const temp = Number(item.value).toFixed(2);
20219
- csvContent += `"${date}",${temp}
20220
- `;
20221
- });
20222
- const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
20223
- const url = URL.createObjectURL(blob);
20224
- const link = document.createElement("a");
20225
- link.href = url;
20226
- link.download = `temperatura_${deviceLabel.replace(/\s+/g, "_")}_${startDate}_${endDate}.csv`;
20227
- document.body.appendChild(link);
20228
- link.click();
20229
- document.body.removeChild(link);
20230
- URL.revokeObjectURL(url);
20231
- }
20232
- var DARK_THEME = {
20233
- background: "rgba(0, 0, 0, 0.85)",
20234
- surface: "#1a1f28",
20235
- text: "#ffffff",
20236
- textMuted: "rgba(255, 255, 255, 0.7)",
20237
- border: "rgba(255, 255, 255, 0.1)",
20238
- primary: "#1976d2",
20239
- success: "#4CAF50",
20240
- warning: "#FF9800",
20241
- danger: "#f44336",
20242
- chartLine: "#1976d2",
20243
- chartGrid: "rgba(255, 255, 255, 0.1)"
20244
- };
20245
- var LIGHT_THEME = {
20246
- background: "rgba(0, 0, 0, 0.6)",
20247
- surface: "#ffffff",
20248
- text: "#333333",
20249
- textMuted: "#666666",
20250
- border: "#e0e0e0",
20251
- primary: "#1976d2",
20252
- success: "#4CAF50",
20253
- warning: "#FF9800",
20254
- danger: "#f44336",
20255
- chartLine: "#1976d2",
20256
- chartGrid: "#e0e0e0"
20257
- };
20258
- function getThemeColors(theme) {
20259
- return theme === "dark" ? DARK_THEME : LIGHT_THEME;
20260
- }
20261
-
20262
21823
  // src/components/temperature/TemperatureModal.ts
20263
21824
  async function openTemperatureModal(params) {
20264
21825
  const modalId = `myio-temp-modal-${Date.now()}`;
@@ -20498,7 +22059,7 @@ ${rangeText}`;
20498
22059
  ">
20499
22060
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Temperatura Atual</span>
20500
22061
  <div style="font-weight: 700; font-size: 24px; color: ${statusColor}; margin-top: 4px;">
20501
- ${state.currentTemperature !== null ? formatTemperature2(state.currentTemperature) : "N/A"}
22062
+ ${state.currentTemperature !== null ? formatTemperature(state.currentTemperature) : "N/A"}
20502
22063
  </div>
20503
22064
  <div style="font-size: 11px; color: ${statusColor}; margin-top: 2px;">${statusText}</div>
20504
22065
  </div>
@@ -20509,7 +22070,7 @@ ${rangeText}`;
20509
22070
  ">
20510
22071
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">M\xE9dia do Per\xEDodo</span>
20511
22072
  <div id="${modalId}-avg" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
20512
- ${state.stats.count > 0 ? formatTemperature2(state.stats.avg) : "N/A"}
22073
+ ${state.stats.count > 0 ? formatTemperature(state.stats.avg) : "N/A"}
20513
22074
  </div>
20514
22075
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${startDateStr} - ${endDateStr}</div>
20515
22076
  </div>
@@ -20520,7 +22081,7 @@ ${rangeText}`;
20520
22081
  ">
20521
22082
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Min / Max</span>
20522
22083
  <div id="${modalId}-minmax" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
20523
- ${state.stats.count > 0 ? `${formatTemperature2(state.stats.min)} / ${formatTemperature2(state.stats.max)}` : "N/A"}
22084
+ ${state.stats.count > 0 ? `${formatTemperature(state.stats.min)} / ${formatTemperature(state.stats.max)}` : "N/A"}
20524
22085
  </div>
20525
22086
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state.stats.count} leituras</div>
20526
22087
  </div>
@@ -20836,7 +22397,7 @@ ${rangeText}`;
20836
22397
  }
20837
22398
  tooltip.innerHTML = `
20838
22399
  <div style="font-weight: 600; margin-bottom: 6px; color: ${colors.primary};">
20839
- ${formatTemperature2(point.y)}
22400
+ ${formatTemperature(point.y)}
20840
22401
  </div>
20841
22402
  <div style="font-size: 11px; color: ${colors.textMuted};">
20842
22403
  \u{1F4C5} ${dateStr}
@@ -21099,7 +22660,7 @@ ${rangeText}`;
21099
22660
  <span style="width: 12px; height: 12px; border-radius: 50%; background: ${dd.color};"></span>
21100
22661
  <span style="color: ${colors.text}; font-size: 13px;">${dd.device.label}</span>
21101
22662
  <span style="color: ${colors.textMuted}; font-size: 11px; margin-left: auto;">
21102
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.avg) : "N/A"}
22663
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.avg) : "N/A"}
21103
22664
  </span>
21104
22665
  </div>
21105
22666
  `).join("");
@@ -21115,15 +22676,15 @@ ${rangeText}`;
21115
22676
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4px; font-size: 11px;">
21116
22677
  <span style="color: ${colors.textMuted};">M\xE9dia:</span>
21117
22678
  <span style="color: ${colors.text}; font-weight: 500;">
21118
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.avg) : "N/A"}
22679
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.avg) : "N/A"}
21119
22680
  </span>
21120
22681
  <span style="color: ${colors.textMuted};">Min:</span>
21121
22682
  <span style="color: ${colors.text};">
21122
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.min) : "N/A"}
22683
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.min) : "N/A"}
21123
22684
  </span>
21124
22685
  <span style="color: ${colors.textMuted};">Max:</span>
21125
22686
  <span style="color: ${colors.text};">
21126
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.max) : "N/A"}
22687
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.max) : "N/A"}
21127
22688
  </span>
21128
22689
  <span style="color: ${colors.textMuted};">Leituras:</span>
21129
22690
  <span style="color: ${colors.text};">${dd.stats.count}</span>
@@ -21659,7 +23220,7 @@ ${rangeText}`;
21659
23220
  <span style="font-weight: 600;">${point.deviceLabel}</span>
21660
23221
  </div>
21661
23222
  <div style="font-weight: 600; font-size: 16px; color: ${point.deviceColor}; margin-bottom: 4px;">
21662
- ${formatTemperature2(point.y)}
23223
+ ${formatTemperature(point.y)}
21663
23224
  </div>
21664
23225
  <div style="font-size: 11px; color: ${colors.textMuted};">
21665
23226
  \u{1F4C5} ${dateStr}
@@ -26151,6 +27712,9 @@ ${rangeText}`;
26151
27712
  exports.MyIODraggableCard = MyIODraggableCard;
26152
27713
  exports.MyIOSelectionStoreClass = MyIOSelectionStoreClass;
26153
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;
26154
27718
  exports.addDetectionContext = addDetectionContext;
26155
27719
  exports.addNamespace = addNamespace;
26156
27720
  exports.aggregateByDay = aggregateByDay;
@@ -26205,7 +27769,7 @@ ${rangeText}`;
26205
27769
  exports.formatNumberReadable = formatNumberReadable;
26206
27770
  exports.formatRelativeTime = formatRelativeTime;
26207
27771
  exports.formatTankHeadFromCm = formatTankHeadFromCm;
26208
- exports.formatTemperature = formatTemperature2;
27772
+ exports.formatTemperature = formatTemperature;
26209
27773
  exports.formatWater = formatWater;
26210
27774
  exports.formatWaterByGroup = formatWaterByGroup;
26211
27775
  exports.formatWaterVolumeM3 = formatWaterVolumeM3;
@@ -26248,6 +27812,7 @@ ${rangeText}`;
26248
27812
  exports.openDashboardPopupWaterTank = openDashboardPopupWaterTank;
26249
27813
  exports.openDemandModal = openDemandModal;
26250
27814
  exports.openGoalsPanel = openGoalsPanel;
27815
+ exports.openPowerLimitsSetupModal = openPowerLimitsSetupModal;
26251
27816
  exports.openRealTimeTelemetryModal = openRealTimeTelemetryModal;
26252
27817
  exports.openTemperatureComparisonModal = openTemperatureComparisonModal;
26253
27818
  exports.openTemperatureModal = openTemperatureModal;