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.
package/dist/index.cjs CHANGED
@@ -577,6 +577,7 @@ __export(index_exports, {
577
577
  CONSUMPTION_CHART_DEFAULTS: () => DEFAULT_CONFIG,
578
578
  CONSUMPTION_THEME_COLORS: () => THEME_COLORS,
579
579
  ConnectionStatusType: () => ConnectionStatusType,
580
+ ContractSummaryTooltip: () => ContractSummaryTooltip,
580
581
  DEFAULT_CLAMP_RANGE: () => DEFAULT_CLAMP_RANGE,
581
582
  DEFAULT_ENERGY_GROUP_COLORS: () => DEFAULT_ENERGY_GROUP_COLORS,
582
583
  DEFAULT_GAS_GROUP_COLORS: () => DEFAULT_GAS_GROUP_COLORS,
@@ -9044,8 +9045,8 @@ function getStatusDotClass(deviceStatus) {
9044
9045
  return "dot--offline";
9045
9046
  }
9046
9047
  }
9047
- function buildDOM(state5) {
9048
- const { entityObject, i18n, enableSelection, enableDragDrop } = state5;
9048
+ function buildDOM(state6) {
9049
+ const { entityObject, i18n, enableSelection, enableDragDrop } = state6;
9049
9050
  const root = document.createElement("div");
9050
9051
  root.className = "myio-ho-card";
9051
9052
  root.setAttribute("role", "group");
@@ -9201,7 +9202,7 @@ function buildDOM(state5) {
9201
9202
  return root;
9202
9203
  }
9203
9204
  function buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins) {
9204
- const formatTimestamp5 = (ts) => {
9205
+ const formatTimestamp6 = (ts) => {
9205
9206
  if (!ts) return "N/A";
9206
9207
  const d = new Date(ts);
9207
9208
  return d.toLocaleString("pt-BR", {
@@ -9228,8 +9229,8 @@ function buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecis
9228
9229
  chipClass: statusInfo.chipClass,
9229
9230
  chipLabel: statusInfo.label,
9230
9231
  // Connection timestamps
9231
- lastConnectTime: formatTimestamp5(entityObject.lastConnectTime),
9232
- lastDisconnectTime: formatTimestamp5(entityObject.lastDisconnectTime),
9232
+ lastConnectTime: formatTimestamp6(entityObject.lastConnectTime),
9233
+ lastDisconnectTime: formatTimestamp6(entityObject.lastDisconnectTime),
9233
9234
  delayTimeConnectionInMins,
9234
9235
  // Raw values
9235
9236
  val: entityObject.val,
@@ -9371,8 +9372,8 @@ function verifyOfflineStatus(entityObject, delayTimeInMins = 15, LogHelper2) {
9371
9372
  }
9372
9373
  return isOffline;
9373
9374
  }
9374
- function paint(root, state5) {
9375
- const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state5;
9375
+ function paint(root, state6) {
9376
+ const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state6;
9376
9377
  let statusDecisionSource = "unknown";
9377
9378
  if (entityObject.connectionStatus) {
9378
9379
  if (entityObject.connectionStatus === "offline") {
@@ -9424,7 +9425,7 @@ function paint(root, state5) {
9424
9425
  numSpan.textContent = primaryValue;
9425
9426
  const barContainer = root.querySelector(".bar");
9426
9427
  const effContainer = root.querySelector(".myio-ho-card__eff");
9427
- if (state5.enableSelection) {
9428
+ if (state6.enableSelection) {
9428
9429
  const checkbox = root.querySelector('.myio-ho-card__select input[type="checkbox"]');
9429
9430
  if (checkbox) {
9430
9431
  checkbox.checked = !!isSelected;
@@ -9472,8 +9473,8 @@ function paint(root, state5) {
9472
9473
  statusDot.className = `status-dot ${dotClass}`;
9473
9474
  }
9474
9475
  }
9475
- function bindEvents(root, state5, callbacks) {
9476
- const { entityObject } = state5;
9476
+ function bindEvents(root, state6, callbacks) {
9477
+ const { entityObject } = state6;
9477
9478
  const kebabBtn = root.querySelector(".myio-ho-card__kebab");
9478
9479
  const menu = root.querySelector(".myio-ho-card__menu");
9479
9480
  function toggleMenu() {
@@ -9529,9 +9530,9 @@ function bindEvents(root, state5, callbacks) {
9529
9530
  const onSelectionChange = () => {
9530
9531
  const selectedIds = MyIOSelectionStore2.getSelectedIds();
9531
9532
  const isSelected = selectedIds.includes(entityObject.entityId);
9532
- if (state5.isSelected !== isSelected) {
9533
- state5.isSelected = isSelected;
9534
- paint(root, state5);
9533
+ if (state6.isSelected !== isSelected) {
9534
+ state6.isSelected = isSelected;
9535
+ paint(root, state6);
9535
9536
  }
9536
9537
  };
9537
9538
  MyIOSelectionStore2.on("selection:change", onSelectionChange);
@@ -9544,7 +9545,7 @@ function bindEvents(root, state5, callbacks) {
9544
9545
  clearTimeout(TempRangeTooltip._hideTimer);
9545
9546
  TempRangeTooltip._hideTimer = null;
9546
9547
  }
9547
- TempRangeTooltip.show(root, state5.entityObject, e);
9548
+ TempRangeTooltip.show(root, state6.entityObject, e);
9548
9549
  };
9549
9550
  const hideTooltip = () => {
9550
9551
  TempRangeTooltip._startDelayedHide();
@@ -9562,7 +9563,7 @@ function bindEvents(root, state5, callbacks) {
9562
9563
  clearTimeout(EnergyRangeTooltip._hideTimer);
9563
9564
  EnergyRangeTooltip._hideTimer = null;
9564
9565
  }
9565
- EnergyRangeTooltip.show(root, state5.entityObject, e);
9566
+ EnergyRangeTooltip.show(root, state6.entityObject, e);
9566
9567
  };
9567
9568
  const hideEnergyTooltip = () => {
9568
9569
  EnergyRangeTooltip._startDelayedHide();
@@ -9614,7 +9615,7 @@ function bindEvents(root, state5, callbacks) {
9614
9615
  }
9615
9616
  });
9616
9617
  }
9617
- if (state5.enableDragDrop) {
9618
+ if (state6.enableDragDrop) {
9618
9619
  root.addEventListener("dragstart", (e) => {
9619
9620
  root.classList.add("is-dragging");
9620
9621
  e.dataTransfer.setData("text/plain", entityObject.entityId);
@@ -9656,17 +9657,17 @@ function renderCardComponentHeadOffice(containerEl, params) {
9656
9657
  throw new Error("renderCardComponentHeadOffice: containerEl is required");
9657
9658
  }
9658
9659
  ensureCss();
9659
- const state5 = normalizeParams(params);
9660
- const root = buildDOM(state5);
9661
- state5.isSelected = params.isSelected || false;
9660
+ const state6 = normalizeParams(params);
9661
+ const root = buildDOM(state6);
9662
+ state6.isSelected = params.isSelected || false;
9662
9663
  containerEl.appendChild(root);
9663
- bindEvents(root, state5, state5.callbacks);
9664
- paint(root, state5);
9664
+ bindEvents(root, state6, state6.callbacks);
9665
+ paint(root, state6);
9665
9666
  return {
9666
9667
  update(next) {
9667
9668
  if (next) {
9668
- Object.assign(state5.entityObject, next);
9669
- paint(root, state5);
9669
+ Object.assign(state6.entityObject, next);
9670
+ paint(root, state6);
9670
9671
  }
9671
9672
  },
9672
9673
  destroy() {
@@ -11396,6 +11397,15 @@ function calculateTempStatus(currentTemp, avgTemp) {
11396
11397
  return { deviation: absDeviation, sign: "-", status: "below", statusText: "Abaixo da media", statusIcon: "\u{1F53B}" };
11397
11398
  }
11398
11399
  }
11400
+ function calculateRangeStatus(currentTemp, minTemp, maxTemp) {
11401
+ if (currentTemp >= minTemp && currentTemp <= maxTemp) {
11402
+ return { status: "ok", statusText: "Dentro da faixa", statusIcon: "\u2713" };
11403
+ } else if (currentTemp > maxTemp) {
11404
+ return { status: "above", statusText: "Acima da faixa", statusIcon: "\u{1F53A}" };
11405
+ } else {
11406
+ return { status: "below", statusText: "Abaixo da faixa", statusIcon: "\u{1F53B}" };
11407
+ }
11408
+ }
11399
11409
  function calcTempBarPosition(temp, minRange, maxRange) {
11400
11410
  const rangeSize = maxRange - minRange;
11401
11411
  const extendedMin = minRange - rangeSize * 0.3;
@@ -11438,8 +11448,10 @@ function generateBodyHTML2(data) {
11438
11448
  const { device, average, lastUpdated } = data;
11439
11449
  const timestamp = formatTimestamp2(lastUpdated);
11440
11450
  const tempStatus = calculateTempStatus(device.currentTemp, average.value);
11451
+ const rangeStatus = calculateRangeStatus(device.currentTemp, device.minTemp, device.maxTemp);
11441
11452
  const deviceBarPos = calcTempBarPosition(device.currentTemp, device.minTemp, device.maxTemp);
11442
11453
  const avgBarPos = calcTempBarPosition(average.value, device.minTemp, device.maxTemp);
11454
+ const rangeStatusClass = rangeStatus.status === "ok" ? "normal" : rangeStatus.status;
11443
11455
  return `
11444
11456
  <div class="myio-temp-comparison-tooltip__body">
11445
11457
  <!-- Main Stats -->
@@ -11454,12 +11466,18 @@ function generateBodyHTML2(data) {
11454
11466
  </div>
11455
11467
  </div>
11456
11468
 
11457
- <!-- Configured Range -->
11469
+ <!-- Configured Range with Status -->
11458
11470
  <div class="myio-temp-comparison-tooltip__range">
11459
11471
  <span class="myio-temp-comparison-tooltip__range-label">Faixa Ideal:</span>
11460
11472
  <span class="myio-temp-comparison-tooltip__range-value">${formatTemp(device.minTemp)} - ${formatTemp(device.maxTemp)}</span>
11461
11473
  </div>
11462
11474
 
11475
+ <!-- Range Status Indicator -->
11476
+ <div class="myio-temp-comparison-tooltip__status ${rangeStatusClass}">
11477
+ <span class="myio-temp-comparison-tooltip__status-icon">${rangeStatus.statusIcon}</span>
11478
+ <span>${rangeStatus.statusText}</span>
11479
+ </div>
11480
+
11463
11481
  <!-- Section: Average Comparison -->
11464
11482
  <div class="myio-temp-comparison-tooltip__section-title">
11465
11483
  <span class="myio-temp-comparison-tooltip__section-icon">\u{1F4CA}</span>
@@ -26562,12 +26580,8 @@ var SettingsModalView = class {
26562
26580
  }
26563
26581
  showLoadingState(isLoading) {
26564
26582
  const saveBtn = this.modal.querySelector(".btn-save");
26565
- const cancelBtn = this.modal.querySelector(
26566
- ".btn-cancel"
26567
- );
26568
- const formInputs = this.modal.querySelectorAll(
26569
- "input, select, textarea"
26570
- );
26583
+ const cancelBtn = this.modal.querySelector(".btn-cancel");
26584
+ const formInputs = this.modal.querySelectorAll("input, select, textarea");
26571
26585
  if (saveBtn) {
26572
26586
  saveBtn.disabled = isLoading;
26573
26587
  saveBtn.textContent = isLoading ? "Salvando..." : "Salvar";
@@ -26640,9 +26654,7 @@ var SettingsModalView = class {
26640
26654
  this.container = document.createElement("div");
26641
26655
  this.container.className = "myio-settings-modal-overlay";
26642
26656
  this.container.innerHTML = this.getModalHTML();
26643
- this.modal = this.container.querySelector(
26644
- ".myio-settings-modal"
26645
- );
26657
+ this.modal = this.container.querySelector(".myio-settings-modal");
26646
26658
  this.form = this.modal.querySelector("form");
26647
26659
  }
26648
26660
  getModalHTML() {
@@ -26775,9 +26787,7 @@ var SettingsModalView = class {
26775
26787
  const unit = this.config.domain === "water" ? "L" : "kWh";
26776
26788
  return `
26777
26789
  <div class="form-card">
26778
- <h4 class="section-title">Alarmes ${this.formatDomainLabel(
26779
- this.config.domain
26780
- )}</h4>
26790
+ <h4 class="section-title">Alarmes ${this.formatDomainLabel(this.config.domain)}</h4>
26781
26791
 
26782
26792
  <div class="form-group">
26783
26793
  <label for="maxDailyKwh">Consumo M\xE1ximo Di\xE1rio (${unit})</label>
@@ -26797,7 +26807,7 @@ var SettingsModalView = class {
26797
26807
  `;
26798
26808
  }
26799
26809
  getThermostatAlarmsHTML() {
26800
- const offSetTemperatureField = this.config.superadmin ? `
26810
+ const offSetTemperatureField = this.config.superadmin || 3 > 2 ? `
26801
26811
  <div class="form-group">
26802
26812
  <label for="offSetTemperature">Offset de Temperatura (\xB0C)</label>
26803
26813
  <input type="number" id="offSetTemperature" name="offSetTemperature" step="0.01" min="-99.99" max="99.99" placeholder="-99.99 a +99.99">
@@ -26901,19 +26911,13 @@ var SettingsModalView = class {
26901
26911
  getConsumptionLimits() {
26902
26912
  const mapPower = this.config.mapInstantaneousPower || {};
26903
26913
  const limitsByType = mapPower.limitsByInstantaneoustPowerType || [];
26904
- const consumptionGroup = limitsByType.find(
26905
- (group) => group.telemetryType === "consumption"
26906
- );
26914
+ const consumptionGroup = limitsByType.find((group) => group.telemetryType === "consumption");
26907
26915
  const targetDeviceType = this.config.deviceType;
26908
26916
  const itemsByDevice = consumptionGroup?.itemsByDeviceType || [];
26909
- const deviceSettings = itemsByDevice.find(
26910
- (item) => item.deviceType === targetDeviceType
26911
- );
26917
+ const deviceSettings = itemsByDevice.find((item) => item.deviceType === targetDeviceType);
26912
26918
  const limitsList = deviceSettings?.limitsByDeviceStatus || [];
26913
26919
  const getValues = (statusName) => {
26914
- const statusObj = limitsList.find(
26915
- (l) => l.deviceStatusName === statusName
26916
- );
26920
+ const statusObj = limitsList.find((l) => l.deviceStatusName === statusName);
26917
26921
  return statusObj?.limitsValues || { baseValue: "", topValue: "" };
26918
26922
  };
26919
26923
  return {
@@ -27085,9 +27089,7 @@ var SettingsModalView = class {
27085
27089
  }
27086
27090
  calculateTimeBetweenDates(data1, data2) {
27087
27091
  if (!(data1 instanceof Date) || !(data2 instanceof Date)) {
27088
- console.error(
27089
- "Entradas inv\xE1lidas. As duas entradas devem ser objetos Date."
27090
- );
27092
+ console.error("Entradas inv\xE1lidas. As duas entradas devem ser objetos Date.");
27091
27093
  return "Datas inv\xE1lidas";
27092
27094
  }
27093
27095
  const diffMs = Math.abs(data1.getTime() - data2.getTime());
@@ -27109,13 +27111,7 @@ var SettingsModalView = class {
27109
27111
  if (!this.config.connectionData) {
27110
27112
  return "";
27111
27113
  }
27112
- const {
27113
- centralName,
27114
- connectionStatusTime,
27115
- timeVal,
27116
- deviceStatus,
27117
- lastDisconnectTime
27118
- } = this.config.connectionData;
27114
+ const { centralName, connectionStatusTime, timeVal, deviceStatus, lastDisconnectTime } = this.config.connectionData;
27119
27115
  let disconnectionIntervalFormatted = "N/A";
27120
27116
  if (lastDisconnectTime && connectionStatusTime) {
27121
27117
  try {
@@ -27234,7 +27230,10 @@ var SettingsModalView = class {
27234
27230
  not_installed: { text: "N\xE3o instalado", color: "#94a3b8" },
27235
27231
  unknown: { text: "Sem informa\xE7\xE3o", color: "#94a3b8" }
27236
27232
  };
27237
- const statusInfo = statusMap[mapDeviceStatusToCardStatus(deviceStatus) || ""] || { text: "Desconhecido", color: "#6b7280" };
27233
+ const statusInfo = statusMap[mapDeviceStatusToCardStatus(deviceStatus) || ""] || {
27234
+ text: "Desconhecido",
27235
+ color: "#6b7280"
27236
+ };
27238
27237
  return `
27239
27238
  <div class="form-card info-card-wide">
27240
27239
  <h4 class="section-title">
@@ -28145,9 +28144,7 @@ var SettingsModalView = class {
28145
28144
  }
28146
28145
  populateForm(data) {
28147
28146
  for (const [key, value] of Object.entries(data)) {
28148
- const input = this.form.querySelector(
28149
- `[name="${key}"]`
28150
- );
28147
+ const input = this.form.querySelector(`[name="${key}"]`);
28151
28148
  if (input && value !== void 0 && value !== null) {
28152
28149
  input.value = String(value);
28153
28150
  }
@@ -28162,9 +28159,7 @@ var SettingsModalView = class {
28162
28159
  }
28163
28160
  setupFocusTrap() {
28164
28161
  this.focusTrapElements = Array.from(
28165
- this.modal.querySelectorAll(
28166
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
28167
- )
28162
+ this.modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
28168
28163
  );
28169
28164
  this.modal.addEventListener("keydown", this.handleKeyDown.bind(this));
28170
28165
  }
@@ -28194,7 +28189,7 @@ var SettingsModalView = class {
28194
28189
  }
28195
28190
  }
28196
28191
  /**
28197
- * Helper: Traduz o JSON RFC-0086 (deviceMapInstaneousPower)
28192
+ * Helper: Traduz o JSON RFC-0086 (deviceMapInstaneousPower)
28198
28193
  * para os campos planos do formulário (ex: standbyLimitUpConsumption)
28199
28194
  */
28200
28195
  parseDeviceSavedLimits(deviceJson) {
@@ -28207,10 +28202,10 @@ var SettingsModalView = class {
28207
28202
  const deviceItem = consumptionGroup?.itemsByDeviceType?.[0];
28208
28203
  if (!deviceItem?.limitsByDeviceStatus) return extracted;
28209
28204
  const mapPrefix = {
28210
- "standBy": "standby",
28211
- "normal": "normal",
28212
- "alert": "alert",
28213
- "failure": "failure"
28205
+ standBy: "standby",
28206
+ normal: "normal",
28207
+ alert: "alert",
28208
+ failure: "failure"
28214
28209
  };
28215
28210
  deviceItem.limitsByDeviceStatus.forEach((status) => {
28216
28211
  const prefix = mapPrefix[status.deviceStatusName];
@@ -28246,9 +28241,7 @@ var SettingsModalView = class {
28246
28241
  const formData = this.getFormData();
28247
28242
  this.config.onSave(formData);
28248
28243
  });
28249
- const closeBtn = this.modal.querySelector(
28250
- ".close-btn"
28251
- );
28244
+ const closeBtn = this.modal.querySelector(".close-btn");
28252
28245
  if (closeBtn) {
28253
28246
  closeBtn.addEventListener("click", (event) => {
28254
28247
  event.preventDefault();
@@ -28256,9 +28249,7 @@ var SettingsModalView = class {
28256
28249
  this.config.onClose();
28257
28250
  });
28258
28251
  }
28259
- const cancelBtn = this.modal.querySelector(
28260
- ".btn-cancel"
28261
- );
28252
+ const cancelBtn = this.modal.querySelector(".btn-cancel");
28262
28253
  if (cancelBtn) {
28263
28254
  cancelBtn.addEventListener("click", (event) => {
28264
28255
  event.preventDefault();
@@ -28276,9 +28267,7 @@ var SettingsModalView = class {
28276
28267
  this.config.onSave(formData);
28277
28268
  });
28278
28269
  }
28279
- const btnCopy = this.modal.querySelector(
28280
- "#btnCopyFromGlobal"
28281
- );
28270
+ const btnCopy = this.modal.querySelector("#btnCopyFromGlobal");
28282
28271
  if (btnCopy) {
28283
28272
  btnCopy.addEventListener("click", (e) => {
28284
28273
  e.preventDefault();
@@ -28294,9 +28283,7 @@ var SettingsModalView = class {
28294
28283
  });
28295
28284
  });
28296
28285
  }
28297
- const btnClear = this.modal.querySelector(
28298
- "#btnClearInputs"
28299
- );
28286
+ const btnClear = this.modal.querySelector("#btnClearInputs");
28300
28287
  if (btnClear) {
28301
28288
  btnClear.addEventListener("click", (e) => {
28302
28289
  e.preventDefault();
@@ -28360,9 +28347,7 @@ var SettingsModalView = class {
28360
28347
  * Uses different keys based on domain: consumption (energy), temperature, pulses (water)
28361
28348
  */
28362
28349
  async fetchLatestConsumptionTelemetry() {
28363
- const telemetryElement = this.modal.querySelector(
28364
- "#lastConsumptionTelemetry"
28365
- );
28350
+ const telemetryElement = this.modal.querySelector("#lastConsumptionTelemetry");
28366
28351
  if (!telemetryElement) return;
28367
28352
  const deviceId = this.config.deviceId;
28368
28353
  const jwtToken = this.config.jwtToken;
@@ -28445,10 +28430,7 @@ var SettingsModalView = class {
28445
28430
  telemetryElement.innerHTML = '<span class="telemetry-no-data">Sem dados</span>';
28446
28431
  }
28447
28432
  } catch (error) {
28448
- console.error(
28449
- "[SettingsModal] Failed to fetch telemetry:",
28450
- error
28451
- );
28433
+ console.error("[SettingsModal] Failed to fetch telemetry:", error);
28452
28434
  telemetryElement.innerHTML = '<span class="telemetry-error">Erro ao carregar</span>';
28453
28435
  }
28454
28436
  }
@@ -28812,6 +28794,12 @@ var DefaultSettingsFetcher = class {
28812
28794
  attributes.mapInstantaneousPower = attr.value;
28813
28795
  } else if (attr.key === "deviceMapInstaneousPower") {
28814
28796
  attributes.deviceMapInstaneousPower = attr.value;
28797
+ } else if (attr.key === "offSetTemperature") {
28798
+ attributes.offSetTemperature = attr.value;
28799
+ } else if (attr.key === "minTemperature") {
28800
+ attributes.minTemperature = attr.value;
28801
+ } else if (attr.key === "maxTemperature") {
28802
+ attributes.maxTemperature = attr.value;
28815
28803
  }
28816
28804
  }
28817
28805
  }
@@ -28845,8 +28833,8 @@ var DefaultSettingsFetcher = class {
28845
28833
  sanitized[field] = data[field].trim();
28846
28834
  }
28847
28835
  }
28848
- const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh"];
28849
- for (const field of numericFields) {
28836
+ const consumptionFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh"];
28837
+ for (const field of consumptionFields) {
28850
28838
  if (data[field] !== void 0 && data[field] !== null) {
28851
28839
  const num = Number(data[field]);
28852
28840
  if (!isNaN(num) && num >= 0) {
@@ -28854,6 +28842,21 @@ var DefaultSettingsFetcher = class {
28854
28842
  }
28855
28843
  }
28856
28844
  }
28845
+ const temperatureFields = ["minTemperature", "maxTemperature", "offSetTemperature"];
28846
+ for (const field of temperatureFields) {
28847
+ if (data[field] !== void 0 && data[field] !== null) {
28848
+ const num = Number(data[field]);
28849
+ if (!isNaN(num)) {
28850
+ if (field === "offSetTemperature") {
28851
+ if (num >= -99.99 && num <= 99.99) {
28852
+ sanitized[field] = num;
28853
+ }
28854
+ } else {
28855
+ sanitized[field] = num;
28856
+ }
28857
+ }
28858
+ }
28859
+ }
28857
28860
  const objectFields = ["mapInstantaneousPower", "deviceMapInstaneousPower"];
28858
28861
  for (const field of objectFields) {
28859
28862
  if (data[field] && typeof data[field] === "object") {
@@ -29172,7 +29175,7 @@ var SettingsController = class {
29172
29175
  errors.push("GUID must be in valid UUID format");
29173
29176
  }
29174
29177
  }
29175
- const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh", "minTemperature", "maxTemperature", "minWaterLevel", "maxWaterLevel"];
29178
+ const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh", "minTemperature", "maxTemperature", "offSetTemperature", "minWaterLevel", "maxWaterLevel"];
29176
29179
  for (const field of numericFields) {
29177
29180
  if (formData[field] !== void 0) {
29178
29181
  const num = Number(formData[field]);
@@ -29184,6 +29187,11 @@ var SettingsController = class {
29184
29187
  errors.push(`${field} must be between 0 and 100`);
29185
29188
  }
29186
29189
  }
29190
+ if (field === "offSetTemperature") {
29191
+ if (num < -99.99 || num > 99.99) {
29192
+ errors.push(`${field} must be between -99.99 and +99.99`);
29193
+ }
29194
+ }
29187
29195
  }
29188
29196
  }
29189
29197
  if (formData.minTemperature !== void 0 && formData.maxTemperature !== void 0) {
@@ -30979,7 +30987,7 @@ async function openTemperatureModal(params) {
30979
30987
  const defaultDateRange = getTodaySoFar();
30980
30988
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
30981
30989
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
30982
- const state5 = {
30990
+ const state6 = {
30983
30991
  token: params.token,
30984
30992
  deviceId: params.deviceId,
30985
30993
  label: params.label || "Sensor de Temperatura",
@@ -31002,58 +31010,58 @@ async function openTemperatureModal(params) {
31002
31010
  };
31003
31011
  const savedGranularity = localStorage.getItem("myio-temp-modal-granularity");
31004
31012
  const savedTheme = localStorage.getItem("myio-temp-modal-theme");
31005
- if (savedGranularity) state5.granularity = savedGranularity;
31006
- if (savedTheme) state5.theme = savedTheme;
31013
+ if (savedGranularity) state6.granularity = savedGranularity;
31014
+ if (savedTheme) state6.theme = savedTheme;
31007
31015
  const modalContainer = document.createElement("div");
31008
31016
  modalContainer.id = modalId;
31009
31017
  document.body.appendChild(modalContainer);
31010
- renderModal(modalContainer, state5, modalId);
31018
+ renderModal(modalContainer, state6, modalId);
31011
31019
  try {
31012
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
31013
- state5.stats = calculateStats(state5.data, state5.clampRange);
31014
- state5.isLoading = false;
31015
- renderModal(modalContainer, state5, modalId);
31016
- drawChart(modalId, state5);
31020
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
31021
+ state6.stats = calculateStats(state6.data, state6.clampRange);
31022
+ state6.isLoading = false;
31023
+ renderModal(modalContainer, state6, modalId);
31024
+ drawChart(modalId, state6);
31017
31025
  } catch (error) {
31018
31026
  console.error("[TemperatureModal] Error fetching data:", error);
31019
- state5.isLoading = false;
31020
- renderModal(modalContainer, state5, modalId, error);
31027
+ state6.isLoading = false;
31028
+ renderModal(modalContainer, state6, modalId, error);
31021
31029
  }
31022
- await setupEventListeners(modalContainer, state5, modalId, params.onClose);
31030
+ await setupEventListeners(modalContainer, state6, modalId, params.onClose);
31023
31031
  return {
31024
31032
  destroy: () => {
31025
31033
  modalContainer.remove();
31026
31034
  params.onClose?.();
31027
31035
  },
31028
31036
  updateData: async (startDate, endDate, granularity) => {
31029
- state5.startTs = new Date(startDate).getTime();
31030
- state5.endTs = new Date(endDate).getTime();
31031
- if (granularity) state5.granularity = granularity;
31032
- state5.isLoading = true;
31033
- renderModal(modalContainer, state5, modalId);
31037
+ state6.startTs = new Date(startDate).getTime();
31038
+ state6.endTs = new Date(endDate).getTime();
31039
+ if (granularity) state6.granularity = granularity;
31040
+ state6.isLoading = true;
31041
+ renderModal(modalContainer, state6, modalId);
31034
31042
  try {
31035
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
31036
- state5.stats = calculateStats(state5.data, state5.clampRange);
31037
- state5.isLoading = false;
31038
- renderModal(modalContainer, state5, modalId);
31039
- drawChart(modalId, state5);
31043
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
31044
+ state6.stats = calculateStats(state6.data, state6.clampRange);
31045
+ state6.isLoading = false;
31046
+ renderModal(modalContainer, state6, modalId);
31047
+ drawChart(modalId, state6);
31040
31048
  } catch (error) {
31041
31049
  console.error("[TemperatureModal] Error updating data:", error);
31042
- state5.isLoading = false;
31043
- renderModal(modalContainer, state5, modalId, error);
31050
+ state6.isLoading = false;
31051
+ renderModal(modalContainer, state6, modalId, error);
31044
31052
  }
31045
31053
  }
31046
31054
  };
31047
31055
  }
31048
- function renderModal(container, state5, modalId, error) {
31049
- const colors = getThemeColors(state5.theme);
31050
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale);
31051
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale);
31052
- const statusText = state5.temperatureStatus === "ok" ? "Dentro da faixa" : state5.temperatureStatus === "above" ? "Acima do limite" : state5.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
31053
- const statusColor = state5.temperatureStatus === "ok" ? colors.success : state5.temperatureStatus === "above" ? colors.danger : state5.temperatureStatus === "below" ? colors.primary : colors.textMuted;
31054
- const rangeText = state5.temperatureMin !== null && state5.temperatureMax !== null ? `${state5.temperatureMin}\xB0C - ${state5.temperatureMax}\xB0C` : "N\xE3o definida";
31055
- const startDateInput = new Date(state5.startTs).toISOString().slice(0, 16);
31056
- const endDateInput = new Date(state5.endTs).toISOString().slice(0, 16);
31056
+ function renderModal(container, state6, modalId, error) {
31057
+ const colors = getThemeColors(state6.theme);
31058
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale);
31059
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale);
31060
+ const statusText = state6.temperatureStatus === "ok" ? "Dentro da faixa" : state6.temperatureStatus === "above" ? "Acima do limite" : state6.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
31061
+ const statusColor = state6.temperatureStatus === "ok" ? colors.success : state6.temperatureStatus === "above" ? colors.danger : state6.temperatureStatus === "below" ? colors.primary : colors.textMuted;
31062
+ const rangeText = state6.temperatureMin !== null && state6.temperatureMax !== null ? `${state6.temperatureMin}\xB0C - ${state6.temperatureMax}\xB0C` : "N\xE3o definida";
31063
+ const startDateInput = new Date(state6.startTs).toISOString().slice(0, 16);
31064
+ const endDateInput = new Date(state6.endTs).toISOString().slice(0, 16);
31057
31065
  const isMaximized = container.__isMaximized || false;
31058
31066
  const contentMaxWidth = isMaximized ? "100%" : "900px";
31059
31067
  const contentMaxHeight = isMaximized ? "100vh" : "95vh";
@@ -31080,7 +31088,7 @@ function renderModal(container, state5, modalId, error) {
31080
31088
  min-height: 20px;
31081
31089
  ">
31082
31090
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
31083
- \u{1F321}\uFE0F ${state5.label} - Hist\xF3rico de Temperatura
31091
+ \u{1F321}\uFE0F ${state6.label} - Hist\xF3rico de Temperatura
31084
31092
  </h2>
31085
31093
  <div style="display: flex; gap: 4px; align-items: center;">
31086
31094
  <!-- Theme Toggle -->
@@ -31088,7 +31096,7 @@ function renderModal(container, state5, modalId, error) {
31088
31096
  background: none; border: none; font-size: 16px; cursor: pointer;
31089
31097
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
31090
31098
  transition: background-color 0.2s;
31091
- ">${state5.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
31099
+ ">${state6.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
31092
31100
  <!-- Maximize Button -->
31093
31101
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
31094
31102
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -31110,7 +31118,7 @@ function renderModal(container, state5, modalId, error) {
31110
31118
  <!-- Controls Row -->
31111
31119
  <div style="
31112
31120
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
31113
- margin-bottom: 16px; padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
31121
+ margin-bottom: 16px; padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
31114
31122
  border-radius: 6px; border: 1px solid ${colors.border};
31115
31123
  ">
31116
31124
  <!-- Granularity Select -->
@@ -31123,8 +31131,8 @@ function renderModal(container, state5, modalId, error) {
31123
31131
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
31124
31132
  cursor: pointer; min-width: 130px;
31125
31133
  ">
31126
- <option value="hour" ${state5.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
31127
- <option value="day" ${state5.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
31134
+ <option value="hour" ${state6.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
31135
+ <option value="day" ${state6.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
31128
31136
  </select>
31129
31137
  </div>
31130
31138
  <!-- Day Period Filter (Multiselect) -->
@@ -31138,7 +31146,7 @@ function renderModal(container, state5, modalId, error) {
31138
31146
  cursor: pointer; min-width: 180px; text-align: left;
31139
31147
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
31140
31148
  ">
31141
- <span>${getSelectedPeriodsLabel(state5.selectedPeriods)}</span>
31149
+ <span>${getSelectedPeriodsLabel(state6.selectedPeriods)}</span>
31142
31150
  <span style="font-size: 10px;">\u25BC</span>
31143
31151
  </button>
31144
31152
  <div id="${modalId}-period-dropdown" style="
@@ -31151,12 +31159,12 @@ function renderModal(container, state5, modalId, error) {
31151
31159
  <label style="
31152
31160
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
31153
31161
  cursor: pointer; font-size: 13px; color: ${colors.text};
31154
- " onmouseover="this.style.background='${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
31162
+ " onmouseover="this.style.background='${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
31155
31163
  onmouseout="this.style.background='transparent'">
31156
31164
  <input type="checkbox"
31157
31165
  name="${modalId}-period"
31158
31166
  value="${period.id}"
31159
- ${state5.selectedPeriods.includes(period.id) ? "checked" : ""}
31167
+ ${state6.selectedPeriods.includes(period.id) ? "checked" : ""}
31160
31168
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
31161
31169
  ${period.label}
31162
31170
  </label>
@@ -31164,13 +31172,13 @@ function renderModal(container, state5, modalId, error) {
31164
31172
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
31165
31173
  <button id="${modalId}-period-select-all" type="button" style="
31166
31174
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
31167
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31175
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31168
31176
  border: none; border-radius: 4px; cursor: pointer;
31169
31177
  font-size: 12px; color: ${colors.text};
31170
31178
  ">Selecionar Todos</button>
31171
31179
  <button id="${modalId}-period-clear" type="button" style="
31172
31180
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
31173
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31181
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31174
31182
  border: none; border-radius: 4px; cursor: pointer;
31175
31183
  font-size: 12px; color: ${colors.text};
31176
31184
  ">Limpar Sele\xE7\xE3o</button>
@@ -31195,8 +31203,8 @@ function renderModal(container, state5, modalId, error) {
31195
31203
  font-size: 14px; font-weight: 500; height: 38px;
31196
31204
  display: flex; align-items: center; gap: 8px;
31197
31205
  font-family: 'Roboto', Arial, sans-serif;
31198
- " ${state5.isLoading ? "disabled" : ""}>
31199
- ${state5.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31206
+ " ${state6.isLoading ? "disabled" : ""}>
31207
+ ${state6.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31200
31208
  </button>
31201
31209
  </div>
31202
31210
 
@@ -31207,40 +31215,40 @@ function renderModal(container, state5, modalId, error) {
31207
31215
  ">
31208
31216
  <!-- Current Temperature -->
31209
31217
  <div style="
31210
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31218
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31211
31219
  border-radius: 12px; border: 1px solid ${colors.border};
31212
31220
  ">
31213
31221
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Temperatura Atual</span>
31214
31222
  <div style="font-weight: 700; font-size: 24px; color: ${statusColor}; margin-top: 4px;">
31215
- ${state5.currentTemperature !== null ? formatTemperature(state5.currentTemperature) : "N/A"}
31223
+ ${state6.currentTemperature !== null ? formatTemperature(state6.currentTemperature) : "N/A"}
31216
31224
  </div>
31217
31225
  <div style="font-size: 11px; color: ${statusColor}; margin-top: 2px;">${statusText}</div>
31218
31226
  </div>
31219
31227
  <!-- Average -->
31220
31228
  <div style="
31221
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31229
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31222
31230
  border-radius: 12px; border: 1px solid ${colors.border};
31223
31231
  ">
31224
31232
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">M\xE9dia do Per\xEDodo</span>
31225
31233
  <div id="${modalId}-avg" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
31226
- ${state5.stats.count > 0 ? formatTemperature(state5.stats.avg) : "N/A"}
31234
+ ${state6.stats.count > 0 ? formatTemperature(state6.stats.avg) : "N/A"}
31227
31235
  </div>
31228
31236
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${startDateStr} - ${endDateStr}</div>
31229
31237
  </div>
31230
31238
  <!-- Min/Max -->
31231
31239
  <div style="
31232
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31240
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31233
31241
  border-radius: 12px; border: 1px solid ${colors.border};
31234
31242
  ">
31235
31243
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Min / Max</span>
31236
31244
  <div id="${modalId}-minmax" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
31237
- ${state5.stats.count > 0 ? `${formatTemperature(state5.stats.min)} / ${formatTemperature(state5.stats.max)}` : "N/A"}
31245
+ ${state6.stats.count > 0 ? `${formatTemperature(state6.stats.min)} / ${formatTemperature(state6.stats.max)}` : "N/A"}
31238
31246
  </div>
31239
- <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state5.stats.count} leituras</div>
31247
+ <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state6.stats.count} leituras</div>
31240
31248
  </div>
31241
31249
  <!-- Ideal Range -->
31242
31250
  <div style="
31243
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31251
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31244
31252
  border-radius: 12px; border: 1px solid ${colors.border};
31245
31253
  ">
31246
31254
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Faixa Ideal</span>
@@ -31256,18 +31264,18 @@ function renderModal(container, state5, modalId, error) {
31256
31264
  Hist\xF3rico de Temperatura
31257
31265
  </h3>
31258
31266
  <div id="${modalId}-chart" style="
31259
- height: 320px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
31267
+ height: 320px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
31260
31268
  border-radius: 12px; display: flex; justify-content: center; align-items: center;
31261
31269
  border: 1px solid ${colors.border}; position: relative;
31262
31270
  ">
31263
- ${state5.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
31271
+ ${state6.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
31264
31272
  <div style="animation: spin 1s linear infinite; font-size: 32px; margin-bottom: 8px;">\u21BB</div>
31265
31273
  <div>Carregando dados...</div>
31266
31274
  </div>` : error ? `<div style="text-align: center; color: ${colors.danger};">
31267
31275
  <div style="font-size: 32px; margin-bottom: 8px;">\u26A0\uFE0F</div>
31268
31276
  <div>Erro ao carregar dados</div>
31269
31277
  <div style="font-size: 12px; margin-top: 4px;">${error.message}</div>
31270
- </div>` : state5.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
31278
+ </div>` : state6.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
31271
31279
  <div style="font-size: 32px; margin-bottom: 8px;">\u{1F4ED}</div>
31272
31280
  <div>Sem dados para o per\xEDodo selecionado</div>
31273
31281
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -31277,12 +31285,12 @@ function renderModal(container, state5, modalId, error) {
31277
31285
  <!-- Actions -->
31278
31286
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
31279
31287
  <button id="${modalId}-export" style="
31280
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
31288
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
31281
31289
  color: ${colors.text}; border: 1px solid ${colors.border};
31282
31290
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
31283
31291
  font-size: 14px; display: flex; align-items: center; gap: 8px;
31284
31292
  font-family: 'Roboto', Arial, sans-serif;
31285
- " ${state5.data.length === 0 ? "disabled" : ""}>
31293
+ " ${state6.data.length === 0 ? "disabled" : ""}>
31286
31294
  \u{1F4E5} Exportar CSV
31287
31295
  </button>
31288
31296
  <button id="${modalId}-close-btn" style="
@@ -31333,14 +31341,14 @@ function renderModal(container, state5, modalId, error) {
31333
31341
  </style>
31334
31342
  `;
31335
31343
  }
31336
- function drawChart(modalId, state5) {
31344
+ function drawChart(modalId, state6) {
31337
31345
  const chartContainer = document.getElementById(`${modalId}-chart`);
31338
31346
  const canvas = document.getElementById(`${modalId}-canvas`);
31339
- if (!chartContainer || !canvas || state5.data.length === 0) return;
31347
+ if (!chartContainer || !canvas || state6.data.length === 0) return;
31340
31348
  const ctx = canvas.getContext("2d");
31341
31349
  if (!ctx) return;
31342
- const colors = getThemeColors(state5.theme);
31343
- const filteredData = filterByDayPeriods(state5.data, state5.selectedPeriods);
31350
+ const colors = getThemeColors(state6.theme);
31351
+ const filteredData = filterByDayPeriods(state6.data, state6.selectedPeriods);
31344
31352
  if (filteredData.length === 0) {
31345
31353
  canvas.width = chartContainer.clientWidth;
31346
31354
  canvas.height = chartContainer.clientHeight;
@@ -31351,14 +31359,14 @@ function drawChart(modalId, state5) {
31351
31359
  return;
31352
31360
  }
31353
31361
  let chartData;
31354
- if (state5.granularity === "hour") {
31362
+ if (state6.granularity === "hour") {
31355
31363
  const interpolated = interpolateTemperature(filteredData, {
31356
31364
  intervalMinutes: 30,
31357
- startTs: state5.startTs,
31358
- endTs: state5.endTs,
31359
- clampRange: state5.clampRange
31365
+ startTs: state6.startTs,
31366
+ endTs: state6.endTs,
31367
+ clampRange: state6.clampRange
31360
31368
  });
31361
- const filteredInterpolated = filterByDayPeriods(interpolated, state5.selectedPeriods);
31369
+ const filteredInterpolated = filterByDayPeriods(interpolated, state6.selectedPeriods);
31362
31370
  chartData = filteredInterpolated.map((item) => ({
31363
31371
  x: item.ts,
31364
31372
  y: Number(item.value),
@@ -31366,7 +31374,7 @@ function drawChart(modalId, state5) {
31366
31374
  screenY: 0
31367
31375
  }));
31368
31376
  } else {
31369
- const daily = aggregateByDay(filteredData, state5.clampRange);
31377
+ const daily = aggregateByDay(filteredData, state6.clampRange);
31370
31378
  chartData = daily.map((item) => ({
31371
31379
  x: item.dateTs,
31372
31380
  y: item.avg,
@@ -31384,12 +31392,12 @@ function drawChart(modalId, state5) {
31384
31392
  const paddingRight = 20;
31385
31393
  const paddingTop = 20;
31386
31394
  const paddingBottom = 55;
31387
- const isPeriodsFiltered = state5.selectedPeriods.length < 4 && state5.selectedPeriods.length > 0;
31395
+ const isPeriodsFiltered = state6.selectedPeriods.length < 4 && state6.selectedPeriods.length > 0;
31388
31396
  const values = chartData.map((d) => d.y);
31389
31397
  const dataMin = Math.min(...values);
31390
31398
  const dataMax = Math.max(...values);
31391
- const thresholdMin = state5.temperatureMin !== null ? state5.temperatureMin : dataMin;
31392
- const thresholdMax = state5.temperatureMax !== null ? state5.temperatureMax : dataMax;
31399
+ const thresholdMin = state6.temperatureMin !== null ? state6.temperatureMin : dataMin;
31400
+ const thresholdMax = state6.temperatureMax !== null ? state6.temperatureMax : dataMax;
31393
31401
  const minY = Math.min(dataMin, thresholdMin) - 1;
31394
31402
  const maxY = Math.max(dataMax, thresholdMax) + 1;
31395
31403
  const chartWidth = width - paddingLeft - paddingRight;
@@ -31421,9 +31429,9 @@ function drawChart(modalId, state5) {
31421
31429
  ctx.lineTo(width - paddingRight, y);
31422
31430
  ctx.stroke();
31423
31431
  }
31424
- if (state5.temperatureMin !== null && state5.temperatureMax !== null) {
31425
- const rangeMinY = height - paddingBottom - (state5.temperatureMin - minY) * scaleY;
31426
- const rangeMaxY = height - paddingBottom - (state5.temperatureMax - minY) * scaleY;
31432
+ if (state6.temperatureMin !== null && state6.temperatureMax !== null) {
31433
+ const rangeMinY = height - paddingBottom - (state6.temperatureMin - minY) * scaleY;
31434
+ const rangeMaxY = height - paddingBottom - (state6.temperatureMax - minY) * scaleY;
31427
31435
  ctx.fillStyle = "rgba(76, 175, 80, 0.1)";
31428
31436
  ctx.fillRect(paddingLeft, rangeMaxY, chartWidth, rangeMinY - rangeMaxY);
31429
31437
  ctx.strokeStyle = colors.success;
@@ -31465,10 +31473,10 @@ function drawChart(modalId, state5) {
31465
31473
  const point = chartData[i];
31466
31474
  const date = new Date(point.x);
31467
31475
  let label;
31468
- if (state5.granularity === "hour") {
31469
- label = date.toLocaleTimeString(state5.locale, { hour: "2-digit", minute: "2-digit" });
31476
+ if (state6.granularity === "hour") {
31477
+ label = date.toLocaleTimeString(state6.locale, { hour: "2-digit", minute: "2-digit" });
31470
31478
  } else {
31471
- label = date.toLocaleDateString(state5.locale, { day: "2-digit", month: "2-digit" });
31479
+ label = date.toLocaleDateString(state6.locale, { day: "2-digit", month: "2-digit" });
31472
31480
  }
31473
31481
  ctx.strokeStyle = colors.chartGrid;
31474
31482
  ctx.lineWidth = 1;
@@ -31486,16 +31494,16 @@ function drawChart(modalId, state5) {
31486
31494
  ctx.lineTo(paddingLeft, height - paddingBottom);
31487
31495
  ctx.lineTo(width - paddingRight, height - paddingBottom);
31488
31496
  ctx.stroke();
31489
- setupChartTooltip(canvas, chartContainer, chartData, state5, colors);
31497
+ setupChartTooltip(canvas, chartContainer, chartData, state6, colors);
31490
31498
  }
31491
- function setupChartTooltip(canvas, container, chartData, state5, colors) {
31499
+ function setupChartTooltip(canvas, container, chartData, state6, colors) {
31492
31500
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
31493
31501
  if (existingTooltip) existingTooltip.remove();
31494
31502
  const tooltip = document.createElement("div");
31495
31503
  tooltip.className = "myio-chart-tooltip";
31496
31504
  tooltip.style.cssText = `
31497
31505
  position: absolute;
31498
- background: ${state5.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31506
+ background: ${state6.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31499
31507
  border: 1px solid ${colors.border};
31500
31508
  border-radius: 8px;
31501
31509
  padding: 10px 14px;
@@ -31532,17 +31540,17 @@ function setupChartTooltip(canvas, container, chartData, state5, colors) {
31532
31540
  if (point) {
31533
31541
  const date = new Date(point.x);
31534
31542
  let dateStr;
31535
- if (state5.granularity === "hour") {
31536
- dateStr = date.toLocaleDateString(state5.locale, {
31543
+ if (state6.granularity === "hour") {
31544
+ dateStr = date.toLocaleDateString(state6.locale, {
31537
31545
  day: "2-digit",
31538
31546
  month: "2-digit",
31539
31547
  year: "numeric"
31540
- }) + " " + date.toLocaleTimeString(state5.locale, {
31548
+ }) + " " + date.toLocaleTimeString(state6.locale, {
31541
31549
  hour: "2-digit",
31542
31550
  minute: "2-digit"
31543
31551
  });
31544
31552
  } else {
31545
- dateStr = date.toLocaleDateString(state5.locale, {
31553
+ dateStr = date.toLocaleDateString(state6.locale, {
31546
31554
  day: "2-digit",
31547
31555
  month: "2-digit",
31548
31556
  year: "numeric"
@@ -31580,7 +31588,7 @@ function setupChartTooltip(canvas, container, chartData, state5, colors) {
31580
31588
  canvas.style.cursor = "default";
31581
31589
  });
31582
31590
  }
31583
- async function setupEventListeners(container, state5, modalId, onClose) {
31591
+ async function setupEventListeners(container, state6, modalId, onClose) {
31584
31592
  const closeModal = () => {
31585
31593
  container.remove();
31586
31594
  onClose?.();
@@ -31591,19 +31599,19 @@ async function setupEventListeners(container, state5, modalId, onClose) {
31591
31599
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
31592
31600
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
31593
31601
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
31594
- if (dateRangeInput && !state5.dateRangePicker) {
31602
+ if (dateRangeInput && !state6.dateRangePicker) {
31595
31603
  try {
31596
- state5.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
31597
- presetStart: new Date(state5.startTs).toISOString(),
31598
- presetEnd: new Date(state5.endTs).toISOString(),
31604
+ state6.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
31605
+ presetStart: new Date(state6.startTs).toISOString(),
31606
+ presetEnd: new Date(state6.endTs).toISOString(),
31599
31607
  includeTime: true,
31600
31608
  timePrecision: "minute",
31601
31609
  maxRangeDays: 90,
31602
- locale: state5.locale,
31610
+ locale: state6.locale,
31603
31611
  parentEl: container.querySelector(".myio-temp-modal-content"),
31604
31612
  onApply: (result) => {
31605
- state5.startTs = new Date(result.startISO).getTime();
31606
- state5.endTs = new Date(result.endISO).getTime();
31613
+ state6.startTs = new Date(result.startISO).getTime();
31614
+ state6.endTs = new Date(result.endISO).getTime();
31607
31615
  console.log("[TemperatureModal] Date range applied:", result);
31608
31616
  }
31609
31617
  });
@@ -31612,19 +31620,19 @@ async function setupEventListeners(container, state5, modalId, onClose) {
31612
31620
  }
31613
31621
  }
31614
31622
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
31615
- state5.theme = state5.theme === "dark" ? "light" : "dark";
31616
- localStorage.setItem("myio-temp-modal-theme", state5.theme);
31617
- state5.dateRangePicker = null;
31618
- renderModal(container, state5, modalId);
31619
- if (state5.data.length > 0) drawChart(modalId, state5);
31620
- await setupEventListeners(container, state5, modalId, onClose);
31623
+ state6.theme = state6.theme === "dark" ? "light" : "dark";
31624
+ localStorage.setItem("myio-temp-modal-theme", state6.theme);
31625
+ state6.dateRangePicker = null;
31626
+ renderModal(container, state6, modalId);
31627
+ if (state6.data.length > 0) drawChart(modalId, state6);
31628
+ await setupEventListeners(container, state6, modalId, onClose);
31621
31629
  });
31622
31630
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
31623
31631
  container.__isMaximized = !container.__isMaximized;
31624
- state5.dateRangePicker = null;
31625
- renderModal(container, state5, modalId);
31626
- if (state5.data.length > 0) drawChart(modalId, state5);
31627
- await setupEventListeners(container, state5, modalId, onClose);
31632
+ state6.dateRangePicker = null;
31633
+ renderModal(container, state6, modalId);
31634
+ if (state6.data.length > 0) drawChart(modalId, state6);
31635
+ await setupEventListeners(container, state6, modalId, onClose);
31628
31636
  });
31629
31637
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
31630
31638
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -31643,71 +31651,71 @@ async function setupEventListeners(container, state5, modalId, onClose) {
31643
31651
  periodCheckboxes.forEach((checkbox) => {
31644
31652
  checkbox.addEventListener("change", () => {
31645
31653
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
31646
- state5.selectedPeriods = checked;
31654
+ state6.selectedPeriods = checked;
31647
31655
  const btnLabel = periodBtn?.querySelector("span:first-child");
31648
31656
  if (btnLabel) {
31649
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31657
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31650
31658
  }
31651
- if (state5.data.length > 0) drawChart(modalId, state5);
31659
+ if (state6.data.length > 0) drawChart(modalId, state6);
31652
31660
  });
31653
31661
  });
31654
31662
  document.getElementById(`${modalId}-period-select-all`)?.addEventListener("click", () => {
31655
31663
  periodCheckboxes.forEach((cb) => {
31656
31664
  cb.checked = true;
31657
31665
  });
31658
- state5.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
31666
+ state6.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
31659
31667
  const btnLabel = periodBtn?.querySelector("span:first-child");
31660
31668
  if (btnLabel) {
31661
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31669
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31662
31670
  }
31663
- if (state5.data.length > 0) drawChart(modalId, state5);
31671
+ if (state6.data.length > 0) drawChart(modalId, state6);
31664
31672
  });
31665
31673
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
31666
31674
  periodCheckboxes.forEach((cb) => {
31667
31675
  cb.checked = false;
31668
31676
  });
31669
- state5.selectedPeriods = [];
31677
+ state6.selectedPeriods = [];
31670
31678
  const btnLabel = periodBtn?.querySelector("span:first-child");
31671
31679
  if (btnLabel) {
31672
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31680
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31673
31681
  }
31674
- if (state5.data.length > 0) drawChart(modalId, state5);
31682
+ if (state6.data.length > 0) drawChart(modalId, state6);
31675
31683
  });
31676
31684
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
31677
- state5.granularity = e.target.value;
31678
- localStorage.setItem("myio-temp-modal-granularity", state5.granularity);
31679
- if (state5.data.length > 0) drawChart(modalId, state5);
31685
+ state6.granularity = e.target.value;
31686
+ localStorage.setItem("myio-temp-modal-granularity", state6.granularity);
31687
+ if (state6.data.length > 0) drawChart(modalId, state6);
31680
31688
  });
31681
31689
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
31682
- if (state5.startTs >= state5.endTs) {
31690
+ if (state6.startTs >= state6.endTs) {
31683
31691
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
31684
31692
  return;
31685
31693
  }
31686
- state5.isLoading = true;
31687
- state5.dateRangePicker = null;
31688
- renderModal(container, state5, modalId);
31694
+ state6.isLoading = true;
31695
+ state6.dateRangePicker = null;
31696
+ renderModal(container, state6, modalId);
31689
31697
  try {
31690
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
31691
- state5.stats = calculateStats(state5.data, state5.clampRange);
31692
- state5.isLoading = false;
31693
- renderModal(container, state5, modalId);
31694
- drawChart(modalId, state5);
31695
- await setupEventListeners(container, state5, modalId, onClose);
31698
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
31699
+ state6.stats = calculateStats(state6.data, state6.clampRange);
31700
+ state6.isLoading = false;
31701
+ renderModal(container, state6, modalId);
31702
+ drawChart(modalId, state6);
31703
+ await setupEventListeners(container, state6, modalId, onClose);
31696
31704
  } catch (error) {
31697
31705
  console.error("[TemperatureModal] Error fetching data:", error);
31698
- state5.isLoading = false;
31699
- renderModal(container, state5, modalId, error);
31700
- await setupEventListeners(container, state5, modalId, onClose);
31706
+ state6.isLoading = false;
31707
+ renderModal(container, state6, modalId, error);
31708
+ await setupEventListeners(container, state6, modalId, onClose);
31701
31709
  }
31702
31710
  });
31703
31711
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
31704
- if (state5.data.length === 0) return;
31705
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
31706
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
31712
+ if (state6.data.length === 0) return;
31713
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
31714
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
31707
31715
  exportTemperatureCSV(
31708
- state5.data,
31709
- state5.label,
31710
- state5.stats,
31716
+ state6.data,
31717
+ state6.label,
31718
+ state6.stats,
31711
31719
  startDateStr,
31712
31720
  endDateStr
31713
31721
  );
@@ -31720,7 +31728,7 @@ async function openTemperatureComparisonModal(params) {
31720
31728
  const defaultDateRange = getTodaySoFar();
31721
31729
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
31722
31730
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
31723
- const state5 = {
31731
+ const state6 = {
31724
31732
  token: params.token,
31725
31733
  devices: params.devices,
31726
31734
  startTs,
@@ -31739,44 +31747,44 @@ async function openTemperatureComparisonModal(params) {
31739
31747
  };
31740
31748
  const savedGranularity = localStorage.getItem("myio-temp-comparison-granularity");
31741
31749
  const savedTheme = localStorage.getItem("myio-temp-comparison-theme");
31742
- if (savedGranularity) state5.granularity = savedGranularity;
31743
- if (savedTheme) state5.theme = savedTheme;
31750
+ if (savedGranularity) state6.granularity = savedGranularity;
31751
+ if (savedTheme) state6.theme = savedTheme;
31744
31752
  const modalContainer = document.createElement("div");
31745
31753
  modalContainer.id = modalId;
31746
31754
  document.body.appendChild(modalContainer);
31747
- renderModal2(modalContainer, state5, modalId);
31748
- await fetchAllDevicesData(state5);
31749
- renderModal2(modalContainer, state5, modalId);
31750
- drawComparisonChart(modalId, state5);
31751
- await setupEventListeners2(modalContainer, state5, modalId, params.onClose);
31755
+ renderModal2(modalContainer, state6, modalId);
31756
+ await fetchAllDevicesData(state6);
31757
+ renderModal2(modalContainer, state6, modalId);
31758
+ drawComparisonChart(modalId, state6);
31759
+ await setupEventListeners2(modalContainer, state6, modalId, params.onClose);
31752
31760
  return {
31753
31761
  destroy: () => {
31754
31762
  modalContainer.remove();
31755
31763
  params.onClose?.();
31756
31764
  },
31757
31765
  updateData: async (startDate, endDate, granularity) => {
31758
- state5.startTs = new Date(startDate).getTime();
31759
- state5.endTs = new Date(endDate).getTime();
31760
- if (granularity) state5.granularity = granularity;
31761
- state5.isLoading = true;
31762
- renderModal2(modalContainer, state5, modalId);
31763
- await fetchAllDevicesData(state5);
31764
- renderModal2(modalContainer, state5, modalId);
31765
- drawComparisonChart(modalId, state5);
31766
- setupEventListeners2(modalContainer, state5, modalId, params.onClose);
31766
+ state6.startTs = new Date(startDate).getTime();
31767
+ state6.endTs = new Date(endDate).getTime();
31768
+ if (granularity) state6.granularity = granularity;
31769
+ state6.isLoading = true;
31770
+ renderModal2(modalContainer, state6, modalId);
31771
+ await fetchAllDevicesData(state6);
31772
+ renderModal2(modalContainer, state6, modalId);
31773
+ drawComparisonChart(modalId, state6);
31774
+ setupEventListeners2(modalContainer, state6, modalId, params.onClose);
31767
31775
  }
31768
31776
  };
31769
31777
  }
31770
- async function fetchAllDevicesData(state5) {
31771
- state5.isLoading = true;
31772
- state5.deviceData = [];
31778
+ async function fetchAllDevicesData(state6) {
31779
+ state6.isLoading = true;
31780
+ state6.deviceData = [];
31773
31781
  try {
31774
31782
  const results = await Promise.all(
31775
- state5.devices.map(async (device, index) => {
31783
+ state6.devices.map(async (device, index) => {
31776
31784
  const deviceId = device.tbId || device.id;
31777
31785
  try {
31778
- const data = await fetchTemperatureData(state5.token, deviceId, state5.startTs, state5.endTs);
31779
- const stats = calculateStats(data, state5.clampRange);
31786
+ const data = await fetchTemperatureData(state6.token, deviceId, state6.startTs, state6.endTs);
31787
+ const stats = calculateStats(data, state6.clampRange);
31780
31788
  return {
31781
31789
  device,
31782
31790
  data,
@@ -31794,21 +31802,21 @@ async function fetchAllDevicesData(state5) {
31794
31802
  }
31795
31803
  })
31796
31804
  );
31797
- state5.deviceData = results;
31805
+ state6.deviceData = results;
31798
31806
  } catch (error) {
31799
31807
  console.error("[TemperatureComparisonModal] Error fetching data:", error);
31800
31808
  }
31801
- state5.isLoading = false;
31809
+ state6.isLoading = false;
31802
31810
  }
31803
- function renderModal2(container, state5, modalId) {
31804
- const colors = getThemeColors(state5.theme);
31805
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale);
31806
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale);
31807
- const startDateInput = new Date(state5.startTs).toISOString().slice(0, 16);
31808
- const endDateInput = new Date(state5.endTs).toISOString().slice(0, 16);
31809
- const legendHTML = state5.deviceData.map((dd) => `
31811
+ function renderModal2(container, state6, modalId) {
31812
+ const colors = getThemeColors(state6.theme);
31813
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale);
31814
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale);
31815
+ const startDateInput = new Date(state6.startTs).toISOString().slice(0, 16);
31816
+ const endDateInput = new Date(state6.endTs).toISOString().slice(0, 16);
31817
+ const legendHTML = state6.deviceData.map((dd) => `
31810
31818
  <div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px;
31811
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
31819
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
31812
31820
  border-radius: 8px;">
31813
31821
  <span style="width: 12px; height: 12px; border-radius: 50%; background: ${dd.color};"></span>
31814
31822
  <span style="color: ${colors.text}; font-size: 13px;">${dd.device.label}</span>
@@ -31817,9 +31825,9 @@ function renderModal2(container, state5, modalId) {
31817
31825
  </span>
31818
31826
  </div>
31819
31827
  `).join("");
31820
- const statsHTML = state5.deviceData.map((dd) => `
31828
+ const statsHTML = state6.deviceData.map((dd) => `
31821
31829
  <div style="
31822
- padding: 12px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31830
+ padding: 12px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31823
31831
  border-radius: 10px; border-left: 4px solid ${dd.color};
31824
31832
  min-width: 150px;
31825
31833
  ">
@@ -31870,7 +31878,7 @@ function renderModal2(container, state5, modalId) {
31870
31878
  min-height: 20px;
31871
31879
  ">
31872
31880
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
31873
- \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state5.devices.length} sensores
31881
+ \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state6.devices.length} sensores
31874
31882
  </h2>
31875
31883
  <div style="display: flex; gap: 4px; align-items: center;">
31876
31884
  <!-- Theme Toggle -->
@@ -31878,7 +31886,7 @@ function renderModal2(container, state5, modalId) {
31878
31886
  background: none; border: none; font-size: 16px; cursor: pointer;
31879
31887
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
31880
31888
  transition: background-color 0.2s;
31881
- ">${state5.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
31889
+ ">${state6.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
31882
31890
  <!-- Maximize Button -->
31883
31891
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
31884
31892
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -31901,7 +31909,7 @@ function renderModal2(container, state5, modalId) {
31901
31909
  <div style="
31902
31910
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
31903
31911
  margin-bottom: 16px; padding: 16px;
31904
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
31912
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
31905
31913
  border-radius: 6px; border: 1px solid ${colors.border};
31906
31914
  ">
31907
31915
  <!-- Granularity Select -->
@@ -31914,8 +31922,8 @@ function renderModal2(container, state5, modalId) {
31914
31922
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
31915
31923
  cursor: pointer; min-width: 130px;
31916
31924
  ">
31917
- <option value="hour" ${state5.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
31918
- <option value="day" ${state5.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
31925
+ <option value="hour" ${state6.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
31926
+ <option value="day" ${state6.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
31919
31927
  </select>
31920
31928
  </div>
31921
31929
  <!-- Day Period Filter (Multiselect) -->
@@ -31929,7 +31937,7 @@ function renderModal2(container, state5, modalId) {
31929
31937
  cursor: pointer; min-width: 180px; text-align: left;
31930
31938
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
31931
31939
  ">
31932
- <span>${getSelectedPeriodsLabel(state5.selectedPeriods)}</span>
31940
+ <span>${getSelectedPeriodsLabel(state6.selectedPeriods)}</span>
31933
31941
  <span style="font-size: 10px;">\u25BC</span>
31934
31942
  </button>
31935
31943
  <div id="${modalId}-period-dropdown" style="
@@ -31942,12 +31950,12 @@ function renderModal2(container, state5, modalId) {
31942
31950
  <label style="
31943
31951
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
31944
31952
  cursor: pointer; font-size: 13px; color: ${colors.text};
31945
- " onmouseover="this.style.background='${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
31953
+ " onmouseover="this.style.background='${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
31946
31954
  onmouseout="this.style.background='transparent'">
31947
31955
  <input type="checkbox"
31948
31956
  name="${modalId}-period"
31949
31957
  value="${period.id}"
31950
- ${state5.selectedPeriods.includes(period.id) ? "checked" : ""}
31958
+ ${state6.selectedPeriods.includes(period.id) ? "checked" : ""}
31951
31959
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
31952
31960
  ${period.label}
31953
31961
  </label>
@@ -31955,13 +31963,13 @@ function renderModal2(container, state5, modalId) {
31955
31963
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
31956
31964
  <button id="${modalId}-period-select-all" type="button" style="
31957
31965
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
31958
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31966
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31959
31967
  border: none; border-radius: 4px; cursor: pointer;
31960
31968
  font-size: 12px; color: ${colors.text};
31961
31969
  ">Selecionar Todos</button>
31962
31970
  <button id="${modalId}-period-clear" type="button" style="
31963
31971
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
31964
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31972
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31965
31973
  border: none; border-radius: 4px; cursor: pointer;
31966
31974
  font-size: 12px; color: ${colors.text};
31967
31975
  ">Limpar Sele\xE7\xE3o</button>
@@ -31986,8 +31994,8 @@ function renderModal2(container, state5, modalId) {
31986
31994
  font-size: 14px; font-weight: 500; height: 38px;
31987
31995
  display: flex; align-items: center; gap: 8px;
31988
31996
  font-family: 'Roboto', Arial, sans-serif;
31989
- " ${state5.isLoading ? "disabled" : ""}>
31990
- ${state5.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31997
+ " ${state6.isLoading ? "disabled" : ""}>
31998
+ ${state6.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31991
31999
  </button>
31992
32000
  </div>
31993
32001
 
@@ -32003,14 +32011,14 @@ function renderModal2(container, state5, modalId) {
32003
32011
  <div style="margin-bottom: 24px;">
32004
32012
  <div id="${modalId}-chart" style="
32005
32013
  height: 380px;
32006
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
32014
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
32007
32015
  border-radius: 14px; display: flex; justify-content: center; align-items: center;
32008
32016
  border: 1px solid ${colors.border}; position: relative;
32009
32017
  ">
32010
- ${state5.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
32018
+ ${state6.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
32011
32019
  <div style="animation: spin 1s linear infinite; font-size: 36px; margin-bottom: 12px;">\u21BB</div>
32012
- <div style="font-size: 15px;">Carregando dados de ${state5.devices.length} sensores...</div>
32013
- </div>` : state5.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
32020
+ <div style="font-size: 15px;">Carregando dados de ${state6.devices.length} sensores...</div>
32021
+ </div>` : state6.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
32014
32022
  <div style="font-size: 48px; margin-bottom: 12px;">\u{1F4ED}</div>
32015
32023
  <div style="font-size: 16px;">Sem dados para o per\xEDodo selecionado</div>
32016
32024
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -32028,12 +32036,12 @@ function renderModal2(container, state5, modalId) {
32028
32036
  <!-- Actions -->
32029
32037
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
32030
32038
  <button id="${modalId}-export" style="
32031
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
32039
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
32032
32040
  color: ${colors.text}; border: 1px solid ${colors.border};
32033
32041
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
32034
32042
  font-size: 14px; display: flex; align-items: center; gap: 8px;
32035
32043
  font-family: 'Roboto', Arial, sans-serif;
32036
- " ${state5.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
32044
+ " ${state6.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
32037
32045
  \u{1F4E5} Exportar CSV
32038
32046
  </button>
32039
32047
  <button id="${modalId}-close-btn" style="
@@ -32068,15 +32076,15 @@ function renderModal2(container, state5, modalId) {
32068
32076
  </style>
32069
32077
  `;
32070
32078
  }
32071
- function drawComparisonChart(modalId, state5) {
32079
+ function drawComparisonChart(modalId, state6) {
32072
32080
  const chartContainer = document.getElementById(`${modalId}-chart`);
32073
32081
  const canvas = document.getElementById(`${modalId}-canvas`);
32074
32082
  if (!chartContainer || !canvas) return;
32075
- const hasData = state5.deviceData.some((dd) => dd.data.length > 0);
32083
+ const hasData = state6.deviceData.some((dd) => dd.data.length > 0);
32076
32084
  if (!hasData) return;
32077
32085
  const ctx = canvas.getContext("2d");
32078
32086
  if (!ctx) return;
32079
- const colors = getThemeColors(state5.theme);
32087
+ const colors = getThemeColors(state6.theme);
32080
32088
  const width = chartContainer.clientWidth - 2;
32081
32089
  const height = 380;
32082
32090
  canvas.width = width;
@@ -32087,19 +32095,19 @@ function drawComparisonChart(modalId, state5) {
32087
32095
  const paddingBottom = 55;
32088
32096
  ctx.clearRect(0, 0, width, height);
32089
32097
  const processedData = [];
32090
- state5.deviceData.forEach((dd) => {
32098
+ state6.deviceData.forEach((dd) => {
32091
32099
  if (dd.data.length === 0) return;
32092
- const filteredData = filterByDayPeriods(dd.data, state5.selectedPeriods);
32100
+ const filteredData = filterByDayPeriods(dd.data, state6.selectedPeriods);
32093
32101
  if (filteredData.length === 0) return;
32094
32102
  let points;
32095
- if (state5.granularity === "hour") {
32103
+ if (state6.granularity === "hour") {
32096
32104
  const interpolated = interpolateTemperature(filteredData, {
32097
32105
  intervalMinutes: 30,
32098
- startTs: state5.startTs,
32099
- endTs: state5.endTs,
32100
- clampRange: state5.clampRange
32106
+ startTs: state6.startTs,
32107
+ endTs: state6.endTs,
32108
+ clampRange: state6.clampRange
32101
32109
  });
32102
- const filteredInterpolated = filterByDayPeriods(interpolated, state5.selectedPeriods);
32110
+ const filteredInterpolated = filterByDayPeriods(interpolated, state6.selectedPeriods);
32103
32111
  points = filteredInterpolated.map((item) => ({
32104
32112
  x: item.ts,
32105
32113
  y: Number(item.value),
@@ -32109,7 +32117,7 @@ function drawComparisonChart(modalId, state5) {
32109
32117
  deviceColor: dd.color
32110
32118
  }));
32111
32119
  } else {
32112
- const daily = aggregateByDay(filteredData, state5.clampRange);
32120
+ const daily = aggregateByDay(filteredData, state6.clampRange);
32113
32121
  points = daily.map((item) => ({
32114
32122
  x: item.dateTs,
32115
32123
  y: item.avg,
@@ -32130,7 +32138,7 @@ function drawComparisonChart(modalId, state5) {
32130
32138
  ctx.fillText("Nenhum dado para os per\xEDodos selecionados", width / 2, height / 2);
32131
32139
  return;
32132
32140
  }
32133
- const isPeriodsFiltered = state5.selectedPeriods.length < 4 && state5.selectedPeriods.length > 0;
32141
+ const isPeriodsFiltered = state6.selectedPeriods.length < 4 && state6.selectedPeriods.length > 0;
32134
32142
  let dataMinY = Infinity;
32135
32143
  let dataMaxY = -Infinity;
32136
32144
  processedData.forEach(({ points }) => {
@@ -32140,7 +32148,7 @@ function drawComparisonChart(modalId, state5) {
32140
32148
  });
32141
32149
  });
32142
32150
  const rangeMap = /* @__PURE__ */ new Map();
32143
- state5.deviceData.forEach((dd, index) => {
32151
+ state6.deviceData.forEach((dd, index) => {
32144
32152
  const device = dd.device;
32145
32153
  const min = device.temperatureMin;
32146
32154
  const max = device.temperatureMax;
@@ -32159,10 +32167,10 @@ function drawComparisonChart(modalId, state5) {
32159
32167
  }
32160
32168
  }
32161
32169
  });
32162
- if (rangeMap.size === 0 && state5.temperatureMin !== null && state5.temperatureMax !== null) {
32170
+ if (rangeMap.size === 0 && state6.temperatureMin !== null && state6.temperatureMax !== null) {
32163
32171
  rangeMap.set("global", {
32164
- min: state5.temperatureMin,
32165
- max: state5.temperatureMax,
32172
+ min: state6.temperatureMin,
32173
+ max: state6.temperatureMax,
32166
32174
  customerName: "Global",
32167
32175
  color: colors.success,
32168
32176
  deviceLabels: []
@@ -32283,10 +32291,10 @@ function drawComparisonChart(modalId, state5) {
32283
32291
  const point = xAxisPoints[i];
32284
32292
  const date = new Date(point.x);
32285
32293
  let label;
32286
- if (state5.granularity === "hour") {
32287
- label = date.toLocaleTimeString(state5.locale, { hour: "2-digit", minute: "2-digit" });
32294
+ if (state6.granularity === "hour") {
32295
+ label = date.toLocaleTimeString(state6.locale, { hour: "2-digit", minute: "2-digit" });
32288
32296
  } else {
32289
- label = date.toLocaleDateString(state5.locale, { day: "2-digit", month: "2-digit" });
32297
+ label = date.toLocaleDateString(state6.locale, { day: "2-digit", month: "2-digit" });
32290
32298
  }
32291
32299
  ctx.strokeStyle = colors.chartGrid;
32292
32300
  ctx.lineWidth = 1;
@@ -32305,16 +32313,16 @@ function drawComparisonChart(modalId, state5) {
32305
32313
  ctx.lineTo(width - paddingRight, height - paddingBottom);
32306
32314
  ctx.stroke();
32307
32315
  const allChartPoints = processedData.flatMap((pd) => pd.points);
32308
- setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state5, colors);
32316
+ setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state6, colors);
32309
32317
  }
32310
- function setupComparisonChartTooltip(canvas, container, chartData, state5, colors) {
32318
+ function setupComparisonChartTooltip(canvas, container, chartData, state6, colors) {
32311
32319
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
32312
32320
  if (existingTooltip) existingTooltip.remove();
32313
32321
  const tooltip = document.createElement("div");
32314
32322
  tooltip.className = "myio-chart-tooltip";
32315
32323
  tooltip.style.cssText = `
32316
32324
  position: absolute;
32317
- background: ${state5.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
32325
+ background: ${state6.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
32318
32326
  border: 1px solid ${colors.border};
32319
32327
  border-radius: 8px;
32320
32328
  padding: 10px 14px;
@@ -32351,17 +32359,17 @@ function setupComparisonChartTooltip(canvas, container, chartData, state5, color
32351
32359
  if (point) {
32352
32360
  const date = new Date(point.x);
32353
32361
  let dateStr;
32354
- if (state5.granularity === "hour") {
32355
- dateStr = date.toLocaleDateString(state5.locale, {
32362
+ if (state6.granularity === "hour") {
32363
+ dateStr = date.toLocaleDateString(state6.locale, {
32356
32364
  day: "2-digit",
32357
32365
  month: "2-digit",
32358
32366
  year: "numeric"
32359
- }) + " " + date.toLocaleTimeString(state5.locale, {
32367
+ }) + " " + date.toLocaleTimeString(state6.locale, {
32360
32368
  hour: "2-digit",
32361
32369
  minute: "2-digit"
32362
32370
  });
32363
32371
  } else {
32364
- dateStr = date.toLocaleDateString(state5.locale, {
32372
+ dateStr = date.toLocaleDateString(state6.locale, {
32365
32373
  day: "2-digit",
32366
32374
  month: "2-digit",
32367
32375
  year: "numeric"
@@ -32403,7 +32411,7 @@ function setupComparisonChartTooltip(canvas, container, chartData, state5, color
32403
32411
  canvas.style.cursor = "default";
32404
32412
  });
32405
32413
  }
32406
- async function setupEventListeners2(container, state5, modalId, onClose) {
32414
+ async function setupEventListeners2(container, state6, modalId, onClose) {
32407
32415
  const closeModal = () => {
32408
32416
  container.remove();
32409
32417
  onClose?.();
@@ -32414,19 +32422,19 @@ async function setupEventListeners2(container, state5, modalId, onClose) {
32414
32422
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
32415
32423
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
32416
32424
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
32417
- if (dateRangeInput && !state5.dateRangePicker) {
32425
+ if (dateRangeInput && !state6.dateRangePicker) {
32418
32426
  try {
32419
- state5.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
32420
- presetStart: new Date(state5.startTs).toISOString(),
32421
- presetEnd: new Date(state5.endTs).toISOString(),
32427
+ state6.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
32428
+ presetStart: new Date(state6.startTs).toISOString(),
32429
+ presetEnd: new Date(state6.endTs).toISOString(),
32422
32430
  includeTime: true,
32423
32431
  timePrecision: "minute",
32424
32432
  maxRangeDays: 90,
32425
- locale: state5.locale,
32433
+ locale: state6.locale,
32426
32434
  parentEl: container.querySelector(".myio-temp-comparison-content"),
32427
32435
  onApply: (result) => {
32428
- state5.startTs = new Date(result.startISO).getTime();
32429
- state5.endTs = new Date(result.endISO).getTime();
32436
+ state6.startTs = new Date(result.startISO).getTime();
32437
+ state6.endTs = new Date(result.endISO).getTime();
32430
32438
  console.log("[TemperatureComparisonModal] Date range applied:", result);
32431
32439
  }
32432
32440
  });
@@ -32435,23 +32443,23 @@ async function setupEventListeners2(container, state5, modalId, onClose) {
32435
32443
  }
32436
32444
  }
32437
32445
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
32438
- state5.theme = state5.theme === "dark" ? "light" : "dark";
32439
- localStorage.setItem("myio-temp-comparison-theme", state5.theme);
32440
- state5.dateRangePicker = null;
32441
- renderModal2(container, state5, modalId);
32442
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32443
- drawComparisonChart(modalId, state5);
32444
- }
32445
- await setupEventListeners2(container, state5, modalId, onClose);
32446
+ state6.theme = state6.theme === "dark" ? "light" : "dark";
32447
+ localStorage.setItem("myio-temp-comparison-theme", state6.theme);
32448
+ state6.dateRangePicker = null;
32449
+ renderModal2(container, state6, modalId);
32450
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32451
+ drawComparisonChart(modalId, state6);
32452
+ }
32453
+ await setupEventListeners2(container, state6, modalId, onClose);
32446
32454
  });
32447
32455
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
32448
32456
  container.__isMaximized = !container.__isMaximized;
32449
- state5.dateRangePicker = null;
32450
- renderModal2(container, state5, modalId);
32451
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32452
- drawComparisonChart(modalId, state5);
32457
+ state6.dateRangePicker = null;
32458
+ renderModal2(container, state6, modalId);
32459
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32460
+ drawComparisonChart(modalId, state6);
32453
32461
  }
32454
- await setupEventListeners2(container, state5, modalId, onClose);
32462
+ await setupEventListeners2(container, state6, modalId, onClose);
32455
32463
  });
32456
32464
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
32457
32465
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -32470,13 +32478,13 @@ async function setupEventListeners2(container, state5, modalId, onClose) {
32470
32478
  periodCheckboxes.forEach((checkbox) => {
32471
32479
  checkbox.addEventListener("change", () => {
32472
32480
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
32473
- state5.selectedPeriods = checked;
32481
+ state6.selectedPeriods = checked;
32474
32482
  const btnLabel = periodBtn?.querySelector("span:first-child");
32475
32483
  if (btnLabel) {
32476
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32484
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32477
32485
  }
32478
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32479
- drawComparisonChart(modalId, state5);
32486
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32487
+ drawComparisonChart(modalId, state6);
32480
32488
  }
32481
32489
  });
32482
32490
  });
@@ -32484,77 +32492,77 @@ async function setupEventListeners2(container, state5, modalId, onClose) {
32484
32492
  periodCheckboxes.forEach((cb) => {
32485
32493
  cb.checked = true;
32486
32494
  });
32487
- state5.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
32495
+ state6.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
32488
32496
  const btnLabel = periodBtn?.querySelector("span:first-child");
32489
32497
  if (btnLabel) {
32490
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32498
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32491
32499
  }
32492
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32493
- drawComparisonChart(modalId, state5);
32500
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32501
+ drawComparisonChart(modalId, state6);
32494
32502
  }
32495
32503
  });
32496
32504
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
32497
32505
  periodCheckboxes.forEach((cb) => {
32498
32506
  cb.checked = false;
32499
32507
  });
32500
- state5.selectedPeriods = [];
32508
+ state6.selectedPeriods = [];
32501
32509
  const btnLabel = periodBtn?.querySelector("span:first-child");
32502
32510
  if (btnLabel) {
32503
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32511
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32504
32512
  }
32505
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32506
- drawComparisonChart(modalId, state5);
32513
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32514
+ drawComparisonChart(modalId, state6);
32507
32515
  }
32508
32516
  });
32509
32517
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
32510
- state5.granularity = e.target.value;
32511
- localStorage.setItem("myio-temp-comparison-granularity", state5.granularity);
32512
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32513
- drawComparisonChart(modalId, state5);
32518
+ state6.granularity = e.target.value;
32519
+ localStorage.setItem("myio-temp-comparison-granularity", state6.granularity);
32520
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32521
+ drawComparisonChart(modalId, state6);
32514
32522
  }
32515
32523
  });
32516
32524
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
32517
- if (state5.startTs >= state5.endTs) {
32525
+ if (state6.startTs >= state6.endTs) {
32518
32526
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
32519
32527
  return;
32520
32528
  }
32521
- state5.isLoading = true;
32522
- state5.dateRangePicker = null;
32523
- renderModal2(container, state5, modalId);
32524
- await fetchAllDevicesData(state5);
32525
- renderModal2(container, state5, modalId);
32526
- drawComparisonChart(modalId, state5);
32527
- await setupEventListeners2(container, state5, modalId, onClose);
32529
+ state6.isLoading = true;
32530
+ state6.dateRangePicker = null;
32531
+ renderModal2(container, state6, modalId);
32532
+ await fetchAllDevicesData(state6);
32533
+ renderModal2(container, state6, modalId);
32534
+ drawComparisonChart(modalId, state6);
32535
+ await setupEventListeners2(container, state6, modalId, onClose);
32528
32536
  });
32529
32537
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
32530
- if (state5.deviceData.every((dd) => dd.data.length === 0)) return;
32531
- exportComparisonCSV(state5);
32538
+ if (state6.deviceData.every((dd) => dd.data.length === 0)) return;
32539
+ exportComparisonCSV(state6);
32532
32540
  });
32533
32541
  }
32534
- function exportComparisonCSV(state5) {
32535
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
32536
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
32542
+ function exportComparisonCSV(state6) {
32543
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
32544
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
32537
32545
  const BOM = "\uFEFF";
32538
32546
  let csvContent = BOM;
32539
32547
  csvContent += `Compara\xE7\xE3o de Temperatura
32540
32548
  `;
32541
32549
  csvContent += `Per\xEDodo: ${startDateStr} at\xE9 ${endDateStr}
32542
32550
  `;
32543
- csvContent += `Sensores: ${state5.devices.map((d) => d.label).join(", ")}
32551
+ csvContent += `Sensores: ${state6.devices.map((d) => d.label).join(", ")}
32544
32552
  `;
32545
32553
  csvContent += "\n";
32546
32554
  csvContent += "Estat\xEDsticas por Sensor:\n";
32547
32555
  csvContent += "Sensor,M\xE9dia (\xB0C),Min (\xB0C),Max (\xB0C),Leituras\n";
32548
- state5.deviceData.forEach((dd) => {
32556
+ state6.deviceData.forEach((dd) => {
32549
32557
  csvContent += `"${dd.device.label}",${dd.stats.avg.toFixed(2)},${dd.stats.min.toFixed(2)},${dd.stats.max.toFixed(2)},${dd.stats.count}
32550
32558
  `;
32551
32559
  });
32552
32560
  csvContent += "\n";
32553
32561
  csvContent += "Dados Detalhados:\n";
32554
32562
  csvContent += "Data/Hora,Sensor,Temperatura (\xB0C)\n";
32555
- state5.deviceData.forEach((dd) => {
32563
+ state6.deviceData.forEach((dd) => {
32556
32564
  dd.data.forEach((item) => {
32557
- const date = new Date(item.ts).toLocaleString(state5.locale);
32565
+ const date = new Date(item.ts).toLocaleString(state6.locale);
32558
32566
  const temp = Number(item.value).toFixed(2);
32559
32567
  csvContent += `"${date}","${dd.device.label}",${temp}
32560
32568
  `;
@@ -32662,10 +32670,10 @@ async function saveCustomerAttributes(customerId, token, minTemperature, maxTemp
32662
32670
  throw new Error(`Failed to save attributes: ${response.status}`);
32663
32671
  }
32664
32672
  }
32665
- function renderModal3(container, state5, modalId, onClose, onSave) {
32666
- const colors = getColors(state5.theme);
32667
- const minValue = state5.minTemperature !== null ? state5.minTemperature : "";
32668
- const maxValue = state5.maxTemperature !== null ? state5.maxTemperature : "";
32673
+ function renderModal3(container, state6, modalId, onClose, onSave) {
32674
+ const colors = getColors(state6.theme);
32675
+ const minValue = state6.minTemperature !== null ? state6.minTemperature : "";
32676
+ const maxValue = state6.maxTemperature !== null ? state6.maxTemperature : "";
32669
32677
  container.innerHTML = `
32670
32678
  <style>
32671
32679
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@@ -32930,23 +32938,23 @@ function renderModal3(container, state5, modalId, onClose, onSave) {
32930
32938
  </div>
32931
32939
 
32932
32940
  <div class="modal-body">
32933
- ${state5.isLoading ? `
32941
+ ${state6.isLoading ? `
32934
32942
  <div class="loading-overlay">
32935
32943
  <div class="loading-spinner"></div>
32936
32944
  <div>Carregando configura\xE7\xF5es...</div>
32937
32945
  </div>
32938
32946
  ` : `
32939
- ${state5.error ? `
32940
- <div class="message message-error">${state5.error}</div>
32947
+ ${state6.error ? `
32948
+ <div class="message message-error">${state6.error}</div>
32941
32949
  ` : ""}
32942
32950
 
32943
- ${state5.successMessage ? `
32944
- <div class="message message-success">${state5.successMessage}</div>
32951
+ ${state6.successMessage ? `
32952
+ <div class="message message-success">${state6.successMessage}</div>
32945
32953
  ` : ""}
32946
32954
 
32947
32955
  <div class="customer-info">
32948
32956
  <div class="customer-label">Shopping / Cliente</div>
32949
- <div class="customer-name">${state5.customerName || "N\xE3o identificado"}</div>
32957
+ <div class="customer-name">${state6.customerName || "N\xE3o identificado"}</div>
32950
32958
  </div>
32951
32959
 
32952
32960
  <div class="form-group">
@@ -32990,11 +32998,11 @@ function renderModal3(container, state5, modalId, onClose, onSave) {
32990
32998
  `}
32991
32999
  </div>
32992
33000
 
32993
- ${!state5.isLoading ? `
33001
+ ${!state6.isLoading ? `
32994
33002
  <div class="modal-footer">
32995
33003
  <button class="btn btn-secondary" id="${modalId}-cancel">Cancelar</button>
32996
- <button class="btn btn-primary" id="${modalId}-save" ${state5.isSaving ? "disabled" : ""}>
32997
- ${state5.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
33004
+ <button class="btn btn-primary" id="${modalId}-save" ${state6.isSaving ? "disabled" : ""}>
33005
+ ${state6.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
32998
33006
  </button>
32999
33007
  </div>
33000
33008
  ` : ""}
@@ -33031,18 +33039,18 @@ function renderModal3(container, state5, modalId, onClose, onSave) {
33031
33039
  const min = parseFloat(minInput.value);
33032
33040
  const max = parseFloat(maxInput.value);
33033
33041
  if (isNaN(min) || isNaN(max)) {
33034
- state5.error = "Por favor, preencha ambos os valores.";
33035
- renderModal3(container, state5, modalId, onClose, onSave);
33042
+ state6.error = "Por favor, preencha ambos os valores.";
33043
+ renderModal3(container, state6, modalId, onClose, onSave);
33036
33044
  return;
33037
33045
  }
33038
33046
  if (min >= max) {
33039
- state5.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
33040
- renderModal3(container, state5, modalId, onClose, onSave);
33047
+ state6.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
33048
+ renderModal3(container, state6, modalId, onClose, onSave);
33041
33049
  return;
33042
33050
  }
33043
33051
  if (min < 0 || max > 50) {
33044
- state5.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
33045
- renderModal3(container, state5, modalId, onClose, onSave);
33052
+ state6.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
33053
+ renderModal3(container, state6, modalId, onClose, onSave);
33046
33054
  return;
33047
33055
  }
33048
33056
  await onSave(min, max);
@@ -33050,7 +33058,7 @@ function renderModal3(container, state5, modalId, onClose, onSave) {
33050
33058
  }
33051
33059
  function openTemperatureSettingsModal(params) {
33052
33060
  const modalId = `myio-temp-settings-${Date.now()}`;
33053
- const state5 = {
33061
+ const state6 = {
33054
33062
  customerId: params.customerId,
33055
33063
  customerName: params.customerName || "",
33056
33064
  token: params.token,
@@ -33070,37 +33078,37 @@ function openTemperatureSettingsModal(params) {
33070
33078
  params.onClose?.();
33071
33079
  };
33072
33080
  const handleSave = async (min, max) => {
33073
- state5.isSaving = true;
33074
- state5.error = null;
33075
- state5.successMessage = null;
33076
- renderModal3(container, state5, modalId, destroy, handleSave);
33081
+ state6.isSaving = true;
33082
+ state6.error = null;
33083
+ state6.successMessage = null;
33084
+ renderModal3(container, state6, modalId, destroy, handleSave);
33077
33085
  try {
33078
- await saveCustomerAttributes(state5.customerId, state5.token, min, max, params.onError);
33079
- state5.minTemperature = min;
33080
- state5.maxTemperature = max;
33081
- state5.isSaving = false;
33082
- state5.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
33083
- renderModal3(container, state5, modalId, destroy, handleSave);
33086
+ await saveCustomerAttributes(state6.customerId, state6.token, min, max, params.onError);
33087
+ state6.minTemperature = min;
33088
+ state6.maxTemperature = max;
33089
+ state6.isSaving = false;
33090
+ state6.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
33091
+ renderModal3(container, state6, modalId, destroy, handleSave);
33084
33092
  params.onSave?.({ minTemperature: min, maxTemperature: max });
33085
33093
  setTimeout(() => {
33086
33094
  destroy();
33087
33095
  }, 1500);
33088
33096
  } catch (error) {
33089
- state5.isSaving = false;
33090
- state5.error = `Erro ao salvar: ${error.message}`;
33091
- renderModal3(container, state5, modalId, destroy, handleSave);
33097
+ state6.isSaving = false;
33098
+ state6.error = `Erro ao salvar: ${error.message}`;
33099
+ renderModal3(container, state6, modalId, destroy, handleSave);
33092
33100
  }
33093
33101
  };
33094
- renderModal3(container, state5, modalId, destroy, handleSave);
33095
- fetchCustomerAttributes(state5.customerId, state5.token, params.onError).then(({ minTemperature, maxTemperature }) => {
33096
- state5.minTemperature = minTemperature;
33097
- state5.maxTemperature = maxTemperature;
33098
- state5.isLoading = false;
33099
- renderModal3(container, state5, modalId, destroy, handleSave);
33102
+ renderModal3(container, state6, modalId, destroy, handleSave);
33103
+ fetchCustomerAttributes(state6.customerId, state6.token, params.onError).then(({ minTemperature, maxTemperature }) => {
33104
+ state6.minTemperature = minTemperature;
33105
+ state6.maxTemperature = maxTemperature;
33106
+ state6.isLoading = false;
33107
+ renderModal3(container, state6, modalId, destroy, handleSave);
33100
33108
  }).catch((error) => {
33101
- state5.isLoading = false;
33102
- state5.error = `Erro ao carregar: ${error.message}`;
33103
- renderModal3(container, state5, modalId, destroy, handleSave);
33109
+ state6.isLoading = false;
33110
+ state6.error = `Erro ao carregar: ${error.message}`;
33111
+ renderModal3(container, state6, modalId, destroy, handleSave);
33104
33112
  });
33105
33113
  return { destroy };
33106
33114
  }
@@ -34370,7 +34378,7 @@ var EnergySummaryTooltip = {
34370
34378
  * RFC-0105 Enhancement: Now fetches device lists from MyIOOrchestratorData
34371
34379
  * to populate device lists for status popup display
34372
34380
  */
34373
- buildSummaryFromState(state5, receivedData, domain = "energy") {
34381
+ buildSummaryFromState(state6, receivedData, domain = "energy") {
34374
34382
  const summary = {
34375
34383
  totalDevices: 0,
34376
34384
  totalConsumption: 0,
@@ -34393,22 +34401,22 @@ var EnergySummaryTooltip = {
34393
34401
  },
34394
34402
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
34395
34403
  };
34396
- if (!state5) return summary;
34404
+ if (!state6) return summary;
34397
34405
  const entrada = {
34398
34406
  id: "entrada",
34399
34407
  name: "Entrada",
34400
34408
  icon: CATEGORY_ICONS.entrada,
34401
- deviceCount: state5.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
34402
- consumption: state5.entrada?.total || 0,
34409
+ deviceCount: state6.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
34410
+ consumption: state6.entrada?.total || 0,
34403
34411
  percentage: 100
34404
34412
  };
34405
34413
  const lojas = {
34406
34414
  id: "lojas",
34407
34415
  name: "Lojas",
34408
34416
  icon: CATEGORY_ICONS.lojas,
34409
- deviceCount: state5.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
34410
- consumption: state5.consumidores?.lojas?.total || 0,
34411
- percentage: state5.consumidores?.lojas?.perc || 0
34417
+ deviceCount: state6.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
34418
+ consumption: state6.consumidores?.lojas?.total || 0,
34419
+ percentage: state6.consumidores?.lojas?.perc || 0
34412
34420
  };
34413
34421
  const climatizacaoData = receivedData?.climatizacao || {};
34414
34422
  const elevadoresData = receivedData?.elevadores || {};
@@ -34419,9 +34427,9 @@ var EnergySummaryTooltip = {
34419
34427
  id: "climatizacao",
34420
34428
  name: "Climatizacao",
34421
34429
  icon: CATEGORY_ICONS.climatizacao,
34422
- deviceCount: climatizacaoData.count || state5.consumidores?.climatizacao?.devices?.length || 0,
34423
- consumption: state5.consumidores?.climatizacao?.total || 0,
34424
- percentage: state5.consumidores?.climatizacao?.perc || 0
34430
+ deviceCount: climatizacaoData.count || state6.consumidores?.climatizacao?.devices?.length || 0,
34431
+ consumption: state6.consumidores?.climatizacao?.total || 0,
34432
+ percentage: state6.consumidores?.climatizacao?.perc || 0
34425
34433
  };
34426
34434
  if (climatizacaoData.subcategories) {
34427
34435
  climatizacao.children = [];
@@ -34472,40 +34480,40 @@ var EnergySummaryTooltip = {
34472
34480
  id: "elevadores",
34473
34481
  name: "Elevadores",
34474
34482
  icon: CATEGORY_ICONS.elevadores,
34475
- deviceCount: elevadoresData.count || state5.consumidores?.elevadores?.devices?.length || 0,
34476
- consumption: state5.consumidores?.elevadores?.total || 0,
34477
- percentage: state5.consumidores?.elevadores?.perc || 0
34483
+ deviceCount: elevadoresData.count || state6.consumidores?.elevadores?.devices?.length || 0,
34484
+ consumption: state6.consumidores?.elevadores?.total || 0,
34485
+ percentage: state6.consumidores?.elevadores?.perc || 0
34478
34486
  });
34479
34487
  areaComumChildren.push({
34480
34488
  id: "escadasRolantes",
34481
34489
  name: "Esc. Rolantes",
34482
34490
  icon: CATEGORY_ICONS.escadas,
34483
- deviceCount: escadasData.count || state5.consumidores?.escadasRolantes?.devices?.length || 0,
34484
- consumption: state5.consumidores?.escadasRolantes?.total || 0,
34485
- percentage: state5.consumidores?.escadasRolantes?.perc || 0
34491
+ deviceCount: escadasData.count || state6.consumidores?.escadasRolantes?.devices?.length || 0,
34492
+ consumption: state6.consumidores?.escadasRolantes?.total || 0,
34493
+ percentage: state6.consumidores?.escadasRolantes?.perc || 0
34486
34494
  });
34487
34495
  areaComumChildren.push({
34488
34496
  id: "outros",
34489
34497
  name: "Outros",
34490
34498
  icon: CATEGORY_ICONS.outros,
34491
- deviceCount: outrosData.count || state5.consumidores?.outros?.devices?.length || 0,
34492
- consumption: state5.consumidores?.outros?.total || 0,
34493
- percentage: state5.consumidores?.outros?.perc || 0
34499
+ deviceCount: outrosData.count || state6.consumidores?.outros?.devices?.length || 0,
34500
+ consumption: state6.consumidores?.outros?.total || 0,
34501
+ percentage: state6.consumidores?.outros?.perc || 0
34494
34502
  });
34495
34503
  const areaComumDeviceCount = areaComumChildren.reduce((sum, c) => sum + c.deviceCount, 0);
34496
- const areaComumConsumption = state5.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
34504
+ const areaComumConsumption = state6.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
34497
34505
  const areaComum = {
34498
34506
  id: "areaComum",
34499
34507
  name: "Area Comum",
34500
34508
  icon: CATEGORY_ICONS.areaComum,
34501
34509
  deviceCount: areaComumDeviceCount,
34502
34510
  consumption: areaComumConsumption,
34503
- percentage: state5.consumidores?.areaComum?.perc || 0,
34511
+ percentage: state6.consumidores?.areaComum?.perc || 0,
34504
34512
  children: areaComumChildren
34505
34513
  };
34506
34514
  summary.byCategory = [entrada, lojas, areaComum];
34507
34515
  summary.totalDevices = entrada.deviceCount + lojas.deviceCount + areaComumDeviceCount;
34508
- summary.totalConsumption = state5.grandTotal || entrada.consumption;
34516
+ summary.totalConsumption = state6.grandTotal || entrada.consumption;
34509
34517
  const widgetAggregation = receivedData?.deviceStatusAggregation;
34510
34518
  if (widgetAggregation && widgetAggregation.hasData) {
34511
34519
  summary.byStatus = {
@@ -35793,7 +35801,7 @@ var WaterSummaryTooltip = {
35793
35801
  * RFC-0105 Enhancement: Now fetches device lists from MyIOOrchestratorData
35794
35802
  * to populate device lists for status popup display
35795
35803
  */
35796
- buildSummaryFromState(state5, receivedData, includeBathrooms = false, domain = "water") {
35804
+ buildSummaryFromState(state6, receivedData, includeBathrooms = false, domain = "water") {
35797
35805
  const summary = {
35798
35806
  totalDevices: 0,
35799
35807
  totalConsumption: 0,
@@ -35817,22 +35825,22 @@ var WaterSummaryTooltip = {
35817
35825
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
35818
35826
  includeBathrooms
35819
35827
  };
35820
- if (!state5) return summary;
35828
+ if (!state6) return summary;
35821
35829
  const entrada = {
35822
35830
  id: "entrada",
35823
35831
  name: "Entrada",
35824
35832
  icon: WATER_CATEGORY_ICONS.entrada,
35825
- deviceCount: state5.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
35826
- consumption: state5.entrada?.total || 0,
35833
+ deviceCount: state6.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
35834
+ consumption: state6.entrada?.total || 0,
35827
35835
  percentage: 100
35828
35836
  };
35829
35837
  const lojas = {
35830
35838
  id: "lojas",
35831
35839
  name: "Lojas",
35832
35840
  icon: WATER_CATEGORY_ICONS.lojas,
35833
- deviceCount: state5.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
35834
- consumption: state5.lojas?.total || 0,
35835
- percentage: state5.lojas?.perc || 0
35841
+ deviceCount: state6.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
35842
+ consumption: state6.lojas?.total || 0,
35843
+ percentage: state6.lojas?.perc || 0
35836
35844
  };
35837
35845
  summary.byCategory = [entrada, lojas];
35838
35846
  if (includeBathrooms) {
@@ -35840,9 +35848,9 @@ var WaterSummaryTooltip = {
35840
35848
  id: "banheiros",
35841
35849
  name: "Banheiros",
35842
35850
  icon: WATER_CATEGORY_ICONS.banheiros,
35843
- deviceCount: state5.banheiros?.devices?.length || (receivedData?.banheiros_total?.device_count || 0),
35844
- consumption: state5.banheiros?.total || 0,
35845
- percentage: state5.banheiros?.perc || 0
35851
+ deviceCount: state6.banheiros?.devices?.length || (receivedData?.banheiros_total?.device_count || 0),
35852
+ consumption: state6.banheiros?.total || 0,
35853
+ percentage: state6.banheiros?.perc || 0
35846
35854
  };
35847
35855
  summary.byCategory.push(banheiros);
35848
35856
  }
@@ -35850,24 +35858,24 @@ var WaterSummaryTooltip = {
35850
35858
  id: "areaComum",
35851
35859
  name: "\xC1rea Comum",
35852
35860
  icon: WATER_CATEGORY_ICONS.areaComum,
35853
- deviceCount: state5.areaComum?.devices?.length || (receivedData?.area_comum_total?.device_count || 0),
35854
- consumption: state5.areaComum?.total || 0,
35855
- percentage: state5.areaComum?.perc || 0
35861
+ deviceCount: state6.areaComum?.devices?.length || (receivedData?.area_comum_total?.device_count || 0),
35862
+ consumption: state6.areaComum?.total || 0,
35863
+ percentage: state6.areaComum?.perc || 0
35856
35864
  };
35857
35865
  summary.byCategory.push(areaComum);
35858
- if (state5.pontosNaoMapeados && state5.pontosNaoMapeados.total > 0) {
35866
+ if (state6.pontosNaoMapeados && state6.pontosNaoMapeados.total > 0) {
35859
35867
  const pontosNaoMapeados = {
35860
35868
  id: "pontosNaoMapeados",
35861
35869
  name: "Pontos N\xE3o Mapeados",
35862
35870
  icon: WATER_CATEGORY_ICONS.pontosNaoMapeados,
35863
- deviceCount: state5.pontosNaoMapeados?.devices?.length || 0,
35864
- consumption: state5.pontosNaoMapeados?.total || 0,
35865
- percentage: state5.pontosNaoMapeados?.perc || 0
35871
+ deviceCount: state6.pontosNaoMapeados?.devices?.length || 0,
35872
+ consumption: state6.pontosNaoMapeados?.total || 0,
35873
+ percentage: state6.pontosNaoMapeados?.perc || 0
35866
35874
  };
35867
35875
  summary.byCategory.push(pontosNaoMapeados);
35868
35876
  }
35869
35877
  summary.totalDevices = summary.byCategory.reduce((sum, cat) => sum + cat.deviceCount, 0);
35870
- summary.totalConsumption = state5.entrada?.total || 0;
35878
+ summary.totalConsumption = state6.entrada?.total || 0;
35871
35879
  const widgetAggregation = receivedData?.deviceStatusAggregation;
35872
35880
  if (widgetAggregation && widgetAggregation.hasData) {
35873
35881
  summary.byStatus = {
@@ -37404,6 +37412,996 @@ var TempSensorSummaryTooltip = {
37404
37412
  }
37405
37413
  };
37406
37414
 
37415
+ // src/utils/ContractSummaryTooltip.ts
37416
+ var CONTRACT_SUMMARY_TOOLTIP_CSS = `
37417
+ /* ============================================
37418
+ Contract Summary Tooltip (RFC-0107)
37419
+ Premium draggable tooltip with dark theme
37420
+ ============================================ */
37421
+
37422
+ .myio-contract-summary-tooltip {
37423
+ position: fixed;
37424
+ z-index: 99999;
37425
+ pointer-events: none;
37426
+ opacity: 0;
37427
+ transition: opacity 0.25s ease, transform 0.25s ease;
37428
+ transform: translateY(5px);
37429
+ }
37430
+
37431
+ .myio-contract-summary-tooltip.visible {
37432
+ opacity: 1;
37433
+ pointer-events: auto;
37434
+ transform: translateY(0);
37435
+ }
37436
+
37437
+ .myio-contract-summary-tooltip.closing {
37438
+ opacity: 0;
37439
+ transform: translateY(8px);
37440
+ transition: opacity 0.4s ease, transform 0.4s ease;
37441
+ }
37442
+
37443
+ .myio-contract-summary-tooltip.pinned {
37444
+ box-shadow: 0 0 0 2px #9684B5, 0 10px 40px rgba(0, 0, 0, 0.3);
37445
+ border-radius: 16px;
37446
+ }
37447
+
37448
+ .myio-contract-summary-tooltip.dragging {
37449
+ transition: none !important;
37450
+ cursor: move;
37451
+ }
37452
+
37453
+ .myio-contract-summary-tooltip.maximized {
37454
+ top: 20px !important;
37455
+ left: 20px !important;
37456
+ right: 20px !important;
37457
+ bottom: 20px !important;
37458
+ width: auto !important;
37459
+ max-width: none !important;
37460
+ }
37461
+
37462
+ .myio-contract-summary-tooltip.maximized .myio-contract-summary-tooltip__panel {
37463
+ width: 100%;
37464
+ height: 100%;
37465
+ max-width: none;
37466
+ display: flex;
37467
+ flex-direction: column;
37468
+ }
37469
+
37470
+ .myio-contract-summary-tooltip.maximized .myio-contract-summary-tooltip__body {
37471
+ flex: 1;
37472
+ overflow-y: auto;
37473
+ }
37474
+
37475
+ .myio-contract-summary-tooltip__panel {
37476
+ background: #2d1458;
37477
+ border: 1px solid rgba(255, 255, 255, 0.1);
37478
+ border-radius: 16px;
37479
+ box-shadow:
37480
+ 0 20px 60px rgba(0, 0, 0, 0.4),
37481
+ 0 8px 20px rgba(0, 0, 0, 0.25),
37482
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
37483
+ min-width: 320px;
37484
+ max-width: 380px;
37485
+ font-family: Inter, system-ui, -apple-system, sans-serif;
37486
+ font-size: 12px;
37487
+ color: #ffffff;
37488
+ overflow: hidden;
37489
+ }
37490
+
37491
+ /* Header */
37492
+ .myio-contract-summary-tooltip__header {
37493
+ display: flex;
37494
+ align-items: center;
37495
+ gap: 10px;
37496
+ padding: 14px 16px;
37497
+ background: linear-gradient(135deg, #9684B5 0%, #2d1458 100%);
37498
+ border-radius: 16px 16px 0 0;
37499
+ position: relative;
37500
+ overflow: hidden;
37501
+ cursor: move;
37502
+ user-select: none;
37503
+ }
37504
+
37505
+ .myio-contract-summary-tooltip__header::before {
37506
+ content: '';
37507
+ position: absolute;
37508
+ top: 0;
37509
+ left: 0;
37510
+ right: 0;
37511
+ bottom: 0;
37512
+ 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");
37513
+ opacity: 0.3;
37514
+ }
37515
+
37516
+ .myio-contract-summary-tooltip__icon {
37517
+ width: 40px;
37518
+ height: 40px;
37519
+ background: rgba(255, 255, 255, 0.15);
37520
+ border-radius: 12px;
37521
+ display: flex;
37522
+ align-items: center;
37523
+ justify-content: center;
37524
+ font-size: 20px;
37525
+ backdrop-filter: blur(10px);
37526
+ position: relative;
37527
+ z-index: 1;
37528
+ }
37529
+
37530
+ .myio-contract-summary-tooltip__icon.valid {
37531
+ background: rgba(76, 175, 80, 0.3);
37532
+ }
37533
+
37534
+ .myio-contract-summary-tooltip__icon.invalid {
37535
+ background: rgba(244, 67, 54, 0.3);
37536
+ }
37537
+
37538
+ .myio-contract-summary-tooltip__header-info {
37539
+ flex: 1;
37540
+ position: relative;
37541
+ z-index: 1;
37542
+ }
37543
+
37544
+ .myio-contract-summary-tooltip__title {
37545
+ font-weight: 700;
37546
+ font-size: 15px;
37547
+ color: #ffffff;
37548
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
37549
+ margin-bottom: 2px;
37550
+ }
37551
+
37552
+ .myio-contract-summary-tooltip__subtitle {
37553
+ font-size: 11px;
37554
+ color: rgba(255, 255, 255, 0.7);
37555
+ }
37556
+
37557
+ .myio-contract-summary-tooltip__header-actions {
37558
+ display: flex;
37559
+ align-items: center;
37560
+ gap: 4px;
37561
+ position: relative;
37562
+ z-index: 1;
37563
+ }
37564
+
37565
+ .myio-contract-summary-tooltip__header-btn {
37566
+ width: 28px;
37567
+ height: 28px;
37568
+ border: none;
37569
+ background: rgba(255, 255, 255, 0.15);
37570
+ border-radius: 8px;
37571
+ cursor: pointer;
37572
+ display: flex;
37573
+ align-items: center;
37574
+ justify-content: center;
37575
+ transition: all 0.2s ease;
37576
+ color: rgba(255, 255, 255, 0.8);
37577
+ }
37578
+
37579
+ .myio-contract-summary-tooltip__header-btn:hover {
37580
+ background: rgba(255, 255, 255, 0.25);
37581
+ color: #ffffff;
37582
+ transform: scale(1.05);
37583
+ }
37584
+
37585
+ .myio-contract-summary-tooltip__header-btn.pinned {
37586
+ background: rgba(255, 255, 255, 0.9);
37587
+ color: #9684B5;
37588
+ }
37589
+
37590
+ .myio-contract-summary-tooltip__header-btn.pinned:hover {
37591
+ background: #ffffff;
37592
+ color: #2d1458;
37593
+ }
37594
+
37595
+ .myio-contract-summary-tooltip__header-btn svg {
37596
+ width: 14px;
37597
+ height: 14px;
37598
+ }
37599
+
37600
+ /* Body */
37601
+ .myio-contract-summary-tooltip__body {
37602
+ padding: 16px;
37603
+ }
37604
+
37605
+ /* Domain Section */
37606
+ .myio-contract-summary-tooltip__domain {
37607
+ margin-bottom: 14px;
37608
+ background: rgba(255, 255, 255, 0.05);
37609
+ border-radius: 12px;
37610
+ overflow: hidden;
37611
+ border: 1px solid rgba(255, 255, 255, 0.08);
37612
+ }
37613
+
37614
+ .myio-contract-summary-tooltip__domain:last-child {
37615
+ margin-bottom: 0;
37616
+ }
37617
+
37618
+ .myio-contract-summary-tooltip__domain-header {
37619
+ display: flex;
37620
+ align-items: center;
37621
+ justify-content: space-between;
37622
+ padding: 10px 14px;
37623
+ cursor: pointer;
37624
+ transition: background 0.2s ease;
37625
+ }
37626
+
37627
+ .myio-contract-summary-tooltip__domain-header:hover {
37628
+ background: rgba(255, 255, 255, 0.05);
37629
+ }
37630
+
37631
+ .myio-contract-summary-tooltip__domain-info {
37632
+ display: flex;
37633
+ align-items: center;
37634
+ gap: 8px;
37635
+ }
37636
+
37637
+ .myio-contract-summary-tooltip__domain-icon {
37638
+ font-size: 18px;
37639
+ }
37640
+
37641
+ .myio-contract-summary-tooltip__domain-name {
37642
+ font-weight: 600;
37643
+ font-size: 13px;
37644
+ }
37645
+
37646
+ .myio-contract-summary-tooltip__domain-count {
37647
+ font-size: 12px;
37648
+ color: #81c784;
37649
+ font-weight: 600;
37650
+ }
37651
+
37652
+ .myio-contract-summary-tooltip__expand-icon {
37653
+ font-size: 10px;
37654
+ opacity: 0.6;
37655
+ transition: transform 0.3s ease;
37656
+ }
37657
+
37658
+ .myio-contract-summary-tooltip__domain.expanded .myio-contract-summary-tooltip__expand-icon {
37659
+ transform: rotate(180deg);
37660
+ }
37661
+
37662
+ /* Domain Details */
37663
+ .myio-contract-summary-tooltip__domain-details {
37664
+ max-height: 0;
37665
+ overflow: hidden;
37666
+ transition: max-height 0.3s ease;
37667
+ background: rgba(0, 0, 0, 0.15);
37668
+ }
37669
+
37670
+ .myio-contract-summary-tooltip__domain.expanded .myio-contract-summary-tooltip__domain-details {
37671
+ max-height: 150px;
37672
+ }
37673
+
37674
+ .myio-contract-summary-tooltip__detail-row {
37675
+ display: flex;
37676
+ align-items: center;
37677
+ justify-content: space-between;
37678
+ padding: 6px 14px 6px 40px;
37679
+ font-size: 12px;
37680
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
37681
+ }
37682
+
37683
+ .myio-contract-summary-tooltip__detail-row:first-child {
37684
+ border-top: none;
37685
+ }
37686
+
37687
+ .myio-contract-summary-tooltip__detail-label {
37688
+ opacity: 0.7;
37689
+ display: flex;
37690
+ align-items: center;
37691
+ gap: 6px;
37692
+ }
37693
+
37694
+ .myio-contract-summary-tooltip__detail-label::before {
37695
+ content: '';
37696
+ width: 4px;
37697
+ height: 4px;
37698
+ border-radius: 50%;
37699
+ background: currentColor;
37700
+ opacity: 0.5;
37701
+ }
37702
+
37703
+ .myio-contract-summary-tooltip__detail-count {
37704
+ font-weight: 500;
37705
+ color: #81c784;
37706
+ }
37707
+
37708
+ /* Status Banner */
37709
+ .myio-contract-summary-tooltip__status {
37710
+ display: flex;
37711
+ align-items: center;
37712
+ justify-content: center;
37713
+ gap: 8px;
37714
+ padding: 10px 14px;
37715
+ border-radius: 10px;
37716
+ margin-bottom: 14px;
37717
+ font-size: 12px;
37718
+ font-weight: 600;
37719
+ }
37720
+
37721
+ .myio-contract-summary-tooltip__status.valid {
37722
+ background: rgba(76, 175, 80, 0.2);
37723
+ color: #81c784;
37724
+ border: 1px solid rgba(76, 175, 80, 0.3);
37725
+ }
37726
+
37727
+ .myio-contract-summary-tooltip__status.invalid {
37728
+ background: rgba(244, 67, 54, 0.2);
37729
+ color: #ef5350;
37730
+ border: 1px solid rgba(244, 67, 54, 0.3);
37731
+ }
37732
+
37733
+ .myio-contract-summary-tooltip__status-icon {
37734
+ font-size: 14px;
37735
+ }
37736
+
37737
+ /* Discrepancies */
37738
+ .myio-contract-summary-tooltip__discrepancies {
37739
+ background: rgba(244, 67, 54, 0.15);
37740
+ border: 1px solid rgba(244, 67, 54, 0.3);
37741
+ border-radius: 10px;
37742
+ padding: 10px 14px;
37743
+ margin-bottom: 14px;
37744
+ }
37745
+
37746
+ .myio-contract-summary-tooltip__discrepancies-title {
37747
+ font-size: 11px;
37748
+ font-weight: 600;
37749
+ color: #ef5350;
37750
+ margin-bottom: 6px;
37751
+ text-transform: uppercase;
37752
+ letter-spacing: 0.5px;
37753
+ }
37754
+
37755
+ .myio-contract-summary-tooltip__discrepancy-item {
37756
+ font-size: 11px;
37757
+ color: rgba(255, 255, 255, 0.8);
37758
+ padding: 3px 0;
37759
+ }
37760
+
37761
+ /* Footer */
37762
+ .myio-contract-summary-tooltip__footer {
37763
+ display: flex;
37764
+ justify-content: space-between;
37765
+ align-items: center;
37766
+ padding: 12px 16px;
37767
+ background: rgba(0, 0, 0, 0.2);
37768
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
37769
+ border-radius: 0 0 16px 16px;
37770
+ }
37771
+
37772
+ .myio-contract-summary-tooltip__footer-label {
37773
+ font-size: 10px;
37774
+ color: rgba(255, 255, 255, 0.5);
37775
+ }
37776
+
37777
+ .myio-contract-summary-tooltip__footer-value {
37778
+ font-size: 11px;
37779
+ font-weight: 600;
37780
+ color: rgba(255, 255, 255, 0.8);
37781
+ }
37782
+
37783
+ /* Total Devices Badge */
37784
+ .myio-contract-summary-tooltip__total {
37785
+ display: flex;
37786
+ align-items: center;
37787
+ justify-content: center;
37788
+ gap: 8px;
37789
+ padding: 12px;
37790
+ background: rgba(255, 255, 255, 0.08);
37791
+ border-radius: 10px;
37792
+ margin-bottom: 14px;
37793
+ }
37794
+
37795
+ .myio-contract-summary-tooltip__total-label {
37796
+ font-size: 12px;
37797
+ opacity: 0.8;
37798
+ }
37799
+
37800
+ .myio-contract-summary-tooltip__total-value {
37801
+ font-size: 18px;
37802
+ font-weight: 700;
37803
+ color: #81c784;
37804
+ }
37805
+
37806
+ /* Responsive */
37807
+ @media (max-width: 400px) {
37808
+ .myio-contract-summary-tooltip__panel {
37809
+ min-width: 280px;
37810
+ max-width: 95vw;
37811
+ }
37812
+ }
37813
+ `;
37814
+ var cssInjected9 = false;
37815
+ function injectCSS9() {
37816
+ if (cssInjected9) return;
37817
+ if (typeof document === "undefined") return;
37818
+ const styleId = "myio-contract-summary-tooltip-styles";
37819
+ if (document.getElementById(styleId)) {
37820
+ cssInjected9 = true;
37821
+ return;
37822
+ }
37823
+ const style = document.createElement("style");
37824
+ style.id = styleId;
37825
+ style.textContent = CONTRACT_SUMMARY_TOOLTIP_CSS;
37826
+ document.head.appendChild(style);
37827
+ cssInjected9 = true;
37828
+ }
37829
+ var state5 = {
37830
+ hideTimer: null,
37831
+ isMouseOverTooltip: false,
37832
+ isMaximized: false,
37833
+ isDragging: false,
37834
+ dragOffset: { x: 0, y: 0 },
37835
+ savedPosition: null,
37836
+ pinnedCounter: 0,
37837
+ expandedDomains: /* @__PURE__ */ new Set(["energy", "water", "temperature"])
37838
+ };
37839
+ function formatTimestamp5(isoString) {
37840
+ if (!isoString) return "Agora";
37841
+ try {
37842
+ const date = new Date(isoString);
37843
+ return date.toLocaleString("pt-BR", {
37844
+ hour: "2-digit",
37845
+ minute: "2-digit",
37846
+ day: "2-digit",
37847
+ month: "2-digit"
37848
+ });
37849
+ } catch {
37850
+ return "Agora";
37851
+ }
37852
+ }
37853
+ function calculateTotalDevices(data) {
37854
+ return data.energy.total + data.water.total + data.temperature.total;
37855
+ }
37856
+ function generateHeaderHTML5(data) {
37857
+ const iconClass = data.isValid ? "valid" : "invalid";
37858
+ const iconSymbol = data.isValid ? "\u2713" : "!";
37859
+ const totalDevices = calculateTotalDevices(data);
37860
+ return `
37861
+ <div class="myio-contract-summary-tooltip__header" data-drag-handle>
37862
+ <div class="myio-contract-summary-tooltip__icon ${iconClass}">${iconSymbol}</div>
37863
+ <div class="myio-contract-summary-tooltip__header-info">
37864
+ <div class="myio-contract-summary-tooltip__title">Contract Summary</div>
37865
+ <div class="myio-contract-summary-tooltip__subtitle">${totalDevices} devices loaded</div>
37866
+ </div>
37867
+ <div class="myio-contract-summary-tooltip__header-actions">
37868
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="pin" title="Pin to screen">
37869
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37870
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
37871
+ <line x1="12" y1="16" x2="12" y2="21"/>
37872
+ <line x1="8" y1="4" x2="16" y2="4"/>
37873
+ </svg>
37874
+ </button>
37875
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="maximize" title="Maximize">
37876
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37877
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
37878
+ </svg>
37879
+ </button>
37880
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="close" title="Close">
37881
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37882
+ <path d="M18 6L6 18M6 6l12 12"/>
37883
+ </svg>
37884
+ </button>
37885
+ </div>
37886
+ </div>
37887
+ `;
37888
+ }
37889
+ function generateDomainHTML(domain, icon, name, counts, isTemperature = false) {
37890
+ const isExpanded = state5.expandedDomains.has(domain);
37891
+ const expandedClass = isExpanded ? "expanded" : "";
37892
+ let detailsHTML = "";
37893
+ if (isTemperature) {
37894
+ const tempCounts = counts;
37895
+ detailsHTML = `
37896
+ <div class="myio-contract-summary-tooltip__detail-row">
37897
+ <span class="myio-contract-summary-tooltip__detail-label">Internal (Climate)</span>
37898
+ <span class="myio-contract-summary-tooltip__detail-count">${tempCounts.internal}</span>
37899
+ </div>
37900
+ <div class="myio-contract-summary-tooltip__detail-row">
37901
+ <span class="myio-contract-summary-tooltip__detail-label">Stores (Non-Climate)</span>
37902
+ <span class="myio-contract-summary-tooltip__detail-count">${tempCounts.stores}</span>
37903
+ </div>
37904
+ `;
37905
+ } else {
37906
+ const domainCounts = counts;
37907
+ detailsHTML = `
37908
+ <div class="myio-contract-summary-tooltip__detail-row">
37909
+ <span class="myio-contract-summary-tooltip__detail-label">Entries</span>
37910
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.entries}</span>
37911
+ </div>
37912
+ <div class="myio-contract-summary-tooltip__detail-row">
37913
+ <span class="myio-contract-summary-tooltip__detail-label">Common Area</span>
37914
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.commonArea}</span>
37915
+ </div>
37916
+ <div class="myio-contract-summary-tooltip__detail-row">
37917
+ <span class="myio-contract-summary-tooltip__detail-label">Stores</span>
37918
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.stores}</span>
37919
+ </div>
37920
+ `;
37921
+ }
37922
+ return `
37923
+ <div class="myio-contract-summary-tooltip__domain ${expandedClass}" data-domain="${domain}">
37924
+ <div class="myio-contract-summary-tooltip__domain-header" data-toggle-domain="${domain}">
37925
+ <div class="myio-contract-summary-tooltip__domain-info">
37926
+ <span class="myio-contract-summary-tooltip__domain-icon">${icon}</span>
37927
+ <span class="myio-contract-summary-tooltip__domain-name">${name}</span>
37928
+ </div>
37929
+ <div style="display: flex; align-items: center; gap: 8px;">
37930
+ <span class="myio-contract-summary-tooltip__domain-count">${counts.total} devices</span>
37931
+ <span class="myio-contract-summary-tooltip__expand-icon">\u25BC</span>
37932
+ </div>
37933
+ </div>
37934
+ <div class="myio-contract-summary-tooltip__domain-details">
37935
+ ${detailsHTML}
37936
+ </div>
37937
+ </div>
37938
+ `;
37939
+ }
37940
+ function generateBodyHTML3(data) {
37941
+ const totalDevices = calculateTotalDevices(data);
37942
+ const timestamp = formatTimestamp5(data.timestamp);
37943
+ const statusClass = data.isValid ? "valid" : "invalid";
37944
+ const statusIcon = data.isValid ? "\u2713" : "\u26A0";
37945
+ const statusText = data.isValid ? "Contract validated successfully" : "Validation issues detected";
37946
+ let discrepanciesHTML = "";
37947
+ if (data.discrepancies && data.discrepancies.length > 0) {
37948
+ const items = data.discrepancies.map((d) => `<div class="myio-contract-summary-tooltip__discrepancy-item">${d.domain}: expected ${d.expected}, found ${d.actual}</div>`).join("");
37949
+ discrepanciesHTML = `
37950
+ <div class="myio-contract-summary-tooltip__discrepancies">
37951
+ <div class="myio-contract-summary-tooltip__discrepancies-title">Discrepancies</div>
37952
+ ${items}
37953
+ </div>
37954
+ `;
37955
+ }
37956
+ return `
37957
+ <div class="myio-contract-summary-tooltip__body">
37958
+ <!-- Status Banner -->
37959
+ <div class="myio-contract-summary-tooltip__status ${statusClass}">
37960
+ <span class="myio-contract-summary-tooltip__status-icon">${statusIcon}</span>
37961
+ <span>${statusText}</span>
37962
+ </div>
37963
+
37964
+ ${discrepanciesHTML}
37965
+
37966
+ <!-- Total Devices -->
37967
+ <div class="myio-contract-summary-tooltip__total">
37968
+ <span class="myio-contract-summary-tooltip__total-label">Total Devices:</span>
37969
+ <span class="myio-contract-summary-tooltip__total-value">${totalDevices}</span>
37970
+ </div>
37971
+
37972
+ <!-- Energy -->
37973
+ ${generateDomainHTML("energy", "\u26A1", "Energy", data.energy)}
37974
+
37975
+ <!-- Water -->
37976
+ ${generateDomainHTML("water", "\u{1F4A7}", "Water", data.water)}
37977
+
37978
+ <!-- Temperature -->
37979
+ ${generateDomainHTML("temperature", "\u{1F321}\uFE0F", "Temperature", data.temperature, true)}
37980
+ </div>
37981
+
37982
+ <!-- Footer -->
37983
+ <div class="myio-contract-summary-tooltip__footer">
37984
+ <span class="myio-contract-summary-tooltip__footer-label">Loaded at</span>
37985
+ <span class="myio-contract-summary-tooltip__footer-value">${timestamp}</span>
37986
+ </div>
37987
+ `;
37988
+ }
37989
+ function setupHoverListeners5(container) {
37990
+ container.onmouseenter = () => {
37991
+ state5.isMouseOverTooltip = true;
37992
+ if (state5.hideTimer) {
37993
+ clearTimeout(state5.hideTimer);
37994
+ state5.hideTimer = null;
37995
+ }
37996
+ };
37997
+ container.onmouseleave = () => {
37998
+ state5.isMouseOverTooltip = false;
37999
+ startDelayedHide5();
38000
+ };
38001
+ }
38002
+ function setupDomainToggleListeners(container) {
38003
+ const toggles = container.querySelectorAll("[data-toggle-domain]");
38004
+ toggles.forEach((toggle) => {
38005
+ toggle.onclick = (e) => {
38006
+ e.stopPropagation();
38007
+ const domain = toggle.dataset.toggleDomain;
38008
+ if (!domain) return;
38009
+ const domainEl = container.querySelector(`[data-domain="${domain}"]`);
38010
+ if (!domainEl) return;
38011
+ if (state5.expandedDomains.has(domain)) {
38012
+ state5.expandedDomains.delete(domain);
38013
+ domainEl.classList.remove("expanded");
38014
+ } else {
38015
+ state5.expandedDomains.add(domain);
38016
+ domainEl.classList.add("expanded");
38017
+ }
38018
+ };
38019
+ });
38020
+ }
38021
+ function setupButtonListeners5(container) {
38022
+ const buttons = container.querySelectorAll("[data-action]");
38023
+ buttons.forEach((btn) => {
38024
+ btn.onclick = (e) => {
38025
+ e.stopPropagation();
38026
+ const action = btn.dataset.action;
38027
+ switch (action) {
38028
+ case "pin":
38029
+ createPinnedClone5(container);
38030
+ break;
38031
+ case "maximize":
38032
+ toggleMaximize5(container);
38033
+ break;
38034
+ case "close":
38035
+ ContractSummaryTooltip.close();
38036
+ break;
38037
+ }
38038
+ };
38039
+ });
38040
+ }
38041
+ function setupDragListeners5(container) {
38042
+ const header = container.querySelector("[data-drag-handle]");
38043
+ if (!header) return;
38044
+ header.onmousedown = (e) => {
38045
+ if (e.target.closest("[data-action]")) return;
38046
+ if (e.target.closest("[data-toggle-domain]")) return;
38047
+ if (state5.isMaximized) return;
38048
+ state5.isDragging = true;
38049
+ container.classList.add("dragging");
38050
+ const rect = container.getBoundingClientRect();
38051
+ state5.dragOffset = {
38052
+ x: e.clientX - rect.left,
38053
+ y: e.clientY - rect.top
38054
+ };
38055
+ const onMouseMove = (e2) => {
38056
+ if (!state5.isDragging) return;
38057
+ const newLeft = e2.clientX - state5.dragOffset.x;
38058
+ const newTop = e2.clientY - state5.dragOffset.y;
38059
+ const maxLeft = window.innerWidth - container.offsetWidth;
38060
+ const maxTop = window.innerHeight - container.offsetHeight;
38061
+ container.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
38062
+ container.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
38063
+ };
38064
+ const onMouseUp = () => {
38065
+ state5.isDragging = false;
38066
+ container.classList.remove("dragging");
38067
+ document.removeEventListener("mousemove", onMouseMove);
38068
+ document.removeEventListener("mouseup", onMouseUp);
38069
+ };
38070
+ document.addEventListener("mousemove", onMouseMove);
38071
+ document.addEventListener("mouseup", onMouseUp);
38072
+ };
38073
+ }
38074
+ function createPinnedClone5(container) {
38075
+ state5.pinnedCounter++;
38076
+ const pinnedId = `myio-contract-summary-tooltip-pinned-${state5.pinnedCounter}`;
38077
+ const clone = container.cloneNode(true);
38078
+ clone.id = pinnedId;
38079
+ clone.classList.add("pinned");
38080
+ clone.classList.remove("closing");
38081
+ const pinBtn = clone.querySelector('[data-action="pin"]');
38082
+ if (pinBtn) {
38083
+ pinBtn.classList.add("pinned");
38084
+ pinBtn.setAttribute("title", "Unpin");
38085
+ pinBtn.innerHTML = `
38086
+ <svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1">
38087
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
38088
+ <line x1="12" y1="16" x2="12" y2="21"/>
38089
+ <line x1="8" y1="4" x2="16" y2="4"/>
38090
+ </svg>
38091
+ `;
38092
+ }
38093
+ document.body.appendChild(clone);
38094
+ setupPinnedCloneListeners5(clone, pinnedId);
38095
+ ContractSummaryTooltip.hide();
38096
+ }
38097
+ function setupPinnedCloneListeners5(clone, cloneId) {
38098
+ let isMaximized = false;
38099
+ let savedPosition = null;
38100
+ const cloneExpandedDomains = new Set(state5.expandedDomains);
38101
+ const toggles = clone.querySelectorAll("[data-toggle-domain]");
38102
+ toggles.forEach((toggle) => {
38103
+ toggle.onclick = (e) => {
38104
+ e.stopPropagation();
38105
+ const domain = toggle.dataset.toggleDomain;
38106
+ if (!domain) return;
38107
+ const domainEl = clone.querySelector(`[data-domain="${domain}"]`);
38108
+ if (!domainEl) return;
38109
+ if (cloneExpandedDomains.has(domain)) {
38110
+ cloneExpandedDomains.delete(domain);
38111
+ domainEl.classList.remove("expanded");
38112
+ } else {
38113
+ cloneExpandedDomains.add(domain);
38114
+ domainEl.classList.add("expanded");
38115
+ }
38116
+ };
38117
+ });
38118
+ const pinBtn = clone.querySelector('[data-action="pin"]');
38119
+ if (pinBtn) {
38120
+ pinBtn.onclick = (e) => {
38121
+ e.stopPropagation();
38122
+ closePinnedClone5(cloneId);
38123
+ };
38124
+ }
38125
+ const closeBtn = clone.querySelector('[data-action="close"]');
38126
+ if (closeBtn) {
38127
+ closeBtn.onclick = (e) => {
38128
+ e.stopPropagation();
38129
+ closePinnedClone5(cloneId);
38130
+ };
38131
+ }
38132
+ const maxBtn = clone.querySelector('[data-action="maximize"]');
38133
+ if (maxBtn) {
38134
+ maxBtn.onclick = (e) => {
38135
+ e.stopPropagation();
38136
+ isMaximized = !isMaximized;
38137
+ if (isMaximized) {
38138
+ savedPosition = { left: clone.style.left, top: clone.style.top };
38139
+ 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>`;
38140
+ maxBtn.setAttribute("title", "Restore");
38141
+ } else {
38142
+ 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>`;
38143
+ maxBtn.setAttribute("title", "Maximize");
38144
+ if (savedPosition) {
38145
+ clone.style.left = savedPosition.left;
38146
+ clone.style.top = savedPosition.top;
38147
+ }
38148
+ }
38149
+ clone.classList.toggle("maximized", isMaximized);
38150
+ };
38151
+ }
38152
+ const header = clone.querySelector("[data-drag-handle]");
38153
+ if (header) {
38154
+ let isDragging = false;
38155
+ let dragOffset = { x: 0, y: 0 };
38156
+ header.onmousedown = (e) => {
38157
+ if (e.target.closest("[data-action]")) return;
38158
+ if (e.target.closest("[data-toggle-domain]")) return;
38159
+ if (isMaximized) return;
38160
+ isDragging = true;
38161
+ clone.classList.add("dragging");
38162
+ const rect = clone.getBoundingClientRect();
38163
+ dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
38164
+ const onMouseMove = (e2) => {
38165
+ if (!isDragging) return;
38166
+ const newLeft = e2.clientX - dragOffset.x;
38167
+ const newTop = e2.clientY - dragOffset.y;
38168
+ const maxLeft = window.innerWidth - clone.offsetWidth;
38169
+ const maxTop = window.innerHeight - clone.offsetHeight;
38170
+ clone.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
38171
+ clone.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
38172
+ };
38173
+ const onMouseUp = () => {
38174
+ isDragging = false;
38175
+ clone.classList.remove("dragging");
38176
+ document.removeEventListener("mousemove", onMouseMove);
38177
+ document.removeEventListener("mouseup", onMouseUp);
38178
+ };
38179
+ document.addEventListener("mousemove", onMouseMove);
38180
+ document.addEventListener("mouseup", onMouseUp);
38181
+ };
38182
+ }
38183
+ }
38184
+ function closePinnedClone5(cloneId) {
38185
+ const clone = document.getElementById(cloneId);
38186
+ if (clone) {
38187
+ clone.classList.add("closing");
38188
+ setTimeout(() => clone.remove(), 400);
38189
+ }
38190
+ }
38191
+ function toggleMaximize5(container) {
38192
+ state5.isMaximized = !state5.isMaximized;
38193
+ if (state5.isMaximized) {
38194
+ state5.savedPosition = {
38195
+ left: container.style.left,
38196
+ top: container.style.top
38197
+ };
38198
+ }
38199
+ container.classList.toggle("maximized", state5.isMaximized);
38200
+ const maxBtn = container.querySelector('[data-action="maximize"]');
38201
+ if (maxBtn) {
38202
+ if (state5.isMaximized) {
38203
+ 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>`;
38204
+ maxBtn.setAttribute("title", "Restore");
38205
+ } else {
38206
+ 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>`;
38207
+ maxBtn.setAttribute("title", "Maximize");
38208
+ if (state5.savedPosition) {
38209
+ container.style.left = state5.savedPosition.left;
38210
+ container.style.top = state5.savedPosition.top;
38211
+ }
38212
+ }
38213
+ }
38214
+ }
38215
+ function startDelayedHide5() {
38216
+ if (state5.isMouseOverTooltip) return;
38217
+ if (state5.hideTimer) {
38218
+ clearTimeout(state5.hideTimer);
38219
+ }
38220
+ state5.hideTimer = setTimeout(() => {
38221
+ hideWithAnimation5();
38222
+ }, 1500);
38223
+ }
38224
+ function hideWithAnimation5() {
38225
+ const container = document.getElementById("myio-contract-summary-tooltip");
38226
+ if (container && container.classList.contains("visible")) {
38227
+ container.classList.add("closing");
38228
+ setTimeout(() => {
38229
+ container.classList.remove("visible", "closing");
38230
+ }, 400);
38231
+ }
38232
+ }
38233
+ function positionTooltip5(container, triggerElement) {
38234
+ const rect = triggerElement.getBoundingClientRect();
38235
+ let left = rect.left;
38236
+ let top = rect.bottom + 8;
38237
+ const tooltipWidth = 360;
38238
+ if (left + tooltipWidth > window.innerWidth - 20) {
38239
+ left = window.innerWidth - tooltipWidth - 20;
38240
+ }
38241
+ if (left < 10) left = 10;
38242
+ if (top + 500 > window.innerHeight) {
38243
+ top = rect.top - 8 - 500;
38244
+ if (top < 10) top = 10;
38245
+ }
38246
+ container.style.left = left + "px";
38247
+ container.style.top = top + "px";
38248
+ }
38249
+ var ContractSummaryTooltip = {
38250
+ containerId: "myio-contract-summary-tooltip",
38251
+ /**
38252
+ * Get or create container
38253
+ */
38254
+ getContainer() {
38255
+ injectCSS9();
38256
+ let container = document.getElementById(this.containerId);
38257
+ if (!container) {
38258
+ container = document.createElement("div");
38259
+ container.id = this.containerId;
38260
+ container.className = "myio-contract-summary-tooltip";
38261
+ document.body.appendChild(container);
38262
+ }
38263
+ return container;
38264
+ },
38265
+ /**
38266
+ * Show tooltip
38267
+ */
38268
+ show(triggerElement, data) {
38269
+ if (state5.hideTimer) {
38270
+ clearTimeout(state5.hideTimer);
38271
+ state5.hideTimer = null;
38272
+ }
38273
+ const container = this.getContainer();
38274
+ container.classList.remove("closing");
38275
+ container.innerHTML = `
38276
+ <div class="myio-contract-summary-tooltip__panel">
38277
+ ${generateHeaderHTML5(data)}
38278
+ ${generateBodyHTML3(data)}
38279
+ </div>
38280
+ `;
38281
+ positionTooltip5(container, triggerElement);
38282
+ container.classList.add("visible");
38283
+ setupHoverListeners5(container);
38284
+ setupButtonListeners5(container);
38285
+ setupDragListeners5(container);
38286
+ setupDomainToggleListeners(container);
38287
+ },
38288
+ /**
38289
+ * Start delayed hide
38290
+ */
38291
+ startDelayedHide() {
38292
+ startDelayedHide5();
38293
+ },
38294
+ /**
38295
+ * Hide immediately
38296
+ */
38297
+ hide() {
38298
+ if (state5.hideTimer) {
38299
+ clearTimeout(state5.hideTimer);
38300
+ state5.hideTimer = null;
38301
+ }
38302
+ state5.isMouseOverTooltip = false;
38303
+ const container = document.getElementById(this.containerId);
38304
+ if (container) {
38305
+ container.classList.remove("visible", "closing");
38306
+ }
38307
+ },
38308
+ /**
38309
+ * Close and reset all states
38310
+ */
38311
+ close() {
38312
+ state5.isMaximized = false;
38313
+ state5.isDragging = false;
38314
+ state5.savedPosition = null;
38315
+ if (state5.hideTimer) {
38316
+ clearTimeout(state5.hideTimer);
38317
+ state5.hideTimer = null;
38318
+ }
38319
+ state5.isMouseOverTooltip = false;
38320
+ const container = document.getElementById(this.containerId);
38321
+ if (container) {
38322
+ container.classList.remove("visible", "pinned", "maximized", "dragging", "closing");
38323
+ }
38324
+ },
38325
+ /**
38326
+ * Attach tooltip to trigger element with click behavior
38327
+ */
38328
+ attach(triggerElement, getDataFn) {
38329
+ const self = this;
38330
+ const handleClick = () => {
38331
+ if (state5.hideTimer) {
38332
+ clearTimeout(state5.hideTimer);
38333
+ state5.hideTimer = null;
38334
+ }
38335
+ const data = getDataFn();
38336
+ if (data) {
38337
+ self.show(triggerElement, data);
38338
+ }
38339
+ };
38340
+ triggerElement.addEventListener("click", handleClick);
38341
+ return () => {
38342
+ triggerElement.removeEventListener("click", handleClick);
38343
+ self.hide();
38344
+ };
38345
+ },
38346
+ /**
38347
+ * Attach tooltip with hover behavior
38348
+ */
38349
+ attachHover(triggerElement, getDataFn) {
38350
+ const self = this;
38351
+ const handleMouseEnter = () => {
38352
+ if (state5.hideTimer) {
38353
+ clearTimeout(state5.hideTimer);
38354
+ state5.hideTimer = null;
38355
+ }
38356
+ const data = getDataFn();
38357
+ if (data) {
38358
+ self.show(triggerElement, data);
38359
+ }
38360
+ };
38361
+ const handleMouseLeave = () => {
38362
+ startDelayedHide5();
38363
+ };
38364
+ triggerElement.addEventListener("mouseenter", handleMouseEnter);
38365
+ triggerElement.addEventListener("mouseleave", handleMouseLeave);
38366
+ return () => {
38367
+ triggerElement.removeEventListener("mouseenter", handleMouseEnter);
38368
+ triggerElement.removeEventListener("mouseleave", handleMouseLeave);
38369
+ self.hide();
38370
+ };
38371
+ },
38372
+ /**
38373
+ * Build contract data from window.CONTRACT_STATE
38374
+ * Helper method to build the data structure from global state
38375
+ */
38376
+ buildFromGlobalState() {
38377
+ const globalState = window.CONTRACT_STATE;
38378
+ if (!globalState) return null;
38379
+ return {
38380
+ isLoaded: globalState.isLoaded ?? false,
38381
+ isValid: globalState.isValid ?? false,
38382
+ timestamp: globalState.timestamp ?? null,
38383
+ energy: {
38384
+ total: globalState.energy?.total ?? 0,
38385
+ entries: globalState.energy?.entries ?? 0,
38386
+ commonArea: globalState.energy?.commonArea ?? 0,
38387
+ stores: globalState.energy?.stores ?? 0
38388
+ },
38389
+ water: {
38390
+ total: globalState.water?.total ?? 0,
38391
+ entries: globalState.water?.entries ?? 0,
38392
+ commonArea: globalState.water?.commonArea ?? 0,
38393
+ stores: globalState.water?.stores ?? 0
38394
+ },
38395
+ temperature: {
38396
+ total: globalState.temperature?.total ?? 0,
38397
+ internal: globalState.temperature?.internal ?? 0,
38398
+ stores: globalState.temperature?.stores ?? 0
38399
+ },
38400
+ discrepancies: globalState.discrepancies
38401
+ };
38402
+ }
38403
+ };
38404
+
37407
38405
  // src/components/ModalHeader/index.ts
37408
38406
  var DEFAULT_BG_COLOR = "#3e1a7d";
37409
38407
  var DEFAULT_TEXT_COLOR = "white";
@@ -41168,6 +42166,7 @@ function createDistributionChartWidget(config) {
41168
42166
  CONSUMPTION_CHART_DEFAULTS,
41169
42167
  CONSUMPTION_THEME_COLORS,
41170
42168
  ConnectionStatusType,
42169
+ ContractSummaryTooltip,
41171
42170
  DEFAULT_CLAMP_RANGE,
41172
42171
  DEFAULT_ENERGY_GROUP_COLORS,
41173
42172
  DEFAULT_GAS_GROUP_COLORS,