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.cjs CHANGED
@@ -588,6 +588,9 @@ __export(index_exports, {
588
588
  MyIOSelectionStore: () => MyIOSelectionStore,
589
589
  MyIOSelectionStoreClass: () => MyIOSelectionStoreClass,
590
590
  MyIOToast: () => MyIOToast,
591
+ POWER_LIMITS_DEVICE_TYPES: () => DEVICE_TYPES,
592
+ POWER_LIMITS_STATUS_CONFIG: () => STATUS_CONFIG,
593
+ POWER_LIMITS_TELEMETRY_TYPES: () => TELEMETRY_TYPES2,
591
594
  addDetectionContext: () => addDetectionContext,
592
595
  addNamespace: () => addNamespace,
593
596
  aggregateByDay: () => aggregateByDay,
@@ -642,7 +645,7 @@ __export(index_exports, {
642
645
  formatNumberReadable: () => formatNumberReadable,
643
646
  formatRelativeTime: () => formatRelativeTime,
644
647
  formatTankHeadFromCm: () => formatTankHeadFromCm,
645
- formatTemperature: () => formatTemperature2,
648
+ formatTemperature: () => formatTemperature,
646
649
  formatWater: () => formatWater,
647
650
  formatWaterByGroup: () => formatWaterByGroup,
648
651
  formatWaterVolumeM3: () => formatWaterVolumeM3,
@@ -685,6 +688,7 @@ __export(index_exports, {
685
688
  openDashboardPopupWaterTank: () => openDashboardPopupWaterTank,
686
689
  openDemandModal: () => openDemandModal,
687
690
  openGoalsPanel: () => openGoalsPanel,
691
+ openPowerLimitsSetupModal: () => openPowerLimitsSetupModal,
688
692
  openRealTimeTelemetryModal: () => openRealTimeTelemetryModal,
689
693
  openTemperatureComparisonModal: () => openTemperatureComparisonModal,
690
694
  openTemperatureModal: () => openTemperatureModal,
@@ -1459,7 +1463,8 @@ var DeviceStatusType = {
1459
1463
  FAILURE: "failure",
1460
1464
  MAINTENANCE: "maintenance",
1461
1465
  NO_INFO: "no_info",
1462
- NOT_INSTALLED: "not_installed"
1466
+ NOT_INSTALLED: "not_installed",
1467
+ OFFLINE: "offline"
1463
1468
  };
1464
1469
  var ConnectionStatusType = {
1465
1470
  CONNECTED: "connected",
@@ -1468,39 +1473,42 @@ var ConnectionStatusType = {
1468
1473
  var deviceStatusIcons = {
1469
1474
  [DeviceStatusType.POWER_ON]: "\u26A1",
1470
1475
  [DeviceStatusType.STANDBY]: "\u{1F50C}",
1471
- [DeviceStatusType.POWER_OFF]: "\u{1F534}",
1476
+ [DeviceStatusType.POWER_OFF]: "\u26AB",
1472
1477
  [DeviceStatusType.WARNING]: "\u26A0\uFE0F",
1473
1478
  [DeviceStatusType.FAILURE]: "\u{1F6A8}",
1474
1479
  [DeviceStatusType.MAINTENANCE]: "\u{1F6E0}\uFE0F",
1475
1480
  [DeviceStatusType.NO_INFO]: "\u2753\uFE0F",
1476
- [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}"
1481
+ [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}",
1482
+ [DeviceStatusType.OFFLINE]: "\u{1F534}"
1477
1483
  };
1478
1484
  var waterDeviceStatusIcons = {
1479
1485
  [DeviceStatusType.POWER_ON]: "\u{1F4A7}",
1480
1486
  [DeviceStatusType.STANDBY]: "\u{1F6B0}",
1481
- [DeviceStatusType.POWER_OFF]: "\u{1F534}",
1487
+ [DeviceStatusType.POWER_OFF]: "\u26AB",
1482
1488
  [DeviceStatusType.WARNING]: "\u26A0\uFE0F",
1483
1489
  [DeviceStatusType.FAILURE]: "\u{1F6A8}",
1484
1490
  [DeviceStatusType.MAINTENANCE]: "\u{1F6E0}\uFE0F",
1485
1491
  [DeviceStatusType.NO_INFO]: "\u2753\uFE0F",
1486
- [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}"
1492
+ [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}",
1493
+ [DeviceStatusType.OFFLINE]: "\u{1F534}"
1487
1494
  };
1488
1495
  var temperatureDeviceStatusIcons = {
1489
1496
  [DeviceStatusType.POWER_ON]: "\u{1F321}\uFE0F",
1490
1497
  [DeviceStatusType.STANDBY]: "\u{1F321}\uFE0F",
1491
- [DeviceStatusType.POWER_OFF]: "\u{1F534}",
1498
+ [DeviceStatusType.POWER_OFF]: "\u26AB",
1492
1499
  [DeviceStatusType.WARNING]: "\u26A0\uFE0F",
1493
1500
  [DeviceStatusType.FAILURE]: "\u{1F6A8}",
1494
1501
  [DeviceStatusType.MAINTENANCE]: "\u{1F6E0}\uFE0F",
1495
1502
  [DeviceStatusType.NO_INFO]: "\u2753\uFE0F",
1496
- [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}"
1503
+ [DeviceStatusType.NOT_INSTALLED]: "\u{1F4E6}",
1504
+ [DeviceStatusType.OFFLINE]: "\u{1F534}"
1497
1505
  };
1498
1506
  var connectionStatusIcons = {
1499
1507
  [ConnectionStatusType.CONNECTED]: "\u{1F7E2}",
1500
1508
  [ConnectionStatusType.OFFLINE]: "\u{1F6AB}"
1501
1509
  };
1502
1510
  function mapDeviceToConnectionStatus(deviceStatus) {
1503
- if (deviceStatus === DeviceStatusType.NO_INFO) {
1511
+ if (deviceStatus === DeviceStatusType.NO_INFO || deviceStatus === DeviceStatusType.OFFLINE) {
1504
1512
  return ConnectionStatusType.OFFLINE;
1505
1513
  }
1506
1514
  return ConnectionStatusType.CONNECTED;
@@ -1524,15 +1532,16 @@ function mapDeviceStatusToCardStatus(deviceStatus) {
1524
1532
  [DeviceStatusType.FAILURE]: "fail",
1525
1533
  [DeviceStatusType.MAINTENANCE]: "alert",
1526
1534
  [DeviceStatusType.NO_INFO]: "unknown",
1527
- [DeviceStatusType.NOT_INSTALLED]: "not_installed"
1535
+ [DeviceStatusType.NOT_INSTALLED]: "not_installed",
1536
+ [DeviceStatusType.OFFLINE]: "offline"
1528
1537
  };
1529
1538
  return statusMap[deviceStatus] || "unknown";
1530
1539
  }
1531
1540
  function shouldFlashIcon(deviceStatus) {
1532
- return deviceStatus === DeviceStatusType.POWER_OFF || deviceStatus === DeviceStatusType.WARNING || deviceStatus === DeviceStatusType.FAILURE || deviceStatus === DeviceStatusType.MAINTENANCE;
1541
+ return deviceStatus === DeviceStatusType.POWER_OFF || deviceStatus === DeviceStatusType.WARNING || deviceStatus === DeviceStatusType.FAILURE || deviceStatus === DeviceStatusType.MAINTENANCE || deviceStatus === DeviceStatusType.OFFLINE;
1533
1542
  }
1534
1543
  function isDeviceOffline(deviceStatus) {
1535
- return deviceStatus === DeviceStatusType.NO_INFO;
1544
+ return deviceStatus === DeviceStatusType.NO_INFO || deviceStatus === DeviceStatusType.OFFLINE;
1536
1545
  }
1537
1546
  function getDeviceStatusIcon(deviceStatus, deviceType = null) {
1538
1547
  const normalizedType = deviceType?.toUpperCase() || "";
@@ -4088,15 +4097,30 @@ var CSS_STRING = `
4088
4097
  --myio-chip-alert-fg: #b45309;
4089
4098
  --myio-border-alert: rgba(245, 158, 11, 0.5);
4090
4099
 
4091
- /* Status colors - Failure (red) */
4100
+ /* Status colors - Failure (dark red) */
4092
4101
  --myio-chip-failure-bg: #fee2e2;
4093
4102
  --myio-chip-failure-fg: #b91c1c;
4094
- --myio-border-failure: rgba(239, 68, 68, 0.5);
4103
+ --myio-border-failure: rgba(153, 27, 27, 0.6);
4104
+
4105
+ /* Status colors - Power Off (light red) */
4106
+ --myio-chip-power-off-bg: #fecaca;
4107
+ --myio-chip-power-off-fg: #dc2626;
4108
+ --myio-border-power-off: rgba(239, 68, 68, 0.5);
4109
+
4110
+ /* Status colors - Offline (dark gray) */
4111
+ --myio-chip-offline-bg: #e2e8f0;
4112
+ --myio-chip-offline-fg: #475569;
4113
+ --myio-border-offline: rgba(71, 85, 105, 0.6);
4114
+
4115
+ /* Status colors - No Info (dark orange) */
4116
+ --myio-chip-no-info-bg: #fed7aa;
4117
+ --myio-chip-no-info-fg: #c2410c;
4118
+ --myio-border-no-info: rgba(194, 65, 12, 0.5);
4095
4119
 
4096
- /* Status colors - Offline (gray) */
4097
- --myio-chip-offline-bg: #f1f5f9;
4098
- --myio-chip-offline-fg: #64748b;
4099
- --myio-border-offline: rgba(100, 116, 139, 0.4);
4120
+ /* Status colors - Not Installed (purple) */
4121
+ --myio-chip-not-installed-bg: #e9d5ff;
4122
+ --myio-chip-not-installed-fg: #7c3aed;
4123
+ --myio-border-not-installed: rgba(124, 58, 237, 0.5);
4100
4124
 
4101
4125
  --myio-text-1: #0f172a;
4102
4126
  --myio-text-2: #4b5563;
@@ -4152,26 +4176,55 @@ var CSS_STRING = `
4152
4176
  }
4153
4177
 
4154
4178
  /* Card border states based on device status */
4155
- .myio-ho-card.is-ok {
4179
+
4180
+ /* POWER_ON - Blue */
4181
+ .myio-ho-card.is-power-on {
4156
4182
  border-color: var(--myio-border-ok);
4157
4183
  box-shadow: 0 0 0 2px var(--myio-border-ok), var(--myio-card-shadow);
4158
4184
  }
4159
4185
 
4186
+ /* STANDBY - Green */
4160
4187
  .myio-ho-card.is-standby {
4161
4188
  border-color: var(--myio-border-standby);
4162
4189
  box-shadow: 0 0 0 2px var(--myio-border-standby), var(--myio-card-shadow);
4163
4190
  }
4164
4191
 
4165
- .myio-ho-card.is-alert {
4192
+ /* WARNING - Yellow */
4193
+ .myio-ho-card.is-warning {
4194
+ border-color: var(--myio-border-alert);
4195
+ box-shadow: 0 0 0 2px var(--myio-border-alert), var(--myio-card-shadow);
4196
+ }
4197
+
4198
+ /* MAINTENANCE - Yellow */
4199
+ .myio-ho-card.is-maintenance {
4166
4200
  border-color: var(--myio-border-alert);
4167
4201
  box-shadow: 0 0 0 2px var(--myio-border-alert), var(--myio-card-shadow);
4168
4202
  }
4169
4203
 
4204
+ /* FAILURE - Dark Red */
4170
4205
  .myio-ho-card.is-failure {
4171
4206
  border-color: var(--myio-border-failure);
4172
4207
  box-shadow: 0 0 0 2px var(--myio-border-failure), var(--myio-card-shadow);
4173
4208
  }
4174
4209
 
4210
+ /* POWER_OFF - Light Red */
4211
+ .myio-ho-card.is-power-off {
4212
+ border-color: var(--myio-border-power-off);
4213
+ box-shadow: 0 0 0 2px var(--myio-border-power-off), var(--myio-card-shadow);
4214
+ }
4215
+
4216
+ /* NO_INFO - Dark Orange */
4217
+ .myio-ho-card.is-no-info {
4218
+ border-color: var(--myio-border-no-info);
4219
+ box-shadow: 0 0 0 2px var(--myio-border-no-info), var(--myio-card-shadow);
4220
+ }
4221
+
4222
+ /* NOT_INSTALLED - Purple */
4223
+ .myio-ho-card.is-not-installed {
4224
+ border-color: var(--myio-border-not-installed);
4225
+ box-shadow: 0 0 0 2px var(--myio-border-not-installed), var(--myio-card-shadow);
4226
+ }
4227
+
4175
4228
  /* Header section */
4176
4229
  .myio-ho-card__header {
4177
4230
  display: flex;
@@ -4189,7 +4242,9 @@ var CSS_STRING = `
4189
4242
  margin-top: 2px;
4190
4243
  }
4191
4244
 
4192
- .myio-ho-card.is-alert .myio-ho-card__icon {
4245
+ /* Icon colors based on status */
4246
+ .myio-ho-card.is-warning .myio-ho-card__icon,
4247
+ .myio-ho-card.is-maintenance .myio-ho-card__icon {
4193
4248
  color: var(--myio-chip-alert-fg);
4194
4249
  }
4195
4250
 
@@ -4197,14 +4252,28 @@ var CSS_STRING = `
4197
4252
  color: var(--myio-chip-failure-fg);
4198
4253
  }
4199
4254
 
4255
+ .myio-ho-card.is-power-off .myio-ho-card__icon {
4256
+ color: var(--myio-chip-power-off-fg);
4257
+ }
4258
+
4259
+ .myio-ho-card.is-offline .myio-ho-card__icon {
4260
+ color: var(--myio-chip-offline-fg);
4261
+ }
4262
+
4263
+ .myio-ho-card.is-no-info .myio-ho-card__icon {
4264
+ color: var(--myio-chip-no-info-fg);
4265
+ }
4266
+
4267
+ .myio-ho-card.is-not-installed .myio-ho-card__icon {
4268
+ color: var(--myio-chip-not-installed-fg);
4269
+ }
4270
+
4200
4271
  .myio-ho-card__title {
4201
4272
  flex: 1;
4202
4273
  min-width: 0;
4203
4274
  }
4204
4275
 
4205
- /* Adicione estas duas novas regras ao seu CSS_STRING */
4206
-
4207
- /* Estado Offline - borda cinza */
4276
+ /* OFFLINE - Dark Gray */
4208
4277
  .myio-ho-card.is-offline {
4209
4278
  border-color: var(--myio-border-offline);
4210
4279
  box-shadow: 0 0 0 2px var(--myio-border-offline), var(--myio-card-shadow);
@@ -4599,6 +4668,37 @@ var CSS_STRING = `
4599
4668
  color: var(--myio-chip-offline-fg);
4600
4669
  }
4601
4670
 
4671
+ /* New chip classes aligned with getCardStateClass */
4672
+ .chip--power-on {
4673
+ background: var(--myio-chip-ok-bg);
4674
+ color: var(--myio-chip-ok-fg);
4675
+ }
4676
+
4677
+ .chip--warning {
4678
+ background: var(--myio-chip-alert-bg);
4679
+ color: var(--myio-chip-alert-fg);
4680
+ }
4681
+
4682
+ .chip--maintenance {
4683
+ background: var(--myio-chip-alert-bg);
4684
+ color: var(--myio-chip-alert-fg);
4685
+ }
4686
+
4687
+ .chip--power-off {
4688
+ background: var(--myio-chip-power-off-bg);
4689
+ color: var(--myio-chip-power-off-fg);
4690
+ }
4691
+
4692
+ .chip--no-info {
4693
+ background: var(--myio-chip-no-info-bg);
4694
+ color: var(--myio-chip-no-info-fg);
4695
+ }
4696
+
4697
+ .chip--not-installed {
4698
+ background: var(--myio-chip-not-installed-bg);
4699
+ color: var(--myio-chip-not-installed-fg);
4700
+ }
4701
+
4602
4702
  /* Status indicator dot for power metric */
4603
4703
  .status-dot {
4604
4704
  width: 8px;
@@ -4632,6 +4732,31 @@ var CSS_STRING = `
4632
4732
  background: var(--myio-muted);
4633
4733
  }
4634
4734
 
4735
+ /* New dot classes aligned with getCardStateClass */
4736
+ .status-dot.dot--power-on {
4737
+ background: var(--myio-chip-ok-fg);
4738
+ }
4739
+
4740
+ .status-dot.dot--warning {
4741
+ background: var(--myio-chip-alert-fg);
4742
+ }
4743
+
4744
+ .status-dot.dot--maintenance {
4745
+ background: var(--myio-chip-alert-fg);
4746
+ }
4747
+
4748
+ .status-dot.dot--power-off {
4749
+ background: var(--myio-chip-power-off-fg);
4750
+ }
4751
+
4752
+ .status-dot.dot--no-info {
4753
+ background: var(--myio-chip-no-info-fg);
4754
+ }
4755
+
4756
+ .status-dot.dot--not-installed {
4757
+ background: var(--myio-chip-not-installed-fg);
4758
+ }
4759
+
4635
4760
  /* Primary metric */
4636
4761
  .myio-ho-card__primary {
4637
4762
  margin-bottom: 14px;
@@ -4814,6 +4939,215 @@ var CSS_STRING = `
4814
4939
  transition: none;
4815
4940
  }
4816
4941
  }
4942
+
4943
+ /* ============================================
4944
+ DEBUG TOOLTIP STYLES (Premium)
4945
+ ============================================ */
4946
+
4947
+ .has-debug-tooltip {
4948
+ cursor: help !important;
4949
+ }
4950
+
4951
+ .has-debug-tooltip::after {
4952
+ content: '\u{1F41B}';
4953
+ position: absolute;
4954
+ top: -4px;
4955
+ right: -4px;
4956
+ font-size: 10px;
4957
+ z-index: 1;
4958
+ }
4959
+
4960
+ .debug-tooltip-container {
4961
+ position: absolute;
4962
+ bottom: calc(100% + 8px);
4963
+ left: 50%;
4964
+ transform: translateX(-50%);
4965
+ z-index: 10000;
4966
+ pointer-events: none;
4967
+ opacity: 0;
4968
+ transition: opacity 0.2s ease, transform 0.2s ease;
4969
+ }
4970
+
4971
+ .has-debug-tooltip:hover .debug-tooltip-container {
4972
+ opacity: 1;
4973
+ pointer-events: auto;
4974
+ }
4975
+
4976
+ .debug-tooltip {
4977
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
4978
+ border: 1px solid rgba(99, 102, 241, 0.3);
4979
+ border-radius: 12px;
4980
+ box-shadow:
4981
+ 0 20px 40px rgba(0, 0, 0, 0.4),
4982
+ 0 0 0 1px rgba(255, 255, 255, 0.05),
4983
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
4984
+ min-width: 340px;
4985
+ max-width: 420px;
4986
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4987
+ font-size: 12px;
4988
+ color: #e2e8f0;
4989
+ overflow: hidden;
4990
+ }
4991
+
4992
+ .debug-tooltip__header {
4993
+ display: flex;
4994
+ align-items: center;
4995
+ gap: 8px;
4996
+ padding: 12px 16px;
4997
+ background: linear-gradient(90deg, rgba(99, 102, 241, 0.2) 0%, rgba(139, 92, 246, 0.1) 100%);
4998
+ border-bottom: 1px solid rgba(99, 102, 241, 0.2);
4999
+ }
5000
+
5001
+ .debug-tooltip__icon {
5002
+ font-size: 16px;
5003
+ }
5004
+
5005
+ .debug-tooltip__title {
5006
+ font-weight: 700;
5007
+ font-size: 13px;
5008
+ color: #f1f5f9;
5009
+ letter-spacing: 0.5px;
5010
+ text-transform: uppercase;
5011
+ }
5012
+
5013
+ .debug-tooltip__content {
5014
+ padding: 12px 16px;
5015
+ max-height: 400px;
5016
+ overflow-y: auto;
5017
+ }
5018
+
5019
+ .debug-tooltip__section {
5020
+ margin-bottom: 14px;
5021
+ padding-bottom: 12px;
5022
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
5023
+ }
5024
+
5025
+ .debug-tooltip__section:last-child {
5026
+ margin-bottom: 0;
5027
+ padding-bottom: 0;
5028
+ border-bottom: none;
5029
+ }
5030
+
5031
+ .debug-tooltip__section-title {
5032
+ font-size: 11px;
5033
+ font-weight: 600;
5034
+ color: #94a3b8;
5035
+ text-transform: uppercase;
5036
+ letter-spacing: 0.8px;
5037
+ margin-bottom: 10px;
5038
+ display: flex;
5039
+ align-items: center;
5040
+ gap: 6px;
5041
+ }
5042
+
5043
+ .debug-tooltip__row {
5044
+ display: flex;
5045
+ justify-content: space-between;
5046
+ align-items: flex-start;
5047
+ padding: 4px 0;
5048
+ gap: 12px;
5049
+ }
5050
+
5051
+ .debug-tooltip__row--full {
5052
+ flex-direction: column;
5053
+ gap: 4px;
5054
+ }
5055
+
5056
+ .debug-tooltip__label {
5057
+ color: #94a3b8;
5058
+ font-size: 11px;
5059
+ flex-shrink: 0;
5060
+ }
5061
+
5062
+ .debug-tooltip__value {
5063
+ color: #f1f5f9;
5064
+ font-weight: 500;
5065
+ text-align: right;
5066
+ word-break: break-all;
5067
+ }
5068
+
5069
+ .debug-tooltip__row--full .debug-tooltip__value {
5070
+ text-align: left;
5071
+ background: rgba(0, 0, 0, 0.3);
5072
+ padding: 6px 10px;
5073
+ border-radius: 6px;
5074
+ font-size: 11px;
5075
+ width: 100%;
5076
+ box-sizing: border-box;
5077
+ }
5078
+
5079
+ .debug-tooltip__value--mono {
5080
+ font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
5081
+ font-size: 11px;
5082
+ background: rgba(0, 0, 0, 0.3);
5083
+ padding: 2px 6px;
5084
+ border-radius: 4px;
5085
+ }
5086
+
5087
+ .debug-tooltip__value--highlight {
5088
+ color: #a5b4fc;
5089
+ font-style: italic;
5090
+ }
5091
+
5092
+ .debug-tooltip__badge {
5093
+ display: inline-block;
5094
+ padding: 2px 8px;
5095
+ border-radius: 4px;
5096
+ font-size: 10px;
5097
+ font-weight: 600;
5098
+ text-transform: uppercase;
5099
+ letter-spacing: 0.5px;
5100
+ background: rgba(99, 102, 241, 0.3);
5101
+ color: #a5b4fc;
5102
+ }
5103
+
5104
+ .debug-tooltip__badge--energy {
5105
+ background: rgba(59, 130, 246, 0.3);
5106
+ color: #93c5fd;
5107
+ }
5108
+
5109
+ .debug-tooltip__badge--water {
5110
+ background: rgba(6, 182, 212, 0.3);
5111
+ color: #67e8f9;
5112
+ }
5113
+
5114
+ .debug-tooltip__badge--temperature {
5115
+ background: rgba(249, 115, 22, 0.3);
5116
+ color: #fdba74;
5117
+ }
5118
+
5119
+ /* Tooltip arrow */
5120
+ .debug-tooltip::after {
5121
+ content: '';
5122
+ position: absolute;
5123
+ bottom: -6px;
5124
+ left: 50%;
5125
+ transform: translateX(-50%) rotate(45deg);
5126
+ width: 12px;
5127
+ height: 12px;
5128
+ background: #0f172a;
5129
+ border-right: 1px solid rgba(99, 102, 241, 0.3);
5130
+ border-bottom: 1px solid rgba(99, 102, 241, 0.3);
5131
+ }
5132
+
5133
+ /* Scrollbar styling for tooltip content */
5134
+ .debug-tooltip__content::-webkit-scrollbar {
5135
+ width: 6px;
5136
+ }
5137
+
5138
+ .debug-tooltip__content::-webkit-scrollbar-track {
5139
+ background: rgba(0, 0, 0, 0.2);
5140
+ border-radius: 3px;
5141
+ }
5142
+
5143
+ .debug-tooltip__content::-webkit-scrollbar-thumb {
5144
+ background: rgba(99, 102, 241, 0.4);
5145
+ border-radius: 3px;
5146
+ }
5147
+
5148
+ .debug-tooltip__content::-webkit-scrollbar-thumb:hover {
5149
+ background: rgba(99, 102, 241, 0.6);
5150
+ }
4817
5151
  `;
4818
5152
 
4819
5153
  // src/thingsboard/main-dashboard-shopping/v-4.0.0/head-office/card-head-office.icons.ts
@@ -4968,46 +5302,296 @@ var DEFAULT_I18N = {
4968
5302
  menu_settings: "Configura\xE7\xF5es"
4969
5303
  };
4970
5304
 
4971
- // src/thingsboard/main-dashboard-shopping/v-4.0.0/card/head-office/card-head-office.js
4972
- var CSS_TAG = "head-office-card-v1";
4973
- function ensureCss() {
4974
- if (!document.querySelector(`style[data-myio-css="${CSS_TAG}"]`)) {
4975
- const style = document.createElement("style");
4976
- style.setAttribute("data-myio-css", CSS_TAG);
4977
- style.textContent = CSS_STRING;
4978
- document.head.appendChild(style);
4979
- }
5305
+ // src/components/temperature/utils.ts
5306
+ var DAY_PERIODS = [
5307
+ { id: "madrugada", label: "Madrugada (00h-06h)", startHour: 0, endHour: 6 },
5308
+ { id: "manha", label: "Manh\xE3 (06h-12h)", startHour: 6, endHour: 12 },
5309
+ { id: "tarde", label: "Tarde (12h-18h)", startHour: 12, endHour: 18 },
5310
+ { id: "noite", label: "Noite (18h-24h)", startHour: 18, endHour: 24 }
5311
+ ];
5312
+ var DEFAULT_CLAMP_RANGE = { min: 15, max: 40 };
5313
+ function getTodaySoFar() {
5314
+ const now = /* @__PURE__ */ new Date();
5315
+ const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
5316
+ return {
5317
+ startTs: startOfDay.getTime(),
5318
+ endTs: now.getTime()
5319
+ };
4980
5320
  }
4981
- function normalizeParams(params) {
4982
- if (!params || !params.entityObject) {
4983
- throw new Error("renderCardCompenteHeadOffice: entityObject is required");
5321
+ var CHART_COLORS = [
5322
+ "#1976d2",
5323
+ // Blue
5324
+ "#FF6B6B",
5325
+ // Red
5326
+ "#4CAF50",
5327
+ // Green
5328
+ "#FF9800",
5329
+ // Orange
5330
+ "#9C27B0",
5331
+ // Purple
5332
+ "#00BCD4",
5333
+ // Cyan
5334
+ "#E91E63",
5335
+ // Pink
5336
+ "#795548"
5337
+ // Brown
5338
+ ];
5339
+ async function fetchTemperatureData(token, deviceId, startTs, endTs) {
5340
+ const url = `/api/plugins/telemetry/DEVICE/${deviceId}/values/timeseries?keys=temperature&startTs=${encodeURIComponent(startTs)}&endTs=${encodeURIComponent(endTs)}&limit=50000&agg=NONE`;
5341
+ const response = await fetch(url, {
5342
+ headers: {
5343
+ "X-Authorization": `Bearer ${token}`,
5344
+ "Content-Type": "application/json"
5345
+ }
5346
+ });
5347
+ if (!response.ok) {
5348
+ throw new Error(`Failed to fetch temperature data: ${response.status}`);
4984
5349
  }
4985
- const entityObject = params.entityObject;
4986
- if (!entityObject.entityId) {
4987
- console.warn("renderCardCompenteHeadOffice: entityId is missing, generating temporary ID");
4988
- entityObject.entityId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
5350
+ const data = await response.json();
5351
+ return data?.temperature || [];
5352
+ }
5353
+ function clampTemperature(value, range = DEFAULT_CLAMP_RANGE) {
5354
+ const num = Number(value || 0);
5355
+ if (num < range.min) return range.min;
5356
+ if (num > range.max) return range.max;
5357
+ return num;
5358
+ }
5359
+ function calculateStats(data, clampRange = DEFAULT_CLAMP_RANGE) {
5360
+ if (data.length === 0) {
5361
+ return { avg: 0, min: 0, max: 0, count: 0 };
4989
5362
  }
5363
+ const values = data.map((item) => clampTemperature(item.value, clampRange));
5364
+ const sum = values.reduce((acc, v) => acc + v, 0);
4990
5365
  return {
4991
- entityObject,
4992
- i18n: { ...DEFAULT_I18N, ...params.i18n || {} },
4993
- enableSelection: Boolean(params.enableSelection),
4994
- enableDragDrop: Boolean(params.enableDragDrop),
4995
- useNewComponents: Boolean(params.useNewComponents),
4996
- // RFC-0091: Configurable delay time for connection status check (default 15 minutes)
4997
- delayTimeConnectionInMins: params.delayTimeConnectionInMins ?? 15,
4998
- callbacks: {
4999
- handleActionDashboard: params.handleActionDashboard,
5000
- handleActionReport: params.handleActionReport,
5001
- handleActionSettings: params.handleActionSettings,
5002
- handleSelect: params.handleSelect,
5003
- handInfo: params.handInfo,
5004
- handleClickCard: params.handleClickCard
5005
- }
5366
+ avg: sum / values.length,
5367
+ min: Math.min(...values),
5368
+ max: Math.max(...values),
5369
+ count: values.length
5006
5370
  };
5007
5371
  }
5008
- function getIconSvg(deviceType, domain) {
5009
- if (domain === "water") {
5010
- return Icons.waterDrop;
5372
+ function interpolateTemperature(data, options) {
5373
+ const { intervalMinutes, startTs, endTs, clampRange = DEFAULT_CLAMP_RANGE } = options;
5374
+ const intervalMs = intervalMinutes * 60 * 1e3;
5375
+ if (data.length === 0) {
5376
+ return [];
5377
+ }
5378
+ const sortedData = [...data].sort((a, b) => a.ts - b.ts);
5379
+ const result = [];
5380
+ let lastKnownValue = clampTemperature(sortedData[0].value, clampRange);
5381
+ let dataIndex = 0;
5382
+ for (let ts = startTs; ts <= endTs; ts += intervalMs) {
5383
+ while (dataIndex < sortedData.length - 1 && sortedData[dataIndex + 1].ts <= ts) {
5384
+ dataIndex++;
5385
+ }
5386
+ const currentData = sortedData[dataIndex];
5387
+ if (currentData && Math.abs(currentData.ts - ts) < intervalMs) {
5388
+ lastKnownValue = clampTemperature(currentData.value, clampRange);
5389
+ }
5390
+ result.push({
5391
+ ts,
5392
+ value: lastKnownValue
5393
+ });
5394
+ }
5395
+ return result;
5396
+ }
5397
+ function aggregateByDay(data, clampRange = DEFAULT_CLAMP_RANGE) {
5398
+ if (data.length === 0) {
5399
+ return [];
5400
+ }
5401
+ const dayMap = /* @__PURE__ */ new Map();
5402
+ data.forEach((item) => {
5403
+ const date = new Date(item.ts);
5404
+ const dateKey = date.toISOString().split("T")[0];
5405
+ if (!dayMap.has(dateKey)) {
5406
+ dayMap.set(dateKey, []);
5407
+ }
5408
+ dayMap.get(dateKey).push(item);
5409
+ });
5410
+ const result = [];
5411
+ dayMap.forEach((dayData, dateKey) => {
5412
+ const values = dayData.map((item) => clampTemperature(item.value, clampRange));
5413
+ const sum = values.reduce((acc, v) => acc + v, 0);
5414
+ result.push({
5415
+ date: dateKey,
5416
+ dateTs: new Date(dateKey).getTime(),
5417
+ avg: sum / values.length,
5418
+ min: Math.min(...values),
5419
+ max: Math.max(...values),
5420
+ count: values.length
5421
+ });
5422
+ });
5423
+ return result.sort((a, b) => a.dateTs - b.dateTs);
5424
+ }
5425
+ function filterByDayPeriods(data, selectedPeriods) {
5426
+ if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
5427
+ return data;
5428
+ }
5429
+ return data.filter((item) => {
5430
+ const date = new Date(item.ts);
5431
+ const hour = date.getHours();
5432
+ return selectedPeriods.some((periodId) => {
5433
+ const period = DAY_PERIODS.find((p) => p.id === periodId);
5434
+ if (!period) return false;
5435
+ return hour >= period.startHour && hour < period.endHour;
5436
+ });
5437
+ });
5438
+ }
5439
+ function getSelectedPeriodsLabel(selectedPeriods) {
5440
+ if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
5441
+ return "Todos os per\xEDodos";
5442
+ }
5443
+ if (selectedPeriods.length === 1) {
5444
+ const period = DAY_PERIODS.find((p) => p.id === selectedPeriods[0]);
5445
+ return period?.label || "";
5446
+ }
5447
+ return `${selectedPeriods.length} per\xEDodos selecionados`;
5448
+ }
5449
+ function formatTemperature(value, decimals = 1) {
5450
+ if (value === null || value === void 0 || isNaN(value)) {
5451
+ return "\u2014";
5452
+ }
5453
+ return `${value.toFixed(decimals)}\xB0C`;
5454
+ }
5455
+ function exportTemperatureCSV(data, deviceLabel, stats, startDate, endDate) {
5456
+ if (data.length === 0) {
5457
+ console.warn("No data to export");
5458
+ return;
5459
+ }
5460
+ const BOM = "\uFEFF";
5461
+ let csvContent = BOM;
5462
+ csvContent += `Relat\xF3rio de Temperatura - ${deviceLabel}
5463
+ `;
5464
+ csvContent += `Per\xEDodo: ${startDate} at\xE9 ${endDate}
5465
+ `;
5466
+ csvContent += `M\xE9dia: ${formatTemperature(stats.avg)}
5467
+ `;
5468
+ csvContent += `M\xEDnima: ${formatTemperature(stats.min)}
5469
+ `;
5470
+ csvContent += `M\xE1xima: ${formatTemperature(stats.max)}
5471
+ `;
5472
+ csvContent += `Total de leituras: ${stats.count}
5473
+ `;
5474
+ csvContent += "\n";
5475
+ csvContent += "Data/Hora,Temperatura (\xB0C)\n";
5476
+ data.forEach((item) => {
5477
+ const date = new Date(item.ts).toLocaleString("pt-BR");
5478
+ const temp = Number(item.value).toFixed(2);
5479
+ csvContent += `"${date}",${temp}
5480
+ `;
5481
+ });
5482
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
5483
+ const url = URL.createObjectURL(blob);
5484
+ const link = document.createElement("a");
5485
+ link.href = url;
5486
+ link.download = `temperatura_${deviceLabel.replace(/\s+/g, "_")}_${startDate}_${endDate}.csv`;
5487
+ document.body.appendChild(link);
5488
+ link.click();
5489
+ document.body.removeChild(link);
5490
+ URL.revokeObjectURL(url);
5491
+ }
5492
+ var DARK_THEME = {
5493
+ background: "rgba(0, 0, 0, 0.85)",
5494
+ surface: "#1a1f28",
5495
+ text: "#ffffff",
5496
+ textMuted: "rgba(255, 255, 255, 0.7)",
5497
+ border: "rgba(255, 255, 255, 0.1)",
5498
+ primary: "#1976d2",
5499
+ success: "#4CAF50",
5500
+ warning: "#FF9800",
5501
+ danger: "#f44336",
5502
+ chartLine: "#1976d2",
5503
+ chartGrid: "rgba(255, 255, 255, 0.1)"
5504
+ };
5505
+ var LIGHT_THEME = {
5506
+ background: "rgba(0, 0, 0, 0.6)",
5507
+ surface: "#ffffff",
5508
+ text: "#333333",
5509
+ textMuted: "#666666",
5510
+ border: "#e0e0e0",
5511
+ primary: "#1976d2",
5512
+ success: "#4CAF50",
5513
+ warning: "#FF9800",
5514
+ danger: "#f44336",
5515
+ chartLine: "#1976d2",
5516
+ chartGrid: "#e0e0e0"
5517
+ };
5518
+ function getThemeColors(theme) {
5519
+ return theme === "dark" ? DARK_THEME : LIGHT_THEME;
5520
+ }
5521
+
5522
+ // src/utils/logHelper.js
5523
+ function createLogHelper(debugActive = false) {
5524
+ return {
5525
+ log: function(...args) {
5526
+ if (debugActive) {
5527
+ console.log(...args);
5528
+ }
5529
+ },
5530
+ warn: function(...args) {
5531
+ if (debugActive) {
5532
+ console.warn(...args);
5533
+ }
5534
+ },
5535
+ error: function(...args) {
5536
+ console.error(...args);
5537
+ }
5538
+ };
5539
+ }
5540
+ var LogHelper = createLogHelper(false);
5541
+
5542
+ // src/thingsboard/main-dashboard-shopping/v-4.0.0/card/head-office/card-head-office.js
5543
+ var LABEL_CHAR_LIMIT = 18;
5544
+ var DEFAUL_DELAY_TIME_CONNECTION_IN_MINS = 1440;
5545
+ var CSS_TAG = "head-office-card-v1";
5546
+ function ensureCss() {
5547
+ if (!document.querySelector(`style[data-myio-css="${CSS_TAG}"]`)) {
5548
+ const style = document.createElement("style");
5549
+ style.setAttribute("data-myio-css", CSS_TAG);
5550
+ style.textContent = CSS_STRING;
5551
+ document.head.appendChild(style);
5552
+ }
5553
+ }
5554
+ function normalizeParams(params) {
5555
+ if (!params || !params.entityObject) {
5556
+ throw new Error("renderCardCompenteHeadOffice: entityObject is required");
5557
+ }
5558
+ const LogHelper2 = createLogHelper(params.debugActive ?? false);
5559
+ const entityObject = params.entityObject;
5560
+ if (!entityObject.entityId) {
5561
+ LogHelper2.warn("[CardHeadOffice] entityId is missing, generating temporary ID");
5562
+ entityObject.entityId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
5563
+ }
5564
+ if (!params.delayTimeConnectionInMins) {
5565
+ LogHelper2.warn(
5566
+ `[CardHeadOffice] delayTimeConnectionInMins is missing, defaulting to ${DEFAUL_DELAY_TIME_CONNECTION_IN_MINS} mins`
5567
+ );
5568
+ }
5569
+ return {
5570
+ entityObject,
5571
+ i18n: { ...DEFAULT_I18N, ...params.i18n || {} },
5572
+ enableSelection: Boolean(params.enableSelection),
5573
+ enableDragDrop: Boolean(params.enableDragDrop),
5574
+ useNewComponents: Boolean(params.useNewComponents),
5575
+ // RFC-0091: Configurable delay time for connection status check (default 15 minutes)
5576
+ delayTimeConnectionInMins: params.delayTimeConnectionInMins ?? DEFAUL_DELAY_TIME_CONNECTION_IN_MINS,
5577
+ // Debug options
5578
+ debugActive: params.debugActive ?? false,
5579
+ activeTooltipDebug: params.activeTooltipDebug ?? false,
5580
+ // LogHelper instance for this card
5581
+ LogHelper: LogHelper2,
5582
+ callbacks: {
5583
+ handleActionDashboard: params.handleActionDashboard,
5584
+ handleActionReport: params.handleActionReport,
5585
+ handleActionSettings: params.handleActionSettings,
5586
+ handleSelect: params.handleSelect,
5587
+ handInfo: params.handInfo,
5588
+ handleClickCard: params.handleClickCard
5589
+ }
5590
+ };
5591
+ }
5592
+ function getIconSvg(deviceType, domain) {
5593
+ if (domain === "water") {
5594
+ return Icons.waterDrop;
5011
5595
  }
5012
5596
  if (domain === "temperature") {
5013
5597
  return Icons.thermometer;
@@ -5032,16 +5616,10 @@ function formatValueByDomain(value, domain) {
5032
5616
  return formatWaterVolumeM3(value);
5033
5617
  }
5034
5618
  if (domain === "temperature") {
5035
- return formatTemperature(value);
5619
+ return formatTemperature(value, 0);
5036
5620
  }
5037
5621
  return formatEnergy(value);
5038
5622
  }
5039
- function formatTemperature(temp) {
5040
- if (temp === null || temp === void 0 || isNaN(temp)) {
5041
- return "\u2014";
5042
- }
5043
- return `${temp.toFixed(0)}\xB0C`;
5044
- }
5045
5623
  function calculateConsumptionPercentage(target, consumption) {
5046
5624
  const numericTarget = Number(target);
5047
5625
  const numericConsumption = Number(consumption);
@@ -5056,32 +5634,32 @@ function getStatusInfo(deviceStatus, i18n, domain) {
5056
5634
  // --- Novos Status de Temperatura ---
5057
5635
  case "normal":
5058
5636
  return { chipClass: "chip--ok", label: "Normal" };
5059
- // Verde/Azul
5060
5637
  case "cold":
5061
5638
  return { chipClass: "chip--standby", label: "Frio" };
5062
- // Azul claro/Ciano
5063
5639
  case "hot":
5064
5640
  return { chipClass: "chip--alert", label: "Quente" };
5065
- // Laranja/Amarelo
5066
- // --- Status Existentes ---
5641
+ // --- Status Existentes (aligned with getCardStateClass) ---
5067
5642
  case DeviceStatusType.POWER_ON:
5068
5643
  if (domain === "water") {
5069
- return { chipClass: "chip--ok", label: i18n.in_operation_water };
5644
+ return { chipClass: "chip--power-on", label: i18n.in_operation_water };
5070
5645
  }
5071
- return { chipClass: "chip--ok", label: i18n.in_operation };
5646
+ return { chipClass: "chip--power-on", label: i18n.in_operation };
5072
5647
  case DeviceStatusType.STANDBY:
5073
5648
  return { chipClass: "chip--standby", label: i18n.standby };
5074
5649
  case DeviceStatusType.WARNING:
5075
- return { chipClass: "chip--alert", label: i18n.alert };
5650
+ return { chipClass: "chip--warning", label: i18n.alert };
5651
+ case DeviceStatusType.MAINTENANCE:
5652
+ return { chipClass: "chip--maintenance", label: i18n.maintenance };
5076
5653
  case DeviceStatusType.FAILURE:
5077
- case DeviceStatusType.POWER_OFF:
5078
5654
  return { chipClass: "chip--failure", label: i18n.failure };
5079
- case DeviceStatusType.MAINTENANCE:
5080
- return { chipClass: "chip--alert", label: i18n.maintenance };
5081
- case DeviceStatusType.NOT_INSTALLED:
5082
- return { chipClass: "chip--offline", label: i18n.not_installed };
5083
- // Default (Cai aqui se não achar 'normal', 'hot' etc)
5655
+ case DeviceStatusType.POWER_OFF:
5656
+ return { chipClass: "chip--power-off", label: i18n.power_off || i18n.failure };
5657
+ case DeviceStatusType.OFFLINE:
5658
+ return { chipClass: "chip--offline", label: i18n.offline };
5084
5659
  case DeviceStatusType.NO_INFO:
5660
+ return { chipClass: "chip--no-info", label: i18n.no_info || i18n.offline };
5661
+ case DeviceStatusType.NOT_INSTALLED:
5662
+ return { chipClass: "chip--not-installed", label: i18n.not_installed };
5085
5663
  default:
5086
5664
  return { chipClass: "chip--offline", label: i18n.offline };
5087
5665
  }
@@ -5089,23 +5667,32 @@ function getStatusInfo(deviceStatus, i18n, domain) {
5089
5667
  function getCardStateClass(deviceStatus) {
5090
5668
  switch (deviceStatus) {
5091
5669
  case DeviceStatusType.POWER_ON:
5092
- return "is-ok";
5670
+ return "is-power-on";
5093
5671
  // Blue border
5094
5672
  case DeviceStatusType.STANDBY:
5095
5673
  return "is-standby";
5096
5674
  // Green border
5097
5675
  case DeviceStatusType.WARNING:
5676
+ return "is-warning";
5677
+ // Yellow border
5098
5678
  case DeviceStatusType.MAINTENANCE:
5099
- return "is-alert";
5679
+ return "is-maintenance";
5100
5680
  // Yellow border
5101
5681
  case DeviceStatusType.FAILURE:
5102
- case DeviceStatusType.POWER_OFF:
5103
5682
  return "is-failure";
5104
- // Red border
5683
+ // Dark Red border
5684
+ case DeviceStatusType.POWER_OFF:
5685
+ return "is-power-off";
5686
+ // Light Red border
5687
+ case DeviceStatusType.OFFLINE:
5688
+ return "is-offline";
5689
+ // Dark Gray border
5105
5690
  case DeviceStatusType.NO_INFO:
5691
+ return "is-no-info";
5692
+ // Dark Orange border
5106
5693
  case DeviceStatusType.NOT_INSTALLED:
5107
- return "is-offline";
5108
- // Gray border
5694
+ return "is-not-installed";
5695
+ // Purple border
5109
5696
  default:
5110
5697
  return "";
5111
5698
  }
@@ -5119,17 +5706,25 @@ function getStatusDotClass(deviceStatus) {
5119
5706
  return "dot--standby";
5120
5707
  case "hot":
5121
5708
  return "dot--alert";
5122
- // --- Status Existentes ---
5709
+ // --- Status Existentes (aligned with getCardStateClass) ---
5123
5710
  case DeviceStatusType.POWER_ON:
5124
- return "dot--ok";
5711
+ return "dot--power-on";
5125
5712
  case DeviceStatusType.STANDBY:
5126
5713
  return "dot--standby";
5127
5714
  case DeviceStatusType.WARNING:
5715
+ return "dot--warning";
5128
5716
  case DeviceStatusType.MAINTENANCE:
5129
- return "dot--alert";
5717
+ return "dot--maintenance";
5130
5718
  case DeviceStatusType.FAILURE:
5131
- case DeviceStatusType.POWER_OFF:
5132
5719
  return "dot--failure";
5720
+ case DeviceStatusType.POWER_OFF:
5721
+ return "dot--power-off";
5722
+ case DeviceStatusType.OFFLINE:
5723
+ return "dot--offline";
5724
+ case DeviceStatusType.NO_INFO:
5725
+ return "dot--no-info";
5726
+ case DeviceStatusType.NOT_INSTALLED:
5727
+ return "dot--not-installed";
5133
5728
  default:
5134
5729
  return "dot--offline";
5135
5730
  }
@@ -5153,7 +5748,13 @@ function buildDOM(state) {
5153
5748
  titleSection.className = "myio-ho-card__title";
5154
5749
  const nameEl = document.createElement("div");
5155
5750
  nameEl.className = "myio-ho-card__name";
5156
- nameEl.textContent = entityObject.labelOrName || "Unknown Device";
5751
+ const fullName = entityObject.labelOrName || "Unknown Device";
5752
+ if (fullName.length > LABEL_CHAR_LIMIT) {
5753
+ nameEl.textContent = fullName.slice(0, LABEL_CHAR_LIMIT) + "\u2026";
5754
+ nameEl.title = fullName;
5755
+ } else {
5756
+ nameEl.textContent = fullName;
5757
+ }
5157
5758
  titleSection.appendChild(nameEl);
5158
5759
  if (entityObject.deviceIdentifier) {
5159
5760
  const codeEl = document.createElement("div");
@@ -5282,29 +5883,207 @@ function buildDOM(state) {
5282
5883
  root.appendChild(footer);
5283
5884
  return root;
5284
5885
  }
5285
- function verifyOfflineStatus(entityObject, delayTimeInMins = 15) {
5886
+ function buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins) {
5887
+ const formatTimestamp = (ts) => {
5888
+ if (!ts) return "N/A";
5889
+ const d = new Date(ts);
5890
+ return d.toLocaleString("pt-BR", {
5891
+ day: "2-digit",
5892
+ month: "2-digit",
5893
+ year: "numeric",
5894
+ hour: "2-digit",
5895
+ minute: "2-digit",
5896
+ second: "2-digit"
5897
+ });
5898
+ };
5899
+ return {
5900
+ // Entity identification
5901
+ entityId: entityObject.entityId || "N/A",
5902
+ name: entityObject.name || entityObject.nameEl || "N/A",
5903
+ domain: entityObject.domain || "energy",
5904
+ // Status decision chain
5905
+ originalDeviceStatus: entityObject._originalDeviceStatus || entityObject.deviceStatus,
5906
+ finalDeviceStatus: entityObject.deviceStatus,
5907
+ connectionStatus: entityObject.connectionStatus || "N/A",
5908
+ statusDecisionSource,
5909
+ // Visual output
5910
+ stateClass,
5911
+ chipClass: statusInfo.chipClass,
5912
+ chipLabel: statusInfo.label,
5913
+ // Connection timestamps
5914
+ lastConnectTime: formatTimestamp(entityObject.lastConnectTime),
5915
+ lastDisconnectTime: formatTimestamp(entityObject.lastDisconnectTime),
5916
+ delayTimeConnectionInMins,
5917
+ // Raw values
5918
+ val: entityObject.val,
5919
+ consumptionTargetValue: entityObject.consumptionTargetValue,
5920
+ deviceType: entityObject.deviceType || "N/A"
5921
+ };
5922
+ }
5923
+ function attachDebugTooltip(element, debugInfo) {
5924
+ const existingTooltip = element.querySelector(".debug-tooltip-container");
5925
+ if (existingTooltip) {
5926
+ existingTooltip.remove();
5927
+ }
5928
+ const tooltipContainer = document.createElement("div");
5929
+ tooltipContainer.className = "debug-tooltip-container";
5930
+ const tooltip = document.createElement("div");
5931
+ tooltip.className = "debug-tooltip";
5932
+ tooltip.innerHTML = `
5933
+ <div class="debug-tooltip__header">
5934
+ <span class="debug-tooltip__icon">\u{1F50D}</span>
5935
+ <span class="debug-tooltip__title">Debug Info</span>
5936
+ </div>
5937
+ <div class="debug-tooltip__content">
5938
+ <div class="debug-tooltip__section">
5939
+ <div class="debug-tooltip__section-title">\u{1F4CB} Identifica\xE7\xE3o</div>
5940
+ <div class="debug-tooltip__row">
5941
+ <span class="debug-tooltip__label">Entity ID:</span>
5942
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.entityId}</span>
5943
+ </div>
5944
+ <div class="debug-tooltip__row">
5945
+ <span class="debug-tooltip__label">Nome:</span>
5946
+ <span class="debug-tooltip__value">${debugInfo.name}</span>
5947
+ </div>
5948
+ <div class="debug-tooltip__row">
5949
+ <span class="debug-tooltip__label">Dom\xEDnio:</span>
5950
+ <span class="debug-tooltip__value debug-tooltip__badge debug-tooltip__badge--${debugInfo.domain}">${debugInfo.domain}</span>
5951
+ </div>
5952
+ </div>
5953
+
5954
+ <div class="debug-tooltip__section">
5955
+ <div class="debug-tooltip__section-title">\u26A1 Decis\xE3o de Status</div>
5956
+ <div class="debug-tooltip__row">
5957
+ <span class="debug-tooltip__label">connectionStatus:</span>
5958
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.connectionStatus}</span>
5959
+ </div>
5960
+ <div class="debug-tooltip__row">
5961
+ <span class="debug-tooltip__label">deviceStatus (final):</span>
5962
+ <span class="debug-tooltip__value debug-tooltip__badge">${debugInfo.finalDeviceStatus}</span>
5963
+ </div>
5964
+ <div class="debug-tooltip__row debug-tooltip__row--full">
5965
+ <span class="debug-tooltip__label">Fonte da decis\xE3o:</span>
5966
+ <span class="debug-tooltip__value debug-tooltip__value--highlight">${debugInfo.statusDecisionSource}</span>
5967
+ </div>
5968
+ </div>
5969
+
5970
+ <div class="debug-tooltip__section">
5971
+ <div class="debug-tooltip__section-title">\u{1F3A8} Output Visual</div>
5972
+ <div class="debug-tooltip__row">
5973
+ <span class="debug-tooltip__label">stateClass:</span>
5974
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.stateClass}</span>
5975
+ </div>
5976
+ <div class="debug-tooltip__row">
5977
+ <span class="debug-tooltip__label">chipClass:</span>
5978
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.chipClass}</span>
5979
+ </div>
5980
+ <div class="debug-tooltip__row">
5981
+ <span class="debug-tooltip__label">chipLabel:</span>
5982
+ <span class="debug-tooltip__value">${debugInfo.chipLabel}</span>
5983
+ </div>
5984
+ </div>
5985
+
5986
+ <div class="debug-tooltip__section">
5987
+ <div class="debug-tooltip__section-title">\u{1F550} Timestamps de Conex\xE3o</div>
5988
+ <div class="debug-tooltip__row">
5989
+ <span class="debug-tooltip__label">lastConnectTime:</span>
5990
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.lastConnectTime}</span>
5991
+ </div>
5992
+ <div class="debug-tooltip__row">
5993
+ <span class="debug-tooltip__label">lastDisconnectTime:</span>
5994
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.lastDisconnectTime}</span>
5995
+ </div>
5996
+ <div class="debug-tooltip__row">
5997
+ <span class="debug-tooltip__label">delayTime:</span>
5998
+ <span class="debug-tooltip__value">${debugInfo.delayTimeConnectionInMins} mins</span>
5999
+ </div>
6000
+ </div>
6001
+
6002
+ <div class="debug-tooltip__section">
6003
+ <div class="debug-tooltip__section-title">\u{1F4CA} Valores</div>
6004
+ <div class="debug-tooltip__row">
6005
+ <span class="debug-tooltip__label">val (consumo):</span>
6006
+ <span class="debug-tooltip__value">${debugInfo.val ?? "N/A"}</span>
6007
+ </div>
6008
+ <div class="debug-tooltip__row">
6009
+ <span class="debug-tooltip__label">target (meta):</span>
6010
+ <span class="debug-tooltip__value">${debugInfo.consumptionTargetValue ?? "N/A"}</span>
6011
+ </div>
6012
+ <div class="debug-tooltip__row">
6013
+ <span class="debug-tooltip__label">deviceType:</span>
6014
+ <span class="debug-tooltip__value debug-tooltip__value--mono">${debugInfo.deviceType}</span>
6015
+ </div>
6016
+ </div>
6017
+ </div>
6018
+ `;
6019
+ tooltipContainer.appendChild(tooltip);
6020
+ element.style.position = "relative";
6021
+ element.style.cursor = "help";
6022
+ element.appendChild(tooltipContainer);
6023
+ element.classList.add("has-debug-tooltip");
6024
+ }
6025
+ function verifyOfflineStatus(entityObject, delayTimeInMins = 15, LogHelper2) {
5286
6026
  const lastConnectionTime = new Date(entityObject.lastConnectTime || 0);
5287
6027
  const lastDisconnectTime = new Date(entityObject.lastDisconnectTime || 0);
5288
6028
  const now = /* @__PURE__ */ new Date();
5289
6029
  const delayTimeInMs = delayTimeInMins * 60 * 1e3;
5290
6030
  const timeSinceConnection = now.getTime() - lastConnectionTime.getTime();
6031
+ let isOffline = false;
5291
6032
  if (lastDisconnectTime.getTime() > lastConnectionTime.getTime()) {
5292
- return false;
5293
- }
5294
- if (timeSinceConnection < delayTimeInMs) {
5295
- return false;
6033
+ isOffline = true;
6034
+ LogHelper2.log(
6035
+ "[CardHeadOffice][ConnectionStatus Verify] Device is OFFLINE because lastDisconnectTime is more recent than lastConnectTime",
6036
+ entityObject.nameEl
6037
+ );
6038
+ } else if (timeSinceConnection > delayTimeInMs) {
6039
+ isOffline = true;
6040
+ LogHelper2.log(
6041
+ "[CardHeadOffice][ConnectionStatus Verify] Device is OFFLINE because lastConnectTime is older than configured delayTimeConnectionInMins:",
6042
+ delayTimeInMins,
6043
+ "for device",
6044
+ entityObject.nameEl
6045
+ );
6046
+ } else {
6047
+ isOffline = false;
6048
+ LogHelper2.log(
6049
+ "[CardHeadOffice][ConnectionStatus Verify] Device is ONLINE because lastConnectTime is within configured delayTimeConnectionInMins:",
6050
+ delayTimeInMins,
6051
+ "for device",
6052
+ entityObject.nameEl
6053
+ );
5296
6054
  }
5297
- return true;
6055
+ return isOffline;
5298
6056
  }
5299
6057
  function paint(root, state) {
5300
- const { entityObject, i18n, delayTimeConnectionInMins, isSelected } = state;
6058
+ const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state;
6059
+ let statusDecisionSource = "unknown";
5301
6060
  if (entityObject.connectionStatus) {
5302
6061
  if (entityObject.connectionStatus === "offline") {
5303
- entityObject.deviceStatus = DeviceStatusType.NO_INFO;
6062
+ LogHelper2.log(
6063
+ "[CardHeadOffice][ConnectionStatus Verify 01] Setting deviceStatus to OFFLINE based on connectionStatus"
6064
+ );
6065
+ entityObject.deviceStatus = DeviceStatusType.OFFLINE;
6066
+ statusDecisionSource = 'connectionStatus === "offline"';
6067
+ } else {
6068
+ LogHelper2.log(
6069
+ "[CardHeadOffice] Device is ONLINE or WAITING based on connectionStatus for device",
6070
+ entityObject.nameEl
6071
+ );
6072
+ statusDecisionSource = `connectionStatus === "${entityObject.connectionStatus}" (kept original deviceStatus)`;
5304
6073
  }
5305
6074
  } else {
5306
- if (verifyOfflineStatus(entityObject, delayTimeConnectionInMins) === false) {
5307
- entityObject.deviceStatus = DeviceStatusType.NO_INFO;
6075
+ if (verifyOfflineStatus(entityObject, delayTimeConnectionInMins, LogHelper2) === false) {
6076
+ LogHelper2.log(
6077
+ "[CardHeadOffice][ConnectionStatus Verify 02] Setting deviceStatus to OFFLINE based on timestamp verification by verifyOfflineStatus METHOD with delayTimeConnectionInMins:",
6078
+ delayTimeConnectionInMins
6079
+ );
6080
+ entityObject.deviceStatus = DeviceStatusType.OFFLINE;
6081
+ statusDecisionSource = `verifyOfflineStatus() returned false (delay: ${delayTimeConnectionInMins} mins)`;
6082
+ } else {
6083
+ LogHelper2.log(
6084
+ `[CardHeadOffice][ConnectionStatus Verify 03] Device is ONLINE with deviceStatus = ${entityObject.deviceStatus} based on timestamp verification for device ${entityObject.nameEl}`
6085
+ );
6086
+ statusDecisionSource = `verifyOfflineStatus() returned true (delay: ${delayTimeConnectionInMins} mins)`;
5308
6087
  }
5309
6088
  }
5310
6089
  const stateClass = getCardStateClass(entityObject.deviceStatus);
@@ -5313,6 +6092,10 @@ function paint(root, state) {
5313
6092
  const chip = root.querySelector(".chip");
5314
6093
  chip.className = `chip ${statusInfo.chipClass}`;
5315
6094
  chip.innerHTML = statusInfo.label;
6095
+ if (activeTooltipDebug) {
6096
+ const debugInfo = buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins);
6097
+ attachDebugTooltip(chip, debugInfo);
6098
+ }
5316
6099
  const primaryValue = formatValueByDomain(entityObject.val, entityObject.domain);
5317
6100
  const numSpan = root.querySelector(".myio-ho-card__value .num");
5318
6101
  const unitSpan = root.querySelector(".myio-ho-card__value .unit");
@@ -5525,6 +6308,7 @@ function renderCardComponentHeadOffice(containerEl, params) {
5525
6308
  }
5526
6309
 
5527
6310
  // src/thingsboard/main-dashboard-shopping/v-5.2.0/card/template-card-v5.js
6311
+ var LABEL_CHAR_LIMIT2 = 18;
5528
6312
  function renderCardComponentV5({
5529
6313
  entityObject,
5530
6314
  handleActionDashboard,
@@ -6041,7 +6825,7 @@ ${rangeText}`;
6041
6825
 
6042
6826
  <div class="device-title-row" style="flex-direction: column; min-height: 38px; text-align: center; width: 100%;">
6043
6827
  <span class="device-title" title="${cardEntity.name}">
6044
- ${cardEntity.name.length > 18 ? cardEntity.name.slice(0, 18) + "\u2026" : cardEntity.name}
6828
+ ${cardEntity.name.length > LABEL_CHAR_LIMIT2 ? cardEntity.name.slice(0, LABEL_CHAR_LIMIT2) + "\u2026" : cardEntity.name}
6045
6829
  </span>
6046
6830
  ${deviceIdentifier ? `
6047
6831
  <span class="device-subtitle" title="${deviceIdentifier}">
@@ -15981,23 +16765,1019 @@ function validateOptions2(options) {
15981
16765
  if (isNaN(level) || level < 0 || level > 100) {
15982
16766
  errors.push("currentLevel must be a number between 0 and 100");
15983
16767
  }
15984
- }
15985
- if (options.limit !== void 0) {
15986
- const limit = Number(options.limit);
15987
- if (isNaN(limit) || limit < 1 || limit > 1e4) {
15988
- errors.push("limit must be a number between 1 and 10000");
16768
+ }
16769
+ if (options.limit !== void 0) {
16770
+ const limit = Number(options.limit);
16771
+ if (isNaN(limit) || limit < 1 || limit > 1e4) {
16772
+ errors.push("limit must be a number between 1 and 10000");
16773
+ }
16774
+ }
16775
+ if (options.aggregation !== void 0) {
16776
+ const validAggregations = ["NONE", "MIN", "MAX", "AVG", "SUM", "COUNT"];
16777
+ if (!validAggregations.includes(options.aggregation)) {
16778
+ errors.push(`aggregation must be one of: ${validAggregations.join(", ")}`);
16779
+ }
16780
+ }
16781
+ if (errors.length > 0) {
16782
+ throw new Error(`Validation failed:
16783
+ - ${errors.join("\n- ")}`);
16784
+ }
16785
+ }
16786
+
16787
+ // src/components/premium-modals/power-limits/types.ts
16788
+ var DEVICE_TYPES = [
16789
+ { value: "ELEVADOR", label: "Elevator" },
16790
+ { value: "ESCADA_ROLANTE", label: "Escalator" },
16791
+ { value: "MOTOR", label: "Motor" },
16792
+ { value: "BOMBA", label: "Pump" },
16793
+ { value: "CHILLER", label: "Chiller" },
16794
+ { value: "AR_CONDICIONADO", label: "Air Conditioner" },
16795
+ { value: "HVAC", label: "HVAC" },
16796
+ { value: "FANCOIL", label: "Fancoil" },
16797
+ { value: "3F_MEDIDOR", label: "Three-phase Meter" }
16798
+ ];
16799
+ var TELEMETRY_TYPES2 = [
16800
+ { value: "consumption", label: "Consumption (kW)", unit: "kW" },
16801
+ { value: "voltage_a", label: "Voltage A (V)", unit: "V" },
16802
+ { value: "voltage_b", label: "Voltage B (V)", unit: "V" },
16803
+ { value: "voltage_c", label: "Voltage C (V)", unit: "V" },
16804
+ { value: "current_a", label: "Current A (A)", unit: "A" },
16805
+ { value: "current_b", label: "Current B (A)", unit: "A" },
16806
+ { value: "current_c", label: "Current C (A)", unit: "A" },
16807
+ { value: "total_current", label: "Total Current (A)", unit: "A" },
16808
+ { value: "fp_a", label: "Power Factor A", unit: "" },
16809
+ { value: "fp_b", label: "Power Factor B", unit: "" },
16810
+ { value: "fp_c", label: "Power Factor C", unit: "" }
16811
+ ];
16812
+ var STATUS_CONFIG = {
16813
+ standBy: { label: "StandBy", color: "#22c55e", bgColor: "rgba(34, 197, 94, 0.1)" },
16814
+ normal: { label: "Normal", color: "#3b82f6", bgColor: "rgba(59, 130, 246, 0.1)" },
16815
+ alert: { label: "Alert", color: "#f59e0b", bgColor: "rgba(245, 158, 11, 0.1)" },
16816
+ failure: { label: "Failure", color: "#ef4444", bgColor: "rgba(239, 68, 68, 0.1)" }
16817
+ };
16818
+
16819
+ // src/components/premium-modals/power-limits/PowerLimitsModalView.ts
16820
+ var PowerLimitsModalView = class {
16821
+ container = null;
16822
+ overlayEl = null;
16823
+ config;
16824
+ formData;
16825
+ isLoading = false;
16826
+ isSaving = false;
16827
+ constructor(config) {
16828
+ this.config = config;
16829
+ this.formData = {
16830
+ deviceType: config.deviceType,
16831
+ telemetryType: config.telemetryType,
16832
+ standby: { baseValue: null, topValue: null },
16833
+ normal: { baseValue: null, topValue: null },
16834
+ alert: { baseValue: null, topValue: null },
16835
+ failure: { baseValue: null, topValue: null }
16836
+ };
16837
+ }
16838
+ render(targetContainer) {
16839
+ this.overlayEl = document.createElement("div");
16840
+ this.overlayEl.className = "myio-power-limits-overlay";
16841
+ this.overlayEl.innerHTML = `
16842
+ <style>${this.getStyles()}</style>
16843
+ <div class="myio-power-limits-card">
16844
+ ${this.renderHeader()}
16845
+ ${this.renderSelectors()}
16846
+ ${this.renderStatusCards()}
16847
+ ${this.renderLoadingState()}
16848
+ ${this.renderErrorState()}
16849
+ ${this.renderSuccessState()}
16850
+ </div>
16851
+ `;
16852
+ const target = targetContainer || document.body;
16853
+ target.appendChild(this.overlayEl);
16854
+ this.container = this.overlayEl.querySelector(".myio-power-limits-card");
16855
+ this.setupEventListeners();
16856
+ requestAnimationFrame(() => {
16857
+ this.overlayEl?.classList.add("active");
16858
+ });
16859
+ return this.overlayEl;
16860
+ }
16861
+ renderHeader() {
16862
+ return `
16863
+ <div class="myio-power-limits-header">
16864
+ <div class="myio-power-limits-title-section">
16865
+ <span class="myio-power-limits-icon">&#x2699;</span>
16866
+ <h2 class="myio-power-limits-title">Power Limits Setup</h2>
16867
+ </div>
16868
+ <div class="myio-power-limits-actions">
16869
+ <button class="myio-btn myio-btn-primary" id="plm-save-btn" type="button">
16870
+ <span class="myio-btn-text">Save</span>
16871
+ <span class="myio-btn-spinner" style="display: none;"></span>
16872
+ </button>
16873
+ <button class="myio-btn myio-btn-secondary" id="plm-reset-btn" type="button">Reset</button>
16874
+ <button class="myio-btn myio-btn-close" id="plm-close-btn" type="button" aria-label="Close">&times;</button>
16875
+ </div>
16876
+ </div>
16877
+ `;
16878
+ }
16879
+ renderSelectors() {
16880
+ const deviceOptions = DEVICE_TYPES.map(
16881
+ (dt) => `<option value="${dt.value}" ${dt.value === this.config.deviceType ? "selected" : ""}>${dt.label}</option>`
16882
+ ).join("");
16883
+ const telemetryOptions = TELEMETRY_TYPES2.map(
16884
+ (tt) => `<option value="${tt.value}" ${tt.value === this.config.telemetryType ? "selected" : ""}>${tt.label}</option>`
16885
+ ).join("");
16886
+ return `
16887
+ <div class="myio-power-limits-selectors">
16888
+ <div class="myio-form-group">
16889
+ <label for="plm-device-type">Device Type</label>
16890
+ <select id="plm-device-type" class="myio-select">
16891
+ ${deviceOptions}
16892
+ </select>
16893
+ </div>
16894
+ <div class="myio-form-group">
16895
+ <label for="plm-telemetry-type">Telemetry Type</label>
16896
+ <select id="plm-telemetry-type" class="myio-select">
16897
+ ${telemetryOptions}
16898
+ </select>
16899
+ </div>
16900
+ </div>
16901
+ `;
16902
+ }
16903
+ renderStatusCards() {
16904
+ const statuses = ["standby", "normal", "alert", "failure"];
16905
+ const cards = statuses.map((status) => {
16906
+ const config = STATUS_CONFIG[status === "standby" ? "standBy" : status];
16907
+ const formValues = this.formData[status];
16908
+ return `
16909
+ <div class="myio-power-limits-card-item myio-status-${status}" style="--status-color: ${config.color}; --status-bg: ${config.bgColor};">
16910
+ <div class="myio-card-header">
16911
+ <span class="myio-status-indicator"></span>
16912
+ <span class="myio-status-label">${config.label}</span>
16913
+ </div>
16914
+ <div class="myio-card-inputs">
16915
+ <div class="myio-input-group">
16916
+ <label for="plm-${status}-base">Base Value</label>
16917
+ <input
16918
+ type="number"
16919
+ id="plm-${status}-base"
16920
+ class="myio-input"
16921
+ min="0"
16922
+ step="0.01"
16923
+ value="${formValues.baseValue ?? ""}"
16924
+ placeholder="0"
16925
+ >
16926
+ </div>
16927
+ <div class="myio-input-group">
16928
+ <label for="plm-${status}-top">Top Value</label>
16929
+ <input
16930
+ type="number"
16931
+ id="plm-${status}-top"
16932
+ class="myio-input"
16933
+ min="0"
16934
+ step="0.01"
16935
+ value="${formValues.topValue ?? ""}"
16936
+ placeholder="0"
16937
+ >
16938
+ </div>
16939
+ </div>
16940
+ </div>
16941
+ `;
16942
+ }).join("");
16943
+ return `
16944
+ <div class="myio-power-limits-grid" id="plm-grid">
16945
+ ${cards}
16946
+ </div>
16947
+ `;
16948
+ }
16949
+ renderLoadingState() {
16950
+ return `
16951
+ <div class="myio-power-limits-loading" id="plm-loading" style="display: none;">
16952
+ <div class="myio-spinner"></div>
16953
+ <span>Loading configuration...</span>
16954
+ </div>
16955
+ `;
16956
+ }
16957
+ renderErrorState() {
16958
+ return `
16959
+ <div class="myio-power-limits-error" id="plm-error" style="display: none;">
16960
+ <span class="myio-error-icon">&#x26A0;</span>
16961
+ <span class="myio-error-message" id="plm-error-msg"></span>
16962
+ </div>
16963
+ `;
16964
+ }
16965
+ renderSuccessState() {
16966
+ return `
16967
+ <div class="myio-power-limits-success" id="plm-success" style="display: none;">
16968
+ <span class="myio-success-icon">&#x2713;</span>
16969
+ <span class="myio-success-message">Configuration saved successfully!</span>
16970
+ </div>
16971
+ `;
16972
+ }
16973
+ setupEventListeners() {
16974
+ if (!this.overlayEl) return;
16975
+ const closeBtn = this.overlayEl.querySelector("#plm-close-btn");
16976
+ closeBtn?.addEventListener("click", () => this.close());
16977
+ this.overlayEl.addEventListener("click", (e) => {
16978
+ if (e.target === this.overlayEl) {
16979
+ this.close();
16980
+ }
16981
+ });
16982
+ document.addEventListener("keydown", this.handleKeyDown);
16983
+ const saveBtn = this.overlayEl.querySelector("#plm-save-btn");
16984
+ saveBtn?.addEventListener("click", () => this.handleSave());
16985
+ const resetBtn = this.overlayEl.querySelector("#plm-reset-btn");
16986
+ resetBtn?.addEventListener("click", () => this.handleReset());
16987
+ const deviceSelect = this.overlayEl.querySelector("#plm-device-type");
16988
+ deviceSelect?.addEventListener("change", (e) => {
16989
+ const value = e.target.value;
16990
+ this.formData.deviceType = value;
16991
+ this.config.onDeviceTypeChange(value);
16992
+ });
16993
+ const telemetrySelect = this.overlayEl.querySelector("#plm-telemetry-type");
16994
+ telemetrySelect?.addEventListener("change", (e) => {
16995
+ const value = e.target.value;
16996
+ this.formData.telemetryType = value;
16997
+ this.config.onTelemetryTypeChange(value);
16998
+ });
16999
+ const statuses = ["standby", "normal", "alert", "failure"];
17000
+ statuses.forEach((status) => {
17001
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
17002
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
17003
+ baseInput?.addEventListener("input", (e) => {
17004
+ const value = e.target.value;
17005
+ this.formData[status].baseValue = value ? parseFloat(value) : null;
17006
+ });
17007
+ topInput?.addEventListener("input", (e) => {
17008
+ const value = e.target.value;
17009
+ this.formData[status].topValue = value ? parseFloat(value) : null;
17010
+ });
17011
+ });
17012
+ }
17013
+ handleKeyDown = (e) => {
17014
+ if (e.key === "Escape") {
17015
+ this.close();
17016
+ }
17017
+ };
17018
+ async handleSave() {
17019
+ if (this.isSaving) return;
17020
+ const validationError = this.validateForm();
17021
+ if (validationError) {
17022
+ this.showError(validationError);
17023
+ return;
17024
+ }
17025
+ this.isSaving = true;
17026
+ this.showSaveLoading(true);
17027
+ this.hideError();
17028
+ this.hideSuccess();
17029
+ try {
17030
+ await this.config.onSave();
17031
+ this.showSuccess();
17032
+ setTimeout(() => this.hideSuccess(), 3e3);
17033
+ } catch (error) {
17034
+ this.showError(error.message || "Failed to save configuration");
17035
+ } finally {
17036
+ this.isSaving = false;
17037
+ this.showSaveLoading(false);
17038
+ }
17039
+ }
17040
+ handleReset() {
17041
+ const statuses = ["standby", "normal", "alert", "failure"];
17042
+ statuses.forEach((status) => {
17043
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
17044
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
17045
+ if (baseInput) baseInput.value = "";
17046
+ if (topInput) topInput.value = "";
17047
+ this.formData[status] = { baseValue: null, topValue: null };
17048
+ });
17049
+ this.hideError();
17050
+ this.hideSuccess();
17051
+ }
17052
+ validateForm() {
17053
+ const statuses = ["standby", "normal", "alert", "failure"];
17054
+ for (const status of statuses) {
17055
+ const base = this.formData[status].baseValue;
17056
+ const top = this.formData[status].topValue;
17057
+ if (base !== null && base < 0) {
17058
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Base value cannot be negative`;
17059
+ }
17060
+ if (top !== null && top < 0) {
17061
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Top value cannot be negative`;
17062
+ }
17063
+ if (base !== null && top !== null && base > top) {
17064
+ return `${STATUS_CONFIG[status === "standby" ? "standBy" : status].label}: Base value should not exceed top value`;
17065
+ }
17066
+ }
17067
+ return null;
17068
+ }
17069
+ close() {
17070
+ document.removeEventListener("keydown", this.handleKeyDown);
17071
+ if (this.overlayEl) {
17072
+ this.overlayEl.classList.remove("active");
17073
+ setTimeout(() => {
17074
+ this.overlayEl?.remove();
17075
+ this.overlayEl = null;
17076
+ this.container = null;
17077
+ this.config.onClose();
17078
+ }, 300);
17079
+ }
17080
+ }
17081
+ destroy() {
17082
+ document.removeEventListener("keydown", this.handleKeyDown);
17083
+ this.overlayEl?.remove();
17084
+ this.overlayEl = null;
17085
+ this.container = null;
17086
+ }
17087
+ showLoading() {
17088
+ this.isLoading = true;
17089
+ const loadingEl = this.overlayEl?.querySelector("#plm-loading");
17090
+ const gridEl = this.overlayEl?.querySelector("#plm-grid");
17091
+ if (loadingEl) loadingEl.style.display = "flex";
17092
+ if (gridEl) gridEl.style.opacity = "0.5";
17093
+ }
17094
+ hideLoading() {
17095
+ this.isLoading = false;
17096
+ const loadingEl = this.overlayEl?.querySelector("#plm-loading");
17097
+ const gridEl = this.overlayEl?.querySelector("#plm-grid");
17098
+ if (loadingEl) loadingEl.style.display = "none";
17099
+ if (gridEl) gridEl.style.opacity = "1";
17100
+ }
17101
+ showSaveLoading(show) {
17102
+ const saveBtn = this.overlayEl?.querySelector("#plm-save-btn");
17103
+ const btnText = saveBtn?.querySelector(".myio-btn-text");
17104
+ const btnSpinner = saveBtn?.querySelector(".myio-btn-spinner");
17105
+ if (saveBtn) saveBtn.disabled = show;
17106
+ if (btnText) btnText.style.display = show ? "none" : "inline";
17107
+ if (btnSpinner) btnSpinner.style.display = show ? "inline-block" : "none";
17108
+ }
17109
+ showError(message) {
17110
+ const errorEl = this.overlayEl?.querySelector("#plm-error");
17111
+ const errorMsg = this.overlayEl?.querySelector("#plm-error-msg");
17112
+ if (errorEl) errorEl.style.display = "flex";
17113
+ if (errorMsg) errorMsg.textContent = message;
17114
+ }
17115
+ hideError() {
17116
+ const errorEl = this.overlayEl?.querySelector("#plm-error");
17117
+ if (errorEl) errorEl.style.display = "none";
17118
+ }
17119
+ showSuccess() {
17120
+ const successEl = this.overlayEl?.querySelector("#plm-success");
17121
+ if (successEl) successEl.style.display = "flex";
17122
+ }
17123
+ hideSuccess() {
17124
+ const successEl = this.overlayEl?.querySelector("#plm-success");
17125
+ if (successEl) successEl.style.display = "none";
17126
+ }
17127
+ getFormData() {
17128
+ return { ...this.formData };
17129
+ }
17130
+ setFormData(data) {
17131
+ if (data.deviceType) this.formData.deviceType = data.deviceType;
17132
+ if (data.telemetryType) this.formData.telemetryType = data.telemetryType;
17133
+ if (data.standby) this.formData.standby = { ...data.standby };
17134
+ if (data.normal) this.formData.normal = { ...data.normal };
17135
+ if (data.alert) this.formData.alert = { ...data.alert };
17136
+ if (data.failure) this.formData.failure = { ...data.failure };
17137
+ this.updateInputValues();
17138
+ }
17139
+ updateInputValues() {
17140
+ const statuses = ["standby", "normal", "alert", "failure"];
17141
+ statuses.forEach((status) => {
17142
+ const baseInput = this.overlayEl?.querySelector(`#plm-${status}-base`);
17143
+ const topInput = this.overlayEl?.querySelector(`#plm-${status}-top`);
17144
+ if (baseInput) {
17145
+ baseInput.value = this.formData[status].baseValue?.toString() ?? "";
17146
+ }
17147
+ if (topInput) {
17148
+ topInput.value = this.formData[status].topValue?.toString() ?? "";
17149
+ }
17150
+ });
17151
+ const deviceSelect = this.overlayEl?.querySelector("#plm-device-type");
17152
+ const telemetrySelect = this.overlayEl?.querySelector("#plm-telemetry-type");
17153
+ if (deviceSelect) deviceSelect.value = this.formData.deviceType;
17154
+ if (telemetrySelect) telemetrySelect.value = this.formData.telemetryType;
17155
+ }
17156
+ getStyles() {
17157
+ const styles = this.config.styles || {};
17158
+ const primaryColor = styles.primaryColor || "#4A148C";
17159
+ const successColor = styles.successColor || "#22c55e";
17160
+ const warningColor = styles.warningColor || "#f59e0b";
17161
+ const dangerColor = styles.dangerColor || "#ef4444";
17162
+ const infoColor = styles.infoColor || "#3b82f6";
17163
+ return `
17164
+ .myio-power-limits-overlay {
17165
+ position: fixed;
17166
+ top: 0;
17167
+ left: 0;
17168
+ right: 0;
17169
+ bottom: 0;
17170
+ background: rgba(0, 0, 0, 0.6);
17171
+ display: flex;
17172
+ align-items: center;
17173
+ justify-content: center;
17174
+ z-index: ${styles.zIndex || 1e4};
17175
+ opacity: 0;
17176
+ visibility: hidden;
17177
+ transition: all 0.3s ease;
17178
+ font-family: ${styles.fontFamily || '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'};
17179
+ }
17180
+
17181
+ .myio-power-limits-overlay.active {
17182
+ opacity: 1;
17183
+ visibility: visible;
17184
+ }
17185
+
17186
+ .myio-power-limits-card {
17187
+ background: ${styles.backgroundColor || "#ffffff"};
17188
+ border-radius: ${styles.borderRadius || "12px"};
17189
+ width: 90%;
17190
+ max-width: 700px;
17191
+ max-height: 90vh;
17192
+ overflow-y: auto;
17193
+ transform: scale(0.9);
17194
+ transition: transform 0.3s ease;
17195
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
17196
+ }
17197
+
17198
+ .myio-power-limits-overlay.active .myio-power-limits-card {
17199
+ transform: scale(1);
17200
+ }
17201
+
17202
+ .myio-power-limits-header {
17203
+ display: flex;
17204
+ align-items: center;
17205
+ justify-content: space-between;
17206
+ padding: 20px 24px;
17207
+ background: linear-gradient(135deg, ${primaryColor}, ${this.lightenColor(primaryColor, 20)});
17208
+ color: white;
17209
+ border-radius: 12px 12px 0 0;
17210
+ }
17211
+
17212
+ .myio-power-limits-title-section {
17213
+ display: flex;
17214
+ align-items: center;
17215
+ gap: 12px;
17216
+ }
17217
+
17218
+ .myio-power-limits-icon {
17219
+ font-size: 24px;
17220
+ }
17221
+
17222
+ .myio-power-limits-title {
17223
+ font-size: 1.25rem;
17224
+ font-weight: 600;
17225
+ margin: 0;
17226
+ }
17227
+
17228
+ .myio-power-limits-actions {
17229
+ display: flex;
17230
+ align-items: center;
17231
+ gap: 8px;
17232
+ }
17233
+
17234
+ .myio-btn {
17235
+ padding: 8px 16px;
17236
+ border-radius: ${styles.buttonRadius || "6px"};
17237
+ font-size: 14px;
17238
+ font-weight: 500;
17239
+ cursor: pointer;
17240
+ border: none;
17241
+ transition: all 0.2s;
17242
+ display: inline-flex;
17243
+ align-items: center;
17244
+ gap: 6px;
17245
+ }
17246
+
17247
+ .myio-btn:disabled {
17248
+ opacity: 0.6;
17249
+ cursor: not-allowed;
17250
+ }
17251
+
17252
+ .myio-btn-primary {
17253
+ background: white;
17254
+ color: ${primaryColor};
17255
+ }
17256
+
17257
+ .myio-btn-primary:hover:not(:disabled) {
17258
+ background: #f3f4f6;
17259
+ }
17260
+
17261
+ .myio-btn-secondary {
17262
+ background: rgba(255, 255, 255, 0.2);
17263
+ color: white;
17264
+ }
17265
+
17266
+ .myio-btn-secondary:hover:not(:disabled) {
17267
+ background: rgba(255, 255, 255, 0.3);
17268
+ }
17269
+
17270
+ .myio-btn-close {
17271
+ background: transparent;
17272
+ color: white;
17273
+ font-size: 24px;
17274
+ padding: 4px 8px;
17275
+ line-height: 1;
17276
+ }
17277
+
17278
+ .myio-btn-close:hover {
17279
+ background: rgba(255, 255, 255, 0.1);
17280
+ }
17281
+
17282
+ .myio-btn-spinner {
17283
+ width: 16px;
17284
+ height: 16px;
17285
+ border: 2px solid ${primaryColor};
17286
+ border-top-color: transparent;
17287
+ border-radius: 50%;
17288
+ animation: spin 0.8s linear infinite;
17289
+ }
17290
+
17291
+ @keyframes spin {
17292
+ to { transform: rotate(360deg); }
17293
+ }
17294
+
17295
+ .myio-power-limits-selectors {
17296
+ display: grid;
17297
+ grid-template-columns: 1fr 1fr;
17298
+ gap: 16px;
17299
+ padding: 20px 24px;
17300
+ background: #f9fafb;
17301
+ border-bottom: 1px solid #e5e7eb;
17302
+ }
17303
+
17304
+ .myio-form-group {
17305
+ display: flex;
17306
+ flex-direction: column;
17307
+ gap: 6px;
17308
+ }
17309
+
17310
+ .myio-form-group label {
17311
+ font-size: 13px;
17312
+ font-weight: 500;
17313
+ color: #374151;
17314
+ }
17315
+
17316
+ .myio-select, .myio-input {
17317
+ padding: 10px 12px;
17318
+ border: 1px solid #d1d5db;
17319
+ border-radius: 6px;
17320
+ font-size: 14px;
17321
+ background: white;
17322
+ color: #1f2937;
17323
+ transition: border-color 0.2s, box-shadow 0.2s;
17324
+ }
17325
+
17326
+ .myio-select:focus, .myio-input:focus {
17327
+ outline: none;
17328
+ border-color: ${primaryColor};
17329
+ box-shadow: 0 0 0 3px ${this.hexToRgba(primaryColor, 0.1)};
17330
+ }
17331
+
17332
+ .myio-power-limits-grid {
17333
+ display: grid;
17334
+ grid-template-columns: repeat(2, 1fr);
17335
+ gap: 16px;
17336
+ padding: 24px;
17337
+ transition: opacity 0.3s;
17338
+ }
17339
+
17340
+ .myio-power-limits-card-item {
17341
+ background: var(--status-bg);
17342
+ border: 1px solid var(--status-color);
17343
+ border-radius: 8px;
17344
+ padding: 16px;
17345
+ transition: transform 0.2s, box-shadow 0.2s;
17346
+ }
17347
+
17348
+ .myio-power-limits-card-item:hover {
17349
+ transform: translateY(-2px);
17350
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
17351
+ }
17352
+
17353
+ .myio-card-header {
17354
+ display: flex;
17355
+ align-items: center;
17356
+ gap: 8px;
17357
+ margin-bottom: 12px;
17358
+ }
17359
+
17360
+ .myio-status-indicator {
17361
+ width: 12px;
17362
+ height: 12px;
17363
+ border-radius: 50%;
17364
+ background: var(--status-color);
17365
+ }
17366
+
17367
+ .myio-status-label {
17368
+ font-weight: 600;
17369
+ font-size: 14px;
17370
+ color: #1f2937;
17371
+ }
17372
+
17373
+ .myio-card-inputs {
17374
+ display: grid;
17375
+ grid-template-columns: 1fr 1fr;
17376
+ gap: 12px;
17377
+ }
17378
+
17379
+ .myio-input-group {
17380
+ display: flex;
17381
+ flex-direction: column;
17382
+ gap: 4px;
17383
+ }
17384
+
17385
+ .myio-input-group label {
17386
+ font-size: 11px;
17387
+ font-weight: 500;
17388
+ color: #6b7280;
17389
+ text-transform: uppercase;
17390
+ }
17391
+
17392
+ .myio-power-limits-loading,
17393
+ .myio-power-limits-error,
17394
+ .myio-power-limits-success {
17395
+ display: flex;
17396
+ align-items: center;
17397
+ justify-content: center;
17398
+ gap: 12px;
17399
+ padding: 16px 24px;
17400
+ margin: 0 24px 24px;
17401
+ border-radius: 8px;
17402
+ }
17403
+
17404
+ .myio-power-limits-loading {
17405
+ background: #f3f4f6;
17406
+ color: #6b7280;
17407
+ }
17408
+
17409
+ .myio-power-limits-error {
17410
+ background: #fef2f2;
17411
+ color: ${dangerColor};
17412
+ border: 1px solid ${dangerColor};
17413
+ }
17414
+
17415
+ .myio-power-limits-success {
17416
+ background: #f0fdf4;
17417
+ color: ${successColor};
17418
+ border: 1px solid ${successColor};
17419
+ }
17420
+
17421
+ .myio-spinner {
17422
+ width: 24px;
17423
+ height: 24px;
17424
+ border: 3px solid #e5e7eb;
17425
+ border-top-color: ${primaryColor};
17426
+ border-radius: 50%;
17427
+ animation: spin 0.8s linear infinite;
17428
+ }
17429
+
17430
+ .myio-error-icon, .myio-success-icon {
17431
+ font-size: 20px;
17432
+ }
17433
+
17434
+ @media (max-width: 600px) {
17435
+ .myio-power-limits-selectors,
17436
+ .myio-power-limits-grid {
17437
+ grid-template-columns: 1fr;
17438
+ }
17439
+
17440
+ .myio-power-limits-header {
17441
+ flex-direction: column;
17442
+ gap: 12px;
17443
+ text-align: center;
17444
+ }
17445
+
17446
+ .myio-power-limits-actions {
17447
+ width: 100%;
17448
+ justify-content: center;
17449
+ }
17450
+ }
17451
+ `;
17452
+ }
17453
+ lightenColor(hex, percent) {
17454
+ const num = parseInt(hex.replace("#", ""), 16);
17455
+ const amt = Math.round(2.55 * percent);
17456
+ const R = (num >> 16) + amt;
17457
+ const G = (num >> 8 & 255) + amt;
17458
+ const B = (num & 255) + amt;
17459
+ 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);
17460
+ }
17461
+ hexToRgba(hex, alpha) {
17462
+ const num = parseInt(hex.replace("#", ""), 16);
17463
+ const R = num >> 16;
17464
+ const G = num >> 8 & 255;
17465
+ const B = num & 255;
17466
+ return `rgba(${R}, ${G}, ${B}, ${alpha})`;
17467
+ }
17468
+ };
17469
+
17470
+ // src/components/premium-modals/power-limits/PowerLimitsPersister.ts
17471
+ var PowerLimitsPersister = class {
17472
+ jwtToken;
17473
+ tbBaseUrl;
17474
+ constructor(jwtToken, tbBaseUrl) {
17475
+ this.jwtToken = jwtToken;
17476
+ this.tbBaseUrl = tbBaseUrl || window.location.origin;
17477
+ }
17478
+ /**
17479
+ * Load existing mapInstantaneousPower from customer server_scope attributes
17480
+ */
17481
+ async loadCustomerPowerLimits(customerId) {
17482
+ try {
17483
+ const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/values/attributes/SERVER_SCOPE?keys=mapInstantaneousPower`;
17484
+ const response = await fetch(url, {
17485
+ method: "GET",
17486
+ headers: {
17487
+ "X-Authorization": `Bearer ${this.jwtToken}`,
17488
+ "Content-Type": "application/json"
17489
+ }
17490
+ });
17491
+ if (!response.ok) {
17492
+ if (response.status === 404) {
17493
+ console.log("[PowerLimitsPersister] No existing mapInstantaneousPower found");
17494
+ return null;
17495
+ }
17496
+ throw this.createHttpError(response.status, await response.text().catch(() => ""));
17497
+ }
17498
+ const data = await response.json();
17499
+ if (!data || data.length === 0) {
17500
+ console.log("[PowerLimitsPersister] No mapInstantaneousPower attribute found");
17501
+ return null;
17502
+ }
17503
+ const attr = data.find((item) => item.key === "mapInstantaneousPower");
17504
+ if (!attr || !attr.value) {
17505
+ return null;
17506
+ }
17507
+ const parsedValue = typeof attr.value === "string" ? JSON.parse(attr.value) : attr.value;
17508
+ console.log("[PowerLimitsPersister] Loaded mapInstantaneousPower:", parsedValue);
17509
+ return parsedValue;
17510
+ } catch (error) {
17511
+ console.error("[PowerLimitsPersister] Error loading power limits:", error);
17512
+ throw this.mapError(error);
17513
+ }
17514
+ }
17515
+ /**
17516
+ * Save mapInstantaneousPower to customer server_scope attributes
17517
+ */
17518
+ async saveCustomerPowerLimits(customerId, limits) {
17519
+ try {
17520
+ const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/attributes/SERVER_SCOPE`;
17521
+ const payload = {
17522
+ mapInstantaneousPower: limits
17523
+ };
17524
+ console.log("[PowerLimitsPersister] Saving power limits:", payload);
17525
+ const response = await fetch(url, {
17526
+ method: "POST",
17527
+ headers: {
17528
+ "X-Authorization": `Bearer ${this.jwtToken}`,
17529
+ "Content-Type": "application/json"
17530
+ },
17531
+ body: JSON.stringify(payload)
17532
+ });
17533
+ if (!response.ok) {
17534
+ throw this.createHttpError(response.status, await response.text().catch(() => ""));
17535
+ }
17536
+ console.log("[PowerLimitsPersister] Successfully saved power limits");
17537
+ return { ok: true };
17538
+ } catch (error) {
17539
+ console.error("[PowerLimitsPersister] Error saving power limits:", error);
17540
+ return { ok: false, error: this.mapError(error) };
17541
+ }
17542
+ }
17543
+ /**
17544
+ * Extract form data for a specific device type and telemetry type
17545
+ */
17546
+ extractFormData(limits, deviceType, telemetryType) {
17547
+ const defaultFormData = {
17548
+ deviceType,
17549
+ telemetryType,
17550
+ standby: { baseValue: null, topValue: null },
17551
+ normal: { baseValue: null, topValue: null },
17552
+ alert: { baseValue: null, topValue: null },
17553
+ failure: { baseValue: null, topValue: null }
17554
+ };
17555
+ if (!limits || !limits.limitsByInstantaneoustPowerType) {
17556
+ return defaultFormData;
17557
+ }
17558
+ const telemetryEntry = limits.limitsByInstantaneoustPowerType.find(
17559
+ (t) => t.telemetryType === telemetryType
17560
+ );
17561
+ if (!telemetryEntry || !telemetryEntry.itemsByDeviceType) {
17562
+ return defaultFormData;
17563
+ }
17564
+ const deviceEntry = telemetryEntry.itemsByDeviceType.find(
17565
+ (d) => d.deviceType === deviceType
17566
+ );
17567
+ if (!deviceEntry || !deviceEntry.limitsByDeviceStatus) {
17568
+ return defaultFormData;
17569
+ }
17570
+ const statusMap = {
17571
+ "standBy": "standby",
17572
+ "normal": "normal",
17573
+ "alert": "alert",
17574
+ "failure": "failure"
17575
+ };
17576
+ deviceEntry.limitsByDeviceStatus.forEach((status) => {
17577
+ const formKey = statusMap[status.deviceStatusName];
17578
+ if (formKey && defaultFormData[formKey]) {
17579
+ defaultFormData[formKey] = {
17580
+ baseValue: status.limitsValues.baseValue,
17581
+ topValue: status.limitsValues.topValue
17582
+ };
17583
+ }
17584
+ });
17585
+ return defaultFormData;
17586
+ }
17587
+ /**
17588
+ * Merge form data into existing limits JSON
17589
+ * Creates new entries if they don't exist
17590
+ */
17591
+ mergeFormDataIntoLimits(existingLimits, formData) {
17592
+ const result = existingLimits ? JSON.parse(JSON.stringify(existingLimits)) : { version: "1.0.0", limitsByInstantaneoustPowerType: [] };
17593
+ const statusLimits = [
17594
+ {
17595
+ deviceStatusName: "standBy",
17596
+ limitsValues: {
17597
+ baseValue: formData.standby.baseValue ?? 0,
17598
+ topValue: formData.standby.topValue ?? 0
17599
+ }
17600
+ },
17601
+ {
17602
+ deviceStatusName: "normal",
17603
+ limitsValues: {
17604
+ baseValue: formData.normal.baseValue ?? 0,
17605
+ topValue: formData.normal.topValue ?? 0
17606
+ }
17607
+ },
17608
+ {
17609
+ deviceStatusName: "alert",
17610
+ limitsValues: {
17611
+ baseValue: formData.alert.baseValue ?? 0,
17612
+ topValue: formData.alert.topValue ?? 0
17613
+ }
17614
+ },
17615
+ {
17616
+ deviceStatusName: "failure",
17617
+ limitsValues: {
17618
+ baseValue: formData.failure.baseValue ?? 0,
17619
+ topValue: formData.failure.topValue ?? 0
17620
+ }
17621
+ }
17622
+ ];
17623
+ let telemetryEntry = result.limitsByInstantaneoustPowerType.find(
17624
+ (t) => t.telemetryType === formData.telemetryType
17625
+ );
17626
+ if (!telemetryEntry) {
17627
+ telemetryEntry = {
17628
+ telemetryType: formData.telemetryType,
17629
+ itemsByDeviceType: []
17630
+ };
17631
+ result.limitsByInstantaneoustPowerType.push(telemetryEntry);
17632
+ }
17633
+ let deviceEntry = telemetryEntry.itemsByDeviceType.find(
17634
+ (d) => d.deviceType === formData.deviceType
17635
+ );
17636
+ if (!deviceEntry) {
17637
+ deviceEntry = {
17638
+ deviceType: formData.deviceType,
17639
+ name: `mapInstantaneousPower${this.formatDeviceTypeName(formData.deviceType)}`,
17640
+ description: `Power limits for ${formData.deviceType}`,
17641
+ limitsByDeviceStatus: []
17642
+ };
17643
+ telemetryEntry.itemsByDeviceType.push(deviceEntry);
17644
+ }
17645
+ deviceEntry.limitsByDeviceStatus = statusLimits;
17646
+ deviceEntry.name = `mapInstantaneousPower${this.formatDeviceTypeName(formData.deviceType)}`;
17647
+ deviceEntry.description = `Power limits for ${formData.deviceType} - ${formData.telemetryType}`;
17648
+ return result;
17649
+ }
17650
+ /**
17651
+ * Format device type name for the JSON name field
17652
+ */
17653
+ formatDeviceTypeName(deviceType) {
17654
+ if (!deviceType) return "";
17655
+ return deviceType.toLowerCase().split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
17656
+ }
17657
+ createHttpError(status, body) {
17658
+ const error = new Error(`HTTP ${status}: ${body}`);
17659
+ error.status = status;
17660
+ error.body = body;
17661
+ return error;
17662
+ }
17663
+ mapError(error) {
17664
+ const status = error.status;
17665
+ if (status === 400) {
17666
+ return {
17667
+ code: "VALIDATION_ERROR",
17668
+ message: "Invalid input data",
17669
+ cause: error
17670
+ };
17671
+ }
17672
+ if (status === 401) {
17673
+ return {
17674
+ code: "TOKEN_EXPIRED",
17675
+ message: "Authentication token has expired",
17676
+ cause: error
17677
+ };
17678
+ }
17679
+ if (status === 403) {
17680
+ return {
17681
+ code: "AUTH_ERROR",
17682
+ message: "Insufficient permissions",
17683
+ cause: error
17684
+ };
17685
+ }
17686
+ if (status === 404) {
17687
+ return {
17688
+ code: "NETWORK_ERROR",
17689
+ message: "Customer not found",
17690
+ cause: error
17691
+ };
17692
+ }
17693
+ if (status >= 500) {
17694
+ return {
17695
+ code: "NETWORK_ERROR",
17696
+ message: "Server error occurred",
17697
+ cause: error
17698
+ };
15989
17699
  }
17700
+ return {
17701
+ code: "UNKNOWN_ERROR",
17702
+ message: error.message || "Unknown error occurred",
17703
+ cause: error
17704
+ };
15990
17705
  }
15991
- if (options.aggregation !== void 0) {
15992
- const validAggregations = ["NONE", "MIN", "MAX", "AVG", "SUM", "COUNT"];
15993
- if (!validAggregations.includes(options.aggregation)) {
15994
- errors.push(`aggregation must be one of: ${validAggregations.join(", ")}`);
17706
+ };
17707
+
17708
+ // src/components/premium-modals/power-limits/openPowerLimitsSetupModal.ts
17709
+ async function openPowerLimitsSetupModal(params) {
17710
+ if (!params.token) {
17711
+ throw new Error("[PowerLimitsSetupModal] token is required");
17712
+ }
17713
+ if (!params.customerId) {
17714
+ throw new Error("[PowerLimitsSetupModal] customerId is required");
17715
+ }
17716
+ const persister = new PowerLimitsPersister(params.token, params.tbBaseUrl);
17717
+ let currentDeviceType = params.deviceType || "ELEVADOR";
17718
+ let currentTelemetryType = params.telemetryType || "consumption";
17719
+ let existingLimits = params.existingMapPower || null;
17720
+ const view = new PowerLimitsModalView({
17721
+ deviceType: currentDeviceType,
17722
+ telemetryType: currentTelemetryType,
17723
+ styles: params.styles,
17724
+ locale: params.locale,
17725
+ onDeviceTypeChange: async (deviceType) => {
17726
+ currentDeviceType = deviceType;
17727
+ await loadFormData();
17728
+ },
17729
+ onTelemetryTypeChange: async (telemetryType) => {
17730
+ currentTelemetryType = telemetryType;
17731
+ await loadFormData();
17732
+ },
17733
+ onSave: async () => {
17734
+ const formData = view.getFormData();
17735
+ const updatedLimits = persister.mergeFormDataIntoLimits(existingLimits, formData);
17736
+ const result = await persister.saveCustomerPowerLimits(params.customerId, updatedLimits);
17737
+ if (!result.ok) {
17738
+ throw new Error(result.error?.message || "Failed to save configuration");
17739
+ }
17740
+ existingLimits = updatedLimits;
17741
+ if (params.onSave) {
17742
+ params.onSave(updatedLimits);
17743
+ }
17744
+ },
17745
+ onClose: () => {
17746
+ if (params.onClose) {
17747
+ params.onClose();
17748
+ }
17749
+ }
17750
+ });
17751
+ async function loadFormData() {
17752
+ view.showLoading();
17753
+ try {
17754
+ if (!existingLimits) {
17755
+ existingLimits = await persister.loadCustomerPowerLimits(params.customerId);
17756
+ }
17757
+ const formData = persister.extractFormData(existingLimits, currentDeviceType, currentTelemetryType);
17758
+ view.setFormData(formData);
17759
+ } catch (error) {
17760
+ console.error("[PowerLimitsSetupModal] Error loading form data:", error);
17761
+ view.showError(error.message || "Failed to load configuration");
17762
+ } finally {
17763
+ view.hideLoading();
15995
17764
  }
15996
17765
  }
15997
- if (errors.length > 0) {
15998
- throw new Error(`Validation failed:
15999
- - ${errors.join("\n- ")}`);
17766
+ let container;
17767
+ if (params.container) {
17768
+ if (typeof params.container === "string") {
17769
+ container = document.querySelector(params.container);
17770
+ } else {
17771
+ container = params.container;
17772
+ }
16000
17773
  }
17774
+ view.render(container);
17775
+ await loadFormData();
17776
+ return {
17777
+ destroy: () => view.destroy(),
17778
+ getFormData: () => view.getFormData(),
17779
+ setFormData: (data) => view.setFormData(data)
17780
+ };
16001
17781
  }
16002
17782
 
16003
17783
  // src/components/premium-modals/settings/SettingsModalView.ts
@@ -20368,220 +22148,6 @@ function openGoalsPanel(params) {
20368
22148
  }
20369
22149
  }
20370
22150
 
20371
- // src/components/temperature/utils.ts
20372
- var DAY_PERIODS = [
20373
- { id: "madrugada", label: "Madrugada (00h-06h)", startHour: 0, endHour: 6 },
20374
- { id: "manha", label: "Manh\xE3 (06h-12h)", startHour: 6, endHour: 12 },
20375
- { id: "tarde", label: "Tarde (12h-18h)", startHour: 12, endHour: 18 },
20376
- { id: "noite", label: "Noite (18h-24h)", startHour: 18, endHour: 24 }
20377
- ];
20378
- var DEFAULT_CLAMP_RANGE = { min: 15, max: 40 };
20379
- function getTodaySoFar() {
20380
- const now = /* @__PURE__ */ new Date();
20381
- const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
20382
- return {
20383
- startTs: startOfDay.getTime(),
20384
- endTs: now.getTime()
20385
- };
20386
- }
20387
- var CHART_COLORS = [
20388
- "#1976d2",
20389
- // Blue
20390
- "#FF6B6B",
20391
- // Red
20392
- "#4CAF50",
20393
- // Green
20394
- "#FF9800",
20395
- // Orange
20396
- "#9C27B0",
20397
- // Purple
20398
- "#00BCD4",
20399
- // Cyan
20400
- "#E91E63",
20401
- // Pink
20402
- "#795548"
20403
- // Brown
20404
- ];
20405
- async function fetchTemperatureData(token, deviceId, startTs, endTs) {
20406
- const url = `/api/plugins/telemetry/DEVICE/${deviceId}/values/timeseries?keys=temperature&startTs=${encodeURIComponent(startTs)}&endTs=${encodeURIComponent(endTs)}&limit=50000&agg=NONE`;
20407
- const response = await fetch(url, {
20408
- headers: {
20409
- "X-Authorization": `Bearer ${token}`,
20410
- "Content-Type": "application/json"
20411
- }
20412
- });
20413
- if (!response.ok) {
20414
- throw new Error(`Failed to fetch temperature data: ${response.status}`);
20415
- }
20416
- const data = await response.json();
20417
- return data?.temperature || [];
20418
- }
20419
- function clampTemperature(value, range = DEFAULT_CLAMP_RANGE) {
20420
- const num = Number(value || 0);
20421
- if (num < range.min) return range.min;
20422
- if (num > range.max) return range.max;
20423
- return num;
20424
- }
20425
- function calculateStats(data, clampRange = DEFAULT_CLAMP_RANGE) {
20426
- if (data.length === 0) {
20427
- return { avg: 0, min: 0, max: 0, count: 0 };
20428
- }
20429
- const values = data.map((item) => clampTemperature(item.value, clampRange));
20430
- const sum = values.reduce((acc, v) => acc + v, 0);
20431
- return {
20432
- avg: sum / values.length,
20433
- min: Math.min(...values),
20434
- max: Math.max(...values),
20435
- count: values.length
20436
- };
20437
- }
20438
- function interpolateTemperature(data, options) {
20439
- const { intervalMinutes, startTs, endTs, clampRange = DEFAULT_CLAMP_RANGE } = options;
20440
- const intervalMs = intervalMinutes * 60 * 1e3;
20441
- if (data.length === 0) {
20442
- return [];
20443
- }
20444
- const sortedData = [...data].sort((a, b) => a.ts - b.ts);
20445
- const result = [];
20446
- let lastKnownValue = clampTemperature(sortedData[0].value, clampRange);
20447
- let dataIndex = 0;
20448
- for (let ts = startTs; ts <= endTs; ts += intervalMs) {
20449
- while (dataIndex < sortedData.length - 1 && sortedData[dataIndex + 1].ts <= ts) {
20450
- dataIndex++;
20451
- }
20452
- const currentData = sortedData[dataIndex];
20453
- if (currentData && Math.abs(currentData.ts - ts) < intervalMs) {
20454
- lastKnownValue = clampTemperature(currentData.value, clampRange);
20455
- }
20456
- result.push({
20457
- ts,
20458
- value: lastKnownValue
20459
- });
20460
- }
20461
- return result;
20462
- }
20463
- function aggregateByDay(data, clampRange = DEFAULT_CLAMP_RANGE) {
20464
- if (data.length === 0) {
20465
- return [];
20466
- }
20467
- const dayMap = /* @__PURE__ */ new Map();
20468
- data.forEach((item) => {
20469
- const date = new Date(item.ts);
20470
- const dateKey = date.toISOString().split("T")[0];
20471
- if (!dayMap.has(dateKey)) {
20472
- dayMap.set(dateKey, []);
20473
- }
20474
- dayMap.get(dateKey).push(item);
20475
- });
20476
- const result = [];
20477
- dayMap.forEach((dayData, dateKey) => {
20478
- const values = dayData.map((item) => clampTemperature(item.value, clampRange));
20479
- const sum = values.reduce((acc, v) => acc + v, 0);
20480
- result.push({
20481
- date: dateKey,
20482
- dateTs: new Date(dateKey).getTime(),
20483
- avg: sum / values.length,
20484
- min: Math.min(...values),
20485
- max: Math.max(...values),
20486
- count: values.length
20487
- });
20488
- });
20489
- return result.sort((a, b) => a.dateTs - b.dateTs);
20490
- }
20491
- function filterByDayPeriods(data, selectedPeriods) {
20492
- if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
20493
- return data;
20494
- }
20495
- return data.filter((item) => {
20496
- const date = new Date(item.ts);
20497
- const hour = date.getHours();
20498
- return selectedPeriods.some((periodId) => {
20499
- const period = DAY_PERIODS.find((p) => p.id === periodId);
20500
- if (!period) return false;
20501
- return hour >= period.startHour && hour < period.endHour;
20502
- });
20503
- });
20504
- }
20505
- function getSelectedPeriodsLabel(selectedPeriods) {
20506
- if (selectedPeriods.length === 0 || selectedPeriods.length === DAY_PERIODS.length) {
20507
- return "Todos os per\xEDodos";
20508
- }
20509
- if (selectedPeriods.length === 1) {
20510
- const period = DAY_PERIODS.find((p) => p.id === selectedPeriods[0]);
20511
- return period?.label || "";
20512
- }
20513
- return `${selectedPeriods.length} per\xEDodos selecionados`;
20514
- }
20515
- function formatTemperature2(value, decimals = 1) {
20516
- return `${value.toFixed(decimals)}\xB0C`;
20517
- }
20518
- function exportTemperatureCSV(data, deviceLabel, stats, startDate, endDate) {
20519
- if (data.length === 0) {
20520
- console.warn("No data to export");
20521
- return;
20522
- }
20523
- const BOM = "\uFEFF";
20524
- let csvContent = BOM;
20525
- csvContent += `Relat\xF3rio de Temperatura - ${deviceLabel}
20526
- `;
20527
- csvContent += `Per\xEDodo: ${startDate} at\xE9 ${endDate}
20528
- `;
20529
- csvContent += `M\xE9dia: ${formatTemperature2(stats.avg)}
20530
- `;
20531
- csvContent += `M\xEDnima: ${formatTemperature2(stats.min)}
20532
- `;
20533
- csvContent += `M\xE1xima: ${formatTemperature2(stats.max)}
20534
- `;
20535
- csvContent += `Total de leituras: ${stats.count}
20536
- `;
20537
- csvContent += "\n";
20538
- csvContent += "Data/Hora,Temperatura (\xB0C)\n";
20539
- data.forEach((item) => {
20540
- const date = new Date(item.ts).toLocaleString("pt-BR");
20541
- const temp = Number(item.value).toFixed(2);
20542
- csvContent += `"${date}",${temp}
20543
- `;
20544
- });
20545
- const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
20546
- const url = URL.createObjectURL(blob);
20547
- const link = document.createElement("a");
20548
- link.href = url;
20549
- link.download = `temperatura_${deviceLabel.replace(/\s+/g, "_")}_${startDate}_${endDate}.csv`;
20550
- document.body.appendChild(link);
20551
- link.click();
20552
- document.body.removeChild(link);
20553
- URL.revokeObjectURL(url);
20554
- }
20555
- var DARK_THEME = {
20556
- background: "rgba(0, 0, 0, 0.85)",
20557
- surface: "#1a1f28",
20558
- text: "#ffffff",
20559
- textMuted: "rgba(255, 255, 255, 0.7)",
20560
- border: "rgba(255, 255, 255, 0.1)",
20561
- primary: "#1976d2",
20562
- success: "#4CAF50",
20563
- warning: "#FF9800",
20564
- danger: "#f44336",
20565
- chartLine: "#1976d2",
20566
- chartGrid: "rgba(255, 255, 255, 0.1)"
20567
- };
20568
- var LIGHT_THEME = {
20569
- background: "rgba(0, 0, 0, 0.6)",
20570
- surface: "#ffffff",
20571
- text: "#333333",
20572
- textMuted: "#666666",
20573
- border: "#e0e0e0",
20574
- primary: "#1976d2",
20575
- success: "#4CAF50",
20576
- warning: "#FF9800",
20577
- danger: "#f44336",
20578
- chartLine: "#1976d2",
20579
- chartGrid: "#e0e0e0"
20580
- };
20581
- function getThemeColors(theme) {
20582
- return theme === "dark" ? DARK_THEME : LIGHT_THEME;
20583
- }
20584
-
20585
22151
  // src/components/temperature/TemperatureModal.ts
20586
22152
  async function openTemperatureModal(params) {
20587
22153
  const modalId = `myio-temp-modal-${Date.now()}`;
@@ -20821,7 +22387,7 @@ function renderModal(container, state, modalId, error) {
20821
22387
  ">
20822
22388
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Temperatura Atual</span>
20823
22389
  <div style="font-weight: 700; font-size: 24px; color: ${statusColor}; margin-top: 4px;">
20824
- ${state.currentTemperature !== null ? formatTemperature2(state.currentTemperature) : "N/A"}
22390
+ ${state.currentTemperature !== null ? formatTemperature(state.currentTemperature) : "N/A"}
20825
22391
  </div>
20826
22392
  <div style="font-size: 11px; color: ${statusColor}; margin-top: 2px;">${statusText}</div>
20827
22393
  </div>
@@ -20832,7 +22398,7 @@ function renderModal(container, state, modalId, error) {
20832
22398
  ">
20833
22399
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">M\xE9dia do Per\xEDodo</span>
20834
22400
  <div id="${modalId}-avg" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
20835
- ${state.stats.count > 0 ? formatTemperature2(state.stats.avg) : "N/A"}
22401
+ ${state.stats.count > 0 ? formatTemperature(state.stats.avg) : "N/A"}
20836
22402
  </div>
20837
22403
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${startDateStr} - ${endDateStr}</div>
20838
22404
  </div>
@@ -20843,7 +22409,7 @@ function renderModal(container, state, modalId, error) {
20843
22409
  ">
20844
22410
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Min / Max</span>
20845
22411
  <div id="${modalId}-minmax" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
20846
- ${state.stats.count > 0 ? `${formatTemperature2(state.stats.min)} / ${formatTemperature2(state.stats.max)}` : "N/A"}
22412
+ ${state.stats.count > 0 ? `${formatTemperature(state.stats.min)} / ${formatTemperature(state.stats.max)}` : "N/A"}
20847
22413
  </div>
20848
22414
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state.stats.count} leituras</div>
20849
22415
  </div>
@@ -21159,7 +22725,7 @@ function setupChartTooltip(canvas, container, chartData, state, colors) {
21159
22725
  }
21160
22726
  tooltip.innerHTML = `
21161
22727
  <div style="font-weight: 600; margin-bottom: 6px; color: ${colors.primary};">
21162
- ${formatTemperature2(point.y)}
22728
+ ${formatTemperature(point.y)}
21163
22729
  </div>
21164
22730
  <div style="font-size: 11px; color: ${colors.textMuted};">
21165
22731
  \u{1F4C5} ${dateStr}
@@ -21422,7 +22988,7 @@ function renderModal2(container, state, modalId) {
21422
22988
  <span style="width: 12px; height: 12px; border-radius: 50%; background: ${dd.color};"></span>
21423
22989
  <span style="color: ${colors.text}; font-size: 13px;">${dd.device.label}</span>
21424
22990
  <span style="color: ${colors.textMuted}; font-size: 11px; margin-left: auto;">
21425
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.avg) : "N/A"}
22991
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.avg) : "N/A"}
21426
22992
  </span>
21427
22993
  </div>
21428
22994
  `).join("");
@@ -21438,15 +23004,15 @@ function renderModal2(container, state, modalId) {
21438
23004
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4px; font-size: 11px;">
21439
23005
  <span style="color: ${colors.textMuted};">M\xE9dia:</span>
21440
23006
  <span style="color: ${colors.text}; font-weight: 500;">
21441
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.avg) : "N/A"}
23007
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.avg) : "N/A"}
21442
23008
  </span>
21443
23009
  <span style="color: ${colors.textMuted};">Min:</span>
21444
23010
  <span style="color: ${colors.text};">
21445
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.min) : "N/A"}
23011
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.min) : "N/A"}
21446
23012
  </span>
21447
23013
  <span style="color: ${colors.textMuted};">Max:</span>
21448
23014
  <span style="color: ${colors.text};">
21449
- ${dd.stats.count > 0 ? formatTemperature2(dd.stats.max) : "N/A"}
23015
+ ${dd.stats.count > 0 ? formatTemperature(dd.stats.max) : "N/A"}
21450
23016
  </span>
21451
23017
  <span style="color: ${colors.textMuted};">Leituras:</span>
21452
23018
  <span style="color: ${colors.text};">${dd.stats.count}</span>
@@ -21982,7 +23548,7 @@ function setupComparisonChartTooltip(canvas, container, chartData, state, colors
21982
23548
  <span style="font-weight: 600;">${point.deviceLabel}</span>
21983
23549
  </div>
21984
23550
  <div style="font-weight: 600; font-size: 16px; color: ${point.deviceColor}; margin-bottom: 4px;">
21985
- ${formatTemperature2(point.y)}
23551
+ ${formatTemperature(point.y)}
21986
23552
  </div>
21987
23553
  <div style="font-size: 11px; color: ${colors.textMuted};">
21988
23554
  \u{1F4C5} ${dateStr}
@@ -26481,6 +28047,9 @@ function createDistributionChartWidget(config) {
26481
28047
  MyIOSelectionStore,
26482
28048
  MyIOSelectionStoreClass,
26483
28049
  MyIOToast,
28050
+ POWER_LIMITS_DEVICE_TYPES,
28051
+ POWER_LIMITS_STATUS_CONFIG,
28052
+ POWER_LIMITS_TELEMETRY_TYPES,
26484
28053
  addDetectionContext,
26485
28054
  addNamespace,
26486
28055
  aggregateByDay,
@@ -26578,6 +28147,7 @@ function createDistributionChartWidget(config) {
26578
28147
  openDashboardPopupWaterTank,
26579
28148
  openDemandModal,
26580
28149
  openGoalsPanel,
28150
+ openPowerLimitsSetupModal,
26581
28151
  openRealTimeTelemetryModal,
26582
28152
  openTemperatureComparisonModal,
26583
28153
  openTemperatureModal,