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.
package/dist/index.js CHANGED
@@ -1318,7 +1318,8 @@ var DeviceStatusType = {
1318
1318
  FAILURE: "failure",
1319
1319
  MAINTENANCE: "maintenance",
1320
1320
  NO_INFO: "no_info",
1321
- NOT_INSTALLED: "not_installed"
1321
+ NOT_INSTALLED: "not_installed",
1322
+ OFFLINE: "offline"
1322
1323
  };
1323
1324
  var ConnectionStatusType = {
1324
1325
  CONNECTED: "connected",
@@ -1327,39 +1328,42 @@ var ConnectionStatusType = {
1327
1328
  var deviceStatusIcons = {
1328
1329
  [DeviceStatusType.POWER_ON]: "\u26A1",
1329
1330
  [DeviceStatusType.STANDBY]: "\u{1F50C}",
1330
- [DeviceStatusType.POWER_OFF]: "\u{1F534}",
1331
+ [DeviceStatusType.POWER_OFF]: "\u26AB",
1331
1332
  [DeviceStatusType.WARNING]: "\u26A0\uFE0F",
1332
1333
  [DeviceStatusType.FAILURE]: "\u{1F6A8}",
1333
1334
  [DeviceStatusType.MAINTENANCE]: "\u{1F6E0}\uFE0F",
1334
1335
  [DeviceStatusType.NO_INFO]: "\u2753\uFE0F",
1335
- [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}"
1336
+ [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}",
1337
+ [DeviceStatusType.OFFLINE]: "\u{1F534}"
1336
1338
  };
1337
1339
  var waterDeviceStatusIcons = {
1338
1340
  [DeviceStatusType.POWER_ON]: "\u{1F4A7}",
1339
1341
  [DeviceStatusType.STANDBY]: "\u{1F6B0}",
1340
- [DeviceStatusType.POWER_OFF]: "\u{1F534}",
1342
+ [DeviceStatusType.POWER_OFF]: "\u26AB",
1341
1343
  [DeviceStatusType.WARNING]: "\u26A0\uFE0F",
1342
1344
  [DeviceStatusType.FAILURE]: "\u{1F6A8}",
1343
1345
  [DeviceStatusType.MAINTENANCE]: "\u{1F6E0}\uFE0F",
1344
1346
  [DeviceStatusType.NO_INFO]: "\u2753\uFE0F",
1345
- [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}"
1347
+ [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}",
1348
+ [DeviceStatusType.OFFLINE]: "\u{1F534}"
1346
1349
  };
1347
1350
  var temperatureDeviceStatusIcons = {
1348
1351
  [DeviceStatusType.POWER_ON]: "\u{1F321}\uFE0F",
1349
1352
  [DeviceStatusType.STANDBY]: "\u{1F321}\uFE0F",
1350
- [DeviceStatusType.POWER_OFF]: "\u{1F534}",
1353
+ [DeviceStatusType.POWER_OFF]: "\u26AB",
1351
1354
  [DeviceStatusType.WARNING]: "\u26A0\uFE0F",
1352
1355
  [DeviceStatusType.FAILURE]: "\u{1F6A8}",
1353
1356
  [DeviceStatusType.MAINTENANCE]: "\u{1F6E0}\uFE0F",
1354
1357
  [DeviceStatusType.NO_INFO]: "\u2753\uFE0F",
1355
- [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}"
1358
+ [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}",
1359
+ [DeviceStatusType.OFFLINE]: "\u{1F534}"
1356
1360
  };
1357
1361
  var connectionStatusIcons = {
1358
1362
  [ConnectionStatusType.CONNECTED]: "\u{1F7E2}",
1359
1363
  [ConnectionStatusType.OFFLINE]: "\u{1F6AB}"
1360
1364
  };
1361
1365
  function mapDeviceToConnectionStatus(deviceStatus) {
1362
- if (deviceStatus === DeviceStatusType.NO_INFO) {
1366
+ if (deviceStatus === DeviceStatusType.NO_INFO || deviceStatus === DeviceStatusType.OFFLINE) {
1363
1367
  return ConnectionStatusType.OFFLINE;
1364
1368
  }
1365
1369
  return ConnectionStatusType.CONNECTED;
@@ -1383,15 +1387,16 @@ function mapDeviceStatusToCardStatus(deviceStatus) {
1383
1387
  [DeviceStatusType.FAILURE]: "fail",
1384
1388
  [DeviceStatusType.MAINTENANCE]: "alert",
1385
1389
  [DeviceStatusType.NO_INFO]: "unknown",
1386
- [DeviceStatusType.NOT_INSTALLED]: "not_installed"
1390
+ [DeviceStatusType.NOT_INSTALLED]: "not_installed",
1391
+ [DeviceStatusType.OFFLINE]: "offline"
1387
1392
  };
1388
1393
  return statusMap[deviceStatus] || "unknown";
1389
1394
  }
1390
1395
  function shouldFlashIcon(deviceStatus) {
1391
- return deviceStatus === DeviceStatusType.POWER_OFF || deviceStatus === DeviceStatusType.WARNING || deviceStatus === DeviceStatusType.FAILURE || deviceStatus === DeviceStatusType.MAINTENANCE;
1396
+ return deviceStatus === DeviceStatusType.POWER_OFF || deviceStatus === DeviceStatusType.WARNING || deviceStatus === DeviceStatusType.FAILURE || deviceStatus === DeviceStatusType.MAINTENANCE || deviceStatus === DeviceStatusType.OFFLINE;
1392
1397
  }
1393
1398
  function isDeviceOffline(deviceStatus) {
1394
- return deviceStatus === DeviceStatusType.NO_INFO;
1399
+ return deviceStatus === DeviceStatusType.NO_INFO || deviceStatus === DeviceStatusType.OFFLINE;
1395
1400
  }
1396
1401
  function getDeviceStatusIcon(deviceStatus, deviceType = null) {
1397
1402
  const normalizedType = deviceType?.toUpperCase() || "";
@@ -3947,15 +3952,30 @@ var CSS_STRING = `
3947
3952
  --myio-chip-alert-fg: #b45309;
3948
3953
  --myio-border-alert: rgba(245, 158, 11, 0.5);
3949
3954
 
3950
- /* Status colors - Failure (red) */
3955
+ /* Status colors - Failure (dark red) */
3951
3956
  --myio-chip-failure-bg: #fee2e2;
3952
3957
  --myio-chip-failure-fg: #b91c1c;
3953
- --myio-border-failure: rgba(239, 68, 68, 0.5);
3958
+ --myio-border-failure: rgba(153, 27, 27, 0.6);
3959
+
3960
+ /* Status colors - Power Off (light red) */
3961
+ --myio-chip-power-off-bg: #fecaca;
3962
+ --myio-chip-power-off-fg: #dc2626;
3963
+ --myio-border-power-off: rgba(239, 68, 68, 0.5);
3964
+
3965
+ /* Status colors - Offline (dark gray) */
3966
+ --myio-chip-offline-bg: #e2e8f0;
3967
+ --myio-chip-offline-fg: #475569;
3968
+ --myio-border-offline: rgba(71, 85, 105, 0.6);
3969
+
3970
+ /* Status colors - No Info (dark orange) */
3971
+ --myio-chip-no-info-bg: #fed7aa;
3972
+ --myio-chip-no-info-fg: #c2410c;
3973
+ --myio-border-no-info: rgba(194, 65, 12, 0.5);
3954
3974
 
3955
- /* Status colors - Offline (gray) */
3956
- --myio-chip-offline-bg: #f1f5f9;
3957
- --myio-chip-offline-fg: #64748b;
3958
- --myio-border-offline: rgba(100, 116, 139, 0.4);
3975
+ /* Status colors - Not Installed (purple) */
3976
+ --myio-chip-not-installed-bg: #e9d5ff;
3977
+ --myio-chip-not-installed-fg: #7c3aed;
3978
+ --myio-border-not-installed: rgba(124, 58, 237, 0.5);
3959
3979
 
3960
3980
  --myio-text-1: #0f172a;
3961
3981
  --myio-text-2: #4b5563;
@@ -4011,26 +4031,55 @@ var CSS_STRING = `
4011
4031
  }
4012
4032
 
4013
4033
  /* Card border states based on device status */
4014
- .myio-ho-card.is-ok {
4034
+
4035
+ /* POWER_ON - Blue */
4036
+ .myio-ho-card.is-power-on {
4015
4037
  border-color: var(--myio-border-ok);
4016
4038
  box-shadow: 0 0 0 2px var(--myio-border-ok), var(--myio-card-shadow);
4017
4039
  }
4018
4040
 
4041
+ /* STANDBY - Green */
4019
4042
  .myio-ho-card.is-standby {
4020
4043
  border-color: var(--myio-border-standby);
4021
4044
  box-shadow: 0 0 0 2px var(--myio-border-standby), var(--myio-card-shadow);
4022
4045
  }
4023
4046
 
4024
- .myio-ho-card.is-alert {
4047
+ /* WARNING - Yellow */
4048
+ .myio-ho-card.is-warning {
4049
+ border-color: var(--myio-border-alert);
4050
+ box-shadow: 0 0 0 2px var(--myio-border-alert), var(--myio-card-shadow);
4051
+ }
4052
+
4053
+ /* MAINTENANCE - Yellow */
4054
+ .myio-ho-card.is-maintenance {
4025
4055
  border-color: var(--myio-border-alert);
4026
4056
  box-shadow: 0 0 0 2px var(--myio-border-alert), var(--myio-card-shadow);
4027
4057
  }
4028
4058
 
4059
+ /* FAILURE - Dark Red */
4029
4060
  .myio-ho-card.is-failure {
4030
4061
  border-color: var(--myio-border-failure);
4031
4062
  box-shadow: 0 0 0 2px var(--myio-border-failure), var(--myio-card-shadow);
4032
4063
  }
4033
4064
 
4065
+ /* POWER_OFF - Light Red */
4066
+ .myio-ho-card.is-power-off {
4067
+ border-color: var(--myio-border-power-off);
4068
+ box-shadow: 0 0 0 2px var(--myio-border-power-off), var(--myio-card-shadow);
4069
+ }
4070
+
4071
+ /* NO_INFO - Dark Orange */
4072
+ .myio-ho-card.is-no-info {
4073
+ border-color: var(--myio-border-no-info);
4074
+ box-shadow: 0 0 0 2px var(--myio-border-no-info), var(--myio-card-shadow);
4075
+ }
4076
+
4077
+ /* NOT_INSTALLED - Purple */
4078
+ .myio-ho-card.is-not-installed {
4079
+ border-color: var(--myio-border-not-installed);
4080
+ box-shadow: 0 0 0 2px var(--myio-border-not-installed), var(--myio-card-shadow);
4081
+ }
4082
+
4034
4083
  /* Header section */
4035
4084
  .myio-ho-card__header {
4036
4085
  display: flex;
@@ -4048,7 +4097,9 @@ var CSS_STRING = `
4048
4097
  margin-top: 2px;
4049
4098
  }
4050
4099
 
4051
- .myio-ho-card.is-alert .myio-ho-card__icon {
4100
+ /* Icon colors based on status */
4101
+ .myio-ho-card.is-warning .myio-ho-card__icon,
4102
+ .myio-ho-card.is-maintenance .myio-ho-card__icon {
4052
4103
  color: var(--myio-chip-alert-fg);
4053
4104
  }
4054
4105
 
@@ -4056,14 +4107,28 @@ var CSS_STRING = `
4056
4107
  color: var(--myio-chip-failure-fg);
4057
4108
  }
4058
4109
 
4110
+ .myio-ho-card.is-power-off .myio-ho-card__icon {
4111
+ color: var(--myio-chip-power-off-fg);
4112
+ }
4113
+
4114
+ .myio-ho-card.is-offline .myio-ho-card__icon {
4115
+ color: var(--myio-chip-offline-fg);
4116
+ }
4117
+
4118
+ .myio-ho-card.is-no-info .myio-ho-card__icon {
4119
+ color: var(--myio-chip-no-info-fg);
4120
+ }
4121
+
4122
+ .myio-ho-card.is-not-installed .myio-ho-card__icon {
4123
+ color: var(--myio-chip-not-installed-fg);
4124
+ }
4125
+
4059
4126
  .myio-ho-card__title {
4060
4127
  flex: 1;
4061
4128
  min-width: 0;
4062
4129
  }
4063
4130
 
4064
- /* Adicione estas duas novas regras ao seu CSS_STRING */
4065
-
4066
- /* Estado Offline - borda cinza */
4131
+ /* OFFLINE - Dark Gray */
4067
4132
  .myio-ho-card.is-offline {
4068
4133
  border-color: var(--myio-border-offline);
4069
4134
  box-shadow: 0 0 0 2px var(--myio-border-offline), var(--myio-card-shadow);
@@ -4458,6 +4523,37 @@ var CSS_STRING = `
4458
4523
  color: var(--myio-chip-offline-fg);
4459
4524
  }
4460
4525
 
4526
+ /* New chip classes aligned with getCardStateClass */
4527
+ .chip--power-on {
4528
+ background: var(--myio-chip-ok-bg);
4529
+ color: var(--myio-chip-ok-fg);
4530
+ }
4531
+
4532
+ .chip--warning {
4533
+ background: var(--myio-chip-alert-bg);
4534
+ color: var(--myio-chip-alert-fg);
4535
+ }
4536
+
4537
+ .chip--maintenance {
4538
+ background: var(--myio-chip-alert-bg);
4539
+ color: var(--myio-chip-alert-fg);
4540
+ }
4541
+
4542
+ .chip--power-off {
4543
+ background: var(--myio-chip-power-off-bg);
4544
+ color: var(--myio-chip-power-off-fg);
4545
+ }
4546
+
4547
+ .chip--no-info {
4548
+ background: var(--myio-chip-no-info-bg);
4549
+ color: var(--myio-chip-no-info-fg);
4550
+ }
4551
+
4552
+ .chip--not-installed {
4553
+ background: var(--myio-chip-not-installed-bg);
4554
+ color: var(--myio-chip-not-installed-fg);
4555
+ }
4556
+
4461
4557
  /* Status indicator dot for power metric */
4462
4558
  .status-dot {
4463
4559
  width: 8px;
@@ -4491,6 +4587,31 @@ var CSS_STRING = `
4491
4587
  background: var(--myio-muted);
4492
4588
  }
4493
4589
 
4590
+ /* New dot classes aligned with getCardStateClass */
4591
+ .status-dot.dot--power-on {
4592
+ background: var(--myio-chip-ok-fg);
4593
+ }
4594
+
4595
+ .status-dot.dot--warning {
4596
+ background: var(--myio-chip-alert-fg);
4597
+ }
4598
+
4599
+ .status-dot.dot--maintenance {
4600
+ background: var(--myio-chip-alert-fg);
4601
+ }
4602
+
4603
+ .status-dot.dot--power-off {
4604
+ background: var(--myio-chip-power-off-fg);
4605
+ }
4606
+
4607
+ .status-dot.dot--no-info {
4608
+ background: var(--myio-chip-no-info-fg);
4609
+ }
4610
+
4611
+ .status-dot.dot--not-installed {
4612
+ background: var(--myio-chip-not-installed-fg);
4613
+ }
4614
+
4494
4615
  /* Primary metric */
4495
4616
  .myio-ho-card__primary {
4496
4617
  margin-bottom: 14px;
@@ -4673,6 +4794,215 @@ var CSS_STRING = `
4673
4794
  transition: none;
4674
4795
  }
4675
4796
  }
4797
+
4798
+ /* ============================================
4799
+ DEBUG TOOLTIP STYLES (Premium)
4800
+ ============================================ */
4801
+
4802
+ .has-debug-tooltip {
4803
+ cursor: help !important;
4804
+ }
4805
+
4806
+ .has-debug-tooltip::after {
4807
+ content: '\u{1F41B}';
4808
+ position: absolute;
4809
+ top: -4px;
4810
+ right: -4px;
4811
+ font-size: 10px;
4812
+ z-index: 1;
4813
+ }
4814
+
4815
+ .debug-tooltip-container {
4816
+ position: absolute;
4817
+ bottom: calc(100% + 8px);
4818
+ left: 50%;
4819
+ transform: translateX(-50%);
4820
+ z-index: 10000;
4821
+ pointer-events: none;
4822
+ opacity: 0;
4823
+ transition: opacity 0.2s ease, transform 0.2s ease;
4824
+ }
4825
+
4826
+ .has-debug-tooltip:hover .debug-tooltip-container {
4827
+ opacity: 1;
4828
+ pointer-events: auto;
4829
+ }
4830
+
4831
+ .debug-tooltip {
4832
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
4833
+ border: 1px solid rgba(99, 102, 241, 0.3);
4834
+ border-radius: 12px;
4835
+ box-shadow:
4836
+ 0 20px 40px rgba(0, 0, 0, 0.4),
4837
+ 0 0 0 1px rgba(255, 255, 255, 0.05),
4838
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
4839
+ min-width: 340px;
4840
+ max-width: 420px;
4841
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4842
+ font-size: 12px;
4843
+ color: #e2e8f0;
4844
+ overflow: hidden;
4845
+ }
4846
+
4847
+ .debug-tooltip__header {
4848
+ display: flex;
4849
+ align-items: center;
4850
+ gap: 8px;
4851
+ padding: 12px 16px;
4852
+ background: linear-gradient(90deg, rgba(99, 102, 241, 0.2) 0%, rgba(139, 92, 246, 0.1) 100%);
4853
+ border-bottom: 1px solid rgba(99, 102, 241, 0.2);
4854
+ }
4855
+
4856
+ .debug-tooltip__icon {
4857
+ font-size: 16px;
4858
+ }
4859
+
4860
+ .debug-tooltip__title {
4861
+ font-weight: 700;
4862
+ font-size: 13px;
4863
+ color: #f1f5f9;
4864
+ letter-spacing: 0.5px;
4865
+ text-transform: uppercase;
4866
+ }
4867
+
4868
+ .debug-tooltip__content {
4869
+ padding: 12px 16px;
4870
+ max-height: 400px;
4871
+ overflow-y: auto;
4872
+ }
4873
+
4874
+ .debug-tooltip__section {
4875
+ margin-bottom: 14px;
4876
+ padding-bottom: 12px;
4877
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
4878
+ }
4879
+
4880
+ .debug-tooltip__section:last-child {
4881
+ margin-bottom: 0;
4882
+ padding-bottom: 0;
4883
+ border-bottom: none;
4884
+ }
4885
+
4886
+ .debug-tooltip__section-title {
4887
+ font-size: 11px;
4888
+ font-weight: 600;
4889
+ color: #94a3b8;
4890
+ text-transform: uppercase;
4891
+ letter-spacing: 0.8px;
4892
+ margin-bottom: 10px;
4893
+ display: flex;
4894
+ align-items: center;
4895
+ gap: 6px;
4896
+ }
4897
+
4898
+ .debug-tooltip__row {
4899
+ display: flex;
4900
+ justify-content: space-between;
4901
+ align-items: flex-start;
4902
+ padding: 4px 0;
4903
+ gap: 12px;
4904
+ }
4905
+
4906
+ .debug-tooltip__row--full {
4907
+ flex-direction: column;
4908
+ gap: 4px;
4909
+ }
4910
+
4911
+ .debug-tooltip__label {
4912
+ color: #94a3b8;
4913
+ font-size: 11px;
4914
+ flex-shrink: 0;
4915
+ }
4916
+
4917
+ .debug-tooltip__value {
4918
+ color: #f1f5f9;
4919
+ font-weight: 500;
4920
+ text-align: right;
4921
+ word-break: break-all;
4922
+ }
4923
+
4924
+ .debug-tooltip__row--full .debug-tooltip__value {
4925
+ text-align: left;
4926
+ background: rgba(0, 0, 0, 0.3);
4927
+ padding: 6px 10px;
4928
+ border-radius: 6px;
4929
+ font-size: 11px;
4930
+ width: 100%;
4931
+ box-sizing: border-box;
4932
+ }
4933
+
4934
+ .debug-tooltip__value--mono {
4935
+ font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
4936
+ font-size: 11px;
4937
+ background: rgba(0, 0, 0, 0.3);
4938
+ padding: 2px 6px;
4939
+ border-radius: 4px;
4940
+ }
4941
+
4942
+ .debug-tooltip__value--highlight {
4943
+ color: #a5b4fc;
4944
+ font-style: italic;
4945
+ }
4946
+
4947
+ .debug-tooltip__badge {
4948
+ display: inline-block;
4949
+ padding: 2px 8px;
4950
+ border-radius: 4px;
4951
+ font-size: 10px;
4952
+ font-weight: 600;
4953
+ text-transform: uppercase;
4954
+ letter-spacing: 0.5px;
4955
+ background: rgba(99, 102, 241, 0.3);
4956
+ color: #a5b4fc;
4957
+ }
4958
+
4959
+ .debug-tooltip__badge--energy {
4960
+ background: rgba(59, 130, 246, 0.3);
4961
+ color: #93c5fd;
4962
+ }
4963
+
4964
+ .debug-tooltip__badge--water {
4965
+ background: rgba(6, 182, 212, 0.3);
4966
+ color: #67e8f9;
4967
+ }
4968
+
4969
+ .debug-tooltip__badge--temperature {
4970
+ background: rgba(249, 115, 22, 0.3);
4971
+ color: #fdba74;
4972
+ }
4973
+
4974
+ /* Tooltip arrow */
4975
+ .debug-tooltip::after {
4976
+ content: '';
4977
+ position: absolute;
4978
+ bottom: -6px;
4979
+ left: 50%;
4980
+ transform: translateX(-50%) rotate(45deg);
4981
+ width: 12px;
4982
+ height: 12px;
4983
+ background: #0f172a;
4984
+ border-right: 1px solid rgba(99, 102, 241, 0.3);
4985
+ border-bottom: 1px solid rgba(99, 102, 241, 0.3);
4986
+ }
4987
+
4988
+ /* Scrollbar styling for tooltip content */
4989
+ .debug-tooltip__content::-webkit-scrollbar {
4990
+ width: 6px;
4991
+ }
4992
+
4993
+ .debug-tooltip__content::-webkit-scrollbar-track {
4994
+ background: rgba(0, 0, 0, 0.2);
4995
+ border-radius: 3px;
4996
+ }
4997
+
4998
+ .debug-tooltip__content::-webkit-scrollbar-thumb {
4999
+ background: rgba(99, 102, 241, 0.4);
5000
+ border-radius: 3px;
5001
+ }
5002
+
5003
+ .debug-tooltip__content::-webkit-scrollbar-thumb:hover {
5004
+ background: rgba(99, 102, 241, 0.6);
5005
+ }
4676
5006
  `;
4677
5007
 
4678
5008
  // src/thingsboard/main-dashboard-shopping/v-4.0.0/head-office/card-head-office.icons.ts
@@ -4827,46 +5157,296 @@ var DEFAULT_I18N = {
4827
5157
  menu_settings: "Configura\xE7\xF5es"
4828
5158
  };
4829
5159
 
4830
- // src/thingsboard/main-dashboard-shopping/v-4.0.0/card/head-office/card-head-office.js
4831
- var CSS_TAG = "head-office-card-v1";
4832
- function ensureCss() {
4833
- if (!document.querySelector(`style[data-myio-css="${CSS_TAG}"]`)) {
4834
- const style = document.createElement("style");
4835
- style.setAttribute("data-myio-css", CSS_TAG);
4836
- style.textContent = CSS_STRING;
4837
- document.head.appendChild(style);
4838
- }
5160
+ // src/components/temperature/utils.ts
5161
+ var DAY_PERIODS = [
5162
+ { id: "madrugada", label: "Madrugada (00h-06h)", startHour: 0, endHour: 6 },
5163
+ { id: "manha", label: "Manh\xE3 (06h-12h)", startHour: 6, endHour: 12 },
5164
+ { id: "tarde", label: "Tarde (12h-18h)", startHour: 12, endHour: 18 },
5165
+ { id: "noite", label: "Noite (18h-24h)", startHour: 18, endHour: 24 }
5166
+ ];
5167
+ var DEFAULT_CLAMP_RANGE = { min: 15, max: 40 };
5168
+ function getTodaySoFar() {
5169
+ const now = /* @__PURE__ */ new Date();
5170
+ const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
5171
+ return {
5172
+ startTs: startOfDay.getTime(),
5173
+ endTs: now.getTime()
5174
+ };
4839
5175
  }
4840
- function normalizeParams(params) {
4841
- if (!params || !params.entityObject) {
4842
- throw new Error("renderCardCompenteHeadOffice: entityObject is required");
5176
+ var CHART_COLORS = [
5177
+ "#1976d2",
5178
+ // Blue
5179
+ "#FF6B6B",
5180
+ // Red
5181
+ "#4CAF50",
5182
+ // Green
5183
+ "#FF9800",
5184
+ // Orange
5185
+ "#9C27B0",
5186
+ // Purple
5187
+ "#00BCD4",
5188
+ // Cyan
5189
+ "#E91E63",
5190
+ // Pink
5191
+ "#795548"
5192
+ // Brown
5193
+ ];
5194
+ async function fetchTemperatureData(token, deviceId, startTs, endTs) {
5195
+ const url = `/api/plugins/telemetry/DEVICE/${deviceId}/values/timeseries?keys=temperature&startTs=${encodeURIComponent(startTs)}&endTs=${encodeURIComponent(endTs)}&limit=50000&agg=NONE`;
5196
+ const response = await fetch(url, {
5197
+ headers: {
5198
+ "X-Authorization": `Bearer ${token}`,
5199
+ "Content-Type": "application/json"
5200
+ }
5201
+ });
5202
+ if (!response.ok) {
5203
+ throw new Error(`Failed to fetch temperature data: ${response.status}`);
4843
5204
  }
4844
- const entityObject = params.entityObject;
4845
- if (!entityObject.entityId) {
4846
- console.warn("renderCardCompenteHeadOffice: entityId is missing, generating temporary ID");
4847
- entityObject.entityId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
5205
+ const data = await response.json();
5206
+ return data?.temperature || [];
5207
+ }
5208
+ function clampTemperature(value, range = DEFAULT_CLAMP_RANGE) {
5209
+ const num = Number(value || 0);
5210
+ if (num < range.min) return range.min;
5211
+ if (num > range.max) return range.max;
5212
+ return num;
5213
+ }
5214
+ function calculateStats(data, clampRange = DEFAULT_CLAMP_RANGE) {
5215
+ if (data.length === 0) {
5216
+ return { avg: 0, min: 0, max: 0, count: 0 };
4848
5217
  }
5218
+ const values = data.map((item) => clampTemperature(item.value, clampRange));
5219
+ const sum = values.reduce((acc, v) => acc + v, 0);
4849
5220
  return {
4850
- entityObject,
4851
- i18n: { ...DEFAULT_I18N, ...params.i18n || {} },
4852
- enableSelection: Boolean(params.enableSelection),
4853
- enableDragDrop: Boolean(params.enableDragDrop),
4854
- useNewComponents: Boolean(params.useNewComponents),
4855
- // RFC-0091: Configurable delay time for connection status check (default 15 minutes)
4856
- delayTimeConnectionInMins: params.delayTimeConnectionInMins ?? 15,
4857
- callbacks: {
4858
- handleActionDashboard: params.handleActionDashboard,
4859
- handleActionReport: params.handleActionReport,
4860
- handleActionSettings: params.handleActionSettings,
4861
- handleSelect: params.handleSelect,
4862
- handInfo: params.handInfo,
4863
- handleClickCard: params.handleClickCard
4864
- }
5221
+ avg: sum / values.length,
5222
+ min: Math.min(...values),
5223
+ max: Math.max(...values),
5224
+ count: values.length
4865
5225
  };
4866
5226
  }
4867
- function getIconSvg(deviceType, domain) {
4868
- if (domain === "water") {
4869
- return Icons.waterDrop;
5227
+ function interpolateTemperature(data, options) {
5228
+ const { intervalMinutes, startTs, endTs, clampRange = DEFAULT_CLAMP_RANGE } = options;
5229
+ const intervalMs = intervalMinutes * 60 * 1e3;
5230
+ if (data.length === 0) {
5231
+ return [];
5232
+ }
5233
+ const sortedData = [...data].sort((a, b) => a.ts - b.ts);
5234
+ const result = [];
5235
+ let lastKnownValue = clampTemperature(sortedData[0].value, clampRange);
5236
+ let dataIndex = 0;
5237
+ for (let ts = startTs; ts <= endTs; ts += intervalMs) {
5238
+ while (dataIndex < sortedData.length - 1 && sortedData[dataIndex + 1].ts <= ts) {
5239
+ dataIndex++;
5240
+ }
5241
+ const currentData = sortedData[dataIndex];
5242
+ if (currentData && Math.abs(currentData.ts - ts) < intervalMs) {
5243
+ lastKnownValue = clampTemperature(currentData.value, clampRange);
5244
+ }
5245
+ result.push({
5246
+ ts,
5247
+ value: lastKnownValue
5248
+ });
5249
+ }
5250
+ return result;
5251
+ }
5252
+ function aggregateByDay(data, clampRange = DEFAULT_CLAMP_RANGE) {
5253
+ if (data.length === 0) {
5254
+ return [];
5255
+ }
5256
+ const dayMap = /* @__PURE__ */ new Map();
5257
+ data.forEach((item) => {
5258
+ const date = new Date(item.ts);
5259
+ const dateKey = date.toISOString().split("T")[0];
5260
+ if (!dayMap.has(dateKey)) {
5261
+ dayMap.set(dateKey, []);
5262
+ }
5263
+ dayMap.get(dateKey).push(item);
5264
+ });
5265
+ const result = [];
5266
+ dayMap.forEach((dayData, dateKey) => {
5267
+ const values = dayData.map((item) => clampTemperature(item.value, clampRange));
5268
+ const sum = values.reduce((acc, v) => acc + v, 0);
5269
+ result.push({
5270
+ date: dateKey,
5271
+ dateTs: new Date(dateKey).getTime(),
5272
+ avg: sum / values.length,
5273
+ min: Math.min(...values),
5274
+ max: Math.max(...values),
5275
+ count: values.length
5276
+ });
5277
+ });
5278
+ return result.sort((a, b) => a.dateTs - b.dateTs);
5279
+ }
5280
+ function filterByDayPeriods(data, selectedPeriods) {
5281
+ if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
5282
+ return data;
5283
+ }
5284
+ return data.filter((item) => {
5285
+ const date = new Date(item.ts);
5286
+ const hour = date.getHours();
5287
+ return selectedPeriods.some((periodId) => {
5288
+ const period = DAY_PERIODS.find((p) => p.id === periodId);
5289
+ if (!period) return false;
5290
+ return hour >= period.startHour && hour < period.endHour;
5291
+ });
5292
+ });
5293
+ }
5294
+ function getSelectedPeriodsLabel(selectedPeriods) {
5295
+ if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
5296
+ return "Todos os per\xEDodos";
5297
+ }
5298
+ if (selectedPeriods.length === 1) {
5299
+ const period = DAY_PERIODS.find((p) => p.id === selectedPeriods[0]);
5300
+ return period?.label || "";
5301
+ }
5302
+ return `${selectedPeriods.length} per\xEDodos selecionados`;
5303
+ }
5304
+ function formatTemperature(value, decimals = 1) {
5305
+ if (value === null || value === void 0 || isNaN(value)) {
5306
+ return "\u2014";
5307
+ }
5308
+ return `${value.toFixed(decimals)}\xB0C`;
5309
+ }
5310
+ function exportTemperatureCSV(data, deviceLabel, stats, startDate, endDate) {
5311
+ if (data.length === 0) {
5312
+ console.warn("No data to export");
5313
+ return;
5314
+ }
5315
+ const BOM = "\uFEFF";
5316
+ let csvContent = BOM;
5317
+ csvContent += `Relat\xF3rio de Temperatura - ${deviceLabel}
5318
+ `;
5319
+ csvContent += `Per\xEDodo: ${startDate} at\xE9 ${endDate}
5320
+ `;
5321
+ csvContent += `M\xE9dia: ${formatTemperature(stats.avg)}
5322
+ `;
5323
+ csvContent += `M\xEDnima: ${formatTemperature(stats.min)}
5324
+ `;
5325
+ csvContent += `M\xE1xima: ${formatTemperature(stats.max)}
5326
+ `;
5327
+ csvContent += `Total de leituras: ${stats.count}
5328
+ `;
5329
+ csvContent += "\n";
5330
+ csvContent += "Data/Hora,Temperatura (\xB0C)\n";
5331
+ data.forEach((item) => {
5332
+ const date = new Date(item.ts).toLocaleString("pt-BR");
5333
+ const temp = Number(item.value).toFixed(2);
5334
+ csvContent += `"${date}",${temp}
5335
+ `;
5336
+ });
5337
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
5338
+ const url = URL.createObjectURL(blob);
5339
+ const link = document.createElement("a");
5340
+ link.href = url;
5341
+ link.download = `temperatura_${deviceLabel.replace(/\s+/g, "_")}_${startDate}_${endDate}.csv`;
5342
+ document.body.appendChild(link);
5343
+ link.click();
5344
+ document.body.removeChild(link);
5345
+ URL.revokeObjectURL(url);
5346
+ }
5347
+ var DARK_THEME = {
5348
+ background: "rgba(0, 0, 0, 0.85)",
5349
+ surface: "#1a1f28",
5350
+ text: "#ffffff",
5351
+ textMuted: "rgba(255, 255, 255, 0.7)",
5352
+ border: "rgba(255, 255, 255, 0.1)",
5353
+ primary: "#1976d2",
5354
+ success: "#4CAF50",
5355
+ warning: "#FF9800",
5356
+ danger: "#f44336",
5357
+ chartLine: "#1976d2",
5358
+ chartGrid: "rgba(255, 255, 255, 0.1)"
5359
+ };
5360
+ var LIGHT_THEME = {
5361
+ background: "rgba(0, 0, 0, 0.6)",
5362
+ surface: "#ffffff",
5363
+ text: "#333333",
5364
+ textMuted: "#666666",
5365
+ border: "#e0e0e0",
5366
+ primary: "#1976d2",
5367
+ success: "#4CAF50",
5368
+ warning: "#FF9800",
5369
+ danger: "#f44336",
5370
+ chartLine: "#1976d2",
5371
+ chartGrid: "#e0e0e0"
5372
+ };
5373
+ function getThemeColors(theme) {
5374
+ return theme === "dark" ? DARK_THEME : LIGHT_THEME;
5375
+ }
5376
+
5377
+ // src/utils/logHelper.js
5378
+ function createLogHelper(debugActive = false) {
5379
+ return {
5380
+ log: function(...args) {
5381
+ if (debugActive) {
5382
+ console.log(...args);
5383
+ }
5384
+ },
5385
+ warn: function(...args) {
5386
+ if (debugActive) {
5387
+ console.warn(...args);
5388
+ }
5389
+ },
5390
+ error: function(...args) {
5391
+ console.error(...args);
5392
+ }
5393
+ };
5394
+ }
5395
+ var LogHelper = createLogHelper(false);
5396
+
5397
+ // src/thingsboard/main-dashboard-shopping/v-4.0.0/card/head-office/card-head-office.js
5398
+ var LABEL_CHAR_LIMIT = 18;
5399
+ var DEFAUL_DELAY_TIME_CONNECTION_IN_MINS = 1440;
5400
+ var CSS_TAG = "head-office-card-v1";
5401
+ function ensureCss() {
5402
+ if (!document.querySelector(`style[data-myio-css="${CSS_TAG}"]`)) {
5403
+ const style = document.createElement("style");
5404
+ style.setAttribute("data-myio-css", CSS_TAG);
5405
+ style.textContent = CSS_STRING;
5406
+ document.head.appendChild(style);
5407
+ }
5408
+ }
5409
+ function normalizeParams(params) {
5410
+ if (!params || !params.entityObject) {
5411
+ throw new Error("renderCardCompenteHeadOffice: entityObject is required");
5412
+ }
5413
+ const LogHelper2 = createLogHelper(params.debugActive ?? false);
5414
+ const entityObject = params.entityObject;
5415
+ if (!entityObject.entityId) {
5416
+ LogHelper2.warn("[CardHeadOffice] entityId is missing, generating temporary ID");
5417
+ entityObject.entityId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
5418
+ }
5419
+ if (!params.delayTimeConnectionInMins) {
5420
+ LogHelper2.warn(
5421
+ `[CardHeadOffice] delayTimeConnectionInMins is missing, defaulting to ${DEFAUL_DELAY_TIME_CONNECTION_IN_MINS} mins`
5422
+ );
5423
+ }
5424
+ return {
5425
+ entityObject,
5426
+ i18n: { ...DEFAULT_I18N, ...params.i18n || {} },
5427
+ enableSelection: Boolean(params.enableSelection),
5428
+ enableDragDrop: Boolean(params.enableDragDrop),
5429
+ useNewComponents: Boolean(params.useNewComponents),
5430
+ // RFC-0091: Configurable delay time for connection status check (default 15 minutes)
5431
+ delayTimeConnectionInMins: params.delayTimeConnectionInMins ?? DEFAUL_DELAY_TIME_CONNECTION_IN_MINS,
5432
+ // Debug options
5433
+ debugActive: params.debugActive ?? false,
5434
+ activeTooltipDebug: params.activeTooltipDebug ?? false,
5435
+ // LogHelper instance for this card
5436
+ LogHelper: LogHelper2,
5437
+ callbacks: {
5438
+ handleActionDashboard: params.handleActionDashboard,
5439
+ handleActionReport: params.handleActionReport,
5440
+ handleActionSettings: params.handleActionSettings,
5441
+ handleSelect: params.handleSelect,
5442
+ handInfo: params.handInfo,
5443
+ handleClickCard: params.handleClickCard
5444
+ }
5445
+ };
5446
+ }
5447
+ function getIconSvg(deviceType, domain) {
5448
+ if (domain === "water") {
5449
+ return Icons.waterDrop;
4870
5450
  }
4871
5451
  if (domain === "temperature") {
4872
5452
  return Icons.thermometer;
@@ -4891,16 +5471,10 @@ function formatValueByDomain(value, domain) {
4891
5471
  return formatWaterVolumeM3(value);
4892
5472
  }
4893
5473
  if (domain === "temperature") {
4894
- return formatTemperature(value);
5474
+ return formatTemperature(value, 0);
4895
5475
  }
4896
5476
  return formatEnergy(value);
4897
5477
  }
4898
- function formatTemperature(temp) {
4899
- if (temp === null || temp === void 0 || isNaN(temp)) {
4900
- return "\u2014";
4901
- }
4902
- return `${temp.toFixed(0)}\xB0C`;
4903
- }
4904
5478
  function calculateConsumptionPercentage(target, consumption) {
4905
5479
  const numericTarget = Number(target);
4906
5480
  const numericConsumption = Number(consumption);
@@ -4915,32 +5489,32 @@ function getStatusInfo(deviceStatus, i18n, domain) {
4915
5489
  // --- Novos Status de Temperatura ---
4916
5490
  case "normal":
4917
5491
  return { chipClass: "chip--ok", label: "Normal" };
4918
- // Verde/Azul
4919
5492
  case "cold":
4920
5493
  return { chipClass: "chip--standby", label: "Frio" };
4921
- // Azul claro/Ciano
4922
5494
  case "hot":
4923
5495
  return { chipClass: "chip--alert", label: "Quente" };
4924
- // Laranja/Amarelo
4925
- // --- Status Existentes ---
5496
+ // --- Status Existentes (aligned with getCardStateClass) ---
4926
5497
  case DeviceStatusType.POWER_ON:
4927
5498
  if (domain === "water") {
4928
- return { chipClass: "chip--ok", label: i18n.in_operation_water };
5499
+ return { chipClass: "chip--power-on", label: i18n.in_operation_water };
4929
5500
  }
4930
- return { chipClass: "chip--ok", label: i18n.in_operation };
5501
+ return { chipClass: "chip--power-on", label: i18n.in_operation };
4931
5502
  case DeviceStatusType.STANDBY:
4932
5503
  return { chipClass: "chip--standby", label: i18n.standby };
4933
5504
  case DeviceStatusType.WARNING:
4934
- return { chipClass: "chip--alert", label: i18n.alert };
5505
+ return { chipClass: "chip--warning", label: i18n.alert };
5506
+ case DeviceStatusType.MAINTENANCE:
5507
+ return { chipClass: "chip--maintenance", label: i18n.maintenance };
4935
5508
  case DeviceStatusType.FAILURE:
4936
- case DeviceStatusType.POWER_OFF:
4937
5509
  return { chipClass: "chip--failure", label: i18n.failure };
4938
- case DeviceStatusType.MAINTENANCE:
4939
- return { chipClass: "chip--alert", label: i18n.maintenance };
4940
- case DeviceStatusType.NOT_INSTALLED:
4941
- return { chipClass: "chip--offline", label: i18n.not_installed };
4942
- // Default (Cai aqui se não achar 'normal', 'hot' etc)
5510
+ case DeviceStatusType.POWER_OFF:
5511
+ return { chipClass: "chip--power-off", label: i18n.power_off || i18n.failure };
5512
+ case DeviceStatusType.OFFLINE:
5513
+ return { chipClass: "chip--offline", label: i18n.offline };
4943
5514
  case DeviceStatusType.NO_INFO:
5515
+ return { chipClass: "chip--no-info", label: i18n.no_info || i18n.offline };
5516
+ case DeviceStatusType.NOT_INSTALLED:
5517
+ return { chipClass: "chip--not-installed", label: i18n.not_installed };
4944
5518
  default:
4945
5519
  return { chipClass: "chip--offline", label: i18n.offline };
4946
5520
  }
@@ -4948,23 +5522,32 @@ function getStatusInfo(deviceStatus, i18n, domain) {
4948
5522
  function getCardStateClass(deviceStatus) {
4949
5523
  switch (deviceStatus) {
4950
5524
  case DeviceStatusType.POWER_ON:
4951
- return "is-ok";
5525
+ return "is-power-on";
4952
5526
  // Blue border
4953
5527
  case DeviceStatusType.STANDBY:
4954
5528
  return "is-standby";
4955
5529
  // Green border
4956
5530
  case DeviceStatusType.WARNING:
5531
+ return "is-warning";
5532
+ // Yellow border
4957
5533
  case DeviceStatusType.MAINTENANCE:
4958
- return "is-alert";
5534
+ return "is-maintenance";
4959
5535
  // Yellow border
4960
5536
  case DeviceStatusType.FAILURE:
4961
- case DeviceStatusType.POWER_OFF:
4962
5537
  return "is-failure";
4963
- // Red border
5538
+ // Dark Red border
5539
+ case DeviceStatusType.POWER_OFF:
5540
+ return "is-power-off";
5541
+ // Light Red border
5542
+ case DeviceStatusType.OFFLINE:
5543
+ return "is-offline";
5544
+ // Dark Gray border
4964
5545
  case DeviceStatusType.NO_INFO:
5546
+ return "is-no-info";
5547
+ // Dark Orange border
4965
5548
  case DeviceStatusType.NOT_INSTALLED:
4966
- return "is-offline";
4967
- // Gray border
5549
+ return "is-not-installed";
5550
+ // Purple border
4968
5551
  default:
4969
5552
  return "";
4970
5553
  }
@@ -4978,17 +5561,25 @@ function getStatusDotClass(deviceStatus) {
4978
5561
  return "dot--standby";
4979
5562
  case "hot":
4980
5563
  return "dot--alert";
4981
- // --- Status Existentes ---
5564
+ // --- Status Existentes (aligned with getCardStateClass) ---
4982
5565
  case DeviceStatusType.POWER_ON:
4983
- return "dot--ok";
5566
+ return "dot--power-on";
4984
5567
  case DeviceStatusType.STANDBY:
4985
5568
  return "dot--standby";
4986
5569
  case DeviceStatusType.WARNING:
5570
+ return "dot--warning";
4987
5571
  case DeviceStatusType.MAINTENANCE:
4988
- return "dot--alert";
5572
+ return "dot--maintenance";
4989
5573
  case DeviceStatusType.FAILURE:
4990
- case DeviceStatusType.POWER_OFF:
4991
5574
  return "dot--failure";
5575
+ case DeviceStatusType.POWER_OFF:
5576
+ return "dot--power-off";
5577
+ case DeviceStatusType.OFFLINE:
5578
+ return "dot--offline";
5579
+ case DeviceStatusType.NO_INFO:
5580
+ return "dot--no-info";
5581
+ case DeviceStatusType.NOT_INSTALLED:
5582
+ return "dot--not-installed";
4992
5583
  default:
4993
5584
  return "dot--offline";
4994
5585
  }
@@ -5012,7 +5603,13 @@ function buildDOM(state) {
5012
5603
  titleSection.className = "myio-ho-card__title";
5013
5604
  const nameEl = document.createElement("div");
5014
5605
  nameEl.className = "myio-ho-card__name";
5015
- nameEl.textContent = entityObject.labelOrName || "Unknown Device";
5606
+ const fullName = entityObject.labelOrName || "Unknown Device";
5607
+ if (fullName.length > LABEL_CHAR_LIMIT) {
5608
+ nameEl.textContent = fullName.slice(0, LABEL_CHAR_LIMIT) + "\u2026";
5609
+ nameEl.title = fullName;
5610
+ } else {
5611
+ nameEl.textContent = fullName;
5612
+ }
5016
5613
  titleSection.appendChild(nameEl);
5017
5614
  if (entityObject.deviceIdentifier) {
5018
5615
  const codeEl = document.createElement("div");
@@ -5141,29 +5738,207 @@ function buildDOM(state) {
5141
5738
  root.appendChild(footer);
5142
5739
  return root;
5143
5740
  }
5144
- function verifyOfflineStatus(entityObject, delayTimeInMins = 15) {
5741
+ function buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins) {
5742
+ const formatTimestamp = (ts) => {
5743
+ if (!ts) return "N/A";
5744
+ const d = new Date(ts);
5745
+ return d.toLocaleString("pt-BR", {
5746
+ day: "2-digit",
5747
+ month: "2-digit",
5748
+ year: "numeric",
5749
+ hour: "2-digit",
5750
+ minute: "2-digit",
5751
+ second: "2-digit"
5752
+ });
5753
+ };
5754
+ return {
5755
+ // Entity identification
5756
+ entityId: entityObject.entityId || "N/A",
5757
+ name: entityObject.name || entityObject.nameEl || "N/A",
5758
+ domain: entityObject.domain || "energy",
5759
+ // Status decision chain
5760
+ originalDeviceStatus: entityObject._originalDeviceStatus || entityObject.deviceStatus,
5761
+ finalDeviceStatus: entityObject.deviceStatus,
5762
+ connectionStatus: entityObject.connectionStatus || "N/A",
5763
+ statusDecisionSource,
5764
+ // Visual output
5765
+ stateClass,
5766
+ chipClass: statusInfo.chipClass,
5767
+ chipLabel: statusInfo.label,
5768
+ // Connection timestamps
5769
+ lastConnectTime: formatTimestamp(entityObject.lastConnectTime),
5770
+ lastDisconnectTime: formatTimestamp(entityObject.lastDisconnectTime),
5771
+ delayTimeConnectionInMins,
5772
+ // Raw values
5773
+ val: entityObject.val,
5774
+ consumptionTargetValue: entityObject.consumptionTargetValue,
5775
+ deviceType: entityObject.deviceType || "N/A"
5776
+ };
5777
+ }
5778
+ function attachDebugTooltip(element, debugInfo) {
5779
+ const existingTooltip = element.querySelector(".debug-tooltip-container");
5780
+ if (existingTooltip) {
5781
+ existingTooltip.remove();
5782
+ }
5783
+ const tooltipContainer = document.createElement("div");
5784
+ tooltipContainer.className = "debug-tooltip-container";
5785
+ const tooltip = document.createElement("div");
5786
+ tooltip.className = "debug-tooltip";
5787
+ tooltip.innerHTML = `
5788
+ <div class="debug-tooltip__header">
5789
+ <span class="debug-tooltip__icon">\u{1F50D}</span>
5790
+ <span class="debug-tooltip__title">Debug Info</span>
5791
+ </div>
5792
+ <div class="debug-tooltip__content">
5793
+ <div class="debug-tooltip__section">
5794
+ <div class="debug-tooltip__section-title">\u{1F4CB} Identifica\xE7\xE3o</div>
5795
+ <div class="debug-tooltip__row">
5796
+ <span class="debug-tooltip__label">Entity ID:</span>
5797
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.entityId}</span>
5798
+ </div>
5799
+ <div class="debug-tooltip__row">
5800
+ <span class="debug-tooltip__label">Nome:</span>
5801
+ <span class="debug-tooltip__value">${debugInfo.name}</span>
5802
+ </div>
5803
+ <div class="debug-tooltip__row">
5804
+ <span class="debug-tooltip__label">Dom\xEDnio:</span>
5805
+ <span class="debug-tooltip__value debug-tooltip__badge debug-tooltip__badge--${debugInfo.domain}">${debugInfo.domain}</span>
5806
+ </div>
5807
+ </div>
5808
+
5809
+ <div class="debug-tooltip__section">
5810
+ <div class="debug-tooltip__section-title">\u26A1 Decis\xE3o de Status</div>
5811
+ <div class="debug-tooltip__row">
5812
+ <span class="debug-tooltip__label">connectionStatus:</span>
5813
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.connectionStatus}</span>
5814
+ </div>
5815
+ <div class="debug-tooltip__row">
5816
+ <span class="debug-tooltip__label">deviceStatus (final):</span>
5817
+ <span class="debug-tooltip__value debug-tooltip__badge">${debugInfo.finalDeviceStatus}</span>
5818
+ </div>
5819
+ <div class="debug-tooltip__row debug-tooltip__row--full">
5820
+ <span class="debug-tooltip__label">Fonte da decis\xE3o:</span>
5821
+ <span class="debug-tooltip__value debug-tooltip__value--highlight">${debugInfo.statusDecisionSource}</span>
5822
+ </div>
5823
+ </div>
5824
+
5825
+ <div class="debug-tooltip__section">
5826
+ <div class="debug-tooltip__section-title">\u{1F3A8} Output Visual</div>
5827
+ <div class="debug-tooltip__row">
5828
+ <span class="debug-tooltip__label">stateClass:</span>
5829
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.stateClass}</span>
5830
+ </div>
5831
+ <div class="debug-tooltip__row">
5832
+ <span class="debug-tooltip__label">chipClass:</span>
5833
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.chipClass}</span>
5834
+ </div>
5835
+ <div class="debug-tooltip__row">
5836
+ <span class="debug-tooltip__label">chipLabel:</span>
5837
+ <span class="debug-tooltip__value">${debugInfo.chipLabel}</span>
5838
+ </div>
5839
+ </div>
5840
+
5841
+ <div class="debug-tooltip__section">
5842
+ <div class="debug-tooltip__section-title">\u{1F550} Timestamps de Conex\xE3o</div>
5843
+ <div class="debug-tooltip__row">
5844
+ <span class="debug-tooltip__label">lastConnectTime:</span>
5845
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.lastConnectTime}</span>
5846
+ </div>
5847
+ <div class="debug-tooltip__row">
5848
+ <span class="debug-tooltip__label">lastDisconnectTime:</span>
5849
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.lastDisconnectTime}</span>
5850
+ </div>
5851
+ <div class="debug-tooltip__row">
5852
+ <span class="debug-tooltip__label">delayTime:</span>
5853
+ <span class="debug-tooltip__value">${debugInfo.delayTimeConnectionInMins} mins</span>
5854
+ </div>
5855
+ </div>
5856
+
5857
+ <div class="debug-tooltip__section">
5858
+ <div class="debug-tooltip__section-title">\u{1F4CA} Valores</div>
5859
+ <div class="debug-tooltip__row">
5860
+ <span class="debug-tooltip__label">val (consumo):</span>
5861
+ <span class="debug-tooltip__value">${debugInfo.val ?? "N/A"}</span>
5862
+ </div>
5863
+ <div class="debug-tooltip__row">
5864
+ <span class="debug-tooltip__label">target (meta):</span>
5865
+ <span class="debug-tooltip__value">${debugInfo.consumptionTargetValue ?? "N/A"}</span>
5866
+ </div>
5867
+ <div class="debug-tooltip__row">
5868
+ <span class="debug-tooltip__label">deviceType:</span>
5869
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.deviceType}</span>
5870
+ </div>
5871
+ </div>
5872
+ </div>
5873
+ `;
5874
+ tooltipContainer.appendChild(tooltip);
5875
+ element.style.position = "relative";
5876
+ element.style.cursor = "help";
5877
+ element.appendChild(tooltipContainer);
5878
+ element.classList.add("has-debug-tooltip");
5879
+ }
5880
+ function verifyOfflineStatus(entityObject, delayTimeInMins = 15, LogHelper2) {
5145
5881
  const lastConnectionTime = new Date(entityObject.lastConnectTime || 0);
5146
5882
  const lastDisconnectTime = new Date(entityObject.lastDisconnectTime || 0);
5147
5883
  const now = /* @__PURE__ */ new Date();
5148
5884
  const delayTimeInMs = delayTimeInMins * 60 * 1e3;
5149
5885
  const timeSinceConnection = now.getTime() - lastConnectionTime.getTime();
5886
+ let isOffline = false;
5150
5887
  if (lastDisconnectTime.getTime() > lastConnectionTime.getTime()) {
5151
- return false;
5152
- }
5153
- if (timeSinceConnection < delayTimeInMs) {
5154
- return false;
5888
+ isOffline = true;
5889
+ LogHelper2.log(
5890
+ "[CardHeadOffice][ConnectionStatus Verify] Device is OFFLINE because lastDisconnectTime is more recent than lastConnectTime",
5891
+ entityObject.nameEl
5892
+ );
5893
+ } else if (timeSinceConnection > delayTimeInMs) {
5894
+ isOffline = true;
5895
+ LogHelper2.log(
5896
+ "[CardHeadOffice][ConnectionStatus Verify] Device is OFFLINE because lastConnectTime is older than configured delayTimeConnectionInMins:",
5897
+ delayTimeInMins,
5898
+ "for device",
5899
+ entityObject.nameEl
5900
+ );
5901
+ } else {
5902
+ isOffline = false;
5903
+ LogHelper2.log(
5904
+ "[CardHeadOffice][ConnectionStatus Verify] Device is ONLINE because lastConnectTime is within configured delayTimeConnectionInMins:",
5905
+ delayTimeInMins,
5906
+ "for device",
5907
+ entityObject.nameEl
5908
+ );
5155
5909
  }
5156
- return true;
5910
+ return isOffline;
5157
5911
  }
5158
5912
  function paint(root, state) {
5159
- const { entityObject, i18n, delayTimeConnectionInMins, isSelected } = state;
5913
+ const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state;
5914
+ let statusDecisionSource = "unknown";
5160
5915
  if (entityObject.connectionStatus) {
5161
5916
  if (entityObject.connectionStatus === "offline") {
5162
- entityObject.deviceStatus = DeviceStatusType.NO_INFO;
5917
+ LogHelper2.log(
5918
+ "[CardHeadOffice][ConnectionStatus Verify 01] Setting deviceStatus to OFFLINE based on connectionStatus"
5919
+ );
5920
+ entityObject.deviceStatus = DeviceStatusType.OFFLINE;
5921
+ statusDecisionSource = 'connectionStatus === "offline"';
5922
+ } else {
5923
+ LogHelper2.log(
5924
+ "[CardHeadOffice] Device is ONLINE or WAITING based on connectionStatus for device",
5925
+ entityObject.nameEl
5926
+ );
5927
+ statusDecisionSource = `connectionStatus === "${entityObject.connectionStatus}" (kept original deviceStatus)`;
5163
5928
  }
5164
5929
  } else {
5165
- if (verifyOfflineStatus(entityObject, delayTimeConnectionInMins) === false) {
5166
- entityObject.deviceStatus = DeviceStatusType.NO_INFO;
5930
+ if (verifyOfflineStatus(entityObject, delayTimeConnectionInMins, LogHelper2) === false) {
5931
+ LogHelper2.log(
5932
+ "[CardHeadOffice][ConnectionStatus Verify 02] Setting deviceStatus to OFFLINE based on timestamp verification by verifyOfflineStatus METHOD with delayTimeConnectionInMins:",
5933
+ delayTimeConnectionInMins
5934
+ );
5935
+ entityObject.deviceStatus = DeviceStatusType.OFFLINE;
5936
+ statusDecisionSource = `verifyOfflineStatus() returned false (delay: ${delayTimeConnectionInMins} mins)`;
5937
+ } else {
5938
+ LogHelper2.log(
5939
+ `[CardHeadOffice][ConnectionStatus Verify 03] Device is ONLINE with deviceStatus = ${entityObject.deviceStatus} based on timestamp verification for device ${entityObject.nameEl}`
5940
+ );
5941
+ statusDecisionSource = `verifyOfflineStatus() returned true (delay: ${delayTimeConnectionInMins} mins)`;
5167
5942
  }
5168
5943
  }
5169
5944
  const stateClass = getCardStateClass(entityObject.deviceStatus);
@@ -5172,6 +5947,10 @@ function paint(root, state) {
5172
5947
  const chip = root.querySelector(".chip");
5173
5948
  chip.className = `chip ${statusInfo.chipClass}`;
5174
5949
  chip.innerHTML = statusInfo.label;
5950
+ if (activeTooltipDebug) {
5951
+ const debugInfo = buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins);
5952
+ attachDebugTooltip(chip, debugInfo);
5953
+ }
5175
5954
  const primaryValue = formatValueByDomain(entityObject.val, entityObject.domain);
5176
5955
  const numSpan = root.querySelector(".myio-ho-card__value .num");
5177
5956
  const unitSpan = root.querySelector(".myio-ho-card__value .unit");
@@ -5384,6 +6163,7 @@ function renderCardComponentHeadOffice(containerEl, params) {
5384
6163
  }
5385
6164
 
5386
6165
  // src/thingsboard/main-dashboard-shopping/v-5.2.0/card/template-card-v5.js
6166
+ var LABEL_CHAR_LIMIT2 = 18;
5387
6167
  function renderCardComponentV5({
5388
6168
  entityObject,
5389
6169
  handleActionDashboard,
@@ -5900,7 +6680,7 @@ ${rangeText}`;
5900
6680
 
5901
6681
  <div class="device-title-row" style="flex-direction: column; min-height: 38px; text-align: center; width: 100%;">
5902
6682
  <span class="device-title" title="${cardEntity.name}">
5903
- ${cardEntity.name.length > 18 ? cardEntity.name.slice(0, 18) + "\u2026" : cardEntity.name}
6683
+ ${cardEntity.name.length > LABEL_CHAR_LIMIT2 ? cardEntity.name.slice(0, LABEL_CHAR_LIMIT2) + "\u2026" : cardEntity.name}
5904
6684
  </span>
5905
6685
  ${deviceIdentifier ? `
5906
6686
  <span class="device-subtitle" title="${deviceIdentifier}">
@@ -15840,23 +16620,1019 @@ function validateOptions2(options) {
15840
16620
  if (isNaN(level) || level < 0 || level > 100) {
15841
16621
  errors.push("currentLevel must be a number between 0 and 100");
15842
16622
  }
15843
- }
15844
- if (options.limit !== void 0) {
15845
- const limit = Number(options.limit);
15846
- if (isNaN(limit) || limit < 1 || limit > 1e4) {
15847
- errors.push("limit must be a number between 1 and 10000");
16623
+ }
16624
+ if (options.limit !== void 0) {
16625
+ const limit = Number(options.limit);
16626
+ if (isNaN(limit) || limit < 1 || limit > 1e4) {
16627
+ errors.push("limit must be a number between 1 and 10000");
16628
+ }
16629
+ }
16630
+ if (options.aggregation !== void 0) {
16631
+ const validAggregations = ["NONE", "MIN", "MAX", "AVG", "SUM", "COUNT"];
16632
+ if (!validAggregations.includes(options.aggregation)) {
16633
+ errors.push(`aggregation must be one of: ${validAggregations.join(", ")}`);
16634
+ }
16635
+ }
16636
+ if (errors.length > 0) {
16637
+ throw new Error(`Validation failed:
16638
+ - ${errors.join("\n- ")}`);
16639
+ }
16640
+ }
16641
+
16642
+ // src/components/premium-modals/power-limits/types.ts
16643
+ var DEVICE_TYPES = [
16644
+ { value: "ELEVADOR", label: "Elevator" },
16645
+ { value: "ESCADA_ROLANTE", label: "Escalator" },
16646
+ { value: "MOTOR", label: "Motor" },
16647
+ { value: "BOMBA", label: "Pump" },
16648
+ { value: "CHILLER", label: "Chiller" },
16649
+ { value: "AR_CONDICIONADO", label: "Air Conditioner" },
16650
+ { value: "HVAC", label: "HVAC" },
16651
+ { value: "FANCOIL", label: "Fancoil" },
16652
+ { value: "3F_MEDIDOR", label: "Three-phase Meter" }
16653
+ ];
16654
+ var TELEMETRY_TYPES2 = [
16655
+ { value: "consumption", label: "Consumption (kW)", unit: "kW" },
16656
+ { value: "voltage_a", label: "Voltage A (V)", unit: "V" },
16657
+ { value: "voltage_b", label: "Voltage B (V)", unit: "V" },
16658
+ { value: "voltage_c", label: "Voltage C (V)", unit: "V" },
16659
+ { value: "current_a", label: "Current A (A)", unit: "A" },
16660
+ { value: "current_b", label: "Current B (A)", unit: "A" },
16661
+ { value: "current_c", label: "Current C (A)", unit: "A" },
16662
+ { value: "total_current", label: "Total Current (A)", unit: "A" },
16663
+ { value: "fp_a", label: "Power Factor A", unit: "" },
16664
+ { value: "fp_b", label: "Power Factor B", unit: "" },
16665
+ { value: "fp_c", label: "Power Factor C", unit: "" }
16666
+ ];
16667
+ var STATUS_CONFIG = {
16668
+ standBy: { label: "StandBy", color: "#22c55e", bgColor: "rgba(34, 197, 94, 0.1)" },
16669
+ normal: { label: "Normal", color: "#3b82f6", bgColor: "rgba(59, 130, 246, 0.1)" },
16670
+ alert: { label: "Alert", color: "#f59e0b", bgColor: "rgba(245, 158, 11, 0.1)" },
16671
+ failure: { label: "Failure", color: "#ef4444", bgColor: "rgba(239, 68, 68, 0.1)" }
16672
+ };
16673
+
16674
+ // src/components/premium-modals/power-limits/PowerLimitsModalView.ts
16675
+ var PowerLimitsModalView = class {
16676
+ container = null;
16677
+ overlayEl = null;
16678
+ config;
16679
+ formData;
16680
+ isLoading = false;
16681
+ isSaving = false;
16682
+ constructor(config) {
16683
+ this.config = config;
16684
+ this.formData = {
16685
+ deviceType: config.deviceType,
16686
+ telemetryType: config.telemetryType,
16687
+ standby: { baseValue: null, topValue: null },
16688
+ normal: { baseValue: null, topValue: null },
16689
+ alert: { baseValue: null, topValue: null },
16690
+ failure: { baseValue: null, topValue: null }
16691
+ };
16692
+ }
16693
+ render(targetContainer) {
16694
+ this.overlayEl = document.createElement("div");
16695
+ this.overlayEl.className = "myio-power-limits-overlay";
16696
+ this.overlayEl.innerHTML = `
16697
+ <style>${this.getStyles()}</style>
16698
+ <div class="myio-power-limits-card">
16699
+ ${this.renderHeader()}
16700
+ ${this.renderSelectors()}
16701
+ ${this.renderStatusCards()}
16702
+ ${this.renderLoadingState()}
16703
+ ${this.renderErrorState()}
16704
+ ${this.renderSuccessState()}
16705
+ </div>
16706
+ `;
16707
+ const target = targetContainer || document.body;
16708
+ target.appendChild(this.overlayEl);
16709
+ this.container = this.overlayEl.querySelector(".myio-power-limits-card");
16710
+ this.setupEventListeners();
16711
+ requestAnimationFrame(() => {
16712
+ this.overlayEl?.classList.add("active");
16713
+ });
16714
+ return this.overlayEl;
16715
+ }
16716
+ renderHeader() {
16717
+ return `
16718
+ <div class="myio-power-limits-header">
16719
+ <div class="myio-power-limits-title-section">
16720
+ <span class="myio-power-limits-icon">&#x2699;</span>
16721
+ <h2 class="myio-power-limits-title">Power Limits Setup</h2>
16722
+ </div>
16723
+ <div class="myio-power-limits-actions">
16724
+ <button class="myio-btn myio-btn-primary" id="plm-save-btn" type="button">
16725
+ <span class="myio-btn-text">Save</span>
16726
+ <span class="myio-btn-spinner" style="display: none;"></span>
16727
+ </button>
16728
+ <button class="myio-btn myio-btn-secondary" id="plm-reset-btn" type="button">Reset</button>
16729
+ <button class="myio-btn myio-btn-close" id="plm-close-btn" type="button" aria-label="Close">&times;</button>
16730
+ </div>
16731
+ </div>
16732
+ `;
16733
+ }
16734
+ renderSelectors() {
16735
+ const deviceOptions = DEVICE_TYPES.map(
16736
+ (dt) => `<option value="${dt.value}" ${dt.value === this.config.deviceType ? "selected" : ""}>${dt.label}</option>`
16737
+ ).join("");
16738
+ const telemetryOptions = TELEMETRY_TYPES2.map(
16739
+ (tt) => `<option value="${tt.value}" ${tt.value === this.config.telemetryType ? "selected" : ""}>${tt.label}</option>`
16740
+ ).join("");
16741
+ return `
16742
+ <div class="myio-power-limits-selectors">
16743
+ <div class="myio-form-group">
16744
+ <label for="plm-device-type">Device Type</label>
16745
+ <select id="plm-device-type" class="myio-select">
16746
+ ${deviceOptions}
16747
+ </select>
16748
+ </div>
16749
+ <div class="myio-form-group">
16750
+ <label for="plm-telemetry-type">Telemetry Type</label>
16751
+ <select id="plm-telemetry-type" class="myio-select">
16752
+ ${telemetryOptions}
16753
+ </select>
16754
+ </div>
16755
+ </div>
16756
+ `;
16757
+ }
16758
+ renderStatusCards() {
16759
+ const statuses = ["standby", "normal", "alert", "failure"];
16760
+ const cards = statuses.map((status) => {
16761
+ const config = STATUS_CONFIG[status === "standby" ? "standBy" : status];
16762
+ const formValues = this.formData[status];
16763
+ return `
16764
+ <div class="myio-power-limits-card-item myio-status-${status}" style="--status-color: ${config.color}; --status-bg: ${config.bgColor};">
16765
+ <div class="myio-card-header">
16766
+ <span class="myio-status-indicator"></span>
16767
+ <span class="myio-status-label">${config.label}</span>
16768
+ </div>
16769
+ <div class="myio-card-inputs">
16770
+ <div class="myio-input-group">
16771
+ <label for="plm-${status}-base">Base Value</label>
16772
+ <input
16773
+ type="number"
16774
+ id="plm-${status}-base"
16775
+ class="myio-input"
16776
+ min="0"
16777
+ step="0.01"
16778
+ value="${formValues.baseValue ?? ""}"
16779
+ placeholder="0"
16780
+ >
16781
+ </div>
16782
+ <div class="myio-input-group">
16783
+ <label for="plm-${status}-top">Top Value</label>
16784
+ <input
16785
+ type="number"
16786
+ id="plm-${status}-top"
16787
+ class="myio-input"
16788
+ min="0"
16789
+ step="0.01"
16790
+ value="${formValues.topValue ?? ""}"
16791
+ placeholder="0"
16792
+ >
16793
+ </div>
16794
+ </div>
16795
+ </div>
16796
+ `;
16797
+ }).join("");
16798
+ return `
16799
+ <div class="myio-power-limits-grid" id="plm-grid">
16800
+ ${cards}
16801
+ </div>
16802
+ `;
16803
+ }
16804
+ renderLoadingState() {
16805
+ return `
16806
+ <div class="myio-power-limits-loading" id="plm-loading" style="display: none;">
16807
+ <div class="myio-spinner"></div>
16808
+ <span>Loading configuration...</span>
16809
+ </div>
16810
+ `;
16811
+ }
16812
+ renderErrorState() {
16813
+ return `
16814
+ <div class="myio-power-limits-error" id="plm-error" style="display: none;">
16815
+ <span class="myio-error-icon">&#x26A0;</span>
16816
+ <span class="myio-error-message" id="plm-error-msg"></span>
16817
+ </div>
16818
+ `;
16819
+ }
16820
+ renderSuccessState() {
16821
+ return `
16822
+ <div class="myio-power-limits-success" id="plm-success" style="display: none;">
16823
+ <span class="myio-success-icon">&#x2713;</span>
16824
+ <span class="myio-success-message">Configuration saved successfully!</span>
16825
+ </div>
16826
+ `;
16827
+ }
16828
+ setupEventListeners() {
16829
+ if (!this.overlayEl) return;
16830
+ const closeBtn = this.overlayEl.querySelector("#plm-close-btn");
16831
+ closeBtn?.addEventListener("click", () => this.close());
16832
+ this.overlayEl.addEventListener("click", (e) => {
16833
+ if (e.target === this.overlayEl) {
16834
+ this.close();
16835
+ }
16836
+ });
16837
+ document.addEventListener("keydown", this.handleKeyDown);
16838
+ const saveBtn = this.overlayEl.querySelector("#plm-save-btn");
16839
+ saveBtn?.addEventListener("click", () => this.handleSave());
16840
+ const resetBtn = this.overlayEl.querySelector("#plm-reset-btn");
16841
+ resetBtn?.addEventListener("click", () => this.handleReset());
16842
+ const deviceSelect = this.overlayEl.querySelector("#plm-device-type");
16843
+ deviceSelect?.addEventListener("change", (e) => {
16844
+ const value = e.target.value;
16845
+ this.formData.deviceType = value;
16846
+ this.config.onDeviceTypeChange(value);
16847
+ });
16848
+ const telemetrySelect = this.overlayEl.querySelector("#plm-telemetry-type");
16849
+ telemetrySelect?.addEventListener("change", (e) => {
16850
+ const value = e.target.value;
16851
+ this.formData.telemetryType = value;
16852
+ this.config.onTelemetryTypeChange(value);
16853
+ });
16854
+ const statuses = ["standby", "normal", "alert", "failure"];
16855
+ statuses.forEach((status) => {
16856
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
16857
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
16858
+ baseInput?.addEventListener("input", (e) => {
16859
+ const value = e.target.value;
16860
+ this.formData[status].baseValue = value ? parseFloat(value) : null;
16861
+ });
16862
+ topInput?.addEventListener("input", (e) => {
16863
+ const value = e.target.value;
16864
+ this.formData[status].topValue = value ? parseFloat(value) : null;
16865
+ });
16866
+ });
16867
+ }
16868
+ handleKeyDown = (e) => {
16869
+ if (e.key === "Escape") {
16870
+ this.close();
16871
+ }
16872
+ };
16873
+ async handleSave() {
16874
+ if (this.isSaving) return;
16875
+ const validationError = this.validateForm();
16876
+ if (validationError) {
16877
+ this.showError(validationError);
16878
+ return;
16879
+ }
16880
+ this.isSaving = true;
16881
+ this.showSaveLoading(true);
16882
+ this.hideError();
16883
+ this.hideSuccess();
16884
+ try {
16885
+ await this.config.onSave();
16886
+ this.showSuccess();
16887
+ setTimeout(() => this.hideSuccess(), 3e3);
16888
+ } catch (error) {
16889
+ this.showError(error.message || "Failed to save configuration");
16890
+ } finally {
16891
+ this.isSaving = false;
16892
+ this.showSaveLoading(false);
16893
+ }
16894
+ }
16895
+ handleReset() {
16896
+ const statuses = ["standby", "normal", "alert", "failure"];
16897
+ statuses.forEach((status) => {
16898
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
16899
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
16900
+ if (baseInput) baseInput.value = "";
16901
+ if (topInput) topInput.value = "";
16902
+ this.formData[status] = { baseValue: null, topValue: null };
16903
+ });
16904
+ this.hideError();
16905
+ this.hideSuccess();
16906
+ }
16907
+ validateForm() {
16908
+ const statuses = ["standby", "normal", "alert", "failure"];
16909
+ for (const status of statuses) {
16910
+ const base = this.formData[status].baseValue;
16911
+ const top = this.formData[status].topValue;
16912
+ if (base !== null && base < 0) {
16913
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Base value cannot be negative`;
16914
+ }
16915
+ if (top !== null && top < 0) {
16916
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Top value cannot be negative`;
16917
+ }
16918
+ if (base !== null && top !== null && base > top) {
16919
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Base value should not exceed top value`;
16920
+ }
16921
+ }
16922
+ return null;
16923
+ }
16924
+ close() {
16925
+ document.removeEventListener("keydown", this.handleKeyDown);
16926
+ if (this.overlayEl) {
16927
+ this.overlayEl.classList.remove("active");
16928
+ setTimeout(() => {
16929
+ this.overlayEl?.remove();
16930
+ this.overlayEl = null;
16931
+ this.container = null;
16932
+ this.config.onClose();
16933
+ }, 300);
16934
+ }
16935
+ }
16936
+ destroy() {
16937
+ document.removeEventListener("keydown", this.handleKeyDown);
16938
+ this.overlayEl?.remove();
16939
+ this.overlayEl = null;
16940
+ this.container = null;
16941
+ }
16942
+ showLoading() {
16943
+ this.isLoading = true;
16944
+ const loadingEl = this.overlayEl?.querySelector("#plm-loading");
16945
+ const gridEl = this.overlayEl?.querySelector("#plm-grid");
16946
+ if (loadingEl) loadingEl.style.display = "flex";
16947
+ if (gridEl) gridEl.style.opacity = "0.5";
16948
+ }
16949
+ hideLoading() {
16950
+ this.isLoading = false;
16951
+ const loadingEl = this.overlayEl?.querySelector("#plm-loading");
16952
+ const gridEl = this.overlayEl?.querySelector("#plm-grid");
16953
+ if (loadingEl) loadingEl.style.display = "none";
16954
+ if (gridEl) gridEl.style.opacity = "1";
16955
+ }
16956
+ showSaveLoading(show) {
16957
+ const saveBtn = this.overlayEl?.querySelector("#plm-save-btn");
16958
+ const btnText = saveBtn?.querySelector(".myio-btn-text");
16959
+ const btnSpinner = saveBtn?.querySelector(".myio-btn-spinner");
16960
+ if (saveBtn) saveBtn.disabled = show;
16961
+ if (btnText) btnText.style.display = show ? "none" : "inline";
16962
+ if (btnSpinner) btnSpinner.style.display = show ? "inline-block" : "none";
16963
+ }
16964
+ showError(message) {
16965
+ const errorEl = this.overlayEl?.querySelector("#plm-error");
16966
+ const errorMsg = this.overlayEl?.querySelector("#plm-error-msg");
16967
+ if (errorEl) errorEl.style.display = "flex";
16968
+ if (errorMsg) errorMsg.textContent = message;
16969
+ }
16970
+ hideError() {
16971
+ const errorEl = this.overlayEl?.querySelector("#plm-error");
16972
+ if (errorEl) errorEl.style.display = "none";
16973
+ }
16974
+ showSuccess() {
16975
+ const successEl = this.overlayEl?.querySelector("#plm-success");
16976
+ if (successEl) successEl.style.display = "flex";
16977
+ }
16978
+ hideSuccess() {
16979
+ const successEl = this.overlayEl?.querySelector("#plm-success");
16980
+ if (successEl) successEl.style.display = "none";
16981
+ }
16982
+ getFormData() {
16983
+ return { ...this.formData };
16984
+ }
16985
+ setFormData(data) {
16986
+ if (data.deviceType) this.formData.deviceType = data.deviceType;
16987
+ if (data.telemetryType) this.formData.telemetryType = data.telemetryType;
16988
+ if (data.standby) this.formData.standby = { ...data.standby };
16989
+ if (data.normal) this.formData.normal = { ...data.normal };
16990
+ if (data.alert) this.formData.alert = { ...data.alert };
16991
+ if (data.failure) this.formData.failure = { ...data.failure };
16992
+ this.updateInputValues();
16993
+ }
16994
+ updateInputValues() {
16995
+ const statuses = ["standby", "normal", "alert", "failure"];
16996
+ statuses.forEach((status) => {
16997
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
16998
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
16999
+ if (baseInput) {
17000
+ baseInput.value = this.formData[status].baseValue?.toString() ?? "";
17001
+ }
17002
+ if (topInput) {
17003
+ topInput.value = this.formData[status].topValue?.toString() ?? "";
17004
+ }
17005
+ });
17006
+ const deviceSelect = this.overlayEl?.querySelector("#plm-device-type");
17007
+ const telemetrySelect = this.overlayEl?.querySelector("#plm-telemetry-type");
17008
+ if (deviceSelect) deviceSelect.value = this.formData.deviceType;
17009
+ if (telemetrySelect) telemetrySelect.value = this.formData.telemetryType;
17010
+ }
17011
+ getStyles() {
17012
+ const styles = this.config.styles || {};
17013
+ const primaryColor = styles.primaryColor || "#4A148C";
17014
+ const successColor = styles.successColor || "#22c55e";
17015
+ const warningColor = styles.warningColor || "#f59e0b";
17016
+ const dangerColor = styles.dangerColor || "#ef4444";
17017
+ const infoColor = styles.infoColor || "#3b82f6";
17018
+ return `
17019
+ .myio-power-limits-overlay {
17020
+ position: fixed;
17021
+ top: 0;
17022
+ left: 0;
17023
+ right: 0;
17024
+ bottom: 0;
17025
+ background: rgba(0, 0, 0, 0.6);
17026
+ display: flex;
17027
+ align-items: center;
17028
+ justify-content: center;
17029
+ z-index: ${styles.zIndex || 1e4};
17030
+ opacity: 0;
17031
+ visibility: hidden;
17032
+ transition: all 0.3s ease;
17033
+ font-family: ${styles.fontFamily || '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'};
17034
+ }
17035
+
17036
+ .myio-power-limits-overlay.active {
17037
+ opacity: 1;
17038
+ visibility: visible;
17039
+ }
17040
+
17041
+ .myio-power-limits-card {
17042
+ background: ${styles.backgroundColor || "#ffffff"};
17043
+ border-radius: ${styles.borderRadius || "12px"};
17044
+ width: 90%;
17045
+ max-width: 700px;
17046
+ max-height: 90vh;
17047
+ overflow-y: auto;
17048
+ transform: scale(0.9);
17049
+ transition: transform 0.3s ease;
17050
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
17051
+ }
17052
+
17053
+ .myio-power-limits-overlay.active .myio-power-limits-card {
17054
+ transform: scale(1);
17055
+ }
17056
+
17057
+ .myio-power-limits-header {
17058
+ display: flex;
17059
+ align-items: center;
17060
+ justify-content: space-between;
17061
+ padding: 20px 24px;
17062
+ background: linear-gradient(135deg, ${primaryColor}, ${this.lightenColor(primaryColor, 20)});
17063
+ color: white;
17064
+ border-radius: 12px 12px 0 0;
17065
+ }
17066
+
17067
+ .myio-power-limits-title-section {
17068
+ display: flex;
17069
+ align-items: center;
17070
+ gap: 12px;
17071
+ }
17072
+
17073
+ .myio-power-limits-icon {
17074
+ font-size: 24px;
17075
+ }
17076
+
17077
+ .myio-power-limits-title {
17078
+ font-size: 1.25rem;
17079
+ font-weight: 600;
17080
+ margin: 0;
17081
+ }
17082
+
17083
+ .myio-power-limits-actions {
17084
+ display: flex;
17085
+ align-items: center;
17086
+ gap: 8px;
17087
+ }
17088
+
17089
+ .myio-btn {
17090
+ padding: 8px 16px;
17091
+ border-radius: ${styles.buttonRadius || "6px"};
17092
+ font-size: 14px;
17093
+ font-weight: 500;
17094
+ cursor: pointer;
17095
+ border: none;
17096
+ transition: all 0.2s;
17097
+ display: inline-flex;
17098
+ align-items: center;
17099
+ gap: 6px;
17100
+ }
17101
+
17102
+ .myio-btn:disabled {
17103
+ opacity: 0.6;
17104
+ cursor: not-allowed;
17105
+ }
17106
+
17107
+ .myio-btn-primary {
17108
+ background: white;
17109
+ color: ${primaryColor};
17110
+ }
17111
+
17112
+ .myio-btn-primary:hover:not(:disabled) {
17113
+ background: #f3f4f6;
17114
+ }
17115
+
17116
+ .myio-btn-secondary {
17117
+ background: rgba(255, 255, 255, 0.2);
17118
+ color: white;
17119
+ }
17120
+
17121
+ .myio-btn-secondary:hover:not(:disabled) {
17122
+ background: rgba(255, 255, 255, 0.3);
17123
+ }
17124
+
17125
+ .myio-btn-close {
17126
+ background: transparent;
17127
+ color: white;
17128
+ font-size: 24px;
17129
+ padding: 4px 8px;
17130
+ line-height: 1;
17131
+ }
17132
+
17133
+ .myio-btn-close:hover {
17134
+ background: rgba(255, 255, 255, 0.1);
17135
+ }
17136
+
17137
+ .myio-btn-spinner {
17138
+ width: 16px;
17139
+ height: 16px;
17140
+ border: 2px solid ${primaryColor};
17141
+ border-top-color: transparent;
17142
+ border-radius: 50%;
17143
+ animation: spin 0.8s linear infinite;
17144
+ }
17145
+
17146
+ @keyframes spin {
17147
+ to { transform: rotate(360deg); }
17148
+ }
17149
+
17150
+ .myio-power-limits-selectors {
17151
+ display: grid;
17152
+ grid-template-columns: 1fr 1fr;
17153
+ gap: 16px;
17154
+ padding: 20px 24px;
17155
+ background: #f9fafb;
17156
+ border-bottom: 1px solid #e5e7eb;
17157
+ }
17158
+
17159
+ .myio-form-group {
17160
+ display: flex;
17161
+ flex-direction: column;
17162
+ gap: 6px;
17163
+ }
17164
+
17165
+ .myio-form-group label {
17166
+ font-size: 13px;
17167
+ font-weight: 500;
17168
+ color: #374151;
17169
+ }
17170
+
17171
+ .myio-select, .myio-input {
17172
+ padding: 10px 12px;
17173
+ border: 1px solid #d1d5db;
17174
+ border-radius: 6px;
17175
+ font-size: 14px;
17176
+ background: white;
17177
+ color: #1f2937;
17178
+ transition: border-color 0.2s, box-shadow 0.2s;
17179
+ }
17180
+
17181
+ .myio-select:focus, .myio-input:focus {
17182
+ outline: none;
17183
+ border-color: ${primaryColor};
17184
+ box-shadow: 0 0 0 3px ${this.hexToRgba(primaryColor, 0.1)};
17185
+ }
17186
+
17187
+ .myio-power-limits-grid {
17188
+ display: grid;
17189
+ grid-template-columns: repeat(2, 1fr);
17190
+ gap: 16px;
17191
+ padding: 24px;
17192
+ transition: opacity 0.3s;
17193
+ }
17194
+
17195
+ .myio-power-limits-card-item {
17196
+ background: var(--status-bg);
17197
+ border: 1px solid var(--status-color);
17198
+ border-radius: 8px;
17199
+ padding: 16px;
17200
+ transition: transform 0.2s, box-shadow 0.2s;
17201
+ }
17202
+
17203
+ .myio-power-limits-card-item:hover {
17204
+ transform: translateY(-2px);
17205
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
17206
+ }
17207
+
17208
+ .myio-card-header {
17209
+ display: flex;
17210
+ align-items: center;
17211
+ gap: 8px;
17212
+ margin-bottom: 12px;
17213
+ }
17214
+
17215
+ .myio-status-indicator {
17216
+ width: 12px;
17217
+ height: 12px;
17218
+ border-radius: 50%;
17219
+ background: var(--status-color);
17220
+ }
17221
+
17222
+ .myio-status-label {
17223
+ font-weight: 600;
17224
+ font-size: 14px;
17225
+ color: #1f2937;
17226
+ }
17227
+
17228
+ .myio-card-inputs {
17229
+ display: grid;
17230
+ grid-template-columns: 1fr 1fr;
17231
+ gap: 12px;
17232
+ }
17233
+
17234
+ .myio-input-group {
17235
+ display: flex;
17236
+ flex-direction: column;
17237
+ gap: 4px;
17238
+ }
17239
+
17240
+ .myio-input-group label {
17241
+ font-size: 11px;
17242
+ font-weight: 500;
17243
+ color: #6b7280;
17244
+ text-transform: uppercase;
17245
+ }
17246
+
17247
+ .myio-power-limits-loading,
17248
+ .myio-power-limits-error,
17249
+ .myio-power-limits-success {
17250
+ display: flex;
17251
+ align-items: center;
17252
+ justify-content: center;
17253
+ gap: 12px;
17254
+ padding: 16px 24px;
17255
+ margin: 0 24px 24px;
17256
+ border-radius: 8px;
17257
+ }
17258
+
17259
+ .myio-power-limits-loading {
17260
+ background: #f3f4f6;
17261
+ color: #6b7280;
17262
+ }
17263
+
17264
+ .myio-power-limits-error {
17265
+ background: #fef2f2;
17266
+ color: ${dangerColor};
17267
+ border: 1px solid ${dangerColor};
17268
+ }
17269
+
17270
+ .myio-power-limits-success {
17271
+ background: #f0fdf4;
17272
+ color: ${successColor};
17273
+ border: 1px solid ${successColor};
17274
+ }
17275
+
17276
+ .myio-spinner {
17277
+ width: 24px;
17278
+ height: 24px;
17279
+ border: 3px solid #e5e7eb;
17280
+ border-top-color: ${primaryColor};
17281
+ border-radius: 50%;
17282
+ animation: spin 0.8s linear infinite;
17283
+ }
17284
+
17285
+ .myio-error-icon, .myio-success-icon {
17286
+ font-size: 20px;
17287
+ }
17288
+
17289
+ @media (max-width: 600px) {
17290
+ .myio-power-limits-selectors,
17291
+ .myio-power-limits-grid {
17292
+ grid-template-columns: 1fr;
17293
+ }
17294
+
17295
+ .myio-power-limits-header {
17296
+ flex-direction: column;
17297
+ gap: 12px;
17298
+ text-align: center;
17299
+ }
17300
+
17301
+ .myio-power-limits-actions {
17302
+ width: 100%;
17303
+ justify-content: center;
17304
+ }
17305
+ }
17306
+ `;
17307
+ }
17308
+ lightenColor(hex, percent) {
17309
+ const num = parseInt(hex.replace("#", ""), 16);
17310
+ const amt = Math.round(2.55 * percent);
17311
+ const R = (num >> 16) + amt;
17312
+ const G = (num >> 8 & 255) + amt;
17313
+ const B = (num & 255) + amt;
17314
+ 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);
17315
+ }
17316
+ hexToRgba(hex, alpha) {
17317
+ const num = parseInt(hex.replace("#", ""), 16);
17318
+ const R = num >> 16;
17319
+ const G = num >> 8 & 255;
17320
+ const B = num & 255;
17321
+ return `rgba(${R}, ${G}, ${B}, ${alpha})`;
17322
+ }
17323
+ };
17324
+
17325
+ // src/components/premium-modals/power-limits/PowerLimitsPersister.ts
17326
+ var PowerLimitsPersister = class {
17327
+ jwtToken;
17328
+ tbBaseUrl;
17329
+ constructor(jwtToken, tbBaseUrl) {
17330
+ this.jwtToken = jwtToken;
17331
+ this.tbBaseUrl = tbBaseUrl || window.location.origin;
17332
+ }
17333
+ /**
17334
+ * Load existing mapInstantaneousPower from customer server_scope attributes
17335
+ */
17336
+ async loadCustomerPowerLimits(customerId) {
17337
+ try {
17338
+ const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/values/attributes/SERVER_SCOPE?keys=mapInstantaneousPower`;
17339
+ const response = await fetch(url, {
17340
+ method: "GET",
17341
+ headers: {
17342
+ "X-Authorization": `Bearer ${this.jwtToken}`,
17343
+ "Content-Type": "application/json"
17344
+ }
17345
+ });
17346
+ if (!response.ok) {
17347
+ if (response.status === 404) {
17348
+ console.log("[PowerLimitsPersister] No existing mapInstantaneousPower found");
17349
+ return null;
17350
+ }
17351
+ throw this.createHttpError(response.status, await response.text().catch(() => ""));
17352
+ }
17353
+ const data = await response.json();
17354
+ if (!data || data.length === 0) {
17355
+ console.log("[PowerLimitsPersister] No mapInstantaneousPower attribute found");
17356
+ return null;
17357
+ }
17358
+ const attr = data.find((item) => item.key === "mapInstantaneousPower");
17359
+ if (!attr || !attr.value) {
17360
+ return null;
17361
+ }
17362
+ const parsedValue = typeof attr.value === "string" ? JSON.parse(attr.value) : attr.value;
17363
+ console.log("[PowerLimitsPersister] Loaded mapInstantaneousPower:", parsedValue);
17364
+ return parsedValue;
17365
+ } catch (error) {
17366
+ console.error("[PowerLimitsPersister] Error loading power limits:", error);
17367
+ throw this.mapError(error);
17368
+ }
17369
+ }
17370
+ /**
17371
+ * Save mapInstantaneousPower to customer server_scope attributes
17372
+ */
17373
+ async saveCustomerPowerLimits(customerId, limits) {
17374
+ try {
17375
+ const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/attributes/SERVER_SCOPE`;
17376
+ const payload = {
17377
+ mapInstantaneousPower: limits
17378
+ };
17379
+ console.log("[PowerLimitsPersister] Saving power limits:", payload);
17380
+ const response = await fetch(url, {
17381
+ method: "POST",
17382
+ headers: {
17383
+ "X-Authorization": `Bearer ${this.jwtToken}`,
17384
+ "Content-Type": "application/json"
17385
+ },
17386
+ body: JSON.stringify(payload)
17387
+ });
17388
+ if (!response.ok) {
17389
+ throw this.createHttpError(response.status, await response.text().catch(() => ""));
17390
+ }
17391
+ console.log("[PowerLimitsPersister] Successfully saved power limits");
17392
+ return { ok: true };
17393
+ } catch (error) {
17394
+ console.error("[PowerLimitsPersister] Error saving power limits:", error);
17395
+ return { ok: false, error: this.mapError(error) };
17396
+ }
17397
+ }
17398
+ /**
17399
+ * Extract form data for a specific device type and telemetry type
17400
+ */
17401
+ extractFormData(limits, deviceType, telemetryType) {
17402
+ const defaultFormData = {
17403
+ deviceType,
17404
+ telemetryType,
17405
+ standby: { baseValue: null, topValue: null },
17406
+ normal: { baseValue: null, topValue: null },
17407
+ alert: { baseValue: null, topValue: null },
17408
+ failure: { baseValue: null, topValue: null }
17409
+ };
17410
+ if (!limits || !limits.limitsByInstantaneoustPowerType) {
17411
+ return defaultFormData;
17412
+ }
17413
+ const telemetryEntry = limits.limitsByInstantaneoustPowerType.find(
17414
+ (t) => t.telemetryType === telemetryType
17415
+ );
17416
+ if (!telemetryEntry || !telemetryEntry.itemsByDeviceType) {
17417
+ return defaultFormData;
17418
+ }
17419
+ const deviceEntry = telemetryEntry.itemsByDeviceType.find(
17420
+ (d) => d.deviceType === deviceType
17421
+ );
17422
+ if (!deviceEntry || !deviceEntry.limitsByDeviceStatus) {
17423
+ return defaultFormData;
17424
+ }
17425
+ const statusMap = {
17426
+ "standBy": "standby",
17427
+ "normal": "normal",
17428
+ "alert": "alert",
17429
+ "failure": "failure"
17430
+ };
17431
+ deviceEntry.limitsByDeviceStatus.forEach((status) => {
17432
+ const formKey = statusMap[status.deviceStatusName];
17433
+ if (formKey && defaultFormData[formKey]) {
17434
+ defaultFormData[formKey] = {
17435
+ baseValue: status.limitsValues.baseValue,
17436
+ topValue: status.limitsValues.topValue
17437
+ };
17438
+ }
17439
+ });
17440
+ return defaultFormData;
17441
+ }
17442
+ /**
17443
+ * Merge form data into existing limits JSON
17444
+ * Creates new entries if they don't exist
17445
+ */
17446
+ mergeFormDataIntoLimits(existingLimits, formData) {
17447
+ const result = existingLimits ? JSON.parse(JSON.stringify(existingLimits)) : { version: "1.0.0", limitsByInstantaneoustPowerType: [] };
17448
+ const statusLimits = [
17449
+ {
17450
+ deviceStatusName: "standBy",
17451
+ limitsValues: {
17452
+ baseValue: formData.standby.baseValue ?? 0,
17453
+ topValue: formData.standby.topValue ?? 0
17454
+ }
17455
+ },
17456
+ {
17457
+ deviceStatusName: "normal",
17458
+ limitsValues: {
17459
+ baseValue: formData.normal.baseValue ?? 0,
17460
+ topValue: formData.normal.topValue ?? 0
17461
+ }
17462
+ },
17463
+ {
17464
+ deviceStatusName: "alert",
17465
+ limitsValues: {
17466
+ baseValue: formData.alert.baseValue ?? 0,
17467
+ topValue: formData.alert.topValue ?? 0
17468
+ }
17469
+ },
17470
+ {
17471
+ deviceStatusName: "failure",
17472
+ limitsValues: {
17473
+ baseValue: formData.failure.baseValue ?? 0,
17474
+ topValue: formData.failure.topValue ?? 0
17475
+ }
17476
+ }
17477
+ ];
17478
+ let telemetryEntry = result.limitsByInstantaneoustPowerType.find(
17479
+ (t) => t.telemetryType === formData.telemetryType
17480
+ );
17481
+ if (!telemetryEntry) {
17482
+ telemetryEntry = {
17483
+ telemetryType: formData.telemetryType,
17484
+ itemsByDeviceType: []
17485
+ };
17486
+ result.limitsByInstantaneoustPowerType.push(telemetryEntry);
17487
+ }
17488
+ let deviceEntry = telemetryEntry.itemsByDeviceType.find(
17489
+ (d) => d.deviceType === formData.deviceType
17490
+ );
17491
+ if (!deviceEntry) {
17492
+ deviceEntry = {
17493
+ deviceType: formData.deviceType,
17494
+ name: `mapInstantaneousPower${this.formatDeviceTypeName(formData.deviceType)}`,
17495
+ description: `Power limits for ${formData.deviceType}`,
17496
+ limitsByDeviceStatus: []
17497
+ };
17498
+ telemetryEntry.itemsByDeviceType.push(deviceEntry);
17499
+ }
17500
+ deviceEntry.limitsByDeviceStatus = statusLimits;
17501
+ deviceEntry.name = `mapInstantaneousPower${this.formatDeviceTypeName(formData.deviceType)}`;
17502
+ deviceEntry.description = `Power limits for ${formData.deviceType} - ${formData.telemetryType}`;
17503
+ return result;
17504
+ }
17505
+ /**
17506
+ * Format device type name for the JSON name field
17507
+ */
17508
+ formatDeviceTypeName(deviceType) {
17509
+ if (!deviceType) return "";
17510
+ return deviceType.toLowerCase().split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
17511
+ }
17512
+ createHttpError(status, body) {
17513
+ const error = new Error(`HTTP ${status}: ${body}`);
17514
+ error.status = status;
17515
+ error.body = body;
17516
+ return error;
17517
+ }
17518
+ mapError(error) {
17519
+ const status = error.status;
17520
+ if (status === 400) {
17521
+ return {
17522
+ code: "VALIDATION_ERROR",
17523
+ message: "Invalid input data",
17524
+ cause: error
17525
+ };
17526
+ }
17527
+ if (status === 401) {
17528
+ return {
17529
+ code: "TOKEN_EXPIRED",
17530
+ message: "Authentication token has expired",
17531
+ cause: error
17532
+ };
17533
+ }
17534
+ if (status === 403) {
17535
+ return {
17536
+ code: "AUTH_ERROR",
17537
+ message: "Insufficient permissions",
17538
+ cause: error
17539
+ };
17540
+ }
17541
+ if (status === 404) {
17542
+ return {
17543
+ code: "NETWORK_ERROR",
17544
+ message: "Customer not found",
17545
+ cause: error
17546
+ };
17547
+ }
17548
+ if (status >= 500) {
17549
+ return {
17550
+ code: "NETWORK_ERROR",
17551
+ message: "Server error occurred",
17552
+ cause: error
17553
+ };
15848
17554
  }
17555
+ return {
17556
+ code: "UNKNOWN_ERROR",
17557
+ message: error.message || "Unknown error occurred",
17558
+ cause: error
17559
+ };
15849
17560
  }
15850
- if (options.aggregation !== void 0) {
15851
- const validAggregations = ["NONE", "MIN", "MAX", "AVG", "SUM", "COUNT"];
15852
- if (!validAggregations.includes(options.aggregation)) {
15853
- errors.push(`aggregation must be one of: ${validAggregations.join(", ")}`);
17561
+ };
17562
+
17563
+ // src/components/premium-modals/power-limits/openPowerLimitsSetupModal.ts
17564
+ async function openPowerLimitsSetupModal(params) {
17565
+ if (!params.token) {
17566
+ throw new Error("[PowerLimitsSetupModal] token is required");
17567
+ }
17568
+ if (!params.customerId) {
17569
+ throw new Error("[PowerLimitsSetupModal] customerId is required");
17570
+ }
17571
+ const persister = new PowerLimitsPersister(params.token, params.tbBaseUrl);
17572
+ let currentDeviceType = params.deviceType || "ELEVADOR";
17573
+ let currentTelemetryType = params.telemetryType || "consumption";
17574
+ let existingLimits = params.existingMapPower || null;
17575
+ const view = new PowerLimitsModalView({
17576
+ deviceType: currentDeviceType,
17577
+ telemetryType: currentTelemetryType,
17578
+ styles: params.styles,
17579
+ locale: params.locale,
17580
+ onDeviceTypeChange: async (deviceType) => {
17581
+ currentDeviceType = deviceType;
17582
+ await loadFormData();
17583
+ },
17584
+ onTelemetryTypeChange: async (telemetryType) => {
17585
+ currentTelemetryType = telemetryType;
17586
+ await loadFormData();
17587
+ },
17588
+ onSave: async () => {
17589
+ const formData = view.getFormData();
17590
+ const updatedLimits = persister.mergeFormDataIntoLimits(existingLimits, formData);
17591
+ const result = await persister.saveCustomerPowerLimits(params.customerId, updatedLimits);
17592
+ if (!result.ok) {
17593
+ throw new Error(result.error?.message || "Failed to save configuration");
17594
+ }
17595
+ existingLimits = updatedLimits;
17596
+ if (params.onSave) {
17597
+ params.onSave(updatedLimits);
17598
+ }
17599
+ },
17600
+ onClose: () => {
17601
+ if (params.onClose) {
17602
+ params.onClose();
17603
+ }
17604
+ }
17605
+ });
17606
+ async function loadFormData() {
17607
+ view.showLoading();
17608
+ try {
17609
+ if (!existingLimits) {
17610
+ existingLimits = await persister.loadCustomerPowerLimits(params.customerId);
17611
+ }
17612
+ const formData = persister.extractFormData(existingLimits, currentDeviceType, currentTelemetryType);
17613
+ view.setFormData(formData);
17614
+ } catch (error) {
17615
+ console.error("[PowerLimitsSetupModal] Error loading form data:", error);
17616
+ view.showError(error.message || "Failed to load configuration");
17617
+ } finally {
17618
+ view.hideLoading();
15854
17619
  }
15855
17620
  }
15856
- if (errors.length > 0) {
15857
- throw new Error(`Validation failed:
15858
- - ${errors.join("\n- ")}`);
17621
+ let container;
17622
+ if (params.container) {
17623
+ if (typeof params.container === "string") {
17624
+ container = document.querySelector(params.container);
17625
+ } else {
17626
+ container = params.container;
17627
+ }
15859
17628
  }
17629
+ view.render(container);
17630
+ await loadFormData();
17631
+ return {
17632
+ destroy: () => view.destroy(),
17633
+ getFormData: () => view.getFormData(),
17634
+ setFormData: (data) => view.setFormData(data)
17635
+ };
15860
17636
  }
15861
17637
 
15862
17638
  // src/components/premium-modals/settings/SettingsModalView.ts
@@ -20227,220 +22003,6 @@ function openGoalsPanel(params) {
20227
22003
  }
20228
22004
  }
20229
22005
 
20230
- // src/components/temperature/utils.ts
20231
- var DAY_PERIODS = [
20232
- { id: "madrugada", label: "Madrugada (00h-06h)", startHour: 0, endHour: 6 },
20233
- { id: "manha", label: "Manh\xE3 (06h-12h)", startHour: 6, endHour: 12 },
20234
- { id: "tarde", label: "Tarde (12h-18h)", startHour: 12, endHour: 18 },
20235
- { id: "noite", label: "Noite (18h-24h)", startHour: 18, endHour: 24 }
20236
- ];
20237
- var DEFAULT_CLAMP_RANGE = { min: 15, max: 40 };
20238
- function getTodaySoFar() {
20239
- const now = /* @__PURE__ */ new Date();
20240
- const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
20241
- return {
20242
- startTs: startOfDay.getTime(),
20243
- endTs: now.getTime()
20244
- };
20245
- }
20246
- var CHART_COLORS = [
20247
- "#1976d2",
20248
- // Blue
20249
- "#FF6B6B",
20250
- // Red
20251
- "#4CAF50",
20252
- // Green
20253
- "#FF9800",
20254
- // Orange
20255
- "#9C27B0",
20256
- // Purple
20257
- "#00BCD4",
20258
- // Cyan
20259
- "#E91E63",
20260
- // Pink
20261
- "#795548"
20262
- // Brown
20263
- ];
20264
- async function fetchTemperatureData(token, deviceId, startTs, endTs) {
20265
- const url = `/api/plugins/telemetry/DEVICE/${deviceId}/values/timeseries?keys=temperature&startTs=${encodeURIComponent(startTs)}&endTs=${encodeURIComponent(endTs)}&limit=50000&agg=NONE`;
20266
- const response = await fetch(url, {
20267
- headers: {
20268
- "X-Authorization": `Bearer ${token}`,
20269
- "Content-Type": "application/json"
20270
- }
20271
- });
20272
- if (!response.ok) {
20273
- throw new Error(`Failed to fetch temperature data: ${response.status}`);
20274
- }
20275
- const data = await response.json();
20276
- return data?.temperature || [];
20277
- }
20278
- function clampTemperature(value, range = DEFAULT_CLAMP_RANGE) {
20279
- const num = Number(value || 0);
20280
- if (num < range.min) return range.min;
20281
- if (num > range.max) return range.max;
20282
- return num;
20283
- }
20284
- function calculateStats(data, clampRange = DEFAULT_CLAMP_RANGE) {
20285
- if (data.length === 0) {
20286
- return { avg: 0, min: 0, max: 0, count: 0 };
20287
- }
20288
- const values = data.map((item) => clampTemperature(item.value, clampRange));
20289
- const sum = values.reduce((acc, v) => acc + v, 0);
20290
- return {
20291
- avg: sum / values.length,
20292
- min: Math.min(...values),
20293
- max: Math.max(...values),
20294
- count: values.length
20295
- };
20296
- }
20297
- function interpolateTemperature(data, options) {
20298
- const { intervalMinutes, startTs, endTs, clampRange = DEFAULT_CLAMP_RANGE } = options;
20299
- const intervalMs = intervalMinutes * 60 * 1e3;
20300
- if (data.length === 0) {
20301
- return [];
20302
- }
20303
- const sortedData = [...data].sort((a, b) => a.ts - b.ts);
20304
- const result = [];
20305
- let lastKnownValue = clampTemperature(sortedData[0].value, clampRange);
20306
- let dataIndex = 0;
20307
- for (let ts = startTs; ts <= endTs; ts += intervalMs) {
20308
- while (dataIndex < sortedData.length - 1 && sortedData[dataIndex + 1].ts <= ts) {
20309
- dataIndex++;
20310
- }
20311
- const currentData = sortedData[dataIndex];
20312
- if (currentData && Math.abs(currentData.ts - ts) < intervalMs) {
20313
- lastKnownValue = clampTemperature(currentData.value, clampRange);
20314
- }
20315
- result.push({
20316
- ts,
20317
- value: lastKnownValue
20318
- });
20319
- }
20320
- return result;
20321
- }
20322
- function aggregateByDay(data, clampRange = DEFAULT_CLAMP_RANGE) {
20323
- if (data.length === 0) {
20324
- return [];
20325
- }
20326
- const dayMap = /* @__PURE__ */ new Map();
20327
- data.forEach((item) => {
20328
- const date = new Date(item.ts);
20329
- const dateKey = date.toISOString().split("T")[0];
20330
- if (!dayMap.has(dateKey)) {
20331
- dayMap.set(dateKey, []);
20332
- }
20333
- dayMap.get(dateKey).push(item);
20334
- });
20335
- const result = [];
20336
- dayMap.forEach((dayData, dateKey) => {
20337
- const values = dayData.map((item) => clampTemperature(item.value, clampRange));
20338
- const sum = values.reduce((acc, v) => acc + v, 0);
20339
- result.push({
20340
- date: dateKey,
20341
- dateTs: new Date(dateKey).getTime(),
20342
- avg: sum / values.length,
20343
- min: Math.min(...values),
20344
- max: Math.max(...values),
20345
- count: values.length
20346
- });
20347
- });
20348
- return result.sort((a, b) => a.dateTs - b.dateTs);
20349
- }
20350
- function filterByDayPeriods(data, selectedPeriods) {
20351
- if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
20352
- return data;
20353
- }
20354
- return data.filter((item) => {
20355
- const date = new Date(item.ts);
20356
- const hour = date.getHours();
20357
- return selectedPeriods.some((periodId) => {
20358
- const period = DAY_PERIODS.find((p) => p.id === periodId);
20359
- if (!period) return false;
20360
- return hour >= period.startHour && hour < period.endHour;
20361
- });
20362
- });
20363
- }
20364
- function getSelectedPeriodsLabel(selectedPeriods) {
20365
- if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
20366
- return "Todos os per\xEDodos";
20367
- }
20368
- if (selectedPeriods.length === 1) {
20369
- const period = DAY_PERIODS.find((p) => p.id === selectedPeriods[0]);
20370
- return period?.label || "";
20371
- }
20372
- return `${selectedPeriods.length} per\xEDodos selecionados`;
20373
- }
20374
- function formatTemperature2(value, decimals = 1) {
20375
- return `${value.toFixed(decimals)}\xB0C`;
20376
- }
20377
- function exportTemperatureCSV(data, deviceLabel, stats, startDate, endDate) {
20378
- if (data.length === 0) {
20379
- console.warn("No data to export");
20380
- return;
20381
- }
20382
- const BOM = "\uFEFF";
20383
- let csvContent = BOM;
20384
- csvContent += `Relat\xF3rio de Temperatura - ${deviceLabel}
20385
- `;
20386
- csvContent += `Per\xEDodo: ${startDate} at\xE9 ${endDate}
20387
- `;
20388
- csvContent += `M\xE9dia: ${formatTemperature2(stats.avg)}
20389
- `;
20390
- csvContent += `M\xEDnima: ${formatTemperature2(stats.min)}
20391
- `;
20392
- csvContent += `M\xE1xima: ${formatTemperature2(stats.max)}
20393
- `;
20394
- csvContent += `Total de leituras: ${stats.count}
20395
- `;
20396
- csvContent += "\n";
20397
- csvContent += "Data/Hora,Temperatura (\xB0C)\n";
20398
- data.forEach((item) => {
20399
- const date = new Date(item.ts).toLocaleString("pt-BR");
20400
- const temp = Number(item.value).toFixed(2);
20401
- csvContent += `"${date}",${temp}
20402
- `;
20403
- });
20404
- const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
20405
- const url = URL.createObjectURL(blob);
20406
- const link = document.createElement("a");
20407
- link.href = url;
20408
- link.download = `temperatura_${deviceLabel.replace(/\s+/g, "_")}_${startDate}_${endDate}.csv`;
20409
- document.body.appendChild(link);
20410
- link.click();
20411
- document.body.removeChild(link);
20412
- URL.revokeObjectURL(url);
20413
- }
20414
- var DARK_THEME = {
20415
- background: "rgba(0, 0, 0, 0.85)",
20416
- surface: "#1a1f28",
20417
- text: "#ffffff",
20418
- textMuted: "rgba(255, 255, 255, 0.7)",
20419
- border: "rgba(255, 255, 255, 0.1)",
20420
- primary: "#1976d2",
20421
- success: "#4CAF50",
20422
- warning: "#FF9800",
20423
- danger: "#f44336",
20424
- chartLine: "#1976d2",
20425
- chartGrid: "rgba(255, 255, 255, 0.1)"
20426
- };
20427
- var LIGHT_THEME = {
20428
- background: "rgba(0, 0, 0, 0.6)",
20429
- surface: "#ffffff",
20430
- text: "#333333",
20431
- textMuted: "#666666",
20432
- border: "#e0e0e0",
20433
- primary: "#1976d2",
20434
- success: "#4CAF50",
20435
- warning: "#FF9800",
20436
- danger: "#f44336",
20437
- chartLine: "#1976d2",
20438
- chartGrid: "#e0e0e0"
20439
- };
20440
- function getThemeColors(theme) {
20441
- return theme === "dark" ? DARK_THEME : LIGHT_THEME;
20442
- }
20443
-
20444
22006
  // src/components/temperature/TemperatureModal.ts
20445
22007
  async function openTemperatureModal(params) {
20446
22008
  const modalId = `myio-temp-modal-${Date.now()}`;
@@ -20680,7 +22242,7 @@ function renderModal(container, state, modalId, error) {
20680
22242
  ">
20681
22243
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Temperatura Atual</span>
20682
22244
  <div style="font-weight: 700; font-size: 24px; color: ${statusColor}; margin-top: 4px;">
20683
- ${state.currentTemperature !== null ? formatTemperature2(state.currentTemperature) : "N/A"}
22245
+ ${state.currentTemperature !== null ? formatTemperature(state.currentTemperature) : "N/A"}
20684
22246
  </div>
20685
22247
  <div style="font-size: 11px; color: ${statusColor}; margin-top: 2px;">${statusText}</div>
20686
22248
  </div>
@@ -20691,7 +22253,7 @@ function renderModal(container, state, modalId, error) {
20691
22253
  ">
20692
22254
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">M\xE9dia do Per\xEDodo</span>
20693
22255
  <div id="${modalId}-avg" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
20694
- ${state.stats.count > 0 ? formatTemperature2(state.stats.avg) : "N/A"}
22256
+ ${state.stats.count > 0 ? formatTemperature(state.stats.avg) : "N/A"}
20695
22257
  </div>
20696
22258
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${startDateStr} - ${endDateStr}</div>
20697
22259
  </div>
@@ -20702,7 +22264,7 @@ function renderModal(container, state, modalId, error) {
20702
22264
  ">
20703
22265
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Min / Max</span>
20704
22266
  <div id="${modalId}-minmax" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
20705
- ${state.stats.count > 0 ? `${formatTemperature2(state.stats.min)} / ${formatTemperature2(state.stats.max)}` : "N/A"}
22267
+ ${state.stats.count > 0 ? `${formatTemperature(state.stats.min)} / ${formatTemperature(state.stats.max)}` : "N/A"}
20706
22268
  </div>
20707
22269
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state.stats.count} leituras</div>
20708
22270
  </div>
@@ -21018,7 +22580,7 @@ function setupChartTooltip(canvas, container, chartData, state, colors) {
21018
22580
  }
21019
22581
  tooltip.innerHTML = `
21020
22582
  <div style="font-weight: 600; margin-bottom: 6px; color: ${colors.primary};">
21021
- ${formatTemperature2(point.y)}
22583
+ ${formatTemperature(point.y)}
21022
22584
  </div>
21023
22585
  <div style="font-size: 11px; color: ${colors.textMuted};">
21024
22586
  \u{1F4C5} ${dateStr}
@@ -21281,7 +22843,7 @@ function renderModal2(container, state, modalId) {
21281
22843
  <span style="width: 12px; height: 12px; border-radius: 50%; background: ${dd.color};"></span>
21282
22844
  <span style="color: ${colors.text}; font-size: 13px;">${dd.device.label}</span>
21283
22845
  <span style="color: ${colors.textMuted}; font-size: 11px; margin-left: auto;">
21284
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.avg) : "N/A"}
22846
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.avg) : "N/A"}
21285
22847
  </span>
21286
22848
  </div>
21287
22849
  `).join("");
@@ -21297,15 +22859,15 @@ function renderModal2(container, state, modalId) {
21297
22859
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4px; font-size: 11px;">
21298
22860
  <span style="color: ${colors.textMuted};">M\xE9dia:</span>
21299
22861
  <span style="color: ${colors.text}; font-weight: 500;">
21300
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.avg) : "N/A"}
22862
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.avg) : "N/A"}
21301
22863
  </span>
21302
22864
  <span style="color: ${colors.textMuted};">Min:</span>
21303
22865
  <span style="color: ${colors.text};">
21304
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.min) : "N/A"}
22866
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.min) : "N/A"}
21305
22867
  </span>
21306
22868
  <span style="color: ${colors.textMuted};">Max:</span>
21307
22869
  <span style="color: ${colors.text};">
21308
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.max) : "N/A"}
22870
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.max) : "N/A"}
21309
22871
  </span>
21310
22872
  <span style="color: ${colors.textMuted};">Leituras:</span>
21311
22873
  <span style="color: ${colors.text};">${dd.stats.count}</span>
@@ -21841,7 +23403,7 @@ function setupComparisonChartTooltip(canvas, container, chartData, state, colors
21841
23403
  <span style="font-weight: 600;">${point.deviceLabel}</span>
21842
23404
  </div>
21843
23405
  <div style="font-weight: 600; font-size: 16px; color: ${point.deviceColor}; margin-bottom: 4px;">
21844
- ${formatTemperature2(point.y)}
23406
+ ${formatTemperature(point.y)}
21845
23407
  </div>
21846
23408
  <div style="font-size: 11px; color: ${colors.textMuted};">
21847
23409
  \u{1F4C5} ${dateStr}
@@ -26339,6 +27901,9 @@ export {
26339
27901
  MyIOSelectionStore,
26340
27902
  MyIOSelectionStoreClass,
26341
27903
  MyIOToast,
27904
+ DEVICE_TYPES as POWER_LIMITS_DEVICE_TYPES,
27905
+ STATUS_CONFIG as POWER_LIMITS_STATUS_CONFIG,
27906
+ TELEMETRY_TYPES2 as POWER_LIMITS_TELEMETRY_TYPES,
26342
27907
  addDetectionContext,
26343
27908
  addNamespace,
26344
27909
  aggregateByDay,
@@ -26393,7 +27958,7 @@ export {
26393
27958
  formatNumberReadable,
26394
27959
  formatRelativeTime,
26395
27960
  formatTankHeadFromCm,
26396
- formatTemperature2 as formatTemperature,
27961
+ formatTemperature,
26397
27962
  formatWater,
26398
27963
  formatWaterByGroup,
26399
27964
  formatWaterVolumeM3,
@@ -26436,6 +28001,7 @@ export {
26436
28001
  openDashboardPopupWaterTank,
26437
28002
  openDemandModal,
26438
28003
  openGoalsPanel,
28004
+ openPowerLimitsSetupModal,
26439
28005
  openRealTimeTelemetryModal,
26440
28006
  openTemperatureComparisonModal,
26441
28007
  openTemperatureModal,