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.
package/dist/index.js CHANGED
@@ -8875,8 +8875,8 @@ function getStatusDotClass(deviceStatus) {
8875
8875
  return "dot--offline";
8876
8876
  }
8877
8877
  }
8878
- function buildDOM(state) {
8879
- const { entityObject, i18n, enableSelection, enableDragDrop } = state;
8878
+ function buildDOM(state2) {
8879
+ const { entityObject, i18n, enableSelection, enableDragDrop } = state2;
8880
8880
  const root = document.createElement("div");
8881
8881
  root.className = "myio-ho-card";
8882
8882
  root.setAttribute("role", "group");
@@ -9202,8 +9202,8 @@ function verifyOfflineStatus(entityObject, delayTimeInMins = 15, LogHelper2) {
9202
9202
  }
9203
9203
  return isOffline;
9204
9204
  }
9205
- function paint(root, state) {
9206
- const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state;
9205
+ function paint(root, state2) {
9206
+ const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state2;
9207
9207
  let statusDecisionSource = "unknown";
9208
9208
  if (entityObject.connectionStatus) {
9209
9209
  if (entityObject.connectionStatus === "offline") {
@@ -9255,7 +9255,7 @@ function paint(root, state) {
9255
9255
  numSpan.textContent = primaryValue;
9256
9256
  const barContainer = root.querySelector(".bar");
9257
9257
  const effContainer = root.querySelector(".myio-ho-card__eff");
9258
- if (state.enableSelection) {
9258
+ if (state2.enableSelection) {
9259
9259
  const checkbox = root.querySelector('.myio-ho-card__select input[type="checkbox"]');
9260
9260
  if (checkbox) {
9261
9261
  checkbox.checked = !!isSelected;
@@ -9303,8 +9303,8 @@ function paint(root, state) {
9303
9303
  statusDot.className = `status-dot ${dotClass}`;
9304
9304
  }
9305
9305
  }
9306
- function bindEvents(root, state, callbacks) {
9307
- const { entityObject } = state;
9306
+ function bindEvents(root, state2, callbacks) {
9307
+ const { entityObject } = state2;
9308
9308
  const kebabBtn = root.querySelector(".myio-ho-card__kebab");
9309
9309
  const menu = root.querySelector(".myio-ho-card__menu");
9310
9310
  function toggleMenu() {
@@ -9360,9 +9360,9 @@ function bindEvents(root, state, callbacks) {
9360
9360
  const onSelectionChange = () => {
9361
9361
  const selectedIds = MyIOSelectionStore2.getSelectedIds();
9362
9362
  const isSelected = selectedIds.includes(entityObject.entityId);
9363
- if (state.isSelected !== isSelected) {
9364
- state.isSelected = isSelected;
9365
- paint(root, state);
9363
+ if (state2.isSelected !== isSelected) {
9364
+ state2.isSelected = isSelected;
9365
+ paint(root, state2);
9366
9366
  }
9367
9367
  };
9368
9368
  MyIOSelectionStore2.on("selection:change", onSelectionChange);
@@ -9375,7 +9375,7 @@ function bindEvents(root, state, callbacks) {
9375
9375
  clearTimeout(TempRangeTooltip._hideTimer);
9376
9376
  TempRangeTooltip._hideTimer = null;
9377
9377
  }
9378
- TempRangeTooltip.show(root, state.entityObject, e);
9378
+ TempRangeTooltip.show(root, state2.entityObject, e);
9379
9379
  };
9380
9380
  const hideTooltip = () => {
9381
9381
  TempRangeTooltip._startDelayedHide();
@@ -9393,7 +9393,7 @@ function bindEvents(root, state, callbacks) {
9393
9393
  clearTimeout(EnergyRangeTooltip._hideTimer);
9394
9394
  EnergyRangeTooltip._hideTimer = null;
9395
9395
  }
9396
- EnergyRangeTooltip.show(root, state.entityObject, e);
9396
+ EnergyRangeTooltip.show(root, state2.entityObject, e);
9397
9397
  };
9398
9398
  const hideEnergyTooltip = () => {
9399
9399
  EnergyRangeTooltip._startDelayedHide();
@@ -9445,7 +9445,7 @@ function bindEvents(root, state, callbacks) {
9445
9445
  }
9446
9446
  });
9447
9447
  }
9448
- if (state.enableDragDrop) {
9448
+ if (state2.enableDragDrop) {
9449
9449
  root.addEventListener("dragstart", (e) => {
9450
9450
  root.classList.add("is-dragging");
9451
9451
  e.dataTransfer.setData("text/plain", entityObject.entityId);
@@ -9487,17 +9487,17 @@ function renderCardComponentHeadOffice(containerEl, params) {
9487
9487
  throw new Error("renderCardComponentHeadOffice: containerEl is required");
9488
9488
  }
9489
9489
  ensureCss();
9490
- const state = normalizeParams(params);
9491
- const root = buildDOM(state);
9492
- state.isSelected = params.isSelected || false;
9490
+ const state2 = normalizeParams(params);
9491
+ const root = buildDOM(state2);
9492
+ state2.isSelected = params.isSelected || false;
9493
9493
  containerEl.appendChild(root);
9494
- bindEvents(root, state, state.callbacks);
9495
- paint(root, state);
9494
+ bindEvents(root, state2, state2.callbacks);
9495
+ paint(root, state2);
9496
9496
  return {
9497
9497
  update(next) {
9498
9498
  if (next) {
9499
- Object.assign(state.entityObject, next);
9500
- paint(root, state);
9499
+ Object.assign(state2.entityObject, next);
9500
+ paint(root, state2);
9501
9501
  }
9502
9502
  },
9503
9503
  destroy() {
@@ -20980,15 +20980,45 @@ var PowerLimitsPersister = class {
20980
20980
  }
20981
20981
  }
20982
20982
  /**
20983
- * Save mapInstantaneousPower to customer server_scope attributes
20983
+ * Fetch child customer relations (level 1) from a parent customer
20984
+ * Returns array of child customer IDs
20984
20985
  */
20985
- async saveCustomerPowerLimits(customerId, limits) {
20986
+ async fetchChildCustomerIds(parentCustomerId) {
20987
+ try {
20988
+ const url = `${this.tbBaseUrl}/api/relations/info?fromId=${parentCustomerId}&fromType=CUSTOMER`;
20989
+ const response = await fetch(url, {
20990
+ method: "GET",
20991
+ headers: {
20992
+ "X-Authorization": `Bearer ${this.jwtToken}`,
20993
+ "Content-Type": "application/json"
20994
+ }
20995
+ });
20996
+ if (!response.ok) {
20997
+ console.warn("[PowerLimitsPersister] Failed to fetch relations:", response.status);
20998
+ return [];
20999
+ }
21000
+ const relations = await response.json();
21001
+ if (!Array.isArray(relations) || relations.length === 0) {
21002
+ console.log("[PowerLimitsPersister] No child customer relations found");
21003
+ return [];
21004
+ }
21005
+ const childCustomerIds = relations.filter((rel) => rel.to?.entityType === "CUSTOMER" && rel.to?.id).map((rel) => rel.to.id);
21006
+ console.log(`[PowerLimitsPersister] Found ${childCustomerIds.length} child customer(s)`);
21007
+ return childCustomerIds;
21008
+ } catch (error) {
21009
+ console.error("[PowerLimitsPersister] Error fetching relations:", error);
21010
+ return [];
21011
+ }
21012
+ }
21013
+ /**
21014
+ * Save power limits to a single customer (internal method)
21015
+ */
21016
+ async saveToSingleCustomer(customerId, limits) {
20986
21017
  try {
20987
21018
  const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/attributes/SERVER_SCOPE`;
20988
21019
  const payload = {
20989
21020
  mapInstantaneousPower: limits
20990
21021
  };
20991
- console.log("[PowerLimitsPersister] Saving power limits:", payload);
20992
21022
  const response = await fetch(url, {
20993
21023
  method: "POST",
20994
21024
  headers: {
@@ -20998,10 +21028,41 @@ var PowerLimitsPersister = class {
20998
21028
  body: JSON.stringify(payload)
20999
21029
  });
21000
21030
  if (!response.ok) {
21001
- throw this.createHttpError(response.status, await response.text().catch(() => ""));
21031
+ console.warn(`[PowerLimitsPersister] Failed to save to customer ${customerId}:`, response.status);
21032
+ return false;
21002
21033
  }
21003
- console.log("[PowerLimitsPersister] Successfully saved power limits");
21004
- return { ok: true };
21034
+ return true;
21035
+ } catch (error) {
21036
+ console.error(`[PowerLimitsPersister] Error saving to customer ${customerId}:`, error);
21037
+ return false;
21038
+ }
21039
+ }
21040
+ /**
21041
+ * Save mapInstantaneousPower to customer server_scope attributes
21042
+ * Also propagates to all child customers (level 1 relations)
21043
+ */
21044
+ async saveCustomerPowerLimits(customerId, limits) {
21045
+ try {
21046
+ console.log("[PowerLimitsPersister] Saving power limits:", { customerId, limits });
21047
+ const mainSaveSuccess = await this.saveToSingleCustomer(customerId, limits);
21048
+ if (!mainSaveSuccess) {
21049
+ throw new Error("Failed to save to main customer");
21050
+ }
21051
+ console.log("[PowerLimitsPersister] Successfully saved to main customer");
21052
+ const childCustomerIds = await this.fetchChildCustomerIds(customerId);
21053
+ let successCount = 1;
21054
+ if (childCustomerIds.length > 0) {
21055
+ console.log(`[PowerLimitsPersister] Saving to ${childCustomerIds.length} child customer(s)...`);
21056
+ const savePromises = childCustomerIds.map(
21057
+ (childId) => this.saveToSingleCustomer(childId, limits)
21058
+ );
21059
+ const results = await Promise.all(savePromises);
21060
+ const childSuccessCount = results.filter(Boolean).length;
21061
+ successCount += childSuccessCount;
21062
+ console.log(`[PowerLimitsPersister] Saved to ${childSuccessCount}/${childCustomerIds.length} child customer(s)`);
21063
+ }
21064
+ console.log(`[PowerLimitsPersister] Total: saved to ${successCount} customer(s)`);
21065
+ return { ok: true, savedCount: successCount };
21005
21066
  } catch (error) {
21006
21067
  console.error("[PowerLimitsPersister] Error saving power limits:", error);
21007
21068
  return { ok: false, error: this.mapError(error) };
@@ -22191,6 +22252,136 @@ var AnnotationsTab = class {
22191
22252
  this.annotations = [];
22192
22253
  }
22193
22254
  }
22255
+ /**
22256
+ * Show a confirmation modal and return user's choice
22257
+ * Replaces native confirm() for better UX
22258
+ */
22259
+ showConfirmation(message, title = "Confirmar") {
22260
+ return new Promise((resolve) => {
22261
+ const overlay = document.createElement("div");
22262
+ overlay.className = "annotations-confirm-overlay";
22263
+ overlay.innerHTML = `
22264
+ <div class="annotations-confirm-modal">
22265
+ <div class="annotations-confirm-header">
22266
+ <span class="annotations-confirm-icon">\u26A0\uFE0F</span>
22267
+ <span class="annotations-confirm-title">${title}</span>
22268
+ </div>
22269
+ <div class="annotations-confirm-body">
22270
+ <p>${message}</p>
22271
+ </div>
22272
+ <div class="annotations-confirm-actions">
22273
+ <button class="annotations-confirm-btn annotations-confirm-btn--cancel" data-action="cancel">
22274
+ Cancelar
22275
+ </button>
22276
+ <button class="annotations-confirm-btn annotations-confirm-btn--confirm" data-action="confirm">
22277
+ Confirmar
22278
+ </button>
22279
+ </div>
22280
+ </div>
22281
+ `;
22282
+ if (!document.getElementById("annotations-confirm-styles")) {
22283
+ const style = document.createElement("style");
22284
+ style.id = "annotations-confirm-styles";
22285
+ style.textContent = `
22286
+ .annotations-confirm-overlay {
22287
+ position: fixed;
22288
+ inset: 0;
22289
+ background: rgba(0, 0, 0, 0.5);
22290
+ backdrop-filter: blur(4px);
22291
+ display: flex;
22292
+ align-items: center;
22293
+ justify-content: center;
22294
+ z-index: 100001;
22295
+ animation: confirmFadeIn 0.2s ease;
22296
+ }
22297
+ @keyframes confirmFadeIn {
22298
+ from { opacity: 0; }
22299
+ to { opacity: 1; }
22300
+ }
22301
+ .annotations-confirm-modal {
22302
+ background: white;
22303
+ border-radius: 12px;
22304
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
22305
+ max-width: 400px;
22306
+ width: 90%;
22307
+ overflow: hidden;
22308
+ animation: confirmSlideIn 0.25s ease;
22309
+ }
22310
+ @keyframes confirmSlideIn {
22311
+ from { transform: translateY(-20px) scale(0.95); opacity: 0; }
22312
+ to { transform: translateY(0) scale(1); opacity: 1; }
22313
+ }
22314
+ .annotations-confirm-header {
22315
+ display: flex;
22316
+ align-items: center;
22317
+ gap: 10px;
22318
+ padding: 16px 20px;
22319
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
22320
+ border-bottom: 1px solid #f59e0b;
22321
+ }
22322
+ .annotations-confirm-icon {
22323
+ font-size: 20px;
22324
+ }
22325
+ .annotations-confirm-title {
22326
+ font-weight: 600;
22327
+ color: #92400e;
22328
+ font-size: 16px;
22329
+ }
22330
+ .annotations-confirm-body {
22331
+ padding: 20px;
22332
+ }
22333
+ .annotations-confirm-body p {
22334
+ margin: 0;
22335
+ color: #374151;
22336
+ font-size: 14px;
22337
+ line-height: 1.5;
22338
+ }
22339
+ .annotations-confirm-actions {
22340
+ display: flex;
22341
+ gap: 12px;
22342
+ padding: 16px 20px;
22343
+ background: #f9fafb;
22344
+ border-top: 1px solid #e5e7eb;
22345
+ justify-content: flex-end;
22346
+ }
22347
+ .annotations-confirm-btn {
22348
+ padding: 10px 20px;
22349
+ border-radius: 8px;
22350
+ font-size: 14px;
22351
+ font-weight: 500;
22352
+ cursor: pointer;
22353
+ transition: all 0.2s ease;
22354
+ border: none;
22355
+ }
22356
+ .annotations-confirm-btn--cancel {
22357
+ background: #e5e7eb;
22358
+ color: #374151;
22359
+ }
22360
+ .annotations-confirm-btn--cancel:hover {
22361
+ background: #d1d5db;
22362
+ }
22363
+ .annotations-confirm-btn--confirm {
22364
+ background: #f59e0b;
22365
+ color: white;
22366
+ }
22367
+ .annotations-confirm-btn--confirm:hover {
22368
+ background: #d97706;
22369
+ }
22370
+ `;
22371
+ document.head.appendChild(style);
22372
+ }
22373
+ document.body.appendChild(overlay);
22374
+ const cleanup = (result) => {
22375
+ overlay.remove();
22376
+ resolve(result);
22377
+ };
22378
+ overlay.querySelector('[data-action="cancel"]')?.addEventListener("click", () => cleanup(false));
22379
+ overlay.querySelector('[data-action="confirm"]')?.addEventListener("click", () => cleanup(true));
22380
+ overlay.addEventListener("click", (e) => {
22381
+ if (e.target === overlay) cleanup(false);
22382
+ });
22383
+ });
22384
+ }
22194
22385
  async saveAnnotations() {
22195
22386
  try {
22196
22387
  const data = {
@@ -22254,7 +22445,7 @@ var AnnotationsTab = class {
22254
22445
  this.render();
22255
22446
  } else {
22256
22447
  this.annotations.shift();
22257
- alert("Erro ao salvar anota\xE7\xE3o. Tente novamente.");
22448
+ MyIOToast.show("Erro ao salvar anota\xE7\xE3o. Tente novamente.", "error");
22258
22449
  }
22259
22450
  }
22260
22451
  async editAnnotation(id, changes) {
@@ -22264,9 +22455,10 @@ var AnnotationsTab = class {
22264
22455
  const now = (/* @__PURE__ */ new Date()).toISOString();
22265
22456
  const changeRecord = {};
22266
22457
  for (const [key, value] of Object.entries(changes)) {
22267
- if (annotation[key] !== value) {
22458
+ const annotationAny = annotation;
22459
+ if (annotationAny[key] !== value) {
22268
22460
  changeRecord[key] = {
22269
- from: annotation[key],
22461
+ from: annotationAny[key],
22270
22462
  to: value
22271
22463
  };
22272
22464
  }
@@ -22291,7 +22483,7 @@ var AnnotationsTab = class {
22291
22483
  const success = await this.saveAnnotations();
22292
22484
  if (!success) {
22293
22485
  this.annotations[index] = annotation;
22294
- alert("Erro ao atualizar anota\xE7\xE3o. Tente novamente.");
22486
+ MyIOToast.show("Erro ao atualizar anota\xE7\xE3o. Tente novamente.", "error");
22295
22487
  }
22296
22488
  }
22297
22489
  async archiveAnnotation(id) {
@@ -22319,7 +22511,7 @@ var AnnotationsTab = class {
22319
22511
  this.render();
22320
22512
  } else {
22321
22513
  this.annotations[index] = annotation;
22322
- alert("Erro ao arquivar anota\xE7\xE3o. Tente novamente.");
22514
+ MyIOToast.show("Erro ao arquivar anota\xE7\xE3o. Tente novamente.", "error");
22323
22515
  }
22324
22516
  }
22325
22517
  async acknowledgeAnnotation(id) {
@@ -22834,8 +23026,8 @@ var AnnotationsTab = class {
22834
23026
  card.querySelector('[data-action="details"]')?.addEventListener("click", () => {
22835
23027
  this.showDetailModal(id);
22836
23028
  });
22837
- card.querySelector('[data-action="archive"]')?.addEventListener("click", () => {
22838
- if (confirm("Tem certeza que deseja arquivar esta anota\xE7\xE3o?")) {
23029
+ card.querySelector('[data-action="archive"]')?.addEventListener("click", async () => {
23030
+ if (await this.showConfirmation("Tem certeza que deseja arquivar esta anota\xE7\xE3o?", "Arquivar Anota\xE7\xE3o")) {
22839
23031
  this.archiveAnnotation(id);
22840
23032
  }
22841
23033
  });
@@ -22852,8 +23044,8 @@ var AnnotationsTab = class {
22852
23044
  row.querySelector('[data-action="details"]')?.addEventListener("click", () => {
22853
23045
  this.showDetailModal(id);
22854
23046
  });
22855
- row.querySelector('[data-action="archive"]')?.addEventListener("click", () => {
22856
- if (confirm("Tem certeza que deseja arquivar esta anota\xE7\xE3o?")) {
23047
+ row.querySelector('[data-action="archive"]')?.addEventListener("click", async () => {
23048
+ if (await this.showConfirmation("Tem certeza que deseja arquivar esta anota\xE7\xE3o?", "Arquivar Anota\xE7\xE3o")) {
22857
23049
  this.archiveAnnotation(id);
22858
23050
  }
22859
23051
  });
@@ -22862,12 +23054,189 @@ var AnnotationsTab = class {
22862
23054
  // ============================================
22863
23055
  // EDIT MODAL
22864
23056
  // ============================================
22865
- showEditModal(id) {
23057
+ /**
23058
+ * Show a styled input modal for editing annotation text
23059
+ * Replaces native prompt() for better UX
23060
+ */
23061
+ showInputModal(title, placeholder, initialValue) {
23062
+ return new Promise((resolve) => {
23063
+ const overlay = document.createElement("div");
23064
+ overlay.className = "annotations-input-overlay";
23065
+ overlay.innerHTML = `
23066
+ <div class="annotations-input-modal">
23067
+ <div class="annotations-input-header">
23068
+ <span class="annotations-input-icon">\u270F\uFE0F</span>
23069
+ <span class="annotations-input-title">${title}</span>
23070
+ </div>
23071
+ <div class="annotations-input-body">
23072
+ <textarea
23073
+ class="annotations-input-textarea"
23074
+ placeholder="${placeholder}"
23075
+ maxlength="255"
23076
+ >${initialValue}</textarea>
23077
+ <div class="annotations-input-char-count">
23078
+ <span id="input-char-count">${initialValue.length}</span> / 255
23079
+ </div>
23080
+ </div>
23081
+ <div class="annotations-input-actions">
23082
+ <button class="annotations-input-btn annotations-input-btn--cancel" data-action="cancel">
23083
+ Cancelar
23084
+ </button>
23085
+ <button class="annotations-input-btn annotations-input-btn--confirm" data-action="confirm">
23086
+ Salvar
23087
+ </button>
23088
+ </div>
23089
+ </div>
23090
+ `;
23091
+ if (!document.getElementById("annotations-input-styles")) {
23092
+ const style = document.createElement("style");
23093
+ style.id = "annotations-input-styles";
23094
+ style.textContent = `
23095
+ .annotations-input-overlay {
23096
+ position: fixed;
23097
+ inset: 0;
23098
+ background: rgba(0, 0, 0, 0.5);
23099
+ backdrop-filter: blur(4px);
23100
+ display: flex;
23101
+ align-items: center;
23102
+ justify-content: center;
23103
+ z-index: 100001;
23104
+ animation: inputFadeIn 0.2s ease;
23105
+ }
23106
+ @keyframes inputFadeIn {
23107
+ from { opacity: 0; }
23108
+ to { opacity: 1; }
23109
+ }
23110
+ .annotations-input-modal {
23111
+ background: white;
23112
+ border-radius: 12px;
23113
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
23114
+ max-width: 500px;
23115
+ width: 90%;
23116
+ overflow: hidden;
23117
+ animation: inputSlideIn 0.25s ease;
23118
+ }
23119
+ @keyframes inputSlideIn {
23120
+ from { transform: translateY(-20px) scale(0.95); opacity: 0; }
23121
+ to { transform: translateY(0) scale(1); opacity: 1; }
23122
+ }
23123
+ .annotations-input-header {
23124
+ display: flex;
23125
+ align-items: center;
23126
+ gap: 10px;
23127
+ padding: 16px 20px;
23128
+ background: linear-gradient(135deg, #6c5ce7 0%, #a29bfe 100%);
23129
+ border-bottom: 1px solid #5b4cdb;
23130
+ }
23131
+ .annotations-input-icon {
23132
+ font-size: 20px;
23133
+ }
23134
+ .annotations-input-title {
23135
+ font-weight: 600;
23136
+ color: white;
23137
+ font-size: 16px;
23138
+ }
23139
+ .annotations-input-body {
23140
+ padding: 20px;
23141
+ }
23142
+ .annotations-input-textarea {
23143
+ width: 100%;
23144
+ min-height: 100px;
23145
+ padding: 12px;
23146
+ border: 1px solid #dee2e6;
23147
+ border-radius: 8px;
23148
+ font-size: 14px;
23149
+ font-family: inherit;
23150
+ resize: vertical;
23151
+ color: #212529;
23152
+ }
23153
+ .annotations-input-textarea:focus {
23154
+ outline: none;
23155
+ border-color: #6c5ce7;
23156
+ box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.15);
23157
+ }
23158
+ .annotations-input-char-count {
23159
+ font-size: 11px;
23160
+ color: #6c757d;
23161
+ text-align: right;
23162
+ margin-top: 6px;
23163
+ }
23164
+ .annotations-input-actions {
23165
+ display: flex;
23166
+ gap: 12px;
23167
+ padding: 16px 20px;
23168
+ background: #f9fafb;
23169
+ border-top: 1px solid #e5e7eb;
23170
+ justify-content: flex-end;
23171
+ }
23172
+ .annotations-input-btn {
23173
+ padding: 10px 20px;
23174
+ border-radius: 8px;
23175
+ font-size: 14px;
23176
+ font-weight: 500;
23177
+ cursor: pointer;
23178
+ transition: all 0.2s ease;
23179
+ border: none;
23180
+ }
23181
+ .annotations-input-btn--cancel {
23182
+ background: #e5e7eb;
23183
+ color: #374151;
23184
+ }
23185
+ .annotations-input-btn--cancel:hover {
23186
+ background: #d1d5db;
23187
+ }
23188
+ .annotations-input-btn--confirm {
23189
+ background: #6c5ce7;
23190
+ color: white;
23191
+ }
23192
+ .annotations-input-btn--confirm:hover {
23193
+ background: #5b4cdb;
23194
+ }
23195
+ `;
23196
+ document.head.appendChild(style);
23197
+ }
23198
+ document.body.appendChild(overlay);
23199
+ const textarea = overlay.querySelector(".annotations-input-textarea");
23200
+ const charCount = overlay.querySelector("#input-char-count");
23201
+ textarea.focus();
23202
+ textarea.select();
23203
+ textarea.addEventListener("input", () => {
23204
+ charCount.textContent = String(textarea.value.length);
23205
+ });
23206
+ const cleanup = (result) => {
23207
+ overlay.remove();
23208
+ resolve(result);
23209
+ };
23210
+ overlay.querySelector('[data-action="cancel"]')?.addEventListener("click", () => cleanup(null));
23211
+ overlay.querySelector('[data-action="confirm"]')?.addEventListener("click", () => {
23212
+ const value = textarea.value.trim();
23213
+ cleanup(value || null);
23214
+ });
23215
+ overlay.addEventListener("click", (e) => {
23216
+ if (e.target === overlay) cleanup(null);
23217
+ });
23218
+ textarea.addEventListener("keydown", (e) => {
23219
+ if (e.key === "Enter" && e.ctrlKey) {
23220
+ const value = textarea.value.trim();
23221
+ cleanup(value || null);
23222
+ }
23223
+ if (e.key === "Escape") {
23224
+ cleanup(null);
23225
+ }
23226
+ });
23227
+ });
23228
+ }
23229
+ async showEditModal(id) {
22866
23230
  const annotation = this.annotations.find((a) => a.id === id);
22867
23231
  if (!annotation) return;
22868
- const newText = prompt("Editar texto da anota\xE7\xE3o:", annotation.text);
22869
- if (newText && newText.trim() !== annotation.text) {
22870
- this.updateAnnotation(id, { text: newText.trim() });
23232
+ const newText = await this.showInputModal(
23233
+ "Editar Anota\xE7\xE3o",
23234
+ "Digite o novo texto da anota\xE7\xE3o...",
23235
+ annotation.text
23236
+ );
23237
+ if (newText && newText !== annotation.text) {
23238
+ await this.editAnnotation(id, { text: newText });
23239
+ this.render();
22871
23240
  }
22872
23241
  }
22873
23242
  // ============================================
@@ -22961,7 +23330,7 @@ var AnnotationsTab = class {
22961
23330
  overlay.querySelector(".annotation-detail__close")?.addEventListener("click", () => overlay.remove());
22962
23331
  overlay.querySelector('[data-action="close"]')?.addEventListener("click", () => overlay.remove());
22963
23332
  overlay.querySelector('[data-action="archive"]')?.addEventListener("click", async () => {
22964
- if (confirm("Tem certeza que deseja arquivar esta anota\xE7\xE3o?")) {
23333
+ if (await this.showConfirmation("Tem certeza que deseja arquivar esta anota\xE7\xE3o?", "Arquivar Anota\xE7\xE3o")) {
22965
23334
  overlay.remove();
22966
23335
  await this.archiveAnnotation(id);
22967
23336
  }
@@ -27526,7 +27895,7 @@ async function openTemperatureModal(params) {
27526
27895
  const defaultDateRange = getTodaySoFar();
27527
27896
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
27528
27897
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
27529
- const state = {
27898
+ const state2 = {
27530
27899
  token: params.token,
27531
27900
  deviceId: params.deviceId,
27532
27901
  label: params.label || "Sensor de Temperatura",
@@ -27549,58 +27918,58 @@ async function openTemperatureModal(params) {
27549
27918
  };
27550
27919
  const savedGranularity = localStorage.getItem("myio-temp-modal-granularity");
27551
27920
  const savedTheme = localStorage.getItem("myio-temp-modal-theme");
27552
- if (savedGranularity) state.granularity = savedGranularity;
27553
- if (savedTheme) state.theme = savedTheme;
27921
+ if (savedGranularity) state2.granularity = savedGranularity;
27922
+ if (savedTheme) state2.theme = savedTheme;
27554
27923
  const modalContainer = document.createElement("div");
27555
27924
  modalContainer.id = modalId;
27556
27925
  document.body.appendChild(modalContainer);
27557
- renderModal(modalContainer, state, modalId);
27926
+ renderModal(modalContainer, state2, modalId);
27558
27927
  try {
27559
- state.data = await fetchTemperatureData(state.token, state.deviceId, state.startTs, state.endTs);
27560
- state.stats = calculateStats(state.data, state.clampRange);
27561
- state.isLoading = false;
27562
- renderModal(modalContainer, state, modalId);
27563
- drawChart(modalId, state);
27928
+ state2.data = await fetchTemperatureData(state2.token, state2.deviceId, state2.startTs, state2.endTs);
27929
+ state2.stats = calculateStats(state2.data, state2.clampRange);
27930
+ state2.isLoading = false;
27931
+ renderModal(modalContainer, state2, modalId);
27932
+ drawChart(modalId, state2);
27564
27933
  } catch (error) {
27565
27934
  console.error("[TemperatureModal] Error fetching data:", error);
27566
- state.isLoading = false;
27567
- renderModal(modalContainer, state, modalId, error);
27935
+ state2.isLoading = false;
27936
+ renderModal(modalContainer, state2, modalId, error);
27568
27937
  }
27569
- await setupEventListeners(modalContainer, state, modalId, params.onClose);
27938
+ await setupEventListeners(modalContainer, state2, modalId, params.onClose);
27570
27939
  return {
27571
27940
  destroy: () => {
27572
27941
  modalContainer.remove();
27573
27942
  params.onClose?.();
27574
27943
  },
27575
27944
  updateData: async (startDate, endDate, granularity) => {
27576
- state.startTs = new Date(startDate).getTime();
27577
- state.endTs = new Date(endDate).getTime();
27578
- if (granularity) state.granularity = granularity;
27579
- state.isLoading = true;
27580
- renderModal(modalContainer, state, modalId);
27945
+ state2.startTs = new Date(startDate).getTime();
27946
+ state2.endTs = new Date(endDate).getTime();
27947
+ if (granularity) state2.granularity = granularity;
27948
+ state2.isLoading = true;
27949
+ renderModal(modalContainer, state2, modalId);
27581
27950
  try {
27582
- state.data = await fetchTemperatureData(state.token, state.deviceId, state.startTs, state.endTs);
27583
- state.stats = calculateStats(state.data, state.clampRange);
27584
- state.isLoading = false;
27585
- renderModal(modalContainer, state, modalId);
27586
- drawChart(modalId, state);
27951
+ state2.data = await fetchTemperatureData(state2.token, state2.deviceId, state2.startTs, state2.endTs);
27952
+ state2.stats = calculateStats(state2.data, state2.clampRange);
27953
+ state2.isLoading = false;
27954
+ renderModal(modalContainer, state2, modalId);
27955
+ drawChart(modalId, state2);
27587
27956
  } catch (error) {
27588
27957
  console.error("[TemperatureModal] Error updating data:", error);
27589
- state.isLoading = false;
27590
- renderModal(modalContainer, state, modalId, error);
27958
+ state2.isLoading = false;
27959
+ renderModal(modalContainer, state2, modalId, error);
27591
27960
  }
27592
27961
  }
27593
27962
  };
27594
27963
  }
27595
- function renderModal(container, state, modalId, error) {
27596
- const colors = getThemeColors(state.theme);
27597
- const startDateStr = new Date(state.startTs).toLocaleDateString(state.locale);
27598
- const endDateStr = new Date(state.endTs).toLocaleDateString(state.locale);
27599
- const statusText = state.temperatureStatus === "ok" ? "Dentro da faixa" : state.temperatureStatus === "above" ? "Acima do limite" : state.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
27600
- const statusColor = state.temperatureStatus === "ok" ? colors.success : state.temperatureStatus === "above" ? colors.danger : state.temperatureStatus === "below" ? colors.primary : colors.textMuted;
27601
- const rangeText = state.temperatureMin !== null && state.temperatureMax !== null ? `${state.temperatureMin}\xB0C - ${state.temperatureMax}\xB0C` : "N\xE3o definida";
27602
- const startDateInput = new Date(state.startTs).toISOString().slice(0, 16);
27603
- const endDateInput = new Date(state.endTs).toISOString().slice(0, 16);
27964
+ function renderModal(container, state2, modalId, error) {
27965
+ const colors = getThemeColors(state2.theme);
27966
+ const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale);
27967
+ const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale);
27968
+ const statusText = state2.temperatureStatus === "ok" ? "Dentro da faixa" : state2.temperatureStatus === "above" ? "Acima do limite" : state2.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
27969
+ const statusColor = state2.temperatureStatus === "ok" ? colors.success : state2.temperatureStatus === "above" ? colors.danger : state2.temperatureStatus === "below" ? colors.primary : colors.textMuted;
27970
+ const rangeText = state2.temperatureMin !== null && state2.temperatureMax !== null ? `${state2.temperatureMin}\xB0C - ${state2.temperatureMax}\xB0C` : "N\xE3o definida";
27971
+ const startDateInput = new Date(state2.startTs).toISOString().slice(0, 16);
27972
+ const endDateInput = new Date(state2.endTs).toISOString().slice(0, 16);
27604
27973
  const isMaximized = container.__isMaximized || false;
27605
27974
  const contentMaxWidth = isMaximized ? "100%" : "900px";
27606
27975
  const contentMaxHeight = isMaximized ? "100vh" : "95vh";
@@ -27627,7 +27996,7 @@ function renderModal(container, state, modalId, error) {
27627
27996
  min-height: 20px;
27628
27997
  ">
27629
27998
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
27630
- \u{1F321}\uFE0F ${state.label} - Hist\xF3rico de Temperatura
27999
+ \u{1F321}\uFE0F ${state2.label} - Hist\xF3rico de Temperatura
27631
28000
  </h2>
27632
28001
  <div style="display: flex; gap: 4px; align-items: center;">
27633
28002
  <!-- Theme Toggle -->
@@ -27635,7 +28004,7 @@ function renderModal(container, state, modalId, error) {
27635
28004
  background: none; border: none; font-size: 16px; cursor: pointer;
27636
28005
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
27637
28006
  transition: background-color 0.2s;
27638
- ">${state.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
28007
+ ">${state2.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
27639
28008
  <!-- Maximize Button -->
27640
28009
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
27641
28010
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -27657,7 +28026,7 @@ function renderModal(container, state, modalId, error) {
27657
28026
  <!-- Controls Row -->
27658
28027
  <div style="
27659
28028
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
27660
- margin-bottom: 16px; padding: 16px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
28029
+ margin-bottom: 16px; padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
27661
28030
  border-radius: 6px; border: 1px solid ${colors.border};
27662
28031
  ">
27663
28032
  <!-- Granularity Select -->
@@ -27670,8 +28039,8 @@ function renderModal(container, state, modalId, error) {
27670
28039
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
27671
28040
  cursor: pointer; min-width: 130px;
27672
28041
  ">
27673
- <option value="hour" ${state.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
27674
- <option value="day" ${state.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
28042
+ <option value="hour" ${state2.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
28043
+ <option value="day" ${state2.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
27675
28044
  </select>
27676
28045
  </div>
27677
28046
  <!-- Day Period Filter (Multiselect) -->
@@ -27685,7 +28054,7 @@ function renderModal(container, state, modalId, error) {
27685
28054
  cursor: pointer; min-width: 180px; text-align: left;
27686
28055
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
27687
28056
  ">
27688
- <span>${getSelectedPeriodsLabel(state.selectedPeriods)}</span>
28057
+ <span>${getSelectedPeriodsLabel(state2.selectedPeriods)}</span>
27689
28058
  <span style="font-size: 10px;">\u25BC</span>
27690
28059
  </button>
27691
28060
  <div id="${modalId}-period-dropdown" style="
@@ -27698,12 +28067,12 @@ function renderModal(container, state, modalId, error) {
27698
28067
  <label style="
27699
28068
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
27700
28069
  cursor: pointer; font-size: 13px; color: ${colors.text};
27701
- " onmouseover="this.style.background='${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
28070
+ " onmouseover="this.style.background='${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
27702
28071
  onmouseout="this.style.background='transparent'">
27703
28072
  <input type="checkbox"
27704
28073
  name="${modalId}-period"
27705
28074
  value="${period.id}"
27706
- ${state.selectedPeriods.includes(period.id) ? "checked" : ""}
28075
+ ${state2.selectedPeriods.includes(period.id) ? "checked" : ""}
27707
28076
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
27708
28077
  ${period.label}
27709
28078
  </label>
@@ -27711,13 +28080,13 @@ function renderModal(container, state, modalId, error) {
27711
28080
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
27712
28081
  <button id="${modalId}-period-select-all" type="button" style="
27713
28082
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
27714
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
28083
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
27715
28084
  border: none; border-radius: 4px; cursor: pointer;
27716
28085
  font-size: 12px; color: ${colors.text};
27717
28086
  ">Selecionar Todos</button>
27718
28087
  <button id="${modalId}-period-clear" type="button" style="
27719
28088
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
27720
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
28089
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
27721
28090
  border: none; border-radius: 4px; cursor: pointer;
27722
28091
  font-size: 12px; color: ${colors.text};
27723
28092
  ">Limpar Sele\xE7\xE3o</button>
@@ -27742,8 +28111,8 @@ function renderModal(container, state, modalId, error) {
27742
28111
  font-size: 14px; font-weight: 500; height: 38px;
27743
28112
  display: flex; align-items: center; gap: 8px;
27744
28113
  font-family: 'Roboto', Arial, sans-serif;
27745
- " ${state.isLoading ? "disabled" : ""}>
27746
- ${state.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
28114
+ " ${state2.isLoading ? "disabled" : ""}>
28115
+ ${state2.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
27747
28116
  </button>
27748
28117
  </div>
27749
28118
 
@@ -27754,40 +28123,40 @@ function renderModal(container, state, modalId, error) {
27754
28123
  ">
27755
28124
  <!-- Current Temperature -->
27756
28125
  <div style="
27757
- padding: 16px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
28126
+ padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27758
28127
  border-radius: 12px; border: 1px solid ${colors.border};
27759
28128
  ">
27760
28129
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Temperatura Atual</span>
27761
28130
  <div style="font-weight: 700; font-size: 24px; color: ${statusColor}; margin-top: 4px;">
27762
- ${state.currentTemperature !== null ? formatTemperature(state.currentTemperature) : "N/A"}
28131
+ ${state2.currentTemperature !== null ? formatTemperature(state2.currentTemperature) : "N/A"}
27763
28132
  </div>
27764
28133
  <div style="font-size: 11px; color: ${statusColor}; margin-top: 2px;">${statusText}</div>
27765
28134
  </div>
27766
28135
  <!-- Average -->
27767
28136
  <div style="
27768
- padding: 16px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
28137
+ padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27769
28138
  border-radius: 12px; border: 1px solid ${colors.border};
27770
28139
  ">
27771
28140
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">M\xE9dia do Per\xEDodo</span>
27772
28141
  <div id="${modalId}-avg" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
27773
- ${state.stats.count > 0 ? formatTemperature(state.stats.avg) : "N/A"}
28142
+ ${state2.stats.count > 0 ? formatTemperature(state2.stats.avg) : "N/A"}
27774
28143
  </div>
27775
28144
  <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${startDateStr} - ${endDateStr}</div>
27776
28145
  </div>
27777
28146
  <!-- Min/Max -->
27778
28147
  <div style="
27779
- padding: 16px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
28148
+ padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27780
28149
  border-radius: 12px; border: 1px solid ${colors.border};
27781
28150
  ">
27782
28151
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Min / Max</span>
27783
28152
  <div id="${modalId}-minmax" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
27784
- ${state.stats.count > 0 ? `${formatTemperature(state.stats.min)} / ${formatTemperature(state.stats.max)}` : "N/A"}
28153
+ ${state2.stats.count > 0 ? `${formatTemperature(state2.stats.min)} / ${formatTemperature(state2.stats.max)}` : "N/A"}
27785
28154
  </div>
27786
- <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state.stats.count} leituras</div>
28155
+ <div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state2.stats.count} leituras</div>
27787
28156
  </div>
27788
28157
  <!-- Ideal Range -->
27789
28158
  <div style="
27790
- padding: 16px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
28159
+ padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
27791
28160
  border-radius: 12px; border: 1px solid ${colors.border};
27792
28161
  ">
27793
28162
  <span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Faixa Ideal</span>
@@ -27803,18 +28172,18 @@ function renderModal(container, state, modalId, error) {
27803
28172
  Hist\xF3rico de Temperatura
27804
28173
  </h3>
27805
28174
  <div id="${modalId}-chart" style="
27806
- height: 320px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
28175
+ height: 320px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
27807
28176
  border-radius: 12px; display: flex; justify-content: center; align-items: center;
27808
28177
  border: 1px solid ${colors.border}; position: relative;
27809
28178
  ">
27810
- ${state.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
28179
+ ${state2.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
27811
28180
  <div style="animation: spin 1s linear infinite; font-size: 32px; margin-bottom: 8px;">\u21BB</div>
27812
28181
  <div>Carregando dados...</div>
27813
28182
  </div>` : error ? `<div style="text-align: center; color: ${colors.danger};">
27814
28183
  <div style="font-size: 32px; margin-bottom: 8px;">\u26A0\uFE0F</div>
27815
28184
  <div>Erro ao carregar dados</div>
27816
28185
  <div style="font-size: 12px; margin-top: 4px;">${error.message}</div>
27817
- </div>` : state.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
28186
+ </div>` : state2.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
27818
28187
  <div style="font-size: 32px; margin-bottom: 8px;">\u{1F4ED}</div>
27819
28188
  <div>Sem dados para o per\xEDodo selecionado</div>
27820
28189
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -27824,12 +28193,12 @@ function renderModal(container, state, modalId, error) {
27824
28193
  <!-- Actions -->
27825
28194
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
27826
28195
  <button id="${modalId}-export" style="
27827
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
28196
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
27828
28197
  color: ${colors.text}; border: 1px solid ${colors.border};
27829
28198
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
27830
28199
  font-size: 14px; display: flex; align-items: center; gap: 8px;
27831
28200
  font-family: 'Roboto', Arial, sans-serif;
27832
- " ${state.data.length === 0 ? "disabled" : ""}>
28201
+ " ${state2.data.length === 0 ? "disabled" : ""}>
27833
28202
  \u{1F4E5} Exportar CSV
27834
28203
  </button>
27835
28204
  <button id="${modalId}-close-btn" style="
@@ -27880,14 +28249,14 @@ function renderModal(container, state, modalId, error) {
27880
28249
  </style>
27881
28250
  `;
27882
28251
  }
27883
- function drawChart(modalId, state) {
28252
+ function drawChart(modalId, state2) {
27884
28253
  const chartContainer = document.getElementById(`${modalId}-chart`);
27885
28254
  const canvas = document.getElementById(`${modalId}-canvas`);
27886
- if (!chartContainer || !canvas || state.data.length === 0) return;
28255
+ if (!chartContainer || !canvas || state2.data.length === 0) return;
27887
28256
  const ctx = canvas.getContext("2d");
27888
28257
  if (!ctx) return;
27889
- const colors = getThemeColors(state.theme);
27890
- const filteredData = filterByDayPeriods(state.data, state.selectedPeriods);
28258
+ const colors = getThemeColors(state2.theme);
28259
+ const filteredData = filterByDayPeriods(state2.data, state2.selectedPeriods);
27891
28260
  if (filteredData.length === 0) {
27892
28261
  canvas.width = chartContainer.clientWidth;
27893
28262
  canvas.height = chartContainer.clientHeight;
@@ -27898,14 +28267,14 @@ function drawChart(modalId, state) {
27898
28267
  return;
27899
28268
  }
27900
28269
  let chartData;
27901
- if (state.granularity === "hour") {
28270
+ if (state2.granularity === "hour") {
27902
28271
  const interpolated = interpolateTemperature(filteredData, {
27903
28272
  intervalMinutes: 30,
27904
- startTs: state.startTs,
27905
- endTs: state.endTs,
27906
- clampRange: state.clampRange
28273
+ startTs: state2.startTs,
28274
+ endTs: state2.endTs,
28275
+ clampRange: state2.clampRange
27907
28276
  });
27908
- const filteredInterpolated = filterByDayPeriods(interpolated, state.selectedPeriods);
28277
+ const filteredInterpolated = filterByDayPeriods(interpolated, state2.selectedPeriods);
27909
28278
  chartData = filteredInterpolated.map((item) => ({
27910
28279
  x: item.ts,
27911
28280
  y: Number(item.value),
@@ -27913,7 +28282,7 @@ function drawChart(modalId, state) {
27913
28282
  screenY: 0
27914
28283
  }));
27915
28284
  } else {
27916
- const daily = aggregateByDay(filteredData, state.clampRange);
28285
+ const daily = aggregateByDay(filteredData, state2.clampRange);
27917
28286
  chartData = daily.map((item) => ({
27918
28287
  x: item.dateTs,
27919
28288
  y: item.avg,
@@ -27931,12 +28300,12 @@ function drawChart(modalId, state) {
27931
28300
  const paddingRight = 20;
27932
28301
  const paddingTop = 20;
27933
28302
  const paddingBottom = 55;
27934
- const isPeriodsFiltered = state.selectedPeriods.length < 4 && state.selectedPeriods.length > 0;
28303
+ const isPeriodsFiltered = state2.selectedPeriods.length < 4 && state2.selectedPeriods.length > 0;
27935
28304
  const values = chartData.map((d) => d.y);
27936
28305
  const dataMin = Math.min(...values);
27937
28306
  const dataMax = Math.max(...values);
27938
- const thresholdMin = state.temperatureMin !== null ? state.temperatureMin : dataMin;
27939
- const thresholdMax = state.temperatureMax !== null ? state.temperatureMax : dataMax;
28307
+ const thresholdMin = state2.temperatureMin !== null ? state2.temperatureMin : dataMin;
28308
+ const thresholdMax = state2.temperatureMax !== null ? state2.temperatureMax : dataMax;
27940
28309
  const minY = Math.min(dataMin, thresholdMin) - 1;
27941
28310
  const maxY = Math.max(dataMax, thresholdMax) + 1;
27942
28311
  const chartWidth = width - paddingLeft - paddingRight;
@@ -27968,9 +28337,9 @@ function drawChart(modalId, state) {
27968
28337
  ctx.lineTo(width - paddingRight, y);
27969
28338
  ctx.stroke();
27970
28339
  }
27971
- if (state.temperatureMin !== null && state.temperatureMax !== null) {
27972
- const rangeMinY = height - paddingBottom - (state.temperatureMin - minY) * scaleY;
27973
- const rangeMaxY = height - paddingBottom - (state.temperatureMax - minY) * scaleY;
28340
+ if (state2.temperatureMin !== null && state2.temperatureMax !== null) {
28341
+ const rangeMinY = height - paddingBottom - (state2.temperatureMin - minY) * scaleY;
28342
+ const rangeMaxY = height - paddingBottom - (state2.temperatureMax - minY) * scaleY;
27974
28343
  ctx.fillStyle = "rgba(76, 175, 80, 0.1)";
27975
28344
  ctx.fillRect(paddingLeft, rangeMaxY, chartWidth, rangeMinY - rangeMaxY);
27976
28345
  ctx.strokeStyle = colors.success;
@@ -28012,10 +28381,10 @@ function drawChart(modalId, state) {
28012
28381
  const point = chartData[i];
28013
28382
  const date = new Date(point.x);
28014
28383
  let label;
28015
- if (state.granularity === "hour") {
28016
- label = date.toLocaleTimeString(state.locale, { hour: "2-digit", minute: "2-digit" });
28384
+ if (state2.granularity === "hour") {
28385
+ label = date.toLocaleTimeString(state2.locale, { hour: "2-digit", minute: "2-digit" });
28017
28386
  } else {
28018
- label = date.toLocaleDateString(state.locale, { day: "2-digit", month: "2-digit" });
28387
+ label = date.toLocaleDateString(state2.locale, { day: "2-digit", month: "2-digit" });
28019
28388
  }
28020
28389
  ctx.strokeStyle = colors.chartGrid;
28021
28390
  ctx.lineWidth = 1;
@@ -28033,16 +28402,16 @@ function drawChart(modalId, state) {
28033
28402
  ctx.lineTo(paddingLeft, height - paddingBottom);
28034
28403
  ctx.lineTo(width - paddingRight, height - paddingBottom);
28035
28404
  ctx.stroke();
28036
- setupChartTooltip(canvas, chartContainer, chartData, state, colors);
28405
+ setupChartTooltip(canvas, chartContainer, chartData, state2, colors);
28037
28406
  }
28038
- function setupChartTooltip(canvas, container, chartData, state, colors) {
28407
+ function setupChartTooltip(canvas, container, chartData, state2, colors) {
28039
28408
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
28040
28409
  if (existingTooltip) existingTooltip.remove();
28041
28410
  const tooltip = document.createElement("div");
28042
28411
  tooltip.className = "myio-chart-tooltip";
28043
28412
  tooltip.style.cssText = `
28044
28413
  position: absolute;
28045
- background: ${state.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
28414
+ background: ${state2.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
28046
28415
  border: 1px solid ${colors.border};
28047
28416
  border-radius: 8px;
28048
28417
  padding: 10px 14px;
@@ -28079,17 +28448,17 @@ function setupChartTooltip(canvas, container, chartData, state, colors) {
28079
28448
  if (point) {
28080
28449
  const date = new Date(point.x);
28081
28450
  let dateStr;
28082
- if (state.granularity === "hour") {
28083
- dateStr = date.toLocaleDateString(state.locale, {
28451
+ if (state2.granularity === "hour") {
28452
+ dateStr = date.toLocaleDateString(state2.locale, {
28084
28453
  day: "2-digit",
28085
28454
  month: "2-digit",
28086
28455
  year: "numeric"
28087
- }) + " " + date.toLocaleTimeString(state.locale, {
28456
+ }) + " " + date.toLocaleTimeString(state2.locale, {
28088
28457
  hour: "2-digit",
28089
28458
  minute: "2-digit"
28090
28459
  });
28091
28460
  } else {
28092
- dateStr = date.toLocaleDateString(state.locale, {
28461
+ dateStr = date.toLocaleDateString(state2.locale, {
28093
28462
  day: "2-digit",
28094
28463
  month: "2-digit",
28095
28464
  year: "numeric"
@@ -28127,7 +28496,7 @@ function setupChartTooltip(canvas, container, chartData, state, colors) {
28127
28496
  canvas.style.cursor = "default";
28128
28497
  });
28129
28498
  }
28130
- async function setupEventListeners(container, state, modalId, onClose) {
28499
+ async function setupEventListeners(container, state2, modalId, onClose) {
28131
28500
  const closeModal = () => {
28132
28501
  container.remove();
28133
28502
  onClose?.();
@@ -28138,19 +28507,19 @@ async function setupEventListeners(container, state, modalId, onClose) {
28138
28507
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
28139
28508
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
28140
28509
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
28141
- if (dateRangeInput && !state.dateRangePicker) {
28510
+ if (dateRangeInput && !state2.dateRangePicker) {
28142
28511
  try {
28143
- state.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
28144
- presetStart: new Date(state.startTs).toISOString(),
28145
- presetEnd: new Date(state.endTs).toISOString(),
28512
+ state2.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
28513
+ presetStart: new Date(state2.startTs).toISOString(),
28514
+ presetEnd: new Date(state2.endTs).toISOString(),
28146
28515
  includeTime: true,
28147
28516
  timePrecision: "minute",
28148
28517
  maxRangeDays: 90,
28149
- locale: state.locale,
28518
+ locale: state2.locale,
28150
28519
  parentEl: container.querySelector(".myio-temp-modal-content"),
28151
28520
  onApply: (result) => {
28152
- state.startTs = new Date(result.startISO).getTime();
28153
- state.endTs = new Date(result.endISO).getTime();
28521
+ state2.startTs = new Date(result.startISO).getTime();
28522
+ state2.endTs = new Date(result.endISO).getTime();
28154
28523
  console.log("[TemperatureModal] Date range applied:", result);
28155
28524
  }
28156
28525
  });
@@ -28159,19 +28528,19 @@ async function setupEventListeners(container, state, modalId, onClose) {
28159
28528
  }
28160
28529
  }
28161
28530
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
28162
- state.theme = state.theme === "dark" ? "light" : "dark";
28163
- localStorage.setItem("myio-temp-modal-theme", state.theme);
28164
- state.dateRangePicker = null;
28165
- renderModal(container, state, modalId);
28166
- if (state.data.length > 0) drawChart(modalId, state);
28167
- await setupEventListeners(container, state, modalId, onClose);
28531
+ state2.theme = state2.theme === "dark" ? "light" : "dark";
28532
+ localStorage.setItem("myio-temp-modal-theme", state2.theme);
28533
+ state2.dateRangePicker = null;
28534
+ renderModal(container, state2, modalId);
28535
+ if (state2.data.length > 0) drawChart(modalId, state2);
28536
+ await setupEventListeners(container, state2, modalId, onClose);
28168
28537
  });
28169
28538
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
28170
28539
  container.__isMaximized = !container.__isMaximized;
28171
- state.dateRangePicker = null;
28172
- renderModal(container, state, modalId);
28173
- if (state.data.length > 0) drawChart(modalId, state);
28174
- await setupEventListeners(container, state, modalId, onClose);
28540
+ state2.dateRangePicker = null;
28541
+ renderModal(container, state2, modalId);
28542
+ if (state2.data.length > 0) drawChart(modalId, state2);
28543
+ await setupEventListeners(container, state2, modalId, onClose);
28175
28544
  });
28176
28545
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
28177
28546
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -28190,71 +28559,71 @@ async function setupEventListeners(container, state, modalId, onClose) {
28190
28559
  periodCheckboxes.forEach((checkbox) => {
28191
28560
  checkbox.addEventListener("change", () => {
28192
28561
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
28193
- state.selectedPeriods = checked;
28562
+ state2.selectedPeriods = checked;
28194
28563
  const btnLabel = periodBtn?.querySelector("span:first-child");
28195
28564
  if (btnLabel) {
28196
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
28565
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
28197
28566
  }
28198
- if (state.data.length > 0) drawChart(modalId, state);
28567
+ if (state2.data.length > 0) drawChart(modalId, state2);
28199
28568
  });
28200
28569
  });
28201
28570
  document.getElementById(`${modalId}-period-select-all`)?.addEventListener("click", () => {
28202
28571
  periodCheckboxes.forEach((cb) => {
28203
28572
  cb.checked = true;
28204
28573
  });
28205
- state.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
28574
+ state2.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
28206
28575
  const btnLabel = periodBtn?.querySelector("span:first-child");
28207
28576
  if (btnLabel) {
28208
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
28577
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
28209
28578
  }
28210
- if (state.data.length > 0) drawChart(modalId, state);
28579
+ if (state2.data.length > 0) drawChart(modalId, state2);
28211
28580
  });
28212
28581
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
28213
28582
  periodCheckboxes.forEach((cb) => {
28214
28583
  cb.checked = false;
28215
28584
  });
28216
- state.selectedPeriods = [];
28585
+ state2.selectedPeriods = [];
28217
28586
  const btnLabel = periodBtn?.querySelector("span:first-child");
28218
28587
  if (btnLabel) {
28219
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
28588
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
28220
28589
  }
28221
- if (state.data.length > 0) drawChart(modalId, state);
28590
+ if (state2.data.length > 0) drawChart(modalId, state2);
28222
28591
  });
28223
28592
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
28224
- state.granularity = e.target.value;
28225
- localStorage.setItem("myio-temp-modal-granularity", state.granularity);
28226
- if (state.data.length > 0) drawChart(modalId, state);
28593
+ state2.granularity = e.target.value;
28594
+ localStorage.setItem("myio-temp-modal-granularity", state2.granularity);
28595
+ if (state2.data.length > 0) drawChart(modalId, state2);
28227
28596
  });
28228
28597
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
28229
- if (state.startTs >= state.endTs) {
28598
+ if (state2.startTs >= state2.endTs) {
28230
28599
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
28231
28600
  return;
28232
28601
  }
28233
- state.isLoading = true;
28234
- state.dateRangePicker = null;
28235
- renderModal(container, state, modalId);
28602
+ state2.isLoading = true;
28603
+ state2.dateRangePicker = null;
28604
+ renderModal(container, state2, modalId);
28236
28605
  try {
28237
- state.data = await fetchTemperatureData(state.token, state.deviceId, state.startTs, state.endTs);
28238
- state.stats = calculateStats(state.data, state.clampRange);
28239
- state.isLoading = false;
28240
- renderModal(container, state, modalId);
28241
- drawChart(modalId, state);
28242
- await setupEventListeners(container, state, modalId, onClose);
28606
+ state2.data = await fetchTemperatureData(state2.token, state2.deviceId, state2.startTs, state2.endTs);
28607
+ state2.stats = calculateStats(state2.data, state2.clampRange);
28608
+ state2.isLoading = false;
28609
+ renderModal(container, state2, modalId);
28610
+ drawChart(modalId, state2);
28611
+ await setupEventListeners(container, state2, modalId, onClose);
28243
28612
  } catch (error) {
28244
28613
  console.error("[TemperatureModal] Error fetching data:", error);
28245
- state.isLoading = false;
28246
- renderModal(container, state, modalId, error);
28247
- await setupEventListeners(container, state, modalId, onClose);
28614
+ state2.isLoading = false;
28615
+ renderModal(container, state2, modalId, error);
28616
+ await setupEventListeners(container, state2, modalId, onClose);
28248
28617
  }
28249
28618
  });
28250
28619
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
28251
- if (state.data.length === 0) return;
28252
- const startDateStr = new Date(state.startTs).toLocaleDateString(state.locale).replace(/\//g, "-");
28253
- const endDateStr = new Date(state.endTs).toLocaleDateString(state.locale).replace(/\//g, "-");
28620
+ if (state2.data.length === 0) return;
28621
+ const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
28622
+ const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
28254
28623
  exportTemperatureCSV(
28255
- state.data,
28256
- state.label,
28257
- state.stats,
28624
+ state2.data,
28625
+ state2.label,
28626
+ state2.stats,
28258
28627
  startDateStr,
28259
28628
  endDateStr
28260
28629
  );
@@ -28267,7 +28636,7 @@ async function openTemperatureComparisonModal(params) {
28267
28636
  const defaultDateRange = getTodaySoFar();
28268
28637
  const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
28269
28638
  const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
28270
- const state = {
28639
+ const state2 = {
28271
28640
  token: params.token,
28272
28641
  devices: params.devices,
28273
28642
  startTs,
@@ -28286,44 +28655,44 @@ async function openTemperatureComparisonModal(params) {
28286
28655
  };
28287
28656
  const savedGranularity = localStorage.getItem("myio-temp-comparison-granularity");
28288
28657
  const savedTheme = localStorage.getItem("myio-temp-comparison-theme");
28289
- if (savedGranularity) state.granularity = savedGranularity;
28290
- if (savedTheme) state.theme = savedTheme;
28658
+ if (savedGranularity) state2.granularity = savedGranularity;
28659
+ if (savedTheme) state2.theme = savedTheme;
28291
28660
  const modalContainer = document.createElement("div");
28292
28661
  modalContainer.id = modalId;
28293
28662
  document.body.appendChild(modalContainer);
28294
- renderModal2(modalContainer, state, modalId);
28295
- await fetchAllDevicesData(state);
28296
- renderModal2(modalContainer, state, modalId);
28297
- drawComparisonChart(modalId, state);
28298
- await setupEventListeners2(modalContainer, state, modalId, params.onClose);
28663
+ renderModal2(modalContainer, state2, modalId);
28664
+ await fetchAllDevicesData(state2);
28665
+ renderModal2(modalContainer, state2, modalId);
28666
+ drawComparisonChart(modalId, state2);
28667
+ await setupEventListeners2(modalContainer, state2, modalId, params.onClose);
28299
28668
  return {
28300
28669
  destroy: () => {
28301
28670
  modalContainer.remove();
28302
28671
  params.onClose?.();
28303
28672
  },
28304
28673
  updateData: async (startDate, endDate, granularity) => {
28305
- state.startTs = new Date(startDate).getTime();
28306
- state.endTs = new Date(endDate).getTime();
28307
- if (granularity) state.granularity = granularity;
28308
- state.isLoading = true;
28309
- renderModal2(modalContainer, state, modalId);
28310
- await fetchAllDevicesData(state);
28311
- renderModal2(modalContainer, state, modalId);
28312
- drawComparisonChart(modalId, state);
28313
- setupEventListeners2(modalContainer, state, modalId, params.onClose);
28674
+ state2.startTs = new Date(startDate).getTime();
28675
+ state2.endTs = new Date(endDate).getTime();
28676
+ if (granularity) state2.granularity = granularity;
28677
+ state2.isLoading = true;
28678
+ renderModal2(modalContainer, state2, modalId);
28679
+ await fetchAllDevicesData(state2);
28680
+ renderModal2(modalContainer, state2, modalId);
28681
+ drawComparisonChart(modalId, state2);
28682
+ setupEventListeners2(modalContainer, state2, modalId, params.onClose);
28314
28683
  }
28315
28684
  };
28316
28685
  }
28317
- async function fetchAllDevicesData(state) {
28318
- state.isLoading = true;
28319
- state.deviceData = [];
28686
+ async function fetchAllDevicesData(state2) {
28687
+ state2.isLoading = true;
28688
+ state2.deviceData = [];
28320
28689
  try {
28321
28690
  const results = await Promise.all(
28322
- state.devices.map(async (device, index) => {
28691
+ state2.devices.map(async (device, index) => {
28323
28692
  const deviceId = device.tbId || device.id;
28324
28693
  try {
28325
- const data = await fetchTemperatureData(state.token, deviceId, state.startTs, state.endTs);
28326
- const stats = calculateStats(data, state.clampRange);
28694
+ const data = await fetchTemperatureData(state2.token, deviceId, state2.startTs, state2.endTs);
28695
+ const stats = calculateStats(data, state2.clampRange);
28327
28696
  return {
28328
28697
  device,
28329
28698
  data,
@@ -28341,21 +28710,21 @@ async function fetchAllDevicesData(state) {
28341
28710
  }
28342
28711
  })
28343
28712
  );
28344
- state.deviceData = results;
28713
+ state2.deviceData = results;
28345
28714
  } catch (error) {
28346
28715
  console.error("[TemperatureComparisonModal] Error fetching data:", error);
28347
28716
  }
28348
- state.isLoading = false;
28717
+ state2.isLoading = false;
28349
28718
  }
28350
- function renderModal2(container, state, modalId) {
28351
- const colors = getThemeColors(state.theme);
28352
- const startDateStr = new Date(state.startTs).toLocaleDateString(state.locale);
28353
- const endDateStr = new Date(state.endTs).toLocaleDateString(state.locale);
28354
- const startDateInput = new Date(state.startTs).toISOString().slice(0, 16);
28355
- const endDateInput = new Date(state.endTs).toISOString().slice(0, 16);
28356
- const legendHTML = state.deviceData.map((dd) => `
28719
+ function renderModal2(container, state2, modalId) {
28720
+ const colors = getThemeColors(state2.theme);
28721
+ const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale);
28722
+ const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale);
28723
+ const startDateInput = new Date(state2.startTs).toISOString().slice(0, 16);
28724
+ const endDateInput = new Date(state2.endTs).toISOString().slice(0, 16);
28725
+ const legendHTML = state2.deviceData.map((dd) => `
28357
28726
  <div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px;
28358
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
28727
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
28359
28728
  border-radius: 8px;">
28360
28729
  <span style="width: 12px; height: 12px; border-radius: 50%; background: ${dd.color};"></span>
28361
28730
  <span style="color: ${colors.text}; font-size: 13px;">${dd.device.label}</span>
@@ -28364,9 +28733,9 @@ function renderModal2(container, state, modalId) {
28364
28733
  </span>
28365
28734
  </div>
28366
28735
  `).join("");
28367
- const statsHTML = state.deviceData.map((dd) => `
28736
+ const statsHTML = state2.deviceData.map((dd) => `
28368
28737
  <div style="
28369
- padding: 12px; background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
28738
+ padding: 12px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
28370
28739
  border-radius: 10px; border-left: 4px solid ${dd.color};
28371
28740
  min-width: 150px;
28372
28741
  ">
@@ -28417,7 +28786,7 @@ function renderModal2(container, state, modalId) {
28417
28786
  min-height: 20px;
28418
28787
  ">
28419
28788
  <h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
28420
- \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state.devices.length} sensores
28789
+ \u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state2.devices.length} sensores
28421
28790
  </h2>
28422
28791
  <div style="display: flex; gap: 4px; align-items: center;">
28423
28792
  <!-- Theme Toggle -->
@@ -28425,7 +28794,7 @@ function renderModal2(container, state, modalId) {
28425
28794
  background: none; border: none; font-size: 16px; cursor: pointer;
28426
28795
  padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
28427
28796
  transition: background-color 0.2s;
28428
- ">${state.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
28797
+ ">${state2.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
28429
28798
  <!-- Maximize Button -->
28430
28799
  <button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
28431
28800
  background: none; border: none; font-size: 16px; cursor: pointer;
@@ -28448,7 +28817,7 @@ function renderModal2(container, state, modalId) {
28448
28817
  <div style="
28449
28818
  display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
28450
28819
  margin-bottom: 16px; padding: 16px;
28451
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
28820
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
28452
28821
  border-radius: 6px; border: 1px solid ${colors.border};
28453
28822
  ">
28454
28823
  <!-- Granularity Select -->
@@ -28461,8 +28830,8 @@ function renderModal2(container, state, modalId) {
28461
28830
  font-size: 14px; color: ${colors.text}; background: ${colors.surface};
28462
28831
  cursor: pointer; min-width: 130px;
28463
28832
  ">
28464
- <option value="hour" ${state.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
28465
- <option value="day" ${state.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
28833
+ <option value="hour" ${state2.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
28834
+ <option value="day" ${state2.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
28466
28835
  </select>
28467
28836
  </div>
28468
28837
  <!-- Day Period Filter (Multiselect) -->
@@ -28476,7 +28845,7 @@ function renderModal2(container, state, modalId) {
28476
28845
  cursor: pointer; min-width: 180px; text-align: left;
28477
28846
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
28478
28847
  ">
28479
- <span>${getSelectedPeriodsLabel(state.selectedPeriods)}</span>
28848
+ <span>${getSelectedPeriodsLabel(state2.selectedPeriods)}</span>
28480
28849
  <span style="font-size: 10px;">\u25BC</span>
28481
28850
  </button>
28482
28851
  <div id="${modalId}-period-dropdown" style="
@@ -28489,12 +28858,12 @@ function renderModal2(container, state, modalId) {
28489
28858
  <label style="
28490
28859
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
28491
28860
  cursor: pointer; font-size: 13px; color: ${colors.text};
28492
- " onmouseover="this.style.background='${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
28861
+ " onmouseover="this.style.background='${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
28493
28862
  onmouseout="this.style.background='transparent'">
28494
28863
  <input type="checkbox"
28495
28864
  name="${modalId}-period"
28496
28865
  value="${period.id}"
28497
- ${state.selectedPeriods.includes(period.id) ? "checked" : ""}
28866
+ ${state2.selectedPeriods.includes(period.id) ? "checked" : ""}
28498
28867
  style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
28499
28868
  ${period.label}
28500
28869
  </label>
@@ -28502,13 +28871,13 @@ function renderModal2(container, state, modalId) {
28502
28871
  <div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
28503
28872
  <button id="${modalId}-period-select-all" type="button" style="
28504
28873
  width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
28505
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
28874
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
28506
28875
  border: none; border-radius: 4px; cursor: pointer;
28507
28876
  font-size: 12px; color: ${colors.text};
28508
28877
  ">Selecionar Todos</button>
28509
28878
  <button id="${modalId}-period-clear" type="button" style="
28510
28879
  width: calc(100% - 16px); margin: 0 8px; padding: 6px;
28511
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
28880
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
28512
28881
  border: none; border-radius: 4px; cursor: pointer;
28513
28882
  font-size: 12px; color: ${colors.text};
28514
28883
  ">Limpar Sele\xE7\xE3o</button>
@@ -28533,8 +28902,8 @@ function renderModal2(container, state, modalId) {
28533
28902
  font-size: 14px; font-weight: 500; height: 38px;
28534
28903
  display: flex; align-items: center; gap: 8px;
28535
28904
  font-family: 'Roboto', Arial, sans-serif;
28536
- " ${state.isLoading ? "disabled" : ""}>
28537
- ${state.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
28905
+ " ${state2.isLoading ? "disabled" : ""}>
28906
+ ${state2.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
28538
28907
  </button>
28539
28908
  </div>
28540
28909
 
@@ -28550,14 +28919,14 @@ function renderModal2(container, state, modalId) {
28550
28919
  <div style="margin-bottom: 24px;">
28551
28920
  <div id="${modalId}-chart" style="
28552
28921
  height: 380px;
28553
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
28922
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
28554
28923
  border-radius: 14px; display: flex; justify-content: center; align-items: center;
28555
28924
  border: 1px solid ${colors.border}; position: relative;
28556
28925
  ">
28557
- ${state.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
28926
+ ${state2.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
28558
28927
  <div style="animation: spin 1s linear infinite; font-size: 36px; margin-bottom: 12px;">\u21BB</div>
28559
- <div style="font-size: 15px;">Carregando dados de ${state.devices.length} sensores...</div>
28560
- </div>` : state.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
28928
+ <div style="font-size: 15px;">Carregando dados de ${state2.devices.length} sensores...</div>
28929
+ </div>` : state2.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
28561
28930
  <div style="font-size: 48px; margin-bottom: 12px;">\u{1F4ED}</div>
28562
28931
  <div style="font-size: 16px;">Sem dados para o per\xEDodo selecionado</div>
28563
28932
  </div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
@@ -28575,12 +28944,12 @@ function renderModal2(container, state, modalId) {
28575
28944
  <!-- Actions -->
28576
28945
  <div style="display: flex; justify-content: flex-end; gap: 12px;">
28577
28946
  <button id="${modalId}-export" style="
28578
- background: ${state.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
28947
+ background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
28579
28948
  color: ${colors.text}; border: 1px solid ${colors.border};
28580
28949
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
28581
28950
  font-size: 14px; display: flex; align-items: center; gap: 8px;
28582
28951
  font-family: 'Roboto', Arial, sans-serif;
28583
- " ${state.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
28952
+ " ${state2.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
28584
28953
  \u{1F4E5} Exportar CSV
28585
28954
  </button>
28586
28955
  <button id="${modalId}-close-btn" style="
@@ -28615,15 +28984,15 @@ function renderModal2(container, state, modalId) {
28615
28984
  </style>
28616
28985
  `;
28617
28986
  }
28618
- function drawComparisonChart(modalId, state) {
28987
+ function drawComparisonChart(modalId, state2) {
28619
28988
  const chartContainer = document.getElementById(`${modalId}-chart`);
28620
28989
  const canvas = document.getElementById(`${modalId}-canvas`);
28621
28990
  if (!chartContainer || !canvas) return;
28622
- const hasData = state.deviceData.some((dd) => dd.data.length > 0);
28991
+ const hasData = state2.deviceData.some((dd) => dd.data.length > 0);
28623
28992
  if (!hasData) return;
28624
28993
  const ctx = canvas.getContext("2d");
28625
28994
  if (!ctx) return;
28626
- const colors = getThemeColors(state.theme);
28995
+ const colors = getThemeColors(state2.theme);
28627
28996
  const width = chartContainer.clientWidth - 2;
28628
28997
  const height = 380;
28629
28998
  canvas.width = width;
@@ -28634,19 +29003,19 @@ function drawComparisonChart(modalId, state) {
28634
29003
  const paddingBottom = 55;
28635
29004
  ctx.clearRect(0, 0, width, height);
28636
29005
  const processedData = [];
28637
- state.deviceData.forEach((dd) => {
29006
+ state2.deviceData.forEach((dd) => {
28638
29007
  if (dd.data.length === 0) return;
28639
- const filteredData = filterByDayPeriods(dd.data, state.selectedPeriods);
29008
+ const filteredData = filterByDayPeriods(dd.data, state2.selectedPeriods);
28640
29009
  if (filteredData.length === 0) return;
28641
29010
  let points;
28642
- if (state.granularity === "hour") {
29011
+ if (state2.granularity === "hour") {
28643
29012
  const interpolated = interpolateTemperature(filteredData, {
28644
29013
  intervalMinutes: 30,
28645
- startTs: state.startTs,
28646
- endTs: state.endTs,
28647
- clampRange: state.clampRange
29014
+ startTs: state2.startTs,
29015
+ endTs: state2.endTs,
29016
+ clampRange: state2.clampRange
28648
29017
  });
28649
- const filteredInterpolated = filterByDayPeriods(interpolated, state.selectedPeriods);
29018
+ const filteredInterpolated = filterByDayPeriods(interpolated, state2.selectedPeriods);
28650
29019
  points = filteredInterpolated.map((item) => ({
28651
29020
  x: item.ts,
28652
29021
  y: Number(item.value),
@@ -28656,7 +29025,7 @@ function drawComparisonChart(modalId, state) {
28656
29025
  deviceColor: dd.color
28657
29026
  }));
28658
29027
  } else {
28659
- const daily = aggregateByDay(filteredData, state.clampRange);
29028
+ const daily = aggregateByDay(filteredData, state2.clampRange);
28660
29029
  points = daily.map((item) => ({
28661
29030
  x: item.dateTs,
28662
29031
  y: item.avg,
@@ -28677,7 +29046,7 @@ function drawComparisonChart(modalId, state) {
28677
29046
  ctx.fillText("Nenhum dado para os per\xEDodos selecionados", width / 2, height / 2);
28678
29047
  return;
28679
29048
  }
28680
- const isPeriodsFiltered = state.selectedPeriods.length < 4 && state.selectedPeriods.length > 0;
29049
+ const isPeriodsFiltered = state2.selectedPeriods.length < 4 && state2.selectedPeriods.length > 0;
28681
29050
  let dataMinY = Infinity;
28682
29051
  let dataMaxY = -Infinity;
28683
29052
  processedData.forEach(({ points }) => {
@@ -28687,7 +29056,7 @@ function drawComparisonChart(modalId, state) {
28687
29056
  });
28688
29057
  });
28689
29058
  const rangeMap = /* @__PURE__ */ new Map();
28690
- state.deviceData.forEach((dd, index) => {
29059
+ state2.deviceData.forEach((dd, index) => {
28691
29060
  const device = dd.device;
28692
29061
  const min = device.temperatureMin;
28693
29062
  const max = device.temperatureMax;
@@ -28706,10 +29075,10 @@ function drawComparisonChart(modalId, state) {
28706
29075
  }
28707
29076
  }
28708
29077
  });
28709
- if (rangeMap.size === 0 && state.temperatureMin !== null && state.temperatureMax !== null) {
29078
+ if (rangeMap.size === 0 && state2.temperatureMin !== null && state2.temperatureMax !== null) {
28710
29079
  rangeMap.set("global", {
28711
- min: state.temperatureMin,
28712
- max: state.temperatureMax,
29080
+ min: state2.temperatureMin,
29081
+ max: state2.temperatureMax,
28713
29082
  customerName: "Global",
28714
29083
  color: colors.success,
28715
29084
  deviceLabels: []
@@ -28830,10 +29199,10 @@ function drawComparisonChart(modalId, state) {
28830
29199
  const point = xAxisPoints[i];
28831
29200
  const date = new Date(point.x);
28832
29201
  let label;
28833
- if (state.granularity === "hour") {
28834
- label = date.toLocaleTimeString(state.locale, { hour: "2-digit", minute: "2-digit" });
29202
+ if (state2.granularity === "hour") {
29203
+ label = date.toLocaleTimeString(state2.locale, { hour: "2-digit", minute: "2-digit" });
28835
29204
  } else {
28836
- label = date.toLocaleDateString(state.locale, { day: "2-digit", month: "2-digit" });
29205
+ label = date.toLocaleDateString(state2.locale, { day: "2-digit", month: "2-digit" });
28837
29206
  }
28838
29207
  ctx.strokeStyle = colors.chartGrid;
28839
29208
  ctx.lineWidth = 1;
@@ -28852,16 +29221,16 @@ function drawComparisonChart(modalId, state) {
28852
29221
  ctx.lineTo(width - paddingRight, height - paddingBottom);
28853
29222
  ctx.stroke();
28854
29223
  const allChartPoints = processedData.flatMap((pd) => pd.points);
28855
- setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state, colors);
29224
+ setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state2, colors);
28856
29225
  }
28857
- function setupComparisonChartTooltip(canvas, container, chartData, state, colors) {
29226
+ function setupComparisonChartTooltip(canvas, container, chartData, state2, colors) {
28858
29227
  const existingTooltip = container.querySelector(".myio-chart-tooltip");
28859
29228
  if (existingTooltip) existingTooltip.remove();
28860
29229
  const tooltip = document.createElement("div");
28861
29230
  tooltip.className = "myio-chart-tooltip";
28862
29231
  tooltip.style.cssText = `
28863
29232
  position: absolute;
28864
- background: ${state.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
29233
+ background: ${state2.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
28865
29234
  border: 1px solid ${colors.border};
28866
29235
  border-radius: 8px;
28867
29236
  padding: 10px 14px;
@@ -28898,17 +29267,17 @@ function setupComparisonChartTooltip(canvas, container, chartData, state, colors
28898
29267
  if (point) {
28899
29268
  const date = new Date(point.x);
28900
29269
  let dateStr;
28901
- if (state.granularity === "hour") {
28902
- dateStr = date.toLocaleDateString(state.locale, {
29270
+ if (state2.granularity === "hour") {
29271
+ dateStr = date.toLocaleDateString(state2.locale, {
28903
29272
  day: "2-digit",
28904
29273
  month: "2-digit",
28905
29274
  year: "numeric"
28906
- }) + " " + date.toLocaleTimeString(state.locale, {
29275
+ }) + " " + date.toLocaleTimeString(state2.locale, {
28907
29276
  hour: "2-digit",
28908
29277
  minute: "2-digit"
28909
29278
  });
28910
29279
  } else {
28911
- dateStr = date.toLocaleDateString(state.locale, {
29280
+ dateStr = date.toLocaleDateString(state2.locale, {
28912
29281
  day: "2-digit",
28913
29282
  month: "2-digit",
28914
29283
  year: "numeric"
@@ -28950,7 +29319,7 @@ function setupComparisonChartTooltip(canvas, container, chartData, state, colors
28950
29319
  canvas.style.cursor = "default";
28951
29320
  });
28952
29321
  }
28953
- async function setupEventListeners2(container, state, modalId, onClose) {
29322
+ async function setupEventListeners2(container, state2, modalId, onClose) {
28954
29323
  const closeModal = () => {
28955
29324
  container.remove();
28956
29325
  onClose?.();
@@ -28961,19 +29330,19 @@ async function setupEventListeners2(container, state, modalId, onClose) {
28961
29330
  document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
28962
29331
  document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
28963
29332
  const dateRangeInput = document.getElementById(`${modalId}-date-range`);
28964
- if (dateRangeInput && !state.dateRangePicker) {
29333
+ if (dateRangeInput && !state2.dateRangePicker) {
28965
29334
  try {
28966
- state.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
28967
- presetStart: new Date(state.startTs).toISOString(),
28968
- presetEnd: new Date(state.endTs).toISOString(),
29335
+ state2.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
29336
+ presetStart: new Date(state2.startTs).toISOString(),
29337
+ presetEnd: new Date(state2.endTs).toISOString(),
28969
29338
  includeTime: true,
28970
29339
  timePrecision: "minute",
28971
29340
  maxRangeDays: 90,
28972
- locale: state.locale,
29341
+ locale: state2.locale,
28973
29342
  parentEl: container.querySelector(".myio-temp-comparison-content"),
28974
29343
  onApply: (result) => {
28975
- state.startTs = new Date(result.startISO).getTime();
28976
- state.endTs = new Date(result.endISO).getTime();
29344
+ state2.startTs = new Date(result.startISO).getTime();
29345
+ state2.endTs = new Date(result.endISO).getTime();
28977
29346
  console.log("[TemperatureComparisonModal] Date range applied:", result);
28978
29347
  }
28979
29348
  });
@@ -28982,23 +29351,23 @@ async function setupEventListeners2(container, state, modalId, onClose) {
28982
29351
  }
28983
29352
  }
28984
29353
  document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
28985
- state.theme = state.theme === "dark" ? "light" : "dark";
28986
- localStorage.setItem("myio-temp-comparison-theme", state.theme);
28987
- state.dateRangePicker = null;
28988
- renderModal2(container, state, modalId);
28989
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
28990
- drawComparisonChart(modalId, state);
28991
- }
28992
- await setupEventListeners2(container, state, modalId, onClose);
29354
+ state2.theme = state2.theme === "dark" ? "light" : "dark";
29355
+ localStorage.setItem("myio-temp-comparison-theme", state2.theme);
29356
+ state2.dateRangePicker = null;
29357
+ renderModal2(container, state2, modalId);
29358
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29359
+ drawComparisonChart(modalId, state2);
29360
+ }
29361
+ await setupEventListeners2(container, state2, modalId, onClose);
28993
29362
  });
28994
29363
  document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
28995
29364
  container.__isMaximized = !container.__isMaximized;
28996
- state.dateRangePicker = null;
28997
- renderModal2(container, state, modalId);
28998
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
28999
- drawComparisonChart(modalId, state);
29365
+ state2.dateRangePicker = null;
29366
+ renderModal2(container, state2, modalId);
29367
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29368
+ drawComparisonChart(modalId, state2);
29000
29369
  }
29001
- await setupEventListeners2(container, state, modalId, onClose);
29370
+ await setupEventListeners2(container, state2, modalId, onClose);
29002
29371
  });
29003
29372
  const periodBtn = document.getElementById(`${modalId}-period-btn`);
29004
29373
  const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
@@ -29017,13 +29386,13 @@ async function setupEventListeners2(container, state, modalId, onClose) {
29017
29386
  periodCheckboxes.forEach((checkbox) => {
29018
29387
  checkbox.addEventListener("change", () => {
29019
29388
  const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
29020
- state.selectedPeriods = checked;
29389
+ state2.selectedPeriods = checked;
29021
29390
  const btnLabel = periodBtn?.querySelector("span:first-child");
29022
29391
  if (btnLabel) {
29023
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
29392
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
29024
29393
  }
29025
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
29026
- drawComparisonChart(modalId, state);
29394
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29395
+ drawComparisonChart(modalId, state2);
29027
29396
  }
29028
29397
  });
29029
29398
  });
@@ -29031,77 +29400,77 @@ async function setupEventListeners2(container, state, modalId, onClose) {
29031
29400
  periodCheckboxes.forEach((cb) => {
29032
29401
  cb.checked = true;
29033
29402
  });
29034
- state.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
29403
+ state2.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
29035
29404
  const btnLabel = periodBtn?.querySelector("span:first-child");
29036
29405
  if (btnLabel) {
29037
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
29406
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
29038
29407
  }
29039
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
29040
- drawComparisonChart(modalId, state);
29408
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29409
+ drawComparisonChart(modalId, state2);
29041
29410
  }
29042
29411
  });
29043
29412
  document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
29044
29413
  periodCheckboxes.forEach((cb) => {
29045
29414
  cb.checked = false;
29046
29415
  });
29047
- state.selectedPeriods = [];
29416
+ state2.selectedPeriods = [];
29048
29417
  const btnLabel = periodBtn?.querySelector("span:first-child");
29049
29418
  if (btnLabel) {
29050
- btnLabel.textContent = getSelectedPeriodsLabel(state.selectedPeriods);
29419
+ btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
29051
29420
  }
29052
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
29053
- drawComparisonChart(modalId, state);
29421
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29422
+ drawComparisonChart(modalId, state2);
29054
29423
  }
29055
29424
  });
29056
29425
  document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
29057
- state.granularity = e.target.value;
29058
- localStorage.setItem("myio-temp-comparison-granularity", state.granularity);
29059
- if (state.deviceData.some((dd) => dd.data.length > 0)) {
29060
- drawComparisonChart(modalId, state);
29426
+ state2.granularity = e.target.value;
29427
+ localStorage.setItem("myio-temp-comparison-granularity", state2.granularity);
29428
+ if (state2.deviceData.some((dd) => dd.data.length > 0)) {
29429
+ drawComparisonChart(modalId, state2);
29061
29430
  }
29062
29431
  });
29063
29432
  document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
29064
- if (state.startTs >= state.endTs) {
29433
+ if (state2.startTs >= state2.endTs) {
29065
29434
  alert("Por favor, selecione um per\xEDodo v\xE1lido");
29066
29435
  return;
29067
29436
  }
29068
- state.isLoading = true;
29069
- state.dateRangePicker = null;
29070
- renderModal2(container, state, modalId);
29071
- await fetchAllDevicesData(state);
29072
- renderModal2(container, state, modalId);
29073
- drawComparisonChart(modalId, state);
29074
- await setupEventListeners2(container, state, modalId, onClose);
29437
+ state2.isLoading = true;
29438
+ state2.dateRangePicker = null;
29439
+ renderModal2(container, state2, modalId);
29440
+ await fetchAllDevicesData(state2);
29441
+ renderModal2(container, state2, modalId);
29442
+ drawComparisonChart(modalId, state2);
29443
+ await setupEventListeners2(container, state2, modalId, onClose);
29075
29444
  });
29076
29445
  document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
29077
- if (state.deviceData.every((dd) => dd.data.length === 0)) return;
29078
- exportComparisonCSV(state);
29446
+ if (state2.deviceData.every((dd) => dd.data.length === 0)) return;
29447
+ exportComparisonCSV(state2);
29079
29448
  });
29080
29449
  }
29081
- function exportComparisonCSV(state) {
29082
- const startDateStr = new Date(state.startTs).toLocaleDateString(state.locale).replace(/\//g, "-");
29083
- const endDateStr = new Date(state.endTs).toLocaleDateString(state.locale).replace(/\//g, "-");
29450
+ function exportComparisonCSV(state2) {
29451
+ const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
29452
+ const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
29084
29453
  const BOM = "\uFEFF";
29085
29454
  let csvContent = BOM;
29086
29455
  csvContent += `Compara\xE7\xE3o de Temperatura
29087
29456
  `;
29088
29457
  csvContent += `Per\xEDodo: ${startDateStr} at\xE9 ${endDateStr}
29089
29458
  `;
29090
- csvContent += `Sensores: ${state.devices.map((d) => d.label).join(", ")}
29459
+ csvContent += `Sensores: ${state2.devices.map((d) => d.label).join(", ")}
29091
29460
  `;
29092
29461
  csvContent += "\n";
29093
29462
  csvContent += "Estat\xEDsticas por Sensor:\n";
29094
29463
  csvContent += "Sensor,M\xE9dia (\xB0C),Min (\xB0C),Max (\xB0C),Leituras\n";
29095
- state.deviceData.forEach((dd) => {
29464
+ state2.deviceData.forEach((dd) => {
29096
29465
  csvContent += `"${dd.device.label}",${dd.stats.avg.toFixed(2)},${dd.stats.min.toFixed(2)},${dd.stats.max.toFixed(2)},${dd.stats.count}
29097
29466
  `;
29098
29467
  });
29099
29468
  csvContent += "\n";
29100
29469
  csvContent += "Dados Detalhados:\n";
29101
29470
  csvContent += "Data/Hora,Sensor,Temperatura (\xB0C)\n";
29102
- state.deviceData.forEach((dd) => {
29471
+ state2.deviceData.forEach((dd) => {
29103
29472
  dd.data.forEach((item) => {
29104
- const date = new Date(item.ts).toLocaleString(state.locale);
29473
+ const date = new Date(item.ts).toLocaleString(state2.locale);
29105
29474
  const temp = Number(item.value).toFixed(2);
29106
29475
  csvContent += `"${date}","${dd.device.label}",${temp}
29107
29476
  `;
@@ -29209,10 +29578,10 @@ async function saveCustomerAttributes(customerId, token, minTemperature, maxTemp
29209
29578
  throw new Error(`Failed to save attributes: ${response.status}`);
29210
29579
  }
29211
29580
  }
29212
- function renderModal3(container, state, modalId, onClose, onSave) {
29213
- const colors = getColors(state.theme);
29214
- const minValue = state.minTemperature !== null ? state.minTemperature : "";
29215
- const maxValue = state.maxTemperature !== null ? state.maxTemperature : "";
29581
+ function renderModal3(container, state2, modalId, onClose, onSave) {
29582
+ const colors = getColors(state2.theme);
29583
+ const minValue = state2.minTemperature !== null ? state2.minTemperature : "";
29584
+ const maxValue = state2.maxTemperature !== null ? state2.maxTemperature : "";
29216
29585
  container.innerHTML = `
29217
29586
  <style>
29218
29587
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@@ -29477,23 +29846,23 @@ function renderModal3(container, state, modalId, onClose, onSave) {
29477
29846
  </div>
29478
29847
 
29479
29848
  <div class="modal-body">
29480
- ${state.isLoading ? `
29849
+ ${state2.isLoading ? `
29481
29850
  <div class="loading-overlay">
29482
29851
  <div class="loading-spinner"></div>
29483
29852
  <div>Carregando configura\xE7\xF5es...</div>
29484
29853
  </div>
29485
29854
  ` : `
29486
- ${state.error ? `
29487
- <div class="message message-error">${state.error}</div>
29855
+ ${state2.error ? `
29856
+ <div class="message message-error">${state2.error}</div>
29488
29857
  ` : ""}
29489
29858
 
29490
- ${state.successMessage ? `
29491
- <div class="message message-success">${state.successMessage}</div>
29859
+ ${state2.successMessage ? `
29860
+ <div class="message message-success">${state2.successMessage}</div>
29492
29861
  ` : ""}
29493
29862
 
29494
29863
  <div class="customer-info">
29495
29864
  <div class="customer-label">Shopping / Cliente</div>
29496
- <div class="customer-name">${state.customerName || "N\xE3o identificado"}</div>
29865
+ <div class="customer-name">${state2.customerName || "N\xE3o identificado"}</div>
29497
29866
  </div>
29498
29867
 
29499
29868
  <div class="form-group">
@@ -29537,11 +29906,11 @@ function renderModal3(container, state, modalId, onClose, onSave) {
29537
29906
  `}
29538
29907
  </div>
29539
29908
 
29540
- ${!state.isLoading ? `
29909
+ ${!state2.isLoading ? `
29541
29910
  <div class="modal-footer">
29542
29911
  <button class="btn btn-secondary" id="${modalId}-cancel">Cancelar</button>
29543
- <button class="btn btn-primary" id="${modalId}-save" ${state.isSaving ? "disabled" : ""}>
29544
- ${state.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
29912
+ <button class="btn btn-primary" id="${modalId}-save" ${state2.isSaving ? "disabled" : ""}>
29913
+ ${state2.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
29545
29914
  </button>
29546
29915
  </div>
29547
29916
  ` : ""}
@@ -29578,18 +29947,18 @@ function renderModal3(container, state, modalId, onClose, onSave) {
29578
29947
  const min = parseFloat(minInput.value);
29579
29948
  const max = parseFloat(maxInput.value);
29580
29949
  if (isNaN(min) || isNaN(max)) {
29581
- state.error = "Por favor, preencha ambos os valores.";
29582
- renderModal3(container, state, modalId, onClose, onSave);
29950
+ state2.error = "Por favor, preencha ambos os valores.";
29951
+ renderModal3(container, state2, modalId, onClose, onSave);
29583
29952
  return;
29584
29953
  }
29585
29954
  if (min >= max) {
29586
- state.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
29587
- renderModal3(container, state, modalId, onClose, onSave);
29955
+ state2.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
29956
+ renderModal3(container, state2, modalId, onClose, onSave);
29588
29957
  return;
29589
29958
  }
29590
29959
  if (min < 0 || max > 50) {
29591
- state.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
29592
- renderModal3(container, state, modalId, onClose, onSave);
29960
+ state2.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
29961
+ renderModal3(container, state2, modalId, onClose, onSave);
29593
29962
  return;
29594
29963
  }
29595
29964
  await onSave(min, max);
@@ -29597,7 +29966,7 @@ function renderModal3(container, state, modalId, onClose, onSave) {
29597
29966
  }
29598
29967
  function openTemperatureSettingsModal(params) {
29599
29968
  const modalId = `myio-temp-settings-${Date.now()}`;
29600
- const state = {
29969
+ const state2 = {
29601
29970
  customerId: params.customerId,
29602
29971
  customerName: params.customerName || "",
29603
29972
  token: params.token,
@@ -29617,37 +29986,37 @@ function openTemperatureSettingsModal(params) {
29617
29986
  params.onClose?.();
29618
29987
  };
29619
29988
  const handleSave = async (min, max) => {
29620
- state.isSaving = true;
29621
- state.error = null;
29622
- state.successMessage = null;
29623
- renderModal3(container, state, modalId, destroy, handleSave);
29989
+ state2.isSaving = true;
29990
+ state2.error = null;
29991
+ state2.successMessage = null;
29992
+ renderModal3(container, state2, modalId, destroy, handleSave);
29624
29993
  try {
29625
- await saveCustomerAttributes(state.customerId, state.token, min, max, params.onError);
29626
- state.minTemperature = min;
29627
- state.maxTemperature = max;
29628
- state.isSaving = false;
29629
- state.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
29630
- renderModal3(container, state, modalId, destroy, handleSave);
29994
+ await saveCustomerAttributes(state2.customerId, state2.token, min, max, params.onError);
29995
+ state2.minTemperature = min;
29996
+ state2.maxTemperature = max;
29997
+ state2.isSaving = false;
29998
+ state2.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
29999
+ renderModal3(container, state2, modalId, destroy, handleSave);
29631
30000
  params.onSave?.({ minTemperature: min, maxTemperature: max });
29632
30001
  setTimeout(() => {
29633
30002
  destroy();
29634
30003
  }, 1500);
29635
30004
  } catch (error) {
29636
- state.isSaving = false;
29637
- state.error = `Erro ao salvar: ${error.message}`;
29638
- renderModal3(container, state, modalId, destroy, handleSave);
30005
+ state2.isSaving = false;
30006
+ state2.error = `Erro ao salvar: ${error.message}`;
30007
+ renderModal3(container, state2, modalId, destroy, handleSave);
29639
30008
  }
29640
30009
  };
29641
- renderModal3(container, state, modalId, destroy, handleSave);
29642
- fetchCustomerAttributes(state.customerId, state.token, params.onError).then(({ minTemperature, maxTemperature }) => {
29643
- state.minTemperature = minTemperature;
29644
- state.maxTemperature = maxTemperature;
29645
- state.isLoading = false;
29646
- renderModal3(container, state, modalId, destroy, handleSave);
30010
+ renderModal3(container, state2, modalId, destroy, handleSave);
30011
+ fetchCustomerAttributes(state2.customerId, state2.token, params.onError).then(({ minTemperature, maxTemperature }) => {
30012
+ state2.minTemperature = minTemperature;
30013
+ state2.maxTemperature = maxTemperature;
30014
+ state2.isLoading = false;
30015
+ renderModal3(container, state2, modalId, destroy, handleSave);
29647
30016
  }).catch((error) => {
29648
- state.isLoading = false;
29649
- state.error = `Erro ao carregar: ${error.message}`;
29650
- renderModal3(container, state, modalId, destroy, handleSave);
30017
+ state2.isLoading = false;
30018
+ state2.error = `Erro ao carregar: ${error.message}`;
30019
+ renderModal3(container, state2, modalId, destroy, handleSave);
29651
30020
  });
29652
30021
  return { destroy };
29653
30022
  }
@@ -29683,11 +30052,13 @@ var ENERGY_SUMMARY_TOOLTIP_CSS = `
29683
30052
  border: 1px solid #e2e8f0;
29684
30053
  border-radius: 12px;
29685
30054
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 2px 10px rgba(0, 0, 0, 0.08);
30055
+ min-width: 380px;
29686
30056
  width: max-content;
29687
30057
  max-width: 90vw;
29688
30058
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
29689
30059
  font-size: 12px;
29690
30060
  color: #1e293b;
30061
+ overflow: hidden;
29691
30062
  }
29692
30063
 
29693
30064
  .energy-summary-tooltip__header {
@@ -29993,8 +30364,7 @@ var ENERGY_SUMMARY_TOOLTIP_CSS = `
29993
30364
  align-items: center;
29994
30365
  padding: 10px 14px;
29995
30366
  background: linear-gradient(135deg, #047857 0%, #059669 100%);
29996
- margin: 12px -14px -14px;
29997
- border-radius: 0 0 12px 12px;
30367
+ border-radius: 0 0 11px 11px;
29998
30368
  }
29999
30369
 
30000
30370
  .energy-summary-tooltip__total-label {
@@ -30137,7 +30507,7 @@ var EnergySummaryTooltip = {
30137
30507
  { key: "failure", label: "Falha", count: status.failure },
30138
30508
  { key: "standby", label: "Standby", count: status.standby },
30139
30509
  { key: "offline", label: "Offline", count: status.offline },
30140
- { key: "no-consumption", label: "Sem Dados", count: status.noConsumption }
30510
+ { key: "no-consumption", label: "Sem Consumo", count: status.noConsumption }
30141
30511
  ];
30142
30512
  return items.map((item) => `
30143
30513
  <div class="energy-summary-tooltip__status-item ${item.key}">
@@ -30582,7 +30952,7 @@ var EnergySummaryTooltip = {
30582
30952
  * Build summary data from TELEMETRY_INFO STATE
30583
30953
  * This is called by the widget controller to get data for the tooltip
30584
30954
  */
30585
- buildSummaryFromState(state, receivedData) {
30955
+ buildSummaryFromState(state2, receivedData) {
30586
30956
  const summary = {
30587
30957
  totalDevices: 0,
30588
30958
  totalConsumption: 0,
@@ -30598,22 +30968,22 @@ var EnergySummaryTooltip = {
30598
30968
  },
30599
30969
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
30600
30970
  };
30601
- if (!state) return summary;
30971
+ if (!state2) return summary;
30602
30972
  const entrada = {
30603
30973
  id: "entrada",
30604
30974
  name: "Entrada",
30605
30975
  icon: CATEGORY_ICONS.entrada,
30606
- deviceCount: state.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
30607
- consumption: state.entrada?.total || 0,
30976
+ deviceCount: state2.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
30977
+ consumption: state2.entrada?.total || 0,
30608
30978
  percentage: 100
30609
30979
  };
30610
30980
  const lojas = {
30611
30981
  id: "lojas",
30612
30982
  name: "Lojas",
30613
30983
  icon: CATEGORY_ICONS.lojas,
30614
- deviceCount: state.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
30615
- consumption: state.consumidores?.lojas?.total || 0,
30616
- percentage: state.consumidores?.lojas?.perc || 0
30984
+ deviceCount: state2.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
30985
+ consumption: state2.consumidores?.lojas?.total || 0,
30986
+ percentage: state2.consumidores?.lojas?.perc || 0
30617
30987
  };
30618
30988
  const climatizacaoData = receivedData?.climatizacao || {};
30619
30989
  const elevadoresData = receivedData?.elevadores || {};
@@ -30624,9 +30994,9 @@ var EnergySummaryTooltip = {
30624
30994
  id: "climatizacao",
30625
30995
  name: "Climatizacao",
30626
30996
  icon: CATEGORY_ICONS.climatizacao,
30627
- deviceCount: climatizacaoData.count || state.consumidores?.climatizacao?.devices?.length || 0,
30628
- consumption: state.consumidores?.climatizacao?.total || 0,
30629
- percentage: state.consumidores?.climatizacao?.perc || 0
30997
+ deviceCount: climatizacaoData.count || state2.consumidores?.climatizacao?.devices?.length || 0,
30998
+ consumption: state2.consumidores?.climatizacao?.total || 0,
30999
+ percentage: state2.consumidores?.climatizacao?.perc || 0
30630
31000
  };
30631
31001
  if (climatizacaoData.subcategories) {
30632
31002
  climatizacao.children = [];
@@ -30677,54 +31047,67 @@ var EnergySummaryTooltip = {
30677
31047
  id: "elevadores",
30678
31048
  name: "Elevadores",
30679
31049
  icon: CATEGORY_ICONS.elevadores,
30680
- deviceCount: elevadoresData.count || state.consumidores?.elevadores?.devices?.length || 0,
30681
- consumption: state.consumidores?.elevadores?.total || 0,
30682
- percentage: state.consumidores?.elevadores?.perc || 0
31050
+ deviceCount: elevadoresData.count || state2.consumidores?.elevadores?.devices?.length || 0,
31051
+ consumption: state2.consumidores?.elevadores?.total || 0,
31052
+ percentage: state2.consumidores?.elevadores?.perc || 0
30683
31053
  });
30684
31054
  areaComumChildren.push({
30685
31055
  id: "escadasRolantes",
30686
31056
  name: "Esc. Rolantes",
30687
31057
  icon: CATEGORY_ICONS.escadas,
30688
- deviceCount: escadasData.count || state.consumidores?.escadasRolantes?.devices?.length || 0,
30689
- consumption: state.consumidores?.escadasRolantes?.total || 0,
30690
- percentage: state.consumidores?.escadasRolantes?.perc || 0
31058
+ deviceCount: escadasData.count || state2.consumidores?.escadasRolantes?.devices?.length || 0,
31059
+ consumption: state2.consumidores?.escadasRolantes?.total || 0,
31060
+ percentage: state2.consumidores?.escadasRolantes?.perc || 0
30691
31061
  });
30692
31062
  areaComumChildren.push({
30693
31063
  id: "outros",
30694
31064
  name: "Outros",
30695
31065
  icon: CATEGORY_ICONS.outros,
30696
- deviceCount: outrosData.count || state.consumidores?.outros?.devices?.length || 0,
30697
- consumption: state.consumidores?.outros?.total || 0,
30698
- percentage: state.consumidores?.outros?.perc || 0
31066
+ deviceCount: outrosData.count || state2.consumidores?.outros?.devices?.length || 0,
31067
+ consumption: state2.consumidores?.outros?.total || 0,
31068
+ percentage: state2.consumidores?.outros?.perc || 0
30699
31069
  });
30700
31070
  const areaComumDeviceCount = areaComumChildren.reduce((sum, c) => sum + c.deviceCount, 0);
30701
- const areaComumConsumption = state.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
31071
+ const areaComumConsumption = state2.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
30702
31072
  const areaComum = {
30703
31073
  id: "areaComum",
30704
31074
  name: "Area Comum",
30705
31075
  icon: CATEGORY_ICONS.areaComum,
30706
31076
  deviceCount: areaComumDeviceCount,
30707
31077
  consumption: areaComumConsumption,
30708
- percentage: state.consumidores?.areaComum?.perc || 0,
31078
+ percentage: state2.consumidores?.areaComum?.perc || 0,
30709
31079
  children: areaComumChildren
30710
31080
  };
30711
31081
  summary.byCategory = [entrada, lojas, areaComum];
30712
31082
  summary.totalDevices = entrada.deviceCount + lojas.deviceCount + areaComumDeviceCount;
30713
- summary.totalConsumption = state.grandTotal || entrada.consumption;
31083
+ summary.totalConsumption = state2.grandTotal || entrada.consumption;
30714
31084
  const totalDevices = summary.totalDevices;
30715
- summary.byStatus = {
30716
- normal: Math.floor(totalDevices * 0.85),
30717
- // Estimate 85% normal
30718
- alert: Math.floor(totalDevices * 0.08),
30719
- // Estimate 8% alert
30720
- failure: Math.floor(totalDevices * 0.02),
30721
- // Estimate 2% failure
30722
- standby: Math.floor(totalDevices * 0.03),
30723
- // Estimate 3% standby
30724
- offline: Math.floor(totalDevices * 0.02),
30725
- // Estimate 2% offline
30726
- noConsumption: 0
30727
- };
31085
+ const statusData = receivedData?.statusCounts || receivedData?.deviceStatus || null;
31086
+ if (statusData && typeof statusData === "object") {
31087
+ summary.byStatus = {
31088
+ normal: statusData.normal || 0,
31089
+ alert: statusData.alert || 0,
31090
+ failure: statusData.failure || 0,
31091
+ standby: statusData.standby || 0,
31092
+ offline: statusData.offline || 0,
31093
+ noConsumption: statusData.noConsumption || statusData.zeroConsumption || 0
31094
+ };
31095
+ } else {
31096
+ summary.byStatus = {
31097
+ normal: Math.floor(totalDevices * 0.75),
31098
+ // Estimate 75% normal (with consumption)
31099
+ alert: Math.floor(totalDevices * 0.06),
31100
+ // Estimate 6% alert
31101
+ failure: Math.floor(totalDevices * 0.02),
31102
+ // Estimate 2% failure
31103
+ standby: Math.floor(totalDevices * 0.02),
31104
+ // Estimate 2% standby
31105
+ offline: Math.floor(totalDevices * 0.03),
31106
+ // Estimate 3% offline
31107
+ noConsumption: Math.floor(totalDevices * 0.12)
31108
+ // Estimate 12% sem consumo
31109
+ };
31110
+ }
30728
31111
  const statusSum = Object.values(summary.byStatus).reduce((a, b) => a + b, 0);
30729
31112
  if (statusSum !== totalDevices && totalDevices > 0) {
30730
31113
  summary.byStatus.normal += totalDevices - statusSum;
@@ -30733,6 +31116,654 @@ var EnergySummaryTooltip = {
30733
31116
  }
30734
31117
  };
30735
31118
 
31119
+ // src/utils/InfoTooltip.ts
31120
+ var INFO_TOOLTIP_CSS = `
31121
+ /* ============================================
31122
+ Info Tooltip (RFC-0105)
31123
+ Premium draggable tooltip with actions
31124
+ ============================================ */
31125
+
31126
+ .myio-info-tooltip {
31127
+ position: fixed;
31128
+ z-index: 99999;
31129
+ pointer-events: none;
31130
+ opacity: 0;
31131
+ transition: opacity 0.25s ease, transform 0.25s ease;
31132
+ transform: translateY(5px);
31133
+ }
31134
+
31135
+ .myio-info-tooltip.visible {
31136
+ opacity: 1;
31137
+ pointer-events: auto;
31138
+ transform: translateY(0);
31139
+ }
31140
+
31141
+ .myio-info-tooltip.closing {
31142
+ opacity: 0;
31143
+ transform: translateY(8px);
31144
+ transition: opacity 0.4s ease, transform 0.4s ease;
31145
+ }
31146
+
31147
+ .myio-info-tooltip.pinned {
31148
+ box-shadow: 0 0 0 2px #047857, 0 10px 40px rgba(0, 0, 0, 0.2);
31149
+ border-radius: 12px;
31150
+ }
31151
+
31152
+ .myio-info-tooltip.dragging {
31153
+ transition: none !important;
31154
+ cursor: move;
31155
+ }
31156
+
31157
+ .myio-info-tooltip.maximized {
31158
+ top: 20px !important;
31159
+ left: 20px !important;
31160
+ right: 20px !important;
31161
+ bottom: 20px !important;
31162
+ width: auto !important;
31163
+ max-width: none !important;
31164
+ }
31165
+
31166
+ .myio-info-tooltip.maximized .myio-info-tooltip__panel {
31167
+ width: 100%;
31168
+ height: 100%;
31169
+ max-width: none;
31170
+ display: flex;
31171
+ flex-direction: column;
31172
+ }
31173
+
31174
+ .myio-info-tooltip.maximized .myio-info-tooltip__content {
31175
+ flex: 1;
31176
+ overflow-y: auto;
31177
+ }
31178
+
31179
+ .myio-info-tooltip__panel {
31180
+ background: #ffffff;
31181
+ border: 1px solid #e2e8f0;
31182
+ border-radius: 12px;
31183
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12), 0 2px 10px rgba(0, 0, 0, 0.08);
31184
+ min-width: 320px;
31185
+ max-width: 400px;
31186
+ font-size: 12px;
31187
+ color: #1e293b;
31188
+ overflow: hidden;
31189
+ font-family: Inter, system-ui, -apple-system, sans-serif;
31190
+ }
31191
+
31192
+ .myio-info-tooltip__header {
31193
+ display: flex;
31194
+ align-items: center;
31195
+ gap: 8px;
31196
+ padding: 12px 16px;
31197
+ background: linear-gradient(90deg, #f1f5f9 0%, #e2e8f0 100%);
31198
+ border-bottom: 1px solid #cbd5e1;
31199
+ cursor: move;
31200
+ user-select: none;
31201
+ }
31202
+
31203
+ .myio-info-tooltip__icon {
31204
+ font-size: 18px;
31205
+ }
31206
+
31207
+ .myio-info-tooltip__title {
31208
+ font-weight: 700;
31209
+ font-size: 14px;
31210
+ color: #475569;
31211
+ letter-spacing: 0.3px;
31212
+ flex: 1;
31213
+ }
31214
+
31215
+ .myio-info-tooltip__header-actions {
31216
+ display: flex;
31217
+ align-items: center;
31218
+ gap: 4px;
31219
+ }
31220
+
31221
+ .myio-info-tooltip__header-btn {
31222
+ width: 24px;
31223
+ height: 24px;
31224
+ border: none;
31225
+ background: rgba(255, 255, 255, 0.6);
31226
+ border-radius: 4px;
31227
+ cursor: pointer;
31228
+ display: flex;
31229
+ align-items: center;
31230
+ justify-content: center;
31231
+ transition: all 0.15s ease;
31232
+ color: #64748b;
31233
+ }
31234
+
31235
+ .myio-info-tooltip__header-btn:hover {
31236
+ background: rgba(255, 255, 255, 0.9);
31237
+ color: #1e293b;
31238
+ }
31239
+
31240
+ .myio-info-tooltip__header-btn.pinned {
31241
+ background: #047857;
31242
+ color: white;
31243
+ }
31244
+
31245
+ .myio-info-tooltip__header-btn.pinned:hover {
31246
+ background: #065f46;
31247
+ color: white;
31248
+ }
31249
+
31250
+ .myio-info-tooltip__header-btn svg {
31251
+ width: 14px;
31252
+ height: 14px;
31253
+ }
31254
+
31255
+ .myio-info-tooltip__content {
31256
+ padding: 16px;
31257
+ max-height: 500px;
31258
+ overflow-y: auto;
31259
+ }
31260
+
31261
+ /* Content styles */
31262
+ .myio-info-tooltip__section {
31263
+ margin-bottom: 14px;
31264
+ padding-bottom: 12px;
31265
+ border-bottom: 1px solid #f1f5f9;
31266
+ }
31267
+
31268
+ .myio-info-tooltip__section:last-child {
31269
+ margin-bottom: 0;
31270
+ padding-bottom: 0;
31271
+ border-bottom: none;
31272
+ }
31273
+
31274
+ .myio-info-tooltip__section-title {
31275
+ font-size: 11px;
31276
+ font-weight: 600;
31277
+ color: #64748b;
31278
+ text-transform: uppercase;
31279
+ letter-spacing: 0.8px;
31280
+ margin-bottom: 10px;
31281
+ display: flex;
31282
+ align-items: center;
31283
+ gap: 6px;
31284
+ }
31285
+
31286
+ .myio-info-tooltip__row {
31287
+ display: flex;
31288
+ justify-content: space-between;
31289
+ align-items: center;
31290
+ padding: 5px 0;
31291
+ gap: 12px;
31292
+ }
31293
+
31294
+ .myio-info-tooltip__label {
31295
+ color: #64748b;
31296
+ font-size: 12px;
31297
+ flex-shrink: 0;
31298
+ }
31299
+
31300
+ .myio-info-tooltip__value {
31301
+ color: #1e293b;
31302
+ font-weight: 600;
31303
+ text-align: right;
31304
+ }
31305
+
31306
+ .myio-info-tooltip__value--highlight {
31307
+ color: #10b981;
31308
+ font-weight: 700;
31309
+ font-size: 14px;
31310
+ }
31311
+
31312
+ .myio-info-tooltip__notice {
31313
+ display: flex;
31314
+ align-items: flex-start;
31315
+ gap: 10px;
31316
+ padding: 12px 14px;
31317
+ background: #f0fdf4;
31318
+ border: 1px solid #bbf7d0;
31319
+ border-radius: 8px;
31320
+ margin-top: 12px;
31321
+ }
31322
+
31323
+ .myio-info-tooltip__notice-icon {
31324
+ font-size: 14px;
31325
+ flex-shrink: 0;
31326
+ margin-top: 1px;
31327
+ }
31328
+
31329
+ .myio-info-tooltip__notice-text {
31330
+ font-size: 11px;
31331
+ color: #475569;
31332
+ line-height: 1.5;
31333
+ }
31334
+
31335
+ .myio-info-tooltip__notice-text strong {
31336
+ font-weight: 700;
31337
+ color: #334155;
31338
+ }
31339
+
31340
+ .myio-info-tooltip__category {
31341
+ display: flex;
31342
+ align-items: center;
31343
+ gap: 10px;
31344
+ padding: 8px 12px;
31345
+ background: #f8fafc;
31346
+ border-radius: 8px;
31347
+ margin-bottom: 6px;
31348
+ border-left: 3px solid #94a3b8;
31349
+ }
31350
+
31351
+ .myio-info-tooltip__category:last-child {
31352
+ margin-bottom: 0;
31353
+ }
31354
+
31355
+ .myio-info-tooltip__category--climatizacao {
31356
+ border-left-color: #00C896;
31357
+ background: #ecfdf5;
31358
+ }
31359
+
31360
+ .myio-info-tooltip__category--outros {
31361
+ border-left-color: #9C27B0;
31362
+ background: #fdf4ff;
31363
+ }
31364
+
31365
+ .myio-info-tooltip__category-icon {
31366
+ font-size: 14px;
31367
+ flex-shrink: 0;
31368
+ }
31369
+
31370
+ .myio-info-tooltip__category-info {
31371
+ flex: 1;
31372
+ }
31373
+
31374
+ .myio-info-tooltip__category-name {
31375
+ font-weight: 600;
31376
+ color: #334155;
31377
+ font-size: 12px;
31378
+ }
31379
+
31380
+ .myio-info-tooltip__category-desc {
31381
+ font-size: 10px;
31382
+ color: #64748b;
31383
+ margin-top: 2px;
31384
+ }
31385
+
31386
+ .myio-info-tooltip__category-value {
31387
+ font-weight: 700;
31388
+ color: #334155;
31389
+ font-size: 13px;
31390
+ }
31391
+ `;
31392
+ var cssInjected4 = false;
31393
+ function injectCSS4() {
31394
+ if (cssInjected4) return;
31395
+ if (typeof document === "undefined") return;
31396
+ const styleId = "myio-info-tooltip-styles";
31397
+ if (document.getElementById(styleId)) {
31398
+ cssInjected4 = true;
31399
+ return;
31400
+ }
31401
+ const style = document.createElement("style");
31402
+ style.id = styleId;
31403
+ style.textContent = INFO_TOOLTIP_CSS;
31404
+ document.head.appendChild(style);
31405
+ cssInjected4 = true;
31406
+ }
31407
+ var state = {
31408
+ hideTimer: null,
31409
+ isMouseOverTooltip: false,
31410
+ isMaximized: false,
31411
+ isDragging: false,
31412
+ dragOffset: { x: 0, y: 0 },
31413
+ savedPosition: null,
31414
+ pinnedCounter: 0
31415
+ };
31416
+ function generateHeaderHTML(icon, title) {
31417
+ return `
31418
+ <div class="myio-info-tooltip__header" data-drag-handle>
31419
+ <span class="myio-info-tooltip__icon">${icon}</span>
31420
+ <span class="myio-info-tooltip__title">${title}</span>
31421
+ <div class="myio-info-tooltip__header-actions">
31422
+ <button class="myio-info-tooltip__header-btn" data-action="pin" title="Fixar na tela">
31423
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
31424
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
31425
+ <line x1="12" y1="16" x2="12" y2="21"/>
31426
+ <line x1="8" y1="4" x2="16" y2="4"/>
31427
+ </svg>
31428
+ </button>
31429
+ <button class="myio-info-tooltip__header-btn" data-action="maximize" title="Maximizar">
31430
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
31431
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
31432
+ </svg>
31433
+ </button>
31434
+ <button class="myio-info-tooltip__header-btn" data-action="close" title="Fechar">
31435
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
31436
+ <path d="M18 6L6 18M6 6l12 12"/>
31437
+ </svg>
31438
+ </button>
31439
+ </div>
31440
+ </div>
31441
+ `;
31442
+ }
31443
+ function setupHoverListeners(container) {
31444
+ container.onmouseenter = () => {
31445
+ state.isMouseOverTooltip = true;
31446
+ if (state.hideTimer) {
31447
+ clearTimeout(state.hideTimer);
31448
+ state.hideTimer = null;
31449
+ }
31450
+ };
31451
+ container.onmouseleave = () => {
31452
+ state.isMouseOverTooltip = false;
31453
+ startDelayedHide();
31454
+ };
31455
+ }
31456
+ function setupButtonListeners(container) {
31457
+ const buttons = container.querySelectorAll("[data-action]");
31458
+ buttons.forEach((btn) => {
31459
+ btn.onclick = (e) => {
31460
+ e.stopPropagation();
31461
+ const action = btn.dataset.action;
31462
+ switch (action) {
31463
+ case "pin":
31464
+ createPinnedClone(container);
31465
+ break;
31466
+ case "maximize":
31467
+ toggleMaximize(container);
31468
+ break;
31469
+ case "close":
31470
+ InfoTooltip.close();
31471
+ break;
31472
+ }
31473
+ };
31474
+ });
31475
+ }
31476
+ function setupDragListeners(container) {
31477
+ const header = container.querySelector("[data-drag-handle]");
31478
+ if (!header) return;
31479
+ header.onmousedown = (e) => {
31480
+ if (e.target.closest("[data-action]")) return;
31481
+ if (state.isMaximized) return;
31482
+ state.isDragging = true;
31483
+ container.classList.add("dragging");
31484
+ const rect = container.getBoundingClientRect();
31485
+ state.dragOffset = {
31486
+ x: e.clientX - rect.left,
31487
+ y: e.clientY - rect.top
31488
+ };
31489
+ const onMouseMove = (e2) => {
31490
+ if (!state.isDragging) return;
31491
+ const newLeft = e2.clientX - state.dragOffset.x;
31492
+ const newTop = e2.clientY - state.dragOffset.y;
31493
+ const maxLeft = window.innerWidth - container.offsetWidth;
31494
+ const maxTop = window.innerHeight - container.offsetHeight;
31495
+ container.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
31496
+ container.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
31497
+ };
31498
+ const onMouseUp = () => {
31499
+ state.isDragging = false;
31500
+ container.classList.remove("dragging");
31501
+ document.removeEventListener("mousemove", onMouseMove);
31502
+ document.removeEventListener("mouseup", onMouseUp);
31503
+ };
31504
+ document.addEventListener("mousemove", onMouseMove);
31505
+ document.addEventListener("mouseup", onMouseUp);
31506
+ };
31507
+ }
31508
+ function createPinnedClone(container) {
31509
+ state.pinnedCounter++;
31510
+ const pinnedId = `myio-info-tooltip-pinned-${state.pinnedCounter}`;
31511
+ const clone = container.cloneNode(true);
31512
+ clone.id = pinnedId;
31513
+ clone.classList.add("pinned");
31514
+ clone.classList.remove("closing");
31515
+ const pinBtn = clone.querySelector('[data-action="pin"]');
31516
+ if (pinBtn) {
31517
+ pinBtn.classList.add("pinned");
31518
+ pinBtn.setAttribute("title", "Desafixar");
31519
+ pinBtn.innerHTML = `
31520
+ <svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1">
31521
+ <path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
31522
+ <line x1="12" y1="16" x2="12" y2="21"/>
31523
+ <line x1="8" y1="4" x2="16" y2="4"/>
31524
+ </svg>
31525
+ `;
31526
+ }
31527
+ document.body.appendChild(clone);
31528
+ setupPinnedCloneListeners(clone, pinnedId);
31529
+ InfoTooltip.hide();
31530
+ }
31531
+ function setupPinnedCloneListeners(clone, cloneId) {
31532
+ let isMaximized = false;
31533
+ let savedPosition = null;
31534
+ const pinBtn = clone.querySelector('[data-action="pin"]');
31535
+ if (pinBtn) {
31536
+ pinBtn.onclick = (e) => {
31537
+ e.stopPropagation();
31538
+ closePinnedClone(cloneId);
31539
+ };
31540
+ }
31541
+ const closeBtn = clone.querySelector('[data-action="close"]');
31542
+ if (closeBtn) {
31543
+ closeBtn.onclick = (e) => {
31544
+ e.stopPropagation();
31545
+ closePinnedClone(cloneId);
31546
+ };
31547
+ }
31548
+ const maxBtn = clone.querySelector('[data-action="maximize"]');
31549
+ if (maxBtn) {
31550
+ maxBtn.onclick = (e) => {
31551
+ e.stopPropagation();
31552
+ isMaximized = !isMaximized;
31553
+ if (isMaximized) {
31554
+ savedPosition = { left: clone.style.left, top: clone.style.top };
31555
+ 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>`;
31556
+ maxBtn.setAttribute("title", "Restaurar");
31557
+ } else {
31558
+ 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>`;
31559
+ maxBtn.setAttribute("title", "Maximizar");
31560
+ if (savedPosition) {
31561
+ clone.style.left = savedPosition.left;
31562
+ clone.style.top = savedPosition.top;
31563
+ }
31564
+ }
31565
+ clone.classList.toggle("maximized", isMaximized);
31566
+ };
31567
+ }
31568
+ const header = clone.querySelector("[data-drag-handle]");
31569
+ if (header) {
31570
+ let isDragging = false;
31571
+ let dragOffset = { x: 0, y: 0 };
31572
+ header.onmousedown = (e) => {
31573
+ if (e.target.closest("[data-action]")) return;
31574
+ if (isMaximized) return;
31575
+ isDragging = true;
31576
+ clone.classList.add("dragging");
31577
+ const rect = clone.getBoundingClientRect();
31578
+ dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
31579
+ const onMouseMove = (e2) => {
31580
+ if (!isDragging) return;
31581
+ const newLeft = e2.clientX - dragOffset.x;
31582
+ const newTop = e2.clientY - dragOffset.y;
31583
+ const maxLeft = window.innerWidth - clone.offsetWidth;
31584
+ const maxTop = window.innerHeight - clone.offsetHeight;
31585
+ clone.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
31586
+ clone.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
31587
+ };
31588
+ const onMouseUp = () => {
31589
+ isDragging = false;
31590
+ clone.classList.remove("dragging");
31591
+ document.removeEventListener("mousemove", onMouseMove);
31592
+ document.removeEventListener("mouseup", onMouseUp);
31593
+ };
31594
+ document.addEventListener("mousemove", onMouseMove);
31595
+ document.addEventListener("mouseup", onMouseUp);
31596
+ };
31597
+ }
31598
+ }
31599
+ function closePinnedClone(cloneId) {
31600
+ const clone = document.getElementById(cloneId);
31601
+ if (clone) {
31602
+ clone.classList.add("closing");
31603
+ setTimeout(() => clone.remove(), 400);
31604
+ }
31605
+ }
31606
+ function toggleMaximize(container) {
31607
+ state.isMaximized = !state.isMaximized;
31608
+ if (state.isMaximized) {
31609
+ state.savedPosition = {
31610
+ left: container.style.left,
31611
+ top: container.style.top
31612
+ };
31613
+ }
31614
+ container.classList.toggle("maximized", state.isMaximized);
31615
+ const maxBtn = container.querySelector('[data-action="maximize"]');
31616
+ if (maxBtn) {
31617
+ if (state.isMaximized) {
31618
+ 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>`;
31619
+ maxBtn.setAttribute("title", "Restaurar");
31620
+ } else {
31621
+ 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>`;
31622
+ maxBtn.setAttribute("title", "Maximizar");
31623
+ if (state.savedPosition) {
31624
+ container.style.left = state.savedPosition.left;
31625
+ container.style.top = state.savedPosition.top;
31626
+ }
31627
+ }
31628
+ }
31629
+ }
31630
+ function startDelayedHide() {
31631
+ if (state.isMouseOverTooltip) return;
31632
+ if (state.hideTimer) {
31633
+ clearTimeout(state.hideTimer);
31634
+ }
31635
+ state.hideTimer = setTimeout(() => {
31636
+ hideWithAnimation();
31637
+ }, 1500);
31638
+ }
31639
+ function hideWithAnimation() {
31640
+ const container = document.getElementById("myio-info-tooltip");
31641
+ if (container && container.classList.contains("visible")) {
31642
+ container.classList.add("closing");
31643
+ setTimeout(() => {
31644
+ container.classList.remove("visible", "closing");
31645
+ }, 400);
31646
+ }
31647
+ }
31648
+ function positionTooltip(container, triggerElement) {
31649
+ const rect = triggerElement.getBoundingClientRect();
31650
+ let left = rect.left;
31651
+ let top = rect.bottom + 8;
31652
+ const tooltipWidth = 380;
31653
+ if (left + tooltipWidth > window.innerWidth - 20) {
31654
+ left = window.innerWidth - tooltipWidth - 20;
31655
+ }
31656
+ if (left < 10) left = 10;
31657
+ if (top + 400 > window.innerHeight) {
31658
+ top = rect.top - 8 - 400;
31659
+ if (top < 10) top = 10;
31660
+ }
31661
+ container.style.left = left + "px";
31662
+ container.style.top = top + "px";
31663
+ }
31664
+ var InfoTooltip = {
31665
+ containerId: "myio-info-tooltip",
31666
+ /**
31667
+ * Get or create container
31668
+ */
31669
+ getContainer() {
31670
+ injectCSS4();
31671
+ let container = document.getElementById(this.containerId);
31672
+ if (!container) {
31673
+ container = document.createElement("div");
31674
+ container.id = this.containerId;
31675
+ container.className = "myio-info-tooltip";
31676
+ document.body.appendChild(container);
31677
+ }
31678
+ return container;
31679
+ },
31680
+ /**
31681
+ * Show tooltip
31682
+ */
31683
+ show(triggerElement, options) {
31684
+ if (state.hideTimer) {
31685
+ clearTimeout(state.hideTimer);
31686
+ state.hideTimer = null;
31687
+ }
31688
+ const container = this.getContainer();
31689
+ container.classList.remove("closing");
31690
+ container.innerHTML = `
31691
+ <div class="myio-info-tooltip__panel">
31692
+ ${generateHeaderHTML(options.icon, options.title)}
31693
+ <div class="myio-info-tooltip__content">
31694
+ ${options.content}
31695
+ </div>
31696
+ </div>
31697
+ `;
31698
+ positionTooltip(container, triggerElement);
31699
+ container.classList.add("visible");
31700
+ setupHoverListeners(container);
31701
+ setupButtonListeners(container);
31702
+ setupDragListeners(container);
31703
+ },
31704
+ /**
31705
+ * Start delayed hide
31706
+ */
31707
+ startDelayedHide() {
31708
+ startDelayedHide();
31709
+ },
31710
+ /**
31711
+ * Hide immediately
31712
+ */
31713
+ hide() {
31714
+ if (state.hideTimer) {
31715
+ clearTimeout(state.hideTimer);
31716
+ state.hideTimer = null;
31717
+ }
31718
+ state.isMouseOverTooltip = false;
31719
+ const container = document.getElementById(this.containerId);
31720
+ if (container) {
31721
+ container.classList.remove("visible", "closing");
31722
+ }
31723
+ },
31724
+ /**
31725
+ * Close and reset all states
31726
+ */
31727
+ close() {
31728
+ state.isMaximized = false;
31729
+ state.isDragging = false;
31730
+ state.savedPosition = null;
31731
+ if (state.hideTimer) {
31732
+ clearTimeout(state.hideTimer);
31733
+ state.hideTimer = null;
31734
+ }
31735
+ state.isMouseOverTooltip = false;
31736
+ const container = document.getElementById(this.containerId);
31737
+ if (container) {
31738
+ container.classList.remove("visible", "pinned", "maximized", "dragging", "closing");
31739
+ }
31740
+ },
31741
+ /**
31742
+ * Attach tooltip to trigger element with hover behavior
31743
+ */
31744
+ attach(triggerElement, getOptions) {
31745
+ const self = this;
31746
+ const handleMouseEnter = () => {
31747
+ if (state.hideTimer) {
31748
+ clearTimeout(state.hideTimer);
31749
+ state.hideTimer = null;
31750
+ }
31751
+ const options = getOptions();
31752
+ self.show(triggerElement, options);
31753
+ };
31754
+ const handleMouseLeave = () => {
31755
+ startDelayedHide();
31756
+ };
31757
+ triggerElement.addEventListener("mouseenter", handleMouseEnter);
31758
+ triggerElement.addEventListener("mouseleave", handleMouseLeave);
31759
+ return () => {
31760
+ triggerElement.removeEventListener("mouseenter", handleMouseEnter);
31761
+ triggerElement.removeEventListener("mouseleave", handleMouseLeave);
31762
+ self.hide();
31763
+ };
31764
+ }
31765
+ };
31766
+
30736
31767
  // src/components/ModalHeader/index.ts
30737
31768
  var DEFAULT_BG_COLOR = "#3e1a7d";
30738
31769
  var DEFAULT_TEXT_COLOR = "white";
@@ -34511,6 +35542,7 @@ export {
34511
35542
  IMPORTANCE_COLORS,
34512
35543
  IMPORTANCE_LABELS,
34513
35544
  IMPORTANCE_LABELS_EN,
35545
+ InfoTooltip,
34514
35546
  MyIOChartModal,
34515
35547
  MyIODraggableCard,
34516
35548
  MyIOSelectionStore,