myio-js-library 0.1.195 → 0.1.198

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.
@@ -8863,8 +8863,8 @@
8863
8863
  return "dot--offline";
8864
8864
  }
8865
8865
  }
8866
- function buildDOM(state5) {
8867
- const { entityObject, i18n, enableSelection, enableDragDrop } = state5;
8866
+ function buildDOM(state6) {
8867
+ const { entityObject, i18n, enableSelection, enableDragDrop } = state6;
8868
8868
  const root = document.createElement("div");
8869
8869
  root.className = "myio-ho-card";
8870
8870
  root.setAttribute("role", "group");
@@ -9020,7 +9020,7 @@
9020
9020
  return root;
9021
9021
  }
9022
9022
  function buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins) {
9023
- const formatTimestamp5 = (ts) => {
9023
+ const formatTimestamp6 = (ts) => {
9024
9024
  if (!ts) return "N/A";
9025
9025
  const d = new Date(ts);
9026
9026
  return d.toLocaleString("pt-BR", {
@@ -9047,8 +9047,8 @@
9047
9047
  chipClass: statusInfo.chipClass,
9048
9048
  chipLabel: statusInfo.label,
9049
9049
  // Connection timestamps
9050
- lastConnectTime: formatTimestamp5(entityObject.lastConnectTime),
9051
- lastDisconnectTime: formatTimestamp5(entityObject.lastDisconnectTime),
9050
+ lastConnectTime: formatTimestamp6(entityObject.lastConnectTime),
9051
+ lastDisconnectTime: formatTimestamp6(entityObject.lastDisconnectTime),
9052
9052
  delayTimeConnectionInMins,
9053
9053
  // Raw values
9054
9054
  val: entityObject.val,
@@ -9190,8 +9190,8 @@
9190
9190
  }
9191
9191
  return isOffline;
9192
9192
  }
9193
- function paint(root, state5) {
9194
- const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state5;
9193
+ function paint(root, state6) {
9194
+ const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state6;
9195
9195
  let statusDecisionSource = "unknown";
9196
9196
  if (entityObject.connectionStatus) {
9197
9197
  if (entityObject.connectionStatus === "offline") {
@@ -9243,7 +9243,7 @@
9243
9243
  numSpan.textContent = primaryValue;
9244
9244
  const barContainer = root.querySelector(".bar");
9245
9245
  const effContainer = root.querySelector(".myio-ho-card__eff");
9246
- if (state5.enableSelection) {
9246
+ if (state6.enableSelection) {
9247
9247
  const checkbox = root.querySelector('.myio-ho-card__select input[type="checkbox"]');
9248
9248
  if (checkbox) {
9249
9249
  checkbox.checked = !!isSelected;
@@ -9291,8 +9291,8 @@
9291
9291
  statusDot.className = `status-dot ${dotClass}`;
9292
9292
  }
9293
9293
  }
9294
- function bindEvents(root, state5, callbacks) {
9295
- const { entityObject } = state5;
9294
+ function bindEvents(root, state6, callbacks) {
9295
+ const { entityObject } = state6;
9296
9296
  const kebabBtn = root.querySelector(".myio-ho-card__kebab");
9297
9297
  const menu = root.querySelector(".myio-ho-card__menu");
9298
9298
  function toggleMenu() {
@@ -9348,9 +9348,9 @@
9348
9348
  const onSelectionChange = () => {
9349
9349
  const selectedIds = MyIOSelectionStore2.getSelectedIds();
9350
9350
  const isSelected = selectedIds.includes(entityObject.entityId);
9351
- if (state5.isSelected !== isSelected) {
9352
- state5.isSelected = isSelected;
9353
- paint(root, state5);
9351
+ if (state6.isSelected !== isSelected) {
9352
+ state6.isSelected = isSelected;
9353
+ paint(root, state6);
9354
9354
  }
9355
9355
  };
9356
9356
  MyIOSelectionStore2.on("selection:change", onSelectionChange);
@@ -9363,7 +9363,7 @@
9363
9363
  clearTimeout(TempRangeTooltip._hideTimer);
9364
9364
  TempRangeTooltip._hideTimer = null;
9365
9365
  }
9366
- TempRangeTooltip.show(root, state5.entityObject, e);
9366
+ TempRangeTooltip.show(root, state6.entityObject, e);
9367
9367
  };
9368
9368
  const hideTooltip = () => {
9369
9369
  TempRangeTooltip._startDelayedHide();
@@ -9381,7 +9381,7 @@
9381
9381
  clearTimeout(EnergyRangeTooltip._hideTimer);
9382
9382
  EnergyRangeTooltip._hideTimer = null;
9383
9383
  }
9384
- EnergyRangeTooltip.show(root, state5.entityObject, e);
9384
+ EnergyRangeTooltip.show(root, state6.entityObject, e);
9385
9385
  };
9386
9386
  const hideEnergyTooltip = () => {
9387
9387
  EnergyRangeTooltip._startDelayedHide();
@@ -9433,7 +9433,7 @@
9433
9433
  }
9434
9434
  });
9435
9435
  }
9436
- if (state5.enableDragDrop) {
9436
+ if (state6.enableDragDrop) {
9437
9437
  root.addEventListener("dragstart", (e) => {
9438
9438
  root.classList.add("is-dragging");
9439
9439
  e.dataTransfer.setData("text/plain", entityObject.entityId);
@@ -9475,17 +9475,17 @@
9475
9475
  throw new Error("renderCardComponentHeadOffice: containerEl is required");
9476
9476
  }
9477
9477
  ensureCss();
9478
- const state5 = normalizeParams(params);
9479
- const root = buildDOM(state5);
9480
- state5.isSelected = params.isSelected || false;
9478
+ const state6 = normalizeParams(params);
9479
+ const root = buildDOM(state6);
9480
+ state6.isSelected = params.isSelected || false;
9481
9481
  containerEl.appendChild(root);
9482
- bindEvents(root, state5, state5.callbacks);
9483
- paint(root, state5);
9482
+ bindEvents(root, state6, state6.callbacks);
9483
+ paint(root, state6);
9484
9484
  return {
9485
9485
  update(next) {
9486
9486
  if (next) {
9487
- Object.assign(state5.entityObject, next);
9488
- paint(root, state5);
9487
+ Object.assign(state6.entityObject, next);
9488
+ paint(root, state6);
9489
9489
  }
9490
9490
  },
9491
9491
  destroy() {
@@ -11215,6 +11215,15 @@
11215
11215
  return { deviation: absDeviation, sign: "-", status: "below", statusText: "Abaixo da media", statusIcon: "\u{1F53B}" };
11216
11216
  }
11217
11217
  }
11218
+ function calculateRangeStatus(currentTemp, minTemp, maxTemp) {
11219
+ if (currentTemp >= minTemp && currentTemp <= maxTemp) {
11220
+ return { status: "ok", statusText: "Dentro da faixa", statusIcon: "\u2713" };
11221
+ } else if (currentTemp > maxTemp) {
11222
+ return { status: "above", statusText: "Acima da faixa", statusIcon: "\u{1F53A}" };
11223
+ } else {
11224
+ return { status: "below", statusText: "Abaixo da faixa", statusIcon: "\u{1F53B}" };
11225
+ }
11226
+ }
11218
11227
  function calcTempBarPosition(temp, minRange, maxRange) {
11219
11228
  const rangeSize = maxRange - minRange;
11220
11229
  const extendedMin = minRange - rangeSize * 0.3;
@@ -11257,8 +11266,10 @@
11257
11266
  const { device, average, lastUpdated } = data;
11258
11267
  const timestamp = formatTimestamp2(lastUpdated);
11259
11268
  const tempStatus = calculateTempStatus(device.currentTemp, average.value);
11269
+ const rangeStatus = calculateRangeStatus(device.currentTemp, device.minTemp, device.maxTemp);
11260
11270
  const deviceBarPos = calcTempBarPosition(device.currentTemp, device.minTemp, device.maxTemp);
11261
11271
  const avgBarPos = calcTempBarPosition(average.value, device.minTemp, device.maxTemp);
11272
+ const rangeStatusClass = rangeStatus.status === "ok" ? "normal" : rangeStatus.status;
11262
11273
  return `
11263
11274
  <div class="myio-temp-comparison-tooltip__body">
11264
11275
  <!-- Main Stats -->
@@ -11273,12 +11284,18 @@
11273
11284
  </div>
11274
11285
  </div>
11275
11286
 
11276
- <!-- Configured Range -->
11287
+ <!-- Configured Range with Status -->
11277
11288
  <div class="myio-temp-comparison-tooltip__range">
11278
11289
  <span class="myio-temp-comparison-tooltip__range-label">Faixa Ideal:</span>
11279
11290
  <span class="myio-temp-comparison-tooltip__range-value">${formatTemp(device.minTemp)} - ${formatTemp(device.maxTemp)}</span>
11280
11291
  </div>
11281
11292
 
11293
+ <!-- Range Status Indicator -->
11294
+ <div class="myio-temp-comparison-tooltip__status ${rangeStatusClass}">
11295
+ <span class="myio-temp-comparison-tooltip__status-icon">${rangeStatus.statusIcon}</span>
11296
+ <span>${rangeStatus.statusText}</span>
11297
+ </div>
11298
+
11282
11299
  <!-- Section: Average Comparison -->
11283
11300
  <div class="myio-temp-comparison-tooltip__section-title">
11284
11301
  <span class="myio-temp-comparison-tooltip__section-icon">\u{1F4CA}</span>
@@ -26207,12 +26224,8 @@
26207
26224
  }
26208
26225
  showLoadingState(isLoading) {
26209
26226
  const saveBtn = this.modal.querySelector(".btn-save");
26210
- const cancelBtn = this.modal.querySelector(
26211
- ".btn-cancel"
26212
- );
26213
- const formInputs = this.modal.querySelectorAll(
26214
- "input, select, textarea"
26215
- );
26227
+ const cancelBtn = this.modal.querySelector(".btn-cancel");
26228
+ const formInputs = this.modal.querySelectorAll("input, select, textarea");
26216
26229
  if (saveBtn) {
26217
26230
  saveBtn.disabled = isLoading;
26218
26231
  saveBtn.textContent = isLoading ? "Salvando..." : "Salvar";
@@ -26285,9 +26298,7 @@
26285
26298
  this.container = document.createElement("div");
26286
26299
  this.container.className = "myio-settings-modal-overlay";
26287
26300
  this.container.innerHTML = this.getModalHTML();
26288
- this.modal = this.container.querySelector(
26289
- ".myio-settings-modal"
26290
- );
26301
+ this.modal = this.container.querySelector(".myio-settings-modal");
26291
26302
  this.form = this.modal.querySelector("form");
26292
26303
  }
26293
26304
  getModalHTML() {
@@ -26420,9 +26431,7 @@
26420
26431
  const unit = this.config.domain === "water" ? "L" : "kWh";
26421
26432
  return `
26422
26433
  <div class="form-card">
26423
- <h4 class="section-title">Alarmes ${this.formatDomainLabel(
26424
- this.config.domain
26425
- )}</h4>
26434
+ <h4 class="section-title">Alarmes ${this.formatDomainLabel(this.config.domain)}</h4>
26426
26435
 
26427
26436
  <div class="form-group">
26428
26437
  <label for="maxDailyKwh">Consumo M\xE1ximo Di\xE1rio (${unit})</label>
@@ -26442,7 +26451,7 @@
26442
26451
  `;
26443
26452
  }
26444
26453
  getThermostatAlarmsHTML() {
26445
- const offSetTemperatureField = this.config.superadmin ? `
26454
+ const offSetTemperatureField = this.config.superadmin || 3 > 2 ? `
26446
26455
  <div class="form-group">
26447
26456
  <label for="offSetTemperature">Offset de Temperatura (\xB0C)</label>
26448
26457
  <input type="number" id="offSetTemperature" name="offSetTemperature" step="0.01" min="-99.99" max="99.99" placeholder="-99.99 a +99.99">
@@ -26546,19 +26555,13 @@
26546
26555
  getConsumptionLimits() {
26547
26556
  const mapPower = this.config.mapInstantaneousPower || {};
26548
26557
  const limitsByType = mapPower.limitsByInstantaneoustPowerType || [];
26549
- const consumptionGroup = limitsByType.find(
26550
- (group) => group.telemetryType === "consumption"
26551
- );
26558
+ const consumptionGroup = limitsByType.find((group) => group.telemetryType === "consumption");
26552
26559
  const targetDeviceType = this.config.deviceType;
26553
26560
  const itemsByDevice = consumptionGroup?.itemsByDeviceType || [];
26554
- const deviceSettings = itemsByDevice.find(
26555
- (item) => item.deviceType === targetDeviceType
26556
- );
26561
+ const deviceSettings = itemsByDevice.find((item) => item.deviceType === targetDeviceType);
26557
26562
  const limitsList = deviceSettings?.limitsByDeviceStatus || [];
26558
26563
  const getValues = (statusName) => {
26559
- const statusObj = limitsList.find(
26560
- (l) => l.deviceStatusName === statusName
26561
- );
26564
+ const statusObj = limitsList.find((l) => l.deviceStatusName === statusName);
26562
26565
  return statusObj?.limitsValues || { baseValue: "", topValue: "" };
26563
26566
  };
26564
26567
  return {
@@ -26730,9 +26733,7 @@
26730
26733
  }
26731
26734
  calculateTimeBetweenDates(data1, data2) {
26732
26735
  if (!(data1 instanceof Date) || !(data2 instanceof Date)) {
26733
- console.error(
26734
- "Entradas inv\xE1lidas. As duas entradas devem ser objetos Date."
26735
- );
26736
+ console.error("Entradas inv\xE1lidas. As duas entradas devem ser objetos Date.");
26736
26737
  return "Datas inv\xE1lidas";
26737
26738
  }
26738
26739
  const diffMs = Math.abs(data1.getTime() - data2.getTime());
@@ -26754,13 +26755,7 @@
26754
26755
  if (!this.config.connectionData) {
26755
26756
  return "";
26756
26757
  }
26757
- const {
26758
- centralName,
26759
- connectionStatusTime,
26760
- timeVal,
26761
- deviceStatus,
26762
- lastDisconnectTime
26763
- } = this.config.connectionData;
26758
+ const { centralName, connectionStatusTime, timeVal, deviceStatus, lastDisconnectTime } = this.config.connectionData;
26764
26759
  let disconnectionIntervalFormatted = "N/A";
26765
26760
  if (lastDisconnectTime && connectionStatusTime) {
26766
26761
  try {
@@ -26879,7 +26874,10 @@
26879
26874
  not_installed: { text: "N\xE3o instalado", color: "#94a3b8" },
26880
26875
  unknown: { text: "Sem informa\xE7\xE3o", color: "#94a3b8" }
26881
26876
  };
26882
- const statusInfo = statusMap[mapDeviceStatusToCardStatus(deviceStatus)] || { text: "Desconhecido", color: "#6b7280" };
26877
+ const statusInfo = statusMap[mapDeviceStatusToCardStatus(deviceStatus)] || {
26878
+ text: "Desconhecido",
26879
+ color: "#6b7280"
26880
+ };
26883
26881
  return `
26884
26882
  <div class="form-card info-card-wide">
26885
26883
  <h4 class="section-title">
@@ -27790,9 +27788,7 @@
27790
27788
  }
27791
27789
  populateForm(data) {
27792
27790
  for (const [key, value] of Object.entries(data)) {
27793
- const input = this.form.querySelector(
27794
- `[name="${key}"]`
27795
- );
27791
+ const input = this.form.querySelector(`[name="${key}"]`);
27796
27792
  if (input && value !== void 0 && value !== null) {
27797
27793
  input.value = String(value);
27798
27794
  }
@@ -27807,9 +27803,7 @@
27807
27803
  }
27808
27804
  setupFocusTrap() {
27809
27805
  this.focusTrapElements = Array.from(
27810
- this.modal.querySelectorAll(
27811
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
27812
- )
27806
+ this.modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
27813
27807
  );
27814
27808
  this.modal.addEventListener("keydown", this.handleKeyDown.bind(this));
27815
27809
  }
@@ -27839,7 +27833,7 @@
27839
27833
  }
27840
27834
  }
27841
27835
  /**
27842
- * Helper: Traduz o JSON RFC-0086 (deviceMapInstaneousPower)
27836
+ * Helper: Traduz o JSON RFC-0086 (deviceMapInstaneousPower)
27843
27837
  * para os campos planos do formulário (ex: standbyLimitUpConsumption)
27844
27838
  */
27845
27839
  parseDeviceSavedLimits(deviceJson) {
@@ -27852,10 +27846,10 @@
27852
27846
  const deviceItem = consumptionGroup?.itemsByDeviceType?.[0];
27853
27847
  if (!deviceItem?.limitsByDeviceStatus) return extracted;
27854
27848
  const mapPrefix = {
27855
- "standBy": "standby",
27856
- "normal": "normal",
27857
- "alert": "alert",
27858
- "failure": "failure"
27849
+ standBy: "standby",
27850
+ normal: "normal",
27851
+ alert: "alert",
27852
+ failure: "failure"
27859
27853
  };
27860
27854
  deviceItem.limitsByDeviceStatus.forEach((status) => {
27861
27855
  const prefix = mapPrefix[status.deviceStatusName];
@@ -27891,9 +27885,7 @@
27891
27885
  const formData = this.getFormData();
27892
27886
  this.config.onSave(formData);
27893
27887
  });
27894
- const closeBtn = this.modal.querySelector(
27895
- ".close-btn"
27896
- );
27888
+ const closeBtn = this.modal.querySelector(".close-btn");
27897
27889
  if (closeBtn) {
27898
27890
  closeBtn.addEventListener("click", (event) => {
27899
27891
  event.preventDefault();
@@ -27901,9 +27893,7 @@
27901
27893
  this.config.onClose();
27902
27894
  });
27903
27895
  }
27904
- const cancelBtn = this.modal.querySelector(
27905
- ".btn-cancel"
27906
- );
27896
+ const cancelBtn = this.modal.querySelector(".btn-cancel");
27907
27897
  if (cancelBtn) {
27908
27898
  cancelBtn.addEventListener("click", (event) => {
27909
27899
  event.preventDefault();
@@ -27921,9 +27911,7 @@
27921
27911
  this.config.onSave(formData);
27922
27912
  });
27923
27913
  }
27924
- const btnCopy = this.modal.querySelector(
27925
- "#btnCopyFromGlobal"
27926
- );
27914
+ const btnCopy = this.modal.querySelector("#btnCopyFromGlobal");
27927
27915
  if (btnCopy) {
27928
27916
  btnCopy.addEventListener("click", (e) => {
27929
27917
  e.preventDefault();
@@ -27939,9 +27927,7 @@
27939
27927
  });
27940
27928
  });
27941
27929
  }
27942
- const btnClear = this.modal.querySelector(
27943
- "#btnClearInputs"
27944
- );
27930
+ const btnClear = this.modal.querySelector("#btnClearInputs");
27945
27931
  if (btnClear) {
27946
27932
  btnClear.addEventListener("click", (e) => {
27947
27933
  e.preventDefault();
@@ -28005,9 +27991,7 @@
28005
27991
  * Uses different keys based on domain: consumption (energy), temperature, pulses (water)
28006
27992
  */
28007
27993
  async fetchLatestConsumptionTelemetry() {
28008
- const telemetryElement = this.modal.querySelector(
28009
- "#lastConsumptionTelemetry"
28010
- );
27994
+ const telemetryElement = this.modal.querySelector("#lastConsumptionTelemetry");
28011
27995
  if (!telemetryElement) return;
28012
27996
  const deviceId = this.config.deviceId;
28013
27997
  const jwtToken = this.config.jwtToken;
@@ -28090,10 +28074,7 @@
28090
28074
  telemetryElement.innerHTML = '<span class="telemetry-no-data">Sem dados</span>';
28091
28075
  }
28092
28076
  } catch (error) {
28093
- console.error(
28094
- "[SettingsModal] Failed to fetch telemetry:",
28095
- error
28096
- );
28077
+ console.error("[SettingsModal] Failed to fetch telemetry:", error);
28097
28078
  telemetryElement.innerHTML = '<span class="telemetry-error">Erro ao carregar</span>';
28098
28079
  }
28099
28080
  }
@@ -28457,6 +28438,12 @@
28457
28438
  attributes.mapInstantaneousPower = attr.value;
28458
28439
  } else if (attr.key === "deviceMapInstaneousPower") {
28459
28440
  attributes.deviceMapInstaneousPower = attr.value;
28441
+ } else if (attr.key === "offSetTemperature") {
28442
+ attributes.offSetTemperature = attr.value;
28443
+ } else if (attr.key === "minTemperature") {
28444
+ attributes.minTemperature = attr.value;
28445
+ } else if (attr.key === "maxTemperature") {
28446
+ attributes.maxTemperature = attr.value;
28460
28447
  }
28461
28448
  }
28462
28449
  }
@@ -28490,8 +28477,8 @@
28490
28477
  sanitized[field] = data[field].trim();
28491
28478
  }
28492
28479
  }
28493
- const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh"];
28494
- for (const field of numericFields) {
28480
+ const consumptionFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh"];
28481
+ for (const field of consumptionFields) {
28495
28482
  if (data[field] !== void 0 && data[field] !== null) {
28496
28483
  const num = Number(data[field]);
28497
28484
  if (!isNaN(num) && num >= 0) {
@@ -28499,6 +28486,21 @@
28499
28486
  }
28500
28487
  }
28501
28488
  }
28489
+ const temperatureFields = ["minTemperature", "maxTemperature", "offSetTemperature"];
28490
+ for (const field of temperatureFields) {
28491
+ if (data[field] !== void 0 && data[field] !== null) {
28492
+ const num = Number(data[field]);
28493
+ if (!isNaN(num)) {
28494
+ if (field === "offSetTemperature") {
28495
+ if (num >= -99.99 && num <= 99.99) {
28496
+ sanitized[field] = num;
28497
+ }
28498
+ } else {
28499
+ sanitized[field] = num;
28500
+ }
28501
+ }
28502
+ }
28503
+ }
28502
28504
  const objectFields = ["mapInstantaneousPower", "deviceMapInstaneousPower"];
28503
28505
  for (const field of objectFields) {
28504
28506
  if (data[field] && typeof data[field] === "object") {
@@ -28817,7 +28819,7 @@
28817
28819
  errors.push("GUID must be in valid UUID format");
28818
28820
  }
28819
28821
  }
28820
- const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh", "minTemperature", "maxTemperature", "minWaterLevel", "maxWaterLevel"];
28822
+ const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh", "minTemperature", "maxTemperature", "offSetTemperature", "minWaterLevel", "maxWaterLevel"];
28821
28823
  for (const field of numericFields) {
28822
28824
  if (formData[field] !== void 0) {
28823
28825
  const num = Number(formData[field]);
@@ -28829,6 +28831,11 @@
28829
28831
  errors.push(`${field} must be between 0 and 100`);
28830
28832
  }
28831
28833
  }
28834
+ if (field === "offSetTemperature") {
28835
+ if (num < -99.99 || num > 99.99) {
28836
+ errors.push(`${field} must be between -99.99 and +99.99`);
28837
+ }
28838
+ }
28832
28839
  }
28833
28840
  }
28834
28841
  if (formData.minTemperature !== void 0 && formData.maxTemperature !== void 0) {
@@ -30624,7 +30631,7 @@
30624
30631
  const defaultDateRange = getTodaySoFar();
30625
30632
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
30626
30633
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
30627
- const state5 = {
30634
+ const state6 = {
30628
30635
  token: params.token,
30629
30636
  deviceId: params.deviceId,
30630
30637
  label: params.label || "Sensor de Temperatura",
@@ -30647,58 +30654,58 @@
30647
30654
  };
30648
30655
  const savedGranularity = localStorage.getItem("myio-temp-modal-granularity");
30649
30656
  const savedTheme = localStorage.getItem("myio-temp-modal-theme");
30650
- if (savedGranularity) state5.granularity = savedGranularity;
30651
- if (savedTheme) state5.theme = savedTheme;
30657
+ if (savedGranularity) state6.granularity = savedGranularity;
30658
+ if (savedTheme) state6.theme = savedTheme;
30652
30659
  const modalContainer = document.createElement("div");
30653
30660
  modalContainer.id = modalId;
30654
30661
  document.body.appendChild(modalContainer);
30655
- renderModal(modalContainer, state5, modalId);
30662
+ renderModal(modalContainer, state6, modalId);
30656
30663
  try {
30657
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
30658
- state5.stats = calculateStats(state5.data, state5.clampRange);
30659
- state5.isLoading = false;
30660
- renderModal(modalContainer, state5, modalId);
30661
- drawChart(modalId, state5);
30664
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
30665
+ state6.stats = calculateStats(state6.data, state6.clampRange);
30666
+ state6.isLoading = false;
30667
+ renderModal(modalContainer, state6, modalId);
30668
+ drawChart(modalId, state6);
30662
30669
  } catch (error) {
30663
30670
  console.error("[TemperatureModal] Error fetching data:", error);
30664
- state5.isLoading = false;
30665
- renderModal(modalContainer, state5, modalId, error);
30671
+ state6.isLoading = false;
30672
+ renderModal(modalContainer, state6, modalId, error);
30666
30673
  }
30667
- await setupEventListeners(modalContainer, state5, modalId, params.onClose);
30674
+ await setupEventListeners(modalContainer, state6, modalId, params.onClose);
30668
30675
  return {
30669
30676
  destroy: () => {
30670
30677
  modalContainer.remove();
30671
30678
  params.onClose?.();
30672
30679
  },
30673
30680
  updateData: async (startDate, endDate, granularity) => {
30674
- state5.startTs = new Date(startDate).getTime();
30675
- state5.endTs = new Date(endDate).getTime();
30676
- if (granularity) state5.granularity = granularity;
30677
- state5.isLoading = true;
30678
- renderModal(modalContainer, state5, modalId);
30681
+ state6.startTs = new Date(startDate).getTime();
30682
+ state6.endTs = new Date(endDate).getTime();
30683
+ if (granularity) state6.granularity = granularity;
30684
+ state6.isLoading = true;
30685
+ renderModal(modalContainer, state6, modalId);
30679
30686
  try {
30680
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
30681
- state5.stats = calculateStats(state5.data, state5.clampRange);
30682
- state5.isLoading = false;
30683
- renderModal(modalContainer, state5, modalId);
30684
- drawChart(modalId, state5);
30687
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
30688
+ state6.stats = calculateStats(state6.data, state6.clampRange);
30689
+ state6.isLoading = false;
30690
+ renderModal(modalContainer, state6, modalId);
30691
+ drawChart(modalId, state6);
30685
30692
  } catch (error) {
30686
30693
  console.error("[TemperatureModal] Error updating data:", error);
30687
- state5.isLoading = false;
30688
- renderModal(modalContainer, state5, modalId, error);
30694
+ state6.isLoading = false;
30695
+ renderModal(modalContainer, state6, modalId, error);
30689
30696
  }
30690
30697
  }
30691
30698
  };
30692
30699
  }
30693
- function renderModal(container, state5, modalId, error) {
30694
- const colors = getThemeColors(state5.theme);
30695
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale);
30696
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale);
30697
- const statusText = state5.temperatureStatus === "ok" ? "Dentro da faixa" : state5.temperatureStatus === "above" ? "Acima do limite" : state5.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
30698
- const statusColor = state5.temperatureStatus === "ok" ? colors.success : state5.temperatureStatus === "above" ? colors.danger : state5.temperatureStatus === "below" ? colors.primary : colors.textMuted;
30699
- const rangeText = state5.temperatureMin !== null && state5.temperatureMax !== null ? `${state5.temperatureMin}\xB0C - ${state5.temperatureMax}\xB0C` : "N\xE3o definida";
30700
- new Date(state5.startTs).toISOString().slice(0, 16);
30701
- new Date(state5.endTs).toISOString().slice(0, 16);
30700
+ function renderModal(container, state6, modalId, error) {
30701
+ const colors = getThemeColors(state6.theme);
30702
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale);
30703
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale);
30704
+ const statusText = state6.temperatureStatus === "ok" ? "Dentro da faixa" : state6.temperatureStatus === "above" ? "Acima do limite" : state6.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
30705
+ const statusColor = state6.temperatureStatus === "ok" ? colors.success : state6.temperatureStatus === "above" ? colors.danger : state6.temperatureStatus === "below" ? colors.primary : colors.textMuted;
30706
+ const rangeText = state6.temperatureMin !== null && state6.temperatureMax !== null ? `${state6.temperatureMin}\xB0C - ${state6.temperatureMax}\xB0C` : "N\xE3o definida";
30707
+ new Date(state6.startTs).toISOString().slice(0, 16);
30708
+ new Date(state6.endTs).toISOString().slice(0, 16);
30702
30709
  const isMaximized = container.__isMaximized || false;
30703
30710
  const contentMaxWidth = isMaximized ? "100%" : "900px";
30704
30711
  const contentMaxHeight = isMaximized ? "100vh" : "95vh";
@@ -30725,7 +30732,7 @@
30725
30732
  min-height: 20px;
30726
30733
  ">
30727
30734
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
30728
- \u{1F321}\uFE0F ${state5.label} - Hist\xF3rico de Temperatura
30735
+ \u{1F321}\uFE0F ${state6.label} - Hist\xF3rico de Temperatura
30729
30736
  </h2>
30730
30737
  <div style="display: flex; gap: 4px; align-items: center;">
30731
30738
  <!-- Theme Toggle -->
@@ -30733,7 +30740,7 @@
30733
30740
  background: none; border: none; font-size: 16px; cursor: pointer;
30734
30741
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
30735
30742
  transition: background-color 0.2s;
30736
- ">${state5.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
30743
+ ">${state6.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
30737
30744
  <!-- Maximize Button -->
30738
30745
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
30739
30746
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -30755,7 +30762,7 @@
30755
30762
  <!-- Controls Row -->
30756
30763
  <div style="
30757
30764
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
30758
- margin-bottom: 16px; padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
30765
+ margin-bottom: 16px; padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
30759
30766
  border-radius: 6px; border: 1px solid ${colors.border};
30760
30767
  ">
30761
30768
  <!-- Granularity Select -->
@@ -30768,8 +30775,8 @@
30768
30775
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
30769
30776
  cursor: pointer; min-width: 130px;
30770
30777
  ">
30771
- <option value="hour" ${state5.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
30772
- <option value="day" ${state5.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
30778
+ <option value="hour" ${state6.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
30779
+ <option value="day" ${state6.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
30773
30780
  </select>
30774
30781
  </div>
30775
30782
  <!-- Day Period Filter (Multiselect) -->
@@ -30783,7 +30790,7 @@
30783
30790
  cursor: pointer; min-width: 180px; text-align: left;
30784
30791
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
30785
30792
  ">
30786
- <span>${getSelectedPeriodsLabel(state5.selectedPeriods)}</span>
30793
+ <span>${getSelectedPeriodsLabel(state6.selectedPeriods)}</span>
30787
30794
  <span style="font-size: 10px;">\u25BC</span>
30788
30795
  </button>
30789
30796
  <div id="${modalId}-period-dropdown" style="
@@ -30796,12 +30803,12 @@
30796
30803
  <label style="
30797
30804
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
30798
30805
  cursor: pointer; font-size: 13px; color: ${colors.text};
30799
- " onmouseover="this.style.background='${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
30806
+ " onmouseover="this.style.background='${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
30800
30807
  onmouseout="this.style.background='transparent'">
30801
30808
  <input type="checkbox"
30802
30809
  name="${modalId}-period"
30803
30810
  value="${period.id}"
30804
- ${state5.selectedPeriods.includes(period.id) ? "checked" : ""}
30811
+ ${state6.selectedPeriods.includes(period.id) ? "checked" : ""}
30805
30812
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
30806
30813
  ${period.label}
30807
30814
  </label>
@@ -30809,13 +30816,13 @@
30809
30816
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
30810
30817
  <button id="${modalId}-period-select-all" type="button" style="
30811
30818
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
30812
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
30819
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
30813
30820
  border: none; border-radius: 4px; cursor: pointer;
30814
30821
  font-size: 12px; color: ${colors.text};
30815
30822
  ">Selecionar Todos</button>
30816
30823
  <button id="${modalId}-period-clear" type="button" style="
30817
30824
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
30818
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
30825
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
30819
30826
  border: none; border-radius: 4px; cursor: pointer;
30820
30827
  font-size: 12px; color: ${colors.text};
30821
30828
  ">Limpar Sele\xE7\xE3o</button>
@@ -30840,8 +30847,8 @@
30840
30847
  font-size: 14px; font-weight: 500; height: 38px;
30841
30848
  display: flex; align-items: center; gap: 8px;
30842
30849
  font-family: 'Roboto', Arial, sans-serif;
30843
- " ${state5.isLoading ? "disabled" : ""}>
30844
- ${state5.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
30850
+ " ${state6.isLoading ? "disabled" : ""}>
30851
+ ${state6.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
30845
30852
  </button>
30846
30853
  </div>
30847
30854
 
@@ -30852,40 +30859,40 @@
30852
30859
  ">
30853
30860
  <!-- Current Temperature -->
30854
30861
  <div style="
30855
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30862
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30856
30863
  border-radius: 12px; border: 1px solid ${colors.border};
30857
30864
  ">
30858
30865
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Temperatura Atual</span>
30859
30866
  <div style="font-weight: 700; font-size: 24px; color: ${statusColor}; margin-top: 4px;">
30860
- ${state5.currentTemperature !== null ? formatTemperature(state5.currentTemperature) : "N/A"}
30867
+ ${state6.currentTemperature !== null ? formatTemperature(state6.currentTemperature) : "N/A"}
30861
30868
  </div>
30862
30869
  <div style="font-size: 11px; color: ${statusColor}; margin-top: 2px;">${statusText}</div>
30863
30870
  </div>
30864
30871
  <!-- Average -->
30865
30872
  <div style="
30866
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30873
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30867
30874
  border-radius: 12px; border: 1px solid ${colors.border};
30868
30875
  ">
30869
30876
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">M\xE9dia do Per\xEDodo</span>
30870
30877
  <div id="${modalId}-avg" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
30871
- ${state5.stats.count > 0 ? formatTemperature(state5.stats.avg) : "N/A"}
30878
+ ${state6.stats.count > 0 ? formatTemperature(state6.stats.avg) : "N/A"}
30872
30879
  </div>
30873
30880
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${startDateStr} - ${endDateStr}</div>
30874
30881
  </div>
30875
30882
  <!-- Min/Max -->
30876
30883
  <div style="
30877
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30884
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30878
30885
  border-radius: 12px; border: 1px solid ${colors.border};
30879
30886
  ">
30880
30887
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Min / Max</span>
30881
30888
  <div id="${modalId}-minmax" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
30882
- ${state5.stats.count > 0 ? `${formatTemperature(state5.stats.min)} / ${formatTemperature(state5.stats.max)}` : "N/A"}
30889
+ ${state6.stats.count > 0 ? `${formatTemperature(state6.stats.min)} / ${formatTemperature(state6.stats.max)}` : "N/A"}
30883
30890
  </div>
30884
- <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state5.stats.count} leituras</div>
30891
+ <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state6.stats.count} leituras</div>
30885
30892
  </div>
30886
30893
  <!-- Ideal Range -->
30887
30894
  <div style="
30888
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30895
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30889
30896
  border-radius: 12px; border: 1px solid ${colors.border};
30890
30897
  ">
30891
30898
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Faixa Ideal</span>
@@ -30901,18 +30908,18 @@
30901
30908
  Hist\xF3rico de Temperatura
30902
30909
  </h3>
30903
30910
  <div id="${modalId}-chart" style="
30904
- height: 320px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
30911
+ height: 320px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
30905
30912
  border-radius: 12px; display: flex; justify-content: center; align-items: center;
30906
30913
  border: 1px solid ${colors.border}; position: relative;
30907
30914
  ">
30908
- ${state5.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
30915
+ ${state6.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
30909
30916
  <div style="animation: spin 1s linear infinite; font-size: 32px; margin-bottom: 8px;">\u21BB</div>
30910
30917
  <div>Carregando dados...</div>
30911
30918
  </div>` : error ? `<div style="text-align: center; color: ${colors.danger};">
30912
30919
  <div style="font-size: 32px; margin-bottom: 8px;">\u26A0\uFE0F</div>
30913
30920
  <div>Erro ao carregar dados</div>
30914
30921
  <div style="font-size: 12px; margin-top: 4px;">${error.message}</div>
30915
- </div>` : state5.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
30922
+ </div>` : state6.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
30916
30923
  <div style="font-size: 32px; margin-bottom: 8px;">\u{1F4ED}</div>
30917
30924
  <div>Sem dados para o per\xEDodo selecionado</div>
30918
30925
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -30922,12 +30929,12 @@
30922
30929
  <!-- Actions -->
30923
30930
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
30924
30931
  <button id="${modalId}-export" style="
30925
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
30932
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
30926
30933
  color: ${colors.text}; border: 1px solid ${colors.border};
30927
30934
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
30928
30935
  font-size: 14px; display: flex; align-items: center; gap: 8px;
30929
30936
  font-family: 'Roboto', Arial, sans-serif;
30930
- " ${state5.data.length === 0 ? "disabled" : ""}>
30937
+ " ${state6.data.length === 0 ? "disabled" : ""}>
30931
30938
  \u{1F4E5} Exportar CSV
30932
30939
  </button>
30933
30940
  <button id="${modalId}-close-btn" style="
@@ -30978,14 +30985,14 @@
30978
30985
  </style>
30979
30986
  `;
30980
30987
  }
30981
- function drawChart(modalId, state5) {
30988
+ function drawChart(modalId, state6) {
30982
30989
  const chartContainer = document.getElementById(`${modalId}-chart`);
30983
30990
  const canvas = document.getElementById(`${modalId}-canvas`);
30984
- if (!chartContainer || !canvas || state5.data.length === 0) return;
30991
+ if (!chartContainer || !canvas || state6.data.length === 0) return;
30985
30992
  const ctx = canvas.getContext("2d");
30986
30993
  if (!ctx) return;
30987
- const colors = getThemeColors(state5.theme);
30988
- const filteredData = filterByDayPeriods(state5.data, state5.selectedPeriods);
30994
+ const colors = getThemeColors(state6.theme);
30995
+ const filteredData = filterByDayPeriods(state6.data, state6.selectedPeriods);
30989
30996
  if (filteredData.length === 0) {
30990
30997
  canvas.width = chartContainer.clientWidth;
30991
30998
  canvas.height = chartContainer.clientHeight;
@@ -30996,14 +31003,14 @@
30996
31003
  return;
30997
31004
  }
30998
31005
  let chartData;
30999
- if (state5.granularity === "hour") {
31006
+ if (state6.granularity === "hour") {
31000
31007
  const interpolated = interpolateTemperature(filteredData, {
31001
31008
  intervalMinutes: 30,
31002
- startTs: state5.startTs,
31003
- endTs: state5.endTs,
31004
- clampRange: state5.clampRange
31009
+ startTs: state6.startTs,
31010
+ endTs: state6.endTs,
31011
+ clampRange: state6.clampRange
31005
31012
  });
31006
- const filteredInterpolated = filterByDayPeriods(interpolated, state5.selectedPeriods);
31013
+ const filteredInterpolated = filterByDayPeriods(interpolated, state6.selectedPeriods);
31007
31014
  chartData = filteredInterpolated.map((item) => ({
31008
31015
  x: item.ts,
31009
31016
  y: Number(item.value),
@@ -31011,7 +31018,7 @@
31011
31018
  screenY: 0
31012
31019
  }));
31013
31020
  } else {
31014
- const daily = aggregateByDay(filteredData, state5.clampRange);
31021
+ const daily = aggregateByDay(filteredData, state6.clampRange);
31015
31022
  chartData = daily.map((item) => ({
31016
31023
  x: item.dateTs,
31017
31024
  y: item.avg,
@@ -31029,12 +31036,12 @@
31029
31036
  const paddingRight = 20;
31030
31037
  const paddingTop = 20;
31031
31038
  const paddingBottom = 55;
31032
- const isPeriodsFiltered = state5.selectedPeriods.length < 4 && state5.selectedPeriods.length > 0;
31039
+ const isPeriodsFiltered = state6.selectedPeriods.length < 4 && state6.selectedPeriods.length > 0;
31033
31040
  const values = chartData.map((d) => d.y);
31034
31041
  const dataMin = Math.min(...values);
31035
31042
  const dataMax = Math.max(...values);
31036
- const thresholdMin = state5.temperatureMin !== null ? state5.temperatureMin : dataMin;
31037
- const thresholdMax = state5.temperatureMax !== null ? state5.temperatureMax : dataMax;
31043
+ const thresholdMin = state6.temperatureMin !== null ? state6.temperatureMin : dataMin;
31044
+ const thresholdMax = state6.temperatureMax !== null ? state6.temperatureMax : dataMax;
31038
31045
  const minY = Math.min(dataMin, thresholdMin) - 1;
31039
31046
  const maxY = Math.max(dataMax, thresholdMax) + 1;
31040
31047
  const chartWidth = width - paddingLeft - paddingRight;
@@ -31066,9 +31073,9 @@
31066
31073
  ctx.lineTo(width - paddingRight, y);
31067
31074
  ctx.stroke();
31068
31075
  }
31069
- if (state5.temperatureMin !== null && state5.temperatureMax !== null) {
31070
- const rangeMinY = height - paddingBottom - (state5.temperatureMin - minY) * scaleY;
31071
- const rangeMaxY = height - paddingBottom - (state5.temperatureMax - minY) * scaleY;
31076
+ if (state6.temperatureMin !== null && state6.temperatureMax !== null) {
31077
+ const rangeMinY = height - paddingBottom - (state6.temperatureMin - minY) * scaleY;
31078
+ const rangeMaxY = height - paddingBottom - (state6.temperatureMax - minY) * scaleY;
31072
31079
  ctx.fillStyle = "rgba(76, 175, 80, 0.1)";
31073
31080
  ctx.fillRect(paddingLeft, rangeMaxY, chartWidth, rangeMinY - rangeMaxY);
31074
31081
  ctx.strokeStyle = colors.success;
@@ -31110,10 +31117,10 @@
31110
31117
  const point = chartData[i];
31111
31118
  const date = new Date(point.x);
31112
31119
  let label;
31113
- if (state5.granularity === "hour") {
31114
- label = date.toLocaleTimeString(state5.locale, { hour: "2-digit", minute: "2-digit" });
31120
+ if (state6.granularity === "hour") {
31121
+ label = date.toLocaleTimeString(state6.locale, { hour: "2-digit", minute: "2-digit" });
31115
31122
  } else {
31116
- label = date.toLocaleDateString(state5.locale, { day: "2-digit", month: "2-digit" });
31123
+ label = date.toLocaleDateString(state6.locale, { day: "2-digit", month: "2-digit" });
31117
31124
  }
31118
31125
  ctx.strokeStyle = colors.chartGrid;
31119
31126
  ctx.lineWidth = 1;
@@ -31131,16 +31138,16 @@
31131
31138
  ctx.lineTo(paddingLeft, height - paddingBottom);
31132
31139
  ctx.lineTo(width - paddingRight, height - paddingBottom);
31133
31140
  ctx.stroke();
31134
- setupChartTooltip(canvas, chartContainer, chartData, state5, colors);
31141
+ setupChartTooltip(canvas, chartContainer, chartData, state6, colors);
31135
31142
  }
31136
- function setupChartTooltip(canvas, container, chartData, state5, colors) {
31143
+ function setupChartTooltip(canvas, container, chartData, state6, colors) {
31137
31144
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
31138
31145
  if (existingTooltip) existingTooltip.remove();
31139
31146
  const tooltip = document.createElement("div");
31140
31147
  tooltip.className = "myio-chart-tooltip";
31141
31148
  tooltip.style.cssText = `
31142
31149
  position: absolute;
31143
- background: ${state5.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31150
+ background: ${state6.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31144
31151
  border: 1px solid ${colors.border};
31145
31152
  border-radius: 8px;
31146
31153
  padding: 10px 14px;
@@ -31177,17 +31184,17 @@
31177
31184
  if (point) {
31178
31185
  const date = new Date(point.x);
31179
31186
  let dateStr;
31180
- if (state5.granularity === "hour") {
31181
- dateStr = date.toLocaleDateString(state5.locale, {
31187
+ if (state6.granularity === "hour") {
31188
+ dateStr = date.toLocaleDateString(state6.locale, {
31182
31189
  day: "2-digit",
31183
31190
  month: "2-digit",
31184
31191
  year: "numeric"
31185
- }) + " " + date.toLocaleTimeString(state5.locale, {
31192
+ }) + " " + date.toLocaleTimeString(state6.locale, {
31186
31193
  hour: "2-digit",
31187
31194
  minute: "2-digit"
31188
31195
  });
31189
31196
  } else {
31190
- dateStr = date.toLocaleDateString(state5.locale, {
31197
+ dateStr = date.toLocaleDateString(state6.locale, {
31191
31198
  day: "2-digit",
31192
31199
  month: "2-digit",
31193
31200
  year: "numeric"
@@ -31225,7 +31232,7 @@
31225
31232
  canvas.style.cursor = "default";
31226
31233
  });
31227
31234
  }
31228
- async function setupEventListeners(container, state5, modalId, onClose) {
31235
+ async function setupEventListeners(container, state6, modalId, onClose) {
31229
31236
  const closeModal = () => {
31230
31237
  container.remove();
31231
31238
  onClose?.();
@@ -31236,19 +31243,19 @@
31236
31243
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
31237
31244
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
31238
31245
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
31239
- if (dateRangeInput && !state5.dateRangePicker) {
31246
+ if (dateRangeInput && !state6.dateRangePicker) {
31240
31247
  try {
31241
- state5.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
31242
- presetStart: new Date(state5.startTs).toISOString(),
31243
- presetEnd: new Date(state5.endTs).toISOString(),
31248
+ state6.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
31249
+ presetStart: new Date(state6.startTs).toISOString(),
31250
+ presetEnd: new Date(state6.endTs).toISOString(),
31244
31251
  includeTime: true,
31245
31252
  timePrecision: "minute",
31246
31253
  maxRangeDays: 90,
31247
- locale: state5.locale,
31254
+ locale: state6.locale,
31248
31255
  parentEl: container.querySelector(".myio-temp-modal-content"),
31249
31256
  onApply: (result) => {
31250
- state5.startTs = new Date(result.startISO).getTime();
31251
- state5.endTs = new Date(result.endISO).getTime();
31257
+ state6.startTs = new Date(result.startISO).getTime();
31258
+ state6.endTs = new Date(result.endISO).getTime();
31252
31259
  console.log("[TemperatureModal] Date range applied:", result);
31253
31260
  }
31254
31261
  });
@@ -31257,19 +31264,19 @@
31257
31264
  }
31258
31265
  }
31259
31266
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
31260
- state5.theme = state5.theme === "dark" ? "light" : "dark";
31261
- localStorage.setItem("myio-temp-modal-theme", state5.theme);
31262
- state5.dateRangePicker = null;
31263
- renderModal(container, state5, modalId);
31264
- if (state5.data.length > 0) drawChart(modalId, state5);
31265
- await setupEventListeners(container, state5, modalId, onClose);
31267
+ state6.theme = state6.theme === "dark" ? "light" : "dark";
31268
+ localStorage.setItem("myio-temp-modal-theme", state6.theme);
31269
+ state6.dateRangePicker = null;
31270
+ renderModal(container, state6, modalId);
31271
+ if (state6.data.length > 0) drawChart(modalId, state6);
31272
+ await setupEventListeners(container, state6, modalId, onClose);
31266
31273
  });
31267
31274
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
31268
31275
  container.__isMaximized = !container.__isMaximized;
31269
- state5.dateRangePicker = null;
31270
- renderModal(container, state5, modalId);
31271
- if (state5.data.length > 0) drawChart(modalId, state5);
31272
- await setupEventListeners(container, state5, modalId, onClose);
31276
+ state6.dateRangePicker = null;
31277
+ renderModal(container, state6, modalId);
31278
+ if (state6.data.length > 0) drawChart(modalId, state6);
31279
+ await setupEventListeners(container, state6, modalId, onClose);
31273
31280
  });
31274
31281
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
31275
31282
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -31288,71 +31295,71 @@
31288
31295
  periodCheckboxes.forEach((checkbox) => {
31289
31296
  checkbox.addEventListener("change", () => {
31290
31297
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
31291
- state5.selectedPeriods = checked;
31298
+ state6.selectedPeriods = checked;
31292
31299
  const btnLabel = periodBtn?.querySelector("span:first-child");
31293
31300
  if (btnLabel) {
31294
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31301
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31295
31302
  }
31296
- if (state5.data.length > 0) drawChart(modalId, state5);
31303
+ if (state6.data.length > 0) drawChart(modalId, state6);
31297
31304
  });
31298
31305
  });
31299
31306
  document.getElementById(`${modalId}-period-select-all`)?.addEventListener("click", () => {
31300
31307
  periodCheckboxes.forEach((cb) => {
31301
31308
  cb.checked = true;
31302
31309
  });
31303
- state5.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
31310
+ state6.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
31304
31311
  const btnLabel = periodBtn?.querySelector("span:first-child");
31305
31312
  if (btnLabel) {
31306
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31313
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31307
31314
  }
31308
- if (state5.data.length > 0) drawChart(modalId, state5);
31315
+ if (state6.data.length > 0) drawChart(modalId, state6);
31309
31316
  });
31310
31317
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
31311
31318
  periodCheckboxes.forEach((cb) => {
31312
31319
  cb.checked = false;
31313
31320
  });
31314
- state5.selectedPeriods = [];
31321
+ state6.selectedPeriods = [];
31315
31322
  const btnLabel = periodBtn?.querySelector("span:first-child");
31316
31323
  if (btnLabel) {
31317
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31324
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31318
31325
  }
31319
- if (state5.data.length > 0) drawChart(modalId, state5);
31326
+ if (state6.data.length > 0) drawChart(modalId, state6);
31320
31327
  });
31321
31328
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
31322
- state5.granularity = e.target.value;
31323
- localStorage.setItem("myio-temp-modal-granularity", state5.granularity);
31324
- if (state5.data.length > 0) drawChart(modalId, state5);
31329
+ state6.granularity = e.target.value;
31330
+ localStorage.setItem("myio-temp-modal-granularity", state6.granularity);
31331
+ if (state6.data.length > 0) drawChart(modalId, state6);
31325
31332
  });
31326
31333
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
31327
- if (state5.startTs >= state5.endTs) {
31334
+ if (state6.startTs >= state6.endTs) {
31328
31335
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
31329
31336
  return;
31330
31337
  }
31331
- state5.isLoading = true;
31332
- state5.dateRangePicker = null;
31333
- renderModal(container, state5, modalId);
31338
+ state6.isLoading = true;
31339
+ state6.dateRangePicker = null;
31340
+ renderModal(container, state6, modalId);
31334
31341
  try {
31335
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
31336
- state5.stats = calculateStats(state5.data, state5.clampRange);
31337
- state5.isLoading = false;
31338
- renderModal(container, state5, modalId);
31339
- drawChart(modalId, state5);
31340
- await setupEventListeners(container, state5, modalId, onClose);
31342
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
31343
+ state6.stats = calculateStats(state6.data, state6.clampRange);
31344
+ state6.isLoading = false;
31345
+ renderModal(container, state6, modalId);
31346
+ drawChart(modalId, state6);
31347
+ await setupEventListeners(container, state6, modalId, onClose);
31341
31348
  } catch (error) {
31342
31349
  console.error("[TemperatureModal] Error fetching data:", error);
31343
- state5.isLoading = false;
31344
- renderModal(container, state5, modalId, error);
31345
- await setupEventListeners(container, state5, modalId, onClose);
31350
+ state6.isLoading = false;
31351
+ renderModal(container, state6, modalId, error);
31352
+ await setupEventListeners(container, state6, modalId, onClose);
31346
31353
  }
31347
31354
  });
31348
31355
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
31349
- if (state5.data.length === 0) return;
31350
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
31351
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
31356
+ if (state6.data.length === 0) return;
31357
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
31358
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
31352
31359
  exportTemperatureCSV(
31353
- state5.data,
31354
- state5.label,
31355
- state5.stats,
31360
+ state6.data,
31361
+ state6.label,
31362
+ state6.stats,
31356
31363
  startDateStr,
31357
31364
  endDateStr
31358
31365
  );
@@ -31365,7 +31372,7 @@
31365
31372
  const defaultDateRange = getTodaySoFar();
31366
31373
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
31367
31374
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
31368
- const state5 = {
31375
+ const state6 = {
31369
31376
  token: params.token,
31370
31377
  devices: params.devices,
31371
31378
  startTs,
@@ -31384,44 +31391,44 @@
31384
31391
  };
31385
31392
  const savedGranularity = localStorage.getItem("myio-temp-comparison-granularity");
31386
31393
  const savedTheme = localStorage.getItem("myio-temp-comparison-theme");
31387
- if (savedGranularity) state5.granularity = savedGranularity;
31388
- if (savedTheme) state5.theme = savedTheme;
31394
+ if (savedGranularity) state6.granularity = savedGranularity;
31395
+ if (savedTheme) state6.theme = savedTheme;
31389
31396
  const modalContainer = document.createElement("div");
31390
31397
  modalContainer.id = modalId;
31391
31398
  document.body.appendChild(modalContainer);
31392
- renderModal2(modalContainer, state5, modalId);
31393
- await fetchAllDevicesData(state5);
31394
- renderModal2(modalContainer, state5, modalId);
31395
- drawComparisonChart(modalId, state5);
31396
- await setupEventListeners2(modalContainer, state5, modalId, params.onClose);
31399
+ renderModal2(modalContainer, state6, modalId);
31400
+ await fetchAllDevicesData(state6);
31401
+ renderModal2(modalContainer, state6, modalId);
31402
+ drawComparisonChart(modalId, state6);
31403
+ await setupEventListeners2(modalContainer, state6, modalId, params.onClose);
31397
31404
  return {
31398
31405
  destroy: () => {
31399
31406
  modalContainer.remove();
31400
31407
  params.onClose?.();
31401
31408
  },
31402
31409
  updateData: async (startDate, endDate, granularity) => {
31403
- state5.startTs = new Date(startDate).getTime();
31404
- state5.endTs = new Date(endDate).getTime();
31405
- if (granularity) state5.granularity = granularity;
31406
- state5.isLoading = true;
31407
- renderModal2(modalContainer, state5, modalId);
31408
- await fetchAllDevicesData(state5);
31409
- renderModal2(modalContainer, state5, modalId);
31410
- drawComparisonChart(modalId, state5);
31411
- setupEventListeners2(modalContainer, state5, modalId, params.onClose);
31410
+ state6.startTs = new Date(startDate).getTime();
31411
+ state6.endTs = new Date(endDate).getTime();
31412
+ if (granularity) state6.granularity = granularity;
31413
+ state6.isLoading = true;
31414
+ renderModal2(modalContainer, state6, modalId);
31415
+ await fetchAllDevicesData(state6);
31416
+ renderModal2(modalContainer, state6, modalId);
31417
+ drawComparisonChart(modalId, state6);
31418
+ setupEventListeners2(modalContainer, state6, modalId, params.onClose);
31412
31419
  }
31413
31420
  };
31414
31421
  }
31415
- async function fetchAllDevicesData(state5) {
31416
- state5.isLoading = true;
31417
- state5.deviceData = [];
31422
+ async function fetchAllDevicesData(state6) {
31423
+ state6.isLoading = true;
31424
+ state6.deviceData = [];
31418
31425
  try {
31419
31426
  const results = await Promise.all(
31420
- state5.devices.map(async (device, index) => {
31427
+ state6.devices.map(async (device, index) => {
31421
31428
  const deviceId = device.tbId || device.id;
31422
31429
  try {
31423
- const data = await fetchTemperatureData(state5.token, deviceId, state5.startTs, state5.endTs);
31424
- const stats = calculateStats(data, state5.clampRange);
31430
+ const data = await fetchTemperatureData(state6.token, deviceId, state6.startTs, state6.endTs);
31431
+ const stats = calculateStats(data, state6.clampRange);
31425
31432
  return {
31426
31433
  device,
31427
31434
  data,
@@ -31439,21 +31446,21 @@
31439
31446
  }
31440
31447
  })
31441
31448
  );
31442
- state5.deviceData = results;
31449
+ state6.deviceData = results;
31443
31450
  } catch (error) {
31444
31451
  console.error("[TemperatureComparisonModal] Error fetching data:", error);
31445
31452
  }
31446
- state5.isLoading = false;
31453
+ state6.isLoading = false;
31447
31454
  }
31448
- function renderModal2(container, state5, modalId) {
31449
- const colors = getThemeColors(state5.theme);
31450
- new Date(state5.startTs).toLocaleDateString(state5.locale);
31451
- new Date(state5.endTs).toLocaleDateString(state5.locale);
31452
- new Date(state5.startTs).toISOString().slice(0, 16);
31453
- new Date(state5.endTs).toISOString().slice(0, 16);
31454
- const legendHTML = state5.deviceData.map((dd) => `
31455
+ function renderModal2(container, state6, modalId) {
31456
+ const colors = getThemeColors(state6.theme);
31457
+ new Date(state6.startTs).toLocaleDateString(state6.locale);
31458
+ new Date(state6.endTs).toLocaleDateString(state6.locale);
31459
+ new Date(state6.startTs).toISOString().slice(0, 16);
31460
+ new Date(state6.endTs).toISOString().slice(0, 16);
31461
+ const legendHTML = state6.deviceData.map((dd) => `
31455
31462
  <div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px;
31456
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
31463
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
31457
31464
  border-radius: 8px;">
31458
31465
  <span style="width: 12px; height: 12px; border-radius: 50%; background: ${dd.color};"></span>
31459
31466
  <span style="color: ${colors.text}; font-size: 13px;">${dd.device.label}</span>
@@ -31462,9 +31469,9 @@
31462
31469
  </span>
31463
31470
  </div>
31464
31471
  `).join("");
31465
- const statsHTML = state5.deviceData.map((dd) => `
31472
+ const statsHTML = state6.deviceData.map((dd) => `
31466
31473
  <div style="
31467
- padding: 12px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31474
+ padding: 12px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31468
31475
  border-radius: 10px; border-left: 4px solid ${dd.color};
31469
31476
  min-width: 150px;
31470
31477
  ">
@@ -31515,7 +31522,7 @@
31515
31522
  min-height: 20px;
31516
31523
  ">
31517
31524
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
31518
- \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state5.devices.length} sensores
31525
+ \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state6.devices.length} sensores
31519
31526
  </h2>
31520
31527
  <div style="display: flex; gap: 4px; align-items: center;">
31521
31528
  <!-- Theme Toggle -->
@@ -31523,7 +31530,7 @@
31523
31530
  background: none; border: none; font-size: 16px; cursor: pointer;
31524
31531
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
31525
31532
  transition: background-color 0.2s;
31526
- ">${state5.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
31533
+ ">${state6.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
31527
31534
  <!-- Maximize Button -->
31528
31535
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
31529
31536
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -31546,7 +31553,7 @@
31546
31553
  <div style="
31547
31554
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
31548
31555
  margin-bottom: 16px; padding: 16px;
31549
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
31556
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
31550
31557
  border-radius: 6px; border: 1px solid ${colors.border};
31551
31558
  ">
31552
31559
  <!-- Granularity Select -->
@@ -31559,8 +31566,8 @@
31559
31566
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
31560
31567
  cursor: pointer; min-width: 130px;
31561
31568
  ">
31562
- <option value="hour" ${state5.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
31563
- <option value="day" ${state5.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
31569
+ <option value="hour" ${state6.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
31570
+ <option value="day" ${state6.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
31564
31571
  </select>
31565
31572
  </div>
31566
31573
  <!-- Day Period Filter (Multiselect) -->
@@ -31574,7 +31581,7 @@
31574
31581
  cursor: pointer; min-width: 180px; text-align: left;
31575
31582
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
31576
31583
  ">
31577
- <span>${getSelectedPeriodsLabel(state5.selectedPeriods)}</span>
31584
+ <span>${getSelectedPeriodsLabel(state6.selectedPeriods)}</span>
31578
31585
  <span style="font-size: 10px;">\u25BC</span>
31579
31586
  </button>
31580
31587
  <div id="${modalId}-period-dropdown" style="
@@ -31587,12 +31594,12 @@
31587
31594
  <label style="
31588
31595
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
31589
31596
  cursor: pointer; font-size: 13px; color: ${colors.text};
31590
- " onmouseover="this.style.background='${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
31597
+ " onmouseover="this.style.background='${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
31591
31598
  onmouseout="this.style.background='transparent'">
31592
31599
  <input type="checkbox"
31593
31600
  name="${modalId}-period"
31594
31601
  value="${period.id}"
31595
- ${state5.selectedPeriods.includes(period.id) ? "checked" : ""}
31602
+ ${state6.selectedPeriods.includes(period.id) ? "checked" : ""}
31596
31603
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
31597
31604
  ${period.label}
31598
31605
  </label>
@@ -31600,13 +31607,13 @@
31600
31607
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
31601
31608
  <button id="${modalId}-period-select-all" type="button" style="
31602
31609
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
31603
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31610
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31604
31611
  border: none; border-radius: 4px; cursor: pointer;
31605
31612
  font-size: 12px; color: ${colors.text};
31606
31613
  ">Selecionar Todos</button>
31607
31614
  <button id="${modalId}-period-clear" type="button" style="
31608
31615
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
31609
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31616
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31610
31617
  border: none; border-radius: 4px; cursor: pointer;
31611
31618
  font-size: 12px; color: ${colors.text};
31612
31619
  ">Limpar Sele\xE7\xE3o</button>
@@ -31631,8 +31638,8 @@
31631
31638
  font-size: 14px; font-weight: 500; height: 38px;
31632
31639
  display: flex; align-items: center; gap: 8px;
31633
31640
  font-family: 'Roboto', Arial, sans-serif;
31634
- " ${state5.isLoading ? "disabled" : ""}>
31635
- ${state5.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31641
+ " ${state6.isLoading ? "disabled" : ""}>
31642
+ ${state6.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31636
31643
  </button>
31637
31644
  </div>
31638
31645
 
@@ -31648,14 +31655,14 @@
31648
31655
  <div style="margin-bottom: 24px;">
31649
31656
  <div id="${modalId}-chart" style="
31650
31657
  height: 380px;
31651
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
31658
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
31652
31659
  border-radius: 14px; display: flex; justify-content: center; align-items: center;
31653
31660
  border: 1px solid ${colors.border}; position: relative;
31654
31661
  ">
31655
- ${state5.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
31662
+ ${state6.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
31656
31663
  <div style="animation: spin 1s linear infinite; font-size: 36px; margin-bottom: 12px;">\u21BB</div>
31657
- <div style="font-size: 15px;">Carregando dados de ${state5.devices.length} sensores...</div>
31658
- </div>` : state5.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
31664
+ <div style="font-size: 15px;">Carregando dados de ${state6.devices.length} sensores...</div>
31665
+ </div>` : state6.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
31659
31666
  <div style="font-size: 48px; margin-bottom: 12px;">\u{1F4ED}</div>
31660
31667
  <div style="font-size: 16px;">Sem dados para o per\xEDodo selecionado</div>
31661
31668
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -31673,12 +31680,12 @@
31673
31680
  <!-- Actions -->
31674
31681
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
31675
31682
  <button id="${modalId}-export" style="
31676
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
31683
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
31677
31684
  color: ${colors.text}; border: 1px solid ${colors.border};
31678
31685
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
31679
31686
  font-size: 14px; display: flex; align-items: center; gap: 8px;
31680
31687
  font-family: 'Roboto', Arial, sans-serif;
31681
- " ${state5.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
31688
+ " ${state6.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
31682
31689
  \u{1F4E5} Exportar CSV
31683
31690
  </button>
31684
31691
  <button id="${modalId}-close-btn" style="
@@ -31713,15 +31720,15 @@
31713
31720
  </style>
31714
31721
  `;
31715
31722
  }
31716
- function drawComparisonChart(modalId, state5) {
31723
+ function drawComparisonChart(modalId, state6) {
31717
31724
  const chartContainer = document.getElementById(`${modalId}-chart`);
31718
31725
  const canvas = document.getElementById(`${modalId}-canvas`);
31719
31726
  if (!chartContainer || !canvas) return;
31720
- const hasData = state5.deviceData.some((dd) => dd.data.length > 0);
31727
+ const hasData = state6.deviceData.some((dd) => dd.data.length > 0);
31721
31728
  if (!hasData) return;
31722
31729
  const ctx = canvas.getContext("2d");
31723
31730
  if (!ctx) return;
31724
- const colors = getThemeColors(state5.theme);
31731
+ const colors = getThemeColors(state6.theme);
31725
31732
  const width = chartContainer.clientWidth - 2;
31726
31733
  const height = 380;
31727
31734
  canvas.width = width;
@@ -31732,19 +31739,19 @@
31732
31739
  const paddingBottom = 55;
31733
31740
  ctx.clearRect(0, 0, width, height);
31734
31741
  const processedData = [];
31735
- state5.deviceData.forEach((dd) => {
31742
+ state6.deviceData.forEach((dd) => {
31736
31743
  if (dd.data.length === 0) return;
31737
- const filteredData = filterByDayPeriods(dd.data, state5.selectedPeriods);
31744
+ const filteredData = filterByDayPeriods(dd.data, state6.selectedPeriods);
31738
31745
  if (filteredData.length === 0) return;
31739
31746
  let points;
31740
- if (state5.granularity === "hour") {
31747
+ if (state6.granularity === "hour") {
31741
31748
  const interpolated = interpolateTemperature(filteredData, {
31742
31749
  intervalMinutes: 30,
31743
- startTs: state5.startTs,
31744
- endTs: state5.endTs,
31745
- clampRange: state5.clampRange
31750
+ startTs: state6.startTs,
31751
+ endTs: state6.endTs,
31752
+ clampRange: state6.clampRange
31746
31753
  });
31747
- const filteredInterpolated = filterByDayPeriods(interpolated, state5.selectedPeriods);
31754
+ const filteredInterpolated = filterByDayPeriods(interpolated, state6.selectedPeriods);
31748
31755
  points = filteredInterpolated.map((item) => ({
31749
31756
  x: item.ts,
31750
31757
  y: Number(item.value),
@@ -31754,7 +31761,7 @@
31754
31761
  deviceColor: dd.color
31755
31762
  }));
31756
31763
  } else {
31757
- const daily = aggregateByDay(filteredData, state5.clampRange);
31764
+ const daily = aggregateByDay(filteredData, state6.clampRange);
31758
31765
  points = daily.map((item) => ({
31759
31766
  x: item.dateTs,
31760
31767
  y: item.avg,
@@ -31775,7 +31782,7 @@
31775
31782
  ctx.fillText("Nenhum dado para os per\xEDodos selecionados", width / 2, height / 2);
31776
31783
  return;
31777
31784
  }
31778
- const isPeriodsFiltered = state5.selectedPeriods.length < 4 && state5.selectedPeriods.length > 0;
31785
+ const isPeriodsFiltered = state6.selectedPeriods.length < 4 && state6.selectedPeriods.length > 0;
31779
31786
  let dataMinY = Infinity;
31780
31787
  let dataMaxY = -Infinity;
31781
31788
  processedData.forEach(({ points }) => {
@@ -31785,7 +31792,7 @@
31785
31792
  });
31786
31793
  });
31787
31794
  const rangeMap = /* @__PURE__ */ new Map();
31788
- state5.deviceData.forEach((dd, index) => {
31795
+ state6.deviceData.forEach((dd, index) => {
31789
31796
  const device = dd.device;
31790
31797
  const min = device.temperatureMin;
31791
31798
  const max = device.temperatureMax;
@@ -31804,10 +31811,10 @@
31804
31811
  }
31805
31812
  }
31806
31813
  });
31807
- if (rangeMap.size === 0 && state5.temperatureMin !== null && state5.temperatureMax !== null) {
31814
+ if (rangeMap.size === 0 && state6.temperatureMin !== null && state6.temperatureMax !== null) {
31808
31815
  rangeMap.set("global", {
31809
- min: state5.temperatureMin,
31810
- max: state5.temperatureMax,
31816
+ min: state6.temperatureMin,
31817
+ max: state6.temperatureMax,
31811
31818
  customerName: "Global",
31812
31819
  color: colors.success,
31813
31820
  deviceLabels: []
@@ -31928,10 +31935,10 @@
31928
31935
  const point = xAxisPoints[i];
31929
31936
  const date = new Date(point.x);
31930
31937
  let label;
31931
- if (state5.granularity === "hour") {
31932
- label = date.toLocaleTimeString(state5.locale, { hour: "2-digit", minute: "2-digit" });
31938
+ if (state6.granularity === "hour") {
31939
+ label = date.toLocaleTimeString(state6.locale, { hour: "2-digit", minute: "2-digit" });
31933
31940
  } else {
31934
- label = date.toLocaleDateString(state5.locale, { day: "2-digit", month: "2-digit" });
31941
+ label = date.toLocaleDateString(state6.locale, { day: "2-digit", month: "2-digit" });
31935
31942
  }
31936
31943
  ctx.strokeStyle = colors.chartGrid;
31937
31944
  ctx.lineWidth = 1;
@@ -31950,16 +31957,16 @@
31950
31957
  ctx.lineTo(width - paddingRight, height - paddingBottom);
31951
31958
  ctx.stroke();
31952
31959
  const allChartPoints = processedData.flatMap((pd) => pd.points);
31953
- setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state5, colors);
31960
+ setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state6, colors);
31954
31961
  }
31955
- function setupComparisonChartTooltip(canvas, container, chartData, state5, colors) {
31962
+ function setupComparisonChartTooltip(canvas, container, chartData, state6, colors) {
31956
31963
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
31957
31964
  if (existingTooltip) existingTooltip.remove();
31958
31965
  const tooltip = document.createElement("div");
31959
31966
  tooltip.className = "myio-chart-tooltip";
31960
31967
  tooltip.style.cssText = `
31961
31968
  position: absolute;
31962
- background: ${state5.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31969
+ background: ${state6.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31963
31970
  border: 1px solid ${colors.border};
31964
31971
  border-radius: 8px;
31965
31972
  padding: 10px 14px;
@@ -31996,17 +32003,17 @@
31996
32003
  if (point) {
31997
32004
  const date = new Date(point.x);
31998
32005
  let dateStr;
31999
- if (state5.granularity === "hour") {
32000
- dateStr = date.toLocaleDateString(state5.locale, {
32006
+ if (state6.granularity === "hour") {
32007
+ dateStr = date.toLocaleDateString(state6.locale, {
32001
32008
  day: "2-digit",
32002
32009
  month: "2-digit",
32003
32010
  year: "numeric"
32004
- }) + " " + date.toLocaleTimeString(state5.locale, {
32011
+ }) + " " + date.toLocaleTimeString(state6.locale, {
32005
32012
  hour: "2-digit",
32006
32013
  minute: "2-digit"
32007
32014
  });
32008
32015
  } else {
32009
- dateStr = date.toLocaleDateString(state5.locale, {
32016
+ dateStr = date.toLocaleDateString(state6.locale, {
32010
32017
  day: "2-digit",
32011
32018
  month: "2-digit",
32012
32019
  year: "numeric"
@@ -32048,7 +32055,7 @@
32048
32055
  canvas.style.cursor = "default";
32049
32056
  });
32050
32057
  }
32051
- async function setupEventListeners2(container, state5, modalId, onClose) {
32058
+ async function setupEventListeners2(container, state6, modalId, onClose) {
32052
32059
  const closeModal = () => {
32053
32060
  container.remove();
32054
32061
  onClose?.();
@@ -32059,19 +32066,19 @@
32059
32066
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
32060
32067
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
32061
32068
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
32062
- if (dateRangeInput && !state5.dateRangePicker) {
32069
+ if (dateRangeInput && !state6.dateRangePicker) {
32063
32070
  try {
32064
- state5.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
32065
- presetStart: new Date(state5.startTs).toISOString(),
32066
- presetEnd: new Date(state5.endTs).toISOString(),
32071
+ state6.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
32072
+ presetStart: new Date(state6.startTs).toISOString(),
32073
+ presetEnd: new Date(state6.endTs).toISOString(),
32067
32074
  includeTime: true,
32068
32075
  timePrecision: "minute",
32069
32076
  maxRangeDays: 90,
32070
- locale: state5.locale,
32077
+ locale: state6.locale,
32071
32078
  parentEl: container.querySelector(".myio-temp-comparison-content"),
32072
32079
  onApply: (result) => {
32073
- state5.startTs = new Date(result.startISO).getTime();
32074
- state5.endTs = new Date(result.endISO).getTime();
32080
+ state6.startTs = new Date(result.startISO).getTime();
32081
+ state6.endTs = new Date(result.endISO).getTime();
32075
32082
  console.log("[TemperatureComparisonModal] Date range applied:", result);
32076
32083
  }
32077
32084
  });
@@ -32080,23 +32087,23 @@
32080
32087
  }
32081
32088
  }
32082
32089
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
32083
- state5.theme = state5.theme === "dark" ? "light" : "dark";
32084
- localStorage.setItem("myio-temp-comparison-theme", state5.theme);
32085
- state5.dateRangePicker = null;
32086
- renderModal2(container, state5, modalId);
32087
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32088
- drawComparisonChart(modalId, state5);
32089
- }
32090
- await setupEventListeners2(container, state5, modalId, onClose);
32090
+ state6.theme = state6.theme === "dark" ? "light" : "dark";
32091
+ localStorage.setItem("myio-temp-comparison-theme", state6.theme);
32092
+ state6.dateRangePicker = null;
32093
+ renderModal2(container, state6, modalId);
32094
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32095
+ drawComparisonChart(modalId, state6);
32096
+ }
32097
+ await setupEventListeners2(container, state6, modalId, onClose);
32091
32098
  });
32092
32099
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
32093
32100
  container.__isMaximized = !container.__isMaximized;
32094
- state5.dateRangePicker = null;
32095
- renderModal2(container, state5, modalId);
32096
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32097
- drawComparisonChart(modalId, state5);
32101
+ state6.dateRangePicker = null;
32102
+ renderModal2(container, state6, modalId);
32103
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32104
+ drawComparisonChart(modalId, state6);
32098
32105
  }
32099
- await setupEventListeners2(container, state5, modalId, onClose);
32106
+ await setupEventListeners2(container, state6, modalId, onClose);
32100
32107
  });
32101
32108
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
32102
32109
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -32115,13 +32122,13 @@
32115
32122
  periodCheckboxes.forEach((checkbox) => {
32116
32123
  checkbox.addEventListener("change", () => {
32117
32124
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
32118
- state5.selectedPeriods = checked;
32125
+ state6.selectedPeriods = checked;
32119
32126
  const btnLabel = periodBtn?.querySelector("span:first-child");
32120
32127
  if (btnLabel) {
32121
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32128
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32122
32129
  }
32123
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32124
- drawComparisonChart(modalId, state5);
32130
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32131
+ drawComparisonChart(modalId, state6);
32125
32132
  }
32126
32133
  });
32127
32134
  });
@@ -32129,77 +32136,77 @@
32129
32136
  periodCheckboxes.forEach((cb) => {
32130
32137
  cb.checked = true;
32131
32138
  });
32132
- state5.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
32139
+ state6.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
32133
32140
  const btnLabel = periodBtn?.querySelector("span:first-child");
32134
32141
  if (btnLabel) {
32135
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32142
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32136
32143
  }
32137
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32138
- drawComparisonChart(modalId, state5);
32144
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32145
+ drawComparisonChart(modalId, state6);
32139
32146
  }
32140
32147
  });
32141
32148
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
32142
32149
  periodCheckboxes.forEach((cb) => {
32143
32150
  cb.checked = false;
32144
32151
  });
32145
- state5.selectedPeriods = [];
32152
+ state6.selectedPeriods = [];
32146
32153
  const btnLabel = periodBtn?.querySelector("span:first-child");
32147
32154
  if (btnLabel) {
32148
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32155
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32149
32156
  }
32150
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32151
- drawComparisonChart(modalId, state5);
32157
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32158
+ drawComparisonChart(modalId, state6);
32152
32159
  }
32153
32160
  });
32154
32161
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
32155
- state5.granularity = e.target.value;
32156
- localStorage.setItem("myio-temp-comparison-granularity", state5.granularity);
32157
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32158
- drawComparisonChart(modalId, state5);
32162
+ state6.granularity = e.target.value;
32163
+ localStorage.setItem("myio-temp-comparison-granularity", state6.granularity);
32164
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32165
+ drawComparisonChart(modalId, state6);
32159
32166
  }
32160
32167
  });
32161
32168
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
32162
- if (state5.startTs >= state5.endTs) {
32169
+ if (state6.startTs >= state6.endTs) {
32163
32170
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
32164
32171
  return;
32165
32172
  }
32166
- state5.isLoading = true;
32167
- state5.dateRangePicker = null;
32168
- renderModal2(container, state5, modalId);
32169
- await fetchAllDevicesData(state5);
32170
- renderModal2(container, state5, modalId);
32171
- drawComparisonChart(modalId, state5);
32172
- await setupEventListeners2(container, state5, modalId, onClose);
32173
+ state6.isLoading = true;
32174
+ state6.dateRangePicker = null;
32175
+ renderModal2(container, state6, modalId);
32176
+ await fetchAllDevicesData(state6);
32177
+ renderModal2(container, state6, modalId);
32178
+ drawComparisonChart(modalId, state6);
32179
+ await setupEventListeners2(container, state6, modalId, onClose);
32173
32180
  });
32174
32181
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
32175
- if (state5.deviceData.every((dd) => dd.data.length === 0)) return;
32176
- exportComparisonCSV(state5);
32182
+ if (state6.deviceData.every((dd) => dd.data.length === 0)) return;
32183
+ exportComparisonCSV(state6);
32177
32184
  });
32178
32185
  }
32179
- function exportComparisonCSV(state5) {
32180
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
32181
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
32186
+ function exportComparisonCSV(state6) {
32187
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
32188
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
32182
32189
  const BOM = "\uFEFF";
32183
32190
  let csvContent = BOM;
32184
32191
  csvContent += `Compara\xE7\xE3o de Temperatura
32185
32192
  `;
32186
32193
  csvContent += `Per\xEDodo: ${startDateStr} at\xE9 ${endDateStr}
32187
32194
  `;
32188
- csvContent += `Sensores: ${state5.devices.map((d) => d.label).join(", ")}
32195
+ csvContent += `Sensores: ${state6.devices.map((d) => d.label).join(", ")}
32189
32196
  `;
32190
32197
  csvContent += "\n";
32191
32198
  csvContent += "Estat\xEDsticas por Sensor:\n";
32192
32199
  csvContent += "Sensor,M\xE9dia (\xB0C),Min (\xB0C),Max (\xB0C),Leituras\n";
32193
- state5.deviceData.forEach((dd) => {
32200
+ state6.deviceData.forEach((dd) => {
32194
32201
  csvContent += `"${dd.device.label}",${dd.stats.avg.toFixed(2)},${dd.stats.min.toFixed(2)},${dd.stats.max.toFixed(2)},${dd.stats.count}
32195
32202
  `;
32196
32203
  });
32197
32204
  csvContent += "\n";
32198
32205
  csvContent += "Dados Detalhados:\n";
32199
32206
  csvContent += "Data/Hora,Sensor,Temperatura (\xB0C)\n";
32200
- state5.deviceData.forEach((dd) => {
32207
+ state6.deviceData.forEach((dd) => {
32201
32208
  dd.data.forEach((item) => {
32202
- const date = new Date(item.ts).toLocaleString(state5.locale);
32209
+ const date = new Date(item.ts).toLocaleString(state6.locale);
32203
32210
  const temp = Number(item.value).toFixed(2);
32204
32211
  csvContent += `"${date}","${dd.device.label}",${temp}
32205
32212
  `;
@@ -32307,10 +32314,10 @@
32307
32314
  throw new Error(`Failed to save attributes: ${response.status}`);
32308
32315
  }
32309
32316
  }
32310
- function renderModal3(container, state5, modalId, onClose, onSave) {
32311
- const colors = getColors(state5.theme);
32312
- const minValue = state5.minTemperature !== null ? state5.minTemperature : "";
32313
- const maxValue = state5.maxTemperature !== null ? state5.maxTemperature : "";
32317
+ function renderModal3(container, state6, modalId, onClose, onSave) {
32318
+ const colors = getColors(state6.theme);
32319
+ const minValue = state6.minTemperature !== null ? state6.minTemperature : "";
32320
+ const maxValue = state6.maxTemperature !== null ? state6.maxTemperature : "";
32314
32321
  container.innerHTML = `
32315
32322
  <style>
32316
32323
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@@ -32575,23 +32582,23 @@
32575
32582
  </div>
32576
32583
 
32577
32584
  <div class="modal-body">
32578
- ${state5.isLoading ? `
32585
+ ${state6.isLoading ? `
32579
32586
  <div class="loading-overlay">
32580
32587
  <div class="loading-spinner"></div>
32581
32588
  <div>Carregando configura\xE7\xF5es...</div>
32582
32589
  </div>
32583
32590
  ` : `
32584
- ${state5.error ? `
32585
- <div class="message message-error">${state5.error}</div>
32591
+ ${state6.error ? `
32592
+ <div class="message message-error">${state6.error}</div>
32586
32593
  ` : ""}
32587
32594
 
32588
- ${state5.successMessage ? `
32589
- <div class="message message-success">${state5.successMessage}</div>
32595
+ ${state6.successMessage ? `
32596
+ <div class="message message-success">${state6.successMessage}</div>
32590
32597
  ` : ""}
32591
32598
 
32592
32599
  <div class="customer-info">
32593
32600
  <div class="customer-label">Shopping / Cliente</div>
32594
- <div class="customer-name">${state5.customerName || "N\xE3o identificado"}</div>
32601
+ <div class="customer-name">${state6.customerName || "N\xE3o identificado"}</div>
32595
32602
  </div>
32596
32603
 
32597
32604
  <div class="form-group">
@@ -32635,11 +32642,11 @@
32635
32642
  `}
32636
32643
  </div>
32637
32644
 
32638
- ${!state5.isLoading ? `
32645
+ ${!state6.isLoading ? `
32639
32646
  <div class="modal-footer">
32640
32647
  <button class="btn btn-secondary" id="${modalId}-cancel">Cancelar</button>
32641
- <button class="btn btn-primary" id="${modalId}-save" ${state5.isSaving ? "disabled" : ""}>
32642
- ${state5.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
32648
+ <button class="btn btn-primary" id="${modalId}-save" ${state6.isSaving ? "disabled" : ""}>
32649
+ ${state6.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
32643
32650
  </button>
32644
32651
  </div>
32645
32652
  ` : ""}
@@ -32676,18 +32683,18 @@
32676
32683
  const min = parseFloat(minInput.value);
32677
32684
  const max = parseFloat(maxInput.value);
32678
32685
  if (isNaN(min) || isNaN(max)) {
32679
- state5.error = "Por favor, preencha ambos os valores.";
32680
- renderModal3(container, state5, modalId, onClose, onSave);
32686
+ state6.error = "Por favor, preencha ambos os valores.";
32687
+ renderModal3(container, state6, modalId, onClose, onSave);
32681
32688
  return;
32682
32689
  }
32683
32690
  if (min >= max) {
32684
- state5.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
32685
- renderModal3(container, state5, modalId, onClose, onSave);
32691
+ state6.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
32692
+ renderModal3(container, state6, modalId, onClose, onSave);
32686
32693
  return;
32687
32694
  }
32688
32695
  if (min < 0 || max > 50) {
32689
- state5.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
32690
- renderModal3(container, state5, modalId, onClose, onSave);
32696
+ state6.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
32697
+ renderModal3(container, state6, modalId, onClose, onSave);
32691
32698
  return;
32692
32699
  }
32693
32700
  await onSave(min, max);
@@ -32695,7 +32702,7 @@
32695
32702
  }
32696
32703
  function openTemperatureSettingsModal(params) {
32697
32704
  const modalId = `myio-temp-settings-${Date.now()}`;
32698
- const state5 = {
32705
+ const state6 = {
32699
32706
  customerId: params.customerId,
32700
32707
  customerName: params.customerName || "",
32701
32708
  token: params.token,
@@ -32715,37 +32722,37 @@
32715
32722
  params.onClose?.();
32716
32723
  };
32717
32724
  const handleSave = async (min, max) => {
32718
- state5.isSaving = true;
32719
- state5.error = null;
32720
- state5.successMessage = null;
32721
- renderModal3(container, state5, modalId, destroy, handleSave);
32725
+ state6.isSaving = true;
32726
+ state6.error = null;
32727
+ state6.successMessage = null;
32728
+ renderModal3(container, state6, modalId, destroy, handleSave);
32722
32729
  try {
32723
- await saveCustomerAttributes(state5.customerId, state5.token, min, max, params.onError);
32724
- state5.minTemperature = min;
32725
- state5.maxTemperature = max;
32726
- state5.isSaving = false;
32727
- state5.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
32728
- renderModal3(container, state5, modalId, destroy, handleSave);
32730
+ await saveCustomerAttributes(state6.customerId, state6.token, min, max, params.onError);
32731
+ state6.minTemperature = min;
32732
+ state6.maxTemperature = max;
32733
+ state6.isSaving = false;
32734
+ state6.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
32735
+ renderModal3(container, state6, modalId, destroy, handleSave);
32729
32736
  params.onSave?.({ minTemperature: min, maxTemperature: max });
32730
32737
  setTimeout(() => {
32731
32738
  destroy();
32732
32739
  }, 1500);
32733
32740
  } catch (error) {
32734
- state5.isSaving = false;
32735
- state5.error = `Erro ao salvar: ${error.message}`;
32736
- renderModal3(container, state5, modalId, destroy, handleSave);
32741
+ state6.isSaving = false;
32742
+ state6.error = `Erro ao salvar: ${error.message}`;
32743
+ renderModal3(container, state6, modalId, destroy, handleSave);
32737
32744
  }
32738
32745
  };
32739
- renderModal3(container, state5, modalId, destroy, handleSave);
32740
- fetchCustomerAttributes(state5.customerId, state5.token, params.onError).then(({ minTemperature, maxTemperature }) => {
32741
- state5.minTemperature = minTemperature;
32742
- state5.maxTemperature = maxTemperature;
32743
- state5.isLoading = false;
32744
- renderModal3(container, state5, modalId, destroy, handleSave);
32746
+ renderModal3(container, state6, modalId, destroy, handleSave);
32747
+ fetchCustomerAttributes(state6.customerId, state6.token, params.onError).then(({ minTemperature, maxTemperature }) => {
32748
+ state6.minTemperature = minTemperature;
32749
+ state6.maxTemperature = maxTemperature;
32750
+ state6.isLoading = false;
32751
+ renderModal3(container, state6, modalId, destroy, handleSave);
32745
32752
  }).catch((error) => {
32746
- state5.isLoading = false;
32747
- state5.error = `Erro ao carregar: ${error.message}`;
32748
- renderModal3(container, state5, modalId, destroy, handleSave);
32753
+ state6.isLoading = false;
32754
+ state6.error = `Erro ao carregar: ${error.message}`;
32755
+ renderModal3(container, state6, modalId, destroy, handleSave);
32749
32756
  });
32750
32757
  return { destroy };
32751
32758
  }
@@ -34015,7 +34022,7 @@
34015
34022
  * RFC-0105 Enhancement: Now fetches device lists from MyIOOrchestratorData
34016
34023
  * to populate device lists for status popup display
34017
34024
  */
34018
- buildSummaryFromState(state5, receivedData, domain = "energy") {
34025
+ buildSummaryFromState(state6, receivedData, domain = "energy") {
34019
34026
  const summary = {
34020
34027
  totalDevices: 0,
34021
34028
  totalConsumption: 0,
@@ -34038,22 +34045,22 @@
34038
34045
  },
34039
34046
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
34040
34047
  };
34041
- if (!state5) return summary;
34048
+ if (!state6) return summary;
34042
34049
  const entrada = {
34043
34050
  id: "entrada",
34044
34051
  name: "Entrada",
34045
34052
  icon: CATEGORY_ICONS.entrada,
34046
- deviceCount: state5.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
34047
- consumption: state5.entrada?.total || 0,
34053
+ deviceCount: state6.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
34054
+ consumption: state6.entrada?.total || 0,
34048
34055
  percentage: 100
34049
34056
  };
34050
34057
  const lojas = {
34051
34058
  id: "lojas",
34052
34059
  name: "Lojas",
34053
34060
  icon: CATEGORY_ICONS.lojas,
34054
- deviceCount: state5.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
34055
- consumption: state5.consumidores?.lojas?.total || 0,
34056
- percentage: state5.consumidores?.lojas?.perc || 0
34061
+ deviceCount: state6.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
34062
+ consumption: state6.consumidores?.lojas?.total || 0,
34063
+ percentage: state6.consumidores?.lojas?.perc || 0
34057
34064
  };
34058
34065
  const climatizacaoData = receivedData?.climatizacao || {};
34059
34066
  const elevadoresData = receivedData?.elevadores || {};
@@ -34064,9 +34071,9 @@
34064
34071
  id: "climatizacao",
34065
34072
  name: "Climatizacao",
34066
34073
  icon: CATEGORY_ICONS.climatizacao,
34067
- deviceCount: climatizacaoData.count || state5.consumidores?.climatizacao?.devices?.length || 0,
34068
- consumption: state5.consumidores?.climatizacao?.total || 0,
34069
- percentage: state5.consumidores?.climatizacao?.perc || 0
34074
+ deviceCount: climatizacaoData.count || state6.consumidores?.climatizacao?.devices?.length || 0,
34075
+ consumption: state6.consumidores?.climatizacao?.total || 0,
34076
+ percentage: state6.consumidores?.climatizacao?.perc || 0
34070
34077
  };
34071
34078
  if (climatizacaoData.subcategories) {
34072
34079
  climatizacao.children = [];
@@ -34117,40 +34124,40 @@
34117
34124
  id: "elevadores",
34118
34125
  name: "Elevadores",
34119
34126
  icon: CATEGORY_ICONS.elevadores,
34120
- deviceCount: elevadoresData.count || state5.consumidores?.elevadores?.devices?.length || 0,
34121
- consumption: state5.consumidores?.elevadores?.total || 0,
34122
- percentage: state5.consumidores?.elevadores?.perc || 0
34127
+ deviceCount: elevadoresData.count || state6.consumidores?.elevadores?.devices?.length || 0,
34128
+ consumption: state6.consumidores?.elevadores?.total || 0,
34129
+ percentage: state6.consumidores?.elevadores?.perc || 0
34123
34130
  });
34124
34131
  areaComumChildren.push({
34125
34132
  id: "escadasRolantes",
34126
34133
  name: "Esc. Rolantes",
34127
34134
  icon: CATEGORY_ICONS.escadas,
34128
- deviceCount: escadasData.count || state5.consumidores?.escadasRolantes?.devices?.length || 0,
34129
- consumption: state5.consumidores?.escadasRolantes?.total || 0,
34130
- percentage: state5.consumidores?.escadasRolantes?.perc || 0
34135
+ deviceCount: escadasData.count || state6.consumidores?.escadasRolantes?.devices?.length || 0,
34136
+ consumption: state6.consumidores?.escadasRolantes?.total || 0,
34137
+ percentage: state6.consumidores?.escadasRolantes?.perc || 0
34131
34138
  });
34132
34139
  areaComumChildren.push({
34133
34140
  id: "outros",
34134
34141
  name: "Outros",
34135
34142
  icon: CATEGORY_ICONS.outros,
34136
- deviceCount: outrosData.count || state5.consumidores?.outros?.devices?.length || 0,
34137
- consumption: state5.consumidores?.outros?.total || 0,
34138
- percentage: state5.consumidores?.outros?.perc || 0
34143
+ deviceCount: outrosData.count || state6.consumidores?.outros?.devices?.length || 0,
34144
+ consumption: state6.consumidores?.outros?.total || 0,
34145
+ percentage: state6.consumidores?.outros?.perc || 0
34139
34146
  });
34140
34147
  const areaComumDeviceCount = areaComumChildren.reduce((sum, c) => sum + c.deviceCount, 0);
34141
- const areaComumConsumption = state5.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
34148
+ const areaComumConsumption = state6.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
34142
34149
  const areaComum = {
34143
34150
  id: "areaComum",
34144
34151
  name: "Area Comum",
34145
34152
  icon: CATEGORY_ICONS.areaComum,
34146
34153
  deviceCount: areaComumDeviceCount,
34147
34154
  consumption: areaComumConsumption,
34148
- percentage: state5.consumidores?.areaComum?.perc || 0,
34155
+ percentage: state6.consumidores?.areaComum?.perc || 0,
34149
34156
  children: areaComumChildren
34150
34157
  };
34151
34158
  summary.byCategory = [entrada, lojas, areaComum];
34152
34159
  summary.totalDevices = entrada.deviceCount + lojas.deviceCount + areaComumDeviceCount;
34153
- summary.totalConsumption = state5.grandTotal || entrada.consumption;
34160
+ summary.totalConsumption = state6.grandTotal || entrada.consumption;
34154
34161
  const widgetAggregation = receivedData?.deviceStatusAggregation;
34155
34162
  if (widgetAggregation && widgetAggregation.hasData) {
34156
34163
  summary.byStatus = {
@@ -35438,7 +35445,7 @@
35438
35445
  * RFC-0105 Enhancement: Now fetches device lists from MyIOOrchestratorData
35439
35446
  * to populate device lists for status popup display
35440
35447
  */
35441
- buildSummaryFromState(state5, receivedData, includeBathrooms = false, domain = "water") {
35448
+ buildSummaryFromState(state6, receivedData, includeBathrooms = false, domain = "water") {
35442
35449
  const summary = {
35443
35450
  totalDevices: 0,
35444
35451
  totalConsumption: 0,
@@ -35462,22 +35469,22 @@
35462
35469
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
35463
35470
  includeBathrooms
35464
35471
  };
35465
- if (!state5) return summary;
35472
+ if (!state6) return summary;
35466
35473
  const entrada = {
35467
35474
  id: "entrada",
35468
35475
  name: "Entrada",
35469
35476
  icon: WATER_CATEGORY_ICONS.entrada,
35470
- deviceCount: state5.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
35471
- consumption: state5.entrada?.total || 0,
35477
+ deviceCount: state6.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
35478
+ consumption: state6.entrada?.total || 0,
35472
35479
  percentage: 100
35473
35480
  };
35474
35481
  const lojas = {
35475
35482
  id: "lojas",
35476
35483
  name: "Lojas",
35477
35484
  icon: WATER_CATEGORY_ICONS.lojas,
35478
- deviceCount: state5.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
35479
- consumption: state5.lojas?.total || 0,
35480
- percentage: state5.lojas?.perc || 0
35485
+ deviceCount: state6.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
35486
+ consumption: state6.lojas?.total || 0,
35487
+ percentage: state6.lojas?.perc || 0
35481
35488
  };
35482
35489
  summary.byCategory = [entrada, lojas];
35483
35490
  if (includeBathrooms) {
@@ -35485,9 +35492,9 @@
35485
35492
  id: "banheiros",
35486
35493
  name: "Banheiros",
35487
35494
  icon: WATER_CATEGORY_ICONS.banheiros,
35488
- deviceCount: state5.banheiros?.devices?.length || (receivedData?.banheiros_total?.device_count || 0),
35489
- consumption: state5.banheiros?.total || 0,
35490
- percentage: state5.banheiros?.perc || 0
35495
+ deviceCount: state6.banheiros?.devices?.length || (receivedData?.banheiros_total?.device_count || 0),
35496
+ consumption: state6.banheiros?.total || 0,
35497
+ percentage: state6.banheiros?.perc || 0
35491
35498
  };
35492
35499
  summary.byCategory.push(banheiros);
35493
35500
  }
@@ -35495,24 +35502,24 @@
35495
35502
  id: "areaComum",
35496
35503
  name: "\xC1rea Comum",
35497
35504
  icon: WATER_CATEGORY_ICONS.areaComum,
35498
- deviceCount: state5.areaComum?.devices?.length || (receivedData?.area_comum_total?.device_count || 0),
35499
- consumption: state5.areaComum?.total || 0,
35500
- percentage: state5.areaComum?.perc || 0
35505
+ deviceCount: state6.areaComum?.devices?.length || (receivedData?.area_comum_total?.device_count || 0),
35506
+ consumption: state6.areaComum?.total || 0,
35507
+ percentage: state6.areaComum?.perc || 0
35501
35508
  };
35502
35509
  summary.byCategory.push(areaComum);
35503
- if (state5.pontosNaoMapeados && state5.pontosNaoMapeados.total > 0) {
35510
+ if (state6.pontosNaoMapeados && state6.pontosNaoMapeados.total > 0) {
35504
35511
  const pontosNaoMapeados = {
35505
35512
  id: "pontosNaoMapeados",
35506
35513
  name: "Pontos N\xE3o Mapeados",
35507
35514
  icon: WATER_CATEGORY_ICONS.pontosNaoMapeados,
35508
- deviceCount: state5.pontosNaoMapeados?.devices?.length || 0,
35509
- consumption: state5.pontosNaoMapeados?.total || 0,
35510
- percentage: state5.pontosNaoMapeados?.perc || 0
35515
+ deviceCount: state6.pontosNaoMapeados?.devices?.length || 0,
35516
+ consumption: state6.pontosNaoMapeados?.total || 0,
35517
+ percentage: state6.pontosNaoMapeados?.perc || 0
35511
35518
  };
35512
35519
  summary.byCategory.push(pontosNaoMapeados);
35513
35520
  }
35514
35521
  summary.totalDevices = summary.byCategory.reduce((sum, cat) => sum + cat.deviceCount, 0);
35515
- summary.totalConsumption = state5.entrada?.total || 0;
35522
+ summary.totalConsumption = state6.entrada?.total || 0;
35516
35523
  const widgetAggregation = receivedData?.deviceStatusAggregation;
35517
35524
  if (widgetAggregation && widgetAggregation.hasData) {
35518
35525
  summary.byStatus = {
@@ -37049,6 +37056,996 @@
37049
37056
  }
37050
37057
  };
37051
37058
 
37059
+ // src/utils/ContractSummaryTooltip.ts
37060
+ var CONTRACT_SUMMARY_TOOLTIP_CSS = `
37061
+ /* ============================================
37062
+ Contract Summary Tooltip (RFC-0107)
37063
+ Premium draggable tooltip with dark theme
37064
+ ============================================ */
37065
+
37066
+ .myio-contract-summary-tooltip {
37067
+ position: fixed;
37068
+ z-index: 99999;
37069
+ pointer-events: none;
37070
+ opacity: 0;
37071
+ transition: opacity 0.25s ease, transform 0.25s ease;
37072
+ transform: translateY(5px);
37073
+ }
37074
+
37075
+ .myio-contract-summary-tooltip.visible {
37076
+ opacity: 1;
37077
+ pointer-events: auto;
37078
+ transform: translateY(0);
37079
+ }
37080
+
37081
+ .myio-contract-summary-tooltip.closing {
37082
+ opacity: 0;
37083
+ transform: translateY(8px);
37084
+ transition: opacity 0.4s ease, transform 0.4s ease;
37085
+ }
37086
+
37087
+ .myio-contract-summary-tooltip.pinned {
37088
+ box-shadow: 0 0 0 2px #9684B5, 0 10px 40px rgba(0, 0, 0, 0.3);
37089
+ border-radius: 16px;
37090
+ }
37091
+
37092
+ .myio-contract-summary-tooltip.dragging {
37093
+ transition: none !important;
37094
+ cursor: move;
37095
+ }
37096
+
37097
+ .myio-contract-summary-tooltip.maximized {
37098
+ top: 20px !important;
37099
+ left: 20px !important;
37100
+ right: 20px !important;
37101
+ bottom: 20px !important;
37102
+ width: auto !important;
37103
+ max-width: none !important;
37104
+ }
37105
+
37106
+ .myio-contract-summary-tooltip.maximized .myio-contract-summary-tooltip__panel {
37107
+ width: 100%;
37108
+ height: 100%;
37109
+ max-width: none;
37110
+ display: flex;
37111
+ flex-direction: column;
37112
+ }
37113
+
37114
+ .myio-contract-summary-tooltip.maximized .myio-contract-summary-tooltip__body {
37115
+ flex: 1;
37116
+ overflow-y: auto;
37117
+ }
37118
+
37119
+ .myio-contract-summary-tooltip__panel {
37120
+ background: #2d1458;
37121
+ border: 1px solid rgba(255, 255, 255, 0.1);
37122
+ border-radius: 16px;
37123
+ box-shadow:
37124
+ 0 20px 60px rgba(0, 0, 0, 0.4),
37125
+ 0 8px 20px rgba(0, 0, 0, 0.25),
37126
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
37127
+ min-width: 320px;
37128
+ max-width: 380px;
37129
+ font-family: Inter, system-ui, -apple-system, sans-serif;
37130
+ font-size: 12px;
37131
+ color: #ffffff;
37132
+ overflow: hidden;
37133
+ }
37134
+
37135
+ /* Header */
37136
+ .myio-contract-summary-tooltip__header {
37137
+ display: flex;
37138
+ align-items: center;
37139
+ gap: 10px;
37140
+ padding: 14px 16px;
37141
+ background: linear-gradient(135deg, #9684B5 0%, #2d1458 100%);
37142
+ border-radius: 16px 16px 0 0;
37143
+ position: relative;
37144
+ overflow: hidden;
37145
+ cursor: move;
37146
+ user-select: none;
37147
+ }
37148
+
37149
+ .myio-contract-summary-tooltip__header::before {
37150
+ content: '';
37151
+ position: absolute;
37152
+ top: 0;
37153
+ left: 0;
37154
+ right: 0;
37155
+ bottom: 0;
37156
+ background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
37157
+ opacity: 0.3;
37158
+ }
37159
+
37160
+ .myio-contract-summary-tooltip__icon {
37161
+ width: 40px;
37162
+ height: 40px;
37163
+ background: rgba(255, 255, 255, 0.15);
37164
+ border-radius: 12px;
37165
+ display: flex;
37166
+ align-items: center;
37167
+ justify-content: center;
37168
+ font-size: 20px;
37169
+ backdrop-filter: blur(10px);
37170
+ position: relative;
37171
+ z-index: 1;
37172
+ }
37173
+
37174
+ .myio-contract-summary-tooltip__icon.valid {
37175
+ background: rgba(76, 175, 80, 0.3);
37176
+ }
37177
+
37178
+ .myio-contract-summary-tooltip__icon.invalid {
37179
+ background: rgba(244, 67, 54, 0.3);
37180
+ }
37181
+
37182
+ .myio-contract-summary-tooltip__header-info {
37183
+ flex: 1;
37184
+ position: relative;
37185
+ z-index: 1;
37186
+ }
37187
+
37188
+ .myio-contract-summary-tooltip__title {
37189
+ font-weight: 700;
37190
+ font-size: 15px;
37191
+ color: #ffffff;
37192
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
37193
+ margin-bottom: 2px;
37194
+ }
37195
+
37196
+ .myio-contract-summary-tooltip__subtitle {
37197
+ font-size: 11px;
37198
+ color: rgba(255, 255, 255, 0.7);
37199
+ }
37200
+
37201
+ .myio-contract-summary-tooltip__header-actions {
37202
+ display: flex;
37203
+ align-items: center;
37204
+ gap: 4px;
37205
+ position: relative;
37206
+ z-index: 1;
37207
+ }
37208
+
37209
+ .myio-contract-summary-tooltip__header-btn {
37210
+ width: 28px;
37211
+ height: 28px;
37212
+ border: none;
37213
+ background: rgba(255, 255, 255, 0.15);
37214
+ border-radius: 8px;
37215
+ cursor: pointer;
37216
+ display: flex;
37217
+ align-items: center;
37218
+ justify-content: center;
37219
+ transition: all 0.2s ease;
37220
+ color: rgba(255, 255, 255, 0.8);
37221
+ }
37222
+
37223
+ .myio-contract-summary-tooltip__header-btn:hover {
37224
+ background: rgba(255, 255, 255, 0.25);
37225
+ color: #ffffff;
37226
+ transform: scale(1.05);
37227
+ }
37228
+
37229
+ .myio-contract-summary-tooltip__header-btn.pinned {
37230
+ background: rgba(255, 255, 255, 0.9);
37231
+ color: #9684B5;
37232
+ }
37233
+
37234
+ .myio-contract-summary-tooltip__header-btn.pinned:hover {
37235
+ background: #ffffff;
37236
+ color: #2d1458;
37237
+ }
37238
+
37239
+ .myio-contract-summary-tooltip__header-btn svg {
37240
+ width: 14px;
37241
+ height: 14px;
37242
+ }
37243
+
37244
+ /* Body */
37245
+ .myio-contract-summary-tooltip__body {
37246
+ padding: 16px;
37247
+ }
37248
+
37249
+ /* Domain Section */
37250
+ .myio-contract-summary-tooltip__domain {
37251
+ margin-bottom: 14px;
37252
+ background: rgba(255, 255, 255, 0.05);
37253
+ border-radius: 12px;
37254
+ overflow: hidden;
37255
+ border: 1px solid rgba(255, 255, 255, 0.08);
37256
+ }
37257
+
37258
+ .myio-contract-summary-tooltip__domain:last-child {
37259
+ margin-bottom: 0;
37260
+ }
37261
+
37262
+ .myio-contract-summary-tooltip__domain-header {
37263
+ display: flex;
37264
+ align-items: center;
37265
+ justify-content: space-between;
37266
+ padding: 10px 14px;
37267
+ cursor: pointer;
37268
+ transition: background 0.2s ease;
37269
+ }
37270
+
37271
+ .myio-contract-summary-tooltip__domain-header:hover {
37272
+ background: rgba(255, 255, 255, 0.05);
37273
+ }
37274
+
37275
+ .myio-contract-summary-tooltip__domain-info {
37276
+ display: flex;
37277
+ align-items: center;
37278
+ gap: 8px;
37279
+ }
37280
+
37281
+ .myio-contract-summary-tooltip__domain-icon {
37282
+ font-size: 18px;
37283
+ }
37284
+
37285
+ .myio-contract-summary-tooltip__domain-name {
37286
+ font-weight: 600;
37287
+ font-size: 13px;
37288
+ }
37289
+
37290
+ .myio-contract-summary-tooltip__domain-count {
37291
+ font-size: 12px;
37292
+ color: #81c784;
37293
+ font-weight: 600;
37294
+ }
37295
+
37296
+ .myio-contract-summary-tooltip__expand-icon {
37297
+ font-size: 10px;
37298
+ opacity: 0.6;
37299
+ transition: transform 0.3s ease;
37300
+ }
37301
+
37302
+ .myio-contract-summary-tooltip__domain.expanded .myio-contract-summary-tooltip__expand-icon {
37303
+ transform: rotate(180deg);
37304
+ }
37305
+
37306
+ /* Domain Details */
37307
+ .myio-contract-summary-tooltip__domain-details {
37308
+ max-height: 0;
37309
+ overflow: hidden;
37310
+ transition: max-height 0.3s ease;
37311
+ background: rgba(0, 0, 0, 0.15);
37312
+ }
37313
+
37314
+ .myio-contract-summary-tooltip__domain.expanded .myio-contract-summary-tooltip__domain-details {
37315
+ max-height: 150px;
37316
+ }
37317
+
37318
+ .myio-contract-summary-tooltip__detail-row {
37319
+ display: flex;
37320
+ align-items: center;
37321
+ justify-content: space-between;
37322
+ padding: 6px 14px 6px 40px;
37323
+ font-size: 12px;
37324
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
37325
+ }
37326
+
37327
+ .myio-contract-summary-tooltip__detail-row:first-child {
37328
+ border-top: none;
37329
+ }
37330
+
37331
+ .myio-contract-summary-tooltip__detail-label {
37332
+ opacity: 0.7;
37333
+ display: flex;
37334
+ align-items: center;
37335
+ gap: 6px;
37336
+ }
37337
+
37338
+ .myio-contract-summary-tooltip__detail-label::before {
37339
+ content: '';
37340
+ width: 4px;
37341
+ height: 4px;
37342
+ border-radius: 50%;
37343
+ background: currentColor;
37344
+ opacity: 0.5;
37345
+ }
37346
+
37347
+ .myio-contract-summary-tooltip__detail-count {
37348
+ font-weight: 500;
37349
+ color: #81c784;
37350
+ }
37351
+
37352
+ /* Status Banner */
37353
+ .myio-contract-summary-tooltip__status {
37354
+ display: flex;
37355
+ align-items: center;
37356
+ justify-content: center;
37357
+ gap: 8px;
37358
+ padding: 10px 14px;
37359
+ border-radius: 10px;
37360
+ margin-bottom: 14px;
37361
+ font-size: 12px;
37362
+ font-weight: 600;
37363
+ }
37364
+
37365
+ .myio-contract-summary-tooltip__status.valid {
37366
+ background: rgba(76, 175, 80, 0.2);
37367
+ color: #81c784;
37368
+ border: 1px solid rgba(76, 175, 80, 0.3);
37369
+ }
37370
+
37371
+ .myio-contract-summary-tooltip__status.invalid {
37372
+ background: rgba(244, 67, 54, 0.2);
37373
+ color: #ef5350;
37374
+ border: 1px solid rgba(244, 67, 54, 0.3);
37375
+ }
37376
+
37377
+ .myio-contract-summary-tooltip__status-icon {
37378
+ font-size: 14px;
37379
+ }
37380
+
37381
+ /* Discrepancies */
37382
+ .myio-contract-summary-tooltip__discrepancies {
37383
+ background: rgba(244, 67, 54, 0.15);
37384
+ border: 1px solid rgba(244, 67, 54, 0.3);
37385
+ border-radius: 10px;
37386
+ padding: 10px 14px;
37387
+ margin-bottom: 14px;
37388
+ }
37389
+
37390
+ .myio-contract-summary-tooltip__discrepancies-title {
37391
+ font-size: 11px;
37392
+ font-weight: 600;
37393
+ color: #ef5350;
37394
+ margin-bottom: 6px;
37395
+ text-transform: uppercase;
37396
+ letter-spacing: 0.5px;
37397
+ }
37398
+
37399
+ .myio-contract-summary-tooltip__discrepancy-item {
37400
+ font-size: 11px;
37401
+ color: rgba(255, 255, 255, 0.8);
37402
+ padding: 3px 0;
37403
+ }
37404
+
37405
+ /* Footer */
37406
+ .myio-contract-summary-tooltip__footer {
37407
+ display: flex;
37408
+ justify-content: space-between;
37409
+ align-items: center;
37410
+ padding: 12px 16px;
37411
+ background: rgba(0, 0, 0, 0.2);
37412
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
37413
+ border-radius: 0 0 16px 16px;
37414
+ }
37415
+
37416
+ .myio-contract-summary-tooltip__footer-label {
37417
+ font-size: 10px;
37418
+ color: rgba(255, 255, 255, 0.5);
37419
+ }
37420
+
37421
+ .myio-contract-summary-tooltip__footer-value {
37422
+ font-size: 11px;
37423
+ font-weight: 600;
37424
+ color: rgba(255, 255, 255, 0.8);
37425
+ }
37426
+
37427
+ /* Total Devices Badge */
37428
+ .myio-contract-summary-tooltip__total {
37429
+ display: flex;
37430
+ align-items: center;
37431
+ justify-content: center;
37432
+ gap: 8px;
37433
+ padding: 12px;
37434
+ background: rgba(255, 255, 255, 0.08);
37435
+ border-radius: 10px;
37436
+ margin-bottom: 14px;
37437
+ }
37438
+
37439
+ .myio-contract-summary-tooltip__total-label {
37440
+ font-size: 12px;
37441
+ opacity: 0.8;
37442
+ }
37443
+
37444
+ .myio-contract-summary-tooltip__total-value {
37445
+ font-size: 18px;
37446
+ font-weight: 700;
37447
+ color: #81c784;
37448
+ }
37449
+
37450
+ /* Responsive */
37451
+ @media (max-width: 400px) {
37452
+ .myio-contract-summary-tooltip__panel {
37453
+ min-width: 280px;
37454
+ max-width: 95vw;
37455
+ }
37456
+ }
37457
+ `;
37458
+ var cssInjected9 = false;
37459
+ function injectCSS9() {
37460
+ if (cssInjected9) return;
37461
+ if (typeof document === "undefined") return;
37462
+ const styleId = "myio-contract-summary-tooltip-styles";
37463
+ if (document.getElementById(styleId)) {
37464
+ cssInjected9 = true;
37465
+ return;
37466
+ }
37467
+ const style = document.createElement("style");
37468
+ style.id = styleId;
37469
+ style.textContent = CONTRACT_SUMMARY_TOOLTIP_CSS;
37470
+ document.head.appendChild(style);
37471
+ cssInjected9 = true;
37472
+ }
37473
+ var state5 = {
37474
+ hideTimer: null,
37475
+ isMouseOverTooltip: false,
37476
+ isMaximized: false,
37477
+ isDragging: false,
37478
+ dragOffset: { x: 0, y: 0 },
37479
+ savedPosition: null,
37480
+ pinnedCounter: 0,
37481
+ expandedDomains: /* @__PURE__ */ new Set(["energy", "water", "temperature"])
37482
+ };
37483
+ function formatTimestamp5(isoString) {
37484
+ if (!isoString) return "Agora";
37485
+ try {
37486
+ const date = new Date(isoString);
37487
+ return date.toLocaleString("pt-BR", {
37488
+ hour: "2-digit",
37489
+ minute: "2-digit",
37490
+ day: "2-digit",
37491
+ month: "2-digit"
37492
+ });
37493
+ } catch {
37494
+ return "Agora";
37495
+ }
37496
+ }
37497
+ function calculateTotalDevices(data) {
37498
+ return data.energy.total + data.water.total + data.temperature.total;
37499
+ }
37500
+ function generateHeaderHTML5(data) {
37501
+ const iconClass = data.isValid ? "valid" : "invalid";
37502
+ const iconSymbol = data.isValid ? "\u2713" : "!";
37503
+ const totalDevices = calculateTotalDevices(data);
37504
+ return `
37505
+ <div class="myio-contract-summary-tooltip__header" data-drag-handle>
37506
+ <div class="myio-contract-summary-tooltip__icon ${iconClass}">${iconSymbol}</div>
37507
+ <div class="myio-contract-summary-tooltip__header-info">
37508
+ <div class="myio-contract-summary-tooltip__title">Contract Summary</div>
37509
+ <div class="myio-contract-summary-tooltip__subtitle">${totalDevices} devices loaded</div>
37510
+ </div>
37511
+ <div class="myio-contract-summary-tooltip__header-actions">
37512
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="pin" title="Pin to screen">
37513
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37514
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
37515
+ <line x1="12" y1="16" x2="12" y2="21"/>
37516
+ <line x1="8" y1="4" x2="16" y2="4"/>
37517
+ </svg>
37518
+ </button>
37519
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="maximize" title="Maximize">
37520
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37521
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
37522
+ </svg>
37523
+ </button>
37524
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="close" title="Close">
37525
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37526
+ <path d="M18 6L6 18M6 6l12 12"/>
37527
+ </svg>
37528
+ </button>
37529
+ </div>
37530
+ </div>
37531
+ `;
37532
+ }
37533
+ function generateDomainHTML(domain, icon, name, counts, isTemperature = false) {
37534
+ const isExpanded = state5.expandedDomains.has(domain);
37535
+ const expandedClass = isExpanded ? "expanded" : "";
37536
+ let detailsHTML = "";
37537
+ if (isTemperature) {
37538
+ const tempCounts = counts;
37539
+ detailsHTML = `
37540
+ <div class="myio-contract-summary-tooltip__detail-row">
37541
+ <span class="myio-contract-summary-tooltip__detail-label">Internal (Climate)</span>
37542
+ <span class="myio-contract-summary-tooltip__detail-count">${tempCounts.internal}</span>
37543
+ </div>
37544
+ <div class="myio-contract-summary-tooltip__detail-row">
37545
+ <span class="myio-contract-summary-tooltip__detail-label">Stores (Non-Climate)</span>
37546
+ <span class="myio-contract-summary-tooltip__detail-count">${tempCounts.stores}</span>
37547
+ </div>
37548
+ `;
37549
+ } else {
37550
+ const domainCounts = counts;
37551
+ detailsHTML = `
37552
+ <div class="myio-contract-summary-tooltip__detail-row">
37553
+ <span class="myio-contract-summary-tooltip__detail-label">Entries</span>
37554
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.entries}</span>
37555
+ </div>
37556
+ <div class="myio-contract-summary-tooltip__detail-row">
37557
+ <span class="myio-contract-summary-tooltip__detail-label">Common Area</span>
37558
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.commonArea}</span>
37559
+ </div>
37560
+ <div class="myio-contract-summary-tooltip__detail-row">
37561
+ <span class="myio-contract-summary-tooltip__detail-label">Stores</span>
37562
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.stores}</span>
37563
+ </div>
37564
+ `;
37565
+ }
37566
+ return `
37567
+ <div class="myio-contract-summary-tooltip__domain ${expandedClass}" data-domain="${domain}">
37568
+ <div class="myio-contract-summary-tooltip__domain-header" data-toggle-domain="${domain}">
37569
+ <div class="myio-contract-summary-tooltip__domain-info">
37570
+ <span class="myio-contract-summary-tooltip__domain-icon">${icon}</span>
37571
+ <span class="myio-contract-summary-tooltip__domain-name">${name}</span>
37572
+ </div>
37573
+ <div style="display: flex; align-items: center; gap: 8px;">
37574
+ <span class="myio-contract-summary-tooltip__domain-count">${counts.total} devices</span>
37575
+ <span class="myio-contract-summary-tooltip__expand-icon">\u25BC</span>
37576
+ </div>
37577
+ </div>
37578
+ <div class="myio-contract-summary-tooltip__domain-details">
37579
+ ${detailsHTML}
37580
+ </div>
37581
+ </div>
37582
+ `;
37583
+ }
37584
+ function generateBodyHTML3(data) {
37585
+ const totalDevices = calculateTotalDevices(data);
37586
+ const timestamp = formatTimestamp5(data.timestamp);
37587
+ const statusClass = data.isValid ? "valid" : "invalid";
37588
+ const statusIcon = data.isValid ? "\u2713" : "\u26A0";
37589
+ const statusText = data.isValid ? "Contract validated successfully" : "Validation issues detected";
37590
+ let discrepanciesHTML = "";
37591
+ if (data.discrepancies && data.discrepancies.length > 0) {
37592
+ const items = data.discrepancies.map((d) => `<div class="myio-contract-summary-tooltip__discrepancy-item">${d.domain}: expected ${d.expected}, found ${d.actual}</div>`).join("");
37593
+ discrepanciesHTML = `
37594
+ <div class="myio-contract-summary-tooltip__discrepancies">
37595
+ <div class="myio-contract-summary-tooltip__discrepancies-title">Discrepancies</div>
37596
+ ${items}
37597
+ </div>
37598
+ `;
37599
+ }
37600
+ return `
37601
+ <div class="myio-contract-summary-tooltip__body">
37602
+ <!-- Status Banner -->
37603
+ <div class="myio-contract-summary-tooltip__status ${statusClass}">
37604
+ <span class="myio-contract-summary-tooltip__status-icon">${statusIcon}</span>
37605
+ <span>${statusText}</span>
37606
+ </div>
37607
+
37608
+ ${discrepanciesHTML}
37609
+
37610
+ <!-- Total Devices -->
37611
+ <div class="myio-contract-summary-tooltip__total">
37612
+ <span class="myio-contract-summary-tooltip__total-label">Total Devices:</span>
37613
+ <span class="myio-contract-summary-tooltip__total-value">${totalDevices}</span>
37614
+ </div>
37615
+
37616
+ <!-- Energy -->
37617
+ ${generateDomainHTML("energy", "\u26A1", "Energy", data.energy)}
37618
+
37619
+ <!-- Water -->
37620
+ ${generateDomainHTML("water", "\u{1F4A7}", "Water", data.water)}
37621
+
37622
+ <!-- Temperature -->
37623
+ ${generateDomainHTML("temperature", "\u{1F321}\uFE0F", "Temperature", data.temperature, true)}
37624
+ </div>
37625
+
37626
+ <!-- Footer -->
37627
+ <div class="myio-contract-summary-tooltip__footer">
37628
+ <span class="myio-contract-summary-tooltip__footer-label">Loaded at</span>
37629
+ <span class="myio-contract-summary-tooltip__footer-value">${timestamp}</span>
37630
+ </div>
37631
+ `;
37632
+ }
37633
+ function setupHoverListeners5(container) {
37634
+ container.onmouseenter = () => {
37635
+ state5.isMouseOverTooltip = true;
37636
+ if (state5.hideTimer) {
37637
+ clearTimeout(state5.hideTimer);
37638
+ state5.hideTimer = null;
37639
+ }
37640
+ };
37641
+ container.onmouseleave = () => {
37642
+ state5.isMouseOverTooltip = false;
37643
+ startDelayedHide5();
37644
+ };
37645
+ }
37646
+ function setupDomainToggleListeners(container) {
37647
+ const toggles = container.querySelectorAll("[data-toggle-domain]");
37648
+ toggles.forEach((toggle) => {
37649
+ toggle.onclick = (e) => {
37650
+ e.stopPropagation();
37651
+ const domain = toggle.dataset.toggleDomain;
37652
+ if (!domain) return;
37653
+ const domainEl = container.querySelector(`[data-domain="${domain}"]`);
37654
+ if (!domainEl) return;
37655
+ if (state5.expandedDomains.has(domain)) {
37656
+ state5.expandedDomains.delete(domain);
37657
+ domainEl.classList.remove("expanded");
37658
+ } else {
37659
+ state5.expandedDomains.add(domain);
37660
+ domainEl.classList.add("expanded");
37661
+ }
37662
+ };
37663
+ });
37664
+ }
37665
+ function setupButtonListeners5(container) {
37666
+ const buttons = container.querySelectorAll("[data-action]");
37667
+ buttons.forEach((btn) => {
37668
+ btn.onclick = (e) => {
37669
+ e.stopPropagation();
37670
+ const action = btn.dataset.action;
37671
+ switch (action) {
37672
+ case "pin":
37673
+ createPinnedClone5(container);
37674
+ break;
37675
+ case "maximize":
37676
+ toggleMaximize5(container);
37677
+ break;
37678
+ case "close":
37679
+ ContractSummaryTooltip.close();
37680
+ break;
37681
+ }
37682
+ };
37683
+ });
37684
+ }
37685
+ function setupDragListeners5(container) {
37686
+ const header = container.querySelector("[data-drag-handle]");
37687
+ if (!header) return;
37688
+ header.onmousedown = (e) => {
37689
+ if (e.target.closest("[data-action]")) return;
37690
+ if (e.target.closest("[data-toggle-domain]")) return;
37691
+ if (state5.isMaximized) return;
37692
+ state5.isDragging = true;
37693
+ container.classList.add("dragging");
37694
+ const rect = container.getBoundingClientRect();
37695
+ state5.dragOffset = {
37696
+ x: e.clientX - rect.left,
37697
+ y: e.clientY - rect.top
37698
+ };
37699
+ const onMouseMove = (e2) => {
37700
+ if (!state5.isDragging) return;
37701
+ const newLeft = e2.clientX - state5.dragOffset.x;
37702
+ const newTop = e2.clientY - state5.dragOffset.y;
37703
+ const maxLeft = window.innerWidth - container.offsetWidth;
37704
+ const maxTop = window.innerHeight - container.offsetHeight;
37705
+ container.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
37706
+ container.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
37707
+ };
37708
+ const onMouseUp = () => {
37709
+ state5.isDragging = false;
37710
+ container.classList.remove("dragging");
37711
+ document.removeEventListener("mousemove", onMouseMove);
37712
+ document.removeEventListener("mouseup", onMouseUp);
37713
+ };
37714
+ document.addEventListener("mousemove", onMouseMove);
37715
+ document.addEventListener("mouseup", onMouseUp);
37716
+ };
37717
+ }
37718
+ function createPinnedClone5(container) {
37719
+ state5.pinnedCounter++;
37720
+ const pinnedId = `myio-contract-summary-tooltip-pinned-${state5.pinnedCounter}`;
37721
+ const clone = container.cloneNode(true);
37722
+ clone.id = pinnedId;
37723
+ clone.classList.add("pinned");
37724
+ clone.classList.remove("closing");
37725
+ const pinBtn = clone.querySelector('[data-action="pin"]');
37726
+ if (pinBtn) {
37727
+ pinBtn.classList.add("pinned");
37728
+ pinBtn.setAttribute("title", "Unpin");
37729
+ pinBtn.innerHTML = `
37730
+ <svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1">
37731
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
37732
+ <line x1="12" y1="16" x2="12" y2="21"/>
37733
+ <line x1="8" y1="4" x2="16" y2="4"/>
37734
+ </svg>
37735
+ `;
37736
+ }
37737
+ document.body.appendChild(clone);
37738
+ setupPinnedCloneListeners5(clone, pinnedId);
37739
+ ContractSummaryTooltip.hide();
37740
+ }
37741
+ function setupPinnedCloneListeners5(clone, cloneId) {
37742
+ let isMaximized = false;
37743
+ let savedPosition = null;
37744
+ const cloneExpandedDomains = new Set(state5.expandedDomains);
37745
+ const toggles = clone.querySelectorAll("[data-toggle-domain]");
37746
+ toggles.forEach((toggle) => {
37747
+ toggle.onclick = (e) => {
37748
+ e.stopPropagation();
37749
+ const domain = toggle.dataset.toggleDomain;
37750
+ if (!domain) return;
37751
+ const domainEl = clone.querySelector(`[data-domain="${domain}"]`);
37752
+ if (!domainEl) return;
37753
+ if (cloneExpandedDomains.has(domain)) {
37754
+ cloneExpandedDomains.delete(domain);
37755
+ domainEl.classList.remove("expanded");
37756
+ } else {
37757
+ cloneExpandedDomains.add(domain);
37758
+ domainEl.classList.add("expanded");
37759
+ }
37760
+ };
37761
+ });
37762
+ const pinBtn = clone.querySelector('[data-action="pin"]');
37763
+ if (pinBtn) {
37764
+ pinBtn.onclick = (e) => {
37765
+ e.stopPropagation();
37766
+ closePinnedClone5(cloneId);
37767
+ };
37768
+ }
37769
+ const closeBtn = clone.querySelector('[data-action="close"]');
37770
+ if (closeBtn) {
37771
+ closeBtn.onclick = (e) => {
37772
+ e.stopPropagation();
37773
+ closePinnedClone5(cloneId);
37774
+ };
37775
+ }
37776
+ const maxBtn = clone.querySelector('[data-action="maximize"]');
37777
+ if (maxBtn) {
37778
+ maxBtn.onclick = (e) => {
37779
+ e.stopPropagation();
37780
+ isMaximized = !isMaximized;
37781
+ if (isMaximized) {
37782
+ savedPosition = { left: clone.style.left, top: clone.style.top };
37783
+ maxBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="5" width="14" height="14" rx="2"/><path d="M9 5V3h12v12h-2"/></svg>`;
37784
+ maxBtn.setAttribute("title", "Restore");
37785
+ } else {
37786
+ maxBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>`;
37787
+ maxBtn.setAttribute("title", "Maximize");
37788
+ if (savedPosition) {
37789
+ clone.style.left = savedPosition.left;
37790
+ clone.style.top = savedPosition.top;
37791
+ }
37792
+ }
37793
+ clone.classList.toggle("maximized", isMaximized);
37794
+ };
37795
+ }
37796
+ const header = clone.querySelector("[data-drag-handle]");
37797
+ if (header) {
37798
+ let isDragging = false;
37799
+ let dragOffset = { x: 0, y: 0 };
37800
+ header.onmousedown = (e) => {
37801
+ if (e.target.closest("[data-action]")) return;
37802
+ if (e.target.closest("[data-toggle-domain]")) return;
37803
+ if (isMaximized) return;
37804
+ isDragging = true;
37805
+ clone.classList.add("dragging");
37806
+ const rect = clone.getBoundingClientRect();
37807
+ dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
37808
+ const onMouseMove = (e2) => {
37809
+ if (!isDragging) return;
37810
+ const newLeft = e2.clientX - dragOffset.x;
37811
+ const newTop = e2.clientY - dragOffset.y;
37812
+ const maxLeft = window.innerWidth - clone.offsetWidth;
37813
+ const maxTop = window.innerHeight - clone.offsetHeight;
37814
+ clone.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
37815
+ clone.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
37816
+ };
37817
+ const onMouseUp = () => {
37818
+ isDragging = false;
37819
+ clone.classList.remove("dragging");
37820
+ document.removeEventListener("mousemove", onMouseMove);
37821
+ document.removeEventListener("mouseup", onMouseUp);
37822
+ };
37823
+ document.addEventListener("mousemove", onMouseMove);
37824
+ document.addEventListener("mouseup", onMouseUp);
37825
+ };
37826
+ }
37827
+ }
37828
+ function closePinnedClone5(cloneId) {
37829
+ const clone = document.getElementById(cloneId);
37830
+ if (clone) {
37831
+ clone.classList.add("closing");
37832
+ setTimeout(() => clone.remove(), 400);
37833
+ }
37834
+ }
37835
+ function toggleMaximize5(container) {
37836
+ state5.isMaximized = !state5.isMaximized;
37837
+ if (state5.isMaximized) {
37838
+ state5.savedPosition = {
37839
+ left: container.style.left,
37840
+ top: container.style.top
37841
+ };
37842
+ }
37843
+ container.classList.toggle("maximized", state5.isMaximized);
37844
+ const maxBtn = container.querySelector('[data-action="maximize"]');
37845
+ if (maxBtn) {
37846
+ if (state5.isMaximized) {
37847
+ maxBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="5" width="14" height="14" rx="2"/><path d="M9 5V3h12v12h-2"/></svg>`;
37848
+ maxBtn.setAttribute("title", "Restore");
37849
+ } else {
37850
+ maxBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>`;
37851
+ maxBtn.setAttribute("title", "Maximize");
37852
+ if (state5.savedPosition) {
37853
+ container.style.left = state5.savedPosition.left;
37854
+ container.style.top = state5.savedPosition.top;
37855
+ }
37856
+ }
37857
+ }
37858
+ }
37859
+ function startDelayedHide5() {
37860
+ if (state5.isMouseOverTooltip) return;
37861
+ if (state5.hideTimer) {
37862
+ clearTimeout(state5.hideTimer);
37863
+ }
37864
+ state5.hideTimer = setTimeout(() => {
37865
+ hideWithAnimation5();
37866
+ }, 1500);
37867
+ }
37868
+ function hideWithAnimation5() {
37869
+ const container = document.getElementById("myio-contract-summary-tooltip");
37870
+ if (container && container.classList.contains("visible")) {
37871
+ container.classList.add("closing");
37872
+ setTimeout(() => {
37873
+ container.classList.remove("visible", "closing");
37874
+ }, 400);
37875
+ }
37876
+ }
37877
+ function positionTooltip5(container, triggerElement) {
37878
+ const rect = triggerElement.getBoundingClientRect();
37879
+ let left = rect.left;
37880
+ let top = rect.bottom + 8;
37881
+ const tooltipWidth = 360;
37882
+ if (left + tooltipWidth > window.innerWidth - 20) {
37883
+ left = window.innerWidth - tooltipWidth - 20;
37884
+ }
37885
+ if (left < 10) left = 10;
37886
+ if (top + 500 > window.innerHeight) {
37887
+ top = rect.top - 8 - 500;
37888
+ if (top < 10) top = 10;
37889
+ }
37890
+ container.style.left = left + "px";
37891
+ container.style.top = top + "px";
37892
+ }
37893
+ var ContractSummaryTooltip = {
37894
+ containerId: "myio-contract-summary-tooltip",
37895
+ /**
37896
+ * Get or create container
37897
+ */
37898
+ getContainer() {
37899
+ injectCSS9();
37900
+ let container = document.getElementById(this.containerId);
37901
+ if (!container) {
37902
+ container = document.createElement("div");
37903
+ container.id = this.containerId;
37904
+ container.className = "myio-contract-summary-tooltip";
37905
+ document.body.appendChild(container);
37906
+ }
37907
+ return container;
37908
+ },
37909
+ /**
37910
+ * Show tooltip
37911
+ */
37912
+ show(triggerElement, data) {
37913
+ if (state5.hideTimer) {
37914
+ clearTimeout(state5.hideTimer);
37915
+ state5.hideTimer = null;
37916
+ }
37917
+ const container = this.getContainer();
37918
+ container.classList.remove("closing");
37919
+ container.innerHTML = `
37920
+ <div class="myio-contract-summary-tooltip__panel">
37921
+ ${generateHeaderHTML5(data)}
37922
+ ${generateBodyHTML3(data)}
37923
+ </div>
37924
+ `;
37925
+ positionTooltip5(container, triggerElement);
37926
+ container.classList.add("visible");
37927
+ setupHoverListeners5(container);
37928
+ setupButtonListeners5(container);
37929
+ setupDragListeners5(container);
37930
+ setupDomainToggleListeners(container);
37931
+ },
37932
+ /**
37933
+ * Start delayed hide
37934
+ */
37935
+ startDelayedHide() {
37936
+ startDelayedHide5();
37937
+ },
37938
+ /**
37939
+ * Hide immediately
37940
+ */
37941
+ hide() {
37942
+ if (state5.hideTimer) {
37943
+ clearTimeout(state5.hideTimer);
37944
+ state5.hideTimer = null;
37945
+ }
37946
+ state5.isMouseOverTooltip = false;
37947
+ const container = document.getElementById(this.containerId);
37948
+ if (container) {
37949
+ container.classList.remove("visible", "closing");
37950
+ }
37951
+ },
37952
+ /**
37953
+ * Close and reset all states
37954
+ */
37955
+ close() {
37956
+ state5.isMaximized = false;
37957
+ state5.isDragging = false;
37958
+ state5.savedPosition = null;
37959
+ if (state5.hideTimer) {
37960
+ clearTimeout(state5.hideTimer);
37961
+ state5.hideTimer = null;
37962
+ }
37963
+ state5.isMouseOverTooltip = false;
37964
+ const container = document.getElementById(this.containerId);
37965
+ if (container) {
37966
+ container.classList.remove("visible", "pinned", "maximized", "dragging", "closing");
37967
+ }
37968
+ },
37969
+ /**
37970
+ * Attach tooltip to trigger element with click behavior
37971
+ */
37972
+ attach(triggerElement, getDataFn) {
37973
+ const self = this;
37974
+ const handleClick = () => {
37975
+ if (state5.hideTimer) {
37976
+ clearTimeout(state5.hideTimer);
37977
+ state5.hideTimer = null;
37978
+ }
37979
+ const data = getDataFn();
37980
+ if (data) {
37981
+ self.show(triggerElement, data);
37982
+ }
37983
+ };
37984
+ triggerElement.addEventListener("click", handleClick);
37985
+ return () => {
37986
+ triggerElement.removeEventListener("click", handleClick);
37987
+ self.hide();
37988
+ };
37989
+ },
37990
+ /**
37991
+ * Attach tooltip with hover behavior
37992
+ */
37993
+ attachHover(triggerElement, getDataFn) {
37994
+ const self = this;
37995
+ const handleMouseEnter = () => {
37996
+ if (state5.hideTimer) {
37997
+ clearTimeout(state5.hideTimer);
37998
+ state5.hideTimer = null;
37999
+ }
38000
+ const data = getDataFn();
38001
+ if (data) {
38002
+ self.show(triggerElement, data);
38003
+ }
38004
+ };
38005
+ const handleMouseLeave = () => {
38006
+ startDelayedHide5();
38007
+ };
38008
+ triggerElement.addEventListener("mouseenter", handleMouseEnter);
38009
+ triggerElement.addEventListener("mouseleave", handleMouseLeave);
38010
+ return () => {
38011
+ triggerElement.removeEventListener("mouseenter", handleMouseEnter);
38012
+ triggerElement.removeEventListener("mouseleave", handleMouseLeave);
38013
+ self.hide();
38014
+ };
38015
+ },
38016
+ /**
38017
+ * Build contract data from window.CONTRACT_STATE
38018
+ * Helper method to build the data structure from global state
38019
+ */
38020
+ buildFromGlobalState() {
38021
+ const globalState = window.CONTRACT_STATE;
38022
+ if (!globalState) return null;
38023
+ return {
38024
+ isLoaded: globalState.isLoaded ?? false,
38025
+ isValid: globalState.isValid ?? false,
38026
+ timestamp: globalState.timestamp ?? null,
38027
+ energy: {
38028
+ total: globalState.energy?.total ?? 0,
38029
+ entries: globalState.energy?.entries ?? 0,
38030
+ commonArea: globalState.energy?.commonArea ?? 0,
38031
+ stores: globalState.energy?.stores ?? 0
38032
+ },
38033
+ water: {
38034
+ total: globalState.water?.total ?? 0,
38035
+ entries: globalState.water?.entries ?? 0,
38036
+ commonArea: globalState.water?.commonArea ?? 0,
38037
+ stores: globalState.water?.stores ?? 0
38038
+ },
38039
+ temperature: {
38040
+ total: globalState.temperature?.total ?? 0,
38041
+ internal: globalState.temperature?.internal ?? 0,
38042
+ stores: globalState.temperature?.stores ?? 0
38043
+ },
38044
+ discrepancies: globalState.discrepancies
38045
+ };
38046
+ }
38047
+ };
38048
+
37052
38049
  // src/components/ModalHeader/index.ts
37053
38050
  var DEFAULT_BG_COLOR = "#3e1a7d";
37054
38051
  var DEFAULT_TEXT_COLOR = "white";
@@ -40807,6 +41804,7 @@
40807
41804
  exports.CONSUMPTION_CHART_DEFAULTS = DEFAULT_CONFIG;
40808
41805
  exports.CONSUMPTION_THEME_COLORS = THEME_COLORS;
40809
41806
  exports.ConnectionStatusType = ConnectionStatusType;
41807
+ exports.ContractSummaryTooltip = ContractSummaryTooltip;
40810
41808
  exports.DEFAULT_CLAMP_RANGE = DEFAULT_CLAMP_RANGE;
40811
41809
  exports.DEFAULT_ENERGY_GROUP_COLORS = DEFAULT_ENERGY_GROUP_COLORS;
40812
41810
  exports.DEFAULT_GAS_GROUP_COLORS = DEFAULT_GAS_GROUP_COLORS;