myio-js-library 0.1.197 → 0.1.199

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8863,8 +8863,8 @@
8863
8863
  return "dot--offline";
8864
8864
  }
8865
8865
  }
8866
- function buildDOM(state5) {
8867
- const { entityObject, i18n, enableSelection, enableDragDrop } = state5;
8866
+ function buildDOM(state6) {
8867
+ const { entityObject, i18n, enableSelection, enableDragDrop } = state6;
8868
8868
  const root = document.createElement("div");
8869
8869
  root.className = "myio-ho-card";
8870
8870
  root.setAttribute("role", "group");
@@ -9020,7 +9020,7 @@
9020
9020
  return root;
9021
9021
  }
9022
9022
  function buildDebugTooltipInfo(entityObject, statusInfo, stateClass, statusDecisionSource, delayTimeConnectionInMins) {
9023
- const formatTimestamp5 = (ts) => {
9023
+ const formatTimestamp6 = (ts) => {
9024
9024
  if (!ts) return "N/A";
9025
9025
  const d = new Date(ts);
9026
9026
  return d.toLocaleString("pt-BR", {
@@ -9047,8 +9047,8 @@
9047
9047
  chipClass: statusInfo.chipClass,
9048
9048
  chipLabel: statusInfo.label,
9049
9049
  // Connection timestamps
9050
- lastConnectTime: formatTimestamp5(entityObject.lastConnectTime),
9051
- lastDisconnectTime: formatTimestamp5(entityObject.lastDisconnectTime),
9050
+ lastConnectTime: formatTimestamp6(entityObject.lastConnectTime),
9051
+ lastDisconnectTime: formatTimestamp6(entityObject.lastDisconnectTime),
9052
9052
  delayTimeConnectionInMins,
9053
9053
  // Raw values
9054
9054
  val: entityObject.val,
@@ -9190,8 +9190,8 @@
9190
9190
  }
9191
9191
  return isOffline;
9192
9192
  }
9193
- function paint(root, state5) {
9194
- const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state5;
9193
+ function paint(root, state6) {
9194
+ const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state6;
9195
9195
  let statusDecisionSource = "unknown";
9196
9196
  if (entityObject.connectionStatus) {
9197
9197
  if (entityObject.connectionStatus === "offline") {
@@ -9243,7 +9243,7 @@
9243
9243
  numSpan.textContent = primaryValue;
9244
9244
  const barContainer = root.querySelector(".bar");
9245
9245
  const effContainer = root.querySelector(".myio-ho-card__eff");
9246
- if (state5.enableSelection) {
9246
+ if (state6.enableSelection) {
9247
9247
  const checkbox = root.querySelector('.myio-ho-card__select input[type="checkbox"]');
9248
9248
  if (checkbox) {
9249
9249
  checkbox.checked = !!isSelected;
@@ -9291,8 +9291,8 @@
9291
9291
  statusDot.className = `status-dot ${dotClass}`;
9292
9292
  }
9293
9293
  }
9294
- function bindEvents(root, state5, callbacks) {
9295
- const { entityObject } = state5;
9294
+ function bindEvents(root, state6, callbacks) {
9295
+ const { entityObject } = state6;
9296
9296
  const kebabBtn = root.querySelector(".myio-ho-card__kebab");
9297
9297
  const menu = root.querySelector(".myio-ho-card__menu");
9298
9298
  function toggleMenu() {
@@ -9348,9 +9348,9 @@
9348
9348
  const onSelectionChange = () => {
9349
9349
  const selectedIds = MyIOSelectionStore2.getSelectedIds();
9350
9350
  const isSelected = selectedIds.includes(entityObject.entityId);
9351
- if (state5.isSelected !== isSelected) {
9352
- state5.isSelected = isSelected;
9353
- paint(root, state5);
9351
+ if (state6.isSelected !== isSelected) {
9352
+ state6.isSelected = isSelected;
9353
+ paint(root, state6);
9354
9354
  }
9355
9355
  };
9356
9356
  MyIOSelectionStore2.on("selection:change", onSelectionChange);
@@ -9363,7 +9363,7 @@
9363
9363
  clearTimeout(TempRangeTooltip._hideTimer);
9364
9364
  TempRangeTooltip._hideTimer = null;
9365
9365
  }
9366
- TempRangeTooltip.show(root, state5.entityObject, e);
9366
+ TempRangeTooltip.show(root, state6.entityObject, e);
9367
9367
  };
9368
9368
  const hideTooltip = () => {
9369
9369
  TempRangeTooltip._startDelayedHide();
@@ -9381,7 +9381,7 @@
9381
9381
  clearTimeout(EnergyRangeTooltip._hideTimer);
9382
9382
  EnergyRangeTooltip._hideTimer = null;
9383
9383
  }
9384
- EnergyRangeTooltip.show(root, state5.entityObject, e);
9384
+ EnergyRangeTooltip.show(root, state6.entityObject, e);
9385
9385
  };
9386
9386
  const hideEnergyTooltip = () => {
9387
9387
  EnergyRangeTooltip._startDelayedHide();
@@ -9433,7 +9433,7 @@
9433
9433
  }
9434
9434
  });
9435
9435
  }
9436
- if (state5.enableDragDrop) {
9436
+ if (state6.enableDragDrop) {
9437
9437
  root.addEventListener("dragstart", (e) => {
9438
9438
  root.classList.add("is-dragging");
9439
9439
  e.dataTransfer.setData("text/plain", entityObject.entityId);
@@ -9475,17 +9475,17 @@
9475
9475
  throw new Error("renderCardComponentHeadOffice: containerEl is required");
9476
9476
  }
9477
9477
  ensureCss();
9478
- const state5 = normalizeParams(params);
9479
- const root = buildDOM(state5);
9480
- state5.isSelected = params.isSelected || false;
9478
+ const state6 = normalizeParams(params);
9479
+ const root = buildDOM(state6);
9480
+ state6.isSelected = params.isSelected || false;
9481
9481
  containerEl.appendChild(root);
9482
- bindEvents(root, state5, state5.callbacks);
9483
- paint(root, state5);
9482
+ bindEvents(root, state6, state6.callbacks);
9483
+ paint(root, state6);
9484
9484
  return {
9485
9485
  update(next) {
9486
9486
  if (next) {
9487
- Object.assign(state5.entityObject, next);
9488
- paint(root, state5);
9487
+ Object.assign(state6.entityObject, next);
9488
+ paint(root, state6);
9489
9489
  }
9490
9490
  },
9491
9491
  destroy() {
@@ -11215,6 +11215,15 @@
11215
11215
  return { deviation: absDeviation, sign: "-", status: "below", statusText: "Abaixo da media", statusIcon: "\u{1F53B}" };
11216
11216
  }
11217
11217
  }
11218
+ function calculateRangeStatus(currentTemp, minTemp, maxTemp) {
11219
+ if (currentTemp >= minTemp && currentTemp <= maxTemp) {
11220
+ return { status: "ok", statusText: "Dentro da faixa", statusIcon: "\u2713" };
11221
+ } else if (currentTemp > maxTemp) {
11222
+ return { status: "above", statusText: "Acima da faixa", statusIcon: "\u{1F53A}" };
11223
+ } else {
11224
+ return { status: "below", statusText: "Abaixo da faixa", statusIcon: "\u{1F53B}" };
11225
+ }
11226
+ }
11218
11227
  function calcTempBarPosition(temp, minRange, maxRange) {
11219
11228
  const rangeSize = maxRange - minRange;
11220
11229
  const extendedMin = minRange - rangeSize * 0.3;
@@ -11257,8 +11266,10 @@
11257
11266
  const { device, average, lastUpdated } = data;
11258
11267
  const timestamp = formatTimestamp2(lastUpdated);
11259
11268
  const tempStatus = calculateTempStatus(device.currentTemp, average.value);
11269
+ const rangeStatus = calculateRangeStatus(device.currentTemp, device.minTemp, device.maxTemp);
11260
11270
  const deviceBarPos = calcTempBarPosition(device.currentTemp, device.minTemp, device.maxTemp);
11261
11271
  const avgBarPos = calcTempBarPosition(average.value, device.minTemp, device.maxTemp);
11272
+ const rangeStatusClass = rangeStatus.status === "ok" ? "normal" : rangeStatus.status;
11262
11273
  return `
11263
11274
  <div class="myio-temp-comparison-tooltip__body">
11264
11275
  <!-- Main Stats -->
@@ -11273,12 +11284,18 @@
11273
11284
  </div>
11274
11285
  </div>
11275
11286
 
11276
- <!-- Configured Range -->
11287
+ <!-- Configured Range with Status -->
11277
11288
  <div class="myio-temp-comparison-tooltip__range">
11278
11289
  <span class="myio-temp-comparison-tooltip__range-label">Faixa Ideal:</span>
11279
11290
  <span class="myio-temp-comparison-tooltip__range-value">${formatTemp(device.minTemp)} - ${formatTemp(device.maxTemp)}</span>
11280
11291
  </div>
11281
11292
 
11293
+ <!-- Range Status Indicator -->
11294
+ <div class="myio-temp-comparison-tooltip__status ${rangeStatusClass}">
11295
+ <span class="myio-temp-comparison-tooltip__status-icon">${rangeStatus.statusIcon}</span>
11296
+ <span>${rangeStatus.statusText}</span>
11297
+ </div>
11298
+
11282
11299
  <!-- Section: Average Comparison -->
11283
11300
  <div class="myio-temp-comparison-tooltip__section-title">
11284
11301
  <span class="myio-temp-comparison-tooltip__section-icon">\u{1F4CA}</span>
@@ -30614,7 +30631,7 @@
30614
30631
  const defaultDateRange = getTodaySoFar();
30615
30632
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
30616
30633
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
30617
- const state5 = {
30634
+ const state6 = {
30618
30635
  token: params.token,
30619
30636
  deviceId: params.deviceId,
30620
30637
  label: params.label || "Sensor de Temperatura",
@@ -30637,58 +30654,58 @@
30637
30654
  };
30638
30655
  const savedGranularity = localStorage.getItem("myio-temp-modal-granularity");
30639
30656
  const savedTheme = localStorage.getItem("myio-temp-modal-theme");
30640
- if (savedGranularity) state5.granularity = savedGranularity;
30641
- if (savedTheme) state5.theme = savedTheme;
30657
+ if (savedGranularity) state6.granularity = savedGranularity;
30658
+ if (savedTheme) state6.theme = savedTheme;
30642
30659
  const modalContainer = document.createElement("div");
30643
30660
  modalContainer.id = modalId;
30644
30661
  document.body.appendChild(modalContainer);
30645
- renderModal(modalContainer, state5, modalId);
30662
+ renderModal(modalContainer, state6, modalId);
30646
30663
  try {
30647
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
30648
- state5.stats = calculateStats(state5.data, state5.clampRange);
30649
- state5.isLoading = false;
30650
- renderModal(modalContainer, state5, modalId);
30651
- drawChart(modalId, state5);
30664
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
30665
+ state6.stats = calculateStats(state6.data, state6.clampRange);
30666
+ state6.isLoading = false;
30667
+ renderModal(modalContainer, state6, modalId);
30668
+ drawChart(modalId, state6);
30652
30669
  } catch (error) {
30653
30670
  console.error("[TemperatureModal] Error fetching data:", error);
30654
- state5.isLoading = false;
30655
- renderModal(modalContainer, state5, modalId, error);
30671
+ state6.isLoading = false;
30672
+ renderModal(modalContainer, state6, modalId, error);
30656
30673
  }
30657
- await setupEventListeners(modalContainer, state5, modalId, params.onClose);
30674
+ await setupEventListeners(modalContainer, state6, modalId, params.onClose);
30658
30675
  return {
30659
30676
  destroy: () => {
30660
30677
  modalContainer.remove();
30661
30678
  params.onClose?.();
30662
30679
  },
30663
30680
  updateData: async (startDate, endDate, granularity) => {
30664
- state5.startTs = new Date(startDate).getTime();
30665
- state5.endTs = new Date(endDate).getTime();
30666
- if (granularity) state5.granularity = granularity;
30667
- state5.isLoading = true;
30668
- renderModal(modalContainer, state5, modalId);
30681
+ state6.startTs = new Date(startDate).getTime();
30682
+ state6.endTs = new Date(endDate).getTime();
30683
+ if (granularity) state6.granularity = granularity;
30684
+ state6.isLoading = true;
30685
+ renderModal(modalContainer, state6, modalId);
30669
30686
  try {
30670
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
30671
- state5.stats = calculateStats(state5.data, state5.clampRange);
30672
- state5.isLoading = false;
30673
- renderModal(modalContainer, state5, modalId);
30674
- drawChart(modalId, state5);
30687
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
30688
+ state6.stats = calculateStats(state6.data, state6.clampRange);
30689
+ state6.isLoading = false;
30690
+ renderModal(modalContainer, state6, modalId);
30691
+ drawChart(modalId, state6);
30675
30692
  } catch (error) {
30676
30693
  console.error("[TemperatureModal] Error updating data:", error);
30677
- state5.isLoading = false;
30678
- renderModal(modalContainer, state5, modalId, error);
30694
+ state6.isLoading = false;
30695
+ renderModal(modalContainer, state6, modalId, error);
30679
30696
  }
30680
30697
  }
30681
30698
  };
30682
30699
  }
30683
- function renderModal(container, state5, modalId, error) {
30684
- const colors = getThemeColors(state5.theme);
30685
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale);
30686
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale);
30687
- const statusText = state5.temperatureStatus === "ok" ? "Dentro da faixa" : state5.temperatureStatus === "above" ? "Acima do limite" : state5.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
30688
- const statusColor = state5.temperatureStatus === "ok" ? colors.success : state5.temperatureStatus === "above" ? colors.danger : state5.temperatureStatus === "below" ? colors.primary : colors.textMuted;
30689
- const rangeText = state5.temperatureMin !== null && state5.temperatureMax !== null ? `${state5.temperatureMin}\xB0C - ${state5.temperatureMax}\xB0C` : "N\xE3o definida";
30690
- new Date(state5.startTs).toISOString().slice(0, 16);
30691
- new Date(state5.endTs).toISOString().slice(0, 16);
30700
+ function renderModal(container, state6, modalId, error) {
30701
+ const colors = getThemeColors(state6.theme);
30702
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale);
30703
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale);
30704
+ const statusText = state6.temperatureStatus === "ok" ? "Dentro da faixa" : state6.temperatureStatus === "above" ? "Acima do limite" : state6.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
30705
+ const statusColor = state6.temperatureStatus === "ok" ? colors.success : state6.temperatureStatus === "above" ? colors.danger : state6.temperatureStatus === "below" ? colors.primary : colors.textMuted;
30706
+ const rangeText = state6.temperatureMin !== null && state6.temperatureMax !== null ? `${state6.temperatureMin}\xB0C - ${state6.temperatureMax}\xB0C` : "N\xE3o definida";
30707
+ new Date(state6.startTs).toISOString().slice(0, 16);
30708
+ new Date(state6.endTs).toISOString().slice(0, 16);
30692
30709
  const isMaximized = container.__isMaximized || false;
30693
30710
  const contentMaxWidth = isMaximized ? "100%" : "900px";
30694
30711
  const contentMaxHeight = isMaximized ? "100vh" : "95vh";
@@ -30715,7 +30732,7 @@
30715
30732
  min-height: 20px;
30716
30733
  ">
30717
30734
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
30718
- \u{1F321}\uFE0F ${state5.label} - Hist\xF3rico de Temperatura
30735
+ \u{1F321}\uFE0F ${state6.label} - Hist\xF3rico de Temperatura
30719
30736
  </h2>
30720
30737
  <div style="display: flex; gap: 4px; align-items: center;">
30721
30738
  <!-- Theme Toggle -->
@@ -30723,7 +30740,7 @@
30723
30740
  background: none; border: none; font-size: 16px; cursor: pointer;
30724
30741
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
30725
30742
  transition: background-color 0.2s;
30726
- ">${state5.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
30743
+ ">${state6.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
30727
30744
  <!-- Maximize Button -->
30728
30745
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
30729
30746
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -30745,7 +30762,7 @@
30745
30762
  <!-- Controls Row -->
30746
30763
  <div style="
30747
30764
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
30748
- margin-bottom: 16px; padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
30765
+ margin-bottom: 16px; padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
30749
30766
  border-radius: 6px; border: 1px solid ${colors.border};
30750
30767
  ">
30751
30768
  <!-- Granularity Select -->
@@ -30758,8 +30775,8 @@
30758
30775
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
30759
30776
  cursor: pointer; min-width: 130px;
30760
30777
  ">
30761
- <option value="hour" ${state5.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
30762
- <option value="day" ${state5.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
30778
+ <option value="hour" ${state6.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
30779
+ <option value="day" ${state6.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
30763
30780
  </select>
30764
30781
  </div>
30765
30782
  <!-- Day Period Filter (Multiselect) -->
@@ -30773,7 +30790,7 @@
30773
30790
  cursor: pointer; min-width: 180px; text-align: left;
30774
30791
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
30775
30792
  ">
30776
- <span>${getSelectedPeriodsLabel(state5.selectedPeriods)}</span>
30793
+ <span>${getSelectedPeriodsLabel(state6.selectedPeriods)}</span>
30777
30794
  <span style="font-size: 10px;">\u25BC</span>
30778
30795
  </button>
30779
30796
  <div id="${modalId}-period-dropdown" style="
@@ -30786,12 +30803,12 @@
30786
30803
  <label style="
30787
30804
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
30788
30805
  cursor: pointer; font-size: 13px; color: ${colors.text};
30789
- " onmouseover="this.style.background='${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
30806
+ " onmouseover="this.style.background='${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
30790
30807
  onmouseout="this.style.background='transparent'">
30791
30808
  <input type="checkbox"
30792
30809
  name="${modalId}-period"
30793
30810
  value="${period.id}"
30794
- ${state5.selectedPeriods.includes(period.id) ? "checked" : ""}
30811
+ ${state6.selectedPeriods.includes(period.id) ? "checked" : ""}
30795
30812
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
30796
30813
  ${period.label}
30797
30814
  </label>
@@ -30799,13 +30816,13 @@
30799
30816
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
30800
30817
  <button id="${modalId}-period-select-all" type="button" style="
30801
30818
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
30802
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
30819
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
30803
30820
  border: none; border-radius: 4px; cursor: pointer;
30804
30821
  font-size: 12px; color: ${colors.text};
30805
30822
  ">Selecionar Todos</button>
30806
30823
  <button id="${modalId}-period-clear" type="button" style="
30807
30824
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
30808
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
30825
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
30809
30826
  border: none; border-radius: 4px; cursor: pointer;
30810
30827
  font-size: 12px; color: ${colors.text};
30811
30828
  ">Limpar Sele\xE7\xE3o</button>
@@ -30830,8 +30847,8 @@
30830
30847
  font-size: 14px; font-weight: 500; height: 38px;
30831
30848
  display: flex; align-items: center; gap: 8px;
30832
30849
  font-family: 'Roboto', Arial, sans-serif;
30833
- " ${state5.isLoading ? "disabled" : ""}>
30834
- ${state5.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
30850
+ " ${state6.isLoading ? "disabled" : ""}>
30851
+ ${state6.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
30835
30852
  </button>
30836
30853
  </div>
30837
30854
 
@@ -30842,40 +30859,40 @@
30842
30859
  ">
30843
30860
  <!-- Current Temperature -->
30844
30861
  <div style="
30845
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30862
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30846
30863
  border-radius: 12px; border: 1px solid ${colors.border};
30847
30864
  ">
30848
30865
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Temperatura Atual</span>
30849
30866
  <div style="font-weight: 700; font-size: 24px; color: ${statusColor}; margin-top: 4px;">
30850
- ${state5.currentTemperature !== null ? formatTemperature(state5.currentTemperature) : "N/A"}
30867
+ ${state6.currentTemperature !== null ? formatTemperature(state6.currentTemperature) : "N/A"}
30851
30868
  </div>
30852
30869
  <div style="font-size: 11px; color: ${statusColor}; margin-top: 2px;">${statusText}</div>
30853
30870
  </div>
30854
30871
  <!-- Average -->
30855
30872
  <div style="
30856
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30873
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30857
30874
  border-radius: 12px; border: 1px solid ${colors.border};
30858
30875
  ">
30859
30876
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">M\xE9dia do Per\xEDodo</span>
30860
30877
  <div id="${modalId}-avg" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
30861
- ${state5.stats.count > 0 ? formatTemperature(state5.stats.avg) : "N/A"}
30878
+ ${state6.stats.count > 0 ? formatTemperature(state6.stats.avg) : "N/A"}
30862
30879
  </div>
30863
30880
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${startDateStr} - ${endDateStr}</div>
30864
30881
  </div>
30865
30882
  <!-- Min/Max -->
30866
30883
  <div style="
30867
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30884
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30868
30885
  border-radius: 12px; border: 1px solid ${colors.border};
30869
30886
  ">
30870
30887
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Min / Max</span>
30871
30888
  <div id="${modalId}-minmax" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
30872
- ${state5.stats.count > 0 ? `${formatTemperature(state5.stats.min)} / ${formatTemperature(state5.stats.max)}` : "N/A"}
30889
+ ${state6.stats.count > 0 ? `${formatTemperature(state6.stats.min)} / ${formatTemperature(state6.stats.max)}` : "N/A"}
30873
30890
  </div>
30874
- <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state5.stats.count} leituras</div>
30891
+ <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state6.stats.count} leituras</div>
30875
30892
  </div>
30876
30893
  <!-- Ideal Range -->
30877
30894
  <div style="
30878
- padding: 16px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30895
+ padding: 16px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
30879
30896
  border-radius: 12px; border: 1px solid ${colors.border};
30880
30897
  ">
30881
30898
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Faixa Ideal</span>
@@ -30891,18 +30908,18 @@
30891
30908
  Hist\xF3rico de Temperatura
30892
30909
  </h3>
30893
30910
  <div id="${modalId}-chart" style="
30894
- height: 320px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
30911
+ height: 320px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
30895
30912
  border-radius: 12px; display: flex; justify-content: center; align-items: center;
30896
30913
  border: 1px solid ${colors.border}; position: relative;
30897
30914
  ">
30898
- ${state5.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
30915
+ ${state6.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
30899
30916
  <div style="animation: spin 1s linear infinite; font-size: 32px; margin-bottom: 8px;">\u21BB</div>
30900
30917
  <div>Carregando dados...</div>
30901
30918
  </div>` : error ? `<div style="text-align: center; color: ${colors.danger};">
30902
30919
  <div style="font-size: 32px; margin-bottom: 8px;">\u26A0\uFE0F</div>
30903
30920
  <div>Erro ao carregar dados</div>
30904
30921
  <div style="font-size: 12px; margin-top: 4px;">${error.message}</div>
30905
- </div>` : state5.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
30922
+ </div>` : state6.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
30906
30923
  <div style="font-size: 32px; margin-bottom: 8px;">\u{1F4ED}</div>
30907
30924
  <div>Sem dados para o per\xEDodo selecionado</div>
30908
30925
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -30912,12 +30929,12 @@
30912
30929
  <!-- Actions -->
30913
30930
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
30914
30931
  <button id="${modalId}-export" style="
30915
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
30932
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
30916
30933
  color: ${colors.text}; border: 1px solid ${colors.border};
30917
30934
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
30918
30935
  font-size: 14px; display: flex; align-items: center; gap: 8px;
30919
30936
  font-family: 'Roboto', Arial, sans-serif;
30920
- " ${state5.data.length === 0 ? "disabled" : ""}>
30937
+ " ${state6.data.length === 0 ? "disabled" : ""}>
30921
30938
  \u{1F4E5} Exportar CSV
30922
30939
  </button>
30923
30940
  <button id="${modalId}-close-btn" style="
@@ -30968,14 +30985,14 @@
30968
30985
  </style>
30969
30986
  `;
30970
30987
  }
30971
- function drawChart(modalId, state5) {
30988
+ function drawChart(modalId, state6) {
30972
30989
  const chartContainer = document.getElementById(`${modalId}-chart`);
30973
30990
  const canvas = document.getElementById(`${modalId}-canvas`);
30974
- if (!chartContainer || !canvas || state5.data.length === 0) return;
30991
+ if (!chartContainer || !canvas || state6.data.length === 0) return;
30975
30992
  const ctx = canvas.getContext("2d");
30976
30993
  if (!ctx) return;
30977
- const colors = getThemeColors(state5.theme);
30978
- const filteredData = filterByDayPeriods(state5.data, state5.selectedPeriods);
30994
+ const colors = getThemeColors(state6.theme);
30995
+ const filteredData = filterByDayPeriods(state6.data, state6.selectedPeriods);
30979
30996
  if (filteredData.length === 0) {
30980
30997
  canvas.width = chartContainer.clientWidth;
30981
30998
  canvas.height = chartContainer.clientHeight;
@@ -30986,14 +31003,14 @@
30986
31003
  return;
30987
31004
  }
30988
31005
  let chartData;
30989
- if (state5.granularity === "hour") {
31006
+ if (state6.granularity === "hour") {
30990
31007
  const interpolated = interpolateTemperature(filteredData, {
30991
31008
  intervalMinutes: 30,
30992
- startTs: state5.startTs,
30993
- endTs: state5.endTs,
30994
- clampRange: state5.clampRange
31009
+ startTs: state6.startTs,
31010
+ endTs: state6.endTs,
31011
+ clampRange: state6.clampRange
30995
31012
  });
30996
- const filteredInterpolated = filterByDayPeriods(interpolated, state5.selectedPeriods);
31013
+ const filteredInterpolated = filterByDayPeriods(interpolated, state6.selectedPeriods);
30997
31014
  chartData = filteredInterpolated.map((item) => ({
30998
31015
  x: item.ts,
30999
31016
  y: Number(item.value),
@@ -31001,7 +31018,7 @@
31001
31018
  screenY: 0
31002
31019
  }));
31003
31020
  } else {
31004
- const daily = aggregateByDay(filteredData, state5.clampRange);
31021
+ const daily = aggregateByDay(filteredData, state6.clampRange);
31005
31022
  chartData = daily.map((item) => ({
31006
31023
  x: item.dateTs,
31007
31024
  y: item.avg,
@@ -31019,12 +31036,12 @@
31019
31036
  const paddingRight = 20;
31020
31037
  const paddingTop = 20;
31021
31038
  const paddingBottom = 55;
31022
- const isPeriodsFiltered = state5.selectedPeriods.length < 4 && state5.selectedPeriods.length > 0;
31039
+ const isPeriodsFiltered = state6.selectedPeriods.length < 4 && state6.selectedPeriods.length > 0;
31023
31040
  const values = chartData.map((d) => d.y);
31024
31041
  const dataMin = Math.min(...values);
31025
31042
  const dataMax = Math.max(...values);
31026
- const thresholdMin = state5.temperatureMin !== null ? state5.temperatureMin : dataMin;
31027
- const thresholdMax = state5.temperatureMax !== null ? state5.temperatureMax : dataMax;
31043
+ const thresholdMin = state6.temperatureMin !== null ? state6.temperatureMin : dataMin;
31044
+ const thresholdMax = state6.temperatureMax !== null ? state6.temperatureMax : dataMax;
31028
31045
  const minY = Math.min(dataMin, thresholdMin) - 1;
31029
31046
  const maxY = Math.max(dataMax, thresholdMax) + 1;
31030
31047
  const chartWidth = width - paddingLeft - paddingRight;
@@ -31056,9 +31073,9 @@
31056
31073
  ctx.lineTo(width - paddingRight, y);
31057
31074
  ctx.stroke();
31058
31075
  }
31059
- if (state5.temperatureMin !== null && state5.temperatureMax !== null) {
31060
- const rangeMinY = height - paddingBottom - (state5.temperatureMin - minY) * scaleY;
31061
- const rangeMaxY = height - paddingBottom - (state5.temperatureMax - minY) * scaleY;
31076
+ if (state6.temperatureMin !== null && state6.temperatureMax !== null) {
31077
+ const rangeMinY = height - paddingBottom - (state6.temperatureMin - minY) * scaleY;
31078
+ const rangeMaxY = height - paddingBottom - (state6.temperatureMax - minY) * scaleY;
31062
31079
  ctx.fillStyle = "rgba(76, 175, 80, 0.1)";
31063
31080
  ctx.fillRect(paddingLeft, rangeMaxY, chartWidth, rangeMinY - rangeMaxY);
31064
31081
  ctx.strokeStyle = colors.success;
@@ -31100,10 +31117,10 @@
31100
31117
  const point = chartData[i];
31101
31118
  const date = new Date(point.x);
31102
31119
  let label;
31103
- if (state5.granularity === "hour") {
31104
- label = date.toLocaleTimeString(state5.locale, { hour: "2-digit", minute: "2-digit" });
31120
+ if (state6.granularity === "hour") {
31121
+ label = date.toLocaleTimeString(state6.locale, { hour: "2-digit", minute: "2-digit" });
31105
31122
  } else {
31106
- label = date.toLocaleDateString(state5.locale, { day: "2-digit", month: "2-digit" });
31123
+ label = date.toLocaleDateString(state6.locale, { day: "2-digit", month: "2-digit" });
31107
31124
  }
31108
31125
  ctx.strokeStyle = colors.chartGrid;
31109
31126
  ctx.lineWidth = 1;
@@ -31121,16 +31138,16 @@
31121
31138
  ctx.lineTo(paddingLeft, height - paddingBottom);
31122
31139
  ctx.lineTo(width - paddingRight, height - paddingBottom);
31123
31140
  ctx.stroke();
31124
- setupChartTooltip(canvas, chartContainer, chartData, state5, colors);
31141
+ setupChartTooltip(canvas, chartContainer, chartData, state6, colors);
31125
31142
  }
31126
- function setupChartTooltip(canvas, container, chartData, state5, colors) {
31143
+ function setupChartTooltip(canvas, container, chartData, state6, colors) {
31127
31144
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
31128
31145
  if (existingTooltip) existingTooltip.remove();
31129
31146
  const tooltip = document.createElement("div");
31130
31147
  tooltip.className = "myio-chart-tooltip";
31131
31148
  tooltip.style.cssText = `
31132
31149
  position: absolute;
31133
- background: ${state5.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31150
+ background: ${state6.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31134
31151
  border: 1px solid ${colors.border};
31135
31152
  border-radius: 8px;
31136
31153
  padding: 10px 14px;
@@ -31167,17 +31184,17 @@
31167
31184
  if (point) {
31168
31185
  const date = new Date(point.x);
31169
31186
  let dateStr;
31170
- if (state5.granularity === "hour") {
31171
- dateStr = date.toLocaleDateString(state5.locale, {
31187
+ if (state6.granularity === "hour") {
31188
+ dateStr = date.toLocaleDateString(state6.locale, {
31172
31189
  day: "2-digit",
31173
31190
  month: "2-digit",
31174
31191
  year: "numeric"
31175
- }) + " " + date.toLocaleTimeString(state5.locale, {
31192
+ }) + " " + date.toLocaleTimeString(state6.locale, {
31176
31193
  hour: "2-digit",
31177
31194
  minute: "2-digit"
31178
31195
  });
31179
31196
  } else {
31180
- dateStr = date.toLocaleDateString(state5.locale, {
31197
+ dateStr = date.toLocaleDateString(state6.locale, {
31181
31198
  day: "2-digit",
31182
31199
  month: "2-digit",
31183
31200
  year: "numeric"
@@ -31215,7 +31232,7 @@
31215
31232
  canvas.style.cursor = "default";
31216
31233
  });
31217
31234
  }
31218
- async function setupEventListeners(container, state5, modalId, onClose) {
31235
+ async function setupEventListeners(container, state6, modalId, onClose) {
31219
31236
  const closeModal = () => {
31220
31237
  container.remove();
31221
31238
  onClose?.();
@@ -31226,19 +31243,19 @@
31226
31243
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
31227
31244
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
31228
31245
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
31229
- if (dateRangeInput && !state5.dateRangePicker) {
31246
+ if (dateRangeInput && !state6.dateRangePicker) {
31230
31247
  try {
31231
- state5.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
31232
- presetStart: new Date(state5.startTs).toISOString(),
31233
- presetEnd: new Date(state5.endTs).toISOString(),
31248
+ state6.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
31249
+ presetStart: new Date(state6.startTs).toISOString(),
31250
+ presetEnd: new Date(state6.endTs).toISOString(),
31234
31251
  includeTime: true,
31235
31252
  timePrecision: "minute",
31236
31253
  maxRangeDays: 90,
31237
- locale: state5.locale,
31254
+ locale: state6.locale,
31238
31255
  parentEl: container.querySelector(".myio-temp-modal-content"),
31239
31256
  onApply: (result) => {
31240
- state5.startTs = new Date(result.startISO).getTime();
31241
- state5.endTs = new Date(result.endISO).getTime();
31257
+ state6.startTs = new Date(result.startISO).getTime();
31258
+ state6.endTs = new Date(result.endISO).getTime();
31242
31259
  console.log("[TemperatureModal] Date range applied:", result);
31243
31260
  }
31244
31261
  });
@@ -31247,19 +31264,19 @@
31247
31264
  }
31248
31265
  }
31249
31266
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
31250
- state5.theme = state5.theme === "dark" ? "light" : "dark";
31251
- localStorage.setItem("myio-temp-modal-theme", state5.theme);
31252
- state5.dateRangePicker = null;
31253
- renderModal(container, state5, modalId);
31254
- if (state5.data.length > 0) drawChart(modalId, state5);
31255
- await setupEventListeners(container, state5, modalId, onClose);
31267
+ state6.theme = state6.theme === "dark" ? "light" : "dark";
31268
+ localStorage.setItem("myio-temp-modal-theme", state6.theme);
31269
+ state6.dateRangePicker = null;
31270
+ renderModal(container, state6, modalId);
31271
+ if (state6.data.length > 0) drawChart(modalId, state6);
31272
+ await setupEventListeners(container, state6, modalId, onClose);
31256
31273
  });
31257
31274
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
31258
31275
  container.__isMaximized = !container.__isMaximized;
31259
- state5.dateRangePicker = null;
31260
- renderModal(container, state5, modalId);
31261
- if (state5.data.length > 0) drawChart(modalId, state5);
31262
- await setupEventListeners(container, state5, modalId, onClose);
31276
+ state6.dateRangePicker = null;
31277
+ renderModal(container, state6, modalId);
31278
+ if (state6.data.length > 0) drawChart(modalId, state6);
31279
+ await setupEventListeners(container, state6, modalId, onClose);
31263
31280
  });
31264
31281
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
31265
31282
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -31278,71 +31295,71 @@
31278
31295
  periodCheckboxes.forEach((checkbox) => {
31279
31296
  checkbox.addEventListener("change", () => {
31280
31297
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
31281
- state5.selectedPeriods = checked;
31298
+ state6.selectedPeriods = checked;
31282
31299
  const btnLabel = periodBtn?.querySelector("span:first-child");
31283
31300
  if (btnLabel) {
31284
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31301
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31285
31302
  }
31286
- if (state5.data.length > 0) drawChart(modalId, state5);
31303
+ if (state6.data.length > 0) drawChart(modalId, state6);
31287
31304
  });
31288
31305
  });
31289
31306
  document.getElementById(`${modalId}-period-select-all`)?.addEventListener("click", () => {
31290
31307
  periodCheckboxes.forEach((cb) => {
31291
31308
  cb.checked = true;
31292
31309
  });
31293
- state5.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
31310
+ state6.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
31294
31311
  const btnLabel = periodBtn?.querySelector("span:first-child");
31295
31312
  if (btnLabel) {
31296
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31313
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31297
31314
  }
31298
- if (state5.data.length > 0) drawChart(modalId, state5);
31315
+ if (state6.data.length > 0) drawChart(modalId, state6);
31299
31316
  });
31300
31317
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
31301
31318
  periodCheckboxes.forEach((cb) => {
31302
31319
  cb.checked = false;
31303
31320
  });
31304
- state5.selectedPeriods = [];
31321
+ state6.selectedPeriods = [];
31305
31322
  const btnLabel = periodBtn?.querySelector("span:first-child");
31306
31323
  if (btnLabel) {
31307
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
31324
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
31308
31325
  }
31309
- if (state5.data.length > 0) drawChart(modalId, state5);
31326
+ if (state6.data.length > 0) drawChart(modalId, state6);
31310
31327
  });
31311
31328
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
31312
- state5.granularity = e.target.value;
31313
- localStorage.setItem("myio-temp-modal-granularity", state5.granularity);
31314
- if (state5.data.length > 0) drawChart(modalId, state5);
31329
+ state6.granularity = e.target.value;
31330
+ localStorage.setItem("myio-temp-modal-granularity", state6.granularity);
31331
+ if (state6.data.length > 0) drawChart(modalId, state6);
31315
31332
  });
31316
31333
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
31317
- if (state5.startTs >= state5.endTs) {
31334
+ if (state6.startTs >= state6.endTs) {
31318
31335
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
31319
31336
  return;
31320
31337
  }
31321
- state5.isLoading = true;
31322
- state5.dateRangePicker = null;
31323
- renderModal(container, state5, modalId);
31338
+ state6.isLoading = true;
31339
+ state6.dateRangePicker = null;
31340
+ renderModal(container, state6, modalId);
31324
31341
  try {
31325
- state5.data = await fetchTemperatureData(state5.token, state5.deviceId, state5.startTs, state5.endTs);
31326
- state5.stats = calculateStats(state5.data, state5.clampRange);
31327
- state5.isLoading = false;
31328
- renderModal(container, state5, modalId);
31329
- drawChart(modalId, state5);
31330
- await setupEventListeners(container, state5, modalId, onClose);
31342
+ state6.data = await fetchTemperatureData(state6.token, state6.deviceId, state6.startTs, state6.endTs);
31343
+ state6.stats = calculateStats(state6.data, state6.clampRange);
31344
+ state6.isLoading = false;
31345
+ renderModal(container, state6, modalId);
31346
+ drawChart(modalId, state6);
31347
+ await setupEventListeners(container, state6, modalId, onClose);
31331
31348
  } catch (error) {
31332
31349
  console.error("[TemperatureModal] Error fetching data:", error);
31333
- state5.isLoading = false;
31334
- renderModal(container, state5, modalId, error);
31335
- await setupEventListeners(container, state5, modalId, onClose);
31350
+ state6.isLoading = false;
31351
+ renderModal(container, state6, modalId, error);
31352
+ await setupEventListeners(container, state6, modalId, onClose);
31336
31353
  }
31337
31354
  });
31338
31355
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
31339
- if (state5.data.length === 0) return;
31340
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
31341
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
31356
+ if (state6.data.length === 0) return;
31357
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
31358
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
31342
31359
  exportTemperatureCSV(
31343
- state5.data,
31344
- state5.label,
31345
- state5.stats,
31360
+ state6.data,
31361
+ state6.label,
31362
+ state6.stats,
31346
31363
  startDateStr,
31347
31364
  endDateStr
31348
31365
  );
@@ -31355,7 +31372,7 @@
31355
31372
  const defaultDateRange = getTodaySoFar();
31356
31373
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
31357
31374
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
31358
- const state5 = {
31375
+ const state6 = {
31359
31376
  token: params.token,
31360
31377
  devices: params.devices,
31361
31378
  startTs,
@@ -31374,44 +31391,44 @@
31374
31391
  };
31375
31392
  const savedGranularity = localStorage.getItem("myio-temp-comparison-granularity");
31376
31393
  const savedTheme = localStorage.getItem("myio-temp-comparison-theme");
31377
- if (savedGranularity) state5.granularity = savedGranularity;
31378
- if (savedTheme) state5.theme = savedTheme;
31394
+ if (savedGranularity) state6.granularity = savedGranularity;
31395
+ if (savedTheme) state6.theme = savedTheme;
31379
31396
  const modalContainer = document.createElement("div");
31380
31397
  modalContainer.id = modalId;
31381
31398
  document.body.appendChild(modalContainer);
31382
- renderModal2(modalContainer, state5, modalId);
31383
- await fetchAllDevicesData(state5);
31384
- renderModal2(modalContainer, state5, modalId);
31385
- drawComparisonChart(modalId, state5);
31386
- await setupEventListeners2(modalContainer, state5, modalId, params.onClose);
31399
+ renderModal2(modalContainer, state6, modalId);
31400
+ await fetchAllDevicesData(state6);
31401
+ renderModal2(modalContainer, state6, modalId);
31402
+ drawComparisonChart(modalId, state6);
31403
+ await setupEventListeners2(modalContainer, state6, modalId, params.onClose);
31387
31404
  return {
31388
31405
  destroy: () => {
31389
31406
  modalContainer.remove();
31390
31407
  params.onClose?.();
31391
31408
  },
31392
31409
  updateData: async (startDate, endDate, granularity) => {
31393
- state5.startTs = new Date(startDate).getTime();
31394
- state5.endTs = new Date(endDate).getTime();
31395
- if (granularity) state5.granularity = granularity;
31396
- state5.isLoading = true;
31397
- renderModal2(modalContainer, state5, modalId);
31398
- await fetchAllDevicesData(state5);
31399
- renderModal2(modalContainer, state5, modalId);
31400
- drawComparisonChart(modalId, state5);
31401
- setupEventListeners2(modalContainer, state5, modalId, params.onClose);
31410
+ state6.startTs = new Date(startDate).getTime();
31411
+ state6.endTs = new Date(endDate).getTime();
31412
+ if (granularity) state6.granularity = granularity;
31413
+ state6.isLoading = true;
31414
+ renderModal2(modalContainer, state6, modalId);
31415
+ await fetchAllDevicesData(state6);
31416
+ renderModal2(modalContainer, state6, modalId);
31417
+ drawComparisonChart(modalId, state6);
31418
+ setupEventListeners2(modalContainer, state6, modalId, params.onClose);
31402
31419
  }
31403
31420
  };
31404
31421
  }
31405
- async function fetchAllDevicesData(state5) {
31406
- state5.isLoading = true;
31407
- state5.deviceData = [];
31422
+ async function fetchAllDevicesData(state6) {
31423
+ state6.isLoading = true;
31424
+ state6.deviceData = [];
31408
31425
  try {
31409
31426
  const results = await Promise.all(
31410
- state5.devices.map(async (device, index) => {
31427
+ state6.devices.map(async (device, index) => {
31411
31428
  const deviceId = device.tbId || device.id;
31412
31429
  try {
31413
- const data = await fetchTemperatureData(state5.token, deviceId, state5.startTs, state5.endTs);
31414
- const stats = calculateStats(data, state5.clampRange);
31430
+ const data = await fetchTemperatureData(state6.token, deviceId, state6.startTs, state6.endTs);
31431
+ const stats = calculateStats(data, state6.clampRange);
31415
31432
  return {
31416
31433
  device,
31417
31434
  data,
@@ -31429,21 +31446,21 @@
31429
31446
  }
31430
31447
  })
31431
31448
  );
31432
- state5.deviceData = results;
31449
+ state6.deviceData = results;
31433
31450
  } catch (error) {
31434
31451
  console.error("[TemperatureComparisonModal] Error fetching data:", error);
31435
31452
  }
31436
- state5.isLoading = false;
31453
+ state6.isLoading = false;
31437
31454
  }
31438
- function renderModal2(container, state5, modalId) {
31439
- const colors = getThemeColors(state5.theme);
31440
- new Date(state5.startTs).toLocaleDateString(state5.locale);
31441
- new Date(state5.endTs).toLocaleDateString(state5.locale);
31442
- new Date(state5.startTs).toISOString().slice(0, 16);
31443
- new Date(state5.endTs).toISOString().slice(0, 16);
31444
- const legendHTML = state5.deviceData.map((dd) => `
31455
+ function renderModal2(container, state6, modalId) {
31456
+ const colors = getThemeColors(state6.theme);
31457
+ new Date(state6.startTs).toLocaleDateString(state6.locale);
31458
+ new Date(state6.endTs).toLocaleDateString(state6.locale);
31459
+ new Date(state6.startTs).toISOString().slice(0, 16);
31460
+ new Date(state6.endTs).toISOString().slice(0, 16);
31461
+ const legendHTML = state6.deviceData.map((dd) => `
31445
31462
  <div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px;
31446
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
31463
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
31447
31464
  border-radius: 8px;">
31448
31465
  <span style="width: 12px; height: 12px; border-radius: 50%; background: ${dd.color};"></span>
31449
31466
  <span style="color: ${colors.text}; font-size: 13px;">${dd.device.label}</span>
@@ -31452,9 +31469,9 @@
31452
31469
  </span>
31453
31470
  </div>
31454
31471
  `).join("");
31455
- const statsHTML = state5.deviceData.map((dd) => `
31472
+ const statsHTML = state6.deviceData.map((dd) => `
31456
31473
  <div style="
31457
- padding: 12px; background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31474
+ padding: 12px; background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
31458
31475
  border-radius: 10px; border-left: 4px solid ${dd.color};
31459
31476
  min-width: 150px;
31460
31477
  ">
@@ -31505,7 +31522,7 @@
31505
31522
  min-height: 20px;
31506
31523
  ">
31507
31524
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
31508
- \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state5.devices.length} sensores
31525
+ \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state6.devices.length} sensores
31509
31526
  </h2>
31510
31527
  <div style="display: flex; gap: 4px; align-items: center;">
31511
31528
  <!-- Theme Toggle -->
@@ -31513,7 +31530,7 @@
31513
31530
  background: none; border: none; font-size: 16px; cursor: pointer;
31514
31531
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
31515
31532
  transition: background-color 0.2s;
31516
- ">${state5.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
31533
+ ">${state6.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
31517
31534
  <!-- Maximize Button -->
31518
31535
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
31519
31536
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -31536,7 +31553,7 @@
31536
31553
  <div style="
31537
31554
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
31538
31555
  margin-bottom: 16px; padding: 16px;
31539
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
31556
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
31540
31557
  border-radius: 6px; border: 1px solid ${colors.border};
31541
31558
  ">
31542
31559
  <!-- Granularity Select -->
@@ -31549,8 +31566,8 @@
31549
31566
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
31550
31567
  cursor: pointer; min-width: 130px;
31551
31568
  ">
31552
- <option value="hour" ${state5.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
31553
- <option value="day" ${state5.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
31569
+ <option value="hour" ${state6.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
31570
+ <option value="day" ${state6.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
31554
31571
  </select>
31555
31572
  </div>
31556
31573
  <!-- Day Period Filter (Multiselect) -->
@@ -31564,7 +31581,7 @@
31564
31581
  cursor: pointer; min-width: 180px; text-align: left;
31565
31582
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
31566
31583
  ">
31567
- <span>${getSelectedPeriodsLabel(state5.selectedPeriods)}</span>
31584
+ <span>${getSelectedPeriodsLabel(state6.selectedPeriods)}</span>
31568
31585
  <span style="font-size: 10px;">\u25BC</span>
31569
31586
  </button>
31570
31587
  <div id="${modalId}-period-dropdown" style="
@@ -31577,12 +31594,12 @@
31577
31594
  <label style="
31578
31595
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
31579
31596
  cursor: pointer; font-size: 13px; color: ${colors.text};
31580
- " onmouseover="this.style.background='${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
31597
+ " onmouseover="this.style.background='${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
31581
31598
  onmouseout="this.style.background='transparent'">
31582
31599
  <input type="checkbox"
31583
31600
  name="${modalId}-period"
31584
31601
  value="${period.id}"
31585
- ${state5.selectedPeriods.includes(period.id) ? "checked" : ""}
31602
+ ${state6.selectedPeriods.includes(period.id) ? "checked" : ""}
31586
31603
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
31587
31604
  ${period.label}
31588
31605
  </label>
@@ -31590,13 +31607,13 @@
31590
31607
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
31591
31608
  <button id="${modalId}-period-select-all" type="button" style="
31592
31609
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
31593
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31610
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31594
31611
  border: none; border-radius: 4px; cursor: pointer;
31595
31612
  font-size: 12px; color: ${colors.text};
31596
31613
  ">Selecionar Todos</button>
31597
31614
  <button id="${modalId}-period-clear" type="button" style="
31598
31615
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
31599
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31616
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
31600
31617
  border: none; border-radius: 4px; cursor: pointer;
31601
31618
  font-size: 12px; color: ${colors.text};
31602
31619
  ">Limpar Sele\xE7\xE3o</button>
@@ -31621,8 +31638,8 @@
31621
31638
  font-size: 14px; font-weight: 500; height: 38px;
31622
31639
  display: flex; align-items: center; gap: 8px;
31623
31640
  font-family: 'Roboto', Arial, sans-serif;
31624
- " ${state5.isLoading ? "disabled" : ""}>
31625
- ${state5.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31641
+ " ${state6.isLoading ? "disabled" : ""}>
31642
+ ${state6.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
31626
31643
  </button>
31627
31644
  </div>
31628
31645
 
@@ -31638,14 +31655,14 @@
31638
31655
  <div style="margin-bottom: 24px;">
31639
31656
  <div id="${modalId}-chart" style="
31640
31657
  height: 380px;
31641
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
31658
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
31642
31659
  border-radius: 14px; display: flex; justify-content: center; align-items: center;
31643
31660
  border: 1px solid ${colors.border}; position: relative;
31644
31661
  ">
31645
- ${state5.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
31662
+ ${state6.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
31646
31663
  <div style="animation: spin 1s linear infinite; font-size: 36px; margin-bottom: 12px;">\u21BB</div>
31647
- <div style="font-size: 15px;">Carregando dados de ${state5.devices.length} sensores...</div>
31648
- </div>` : state5.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
31664
+ <div style="font-size: 15px;">Carregando dados de ${state6.devices.length} sensores...</div>
31665
+ </div>` : state6.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
31649
31666
  <div style="font-size: 48px; margin-bottom: 12px;">\u{1F4ED}</div>
31650
31667
  <div style="font-size: 16px;">Sem dados para o per\xEDodo selecionado</div>
31651
31668
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -31663,12 +31680,12 @@
31663
31680
  <!-- Actions -->
31664
31681
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
31665
31682
  <button id="${modalId}-export" style="
31666
- background: ${state5.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
31683
+ background: ${state6.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
31667
31684
  color: ${colors.text}; border: 1px solid ${colors.border};
31668
31685
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
31669
31686
  font-size: 14px; display: flex; align-items: center; gap: 8px;
31670
31687
  font-family: 'Roboto', Arial, sans-serif;
31671
- " ${state5.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
31688
+ " ${state6.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
31672
31689
  \u{1F4E5} Exportar CSV
31673
31690
  </button>
31674
31691
  <button id="${modalId}-close-btn" style="
@@ -31703,15 +31720,15 @@
31703
31720
  </style>
31704
31721
  `;
31705
31722
  }
31706
- function drawComparisonChart(modalId, state5) {
31723
+ function drawComparisonChart(modalId, state6) {
31707
31724
  const chartContainer = document.getElementById(`${modalId}-chart`);
31708
31725
  const canvas = document.getElementById(`${modalId}-canvas`);
31709
31726
  if (!chartContainer || !canvas) return;
31710
- const hasData = state5.deviceData.some((dd) => dd.data.length > 0);
31727
+ const hasData = state6.deviceData.some((dd) => dd.data.length > 0);
31711
31728
  if (!hasData) return;
31712
31729
  const ctx = canvas.getContext("2d");
31713
31730
  if (!ctx) return;
31714
- const colors = getThemeColors(state5.theme);
31731
+ const colors = getThemeColors(state6.theme);
31715
31732
  const width = chartContainer.clientWidth - 2;
31716
31733
  const height = 380;
31717
31734
  canvas.width = width;
@@ -31722,19 +31739,19 @@
31722
31739
  const paddingBottom = 55;
31723
31740
  ctx.clearRect(0, 0, width, height);
31724
31741
  const processedData = [];
31725
- state5.deviceData.forEach((dd) => {
31742
+ state6.deviceData.forEach((dd) => {
31726
31743
  if (dd.data.length === 0) return;
31727
- const filteredData = filterByDayPeriods(dd.data, state5.selectedPeriods);
31744
+ const filteredData = filterByDayPeriods(dd.data, state6.selectedPeriods);
31728
31745
  if (filteredData.length === 0) return;
31729
31746
  let points;
31730
- if (state5.granularity === "hour") {
31747
+ if (state6.granularity === "hour") {
31731
31748
  const interpolated = interpolateTemperature(filteredData, {
31732
31749
  intervalMinutes: 30,
31733
- startTs: state5.startTs,
31734
- endTs: state5.endTs,
31735
- clampRange: state5.clampRange
31750
+ startTs: state6.startTs,
31751
+ endTs: state6.endTs,
31752
+ clampRange: state6.clampRange
31736
31753
  });
31737
- const filteredInterpolated = filterByDayPeriods(interpolated, state5.selectedPeriods);
31754
+ const filteredInterpolated = filterByDayPeriods(interpolated, state6.selectedPeriods);
31738
31755
  points = filteredInterpolated.map((item) => ({
31739
31756
  x: item.ts,
31740
31757
  y: Number(item.value),
@@ -31744,7 +31761,7 @@
31744
31761
  deviceColor: dd.color
31745
31762
  }));
31746
31763
  } else {
31747
- const daily = aggregateByDay(filteredData, state5.clampRange);
31764
+ const daily = aggregateByDay(filteredData, state6.clampRange);
31748
31765
  points = daily.map((item) => ({
31749
31766
  x: item.dateTs,
31750
31767
  y: item.avg,
@@ -31765,7 +31782,7 @@
31765
31782
  ctx.fillText("Nenhum dado para os per\xEDodos selecionados", width / 2, height / 2);
31766
31783
  return;
31767
31784
  }
31768
- const isPeriodsFiltered = state5.selectedPeriods.length < 4 && state5.selectedPeriods.length > 0;
31785
+ const isPeriodsFiltered = state6.selectedPeriods.length < 4 && state6.selectedPeriods.length > 0;
31769
31786
  let dataMinY = Infinity;
31770
31787
  let dataMaxY = -Infinity;
31771
31788
  processedData.forEach(({ points }) => {
@@ -31775,7 +31792,7 @@
31775
31792
  });
31776
31793
  });
31777
31794
  const rangeMap = /* @__PURE__ */ new Map();
31778
- state5.deviceData.forEach((dd, index) => {
31795
+ state6.deviceData.forEach((dd, index) => {
31779
31796
  const device = dd.device;
31780
31797
  const min = device.temperatureMin;
31781
31798
  const max = device.temperatureMax;
@@ -31794,10 +31811,10 @@
31794
31811
  }
31795
31812
  }
31796
31813
  });
31797
- if (rangeMap.size === 0 && state5.temperatureMin !== null && state5.temperatureMax !== null) {
31814
+ if (rangeMap.size === 0 && state6.temperatureMin !== null && state6.temperatureMax !== null) {
31798
31815
  rangeMap.set("global", {
31799
- min: state5.temperatureMin,
31800
- max: state5.temperatureMax,
31816
+ min: state6.temperatureMin,
31817
+ max: state6.temperatureMax,
31801
31818
  customerName: "Global",
31802
31819
  color: colors.success,
31803
31820
  deviceLabels: []
@@ -31918,10 +31935,10 @@
31918
31935
  const point = xAxisPoints[i];
31919
31936
  const date = new Date(point.x);
31920
31937
  let label;
31921
- if (state5.granularity === "hour") {
31922
- label = date.toLocaleTimeString(state5.locale, { hour: "2-digit", minute: "2-digit" });
31938
+ if (state6.granularity === "hour") {
31939
+ label = date.toLocaleTimeString(state6.locale, { hour: "2-digit", minute: "2-digit" });
31923
31940
  } else {
31924
- label = date.toLocaleDateString(state5.locale, { day: "2-digit", month: "2-digit" });
31941
+ label = date.toLocaleDateString(state6.locale, { day: "2-digit", month: "2-digit" });
31925
31942
  }
31926
31943
  ctx.strokeStyle = colors.chartGrid;
31927
31944
  ctx.lineWidth = 1;
@@ -31940,16 +31957,16 @@
31940
31957
  ctx.lineTo(width - paddingRight, height - paddingBottom);
31941
31958
  ctx.stroke();
31942
31959
  const allChartPoints = processedData.flatMap((pd) => pd.points);
31943
- setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state5, colors);
31960
+ setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state6, colors);
31944
31961
  }
31945
- function setupComparisonChartTooltip(canvas, container, chartData, state5, colors) {
31962
+ function setupComparisonChartTooltip(canvas, container, chartData, state6, colors) {
31946
31963
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
31947
31964
  if (existingTooltip) existingTooltip.remove();
31948
31965
  const tooltip = document.createElement("div");
31949
31966
  tooltip.className = "myio-chart-tooltip";
31950
31967
  tooltip.style.cssText = `
31951
31968
  position: absolute;
31952
- background: ${state5.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31969
+ background: ${state6.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
31953
31970
  border: 1px solid ${colors.border};
31954
31971
  border-radius: 8px;
31955
31972
  padding: 10px 14px;
@@ -31986,17 +32003,17 @@
31986
32003
  if (point) {
31987
32004
  const date = new Date(point.x);
31988
32005
  let dateStr;
31989
- if (state5.granularity === "hour") {
31990
- dateStr = date.toLocaleDateString(state5.locale, {
32006
+ if (state6.granularity === "hour") {
32007
+ dateStr = date.toLocaleDateString(state6.locale, {
31991
32008
  day: "2-digit",
31992
32009
  month: "2-digit",
31993
32010
  year: "numeric"
31994
- }) + " " + date.toLocaleTimeString(state5.locale, {
32011
+ }) + " " + date.toLocaleTimeString(state6.locale, {
31995
32012
  hour: "2-digit",
31996
32013
  minute: "2-digit"
31997
32014
  });
31998
32015
  } else {
31999
- dateStr = date.toLocaleDateString(state5.locale, {
32016
+ dateStr = date.toLocaleDateString(state6.locale, {
32000
32017
  day: "2-digit",
32001
32018
  month: "2-digit",
32002
32019
  year: "numeric"
@@ -32038,7 +32055,7 @@
32038
32055
  canvas.style.cursor = "default";
32039
32056
  });
32040
32057
  }
32041
- async function setupEventListeners2(container, state5, modalId, onClose) {
32058
+ async function setupEventListeners2(container, state6, modalId, onClose) {
32042
32059
  const closeModal = () => {
32043
32060
  container.remove();
32044
32061
  onClose?.();
@@ -32049,19 +32066,19 @@
32049
32066
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
32050
32067
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
32051
32068
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
32052
- if (dateRangeInput && !state5.dateRangePicker) {
32069
+ if (dateRangeInput && !state6.dateRangePicker) {
32053
32070
  try {
32054
- state5.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
32055
- presetStart: new Date(state5.startTs).toISOString(),
32056
- presetEnd: new Date(state5.endTs).toISOString(),
32071
+ state6.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
32072
+ presetStart: new Date(state6.startTs).toISOString(),
32073
+ presetEnd: new Date(state6.endTs).toISOString(),
32057
32074
  includeTime: true,
32058
32075
  timePrecision: "minute",
32059
32076
  maxRangeDays: 90,
32060
- locale: state5.locale,
32077
+ locale: state6.locale,
32061
32078
  parentEl: container.querySelector(".myio-temp-comparison-content"),
32062
32079
  onApply: (result) => {
32063
- state5.startTs = new Date(result.startISO).getTime();
32064
- state5.endTs = new Date(result.endISO).getTime();
32080
+ state6.startTs = new Date(result.startISO).getTime();
32081
+ state6.endTs = new Date(result.endISO).getTime();
32065
32082
  console.log("[TemperatureComparisonModal] Date range applied:", result);
32066
32083
  }
32067
32084
  });
@@ -32070,23 +32087,23 @@
32070
32087
  }
32071
32088
  }
32072
32089
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
32073
- state5.theme = state5.theme === "dark" ? "light" : "dark";
32074
- localStorage.setItem("myio-temp-comparison-theme", state5.theme);
32075
- state5.dateRangePicker = null;
32076
- renderModal2(container, state5, modalId);
32077
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32078
- drawComparisonChart(modalId, state5);
32079
- }
32080
- await setupEventListeners2(container, state5, modalId, onClose);
32090
+ state6.theme = state6.theme === "dark" ? "light" : "dark";
32091
+ localStorage.setItem("myio-temp-comparison-theme", state6.theme);
32092
+ state6.dateRangePicker = null;
32093
+ renderModal2(container, state6, modalId);
32094
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32095
+ drawComparisonChart(modalId, state6);
32096
+ }
32097
+ await setupEventListeners2(container, state6, modalId, onClose);
32081
32098
  });
32082
32099
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
32083
32100
  container.__isMaximized = !container.__isMaximized;
32084
- state5.dateRangePicker = null;
32085
- renderModal2(container, state5, modalId);
32086
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32087
- drawComparisonChart(modalId, state5);
32101
+ state6.dateRangePicker = null;
32102
+ renderModal2(container, state6, modalId);
32103
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32104
+ drawComparisonChart(modalId, state6);
32088
32105
  }
32089
- await setupEventListeners2(container, state5, modalId, onClose);
32106
+ await setupEventListeners2(container, state6, modalId, onClose);
32090
32107
  });
32091
32108
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
32092
32109
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -32105,13 +32122,13 @@
32105
32122
  periodCheckboxes.forEach((checkbox) => {
32106
32123
  checkbox.addEventListener("change", () => {
32107
32124
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
32108
- state5.selectedPeriods = checked;
32125
+ state6.selectedPeriods = checked;
32109
32126
  const btnLabel = periodBtn?.querySelector("span:first-child");
32110
32127
  if (btnLabel) {
32111
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32128
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32112
32129
  }
32113
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32114
- drawComparisonChart(modalId, state5);
32130
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32131
+ drawComparisonChart(modalId, state6);
32115
32132
  }
32116
32133
  });
32117
32134
  });
@@ -32119,77 +32136,77 @@
32119
32136
  periodCheckboxes.forEach((cb) => {
32120
32137
  cb.checked = true;
32121
32138
  });
32122
- state5.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
32139
+ state6.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
32123
32140
  const btnLabel = periodBtn?.querySelector("span:first-child");
32124
32141
  if (btnLabel) {
32125
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32142
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32126
32143
  }
32127
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32128
- drawComparisonChart(modalId, state5);
32144
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32145
+ drawComparisonChart(modalId, state6);
32129
32146
  }
32130
32147
  });
32131
32148
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
32132
32149
  periodCheckboxes.forEach((cb) => {
32133
32150
  cb.checked = false;
32134
32151
  });
32135
- state5.selectedPeriods = [];
32152
+ state6.selectedPeriods = [];
32136
32153
  const btnLabel = periodBtn?.querySelector("span:first-child");
32137
32154
  if (btnLabel) {
32138
- btnLabel.textContent = getSelectedPeriodsLabel(state5.selectedPeriods);
32155
+ btnLabel.textContent = getSelectedPeriodsLabel(state6.selectedPeriods);
32139
32156
  }
32140
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32141
- drawComparisonChart(modalId, state5);
32157
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32158
+ drawComparisonChart(modalId, state6);
32142
32159
  }
32143
32160
  });
32144
32161
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
32145
- state5.granularity = e.target.value;
32146
- localStorage.setItem("myio-temp-comparison-granularity", state5.granularity);
32147
- if (state5.deviceData.some((dd) => dd.data.length > 0)) {
32148
- drawComparisonChart(modalId, state5);
32162
+ state6.granularity = e.target.value;
32163
+ localStorage.setItem("myio-temp-comparison-granularity", state6.granularity);
32164
+ if (state6.deviceData.some((dd) => dd.data.length > 0)) {
32165
+ drawComparisonChart(modalId, state6);
32149
32166
  }
32150
32167
  });
32151
32168
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
32152
- if (state5.startTs >= state5.endTs) {
32169
+ if (state6.startTs >= state6.endTs) {
32153
32170
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
32154
32171
  return;
32155
32172
  }
32156
- state5.isLoading = true;
32157
- state5.dateRangePicker = null;
32158
- renderModal2(container, state5, modalId);
32159
- await fetchAllDevicesData(state5);
32160
- renderModal2(container, state5, modalId);
32161
- drawComparisonChart(modalId, state5);
32162
- await setupEventListeners2(container, state5, modalId, onClose);
32173
+ state6.isLoading = true;
32174
+ state6.dateRangePicker = null;
32175
+ renderModal2(container, state6, modalId);
32176
+ await fetchAllDevicesData(state6);
32177
+ renderModal2(container, state6, modalId);
32178
+ drawComparisonChart(modalId, state6);
32179
+ await setupEventListeners2(container, state6, modalId, onClose);
32163
32180
  });
32164
32181
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
32165
- if (state5.deviceData.every((dd) => dd.data.length === 0)) return;
32166
- exportComparisonCSV(state5);
32182
+ if (state6.deviceData.every((dd) => dd.data.length === 0)) return;
32183
+ exportComparisonCSV(state6);
32167
32184
  });
32168
32185
  }
32169
- function exportComparisonCSV(state5) {
32170
- const startDateStr = new Date(state5.startTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
32171
- const endDateStr = new Date(state5.endTs).toLocaleDateString(state5.locale).replace(/\//g, "-");
32186
+ function exportComparisonCSV(state6) {
32187
+ const startDateStr = new Date(state6.startTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
32188
+ const endDateStr = new Date(state6.endTs).toLocaleDateString(state6.locale).replace(/\//g, "-");
32172
32189
  const BOM = "\uFEFF";
32173
32190
  let csvContent = BOM;
32174
32191
  csvContent += `Compara\xE7\xE3o de Temperatura
32175
32192
  `;
32176
32193
  csvContent += `Per\xEDodo: ${startDateStr} at\xE9 ${endDateStr}
32177
32194
  `;
32178
- csvContent += `Sensores: ${state5.devices.map((d) => d.label).join(", ")}
32195
+ csvContent += `Sensores: ${state6.devices.map((d) => d.label).join(", ")}
32179
32196
  `;
32180
32197
  csvContent += "\n";
32181
32198
  csvContent += "Estat\xEDsticas por Sensor:\n";
32182
32199
  csvContent += "Sensor,M\xE9dia (\xB0C),Min (\xB0C),Max (\xB0C),Leituras\n";
32183
- state5.deviceData.forEach((dd) => {
32200
+ state6.deviceData.forEach((dd) => {
32184
32201
  csvContent += `"${dd.device.label}",${dd.stats.avg.toFixed(2)},${dd.stats.min.toFixed(2)},${dd.stats.max.toFixed(2)},${dd.stats.count}
32185
32202
  `;
32186
32203
  });
32187
32204
  csvContent += "\n";
32188
32205
  csvContent += "Dados Detalhados:\n";
32189
32206
  csvContent += "Data/Hora,Sensor,Temperatura (\xB0C)\n";
32190
- state5.deviceData.forEach((dd) => {
32207
+ state6.deviceData.forEach((dd) => {
32191
32208
  dd.data.forEach((item) => {
32192
- const date = new Date(item.ts).toLocaleString(state5.locale);
32209
+ const date = new Date(item.ts).toLocaleString(state6.locale);
32193
32210
  const temp = Number(item.value).toFixed(2);
32194
32211
  csvContent += `"${date}","${dd.device.label}",${temp}
32195
32212
  `;
@@ -32297,10 +32314,10 @@
32297
32314
  throw new Error(`Failed to save attributes: ${response.status}`);
32298
32315
  }
32299
32316
  }
32300
- function renderModal3(container, state5, modalId, onClose, onSave) {
32301
- const colors = getColors(state5.theme);
32302
- const minValue = state5.minTemperature !== null ? state5.minTemperature : "";
32303
- const maxValue = state5.maxTemperature !== null ? state5.maxTemperature : "";
32317
+ function renderModal3(container, state6, modalId, onClose, onSave) {
32318
+ const colors = getColors(state6.theme);
32319
+ const minValue = state6.minTemperature !== null ? state6.minTemperature : "";
32320
+ const maxValue = state6.maxTemperature !== null ? state6.maxTemperature : "";
32304
32321
  container.innerHTML = `
32305
32322
  <style>
32306
32323
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@@ -32565,23 +32582,23 @@
32565
32582
  </div>
32566
32583
 
32567
32584
  <div class="modal-body">
32568
- ${state5.isLoading ? `
32585
+ ${state6.isLoading ? `
32569
32586
  <div class="loading-overlay">
32570
32587
  <div class="loading-spinner"></div>
32571
32588
  <div>Carregando configura\xE7\xF5es...</div>
32572
32589
  </div>
32573
32590
  ` : `
32574
- ${state5.error ? `
32575
- <div class="message message-error">${state5.error}</div>
32591
+ ${state6.error ? `
32592
+ <div class="message message-error">${state6.error}</div>
32576
32593
  ` : ""}
32577
32594
 
32578
- ${state5.successMessage ? `
32579
- <div class="message message-success">${state5.successMessage}</div>
32595
+ ${state6.successMessage ? `
32596
+ <div class="message message-success">${state6.successMessage}</div>
32580
32597
  ` : ""}
32581
32598
 
32582
32599
  <div class="customer-info">
32583
32600
  <div class="customer-label">Shopping / Cliente</div>
32584
- <div class="customer-name">${state5.customerName || "N\xE3o identificado"}</div>
32601
+ <div class="customer-name">${state6.customerName || "N\xE3o identificado"}</div>
32585
32602
  </div>
32586
32603
 
32587
32604
  <div class="form-group">
@@ -32625,11 +32642,11 @@
32625
32642
  `}
32626
32643
  </div>
32627
32644
 
32628
- ${!state5.isLoading ? `
32645
+ ${!state6.isLoading ? `
32629
32646
  <div class="modal-footer">
32630
32647
  <button class="btn btn-secondary" id="${modalId}-cancel">Cancelar</button>
32631
- <button class="btn btn-primary" id="${modalId}-save" ${state5.isSaving ? "disabled" : ""}>
32632
- ${state5.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
32648
+ <button class="btn btn-primary" id="${modalId}-save" ${state6.isSaving ? "disabled" : ""}>
32649
+ ${state6.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
32633
32650
  </button>
32634
32651
  </div>
32635
32652
  ` : ""}
@@ -32666,18 +32683,18 @@
32666
32683
  const min = parseFloat(minInput.value);
32667
32684
  const max = parseFloat(maxInput.value);
32668
32685
  if (isNaN(min) || isNaN(max)) {
32669
- state5.error = "Por favor, preencha ambos os valores.";
32670
- renderModal3(container, state5, modalId, onClose, onSave);
32686
+ state6.error = "Por favor, preencha ambos os valores.";
32687
+ renderModal3(container, state6, modalId, onClose, onSave);
32671
32688
  return;
32672
32689
  }
32673
32690
  if (min >= max) {
32674
- state5.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
32675
- renderModal3(container, state5, modalId, onClose, onSave);
32691
+ state6.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
32692
+ renderModal3(container, state6, modalId, onClose, onSave);
32676
32693
  return;
32677
32694
  }
32678
32695
  if (min < 0 || max > 50) {
32679
- state5.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
32680
- renderModal3(container, state5, modalId, onClose, onSave);
32696
+ state6.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
32697
+ renderModal3(container, state6, modalId, onClose, onSave);
32681
32698
  return;
32682
32699
  }
32683
32700
  await onSave(min, max);
@@ -32685,7 +32702,7 @@
32685
32702
  }
32686
32703
  function openTemperatureSettingsModal(params) {
32687
32704
  const modalId = `myio-temp-settings-${Date.now()}`;
32688
- const state5 = {
32705
+ const state6 = {
32689
32706
  customerId: params.customerId,
32690
32707
  customerName: params.customerName || "",
32691
32708
  token: params.token,
@@ -32705,37 +32722,37 @@
32705
32722
  params.onClose?.();
32706
32723
  };
32707
32724
  const handleSave = async (min, max) => {
32708
- state5.isSaving = true;
32709
- state5.error = null;
32710
- state5.successMessage = null;
32711
- renderModal3(container, state5, modalId, destroy, handleSave);
32725
+ state6.isSaving = true;
32726
+ state6.error = null;
32727
+ state6.successMessage = null;
32728
+ renderModal3(container, state6, modalId, destroy, handleSave);
32712
32729
  try {
32713
- await saveCustomerAttributes(state5.customerId, state5.token, min, max, params.onError);
32714
- state5.minTemperature = min;
32715
- state5.maxTemperature = max;
32716
- state5.isSaving = false;
32717
- state5.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
32718
- renderModal3(container, state5, modalId, destroy, handleSave);
32730
+ await saveCustomerAttributes(state6.customerId, state6.token, min, max, params.onError);
32731
+ state6.minTemperature = min;
32732
+ state6.maxTemperature = max;
32733
+ state6.isSaving = false;
32734
+ state6.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
32735
+ renderModal3(container, state6, modalId, destroy, handleSave);
32719
32736
  params.onSave?.({ minTemperature: min, maxTemperature: max });
32720
32737
  setTimeout(() => {
32721
32738
  destroy();
32722
32739
  }, 1500);
32723
32740
  } catch (error) {
32724
- state5.isSaving = false;
32725
- state5.error = `Erro ao salvar: ${error.message}`;
32726
- renderModal3(container, state5, modalId, destroy, handleSave);
32741
+ state6.isSaving = false;
32742
+ state6.error = `Erro ao salvar: ${error.message}`;
32743
+ renderModal3(container, state6, modalId, destroy, handleSave);
32727
32744
  }
32728
32745
  };
32729
- renderModal3(container, state5, modalId, destroy, handleSave);
32730
- fetchCustomerAttributes(state5.customerId, state5.token, params.onError).then(({ minTemperature, maxTemperature }) => {
32731
- state5.minTemperature = minTemperature;
32732
- state5.maxTemperature = maxTemperature;
32733
- state5.isLoading = false;
32734
- renderModal3(container, state5, modalId, destroy, handleSave);
32746
+ renderModal3(container, state6, modalId, destroy, handleSave);
32747
+ fetchCustomerAttributes(state6.customerId, state6.token, params.onError).then(({ minTemperature, maxTemperature }) => {
32748
+ state6.minTemperature = minTemperature;
32749
+ state6.maxTemperature = maxTemperature;
32750
+ state6.isLoading = false;
32751
+ renderModal3(container, state6, modalId, destroy, handleSave);
32735
32752
  }).catch((error) => {
32736
- state5.isLoading = false;
32737
- state5.error = `Erro ao carregar: ${error.message}`;
32738
- renderModal3(container, state5, modalId, destroy, handleSave);
32753
+ state6.isLoading = false;
32754
+ state6.error = `Erro ao carregar: ${error.message}`;
32755
+ renderModal3(container, state6, modalId, destroy, handleSave);
32739
32756
  });
32740
32757
  return { destroy };
32741
32758
  }
@@ -34005,7 +34022,7 @@
34005
34022
  * RFC-0105 Enhancement: Now fetches device lists from MyIOOrchestratorData
34006
34023
  * to populate device lists for status popup display
34007
34024
  */
34008
- buildSummaryFromState(state5, receivedData, domain = "energy") {
34025
+ buildSummaryFromState(state6, receivedData, domain = "energy") {
34009
34026
  const summary = {
34010
34027
  totalDevices: 0,
34011
34028
  totalConsumption: 0,
@@ -34028,22 +34045,22 @@
34028
34045
  },
34029
34046
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
34030
34047
  };
34031
- if (!state5) return summary;
34048
+ if (!state6) return summary;
34032
34049
  const entrada = {
34033
34050
  id: "entrada",
34034
34051
  name: "Entrada",
34035
34052
  icon: CATEGORY_ICONS.entrada,
34036
- deviceCount: state5.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
34037
- consumption: state5.entrada?.total || 0,
34053
+ deviceCount: state6.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
34054
+ consumption: state6.entrada?.total || 0,
34038
34055
  percentage: 100
34039
34056
  };
34040
34057
  const lojas = {
34041
34058
  id: "lojas",
34042
34059
  name: "Lojas",
34043
34060
  icon: CATEGORY_ICONS.lojas,
34044
- deviceCount: state5.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
34045
- consumption: state5.consumidores?.lojas?.total || 0,
34046
- percentage: state5.consumidores?.lojas?.perc || 0
34061
+ deviceCount: state6.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
34062
+ consumption: state6.consumidores?.lojas?.total || 0,
34063
+ percentage: state6.consumidores?.lojas?.perc || 0
34047
34064
  };
34048
34065
  const climatizacaoData = receivedData?.climatizacao || {};
34049
34066
  const elevadoresData = receivedData?.elevadores || {};
@@ -34054,9 +34071,9 @@
34054
34071
  id: "climatizacao",
34055
34072
  name: "Climatizacao",
34056
34073
  icon: CATEGORY_ICONS.climatizacao,
34057
- deviceCount: climatizacaoData.count || state5.consumidores?.climatizacao?.devices?.length || 0,
34058
- consumption: state5.consumidores?.climatizacao?.total || 0,
34059
- percentage: state5.consumidores?.climatizacao?.perc || 0
34074
+ deviceCount: climatizacaoData.count || state6.consumidores?.climatizacao?.devices?.length || 0,
34075
+ consumption: state6.consumidores?.climatizacao?.total || 0,
34076
+ percentage: state6.consumidores?.climatizacao?.perc || 0
34060
34077
  };
34061
34078
  if (climatizacaoData.subcategories) {
34062
34079
  climatizacao.children = [];
@@ -34107,40 +34124,40 @@
34107
34124
  id: "elevadores",
34108
34125
  name: "Elevadores",
34109
34126
  icon: CATEGORY_ICONS.elevadores,
34110
- deviceCount: elevadoresData.count || state5.consumidores?.elevadores?.devices?.length || 0,
34111
- consumption: state5.consumidores?.elevadores?.total || 0,
34112
- percentage: state5.consumidores?.elevadores?.perc || 0
34127
+ deviceCount: elevadoresData.count || state6.consumidores?.elevadores?.devices?.length || 0,
34128
+ consumption: state6.consumidores?.elevadores?.total || 0,
34129
+ percentage: state6.consumidores?.elevadores?.perc || 0
34113
34130
  });
34114
34131
  areaComumChildren.push({
34115
34132
  id: "escadasRolantes",
34116
34133
  name: "Esc. Rolantes",
34117
34134
  icon: CATEGORY_ICONS.escadas,
34118
- deviceCount: escadasData.count || state5.consumidores?.escadasRolantes?.devices?.length || 0,
34119
- consumption: state5.consumidores?.escadasRolantes?.total || 0,
34120
- percentage: state5.consumidores?.escadasRolantes?.perc || 0
34135
+ deviceCount: escadasData.count || state6.consumidores?.escadasRolantes?.devices?.length || 0,
34136
+ consumption: state6.consumidores?.escadasRolantes?.total || 0,
34137
+ percentage: state6.consumidores?.escadasRolantes?.perc || 0
34121
34138
  });
34122
34139
  areaComumChildren.push({
34123
34140
  id: "outros",
34124
34141
  name: "Outros",
34125
34142
  icon: CATEGORY_ICONS.outros,
34126
- deviceCount: outrosData.count || state5.consumidores?.outros?.devices?.length || 0,
34127
- consumption: state5.consumidores?.outros?.total || 0,
34128
- percentage: state5.consumidores?.outros?.perc || 0
34143
+ deviceCount: outrosData.count || state6.consumidores?.outros?.devices?.length || 0,
34144
+ consumption: state6.consumidores?.outros?.total || 0,
34145
+ percentage: state6.consumidores?.outros?.perc || 0
34129
34146
  });
34130
34147
  const areaComumDeviceCount = areaComumChildren.reduce((sum, c) => sum + c.deviceCount, 0);
34131
- const areaComumConsumption = state5.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
34148
+ const areaComumConsumption = state6.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
34132
34149
  const areaComum = {
34133
34150
  id: "areaComum",
34134
34151
  name: "Area Comum",
34135
34152
  icon: CATEGORY_ICONS.areaComum,
34136
34153
  deviceCount: areaComumDeviceCount,
34137
34154
  consumption: areaComumConsumption,
34138
- percentage: state5.consumidores?.areaComum?.perc || 0,
34155
+ percentage: state6.consumidores?.areaComum?.perc || 0,
34139
34156
  children: areaComumChildren
34140
34157
  };
34141
34158
  summary.byCategory = [entrada, lojas, areaComum];
34142
34159
  summary.totalDevices = entrada.deviceCount + lojas.deviceCount + areaComumDeviceCount;
34143
- summary.totalConsumption = state5.grandTotal || entrada.consumption;
34160
+ summary.totalConsumption = state6.grandTotal || entrada.consumption;
34144
34161
  const widgetAggregation = receivedData?.deviceStatusAggregation;
34145
34162
  if (widgetAggregation && widgetAggregation.hasData) {
34146
34163
  summary.byStatus = {
@@ -35428,7 +35445,7 @@
35428
35445
  * RFC-0105 Enhancement: Now fetches device lists from MyIOOrchestratorData
35429
35446
  * to populate device lists for status popup display
35430
35447
  */
35431
- buildSummaryFromState(state5, receivedData, includeBathrooms = false, domain = "water") {
35448
+ buildSummaryFromState(state6, receivedData, includeBathrooms = false, domain = "water") {
35432
35449
  const summary = {
35433
35450
  totalDevices: 0,
35434
35451
  totalConsumption: 0,
@@ -35452,22 +35469,22 @@
35452
35469
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
35453
35470
  includeBathrooms
35454
35471
  };
35455
- if (!state5) return summary;
35472
+ if (!state6) return summary;
35456
35473
  const entrada = {
35457
35474
  id: "entrada",
35458
35475
  name: "Entrada",
35459
35476
  icon: WATER_CATEGORY_ICONS.entrada,
35460
- deviceCount: state5.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
35461
- consumption: state5.entrada?.total || 0,
35477
+ deviceCount: state6.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
35478
+ consumption: state6.entrada?.total || 0,
35462
35479
  percentage: 100
35463
35480
  };
35464
35481
  const lojas = {
35465
35482
  id: "lojas",
35466
35483
  name: "Lojas",
35467
35484
  icon: WATER_CATEGORY_ICONS.lojas,
35468
- deviceCount: state5.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
35469
- consumption: state5.lojas?.total || 0,
35470
- percentage: state5.lojas?.perc || 0
35485
+ deviceCount: state6.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
35486
+ consumption: state6.lojas?.total || 0,
35487
+ percentage: state6.lojas?.perc || 0
35471
35488
  };
35472
35489
  summary.byCategory = [entrada, lojas];
35473
35490
  if (includeBathrooms) {
@@ -35475,9 +35492,9 @@
35475
35492
  id: "banheiros",
35476
35493
  name: "Banheiros",
35477
35494
  icon: WATER_CATEGORY_ICONS.banheiros,
35478
- deviceCount: state5.banheiros?.devices?.length || (receivedData?.banheiros_total?.device_count || 0),
35479
- consumption: state5.banheiros?.total || 0,
35480
- percentage: state5.banheiros?.perc || 0
35495
+ deviceCount: state6.banheiros?.devices?.length || (receivedData?.banheiros_total?.device_count || 0),
35496
+ consumption: state6.banheiros?.total || 0,
35497
+ percentage: state6.banheiros?.perc || 0
35481
35498
  };
35482
35499
  summary.byCategory.push(banheiros);
35483
35500
  }
@@ -35485,24 +35502,24 @@
35485
35502
  id: "areaComum",
35486
35503
  name: "\xC1rea Comum",
35487
35504
  icon: WATER_CATEGORY_ICONS.areaComum,
35488
- deviceCount: state5.areaComum?.devices?.length || (receivedData?.area_comum_total?.device_count || 0),
35489
- consumption: state5.areaComum?.total || 0,
35490
- percentage: state5.areaComum?.perc || 0
35505
+ deviceCount: state6.areaComum?.devices?.length || (receivedData?.area_comum_total?.device_count || 0),
35506
+ consumption: state6.areaComum?.total || 0,
35507
+ percentage: state6.areaComum?.perc || 0
35491
35508
  };
35492
35509
  summary.byCategory.push(areaComum);
35493
- if (state5.pontosNaoMapeados && state5.pontosNaoMapeados.total > 0) {
35510
+ if (state6.pontosNaoMapeados && state6.pontosNaoMapeados.total > 0) {
35494
35511
  const pontosNaoMapeados = {
35495
35512
  id: "pontosNaoMapeados",
35496
35513
  name: "Pontos N\xE3o Mapeados",
35497
35514
  icon: WATER_CATEGORY_ICONS.pontosNaoMapeados,
35498
- deviceCount: state5.pontosNaoMapeados?.devices?.length || 0,
35499
- consumption: state5.pontosNaoMapeados?.total || 0,
35500
- percentage: state5.pontosNaoMapeados?.perc || 0
35515
+ deviceCount: state6.pontosNaoMapeados?.devices?.length || 0,
35516
+ consumption: state6.pontosNaoMapeados?.total || 0,
35517
+ percentage: state6.pontosNaoMapeados?.perc || 0
35501
35518
  };
35502
35519
  summary.byCategory.push(pontosNaoMapeados);
35503
35520
  }
35504
35521
  summary.totalDevices = summary.byCategory.reduce((sum, cat) => sum + cat.deviceCount, 0);
35505
- summary.totalConsumption = state5.entrada?.total || 0;
35522
+ summary.totalConsumption = state6.entrada?.total || 0;
35506
35523
  const widgetAggregation = receivedData?.deviceStatusAggregation;
35507
35524
  if (widgetAggregation && widgetAggregation.hasData) {
35508
35525
  summary.byStatus = {
@@ -37039,6 +37056,996 @@
37039
37056
  }
37040
37057
  };
37041
37058
 
37059
+ // src/utils/ContractSummaryTooltip.ts
37060
+ var CONTRACT_SUMMARY_TOOLTIP_CSS = `
37061
+ /* ============================================
37062
+ Contract Summary Tooltip (RFC-0107)
37063
+ Premium draggable tooltip with dark theme
37064
+ ============================================ */
37065
+
37066
+ .myio-contract-summary-tooltip {
37067
+ position: fixed;
37068
+ z-index: 99999;
37069
+ pointer-events: none;
37070
+ opacity: 0;
37071
+ transition: opacity 0.25s ease, transform 0.25s ease;
37072
+ transform: translateY(5px);
37073
+ }
37074
+
37075
+ .myio-contract-summary-tooltip.visible {
37076
+ opacity: 1;
37077
+ pointer-events: auto;
37078
+ transform: translateY(0);
37079
+ }
37080
+
37081
+ .myio-contract-summary-tooltip.closing {
37082
+ opacity: 0;
37083
+ transform: translateY(8px);
37084
+ transition: opacity 0.4s ease, transform 0.4s ease;
37085
+ }
37086
+
37087
+ .myio-contract-summary-tooltip.pinned {
37088
+ box-shadow: 0 0 0 2px #9684B5, 0 10px 40px rgba(0, 0, 0, 0.3);
37089
+ border-radius: 16px;
37090
+ }
37091
+
37092
+ .myio-contract-summary-tooltip.dragging {
37093
+ transition: none !important;
37094
+ cursor: move;
37095
+ }
37096
+
37097
+ .myio-contract-summary-tooltip.maximized {
37098
+ top: 20px !important;
37099
+ left: 20px !important;
37100
+ right: 20px !important;
37101
+ bottom: 20px !important;
37102
+ width: auto !important;
37103
+ max-width: none !important;
37104
+ }
37105
+
37106
+ .myio-contract-summary-tooltip.maximized .myio-contract-summary-tooltip__panel {
37107
+ width: 100%;
37108
+ height: 100%;
37109
+ max-width: none;
37110
+ display: flex;
37111
+ flex-direction: column;
37112
+ }
37113
+
37114
+ .myio-contract-summary-tooltip.maximized .myio-contract-summary-tooltip__body {
37115
+ flex: 1;
37116
+ overflow-y: auto;
37117
+ }
37118
+
37119
+ .myio-contract-summary-tooltip__panel {
37120
+ background: #2d1458;
37121
+ border: 1px solid rgba(255, 255, 255, 0.1);
37122
+ border-radius: 16px;
37123
+ box-shadow:
37124
+ 0 20px 60px rgba(0, 0, 0, 0.4),
37125
+ 0 8px 20px rgba(0, 0, 0, 0.25),
37126
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
37127
+ min-width: 320px;
37128
+ max-width: 380px;
37129
+ font-family: Inter, system-ui, -apple-system, sans-serif;
37130
+ font-size: 12px;
37131
+ color: #ffffff;
37132
+ overflow: hidden;
37133
+ }
37134
+
37135
+ /* Header */
37136
+ .myio-contract-summary-tooltip__header {
37137
+ display: flex;
37138
+ align-items: center;
37139
+ gap: 10px;
37140
+ padding: 14px 16px;
37141
+ background: linear-gradient(135deg, #9684B5 0%, #2d1458 100%);
37142
+ border-radius: 16px 16px 0 0;
37143
+ position: relative;
37144
+ overflow: hidden;
37145
+ cursor: move;
37146
+ user-select: none;
37147
+ }
37148
+
37149
+ .myio-contract-summary-tooltip__header::before {
37150
+ content: '';
37151
+ position: absolute;
37152
+ top: 0;
37153
+ left: 0;
37154
+ right: 0;
37155
+ bottom: 0;
37156
+ background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
37157
+ opacity: 0.3;
37158
+ }
37159
+
37160
+ .myio-contract-summary-tooltip__icon {
37161
+ width: 40px;
37162
+ height: 40px;
37163
+ background: rgba(255, 255, 255, 0.15);
37164
+ border-radius: 12px;
37165
+ display: flex;
37166
+ align-items: center;
37167
+ justify-content: center;
37168
+ font-size: 20px;
37169
+ backdrop-filter: blur(10px);
37170
+ position: relative;
37171
+ z-index: 1;
37172
+ }
37173
+
37174
+ .myio-contract-summary-tooltip__icon.valid {
37175
+ background: rgba(76, 175, 80, 0.3);
37176
+ }
37177
+
37178
+ .myio-contract-summary-tooltip__icon.invalid {
37179
+ background: rgba(244, 67, 54, 0.3);
37180
+ }
37181
+
37182
+ .myio-contract-summary-tooltip__header-info {
37183
+ flex: 1;
37184
+ position: relative;
37185
+ z-index: 1;
37186
+ }
37187
+
37188
+ .myio-contract-summary-tooltip__title {
37189
+ font-weight: 700;
37190
+ font-size: 15px;
37191
+ color: #ffffff;
37192
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
37193
+ margin-bottom: 2px;
37194
+ }
37195
+
37196
+ .myio-contract-summary-tooltip__subtitle {
37197
+ font-size: 11px;
37198
+ color: rgba(255, 255, 255, 0.7);
37199
+ }
37200
+
37201
+ .myio-contract-summary-tooltip__header-actions {
37202
+ display: flex;
37203
+ align-items: center;
37204
+ gap: 4px;
37205
+ position: relative;
37206
+ z-index: 1;
37207
+ }
37208
+
37209
+ .myio-contract-summary-tooltip__header-btn {
37210
+ width: 28px;
37211
+ height: 28px;
37212
+ border: none;
37213
+ background: rgba(255, 255, 255, 0.15);
37214
+ border-radius: 8px;
37215
+ cursor: pointer;
37216
+ display: flex;
37217
+ align-items: center;
37218
+ justify-content: center;
37219
+ transition: all 0.2s ease;
37220
+ color: rgba(255, 255, 255, 0.8);
37221
+ }
37222
+
37223
+ .myio-contract-summary-tooltip__header-btn:hover {
37224
+ background: rgba(255, 255, 255, 0.25);
37225
+ color: #ffffff;
37226
+ transform: scale(1.05);
37227
+ }
37228
+
37229
+ .myio-contract-summary-tooltip__header-btn.pinned {
37230
+ background: rgba(255, 255, 255, 0.9);
37231
+ color: #9684B5;
37232
+ }
37233
+
37234
+ .myio-contract-summary-tooltip__header-btn.pinned:hover {
37235
+ background: #ffffff;
37236
+ color: #2d1458;
37237
+ }
37238
+
37239
+ .myio-contract-summary-tooltip__header-btn svg {
37240
+ width: 14px;
37241
+ height: 14px;
37242
+ }
37243
+
37244
+ /* Body */
37245
+ .myio-contract-summary-tooltip__body {
37246
+ padding: 16px;
37247
+ }
37248
+
37249
+ /* Domain Section */
37250
+ .myio-contract-summary-tooltip__domain {
37251
+ margin-bottom: 14px;
37252
+ background: rgba(255, 255, 255, 0.05);
37253
+ border-radius: 12px;
37254
+ overflow: hidden;
37255
+ border: 1px solid rgba(255, 255, 255, 0.08);
37256
+ }
37257
+
37258
+ .myio-contract-summary-tooltip__domain:last-child {
37259
+ margin-bottom: 0;
37260
+ }
37261
+
37262
+ .myio-contract-summary-tooltip__domain-header {
37263
+ display: flex;
37264
+ align-items: center;
37265
+ justify-content: space-between;
37266
+ padding: 10px 14px;
37267
+ cursor: pointer;
37268
+ transition: background 0.2s ease;
37269
+ }
37270
+
37271
+ .myio-contract-summary-tooltip__domain-header:hover {
37272
+ background: rgba(255, 255, 255, 0.05);
37273
+ }
37274
+
37275
+ .myio-contract-summary-tooltip__domain-info {
37276
+ display: flex;
37277
+ align-items: center;
37278
+ gap: 8px;
37279
+ }
37280
+
37281
+ .myio-contract-summary-tooltip__domain-icon {
37282
+ font-size: 18px;
37283
+ }
37284
+
37285
+ .myio-contract-summary-tooltip__domain-name {
37286
+ font-weight: 600;
37287
+ font-size: 13px;
37288
+ }
37289
+
37290
+ .myio-contract-summary-tooltip__domain-count {
37291
+ font-size: 12px;
37292
+ color: #81c784;
37293
+ font-weight: 600;
37294
+ }
37295
+
37296
+ .myio-contract-summary-tooltip__expand-icon {
37297
+ font-size: 10px;
37298
+ opacity: 0.6;
37299
+ transition: transform 0.3s ease;
37300
+ }
37301
+
37302
+ .myio-contract-summary-tooltip__domain.expanded .myio-contract-summary-tooltip__expand-icon {
37303
+ transform: rotate(180deg);
37304
+ }
37305
+
37306
+ /* Domain Details */
37307
+ .myio-contract-summary-tooltip__domain-details {
37308
+ max-height: 0;
37309
+ overflow: hidden;
37310
+ transition: max-height 0.3s ease;
37311
+ background: rgba(0, 0, 0, 0.15);
37312
+ }
37313
+
37314
+ .myio-contract-summary-tooltip__domain.expanded .myio-contract-summary-tooltip__domain-details {
37315
+ max-height: 150px;
37316
+ }
37317
+
37318
+ .myio-contract-summary-tooltip__detail-row {
37319
+ display: flex;
37320
+ align-items: center;
37321
+ justify-content: space-between;
37322
+ padding: 6px 14px 6px 40px;
37323
+ font-size: 12px;
37324
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
37325
+ }
37326
+
37327
+ .myio-contract-summary-tooltip__detail-row:first-child {
37328
+ border-top: none;
37329
+ }
37330
+
37331
+ .myio-contract-summary-tooltip__detail-label {
37332
+ opacity: 0.7;
37333
+ display: flex;
37334
+ align-items: center;
37335
+ gap: 6px;
37336
+ }
37337
+
37338
+ .myio-contract-summary-tooltip__detail-label::before {
37339
+ content: '';
37340
+ width: 4px;
37341
+ height: 4px;
37342
+ border-radius: 50%;
37343
+ background: currentColor;
37344
+ opacity: 0.5;
37345
+ }
37346
+
37347
+ .myio-contract-summary-tooltip__detail-count {
37348
+ font-weight: 500;
37349
+ color: #81c784;
37350
+ }
37351
+
37352
+ /* Status Banner */
37353
+ .myio-contract-summary-tooltip__status {
37354
+ display: flex;
37355
+ align-items: center;
37356
+ justify-content: center;
37357
+ gap: 8px;
37358
+ padding: 10px 14px;
37359
+ border-radius: 10px;
37360
+ margin-bottom: 14px;
37361
+ font-size: 12px;
37362
+ font-weight: 600;
37363
+ }
37364
+
37365
+ .myio-contract-summary-tooltip__status.valid {
37366
+ background: rgba(76, 175, 80, 0.2);
37367
+ color: #81c784;
37368
+ border: 1px solid rgba(76, 175, 80, 0.3);
37369
+ }
37370
+
37371
+ .myio-contract-summary-tooltip__status.invalid {
37372
+ background: rgba(244, 67, 54, 0.2);
37373
+ color: #ef5350;
37374
+ border: 1px solid rgba(244, 67, 54, 0.3);
37375
+ }
37376
+
37377
+ .myio-contract-summary-tooltip__status-icon {
37378
+ font-size: 14px;
37379
+ }
37380
+
37381
+ /* Discrepancies */
37382
+ .myio-contract-summary-tooltip__discrepancies {
37383
+ background: rgba(244, 67, 54, 0.15);
37384
+ border: 1px solid rgba(244, 67, 54, 0.3);
37385
+ border-radius: 10px;
37386
+ padding: 10px 14px;
37387
+ margin-bottom: 14px;
37388
+ }
37389
+
37390
+ .myio-contract-summary-tooltip__discrepancies-title {
37391
+ font-size: 11px;
37392
+ font-weight: 600;
37393
+ color: #ef5350;
37394
+ margin-bottom: 6px;
37395
+ text-transform: uppercase;
37396
+ letter-spacing: 0.5px;
37397
+ }
37398
+
37399
+ .myio-contract-summary-tooltip__discrepancy-item {
37400
+ font-size: 11px;
37401
+ color: rgba(255, 255, 255, 0.8);
37402
+ padding: 3px 0;
37403
+ }
37404
+
37405
+ /* Footer */
37406
+ .myio-contract-summary-tooltip__footer {
37407
+ display: flex;
37408
+ justify-content: space-between;
37409
+ align-items: center;
37410
+ padding: 12px 16px;
37411
+ background: rgba(0, 0, 0, 0.2);
37412
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
37413
+ border-radius: 0 0 16px 16px;
37414
+ }
37415
+
37416
+ .myio-contract-summary-tooltip__footer-label {
37417
+ font-size: 10px;
37418
+ color: rgba(255, 255, 255, 0.5);
37419
+ }
37420
+
37421
+ .myio-contract-summary-tooltip__footer-value {
37422
+ font-size: 11px;
37423
+ font-weight: 600;
37424
+ color: rgba(255, 255, 255, 0.8);
37425
+ }
37426
+
37427
+ /* Total Devices Badge */
37428
+ .myio-contract-summary-tooltip__total {
37429
+ display: flex;
37430
+ align-items: center;
37431
+ justify-content: center;
37432
+ gap: 8px;
37433
+ padding: 12px;
37434
+ background: rgba(255, 255, 255, 0.08);
37435
+ border-radius: 10px;
37436
+ margin-bottom: 14px;
37437
+ }
37438
+
37439
+ .myio-contract-summary-tooltip__total-label {
37440
+ font-size: 12px;
37441
+ opacity: 0.8;
37442
+ }
37443
+
37444
+ .myio-contract-summary-tooltip__total-value {
37445
+ font-size: 18px;
37446
+ font-weight: 700;
37447
+ color: #81c784;
37448
+ }
37449
+
37450
+ /* Responsive */
37451
+ @media (max-width: 400px) {
37452
+ .myio-contract-summary-tooltip__panel {
37453
+ min-width: 280px;
37454
+ max-width: 95vw;
37455
+ }
37456
+ }
37457
+ `;
37458
+ var cssInjected9 = false;
37459
+ function injectCSS9() {
37460
+ if (cssInjected9) return;
37461
+ if (typeof document === "undefined") return;
37462
+ const styleId = "myio-contract-summary-tooltip-styles";
37463
+ if (document.getElementById(styleId)) {
37464
+ cssInjected9 = true;
37465
+ return;
37466
+ }
37467
+ const style = document.createElement("style");
37468
+ style.id = styleId;
37469
+ style.textContent = CONTRACT_SUMMARY_TOOLTIP_CSS;
37470
+ document.head.appendChild(style);
37471
+ cssInjected9 = true;
37472
+ }
37473
+ var state5 = {
37474
+ hideTimer: null,
37475
+ isMouseOverTooltip: false,
37476
+ isMaximized: false,
37477
+ isDragging: false,
37478
+ dragOffset: { x: 0, y: 0 },
37479
+ savedPosition: null,
37480
+ pinnedCounter: 0,
37481
+ expandedDomains: /* @__PURE__ */ new Set(["energy", "water", "temperature"])
37482
+ };
37483
+ function formatTimestamp5(isoString) {
37484
+ if (!isoString) return "Agora";
37485
+ try {
37486
+ const date = new Date(isoString);
37487
+ return date.toLocaleString("pt-BR", {
37488
+ hour: "2-digit",
37489
+ minute: "2-digit",
37490
+ day: "2-digit",
37491
+ month: "2-digit"
37492
+ });
37493
+ } catch {
37494
+ return "Agora";
37495
+ }
37496
+ }
37497
+ function calculateTotalDevices(data) {
37498
+ return data.energy.total + data.water.total + data.temperature.total;
37499
+ }
37500
+ function generateHeaderHTML5(data) {
37501
+ const iconClass = data.isValid ? "valid" : "invalid";
37502
+ const iconSymbol = data.isValid ? "\u2713" : "!";
37503
+ const totalDevices = calculateTotalDevices(data);
37504
+ return `
37505
+ <div class="myio-contract-summary-tooltip__header" data-drag-handle>
37506
+ <div class="myio-contract-summary-tooltip__icon ${iconClass}">${iconSymbol}</div>
37507
+ <div class="myio-contract-summary-tooltip__header-info">
37508
+ <div class="myio-contract-summary-tooltip__title">Contract Summary</div>
37509
+ <div class="myio-contract-summary-tooltip__subtitle">${totalDevices} devices loaded</div>
37510
+ </div>
37511
+ <div class="myio-contract-summary-tooltip__header-actions">
37512
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="pin" title="Pin to screen">
37513
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37514
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
37515
+ <line x1="12" y1="16" x2="12" y2="21"/>
37516
+ <line x1="8" y1="4" x2="16" y2="4"/>
37517
+ </svg>
37518
+ </button>
37519
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="maximize" title="Maximize">
37520
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37521
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
37522
+ </svg>
37523
+ </button>
37524
+ <button class="myio-contract-summary-tooltip__header-btn" data-action="close" title="Close">
37525
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37526
+ <path d="M18 6L6 18M6 6l12 12"/>
37527
+ </svg>
37528
+ </button>
37529
+ </div>
37530
+ </div>
37531
+ `;
37532
+ }
37533
+ function generateDomainHTML(domain, icon, name, counts, isTemperature = false) {
37534
+ const isExpanded = state5.expandedDomains.has(domain);
37535
+ const expandedClass = isExpanded ? "expanded" : "";
37536
+ let detailsHTML = "";
37537
+ if (isTemperature) {
37538
+ const tempCounts = counts;
37539
+ detailsHTML = `
37540
+ <div class="myio-contract-summary-tooltip__detail-row">
37541
+ <span class="myio-contract-summary-tooltip__detail-label">Internal (Climate)</span>
37542
+ <span class="myio-contract-summary-tooltip__detail-count">${tempCounts.internal}</span>
37543
+ </div>
37544
+ <div class="myio-contract-summary-tooltip__detail-row">
37545
+ <span class="myio-contract-summary-tooltip__detail-label">Stores (Non-Climate)</span>
37546
+ <span class="myio-contract-summary-tooltip__detail-count">${tempCounts.stores}</span>
37547
+ </div>
37548
+ `;
37549
+ } else {
37550
+ const domainCounts = counts;
37551
+ detailsHTML = `
37552
+ <div class="myio-contract-summary-tooltip__detail-row">
37553
+ <span class="myio-contract-summary-tooltip__detail-label">Entries</span>
37554
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.entries}</span>
37555
+ </div>
37556
+ <div class="myio-contract-summary-tooltip__detail-row">
37557
+ <span class="myio-contract-summary-tooltip__detail-label">Common Area</span>
37558
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.commonArea}</span>
37559
+ </div>
37560
+ <div class="myio-contract-summary-tooltip__detail-row">
37561
+ <span class="myio-contract-summary-tooltip__detail-label">Stores</span>
37562
+ <span class="myio-contract-summary-tooltip__detail-count">${domainCounts.stores}</span>
37563
+ </div>
37564
+ `;
37565
+ }
37566
+ return `
37567
+ <div class="myio-contract-summary-tooltip__domain ${expandedClass}" data-domain="${domain}">
37568
+ <div class="myio-contract-summary-tooltip__domain-header" data-toggle-domain="${domain}">
37569
+ <div class="myio-contract-summary-tooltip__domain-info">
37570
+ <span class="myio-contract-summary-tooltip__domain-icon">${icon}</span>
37571
+ <span class="myio-contract-summary-tooltip__domain-name">${name}</span>
37572
+ </div>
37573
+ <div style="display: flex; align-items: center; gap: 8px;">
37574
+ <span class="myio-contract-summary-tooltip__domain-count">${counts.total} devices</span>
37575
+ <span class="myio-contract-summary-tooltip__expand-icon">\u25BC</span>
37576
+ </div>
37577
+ </div>
37578
+ <div class="myio-contract-summary-tooltip__domain-details">
37579
+ ${detailsHTML}
37580
+ </div>
37581
+ </div>
37582
+ `;
37583
+ }
37584
+ function generateBodyHTML3(data) {
37585
+ const totalDevices = calculateTotalDevices(data);
37586
+ const timestamp = formatTimestamp5(data.timestamp);
37587
+ const statusClass = data.isValid ? "valid" : "invalid";
37588
+ const statusIcon = data.isValid ? "\u2713" : "\u26A0";
37589
+ const statusText = data.isValid ? "Contract validated successfully" : "Validation issues detected";
37590
+ let discrepanciesHTML = "";
37591
+ if (data.discrepancies && data.discrepancies.length > 0) {
37592
+ const items = data.discrepancies.map((d) => `<div class="myio-contract-summary-tooltip__discrepancy-item">${d.domain}: expected ${d.expected}, found ${d.actual}</div>`).join("");
37593
+ discrepanciesHTML = `
37594
+ <div class="myio-contract-summary-tooltip__discrepancies">
37595
+ <div class="myio-contract-summary-tooltip__discrepancies-title">Discrepancies</div>
37596
+ ${items}
37597
+ </div>
37598
+ `;
37599
+ }
37600
+ return `
37601
+ <div class="myio-contract-summary-tooltip__body">
37602
+ <!-- Status Banner -->
37603
+ <div class="myio-contract-summary-tooltip__status ${statusClass}">
37604
+ <span class="myio-contract-summary-tooltip__status-icon">${statusIcon}</span>
37605
+ <span>${statusText}</span>
37606
+ </div>
37607
+
37608
+ ${discrepanciesHTML}
37609
+
37610
+ <!-- Total Devices -->
37611
+ <div class="myio-contract-summary-tooltip__total">
37612
+ <span class="myio-contract-summary-tooltip__total-label">Total Devices:</span>
37613
+ <span class="myio-contract-summary-tooltip__total-value">${totalDevices}</span>
37614
+ </div>
37615
+
37616
+ <!-- Energy -->
37617
+ ${generateDomainHTML("energy", "\u26A1", "Energy", data.energy)}
37618
+
37619
+ <!-- Water -->
37620
+ ${generateDomainHTML("water", "\u{1F4A7}", "Water", data.water)}
37621
+
37622
+ <!-- Temperature -->
37623
+ ${generateDomainHTML("temperature", "\u{1F321}\uFE0F", "Temperature", data.temperature, true)}
37624
+ </div>
37625
+
37626
+ <!-- Footer -->
37627
+ <div class="myio-contract-summary-tooltip__footer">
37628
+ <span class="myio-contract-summary-tooltip__footer-label">Loaded at</span>
37629
+ <span class="myio-contract-summary-tooltip__footer-value">${timestamp}</span>
37630
+ </div>
37631
+ `;
37632
+ }
37633
+ function setupHoverListeners5(container) {
37634
+ container.onmouseenter = () => {
37635
+ state5.isMouseOverTooltip = true;
37636
+ if (state5.hideTimer) {
37637
+ clearTimeout(state5.hideTimer);
37638
+ state5.hideTimer = null;
37639
+ }
37640
+ };
37641
+ container.onmouseleave = () => {
37642
+ state5.isMouseOverTooltip = false;
37643
+ startDelayedHide5();
37644
+ };
37645
+ }
37646
+ function setupDomainToggleListeners(container) {
37647
+ const toggles = container.querySelectorAll("[data-toggle-domain]");
37648
+ toggles.forEach((toggle) => {
37649
+ toggle.onclick = (e) => {
37650
+ e.stopPropagation();
37651
+ const domain = toggle.dataset.toggleDomain;
37652
+ if (!domain) return;
37653
+ const domainEl = container.querySelector(`[data-domain="${domain}"]`);
37654
+ if (!domainEl) return;
37655
+ if (state5.expandedDomains.has(domain)) {
37656
+ state5.expandedDomains.delete(domain);
37657
+ domainEl.classList.remove("expanded");
37658
+ } else {
37659
+ state5.expandedDomains.add(domain);
37660
+ domainEl.classList.add("expanded");
37661
+ }
37662
+ };
37663
+ });
37664
+ }
37665
+ function setupButtonListeners5(container) {
37666
+ const buttons = container.querySelectorAll("[data-action]");
37667
+ buttons.forEach((btn) => {
37668
+ btn.onclick = (e) => {
37669
+ e.stopPropagation();
37670
+ const action = btn.dataset.action;
37671
+ switch (action) {
37672
+ case "pin":
37673
+ createPinnedClone5(container);
37674
+ break;
37675
+ case "maximize":
37676
+ toggleMaximize5(container);
37677
+ break;
37678
+ case "close":
37679
+ ContractSummaryTooltip.close();
37680
+ break;
37681
+ }
37682
+ };
37683
+ });
37684
+ }
37685
+ function setupDragListeners5(container) {
37686
+ const header = container.querySelector("[data-drag-handle]");
37687
+ if (!header) return;
37688
+ header.onmousedown = (e) => {
37689
+ if (e.target.closest("[data-action]")) return;
37690
+ if (e.target.closest("[data-toggle-domain]")) return;
37691
+ if (state5.isMaximized) return;
37692
+ state5.isDragging = true;
37693
+ container.classList.add("dragging");
37694
+ const rect = container.getBoundingClientRect();
37695
+ state5.dragOffset = {
37696
+ x: e.clientX - rect.left,
37697
+ y: e.clientY - rect.top
37698
+ };
37699
+ const onMouseMove = (e2) => {
37700
+ if (!state5.isDragging) return;
37701
+ const newLeft = e2.clientX - state5.dragOffset.x;
37702
+ const newTop = e2.clientY - state5.dragOffset.y;
37703
+ const maxLeft = window.innerWidth - container.offsetWidth;
37704
+ const maxTop = window.innerHeight - container.offsetHeight;
37705
+ container.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
37706
+ container.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
37707
+ };
37708
+ const onMouseUp = () => {
37709
+ state5.isDragging = false;
37710
+ container.classList.remove("dragging");
37711
+ document.removeEventListener("mousemove", onMouseMove);
37712
+ document.removeEventListener("mouseup", onMouseUp);
37713
+ };
37714
+ document.addEventListener("mousemove", onMouseMove);
37715
+ document.addEventListener("mouseup", onMouseUp);
37716
+ };
37717
+ }
37718
+ function createPinnedClone5(container) {
37719
+ state5.pinnedCounter++;
37720
+ const pinnedId = `myio-contract-summary-tooltip-pinned-${state5.pinnedCounter}`;
37721
+ const clone = container.cloneNode(true);
37722
+ clone.id = pinnedId;
37723
+ clone.classList.add("pinned");
37724
+ clone.classList.remove("closing");
37725
+ const pinBtn = clone.querySelector('[data-action="pin"]');
37726
+ if (pinBtn) {
37727
+ pinBtn.classList.add("pinned");
37728
+ pinBtn.setAttribute("title", "Unpin");
37729
+ pinBtn.innerHTML = `
37730
+ <svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1">
37731
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
37732
+ <line x1="12" y1="16" x2="12" y2="21"/>
37733
+ <line x1="8" y1="4" x2="16" y2="4"/>
37734
+ </svg>
37735
+ `;
37736
+ }
37737
+ document.body.appendChild(clone);
37738
+ setupPinnedCloneListeners5(clone, pinnedId);
37739
+ ContractSummaryTooltip.hide();
37740
+ }
37741
+ function setupPinnedCloneListeners5(clone, cloneId) {
37742
+ let isMaximized = false;
37743
+ let savedPosition = null;
37744
+ const cloneExpandedDomains = new Set(state5.expandedDomains);
37745
+ const toggles = clone.querySelectorAll("[data-toggle-domain]");
37746
+ toggles.forEach((toggle) => {
37747
+ toggle.onclick = (e) => {
37748
+ e.stopPropagation();
37749
+ const domain = toggle.dataset.toggleDomain;
37750
+ if (!domain) return;
37751
+ const domainEl = clone.querySelector(`[data-domain="${domain}"]`);
37752
+ if (!domainEl) return;
37753
+ if (cloneExpandedDomains.has(domain)) {
37754
+ cloneExpandedDomains.delete(domain);
37755
+ domainEl.classList.remove("expanded");
37756
+ } else {
37757
+ cloneExpandedDomains.add(domain);
37758
+ domainEl.classList.add("expanded");
37759
+ }
37760
+ };
37761
+ });
37762
+ const pinBtn = clone.querySelector('[data-action="pin"]');
37763
+ if (pinBtn) {
37764
+ pinBtn.onclick = (e) => {
37765
+ e.stopPropagation();
37766
+ closePinnedClone5(cloneId);
37767
+ };
37768
+ }
37769
+ const closeBtn = clone.querySelector('[data-action="close"]');
37770
+ if (closeBtn) {
37771
+ closeBtn.onclick = (e) => {
37772
+ e.stopPropagation();
37773
+ closePinnedClone5(cloneId);
37774
+ };
37775
+ }
37776
+ const maxBtn = clone.querySelector('[data-action="maximize"]');
37777
+ if (maxBtn) {
37778
+ maxBtn.onclick = (e) => {
37779
+ e.stopPropagation();
37780
+ isMaximized = !isMaximized;
37781
+ if (isMaximized) {
37782
+ savedPosition = { left: clone.style.left, top: clone.style.top };
37783
+ maxBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="5" width="14" height="14" rx="2"/><path d="M9 5V3h12v12h-2"/></svg>`;
37784
+ maxBtn.setAttribute("title", "Restore");
37785
+ } else {
37786
+ maxBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>`;
37787
+ maxBtn.setAttribute("title", "Maximize");
37788
+ if (savedPosition) {
37789
+ clone.style.left = savedPosition.left;
37790
+ clone.style.top = savedPosition.top;
37791
+ }
37792
+ }
37793
+ clone.classList.toggle("maximized", isMaximized);
37794
+ };
37795
+ }
37796
+ const header = clone.querySelector("[data-drag-handle]");
37797
+ if (header) {
37798
+ let isDragging = false;
37799
+ let dragOffset = { x: 0, y: 0 };
37800
+ header.onmousedown = (e) => {
37801
+ if (e.target.closest("[data-action]")) return;
37802
+ if (e.target.closest("[data-toggle-domain]")) return;
37803
+ if (isMaximized) return;
37804
+ isDragging = true;
37805
+ clone.classList.add("dragging");
37806
+ const rect = clone.getBoundingClientRect();
37807
+ dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
37808
+ const onMouseMove = (e2) => {
37809
+ if (!isDragging) return;
37810
+ const newLeft = e2.clientX - dragOffset.x;
37811
+ const newTop = e2.clientY - dragOffset.y;
37812
+ const maxLeft = window.innerWidth - clone.offsetWidth;
37813
+ const maxTop = window.innerHeight - clone.offsetHeight;
37814
+ clone.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
37815
+ clone.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
37816
+ };
37817
+ const onMouseUp = () => {
37818
+ isDragging = false;
37819
+ clone.classList.remove("dragging");
37820
+ document.removeEventListener("mousemove", onMouseMove);
37821
+ document.removeEventListener("mouseup", onMouseUp);
37822
+ };
37823
+ document.addEventListener("mousemove", onMouseMove);
37824
+ document.addEventListener("mouseup", onMouseUp);
37825
+ };
37826
+ }
37827
+ }
37828
+ function closePinnedClone5(cloneId) {
37829
+ const clone = document.getElementById(cloneId);
37830
+ if (clone) {
37831
+ clone.classList.add("closing");
37832
+ setTimeout(() => clone.remove(), 400);
37833
+ }
37834
+ }
37835
+ function toggleMaximize5(container) {
37836
+ state5.isMaximized = !state5.isMaximized;
37837
+ if (state5.isMaximized) {
37838
+ state5.savedPosition = {
37839
+ left: container.style.left,
37840
+ top: container.style.top
37841
+ };
37842
+ }
37843
+ container.classList.toggle("maximized", state5.isMaximized);
37844
+ const maxBtn = container.querySelector('[data-action="maximize"]');
37845
+ if (maxBtn) {
37846
+ if (state5.isMaximized) {
37847
+ maxBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="5" width="14" height="14" rx="2"/><path d="M9 5V3h12v12h-2"/></svg>`;
37848
+ maxBtn.setAttribute("title", "Restore");
37849
+ } else {
37850
+ maxBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>`;
37851
+ maxBtn.setAttribute("title", "Maximize");
37852
+ if (state5.savedPosition) {
37853
+ container.style.left = state5.savedPosition.left;
37854
+ container.style.top = state5.savedPosition.top;
37855
+ }
37856
+ }
37857
+ }
37858
+ }
37859
+ function startDelayedHide5() {
37860
+ if (state5.isMouseOverTooltip) return;
37861
+ if (state5.hideTimer) {
37862
+ clearTimeout(state5.hideTimer);
37863
+ }
37864
+ state5.hideTimer = setTimeout(() => {
37865
+ hideWithAnimation5();
37866
+ }, 1500);
37867
+ }
37868
+ function hideWithAnimation5() {
37869
+ const container = document.getElementById("myio-contract-summary-tooltip");
37870
+ if (container && container.classList.contains("visible")) {
37871
+ container.classList.add("closing");
37872
+ setTimeout(() => {
37873
+ container.classList.remove("visible", "closing");
37874
+ }, 400);
37875
+ }
37876
+ }
37877
+ function positionTooltip5(container, triggerElement) {
37878
+ const rect = triggerElement.getBoundingClientRect();
37879
+ let left = rect.left;
37880
+ let top = rect.bottom + 8;
37881
+ const tooltipWidth = 360;
37882
+ if (left + tooltipWidth > window.innerWidth - 20) {
37883
+ left = window.innerWidth - tooltipWidth - 20;
37884
+ }
37885
+ if (left < 10) left = 10;
37886
+ if (top + 500 > window.innerHeight) {
37887
+ top = rect.top - 8 - 500;
37888
+ if (top < 10) top = 10;
37889
+ }
37890
+ container.style.left = left + "px";
37891
+ container.style.top = top + "px";
37892
+ }
37893
+ var ContractSummaryTooltip = {
37894
+ containerId: "myio-contract-summary-tooltip",
37895
+ /**
37896
+ * Get or create container
37897
+ */
37898
+ getContainer() {
37899
+ injectCSS9();
37900
+ let container = document.getElementById(this.containerId);
37901
+ if (!container) {
37902
+ container = document.createElement("div");
37903
+ container.id = this.containerId;
37904
+ container.className = "myio-contract-summary-tooltip";
37905
+ document.body.appendChild(container);
37906
+ }
37907
+ return container;
37908
+ },
37909
+ /**
37910
+ * Show tooltip
37911
+ */
37912
+ show(triggerElement, data) {
37913
+ if (state5.hideTimer) {
37914
+ clearTimeout(state5.hideTimer);
37915
+ state5.hideTimer = null;
37916
+ }
37917
+ const container = this.getContainer();
37918
+ container.classList.remove("closing");
37919
+ container.innerHTML = `
37920
+ <div class="myio-contract-summary-tooltip__panel">
37921
+ ${generateHeaderHTML5(data)}
37922
+ ${generateBodyHTML3(data)}
37923
+ </div>
37924
+ `;
37925
+ positionTooltip5(container, triggerElement);
37926
+ container.classList.add("visible");
37927
+ setupHoverListeners5(container);
37928
+ setupButtonListeners5(container);
37929
+ setupDragListeners5(container);
37930
+ setupDomainToggleListeners(container);
37931
+ },
37932
+ /**
37933
+ * Start delayed hide
37934
+ */
37935
+ startDelayedHide() {
37936
+ startDelayedHide5();
37937
+ },
37938
+ /**
37939
+ * Hide immediately
37940
+ */
37941
+ hide() {
37942
+ if (state5.hideTimer) {
37943
+ clearTimeout(state5.hideTimer);
37944
+ state5.hideTimer = null;
37945
+ }
37946
+ state5.isMouseOverTooltip = false;
37947
+ const container = document.getElementById(this.containerId);
37948
+ if (container) {
37949
+ container.classList.remove("visible", "closing");
37950
+ }
37951
+ },
37952
+ /**
37953
+ * Close and reset all states
37954
+ */
37955
+ close() {
37956
+ state5.isMaximized = false;
37957
+ state5.isDragging = false;
37958
+ state5.savedPosition = null;
37959
+ if (state5.hideTimer) {
37960
+ clearTimeout(state5.hideTimer);
37961
+ state5.hideTimer = null;
37962
+ }
37963
+ state5.isMouseOverTooltip = false;
37964
+ const container = document.getElementById(this.containerId);
37965
+ if (container) {
37966
+ container.classList.remove("visible", "pinned", "maximized", "dragging", "closing");
37967
+ }
37968
+ },
37969
+ /**
37970
+ * Attach tooltip to trigger element with click behavior
37971
+ */
37972
+ attach(triggerElement, getDataFn) {
37973
+ const self = this;
37974
+ const handleClick = () => {
37975
+ if (state5.hideTimer) {
37976
+ clearTimeout(state5.hideTimer);
37977
+ state5.hideTimer = null;
37978
+ }
37979
+ const data = getDataFn();
37980
+ if (data) {
37981
+ self.show(triggerElement, data);
37982
+ }
37983
+ };
37984
+ triggerElement.addEventListener("click", handleClick);
37985
+ return () => {
37986
+ triggerElement.removeEventListener("click", handleClick);
37987
+ self.hide();
37988
+ };
37989
+ },
37990
+ /**
37991
+ * Attach tooltip with hover behavior
37992
+ */
37993
+ attachHover(triggerElement, getDataFn) {
37994
+ const self = this;
37995
+ const handleMouseEnter = () => {
37996
+ if (state5.hideTimer) {
37997
+ clearTimeout(state5.hideTimer);
37998
+ state5.hideTimer = null;
37999
+ }
38000
+ const data = getDataFn();
38001
+ if (data) {
38002
+ self.show(triggerElement, data);
38003
+ }
38004
+ };
38005
+ const handleMouseLeave = () => {
38006
+ startDelayedHide5();
38007
+ };
38008
+ triggerElement.addEventListener("mouseenter", handleMouseEnter);
38009
+ triggerElement.addEventListener("mouseleave", handleMouseLeave);
38010
+ return () => {
38011
+ triggerElement.removeEventListener("mouseenter", handleMouseEnter);
38012
+ triggerElement.removeEventListener("mouseleave", handleMouseLeave);
38013
+ self.hide();
38014
+ };
38015
+ },
38016
+ /**
38017
+ * Build contract data from window.CONTRACT_STATE
38018
+ * Helper method to build the data structure from global state
38019
+ */
38020
+ buildFromGlobalState() {
38021
+ const globalState = window.CONTRACT_STATE;
38022
+ if (!globalState) return null;
38023
+ return {
38024
+ isLoaded: globalState.isLoaded ?? false,
38025
+ isValid: globalState.isValid ?? false,
38026
+ timestamp: globalState.timestamp ?? null,
38027
+ energy: {
38028
+ total: globalState.energy?.total ?? 0,
38029
+ entries: globalState.energy?.entries ?? 0,
38030
+ commonArea: globalState.energy?.commonArea ?? 0,
38031
+ stores: globalState.energy?.stores ?? 0
38032
+ },
38033
+ water: {
38034
+ total: globalState.water?.total ?? 0,
38035
+ entries: globalState.water?.entries ?? 0,
38036
+ commonArea: globalState.water?.commonArea ?? 0,
38037
+ stores: globalState.water?.stores ?? 0
38038
+ },
38039
+ temperature: {
38040
+ total: globalState.temperature?.total ?? 0,
38041
+ internal: globalState.temperature?.internal ?? 0,
38042
+ stores: globalState.temperature?.stores ?? 0
38043
+ },
38044
+ discrepancies: globalState.discrepancies
38045
+ };
38046
+ }
38047
+ };
38048
+
37042
38049
  // src/components/ModalHeader/index.ts
37043
38050
  var DEFAULT_BG_COLOR = "#3e1a7d";
37044
38051
  var DEFAULT_TEXT_COLOR = "white";
@@ -40797,6 +41804,7 @@
40797
41804
  exports.CONSUMPTION_CHART_DEFAULTS = DEFAULT_CONFIG;
40798
41805
  exports.CONSUMPTION_THEME_COLORS = THEME_COLORS;
40799
41806
  exports.ConnectionStatusType = ConnectionStatusType;
41807
+ exports.ContractSummaryTooltip = ContractSummaryTooltip;
40800
41808
  exports.DEFAULT_CLAMP_RANGE = DEFAULT_CLAMP_RANGE;
40801
41809
  exports.DEFAULT_ENERGY_GROUP_COLORS = DEFAULT_ENERGY_GROUP_COLORS;
40802
41810
  exports.DEFAULT_GAS_GROUP_COLORS = DEFAULT_GAS_GROUP_COLORS;