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.js CHANGED
@@ -8875,8 +8875,8 @@ function getStatusDotClass(deviceStatus) {
8875
8875
  return "dot--offline";
8876
8876
  }
8877
8877
  }
8878
- function buildDOM(state5) {
8879
- const { entityObject, i18n, enableSelection, enableDragDrop } = state5;
8878
+ function buildDOM(state6) {
8879
+ const { entityObject, i18n, enableSelection, enableDragDrop } = state6;
8880
8880
  const root = document.createElement("div");
8881
8881
  root.className = "myio-ho-card";
8882
8882
  root.setAttribute("role", "group");
@@ -9032,7 +9032,7 @@ function buildDOM(state5) {
9032
9032
  return root;
9033
9033
  }
9034
9034
  function buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins) {
9035
- const formatTimestamp5 = (ts) => {
9035
+ const formatTimestamp6 = (ts) => {
9036
9036
  if (!ts) return "N/A";
9037
9037
  const d = new Date(ts);
9038
9038
  return d.toLocaleString("pt-BR", {
@@ -9059,8 +9059,8 @@ function buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecis
9059
9059
  chipClass: statusInfo.chipClass,
9060
9060
  chipLabel: statusInfo.label,
9061
9061
  // Connection timestamps
9062
- lastConnectTime: formatTimestamp5(entityObject.lastConnectTime),
9063
- lastDisconnectTime: formatTimestamp5(entityObject.lastDisconnectTime),
9062
+ lastConnectTime: formatTimestamp6(entityObject.lastConnectTime),
9063
+ lastDisconnectTime: formatTimestamp6(entityObject.lastDisconnectTime),
9064
9064
  delayTimeConnectionInMins,
9065
9065
  // Raw values
9066
9066
  val: entityObject.val,
@@ -9202,8 +9202,8 @@ function verifyOfflineStatus(entityObject, delayTimeInMins = 15, LogHelper2) {
9202
9202
  }
9203
9203
  return isOffline;
9204
9204
  }
9205
- function paint(root, state5) {
9206
- const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state5;
9205
+ function paint(root, state6) {
9206
+ const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state6;
9207
9207
  let statusDecisionSource = "unknown";
9208
9208
  if (entityObject.connectionStatus) {
9209
9209
  if (entityObject.connectionStatus === "offline") {
@@ -9255,7 +9255,7 @@ function paint(root, state5) {
9255
9255
  numSpan.textContent = primaryValue;
9256
9256
  const barContainer = root.querySelector(".bar");
9257
9257
  const effContainer = root.querySelector(".myio-ho-card__eff");
9258
- if (state5.enableSelection) {
9258
+ if (state6.enableSelection) {
9259
9259
  const checkbox = root.querySelector('.myio-ho-card__select input[type="checkbox"]');
9260
9260
  if (checkbox) {
9261
9261
  checkbox.checked = !!isSelected;
@@ -9303,8 +9303,8 @@ function paint(root, state5) {
9303
9303
  statusDot.className = `status-dot ${dotClass}`;
9304
9304
  }
9305
9305
  }
9306
- function bindEvents(root, state5, callbacks) {
9307
- const { entityObject } = state5;
9306
+ function bindEvents(root, state6, callbacks) {
9307
+ const { entityObject } = state6;
9308
9308
  const kebabBtn = root.querySelector(".myio-ho-card__kebab");
9309
9309
  const menu = root.querySelector(".myio-ho-card__menu");
9310
9310
  function toggleMenu() {
@@ -9360,9 +9360,9 @@ function bindEvents(root, state5, callbacks) {
9360
9360
  const onSelectionChange = () => {
9361
9361
  const selectedIds = MyIOSelectionStore2.getSelectedIds();
9362
9362
  const isSelected = selectedIds.includes(entityObject.entityId);
9363
- if (state5.isSelected !== isSelected) {
9364
- state5.isSelected = isSelected;
9365
- paint(root, state5);
9363
+ if (state6.isSelected !== isSelected) {
9364
+ state6.isSelected = isSelected;
9365
+ paint(root, state6);
9366
9366
  }
9367
9367
  };
9368
9368
  MyIOSelectionStore2.on("selection:change", onSelectionChange);
@@ -9375,7 +9375,7 @@ function bindEvents(root, state5, callbacks) {
9375
9375
  clearTimeout(TempRangeTooltip._hideTimer);
9376
9376
  TempRangeTooltip._hideTimer = null;
9377
9377
  }
9378
- TempRangeTooltip.show(root, state5.entityObject, e);
9378
+ TempRangeTooltip.show(root, state6.entityObject, e);
9379
9379
  };
9380
9380
  const hideTooltip = () => {
9381
9381
  TempRangeTooltip._startDelayedHide();
@@ -9393,7 +9393,7 @@ function bindEvents(root, state5, callbacks) {
9393
9393
  clearTimeout(EnergyRangeTooltip._hideTimer);
9394
9394
  EnergyRangeTooltip._hideTimer = null;
9395
9395
  }
9396
- EnergyRangeTooltip.show(root, state5.entityObject, e);
9396
+ EnergyRangeTooltip.show(root, state6.entityObject, e);
9397
9397
  };
9398
9398
  const hideEnergyTooltip = () => {
9399
9399
  EnergyRangeTooltip._startDelayedHide();
@@ -9445,7 +9445,7 @@ function bindEvents(root, state5, callbacks) {
9445
9445
  }
9446
9446
  });
9447
9447
  }
9448
- if (state5.enableDragDrop) {
9448
+ if (state6.enableDragDrop) {
9449
9449
  root.addEventListener("dragstart", (e) => {
9450
9450
  root.classList.add("is-dragging");
9451
9451
  e.dataTransfer.setData("text/plain", entityObject.entityId);
@@ -9487,17 +9487,17 @@ function renderCardComponentHeadOffice(containerEl, params) {
9487
9487
  throw new Error("renderCardComponentHeadOffice: containerEl is required");
9488
9488
  }
9489
9489
  ensureCss();
9490
- const state5 = normalizeParams(params);
9491
- const root = buildDOM(state5);
9492
- state5.isSelected = params.isSelected || false;
9490
+ const state6 = normalizeParams(params);
9491
+ const root = buildDOM(state6);
9492
+ state6.isSelected = params.isSelected || false;
9493
9493
  containerEl.appendChild(root);
9494
- bindEvents(root, state5, state5.callbacks);
9495
- paint(root, state5);
9494
+ bindEvents(root, state6, state6.callbacks);
9495
+ paint(root, state6);
9496
9496
  return {
9497
9497
  update(next) {
9498
9498
  if (next) {
9499
- Object.assign(state5.entityObject, next);
9500
- paint(root, state5);
9499
+ Object.assign(state6.entityObject, next);
9500
+ paint(root, state6);
9501
9501
  }
9502
9502
  },
9503
9503
  destroy() {
@@ -11227,6 +11227,15 @@ function calculateTempStatus(currentTemp, avgTemp) {
11227
11227
  return { deviation: absDeviation, sign: "-", status: "below", statusText: "Abaixo da media", statusIcon: "\u{1F53B}" };
11228
11228
  }
11229
11229
  }
11230
+ function calculateRangeStatus(currentTemp, minTemp, maxTemp) {
11231
+ if (currentTemp >= minTemp && currentTemp <= maxTemp) {
11232
+ return { status: "ok", statusText: "Dentro da faixa", statusIcon: "\u2713" };
11233
+ } else if (currentTemp > maxTemp) {
11234
+ return { status: "above", statusText: "Acima da faixa", statusIcon: "\u{1F53A}" };
11235
+ } else {
11236
+ return { status: "below", statusText: "Abaixo da faixa", statusIcon: "\u{1F53B}" };
11237
+ }
11238
+ }
11230
11239
  function calcTempBarPosition(temp, minRange, maxRange) {
11231
11240
  const rangeSize = maxRange - minRange;
11232
11241
  const extendedMin = minRange - rangeSize * 0.3;
@@ -11269,8 +11278,10 @@ function generateBodyHTML2(data) {
11269
11278
  const { device, average, lastUpdated } = data;
11270
11279
  const timestamp = formatTimestamp2(lastUpdated);
11271
11280
  const tempStatus = calculateTempStatus(device.currentTemp, average.value);
11281
+ const rangeStatus = calculateRangeStatus(device.currentTemp, device.minTemp, device.maxTemp);
11272
11282
  const deviceBarPos = calcTempBarPosition(device.currentTemp, device.minTemp, device.maxTemp);
11273
11283
  const avgBarPos = calcTempBarPosition(average.value, device.minTemp, device.maxTemp);
11284
+ const rangeStatusClass = rangeStatus.status === "ok" ? "normal" : rangeStatus.status;
11274
11285
  return `
11275
11286
  <div class="myio-temp-comparison-tooltip__body">
11276
11287
  <!-- Main Stats -->
@@ -11285,12 +11296,18 @@ function generateBodyHTML2(data) {
11285
11296
  </div>
11286
11297
  </div>
11287
11298
 
11288
- <!-- Configured Range -->
11299
+ <!-- Configured Range with Status -->
11289
11300
  <div class="myio-temp-comparison-tooltip__range">
11290
11301
  <span class="myio-temp-comparison-tooltip__range-label">Faixa Ideal:</span>
11291
11302
  <span class="myio-temp-comparison-tooltip__range-value">${formatTemp(device.minTemp)} - ${formatTemp(device.maxTemp)}</span>
11292
11303
  </div>
11293
11304
 
11305
+ <!-- Range Status Indicator -->
11306
+ <div class="myio-temp-comparison-tooltip__status ${rangeStatusClass}">
11307
+ <span class="myio-temp-comparison-tooltip__status-icon">${rangeStatus.statusIcon}</span>
11308
+ <span>${rangeStatus.statusText}</span>
11309
+ </div>
11310
+
11294
11311
  <!-- Section: Average Comparison -->
11295
11312
  <div class="myio-temp-comparison-tooltip__section-title">
11296
11313
  <span class="myio-temp-comparison-tooltip__section-icon">\u{1F4CA}</span>
@@ -26393,12 +26410,8 @@ var SettingsModalView = class {
26393
26410
  }
26394
26411
  showLoadingState(isLoading) {
26395
26412
  const saveBtn = this.modal.querySelector(".btn-save");
26396
- const cancelBtn = this.modal.querySelector(
26397
- ".btn-cancel"
26398
- );
26399
- const formInputs = this.modal.querySelectorAll(
26400
- "input, select, textarea"
26401
- );
26413
+ const cancelBtn = this.modal.querySelector(".btn-cancel");
26414
+ const formInputs = this.modal.querySelectorAll("input, select, textarea");
26402
26415
  if (saveBtn) {
26403
26416
  saveBtn.disabled = isLoading;
26404
26417
  saveBtn.textContent = isLoading ? "Salvando..." : "Salvar";
@@ -26471,9 +26484,7 @@ var SettingsModalView = class {
26471
26484
  this.container = document.createElement("div");
26472
26485
  this.container.className = "myio-settings-modal-overlay";
26473
26486
  this.container.innerHTML = this.getModalHTML();
26474
- this.modal = this.container.querySelector(
26475
- ".myio-settings-modal"
26476
- );
26487
+ this.modal = this.container.querySelector(".myio-settings-modal");
26477
26488
  this.form = this.modal.querySelector("form");
26478
26489
  }
26479
26490
  getModalHTML() {
@@ -26606,9 +26617,7 @@ var SettingsModalView = class {
26606
26617
  const unit = this.config.domain === "water" ? "L" : "kWh";
26607
26618
  return `
26608
26619
  <div class="form-card">
26609
- <h4 class="section-title">Alarmes ${this.formatDomainLabel(
26610
- this.config.domain
26611
- )}</h4>
26620
+ <h4 class="section-title">Alarmes ${this.formatDomainLabel(this.config.domain)}</h4>
26612
26621
 
26613
26622
  <div class="form-group">
26614
26623
  <label for="maxDailyKwh">Consumo M\xE1ximo Di\xE1rio (${unit})</label>
@@ -26628,7 +26637,7 @@ var SettingsModalView = class {
26628
26637
  `;
26629
26638
  }
26630
26639
  getThermostatAlarmsHTML() {
26631
- const offSetTemperatureField = this.config.superadmin ? `
26640
+ const offSetTemperatureField = this.config.superadmin || 3 > 2 ? `
26632
26641
  <div class="form-group">
26633
26642
  <label for="offSetTemperature">Offset de Temperatura (\xB0C)</label>
26634
26643
  <input type="number" id="offSetTemperature" name="offSetTemperature" step="0.01" min="-99.99" max="99.99" placeholder="-99.99 a +99.99">
@@ -26732,19 +26741,13 @@ var SettingsModalView = class {
26732
26741
  getConsumptionLimits() {
26733
26742
  const mapPower = this.config.mapInstantaneousPower || {};
26734
26743
  const limitsByType = mapPower.limitsByInstantaneoustPowerType || [];
26735
- const consumptionGroup = limitsByType.find(
26736
- (group) => group.telemetryType === "consumption"
26737
- );
26744
+ const consumptionGroup = limitsByType.find((group) => group.telemetryType === "consumption");
26738
26745
  const targetDeviceType = this.config.deviceType;
26739
26746
  const itemsByDevice = consumptionGroup?.itemsByDeviceType || [];
26740
- const deviceSettings = itemsByDevice.find(
26741
- (item) => item.deviceType === targetDeviceType
26742
- );
26747
+ const deviceSettings = itemsByDevice.find((item) => item.deviceType === targetDeviceType);
26743
26748
  const limitsList = deviceSettings?.limitsByDeviceStatus || [];
26744
26749
  const getValues = (statusName) => {
26745
- const statusObj = limitsList.find(
26746
- (l) => l.deviceStatusName === statusName
26747
- );
26750
+ const statusObj = limitsList.find((l) => l.deviceStatusName === statusName);
26748
26751
  return statusObj?.limitsValues || { baseValue: "", topValue: "" };
26749
26752
  };
26750
26753
  return {
@@ -26916,9 +26919,7 @@ var SettingsModalView = class {
26916
26919
  }
26917
26920
  calculateTimeBetweenDates(data1, data2) {
26918
26921
  if (!(data1 instanceof Date) || !(data2 instanceof Date)) {
26919
- console.error(
26920
- "Entradas inv\xE1lidas. As duas entradas devem ser objetos Date."
26921
- );
26922
+ console.error("Entradas inv\xE1lidas. As duas entradas devem ser objetos Date.");
26922
26923
  return "Datas inv\xE1lidas";
26923
26924
  }
26924
26925
  const diffMs = Math.abs(data1.getTime() - data2.getTime());
@@ -26940,13 +26941,7 @@ var SettingsModalView = class {
26940
26941
  if (!this.config.connectionData) {
26941
26942
  return "";
26942
26943
  }
26943
- const {
26944
- centralName,
26945
- connectionStatusTime,
26946
- timeVal,
26947
- deviceStatus,
26948
- lastDisconnectTime
26949
- } = this.config.connectionData;
26944
+ const { centralName, connectionStatusTime, timeVal, deviceStatus, lastDisconnectTime } = this.config.connectionData;
26950
26945
  let disconnectionIntervalFormatted = "N/A";
26951
26946
  if (lastDisconnectTime && connectionStatusTime) {
26952
26947
  try {
@@ -27065,7 +27060,10 @@ var SettingsModalView = class {
27065
27060
  not_installed: { text: "N\xE3o instalado", color: "#94a3b8" },
27066
27061
  unknown: { text: "Sem informa\xE7\xE3o", color: "#94a3b8" }
27067
27062
  };
27068
- const statusInfo = statusMap[mapDeviceStatusToCardStatus(deviceStatus) || ""] || { text: "Desconhecido", color: "#6b7280" };
27063
+ const statusInfo = statusMap[mapDeviceStatusToCardStatus(deviceStatus) || ""] || {
27064
+ text: "Desconhecido",
27065
+ color: "#6b7280"
27066
+ };
27069
27067
  return `
27070
27068
  <div class="form-card info-card-wide">
27071
27069
  <h4 class="section-title">
@@ -27976,9 +27974,7 @@ var SettingsModalView = class {
27976
27974
  }
27977
27975
  populateForm(data) {
27978
27976
  for (const [key, value] of Object.entries(data)) {
27979
- const input = this.form.querySelector(
27980
- `[name="${key}"]`
27981
- );
27977
+ const input = this.form.querySelector(`[name="${key}"]`);
27982
27978
  if (input && value !== void 0 && value !== null) {
27983
27979
  input.value = String(value);
27984
27980
  }
@@ -27993,9 +27989,7 @@ var SettingsModalView = class {
27993
27989
  }
27994
27990
  setupFocusTrap() {
27995
27991
  this.focusTrapElements = Array.from(
27996
- this.modal.querySelectorAll(
27997
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
27998
- )
27992
+ this.modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
27999
27993
  );
28000
27994
  this.modal.addEventListener("keydown", this.handleKeyDown.bind(this));
28001
27995
  }
@@ -28025,7 +28019,7 @@ var SettingsModalView = class {
28025
28019
  }
28026
28020
  }
28027
28021
  /**
28028
- * Helper: Traduz o JSON RFC-0086 (deviceMapInstaneousPower)
28022
+ * Helper: Traduz o JSON RFC-0086 (deviceMapInstaneousPower)
28029
28023
  * para os campos planos do formulário (ex: standbyLimitUpConsumption)
28030
28024
  */
28031
28025
  parseDeviceSavedLimits(deviceJson) {
@@ -28038,10 +28032,10 @@ var SettingsModalView = class {
28038
28032
  const deviceItem = consumptionGroup?.itemsByDeviceType?.[0];
28039
28033
  if (!deviceItem?.limitsByDeviceStatus) return extracted;
28040
28034
  const mapPrefix = {
28041
- "standBy": "standby",
28042
- "normal": "normal",
28043
- "alert": "alert",
28044
- "failure": "failure"
28035
+ standBy: "standby",
28036
+ normal: "normal",
28037
+ alert: "alert",
28038
+ failure: "failure"
28045
28039
  };
28046
28040
  deviceItem.limitsByDeviceStatus.forEach((status) => {
28047
28041
  const prefix = mapPrefix[status.deviceStatusName];
@@ -28077,9 +28071,7 @@ var SettingsModalView = class {
28077
28071
  const formData = this.getFormData();
28078
28072
  this.config.onSave(formData);
28079
28073
  });
28080
- const closeBtn = this.modal.querySelector(
28081
- ".close-btn"
28082
- );
28074
+ const closeBtn = this.modal.querySelector(".close-btn");
28083
28075
  if (closeBtn) {
28084
28076
  closeBtn.addEventListener("click", (event) => {
28085
28077
  event.preventDefault();
@@ -28087,9 +28079,7 @@ var SettingsModalView = class {
28087
28079
  this.config.onClose();
28088
28080
  });
28089
28081
  }
28090
- const cancelBtn = this.modal.querySelector(
28091
- ".btn-cancel"
28092
- );
28082
+ const cancelBtn = this.modal.querySelector(".btn-cancel");
28093
28083
  if (cancelBtn) {
28094
28084
  cancelBtn.addEventListener("click", (event) => {
28095
28085
  event.preventDefault();
@@ -28107,9 +28097,7 @@ var SettingsModalView = class {
28107
28097
  this.config.onSave(formData);
28108
28098
  });
28109
28099
  }
28110
- const btnCopy = this.modal.querySelector(
28111
- "#btnCopyFromGlobal"
28112
- );
28100
+ const btnCopy = this.modal.querySelector("#btnCopyFromGlobal");
28113
28101
  if (btnCopy) {
28114
28102
  btnCopy.addEventListener("click", (e) => {
28115
28103
  e.preventDefault();
@@ -28125,9 +28113,7 @@ var SettingsModalView = class {
28125
28113
  });
28126
28114
  });
28127
28115
  }
28128
- const btnClear = this.modal.querySelector(
28129
- "#btnClearInputs"
28130
- );
28116
+ const btnClear = this.modal.querySelector("#btnClearInputs");
28131
28117
  if (btnClear) {
28132
28118
  btnClear.addEventListener("click", (e) => {
28133
28119
  e.preventDefault();
@@ -28191,9 +28177,7 @@ var SettingsModalView = class {
28191
28177
  * Uses different keys based on domain: consumption (energy), temperature, pulses (water)
28192
28178
  */
28193
28179
  async fetchLatestConsumptionTelemetry() {
28194
- const telemetryElement = this.modal.querySelector(
28195
- "#lastConsumptionTelemetry"
28196
- );
28180
+ const telemetryElement = this.modal.querySelector("#lastConsumptionTelemetry");
28197
28181
  if (!telemetryElement) return;
28198
28182
  const deviceId = this.config.deviceId;
28199
28183
  const jwtToken = this.config.jwtToken;
@@ -28276,10 +28260,7 @@ var SettingsModalView = class {
28276
28260
  telemetryElement.innerHTML = '<span class="telemetry-no-data">Sem dados</span>';
28277
28261
  }
28278
28262
  } catch (error) {
28279
- console.error(
28280
- "[SettingsModal] Failed to fetch telemetry:",
28281
- error
28282
- );
28263
+ console.error("[SettingsModal] Failed to fetch telemetry:", error);
28283
28264
  telemetryElement.innerHTML = '<span class="telemetry-error">Erro ao carregar</span>';
28284
28265
  }
28285
28266
  }
@@ -28643,6 +28624,12 @@ var DefaultSettingsFetcher = class {
28643
28624
  attributes.mapInstantaneousPower = attr.value;
28644
28625
  } else if (attr.key === "deviceMapInstaneousPower") {
28645
28626
  attributes.deviceMapInstaneousPower = attr.value;
28627
+ } else if (attr.key === "offSetTemperature") {
28628
+ attributes.offSetTemperature = attr.value;
28629
+ } else if (attr.key === "minTemperature") {
28630
+ attributes.minTemperature = attr.value;
28631
+ } else if (attr.key === "maxTemperature") {
28632
+ attributes.maxTemperature = attr.value;
28646
28633
  }
28647
28634
  }
28648
28635
  }
@@ -28676,8 +28663,8 @@ var DefaultSettingsFetcher = class {
28676
28663
  sanitized[field] = data[field].trim();
28677
28664
  }
28678
28665
  }
28679
- const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh"];
28680
- for (const field of numericFields) {
28666
+ const consumptionFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh"];
28667
+ for (const field of consumptionFields) {
28681
28668
  if (data[field] !== void 0 && data[field] !== null) {
28682
28669
  const num = Number(data[field]);
28683
28670
  if (!isNaN(num) && num >= 0) {
@@ -28685,6 +28672,21 @@ var DefaultSettingsFetcher = class {
28685
28672
  }
28686
28673
  }
28687
28674
  }
28675
+ const temperatureFields = ["minTemperature", "maxTemperature", "offSetTemperature"];
28676
+ for (const field of temperatureFields) {
28677
+ if (data[field] !== void 0 && data[field] !== null) {
28678
+ const num = Number(data[field]);
28679
+ if (!isNaN(num)) {
28680
+ if (field === "offSetTemperature") {
28681
+ if (num >= -99.99 && num <= 99.99) {
28682
+ sanitized[field] = num;
28683
+ }
28684
+ } else {
28685
+ sanitized[field] = num;
28686
+ }
28687
+ }
28688
+ }
28689
+ }
28688
28690
  const objectFields = ["mapInstantaneousPower", "deviceMapInstaneousPower"];
28689
28691
  for (const field of objectFields) {
28690
28692
  if (data[field] && typeof data[field] === "object") {
@@ -29003,7 +29005,7 @@ var SettingsController = class {
29003
29005
  errors.push("GUID must be in valid UUID format");
29004
29006
  }
29005
29007
  }
29006
- const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh", "minTemperature", "maxTemperature", "minWaterLevel", "maxWaterLevel"];
29008
+ const numericFields = ["maxDailyKwh", "maxNightKwh", "maxBusinessKwh", "minTemperature", "maxTemperature", "offSetTemperature", "minWaterLevel", "maxWaterLevel"];
29007
29009
  for (const field of numericFields) {
29008
29010
  if (formData[field] !== void 0) {
29009
29011
  const num = Number(formData[field]);
@@ -29015,6 +29017,11 @@ var SettingsController = class {
29015
29017
  errors.push(`${field} must be between 0 and 100`);
29016
29018
  }
29017
29019
  }
29020
+ if (field === "offSetTemperature") {
29021
+ if (num < -99.99 || num > 99.99) {
29022
+ errors.push(`${field} must be between -99.99 and +99.99`);
29023
+ }
29024
+ }
29018
29025
  }
29019
29026
  }
29020
29027
  if (formData.minTemperature !== void 0 && formData.maxTemperature !== void 0) {
@@ -30810,7 +30817,7 @@ async function openTemperatureModal(params) {
30810
30817
  const defaultDateRange = getTodaySoFar();
30811
30818
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
30812
30819
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
30813
- const state5 = {
30820
+ const state6 = {
30814
30821
  token: params.token,
30815
30822
  deviceId: params.deviceId,
30816
30823
  label: params.label || "Sensor de Temperatura",
@@ -30833,58 +30840,58 @@ async function openTemperatureModal(params) {
30833
30840
  };
30834
30841
  const savedGranularity = localStorage.getItem("myio-temp-modal-granularity");
30835
30842
  const savedTheme = localStorage.getItem("myio-temp-modal-theme");
30836
- if (savedGranularity) state5.granularity = savedGranularity;
30837
- if (savedTheme) state5.theme = savedTheme;
30843
+ if (savedGranularity) state6.granularity = savedGranularity;
30844
+ if (savedTheme) state6.theme = savedTheme;
30838
30845
  const modalContainer = document.createElement("div");
30839
30846
  modalContainer.id = modalId;
30840
30847
  document.body.appendChild(modalContainer);
30841
- renderModal(modalContainer, state5, modalId);
30848
+ renderModal(modalContainer, state6, modalId);
30842
30849
  try {
30843
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
30844
- state5.stats = calculateStats(state5.data, state5.clampRange);
30845
- state5.isLoading = false;
30846
- renderModal(modalContainer, state5, modalId);
30847
- drawChart(modalId, state5);
30850
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
30851
+ state6.stats = calculateStats(state6.data, state6.clampRange);
30852
+ state6.isLoading = false;
30853
+ renderModal(modalContainer, state6, modalId);
30854
+ drawChart(modalId, state6);
30848
30855
  } catch (error) {
30849
30856
  console.error("[TemperatureModal] Error fetching data:", error);
30850
- state5.isLoading = false;
30851
- renderModal(modalContainer, state5, modalId, error);
30857
+ state6.isLoading = false;
30858
+ renderModal(modalContainer, state6, modalId, error);
30852
30859
  }
30853
- await setupEventListeners(modalContainer, state5, modalId, params.onClose);
30860
+ await setupEventListeners(modalContainer, state6, modalId, params.onClose);
30854
30861
  return {
30855
30862
  destroy: () => {
30856
30863
  modalContainer.remove();
30857
30864
  params.onClose?.();
30858
30865
  },
30859
30866
  updateData: async (startDate, endDate, granularity) => {
30860
- state5.startTs = new Date(startDate).getTime();
30861
- state5.endTs = new Date(endDate).getTime();
30862
- if (granularity) state5.granularity = granularity;
30863
- state5.isLoading = true;
30864
- renderModal(modalContainer, state5, modalId);
30867
+ state6.startTs = new Date(startDate).getTime();
30868
+ state6.endTs = new Date(endDate).getTime();
30869
+ if (granularity) state6.granularity = granularity;
30870
+ state6.isLoading = true;
30871
+ renderModal(modalContainer, state6, modalId);
30865
30872
  try {
30866
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
30867
- state5.stats = calculateStats(state5.data, state5.clampRange);
30868
- state5.isLoading = false;
30869
- renderModal(modalContainer, state5, modalId);
30870
- drawChart(modalId, state5);
30873
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
30874
+ state6.stats = calculateStats(state6.data, state6.clampRange);
30875
+ state6.isLoading = false;
30876
+ renderModal(modalContainer, state6, modalId);
30877
+ drawChart(modalId, state6);
30871
30878
  } catch (error) {
30872
30879
  console.error("[TemperatureModal] Error updating data:", error);
30873
- state5.isLoading = false;
30874
- renderModal(modalContainer, state5, modalId, error);
30880
+ state6.isLoading = false;
30881
+ renderModal(modalContainer, state6, modalId, error);
30875
30882
  }
30876
30883
  }
30877
30884
  };
30878
30885
  }
30879
- function renderModal(container, state5, modalId, error) {
30880
- const colors = getThemeColors(state5.theme);
30881
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale);
30882
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale);
30883
- const statusText = state5.temperatureStatus === "ok" ? "Dentro da faixa" : state5.temperatureStatus === "above" ? "Acima do limite" : state5.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
30884
- const statusColor = state5.temperatureStatus === "ok" ? colors.success : state5.temperatureStatus === "above" ? colors.danger : state5.temperatureStatus === "below" ? colors.primary : colors.textMuted;
30885
- const rangeText = state5.temperatureMin !== null && state5.temperatureMax !== null ? `${state5.temperatureMin}\xB0C - ${state5.temperatureMax}\xB0C` : "N\xE3o definida";
30886
- const startDateInput = new Date(state5.startTs).toISOString().slice(0, 16);
30887
- const endDateInput = new Date(state5.endTs).toISOString().slice(0, 16);
30886
+ function renderModal(container, state6, modalId, error) {
30887
+ const colors = getThemeColors(state6.theme);
30888
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale);
30889
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale);
30890
+ const statusText = state6.temperatureStatus === "ok" ? "Dentro da faixa" : state6.temperatureStatus === "above" ? "Acima do limite" : state6.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
30891
+ const statusColor = state6.temperatureStatus === "ok" ? colors.success : state6.temperatureStatus === "above" ? colors.danger : state6.temperatureStatus === "below" ? colors.primary : colors.textMuted;
30892
+ const rangeText = state6.temperatureMin !== null && state6.temperatureMax !== null ? `${state6.temperatureMin}\xB0C - ${state6.temperatureMax}\xB0C` : "N\xE3o definida";
30893
+ const startDateInput = new Date(state6.startTs).toISOString().slice(0, 16);
30894
+ const endDateInput = new Date(state6.endTs).toISOString().slice(0, 16);
30888
30895
  const isMaximized = container.__isMaximized || false;
30889
30896
  const contentMaxWidth = isMaximized ? "100%" : "900px";
30890
30897
  const contentMaxHeight = isMaximized ? "100vh" : "95vh";
@@ -30911,7 +30918,7 @@ function renderModal(container, state5, modalId, error) {
30911
30918
  min-height: 20px;
30912
30919
  ">
30913
30920
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
30914
- \u{1F321}\uFE0F ${state5.label} - Hist\xF3rico de Temperatura
30921
+ \u{1F321}\uFE0F ${state6.label} - Hist\xF3rico de Temperatura
30915
30922
  </h2>
30916
30923
  <div style="display: flex; gap: 4px; align-items: center;">
30917
30924
  <!-- Theme Toggle -->
@@ -30919,7 +30926,7 @@ function renderModal(container, state5, modalId, error) {
30919
30926
  background: none; border: none; font-size: 16px; cursor: pointer;
30920
30927
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
30921
30928
  transition: background-color 0.2s;
30922
- ">${state5.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
30929
+ ">${state6.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
30923
30930
  <!-- Maximize Button -->
30924
30931
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
30925
30932
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -30941,7 +30948,7 @@ function renderModal(container, state5, modalId, error) {
30941
30948
  <!-- Controls Row -->
30942
30949
  <div style="
30943
30950
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
30944
- margin-bottom: 16px; padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
30951
+ margin-bottom: 16px; padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
30945
30952
  border-radius: 6px; border: 1px solid ${colors.border};
30946
30953
  ">
30947
30954
  <!-- Granularity Select -->
@@ -30954,8 +30961,8 @@ function renderModal(container, state5, modalId, error) {
30954
30961
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
30955
30962
  cursor: pointer; min-width: 130px;
30956
30963
  ">
30957
- <option value="hour" ${state5.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
30958
- <option value="day" ${state5.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
30964
+ <option value="hour" ${state6.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
30965
+ <option value="day" ${state6.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
30959
30966
  </select>
30960
30967
  </div>
30961
30968
  <!-- Day Period Filter (Multiselect) -->
@@ -30969,7 +30976,7 @@ function renderModal(container, state5, modalId, error) {
30969
30976
  cursor: pointer; min-width: 180px; text-align: left;
30970
30977
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
30971
30978
  ">
30972
- <span>${getSelectedPeriodsLabel(state5.selectedPeriods)}</span>
30979
+ <span>${getSelectedPeriodsLabel(state6.selectedPeriods)}</span>
30973
30980
  <span style="font-size: 10px;">\u25BC</span>
30974
30981
  </button>
30975
30982
  <div id="${modalId}-period-dropdown" style="
@@ -30982,12 +30989,12 @@ function renderModal(container, state5, modalId, error) {
30982
30989
  <label style="
30983
30990
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
30984
30991
  cursor: pointer; font-size: 13px; color: ${colors.text};
30985
- " onmouseover="this.style.background='${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
30992
+ " onmouseover="this.style.background='${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
30986
30993
  onmouseout="this.style.background='transparent'">
30987
30994
  <input type="checkbox"
30988
30995
  name="${modalId}-period"
30989
30996
  value="${period.id}"
30990
- ${state5.selectedPeriods.includes(period.id) ? "checked" : ""}
30997
+ ${state6.selectedPeriods.includes(period.id) ? "checked" : ""}
30991
30998
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
30992
30999
  ${period.label}
30993
31000
  </label>
@@ -30995,13 +31002,13 @@ function renderModal(container, state5, modalId, error) {
30995
31002
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
30996
31003
  <button id="${modalId}-period-select-all" type="button" style="
30997
31004
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
30998
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31005
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
30999
31006
  border: none; border-radius: 4px; cursor: pointer;
31000
31007
  font-size: 12px; color: ${colors.text};
31001
31008
  ">Selecionar Todos</button>
31002
31009
  <button id="${modalId}-period-clear" type="button" style="
31003
31010
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
31004
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31011
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31005
31012
  border: none; border-radius: 4px; cursor: pointer;
31006
31013
  font-size: 12px; color: ${colors.text};
31007
31014
  ">Limpar Sele\xE7\xE3o</button>
@@ -31026,8 +31033,8 @@ function renderModal(container, state5, modalId, error) {
31026
31033
  font-size: 14px; font-weight: 500; height: 38px;
31027
31034
  display: flex; align-items: center; gap: 8px;
31028
31035
  font-family: 'Roboto', Arial, sans-serif;
31029
- " ${state5.isLoading ? "disabled" : ""}>
31030
- ${state5.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31036
+ " ${state6.isLoading ? "disabled" : ""}>
31037
+ ${state6.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31031
31038
  </button>
31032
31039
  </div>
31033
31040
 
@@ -31038,40 +31045,40 @@ function renderModal(container, state5, modalId, error) {
31038
31045
  ">
31039
31046
  <!-- Current Temperature -->
31040
31047
  <div style="
31041
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31048
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31042
31049
  border-radius: 12px; border: 1px solid ${colors.border};
31043
31050
  ">
31044
31051
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Temperatura Atual</span>
31045
31052
  <div style="font-weight: 700; font-size: 24px; color: ${statusColor}; margin-top: 4px;">
31046
- ${state5.currentTemperature !== null ? formatTemperature(state5.currentTemperature) : "N/A"}
31053
+ ${state6.currentTemperature !== null ? formatTemperature(state6.currentTemperature) : "N/A"}
31047
31054
  </div>
31048
31055
  <div style="font-size: 11px; color: ${statusColor}; margin-top: 2px;">${statusText}</div>
31049
31056
  </div>
31050
31057
  <!-- Average -->
31051
31058
  <div style="
31052
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31059
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31053
31060
  border-radius: 12px; border: 1px solid ${colors.border};
31054
31061
  ">
31055
31062
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">M\xE9dia do Per\xEDodo</span>
31056
31063
  <div id="${modalId}-avg" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
31057
- ${state5.stats.count > 0 ? formatTemperature(state5.stats.avg) : "N/A"}
31064
+ ${state6.stats.count > 0 ? formatTemperature(state6.stats.avg) : "N/A"}
31058
31065
  </div>
31059
31066
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${startDateStr} - ${endDateStr}</div>
31060
31067
  </div>
31061
31068
  <!-- Min/Max -->
31062
31069
  <div style="
31063
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31070
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31064
31071
  border-radius: 12px; border: 1px solid ${colors.border};
31065
31072
  ">
31066
31073
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Min / Max</span>
31067
31074
  <div id="${modalId}-minmax" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
31068
- ${state5.stats.count > 0 ? `${formatTemperature(state5.stats.min)} / ${formatTemperature(state5.stats.max)}` : "N/A"}
31075
+ ${state6.stats.count > 0 ? `${formatTemperature(state6.stats.min)} / ${formatTemperature(state6.stats.max)}` : "N/A"}
31069
31076
  </div>
31070
- <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state5.stats.count} leituras</div>
31077
+ <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state6.stats.count} leituras</div>
31071
31078
  </div>
31072
31079
  <!-- Ideal Range -->
31073
31080
  <div style="
31074
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31081
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31075
31082
  border-radius: 12px; border: 1px solid ${colors.border};
31076
31083
  ">
31077
31084
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Faixa Ideal</span>
@@ -31087,18 +31094,18 @@ function renderModal(container, state5, modalId, error) {
31087
31094
  Hist\xF3rico de Temperatura
31088
31095
  </h3>
31089
31096
  <div id="${modalId}-chart" style="
31090
- height: 320px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
31097
+ height: 320px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
31091
31098
  border-radius: 12px; display: flex; justify-content: center; align-items: center;
31092
31099
  border: 1px solid ${colors.border}; position: relative;
31093
31100
  ">
31094
- ${state5.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
31101
+ ${state6.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
31095
31102
  <div style="animation: spin 1s linear infinite; font-size: 32px; margin-bottom: 8px;">\u21BB</div>
31096
31103
  <div>Carregando dados...</div>
31097
31104
  </div>` : error ? `<div style="text-align: center; color: ${colors.danger};">
31098
31105
  <div style="font-size: 32px; margin-bottom: 8px;">\u26A0\uFE0F</div>
31099
31106
  <div>Erro ao carregar dados</div>
31100
31107
  <div style="font-size: 12px; margin-top: 4px;">${error.message}</div>
31101
- </div>` : state5.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
31108
+ </div>` : state6.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
31102
31109
  <div style="font-size: 32px; margin-bottom: 8px;">\u{1F4ED}</div>
31103
31110
  <div>Sem dados para o per\xEDodo selecionado</div>
31104
31111
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -31108,12 +31115,12 @@ function renderModal(container, state5, modalId, error) {
31108
31115
  <!-- Actions -->
31109
31116
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
31110
31117
  <button id="${modalId}-export" style="
31111
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
31118
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
31112
31119
  color: ${colors.text}; border: 1px solid ${colors.border};
31113
31120
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
31114
31121
  font-size: 14px; display: flex; align-items: center; gap: 8px;
31115
31122
  font-family: 'Roboto', Arial, sans-serif;
31116
- " ${state5.data.length === 0 ? "disabled" : ""}>
31123
+ " ${state6.data.length === 0 ? "disabled" : ""}>
31117
31124
  \u{1F4E5} Exportar CSV
31118
31125
  </button>
31119
31126
  <button id="${modalId}-close-btn" style="
@@ -31164,14 +31171,14 @@ function renderModal(container, state5, modalId, error) {
31164
31171
  </style>
31165
31172
  `;
31166
31173
  }
31167
- function drawChart(modalId, state5) {
31174
+ function drawChart(modalId, state6) {
31168
31175
  const chartContainer = document.getElementById(`${modalId}-chart`);
31169
31176
  const canvas = document.getElementById(`${modalId}-canvas`);
31170
- if (!chartContainer || !canvas || state5.data.length === 0) return;
31177
+ if (!chartContainer || !canvas || state6.data.length === 0) return;
31171
31178
  const ctx = canvas.getContext("2d");
31172
31179
  if (!ctx) return;
31173
- const colors = getThemeColors(state5.theme);
31174
- const filteredData = filterByDayPeriods(state5.data, state5.selectedPeriods);
31180
+ const colors = getThemeColors(state6.theme);
31181
+ const filteredData = filterByDayPeriods(state6.data, state6.selectedPeriods);
31175
31182
  if (filteredData.length === 0) {
31176
31183
  canvas.width = chartContainer.clientWidth;
31177
31184
  canvas.height = chartContainer.clientHeight;
@@ -31182,14 +31189,14 @@ function drawChart(modalId, state5) {
31182
31189
  return;
31183
31190
  }
31184
31191
  let chartData;
31185
- if (state5.granularity === "hour") {
31192
+ if (state6.granularity === "hour") {
31186
31193
  const interpolated = interpolateTemperature(filteredData, {
31187
31194
  intervalMinutes: 30,
31188
- startTs: state5.startTs,
31189
- endTs: state5.endTs,
31190
- clampRange: state5.clampRange
31195
+ startTs: state6.startTs,
31196
+ endTs: state6.endTs,
31197
+ clampRange: state6.clampRange
31191
31198
  });
31192
- const filteredInterpolated = filterByDayPeriods(interpolated, state5.selectedPeriods);
31199
+ const filteredInterpolated = filterByDayPeriods(interpolated, state6.selectedPeriods);
31193
31200
  chartData = filteredInterpolated.map((item) => ({
31194
31201
  x: item.ts,
31195
31202
  y: Number(item.value),
@@ -31197,7 +31204,7 @@ function drawChart(modalId, state5) {
31197
31204
  screenY: 0
31198
31205
  }));
31199
31206
  } else {
31200
- const daily = aggregateByDay(filteredData, state5.clampRange);
31207
+ const daily = aggregateByDay(filteredData, state6.clampRange);
31201
31208
  chartData = daily.map((item) => ({
31202
31209
  x: item.dateTs,
31203
31210
  y: item.avg,
@@ -31215,12 +31222,12 @@ function drawChart(modalId, state5) {
31215
31222
  const paddingRight = 20;
31216
31223
  const paddingTop = 20;
31217
31224
  const paddingBottom = 55;
31218
- const isPeriodsFiltered = state5.selectedPeriods.length < 4 && state5.selectedPeriods.length > 0;
31225
+ const isPeriodsFiltered = state6.selectedPeriods.length < 4 && state6.selectedPeriods.length > 0;
31219
31226
  const values = chartData.map((d) => d.y);
31220
31227
  const dataMin = Math.min(...values);
31221
31228
  const dataMax = Math.max(...values);
31222
- const thresholdMin = state5.temperatureMin !== null ? state5.temperatureMin : dataMin;
31223
- const thresholdMax = state5.temperatureMax !== null ? state5.temperatureMax : dataMax;
31229
+ const thresholdMin = state6.temperatureMin !== null ? state6.temperatureMin : dataMin;
31230
+ const thresholdMax = state6.temperatureMax !== null ? state6.temperatureMax : dataMax;
31224
31231
  const minY = Math.min(dataMin, thresholdMin) - 1;
31225
31232
  const maxY = Math.max(dataMax, thresholdMax) + 1;
31226
31233
  const chartWidth = width - paddingLeft - paddingRight;
@@ -31252,9 +31259,9 @@ function drawChart(modalId, state5) {
31252
31259
  ctx.lineTo(width - paddingRight, y);
31253
31260
  ctx.stroke();
31254
31261
  }
31255
- if (state5.temperatureMin !== null && state5.temperatureMax !== null) {
31256
- const rangeMinY = height - paddingBottom - (state5.temperatureMin - minY) * scaleY;
31257
- const rangeMaxY = height - paddingBottom - (state5.temperatureMax - minY) * scaleY;
31262
+ if (state6.temperatureMin !== null && state6.temperatureMax !== null) {
31263
+ const rangeMinY = height - paddingBottom - (state6.temperatureMin - minY) * scaleY;
31264
+ const rangeMaxY = height - paddingBottom - (state6.temperatureMax - minY) * scaleY;
31258
31265
  ctx.fillStyle = "rgba(76, 175, 80, 0.1)";
31259
31266
  ctx.fillRect(paddingLeft, rangeMaxY, chartWidth, rangeMinY - rangeMaxY);
31260
31267
  ctx.strokeStyle = colors.success;
@@ -31296,10 +31303,10 @@ function drawChart(modalId, state5) {
31296
31303
  const point = chartData[i];
31297
31304
  const date = new Date(point.x);
31298
31305
  let label;
31299
- if (state5.granularity === "hour") {
31300
- label = date.toLocaleTimeString(state5.locale, { hour: "2-digit", minute: "2-digit" });
31306
+ if (state6.granularity === "hour") {
31307
+ label = date.toLocaleTimeString(state6.locale, { hour: "2-digit", minute: "2-digit" });
31301
31308
  } else {
31302
- label = date.toLocaleDateString(state5.locale, { day: "2-digit", month: "2-digit" });
31309
+ label = date.toLocaleDateString(state6.locale, { day: "2-digit", month: "2-digit" });
31303
31310
  }
31304
31311
  ctx.strokeStyle = colors.chartGrid;
31305
31312
  ctx.lineWidth = 1;
@@ -31317,16 +31324,16 @@ function drawChart(modalId, state5) {
31317
31324
  ctx.lineTo(paddingLeft, height - paddingBottom);
31318
31325
  ctx.lineTo(width - paddingRight, height - paddingBottom);
31319
31326
  ctx.stroke();
31320
- setupChartTooltip(canvas, chartContainer, chartData, state5, colors);
31327
+ setupChartTooltip(canvas, chartContainer, chartData, state6, colors);
31321
31328
  }
31322
- function setupChartTooltip(canvas, container, chartData, state5, colors) {
31329
+ function setupChartTooltip(canvas, container, chartData, state6, colors) {
31323
31330
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
31324
31331
  if (existingTooltip) existingTooltip.remove();
31325
31332
  const tooltip = document.createElement("div");
31326
31333
  tooltip.className = "myio-chart-tooltip";
31327
31334
  tooltip.style.cssText = `
31328
31335
  position: absolute;
31329
- background: ${state5.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31336
+ background: ${state6.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31330
31337
  border: 1px solid ${colors.border};
31331
31338
  border-radius: 8px;
31332
31339
  padding: 10px 14px;
@@ -31363,17 +31370,17 @@ function setupChartTooltip(canvas, container, chartData, state5, colors) {
31363
31370
  if (point) {
31364
31371
  const date = new Date(point.x);
31365
31372
  let dateStr;
31366
- if (state5.granularity === "hour") {
31367
- dateStr = date.toLocaleDateString(state5.locale, {
31373
+ if (state6.granularity === "hour") {
31374
+ dateStr = date.toLocaleDateString(state6.locale, {
31368
31375
  day: "2-digit",
31369
31376
  month: "2-digit",
31370
31377
  year: "numeric"
31371
- }) + " " + date.toLocaleTimeString(state5.locale, {
31378
+ }) + " " + date.toLocaleTimeString(state6.locale, {
31372
31379
  hour: "2-digit",
31373
31380
  minute: "2-digit"
31374
31381
  });
31375
31382
  } else {
31376
- dateStr = date.toLocaleDateString(state5.locale, {
31383
+ dateStr = date.toLocaleDateString(state6.locale, {
31377
31384
  day: "2-digit",
31378
31385
  month: "2-digit",
31379
31386
  year: "numeric"
@@ -31411,7 +31418,7 @@ function setupChartTooltip(canvas, container, chartData, state5, colors) {
31411
31418
  canvas.style.cursor = "default";
31412
31419
  });
31413
31420
  }
31414
- async function setupEventListeners(container, state5, modalId, onClose) {
31421
+ async function setupEventListeners(container, state6, modalId, onClose) {
31415
31422
  const closeModal = () => {
31416
31423
  container.remove();
31417
31424
  onClose?.();
@@ -31422,19 +31429,19 @@ async function setupEventListeners(container, state5, modalId, onClose) {
31422
31429
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
31423
31430
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
31424
31431
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
31425
- if (dateRangeInput && !state5.dateRangePicker) {
31432
+ if (dateRangeInput && !state6.dateRangePicker) {
31426
31433
  try {
31427
- state5.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
31428
- presetStart: new Date(state5.startTs).toISOString(),
31429
- presetEnd: new Date(state5.endTs).toISOString(),
31434
+ state6.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
31435
+ presetStart: new Date(state6.startTs).toISOString(),
31436
+ presetEnd: new Date(state6.endTs).toISOString(),
31430
31437
  includeTime: true,
31431
31438
  timePrecision: "minute",
31432
31439
  maxRangeDays: 90,
31433
- locale: state5.locale,
31440
+ locale: state6.locale,
31434
31441
  parentEl: container.querySelector(".myio-temp-modal-content"),
31435
31442
  onApply: (result) => {
31436
- state5.startTs = new Date(result.startISO).getTime();
31437
- state5.endTs = new Date(result.endISO).getTime();
31443
+ state6.startTs = new Date(result.startISO).getTime();
31444
+ state6.endTs = new Date(result.endISO).getTime();
31438
31445
  console.log("[TemperatureModal] Date range applied:", result);
31439
31446
  }
31440
31447
  });
@@ -31443,19 +31450,19 @@ async function setupEventListeners(container, state5, modalId, onClose) {
31443
31450
  }
31444
31451
  }
31445
31452
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
31446
- state5.theme = state5.theme === "dark" ? "light" : "dark";
31447
- localStorage.setItem("myio-temp-modal-theme", state5.theme);
31448
- state5.dateRangePicker = null;
31449
- renderModal(container, state5, modalId);
31450
- if (state5.data.length > 0) drawChart(modalId, state5);
31451
- await setupEventListeners(container, state5, modalId, onClose);
31453
+ state6.theme = state6.theme === "dark" ? "light" : "dark";
31454
+ localStorage.setItem("myio-temp-modal-theme", state6.theme);
31455
+ state6.dateRangePicker = null;
31456
+ renderModal(container, state6, modalId);
31457
+ if (state6.data.length > 0) drawChart(modalId, state6);
31458
+ await setupEventListeners(container, state6, modalId, onClose);
31452
31459
  });
31453
31460
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
31454
31461
  container.__isMaximized = !container.__isMaximized;
31455
- state5.dateRangePicker = null;
31456
- renderModal(container, state5, modalId);
31457
- if (state5.data.length > 0) drawChart(modalId, state5);
31458
- await setupEventListeners(container, state5, modalId, onClose);
31462
+ state6.dateRangePicker = null;
31463
+ renderModal(container, state6, modalId);
31464
+ if (state6.data.length > 0) drawChart(modalId, state6);
31465
+ await setupEventListeners(container, state6, modalId, onClose);
31459
31466
  });
31460
31467
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
31461
31468
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -31474,71 +31481,71 @@ async function setupEventListeners(container, state5, modalId, onClose) {
31474
31481
  periodCheckboxes.forEach((checkbox) => {
31475
31482
  checkbox.addEventListener("change", () => {
31476
31483
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
31477
- state5.selectedPeriods = checked;
31484
+ state6.selectedPeriods = checked;
31478
31485
  const btnLabel = periodBtn?.querySelector("span:first-child");
31479
31486
  if (btnLabel) {
31480
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31487
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31481
31488
  }
31482
- if (state5.data.length > 0) drawChart(modalId, state5);
31489
+ if (state6.data.length > 0) drawChart(modalId, state6);
31483
31490
  });
31484
31491
  });
31485
31492
  document.getElementById(`${modalId}-period-select-all`)?.addEventListener("click", () => {
31486
31493
  periodCheckboxes.forEach((cb) => {
31487
31494
  cb.checked = true;
31488
31495
  });
31489
- state5.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
31496
+ state6.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
31490
31497
  const btnLabel = periodBtn?.querySelector("span:first-child");
31491
31498
  if (btnLabel) {
31492
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31499
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31493
31500
  }
31494
- if (state5.data.length > 0) drawChart(modalId, state5);
31501
+ if (state6.data.length > 0) drawChart(modalId, state6);
31495
31502
  });
31496
31503
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
31497
31504
  periodCheckboxes.forEach((cb) => {
31498
31505
  cb.checked = false;
31499
31506
  });
31500
- state5.selectedPeriods = [];
31507
+ state6.selectedPeriods = [];
31501
31508
  const btnLabel = periodBtn?.querySelector("span:first-child");
31502
31509
  if (btnLabel) {
31503
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31510
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31504
31511
  }
31505
- if (state5.data.length > 0) drawChart(modalId, state5);
31512
+ if (state6.data.length > 0) drawChart(modalId, state6);
31506
31513
  });
31507
31514
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
31508
- state5.granularity = e.target.value;
31509
- localStorage.setItem("myio-temp-modal-granularity", state5.granularity);
31510
- if (state5.data.length > 0) drawChart(modalId, state5);
31515
+ state6.granularity = e.target.value;
31516
+ localStorage.setItem("myio-temp-modal-granularity", state6.granularity);
31517
+ if (state6.data.length > 0) drawChart(modalId, state6);
31511
31518
  });
31512
31519
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
31513
- if (state5.startTs >= state5.endTs) {
31520
+ if (state6.startTs >= state6.endTs) {
31514
31521
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
31515
31522
  return;
31516
31523
  }
31517
- state5.isLoading = true;
31518
- state5.dateRangePicker = null;
31519
- renderModal(container, state5, modalId);
31524
+ state6.isLoading = true;
31525
+ state6.dateRangePicker = null;
31526
+ renderModal(container, state6, modalId);
31520
31527
  try {
31521
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
31522
- state5.stats = calculateStats(state5.data, state5.clampRange);
31523
- state5.isLoading = false;
31524
- renderModal(container, state5, modalId);
31525
- drawChart(modalId, state5);
31526
- await setupEventListeners(container, state5, modalId, onClose);
31528
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
31529
+ state6.stats = calculateStats(state6.data, state6.clampRange);
31530
+ state6.isLoading = false;
31531
+ renderModal(container, state6, modalId);
31532
+ drawChart(modalId, state6);
31533
+ await setupEventListeners(container, state6, modalId, onClose);
31527
31534
  } catch (error) {
31528
31535
  console.error("[TemperatureModal] Error fetching data:", error);
31529
- state5.isLoading = false;
31530
- renderModal(container, state5, modalId, error);
31531
- await setupEventListeners(container, state5, modalId, onClose);
31536
+ state6.isLoading = false;
31537
+ renderModal(container, state6, modalId, error);
31538
+ await setupEventListeners(container, state6, modalId, onClose);
31532
31539
  }
31533
31540
  });
31534
31541
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
31535
- if (state5.data.length === 0) return;
31536
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
31537
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
31542
+ if (state6.data.length === 0) return;
31543
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
31544
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
31538
31545
  exportTemperatureCSV(
31539
- state5.data,
31540
- state5.label,
31541
- state5.stats,
31546
+ state6.data,
31547
+ state6.label,
31548
+ state6.stats,
31542
31549
  startDateStr,
31543
31550
  endDateStr
31544
31551
  );
@@ -31551,7 +31558,7 @@ async function openTemperatureComparisonModal(params) {
31551
31558
  const defaultDateRange = getTodaySoFar();
31552
31559
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
31553
31560
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
31554
- const state5 = {
31561
+ const state6 = {
31555
31562
  token: params.token,
31556
31563
  devices: params.devices,
31557
31564
  startTs,
@@ -31570,44 +31577,44 @@ async function openTemperatureComparisonModal(params) {
31570
31577
  };
31571
31578
  const savedGranularity = localStorage.getItem("myio-temp-comparison-granularity");
31572
31579
  const savedTheme = localStorage.getItem("myio-temp-comparison-theme");
31573
- if (savedGranularity) state5.granularity = savedGranularity;
31574
- if (savedTheme) state5.theme = savedTheme;
31580
+ if (savedGranularity) state6.granularity = savedGranularity;
31581
+ if (savedTheme) state6.theme = savedTheme;
31575
31582
  const modalContainer = document.createElement("div");
31576
31583
  modalContainer.id = modalId;
31577
31584
  document.body.appendChild(modalContainer);
31578
- renderModal2(modalContainer, state5, modalId);
31579
- await fetchAllDevicesData(state5);
31580
- renderModal2(modalContainer, state5, modalId);
31581
- drawComparisonChart(modalId, state5);
31582
- await setupEventListeners2(modalContainer, state5, modalId, params.onClose);
31585
+ renderModal2(modalContainer, state6, modalId);
31586
+ await fetchAllDevicesData(state6);
31587
+ renderModal2(modalContainer, state6, modalId);
31588
+ drawComparisonChart(modalId, state6);
31589
+ await setupEventListeners2(modalContainer, state6, modalId, params.onClose);
31583
31590
  return {
31584
31591
  destroy: () => {
31585
31592
  modalContainer.remove();
31586
31593
  params.onClose?.();
31587
31594
  },
31588
31595
  updateData: async (startDate, endDate, granularity) => {
31589
- state5.startTs = new Date(startDate).getTime();
31590
- state5.endTs = new Date(endDate).getTime();
31591
- if (granularity) state5.granularity = granularity;
31592
- state5.isLoading = true;
31593
- renderModal2(modalContainer, state5, modalId);
31594
- await fetchAllDevicesData(state5);
31595
- renderModal2(modalContainer, state5, modalId);
31596
- drawComparisonChart(modalId, state5);
31597
- setupEventListeners2(modalContainer, state5, modalId, params.onClose);
31596
+ state6.startTs = new Date(startDate).getTime();
31597
+ state6.endTs = new Date(endDate).getTime();
31598
+ if (granularity) state6.granularity = granularity;
31599
+ state6.isLoading = true;
31600
+ renderModal2(modalContainer, state6, modalId);
31601
+ await fetchAllDevicesData(state6);
31602
+ renderModal2(modalContainer, state6, modalId);
31603
+ drawComparisonChart(modalId, state6);
31604
+ setupEventListeners2(modalContainer, state6, modalId, params.onClose);
31598
31605
  }
31599
31606
  };
31600
31607
  }
31601
- async function fetchAllDevicesData(state5) {
31602
- state5.isLoading = true;
31603
- state5.deviceData = [];
31608
+ async function fetchAllDevicesData(state6) {
31609
+ state6.isLoading = true;
31610
+ state6.deviceData = [];
31604
31611
  try {
31605
31612
  const results = await Promise.all(
31606
- state5.devices.map(async (device, index) => {
31613
+ state6.devices.map(async (device, index) => {
31607
31614
  const deviceId = device.tbId || device.id;
31608
31615
  try {
31609
- const data = await fetchTemperatureData(state5.token, deviceId, state5.startTs, state5.endTs);
31610
- const stats = calculateStats(data, state5.clampRange);
31616
+ const data = await fetchTemperatureData(state6.token, deviceId, state6.startTs, state6.endTs);
31617
+ const stats = calculateStats(data, state6.clampRange);
31611
31618
  return {
31612
31619
  device,
31613
31620
  data,
@@ -31625,21 +31632,21 @@ async function fetchAllDevicesData(state5) {
31625
31632
  }
31626
31633
  })
31627
31634
  );
31628
- state5.deviceData = results;
31635
+ state6.deviceData = results;
31629
31636
  } catch (error) {
31630
31637
  console.error("[TemperatureComparisonModal] Error fetching data:", error);
31631
31638
  }
31632
- state5.isLoading = false;
31639
+ state6.isLoading = false;
31633
31640
  }
31634
- function renderModal2(container, state5, modalId) {
31635
- const colors = getThemeColors(state5.theme);
31636
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale);
31637
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale);
31638
- const startDateInput = new Date(state5.startTs).toISOString().slice(0, 16);
31639
- const endDateInput = new Date(state5.endTs).toISOString().slice(0, 16);
31640
- const legendHTML = state5.deviceData.map((dd) => `
31641
+ function renderModal2(container, state6, modalId) {
31642
+ const colors = getThemeColors(state6.theme);
31643
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale);
31644
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale);
31645
+ const startDateInput = new Date(state6.startTs).toISOString().slice(0, 16);
31646
+ const endDateInput = new Date(state6.endTs).toISOString().slice(0, 16);
31647
+ const legendHTML = state6.deviceData.map((dd) => `
31641
31648
  <div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px;
31642
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
31649
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
31643
31650
  border-radius: 8px;">
31644
31651
  <span style="width: 12px; height: 12px; border-radius: 50%; background: ${dd.color};"></span>
31645
31652
  <span style="color: ${colors.text}; font-size: 13px;">${dd.device.label}</span>
@@ -31648,9 +31655,9 @@ function renderModal2(container, state5, modalId) {
31648
31655
  </span>
31649
31656
  </div>
31650
31657
  `).join("");
31651
- const statsHTML = state5.deviceData.map((dd) => `
31658
+ const statsHTML = state6.deviceData.map((dd) => `
31652
31659
  <div style="
31653
- padding: 12px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31660
+ padding: 12px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31654
31661
  border-radius: 10px; border-left: 4px solid ${dd.color};
31655
31662
  min-width: 150px;
31656
31663
  ">
@@ -31701,7 +31708,7 @@ function renderModal2(container, state5, modalId) {
31701
31708
  min-height: 20px;
31702
31709
  ">
31703
31710
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
31704
- \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state5.devices.length} sensores
31711
+ \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state6.devices.length} sensores
31705
31712
  </h2>
31706
31713
  <div style="display: flex; gap: 4px; align-items: center;">
31707
31714
  <!-- Theme Toggle -->
@@ -31709,7 +31716,7 @@ function renderModal2(container, state5, modalId) {
31709
31716
  background: none; border: none; font-size: 16px; cursor: pointer;
31710
31717
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
31711
31718
  transition: background-color 0.2s;
31712
- ">${state5.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
31719
+ ">${state6.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
31713
31720
  <!-- Maximize Button -->
31714
31721
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
31715
31722
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -31732,7 +31739,7 @@ function renderModal2(container, state5, modalId) {
31732
31739
  <div style="
31733
31740
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
31734
31741
  margin-bottom: 16px; padding: 16px;
31735
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
31742
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
31736
31743
  border-radius: 6px; border: 1px solid ${colors.border};
31737
31744
  ">
31738
31745
  <!-- Granularity Select -->
@@ -31745,8 +31752,8 @@ function renderModal2(container, state5, modalId) {
31745
31752
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
31746
31753
  cursor: pointer; min-width: 130px;
31747
31754
  ">
31748
- <option value="hour" ${state5.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
31749
- <option value="day" ${state5.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
31755
+ <option value="hour" ${state6.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
31756
+ <option value="day" ${state6.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
31750
31757
  </select>
31751
31758
  </div>
31752
31759
  <!-- Day Period Filter (Multiselect) -->
@@ -31760,7 +31767,7 @@ function renderModal2(container, state5, modalId) {
31760
31767
  cursor: pointer; min-width: 180px; text-align: left;
31761
31768
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
31762
31769
  ">
31763
- <span>${getSelectedPeriodsLabel(state5.selectedPeriods)}</span>
31770
+ <span>${getSelectedPeriodsLabel(state6.selectedPeriods)}</span>
31764
31771
  <span style="font-size: 10px;">\u25BC</span>
31765
31772
  </button>
31766
31773
  <div id="${modalId}-period-dropdown" style="
@@ -31773,12 +31780,12 @@ function renderModal2(container, state5, modalId) {
31773
31780
  <label style="
31774
31781
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
31775
31782
  cursor: pointer; font-size: 13px; color: ${colors.text};
31776
- " onmouseover="this.style.background='${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
31783
+ " onmouseover="this.style.background='${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
31777
31784
  onmouseout="this.style.background='transparent'">
31778
31785
  <input type="checkbox"
31779
31786
  name="${modalId}-period"
31780
31787
  value="${period.id}"
31781
- ${state5.selectedPeriods.includes(period.id) ? "checked" : ""}
31788
+ ${state6.selectedPeriods.includes(period.id) ? "checked" : ""}
31782
31789
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
31783
31790
  ${period.label}
31784
31791
  </label>
@@ -31786,13 +31793,13 @@ function renderModal2(container, state5, modalId) {
31786
31793
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
31787
31794
  <button id="${modalId}-period-select-all" type="button" style="
31788
31795
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
31789
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31796
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31790
31797
  border: none; border-radius: 4px; cursor: pointer;
31791
31798
  font-size: 12px; color: ${colors.text};
31792
31799
  ">Selecionar Todos</button>
31793
31800
  <button id="${modalId}-period-clear" type="button" style="
31794
31801
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
31795
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31802
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31796
31803
  border: none; border-radius: 4px; cursor: pointer;
31797
31804
  font-size: 12px; color: ${colors.text};
31798
31805
  ">Limpar Sele\xE7\xE3o</button>
@@ -31817,8 +31824,8 @@ function renderModal2(container, state5, modalId) {
31817
31824
  font-size: 14px; font-weight: 500; height: 38px;
31818
31825
  display: flex; align-items: center; gap: 8px;
31819
31826
  font-family: 'Roboto', Arial, sans-serif;
31820
- " ${state5.isLoading ? "disabled" : ""}>
31821
- ${state5.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31827
+ " ${state6.isLoading ? "disabled" : ""}>
31828
+ ${state6.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31822
31829
  </button>
31823
31830
  </div>
31824
31831
 
@@ -31834,14 +31841,14 @@ function renderModal2(container, state5, modalId) {
31834
31841
  <div style="margin-bottom: 24px;">
31835
31842
  <div id="${modalId}-chart" style="
31836
31843
  height: 380px;
31837
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
31844
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
31838
31845
  border-radius: 14px; display: flex; justify-content: center; align-items: center;
31839
31846
  border: 1px solid ${colors.border}; position: relative;
31840
31847
  ">
31841
- ${state5.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
31848
+ ${state6.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
31842
31849
  <div style="animation: spin 1s linear infinite; font-size: 36px; margin-bottom: 12px;">\u21BB</div>
31843
- <div style="font-size: 15px;">Carregando dados de ${state5.devices.length} sensores...</div>
31844
- </div>` : state5.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
31850
+ <div style="font-size: 15px;">Carregando dados de ${state6.devices.length} sensores...</div>
31851
+ </div>` : state6.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
31845
31852
  <div style="font-size: 48px; margin-bottom: 12px;">\u{1F4ED}</div>
31846
31853
  <div style="font-size: 16px;">Sem dados para o per\xEDodo selecionado</div>
31847
31854
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -31859,12 +31866,12 @@ function renderModal2(container, state5, modalId) {
31859
31866
  <!-- Actions -->
31860
31867
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
31861
31868
  <button id="${modalId}-export" style="
31862
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
31869
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
31863
31870
  color: ${colors.text}; border: 1px solid ${colors.border};
31864
31871
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
31865
31872
  font-size: 14px; display: flex; align-items: center; gap: 8px;
31866
31873
  font-family: 'Roboto', Arial, sans-serif;
31867
- " ${state5.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
31874
+ " ${state6.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
31868
31875
  \u{1F4E5} Exportar CSV
31869
31876
  </button>
31870
31877
  <button id="${modalId}-close-btn" style="
@@ -31899,15 +31906,15 @@ function renderModal2(container, state5, modalId) {
31899
31906
  </style>
31900
31907
  `;
31901
31908
  }
31902
- function drawComparisonChart(modalId, state5) {
31909
+ function drawComparisonChart(modalId, state6) {
31903
31910
  const chartContainer = document.getElementById(`${modalId}-chart`);
31904
31911
  const canvas = document.getElementById(`${modalId}-canvas`);
31905
31912
  if (!chartContainer || !canvas) return;
31906
- const hasData = state5.deviceData.some((dd) => dd.data.length > 0);
31913
+ const hasData = state6.deviceData.some((dd) => dd.data.length > 0);
31907
31914
  if (!hasData) return;
31908
31915
  const ctx = canvas.getContext("2d");
31909
31916
  if (!ctx) return;
31910
- const colors = getThemeColors(state5.theme);
31917
+ const colors = getThemeColors(state6.theme);
31911
31918
  const width = chartContainer.clientWidth - 2;
31912
31919
  const height = 380;
31913
31920
  canvas.width = width;
@@ -31918,19 +31925,19 @@ function drawComparisonChart(modalId, state5) {
31918
31925
  const paddingBottom = 55;
31919
31926
  ctx.clearRect(0, 0, width, height);
31920
31927
  const processedData = [];
31921
- state5.deviceData.forEach((dd) => {
31928
+ state6.deviceData.forEach((dd) => {
31922
31929
  if (dd.data.length === 0) return;
31923
- const filteredData = filterByDayPeriods(dd.data, state5.selectedPeriods);
31930
+ const filteredData = filterByDayPeriods(dd.data, state6.selectedPeriods);
31924
31931
  if (filteredData.length === 0) return;
31925
31932
  let points;
31926
- if (state5.granularity === "hour") {
31933
+ if (state6.granularity === "hour") {
31927
31934
  const interpolated = interpolateTemperature(filteredData, {
31928
31935
  intervalMinutes: 30,
31929
- startTs: state5.startTs,
31930
- endTs: state5.endTs,
31931
- clampRange: state5.clampRange
31936
+ startTs: state6.startTs,
31937
+ endTs: state6.endTs,
31938
+ clampRange: state6.clampRange
31932
31939
  });
31933
- const filteredInterpolated = filterByDayPeriods(interpolated, state5.selectedPeriods);
31940
+ const filteredInterpolated = filterByDayPeriods(interpolated, state6.selectedPeriods);
31934
31941
  points = filteredInterpolated.map((item) => ({
31935
31942
  x: item.ts,
31936
31943
  y: Number(item.value),
@@ -31940,7 +31947,7 @@ function drawComparisonChart(modalId, state5) {
31940
31947
  deviceColor: dd.color
31941
31948
  }));
31942
31949
  } else {
31943
- const daily = aggregateByDay(filteredData, state5.clampRange);
31950
+ const daily = aggregateByDay(filteredData, state6.clampRange);
31944
31951
  points = daily.map((item) => ({
31945
31952
  x: item.dateTs,
31946
31953
  y: item.avg,
@@ -31961,7 +31968,7 @@ function drawComparisonChart(modalId, state5) {
31961
31968
  ctx.fillText("Nenhum dado para os per\xEDodos selecionados", width / 2, height / 2);
31962
31969
  return;
31963
31970
  }
31964
- const isPeriodsFiltered = state5.selectedPeriods.length < 4 && state5.selectedPeriods.length > 0;
31971
+ const isPeriodsFiltered = state6.selectedPeriods.length < 4 && state6.selectedPeriods.length > 0;
31965
31972
  let dataMinY = Infinity;
31966
31973
  let dataMaxY = -Infinity;
31967
31974
  processedData.forEach(({ points }) => {
@@ -31971,7 +31978,7 @@ function drawComparisonChart(modalId, state5) {
31971
31978
  });
31972
31979
  });
31973
31980
  const rangeMap = /* @__PURE__ */ new Map();
31974
- state5.deviceData.forEach((dd, index) => {
31981
+ state6.deviceData.forEach((dd, index) => {
31975
31982
  const device = dd.device;
31976
31983
  const min = device.temperatureMin;
31977
31984
  const max = device.temperatureMax;
@@ -31990,10 +31997,10 @@ function drawComparisonChart(modalId, state5) {
31990
31997
  }
31991
31998
  }
31992
31999
  });
31993
- if (rangeMap.size === 0 && state5.temperatureMin !== null && state5.temperatureMax !== null) {
32000
+ if (rangeMap.size === 0 && state6.temperatureMin !== null && state6.temperatureMax !== null) {
31994
32001
  rangeMap.set("global", {
31995
- min: state5.temperatureMin,
31996
- max: state5.temperatureMax,
32002
+ min: state6.temperatureMin,
32003
+ max: state6.temperatureMax,
31997
32004
  customerName: "Global",
31998
32005
  color: colors.success,
31999
32006
  deviceLabels: []
@@ -32114,10 +32121,10 @@ function drawComparisonChart(modalId, state5) {
32114
32121
  const point = xAxisPoints[i];
32115
32122
  const date = new Date(point.x);
32116
32123
  let label;
32117
- if (state5.granularity === "hour") {
32118
- label = date.toLocaleTimeString(state5.locale, { hour: "2-digit", minute: "2-digit" });
32124
+ if (state6.granularity === "hour") {
32125
+ label = date.toLocaleTimeString(state6.locale, { hour: "2-digit", minute: "2-digit" });
32119
32126
  } else {
32120
- label = date.toLocaleDateString(state5.locale, { day: "2-digit", month: "2-digit" });
32127
+ label = date.toLocaleDateString(state6.locale, { day: "2-digit", month: "2-digit" });
32121
32128
  }
32122
32129
  ctx.strokeStyle = colors.chartGrid;
32123
32130
  ctx.lineWidth = 1;
@@ -32136,16 +32143,16 @@ function drawComparisonChart(modalId, state5) {
32136
32143
  ctx.lineTo(width - paddingRight, height - paddingBottom);
32137
32144
  ctx.stroke();
32138
32145
  const allChartPoints = processedData.flatMap((pd) => pd.points);
32139
- setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state5, colors);
32146
+ setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state6, colors);
32140
32147
  }
32141
- function setupComparisonChartTooltip(canvas, container, chartData, state5, colors) {
32148
+ function setupComparisonChartTooltip(canvas, container, chartData, state6, colors) {
32142
32149
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
32143
32150
  if (existingTooltip) existingTooltip.remove();
32144
32151
  const tooltip = document.createElement("div");
32145
32152
  tooltip.className = "myio-chart-tooltip";
32146
32153
  tooltip.style.cssText = `
32147
32154
  position: absolute;
32148
- background: ${state5.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
32155
+ background: ${state6.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
32149
32156
  border: 1px solid ${colors.border};
32150
32157
  border-radius: 8px;
32151
32158
  padding: 10px 14px;
@@ -32182,17 +32189,17 @@ function setupComparisonChartTooltip(canvas, container, chartData, state5, color
32182
32189
  if (point) {
32183
32190
  const date = new Date(point.x);
32184
32191
  let dateStr;
32185
- if (state5.granularity === "hour") {
32186
- dateStr = date.toLocaleDateString(state5.locale, {
32192
+ if (state6.granularity === "hour") {
32193
+ dateStr = date.toLocaleDateString(state6.locale, {
32187
32194
  day: "2-digit",
32188
32195
  month: "2-digit",
32189
32196
  year: "numeric"
32190
- }) + " " + date.toLocaleTimeString(state5.locale, {
32197
+ }) + " " + date.toLocaleTimeString(state6.locale, {
32191
32198
  hour: "2-digit",
32192
32199
  minute: "2-digit"
32193
32200
  });
32194
32201
  } else {
32195
- dateStr = date.toLocaleDateString(state5.locale, {
32202
+ dateStr = date.toLocaleDateString(state6.locale, {
32196
32203
  day: "2-digit",
32197
32204
  month: "2-digit",
32198
32205
  year: "numeric"
@@ -32234,7 +32241,7 @@ function setupComparisonChartTooltip(canvas, container, chartData, state5, color
32234
32241
  canvas.style.cursor = "default";
32235
32242
  });
32236
32243
  }
32237
- async function setupEventListeners2(container, state5, modalId, onClose) {
32244
+ async function setupEventListeners2(container, state6, modalId, onClose) {
32238
32245
  const closeModal = () => {
32239
32246
  container.remove();
32240
32247
  onClose?.();
@@ -32245,19 +32252,19 @@ async function setupEventListeners2(container, state5, modalId, onClose) {
32245
32252
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
32246
32253
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
32247
32254
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
32248
- if (dateRangeInput && !state5.dateRangePicker) {
32255
+ if (dateRangeInput && !state6.dateRangePicker) {
32249
32256
  try {
32250
- state5.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
32251
- presetStart: new Date(state5.startTs).toISOString(),
32252
- presetEnd: new Date(state5.endTs).toISOString(),
32257
+ state6.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
32258
+ presetStart: new Date(state6.startTs).toISOString(),
32259
+ presetEnd: new Date(state6.endTs).toISOString(),
32253
32260
  includeTime: true,
32254
32261
  timePrecision: "minute",
32255
32262
  maxRangeDays: 90,
32256
- locale: state5.locale,
32263
+ locale: state6.locale,
32257
32264
  parentEl: container.querySelector(".myio-temp-comparison-content"),
32258
32265
  onApply: (result) => {
32259
- state5.startTs = new Date(result.startISO).getTime();
32260
- state5.endTs = new Date(result.endISO).getTime();
32266
+ state6.startTs = new Date(result.startISO).getTime();
32267
+ state6.endTs = new Date(result.endISO).getTime();
32261
32268
  console.log("[TemperatureComparisonModal] Date range applied:", result);
32262
32269
  }
32263
32270
  });
@@ -32266,23 +32273,23 @@ async function setupEventListeners2(container, state5, modalId, onClose) {
32266
32273
  }
32267
32274
  }
32268
32275
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
32269
- state5.theme = state5.theme === "dark" ? "light" : "dark";
32270
- localStorage.setItem("myio-temp-comparison-theme", state5.theme);
32271
- state5.dateRangePicker = null;
32272
- renderModal2(container, state5, modalId);
32273
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32274
- drawComparisonChart(modalId, state5);
32275
- }
32276
- await setupEventListeners2(container, state5, modalId, onClose);
32276
+ state6.theme = state6.theme === "dark" ? "light" : "dark";
32277
+ localStorage.setItem("myio-temp-comparison-theme", state6.theme);
32278
+ state6.dateRangePicker = null;
32279
+ renderModal2(container, state6, modalId);
32280
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32281
+ drawComparisonChart(modalId, state6);
32282
+ }
32283
+ await setupEventListeners2(container, state6, modalId, onClose);
32277
32284
  });
32278
32285
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
32279
32286
  container.__isMaximized = !container.__isMaximized;
32280
- state5.dateRangePicker = null;
32281
- renderModal2(container, state5, modalId);
32282
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32283
- drawComparisonChart(modalId, state5);
32287
+ state6.dateRangePicker = null;
32288
+ renderModal2(container, state6, modalId);
32289
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32290
+ drawComparisonChart(modalId, state6);
32284
32291
  }
32285
- await setupEventListeners2(container, state5, modalId, onClose);
32292
+ await setupEventListeners2(container, state6, modalId, onClose);
32286
32293
  });
32287
32294
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
32288
32295
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -32301,13 +32308,13 @@ async function setupEventListeners2(container, state5, modalId, onClose) {
32301
32308
  periodCheckboxes.forEach((checkbox) => {
32302
32309
  checkbox.addEventListener("change", () => {
32303
32310
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
32304
- state5.selectedPeriods = checked;
32311
+ state6.selectedPeriods = checked;
32305
32312
  const btnLabel = periodBtn?.querySelector("span:first-child");
32306
32313
  if (btnLabel) {
32307
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32314
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32308
32315
  }
32309
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32310
- drawComparisonChart(modalId, state5);
32316
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32317
+ drawComparisonChart(modalId, state6);
32311
32318
  }
32312
32319
  });
32313
32320
  });
@@ -32315,77 +32322,77 @@ async function setupEventListeners2(container, state5, modalId, onClose) {
32315
32322
  periodCheckboxes.forEach((cb) => {
32316
32323
  cb.checked = true;
32317
32324
  });
32318
- state5.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
32325
+ state6.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
32319
32326
  const btnLabel = periodBtn?.querySelector("span:first-child");
32320
32327
  if (btnLabel) {
32321
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32328
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32322
32329
  }
32323
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32324
- drawComparisonChart(modalId, state5);
32330
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32331
+ drawComparisonChart(modalId, state6);
32325
32332
  }
32326
32333
  });
32327
32334
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
32328
32335
  periodCheckboxes.forEach((cb) => {
32329
32336
  cb.checked = false;
32330
32337
  });
32331
- state5.selectedPeriods = [];
32338
+ state6.selectedPeriods = [];
32332
32339
  const btnLabel = periodBtn?.querySelector("span:first-child");
32333
32340
  if (btnLabel) {
32334
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32341
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32335
32342
  }
32336
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32337
- drawComparisonChart(modalId, state5);
32343
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32344
+ drawComparisonChart(modalId, state6);
32338
32345
  }
32339
32346
  });
32340
32347
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
32341
- state5.granularity = e.target.value;
32342
- localStorage.setItem("myio-temp-comparison-granularity", state5.granularity);
32343
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32344
- drawComparisonChart(modalId, state5);
32348
+ state6.granularity = e.target.value;
32349
+ localStorage.setItem("myio-temp-comparison-granularity", state6.granularity);
32350
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32351
+ drawComparisonChart(modalId, state6);
32345
32352
  }
32346
32353
  });
32347
32354
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
32348
- if (state5.startTs >= state5.endTs) {
32355
+ if (state6.startTs >= state6.endTs) {
32349
32356
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
32350
32357
  return;
32351
32358
  }
32352
- state5.isLoading = true;
32353
- state5.dateRangePicker = null;
32354
- renderModal2(container, state5, modalId);
32355
- await fetchAllDevicesData(state5);
32356
- renderModal2(container, state5, modalId);
32357
- drawComparisonChart(modalId, state5);
32358
- await setupEventListeners2(container, state5, modalId, onClose);
32359
+ state6.isLoading = true;
32360
+ state6.dateRangePicker = null;
32361
+ renderModal2(container, state6, modalId);
32362
+ await fetchAllDevicesData(state6);
32363
+ renderModal2(container, state6, modalId);
32364
+ drawComparisonChart(modalId, state6);
32365
+ await setupEventListeners2(container, state6, modalId, onClose);
32359
32366
  });
32360
32367
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
32361
- if (state5.deviceData.every((dd) => dd.data.length === 0)) return;
32362
- exportComparisonCSV(state5);
32368
+ if (state6.deviceData.every((dd) => dd.data.length === 0)) return;
32369
+ exportComparisonCSV(state6);
32363
32370
  });
32364
32371
  }
32365
- function exportComparisonCSV(state5) {
32366
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
32367
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
32372
+ function exportComparisonCSV(state6) {
32373
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
32374
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
32368
32375
  const BOM = "\uFEFF";
32369
32376
  let csvContent = BOM;
32370
32377
  csvContent += `Compara\xE7\xE3o de Temperatura
32371
32378
  `;
32372
32379
  csvContent += `Per\xEDodo: ${startDateStr} at\xE9 ${endDateStr}
32373
32380
  `;
32374
- csvContent += `Sensores: ${state5.devices.map((d) => d.label).join(", ")}
32381
+ csvContent += `Sensores: ${state6.devices.map((d) => d.label).join(", ")}
32375
32382
  `;
32376
32383
  csvContent += "\n";
32377
32384
  csvContent += "Estat\xEDsticas por Sensor:\n";
32378
32385
  csvContent += "Sensor,M\xE9dia (\xB0C),Min (\xB0C),Max (\xB0C),Leituras\n";
32379
- state5.deviceData.forEach((dd) => {
32386
+ state6.deviceData.forEach((dd) => {
32380
32387
  csvContent += `"${dd.device.label}",${dd.stats.avg.toFixed(2)},${dd.stats.min.toFixed(2)},${dd.stats.max.toFixed(2)},${dd.stats.count}
32381
32388
  `;
32382
32389
  });
32383
32390
  csvContent += "\n";
32384
32391
  csvContent += "Dados Detalhados:\n";
32385
32392
  csvContent += "Data/Hora,Sensor,Temperatura (\xB0C)\n";
32386
- state5.deviceData.forEach((dd) => {
32393
+ state6.deviceData.forEach((dd) => {
32387
32394
  dd.data.forEach((item) => {
32388
- const date = new Date(item.ts).toLocaleString(state5.locale);
32395
+ const date = new Date(item.ts).toLocaleString(state6.locale);
32389
32396
  const temp = Number(item.value).toFixed(2);
32390
32397
  csvContent += `"${date}","${dd.device.label}",${temp}
32391
32398
  `;
@@ -32493,10 +32500,10 @@ async function saveCustomerAttributes(customerId, token, minTemperature, maxTemp
32493
32500
  throw new Error(`Failed to save attributes: ${response.status}`);
32494
32501
  }
32495
32502
  }
32496
- function renderModal3(container, state5, modalId, onClose, onSave) {
32497
- const colors = getColors(state5.theme);
32498
- const minValue = state5.minTemperature !== null ? state5.minTemperature : "";
32499
- const maxValue = state5.maxTemperature !== null ? state5.maxTemperature : "";
32503
+ function renderModal3(container, state6, modalId, onClose, onSave) {
32504
+ const colors = getColors(state6.theme);
32505
+ const minValue = state6.minTemperature !== null ? state6.minTemperature : "";
32506
+ const maxValue = state6.maxTemperature !== null ? state6.maxTemperature : "";
32500
32507
  container.innerHTML = `
32501
32508
  <style>
32502
32509
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@@ -32761,23 +32768,23 @@ function renderModal3(container, state5, modalId, onClose, onSave) {
32761
32768
  </div>
32762
32769
 
32763
32770
  <div class="modal-body">
32764
- ${state5.isLoading ? `
32771
+ ${state6.isLoading ? `
32765
32772
  <div class="loading-overlay">
32766
32773
  <div class="loading-spinner"></div>
32767
32774
  <div>Carregando configura\xE7\xF5es...</div>
32768
32775
  </div>
32769
32776
  ` : `
32770
- ${state5.error ? `
32771
- <div class="message message-error">${state5.error}</div>
32777
+ ${state6.error ? `
32778
+ <div class="message message-error">${state6.error}</div>
32772
32779
  ` : ""}
32773
32780
 
32774
- ${state5.successMessage ? `
32775
- <div class="message message-success">${state5.successMessage}</div>
32781
+ ${state6.successMessage ? `
32782
+ <div class="message message-success">${state6.successMessage}</div>
32776
32783
  ` : ""}
32777
32784
 
32778
32785
  <div class="customer-info">
32779
32786
  <div class="customer-label">Shopping / Cliente</div>
32780
- <div class="customer-name">${state5.customerName || "N\xE3o identificado"}</div>
32787
+ <div class="customer-name">${state6.customerName || "N\xE3o identificado"}</div>
32781
32788
  </div>
32782
32789
 
32783
32790
  <div class="form-group">
@@ -32821,11 +32828,11 @@ function renderModal3(container, state5, modalId, onClose, onSave) {
32821
32828
  `}
32822
32829
  </div>
32823
32830
 
32824
- ${!state5.isLoading ? `
32831
+ ${!state6.isLoading ? `
32825
32832
  <div class="modal-footer">
32826
32833
  <button class="btn btn-secondary" id="${modalId}-cancel">Cancelar</button>
32827
- <button class="btn btn-primary" id="${modalId}-save" ${state5.isSaving ? "disabled" : ""}>
32828
- ${state5.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
32834
+ <button class="btn btn-primary" id="${modalId}-save" ${state6.isSaving ? "disabled" : ""}>
32835
+ ${state6.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
32829
32836
  </button>
32830
32837
  </div>
32831
32838
  ` : ""}
@@ -32862,18 +32869,18 @@ function renderModal3(container, state5, modalId, onClose, onSave) {
32862
32869
  const min = parseFloat(minInput.value);
32863
32870
  const max = parseFloat(maxInput.value);
32864
32871
  if (isNaN(min) || isNaN(max)) {
32865
- state5.error = "Por favor, preencha ambos os valores.";
32866
- renderModal3(container, state5, modalId, onClose, onSave);
32872
+ state6.error = "Por favor, preencha ambos os valores.";
32873
+ renderModal3(container, state6, modalId, onClose, onSave);
32867
32874
  return;
32868
32875
  }
32869
32876
  if (min >= max) {
32870
- state5.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
32871
- renderModal3(container, state5, modalId, onClose, onSave);
32877
+ state6.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
32878
+ renderModal3(container, state6, modalId, onClose, onSave);
32872
32879
  return;
32873
32880
  }
32874
32881
  if (min < 0 || max > 50) {
32875
- state5.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
32876
- renderModal3(container, state5, modalId, onClose, onSave);
32882
+ state6.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
32883
+ renderModal3(container, state6, modalId, onClose, onSave);
32877
32884
  return;
32878
32885
  }
32879
32886
  await onSave(min, max);
@@ -32881,7 +32888,7 @@ function renderModal3(container, state5, modalId, onClose, onSave) {
32881
32888
  }
32882
32889
  function openTemperatureSettingsModal(params) {
32883
32890
  const modalId = `myio-temp-settings-${Date.now()}`;
32884
- const state5 = {
32891
+ const state6 = {
32885
32892
  customerId: params.customerId,
32886
32893
  customerName: params.customerName || "",
32887
32894
  token: params.token,
@@ -32901,37 +32908,37 @@ function openTemperatureSettingsModal(params) {
32901
32908
  params.onClose?.();
32902
32909
  };
32903
32910
  const handleSave = async (min, max) => {
32904
- state5.isSaving = true;
32905
- state5.error = null;
32906
- state5.successMessage = null;
32907
- renderModal3(container, state5, modalId, destroy, handleSave);
32911
+ state6.isSaving = true;
32912
+ state6.error = null;
32913
+ state6.successMessage = null;
32914
+ renderModal3(container, state6, modalId, destroy, handleSave);
32908
32915
  try {
32909
- await saveCustomerAttributes(state5.customerId, state5.token, min, max, params.onError);
32910
- state5.minTemperature = min;
32911
- state5.maxTemperature = max;
32912
- state5.isSaving = false;
32913
- state5.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
32914
- renderModal3(container, state5, modalId, destroy, handleSave);
32916
+ await saveCustomerAttributes(state6.customerId, state6.token, min, max, params.onError);
32917
+ state6.minTemperature = min;
32918
+ state6.maxTemperature = max;
32919
+ state6.isSaving = false;
32920
+ state6.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
32921
+ renderModal3(container, state6, modalId, destroy, handleSave);
32915
32922
  params.onSave?.({ minTemperature: min, maxTemperature: max });
32916
32923
  setTimeout(() => {
32917
32924
  destroy();
32918
32925
  }, 1500);
32919
32926
  } catch (error) {
32920
- state5.isSaving = false;
32921
- state5.error = `Erro ao salvar: ${error.message}`;
32922
- renderModal3(container, state5, modalId, destroy, handleSave);
32927
+ state6.isSaving = false;
32928
+ state6.error = `Erro ao salvar: ${error.message}`;
32929
+ renderModal3(container, state6, modalId, destroy, handleSave);
32923
32930
  }
32924
32931
  };
32925
- renderModal3(container, state5, modalId, destroy, handleSave);
32926
- fetchCustomerAttributes(state5.customerId, state5.token, params.onError).then(({ minTemperature, maxTemperature }) => {
32927
- state5.minTemperature = minTemperature;
32928
- state5.maxTemperature = maxTemperature;
32929
- state5.isLoading = false;
32930
- renderModal3(container, state5, modalId, destroy, handleSave);
32932
+ renderModal3(container, state6, modalId, destroy, handleSave);
32933
+ fetchCustomerAttributes(state6.customerId, state6.token, params.onError).then(({ minTemperature, maxTemperature }) => {
32934
+ state6.minTemperature = minTemperature;
32935
+ state6.maxTemperature = maxTemperature;
32936
+ state6.isLoading = false;
32937
+ renderModal3(container, state6, modalId, destroy, handleSave);
32931
32938
  }).catch((error) => {
32932
- state5.isLoading = false;
32933
- state5.error = `Erro ao carregar: ${error.message}`;
32934
- renderModal3(container, state5, modalId, destroy, handleSave);
32939
+ state6.isLoading = false;
32940
+ state6.error = `Erro ao carregar: ${error.message}`;
32941
+ renderModal3(container, state6, modalId, destroy, handleSave);
32935
32942
  });
32936
32943
  return { destroy };
32937
32944
  }
@@ -34201,7 +34208,7 @@ var EnergySummaryTooltip = {
34201
34208
  * RFC-0105 Enhancement: Now fetches device lists from MyIOOrchestratorData
34202
34209
  * to populate device lists for status popup display
34203
34210
  */
34204
- buildSummaryFromState(state5, receivedData, domain = "energy") {
34211
+ buildSummaryFromState(state6, receivedData, domain = "energy") {
34205
34212
  const summary = {
34206
34213
  totalDevices: 0,
34207
34214
  totalConsumption: 0,
@@ -34224,22 +34231,22 @@ var EnergySummaryTooltip = {
34224
34231
  },
34225
34232
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
34226
34233
  };
34227
- if (!state5) return summary;
34234
+ if (!state6) return summary;
34228
34235
  const entrada = {
34229
34236
  id: "entrada",
34230
34237
  name: "Entrada",
34231
34238
  icon: CATEGORY_ICONS.entrada,
34232
- deviceCount: state5.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
34233
- consumption: state5.entrada?.total || 0,
34239
+ deviceCount: state6.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
34240
+ consumption: state6.entrada?.total || 0,
34234
34241
  percentage: 100
34235
34242
  };
34236
34243
  const lojas = {
34237
34244
  id: "lojas",
34238
34245
  name: "Lojas",
34239
34246
  icon: CATEGORY_ICONS.lojas,
34240
- deviceCount: state5.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
34241
- consumption: state5.consumidores?.lojas?.total || 0,
34242
- percentage: state5.consumidores?.lojas?.perc || 0
34247
+ deviceCount: state6.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
34248
+ consumption: state6.consumidores?.lojas?.total || 0,
34249
+ percentage: state6.consumidores?.lojas?.perc || 0
34243
34250
  };
34244
34251
  const climatizacaoData = receivedData?.climatizacao || {};
34245
34252
  const elevadoresData = receivedData?.elevadores || {};
@@ -34250,9 +34257,9 @@ var EnergySummaryTooltip = {
34250
34257
  id: "climatizacao",
34251
34258
  name: "Climatizacao",
34252
34259
  icon: CATEGORY_ICONS.climatizacao,
34253
- deviceCount: climatizacaoData.count || state5.consumidores?.climatizacao?.devices?.length || 0,
34254
- consumption: state5.consumidores?.climatizacao?.total || 0,
34255
- percentage: state5.consumidores?.climatizacao?.perc || 0
34260
+ deviceCount: climatizacaoData.count || state6.consumidores?.climatizacao?.devices?.length || 0,
34261
+ consumption: state6.consumidores?.climatizacao?.total || 0,
34262
+ percentage: state6.consumidores?.climatizacao?.perc || 0
34256
34263
  };
34257
34264
  if (climatizacaoData.subcategories) {
34258
34265
  climatizacao.children = [];
@@ -34303,40 +34310,40 @@ var EnergySummaryTooltip = {
34303
34310
  id: "elevadores",
34304
34311
  name: "Elevadores",
34305
34312
  icon: CATEGORY_ICONS.elevadores,
34306
- deviceCount: elevadoresData.count || state5.consumidores?.elevadores?.devices?.length || 0,
34307
- consumption: state5.consumidores?.elevadores?.total || 0,
34308
- percentage: state5.consumidores?.elevadores?.perc || 0
34313
+ deviceCount: elevadoresData.count || state6.consumidores?.elevadores?.devices?.length || 0,
34314
+ consumption: state6.consumidores?.elevadores?.total || 0,
34315
+ percentage: state6.consumidores?.elevadores?.perc || 0
34309
34316
  });
34310
34317
  areaComumChildren.push({
34311
34318
  id: "escadasRolantes",
34312
34319
  name: "Esc. Rolantes",
34313
34320
  icon: CATEGORY_ICONS.escadas,
34314
- deviceCount: escadasData.count || state5.consumidores?.escadasRolantes?.devices?.length || 0,
34315
- consumption: state5.consumidores?.escadasRolantes?.total || 0,
34316
- percentage: state5.consumidores?.escadasRolantes?.perc || 0
34321
+ deviceCount: escadasData.count || state6.consumidores?.escadasRolantes?.devices?.length || 0,
34322
+ consumption: state6.consumidores?.escadasRolantes?.total || 0,
34323
+ percentage: state6.consumidores?.escadasRolantes?.perc || 0
34317
34324
  });
34318
34325
  areaComumChildren.push({
34319
34326
  id: "outros",
34320
34327
  name: "Outros",
34321
34328
  icon: CATEGORY_ICONS.outros,
34322
- deviceCount: outrosData.count || state5.consumidores?.outros?.devices?.length || 0,
34323
- consumption: state5.consumidores?.outros?.total || 0,
34324
- percentage: state5.consumidores?.outros?.perc || 0
34329
+ deviceCount: outrosData.count || state6.consumidores?.outros?.devices?.length || 0,
34330
+ consumption: state6.consumidores?.outros?.total || 0,
34331
+ percentage: state6.consumidores?.outros?.perc || 0
34325
34332
  });
34326
34333
  const areaComumDeviceCount = areaComumChildren.reduce((sum, c) => sum + c.deviceCount, 0);
34327
- const areaComumConsumption = state5.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
34334
+ const areaComumConsumption = state6.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
34328
34335
  const areaComum = {
34329
34336
  id: "areaComum",
34330
34337
  name: "Area Comum",
34331
34338
  icon: CATEGORY_ICONS.areaComum,
34332
34339
  deviceCount: areaComumDeviceCount,
34333
34340
  consumption: areaComumConsumption,
34334
- percentage: state5.consumidores?.areaComum?.perc || 0,
34341
+ percentage: state6.consumidores?.areaComum?.perc || 0,
34335
34342
  children: areaComumChildren
34336
34343
  };
34337
34344
  summary.byCategory = [entrada, lojas, areaComum];
34338
34345
  summary.totalDevices = entrada.deviceCount + lojas.deviceCount + areaComumDeviceCount;
34339
- summary.totalConsumption = state5.grandTotal || entrada.consumption;
34346
+ summary.totalConsumption = state6.grandTotal || entrada.consumption;
34340
34347
  const widgetAggregation = receivedData?.deviceStatusAggregation;
34341
34348
  if (widgetAggregation && widgetAggregation.hasData) {
34342
34349
  summary.byStatus = {
@@ -35624,7 +35631,7 @@ var WaterSummaryTooltip = {
35624
35631
  * RFC-0105 Enhancement: Now fetches device lists from MyIOOrchestratorData
35625
35632
  * to populate device lists for status popup display
35626
35633
  */
35627
- buildSummaryFromState(state5, receivedData, includeBathrooms = false, domain = "water") {
35634
+ buildSummaryFromState(state6, receivedData, includeBathrooms = false, domain = "water") {
35628
35635
  const summary = {
35629
35636
  totalDevices: 0,
35630
35637
  totalConsumption: 0,
@@ -35648,22 +35655,22 @@ var WaterSummaryTooltip = {
35648
35655
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
35649
35656
  includeBathrooms
35650
35657
  };
35651
- if (!state5) return summary;
35658
+ if (!state6) return summary;
35652
35659
  const entrada = {
35653
35660
  id: "entrada",
35654
35661
  name: "Entrada",
35655
35662
  icon: WATER_CATEGORY_ICONS.entrada,
35656
- deviceCount: state5.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
35657
- consumption: state5.entrada?.total || 0,
35663
+ deviceCount: state6.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
35664
+ consumption: state6.entrada?.total || 0,
35658
35665
  percentage: 100
35659
35666
  };
35660
35667
  const lojas = {
35661
35668
  id: "lojas",
35662
35669
  name: "Lojas",
35663
35670
  icon: WATER_CATEGORY_ICONS.lojas,
35664
- deviceCount: state5.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
35665
- consumption: state5.lojas?.total || 0,
35666
- percentage: state5.lojas?.perc || 0
35671
+ deviceCount: state6.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
35672
+ consumption: state6.lojas?.total || 0,
35673
+ percentage: state6.lojas?.perc || 0
35667
35674
  };
35668
35675
  summary.byCategory = [entrada, lojas];
35669
35676
  if (includeBathrooms) {
@@ -35671,9 +35678,9 @@ var WaterSummaryTooltip = {
35671
35678
  id: "banheiros",
35672
35679
  name: "Banheiros",
35673
35680
  icon: WATER_CATEGORY_ICONS.banheiros,
35674
- deviceCount: state5.banheiros?.devices?.length || (receivedData?.banheiros_total?.device_count || 0),
35675
- consumption: state5.banheiros?.total || 0,
35676
- percentage: state5.banheiros?.perc || 0
35681
+ deviceCount: state6.banheiros?.devices?.length || (receivedData?.banheiros_total?.device_count || 0),
35682
+ consumption: state6.banheiros?.total || 0,
35683
+ percentage: state6.banheiros?.perc || 0
35677
35684
  };
35678
35685
  summary.byCategory.push(banheiros);
35679
35686
  }
@@ -35681,24 +35688,24 @@ var WaterSummaryTooltip = {
35681
35688
  id: "areaComum",
35682
35689
  name: "\xC1rea Comum",
35683
35690
  icon: WATER_CATEGORY_ICONS.areaComum,
35684
- deviceCount: state5.areaComum?.devices?.length || (receivedData?.area_comum_total?.device_count || 0),
35685
- consumption: state5.areaComum?.total || 0,
35686
- percentage: state5.areaComum?.perc || 0
35691
+ deviceCount: state6.areaComum?.devices?.length || (receivedData?.area_comum_total?.device_count || 0),
35692
+ consumption: state6.areaComum?.total || 0,
35693
+ percentage: state6.areaComum?.perc || 0
35687
35694
  };
35688
35695
  summary.byCategory.push(areaComum);
35689
- if (state5.pontosNaoMapeados && state5.pontosNaoMapeados.total > 0) {
35696
+ if (state6.pontosNaoMapeados && state6.pontosNaoMapeados.total > 0) {
35690
35697
  const pontosNaoMapeados = {
35691
35698
  id: "pontosNaoMapeados",
35692
35699
  name: "Pontos N\xE3o Mapeados",
35693
35700
  icon: WATER_CATEGORY_ICONS.pontosNaoMapeados,
35694
- deviceCount: state5.pontosNaoMapeados?.devices?.length || 0,
35695
- consumption: state5.pontosNaoMapeados?.total || 0,
35696
- percentage: state5.pontosNaoMapeados?.perc || 0
35701
+ deviceCount: state6.pontosNaoMapeados?.devices?.length || 0,
35702
+ consumption: state6.pontosNaoMapeados?.total || 0,
35703
+ percentage: state6.pontosNaoMapeados?.perc || 0
35697
35704
  };
35698
35705
  summary.byCategory.push(pontosNaoMapeados);
35699
35706
  }
35700
35707
  summary.totalDevices = summary.byCategory.reduce((sum, cat) => sum + cat.deviceCount, 0);
35701
- summary.totalConsumption = state5.entrada?.total || 0;
35708
+ summary.totalConsumption = state6.entrada?.total || 0;
35702
35709
  const widgetAggregation = receivedData?.deviceStatusAggregation;
35703
35710
  if (widgetAggregation && widgetAggregation.hasData) {
35704
35711
  summary.byStatus = {
@@ -37235,6 +37242,996 @@ var TempSensorSummaryTooltip = {
37235
37242
  }
37236
37243
  };
37237
37244
 
37245
+ // src/utils/ContractSummaryTooltip.ts
37246
+ var CONTRACT_SUMMARY_TOOLTIP_CSS = `
37247
+ /* ============================================
37248
+ Contract Summary Tooltip (RFC-0107)
37249
+ Premium draggable tooltip with dark theme
37250
+ ============================================ */
37251
+
37252
+ .myio-contract-summary-tooltip {
37253
+ position: fixed;
37254
+ z-index: 99999;
37255
+ pointer-events: none;
37256
+ opacity: 0;
37257
+ transition: opacity 0.25s ease, transform 0.25s ease;
37258
+ transform: translateY(5px);
37259
+ }
37260
+
37261
+ .myio-contract-summary-tooltip.visible {
37262
+ opacity: 1;
37263
+ pointer-events: auto;
37264
+ transform: translateY(0);
37265
+ }
37266
+
37267
+ .myio-contract-summary-tooltip.closing {
37268
+ opacity: 0;
37269
+ transform: translateY(8px);
37270
+ transition: opacity 0.4s ease, transform 0.4s ease;
37271
+ }
37272
+
37273
+ .myio-contract-summary-tooltip.pinned {
37274
+ box-shadow: 0 0 0 2px #9684B5, 0 10px 40px rgba(0, 0, 0, 0.3);
37275
+ border-radius: 16px;
37276
+ }
37277
+
37278
+ .myio-contract-summary-tooltip.dragging {
37279
+ transition: none !important;
37280
+ cursor: move;
37281
+ }
37282
+
37283
+ .myio-contract-summary-tooltip.maximized {
37284
+ top: 20px !important;
37285
+ left: 20px !important;
37286
+ right: 20px !important;
37287
+ bottom: 20px !important;
37288
+ width: auto !important;
37289
+ max-width: none !important;
37290
+ }
37291
+
37292
+ .myio-contract-summary-tooltip.maximized .myio-contract-summary-tooltip__panel {
37293
+ width: 100%;
37294
+ height: 100%;
37295
+ max-width: none;
37296
+ display: flex;
37297
+ flex-direction: column;
37298
+ }
37299
+
37300
+ .myio-contract-summary-tooltip.maximized .myio-contract-summary-tooltip__body {
37301
+ flex: 1;
37302
+ overflow-y: auto;
37303
+ }
37304
+
37305
+ .myio-contract-summary-tooltip__panel {
37306
+ background: #2d1458;
37307
+ border: 1px solid rgba(255, 255, 255, 0.1);
37308
+ border-radius: 16px;
37309
+ box-shadow:
37310
+ 0 20px 60px rgba(0, 0, 0, 0.4),
37311
+ 0 8px 20px rgba(0, 0, 0, 0.25),
37312
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
37313
+ min-width: 320px;
37314
+ max-width: 380px;
37315
+ font-family: Inter, system-ui, -apple-system, sans-serif;
37316
+ font-size: 12px;
37317
+ color: #ffffff;
37318
+ overflow: hidden;
37319
+ }
37320
+
37321
+ /* Header */
37322
+ .myio-contract-summary-tooltip__header {
37323
+ display: flex;
37324
+ align-items: center;
37325
+ gap: 10px;
37326
+ padding: 14px 16px;
37327
+ background: linear-gradient(135deg, #9684B5 0%, #2d1458 100%);
37328
+ border-radius: 16px 16px 0 0;
37329
+ position: relative;
37330
+ overflow: hidden;
37331
+ cursor: move;
37332
+ user-select: none;
37333
+ }
37334
+
37335
+ .myio-contract-summary-tooltip__header::before {
37336
+ content: '';
37337
+ position: absolute;
37338
+ top: 0;
37339
+ left: 0;
37340
+ right: 0;
37341
+ bottom: 0;
37342
+ 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");
37343
+ opacity: 0.3;
37344
+ }
37345
+
37346
+ .myio-contract-summary-tooltip__icon {
37347
+ width: 40px;
37348
+ height: 40px;
37349
+ background: rgba(255, 255, 255, 0.15);
37350
+ border-radius: 12px;
37351
+ display: flex;
37352
+ align-items: center;
37353
+ justify-content: center;
37354
+ font-size: 20px;
37355
+ backdrop-filter: blur(10px);
37356
+ position: relative;
37357
+ z-index: 1;
37358
+ }
37359
+
37360
+ .myio-contract-summary-tooltip__icon.valid {
37361
+ background: rgba(76, 175, 80, 0.3);
37362
+ }
37363
+
37364
+ .myio-contract-summary-tooltip__icon.invalid {
37365
+ background: rgba(244, 67, 54, 0.3);
37366
+ }
37367
+
37368
+ .myio-contract-summary-tooltip__header-info {
37369
+ flex: 1;
37370
+ position: relative;
37371
+ z-index: 1;
37372
+ }
37373
+
37374
+ .myio-contract-summary-tooltip__title {
37375
+ font-weight: 700;
37376
+ font-size: 15px;
37377
+ color: #ffffff;
37378
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
37379
+ margin-bottom: 2px;
37380
+ }
37381
+
37382
+ .myio-contract-summary-tooltip__subtitle {
37383
+ font-size: 11px;
37384
+ color: rgba(255, 255, 255, 0.7);
37385
+ }
37386
+
37387
+ .myio-contract-summary-tooltip__header-actions {
37388
+ display: flex;
37389
+ align-items: center;
37390
+ gap: 4px;
37391
+ position: relative;
37392
+ z-index: 1;
37393
+ }
37394
+
37395
+ .myio-contract-summary-tooltip__header-btn {
37396
+ width: 28px;
37397
+ height: 28px;
37398
+ border: none;
37399
+ background: rgba(255, 255, 255, 0.15);
37400
+ border-radius: 8px;
37401
+ cursor: pointer;
37402
+ display: flex;
37403
+ align-items: center;
37404
+ justify-content: center;
37405
+ transition: all 0.2s ease;
37406
+ color: rgba(255, 255, 255, 0.8);
37407
+ }
37408
+
37409
+ .myio-contract-summary-tooltip__header-btn:hover {
37410
+ background: rgba(255, 255, 255, 0.25);
37411
+ color: #ffffff;
37412
+ transform: scale(1.05);
37413
+ }
37414
+
37415
+ .myio-contract-summary-tooltip__header-btn.pinned {
37416
+ background: rgba(255, 255, 255, 0.9);
37417
+ color: #9684B5;
37418
+ }
37419
+
37420
+ .myio-contract-summary-tooltip__header-btn.pinned:hover {
37421
+ background: #ffffff;
37422
+ color: #2d1458;
37423
+ }
37424
+
37425
+ .myio-contract-summary-tooltip__header-btn svg {
37426
+ width: 14px;
37427
+ height: 14px;
37428
+ }
37429
+
37430
+ /* Body */
37431
+ .myio-contract-summary-tooltip__body {
37432
+ padding: 16px;
37433
+ }
37434
+
37435
+ /* Domain Section */
37436
+ .myio-contract-summary-tooltip__domain {
37437
+ margin-bottom: 14px;
37438
+ background: rgba(255, 255, 255, 0.05);
37439
+ border-radius: 12px;
37440
+ overflow: hidden;
37441
+ border: 1px solid rgba(255, 255, 255, 0.08);
37442
+ }
37443
+
37444
+ .myio-contract-summary-tooltip__domain:last-child {
37445
+ margin-bottom: 0;
37446
+ }
37447
+
37448
+ .myio-contract-summary-tooltip__domain-header {
37449
+ display: flex;
37450
+ align-items: center;
37451
+ justify-content: space-between;
37452
+ padding: 10px 14px;
37453
+ cursor: pointer;
37454
+ transition: background 0.2s ease;
37455
+ }
37456
+
37457
+ .myio-contract-summary-tooltip__domain-header:hover {
37458
+ background: rgba(255, 255, 255, 0.05);
37459
+ }
37460
+
37461
+ .myio-contract-summary-tooltip__domain-info {
37462
+ display: flex;
37463
+ align-items: center;
37464
+ gap: 8px;
37465
+ }
37466
+
37467
+ .myio-contract-summary-tooltip__domain-icon {
37468
+ font-size: 18px;
37469
+ }
37470
+
37471
+ .myio-contract-summary-tooltip__domain-name {
37472
+ font-weight: 600;
37473
+ font-size: 13px;
37474
+ }
37475
+
37476
+ .myio-contract-summary-tooltip__domain-count {
37477
+ font-size: 12px;
37478
+ color: #81c784;
37479
+ font-weight: 600;
37480
+ }
37481
+
37482
+ .myio-contract-summary-tooltip__expand-icon {
37483
+ font-size: 10px;
37484
+ opacity: 0.6;
37485
+ transition: transform 0.3s ease;
37486
+ }
37487
+
37488
+ .myio-contract-summary-tooltip__domain.expanded .myio-contract-summary-tooltip__expand-icon {
37489
+ transform: rotate(180deg);
37490
+ }
37491
+
37492
+ /* Domain Details */
37493
+ .myio-contract-summary-tooltip__domain-details {
37494
+ max-height: 0;
37495
+ overflow: hidden;
37496
+ transition: max-height 0.3s ease;
37497
+ background: rgba(0, 0, 0, 0.15);
37498
+ }
37499
+
37500
+ .myio-contract-summary-tooltip__domain.expanded .myio-contract-summary-tooltip__domain-details {
37501
+ max-height: 150px;
37502
+ }
37503
+
37504
+ .myio-contract-summary-tooltip__detail-row {
37505
+ display: flex;
37506
+ align-items: center;
37507
+ justify-content: space-between;
37508
+ padding: 6px 14px 6px 40px;
37509
+ font-size: 12px;
37510
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
37511
+ }
37512
+
37513
+ .myio-contract-summary-tooltip__detail-row:first-child {
37514
+ border-top: none;
37515
+ }
37516
+
37517
+ .myio-contract-summary-tooltip__detail-label {
37518
+ opacity: 0.7;
37519
+ display: flex;
37520
+ align-items: center;
37521
+ gap: 6px;
37522
+ }
37523
+
37524
+ .myio-contract-summary-tooltip__detail-label::before {
37525
+ content: '';
37526
+ width: 4px;
37527
+ height: 4px;
37528
+ border-radius: 50%;
37529
+ background: currentColor;
37530
+ opacity: 0.5;
37531
+ }
37532
+
37533
+ .myio-contract-summary-tooltip__detail-count {
37534
+ font-weight: 500;
37535
+ color: #81c784;
37536
+ }
37537
+
37538
+ /* Status Banner */
37539
+ .myio-contract-summary-tooltip__status {
37540
+ display: flex;
37541
+ align-items: center;
37542
+ justify-content: center;
37543
+ gap: 8px;
37544
+ padding: 10px 14px;
37545
+ border-radius: 10px;
37546
+ margin-bottom: 14px;
37547
+ font-size: 12px;
37548
+ font-weight: 600;
37549
+ }
37550
+
37551
+ .myio-contract-summary-tooltip__status.valid {
37552
+ background: rgba(76, 175, 80, 0.2);
37553
+ color: #81c784;
37554
+ border: 1px solid rgba(76, 175, 80, 0.3);
37555
+ }
37556
+
37557
+ .myio-contract-summary-tooltip__status.invalid {
37558
+ background: rgba(244, 67, 54, 0.2);
37559
+ color: #ef5350;
37560
+ border: 1px solid rgba(244, 67, 54, 0.3);
37561
+ }
37562
+
37563
+ .myio-contract-summary-tooltip__status-icon {
37564
+ font-size: 14px;
37565
+ }
37566
+
37567
+ /* Discrepancies */
37568
+ .myio-contract-summary-tooltip__discrepancies {
37569
+ background: rgba(244, 67, 54, 0.15);
37570
+ border: 1px solid rgba(244, 67, 54, 0.3);
37571
+ border-radius: 10px;
37572
+ padding: 10px 14px;
37573
+ margin-bottom: 14px;
37574
+ }
37575
+
37576
+ .myio-contract-summary-tooltip__discrepancies-title {
37577
+ font-size: 11px;
37578
+ font-weight: 600;
37579
+ color: #ef5350;
37580
+ margin-bottom: 6px;
37581
+ text-transform: uppercase;
37582
+ letter-spacing: 0.5px;
37583
+ }
37584
+
37585
+ .myio-contract-summary-tooltip__discrepancy-item {
37586
+ font-size: 11px;
37587
+ color: rgba(255, 255, 255, 0.8);
37588
+ padding: 3px 0;
37589
+ }
37590
+
37591
+ /* Footer */
37592
+ .myio-contract-summary-tooltip__footer {
37593
+ display: flex;
37594
+ justify-content: space-between;
37595
+ align-items: center;
37596
+ padding: 12px 16px;
37597
+ background: rgba(0, 0, 0, 0.2);
37598
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
37599
+ border-radius: 0 0 16px 16px;
37600
+ }
37601
+
37602
+ .myio-contract-summary-tooltip__footer-label {
37603
+ font-size: 10px;
37604
+ color: rgba(255, 255, 255, 0.5);
37605
+ }
37606
+
37607
+ .myio-contract-summary-tooltip__footer-value {
37608
+ font-size: 11px;
37609
+ font-weight: 600;
37610
+ color: rgba(255, 255, 255, 0.8);
37611
+ }
37612
+
37613
+ /* Total Devices Badge */
37614
+ .myio-contract-summary-tooltip__total {
37615
+ display: flex;
37616
+ align-items: center;
37617
+ justify-content: center;
37618
+ gap: 8px;
37619
+ padding: 12px;
37620
+ background: rgba(255, 255, 255, 0.08);
37621
+ border-radius: 10px;
37622
+ margin-bottom: 14px;
37623
+ }
37624
+
37625
+ .myio-contract-summary-tooltip__total-label {
37626
+ font-size: 12px;
37627
+ opacity: 0.8;
37628
+ }
37629
+
37630
+ .myio-contract-summary-tooltip__total-value {
37631
+ font-size: 18px;
37632
+ font-weight: 700;
37633
+ color: #81c784;
37634
+ }
37635
+
37636
+ /* Responsive */
37637
+ @media (max-width: 400px) {
37638
+ .myio-contract-summary-tooltip__panel {
37639
+ min-width: 280px;
37640
+ max-width: 95vw;
37641
+ }
37642
+ }
37643
+ `;
37644
+ var cssInjected9 = false;
37645
+ function injectCSS9() {
37646
+ if (cssInjected9) return;
37647
+ if (typeof document === "undefined") return;
37648
+ const styleId = "myio-contract-summary-tooltip-styles";
37649
+ if (document.getElementById(styleId)) {
37650
+ cssInjected9 = true;
37651
+ return;
37652
+ }
37653
+ const style = document.createElement("style");
37654
+ style.id = styleId;
37655
+ style.textContent = CONTRACT_SUMMARY_TOOLTIP_CSS;
37656
+ document.head.appendChild(style);
37657
+ cssInjected9 = true;
37658
+ }
37659
+ var state5 = {
37660
+ hideTimer: null,
37661
+ isMouseOverTooltip: false,
37662
+ isMaximized: false,
37663
+ isDragging: false,
37664
+ dragOffset: { x: 0, y: 0 },
37665
+ savedPosition: null,
37666
+ pinnedCounter: 0,
37667
+ expandedDomains: /* @__PURE__ */ new Set(["energy", "water", "temperature"])
37668
+ };
37669
+ function formatTimestamp5(isoString) {
37670
+ if (!isoString) return "Agora";
37671
+ try {
37672
+ const date = new Date(isoString);
37673
+ return date.toLocaleString("pt-BR", {
37674
+ hour: "2-digit",
37675
+ minute: "2-digit",
37676
+ day: "2-digit",
37677
+ month: "2-digit"
37678
+ });
37679
+ } catch {
37680
+ return "Agora";
37681
+ }
37682
+ }
37683
+ function calculateTotalDevices(data) {
37684
+ return data.energy.total + data.water.total + data.temperature.total;
37685
+ }
37686
+ function generateHeaderHTML5(data) {
37687
+ const iconClass = data.isValid ? "valid" : "invalid";
37688
+ const iconSymbol = data.isValid ? "\u2713" : "!";
37689
+ const totalDevices = calculateTotalDevices(data);
37690
+ return `
37691
+ <div class="myio-contract-summary-tooltip__header" data-drag-handle>
37692
+ <div class="myio-contract-summary-tooltip__icon ${iconClass}">${iconSymbol}</div>
37693
+ <div class="myio-contract-summary-tooltip__header-info">
37694
+ <div class="myio-contract-summary-tooltip__title">Contract Summary</div>
37695
+ <div class="myio-contract-summary-tooltip__subtitle">${totalDevices} devices loaded</div>
37696
+ </div>
37697
+ <div class="myio-contract-summary-tooltip__header-actions">
37698
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="pin" title="Pin to screen">
37699
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37700
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
37701
+ <line x1="12" y1="16" x2="12" y2="21"/>
37702
+ <line x1="8" y1="4" x2="16" y2="4"/>
37703
+ </svg>
37704
+ </button>
37705
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="maximize" title="Maximize">
37706
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37707
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
37708
+ </svg>
37709
+ </button>
37710
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="close" title="Close">
37711
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37712
+ <path d="M18 6L6 18M6 6l12 12"/>
37713
+ </svg>
37714
+ </button>
37715
+ </div>
37716
+ </div>
37717
+ `;
37718
+ }
37719
+ function generateDomainHTML(domain, icon, name, counts, isTemperature = false) {
37720
+ const isExpanded = state5.expandedDomains.has(domain);
37721
+ const expandedClass = isExpanded ? "expanded" : "";
37722
+ let detailsHTML = "";
37723
+ if (isTemperature) {
37724
+ const tempCounts = counts;
37725
+ detailsHTML = `
37726
+ <div class="myio-contract-summary-tooltip__detail-row">
37727
+ <span class="myio-contract-summary-tooltip__detail-label">Internal (Climate)</span>
37728
+ <span class="myio-contract-summary-tooltip__detail-count">${tempCounts.internal}</span>
37729
+ </div>
37730
+ <div class="myio-contract-summary-tooltip__detail-row">
37731
+ <span class="myio-contract-summary-tooltip__detail-label">Stores (Non-Climate)</span>
37732
+ <span class="myio-contract-summary-tooltip__detail-count">${tempCounts.stores}</span>
37733
+ </div>
37734
+ `;
37735
+ } else {
37736
+ const domainCounts = counts;
37737
+ detailsHTML = `
37738
+ <div class="myio-contract-summary-tooltip__detail-row">
37739
+ <span class="myio-contract-summary-tooltip__detail-label">Entries</span>
37740
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.entries}</span>
37741
+ </div>
37742
+ <div class="myio-contract-summary-tooltip__detail-row">
37743
+ <span class="myio-contract-summary-tooltip__detail-label">Common Area</span>
37744
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.commonArea}</span>
37745
+ </div>
37746
+ <div class="myio-contract-summary-tooltip__detail-row">
37747
+ <span class="myio-contract-summary-tooltip__detail-label">Stores</span>
37748
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.stores}</span>
37749
+ </div>
37750
+ `;
37751
+ }
37752
+ return `
37753
+ <div class="myio-contract-summary-tooltip__domain ${expandedClass}" data-domain="${domain}">
37754
+ <div class="myio-contract-summary-tooltip__domain-header" data-toggle-domain="${domain}">
37755
+ <div class="myio-contract-summary-tooltip__domain-info">
37756
+ <span class="myio-contract-summary-tooltip__domain-icon">${icon}</span>
37757
+ <span class="myio-contract-summary-tooltip__domain-name">${name}</span>
37758
+ </div>
37759
+ <div style="display: flex; align-items: center; gap: 8px;">
37760
+ <span class="myio-contract-summary-tooltip__domain-count">${counts.total} devices</span>
37761
+ <span class="myio-contract-summary-tooltip__expand-icon">\u25BC</span>
37762
+ </div>
37763
+ </div>
37764
+ <div class="myio-contract-summary-tooltip__domain-details">
37765
+ ${detailsHTML}
37766
+ </div>
37767
+ </div>
37768
+ `;
37769
+ }
37770
+ function generateBodyHTML3(data) {
37771
+ const totalDevices = calculateTotalDevices(data);
37772
+ const timestamp = formatTimestamp5(data.timestamp);
37773
+ const statusClass = data.isValid ? "valid" : "invalid";
37774
+ const statusIcon = data.isValid ? "\u2713" : "\u26A0";
37775
+ const statusText = data.isValid ? "Contract validated successfully" : "Validation issues detected";
37776
+ let discrepanciesHTML = "";
37777
+ if (data.discrepancies && data.discrepancies.length > 0) {
37778
+ const items = data.discrepancies.map((d) => `<div class="myio-contract-summary-tooltip__discrepancy-item">${d.domain}: expected ${d.expected}, found ${d.actual}</div>`).join("");
37779
+ discrepanciesHTML = `
37780
+ <div class="myio-contract-summary-tooltip__discrepancies">
37781
+ <div class="myio-contract-summary-tooltip__discrepancies-title">Discrepancies</div>
37782
+ ${items}
37783
+ </div>
37784
+ `;
37785
+ }
37786
+ return `
37787
+ <div class="myio-contract-summary-tooltip__body">
37788
+ <!-- Status Banner -->
37789
+ <div class="myio-contract-summary-tooltip__status ${statusClass}">
37790
+ <span class="myio-contract-summary-tooltip__status-icon">${statusIcon}</span>
37791
+ <span>${statusText}</span>
37792
+ </div>
37793
+
37794
+ ${discrepanciesHTML}
37795
+
37796
+ <!-- Total Devices -->
37797
+ <div class="myio-contract-summary-tooltip__total">
37798
+ <span class="myio-contract-summary-tooltip__total-label">Total Devices:</span>
37799
+ <span class="myio-contract-summary-tooltip__total-value">${totalDevices}</span>
37800
+ </div>
37801
+
37802
+ <!-- Energy -->
37803
+ ${generateDomainHTML("energy", "\u26A1", "Energy", data.energy)}
37804
+
37805
+ <!-- Water -->
37806
+ ${generateDomainHTML("water", "\u{1F4A7}", "Water", data.water)}
37807
+
37808
+ <!-- Temperature -->
37809
+ ${generateDomainHTML("temperature", "\u{1F321}\uFE0F", "Temperature", data.temperature, true)}
37810
+ </div>
37811
+
37812
+ <!-- Footer -->
37813
+ <div class="myio-contract-summary-tooltip__footer">
37814
+ <span class="myio-contract-summary-tooltip__footer-label">Loaded at</span>
37815
+ <span class="myio-contract-summary-tooltip__footer-value">${timestamp}</span>
37816
+ </div>
37817
+ `;
37818
+ }
37819
+ function setupHoverListeners5(container) {
37820
+ container.onmouseenter = () => {
37821
+ state5.isMouseOverTooltip = true;
37822
+ if (state5.hideTimer) {
37823
+ clearTimeout(state5.hideTimer);
37824
+ state5.hideTimer = null;
37825
+ }
37826
+ };
37827
+ container.onmouseleave = () => {
37828
+ state5.isMouseOverTooltip = false;
37829
+ startDelayedHide5();
37830
+ };
37831
+ }
37832
+ function setupDomainToggleListeners(container) {
37833
+ const toggles = container.querySelectorAll("[data-toggle-domain]");
37834
+ toggles.forEach((toggle) => {
37835
+ toggle.onclick = (e) => {
37836
+ e.stopPropagation();
37837
+ const domain = toggle.dataset.toggleDomain;
37838
+ if (!domain) return;
37839
+ const domainEl = container.querySelector(`[data-domain="${domain}"]`);
37840
+ if (!domainEl) return;
37841
+ if (state5.expandedDomains.has(domain)) {
37842
+ state5.expandedDomains.delete(domain);
37843
+ domainEl.classList.remove("expanded");
37844
+ } else {
37845
+ state5.expandedDomains.add(domain);
37846
+ domainEl.classList.add("expanded");
37847
+ }
37848
+ };
37849
+ });
37850
+ }
37851
+ function setupButtonListeners5(container) {
37852
+ const buttons = container.querySelectorAll("[data-action]");
37853
+ buttons.forEach((btn) => {
37854
+ btn.onclick = (e) => {
37855
+ e.stopPropagation();
37856
+ const action = btn.dataset.action;
37857
+ switch (action) {
37858
+ case "pin":
37859
+ createPinnedClone5(container);
37860
+ break;
37861
+ case "maximize":
37862
+ toggleMaximize5(container);
37863
+ break;
37864
+ case "close":
37865
+ ContractSummaryTooltip.close();
37866
+ break;
37867
+ }
37868
+ };
37869
+ });
37870
+ }
37871
+ function setupDragListeners5(container) {
37872
+ const header = container.querySelector("[data-drag-handle]");
37873
+ if (!header) return;
37874
+ header.onmousedown = (e) => {
37875
+ if (e.target.closest("[data-action]")) return;
37876
+ if (e.target.closest("[data-toggle-domain]")) return;
37877
+ if (state5.isMaximized) return;
37878
+ state5.isDragging = true;
37879
+ container.classList.add("dragging");
37880
+ const rect = container.getBoundingClientRect();
37881
+ state5.dragOffset = {
37882
+ x: e.clientX - rect.left,
37883
+ y: e.clientY - rect.top
37884
+ };
37885
+ const onMouseMove = (e2) => {
37886
+ if (!state5.isDragging) return;
37887
+ const newLeft = e2.clientX - state5.dragOffset.x;
37888
+ const newTop = e2.clientY - state5.dragOffset.y;
37889
+ const maxLeft = window.innerWidth - container.offsetWidth;
37890
+ const maxTop = window.innerHeight - container.offsetHeight;
37891
+ container.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
37892
+ container.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
37893
+ };
37894
+ const onMouseUp = () => {
37895
+ state5.isDragging = false;
37896
+ container.classList.remove("dragging");
37897
+ document.removeEventListener("mousemove", onMouseMove);
37898
+ document.removeEventListener("mouseup", onMouseUp);
37899
+ };
37900
+ document.addEventListener("mousemove", onMouseMove);
37901
+ document.addEventListener("mouseup", onMouseUp);
37902
+ };
37903
+ }
37904
+ function createPinnedClone5(container) {
37905
+ state5.pinnedCounter++;
37906
+ const pinnedId = `myio-contract-summary-tooltip-pinned-${state5.pinnedCounter}`;
37907
+ const clone = container.cloneNode(true);
37908
+ clone.id = pinnedId;
37909
+ clone.classList.add("pinned");
37910
+ clone.classList.remove("closing");
37911
+ const pinBtn = clone.querySelector('[data-action="pin"]');
37912
+ if (pinBtn) {
37913
+ pinBtn.classList.add("pinned");
37914
+ pinBtn.setAttribute("title", "Unpin");
37915
+ pinBtn.innerHTML = `
37916
+ <svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1">
37917
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
37918
+ <line x1="12" y1="16" x2="12" y2="21"/>
37919
+ <line x1="8" y1="4" x2="16" y2="4"/>
37920
+ </svg>
37921
+ `;
37922
+ }
37923
+ document.body.appendChild(clone);
37924
+ setupPinnedCloneListeners5(clone, pinnedId);
37925
+ ContractSummaryTooltip.hide();
37926
+ }
37927
+ function setupPinnedCloneListeners5(clone, cloneId) {
37928
+ let isMaximized = false;
37929
+ let savedPosition = null;
37930
+ const cloneExpandedDomains = new Set(state5.expandedDomains);
37931
+ const toggles = clone.querySelectorAll("[data-toggle-domain]");
37932
+ toggles.forEach((toggle) => {
37933
+ toggle.onclick = (e) => {
37934
+ e.stopPropagation();
37935
+ const domain = toggle.dataset.toggleDomain;
37936
+ if (!domain) return;
37937
+ const domainEl = clone.querySelector(`[data-domain="${domain}"]`);
37938
+ if (!domainEl) return;
37939
+ if (cloneExpandedDomains.has(domain)) {
37940
+ cloneExpandedDomains.delete(domain);
37941
+ domainEl.classList.remove("expanded");
37942
+ } else {
37943
+ cloneExpandedDomains.add(domain);
37944
+ domainEl.classList.add("expanded");
37945
+ }
37946
+ };
37947
+ });
37948
+ const pinBtn = clone.querySelector('[data-action="pin"]');
37949
+ if (pinBtn) {
37950
+ pinBtn.onclick = (e) => {
37951
+ e.stopPropagation();
37952
+ closePinnedClone5(cloneId);
37953
+ };
37954
+ }
37955
+ const closeBtn = clone.querySelector('[data-action="close"]');
37956
+ if (closeBtn) {
37957
+ closeBtn.onclick = (e) => {
37958
+ e.stopPropagation();
37959
+ closePinnedClone5(cloneId);
37960
+ };
37961
+ }
37962
+ const maxBtn = clone.querySelector('[data-action="maximize"]');
37963
+ if (maxBtn) {
37964
+ maxBtn.onclick = (e) => {
37965
+ e.stopPropagation();
37966
+ isMaximized = !isMaximized;
37967
+ if (isMaximized) {
37968
+ savedPosition = { left: clone.style.left, top: clone.style.top };
37969
+ 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>`;
37970
+ maxBtn.setAttribute("title", "Restore");
37971
+ } else {
37972
+ 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>`;
37973
+ maxBtn.setAttribute("title", "Maximize");
37974
+ if (savedPosition) {
37975
+ clone.style.left = savedPosition.left;
37976
+ clone.style.top = savedPosition.top;
37977
+ }
37978
+ }
37979
+ clone.classList.toggle("maximized", isMaximized);
37980
+ };
37981
+ }
37982
+ const header = clone.querySelector("[data-drag-handle]");
37983
+ if (header) {
37984
+ let isDragging = false;
37985
+ let dragOffset = { x: 0, y: 0 };
37986
+ header.onmousedown = (e) => {
37987
+ if (e.target.closest("[data-action]")) return;
37988
+ if (e.target.closest("[data-toggle-domain]")) return;
37989
+ if (isMaximized) return;
37990
+ isDragging = true;
37991
+ clone.classList.add("dragging");
37992
+ const rect = clone.getBoundingClientRect();
37993
+ dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
37994
+ const onMouseMove = (e2) => {
37995
+ if (!isDragging) return;
37996
+ const newLeft = e2.clientX - dragOffset.x;
37997
+ const newTop = e2.clientY - dragOffset.y;
37998
+ const maxLeft = window.innerWidth - clone.offsetWidth;
37999
+ const maxTop = window.innerHeight - clone.offsetHeight;
38000
+ clone.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
38001
+ clone.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
38002
+ };
38003
+ const onMouseUp = () => {
38004
+ isDragging = false;
38005
+ clone.classList.remove("dragging");
38006
+ document.removeEventListener("mousemove", onMouseMove);
38007
+ document.removeEventListener("mouseup", onMouseUp);
38008
+ };
38009
+ document.addEventListener("mousemove", onMouseMove);
38010
+ document.addEventListener("mouseup", onMouseUp);
38011
+ };
38012
+ }
38013
+ }
38014
+ function closePinnedClone5(cloneId) {
38015
+ const clone = document.getElementById(cloneId);
38016
+ if (clone) {
38017
+ clone.classList.add("closing");
38018
+ setTimeout(() => clone.remove(), 400);
38019
+ }
38020
+ }
38021
+ function toggleMaximize5(container) {
38022
+ state5.isMaximized = !state5.isMaximized;
38023
+ if (state5.isMaximized) {
38024
+ state5.savedPosition = {
38025
+ left: container.style.left,
38026
+ top: container.style.top
38027
+ };
38028
+ }
38029
+ container.classList.toggle("maximized", state5.isMaximized);
38030
+ const maxBtn = container.querySelector('[data-action="maximize"]');
38031
+ if (maxBtn) {
38032
+ if (state5.isMaximized) {
38033
+ 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>`;
38034
+ maxBtn.setAttribute("title", "Restore");
38035
+ } else {
38036
+ 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>`;
38037
+ maxBtn.setAttribute("title", "Maximize");
38038
+ if (state5.savedPosition) {
38039
+ container.style.left = state5.savedPosition.left;
38040
+ container.style.top = state5.savedPosition.top;
38041
+ }
38042
+ }
38043
+ }
38044
+ }
38045
+ function startDelayedHide5() {
38046
+ if (state5.isMouseOverTooltip) return;
38047
+ if (state5.hideTimer) {
38048
+ clearTimeout(state5.hideTimer);
38049
+ }
38050
+ state5.hideTimer = setTimeout(() => {
38051
+ hideWithAnimation5();
38052
+ }, 1500);
38053
+ }
38054
+ function hideWithAnimation5() {
38055
+ const container = document.getElementById("myio-contract-summary-tooltip");
38056
+ if (container && container.classList.contains("visible")) {
38057
+ container.classList.add("closing");
38058
+ setTimeout(() => {
38059
+ container.classList.remove("visible", "closing");
38060
+ }, 400);
38061
+ }
38062
+ }
38063
+ function positionTooltip5(container, triggerElement) {
38064
+ const rect = triggerElement.getBoundingClientRect();
38065
+ let left = rect.left;
38066
+ let top = rect.bottom + 8;
38067
+ const tooltipWidth = 360;
38068
+ if (left + tooltipWidth > window.innerWidth - 20) {
38069
+ left = window.innerWidth - tooltipWidth - 20;
38070
+ }
38071
+ if (left < 10) left = 10;
38072
+ if (top + 500 > window.innerHeight) {
38073
+ top = rect.top - 8 - 500;
38074
+ if (top < 10) top = 10;
38075
+ }
38076
+ container.style.left = left + "px";
38077
+ container.style.top = top + "px";
38078
+ }
38079
+ var ContractSummaryTooltip = {
38080
+ containerId: "myio-contract-summary-tooltip",
38081
+ /**
38082
+ * Get or create container
38083
+ */
38084
+ getContainer() {
38085
+ injectCSS9();
38086
+ let container = document.getElementById(this.containerId);
38087
+ if (!container) {
38088
+ container = document.createElement("div");
38089
+ container.id = this.containerId;
38090
+ container.className = "myio-contract-summary-tooltip";
38091
+ document.body.appendChild(container);
38092
+ }
38093
+ return container;
38094
+ },
38095
+ /**
38096
+ * Show tooltip
38097
+ */
38098
+ show(triggerElement, data) {
38099
+ if (state5.hideTimer) {
38100
+ clearTimeout(state5.hideTimer);
38101
+ state5.hideTimer = null;
38102
+ }
38103
+ const container = this.getContainer();
38104
+ container.classList.remove("closing");
38105
+ container.innerHTML = `
38106
+ <div class="myio-contract-summary-tooltip__panel">
38107
+ ${generateHeaderHTML5(data)}
38108
+ ${generateBodyHTML3(data)}
38109
+ </div>
38110
+ `;
38111
+ positionTooltip5(container, triggerElement);
38112
+ container.classList.add("visible");
38113
+ setupHoverListeners5(container);
38114
+ setupButtonListeners5(container);
38115
+ setupDragListeners5(container);
38116
+ setupDomainToggleListeners(container);
38117
+ },
38118
+ /**
38119
+ * Start delayed hide
38120
+ */
38121
+ startDelayedHide() {
38122
+ startDelayedHide5();
38123
+ },
38124
+ /**
38125
+ * Hide immediately
38126
+ */
38127
+ hide() {
38128
+ if (state5.hideTimer) {
38129
+ clearTimeout(state5.hideTimer);
38130
+ state5.hideTimer = null;
38131
+ }
38132
+ state5.isMouseOverTooltip = false;
38133
+ const container = document.getElementById(this.containerId);
38134
+ if (container) {
38135
+ container.classList.remove("visible", "closing");
38136
+ }
38137
+ },
38138
+ /**
38139
+ * Close and reset all states
38140
+ */
38141
+ close() {
38142
+ state5.isMaximized = false;
38143
+ state5.isDragging = false;
38144
+ state5.savedPosition = null;
38145
+ if (state5.hideTimer) {
38146
+ clearTimeout(state5.hideTimer);
38147
+ state5.hideTimer = null;
38148
+ }
38149
+ state5.isMouseOverTooltip = false;
38150
+ const container = document.getElementById(this.containerId);
38151
+ if (container) {
38152
+ container.classList.remove("visible", "pinned", "maximized", "dragging", "closing");
38153
+ }
38154
+ },
38155
+ /**
38156
+ * Attach tooltip to trigger element with click behavior
38157
+ */
38158
+ attach(triggerElement, getDataFn) {
38159
+ const self = this;
38160
+ const handleClick = () => {
38161
+ if (state5.hideTimer) {
38162
+ clearTimeout(state5.hideTimer);
38163
+ state5.hideTimer = null;
38164
+ }
38165
+ const data = getDataFn();
38166
+ if (data) {
38167
+ self.show(triggerElement, data);
38168
+ }
38169
+ };
38170
+ triggerElement.addEventListener("click", handleClick);
38171
+ return () => {
38172
+ triggerElement.removeEventListener("click", handleClick);
38173
+ self.hide();
38174
+ };
38175
+ },
38176
+ /**
38177
+ * Attach tooltip with hover behavior
38178
+ */
38179
+ attachHover(triggerElement, getDataFn) {
38180
+ const self = this;
38181
+ const handleMouseEnter = () => {
38182
+ if (state5.hideTimer) {
38183
+ clearTimeout(state5.hideTimer);
38184
+ state5.hideTimer = null;
38185
+ }
38186
+ const data = getDataFn();
38187
+ if (data) {
38188
+ self.show(triggerElement, data);
38189
+ }
38190
+ };
38191
+ const handleMouseLeave = () => {
38192
+ startDelayedHide5();
38193
+ };
38194
+ triggerElement.addEventListener("mouseenter", handleMouseEnter);
38195
+ triggerElement.addEventListener("mouseleave", handleMouseLeave);
38196
+ return () => {
38197
+ triggerElement.removeEventListener("mouseenter", handleMouseEnter);
38198
+ triggerElement.removeEventListener("mouseleave", handleMouseLeave);
38199
+ self.hide();
38200
+ };
38201
+ },
38202
+ /**
38203
+ * Build contract data from window.CONTRACT_STATE
38204
+ * Helper method to build the data structure from global state
38205
+ */
38206
+ buildFromGlobalState() {
38207
+ const globalState = window.CONTRACT_STATE;
38208
+ if (!globalState) return null;
38209
+ return {
38210
+ isLoaded: globalState.isLoaded ?? false,
38211
+ isValid: globalState.isValid ?? false,
38212
+ timestamp: globalState.timestamp ?? null,
38213
+ energy: {
38214
+ total: globalState.energy?.total ?? 0,
38215
+ entries: globalState.energy?.entries ?? 0,
38216
+ commonArea: globalState.energy?.commonArea ?? 0,
38217
+ stores: globalState.energy?.stores ?? 0
38218
+ },
38219
+ water: {
38220
+ total: globalState.water?.total ?? 0,
38221
+ entries: globalState.water?.entries ?? 0,
38222
+ commonArea: globalState.water?.commonArea ?? 0,
38223
+ stores: globalState.water?.stores ?? 0
38224
+ },
38225
+ temperature: {
38226
+ total: globalState.temperature?.total ?? 0,
38227
+ internal: globalState.temperature?.internal ?? 0,
38228
+ stores: globalState.temperature?.stores ?? 0
38229
+ },
38230
+ discrepancies: globalState.discrepancies
38231
+ };
38232
+ }
38233
+ };
38234
+
37238
38235
  // src/components/ModalHeader/index.ts
37239
38236
  var DEFAULT_BG_COLOR = "#3e1a7d";
37240
38237
  var DEFAULT_TEXT_COLOR = "white";
@@ -40998,6 +41995,7 @@ export {
40998
41995
  DEFAULT_CONFIG as CONSUMPTION_CHART_DEFAULTS,
40999
41996
  THEME_COLORS as CONSUMPTION_THEME_COLORS,
41000
41997
  ConnectionStatusType,
41998
+ ContractSummaryTooltip,
41001
41999
  DEFAULT_CLAMP_RANGE,
41002
42000
  DEFAULT_ENERGY_GROUP_COLORS,
41003
42001
  DEFAULT_GAS_GROUP_COLORS,