myio-js-library 0.1.185 → 0.1.186

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(state) {
8867
- const { entityObject, i18n, enableSelection, enableDragDrop } = state;
8866
+ function buildDOM(state2) {
8867
+ const { entityObject, i18n, enableSelection, enableDragDrop } = state2;
8868
8868
  const root = document.createElement("div");
8869
8869
  root.className = "myio-ho-card";
8870
8870
  root.setAttribute("role", "group");
@@ -9190,8 +9190,8 @@
9190
9190
  }
9191
9191
  return isOffline;
9192
9192
  }
9193
- function paint(root, state) {
9194
- const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state;
9193
+ function paint(root, state2) {
9194
+ const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state2;
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 (state.enableSelection) {
9246
+ if (state2.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, state, callbacks) {
9295
- const { entityObject } = state;
9294
+ function bindEvents(root, state2, callbacks) {
9295
+ const { entityObject } = state2;
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 (state.isSelected !== isSelected) {
9352
- state.isSelected = isSelected;
9353
- paint(root, state);
9351
+ if (state2.isSelected !== isSelected) {
9352
+ state2.isSelected = isSelected;
9353
+ paint(root, state2);
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, state.entityObject, e);
9366
+ TempRangeTooltip.show(root, state2.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, state.entityObject, e);
9384
+ EnergyRangeTooltip.show(root, state2.entityObject, e);
9385
9385
  };
9386
9386
  const hideEnergyTooltip = () => {
9387
9387
  EnergyRangeTooltip._startDelayedHide();
@@ -9433,7 +9433,7 @@
9433
9433
  }
9434
9434
  });
9435
9435
  }
9436
- if (state.enableDragDrop) {
9436
+ if (state2.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 state = normalizeParams(params);
9479
- const root = buildDOM(state);
9480
- state.isSelected = params.isSelected || false;
9478
+ const state2 = normalizeParams(params);
9479
+ const root = buildDOM(state2);
9480
+ state2.isSelected = params.isSelected || false;
9481
9481
  containerEl.appendChild(root);
9482
- bindEvents(root, state, state.callbacks);
9483
- paint(root, state);
9482
+ bindEvents(root, state2, state2.callbacks);
9483
+ paint(root, state2);
9484
9484
  return {
9485
9485
  update(next) {
9486
9486
  if (next) {
9487
- Object.assign(state.entityObject, next);
9488
- paint(root, state);
9487
+ Object.assign(state2.entityObject, next);
9488
+ paint(root, state2);
9489
9489
  }
9490
9490
  },
9491
9491
  destroy() {
@@ -20794,15 +20794,45 @@
20794
20794
  }
20795
20795
  }
20796
20796
  /**
20797
- * Save mapInstantaneousPower to customer server_scope attributes
20797
+ * Fetch child customer relations (level 1) from a parent customer
20798
+ * Returns array of child customer IDs
20798
20799
  */
20799
- async saveCustomerPowerLimits(customerId, limits) {
20800
+ async fetchChildCustomerIds(parentCustomerId) {
20801
+ try {
20802
+ const url = `${this.tbBaseUrl}/api/relations/info?fromId=${parentCustomerId}&fromType=CUSTOMER`;
20803
+ const response = await fetch(url, {
20804
+ method: "GET",
20805
+ headers: {
20806
+ "X-Authorization": `Bearer ${this.jwtToken}`,
20807
+ "Content-Type": "application/json"
20808
+ }
20809
+ });
20810
+ if (!response.ok) {
20811
+ console.warn("[PowerLimitsPersister] Failed to fetch relations:", response.status);
20812
+ return [];
20813
+ }
20814
+ const relations = await response.json();
20815
+ if (!Array.isArray(relations) || relations.length === 0) {
20816
+ console.log("[PowerLimitsPersister] No child customer relations found");
20817
+ return [];
20818
+ }
20819
+ const childCustomerIds = relations.filter((rel) => rel.to?.entityType === "CUSTOMER" && rel.to?.id).map((rel) => rel.to.id);
20820
+ console.log(`[PowerLimitsPersister] Found ${childCustomerIds.length} child customer(s)`);
20821
+ return childCustomerIds;
20822
+ } catch (error) {
20823
+ console.error("[PowerLimitsPersister] Error fetching relations:", error);
20824
+ return [];
20825
+ }
20826
+ }
20827
+ /**
20828
+ * Save power limits to a single customer (internal method)
20829
+ */
20830
+ async saveToSingleCustomer(customerId, limits) {
20800
20831
  try {
20801
20832
  const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/attributes/SERVER_SCOPE`;
20802
20833
  const payload = {
20803
20834
  mapInstantaneousPower: limits
20804
20835
  };
20805
- console.log("[PowerLimitsPersister] Saving power limits:", payload);
20806
20836
  const response = await fetch(url, {
20807
20837
  method: "POST",
20808
20838
  headers: {
@@ -20812,10 +20842,41 @@
20812
20842
  body: JSON.stringify(payload)
20813
20843
  });
20814
20844
  if (!response.ok) {
20815
- throw this.createHttpError(response.status, await response.text().catch(() => ""));
20845
+ console.warn(`[PowerLimitsPersister] Failed to save to customer ${customerId}:`, response.status);
20846
+ return false;
20816
20847
  }
20817
- console.log("[PowerLimitsPersister] Successfully saved power limits");
20818
- return { ok: true };
20848
+ return true;
20849
+ } catch (error) {
20850
+ console.error(`[PowerLimitsPersister] Error saving to customer ${customerId}:`, error);
20851
+ return false;
20852
+ }
20853
+ }
20854
+ /**
20855
+ * Save mapInstantaneousPower to customer server_scope attributes
20856
+ * Also propagates to all child customers (level 1 relations)
20857
+ */
20858
+ async saveCustomerPowerLimits(customerId, limits) {
20859
+ try {
20860
+ console.log("[PowerLimitsPersister] Saving power limits:", { customerId, limits });
20861
+ const mainSaveSuccess = await this.saveToSingleCustomer(customerId, limits);
20862
+ if (!mainSaveSuccess) {
20863
+ throw new Error("Failed to save to main customer");
20864
+ }
20865
+ console.log("[PowerLimitsPersister] Successfully saved to main customer");
20866
+ const childCustomerIds = await this.fetchChildCustomerIds(customerId);
20867
+ let successCount = 1;
20868
+ if (childCustomerIds.length > 0) {
20869
+ console.log(`[PowerLimitsPersister] Saving to ${childCustomerIds.length} child customer(s)...`);
20870
+ const savePromises = childCustomerIds.map(
20871
+ (childId) => this.saveToSingleCustomer(childId, limits)
20872
+ );
20873
+ const results = await Promise.all(savePromises);
20874
+ const childSuccessCount = results.filter(Boolean).length;
20875
+ successCount += childSuccessCount;
20876
+ console.log(`[PowerLimitsPersister] Saved to ${childSuccessCount}/${childCustomerIds.length} child customer(s)`);
20877
+ }
20878
+ console.log(`[PowerLimitsPersister] Total: saved to ${successCount} customer(s)`);
20879
+ return { ok: true, savedCount: successCount };
20819
20880
  } catch (error) {
20820
20881
  console.error("[PowerLimitsPersister] Error saving power limits:", error);
20821
20882
  return { ok: false, error: this.mapError(error) };
@@ -22005,6 +22066,136 @@
22005
22066
  this.annotations = [];
22006
22067
  }
22007
22068
  }
22069
+ /**
22070
+ * Show a confirmation modal and return user's choice
22071
+ * Replaces native confirm() for better UX
22072
+ */
22073
+ showConfirmation(message, title = "Confirmar") {
22074
+ return new Promise((resolve) => {
22075
+ const overlay = document.createElement("div");
22076
+ overlay.className = "annotations-confirm-overlay";
22077
+ overlay.innerHTML = `
22078
+ <div class="annotations-confirm-modal">
22079
+ <div class="annotations-confirm-header">
22080
+ <span class="annotations-confirm-icon">\u26A0\uFE0F</span>
22081
+ <span class="annotations-confirm-title">${title}</span>
22082
+ </div>
22083
+ <div class="annotations-confirm-body">
22084
+ <p>${message}</p>
22085
+ </div>
22086
+ <div class="annotations-confirm-actions">
22087
+ <button class="annotations-confirm-btn annotations-confirm-btn--cancel" data-action="cancel">
22088
+ Cancelar
22089
+ </button>
22090
+ <button class="annotations-confirm-btn annotations-confirm-btn--confirm" data-action="confirm">
22091
+ Confirmar
22092
+ </button>
22093
+ </div>
22094
+ </div>
22095
+ `;
22096
+ if (!document.getElementById("annotations-confirm-styles")) {
22097
+ const style = document.createElement("style");
22098
+ style.id = "annotations-confirm-styles";
22099
+ style.textContent = `
22100
+ .annotations-confirm-overlay {
22101
+ position: fixed;
22102
+ inset: 0;
22103
+ background: rgba(0, 0, 0, 0.5);
22104
+ backdrop-filter: blur(4px);
22105
+ display: flex;
22106
+ align-items: center;
22107
+ justify-content: center;
22108
+ z-index: 100001;
22109
+ animation: confirmFadeIn 0.2s ease;
22110
+ }
22111
+ @keyframes confirmFadeIn {
22112
+ from { opacity: 0; }
22113
+ to { opacity: 1; }
22114
+ }
22115
+ .annotations-confirm-modal {
22116
+ background: white;
22117
+ border-radius: 12px;
22118
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
22119
+ max-width: 400px;
22120
+ width: 90%;
22121
+ overflow: hidden;
22122
+ animation: confirmSlideIn 0.25s ease;
22123
+ }
22124
+ @keyframes confirmSlideIn {
22125
+ from { transform: translateY(-20px) scale(0.95); opacity: 0; }
22126
+ to { transform: translateY(0) scale(1); opacity: 1; }
22127
+ }
22128
+ .annotations-confirm-header {
22129
+ display: flex;
22130
+ align-items: center;
22131
+ gap: 10px;
22132
+ padding: 16px 20px;
22133
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
22134
+ border-bottom: 1px solid #f59e0b;
22135
+ }
22136
+ .annotations-confirm-icon {
22137
+ font-size: 20px;
22138
+ }
22139
+ .annotations-confirm-title {
22140
+ font-weight: 600;
22141
+ color: #92400e;
22142
+ font-size: 16px;
22143
+ }
22144
+ .annotations-confirm-body {
22145
+ padding: 20px;
22146
+ }
22147
+ .annotations-confirm-body p {
22148
+ margin: 0;
22149
+ color: #374151;
22150
+ font-size: 14px;
22151
+ line-height: 1.5;
22152
+ }
22153
+ .annotations-confirm-actions {
22154
+ display: flex;
22155
+ gap: 12px;
22156
+ padding: 16px 20px;
22157
+ background: #f9fafb;
22158
+ border-top: 1px solid #e5e7eb;
22159
+ justify-content: flex-end;
22160
+ }
22161
+ .annotations-confirm-btn {
22162
+ padding: 10px 20px;
22163
+ border-radius: 8px;
22164
+ font-size: 14px;
22165
+ font-weight: 500;
22166
+ cursor: pointer;
22167
+ transition: all 0.2s ease;
22168
+ border: none;
22169
+ }
22170
+ .annotations-confirm-btn--cancel {
22171
+ background: #e5e7eb;
22172
+ color: #374151;
22173
+ }
22174
+ .annotations-confirm-btn--cancel:hover {
22175
+ background: #d1d5db;
22176
+ }
22177
+ .annotations-confirm-btn--confirm {
22178
+ background: #f59e0b;
22179
+ color: white;
22180
+ }
22181
+ .annotations-confirm-btn--confirm:hover {
22182
+ background: #d97706;
22183
+ }
22184
+ `;
22185
+ document.head.appendChild(style);
22186
+ }
22187
+ document.body.appendChild(overlay);
22188
+ const cleanup = (result) => {
22189
+ overlay.remove();
22190
+ resolve(result);
22191
+ };
22192
+ overlay.querySelector('[data-action="cancel"]')?.addEventListener("click", () => cleanup(false));
22193
+ overlay.querySelector('[data-action="confirm"]')?.addEventListener("click", () => cleanup(true));
22194
+ overlay.addEventListener("click", (e) => {
22195
+ if (e.target === overlay) cleanup(false);
22196
+ });
22197
+ });
22198
+ }
22008
22199
  async saveAnnotations() {
22009
22200
  try {
22010
22201
  const data = {
@@ -22068,7 +22259,7 @@
22068
22259
  this.render();
22069
22260
  } else {
22070
22261
  this.annotations.shift();
22071
- alert("Erro ao salvar anota\xE7\xE3o. Tente novamente.");
22262
+ MyIOToast.show("Erro ao salvar anota\xE7\xE3o. Tente novamente.", "error");
22072
22263
  }
22073
22264
  }
22074
22265
  async editAnnotation(id, changes) {
@@ -22078,9 +22269,10 @@
22078
22269
  const now = (/* @__PURE__ */ new Date()).toISOString();
22079
22270
  const changeRecord = {};
22080
22271
  for (const [key, value] of Object.entries(changes)) {
22081
- if (annotation[key] !== value) {
22272
+ const annotationAny = annotation;
22273
+ if (annotationAny[key] !== value) {
22082
22274
  changeRecord[key] = {
22083
- from: annotation[key],
22275
+ from: annotationAny[key],
22084
22276
  to: value
22085
22277
  };
22086
22278
  }
@@ -22105,7 +22297,7 @@
22105
22297
  const success = await this.saveAnnotations();
22106
22298
  if (!success) {
22107
22299
  this.annotations[index] = annotation;
22108
- alert("Erro ao atualizar anota\xE7\xE3o. Tente novamente.");
22300
+ MyIOToast.show("Erro ao atualizar anota\xE7\xE3o. Tente novamente.", "error");
22109
22301
  }
22110
22302
  }
22111
22303
  async archiveAnnotation(id) {
@@ -22133,7 +22325,7 @@
22133
22325
  this.render();
22134
22326
  } else {
22135
22327
  this.annotations[index] = annotation;
22136
- alert("Erro ao arquivar anota\xE7\xE3o. Tente novamente.");
22328
+ MyIOToast.show("Erro ao arquivar anota\xE7\xE3o. Tente novamente.", "error");
22137
22329
  }
22138
22330
  }
22139
22331
  async acknowledgeAnnotation(id) {
@@ -22648,8 +22840,8 @@
22648
22840
  card.querySelector('[data-action="details"]')?.addEventListener("click", () => {
22649
22841
  this.showDetailModal(id);
22650
22842
  });
22651
- card.querySelector('[data-action="archive"]')?.addEventListener("click", () => {
22652
- if (confirm("Tem certeza que deseja arquivar esta anota\xE7\xE3o?")) {
22843
+ card.querySelector('[data-action="archive"]')?.addEventListener("click", async () => {
22844
+ if (await this.showConfirmation("Tem certeza que deseja arquivar esta anota\xE7\xE3o?", "Arquivar Anota\xE7\xE3o")) {
22653
22845
  this.archiveAnnotation(id);
22654
22846
  }
22655
22847
  });
@@ -22666,8 +22858,8 @@
22666
22858
  row.querySelector('[data-action="details"]')?.addEventListener("click", () => {
22667
22859
  this.showDetailModal(id);
22668
22860
  });
22669
- row.querySelector('[data-action="archive"]')?.addEventListener("click", () => {
22670
- if (confirm("Tem certeza que deseja arquivar esta anota\xE7\xE3o?")) {
22861
+ row.querySelector('[data-action="archive"]')?.addEventListener("click", async () => {
22862
+ if (await this.showConfirmation("Tem certeza que deseja arquivar esta anota\xE7\xE3o?", "Arquivar Anota\xE7\xE3o")) {
22671
22863
  this.archiveAnnotation(id);
22672
22864
  }
22673
22865
  });
@@ -22676,12 +22868,189 @@
22676
22868
  // ============================================
22677
22869
  // EDIT MODAL
22678
22870
  // ============================================
22679
- showEditModal(id) {
22871
+ /**
22872
+ * Show a styled input modal for editing annotation text
22873
+ * Replaces native prompt() for better UX
22874
+ */
22875
+ showInputModal(title, placeholder, initialValue) {
22876
+ return new Promise((resolve) => {
22877
+ const overlay = document.createElement("div");
22878
+ overlay.className = "annotations-input-overlay";
22879
+ overlay.innerHTML = `
22880
+ <div class="annotations-input-modal">
22881
+ <div class="annotations-input-header">
22882
+ <span class="annotations-input-icon">\u270F\uFE0F</span>
22883
+ <span class="annotations-input-title">${title}</span>
22884
+ </div>
22885
+ <div class="annotations-input-body">
22886
+ <textarea
22887
+ class="annotations-input-textarea"
22888
+ placeholder="${placeholder}"
22889
+ maxlength="255"
22890
+ >${initialValue}</textarea>
22891
+ <div class="annotations-input-char-count">
22892
+ <span id="input-char-count">${initialValue.length}</span> / 255
22893
+ </div>
22894
+ </div>
22895
+ <div class="annotations-input-actions">
22896
+ <button class="annotations-input-btn annotations-input-btn--cancel" data-action="cancel">
22897
+ Cancelar
22898
+ </button>
22899
+ <button class="annotations-input-btn annotations-input-btn--confirm" data-action="confirm">
22900
+ Salvar
22901
+ </button>
22902
+ </div>
22903
+ </div>
22904
+ `;
22905
+ if (!document.getElementById("annotations-input-styles")) {
22906
+ const style = document.createElement("style");
22907
+ style.id = "annotations-input-styles";
22908
+ style.textContent = `
22909
+ .annotations-input-overlay {
22910
+ position: fixed;
22911
+ inset: 0;
22912
+ background: rgba(0, 0, 0, 0.5);
22913
+ backdrop-filter: blur(4px);
22914
+ display: flex;
22915
+ align-items: center;
22916
+ justify-content: center;
22917
+ z-index: 100001;
22918
+ animation: inputFadeIn 0.2s ease;
22919
+ }
22920
+ @keyframes inputFadeIn {
22921
+ from { opacity: 0; }
22922
+ to { opacity: 1; }
22923
+ }
22924
+ .annotations-input-modal {
22925
+ background: white;
22926
+ border-radius: 12px;
22927
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
22928
+ max-width: 500px;
22929
+ width: 90%;
22930
+ overflow: hidden;
22931
+ animation: inputSlideIn 0.25s ease;
22932
+ }
22933
+ @keyframes inputSlideIn {
22934
+ from { transform: translateY(-20px) scale(0.95); opacity: 0; }
22935
+ to { transform: translateY(0) scale(1); opacity: 1; }
22936
+ }
22937
+ .annotations-input-header {
22938
+ display: flex;
22939
+ align-items: center;
22940
+ gap: 10px;
22941
+ padding: 16px 20px;
22942
+ background: linear-gradient(135deg, #6c5ce7 0%, #a29bfe 100%);
22943
+ border-bottom: 1px solid #5b4cdb;
22944
+ }
22945
+ .annotations-input-icon {
22946
+ font-size: 20px;
22947
+ }
22948
+ .annotations-input-title {
22949
+ font-weight: 600;
22950
+ color: white;
22951
+ font-size: 16px;
22952
+ }
22953
+ .annotations-input-body {
22954
+ padding: 20px;
22955
+ }
22956
+ .annotations-input-textarea {
22957
+ width: 100%;
22958
+ min-height: 100px;
22959
+ padding: 12px;
22960
+ border: 1px solid #dee2e6;
22961
+ border-radius: 8px;
22962
+ font-size: 14px;
22963
+ font-family: inherit;
22964
+ resize: vertical;
22965
+ color: #212529;
22966
+ }
22967
+ .annotations-input-textarea:focus {
22968
+ outline: none;
22969
+ border-color: #6c5ce7;
22970
+ box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.15);
22971
+ }
22972
+ .annotations-input-char-count {
22973
+ font-size: 11px;
22974
+ color: #6c757d;
22975
+ text-align: right;
22976
+ margin-top: 6px;
22977
+ }
22978
+ .annotations-input-actions {
22979
+ display: flex;
22980
+ gap: 12px;
22981
+ padding: 16px 20px;
22982
+ background: #f9fafb;
22983
+ border-top: 1px solid #e5e7eb;
22984
+ justify-content: flex-end;
22985
+ }
22986
+ .annotations-input-btn {
22987
+ padding: 10px 20px;
22988
+ border-radius: 8px;
22989
+ font-size: 14px;
22990
+ font-weight: 500;
22991
+ cursor: pointer;
22992
+ transition: all 0.2s ease;
22993
+ border: none;
22994
+ }
22995
+ .annotations-input-btn--cancel {
22996
+ background: #e5e7eb;
22997
+ color: #374151;
22998
+ }
22999
+ .annotations-input-btn--cancel:hover {
23000
+ background: #d1d5db;
23001
+ }
23002
+ .annotations-input-btn--confirm {
23003
+ background: #6c5ce7;
23004
+ color: white;
23005
+ }
23006
+ .annotations-input-btn--confirm:hover {
23007
+ background: #5b4cdb;
23008
+ }
23009
+ `;
23010
+ document.head.appendChild(style);
23011
+ }
23012
+ document.body.appendChild(overlay);
23013
+ const textarea = overlay.querySelector(".annotations-input-textarea");
23014
+ const charCount = overlay.querySelector("#input-char-count");
23015
+ textarea.focus();
23016
+ textarea.select();
23017
+ textarea.addEventListener("input", () => {
23018
+ charCount.textContent = String(textarea.value.length);
23019
+ });
23020
+ const cleanup = (result) => {
23021
+ overlay.remove();
23022
+ resolve(result);
23023
+ };
23024
+ overlay.querySelector('[data-action="cancel"]')?.addEventListener("click", () => cleanup(null));
23025
+ overlay.querySelector('[data-action="confirm"]')?.addEventListener("click", () => {
23026
+ const value = textarea.value.trim();
23027
+ cleanup(value || null);
23028
+ });
23029
+ overlay.addEventListener("click", (e) => {
23030
+ if (e.target === overlay) cleanup(null);
23031
+ });
23032
+ textarea.addEventListener("keydown", (e) => {
23033
+ if (e.key === "Enter" && e.ctrlKey) {
23034
+ const value = textarea.value.trim();
23035
+ cleanup(value || null);
23036
+ }
23037
+ if (e.key === "Escape") {
23038
+ cleanup(null);
23039
+ }
23040
+ });
23041
+ });
23042
+ }
23043
+ async showEditModal(id) {
22680
23044
  const annotation = this.annotations.find((a) => a.id === id);
22681
23045
  if (!annotation) return;
22682
- const newText = prompt("Editar texto da anota\xE7\xE3o:", annotation.text);
22683
- if (newText && newText.trim() !== annotation.text) {
22684
- this.updateAnnotation(id, { text: newText.trim() });
23046
+ const newText = await this.showInputModal(
23047
+ "Editar Anota\xE7\xE3o",
23048
+ "Digite o novo texto da anota\xE7\xE3o...",
23049
+ annotation.text
23050
+ );
23051
+ if (newText && newText !== annotation.text) {
23052
+ await this.editAnnotation(id, { text: newText });
23053
+ this.render();
22685
23054
  }
22686
23055
  }
22687
23056
  // ============================================
@@ -22775,7 +23144,7 @@
22775
23144
  overlay.querySelector(".annotation-detail__close")?.addEventListener("click", () => overlay.remove());
22776
23145
  overlay.querySelector('[data-action="close"]')?.addEventListener("click", () => overlay.remove());
22777
23146
  overlay.querySelector('[data-action="archive"]')?.addEventListener("click", async () => {
22778
- if (confirm("Tem certeza que deseja arquivar esta anota\xE7\xE3o?")) {
23147
+ if (await this.showConfirmation("Tem certeza que deseja arquivar esta anota\xE7\xE3o?", "Arquivar Anota\xE7\xE3o")) {
22779
23148
  overlay.remove();
22780
23149
  await this.archiveAnnotation(id);
22781
23150
  }
@@ -27340,7 +27709,7 @@
27340
27709
  const defaultDateRange = getTodaySoFar();
27341
27710
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
27342
27711
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
27343
- const state = {
27712
+ const state2 = {
27344
27713
  token: params.token,
27345
27714
  deviceId: params.deviceId,
27346
27715
  label: params.label || "Sensor de Temperatura",
@@ -27363,58 +27732,58 @@
27363
27732
  };
27364
27733
  const savedGranularity = localStorage.getItem("myio-temp-modal-granularity");
27365
27734
  const savedTheme = localStorage.getItem("myio-temp-modal-theme");
27366
- if (savedGranularity) state.granularity = savedGranularity;
27367
- if (savedTheme) state.theme = savedTheme;
27735
+ if (savedGranularity) state2.granularity = savedGranularity;
27736
+ if (savedTheme) state2.theme = savedTheme;
27368
27737
  const modalContainer = document.createElement("div");
27369
27738
  modalContainer.id = modalId;
27370
27739
  document.body.appendChild(modalContainer);
27371
- renderModal(modalContainer, state, modalId);
27740
+ renderModal(modalContainer, state2, modalId);
27372
27741
  try {
27373
- state.data = await fetchTemperatureData(state.token, state.deviceId, state.startTs, state.endTs);
27374
- state.stats = calculateStats(state.data, state.clampRange);
27375
- state.isLoading = false;
27376
- renderModal(modalContainer, state, modalId);
27377
- drawChart(modalId, state);
27742
+ state2.data = await fetchTemperatureData(state2.token, state2.deviceId, state2.startTs, state2.endTs);
27743
+ state2.stats = calculateStats(state2.data, state2.clampRange);
27744
+ state2.isLoading = false;
27745
+ renderModal(modalContainer, state2, modalId);
27746
+ drawChart(modalId, state2);
27378
27747
  } catch (error) {
27379
27748
  console.error("[TemperatureModal] Error fetching data:", error);
27380
- state.isLoading = false;
27381
- renderModal(modalContainer, state, modalId, error);
27749
+ state2.isLoading = false;
27750
+ renderModal(modalContainer, state2, modalId, error);
27382
27751
  }
27383
- await setupEventListeners(modalContainer, state, modalId, params.onClose);
27752
+ await setupEventListeners(modalContainer, state2, modalId, params.onClose);
27384
27753
  return {
27385
27754
  destroy: () => {
27386
27755
  modalContainer.remove();
27387
27756
  params.onClose?.();
27388
27757
  },
27389
27758
  updateData: async (startDate, endDate, granularity) => {
27390
- state.startTs = new Date(startDate).getTime();
27391
- state.endTs = new Date(endDate).getTime();
27392
- if (granularity) state.granularity = granularity;
27393
- state.isLoading = true;
27394
- renderModal(modalContainer, state, modalId);
27759
+ state2.startTs = new Date(startDate).getTime();
27760
+ state2.endTs = new Date(endDate).getTime();
27761
+ if (granularity) state2.granularity = granularity;
27762
+ state2.isLoading = true;
27763
+ renderModal(modalContainer, state2, modalId);
27395
27764
  try {
27396
- state.data = await fetchTemperatureData(state.token, state.deviceId, state.startTs, state.endTs);
27397
- state.stats = calculateStats(state.data, state.clampRange);
27398
- state.isLoading = false;
27399
- renderModal(modalContainer, state, modalId);
27400
- drawChart(modalId, state);
27765
+ state2.data = await fetchTemperatureData(state2.token, state2.deviceId, state2.startTs, state2.endTs);
27766
+ state2.stats = calculateStats(state2.data, state2.clampRange);
27767
+ state2.isLoading = false;
27768
+ renderModal(modalContainer, state2, modalId);
27769
+ drawChart(modalId, state2);
27401
27770
  } catch (error) {
27402
27771
  console.error("[TemperatureModal] Error updating data:", error);
27403
- state.isLoading = false;
27404
- renderModal(modalContainer, state, modalId, error);
27772
+ state2.isLoading = false;
27773
+ renderModal(modalContainer, state2, modalId, error);
27405
27774
  }
27406
27775
  }
27407
27776
  };
27408
27777
  }
27409
- function renderModal(container, state, modalId, error) {
27410
- const colors = getThemeColors(state.theme);
27411
- const startDateStr = new Date(state.startTs).toLocaleDateString(state.locale);
27412
- const endDateStr = new Date(state.endTs).toLocaleDateString(state.locale);
27413
- const statusText = state.temperatureStatus === "ok" ? "Dentro da faixa" : state.temperatureStatus === "above" ? "Acima do limite" : state.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
27414
- const statusColor = state.temperatureStatus === "ok" ? colors.success : state.temperatureStatus === "above" ? colors.danger : state.temperatureStatus === "below" ? colors.primary : colors.textMuted;
27415
- const rangeText = state.temperatureMin !== null && state.temperatureMax !== null ? `${state.temperatureMin}\xB0C - ${state.temperatureMax}\xB0C` : "N\xE3o definida";
27416
- new Date(state.startTs).toISOString().slice(0, 16);
27417
- new Date(state.endTs).toISOString().slice(0, 16);
27778
+ function renderModal(container, state2, modalId, error) {
27779
+ const colors = getThemeColors(state2.theme);
27780
+ const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale);
27781
+ const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale);
27782
+ const statusText = state2.temperatureStatus === "ok" ? "Dentro da faixa" : state2.temperatureStatus === "above" ? "Acima do limite" : state2.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
27783
+ const statusColor = state2.temperatureStatus === "ok" ? colors.success : state2.temperatureStatus === "above" ? colors.danger : state2.temperatureStatus === "below" ? colors.primary : colors.textMuted;
27784
+ const rangeText = state2.temperatureMin !== null && state2.temperatureMax !== null ? `${state2.temperatureMin}\xB0C - ${state2.temperatureMax}\xB0C` : "N\xE3o definida";
27785
+ new Date(state2.startTs).toISOString().slice(0, 16);
27786
+ new Date(state2.endTs).toISOString().slice(0, 16);
27418
27787
  const isMaximized = container.__isMaximized || false;
27419
27788
  const contentMaxWidth = isMaximized ? "100%" : "900px";
27420
27789
  const contentMaxHeight = isMaximized ? "100vh" : "95vh";
@@ -27441,7 +27810,7 @@
27441
27810
  min-height: 20px;
27442
27811
  ">
27443
27812
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
27444
- \u{1F321}\uFE0F ${state.label} - Hist\xF3rico de Temperatura
27813
+ \u{1F321}\uFE0F ${state2.label} - Hist\xF3rico de Temperatura
27445
27814
  </h2>
27446
27815
  <div style="display: flex; gap: 4px; align-items: center;">
27447
27816
  <!-- Theme Toggle -->
@@ -27449,7 +27818,7 @@
27449
27818
  background: none; border: none; font-size: 16px; cursor: pointer;
27450
27819
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
27451
27820
  transition: background-color 0.2s;
27452
- ">${state.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
27821
+ ">${state2.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
27453
27822
  <!-- Maximize Button -->
27454
27823
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
27455
27824
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -27471,7 +27840,7 @@
27471
27840
  <!-- Controls Row -->
27472
27841
  <div style="
27473
27842
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
27474
- margin-bottom: 16px; padding: 16px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
27843
+ margin-bottom: 16px; padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
27475
27844
  border-radius: 6px; border: 1px solid ${colors.border};
27476
27845
  ">
27477
27846
  <!-- Granularity Select -->
@@ -27484,8 +27853,8 @@
27484
27853
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
27485
27854
  cursor: pointer; min-width: 130px;
27486
27855
  ">
27487
- <option value="hour" ${state.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
27488
- <option value="day" ${state.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
27856
+ <option value="hour" ${state2.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
27857
+ <option value="day" ${state2.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
27489
27858
  </select>
27490
27859
  </div>
27491
27860
  <!-- Day Period Filter (Multiselect) -->
@@ -27499,7 +27868,7 @@
27499
27868
  cursor: pointer; min-width: 180px; text-align: left;
27500
27869
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
27501
27870
  ">
27502
- <span>${getSelectedPeriodsLabel(state.selectedPeriods)}</span>
27871
+ <span>${getSelectedPeriodsLabel(state2.selectedPeriods)}</span>
27503
27872
  <span style="font-size: 10px;">\u25BC</span>
27504
27873
  </button>
27505
27874
  <div id="${modalId}-period-dropdown" style="
@@ -27512,12 +27881,12 @@
27512
27881
  <label style="
27513
27882
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
27514
27883
  cursor: pointer; font-size: 13px; color: ${colors.text};
27515
- " onmouseover="this.style.background='${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
27884
+ " onmouseover="this.style.background='${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
27516
27885
  onmouseout="this.style.background='transparent'">
27517
27886
  <input type="checkbox"
27518
27887
  name="${modalId}-period"
27519
27888
  value="${period.id}"
27520
- ${state.selectedPeriods.includes(period.id) ? "checked" : ""}
27889
+ ${state2.selectedPeriods.includes(period.id) ? "checked" : ""}
27521
27890
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
27522
27891
  ${period.label}
27523
27892
  </label>
@@ -27525,13 +27894,13 @@
27525
27894
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
27526
27895
  <button id="${modalId}-period-select-all" type="button" style="
27527
27896
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
27528
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
27897
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
27529
27898
  border: none; border-radius: 4px; cursor: pointer;
27530
27899
  font-size: 12px; color: ${colors.text};
27531
27900
  ">Selecionar Todos</button>
27532
27901
  <button id="${modalId}-period-clear" type="button" style="
27533
27902
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
27534
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
27903
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
27535
27904
  border: none; border-radius: 4px; cursor: pointer;
27536
27905
  font-size: 12px; color: ${colors.text};
27537
27906
  ">Limpar Sele\xE7\xE3o</button>
@@ -27556,8 +27925,8 @@
27556
27925
  font-size: 14px; font-weight: 500; height: 38px;
27557
27926
  display: flex; align-items: center; gap: 8px;
27558
27927
  font-family: 'Roboto', Arial, sans-serif;
27559
- " ${state.isLoading ? "disabled" : ""}>
27560
- ${state.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
27928
+ " ${state2.isLoading ? "disabled" : ""}>
27929
+ ${state2.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
27561
27930
  </button>
27562
27931
  </div>
27563
27932
 
@@ -27568,40 +27937,40 @@
27568
27937
  ">
27569
27938
  <!-- Current Temperature -->
27570
27939
  <div style="
27571
- padding: 16px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27940
+ padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27572
27941
  border-radius: 12px; border: 1px solid ${colors.border};
27573
27942
  ">
27574
27943
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Temperatura Atual</span>
27575
27944
  <div style="font-weight: 700; font-size: 24px; color: ${statusColor}; margin-top: 4px;">
27576
- ${state.currentTemperature !== null ? formatTemperature(state.currentTemperature) : "N/A"}
27945
+ ${state2.currentTemperature !== null ? formatTemperature(state2.currentTemperature) : "N/A"}
27577
27946
  </div>
27578
27947
  <div style="font-size: 11px; color: ${statusColor}; margin-top: 2px;">${statusText}</div>
27579
27948
  </div>
27580
27949
  <!-- Average -->
27581
27950
  <div style="
27582
- padding: 16px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27951
+ padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27583
27952
  border-radius: 12px; border: 1px solid ${colors.border};
27584
27953
  ">
27585
27954
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">M\xE9dia do Per\xEDodo</span>
27586
27955
  <div id="${modalId}-avg" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
27587
- ${state.stats.count > 0 ? formatTemperature(state.stats.avg) : "N/A"}
27956
+ ${state2.stats.count > 0 ? formatTemperature(state2.stats.avg) : "N/A"}
27588
27957
  </div>
27589
27958
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${startDateStr} - ${endDateStr}</div>
27590
27959
  </div>
27591
27960
  <!-- Min/Max -->
27592
27961
  <div style="
27593
- padding: 16px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27962
+ padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27594
27963
  border-radius: 12px; border: 1px solid ${colors.border};
27595
27964
  ">
27596
27965
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Min / Max</span>
27597
27966
  <div id="${modalId}-minmax" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
27598
- ${state.stats.count > 0 ? `${formatTemperature(state.stats.min)} / ${formatTemperature(state.stats.max)}` : "N/A"}
27967
+ ${state2.stats.count > 0 ? `${formatTemperature(state2.stats.min)} / ${formatTemperature(state2.stats.max)}` : "N/A"}
27599
27968
  </div>
27600
- <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state.stats.count} leituras</div>
27969
+ <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state2.stats.count} leituras</div>
27601
27970
  </div>
27602
27971
  <!-- Ideal Range -->
27603
27972
  <div style="
27604
- padding: 16px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27973
+ padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27605
27974
  border-radius: 12px; border: 1px solid ${colors.border};
27606
27975
  ">
27607
27976
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Faixa Ideal</span>
@@ -27617,18 +27986,18 @@
27617
27986
  Hist\xF3rico de Temperatura
27618
27987
  </h3>
27619
27988
  <div id="${modalId}-chart" style="
27620
- height: 320px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
27989
+ height: 320px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
27621
27990
  border-radius: 12px; display: flex; justify-content: center; align-items: center;
27622
27991
  border: 1px solid ${colors.border}; position: relative;
27623
27992
  ">
27624
- ${state.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
27993
+ ${state2.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
27625
27994
  <div style="animation: spin 1s linear infinite; font-size: 32px; margin-bottom: 8px;">\u21BB</div>
27626
27995
  <div>Carregando dados...</div>
27627
27996
  </div>` : error ? `<div style="text-align: center; color: ${colors.danger};">
27628
27997
  <div style="font-size: 32px; margin-bottom: 8px;">\u26A0\uFE0F</div>
27629
27998
  <div>Erro ao carregar dados</div>
27630
27999
  <div style="font-size: 12px; margin-top: 4px;">${error.message}</div>
27631
- </div>` : state.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
28000
+ </div>` : state2.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
27632
28001
  <div style="font-size: 32px; margin-bottom: 8px;">\u{1F4ED}</div>
27633
28002
  <div>Sem dados para o per\xEDodo selecionado</div>
27634
28003
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -27638,12 +28007,12 @@
27638
28007
  <!-- Actions -->
27639
28008
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
27640
28009
  <button id="${modalId}-export" style="
27641
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
28010
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
27642
28011
  color: ${colors.text}; border: 1px solid ${colors.border};
27643
28012
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
27644
28013
  font-size: 14px; display: flex; align-items: center; gap: 8px;
27645
28014
  font-family: 'Roboto', Arial, sans-serif;
27646
- " ${state.data.length === 0 ? "disabled" : ""}>
28015
+ " ${state2.data.length === 0 ? "disabled" : ""}>
27647
28016
  \u{1F4E5} Exportar CSV
27648
28017
  </button>
27649
28018
  <button id="${modalId}-close-btn" style="
@@ -27694,14 +28063,14 @@
27694
28063
  </style>
27695
28064
  `;
27696
28065
  }
27697
- function drawChart(modalId, state) {
28066
+ function drawChart(modalId, state2) {
27698
28067
  const chartContainer = document.getElementById(`${modalId}-chart`);
27699
28068
  const canvas = document.getElementById(`${modalId}-canvas`);
27700
- if (!chartContainer || !canvas || state.data.length === 0) return;
28069
+ if (!chartContainer || !canvas || state2.data.length === 0) return;
27701
28070
  const ctx = canvas.getContext("2d");
27702
28071
  if (!ctx) return;
27703
- const colors = getThemeColors(state.theme);
27704
- const filteredData = filterByDayPeriods(state.data, state.selectedPeriods);
28072
+ const colors = getThemeColors(state2.theme);
28073
+ const filteredData = filterByDayPeriods(state2.data, state2.selectedPeriods);
27705
28074
  if (filteredData.length === 0) {
27706
28075
  canvas.width = chartContainer.clientWidth;
27707
28076
  canvas.height = chartContainer.clientHeight;
@@ -27712,14 +28081,14 @@
27712
28081
  return;
27713
28082
  }
27714
28083
  let chartData;
27715
- if (state.granularity === "hour") {
28084
+ if (state2.granularity === "hour") {
27716
28085
  const interpolated = interpolateTemperature(filteredData, {
27717
28086
  intervalMinutes: 30,
27718
- startTs: state.startTs,
27719
- endTs: state.endTs,
27720
- clampRange: state.clampRange
28087
+ startTs: state2.startTs,
28088
+ endTs: state2.endTs,
28089
+ clampRange: state2.clampRange
27721
28090
  });
27722
- const filteredInterpolated = filterByDayPeriods(interpolated, state.selectedPeriods);
28091
+ const filteredInterpolated = filterByDayPeriods(interpolated, state2.selectedPeriods);
27723
28092
  chartData = filteredInterpolated.map((item) => ({
27724
28093
  x: item.ts,
27725
28094
  y: Number(item.value),
@@ -27727,7 +28096,7 @@
27727
28096
  screenY: 0
27728
28097
  }));
27729
28098
  } else {
27730
- const daily = aggregateByDay(filteredData, state.clampRange);
28099
+ const daily = aggregateByDay(filteredData, state2.clampRange);
27731
28100
  chartData = daily.map((item) => ({
27732
28101
  x: item.dateTs,
27733
28102
  y: item.avg,
@@ -27745,12 +28114,12 @@
27745
28114
  const paddingRight = 20;
27746
28115
  const paddingTop = 20;
27747
28116
  const paddingBottom = 55;
27748
- const isPeriodsFiltered = state.selectedPeriods.length < 4 && state.selectedPeriods.length > 0;
28117
+ const isPeriodsFiltered = state2.selectedPeriods.length < 4 && state2.selectedPeriods.length > 0;
27749
28118
  const values = chartData.map((d) => d.y);
27750
28119
  const dataMin = Math.min(...values);
27751
28120
  const dataMax = Math.max(...values);
27752
- const thresholdMin = state.temperatureMin !== null ? state.temperatureMin : dataMin;
27753
- const thresholdMax = state.temperatureMax !== null ? state.temperatureMax : dataMax;
28121
+ const thresholdMin = state2.temperatureMin !== null ? state2.temperatureMin : dataMin;
28122
+ const thresholdMax = state2.temperatureMax !== null ? state2.temperatureMax : dataMax;
27754
28123
  const minY = Math.min(dataMin, thresholdMin) - 1;
27755
28124
  const maxY = Math.max(dataMax, thresholdMax) + 1;
27756
28125
  const chartWidth = width - paddingLeft - paddingRight;
@@ -27782,9 +28151,9 @@
27782
28151
  ctx.lineTo(width - paddingRight, y);
27783
28152
  ctx.stroke();
27784
28153
  }
27785
- if (state.temperatureMin !== null && state.temperatureMax !== null) {
27786
- const rangeMinY = height - paddingBottom - (state.temperatureMin - minY) * scaleY;
27787
- const rangeMaxY = height - paddingBottom - (state.temperatureMax - minY) * scaleY;
28154
+ if (state2.temperatureMin !== null && state2.temperatureMax !== null) {
28155
+ const rangeMinY = height - paddingBottom - (state2.temperatureMin - minY) * scaleY;
28156
+ const rangeMaxY = height - paddingBottom - (state2.temperatureMax - minY) * scaleY;
27788
28157
  ctx.fillStyle = "rgba(76, 175, 80, 0.1)";
27789
28158
  ctx.fillRect(paddingLeft, rangeMaxY, chartWidth, rangeMinY - rangeMaxY);
27790
28159
  ctx.strokeStyle = colors.success;
@@ -27826,10 +28195,10 @@
27826
28195
  const point = chartData[i];
27827
28196
  const date = new Date(point.x);
27828
28197
  let label;
27829
- if (state.granularity === "hour") {
27830
- label = date.toLocaleTimeString(state.locale, { hour: "2-digit", minute: "2-digit" });
28198
+ if (state2.granularity === "hour") {
28199
+ label = date.toLocaleTimeString(state2.locale, { hour: "2-digit", minute: "2-digit" });
27831
28200
  } else {
27832
- label = date.toLocaleDateString(state.locale, { day: "2-digit", month: "2-digit" });
28201
+ label = date.toLocaleDateString(state2.locale, { day: "2-digit", month: "2-digit" });
27833
28202
  }
27834
28203
  ctx.strokeStyle = colors.chartGrid;
27835
28204
  ctx.lineWidth = 1;
@@ -27847,16 +28216,16 @@
27847
28216
  ctx.lineTo(paddingLeft, height - paddingBottom);
27848
28217
  ctx.lineTo(width - paddingRight, height - paddingBottom);
27849
28218
  ctx.stroke();
27850
- setupChartTooltip(canvas, chartContainer, chartData, state, colors);
28219
+ setupChartTooltip(canvas, chartContainer, chartData, state2, colors);
27851
28220
  }
27852
- function setupChartTooltip(canvas, container, chartData, state, colors) {
28221
+ function setupChartTooltip(canvas, container, chartData, state2, colors) {
27853
28222
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
27854
28223
  if (existingTooltip) existingTooltip.remove();
27855
28224
  const tooltip = document.createElement("div");
27856
28225
  tooltip.className = "myio-chart-tooltip";
27857
28226
  tooltip.style.cssText = `
27858
28227
  position: absolute;
27859
- background: ${state.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
28228
+ background: ${state2.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
27860
28229
  border: 1px solid ${colors.border};
27861
28230
  border-radius: 8px;
27862
28231
  padding: 10px 14px;
@@ -27893,17 +28262,17 @@
27893
28262
  if (point) {
27894
28263
  const date = new Date(point.x);
27895
28264
  let dateStr;
27896
- if (state.granularity === "hour") {
27897
- dateStr = date.toLocaleDateString(state.locale, {
28265
+ if (state2.granularity === "hour") {
28266
+ dateStr = date.toLocaleDateString(state2.locale, {
27898
28267
  day: "2-digit",
27899
28268
  month: "2-digit",
27900
28269
  year: "numeric"
27901
- }) + " " + date.toLocaleTimeString(state.locale, {
28270
+ }) + " " + date.toLocaleTimeString(state2.locale, {
27902
28271
  hour: "2-digit",
27903
28272
  minute: "2-digit"
27904
28273
  });
27905
28274
  } else {
27906
- dateStr = date.toLocaleDateString(state.locale, {
28275
+ dateStr = date.toLocaleDateString(state2.locale, {
27907
28276
  day: "2-digit",
27908
28277
  month: "2-digit",
27909
28278
  year: "numeric"
@@ -27941,7 +28310,7 @@
27941
28310
  canvas.style.cursor = "default";
27942
28311
  });
27943
28312
  }
27944
- async function setupEventListeners(container, state, modalId, onClose) {
28313
+ async function setupEventListeners(container, state2, modalId, onClose) {
27945
28314
  const closeModal = () => {
27946
28315
  container.remove();
27947
28316
  onClose?.();
@@ -27952,19 +28321,19 @@
27952
28321
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
27953
28322
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
27954
28323
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
27955
- if (dateRangeInput && !state.dateRangePicker) {
28324
+ if (dateRangeInput && !state2.dateRangePicker) {
27956
28325
  try {
27957
- state.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
27958
- presetStart: new Date(state.startTs).toISOString(),
27959
- presetEnd: new Date(state.endTs).toISOString(),
28326
+ state2.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
28327
+ presetStart: new Date(state2.startTs).toISOString(),
28328
+ presetEnd: new Date(state2.endTs).toISOString(),
27960
28329
  includeTime: true,
27961
28330
  timePrecision: "minute",
27962
28331
  maxRangeDays: 90,
27963
- locale: state.locale,
28332
+ locale: state2.locale,
27964
28333
  parentEl: container.querySelector(".myio-temp-modal-content"),
27965
28334
  onApply: (result) => {
27966
- state.startTs = new Date(result.startISO).getTime();
27967
- state.endTs = new Date(result.endISO).getTime();
28335
+ state2.startTs = new Date(result.startISO).getTime();
28336
+ state2.endTs = new Date(result.endISO).getTime();
27968
28337
  console.log("[TemperatureModal] Date range applied:", result);
27969
28338
  }
27970
28339
  });
@@ -27973,19 +28342,19 @@
27973
28342
  }
27974
28343
  }
27975
28344
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
27976
- state.theme = state.theme === "dark" ? "light" : "dark";
27977
- localStorage.setItem("myio-temp-modal-theme", state.theme);
27978
- state.dateRangePicker = null;
27979
- renderModal(container, state, modalId);
27980
- if (state.data.length > 0) drawChart(modalId, state);
27981
- await setupEventListeners(container, state, modalId, onClose);
28345
+ state2.theme = state2.theme === "dark" ? "light" : "dark";
28346
+ localStorage.setItem("myio-temp-modal-theme", state2.theme);
28347
+ state2.dateRangePicker = null;
28348
+ renderModal(container, state2, modalId);
28349
+ if (state2.data.length > 0) drawChart(modalId, state2);
28350
+ await setupEventListeners(container, state2, modalId, onClose);
27982
28351
  });
27983
28352
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
27984
28353
  container.__isMaximized = !container.__isMaximized;
27985
- state.dateRangePicker = null;
27986
- renderModal(container, state, modalId);
27987
- if (state.data.length > 0) drawChart(modalId, state);
27988
- await setupEventListeners(container, state, modalId, onClose);
28354
+ state2.dateRangePicker = null;
28355
+ renderModal(container, state2, modalId);
28356
+ if (state2.data.length > 0) drawChart(modalId, state2);
28357
+ await setupEventListeners(container, state2, modalId, onClose);
27989
28358
  });
27990
28359
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
27991
28360
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -28004,71 +28373,71 @@
28004
28373
  periodCheckboxes.forEach((checkbox) => {
28005
28374
  checkbox.addEventListener("change", () => {
28006
28375
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
28007
- state.selectedPeriods = checked;
28376
+ state2.selectedPeriods = checked;
28008
28377
  const btnLabel = periodBtn?.querySelector("span:first-child");
28009
28378
  if (btnLabel) {
28010
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
28379
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
28011
28380
  }
28012
- if (state.data.length > 0) drawChart(modalId, state);
28381
+ if (state2.data.length > 0) drawChart(modalId, state2);
28013
28382
  });
28014
28383
  });
28015
28384
  document.getElementById(`${modalId}-period-select-all`)?.addEventListener("click", () => {
28016
28385
  periodCheckboxes.forEach((cb) => {
28017
28386
  cb.checked = true;
28018
28387
  });
28019
- state.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
28388
+ state2.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
28020
28389
  const btnLabel = periodBtn?.querySelector("span:first-child");
28021
28390
  if (btnLabel) {
28022
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
28391
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
28023
28392
  }
28024
- if (state.data.length > 0) drawChart(modalId, state);
28393
+ if (state2.data.length > 0) drawChart(modalId, state2);
28025
28394
  });
28026
28395
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
28027
28396
  periodCheckboxes.forEach((cb) => {
28028
28397
  cb.checked = false;
28029
28398
  });
28030
- state.selectedPeriods = [];
28399
+ state2.selectedPeriods = [];
28031
28400
  const btnLabel = periodBtn?.querySelector("span:first-child");
28032
28401
  if (btnLabel) {
28033
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
28402
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
28034
28403
  }
28035
- if (state.data.length > 0) drawChart(modalId, state);
28404
+ if (state2.data.length > 0) drawChart(modalId, state2);
28036
28405
  });
28037
28406
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
28038
- state.granularity = e.target.value;
28039
- localStorage.setItem("myio-temp-modal-granularity", state.granularity);
28040
- if (state.data.length > 0) drawChart(modalId, state);
28407
+ state2.granularity = e.target.value;
28408
+ localStorage.setItem("myio-temp-modal-granularity", state2.granularity);
28409
+ if (state2.data.length > 0) drawChart(modalId, state2);
28041
28410
  });
28042
28411
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
28043
- if (state.startTs >= state.endTs) {
28412
+ if (state2.startTs >= state2.endTs) {
28044
28413
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
28045
28414
  return;
28046
28415
  }
28047
- state.isLoading = true;
28048
- state.dateRangePicker = null;
28049
- renderModal(container, state, modalId);
28416
+ state2.isLoading = true;
28417
+ state2.dateRangePicker = null;
28418
+ renderModal(container, state2, modalId);
28050
28419
  try {
28051
- state.data = await fetchTemperatureData(state.token, state.deviceId, state.startTs, state.endTs);
28052
- state.stats = calculateStats(state.data, state.clampRange);
28053
- state.isLoading = false;
28054
- renderModal(container, state, modalId);
28055
- drawChart(modalId, state);
28056
- await setupEventListeners(container, state, modalId, onClose);
28420
+ state2.data = await fetchTemperatureData(state2.token, state2.deviceId, state2.startTs, state2.endTs);
28421
+ state2.stats = calculateStats(state2.data, state2.clampRange);
28422
+ state2.isLoading = false;
28423
+ renderModal(container, state2, modalId);
28424
+ drawChart(modalId, state2);
28425
+ await setupEventListeners(container, state2, modalId, onClose);
28057
28426
  } catch (error) {
28058
28427
  console.error("[TemperatureModal] Error fetching data:", error);
28059
- state.isLoading = false;
28060
- renderModal(container, state, modalId, error);
28061
- await setupEventListeners(container, state, modalId, onClose);
28428
+ state2.isLoading = false;
28429
+ renderModal(container, state2, modalId, error);
28430
+ await setupEventListeners(container, state2, modalId, onClose);
28062
28431
  }
28063
28432
  });
28064
28433
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
28065
- if (state.data.length === 0) return;
28066
- const startDateStr = new Date(state.startTs).toLocaleDateString(state.locale).replace(/\//g, "-");
28067
- const endDateStr = new Date(state.endTs).toLocaleDateString(state.locale).replace(/\//g, "-");
28434
+ if (state2.data.length === 0) return;
28435
+ const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
28436
+ const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
28068
28437
  exportTemperatureCSV(
28069
- state.data,
28070
- state.label,
28071
- state.stats,
28438
+ state2.data,
28439
+ state2.label,
28440
+ state2.stats,
28072
28441
  startDateStr,
28073
28442
  endDateStr
28074
28443
  );
@@ -28081,7 +28450,7 @@
28081
28450
  const defaultDateRange = getTodaySoFar();
28082
28451
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
28083
28452
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
28084
- const state = {
28453
+ const state2 = {
28085
28454
  token: params.token,
28086
28455
  devices: params.devices,
28087
28456
  startTs,
@@ -28100,44 +28469,44 @@
28100
28469
  };
28101
28470
  const savedGranularity = localStorage.getItem("myio-temp-comparison-granularity");
28102
28471
  const savedTheme = localStorage.getItem("myio-temp-comparison-theme");
28103
- if (savedGranularity) state.granularity = savedGranularity;
28104
- if (savedTheme) state.theme = savedTheme;
28472
+ if (savedGranularity) state2.granularity = savedGranularity;
28473
+ if (savedTheme) state2.theme = savedTheme;
28105
28474
  const modalContainer = document.createElement("div");
28106
28475
  modalContainer.id = modalId;
28107
28476
  document.body.appendChild(modalContainer);
28108
- renderModal2(modalContainer, state, modalId);
28109
- await fetchAllDevicesData(state);
28110
- renderModal2(modalContainer, state, modalId);
28111
- drawComparisonChart(modalId, state);
28112
- await setupEventListeners2(modalContainer, state, modalId, params.onClose);
28477
+ renderModal2(modalContainer, state2, modalId);
28478
+ await fetchAllDevicesData(state2);
28479
+ renderModal2(modalContainer, state2, modalId);
28480
+ drawComparisonChart(modalId, state2);
28481
+ await setupEventListeners2(modalContainer, state2, modalId, params.onClose);
28113
28482
  return {
28114
28483
  destroy: () => {
28115
28484
  modalContainer.remove();
28116
28485
  params.onClose?.();
28117
28486
  },
28118
28487
  updateData: async (startDate, endDate, granularity) => {
28119
- state.startTs = new Date(startDate).getTime();
28120
- state.endTs = new Date(endDate).getTime();
28121
- if (granularity) state.granularity = granularity;
28122
- state.isLoading = true;
28123
- renderModal2(modalContainer, state, modalId);
28124
- await fetchAllDevicesData(state);
28125
- renderModal2(modalContainer, state, modalId);
28126
- drawComparisonChart(modalId, state);
28127
- setupEventListeners2(modalContainer, state, modalId, params.onClose);
28488
+ state2.startTs = new Date(startDate).getTime();
28489
+ state2.endTs = new Date(endDate).getTime();
28490
+ if (granularity) state2.granularity = granularity;
28491
+ state2.isLoading = true;
28492
+ renderModal2(modalContainer, state2, modalId);
28493
+ await fetchAllDevicesData(state2);
28494
+ renderModal2(modalContainer, state2, modalId);
28495
+ drawComparisonChart(modalId, state2);
28496
+ setupEventListeners2(modalContainer, state2, modalId, params.onClose);
28128
28497
  }
28129
28498
  };
28130
28499
  }
28131
- async function fetchAllDevicesData(state) {
28132
- state.isLoading = true;
28133
- state.deviceData = [];
28500
+ async function fetchAllDevicesData(state2) {
28501
+ state2.isLoading = true;
28502
+ state2.deviceData = [];
28134
28503
  try {
28135
28504
  const results = await Promise.all(
28136
- state.devices.map(async (device, index) => {
28505
+ state2.devices.map(async (device, index) => {
28137
28506
  const deviceId = device.tbId || device.id;
28138
28507
  try {
28139
- const data = await fetchTemperatureData(state.token, deviceId, state.startTs, state.endTs);
28140
- const stats = calculateStats(data, state.clampRange);
28508
+ const data = await fetchTemperatureData(state2.token, deviceId, state2.startTs, state2.endTs);
28509
+ const stats = calculateStats(data, state2.clampRange);
28141
28510
  return {
28142
28511
  device,
28143
28512
  data,
@@ -28155,21 +28524,21 @@
28155
28524
  }
28156
28525
  })
28157
28526
  );
28158
- state.deviceData = results;
28527
+ state2.deviceData = results;
28159
28528
  } catch (error) {
28160
28529
  console.error("[TemperatureComparisonModal] Error fetching data:", error);
28161
28530
  }
28162
- state.isLoading = false;
28531
+ state2.isLoading = false;
28163
28532
  }
28164
- function renderModal2(container, state, modalId) {
28165
- const colors = getThemeColors(state.theme);
28166
- new Date(state.startTs).toLocaleDateString(state.locale);
28167
- new Date(state.endTs).toLocaleDateString(state.locale);
28168
- new Date(state.startTs).toISOString().slice(0, 16);
28169
- new Date(state.endTs).toISOString().slice(0, 16);
28170
- const legendHTML = state.deviceData.map((dd) => `
28533
+ function renderModal2(container, state2, modalId) {
28534
+ const colors = getThemeColors(state2.theme);
28535
+ new Date(state2.startTs).toLocaleDateString(state2.locale);
28536
+ new Date(state2.endTs).toLocaleDateString(state2.locale);
28537
+ new Date(state2.startTs).toISOString().slice(0, 16);
28538
+ new Date(state2.endTs).toISOString().slice(0, 16);
28539
+ const legendHTML = state2.deviceData.map((dd) => `
28171
28540
  <div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px;
28172
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
28541
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
28173
28542
  border-radius: 8px;">
28174
28543
  <span style="width: 12px; height: 12px; border-radius: 50%; background: ${dd.color};"></span>
28175
28544
  <span style="color: ${colors.text}; font-size: 13px;">${dd.device.label}</span>
@@ -28178,9 +28547,9 @@
28178
28547
  </span>
28179
28548
  </div>
28180
28549
  `).join("");
28181
- const statsHTML = state.deviceData.map((dd) => `
28550
+ const statsHTML = state2.deviceData.map((dd) => `
28182
28551
  <div style="
28183
- padding: 12px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
28552
+ padding: 12px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
28184
28553
  border-radius: 10px; border-left: 4px solid ${dd.color};
28185
28554
  min-width: 150px;
28186
28555
  ">
@@ -28231,7 +28600,7 @@
28231
28600
  min-height: 20px;
28232
28601
  ">
28233
28602
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
28234
- \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state.devices.length} sensores
28603
+ \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state2.devices.length} sensores
28235
28604
  </h2>
28236
28605
  <div style="display: flex; gap: 4px; align-items: center;">
28237
28606
  <!-- Theme Toggle -->
@@ -28239,7 +28608,7 @@
28239
28608
  background: none; border: none; font-size: 16px; cursor: pointer;
28240
28609
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
28241
28610
  transition: background-color 0.2s;
28242
- ">${state.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
28611
+ ">${state2.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
28243
28612
  <!-- Maximize Button -->
28244
28613
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
28245
28614
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -28262,7 +28631,7 @@
28262
28631
  <div style="
28263
28632
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
28264
28633
  margin-bottom: 16px; padding: 16px;
28265
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
28634
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
28266
28635
  border-radius: 6px; border: 1px solid ${colors.border};
28267
28636
  ">
28268
28637
  <!-- Granularity Select -->
@@ -28275,8 +28644,8 @@
28275
28644
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
28276
28645
  cursor: pointer; min-width: 130px;
28277
28646
  ">
28278
- <option value="hour" ${state.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
28279
- <option value="day" ${state.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
28647
+ <option value="hour" ${state2.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
28648
+ <option value="day" ${state2.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
28280
28649
  </select>
28281
28650
  </div>
28282
28651
  <!-- Day Period Filter (Multiselect) -->
@@ -28290,7 +28659,7 @@
28290
28659
  cursor: pointer; min-width: 180px; text-align: left;
28291
28660
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
28292
28661
  ">
28293
- <span>${getSelectedPeriodsLabel(state.selectedPeriods)}</span>
28662
+ <span>${getSelectedPeriodsLabel(state2.selectedPeriods)}</span>
28294
28663
  <span style="font-size: 10px;">\u25BC</span>
28295
28664
  </button>
28296
28665
  <div id="${modalId}-period-dropdown" style="
@@ -28303,12 +28672,12 @@
28303
28672
  <label style="
28304
28673
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
28305
28674
  cursor: pointer; font-size: 13px; color: ${colors.text};
28306
- " onmouseover="this.style.background='${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
28675
+ " onmouseover="this.style.background='${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
28307
28676
  onmouseout="this.style.background='transparent'">
28308
28677
  <input type="checkbox"
28309
28678
  name="${modalId}-period"
28310
28679
  value="${period.id}"
28311
- ${state.selectedPeriods.includes(period.id) ? "checked" : ""}
28680
+ ${state2.selectedPeriods.includes(period.id) ? "checked" : ""}
28312
28681
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
28313
28682
  ${period.label}
28314
28683
  </label>
@@ -28316,13 +28685,13 @@
28316
28685
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
28317
28686
  <button id="${modalId}-period-select-all" type="button" style="
28318
28687
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
28319
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
28688
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
28320
28689
  border: none; border-radius: 4px; cursor: pointer;
28321
28690
  font-size: 12px; color: ${colors.text};
28322
28691
  ">Selecionar Todos</button>
28323
28692
  <button id="${modalId}-period-clear" type="button" style="
28324
28693
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
28325
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
28694
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
28326
28695
  border: none; border-radius: 4px; cursor: pointer;
28327
28696
  font-size: 12px; color: ${colors.text};
28328
28697
  ">Limpar Sele\xE7\xE3o</button>
@@ -28347,8 +28716,8 @@
28347
28716
  font-size: 14px; font-weight: 500; height: 38px;
28348
28717
  display: flex; align-items: center; gap: 8px;
28349
28718
  font-family: 'Roboto', Arial, sans-serif;
28350
- " ${state.isLoading ? "disabled" : ""}>
28351
- ${state.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
28719
+ " ${state2.isLoading ? "disabled" : ""}>
28720
+ ${state2.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
28352
28721
  </button>
28353
28722
  </div>
28354
28723
 
@@ -28364,14 +28733,14 @@
28364
28733
  <div style="margin-bottom: 24px;">
28365
28734
  <div id="${modalId}-chart" style="
28366
28735
  height: 380px;
28367
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
28736
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
28368
28737
  border-radius: 14px; display: flex; justify-content: center; align-items: center;
28369
28738
  border: 1px solid ${colors.border}; position: relative;
28370
28739
  ">
28371
- ${state.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
28740
+ ${state2.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
28372
28741
  <div style="animation: spin 1s linear infinite; font-size: 36px; margin-bottom: 12px;">\u21BB</div>
28373
- <div style="font-size: 15px;">Carregando dados de ${state.devices.length} sensores...</div>
28374
- </div>` : state.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
28742
+ <div style="font-size: 15px;">Carregando dados de ${state2.devices.length} sensores...</div>
28743
+ </div>` : state2.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
28375
28744
  <div style="font-size: 48px; margin-bottom: 12px;">\u{1F4ED}</div>
28376
28745
  <div style="font-size: 16px;">Sem dados para o per\xEDodo selecionado</div>
28377
28746
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -28389,12 +28758,12 @@
28389
28758
  <!-- Actions -->
28390
28759
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
28391
28760
  <button id="${modalId}-export" style="
28392
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
28761
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
28393
28762
  color: ${colors.text}; border: 1px solid ${colors.border};
28394
28763
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
28395
28764
  font-size: 14px; display: flex; align-items: center; gap: 8px;
28396
28765
  font-family: 'Roboto', Arial, sans-serif;
28397
- " ${state.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
28766
+ " ${state2.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
28398
28767
  \u{1F4E5} Exportar CSV
28399
28768
  </button>
28400
28769
  <button id="${modalId}-close-btn" style="
@@ -28429,15 +28798,15 @@
28429
28798
  </style>
28430
28799
  `;
28431
28800
  }
28432
- function drawComparisonChart(modalId, state) {
28801
+ function drawComparisonChart(modalId, state2) {
28433
28802
  const chartContainer = document.getElementById(`${modalId}-chart`);
28434
28803
  const canvas = document.getElementById(`${modalId}-canvas`);
28435
28804
  if (!chartContainer || !canvas) return;
28436
- const hasData = state.deviceData.some((dd) => dd.data.length > 0);
28805
+ const hasData = state2.deviceData.some((dd) => dd.data.length > 0);
28437
28806
  if (!hasData) return;
28438
28807
  const ctx = canvas.getContext("2d");
28439
28808
  if (!ctx) return;
28440
- const colors = getThemeColors(state.theme);
28809
+ const colors = getThemeColors(state2.theme);
28441
28810
  const width = chartContainer.clientWidth - 2;
28442
28811
  const height = 380;
28443
28812
  canvas.width = width;
@@ -28448,19 +28817,19 @@
28448
28817
  const paddingBottom = 55;
28449
28818
  ctx.clearRect(0, 0, width, height);
28450
28819
  const processedData = [];
28451
- state.deviceData.forEach((dd) => {
28820
+ state2.deviceData.forEach((dd) => {
28452
28821
  if (dd.data.length === 0) return;
28453
- const filteredData = filterByDayPeriods(dd.data, state.selectedPeriods);
28822
+ const filteredData = filterByDayPeriods(dd.data, state2.selectedPeriods);
28454
28823
  if (filteredData.length === 0) return;
28455
28824
  let points;
28456
- if (state.granularity === "hour") {
28825
+ if (state2.granularity === "hour") {
28457
28826
  const interpolated = interpolateTemperature(filteredData, {
28458
28827
  intervalMinutes: 30,
28459
- startTs: state.startTs,
28460
- endTs: state.endTs,
28461
- clampRange: state.clampRange
28828
+ startTs: state2.startTs,
28829
+ endTs: state2.endTs,
28830
+ clampRange: state2.clampRange
28462
28831
  });
28463
- const filteredInterpolated = filterByDayPeriods(interpolated, state.selectedPeriods);
28832
+ const filteredInterpolated = filterByDayPeriods(interpolated, state2.selectedPeriods);
28464
28833
  points = filteredInterpolated.map((item) => ({
28465
28834
  x: item.ts,
28466
28835
  y: Number(item.value),
@@ -28470,7 +28839,7 @@
28470
28839
  deviceColor: dd.color
28471
28840
  }));
28472
28841
  } else {
28473
- const daily = aggregateByDay(filteredData, state.clampRange);
28842
+ const daily = aggregateByDay(filteredData, state2.clampRange);
28474
28843
  points = daily.map((item) => ({
28475
28844
  x: item.dateTs,
28476
28845
  y: item.avg,
@@ -28491,7 +28860,7 @@
28491
28860
  ctx.fillText("Nenhum dado para os per\xEDodos selecionados", width / 2, height / 2);
28492
28861
  return;
28493
28862
  }
28494
- const isPeriodsFiltered = state.selectedPeriods.length < 4 && state.selectedPeriods.length > 0;
28863
+ const isPeriodsFiltered = state2.selectedPeriods.length < 4 && state2.selectedPeriods.length > 0;
28495
28864
  let dataMinY = Infinity;
28496
28865
  let dataMaxY = -Infinity;
28497
28866
  processedData.forEach(({ points }) => {
@@ -28501,7 +28870,7 @@
28501
28870
  });
28502
28871
  });
28503
28872
  const rangeMap = /* @__PURE__ */ new Map();
28504
- state.deviceData.forEach((dd, index) => {
28873
+ state2.deviceData.forEach((dd, index) => {
28505
28874
  const device = dd.device;
28506
28875
  const min = device.temperatureMin;
28507
28876
  const max = device.temperatureMax;
@@ -28520,10 +28889,10 @@
28520
28889
  }
28521
28890
  }
28522
28891
  });
28523
- if (rangeMap.size === 0 && state.temperatureMin !== null && state.temperatureMax !== null) {
28892
+ if (rangeMap.size === 0 && state2.temperatureMin !== null && state2.temperatureMax !== null) {
28524
28893
  rangeMap.set("global", {
28525
- min: state.temperatureMin,
28526
- max: state.temperatureMax,
28894
+ min: state2.temperatureMin,
28895
+ max: state2.temperatureMax,
28527
28896
  customerName: "Global",
28528
28897
  color: colors.success,
28529
28898
  deviceLabels: []
@@ -28644,10 +29013,10 @@
28644
29013
  const point = xAxisPoints[i];
28645
29014
  const date = new Date(point.x);
28646
29015
  let label;
28647
- if (state.granularity === "hour") {
28648
- label = date.toLocaleTimeString(state.locale, { hour: "2-digit", minute: "2-digit" });
29016
+ if (state2.granularity === "hour") {
29017
+ label = date.toLocaleTimeString(state2.locale, { hour: "2-digit", minute: "2-digit" });
28649
29018
  } else {
28650
- label = date.toLocaleDateString(state.locale, { day: "2-digit", month: "2-digit" });
29019
+ label = date.toLocaleDateString(state2.locale, { day: "2-digit", month: "2-digit" });
28651
29020
  }
28652
29021
  ctx.strokeStyle = colors.chartGrid;
28653
29022
  ctx.lineWidth = 1;
@@ -28666,16 +29035,16 @@
28666
29035
  ctx.lineTo(width - paddingRight, height - paddingBottom);
28667
29036
  ctx.stroke();
28668
29037
  const allChartPoints = processedData.flatMap((pd) => pd.points);
28669
- setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state, colors);
29038
+ setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state2, colors);
28670
29039
  }
28671
- function setupComparisonChartTooltip(canvas, container, chartData, state, colors) {
29040
+ function setupComparisonChartTooltip(canvas, container, chartData, state2, colors) {
28672
29041
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
28673
29042
  if (existingTooltip) existingTooltip.remove();
28674
29043
  const tooltip = document.createElement("div");
28675
29044
  tooltip.className = "myio-chart-tooltip";
28676
29045
  tooltip.style.cssText = `
28677
29046
  position: absolute;
28678
- background: ${state.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
29047
+ background: ${state2.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
28679
29048
  border: 1px solid ${colors.border};
28680
29049
  border-radius: 8px;
28681
29050
  padding: 10px 14px;
@@ -28712,17 +29081,17 @@
28712
29081
  if (point) {
28713
29082
  const date = new Date(point.x);
28714
29083
  let dateStr;
28715
- if (state.granularity === "hour") {
28716
- dateStr = date.toLocaleDateString(state.locale, {
29084
+ if (state2.granularity === "hour") {
29085
+ dateStr = date.toLocaleDateString(state2.locale, {
28717
29086
  day: "2-digit",
28718
29087
  month: "2-digit",
28719
29088
  year: "numeric"
28720
- }) + " " + date.toLocaleTimeString(state.locale, {
29089
+ }) + " " + date.toLocaleTimeString(state2.locale, {
28721
29090
  hour: "2-digit",
28722
29091
  minute: "2-digit"
28723
29092
  });
28724
29093
  } else {
28725
- dateStr = date.toLocaleDateString(state.locale, {
29094
+ dateStr = date.toLocaleDateString(state2.locale, {
28726
29095
  day: "2-digit",
28727
29096
  month: "2-digit",
28728
29097
  year: "numeric"
@@ -28764,7 +29133,7 @@
28764
29133
  canvas.style.cursor = "default";
28765
29134
  });
28766
29135
  }
28767
- async function setupEventListeners2(container, state, modalId, onClose) {
29136
+ async function setupEventListeners2(container, state2, modalId, onClose) {
28768
29137
  const closeModal = () => {
28769
29138
  container.remove();
28770
29139
  onClose?.();
@@ -28775,19 +29144,19 @@
28775
29144
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
28776
29145
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
28777
29146
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
28778
- if (dateRangeInput && !state.dateRangePicker) {
29147
+ if (dateRangeInput && !state2.dateRangePicker) {
28779
29148
  try {
28780
- state.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
28781
- presetStart: new Date(state.startTs).toISOString(),
28782
- presetEnd: new Date(state.endTs).toISOString(),
29149
+ state2.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
29150
+ presetStart: new Date(state2.startTs).toISOString(),
29151
+ presetEnd: new Date(state2.endTs).toISOString(),
28783
29152
  includeTime: true,
28784
29153
  timePrecision: "minute",
28785
29154
  maxRangeDays: 90,
28786
- locale: state.locale,
29155
+ locale: state2.locale,
28787
29156
  parentEl: container.querySelector(".myio-temp-comparison-content"),
28788
29157
  onApply: (result) => {
28789
- state.startTs = new Date(result.startISO).getTime();
28790
- state.endTs = new Date(result.endISO).getTime();
29158
+ state2.startTs = new Date(result.startISO).getTime();
29159
+ state2.endTs = new Date(result.endISO).getTime();
28791
29160
  console.log("[TemperatureComparisonModal] Date range applied:", result);
28792
29161
  }
28793
29162
  });
@@ -28796,23 +29165,23 @@
28796
29165
  }
28797
29166
  }
28798
29167
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
28799
- state.theme = state.theme === "dark" ? "light" : "dark";
28800
- localStorage.setItem("myio-temp-comparison-theme", state.theme);
28801
- state.dateRangePicker = null;
28802
- renderModal2(container, state, modalId);
28803
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
28804
- drawComparisonChart(modalId, state);
28805
- }
28806
- await setupEventListeners2(container, state, modalId, onClose);
29168
+ state2.theme = state2.theme === "dark" ? "light" : "dark";
29169
+ localStorage.setItem("myio-temp-comparison-theme", state2.theme);
29170
+ state2.dateRangePicker = null;
29171
+ renderModal2(container, state2, modalId);
29172
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29173
+ drawComparisonChart(modalId, state2);
29174
+ }
29175
+ await setupEventListeners2(container, state2, modalId, onClose);
28807
29176
  });
28808
29177
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
28809
29178
  container.__isMaximized = !container.__isMaximized;
28810
- state.dateRangePicker = null;
28811
- renderModal2(container, state, modalId);
28812
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
28813
- drawComparisonChart(modalId, state);
29179
+ state2.dateRangePicker = null;
29180
+ renderModal2(container, state2, modalId);
29181
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29182
+ drawComparisonChart(modalId, state2);
28814
29183
  }
28815
- await setupEventListeners2(container, state, modalId, onClose);
29184
+ await setupEventListeners2(container, state2, modalId, onClose);
28816
29185
  });
28817
29186
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
28818
29187
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -28831,13 +29200,13 @@
28831
29200
  periodCheckboxes.forEach((checkbox) => {
28832
29201
  checkbox.addEventListener("change", () => {
28833
29202
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
28834
- state.selectedPeriods = checked;
29203
+ state2.selectedPeriods = checked;
28835
29204
  const btnLabel = periodBtn?.querySelector("span:first-child");
28836
29205
  if (btnLabel) {
28837
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
29206
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
28838
29207
  }
28839
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
28840
- drawComparisonChart(modalId, state);
29208
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29209
+ drawComparisonChart(modalId, state2);
28841
29210
  }
28842
29211
  });
28843
29212
  });
@@ -28845,77 +29214,77 @@
28845
29214
  periodCheckboxes.forEach((cb) => {
28846
29215
  cb.checked = true;
28847
29216
  });
28848
- state.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
29217
+ state2.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
28849
29218
  const btnLabel = periodBtn?.querySelector("span:first-child");
28850
29219
  if (btnLabel) {
28851
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
29220
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
28852
29221
  }
28853
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
28854
- drawComparisonChart(modalId, state);
29222
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29223
+ drawComparisonChart(modalId, state2);
28855
29224
  }
28856
29225
  });
28857
29226
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
28858
29227
  periodCheckboxes.forEach((cb) => {
28859
29228
  cb.checked = false;
28860
29229
  });
28861
- state.selectedPeriods = [];
29230
+ state2.selectedPeriods = [];
28862
29231
  const btnLabel = periodBtn?.querySelector("span:first-child");
28863
29232
  if (btnLabel) {
28864
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
29233
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
28865
29234
  }
28866
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
28867
- drawComparisonChart(modalId, state);
29235
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29236
+ drawComparisonChart(modalId, state2);
28868
29237
  }
28869
29238
  });
28870
29239
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
28871
- state.granularity = e.target.value;
28872
- localStorage.setItem("myio-temp-comparison-granularity", state.granularity);
28873
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
28874
- drawComparisonChart(modalId, state);
29240
+ state2.granularity = e.target.value;
29241
+ localStorage.setItem("myio-temp-comparison-granularity", state2.granularity);
29242
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29243
+ drawComparisonChart(modalId, state2);
28875
29244
  }
28876
29245
  });
28877
29246
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
28878
- if (state.startTs >= state.endTs) {
29247
+ if (state2.startTs >= state2.endTs) {
28879
29248
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
28880
29249
  return;
28881
29250
  }
28882
- state.isLoading = true;
28883
- state.dateRangePicker = null;
28884
- renderModal2(container, state, modalId);
28885
- await fetchAllDevicesData(state);
28886
- renderModal2(container, state, modalId);
28887
- drawComparisonChart(modalId, state);
28888
- await setupEventListeners2(container, state, modalId, onClose);
29251
+ state2.isLoading = true;
29252
+ state2.dateRangePicker = null;
29253
+ renderModal2(container, state2, modalId);
29254
+ await fetchAllDevicesData(state2);
29255
+ renderModal2(container, state2, modalId);
29256
+ drawComparisonChart(modalId, state2);
29257
+ await setupEventListeners2(container, state2, modalId, onClose);
28889
29258
  });
28890
29259
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
28891
- if (state.deviceData.every((dd) => dd.data.length === 0)) return;
28892
- exportComparisonCSV(state);
29260
+ if (state2.deviceData.every((dd) => dd.data.length === 0)) return;
29261
+ exportComparisonCSV(state2);
28893
29262
  });
28894
29263
  }
28895
- function exportComparisonCSV(state) {
28896
- const startDateStr = new Date(state.startTs).toLocaleDateString(state.locale).replace(/\//g, "-");
28897
- const endDateStr = new Date(state.endTs).toLocaleDateString(state.locale).replace(/\//g, "-");
29264
+ function exportComparisonCSV(state2) {
29265
+ const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
29266
+ const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
28898
29267
  const BOM = "\uFEFF";
28899
29268
  let csvContent = BOM;
28900
29269
  csvContent += `Compara\xE7\xE3o de Temperatura
28901
29270
  `;
28902
29271
  csvContent += `Per\xEDodo: ${startDateStr} at\xE9 ${endDateStr}
28903
29272
  `;
28904
- csvContent += `Sensores: ${state.devices.map((d) => d.label).join(", ")}
29273
+ csvContent += `Sensores: ${state2.devices.map((d) => d.label).join(", ")}
28905
29274
  `;
28906
29275
  csvContent += "\n";
28907
29276
  csvContent += "Estat\xEDsticas por Sensor:\n";
28908
29277
  csvContent += "Sensor,M\xE9dia (\xB0C),Min (\xB0C),Max (\xB0C),Leituras\n";
28909
- state.deviceData.forEach((dd) => {
29278
+ state2.deviceData.forEach((dd) => {
28910
29279
  csvContent += `"${dd.device.label}",${dd.stats.avg.toFixed(2)},${dd.stats.min.toFixed(2)},${dd.stats.max.toFixed(2)},${dd.stats.count}
28911
29280
  `;
28912
29281
  });
28913
29282
  csvContent += "\n";
28914
29283
  csvContent += "Dados Detalhados:\n";
28915
29284
  csvContent += "Data/Hora,Sensor,Temperatura (\xB0C)\n";
28916
- state.deviceData.forEach((dd) => {
29285
+ state2.deviceData.forEach((dd) => {
28917
29286
  dd.data.forEach((item) => {
28918
- const date = new Date(item.ts).toLocaleString(state.locale);
29287
+ const date = new Date(item.ts).toLocaleString(state2.locale);
28919
29288
  const temp = Number(item.value).toFixed(2);
28920
29289
  csvContent += `"${date}","${dd.device.label}",${temp}
28921
29290
  `;
@@ -29023,10 +29392,10 @@
29023
29392
  throw new Error(`Failed to save attributes: ${response.status}`);
29024
29393
  }
29025
29394
  }
29026
- function renderModal3(container, state, modalId, onClose, onSave) {
29027
- const colors = getColors(state.theme);
29028
- const minValue = state.minTemperature !== null ? state.minTemperature : "";
29029
- const maxValue = state.maxTemperature !== null ? state.maxTemperature : "";
29395
+ function renderModal3(container, state2, modalId, onClose, onSave) {
29396
+ const colors = getColors(state2.theme);
29397
+ const minValue = state2.minTemperature !== null ? state2.minTemperature : "";
29398
+ const maxValue = state2.maxTemperature !== null ? state2.maxTemperature : "";
29030
29399
  container.innerHTML = `
29031
29400
  <style>
29032
29401
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@@ -29291,23 +29660,23 @@
29291
29660
  </div>
29292
29661
 
29293
29662
  <div class="modal-body">
29294
- ${state.isLoading ? `
29663
+ ${state2.isLoading ? `
29295
29664
  <div class="loading-overlay">
29296
29665
  <div class="loading-spinner"></div>
29297
29666
  <div>Carregando configura\xE7\xF5es...</div>
29298
29667
  </div>
29299
29668
  ` : `
29300
- ${state.error ? `
29301
- <div class="message message-error">${state.error}</div>
29669
+ ${state2.error ? `
29670
+ <div class="message message-error">${state2.error}</div>
29302
29671
  ` : ""}
29303
29672
 
29304
- ${state.successMessage ? `
29305
- <div class="message message-success">${state.successMessage}</div>
29673
+ ${state2.successMessage ? `
29674
+ <div class="message message-success">${state2.successMessage}</div>
29306
29675
  ` : ""}
29307
29676
 
29308
29677
  <div class="customer-info">
29309
29678
  <div class="customer-label">Shopping / Cliente</div>
29310
- <div class="customer-name">${state.customerName || "N\xE3o identificado"}</div>
29679
+ <div class="customer-name">${state2.customerName || "N\xE3o identificado"}</div>
29311
29680
  </div>
29312
29681
 
29313
29682
  <div class="form-group">
@@ -29351,11 +29720,11 @@
29351
29720
  `}
29352
29721
  </div>
29353
29722
 
29354
- ${!state.isLoading ? `
29723
+ ${!state2.isLoading ? `
29355
29724
  <div class="modal-footer">
29356
29725
  <button class="btn btn-secondary" id="${modalId}-cancel">Cancelar</button>
29357
- <button class="btn btn-primary" id="${modalId}-save" ${state.isSaving ? "disabled" : ""}>
29358
- ${state.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
29726
+ <button class="btn btn-primary" id="${modalId}-save" ${state2.isSaving ? "disabled" : ""}>
29727
+ ${state2.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
29359
29728
  </button>
29360
29729
  </div>
29361
29730
  ` : ""}
@@ -29392,18 +29761,18 @@
29392
29761
  const min = parseFloat(minInput.value);
29393
29762
  const max = parseFloat(maxInput.value);
29394
29763
  if (isNaN(min) || isNaN(max)) {
29395
- state.error = "Por favor, preencha ambos os valores.";
29396
- renderModal3(container, state, modalId, onClose, onSave);
29764
+ state2.error = "Por favor, preencha ambos os valores.";
29765
+ renderModal3(container, state2, modalId, onClose, onSave);
29397
29766
  return;
29398
29767
  }
29399
29768
  if (min >= max) {
29400
- state.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
29401
- renderModal3(container, state, modalId, onClose, onSave);
29769
+ state2.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
29770
+ renderModal3(container, state2, modalId, onClose, onSave);
29402
29771
  return;
29403
29772
  }
29404
29773
  if (min < 0 || max > 50) {
29405
- state.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
29406
- renderModal3(container, state, modalId, onClose, onSave);
29774
+ state2.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
29775
+ renderModal3(container, state2, modalId, onClose, onSave);
29407
29776
  return;
29408
29777
  }
29409
29778
  await onSave(min, max);
@@ -29411,7 +29780,7 @@
29411
29780
  }
29412
29781
  function openTemperatureSettingsModal(params) {
29413
29782
  const modalId = `myio-temp-settings-${Date.now()}`;
29414
- const state = {
29783
+ const state2 = {
29415
29784
  customerId: params.customerId,
29416
29785
  customerName: params.customerName || "",
29417
29786
  token: params.token,
@@ -29431,37 +29800,37 @@
29431
29800
  params.onClose?.();
29432
29801
  };
29433
29802
  const handleSave = async (min, max) => {
29434
- state.isSaving = true;
29435
- state.error = null;
29436
- state.successMessage = null;
29437
- renderModal3(container, state, modalId, destroy, handleSave);
29803
+ state2.isSaving = true;
29804
+ state2.error = null;
29805
+ state2.successMessage = null;
29806
+ renderModal3(container, state2, modalId, destroy, handleSave);
29438
29807
  try {
29439
- await saveCustomerAttributes(state.customerId, state.token, min, max, params.onError);
29440
- state.minTemperature = min;
29441
- state.maxTemperature = max;
29442
- state.isSaving = false;
29443
- state.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
29444
- renderModal3(container, state, modalId, destroy, handleSave);
29808
+ await saveCustomerAttributes(state2.customerId, state2.token, min, max, params.onError);
29809
+ state2.minTemperature = min;
29810
+ state2.maxTemperature = max;
29811
+ state2.isSaving = false;
29812
+ state2.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
29813
+ renderModal3(container, state2, modalId, destroy, handleSave);
29445
29814
  params.onSave?.({ minTemperature: min, maxTemperature: max });
29446
29815
  setTimeout(() => {
29447
29816
  destroy();
29448
29817
  }, 1500);
29449
29818
  } catch (error) {
29450
- state.isSaving = false;
29451
- state.error = `Erro ao salvar: ${error.message}`;
29452
- renderModal3(container, state, modalId, destroy, handleSave);
29819
+ state2.isSaving = false;
29820
+ state2.error = `Erro ao salvar: ${error.message}`;
29821
+ renderModal3(container, state2, modalId, destroy, handleSave);
29453
29822
  }
29454
29823
  };
29455
- renderModal3(container, state, modalId, destroy, handleSave);
29456
- fetchCustomerAttributes(state.customerId, state.token, params.onError).then(({ minTemperature, maxTemperature }) => {
29457
- state.minTemperature = minTemperature;
29458
- state.maxTemperature = maxTemperature;
29459
- state.isLoading = false;
29460
- renderModal3(container, state, modalId, destroy, handleSave);
29824
+ renderModal3(container, state2, modalId, destroy, handleSave);
29825
+ fetchCustomerAttributes(state2.customerId, state2.token, params.onError).then(({ minTemperature, maxTemperature }) => {
29826
+ state2.minTemperature = minTemperature;
29827
+ state2.maxTemperature = maxTemperature;
29828
+ state2.isLoading = false;
29829
+ renderModal3(container, state2, modalId, destroy, handleSave);
29461
29830
  }).catch((error) => {
29462
- state.isLoading = false;
29463
- state.error = `Erro ao carregar: ${error.message}`;
29464
- renderModal3(container, state, modalId, destroy, handleSave);
29831
+ state2.isLoading = false;
29832
+ state2.error = `Erro ao carregar: ${error.message}`;
29833
+ renderModal3(container, state2, modalId, destroy, handleSave);
29465
29834
  });
29466
29835
  return { destroy };
29467
29836
  }
@@ -29497,11 +29866,13 @@
29497
29866
  border: 1px solid #e2e8f0;
29498
29867
  border-radius: 12px;
29499
29868
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 2px 10px rgba(0, 0, 0, 0.08);
29869
+ min-width: 380px;
29500
29870
  width: max-content;
29501
29871
  max-width: 90vw;
29502
29872
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
29503
29873
  font-size: 12px;
29504
29874
  color: #1e293b;
29875
+ overflow: hidden;
29505
29876
  }
29506
29877
 
29507
29878
  .energy-summary-tooltip__header {
@@ -29807,8 +30178,7 @@
29807
30178
  align-items: center;
29808
30179
  padding: 10px 14px;
29809
30180
  background: linear-gradient(135deg, #047857 0%, #059669 100%);
29810
- margin: 12px -14px -14px;
29811
- border-radius: 0 0 12px 12px;
30181
+ border-radius: 0 0 11px 11px;
29812
30182
  }
29813
30183
 
29814
30184
  .energy-summary-tooltip__total-label {
@@ -29951,7 +30321,7 @@
29951
30321
  { key: "failure", label: "Falha", count: status.failure },
29952
30322
  { key: "standby", label: "Standby", count: status.standby },
29953
30323
  { key: "offline", label: "Offline", count: status.offline },
29954
- { key: "no-consumption", label: "Sem Dados", count: status.noConsumption }
30324
+ { key: "no-consumption", label: "Sem Consumo", count: status.noConsumption }
29955
30325
  ];
29956
30326
  return items.map((item) => `
29957
30327
  <div class="energy-summary-tooltip__status-item ${item.key}">
@@ -30396,7 +30766,7 @@
30396
30766
  * Build summary data from TELEMETRY_INFO STATE
30397
30767
  * This is called by the widget controller to get data for the tooltip
30398
30768
  */
30399
- buildSummaryFromState(state, receivedData) {
30769
+ buildSummaryFromState(state2, receivedData) {
30400
30770
  const summary = {
30401
30771
  totalDevices: 0,
30402
30772
  totalConsumption: 0,
@@ -30412,22 +30782,22 @@
30412
30782
  },
30413
30783
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
30414
30784
  };
30415
- if (!state) return summary;
30785
+ if (!state2) return summary;
30416
30786
  const entrada = {
30417
30787
  id: "entrada",
30418
30788
  name: "Entrada",
30419
30789
  icon: CATEGORY_ICONS.entrada,
30420
- deviceCount: state.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
30421
- consumption: state.entrada?.total || 0,
30790
+ deviceCount: state2.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
30791
+ consumption: state2.entrada?.total || 0,
30422
30792
  percentage: 100
30423
30793
  };
30424
30794
  const lojas = {
30425
30795
  id: "lojas",
30426
30796
  name: "Lojas",
30427
30797
  icon: CATEGORY_ICONS.lojas,
30428
- deviceCount: state.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
30429
- consumption: state.consumidores?.lojas?.total || 0,
30430
- percentage: state.consumidores?.lojas?.perc || 0
30798
+ deviceCount: state2.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
30799
+ consumption: state2.consumidores?.lojas?.total || 0,
30800
+ percentage: state2.consumidores?.lojas?.perc || 0
30431
30801
  };
30432
30802
  const climatizacaoData = receivedData?.climatizacao || {};
30433
30803
  const elevadoresData = receivedData?.elevadores || {};
@@ -30438,9 +30808,9 @@
30438
30808
  id: "climatizacao",
30439
30809
  name: "Climatizacao",
30440
30810
  icon: CATEGORY_ICONS.climatizacao,
30441
- deviceCount: climatizacaoData.count || state.consumidores?.climatizacao?.devices?.length || 0,
30442
- consumption: state.consumidores?.climatizacao?.total || 0,
30443
- percentage: state.consumidores?.climatizacao?.perc || 0
30811
+ deviceCount: climatizacaoData.count || state2.consumidores?.climatizacao?.devices?.length || 0,
30812
+ consumption: state2.consumidores?.climatizacao?.total || 0,
30813
+ percentage: state2.consumidores?.climatizacao?.perc || 0
30444
30814
  };
30445
30815
  if (climatizacaoData.subcategories) {
30446
30816
  climatizacao.children = [];
@@ -30491,54 +30861,67 @@
30491
30861
  id: "elevadores",
30492
30862
  name: "Elevadores",
30493
30863
  icon: CATEGORY_ICONS.elevadores,
30494
- deviceCount: elevadoresData.count || state.consumidores?.elevadores?.devices?.length || 0,
30495
- consumption: state.consumidores?.elevadores?.total || 0,
30496
- percentage: state.consumidores?.elevadores?.perc || 0
30864
+ deviceCount: elevadoresData.count || state2.consumidores?.elevadores?.devices?.length || 0,
30865
+ consumption: state2.consumidores?.elevadores?.total || 0,
30866
+ percentage: state2.consumidores?.elevadores?.perc || 0
30497
30867
  });
30498
30868
  areaComumChildren.push({
30499
30869
  id: "escadasRolantes",
30500
30870
  name: "Esc. Rolantes",
30501
30871
  icon: CATEGORY_ICONS.escadas,
30502
- deviceCount: escadasData.count || state.consumidores?.escadasRolantes?.devices?.length || 0,
30503
- consumption: state.consumidores?.escadasRolantes?.total || 0,
30504
- percentage: state.consumidores?.escadasRolantes?.perc || 0
30872
+ deviceCount: escadasData.count || state2.consumidores?.escadasRolantes?.devices?.length || 0,
30873
+ consumption: state2.consumidores?.escadasRolantes?.total || 0,
30874
+ percentage: state2.consumidores?.escadasRolantes?.perc || 0
30505
30875
  });
30506
30876
  areaComumChildren.push({
30507
30877
  id: "outros",
30508
30878
  name: "Outros",
30509
30879
  icon: CATEGORY_ICONS.outros,
30510
- deviceCount: outrosData.count || state.consumidores?.outros?.devices?.length || 0,
30511
- consumption: state.consumidores?.outros?.total || 0,
30512
- percentage: state.consumidores?.outros?.perc || 0
30880
+ deviceCount: outrosData.count || state2.consumidores?.outros?.devices?.length || 0,
30881
+ consumption: state2.consumidores?.outros?.total || 0,
30882
+ percentage: state2.consumidores?.outros?.perc || 0
30513
30883
  });
30514
30884
  const areaComumDeviceCount = areaComumChildren.reduce((sum, c) => sum + c.deviceCount, 0);
30515
- const areaComumConsumption = state.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
30885
+ const areaComumConsumption = state2.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
30516
30886
  const areaComum = {
30517
30887
  id: "areaComum",
30518
30888
  name: "Area Comum",
30519
30889
  icon: CATEGORY_ICONS.areaComum,
30520
30890
  deviceCount: areaComumDeviceCount,
30521
30891
  consumption: areaComumConsumption,
30522
- percentage: state.consumidores?.areaComum?.perc || 0,
30892
+ percentage: state2.consumidores?.areaComum?.perc || 0,
30523
30893
  children: areaComumChildren
30524
30894
  };
30525
30895
  summary.byCategory = [entrada, lojas, areaComum];
30526
30896
  summary.totalDevices = entrada.deviceCount + lojas.deviceCount + areaComumDeviceCount;
30527
- summary.totalConsumption = state.grandTotal || entrada.consumption;
30897
+ summary.totalConsumption = state2.grandTotal || entrada.consumption;
30528
30898
  const totalDevices = summary.totalDevices;
30529
- summary.byStatus = {
30530
- normal: Math.floor(totalDevices * 0.85),
30531
- // Estimate 85% normal
30532
- alert: Math.floor(totalDevices * 0.08),
30533
- // Estimate 8% alert
30534
- failure: Math.floor(totalDevices * 0.02),
30535
- // Estimate 2% failure
30536
- standby: Math.floor(totalDevices * 0.03),
30537
- // Estimate 3% standby
30538
- offline: Math.floor(totalDevices * 0.02),
30539
- // Estimate 2% offline
30540
- noConsumption: 0
30541
- };
30899
+ const statusData = receivedData?.statusCounts || receivedData?.deviceStatus || null;
30900
+ if (statusData && typeof statusData === "object") {
30901
+ summary.byStatus = {
30902
+ normal: statusData.normal || 0,
30903
+ alert: statusData.alert || 0,
30904
+ failure: statusData.failure || 0,
30905
+ standby: statusData.standby || 0,
30906
+ offline: statusData.offline || 0,
30907
+ noConsumption: statusData.noConsumption || statusData.zeroConsumption || 0
30908
+ };
30909
+ } else {
30910
+ summary.byStatus = {
30911
+ normal: Math.floor(totalDevices * 0.75),
30912
+ // Estimate 75% normal (with consumption)
30913
+ alert: Math.floor(totalDevices * 0.06),
30914
+ // Estimate 6% alert
30915
+ failure: Math.floor(totalDevices * 0.02),
30916
+ // Estimate 2% failure
30917
+ standby: Math.floor(totalDevices * 0.02),
30918
+ // Estimate 2% standby
30919
+ offline: Math.floor(totalDevices * 0.03),
30920
+ // Estimate 3% offline
30921
+ noConsumption: Math.floor(totalDevices * 0.12)
30922
+ // Estimate 12% sem consumo
30923
+ };
30924
+ }
30542
30925
  const statusSum = Object.values(summary.byStatus).reduce((a, b) => a + b, 0);
30543
30926
  if (statusSum !== totalDevices && totalDevices > 0) {
30544
30927
  summary.byStatus.normal += totalDevices - statusSum;
@@ -30547,6 +30930,654 @@
30547
30930
  }
30548
30931
  };
30549
30932
 
30933
+ // src/utils/InfoTooltip.ts
30934
+ var INFO_TOOLTIP_CSS = `
30935
+ /* ============================================
30936
+ Info Tooltip (RFC-0105)
30937
+ Premium draggable tooltip with actions
30938
+ ============================================ */
30939
+
30940
+ .myio-info-tooltip {
30941
+ position: fixed;
30942
+ z-index: 99999;
30943
+ pointer-events: none;
30944
+ opacity: 0;
30945
+ transition: opacity 0.25s ease, transform 0.25s ease;
30946
+ transform: translateY(5px);
30947
+ }
30948
+
30949
+ .myio-info-tooltip.visible {
30950
+ opacity: 1;
30951
+ pointer-events: auto;
30952
+ transform: translateY(0);
30953
+ }
30954
+
30955
+ .myio-info-tooltip.closing {
30956
+ opacity: 0;
30957
+ transform: translateY(8px);
30958
+ transition: opacity 0.4s ease, transform 0.4s ease;
30959
+ }
30960
+
30961
+ .myio-info-tooltip.pinned {
30962
+ box-shadow: 0 0 0 2px #047857, 0 10px 40px rgba(0, 0, 0, 0.2);
30963
+ border-radius: 12px;
30964
+ }
30965
+
30966
+ .myio-info-tooltip.dragging {
30967
+ transition: none !important;
30968
+ cursor: move;
30969
+ }
30970
+
30971
+ .myio-info-tooltip.maximized {
30972
+ top: 20px !important;
30973
+ left: 20px !important;
30974
+ right: 20px !important;
30975
+ bottom: 20px !important;
30976
+ width: auto !important;
30977
+ max-width: none !important;
30978
+ }
30979
+
30980
+ .myio-info-tooltip.maximized .myio-info-tooltip__panel {
30981
+ width: 100%;
30982
+ height: 100%;
30983
+ max-width: none;
30984
+ display: flex;
30985
+ flex-direction: column;
30986
+ }
30987
+
30988
+ .myio-info-tooltip.maximized .myio-info-tooltip__content {
30989
+ flex: 1;
30990
+ overflow-y: auto;
30991
+ }
30992
+
30993
+ .myio-info-tooltip__panel {
30994
+ background: #ffffff;
30995
+ border: 1px solid #e2e8f0;
30996
+ border-radius: 12px;
30997
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12), 0 2px 10px rgba(0, 0, 0, 0.08);
30998
+ min-width: 320px;
30999
+ max-width: 400px;
31000
+ font-size: 12px;
31001
+ color: #1e293b;
31002
+ overflow: hidden;
31003
+ font-family: Inter, system-ui, -apple-system, sans-serif;
31004
+ }
31005
+
31006
+ .myio-info-tooltip__header {
31007
+ display: flex;
31008
+ align-items: center;
31009
+ gap: 8px;
31010
+ padding: 12px 16px;
31011
+ background: linear-gradient(90deg, #f1f5f9 0%, #e2e8f0 100%);
31012
+ border-bottom: 1px solid #cbd5e1;
31013
+ cursor: move;
31014
+ user-select: none;
31015
+ }
31016
+
31017
+ .myio-info-tooltip__icon {
31018
+ font-size: 18px;
31019
+ }
31020
+
31021
+ .myio-info-tooltip__title {
31022
+ font-weight: 700;
31023
+ font-size: 14px;
31024
+ color: #475569;
31025
+ letter-spacing: 0.3px;
31026
+ flex: 1;
31027
+ }
31028
+
31029
+ .myio-info-tooltip__header-actions {
31030
+ display: flex;
31031
+ align-items: center;
31032
+ gap: 4px;
31033
+ }
31034
+
31035
+ .myio-info-tooltip__header-btn {
31036
+ width: 24px;
31037
+ height: 24px;
31038
+ border: none;
31039
+ background: rgba(255, 255, 255, 0.6);
31040
+ border-radius: 4px;
31041
+ cursor: pointer;
31042
+ display: flex;
31043
+ align-items: center;
31044
+ justify-content: center;
31045
+ transition: all 0.15s ease;
31046
+ color: #64748b;
31047
+ }
31048
+
31049
+ .myio-info-tooltip__header-btn:hover {
31050
+ background: rgba(255, 255, 255, 0.9);
31051
+ color: #1e293b;
31052
+ }
31053
+
31054
+ .myio-info-tooltip__header-btn.pinned {
31055
+ background: #047857;
31056
+ color: white;
31057
+ }
31058
+
31059
+ .myio-info-tooltip__header-btn.pinned:hover {
31060
+ background: #065f46;
31061
+ color: white;
31062
+ }
31063
+
31064
+ .myio-info-tooltip__header-btn svg {
31065
+ width: 14px;
31066
+ height: 14px;
31067
+ }
31068
+
31069
+ .myio-info-tooltip__content {
31070
+ padding: 16px;
31071
+ max-height: 500px;
31072
+ overflow-y: auto;
31073
+ }
31074
+
31075
+ /* Content styles */
31076
+ .myio-info-tooltip__section {
31077
+ margin-bottom: 14px;
31078
+ padding-bottom: 12px;
31079
+ border-bottom: 1px solid #f1f5f9;
31080
+ }
31081
+
31082
+ .myio-info-tooltip__section:last-child {
31083
+ margin-bottom: 0;
31084
+ padding-bottom: 0;
31085
+ border-bottom: none;
31086
+ }
31087
+
31088
+ .myio-info-tooltip__section-title {
31089
+ font-size: 11px;
31090
+ font-weight: 600;
31091
+ color: #64748b;
31092
+ text-transform: uppercase;
31093
+ letter-spacing: 0.8px;
31094
+ margin-bottom: 10px;
31095
+ display: flex;
31096
+ align-items: center;
31097
+ gap: 6px;
31098
+ }
31099
+
31100
+ .myio-info-tooltip__row {
31101
+ display: flex;
31102
+ justify-content: space-between;
31103
+ align-items: center;
31104
+ padding: 5px 0;
31105
+ gap: 12px;
31106
+ }
31107
+
31108
+ .myio-info-tooltip__label {
31109
+ color: #64748b;
31110
+ font-size: 12px;
31111
+ flex-shrink: 0;
31112
+ }
31113
+
31114
+ .myio-info-tooltip__value {
31115
+ color: #1e293b;
31116
+ font-weight: 600;
31117
+ text-align: right;
31118
+ }
31119
+
31120
+ .myio-info-tooltip__value--highlight {
31121
+ color: #10b981;
31122
+ font-weight: 700;
31123
+ font-size: 14px;
31124
+ }
31125
+
31126
+ .myio-info-tooltip__notice {
31127
+ display: flex;
31128
+ align-items: flex-start;
31129
+ gap: 10px;
31130
+ padding: 12px 14px;
31131
+ background: #f0fdf4;
31132
+ border: 1px solid #bbf7d0;
31133
+ border-radius: 8px;
31134
+ margin-top: 12px;
31135
+ }
31136
+
31137
+ .myio-info-tooltip__notice-icon {
31138
+ font-size: 14px;
31139
+ flex-shrink: 0;
31140
+ margin-top: 1px;
31141
+ }
31142
+
31143
+ .myio-info-tooltip__notice-text {
31144
+ font-size: 11px;
31145
+ color: #475569;
31146
+ line-height: 1.5;
31147
+ }
31148
+
31149
+ .myio-info-tooltip__notice-text strong {
31150
+ font-weight: 700;
31151
+ color: #334155;
31152
+ }
31153
+
31154
+ .myio-info-tooltip__category {
31155
+ display: flex;
31156
+ align-items: center;
31157
+ gap: 10px;
31158
+ padding: 8px 12px;
31159
+ background: #f8fafc;
31160
+ border-radius: 8px;
31161
+ margin-bottom: 6px;
31162
+ border-left: 3px solid #94a3b8;
31163
+ }
31164
+
31165
+ .myio-info-tooltip__category:last-child {
31166
+ margin-bottom: 0;
31167
+ }
31168
+
31169
+ .myio-info-tooltip__category--climatizacao {
31170
+ border-left-color: #00C896;
31171
+ background: #ecfdf5;
31172
+ }
31173
+
31174
+ .myio-info-tooltip__category--outros {
31175
+ border-left-color: #9C27B0;
31176
+ background: #fdf4ff;
31177
+ }
31178
+
31179
+ .myio-info-tooltip__category-icon {
31180
+ font-size: 14px;
31181
+ flex-shrink: 0;
31182
+ }
31183
+
31184
+ .myio-info-tooltip__category-info {
31185
+ flex: 1;
31186
+ }
31187
+
31188
+ .myio-info-tooltip__category-name {
31189
+ font-weight: 600;
31190
+ color: #334155;
31191
+ font-size: 12px;
31192
+ }
31193
+
31194
+ .myio-info-tooltip__category-desc {
31195
+ font-size: 10px;
31196
+ color: #64748b;
31197
+ margin-top: 2px;
31198
+ }
31199
+
31200
+ .myio-info-tooltip__category-value {
31201
+ font-weight: 700;
31202
+ color: #334155;
31203
+ font-size: 13px;
31204
+ }
31205
+ `;
31206
+ var cssInjected4 = false;
31207
+ function injectCSS4() {
31208
+ if (cssInjected4) return;
31209
+ if (typeof document === "undefined") return;
31210
+ const styleId = "myio-info-tooltip-styles";
31211
+ if (document.getElementById(styleId)) {
31212
+ cssInjected4 = true;
31213
+ return;
31214
+ }
31215
+ const style = document.createElement("style");
31216
+ style.id = styleId;
31217
+ style.textContent = INFO_TOOLTIP_CSS;
31218
+ document.head.appendChild(style);
31219
+ cssInjected4 = true;
31220
+ }
31221
+ var state = {
31222
+ hideTimer: null,
31223
+ isMouseOverTooltip: false,
31224
+ isMaximized: false,
31225
+ isDragging: false,
31226
+ dragOffset: { x: 0, y: 0 },
31227
+ savedPosition: null,
31228
+ pinnedCounter: 0
31229
+ };
31230
+ function generateHeaderHTML(icon, title) {
31231
+ return `
31232
+ <div class="myio-info-tooltip__header" data-drag-handle>
31233
+ <span class="myio-info-tooltip__icon">${icon}</span>
31234
+ <span class="myio-info-tooltip__title">${title}</span>
31235
+ <div class="myio-info-tooltip__header-actions">
31236
+ <button class="myio-info-tooltip__header-btn" data-action="pin" title="Fixar na tela">
31237
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
31238
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
31239
+ <line x1="12" y1="16" x2="12" y2="21"/>
31240
+ <line x1="8" y1="4" x2="16" y2="4"/>
31241
+ </svg>
31242
+ </button>
31243
+ <button class="myio-info-tooltip__header-btn" data-action="maximize" title="Maximizar">
31244
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
31245
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
31246
+ </svg>
31247
+ </button>
31248
+ <button class="myio-info-tooltip__header-btn" data-action="close" title="Fechar">
31249
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
31250
+ <path d="M18 6L6 18M6 6l12 12"/>
31251
+ </svg>
31252
+ </button>
31253
+ </div>
31254
+ </div>
31255
+ `;
31256
+ }
31257
+ function setupHoverListeners(container) {
31258
+ container.onmouseenter = () => {
31259
+ state.isMouseOverTooltip = true;
31260
+ if (state.hideTimer) {
31261
+ clearTimeout(state.hideTimer);
31262
+ state.hideTimer = null;
31263
+ }
31264
+ };
31265
+ container.onmouseleave = () => {
31266
+ state.isMouseOverTooltip = false;
31267
+ startDelayedHide();
31268
+ };
31269
+ }
31270
+ function setupButtonListeners(container) {
31271
+ const buttons = container.querySelectorAll("[data-action]");
31272
+ buttons.forEach((btn) => {
31273
+ btn.onclick = (e) => {
31274
+ e.stopPropagation();
31275
+ const action = btn.dataset.action;
31276
+ switch (action) {
31277
+ case "pin":
31278
+ createPinnedClone(container);
31279
+ break;
31280
+ case "maximize":
31281
+ toggleMaximize(container);
31282
+ break;
31283
+ case "close":
31284
+ InfoTooltip.close();
31285
+ break;
31286
+ }
31287
+ };
31288
+ });
31289
+ }
31290
+ function setupDragListeners(container) {
31291
+ const header = container.querySelector("[data-drag-handle]");
31292
+ if (!header) return;
31293
+ header.onmousedown = (e) => {
31294
+ if (e.target.closest("[data-action]")) return;
31295
+ if (state.isMaximized) return;
31296
+ state.isDragging = true;
31297
+ container.classList.add("dragging");
31298
+ const rect = container.getBoundingClientRect();
31299
+ state.dragOffset = {
31300
+ x: e.clientX - rect.left,
31301
+ y: e.clientY - rect.top
31302
+ };
31303
+ const onMouseMove = (e2) => {
31304
+ if (!state.isDragging) return;
31305
+ const newLeft = e2.clientX - state.dragOffset.x;
31306
+ const newTop = e2.clientY - state.dragOffset.y;
31307
+ const maxLeft = window.innerWidth - container.offsetWidth;
31308
+ const maxTop = window.innerHeight - container.offsetHeight;
31309
+ container.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
31310
+ container.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
31311
+ };
31312
+ const onMouseUp = () => {
31313
+ state.isDragging = false;
31314
+ container.classList.remove("dragging");
31315
+ document.removeEventListener("mousemove", onMouseMove);
31316
+ document.removeEventListener("mouseup", onMouseUp);
31317
+ };
31318
+ document.addEventListener("mousemove", onMouseMove);
31319
+ document.addEventListener("mouseup", onMouseUp);
31320
+ };
31321
+ }
31322
+ function createPinnedClone(container) {
31323
+ state.pinnedCounter++;
31324
+ const pinnedId = `myio-info-tooltip-pinned-${state.pinnedCounter}`;
31325
+ const clone = container.cloneNode(true);
31326
+ clone.id = pinnedId;
31327
+ clone.classList.add("pinned");
31328
+ clone.classList.remove("closing");
31329
+ const pinBtn = clone.querySelector('[data-action="pin"]');
31330
+ if (pinBtn) {
31331
+ pinBtn.classList.add("pinned");
31332
+ pinBtn.setAttribute("title", "Desafixar");
31333
+ pinBtn.innerHTML = `
31334
+ <svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1">
31335
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
31336
+ <line x1="12" y1="16" x2="12" y2="21"/>
31337
+ <line x1="8" y1="4" x2="16" y2="4"/>
31338
+ </svg>
31339
+ `;
31340
+ }
31341
+ document.body.appendChild(clone);
31342
+ setupPinnedCloneListeners(clone, pinnedId);
31343
+ InfoTooltip.hide();
31344
+ }
31345
+ function setupPinnedCloneListeners(clone, cloneId) {
31346
+ let isMaximized = false;
31347
+ let savedPosition = null;
31348
+ const pinBtn = clone.querySelector('[data-action="pin"]');
31349
+ if (pinBtn) {
31350
+ pinBtn.onclick = (e) => {
31351
+ e.stopPropagation();
31352
+ closePinnedClone(cloneId);
31353
+ };
31354
+ }
31355
+ const closeBtn = clone.querySelector('[data-action="close"]');
31356
+ if (closeBtn) {
31357
+ closeBtn.onclick = (e) => {
31358
+ e.stopPropagation();
31359
+ closePinnedClone(cloneId);
31360
+ };
31361
+ }
31362
+ const maxBtn = clone.querySelector('[data-action="maximize"]');
31363
+ if (maxBtn) {
31364
+ maxBtn.onclick = (e) => {
31365
+ e.stopPropagation();
31366
+ isMaximized = !isMaximized;
31367
+ if (isMaximized) {
31368
+ savedPosition = { left: clone.style.left, top: clone.style.top };
31369
+ 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>`;
31370
+ maxBtn.setAttribute("title", "Restaurar");
31371
+ } else {
31372
+ 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>`;
31373
+ maxBtn.setAttribute("title", "Maximizar");
31374
+ if (savedPosition) {
31375
+ clone.style.left = savedPosition.left;
31376
+ clone.style.top = savedPosition.top;
31377
+ }
31378
+ }
31379
+ clone.classList.toggle("maximized", isMaximized);
31380
+ };
31381
+ }
31382
+ const header = clone.querySelector("[data-drag-handle]");
31383
+ if (header) {
31384
+ let isDragging = false;
31385
+ let dragOffset = { x: 0, y: 0 };
31386
+ header.onmousedown = (e) => {
31387
+ if (e.target.closest("[data-action]")) return;
31388
+ if (isMaximized) return;
31389
+ isDragging = true;
31390
+ clone.classList.add("dragging");
31391
+ const rect = clone.getBoundingClientRect();
31392
+ dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
31393
+ const onMouseMove = (e2) => {
31394
+ if (!isDragging) return;
31395
+ const newLeft = e2.clientX - dragOffset.x;
31396
+ const newTop = e2.clientY - dragOffset.y;
31397
+ const maxLeft = window.innerWidth - clone.offsetWidth;
31398
+ const maxTop = window.innerHeight - clone.offsetHeight;
31399
+ clone.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
31400
+ clone.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
31401
+ };
31402
+ const onMouseUp = () => {
31403
+ isDragging = false;
31404
+ clone.classList.remove("dragging");
31405
+ document.removeEventListener("mousemove", onMouseMove);
31406
+ document.removeEventListener("mouseup", onMouseUp);
31407
+ };
31408
+ document.addEventListener("mousemove", onMouseMove);
31409
+ document.addEventListener("mouseup", onMouseUp);
31410
+ };
31411
+ }
31412
+ }
31413
+ function closePinnedClone(cloneId) {
31414
+ const clone = document.getElementById(cloneId);
31415
+ if (clone) {
31416
+ clone.classList.add("closing");
31417
+ setTimeout(() => clone.remove(), 400);
31418
+ }
31419
+ }
31420
+ function toggleMaximize(container) {
31421
+ state.isMaximized = !state.isMaximized;
31422
+ if (state.isMaximized) {
31423
+ state.savedPosition = {
31424
+ left: container.style.left,
31425
+ top: container.style.top
31426
+ };
31427
+ }
31428
+ container.classList.toggle("maximized", state.isMaximized);
31429
+ const maxBtn = container.querySelector('[data-action="maximize"]');
31430
+ if (maxBtn) {
31431
+ if (state.isMaximized) {
31432
+ 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>`;
31433
+ maxBtn.setAttribute("title", "Restaurar");
31434
+ } else {
31435
+ 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>`;
31436
+ maxBtn.setAttribute("title", "Maximizar");
31437
+ if (state.savedPosition) {
31438
+ container.style.left = state.savedPosition.left;
31439
+ container.style.top = state.savedPosition.top;
31440
+ }
31441
+ }
31442
+ }
31443
+ }
31444
+ function startDelayedHide() {
31445
+ if (state.isMouseOverTooltip) return;
31446
+ if (state.hideTimer) {
31447
+ clearTimeout(state.hideTimer);
31448
+ }
31449
+ state.hideTimer = setTimeout(() => {
31450
+ hideWithAnimation();
31451
+ }, 1500);
31452
+ }
31453
+ function hideWithAnimation() {
31454
+ const container = document.getElementById("myio-info-tooltip");
31455
+ if (container && container.classList.contains("visible")) {
31456
+ container.classList.add("closing");
31457
+ setTimeout(() => {
31458
+ container.classList.remove("visible", "closing");
31459
+ }, 400);
31460
+ }
31461
+ }
31462
+ function positionTooltip(container, triggerElement) {
31463
+ const rect = triggerElement.getBoundingClientRect();
31464
+ let left = rect.left;
31465
+ let top = rect.bottom + 8;
31466
+ const tooltipWidth = 380;
31467
+ if (left + tooltipWidth > window.innerWidth - 20) {
31468
+ left = window.innerWidth - tooltipWidth - 20;
31469
+ }
31470
+ if (left < 10) left = 10;
31471
+ if (top + 400 > window.innerHeight) {
31472
+ top = rect.top - 8 - 400;
31473
+ if (top < 10) top = 10;
31474
+ }
31475
+ container.style.left = left + "px";
31476
+ container.style.top = top + "px";
31477
+ }
31478
+ var InfoTooltip = {
31479
+ containerId: "myio-info-tooltip",
31480
+ /**
31481
+ * Get or create container
31482
+ */
31483
+ getContainer() {
31484
+ injectCSS4();
31485
+ let container = document.getElementById(this.containerId);
31486
+ if (!container) {
31487
+ container = document.createElement("div");
31488
+ container.id = this.containerId;
31489
+ container.className = "myio-info-tooltip";
31490
+ document.body.appendChild(container);
31491
+ }
31492
+ return container;
31493
+ },
31494
+ /**
31495
+ * Show tooltip
31496
+ */
31497
+ show(triggerElement, options) {
31498
+ if (state.hideTimer) {
31499
+ clearTimeout(state.hideTimer);
31500
+ state.hideTimer = null;
31501
+ }
31502
+ const container = this.getContainer();
31503
+ container.classList.remove("closing");
31504
+ container.innerHTML = `
31505
+ <div class="myio-info-tooltip__panel">
31506
+ ${generateHeaderHTML(options.icon, options.title)}
31507
+ <div class="myio-info-tooltip__content">
31508
+ ${options.content}
31509
+ </div>
31510
+ </div>
31511
+ `;
31512
+ positionTooltip(container, triggerElement);
31513
+ container.classList.add("visible");
31514
+ setupHoverListeners(container);
31515
+ setupButtonListeners(container);
31516
+ setupDragListeners(container);
31517
+ },
31518
+ /**
31519
+ * Start delayed hide
31520
+ */
31521
+ startDelayedHide() {
31522
+ startDelayedHide();
31523
+ },
31524
+ /**
31525
+ * Hide immediately
31526
+ */
31527
+ hide() {
31528
+ if (state.hideTimer) {
31529
+ clearTimeout(state.hideTimer);
31530
+ state.hideTimer = null;
31531
+ }
31532
+ state.isMouseOverTooltip = false;
31533
+ const container = document.getElementById(this.containerId);
31534
+ if (container) {
31535
+ container.classList.remove("visible", "closing");
31536
+ }
31537
+ },
31538
+ /**
31539
+ * Close and reset all states
31540
+ */
31541
+ close() {
31542
+ state.isMaximized = false;
31543
+ state.isDragging = false;
31544
+ state.savedPosition = null;
31545
+ if (state.hideTimer) {
31546
+ clearTimeout(state.hideTimer);
31547
+ state.hideTimer = null;
31548
+ }
31549
+ state.isMouseOverTooltip = false;
31550
+ const container = document.getElementById(this.containerId);
31551
+ if (container) {
31552
+ container.classList.remove("visible", "pinned", "maximized", "dragging", "closing");
31553
+ }
31554
+ },
31555
+ /**
31556
+ * Attach tooltip to trigger element with hover behavior
31557
+ */
31558
+ attach(triggerElement, getOptions) {
31559
+ const self = this;
31560
+ const handleMouseEnter = () => {
31561
+ if (state.hideTimer) {
31562
+ clearTimeout(state.hideTimer);
31563
+ state.hideTimer = null;
31564
+ }
31565
+ const options = getOptions();
31566
+ self.show(triggerElement, options);
31567
+ };
31568
+ const handleMouseLeave = () => {
31569
+ startDelayedHide();
31570
+ };
31571
+ triggerElement.addEventListener("mouseenter", handleMouseEnter);
31572
+ triggerElement.addEventListener("mouseleave", handleMouseLeave);
31573
+ return () => {
31574
+ triggerElement.removeEventListener("mouseenter", handleMouseEnter);
31575
+ triggerElement.removeEventListener("mouseleave", handleMouseLeave);
31576
+ self.hide();
31577
+ };
31578
+ }
31579
+ };
31580
+
30550
31581
  // src/components/ModalHeader/index.ts
30551
31582
  var DEFAULT_BG_COLOR = "#3e1a7d";
30552
31583
  var DEFAULT_TEXT_COLOR = "white";
@@ -34320,6 +35351,7 @@
34320
35351
  exports.IMPORTANCE_COLORS = IMPORTANCE_COLORS;
34321
35352
  exports.IMPORTANCE_LABELS = IMPORTANCE_LABELS;
34322
35353
  exports.IMPORTANCE_LABELS_EN = IMPORTANCE_LABELS_EN;
35354
+ exports.InfoTooltip = InfoTooltip;
34323
35355
  exports.MyIOChartModal = MyIOChartModal;
34324
35356
  exports.MyIODraggableCard = MyIODraggableCard;
34325
35357
  exports.MyIOSelectionStoreClass = MyIOSelectionStoreClass;