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.cjs +1419 -386
- package/dist/index.d.cts +58 -1
- package/dist/index.js +1418 -386
- package/dist/myio-js-library.umd.js +1418 -386
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8875,8 +8875,8 @@ function getStatusDotClass(deviceStatus) {
|
|
|
8875
8875
|
return "dot--offline";
|
|
8876
8876
|
}
|
|
8877
8877
|
}
|
|
8878
|
-
function buildDOM(
|
|
8879
|
-
const { entityObject, i18n, enableSelection, enableDragDrop } =
|
|
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,
|
|
9206
|
-
const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } =
|
|
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 (
|
|
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,
|
|
9307
|
-
const { entityObject } =
|
|
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 (
|
|
9364
|
-
|
|
9365
|
-
paint(root,
|
|
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,
|
|
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,
|
|
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 (
|
|
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
|
|
9491
|
-
const root = buildDOM(
|
|
9492
|
-
|
|
9490
|
+
const state2 = normalizeParams(params);
|
|
9491
|
+
const root = buildDOM(state2);
|
|
9492
|
+
state2.isSelected = params.isSelected || false;
|
|
9493
9493
|
containerEl.appendChild(root);
|
|
9494
|
-
bindEvents(root,
|
|
9495
|
-
paint(root,
|
|
9494
|
+
bindEvents(root, state2, state2.callbacks);
|
|
9495
|
+
paint(root, state2);
|
|
9496
9496
|
return {
|
|
9497
9497
|
update(next) {
|
|
9498
9498
|
if (next) {
|
|
9499
|
-
Object.assign(
|
|
9500
|
-
paint(root,
|
|
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
|
-
*
|
|
20983
|
+
* Fetch child customer relations (level 1) from a parent customer
|
|
20984
|
+
* Returns array of child customer IDs
|
|
20984
20985
|
*/
|
|
20985
|
-
async
|
|
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
|
-
|
|
21031
|
+
console.warn(`[PowerLimitsPersister] Failed to save to customer ${customerId}:`, response.status);
|
|
21032
|
+
return false;
|
|
21002
21033
|
}
|
|
21003
|
-
|
|
21004
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22458
|
+
const annotationAny = annotation;
|
|
22459
|
+
if (annotationAny[key] !== value) {
|
|
22268
22460
|
changeRecord[key] = {
|
|
22269
|
-
from:
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
22869
|
-
|
|
22870
|
-
|
|
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 (
|
|
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
|
|
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)
|
|
27553
|
-
if (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,
|
|
27926
|
+
renderModal(modalContainer, state2, modalId);
|
|
27558
27927
|
try {
|
|
27559
|
-
|
|
27560
|
-
|
|
27561
|
-
|
|
27562
|
-
renderModal(modalContainer,
|
|
27563
|
-
drawChart(modalId,
|
|
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
|
-
|
|
27567
|
-
renderModal(modalContainer,
|
|
27935
|
+
state2.isLoading = false;
|
|
27936
|
+
renderModal(modalContainer, state2, modalId, error);
|
|
27568
27937
|
}
|
|
27569
|
-
await setupEventListeners(modalContainer,
|
|
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
|
-
|
|
27577
|
-
|
|
27578
|
-
if (granularity)
|
|
27579
|
-
|
|
27580
|
-
renderModal(modalContainer,
|
|
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
|
-
|
|
27583
|
-
|
|
27584
|
-
|
|
27585
|
-
renderModal(modalContainer,
|
|
27586
|
-
drawChart(modalId,
|
|
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
|
-
|
|
27590
|
-
renderModal(modalContainer,
|
|
27958
|
+
state2.isLoading = false;
|
|
27959
|
+
renderModal(modalContainer, state2, modalId, error);
|
|
27591
27960
|
}
|
|
27592
27961
|
}
|
|
27593
27962
|
};
|
|
27594
27963
|
}
|
|
27595
|
-
function renderModal(container,
|
|
27596
|
-
const colors = getThemeColors(
|
|
27597
|
-
const startDateStr = new Date(
|
|
27598
|
-
const endDateStr = new Date(
|
|
27599
|
-
const statusText =
|
|
27600
|
-
const statusColor =
|
|
27601
|
-
const rangeText =
|
|
27602
|
-
const startDateInput = new Date(
|
|
27603
|
-
const endDateInput = new Date(
|
|
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 ${
|
|
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
|
-
">${
|
|
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: ${
|
|
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" ${
|
|
27674
|
-
<option value="day" ${
|
|
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(
|
|
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='${
|
|
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
|
-
${
|
|
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: ${
|
|
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: ${
|
|
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
|
-
" ${
|
|
27746
|
-
${
|
|
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: ${
|
|
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
|
-
${
|
|
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: ${
|
|
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
|
-
${
|
|
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: ${
|
|
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
|
-
${
|
|
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;">${
|
|
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: ${
|
|
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: ${
|
|
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
|
-
${
|
|
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>` :
|
|
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: ${
|
|
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
|
-
" ${
|
|
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,
|
|
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 ||
|
|
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(
|
|
27890
|
-
const filteredData = filterByDayPeriods(
|
|
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 (
|
|
28270
|
+
if (state2.granularity === "hour") {
|
|
27902
28271
|
const interpolated = interpolateTemperature(filteredData, {
|
|
27903
28272
|
intervalMinutes: 30,
|
|
27904
|
-
startTs:
|
|
27905
|
-
endTs:
|
|
27906
|
-
clampRange:
|
|
28273
|
+
startTs: state2.startTs,
|
|
28274
|
+
endTs: state2.endTs,
|
|
28275
|
+
clampRange: state2.clampRange
|
|
27907
28276
|
});
|
|
27908
|
-
const filteredInterpolated = filterByDayPeriods(interpolated,
|
|
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,
|
|
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 =
|
|
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 =
|
|
27939
|
-
const thresholdMax =
|
|
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 (
|
|
27972
|
-
const rangeMinY = height - paddingBottom - (
|
|
27973
|
-
const rangeMaxY = height - paddingBottom - (
|
|
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 (
|
|
28016
|
-
label = date.toLocaleTimeString(
|
|
28384
|
+
if (state2.granularity === "hour") {
|
|
28385
|
+
label = date.toLocaleTimeString(state2.locale, { hour: "2-digit", minute: "2-digit" });
|
|
28017
28386
|
} else {
|
|
28018
|
-
label = date.toLocaleDateString(
|
|
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,
|
|
28405
|
+
setupChartTooltip(canvas, chartContainer, chartData, state2, colors);
|
|
28037
28406
|
}
|
|
28038
|
-
function setupChartTooltip(canvas, container, chartData,
|
|
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: ${
|
|
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 (
|
|
28083
|
-
dateStr = date.toLocaleDateString(
|
|
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(
|
|
28456
|
+
}) + " " + date.toLocaleTimeString(state2.locale, {
|
|
28088
28457
|
hour: "2-digit",
|
|
28089
28458
|
minute: "2-digit"
|
|
28090
28459
|
});
|
|
28091
28460
|
} else {
|
|
28092
|
-
dateStr = date.toLocaleDateString(
|
|
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,
|
|
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 && !
|
|
28510
|
+
if (dateRangeInput && !state2.dateRangePicker) {
|
|
28142
28511
|
try {
|
|
28143
|
-
|
|
28144
|
-
presetStart: new Date(
|
|
28145
|
-
presetEnd: new Date(
|
|
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:
|
|
28518
|
+
locale: state2.locale,
|
|
28150
28519
|
parentEl: container.querySelector(".myio-temp-modal-content"),
|
|
28151
28520
|
onApply: (result) => {
|
|
28152
|
-
|
|
28153
|
-
|
|
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
|
-
|
|
28163
|
-
localStorage.setItem("myio-temp-modal-theme",
|
|
28164
|
-
|
|
28165
|
-
renderModal(container,
|
|
28166
|
-
if (
|
|
28167
|
-
await setupEventListeners(container,
|
|
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
|
-
|
|
28172
|
-
renderModal(container,
|
|
28173
|
-
if (
|
|
28174
|
-
await setupEventListeners(container,
|
|
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
|
-
|
|
28562
|
+
state2.selectedPeriods = checked;
|
|
28194
28563
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
28195
28564
|
if (btnLabel) {
|
|
28196
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
28565
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
28197
28566
|
}
|
|
28198
|
-
if (
|
|
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
|
-
|
|
28574
|
+
state2.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
|
|
28206
28575
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
28207
28576
|
if (btnLabel) {
|
|
28208
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
28577
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
28209
28578
|
}
|
|
28210
|
-
if (
|
|
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
|
-
|
|
28585
|
+
state2.selectedPeriods = [];
|
|
28217
28586
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
28218
28587
|
if (btnLabel) {
|
|
28219
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
28588
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
28220
28589
|
}
|
|
28221
|
-
if (
|
|
28590
|
+
if (state2.data.length > 0) drawChart(modalId, state2);
|
|
28222
28591
|
});
|
|
28223
28592
|
document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
|
|
28224
|
-
|
|
28225
|
-
localStorage.setItem("myio-temp-modal-granularity",
|
|
28226
|
-
if (
|
|
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 (
|
|
28598
|
+
if (state2.startTs >= state2.endTs) {
|
|
28230
28599
|
alert("Por favor, selecione um per\xEDodo v\xE1lido");
|
|
28231
28600
|
return;
|
|
28232
28601
|
}
|
|
28233
|
-
|
|
28234
|
-
|
|
28235
|
-
renderModal(container,
|
|
28602
|
+
state2.isLoading = true;
|
|
28603
|
+
state2.dateRangePicker = null;
|
|
28604
|
+
renderModal(container, state2, modalId);
|
|
28236
28605
|
try {
|
|
28237
|
-
|
|
28238
|
-
|
|
28239
|
-
|
|
28240
|
-
renderModal(container,
|
|
28241
|
-
drawChart(modalId,
|
|
28242
|
-
await setupEventListeners(container,
|
|
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
|
-
|
|
28246
|
-
renderModal(container,
|
|
28247
|
-
await setupEventListeners(container,
|
|
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 (
|
|
28252
|
-
const startDateStr = new Date(
|
|
28253
|
-
const endDateStr = new Date(
|
|
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
|
-
|
|
28256
|
-
|
|
28257
|
-
|
|
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
|
|
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)
|
|
28290
|
-
if (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,
|
|
28295
|
-
await fetchAllDevicesData(
|
|
28296
|
-
renderModal2(modalContainer,
|
|
28297
|
-
drawComparisonChart(modalId,
|
|
28298
|
-
await setupEventListeners2(modalContainer,
|
|
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
|
-
|
|
28306
|
-
|
|
28307
|
-
if (granularity)
|
|
28308
|
-
|
|
28309
|
-
renderModal2(modalContainer,
|
|
28310
|
-
await fetchAllDevicesData(
|
|
28311
|
-
renderModal2(modalContainer,
|
|
28312
|
-
drawComparisonChart(modalId,
|
|
28313
|
-
setupEventListeners2(modalContainer,
|
|
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(
|
|
28318
|
-
|
|
28319
|
-
|
|
28686
|
+
async function fetchAllDevicesData(state2) {
|
|
28687
|
+
state2.isLoading = true;
|
|
28688
|
+
state2.deviceData = [];
|
|
28320
28689
|
try {
|
|
28321
28690
|
const results = await Promise.all(
|
|
28322
|
-
|
|
28691
|
+
state2.devices.map(async (device, index) => {
|
|
28323
28692
|
const deviceId = device.tbId || device.id;
|
|
28324
28693
|
try {
|
|
28325
|
-
const data = await fetchTemperatureData(
|
|
28326
|
-
const stats = calculateStats(data,
|
|
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
|
-
|
|
28713
|
+
state2.deviceData = results;
|
|
28345
28714
|
} catch (error) {
|
|
28346
28715
|
console.error("[TemperatureComparisonModal] Error fetching data:", error);
|
|
28347
28716
|
}
|
|
28348
|
-
|
|
28717
|
+
state2.isLoading = false;
|
|
28349
28718
|
}
|
|
28350
|
-
function renderModal2(container,
|
|
28351
|
-
const colors = getThemeColors(
|
|
28352
|
-
const startDateStr = new Date(
|
|
28353
|
-
const endDateStr = new Date(
|
|
28354
|
-
const startDateInput = new Date(
|
|
28355
|
-
const endDateInput = new Date(
|
|
28356
|
-
const legendHTML =
|
|
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: ${
|
|
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 =
|
|
28736
|
+
const statsHTML = state2.deviceData.map((dd) => `
|
|
28368
28737
|
<div style="
|
|
28369
|
-
padding: 12px; background: ${
|
|
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 - ${
|
|
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
|
-
">${
|
|
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: ${
|
|
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" ${
|
|
28465
|
-
<option value="day" ${
|
|
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(
|
|
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='${
|
|
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
|
-
${
|
|
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: ${
|
|
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: ${
|
|
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
|
-
" ${
|
|
28537
|
-
${
|
|
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: ${
|
|
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
|
-
${
|
|
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 ${
|
|
28560
|
-
</div>` :
|
|
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: ${
|
|
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
|
-
" ${
|
|
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,
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
29006
|
+
state2.deviceData.forEach((dd) => {
|
|
28638
29007
|
if (dd.data.length === 0) return;
|
|
28639
|
-
const filteredData = filterByDayPeriods(dd.data,
|
|
29008
|
+
const filteredData = filterByDayPeriods(dd.data, state2.selectedPeriods);
|
|
28640
29009
|
if (filteredData.length === 0) return;
|
|
28641
29010
|
let points;
|
|
28642
|
-
if (
|
|
29011
|
+
if (state2.granularity === "hour") {
|
|
28643
29012
|
const interpolated = interpolateTemperature(filteredData, {
|
|
28644
29013
|
intervalMinutes: 30,
|
|
28645
|
-
startTs:
|
|
28646
|
-
endTs:
|
|
28647
|
-
clampRange:
|
|
29014
|
+
startTs: state2.startTs,
|
|
29015
|
+
endTs: state2.endTs,
|
|
29016
|
+
clampRange: state2.clampRange
|
|
28648
29017
|
});
|
|
28649
|
-
const filteredInterpolated = filterByDayPeriods(interpolated,
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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 &&
|
|
29078
|
+
if (rangeMap.size === 0 && state2.temperatureMin !== null && state2.temperatureMax !== null) {
|
|
28710
29079
|
rangeMap.set("global", {
|
|
28711
|
-
min:
|
|
28712
|
-
max:
|
|
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 (
|
|
28834
|
-
label = date.toLocaleTimeString(
|
|
29202
|
+
if (state2.granularity === "hour") {
|
|
29203
|
+
label = date.toLocaleTimeString(state2.locale, { hour: "2-digit", minute: "2-digit" });
|
|
28835
29204
|
} else {
|
|
28836
|
-
label = date.toLocaleDateString(
|
|
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,
|
|
29224
|
+
setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state2, colors);
|
|
28856
29225
|
}
|
|
28857
|
-
function setupComparisonChartTooltip(canvas, container, chartData,
|
|
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: ${
|
|
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 (
|
|
28902
|
-
dateStr = date.toLocaleDateString(
|
|
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(
|
|
29275
|
+
}) + " " + date.toLocaleTimeString(state2.locale, {
|
|
28907
29276
|
hour: "2-digit",
|
|
28908
29277
|
minute: "2-digit"
|
|
28909
29278
|
});
|
|
28910
29279
|
} else {
|
|
28911
|
-
dateStr = date.toLocaleDateString(
|
|
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,
|
|
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 && !
|
|
29333
|
+
if (dateRangeInput && !state2.dateRangePicker) {
|
|
28965
29334
|
try {
|
|
28966
|
-
|
|
28967
|
-
presetStart: new Date(
|
|
28968
|
-
presetEnd: new Date(
|
|
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:
|
|
29341
|
+
locale: state2.locale,
|
|
28973
29342
|
parentEl: container.querySelector(".myio-temp-comparison-content"),
|
|
28974
29343
|
onApply: (result) => {
|
|
28975
|
-
|
|
28976
|
-
|
|
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
|
-
|
|
28986
|
-
localStorage.setItem("myio-temp-comparison-theme",
|
|
28987
|
-
|
|
28988
|
-
renderModal2(container,
|
|
28989
|
-
if (
|
|
28990
|
-
drawComparisonChart(modalId,
|
|
28991
|
-
}
|
|
28992
|
-
await setupEventListeners2(container,
|
|
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
|
-
|
|
28997
|
-
renderModal2(container,
|
|
28998
|
-
if (
|
|
28999
|
-
drawComparisonChart(modalId,
|
|
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,
|
|
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
|
-
|
|
29389
|
+
state2.selectedPeriods = checked;
|
|
29021
29390
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
29022
29391
|
if (btnLabel) {
|
|
29023
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
29392
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
29024
29393
|
}
|
|
29025
|
-
if (
|
|
29026
|
-
drawComparisonChart(modalId,
|
|
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
|
-
|
|
29403
|
+
state2.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
|
|
29035
29404
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
29036
29405
|
if (btnLabel) {
|
|
29037
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
29406
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
29038
29407
|
}
|
|
29039
|
-
if (
|
|
29040
|
-
drawComparisonChart(modalId,
|
|
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
|
-
|
|
29416
|
+
state2.selectedPeriods = [];
|
|
29048
29417
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
29049
29418
|
if (btnLabel) {
|
|
29050
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
29419
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
29051
29420
|
}
|
|
29052
|
-
if (
|
|
29053
|
-
drawComparisonChart(modalId,
|
|
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
|
-
|
|
29058
|
-
localStorage.setItem("myio-temp-comparison-granularity",
|
|
29059
|
-
if (
|
|
29060
|
-
drawComparisonChart(modalId,
|
|
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 (
|
|
29433
|
+
if (state2.startTs >= state2.endTs) {
|
|
29065
29434
|
alert("Por favor, selecione um per\xEDodo v\xE1lido");
|
|
29066
29435
|
return;
|
|
29067
29436
|
}
|
|
29068
|
-
|
|
29069
|
-
|
|
29070
|
-
renderModal2(container,
|
|
29071
|
-
await fetchAllDevicesData(
|
|
29072
|
-
renderModal2(container,
|
|
29073
|
-
drawComparisonChart(modalId,
|
|
29074
|
-
await setupEventListeners2(container,
|
|
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 (
|
|
29078
|
-
exportComparisonCSV(
|
|
29446
|
+
if (state2.deviceData.every((dd) => dd.data.length === 0)) return;
|
|
29447
|
+
exportComparisonCSV(state2);
|
|
29079
29448
|
});
|
|
29080
29449
|
}
|
|
29081
|
-
function exportComparisonCSV(
|
|
29082
|
-
const startDateStr = new Date(
|
|
29083
|
-
const endDateStr = new Date(
|
|
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: ${
|
|
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
|
-
|
|
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
|
-
|
|
29471
|
+
state2.deviceData.forEach((dd) => {
|
|
29103
29472
|
dd.data.forEach((item) => {
|
|
29104
|
-
const date = new Date(item.ts).toLocaleString(
|
|
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,
|
|
29213
|
-
const colors = getColors(
|
|
29214
|
-
const minValue =
|
|
29215
|
-
const maxValue =
|
|
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
|
-
${
|
|
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
|
-
${
|
|
29487
|
-
<div class="message message-error">${
|
|
29855
|
+
${state2.error ? `
|
|
29856
|
+
<div class="message message-error">${state2.error}</div>
|
|
29488
29857
|
` : ""}
|
|
29489
29858
|
|
|
29490
|
-
${
|
|
29491
|
-
<div class="message message-success">${
|
|
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">${
|
|
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
|
-
${!
|
|
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" ${
|
|
29544
|
-
${
|
|
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
|
-
|
|
29582
|
-
renderModal3(container,
|
|
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
|
-
|
|
29587
|
-
renderModal3(container,
|
|
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
|
-
|
|
29592
|
-
renderModal3(container,
|
|
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
|
|
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
|
-
|
|
29621
|
-
|
|
29622
|
-
|
|
29623
|
-
renderModal3(container,
|
|
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(
|
|
29626
|
-
|
|
29627
|
-
|
|
29628
|
-
|
|
29629
|
-
|
|
29630
|
-
renderModal3(container,
|
|
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
|
-
|
|
29637
|
-
|
|
29638
|
-
renderModal3(container,
|
|
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,
|
|
29642
|
-
fetchCustomerAttributes(
|
|
29643
|
-
|
|
29644
|
-
|
|
29645
|
-
|
|
29646
|
-
renderModal3(container,
|
|
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
|
-
|
|
29649
|
-
|
|
29650
|
-
renderModal3(container,
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 (!
|
|
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:
|
|
30607
|
-
consumption:
|
|
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:
|
|
30615
|
-
consumption:
|
|
30616
|
-
percentage:
|
|
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 ||
|
|
30628
|
-
consumption:
|
|
30629
|
-
percentage:
|
|
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 ||
|
|
30681
|
-
consumption:
|
|
30682
|
-
percentage:
|
|
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 ||
|
|
30689
|
-
consumption:
|
|
30690
|
-
percentage:
|
|
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 ||
|
|
30697
|
-
consumption:
|
|
30698
|
-
percentage:
|
|
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 =
|
|
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:
|
|
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 =
|
|
31083
|
+
summary.totalConsumption = state2.grandTotal || entrada.consumption;
|
|
30714
31084
|
const totalDevices = summary.totalDevices;
|
|
30715
|
-
|
|
30716
|
-
|
|
30717
|
-
|
|
30718
|
-
|
|
30719
|
-
|
|
30720
|
-
|
|
30721
|
-
|
|
30722
|
-
|
|
30723
|
-
|
|
30724
|
-
|
|
30725
|
-
|
|
30726
|
-
|
|
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,
|