myio-js-library 0.1.179 → 0.1.181

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
@@ -711,7 +711,7 @@ __export(index_exports, {
711
711
  module.exports = __toCommonJS(index_exports);
712
712
 
713
713
  // src/format/energy.ts
714
- function formatEnergy(value, unit) {
714
+ function formatEnergy(value, unit, decimals = 3) {
715
715
  if (value === null || value === void 0 || isNaN(value)) {
716
716
  return "-";
717
717
  }
@@ -729,8 +729,8 @@ function formatEnergy(value, unit) {
729
729
  }
730
730
  }
731
731
  const formattedValue = adjustedValue.toLocaleString("pt-BR", {
732
- minimumFractionDigits: 2,
733
- maximumFractionDigits: 2
732
+ minimumFractionDigits: decimals,
733
+ maximumFractionDigits: decimals
734
734
  });
735
735
  return `${formattedValue} ${adjustedUnit}`;
736
736
  }
@@ -4122,6 +4122,14 @@ var CSS_STRING = `
4122
4122
  --myio-chip-not-installed-fg: #7c3aed;
4123
4123
  --myio-border-not-installed: rgba(124, 58, 237, 0.5);
4124
4124
 
4125
+ /* Temperature range colors - solid backgrounds for better visibility */
4126
+ --myio-temp-cold-bg: #dbeafe; /* Blue 100 - below ideal range */
4127
+ --myio-temp-cold-border: rgba(59, 130, 246, 0.6);
4128
+ --myio-temp-ok-bg: #dcfce7; /* Green 100 - within ideal range */
4129
+ --myio-temp-ok-border: rgba(34, 197, 94, 0.6);
4130
+ --myio-temp-hot-bg: #fee2e2; /* Red 100 - above ideal range */
4131
+ --myio-temp-hot-border: rgba(239, 68, 68, 0.6);
4132
+
4125
4133
  --myio-text-1: #0f172a;
4126
4134
  --myio-text-2: #4b5563;
4127
4135
  --myio-muted: #94a3b8;
@@ -4225,6 +4233,25 @@ var CSS_STRING = `
4225
4233
  box-shadow: 0 0 0 2px var(--myio-border-not-installed), var(--myio-card-shadow);
4226
4234
  }
4227
4235
 
4236
+ /* Temperature range backgrounds (domain=temperature only) */
4237
+ .myio-ho-card.is-temp-cold {
4238
+ background: var(--myio-temp-cold-bg);
4239
+ border-color: var(--myio-temp-cold-border);
4240
+ box-shadow: 0 0 0 2px var(--myio-temp-cold-border), var(--myio-card-shadow);
4241
+ }
4242
+
4243
+ .myio-ho-card.is-temp-ok {
4244
+ background: var(--myio-temp-ok-bg);
4245
+ border-color: var(--myio-temp-ok-border);
4246
+ box-shadow: 0 0 0 2px var(--myio-temp-ok-border), var(--myio-card-shadow);
4247
+ }
4248
+
4249
+ .myio-ho-card.is-temp-hot {
4250
+ background: var(--myio-temp-hot-bg);
4251
+ border-color: var(--myio-temp-hot-border);
4252
+ box-shadow: 0 0 0 2px var(--myio-temp-hot-border), var(--myio-card-shadow);
4253
+ }
4254
+
4228
4255
  /* Header section */
4229
4256
  .myio-ho-card__header {
4230
4257
  display: flex;
@@ -5148,6 +5175,504 @@ var CSS_STRING = `
5148
5175
  .debug-tooltip__content::-webkit-scrollbar-thumb:hover {
5149
5176
  background: rgba(99, 102, 241, 0.6);
5150
5177
  }
5178
+
5179
+ /* ============================================
5180
+ Temperature Range Tooltip (for domain=temperature)
5181
+ Shows temperature ruler with current position and deviation
5182
+ ============================================ */
5183
+ .temp-range-tooltip {
5184
+ position: fixed;
5185
+ z-index: 99999;
5186
+ pointer-events: none;
5187
+ opacity: 0;
5188
+ transition: opacity 0.2s ease, transform 0.2s ease;
5189
+ transform: translateY(5px);
5190
+ }
5191
+
5192
+ .temp-range-tooltip.visible {
5193
+ opacity: 1;
5194
+ pointer-events: auto;
5195
+ transform: translateY(0);
5196
+ }
5197
+
5198
+ .temp-range-tooltip__content {
5199
+ background: #ffffff;
5200
+ border: 1px solid #e2e8f0;
5201
+ border-radius: 12px;
5202
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 2px 10px rgba(0, 0, 0, 0.08);
5203
+ min-width: 280px;
5204
+ max-width: 320px;
5205
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5206
+ font-size: 12px;
5207
+ color: #1e293b;
5208
+ overflow: hidden;
5209
+ }
5210
+
5211
+ .temp-range-tooltip__header {
5212
+ display: flex;
5213
+ align-items: center;
5214
+ gap: 8px;
5215
+ padding: 12px 16px;
5216
+ background: linear-gradient(90deg, #fff7ed 0%, #fed7aa 100%);
5217
+ border-bottom: 1px solid #fdba74;
5218
+ }
5219
+
5220
+ .temp-range-tooltip__icon {
5221
+ font-size: 18px;
5222
+ }
5223
+
5224
+ .temp-range-tooltip__title {
5225
+ font-weight: 700;
5226
+ font-size: 13px;
5227
+ color: #c2410c;
5228
+ }
5229
+
5230
+ .temp-range-tooltip__body {
5231
+ padding: 16px;
5232
+ }
5233
+
5234
+ /* Temperature value display */
5235
+ .temp-range-tooltip__value-row {
5236
+ display: flex;
5237
+ justify-content: space-between;
5238
+ align-items: center;
5239
+ margin-bottom: 16px;
5240
+ }
5241
+
5242
+ .temp-range-tooltip__current {
5243
+ font-size: 28px;
5244
+ font-weight: 700;
5245
+ color: #1e293b;
5246
+ }
5247
+
5248
+ .temp-range-tooltip__current sup {
5249
+ font-size: 14px;
5250
+ color: #64748b;
5251
+ }
5252
+
5253
+ .temp-range-tooltip__deviation {
5254
+ text-align: right;
5255
+ }
5256
+
5257
+ .temp-range-tooltip__deviation-value {
5258
+ font-size: 16px;
5259
+ font-weight: 700;
5260
+ }
5261
+
5262
+ .temp-range-tooltip__deviation-value.cold {
5263
+ color: #2563eb;
5264
+ }
5265
+
5266
+ .temp-range-tooltip__deviation-value.ok {
5267
+ color: #16a34a;
5268
+ }
5269
+
5270
+ .temp-range-tooltip__deviation-value.hot {
5271
+ color: #dc2626;
5272
+ }
5273
+
5274
+ .temp-range-tooltip__deviation-label {
5275
+ font-size: 10px;
5276
+ color: #64748b;
5277
+ text-transform: uppercase;
5278
+ letter-spacing: 0.5px;
5279
+ }
5280
+
5281
+ /* Temperature ruler/gauge */
5282
+ .temp-range-tooltip__ruler {
5283
+ position: relative;
5284
+ height: 32px;
5285
+ margin: 12px 0;
5286
+ border-radius: 8px;
5287
+ overflow: visible;
5288
+ }
5289
+
5290
+ .temp-range-tooltip__ruler-track {
5291
+ position: absolute;
5292
+ top: 12px;
5293
+ left: 0;
5294
+ right: 0;
5295
+ height: 8px;
5296
+ background: linear-gradient(90deg, #dbeafe 0%, #dcfce7 50%, #fee2e2 100%);
5297
+ border-radius: 4px;
5298
+ border: 1px solid #e2e8f0;
5299
+ }
5300
+
5301
+ .temp-range-tooltip__ruler-range {
5302
+ position: absolute;
5303
+ top: 12px;
5304
+ height: 8px;
5305
+ background: #22c55e;
5306
+ border-radius: 4px;
5307
+ opacity: 0.6;
5308
+ }
5309
+
5310
+ .temp-range-tooltip__ruler-marker {
5311
+ position: absolute;
5312
+ top: 4px;
5313
+ width: 4px;
5314
+ height: 24px;
5315
+ background: #1e293b;
5316
+ border-radius: 2px;
5317
+ transform: translateX(-50%);
5318
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
5319
+ }
5320
+
5321
+ .temp-range-tooltip__ruler-marker::after {
5322
+ content: '';
5323
+ position: absolute;
5324
+ top: -4px;
5325
+ left: 50%;
5326
+ transform: translateX(-50%);
5327
+ width: 12px;
5328
+ height: 12px;
5329
+ background: #1e293b;
5330
+ border-radius: 50%;
5331
+ border: 2px solid #fff;
5332
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
5333
+ }
5334
+
5335
+ .temp-range-tooltip__ruler-labels {
5336
+ display: flex;
5337
+ justify-content: space-between;
5338
+ margin-top: 8px;
5339
+ font-size: 10px;
5340
+ color: #64748b;
5341
+ }
5342
+
5343
+ .temp-range-tooltip__ruler-min,
5344
+ .temp-range-tooltip__ruler-max {
5345
+ font-weight: 600;
5346
+ }
5347
+
5348
+ /* Range info */
5349
+ .temp-range-tooltip__range-info {
5350
+ display: flex;
5351
+ justify-content: space-between;
5352
+ padding: 10px 12px;
5353
+ background: #f8fafc;
5354
+ border-radius: 8px;
5355
+ margin-top: 12px;
5356
+ }
5357
+
5358
+ .temp-range-tooltip__range-item {
5359
+ text-align: center;
5360
+ }
5361
+
5362
+ .temp-range-tooltip__range-label {
5363
+ font-size: 10px;
5364
+ color: #64748b;
5365
+ text-transform: uppercase;
5366
+ letter-spacing: 0.3px;
5367
+ margin-bottom: 2px;
5368
+ }
5369
+
5370
+ .temp-range-tooltip__range-value {
5371
+ font-size: 14px;
5372
+ font-weight: 600;
5373
+ color: #334155;
5374
+ }
5375
+
5376
+ /* Status badge */
5377
+ .temp-range-tooltip__status {
5378
+ display: flex;
5379
+ align-items: center;
5380
+ justify-content: center;
5381
+ gap: 6px;
5382
+ margin-top: 12px;
5383
+ padding: 8px 12px;
5384
+ border-radius: 6px;
5385
+ font-size: 11px;
5386
+ font-weight: 600;
5387
+ }
5388
+
5389
+ .temp-range-tooltip__status.cold {
5390
+ background: #dbeafe;
5391
+ color: #1d4ed8;
5392
+ border: 1px solid #93c5fd;
5393
+ }
5394
+
5395
+ .temp-range-tooltip__status.ok {
5396
+ background: #dcfce7;
5397
+ color: #15803d;
5398
+ border: 1px solid #86efac;
5399
+ }
5400
+
5401
+ .temp-range-tooltip__status.hot {
5402
+ background: #fee2e2;
5403
+ color: #b91c1c;
5404
+ border: 1px solid #fca5a5;
5405
+ }
5406
+
5407
+ .temp-range-tooltip__status.unknown {
5408
+ background: #f3f4f6;
5409
+ color: #6b7280;
5410
+ border: 1px solid #d1d5db;
5411
+ }
5412
+
5413
+ /* ============================================
5414
+ Energy Range Tooltip (for domain=energy)
5415
+ Shows power ruler with current position and status ranges
5416
+ ============================================ */
5417
+ .energy-range-tooltip {
5418
+ position: fixed;
5419
+ z-index: 99999;
5420
+ pointer-events: none;
5421
+ opacity: 0;
5422
+ transition: opacity 0.2s ease, transform 0.2s ease;
5423
+ transform: translateY(5px);
5424
+ }
5425
+
5426
+ .energy-range-tooltip.visible {
5427
+ opacity: 1;
5428
+ pointer-events: auto;
5429
+ transform: translateY(0);
5430
+ }
5431
+
5432
+ .energy-range-tooltip__content {
5433
+ background: #ffffff;
5434
+ border: 1px solid #e2e8f0;
5435
+ border-radius: 12px;
5436
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 2px 10px rgba(0, 0, 0, 0.08);
5437
+ min-width: 300px;
5438
+ max-width: 360px;
5439
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5440
+ font-size: 12px;
5441
+ color: #1e293b;
5442
+ overflow: hidden;
5443
+ }
5444
+
5445
+ .energy-range-tooltip__header {
5446
+ display: flex;
5447
+ align-items: center;
5448
+ gap: 8px;
5449
+ padding: 12px 16px;
5450
+ background: linear-gradient(90deg, #ecfdf5 0%, #d1fae5 100%);
5451
+ border-bottom: 1px solid #6ee7b7;
5452
+ }
5453
+
5454
+ .energy-range-tooltip__icon {
5455
+ font-size: 18px;
5456
+ }
5457
+
5458
+ .energy-range-tooltip__title {
5459
+ font-weight: 700;
5460
+ font-size: 13px;
5461
+ color: #047857;
5462
+ }
5463
+
5464
+ .energy-range-tooltip__body {
5465
+ padding: 16px;
5466
+ }
5467
+
5468
+ /* Power value display */
5469
+ .energy-range-tooltip__value-row {
5470
+ display: flex;
5471
+ justify-content: space-between;
5472
+ align-items: center;
5473
+ margin-bottom: 16px;
5474
+ }
5475
+
5476
+ .energy-range-tooltip__current {
5477
+ font-size: 28px;
5478
+ font-weight: 700;
5479
+ color: #1e293b;
5480
+ }
5481
+
5482
+ .energy-range-tooltip__current sup {
5483
+ font-size: 14px;
5484
+ color: #64748b;
5485
+ }
5486
+
5487
+ .energy-range-tooltip__status-badge {
5488
+ text-align: right;
5489
+ }
5490
+
5491
+ .energy-range-tooltip__status-value {
5492
+ font-size: 14px;
5493
+ font-weight: 700;
5494
+ padding: 4px 10px;
5495
+ border-radius: 6px;
5496
+ }
5497
+
5498
+ .energy-range-tooltip__status-value.standby {
5499
+ background: #dbeafe;
5500
+ color: #1d4ed8;
5501
+ }
5502
+
5503
+ .energy-range-tooltip__status-value.normal {
5504
+ background: #dcfce7;
5505
+ color: #15803d;
5506
+ }
5507
+
5508
+ .energy-range-tooltip__status-value.alert {
5509
+ background: #fef3c7;
5510
+ color: #b45309;
5511
+ }
5512
+
5513
+ .energy-range-tooltip__status-value.failure {
5514
+ background: #fee2e2;
5515
+ color: #b91c1c;
5516
+ }
5517
+
5518
+ .energy-range-tooltip__status-value.offline {
5519
+ background: #f3f4f6;
5520
+ color: #6b7280;
5521
+ }
5522
+
5523
+ /* Power ruler/gauge */
5524
+ .energy-range-tooltip__ruler {
5525
+ position: relative;
5526
+ height: 40px;
5527
+ margin: 12px 0;
5528
+ border-radius: 8px;
5529
+ overflow: visible;
5530
+ }
5531
+
5532
+ .energy-range-tooltip__ruler-track {
5533
+ position: absolute;
5534
+ top: 16px;
5535
+ left: 0;
5536
+ right: 0;
5537
+ height: 8px;
5538
+ display: flex;
5539
+ border-radius: 4px;
5540
+ overflow: hidden;
5541
+ border: 1px solid #e2e8f0;
5542
+ }
5543
+
5544
+ .energy-range-tooltip__ruler-segment {
5545
+ height: 100%;
5546
+ }
5547
+
5548
+ .energy-range-tooltip__ruler-segment.standby {
5549
+ background: #dbeafe;
5550
+ }
5551
+
5552
+ .energy-range-tooltip__ruler-segment.normal {
5553
+ background: #dcfce7;
5554
+ }
5555
+
5556
+ .energy-range-tooltip__ruler-segment.alert {
5557
+ background: #fef3c7;
5558
+ }
5559
+
5560
+ .energy-range-tooltip__ruler-segment.failure {
5561
+ background: #fee2e2;
5562
+ }
5563
+
5564
+ .energy-range-tooltip__ruler-marker {
5565
+ position: absolute;
5566
+ top: 8px;
5567
+ width: 4px;
5568
+ height: 24px;
5569
+ background: #1e293b;
5570
+ border-radius: 2px;
5571
+ transform: translateX(-50%);
5572
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
5573
+ }
5574
+
5575
+ .energy-range-tooltip__ruler-marker::after {
5576
+ content: '';
5577
+ position: absolute;
5578
+ top: -4px;
5579
+ left: 50%;
5580
+ transform: translateX(-50%);
5581
+ width: 12px;
5582
+ height: 12px;
5583
+ background: #1e293b;
5584
+ border-radius: 50%;
5585
+ border: 2px solid #fff;
5586
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
5587
+ }
5588
+
5589
+ /* Range info grid */
5590
+ .energy-range-tooltip__ranges {
5591
+ display: grid;
5592
+ grid-template-columns: repeat(4, 1fr);
5593
+ gap: 8px;
5594
+ margin-top: 16px;
5595
+ }
5596
+
5597
+ .energy-range-tooltip__range-item {
5598
+ text-align: center;
5599
+ padding: 8px 4px;
5600
+ border-radius: 6px;
5601
+ background: #f8fafc;
5602
+ }
5603
+
5604
+ .energy-range-tooltip__range-item.standby {
5605
+ border-left: 3px solid #3b82f6;
5606
+ }
5607
+
5608
+ .energy-range-tooltip__range-item.normal {
5609
+ border-left: 3px solid #22c55e;
5610
+ }
5611
+
5612
+ .energy-range-tooltip__range-item.alert {
5613
+ border-left: 3px solid #f59e0b;
5614
+ }
5615
+
5616
+ .energy-range-tooltip__range-item.failure {
5617
+ border-left: 3px solid #ef4444;
5618
+ }
5619
+
5620
+ .energy-range-tooltip__range-label {
5621
+ font-size: 9px;
5622
+ color: #64748b;
5623
+ text-transform: uppercase;
5624
+ letter-spacing: 0.3px;
5625
+ margin-bottom: 2px;
5626
+ }
5627
+
5628
+ .energy-range-tooltip__range-value {
5629
+ font-size: 11px;
5630
+ font-weight: 600;
5631
+ color: #334155;
5632
+ }
5633
+
5634
+ /* Status info */
5635
+ .energy-range-tooltip__status-info {
5636
+ display: flex;
5637
+ align-items: center;
5638
+ justify-content: center;
5639
+ gap: 6px;
5640
+ margin-top: 12px;
5641
+ padding: 8px 12px;
5642
+ border-radius: 6px;
5643
+ font-size: 11px;
5644
+ font-weight: 600;
5645
+ }
5646
+
5647
+ .energy-range-tooltip__status-info.standby {
5648
+ background: #dbeafe;
5649
+ color: #1d4ed8;
5650
+ border: 1px solid #93c5fd;
5651
+ }
5652
+
5653
+ .energy-range-tooltip__status-info.normal {
5654
+ background: #dcfce7;
5655
+ color: #15803d;
5656
+ border: 1px solid #86efac;
5657
+ }
5658
+
5659
+ .energy-range-tooltip__status-info.alert {
5660
+ background: #fef3c7;
5661
+ color: #b45309;
5662
+ border: 1px solid #fcd34d;
5663
+ }
5664
+
5665
+ .energy-range-tooltip__status-info.failure {
5666
+ background: #fee2e2;
5667
+ color: #b91c1c;
5668
+ border: 1px solid #fca5a5;
5669
+ }
5670
+
5671
+ .energy-range-tooltip__status-info.offline {
5672
+ background: #f3f4f6;
5673
+ color: #6b7280;
5674
+ border: 1px solid #d1d5db;
5675
+ }
5151
5676
  `;
5152
5677
 
5153
5678
  // src/thingsboard/main-dashboard-shopping/v-4.0.0/head-office/card-head-office.icons.ts
@@ -5707,6 +6232,380 @@ function getCardStateClass(deviceStatus) {
5707
6232
  return "";
5708
6233
  }
5709
6234
  }
6235
+ function getTempRangeClass(entityObject) {
6236
+ if (entityObject.domain !== "temperature") return "";
6237
+ const currentTemp = Number(entityObject.val ?? entityObject.currentTemperature ?? entityObject.temperature) || 0;
6238
+ const tempMin = entityObject.temperatureMin ?? entityObject.minTemperature;
6239
+ const tempMax = entityObject.temperatureMax ?? entityObject.maxTemperature;
6240
+ if (tempMin === void 0 || tempMax === void 0 || tempMin === null || tempMax === null) {
6241
+ return "";
6242
+ }
6243
+ if (currentTemp > tempMax) return "is-temp-hot";
6244
+ if (currentTemp < tempMin) return "is-temp-cold";
6245
+ return "is-temp-ok";
6246
+ }
6247
+ var TempRangeTooltip = {
6248
+ containerId: "myio-temp-range-tooltip",
6249
+ /**
6250
+ * Create or get the tooltip container
6251
+ */
6252
+ getContainer() {
6253
+ let container = document.getElementById(this.containerId);
6254
+ if (!container) {
6255
+ container = document.createElement("div");
6256
+ container.id = this.containerId;
6257
+ container.className = "temp-range-tooltip";
6258
+ document.body.appendChild(container);
6259
+ }
6260
+ return container;
6261
+ },
6262
+ /**
6263
+ * Calculate temperature status and deviation
6264
+ */
6265
+ calculateStatus(currentTemp, tempMin, tempMax) {
6266
+ if (tempMin == null || tempMax == null) {
6267
+ return { status: "unknown", deviation: null, deviationPercent: null };
6268
+ }
6269
+ const rangeSize = tempMax - tempMin;
6270
+ const midPoint = (tempMin + tempMax) / 2;
6271
+ if (currentTemp < tempMin) {
6272
+ const deviation = tempMin - currentTemp;
6273
+ const deviationPercent = rangeSize > 0 ? deviation / rangeSize * 100 : 0;
6274
+ return { status: "cold", deviation: -deviation, deviationPercent: -deviationPercent };
6275
+ } else if (currentTemp > tempMax) {
6276
+ const deviation = currentTemp - tempMax;
6277
+ const deviationPercent = rangeSize > 0 ? deviation / rangeSize * 100 : 0;
6278
+ return { status: "hot", deviation, deviationPercent };
6279
+ } else {
6280
+ const deviationFromMid = currentTemp - midPoint;
6281
+ const halfRange = rangeSize / 2;
6282
+ const deviationPercent = halfRange > 0 ? deviationFromMid / halfRange * 100 : 0;
6283
+ return { status: "ok", deviation: deviationFromMid, deviationPercent: 0 };
6284
+ }
6285
+ },
6286
+ /**
6287
+ * Calculate marker position on ruler (0-100%)
6288
+ */
6289
+ calculateMarkerPosition(currentTemp, tempMin, tempMax) {
6290
+ if (tempMin == null || tempMax == null) return 50;
6291
+ const rangeSize = tempMax - tempMin;
6292
+ const extension = rangeSize * 0.3;
6293
+ const visibleMin = tempMin - extension;
6294
+ const visibleMax = tempMax + extension;
6295
+ const visibleRange = visibleMax - visibleMin;
6296
+ const clampedTemp = Math.max(visibleMin, Math.min(visibleMax, currentTemp));
6297
+ const position = (clampedTemp - visibleMin) / visibleRange * 100;
6298
+ return Math.max(2, Math.min(98, position));
6299
+ },
6300
+ /**
6301
+ * Show tooltip for a temperature card
6302
+ * @param {HTMLElement} triggerElement - The card element
6303
+ * @param {Object} entityObject - Entity data
6304
+ * @param {MouseEvent} event - Mouse event for cursor position
6305
+ */
6306
+ show(triggerElement, entityObject, event) {
6307
+ const container = this.getContainer();
6308
+ const currentTemp = Number(entityObject.val ?? entityObject.currentTemperature ?? entityObject.temperature) || 0;
6309
+ const tempMin = entityObject.temperatureMin ?? entityObject.minTemperature;
6310
+ const tempMax = entityObject.temperatureMax ?? entityObject.maxTemperature;
6311
+ const hasRange = tempMin != null && tempMax != null;
6312
+ const { status, deviation, deviationPercent } = this.calculateStatus(currentTemp, tempMin, tempMax);
6313
+ const markerPos = this.calculateMarkerPosition(currentTemp, tempMin, tempMax);
6314
+ let rangeLeft = 0, rangeWidth = 100;
6315
+ if (hasRange) {
6316
+ const rangeSize = tempMax - tempMin;
6317
+ const extension = rangeSize * 0.3;
6318
+ const visibleMin = tempMin - extension;
6319
+ const visibleMax = tempMax + extension;
6320
+ const visibleRange = visibleMax - visibleMin;
6321
+ rangeLeft = (tempMin - visibleMin) / visibleRange * 100;
6322
+ rangeWidth = rangeSize / visibleRange * 100;
6323
+ }
6324
+ let deviationText = "";
6325
+ let deviationClass = status;
6326
+ if (status === "cold") {
6327
+ deviationText = `${Math.abs(deviation).toFixed(1)}\xB0C abaixo`;
6328
+ } else if (status === "hot") {
6329
+ deviationText = `+${deviation.toFixed(1)}\xB0C acima`;
6330
+ } else if (status === "ok") {
6331
+ deviationText = "Na faixa ideal";
6332
+ } else {
6333
+ deviationText = "Faixa n\xE3o configurada";
6334
+ }
6335
+ const statusLabels = {
6336
+ cold: "\u2744\uFE0F Abaixo da Faixa Ideal",
6337
+ ok: "\u2714\uFE0F Dentro da Faixa Ideal",
6338
+ hot: "\u{1F525} Acima da Faixa Ideal",
6339
+ unknown: "\u2753 Faixa N\xE3o Configurada"
6340
+ };
6341
+ container.innerHTML = `
6342
+ <div class="temp-range-tooltip__content">
6343
+ <div class="temp-range-tooltip__header">
6344
+ <span class="temp-range-tooltip__icon">\u{1F321}\uFE0F</span>
6345
+ <span class="temp-range-tooltip__title">${entityObject.labelOrName || entityObject.name || "Sensor"}</span>
6346
+ </div>
6347
+ <div class="temp-range-tooltip__body">
6348
+ <div class="temp-range-tooltip__value-row">
6349
+ <div class="temp-range-tooltip__current">
6350
+ ${currentTemp.toFixed(1)}<sup>\xB0C</sup>
6351
+ </div>
6352
+ <div class="temp-range-tooltip__deviation">
6353
+ <div class="temp-range-tooltip__deviation-value ${deviationClass}">
6354
+ ${status === "ok" ? "\u2713" : status === "cold" ? "\u2193" : status === "hot" ? "\u2191" : "?"} ${Math.abs(deviationPercent || 0).toFixed(0)}%
6355
+ </div>
6356
+ <div class="temp-range-tooltip__deviation-label">Desvio</div>
6357
+ </div>
6358
+ </div>
6359
+
6360
+ ${hasRange ? `
6361
+ <div class="temp-range-tooltip__ruler">
6362
+ <div class="temp-range-tooltip__ruler-track"></div>
6363
+ <div class="temp-range-tooltip__ruler-range" style="left: ${rangeLeft}%; width: ${rangeWidth}%;"></div>
6364
+ <div class="temp-range-tooltip__ruler-marker" style="left: ${markerPos}%;"></div>
6365
+ </div>
6366
+ <div class="temp-range-tooltip__ruler-labels">
6367
+ <span class="temp-range-tooltip__ruler-min">${tempMin}\xB0C</span>
6368
+ <span style="color: #22c55e; font-weight: 600;">Faixa Ideal</span>
6369
+ <span class="temp-range-tooltip__ruler-max">${tempMax}\xB0C</span>
6370
+ </div>
6371
+ ` : ""}
6372
+
6373
+ <div class="temp-range-tooltip__range-info">
6374
+ <div class="temp-range-tooltip__range-item">
6375
+ <div class="temp-range-tooltip__range-label">M\xEDnimo</div>
6376
+ <div class="temp-range-tooltip__range-value">${hasRange ? tempMin + "\xB0C" : "--"}</div>
6377
+ </div>
6378
+ <div class="temp-range-tooltip__range-item">
6379
+ <div class="temp-range-tooltip__range-label">Atual</div>
6380
+ <div class="temp-range-tooltip__range-value" style="color: ${status === "ok" ? "#16a34a" : status === "cold" ? "#2563eb" : "#dc2626"}">${currentTemp.toFixed(1)}\xB0C</div>
6381
+ </div>
6382
+ <div class="temp-range-tooltip__range-item">
6383
+ <div class="temp-range-tooltip__range-label">M\xE1ximo</div>
6384
+ <div class="temp-range-tooltip__range-value">${hasRange ? tempMax + "\xB0C" : "--"}</div>
6385
+ </div>
6386
+ </div>
6387
+
6388
+ <div class="temp-range-tooltip__status ${status}">
6389
+ ${statusLabels[status]}
6390
+ </div>
6391
+ </div>
6392
+ </div>
6393
+ `;
6394
+ let left, top;
6395
+ if (event && event.clientX && event.clientY) {
6396
+ left = event.clientX + 15;
6397
+ top = event.clientY + 15;
6398
+ } else {
6399
+ const rect = triggerElement.getBoundingClientRect();
6400
+ left = rect.left + rect.width / 2 - 150;
6401
+ top = rect.bottom + 8;
6402
+ }
6403
+ const tooltipWidth = 300;
6404
+ const tooltipHeight = 350;
6405
+ if (left + tooltipWidth > window.innerWidth - 10) {
6406
+ left = (event?.clientX || left) - tooltipWidth - 15;
6407
+ }
6408
+ if (left < 10) left = 10;
6409
+ if (top + tooltipHeight > window.innerHeight - 10) {
6410
+ top = (event?.clientY || top) - tooltipHeight - 15;
6411
+ }
6412
+ if (top < 10) top = 10;
6413
+ container.style.left = left + "px";
6414
+ container.style.top = top + "px";
6415
+ container.classList.add("visible");
6416
+ },
6417
+ /**
6418
+ * Hide tooltip
6419
+ */
6420
+ hide() {
6421
+ const container = document.getElementById(this.containerId);
6422
+ if (container) {
6423
+ container.classList.remove("visible");
6424
+ }
6425
+ }
6426
+ };
6427
+ var EnergyRangeTooltip = {
6428
+ containerId: "myio-energy-range-tooltip",
6429
+ /**
6430
+ * Create or get the tooltip container
6431
+ */
6432
+ getContainer() {
6433
+ let container = document.getElementById(this.containerId);
6434
+ if (!container) {
6435
+ container = document.createElement("div");
6436
+ container.id = this.containerId;
6437
+ container.className = "energy-range-tooltip";
6438
+ document.body.appendChild(container);
6439
+ }
6440
+ return container;
6441
+ },
6442
+ /**
6443
+ * Determine status based on power value and ranges
6444
+ */
6445
+ calculateStatus(powerValue, ranges) {
6446
+ if (!ranges || powerValue == null) {
6447
+ return { status: "offline", label: "Sem dados" };
6448
+ }
6449
+ const power = Number(powerValue) || 0;
6450
+ const { standbyRange, normalRange, alertRange, failureRange } = ranges;
6451
+ if (standbyRange && power >= standbyRange.down && power <= standbyRange.up) {
6452
+ return { status: "standby", label: "Standby" };
6453
+ }
6454
+ if (normalRange && power >= normalRange.down && power <= normalRange.up) {
6455
+ return { status: "normal", label: "Normal" };
6456
+ }
6457
+ if (alertRange && power >= alertRange.down && power <= alertRange.up) {
6458
+ return { status: "alert", label: "Alerta" };
6459
+ }
6460
+ if (failureRange && power >= failureRange.down && power <= failureRange.up) {
6461
+ return { status: "failure", label: "Falha" };
6462
+ }
6463
+ return { status: "offline", label: "Fora da faixa" };
6464
+ },
6465
+ /**
6466
+ * Calculate marker position on ruler (0-100%)
6467
+ */
6468
+ calculateMarkerPosition(powerValue, ranges) {
6469
+ if (!ranges) return 50;
6470
+ const power = Number(powerValue) || 0;
6471
+ const maxRange = ranges.failureRange?.up || ranges.alertRange?.up || 1e3;
6472
+ const displayMax = Math.min(maxRange, (ranges.alertRange?.up || maxRange) * 1.2);
6473
+ const position = power / displayMax * 100;
6474
+ return Math.max(2, Math.min(98, position));
6475
+ },
6476
+ /**
6477
+ * Calculate segment widths for the ruler
6478
+ */
6479
+ calculateSegmentWidths(ranges) {
6480
+ if (!ranges) return { standby: 25, normal: 25, alert: 25, failure: 25 };
6481
+ const maxValue = ranges.failureRange?.up || 1e4;
6482
+ const total = Math.min(maxValue, (ranges.alertRange?.up || maxValue) * 1.2);
6483
+ return {
6484
+ standby: (ranges.standbyRange?.up || 0) / total * 100,
6485
+ normal: ((ranges.normalRange?.up || 0) - (ranges.normalRange?.down || 0)) / total * 100,
6486
+ alert: ((ranges.alertRange?.up || 0) - (ranges.alertRange?.down || 0)) / total * 100,
6487
+ failure: Math.max(5, 100 - (ranges.alertRange?.up || 0) / total * 100)
6488
+ };
6489
+ },
6490
+ /**
6491
+ * Format power value for display
6492
+ */
6493
+ formatPower(value) {
6494
+ if (value == null || isNaN(value)) return "-";
6495
+ const num = Number(value);
6496
+ if (num >= 1e3) {
6497
+ return `${(num / 1e3).toFixed(2)} kW`;
6498
+ }
6499
+ return `${Math.round(num)} W`;
6500
+ },
6501
+ /**
6502
+ * Show tooltip for an energy card
6503
+ */
6504
+ show(triggerElement, entityObject, event) {
6505
+ const container = this.getContainer();
6506
+ const powerValue = entityObject.instantaneousPower ?? entityObject.consumption_power ?? 0;
6507
+ const ranges = entityObject.powerRanges || entityObject.ranges;
6508
+ const hasRanges = ranges && ranges.normalRange;
6509
+ const { status, label } = this.calculateStatus(powerValue, ranges);
6510
+ const markerPos = this.calculateMarkerPosition(powerValue, ranges);
6511
+ const segmentWidths = this.calculateSegmentWidths(ranges);
6512
+ const statusLabels = {
6513
+ standby: "\u{1F535} Standby",
6514
+ normal: "\u2705 Opera\xE7\xE3o Normal",
6515
+ alert: "\u26A0\uFE0F Alerta",
6516
+ failure: "\u{1F534} Falha",
6517
+ offline: "\u26AB Offline / Sem dados"
6518
+ };
6519
+ container.innerHTML = `
6520
+ <div class="energy-range-tooltip__content">
6521
+ <div class="energy-range-tooltip__header">
6522
+ <span class="energy-range-tooltip__icon">\u26A1</span>
6523
+ <span class="energy-range-tooltip__title">${entityObject.labelOrName || entityObject.name || "Equipamento"}</span>
6524
+ </div>
6525
+ <div class="energy-range-tooltip__body">
6526
+ <div class="energy-range-tooltip__value-row">
6527
+ <div class="energy-range-tooltip__current">
6528
+ ${this.formatPower(powerValue)}
6529
+ </div>
6530
+ <div class="energy-range-tooltip__status-badge">
6531
+ <span class="energy-range-tooltip__status-value ${status}">${label}</span>
6532
+ </div>
6533
+ </div>
6534
+
6535
+ ${hasRanges ? `
6536
+ <div class="energy-range-tooltip__ruler">
6537
+ <div class="energy-range-tooltip__ruler-track">
6538
+ <div class="energy-range-tooltip__ruler-segment standby" style="width: ${segmentWidths.standby}%"></div>
6539
+ <div class="energy-range-tooltip__ruler-segment normal" style="width: ${segmentWidths.normal}%"></div>
6540
+ <div class="energy-range-tooltip__ruler-segment alert" style="width: ${segmentWidths.alert}%"></div>
6541
+ <div class="energy-range-tooltip__ruler-segment failure" style="width: ${segmentWidths.failure}%"></div>
6542
+ </div>
6543
+ <div class="energy-range-tooltip__ruler-marker" style="left: ${markerPos}%;"></div>
6544
+ </div>
6545
+
6546
+ <div class="energy-range-tooltip__ranges">
6547
+ <div class="energy-range-tooltip__range-item standby">
6548
+ <div class="energy-range-tooltip__range-label">Standby</div>
6549
+ <div class="energy-range-tooltip__range-value">${ranges.standbyRange?.down || 0}-${ranges.standbyRange?.up || 0}W</div>
6550
+ </div>
6551
+ <div class="energy-range-tooltip__range-item normal">
6552
+ <div class="energy-range-tooltip__range-label">Normal</div>
6553
+ <div class="energy-range-tooltip__range-value">${ranges.normalRange?.down || 0}-${ranges.normalRange?.up || 0}W</div>
6554
+ </div>
6555
+ <div class="energy-range-tooltip__range-item alert">
6556
+ <div class="energy-range-tooltip__range-label">Alerta</div>
6557
+ <div class="energy-range-tooltip__range-value">${ranges.alertRange?.down || 0}-${ranges.alertRange?.up || 0}W</div>
6558
+ </div>
6559
+ <div class="energy-range-tooltip__range-item failure">
6560
+ <div class="energy-range-tooltip__range-label">Falha</div>
6561
+ <div class="energy-range-tooltip__range-value">&gt;${ranges.failureRange?.down || 0}W</div>
6562
+ </div>
6563
+ </div>
6564
+ ` : `
6565
+ <div style="text-align: center; padding: 16px; color: #64748b; font-size: 12px;">
6566
+ Ranges de pot\xEAncia n\xE3o configurados
6567
+ </div>
6568
+ `}
6569
+
6570
+ <div class="energy-range-tooltip__status-info ${status}">
6571
+ ${statusLabels[status] || statusLabels.offline}
6572
+ </div>
6573
+ </div>
6574
+ </div>
6575
+ `;
6576
+ let left, top;
6577
+ if (event && event.clientX && event.clientY) {
6578
+ left = event.clientX + 15;
6579
+ top = event.clientY + 15;
6580
+ } else {
6581
+ const rect = triggerElement.getBoundingClientRect();
6582
+ left = rect.left + rect.width / 2 - 150;
6583
+ top = rect.bottom + 8;
6584
+ }
6585
+ const tooltipWidth = 320;
6586
+ const tooltipHeight = 380;
6587
+ if (left + tooltipWidth > window.innerWidth - 10) {
6588
+ left = (event?.clientX || left) - tooltipWidth - 15;
6589
+ }
6590
+ if (left < 10) left = 10;
6591
+ if (top + tooltipHeight > window.innerHeight - 10) {
6592
+ top = (event?.clientY || top) - tooltipHeight - 15;
6593
+ }
6594
+ if (top < 10) top = 10;
6595
+ container.style.left = left + "px";
6596
+ container.style.top = top + "px";
6597
+ container.classList.add("visible");
6598
+ },
6599
+ /**
6600
+ * Hide tooltip
6601
+ */
6602
+ hide() {
6603
+ const container = document.getElementById(this.containerId);
6604
+ if (container) {
6605
+ container.classList.remove("visible");
6606
+ }
6607
+ }
6608
+ };
5710
6609
  function getStatusDotClass(deviceStatus) {
5711
6610
  switch (deviceStatus) {
5712
6611
  // --- Novos Status de Temperatura ---
@@ -6099,11 +6998,16 @@ function paint(root, state) {
6099
6998
  }
6100
6999
  }
6101
7000
  const stateClass = getCardStateClass(entityObject.deviceStatus);
6102
- root.className = `myio-ho-card ${stateClass}`;
7001
+ const tempRangeClass = getTempRangeClass(entityObject);
7002
+ root.className = `myio-ho-card ${stateClass} ${tempRangeClass}`.trim();
6103
7003
  const statusInfo = getStatusInfo(entityObject.deviceStatus, i18n, entityObject.domain);
6104
7004
  const chip = root.querySelector(".chip");
6105
7005
  chip.className = `chip ${statusInfo.chipClass}`;
6106
7006
  chip.innerHTML = statusInfo.label;
7007
+ const iconContainer = root.querySelector(".myio-ho-card__icon");
7008
+ if (iconContainer) {
7009
+ iconContainer.innerHTML = getIconSvg(entityObject.deviceType, entityObject.domain);
7010
+ }
6107
7011
  if (activeTooltipDebug) {
6108
7012
  const debugInfo = buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins);
6109
7013
  attachDebugTooltip(chip, debugInfo);
@@ -6139,14 +7043,9 @@ function paint(root, state) {
6139
7043
  const opTimeVal = root.querySelector(".myio-ho-card__footer .metric:nth-child(1) .val");
6140
7044
  const opTimeLabel = root.querySelector(".myio-ho-card__footer .metric:nth-child(1) .label");
6141
7045
  if (opTimeVal) {
6142
- if (entityObject.domain === "temperature") {
6143
- if (opTimeLabel) opTimeLabel.textContent = "Temp. Atual";
6144
- const currentTemp = entityObject.currentTemperature ?? entityObject.temperature ?? entityObject.val ?? null;
6145
- opTimeVal.textContent = currentTemp !== null ? `${Number(currentTemp).toFixed(1)}\xB0C` : "-";
6146
- } else {
6147
- if (opTimeLabel) opTimeLabel.textContent = i18n.operation_time;
6148
- opTimeVal.textContent = entityObject.operationHours ?? "-";
6149
- }
7046
+ if (opTimeLabel) opTimeLabel.textContent = i18n.operation_time;
7047
+ const isOffline = entityObject.deviceStatus === DeviceStatusType.OFFLINE || entityObject.deviceStatus === "offline" || entityObject.deviceStatus === DeviceStatusType.NO_INFO;
7048
+ opTimeVal.textContent = isOffline ? "-" : entityObject.operationHours ?? "-";
6150
7049
  }
6151
7050
  const powerVal = root.querySelector(".myio-ho-card__footer .metric:nth-child(2) .val");
6152
7051
  if (powerVal) {
@@ -6232,12 +7131,51 @@ function bindEvents(root, state, callbacks) {
6232
7131
  MyIOSelectionStore2.on("selection:change", onSelectionChange);
6233
7132
  root._selectionListener = onSelectionChange;
6234
7133
  }
7134
+ const valueElement = root.querySelector(".myio-ho-card__value");
7135
+ if (entityObject.domain === "temperature" && valueElement) {
7136
+ const showTooltip = (e) => {
7137
+ TempRangeTooltip.show(root, state.entityObject, e);
7138
+ };
7139
+ const hideTooltip = () => {
7140
+ TempRangeTooltip.hide();
7141
+ };
7142
+ valueElement.style.cursor = "help";
7143
+ valueElement.addEventListener("mouseenter", showTooltip);
7144
+ valueElement.addEventListener("mouseleave", hideTooltip);
7145
+ root._tempTooltipShowFn = showTooltip;
7146
+ root._tempTooltipHideFn = hideTooltip;
7147
+ root._tooltipElement = valueElement;
7148
+ }
7149
+ if (entityObject.domain === "energy" && valueElement) {
7150
+ const showEnergyTooltip = (e) => {
7151
+ EnergyRangeTooltip.show(root, state.entityObject, e);
7152
+ };
7153
+ const hideEnergyTooltip = () => {
7154
+ EnergyRangeTooltip.hide();
7155
+ };
7156
+ valueElement.style.cursor = "help";
7157
+ valueElement.addEventListener("mouseenter", showEnergyTooltip);
7158
+ valueElement.addEventListener("mouseleave", hideEnergyTooltip);
7159
+ root._energyTooltipShowFn = showEnergyTooltip;
7160
+ root._energyTooltipHideFn = hideEnergyTooltip;
7161
+ root._tooltipElement = valueElement;
7162
+ }
6235
7163
  root._cleanup = () => {
6236
7164
  document.removeEventListener("click", closeMenu);
6237
7165
  document.removeEventListener("keydown", closeMenu);
6238
7166
  if (MyIOSelectionStore2 && root._selectionListener) {
6239
7167
  MyIOSelectionStore2.off("selection:change", root._selectionListener);
6240
7168
  }
7169
+ if (root._tempTooltipShowFn && root._tooltipElement) {
7170
+ root._tooltipElement.removeEventListener("mouseenter", root._tempTooltipShowFn);
7171
+ root._tooltipElement.removeEventListener("mouseleave", root._tempTooltipHideFn);
7172
+ TempRangeTooltip.hide();
7173
+ }
7174
+ if (root._energyTooltipShowFn && root._tooltipElement) {
7175
+ root._tooltipElement.removeEventListener("mouseenter", root._energyTooltipShowFn);
7176
+ root._tooltipElement.removeEventListener("mouseleave", root._energyTooltipHideFn);
7177
+ EnergyRangeTooltip.hide();
7178
+ }
6241
7179
  };
6242
7180
  const checkbox = root.querySelector('.myio-ho-card__select input[type="checkbox"]');
6243
7181
  if (checkbox && callbacks.handleSelect) {