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.cjs
CHANGED
|
@@ -592,6 +592,7 @@ __export(index_exports, {
|
|
|
592
592
|
IMPORTANCE_COLORS: () => IMPORTANCE_COLORS,
|
|
593
593
|
IMPORTANCE_LABELS: () => IMPORTANCE_LABELS,
|
|
594
594
|
IMPORTANCE_LABELS_EN: () => IMPORTANCE_LABELS_EN,
|
|
595
|
+
InfoTooltip: () => InfoTooltip,
|
|
595
596
|
MyIOChartModal: () => MyIOChartModal,
|
|
596
597
|
MyIODraggableCard: () => MyIODraggableCard,
|
|
597
598
|
MyIOSelectionStore: () => MyIOSelectionStore,
|
|
@@ -9039,8 +9040,8 @@ function getStatusDotClass(deviceStatus) {
|
|
|
9039
9040
|
return "dot--offline";
|
|
9040
9041
|
}
|
|
9041
9042
|
}
|
|
9042
|
-
function buildDOM(
|
|
9043
|
-
const { entityObject, i18n, enableSelection, enableDragDrop } =
|
|
9043
|
+
function buildDOM(state2) {
|
|
9044
|
+
const { entityObject, i18n, enableSelection, enableDragDrop } = state2;
|
|
9044
9045
|
const root = document.createElement("div");
|
|
9045
9046
|
root.className = "myio-ho-card";
|
|
9046
9047
|
root.setAttribute("role", "group");
|
|
@@ -9366,8 +9367,8 @@ function verifyOfflineStatus(entityObject, delayTimeInMins = 15, LogHelper2) {
|
|
|
9366
9367
|
}
|
|
9367
9368
|
return isOffline;
|
|
9368
9369
|
}
|
|
9369
|
-
function paint(root,
|
|
9370
|
-
const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } =
|
|
9370
|
+
function paint(root, state2) {
|
|
9371
|
+
const { entityObject, i18n, delayTimeConnectionInMins, isSelected, LogHelper: LogHelper2, activeTooltipDebug } = state2;
|
|
9371
9372
|
let statusDecisionSource = "unknown";
|
|
9372
9373
|
if (entityObject.connectionStatus) {
|
|
9373
9374
|
if (entityObject.connectionStatus === "offline") {
|
|
@@ -9419,7 +9420,7 @@ function paint(root, state) {
|
|
|
9419
9420
|
numSpan.textContent = primaryValue;
|
|
9420
9421
|
const barContainer = root.querySelector(".bar");
|
|
9421
9422
|
const effContainer = root.querySelector(".myio-ho-card__eff");
|
|
9422
|
-
if (
|
|
9423
|
+
if (state2.enableSelection) {
|
|
9423
9424
|
const checkbox = root.querySelector('.myio-ho-card__select input[type="checkbox"]');
|
|
9424
9425
|
if (checkbox) {
|
|
9425
9426
|
checkbox.checked = !!isSelected;
|
|
@@ -9467,8 +9468,8 @@ function paint(root, state) {
|
|
|
9467
9468
|
statusDot.className = `status-dot ${dotClass}`;
|
|
9468
9469
|
}
|
|
9469
9470
|
}
|
|
9470
|
-
function bindEvents(root,
|
|
9471
|
-
const { entityObject } =
|
|
9471
|
+
function bindEvents(root, state2, callbacks) {
|
|
9472
|
+
const { entityObject } = state2;
|
|
9472
9473
|
const kebabBtn = root.querySelector(".myio-ho-card__kebab");
|
|
9473
9474
|
const menu = root.querySelector(".myio-ho-card__menu");
|
|
9474
9475
|
function toggleMenu() {
|
|
@@ -9524,9 +9525,9 @@ function bindEvents(root, state, callbacks) {
|
|
|
9524
9525
|
const onSelectionChange = () => {
|
|
9525
9526
|
const selectedIds = MyIOSelectionStore2.getSelectedIds();
|
|
9526
9527
|
const isSelected = selectedIds.includes(entityObject.entityId);
|
|
9527
|
-
if (
|
|
9528
|
-
|
|
9529
|
-
paint(root,
|
|
9528
|
+
if (state2.isSelected !== isSelected) {
|
|
9529
|
+
state2.isSelected = isSelected;
|
|
9530
|
+
paint(root, state2);
|
|
9530
9531
|
}
|
|
9531
9532
|
};
|
|
9532
9533
|
MyIOSelectionStore2.on("selection:change", onSelectionChange);
|
|
@@ -9539,7 +9540,7 @@ function bindEvents(root, state, callbacks) {
|
|
|
9539
9540
|
clearTimeout(TempRangeTooltip._hideTimer);
|
|
9540
9541
|
TempRangeTooltip._hideTimer = null;
|
|
9541
9542
|
}
|
|
9542
|
-
TempRangeTooltip.show(root,
|
|
9543
|
+
TempRangeTooltip.show(root, state2.entityObject, e);
|
|
9543
9544
|
};
|
|
9544
9545
|
const hideTooltip = () => {
|
|
9545
9546
|
TempRangeTooltip._startDelayedHide();
|
|
@@ -9557,7 +9558,7 @@ function bindEvents(root, state, callbacks) {
|
|
|
9557
9558
|
clearTimeout(EnergyRangeTooltip._hideTimer);
|
|
9558
9559
|
EnergyRangeTooltip._hideTimer = null;
|
|
9559
9560
|
}
|
|
9560
|
-
EnergyRangeTooltip.show(root,
|
|
9561
|
+
EnergyRangeTooltip.show(root, state2.entityObject, e);
|
|
9561
9562
|
};
|
|
9562
9563
|
const hideEnergyTooltip = () => {
|
|
9563
9564
|
EnergyRangeTooltip._startDelayedHide();
|
|
@@ -9609,7 +9610,7 @@ function bindEvents(root, state, callbacks) {
|
|
|
9609
9610
|
}
|
|
9610
9611
|
});
|
|
9611
9612
|
}
|
|
9612
|
-
if (
|
|
9613
|
+
if (state2.enableDragDrop) {
|
|
9613
9614
|
root.addEventListener("dragstart", (e) => {
|
|
9614
9615
|
root.classList.add("is-dragging");
|
|
9615
9616
|
e.dataTransfer.setData("text/plain", entityObject.entityId);
|
|
@@ -9651,17 +9652,17 @@ function renderCardComponentHeadOffice(containerEl, params) {
|
|
|
9651
9652
|
throw new Error("renderCardComponentHeadOffice: containerEl is required");
|
|
9652
9653
|
}
|
|
9653
9654
|
ensureCss();
|
|
9654
|
-
const
|
|
9655
|
-
const root = buildDOM(
|
|
9656
|
-
|
|
9655
|
+
const state2 = normalizeParams(params);
|
|
9656
|
+
const root = buildDOM(state2);
|
|
9657
|
+
state2.isSelected = params.isSelected || false;
|
|
9657
9658
|
containerEl.appendChild(root);
|
|
9658
|
-
bindEvents(root,
|
|
9659
|
-
paint(root,
|
|
9659
|
+
bindEvents(root, state2, state2.callbacks);
|
|
9660
|
+
paint(root, state2);
|
|
9660
9661
|
return {
|
|
9661
9662
|
update(next) {
|
|
9662
9663
|
if (next) {
|
|
9663
|
-
Object.assign(
|
|
9664
|
-
paint(root,
|
|
9664
|
+
Object.assign(state2.entityObject, next);
|
|
9665
|
+
paint(root, state2);
|
|
9665
9666
|
}
|
|
9666
9667
|
},
|
|
9667
9668
|
destroy() {
|
|
@@ -21144,15 +21145,45 @@ var PowerLimitsPersister = class {
|
|
|
21144
21145
|
}
|
|
21145
21146
|
}
|
|
21146
21147
|
/**
|
|
21147
|
-
*
|
|
21148
|
+
* Fetch child customer relations (level 1) from a parent customer
|
|
21149
|
+
* Returns array of child customer IDs
|
|
21148
21150
|
*/
|
|
21149
|
-
async
|
|
21151
|
+
async fetchChildCustomerIds(parentCustomerId) {
|
|
21152
|
+
try {
|
|
21153
|
+
const url = `${this.tbBaseUrl}/api/relations/info?fromId=${parentCustomerId}&fromType=CUSTOMER`;
|
|
21154
|
+
const response = await fetch(url, {
|
|
21155
|
+
method: "GET",
|
|
21156
|
+
headers: {
|
|
21157
|
+
"X-Authorization": `Bearer ${this.jwtToken}`,
|
|
21158
|
+
"Content-Type": "application/json"
|
|
21159
|
+
}
|
|
21160
|
+
});
|
|
21161
|
+
if (!response.ok) {
|
|
21162
|
+
console.warn("[PowerLimitsPersister] Failed to fetch relations:", response.status);
|
|
21163
|
+
return [];
|
|
21164
|
+
}
|
|
21165
|
+
const relations = await response.json();
|
|
21166
|
+
if (!Array.isArray(relations) || relations.length === 0) {
|
|
21167
|
+
console.log("[PowerLimitsPersister] No child customer relations found");
|
|
21168
|
+
return [];
|
|
21169
|
+
}
|
|
21170
|
+
const childCustomerIds = relations.filter((rel) => rel.to?.entityType === "CUSTOMER" && rel.to?.id).map((rel) => rel.to.id);
|
|
21171
|
+
console.log(`[PowerLimitsPersister] Found ${childCustomerIds.length} child customer(s)`);
|
|
21172
|
+
return childCustomerIds;
|
|
21173
|
+
} catch (error) {
|
|
21174
|
+
console.error("[PowerLimitsPersister] Error fetching relations:", error);
|
|
21175
|
+
return [];
|
|
21176
|
+
}
|
|
21177
|
+
}
|
|
21178
|
+
/**
|
|
21179
|
+
* Save power limits to a single customer (internal method)
|
|
21180
|
+
*/
|
|
21181
|
+
async saveToSingleCustomer(customerId, limits) {
|
|
21150
21182
|
try {
|
|
21151
21183
|
const url = `${this.tbBaseUrl}/api/plugins/telemetry/CUSTOMER/${customerId}/attributes/SERVER_SCOPE`;
|
|
21152
21184
|
const payload = {
|
|
21153
21185
|
mapInstantaneousPower: limits
|
|
21154
21186
|
};
|
|
21155
|
-
console.log("[PowerLimitsPersister] Saving power limits:", payload);
|
|
21156
21187
|
const response = await fetch(url, {
|
|
21157
21188
|
method: "POST",
|
|
21158
21189
|
headers: {
|
|
@@ -21162,10 +21193,41 @@ var PowerLimitsPersister = class {
|
|
|
21162
21193
|
body: JSON.stringify(payload)
|
|
21163
21194
|
});
|
|
21164
21195
|
if (!response.ok) {
|
|
21165
|
-
|
|
21196
|
+
console.warn(`[PowerLimitsPersister] Failed to save to customer ${customerId}:`, response.status);
|
|
21197
|
+
return false;
|
|
21166
21198
|
}
|
|
21167
|
-
|
|
21168
|
-
|
|
21199
|
+
return true;
|
|
21200
|
+
} catch (error) {
|
|
21201
|
+
console.error(`[PowerLimitsPersister] Error saving to customer ${customerId}:`, error);
|
|
21202
|
+
return false;
|
|
21203
|
+
}
|
|
21204
|
+
}
|
|
21205
|
+
/**
|
|
21206
|
+
* Save mapInstantaneousPower to customer server_scope attributes
|
|
21207
|
+
* Also propagates to all child customers (level 1 relations)
|
|
21208
|
+
*/
|
|
21209
|
+
async saveCustomerPowerLimits(customerId, limits) {
|
|
21210
|
+
try {
|
|
21211
|
+
console.log("[PowerLimitsPersister] Saving power limits:", { customerId, limits });
|
|
21212
|
+
const mainSaveSuccess = await this.saveToSingleCustomer(customerId, limits);
|
|
21213
|
+
if (!mainSaveSuccess) {
|
|
21214
|
+
throw new Error("Failed to save to main customer");
|
|
21215
|
+
}
|
|
21216
|
+
console.log("[PowerLimitsPersister] Successfully saved to main customer");
|
|
21217
|
+
const childCustomerIds = await this.fetchChildCustomerIds(customerId);
|
|
21218
|
+
let successCount = 1;
|
|
21219
|
+
if (childCustomerIds.length > 0) {
|
|
21220
|
+
console.log(`[PowerLimitsPersister] Saving to ${childCustomerIds.length} child customer(s)...`);
|
|
21221
|
+
const savePromises = childCustomerIds.map(
|
|
21222
|
+
(childId) => this.saveToSingleCustomer(childId, limits)
|
|
21223
|
+
);
|
|
21224
|
+
const results = await Promise.all(savePromises);
|
|
21225
|
+
const childSuccessCount = results.filter(Boolean).length;
|
|
21226
|
+
successCount += childSuccessCount;
|
|
21227
|
+
console.log(`[PowerLimitsPersister] Saved to ${childSuccessCount}/${childCustomerIds.length} child customer(s)`);
|
|
21228
|
+
}
|
|
21229
|
+
console.log(`[PowerLimitsPersister] Total: saved to ${successCount} customer(s)`);
|
|
21230
|
+
return { ok: true, savedCount: successCount };
|
|
21169
21231
|
} catch (error) {
|
|
21170
21232
|
console.error("[PowerLimitsPersister] Error saving power limits:", error);
|
|
21171
21233
|
return { ok: false, error: this.mapError(error) };
|
|
@@ -22355,6 +22417,136 @@ var AnnotationsTab = class {
|
|
|
22355
22417
|
this.annotations = [];
|
|
22356
22418
|
}
|
|
22357
22419
|
}
|
|
22420
|
+
/**
|
|
22421
|
+
* Show a confirmation modal and return user's choice
|
|
22422
|
+
* Replaces native confirm() for better UX
|
|
22423
|
+
*/
|
|
22424
|
+
showConfirmation(message, title = "Confirmar") {
|
|
22425
|
+
return new Promise((resolve) => {
|
|
22426
|
+
const overlay = document.createElement("div");
|
|
22427
|
+
overlay.className = "annotations-confirm-overlay";
|
|
22428
|
+
overlay.innerHTML = `
|
|
22429
|
+
<div class="annotations-confirm-modal">
|
|
22430
|
+
<div class="annotations-confirm-header">
|
|
22431
|
+
<span class="annotations-confirm-icon">\u26A0\uFE0F</span>
|
|
22432
|
+
<span class="annotations-confirm-title">${title}</span>
|
|
22433
|
+
</div>
|
|
22434
|
+
<div class="annotations-confirm-body">
|
|
22435
|
+
<p>${message}</p>
|
|
22436
|
+
</div>
|
|
22437
|
+
<div class="annotations-confirm-actions">
|
|
22438
|
+
<button class="annotations-confirm-btn annotations-confirm-btn--cancel" data-action="cancel">
|
|
22439
|
+
Cancelar
|
|
22440
|
+
</button>
|
|
22441
|
+
<button class="annotations-confirm-btn annotations-confirm-btn--confirm" data-action="confirm">
|
|
22442
|
+
Confirmar
|
|
22443
|
+
</button>
|
|
22444
|
+
</div>
|
|
22445
|
+
</div>
|
|
22446
|
+
`;
|
|
22447
|
+
if (!document.getElementById("annotations-confirm-styles")) {
|
|
22448
|
+
const style = document.createElement("style");
|
|
22449
|
+
style.id = "annotations-confirm-styles";
|
|
22450
|
+
style.textContent = `
|
|
22451
|
+
.annotations-confirm-overlay {
|
|
22452
|
+
position: fixed;
|
|
22453
|
+
inset: 0;
|
|
22454
|
+
background: rgba(0, 0, 0, 0.5);
|
|
22455
|
+
backdrop-filter: blur(4px);
|
|
22456
|
+
display: flex;
|
|
22457
|
+
align-items: center;
|
|
22458
|
+
justify-content: center;
|
|
22459
|
+
z-index: 100001;
|
|
22460
|
+
animation: confirmFadeIn 0.2s ease;
|
|
22461
|
+
}
|
|
22462
|
+
@keyframes confirmFadeIn {
|
|
22463
|
+
from { opacity: 0; }
|
|
22464
|
+
to { opacity: 1; }
|
|
22465
|
+
}
|
|
22466
|
+
.annotations-confirm-modal {
|
|
22467
|
+
background: white;
|
|
22468
|
+
border-radius: 12px;
|
|
22469
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
22470
|
+
max-width: 400px;
|
|
22471
|
+
width: 90%;
|
|
22472
|
+
overflow: hidden;
|
|
22473
|
+
animation: confirmSlideIn 0.25s ease;
|
|
22474
|
+
}
|
|
22475
|
+
@keyframes confirmSlideIn {
|
|
22476
|
+
from { transform: translateY(-20px) scale(0.95); opacity: 0; }
|
|
22477
|
+
to { transform: translateY(0) scale(1); opacity: 1; }
|
|
22478
|
+
}
|
|
22479
|
+
.annotations-confirm-header {
|
|
22480
|
+
display: flex;
|
|
22481
|
+
align-items: center;
|
|
22482
|
+
gap: 10px;
|
|
22483
|
+
padding: 16px 20px;
|
|
22484
|
+
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
|
|
22485
|
+
border-bottom: 1px solid #f59e0b;
|
|
22486
|
+
}
|
|
22487
|
+
.annotations-confirm-icon {
|
|
22488
|
+
font-size: 20px;
|
|
22489
|
+
}
|
|
22490
|
+
.annotations-confirm-title {
|
|
22491
|
+
font-weight: 600;
|
|
22492
|
+
color: #92400e;
|
|
22493
|
+
font-size: 16px;
|
|
22494
|
+
}
|
|
22495
|
+
.annotations-confirm-body {
|
|
22496
|
+
padding: 20px;
|
|
22497
|
+
}
|
|
22498
|
+
.annotations-confirm-body p {
|
|
22499
|
+
margin: 0;
|
|
22500
|
+
color: #374151;
|
|
22501
|
+
font-size: 14px;
|
|
22502
|
+
line-height: 1.5;
|
|
22503
|
+
}
|
|
22504
|
+
.annotations-confirm-actions {
|
|
22505
|
+
display: flex;
|
|
22506
|
+
gap: 12px;
|
|
22507
|
+
padding: 16px 20px;
|
|
22508
|
+
background: #f9fafb;
|
|
22509
|
+
border-top: 1px solid #e5e7eb;
|
|
22510
|
+
justify-content: flex-end;
|
|
22511
|
+
}
|
|
22512
|
+
.annotations-confirm-btn {
|
|
22513
|
+
padding: 10px 20px;
|
|
22514
|
+
border-radius: 8px;
|
|
22515
|
+
font-size: 14px;
|
|
22516
|
+
font-weight: 500;
|
|
22517
|
+
cursor: pointer;
|
|
22518
|
+
transition: all 0.2s ease;
|
|
22519
|
+
border: none;
|
|
22520
|
+
}
|
|
22521
|
+
.annotations-confirm-btn--cancel {
|
|
22522
|
+
background: #e5e7eb;
|
|
22523
|
+
color: #374151;
|
|
22524
|
+
}
|
|
22525
|
+
.annotations-confirm-btn--cancel:hover {
|
|
22526
|
+
background: #d1d5db;
|
|
22527
|
+
}
|
|
22528
|
+
.annotations-confirm-btn--confirm {
|
|
22529
|
+
background: #f59e0b;
|
|
22530
|
+
color: white;
|
|
22531
|
+
}
|
|
22532
|
+
.annotations-confirm-btn--confirm:hover {
|
|
22533
|
+
background: #d97706;
|
|
22534
|
+
}
|
|
22535
|
+
`;
|
|
22536
|
+
document.head.appendChild(style);
|
|
22537
|
+
}
|
|
22538
|
+
document.body.appendChild(overlay);
|
|
22539
|
+
const cleanup = (result) => {
|
|
22540
|
+
overlay.remove();
|
|
22541
|
+
resolve(result);
|
|
22542
|
+
};
|
|
22543
|
+
overlay.querySelector('[data-action="cancel"]')?.addEventListener("click", () => cleanup(false));
|
|
22544
|
+
overlay.querySelector('[data-action="confirm"]')?.addEventListener("click", () => cleanup(true));
|
|
22545
|
+
overlay.addEventListener("click", (e) => {
|
|
22546
|
+
if (e.target === overlay) cleanup(false);
|
|
22547
|
+
});
|
|
22548
|
+
});
|
|
22549
|
+
}
|
|
22358
22550
|
async saveAnnotations() {
|
|
22359
22551
|
try {
|
|
22360
22552
|
const data = {
|
|
@@ -22418,7 +22610,7 @@ var AnnotationsTab = class {
|
|
|
22418
22610
|
this.render();
|
|
22419
22611
|
} else {
|
|
22420
22612
|
this.annotations.shift();
|
|
22421
|
-
|
|
22613
|
+
MyIOToast.show("Erro ao salvar anota\xE7\xE3o. Tente novamente.", "error");
|
|
22422
22614
|
}
|
|
22423
22615
|
}
|
|
22424
22616
|
async editAnnotation(id, changes) {
|
|
@@ -22428,9 +22620,10 @@ var AnnotationsTab = class {
|
|
|
22428
22620
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22429
22621
|
const changeRecord = {};
|
|
22430
22622
|
for (const [key, value] of Object.entries(changes)) {
|
|
22431
|
-
|
|
22623
|
+
const annotationAny = annotation;
|
|
22624
|
+
if (annotationAny[key] !== value) {
|
|
22432
22625
|
changeRecord[key] = {
|
|
22433
|
-
from:
|
|
22626
|
+
from: annotationAny[key],
|
|
22434
22627
|
to: value
|
|
22435
22628
|
};
|
|
22436
22629
|
}
|
|
@@ -22455,7 +22648,7 @@ var AnnotationsTab = class {
|
|
|
22455
22648
|
const success = await this.saveAnnotations();
|
|
22456
22649
|
if (!success) {
|
|
22457
22650
|
this.annotations[index] = annotation;
|
|
22458
|
-
|
|
22651
|
+
MyIOToast.show("Erro ao atualizar anota\xE7\xE3o. Tente novamente.", "error");
|
|
22459
22652
|
}
|
|
22460
22653
|
}
|
|
22461
22654
|
async archiveAnnotation(id) {
|
|
@@ -22483,7 +22676,7 @@ var AnnotationsTab = class {
|
|
|
22483
22676
|
this.render();
|
|
22484
22677
|
} else {
|
|
22485
22678
|
this.annotations[index] = annotation;
|
|
22486
|
-
|
|
22679
|
+
MyIOToast.show("Erro ao arquivar anota\xE7\xE3o. Tente novamente.", "error");
|
|
22487
22680
|
}
|
|
22488
22681
|
}
|
|
22489
22682
|
async acknowledgeAnnotation(id) {
|
|
@@ -22998,8 +23191,8 @@ var AnnotationsTab = class {
|
|
|
22998
23191
|
card.querySelector('[data-action="details"]')?.addEventListener("click", () => {
|
|
22999
23192
|
this.showDetailModal(id);
|
|
23000
23193
|
});
|
|
23001
|
-
card.querySelector('[data-action="archive"]')?.addEventListener("click", () => {
|
|
23002
|
-
if (
|
|
23194
|
+
card.querySelector('[data-action="archive"]')?.addEventListener("click", async () => {
|
|
23195
|
+
if (await this.showConfirmation("Tem certeza que deseja arquivar esta anota\xE7\xE3o?", "Arquivar Anota\xE7\xE3o")) {
|
|
23003
23196
|
this.archiveAnnotation(id);
|
|
23004
23197
|
}
|
|
23005
23198
|
});
|
|
@@ -23016,8 +23209,8 @@ var AnnotationsTab = class {
|
|
|
23016
23209
|
row.querySelector('[data-action="details"]')?.addEventListener("click", () => {
|
|
23017
23210
|
this.showDetailModal(id);
|
|
23018
23211
|
});
|
|
23019
|
-
row.querySelector('[data-action="archive"]')?.addEventListener("click", () => {
|
|
23020
|
-
if (
|
|
23212
|
+
row.querySelector('[data-action="archive"]')?.addEventListener("click", async () => {
|
|
23213
|
+
if (await this.showConfirmation("Tem certeza que deseja arquivar esta anota\xE7\xE3o?", "Arquivar Anota\xE7\xE3o")) {
|
|
23021
23214
|
this.archiveAnnotation(id);
|
|
23022
23215
|
}
|
|
23023
23216
|
});
|
|
@@ -23026,12 +23219,189 @@ var AnnotationsTab = class {
|
|
|
23026
23219
|
// ============================================
|
|
23027
23220
|
// EDIT MODAL
|
|
23028
23221
|
// ============================================
|
|
23029
|
-
|
|
23222
|
+
/**
|
|
23223
|
+
* Show a styled input modal for editing annotation text
|
|
23224
|
+
* Replaces native prompt() for better UX
|
|
23225
|
+
*/
|
|
23226
|
+
showInputModal(title, placeholder, initialValue) {
|
|
23227
|
+
return new Promise((resolve) => {
|
|
23228
|
+
const overlay = document.createElement("div");
|
|
23229
|
+
overlay.className = "annotations-input-overlay";
|
|
23230
|
+
overlay.innerHTML = `
|
|
23231
|
+
<div class="annotations-input-modal">
|
|
23232
|
+
<div class="annotations-input-header">
|
|
23233
|
+
<span class="annotations-input-icon">\u270F\uFE0F</span>
|
|
23234
|
+
<span class="annotations-input-title">${title}</span>
|
|
23235
|
+
</div>
|
|
23236
|
+
<div class="annotations-input-body">
|
|
23237
|
+
<textarea
|
|
23238
|
+
class="annotations-input-textarea"
|
|
23239
|
+
placeholder="${placeholder}"
|
|
23240
|
+
maxlength="255"
|
|
23241
|
+
>${initialValue}</textarea>
|
|
23242
|
+
<div class="annotations-input-char-count">
|
|
23243
|
+
<span id="input-char-count">${initialValue.length}</span> / 255
|
|
23244
|
+
</div>
|
|
23245
|
+
</div>
|
|
23246
|
+
<div class="annotations-input-actions">
|
|
23247
|
+
<button class="annotations-input-btn annotations-input-btn--cancel" data-action="cancel">
|
|
23248
|
+
Cancelar
|
|
23249
|
+
</button>
|
|
23250
|
+
<button class="annotations-input-btn annotations-input-btn--confirm" data-action="confirm">
|
|
23251
|
+
Salvar
|
|
23252
|
+
</button>
|
|
23253
|
+
</div>
|
|
23254
|
+
</div>
|
|
23255
|
+
`;
|
|
23256
|
+
if (!document.getElementById("annotations-input-styles")) {
|
|
23257
|
+
const style = document.createElement("style");
|
|
23258
|
+
style.id = "annotations-input-styles";
|
|
23259
|
+
style.textContent = `
|
|
23260
|
+
.annotations-input-overlay {
|
|
23261
|
+
position: fixed;
|
|
23262
|
+
inset: 0;
|
|
23263
|
+
background: rgba(0, 0, 0, 0.5);
|
|
23264
|
+
backdrop-filter: blur(4px);
|
|
23265
|
+
display: flex;
|
|
23266
|
+
align-items: center;
|
|
23267
|
+
justify-content: center;
|
|
23268
|
+
z-index: 100001;
|
|
23269
|
+
animation: inputFadeIn 0.2s ease;
|
|
23270
|
+
}
|
|
23271
|
+
@keyframes inputFadeIn {
|
|
23272
|
+
from { opacity: 0; }
|
|
23273
|
+
to { opacity: 1; }
|
|
23274
|
+
}
|
|
23275
|
+
.annotations-input-modal {
|
|
23276
|
+
background: white;
|
|
23277
|
+
border-radius: 12px;
|
|
23278
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
23279
|
+
max-width: 500px;
|
|
23280
|
+
width: 90%;
|
|
23281
|
+
overflow: hidden;
|
|
23282
|
+
animation: inputSlideIn 0.25s ease;
|
|
23283
|
+
}
|
|
23284
|
+
@keyframes inputSlideIn {
|
|
23285
|
+
from { transform: translateY(-20px) scale(0.95); opacity: 0; }
|
|
23286
|
+
to { transform: translateY(0) scale(1); opacity: 1; }
|
|
23287
|
+
}
|
|
23288
|
+
.annotations-input-header {
|
|
23289
|
+
display: flex;
|
|
23290
|
+
align-items: center;
|
|
23291
|
+
gap: 10px;
|
|
23292
|
+
padding: 16px 20px;
|
|
23293
|
+
background: linear-gradient(135deg, #6c5ce7 0%, #a29bfe 100%);
|
|
23294
|
+
border-bottom: 1px solid #5b4cdb;
|
|
23295
|
+
}
|
|
23296
|
+
.annotations-input-icon {
|
|
23297
|
+
font-size: 20px;
|
|
23298
|
+
}
|
|
23299
|
+
.annotations-input-title {
|
|
23300
|
+
font-weight: 600;
|
|
23301
|
+
color: white;
|
|
23302
|
+
font-size: 16px;
|
|
23303
|
+
}
|
|
23304
|
+
.annotations-input-body {
|
|
23305
|
+
padding: 20px;
|
|
23306
|
+
}
|
|
23307
|
+
.annotations-input-textarea {
|
|
23308
|
+
width: 100%;
|
|
23309
|
+
min-height: 100px;
|
|
23310
|
+
padding: 12px;
|
|
23311
|
+
border: 1px solid #dee2e6;
|
|
23312
|
+
border-radius: 8px;
|
|
23313
|
+
font-size: 14px;
|
|
23314
|
+
font-family: inherit;
|
|
23315
|
+
resize: vertical;
|
|
23316
|
+
color: #212529;
|
|
23317
|
+
}
|
|
23318
|
+
.annotations-input-textarea:focus {
|
|
23319
|
+
outline: none;
|
|
23320
|
+
border-color: #6c5ce7;
|
|
23321
|
+
box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.15);
|
|
23322
|
+
}
|
|
23323
|
+
.annotations-input-char-count {
|
|
23324
|
+
font-size: 11px;
|
|
23325
|
+
color: #6c757d;
|
|
23326
|
+
text-align: right;
|
|
23327
|
+
margin-top: 6px;
|
|
23328
|
+
}
|
|
23329
|
+
.annotations-input-actions {
|
|
23330
|
+
display: flex;
|
|
23331
|
+
gap: 12px;
|
|
23332
|
+
padding: 16px 20px;
|
|
23333
|
+
background: #f9fafb;
|
|
23334
|
+
border-top: 1px solid #e5e7eb;
|
|
23335
|
+
justify-content: flex-end;
|
|
23336
|
+
}
|
|
23337
|
+
.annotations-input-btn {
|
|
23338
|
+
padding: 10px 20px;
|
|
23339
|
+
border-radius: 8px;
|
|
23340
|
+
font-size: 14px;
|
|
23341
|
+
font-weight: 500;
|
|
23342
|
+
cursor: pointer;
|
|
23343
|
+
transition: all 0.2s ease;
|
|
23344
|
+
border: none;
|
|
23345
|
+
}
|
|
23346
|
+
.annotations-input-btn--cancel {
|
|
23347
|
+
background: #e5e7eb;
|
|
23348
|
+
color: #374151;
|
|
23349
|
+
}
|
|
23350
|
+
.annotations-input-btn--cancel:hover {
|
|
23351
|
+
background: #d1d5db;
|
|
23352
|
+
}
|
|
23353
|
+
.annotations-input-btn--confirm {
|
|
23354
|
+
background: #6c5ce7;
|
|
23355
|
+
color: white;
|
|
23356
|
+
}
|
|
23357
|
+
.annotations-input-btn--confirm:hover {
|
|
23358
|
+
background: #5b4cdb;
|
|
23359
|
+
}
|
|
23360
|
+
`;
|
|
23361
|
+
document.head.appendChild(style);
|
|
23362
|
+
}
|
|
23363
|
+
document.body.appendChild(overlay);
|
|
23364
|
+
const textarea = overlay.querySelector(".annotations-input-textarea");
|
|
23365
|
+
const charCount = overlay.querySelector("#input-char-count");
|
|
23366
|
+
textarea.focus();
|
|
23367
|
+
textarea.select();
|
|
23368
|
+
textarea.addEventListener("input", () => {
|
|
23369
|
+
charCount.textContent = String(textarea.value.length);
|
|
23370
|
+
});
|
|
23371
|
+
const cleanup = (result) => {
|
|
23372
|
+
overlay.remove();
|
|
23373
|
+
resolve(result);
|
|
23374
|
+
};
|
|
23375
|
+
overlay.querySelector('[data-action="cancel"]')?.addEventListener("click", () => cleanup(null));
|
|
23376
|
+
overlay.querySelector('[data-action="confirm"]')?.addEventListener("click", () => {
|
|
23377
|
+
const value = textarea.value.trim();
|
|
23378
|
+
cleanup(value || null);
|
|
23379
|
+
});
|
|
23380
|
+
overlay.addEventListener("click", (e) => {
|
|
23381
|
+
if (e.target === overlay) cleanup(null);
|
|
23382
|
+
});
|
|
23383
|
+
textarea.addEventListener("keydown", (e) => {
|
|
23384
|
+
if (e.key === "Enter" && e.ctrlKey) {
|
|
23385
|
+
const value = textarea.value.trim();
|
|
23386
|
+
cleanup(value || null);
|
|
23387
|
+
}
|
|
23388
|
+
if (e.key === "Escape") {
|
|
23389
|
+
cleanup(null);
|
|
23390
|
+
}
|
|
23391
|
+
});
|
|
23392
|
+
});
|
|
23393
|
+
}
|
|
23394
|
+
async showEditModal(id) {
|
|
23030
23395
|
const annotation = this.annotations.find((a) => a.id === id);
|
|
23031
23396
|
if (!annotation) return;
|
|
23032
|
-
const newText =
|
|
23033
|
-
|
|
23034
|
-
|
|
23397
|
+
const newText = await this.showInputModal(
|
|
23398
|
+
"Editar Anota\xE7\xE3o",
|
|
23399
|
+
"Digite o novo texto da anota\xE7\xE3o...",
|
|
23400
|
+
annotation.text
|
|
23401
|
+
);
|
|
23402
|
+
if (newText && newText !== annotation.text) {
|
|
23403
|
+
await this.editAnnotation(id, { text: newText });
|
|
23404
|
+
this.render();
|
|
23035
23405
|
}
|
|
23036
23406
|
}
|
|
23037
23407
|
// ============================================
|
|
@@ -23125,7 +23495,7 @@ var AnnotationsTab = class {
|
|
|
23125
23495
|
overlay.querySelector(".annotation-detail__close")?.addEventListener("click", () => overlay.remove());
|
|
23126
23496
|
overlay.querySelector('[data-action="close"]')?.addEventListener("click", () => overlay.remove());
|
|
23127
23497
|
overlay.querySelector('[data-action="archive"]')?.addEventListener("click", async () => {
|
|
23128
|
-
if (
|
|
23498
|
+
if (await this.showConfirmation("Tem certeza que deseja arquivar esta anota\xE7\xE3o?", "Arquivar Anota\xE7\xE3o")) {
|
|
23129
23499
|
overlay.remove();
|
|
23130
23500
|
await this.archiveAnnotation(id);
|
|
23131
23501
|
}
|
|
@@ -27690,7 +28060,7 @@ async function openTemperatureModal(params) {
|
|
|
27690
28060
|
const defaultDateRange = getTodaySoFar();
|
|
27691
28061
|
const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
|
|
27692
28062
|
const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
|
|
27693
|
-
const
|
|
28063
|
+
const state2 = {
|
|
27694
28064
|
token: params.token,
|
|
27695
28065
|
deviceId: params.deviceId,
|
|
27696
28066
|
label: params.label || "Sensor de Temperatura",
|
|
@@ -27713,58 +28083,58 @@ async function openTemperatureModal(params) {
|
|
|
27713
28083
|
};
|
|
27714
28084
|
const savedGranularity = localStorage.getItem("myio-temp-modal-granularity");
|
|
27715
28085
|
const savedTheme = localStorage.getItem("myio-temp-modal-theme");
|
|
27716
|
-
if (savedGranularity)
|
|
27717
|
-
if (savedTheme)
|
|
28086
|
+
if (savedGranularity) state2.granularity = savedGranularity;
|
|
28087
|
+
if (savedTheme) state2.theme = savedTheme;
|
|
27718
28088
|
const modalContainer = document.createElement("div");
|
|
27719
28089
|
modalContainer.id = modalId;
|
|
27720
28090
|
document.body.appendChild(modalContainer);
|
|
27721
|
-
renderModal(modalContainer,
|
|
28091
|
+
renderModal(modalContainer, state2, modalId);
|
|
27722
28092
|
try {
|
|
27723
|
-
|
|
27724
|
-
|
|
27725
|
-
|
|
27726
|
-
renderModal(modalContainer,
|
|
27727
|
-
drawChart(modalId,
|
|
28093
|
+
state2.data = await fetchTemperatureData(state2.token, state2.deviceId, state2.startTs, state2.endTs);
|
|
28094
|
+
state2.stats = calculateStats(state2.data, state2.clampRange);
|
|
28095
|
+
state2.isLoading = false;
|
|
28096
|
+
renderModal(modalContainer, state2, modalId);
|
|
28097
|
+
drawChart(modalId, state2);
|
|
27728
28098
|
} catch (error) {
|
|
27729
28099
|
console.error("[TemperatureModal] Error fetching data:", error);
|
|
27730
|
-
|
|
27731
|
-
renderModal(modalContainer,
|
|
28100
|
+
state2.isLoading = false;
|
|
28101
|
+
renderModal(modalContainer, state2, modalId, error);
|
|
27732
28102
|
}
|
|
27733
|
-
await setupEventListeners(modalContainer,
|
|
28103
|
+
await setupEventListeners(modalContainer, state2, modalId, params.onClose);
|
|
27734
28104
|
return {
|
|
27735
28105
|
destroy: () => {
|
|
27736
28106
|
modalContainer.remove();
|
|
27737
28107
|
params.onClose?.();
|
|
27738
28108
|
},
|
|
27739
28109
|
updateData: async (startDate, endDate, granularity) => {
|
|
27740
|
-
|
|
27741
|
-
|
|
27742
|
-
if (granularity)
|
|
27743
|
-
|
|
27744
|
-
renderModal(modalContainer,
|
|
28110
|
+
state2.startTs = new Date(startDate).getTime();
|
|
28111
|
+
state2.endTs = new Date(endDate).getTime();
|
|
28112
|
+
if (granularity) state2.granularity = granularity;
|
|
28113
|
+
state2.isLoading = true;
|
|
28114
|
+
renderModal(modalContainer, state2, modalId);
|
|
27745
28115
|
try {
|
|
27746
|
-
|
|
27747
|
-
|
|
27748
|
-
|
|
27749
|
-
renderModal(modalContainer,
|
|
27750
|
-
drawChart(modalId,
|
|
28116
|
+
state2.data = await fetchTemperatureData(state2.token, state2.deviceId, state2.startTs, state2.endTs);
|
|
28117
|
+
state2.stats = calculateStats(state2.data, state2.clampRange);
|
|
28118
|
+
state2.isLoading = false;
|
|
28119
|
+
renderModal(modalContainer, state2, modalId);
|
|
28120
|
+
drawChart(modalId, state2);
|
|
27751
28121
|
} catch (error) {
|
|
27752
28122
|
console.error("[TemperatureModal] Error updating data:", error);
|
|
27753
|
-
|
|
27754
|
-
renderModal(modalContainer,
|
|
28123
|
+
state2.isLoading = false;
|
|
28124
|
+
renderModal(modalContainer, state2, modalId, error);
|
|
27755
28125
|
}
|
|
27756
28126
|
}
|
|
27757
28127
|
};
|
|
27758
28128
|
}
|
|
27759
|
-
function renderModal(container,
|
|
27760
|
-
const colors = getThemeColors(
|
|
27761
|
-
const startDateStr = new Date(
|
|
27762
|
-
const endDateStr = new Date(
|
|
27763
|
-
const statusText =
|
|
27764
|
-
const statusColor =
|
|
27765
|
-
const rangeText =
|
|
27766
|
-
const startDateInput = new Date(
|
|
27767
|
-
const endDateInput = new Date(
|
|
28129
|
+
function renderModal(container, state2, modalId, error) {
|
|
28130
|
+
const colors = getThemeColors(state2.theme);
|
|
28131
|
+
const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale);
|
|
28132
|
+
const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale);
|
|
28133
|
+
const statusText = state2.temperatureStatus === "ok" ? "Dentro da faixa" : state2.temperatureStatus === "above" ? "Acima do limite" : state2.temperatureStatus === "below" ? "Abaixo do limite" : "N/A";
|
|
28134
|
+
const statusColor = state2.temperatureStatus === "ok" ? colors.success : state2.temperatureStatus === "above" ? colors.danger : state2.temperatureStatus === "below" ? colors.primary : colors.textMuted;
|
|
28135
|
+
const rangeText = state2.temperatureMin !== null && state2.temperatureMax !== null ? `${state2.temperatureMin}\xB0C - ${state2.temperatureMax}\xB0C` : "N\xE3o definida";
|
|
28136
|
+
const startDateInput = new Date(state2.startTs).toISOString().slice(0, 16);
|
|
28137
|
+
const endDateInput = new Date(state2.endTs).toISOString().slice(0, 16);
|
|
27768
28138
|
const isMaximized = container.__isMaximized || false;
|
|
27769
28139
|
const contentMaxWidth = isMaximized ? "100%" : "900px";
|
|
27770
28140
|
const contentMaxHeight = isMaximized ? "100vh" : "95vh";
|
|
@@ -27791,7 +28161,7 @@ function renderModal(container, state, modalId, error) {
|
|
|
27791
28161
|
min-height: 20px;
|
|
27792
28162
|
">
|
|
27793
28163
|
<h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
|
|
27794
|
-
\u{1F321}\uFE0F ${
|
|
28164
|
+
\u{1F321}\uFE0F ${state2.label} - Hist\xF3rico de Temperatura
|
|
27795
28165
|
</h2>
|
|
27796
28166
|
<div style="display: flex; gap: 4px; align-items: center;">
|
|
27797
28167
|
<!-- Theme Toggle -->
|
|
@@ -27799,7 +28169,7 @@ function renderModal(container, state, modalId, error) {
|
|
|
27799
28169
|
background: none; border: none; font-size: 16px; cursor: pointer;
|
|
27800
28170
|
padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
|
|
27801
28171
|
transition: background-color 0.2s;
|
|
27802
|
-
">${
|
|
28172
|
+
">${state2.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
|
|
27803
28173
|
<!-- Maximize Button -->
|
|
27804
28174
|
<button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
|
|
27805
28175
|
background: none; border: none; font-size: 16px; cursor: pointer;
|
|
@@ -27821,7 +28191,7 @@ function renderModal(container, state, modalId, error) {
|
|
|
27821
28191
|
<!-- Controls Row -->
|
|
27822
28192
|
<div style="
|
|
27823
28193
|
display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
|
|
27824
|
-
margin-bottom: 16px; padding: 16px; background: ${
|
|
28194
|
+
margin-bottom: 16px; padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
|
|
27825
28195
|
border-radius: 6px; border: 1px solid ${colors.border};
|
|
27826
28196
|
">
|
|
27827
28197
|
<!-- Granularity Select -->
|
|
@@ -27834,8 +28204,8 @@ function renderModal(container, state, modalId, error) {
|
|
|
27834
28204
|
font-size: 14px; color: ${colors.text}; background: ${colors.surface};
|
|
27835
28205
|
cursor: pointer; min-width: 130px;
|
|
27836
28206
|
">
|
|
27837
|
-
<option value="hour" ${
|
|
27838
|
-
<option value="day" ${
|
|
28207
|
+
<option value="hour" ${state2.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
|
|
28208
|
+
<option value="day" ${state2.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
|
|
27839
28209
|
</select>
|
|
27840
28210
|
</div>
|
|
27841
28211
|
<!-- Day Period Filter (Multiselect) -->
|
|
@@ -27849,7 +28219,7 @@ function renderModal(container, state, modalId, error) {
|
|
|
27849
28219
|
cursor: pointer; min-width: 180px; text-align: left;
|
|
27850
28220
|
display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
27851
28221
|
">
|
|
27852
|
-
<span>${getSelectedPeriodsLabel(
|
|
28222
|
+
<span>${getSelectedPeriodsLabel(state2.selectedPeriods)}</span>
|
|
27853
28223
|
<span style="font-size: 10px;">\u25BC</span>
|
|
27854
28224
|
</button>
|
|
27855
28225
|
<div id="${modalId}-period-dropdown" style="
|
|
@@ -27862,12 +28232,12 @@ function renderModal(container, state, modalId, error) {
|
|
|
27862
28232
|
<label style="
|
|
27863
28233
|
display: flex; align-items: center; gap: 8px; padding: 8px 12px;
|
|
27864
28234
|
cursor: pointer; font-size: 13px; color: ${colors.text};
|
|
27865
|
-
" onmouseover="this.style.background='${
|
|
28235
|
+
" onmouseover="this.style.background='${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
|
|
27866
28236
|
onmouseout="this.style.background='transparent'">
|
|
27867
28237
|
<input type="checkbox"
|
|
27868
28238
|
name="${modalId}-period"
|
|
27869
28239
|
value="${period.id}"
|
|
27870
|
-
${
|
|
28240
|
+
${state2.selectedPeriods.includes(period.id) ? "checked" : ""}
|
|
27871
28241
|
style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
|
|
27872
28242
|
${period.label}
|
|
27873
28243
|
</label>
|
|
@@ -27875,13 +28245,13 @@ function renderModal(container, state, modalId, error) {
|
|
|
27875
28245
|
<div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
|
|
27876
28246
|
<button id="${modalId}-period-select-all" type="button" style="
|
|
27877
28247
|
width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
|
|
27878
|
-
background: ${
|
|
28248
|
+
background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
|
|
27879
28249
|
border: none; border-radius: 4px; cursor: pointer;
|
|
27880
28250
|
font-size: 12px; color: ${colors.text};
|
|
27881
28251
|
">Selecionar Todos</button>
|
|
27882
28252
|
<button id="${modalId}-period-clear" type="button" style="
|
|
27883
28253
|
width: calc(100% - 16px); margin: 0 8px; padding: 6px;
|
|
27884
|
-
background: ${
|
|
28254
|
+
background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
|
|
27885
28255
|
border: none; border-radius: 4px; cursor: pointer;
|
|
27886
28256
|
font-size: 12px; color: ${colors.text};
|
|
27887
28257
|
">Limpar Sele\xE7\xE3o</button>
|
|
@@ -27906,8 +28276,8 @@ function renderModal(container, state, modalId, error) {
|
|
|
27906
28276
|
font-size: 14px; font-weight: 500; height: 38px;
|
|
27907
28277
|
display: flex; align-items: center; gap: 8px;
|
|
27908
28278
|
font-family: 'Roboto', Arial, sans-serif;
|
|
27909
|
-
" ${
|
|
27910
|
-
${
|
|
28279
|
+
" ${state2.isLoading ? "disabled" : ""}>
|
|
28280
|
+
${state2.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
|
|
27911
28281
|
</button>
|
|
27912
28282
|
</div>
|
|
27913
28283
|
|
|
@@ -27918,40 +28288,40 @@ function renderModal(container, state, modalId, error) {
|
|
|
27918
28288
|
">
|
|
27919
28289
|
<!-- Current Temperature -->
|
|
27920
28290
|
<div style="
|
|
27921
|
-
padding: 16px; background: ${
|
|
28291
|
+
padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
|
|
27922
28292
|
border-radius: 12px; border: 1px solid ${colors.border};
|
|
27923
28293
|
">
|
|
27924
28294
|
<span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Temperatura Atual</span>
|
|
27925
28295
|
<div style="font-weight: 700; font-size: 24px; color: ${statusColor}; margin-top: 4px;">
|
|
27926
|
-
${
|
|
28296
|
+
${state2.currentTemperature !== null ? formatTemperature(state2.currentTemperature) : "N/A"}
|
|
27927
28297
|
</div>
|
|
27928
28298
|
<div style="font-size: 11px; color: ${statusColor}; margin-top: 2px;">${statusText}</div>
|
|
27929
28299
|
</div>
|
|
27930
28300
|
<!-- Average -->
|
|
27931
28301
|
<div style="
|
|
27932
|
-
padding: 16px; background: ${
|
|
28302
|
+
padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
|
|
27933
28303
|
border-radius: 12px; border: 1px solid ${colors.border};
|
|
27934
28304
|
">
|
|
27935
28305
|
<span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">M\xE9dia do Per\xEDodo</span>
|
|
27936
28306
|
<div id="${modalId}-avg" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
|
|
27937
|
-
${
|
|
28307
|
+
${state2.stats.count > 0 ? formatTemperature(state2.stats.avg) : "N/A"}
|
|
27938
28308
|
</div>
|
|
27939
28309
|
<div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${startDateStr} - ${endDateStr}</div>
|
|
27940
28310
|
</div>
|
|
27941
28311
|
<!-- Min/Max -->
|
|
27942
28312
|
<div style="
|
|
27943
|
-
padding: 16px; background: ${
|
|
28313
|
+
padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
|
|
27944
28314
|
border-radius: 12px; border: 1px solid ${colors.border};
|
|
27945
28315
|
">
|
|
27946
28316
|
<span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Min / Max</span>
|
|
27947
28317
|
<div id="${modalId}-minmax" style="font-weight: 600; font-size: 20px; color: ${colors.text}; margin-top: 4px;">
|
|
27948
|
-
${
|
|
28318
|
+
${state2.stats.count > 0 ? `${formatTemperature(state2.stats.min)} / ${formatTemperature(state2.stats.max)}` : "N/A"}
|
|
27949
28319
|
</div>
|
|
27950
|
-
<div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${
|
|
28320
|
+
<div style="font-size: 11px; color: ${colors.textMuted}; margin-top: 2px;">${state2.stats.count} leituras</div>
|
|
27951
28321
|
</div>
|
|
27952
28322
|
<!-- Ideal Range -->
|
|
27953
28323
|
<div style="
|
|
27954
|
-
padding: 16px; background: ${
|
|
28324
|
+
padding: 16px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
|
|
27955
28325
|
border-radius: 12px; border: 1px solid ${colors.border};
|
|
27956
28326
|
">
|
|
27957
28327
|
<span style="color: ${colors.textMuted}; font-size: 12px; font-weight: 500;">Faixa Ideal</span>
|
|
@@ -27967,18 +28337,18 @@ function renderModal(container, state, modalId, error) {
|
|
|
27967
28337
|
Hist\xF3rico de Temperatura
|
|
27968
28338
|
</h3>
|
|
27969
28339
|
<div id="${modalId}-chart" style="
|
|
27970
|
-
height: 320px; background: ${
|
|
28340
|
+
height: 320px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
|
|
27971
28341
|
border-radius: 12px; display: flex; justify-content: center; align-items: center;
|
|
27972
28342
|
border: 1px solid ${colors.border}; position: relative;
|
|
27973
28343
|
">
|
|
27974
|
-
${
|
|
28344
|
+
${state2.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
|
|
27975
28345
|
<div style="animation: spin 1s linear infinite; font-size: 32px; margin-bottom: 8px;">\u21BB</div>
|
|
27976
28346
|
<div>Carregando dados...</div>
|
|
27977
28347
|
</div>` : error ? `<div style="text-align: center; color: ${colors.danger};">
|
|
27978
28348
|
<div style="font-size: 32px; margin-bottom: 8px;">\u26A0\uFE0F</div>
|
|
27979
28349
|
<div>Erro ao carregar dados</div>
|
|
27980
28350
|
<div style="font-size: 12px; margin-top: 4px;">${error.message}</div>
|
|
27981
|
-
</div>` :
|
|
28351
|
+
</div>` : state2.data.length === 0 ? `<div style="text-align: center; color: ${colors.textMuted};">
|
|
27982
28352
|
<div style="font-size: 32px; margin-bottom: 8px;">\u{1F4ED}</div>
|
|
27983
28353
|
<div>Sem dados para o per\xEDodo selecionado</div>
|
|
27984
28354
|
</div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
|
|
@@ -27988,12 +28358,12 @@ function renderModal(container, state, modalId, error) {
|
|
|
27988
28358
|
<!-- Actions -->
|
|
27989
28359
|
<div style="display: flex; justify-content: flex-end; gap: 12px;">
|
|
27990
28360
|
<button id="${modalId}-export" style="
|
|
27991
|
-
background: ${
|
|
28361
|
+
background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
|
|
27992
28362
|
color: ${colors.text}; border: 1px solid ${colors.border};
|
|
27993
28363
|
padding: 8px 16px; border-radius: 6px; cursor: pointer;
|
|
27994
28364
|
font-size: 14px; display: flex; align-items: center; gap: 8px;
|
|
27995
28365
|
font-family: 'Roboto', Arial, sans-serif;
|
|
27996
|
-
" ${
|
|
28366
|
+
" ${state2.data.length === 0 ? "disabled" : ""}>
|
|
27997
28367
|
\u{1F4E5} Exportar CSV
|
|
27998
28368
|
</button>
|
|
27999
28369
|
<button id="${modalId}-close-btn" style="
|
|
@@ -28044,14 +28414,14 @@ function renderModal(container, state, modalId, error) {
|
|
|
28044
28414
|
</style>
|
|
28045
28415
|
`;
|
|
28046
28416
|
}
|
|
28047
|
-
function drawChart(modalId,
|
|
28417
|
+
function drawChart(modalId, state2) {
|
|
28048
28418
|
const chartContainer = document.getElementById(`${modalId}-chart`);
|
|
28049
28419
|
const canvas = document.getElementById(`${modalId}-canvas`);
|
|
28050
|
-
if (!chartContainer || !canvas ||
|
|
28420
|
+
if (!chartContainer || !canvas || state2.data.length === 0) return;
|
|
28051
28421
|
const ctx = canvas.getContext("2d");
|
|
28052
28422
|
if (!ctx) return;
|
|
28053
|
-
const colors = getThemeColors(
|
|
28054
|
-
const filteredData = filterByDayPeriods(
|
|
28423
|
+
const colors = getThemeColors(state2.theme);
|
|
28424
|
+
const filteredData = filterByDayPeriods(state2.data, state2.selectedPeriods);
|
|
28055
28425
|
if (filteredData.length === 0) {
|
|
28056
28426
|
canvas.width = chartContainer.clientWidth;
|
|
28057
28427
|
canvas.height = chartContainer.clientHeight;
|
|
@@ -28062,14 +28432,14 @@ function drawChart(modalId, state) {
|
|
|
28062
28432
|
return;
|
|
28063
28433
|
}
|
|
28064
28434
|
let chartData;
|
|
28065
|
-
if (
|
|
28435
|
+
if (state2.granularity === "hour") {
|
|
28066
28436
|
const interpolated = interpolateTemperature(filteredData, {
|
|
28067
28437
|
intervalMinutes: 30,
|
|
28068
|
-
startTs:
|
|
28069
|
-
endTs:
|
|
28070
|
-
clampRange:
|
|
28438
|
+
startTs: state2.startTs,
|
|
28439
|
+
endTs: state2.endTs,
|
|
28440
|
+
clampRange: state2.clampRange
|
|
28071
28441
|
});
|
|
28072
|
-
const filteredInterpolated = filterByDayPeriods(interpolated,
|
|
28442
|
+
const filteredInterpolated = filterByDayPeriods(interpolated, state2.selectedPeriods);
|
|
28073
28443
|
chartData = filteredInterpolated.map((item) => ({
|
|
28074
28444
|
x: item.ts,
|
|
28075
28445
|
y: Number(item.value),
|
|
@@ -28077,7 +28447,7 @@ function drawChart(modalId, state) {
|
|
|
28077
28447
|
screenY: 0
|
|
28078
28448
|
}));
|
|
28079
28449
|
} else {
|
|
28080
|
-
const daily = aggregateByDay(filteredData,
|
|
28450
|
+
const daily = aggregateByDay(filteredData, state2.clampRange);
|
|
28081
28451
|
chartData = daily.map((item) => ({
|
|
28082
28452
|
x: item.dateTs,
|
|
28083
28453
|
y: item.avg,
|
|
@@ -28095,12 +28465,12 @@ function drawChart(modalId, state) {
|
|
|
28095
28465
|
const paddingRight = 20;
|
|
28096
28466
|
const paddingTop = 20;
|
|
28097
28467
|
const paddingBottom = 55;
|
|
28098
|
-
const isPeriodsFiltered =
|
|
28468
|
+
const isPeriodsFiltered = state2.selectedPeriods.length < 4 && state2.selectedPeriods.length > 0;
|
|
28099
28469
|
const values = chartData.map((d) => d.y);
|
|
28100
28470
|
const dataMin = Math.min(...values);
|
|
28101
28471
|
const dataMax = Math.max(...values);
|
|
28102
|
-
const thresholdMin =
|
|
28103
|
-
const thresholdMax =
|
|
28472
|
+
const thresholdMin = state2.temperatureMin !== null ? state2.temperatureMin : dataMin;
|
|
28473
|
+
const thresholdMax = state2.temperatureMax !== null ? state2.temperatureMax : dataMax;
|
|
28104
28474
|
const minY = Math.min(dataMin, thresholdMin) - 1;
|
|
28105
28475
|
const maxY = Math.max(dataMax, thresholdMax) + 1;
|
|
28106
28476
|
const chartWidth = width - paddingLeft - paddingRight;
|
|
@@ -28132,9 +28502,9 @@ function drawChart(modalId, state) {
|
|
|
28132
28502
|
ctx.lineTo(width - paddingRight, y);
|
|
28133
28503
|
ctx.stroke();
|
|
28134
28504
|
}
|
|
28135
|
-
if (
|
|
28136
|
-
const rangeMinY = height - paddingBottom - (
|
|
28137
|
-
const rangeMaxY = height - paddingBottom - (
|
|
28505
|
+
if (state2.temperatureMin !== null && state2.temperatureMax !== null) {
|
|
28506
|
+
const rangeMinY = height - paddingBottom - (state2.temperatureMin - minY) * scaleY;
|
|
28507
|
+
const rangeMaxY = height - paddingBottom - (state2.temperatureMax - minY) * scaleY;
|
|
28138
28508
|
ctx.fillStyle = "rgba(76, 175, 80, 0.1)";
|
|
28139
28509
|
ctx.fillRect(paddingLeft, rangeMaxY, chartWidth, rangeMinY - rangeMaxY);
|
|
28140
28510
|
ctx.strokeStyle = colors.success;
|
|
@@ -28176,10 +28546,10 @@ function drawChart(modalId, state) {
|
|
|
28176
28546
|
const point = chartData[i];
|
|
28177
28547
|
const date = new Date(point.x);
|
|
28178
28548
|
let label;
|
|
28179
|
-
if (
|
|
28180
|
-
label = date.toLocaleTimeString(
|
|
28549
|
+
if (state2.granularity === "hour") {
|
|
28550
|
+
label = date.toLocaleTimeString(state2.locale, { hour: "2-digit", minute: "2-digit" });
|
|
28181
28551
|
} else {
|
|
28182
|
-
label = date.toLocaleDateString(
|
|
28552
|
+
label = date.toLocaleDateString(state2.locale, { day: "2-digit", month: "2-digit" });
|
|
28183
28553
|
}
|
|
28184
28554
|
ctx.strokeStyle = colors.chartGrid;
|
|
28185
28555
|
ctx.lineWidth = 1;
|
|
@@ -28197,16 +28567,16 @@ function drawChart(modalId, state) {
|
|
|
28197
28567
|
ctx.lineTo(paddingLeft, height - paddingBottom);
|
|
28198
28568
|
ctx.lineTo(width - paddingRight, height - paddingBottom);
|
|
28199
28569
|
ctx.stroke();
|
|
28200
|
-
setupChartTooltip(canvas, chartContainer, chartData,
|
|
28570
|
+
setupChartTooltip(canvas, chartContainer, chartData, state2, colors);
|
|
28201
28571
|
}
|
|
28202
|
-
function setupChartTooltip(canvas, container, chartData,
|
|
28572
|
+
function setupChartTooltip(canvas, container, chartData, state2, colors) {
|
|
28203
28573
|
const existingTooltip = container.querySelector(".myio-chart-tooltip");
|
|
28204
28574
|
if (existingTooltip) existingTooltip.remove();
|
|
28205
28575
|
const tooltip = document.createElement("div");
|
|
28206
28576
|
tooltip.className = "myio-chart-tooltip";
|
|
28207
28577
|
tooltip.style.cssText = `
|
|
28208
28578
|
position: absolute;
|
|
28209
|
-
background: ${
|
|
28579
|
+
background: ${state2.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
|
|
28210
28580
|
border: 1px solid ${colors.border};
|
|
28211
28581
|
border-radius: 8px;
|
|
28212
28582
|
padding: 10px 14px;
|
|
@@ -28243,17 +28613,17 @@ function setupChartTooltip(canvas, container, chartData, state, colors) {
|
|
|
28243
28613
|
if (point) {
|
|
28244
28614
|
const date = new Date(point.x);
|
|
28245
28615
|
let dateStr;
|
|
28246
|
-
if (
|
|
28247
|
-
dateStr = date.toLocaleDateString(
|
|
28616
|
+
if (state2.granularity === "hour") {
|
|
28617
|
+
dateStr = date.toLocaleDateString(state2.locale, {
|
|
28248
28618
|
day: "2-digit",
|
|
28249
28619
|
month: "2-digit",
|
|
28250
28620
|
year: "numeric"
|
|
28251
|
-
}) + " " + date.toLocaleTimeString(
|
|
28621
|
+
}) + " " + date.toLocaleTimeString(state2.locale, {
|
|
28252
28622
|
hour: "2-digit",
|
|
28253
28623
|
minute: "2-digit"
|
|
28254
28624
|
});
|
|
28255
28625
|
} else {
|
|
28256
|
-
dateStr = date.toLocaleDateString(
|
|
28626
|
+
dateStr = date.toLocaleDateString(state2.locale, {
|
|
28257
28627
|
day: "2-digit",
|
|
28258
28628
|
month: "2-digit",
|
|
28259
28629
|
year: "numeric"
|
|
@@ -28291,7 +28661,7 @@ function setupChartTooltip(canvas, container, chartData, state, colors) {
|
|
|
28291
28661
|
canvas.style.cursor = "default";
|
|
28292
28662
|
});
|
|
28293
28663
|
}
|
|
28294
|
-
async function setupEventListeners(container,
|
|
28664
|
+
async function setupEventListeners(container, state2, modalId, onClose) {
|
|
28295
28665
|
const closeModal = () => {
|
|
28296
28666
|
container.remove();
|
|
28297
28667
|
onClose?.();
|
|
@@ -28302,19 +28672,19 @@ async function setupEventListeners(container, state, modalId, onClose) {
|
|
|
28302
28672
|
document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
|
|
28303
28673
|
document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
|
|
28304
28674
|
const dateRangeInput = document.getElementById(`${modalId}-date-range`);
|
|
28305
|
-
if (dateRangeInput && !
|
|
28675
|
+
if (dateRangeInput && !state2.dateRangePicker) {
|
|
28306
28676
|
try {
|
|
28307
|
-
|
|
28308
|
-
presetStart: new Date(
|
|
28309
|
-
presetEnd: new Date(
|
|
28677
|
+
state2.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
|
|
28678
|
+
presetStart: new Date(state2.startTs).toISOString(),
|
|
28679
|
+
presetEnd: new Date(state2.endTs).toISOString(),
|
|
28310
28680
|
includeTime: true,
|
|
28311
28681
|
timePrecision: "minute",
|
|
28312
28682
|
maxRangeDays: 90,
|
|
28313
|
-
locale:
|
|
28683
|
+
locale: state2.locale,
|
|
28314
28684
|
parentEl: container.querySelector(".myio-temp-modal-content"),
|
|
28315
28685
|
onApply: (result) => {
|
|
28316
|
-
|
|
28317
|
-
|
|
28686
|
+
state2.startTs = new Date(result.startISO).getTime();
|
|
28687
|
+
state2.endTs = new Date(result.endISO).getTime();
|
|
28318
28688
|
console.log("[TemperatureModal] Date range applied:", result);
|
|
28319
28689
|
}
|
|
28320
28690
|
});
|
|
@@ -28323,19 +28693,19 @@ async function setupEventListeners(container, state, modalId, onClose) {
|
|
|
28323
28693
|
}
|
|
28324
28694
|
}
|
|
28325
28695
|
document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
|
|
28326
|
-
|
|
28327
|
-
localStorage.setItem("myio-temp-modal-theme",
|
|
28328
|
-
|
|
28329
|
-
renderModal(container,
|
|
28330
|
-
if (
|
|
28331
|
-
await setupEventListeners(container,
|
|
28696
|
+
state2.theme = state2.theme === "dark" ? "light" : "dark";
|
|
28697
|
+
localStorage.setItem("myio-temp-modal-theme", state2.theme);
|
|
28698
|
+
state2.dateRangePicker = null;
|
|
28699
|
+
renderModal(container, state2, modalId);
|
|
28700
|
+
if (state2.data.length > 0) drawChart(modalId, state2);
|
|
28701
|
+
await setupEventListeners(container, state2, modalId, onClose);
|
|
28332
28702
|
});
|
|
28333
28703
|
document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
|
|
28334
28704
|
container.__isMaximized = !container.__isMaximized;
|
|
28335
|
-
|
|
28336
|
-
renderModal(container,
|
|
28337
|
-
if (
|
|
28338
|
-
await setupEventListeners(container,
|
|
28705
|
+
state2.dateRangePicker = null;
|
|
28706
|
+
renderModal(container, state2, modalId);
|
|
28707
|
+
if (state2.data.length > 0) drawChart(modalId, state2);
|
|
28708
|
+
await setupEventListeners(container, state2, modalId, onClose);
|
|
28339
28709
|
});
|
|
28340
28710
|
const periodBtn = document.getElementById(`${modalId}-period-btn`);
|
|
28341
28711
|
const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
|
|
@@ -28354,71 +28724,71 @@ async function setupEventListeners(container, state, modalId, onClose) {
|
|
|
28354
28724
|
periodCheckboxes.forEach((checkbox) => {
|
|
28355
28725
|
checkbox.addEventListener("change", () => {
|
|
28356
28726
|
const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
|
|
28357
|
-
|
|
28727
|
+
state2.selectedPeriods = checked;
|
|
28358
28728
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
28359
28729
|
if (btnLabel) {
|
|
28360
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
28730
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
28361
28731
|
}
|
|
28362
|
-
if (
|
|
28732
|
+
if (state2.data.length > 0) drawChart(modalId, state2);
|
|
28363
28733
|
});
|
|
28364
28734
|
});
|
|
28365
28735
|
document.getElementById(`${modalId}-period-select-all`)?.addEventListener("click", () => {
|
|
28366
28736
|
periodCheckboxes.forEach((cb) => {
|
|
28367
28737
|
cb.checked = true;
|
|
28368
28738
|
});
|
|
28369
|
-
|
|
28739
|
+
state2.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
|
|
28370
28740
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
28371
28741
|
if (btnLabel) {
|
|
28372
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
28742
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
28373
28743
|
}
|
|
28374
|
-
if (
|
|
28744
|
+
if (state2.data.length > 0) drawChart(modalId, state2);
|
|
28375
28745
|
});
|
|
28376
28746
|
document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
|
|
28377
28747
|
periodCheckboxes.forEach((cb) => {
|
|
28378
28748
|
cb.checked = false;
|
|
28379
28749
|
});
|
|
28380
|
-
|
|
28750
|
+
state2.selectedPeriods = [];
|
|
28381
28751
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
28382
28752
|
if (btnLabel) {
|
|
28383
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
28753
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
28384
28754
|
}
|
|
28385
|
-
if (
|
|
28755
|
+
if (state2.data.length > 0) drawChart(modalId, state2);
|
|
28386
28756
|
});
|
|
28387
28757
|
document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
|
|
28388
|
-
|
|
28389
|
-
localStorage.setItem("myio-temp-modal-granularity",
|
|
28390
|
-
if (
|
|
28758
|
+
state2.granularity = e.target.value;
|
|
28759
|
+
localStorage.setItem("myio-temp-modal-granularity", state2.granularity);
|
|
28760
|
+
if (state2.data.length > 0) drawChart(modalId, state2);
|
|
28391
28761
|
});
|
|
28392
28762
|
document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
|
|
28393
|
-
if (
|
|
28763
|
+
if (state2.startTs >= state2.endTs) {
|
|
28394
28764
|
alert("Por favor, selecione um per\xEDodo v\xE1lido");
|
|
28395
28765
|
return;
|
|
28396
28766
|
}
|
|
28397
|
-
|
|
28398
|
-
|
|
28399
|
-
renderModal(container,
|
|
28767
|
+
state2.isLoading = true;
|
|
28768
|
+
state2.dateRangePicker = null;
|
|
28769
|
+
renderModal(container, state2, modalId);
|
|
28400
28770
|
try {
|
|
28401
|
-
|
|
28402
|
-
|
|
28403
|
-
|
|
28404
|
-
renderModal(container,
|
|
28405
|
-
drawChart(modalId,
|
|
28406
|
-
await setupEventListeners(container,
|
|
28771
|
+
state2.data = await fetchTemperatureData(state2.token, state2.deviceId, state2.startTs, state2.endTs);
|
|
28772
|
+
state2.stats = calculateStats(state2.data, state2.clampRange);
|
|
28773
|
+
state2.isLoading = false;
|
|
28774
|
+
renderModal(container, state2, modalId);
|
|
28775
|
+
drawChart(modalId, state2);
|
|
28776
|
+
await setupEventListeners(container, state2, modalId, onClose);
|
|
28407
28777
|
} catch (error) {
|
|
28408
28778
|
console.error("[TemperatureModal] Error fetching data:", error);
|
|
28409
|
-
|
|
28410
|
-
renderModal(container,
|
|
28411
|
-
await setupEventListeners(container,
|
|
28779
|
+
state2.isLoading = false;
|
|
28780
|
+
renderModal(container, state2, modalId, error);
|
|
28781
|
+
await setupEventListeners(container, state2, modalId, onClose);
|
|
28412
28782
|
}
|
|
28413
28783
|
});
|
|
28414
28784
|
document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
|
|
28415
|
-
if (
|
|
28416
|
-
const startDateStr = new Date(
|
|
28417
|
-
const endDateStr = new Date(
|
|
28785
|
+
if (state2.data.length === 0) return;
|
|
28786
|
+
const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
|
|
28787
|
+
const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
|
|
28418
28788
|
exportTemperatureCSV(
|
|
28419
|
-
|
|
28420
|
-
|
|
28421
|
-
|
|
28789
|
+
state2.data,
|
|
28790
|
+
state2.label,
|
|
28791
|
+
state2.stats,
|
|
28422
28792
|
startDateStr,
|
|
28423
28793
|
endDateStr
|
|
28424
28794
|
);
|
|
@@ -28431,7 +28801,7 @@ async function openTemperatureComparisonModal(params) {
|
|
|
28431
28801
|
const defaultDateRange = getTodaySoFar();
|
|
28432
28802
|
const startTs = params.startDate ? new Date(params.startDate).getTime() : defaultDateRange.startTs;
|
|
28433
28803
|
const endTs = params.endDate ? new Date(params.endDate).getTime() : defaultDateRange.endTs;
|
|
28434
|
-
const
|
|
28804
|
+
const state2 = {
|
|
28435
28805
|
token: params.token,
|
|
28436
28806
|
devices: params.devices,
|
|
28437
28807
|
startTs,
|
|
@@ -28450,44 +28820,44 @@ async function openTemperatureComparisonModal(params) {
|
|
|
28450
28820
|
};
|
|
28451
28821
|
const savedGranularity = localStorage.getItem("myio-temp-comparison-granularity");
|
|
28452
28822
|
const savedTheme = localStorage.getItem("myio-temp-comparison-theme");
|
|
28453
|
-
if (savedGranularity)
|
|
28454
|
-
if (savedTheme)
|
|
28823
|
+
if (savedGranularity) state2.granularity = savedGranularity;
|
|
28824
|
+
if (savedTheme) state2.theme = savedTheme;
|
|
28455
28825
|
const modalContainer = document.createElement("div");
|
|
28456
28826
|
modalContainer.id = modalId;
|
|
28457
28827
|
document.body.appendChild(modalContainer);
|
|
28458
|
-
renderModal2(modalContainer,
|
|
28459
|
-
await fetchAllDevicesData(
|
|
28460
|
-
renderModal2(modalContainer,
|
|
28461
|
-
drawComparisonChart(modalId,
|
|
28462
|
-
await setupEventListeners2(modalContainer,
|
|
28828
|
+
renderModal2(modalContainer, state2, modalId);
|
|
28829
|
+
await fetchAllDevicesData(state2);
|
|
28830
|
+
renderModal2(modalContainer, state2, modalId);
|
|
28831
|
+
drawComparisonChart(modalId, state2);
|
|
28832
|
+
await setupEventListeners2(modalContainer, state2, modalId, params.onClose);
|
|
28463
28833
|
return {
|
|
28464
28834
|
destroy: () => {
|
|
28465
28835
|
modalContainer.remove();
|
|
28466
28836
|
params.onClose?.();
|
|
28467
28837
|
},
|
|
28468
28838
|
updateData: async (startDate, endDate, granularity) => {
|
|
28469
|
-
|
|
28470
|
-
|
|
28471
|
-
if (granularity)
|
|
28472
|
-
|
|
28473
|
-
renderModal2(modalContainer,
|
|
28474
|
-
await fetchAllDevicesData(
|
|
28475
|
-
renderModal2(modalContainer,
|
|
28476
|
-
drawComparisonChart(modalId,
|
|
28477
|
-
setupEventListeners2(modalContainer,
|
|
28839
|
+
state2.startTs = new Date(startDate).getTime();
|
|
28840
|
+
state2.endTs = new Date(endDate).getTime();
|
|
28841
|
+
if (granularity) state2.granularity = granularity;
|
|
28842
|
+
state2.isLoading = true;
|
|
28843
|
+
renderModal2(modalContainer, state2, modalId);
|
|
28844
|
+
await fetchAllDevicesData(state2);
|
|
28845
|
+
renderModal2(modalContainer, state2, modalId);
|
|
28846
|
+
drawComparisonChart(modalId, state2);
|
|
28847
|
+
setupEventListeners2(modalContainer, state2, modalId, params.onClose);
|
|
28478
28848
|
}
|
|
28479
28849
|
};
|
|
28480
28850
|
}
|
|
28481
|
-
async function fetchAllDevicesData(
|
|
28482
|
-
|
|
28483
|
-
|
|
28851
|
+
async function fetchAllDevicesData(state2) {
|
|
28852
|
+
state2.isLoading = true;
|
|
28853
|
+
state2.deviceData = [];
|
|
28484
28854
|
try {
|
|
28485
28855
|
const results = await Promise.all(
|
|
28486
|
-
|
|
28856
|
+
state2.devices.map(async (device, index) => {
|
|
28487
28857
|
const deviceId = device.tbId || device.id;
|
|
28488
28858
|
try {
|
|
28489
|
-
const data = await fetchTemperatureData(
|
|
28490
|
-
const stats = calculateStats(data,
|
|
28859
|
+
const data = await fetchTemperatureData(state2.token, deviceId, state2.startTs, state2.endTs);
|
|
28860
|
+
const stats = calculateStats(data, state2.clampRange);
|
|
28491
28861
|
return {
|
|
28492
28862
|
device,
|
|
28493
28863
|
data,
|
|
@@ -28505,21 +28875,21 @@ async function fetchAllDevicesData(state) {
|
|
|
28505
28875
|
}
|
|
28506
28876
|
})
|
|
28507
28877
|
);
|
|
28508
|
-
|
|
28878
|
+
state2.deviceData = results;
|
|
28509
28879
|
} catch (error) {
|
|
28510
28880
|
console.error("[TemperatureComparisonModal] Error fetching data:", error);
|
|
28511
28881
|
}
|
|
28512
|
-
|
|
28882
|
+
state2.isLoading = false;
|
|
28513
28883
|
}
|
|
28514
|
-
function renderModal2(container,
|
|
28515
|
-
const colors = getThemeColors(
|
|
28516
|
-
const startDateStr = new Date(
|
|
28517
|
-
const endDateStr = new Date(
|
|
28518
|
-
const startDateInput = new Date(
|
|
28519
|
-
const endDateInput = new Date(
|
|
28520
|
-
const legendHTML =
|
|
28884
|
+
function renderModal2(container, state2, modalId) {
|
|
28885
|
+
const colors = getThemeColors(state2.theme);
|
|
28886
|
+
const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale);
|
|
28887
|
+
const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale);
|
|
28888
|
+
const startDateInput = new Date(state2.startTs).toISOString().slice(0, 16);
|
|
28889
|
+
const endDateInput = new Date(state2.endTs).toISOString().slice(0, 16);
|
|
28890
|
+
const legendHTML = state2.deviceData.map((dd) => `
|
|
28521
28891
|
<div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px;
|
|
28522
|
-
background: ${
|
|
28892
|
+
background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
|
|
28523
28893
|
border-radius: 8px;">
|
|
28524
28894
|
<span style="width: 12px; height: 12px; border-radius: 50%; background: ${dd.color};"></span>
|
|
28525
28895
|
<span style="color: ${colors.text}; font-size: 13px;">${dd.device.label}</span>
|
|
@@ -28528,9 +28898,9 @@ function renderModal2(container, state, modalId) {
|
|
|
28528
28898
|
</span>
|
|
28529
28899
|
</div>
|
|
28530
28900
|
`).join("");
|
|
28531
|
-
const statsHTML =
|
|
28901
|
+
const statsHTML = state2.deviceData.map((dd) => `
|
|
28532
28902
|
<div style="
|
|
28533
|
-
padding: 12px; background: ${
|
|
28903
|
+
padding: 12px; background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#fafafa"};
|
|
28534
28904
|
border-radius: 10px; border-left: 4px solid ${dd.color};
|
|
28535
28905
|
min-width: 150px;
|
|
28536
28906
|
">
|
|
@@ -28581,7 +28951,7 @@ function renderModal2(container, state, modalId) {
|
|
|
28581
28951
|
min-height: 20px;
|
|
28582
28952
|
">
|
|
28583
28953
|
<h2 style="margin: 6px; font-size: 18px; font-weight: 600; color: white; line-height: 2;">
|
|
28584
|
-
\u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${
|
|
28954
|
+
\u{1F321}\uFE0F Compara\xE7\xE3o de Temperatura - ${state2.devices.length} sensores
|
|
28585
28955
|
</h2>
|
|
28586
28956
|
<div style="display: flex; gap: 4px; align-items: center;">
|
|
28587
28957
|
<!-- Theme Toggle -->
|
|
@@ -28589,7 +28959,7 @@ function renderModal2(container, state, modalId) {
|
|
|
28589
28959
|
background: none; border: none; font-size: 16px; cursor: pointer;
|
|
28590
28960
|
padding: 4px 8px; border-radius: 6px; color: rgba(255,255,255,0.8);
|
|
28591
28961
|
transition: background-color 0.2s;
|
|
28592
|
-
">${
|
|
28962
|
+
">${state2.theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}</button>
|
|
28593
28963
|
<!-- Maximize Button -->
|
|
28594
28964
|
<button id="${modalId}-maximize" title="${isMaximized ? "Restaurar" : "Maximizar"}" style="
|
|
28595
28965
|
background: none; border: none; font-size: 16px; cursor: pointer;
|
|
@@ -28612,7 +28982,7 @@ function renderModal2(container, state, modalId) {
|
|
|
28612
28982
|
<div style="
|
|
28613
28983
|
display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end;
|
|
28614
28984
|
margin-bottom: 16px; padding: 16px;
|
|
28615
|
-
background: ${
|
|
28985
|
+
background: ${state2.theme === "dark" ? "rgba(255,255,255,0.05)" : "#f7f7f7"};
|
|
28616
28986
|
border-radius: 6px; border: 1px solid ${colors.border};
|
|
28617
28987
|
">
|
|
28618
28988
|
<!-- Granularity Select -->
|
|
@@ -28625,8 +28995,8 @@ function renderModal2(container, state, modalId) {
|
|
|
28625
28995
|
font-size: 14px; color: ${colors.text}; background: ${colors.surface};
|
|
28626
28996
|
cursor: pointer; min-width: 130px;
|
|
28627
28997
|
">
|
|
28628
|
-
<option value="hour" ${
|
|
28629
|
-
<option value="day" ${
|
|
28998
|
+
<option value="hour" ${state2.granularity === "hour" ? "selected" : ""}>Hora (30 min)</option>
|
|
28999
|
+
<option value="day" ${state2.granularity === "day" ? "selected" : ""}>Dia (m\xE9dia)</option>
|
|
28630
29000
|
</select>
|
|
28631
29001
|
</div>
|
|
28632
29002
|
<!-- Day Period Filter (Multiselect) -->
|
|
@@ -28640,7 +29010,7 @@ function renderModal2(container, state, modalId) {
|
|
|
28640
29010
|
cursor: pointer; min-width: 180px; text-align: left;
|
|
28641
29011
|
display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
28642
29012
|
">
|
|
28643
|
-
<span>${getSelectedPeriodsLabel(
|
|
29013
|
+
<span>${getSelectedPeriodsLabel(state2.selectedPeriods)}</span>
|
|
28644
29014
|
<span style="font-size: 10px;">\u25BC</span>
|
|
28645
29015
|
</button>
|
|
28646
29016
|
<div id="${modalId}-period-dropdown" style="
|
|
@@ -28653,12 +29023,12 @@ function renderModal2(container, state, modalId) {
|
|
|
28653
29023
|
<label style="
|
|
28654
29024
|
display: flex; align-items: center; gap: 8px; padding: 8px 12px;
|
|
28655
29025
|
cursor: pointer; font-size: 13px; color: ${colors.text};
|
|
28656
|
-
" onmouseover="this.style.background='${
|
|
29026
|
+
" onmouseover="this.style.background='${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"}'"
|
|
28657
29027
|
onmouseout="this.style.background='transparent'">
|
|
28658
29028
|
<input type="checkbox"
|
|
28659
29029
|
name="${modalId}-period"
|
|
28660
29030
|
value="${period.id}"
|
|
28661
|
-
${
|
|
29031
|
+
${state2.selectedPeriods.includes(period.id) ? "checked" : ""}
|
|
28662
29032
|
style="width: 16px; height: 16px; cursor: pointer; accent-color: #3e1a7d;">
|
|
28663
29033
|
${period.label}
|
|
28664
29034
|
</label>
|
|
@@ -28666,13 +29036,13 @@ function renderModal2(container, state, modalId) {
|
|
|
28666
29036
|
<div style="border-top: 1px solid ${colors.border}; margin-top: 8px; padding-top: 8px;">
|
|
28667
29037
|
<button id="${modalId}-period-select-all" type="button" style="
|
|
28668
29038
|
width: calc(100% - 16px); margin: 0 8px 4px; padding: 6px;
|
|
28669
|
-
background: ${
|
|
29039
|
+
background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
|
|
28670
29040
|
border: none; border-radius: 4px; cursor: pointer;
|
|
28671
29041
|
font-size: 12px; color: ${colors.text};
|
|
28672
29042
|
">Selecionar Todos</button>
|
|
28673
29043
|
<button id="${modalId}-period-clear" type="button" style="
|
|
28674
29044
|
width: calc(100% - 16px); margin: 0 8px; padding: 6px;
|
|
28675
|
-
background: ${
|
|
29045
|
+
background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f0f0f0"};
|
|
28676
29046
|
border: none; border-radius: 4px; cursor: pointer;
|
|
28677
29047
|
font-size: 12px; color: ${colors.text};
|
|
28678
29048
|
">Limpar Sele\xE7\xE3o</button>
|
|
@@ -28697,8 +29067,8 @@ function renderModal2(container, state, modalId) {
|
|
|
28697
29067
|
font-size: 14px; font-weight: 500; height: 38px;
|
|
28698
29068
|
display: flex; align-items: center; gap: 8px;
|
|
28699
29069
|
font-family: 'Roboto', Arial, sans-serif;
|
|
28700
|
-
" ${
|
|
28701
|
-
${
|
|
29070
|
+
" ${state2.isLoading ? "disabled" : ""}>
|
|
29071
|
+
${state2.isLoading ? '<span style="animation: spin 1s linear infinite; display: inline-block;">\u21BB</span> Carregando...' : "Carregar"}
|
|
28702
29072
|
</button>
|
|
28703
29073
|
</div>
|
|
28704
29074
|
|
|
@@ -28714,14 +29084,14 @@ function renderModal2(container, state, modalId) {
|
|
|
28714
29084
|
<div style="margin-bottom: 24px;">
|
|
28715
29085
|
<div id="${modalId}-chart" style="
|
|
28716
29086
|
height: 380px;
|
|
28717
|
-
background: ${
|
|
29087
|
+
background: ${state2.theme === "dark" ? "rgba(255,255,255,0.03)" : "#fafafa"};
|
|
28718
29088
|
border-radius: 14px; display: flex; justify-content: center; align-items: center;
|
|
28719
29089
|
border: 1px solid ${colors.border}; position: relative;
|
|
28720
29090
|
">
|
|
28721
|
-
${
|
|
29091
|
+
${state2.isLoading ? `<div style="text-align: center; color: ${colors.textMuted};">
|
|
28722
29092
|
<div style="animation: spin 1s linear infinite; font-size: 36px; margin-bottom: 12px;">\u21BB</div>
|
|
28723
|
-
<div style="font-size: 15px;">Carregando dados de ${
|
|
28724
|
-
</div>` :
|
|
29093
|
+
<div style="font-size: 15px;">Carregando dados de ${state2.devices.length} sensores...</div>
|
|
29094
|
+
</div>` : state2.deviceData.every((dd) => dd.data.length === 0) ? `<div style="text-align: center; color: ${colors.textMuted};">
|
|
28725
29095
|
<div style="font-size: 48px; margin-bottom: 12px;">\u{1F4ED}</div>
|
|
28726
29096
|
<div style="font-size: 16px;">Sem dados para o per\xEDodo selecionado</div>
|
|
28727
29097
|
</div>` : `<canvas id="${modalId}-canvas" style="width: 100%; height: 100%;"></canvas>`}
|
|
@@ -28739,12 +29109,12 @@ function renderModal2(container, state, modalId) {
|
|
|
28739
29109
|
<!-- Actions -->
|
|
28740
29110
|
<div style="display: flex; justify-content: flex-end; gap: 12px;">
|
|
28741
29111
|
<button id="${modalId}-export" style="
|
|
28742
|
-
background: ${
|
|
29112
|
+
background: ${state2.theme === "dark" ? "rgba(255,255,255,0.1)" : "#f7f7f7"};
|
|
28743
29113
|
color: ${colors.text}; border: 1px solid ${colors.border};
|
|
28744
29114
|
padding: 8px 16px; border-radius: 6px; cursor: pointer;
|
|
28745
29115
|
font-size: 14px; display: flex; align-items: center; gap: 8px;
|
|
28746
29116
|
font-family: 'Roboto', Arial, sans-serif;
|
|
28747
|
-
" ${
|
|
29117
|
+
" ${state2.deviceData.every((dd) => dd.data.length === 0) ? "disabled" : ""}>
|
|
28748
29118
|
\u{1F4E5} Exportar CSV
|
|
28749
29119
|
</button>
|
|
28750
29120
|
<button id="${modalId}-close-btn" style="
|
|
@@ -28779,15 +29149,15 @@ function renderModal2(container, state, modalId) {
|
|
|
28779
29149
|
</style>
|
|
28780
29150
|
`;
|
|
28781
29151
|
}
|
|
28782
|
-
function drawComparisonChart(modalId,
|
|
29152
|
+
function drawComparisonChart(modalId, state2) {
|
|
28783
29153
|
const chartContainer = document.getElementById(`${modalId}-chart`);
|
|
28784
29154
|
const canvas = document.getElementById(`${modalId}-canvas`);
|
|
28785
29155
|
if (!chartContainer || !canvas) return;
|
|
28786
|
-
const hasData =
|
|
29156
|
+
const hasData = state2.deviceData.some((dd) => dd.data.length > 0);
|
|
28787
29157
|
if (!hasData) return;
|
|
28788
29158
|
const ctx = canvas.getContext("2d");
|
|
28789
29159
|
if (!ctx) return;
|
|
28790
|
-
const colors = getThemeColors(
|
|
29160
|
+
const colors = getThemeColors(state2.theme);
|
|
28791
29161
|
const width = chartContainer.clientWidth - 2;
|
|
28792
29162
|
const height = 380;
|
|
28793
29163
|
canvas.width = width;
|
|
@@ -28798,19 +29168,19 @@ function drawComparisonChart(modalId, state) {
|
|
|
28798
29168
|
const paddingBottom = 55;
|
|
28799
29169
|
ctx.clearRect(0, 0, width, height);
|
|
28800
29170
|
const processedData = [];
|
|
28801
|
-
|
|
29171
|
+
state2.deviceData.forEach((dd) => {
|
|
28802
29172
|
if (dd.data.length === 0) return;
|
|
28803
|
-
const filteredData = filterByDayPeriods(dd.data,
|
|
29173
|
+
const filteredData = filterByDayPeriods(dd.data, state2.selectedPeriods);
|
|
28804
29174
|
if (filteredData.length === 0) return;
|
|
28805
29175
|
let points;
|
|
28806
|
-
if (
|
|
29176
|
+
if (state2.granularity === "hour") {
|
|
28807
29177
|
const interpolated = interpolateTemperature(filteredData, {
|
|
28808
29178
|
intervalMinutes: 30,
|
|
28809
|
-
startTs:
|
|
28810
|
-
endTs:
|
|
28811
|
-
clampRange:
|
|
29179
|
+
startTs: state2.startTs,
|
|
29180
|
+
endTs: state2.endTs,
|
|
29181
|
+
clampRange: state2.clampRange
|
|
28812
29182
|
});
|
|
28813
|
-
const filteredInterpolated = filterByDayPeriods(interpolated,
|
|
29183
|
+
const filteredInterpolated = filterByDayPeriods(interpolated, state2.selectedPeriods);
|
|
28814
29184
|
points = filteredInterpolated.map((item) => ({
|
|
28815
29185
|
x: item.ts,
|
|
28816
29186
|
y: Number(item.value),
|
|
@@ -28820,7 +29190,7 @@ function drawComparisonChart(modalId, state) {
|
|
|
28820
29190
|
deviceColor: dd.color
|
|
28821
29191
|
}));
|
|
28822
29192
|
} else {
|
|
28823
|
-
const daily = aggregateByDay(filteredData,
|
|
29193
|
+
const daily = aggregateByDay(filteredData, state2.clampRange);
|
|
28824
29194
|
points = daily.map((item) => ({
|
|
28825
29195
|
x: item.dateTs,
|
|
28826
29196
|
y: item.avg,
|
|
@@ -28841,7 +29211,7 @@ function drawComparisonChart(modalId, state) {
|
|
|
28841
29211
|
ctx.fillText("Nenhum dado para os per\xEDodos selecionados", width / 2, height / 2);
|
|
28842
29212
|
return;
|
|
28843
29213
|
}
|
|
28844
|
-
const isPeriodsFiltered =
|
|
29214
|
+
const isPeriodsFiltered = state2.selectedPeriods.length < 4 && state2.selectedPeriods.length > 0;
|
|
28845
29215
|
let dataMinY = Infinity;
|
|
28846
29216
|
let dataMaxY = -Infinity;
|
|
28847
29217
|
processedData.forEach(({ points }) => {
|
|
@@ -28851,7 +29221,7 @@ function drawComparisonChart(modalId, state) {
|
|
|
28851
29221
|
});
|
|
28852
29222
|
});
|
|
28853
29223
|
const rangeMap = /* @__PURE__ */ new Map();
|
|
28854
|
-
|
|
29224
|
+
state2.deviceData.forEach((dd, index) => {
|
|
28855
29225
|
const device = dd.device;
|
|
28856
29226
|
const min = device.temperatureMin;
|
|
28857
29227
|
const max = device.temperatureMax;
|
|
@@ -28870,10 +29240,10 @@ function drawComparisonChart(modalId, state) {
|
|
|
28870
29240
|
}
|
|
28871
29241
|
}
|
|
28872
29242
|
});
|
|
28873
|
-
if (rangeMap.size === 0 &&
|
|
29243
|
+
if (rangeMap.size === 0 && state2.temperatureMin !== null && state2.temperatureMax !== null) {
|
|
28874
29244
|
rangeMap.set("global", {
|
|
28875
|
-
min:
|
|
28876
|
-
max:
|
|
29245
|
+
min: state2.temperatureMin,
|
|
29246
|
+
max: state2.temperatureMax,
|
|
28877
29247
|
customerName: "Global",
|
|
28878
29248
|
color: colors.success,
|
|
28879
29249
|
deviceLabels: []
|
|
@@ -28994,10 +29364,10 @@ function drawComparisonChart(modalId, state) {
|
|
|
28994
29364
|
const point = xAxisPoints[i];
|
|
28995
29365
|
const date = new Date(point.x);
|
|
28996
29366
|
let label;
|
|
28997
|
-
if (
|
|
28998
|
-
label = date.toLocaleTimeString(
|
|
29367
|
+
if (state2.granularity === "hour") {
|
|
29368
|
+
label = date.toLocaleTimeString(state2.locale, { hour: "2-digit", minute: "2-digit" });
|
|
28999
29369
|
} else {
|
|
29000
|
-
label = date.toLocaleDateString(
|
|
29370
|
+
label = date.toLocaleDateString(state2.locale, { day: "2-digit", month: "2-digit" });
|
|
29001
29371
|
}
|
|
29002
29372
|
ctx.strokeStyle = colors.chartGrid;
|
|
29003
29373
|
ctx.lineWidth = 1;
|
|
@@ -29016,16 +29386,16 @@ function drawComparisonChart(modalId, state) {
|
|
|
29016
29386
|
ctx.lineTo(width - paddingRight, height - paddingBottom);
|
|
29017
29387
|
ctx.stroke();
|
|
29018
29388
|
const allChartPoints = processedData.flatMap((pd) => pd.points);
|
|
29019
|
-
setupComparisonChartTooltip(canvas, chartContainer, allChartPoints,
|
|
29389
|
+
setupComparisonChartTooltip(canvas, chartContainer, allChartPoints, state2, colors);
|
|
29020
29390
|
}
|
|
29021
|
-
function setupComparisonChartTooltip(canvas, container, chartData,
|
|
29391
|
+
function setupComparisonChartTooltip(canvas, container, chartData, state2, colors) {
|
|
29022
29392
|
const existingTooltip = container.querySelector(".myio-chart-tooltip");
|
|
29023
29393
|
if (existingTooltip) existingTooltip.remove();
|
|
29024
29394
|
const tooltip = document.createElement("div");
|
|
29025
29395
|
tooltip.className = "myio-chart-tooltip";
|
|
29026
29396
|
tooltip.style.cssText = `
|
|
29027
29397
|
position: absolute;
|
|
29028
|
-
background: ${
|
|
29398
|
+
background: ${state2.theme === "dark" ? "rgba(30, 30, 40, 0.95)" : "rgba(255, 255, 255, 0.98)"};
|
|
29029
29399
|
border: 1px solid ${colors.border};
|
|
29030
29400
|
border-radius: 8px;
|
|
29031
29401
|
padding: 10px 14px;
|
|
@@ -29062,17 +29432,17 @@ function setupComparisonChartTooltip(canvas, container, chartData, state, colors
|
|
|
29062
29432
|
if (point) {
|
|
29063
29433
|
const date = new Date(point.x);
|
|
29064
29434
|
let dateStr;
|
|
29065
|
-
if (
|
|
29066
|
-
dateStr = date.toLocaleDateString(
|
|
29435
|
+
if (state2.granularity === "hour") {
|
|
29436
|
+
dateStr = date.toLocaleDateString(state2.locale, {
|
|
29067
29437
|
day: "2-digit",
|
|
29068
29438
|
month: "2-digit",
|
|
29069
29439
|
year: "numeric"
|
|
29070
|
-
}) + " " + date.toLocaleTimeString(
|
|
29440
|
+
}) + " " + date.toLocaleTimeString(state2.locale, {
|
|
29071
29441
|
hour: "2-digit",
|
|
29072
29442
|
minute: "2-digit"
|
|
29073
29443
|
});
|
|
29074
29444
|
} else {
|
|
29075
|
-
dateStr = date.toLocaleDateString(
|
|
29445
|
+
dateStr = date.toLocaleDateString(state2.locale, {
|
|
29076
29446
|
day: "2-digit",
|
|
29077
29447
|
month: "2-digit",
|
|
29078
29448
|
year: "numeric"
|
|
@@ -29114,7 +29484,7 @@ function setupComparisonChartTooltip(canvas, container, chartData, state, colors
|
|
|
29114
29484
|
canvas.style.cursor = "default";
|
|
29115
29485
|
});
|
|
29116
29486
|
}
|
|
29117
|
-
async function setupEventListeners2(container,
|
|
29487
|
+
async function setupEventListeners2(container, state2, modalId, onClose) {
|
|
29118
29488
|
const closeModal = () => {
|
|
29119
29489
|
container.remove();
|
|
29120
29490
|
onClose?.();
|
|
@@ -29125,19 +29495,19 @@ async function setupEventListeners2(container, state, modalId, onClose) {
|
|
|
29125
29495
|
document.getElementById(`${modalId}-close`)?.addEventListener("click", closeModal);
|
|
29126
29496
|
document.getElementById(`${modalId}-close-btn`)?.addEventListener("click", closeModal);
|
|
29127
29497
|
const dateRangeInput = document.getElementById(`${modalId}-date-range`);
|
|
29128
|
-
if (dateRangeInput && !
|
|
29498
|
+
if (dateRangeInput && !state2.dateRangePicker) {
|
|
29129
29499
|
try {
|
|
29130
|
-
|
|
29131
|
-
presetStart: new Date(
|
|
29132
|
-
presetEnd: new Date(
|
|
29500
|
+
state2.dateRangePicker = await createDateRangePicker2(dateRangeInput, {
|
|
29501
|
+
presetStart: new Date(state2.startTs).toISOString(),
|
|
29502
|
+
presetEnd: new Date(state2.endTs).toISOString(),
|
|
29133
29503
|
includeTime: true,
|
|
29134
29504
|
timePrecision: "minute",
|
|
29135
29505
|
maxRangeDays: 90,
|
|
29136
|
-
locale:
|
|
29506
|
+
locale: state2.locale,
|
|
29137
29507
|
parentEl: container.querySelector(".myio-temp-comparison-content"),
|
|
29138
29508
|
onApply: (result) => {
|
|
29139
|
-
|
|
29140
|
-
|
|
29509
|
+
state2.startTs = new Date(result.startISO).getTime();
|
|
29510
|
+
state2.endTs = new Date(result.endISO).getTime();
|
|
29141
29511
|
console.log("[TemperatureComparisonModal] Date range applied:", result);
|
|
29142
29512
|
}
|
|
29143
29513
|
});
|
|
@@ -29146,23 +29516,23 @@ async function setupEventListeners2(container, state, modalId, onClose) {
|
|
|
29146
29516
|
}
|
|
29147
29517
|
}
|
|
29148
29518
|
document.getElementById(`${modalId}-theme-toggle`)?.addEventListener("click", async () => {
|
|
29149
|
-
|
|
29150
|
-
localStorage.setItem("myio-temp-comparison-theme",
|
|
29151
|
-
|
|
29152
|
-
renderModal2(container,
|
|
29153
|
-
if (
|
|
29154
|
-
drawComparisonChart(modalId,
|
|
29155
|
-
}
|
|
29156
|
-
await setupEventListeners2(container,
|
|
29519
|
+
state2.theme = state2.theme === "dark" ? "light" : "dark";
|
|
29520
|
+
localStorage.setItem("myio-temp-comparison-theme", state2.theme);
|
|
29521
|
+
state2.dateRangePicker = null;
|
|
29522
|
+
renderModal2(container, state2, modalId);
|
|
29523
|
+
if (state2.deviceData.some((dd) => dd.data.length > 0)) {
|
|
29524
|
+
drawComparisonChart(modalId, state2);
|
|
29525
|
+
}
|
|
29526
|
+
await setupEventListeners2(container, state2, modalId, onClose);
|
|
29157
29527
|
});
|
|
29158
29528
|
document.getElementById(`${modalId}-maximize`)?.addEventListener("click", async () => {
|
|
29159
29529
|
container.__isMaximized = !container.__isMaximized;
|
|
29160
|
-
|
|
29161
|
-
renderModal2(container,
|
|
29162
|
-
if (
|
|
29163
|
-
drawComparisonChart(modalId,
|
|
29530
|
+
state2.dateRangePicker = null;
|
|
29531
|
+
renderModal2(container, state2, modalId);
|
|
29532
|
+
if (state2.deviceData.some((dd) => dd.data.length > 0)) {
|
|
29533
|
+
drawComparisonChart(modalId, state2);
|
|
29164
29534
|
}
|
|
29165
|
-
await setupEventListeners2(container,
|
|
29535
|
+
await setupEventListeners2(container, state2, modalId, onClose);
|
|
29166
29536
|
});
|
|
29167
29537
|
const periodBtn = document.getElementById(`${modalId}-period-btn`);
|
|
29168
29538
|
const periodDropdown = document.getElementById(`${modalId}-period-dropdown`);
|
|
@@ -29181,13 +29551,13 @@ async function setupEventListeners2(container, state, modalId, onClose) {
|
|
|
29181
29551
|
periodCheckboxes.forEach((checkbox) => {
|
|
29182
29552
|
checkbox.addEventListener("change", () => {
|
|
29183
29553
|
const checked = Array.from(periodCheckboxes).filter((cb) => cb.checked).map((cb) => cb.value);
|
|
29184
|
-
|
|
29554
|
+
state2.selectedPeriods = checked;
|
|
29185
29555
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
29186
29556
|
if (btnLabel) {
|
|
29187
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
29557
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
29188
29558
|
}
|
|
29189
|
-
if (
|
|
29190
|
-
drawComparisonChart(modalId,
|
|
29559
|
+
if (state2.deviceData.some((dd) => dd.data.length > 0)) {
|
|
29560
|
+
drawComparisonChart(modalId, state2);
|
|
29191
29561
|
}
|
|
29192
29562
|
});
|
|
29193
29563
|
});
|
|
@@ -29195,77 +29565,77 @@ async function setupEventListeners2(container, state, modalId, onClose) {
|
|
|
29195
29565
|
periodCheckboxes.forEach((cb) => {
|
|
29196
29566
|
cb.checked = true;
|
|
29197
29567
|
});
|
|
29198
|
-
|
|
29568
|
+
state2.selectedPeriods = ["madrugada", "manha", "tarde", "noite"];
|
|
29199
29569
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
29200
29570
|
if (btnLabel) {
|
|
29201
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
29571
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
29202
29572
|
}
|
|
29203
|
-
if (
|
|
29204
|
-
drawComparisonChart(modalId,
|
|
29573
|
+
if (state2.deviceData.some((dd) => dd.data.length > 0)) {
|
|
29574
|
+
drawComparisonChart(modalId, state2);
|
|
29205
29575
|
}
|
|
29206
29576
|
});
|
|
29207
29577
|
document.getElementById(`${modalId}-period-clear`)?.addEventListener("click", () => {
|
|
29208
29578
|
periodCheckboxes.forEach((cb) => {
|
|
29209
29579
|
cb.checked = false;
|
|
29210
29580
|
});
|
|
29211
|
-
|
|
29581
|
+
state2.selectedPeriods = [];
|
|
29212
29582
|
const btnLabel = periodBtn?.querySelector("span:first-child");
|
|
29213
29583
|
if (btnLabel) {
|
|
29214
|
-
btnLabel.textContent = getSelectedPeriodsLabel(
|
|
29584
|
+
btnLabel.textContent = getSelectedPeriodsLabel(state2.selectedPeriods);
|
|
29215
29585
|
}
|
|
29216
|
-
if (
|
|
29217
|
-
drawComparisonChart(modalId,
|
|
29586
|
+
if (state2.deviceData.some((dd) => dd.data.length > 0)) {
|
|
29587
|
+
drawComparisonChart(modalId, state2);
|
|
29218
29588
|
}
|
|
29219
29589
|
});
|
|
29220
29590
|
document.getElementById(`${modalId}-granularity`)?.addEventListener("change", (e) => {
|
|
29221
|
-
|
|
29222
|
-
localStorage.setItem("myio-temp-comparison-granularity",
|
|
29223
|
-
if (
|
|
29224
|
-
drawComparisonChart(modalId,
|
|
29591
|
+
state2.granularity = e.target.value;
|
|
29592
|
+
localStorage.setItem("myio-temp-comparison-granularity", state2.granularity);
|
|
29593
|
+
if (state2.deviceData.some((dd) => dd.data.length > 0)) {
|
|
29594
|
+
drawComparisonChart(modalId, state2);
|
|
29225
29595
|
}
|
|
29226
29596
|
});
|
|
29227
29597
|
document.getElementById(`${modalId}-query`)?.addEventListener("click", async () => {
|
|
29228
|
-
if (
|
|
29598
|
+
if (state2.startTs >= state2.endTs) {
|
|
29229
29599
|
alert("Por favor, selecione um per\xEDodo v\xE1lido");
|
|
29230
29600
|
return;
|
|
29231
29601
|
}
|
|
29232
|
-
|
|
29233
|
-
|
|
29234
|
-
renderModal2(container,
|
|
29235
|
-
await fetchAllDevicesData(
|
|
29236
|
-
renderModal2(container,
|
|
29237
|
-
drawComparisonChart(modalId,
|
|
29238
|
-
await setupEventListeners2(container,
|
|
29602
|
+
state2.isLoading = true;
|
|
29603
|
+
state2.dateRangePicker = null;
|
|
29604
|
+
renderModal2(container, state2, modalId);
|
|
29605
|
+
await fetchAllDevicesData(state2);
|
|
29606
|
+
renderModal2(container, state2, modalId);
|
|
29607
|
+
drawComparisonChart(modalId, state2);
|
|
29608
|
+
await setupEventListeners2(container, state2, modalId, onClose);
|
|
29239
29609
|
});
|
|
29240
29610
|
document.getElementById(`${modalId}-export`)?.addEventListener("click", () => {
|
|
29241
|
-
if (
|
|
29242
|
-
exportComparisonCSV(
|
|
29611
|
+
if (state2.deviceData.every((dd) => dd.data.length === 0)) return;
|
|
29612
|
+
exportComparisonCSV(state2);
|
|
29243
29613
|
});
|
|
29244
29614
|
}
|
|
29245
|
-
function exportComparisonCSV(
|
|
29246
|
-
const startDateStr = new Date(
|
|
29247
|
-
const endDateStr = new Date(
|
|
29615
|
+
function exportComparisonCSV(state2) {
|
|
29616
|
+
const startDateStr = new Date(state2.startTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
|
|
29617
|
+
const endDateStr = new Date(state2.endTs).toLocaleDateString(state2.locale).replace(/\//g, "-");
|
|
29248
29618
|
const BOM = "\uFEFF";
|
|
29249
29619
|
let csvContent = BOM;
|
|
29250
29620
|
csvContent += `Compara\xE7\xE3o de Temperatura
|
|
29251
29621
|
`;
|
|
29252
29622
|
csvContent += `Per\xEDodo: ${startDateStr} at\xE9 ${endDateStr}
|
|
29253
29623
|
`;
|
|
29254
|
-
csvContent += `Sensores: ${
|
|
29624
|
+
csvContent += `Sensores: ${state2.devices.map((d) => d.label).join(", ")}
|
|
29255
29625
|
`;
|
|
29256
29626
|
csvContent += "\n";
|
|
29257
29627
|
csvContent += "Estat\xEDsticas por Sensor:\n";
|
|
29258
29628
|
csvContent += "Sensor,M\xE9dia (\xB0C),Min (\xB0C),Max (\xB0C),Leituras\n";
|
|
29259
|
-
|
|
29629
|
+
state2.deviceData.forEach((dd) => {
|
|
29260
29630
|
csvContent += `"${dd.device.label}",${dd.stats.avg.toFixed(2)},${dd.stats.min.toFixed(2)},${dd.stats.max.toFixed(2)},${dd.stats.count}
|
|
29261
29631
|
`;
|
|
29262
29632
|
});
|
|
29263
29633
|
csvContent += "\n";
|
|
29264
29634
|
csvContent += "Dados Detalhados:\n";
|
|
29265
29635
|
csvContent += "Data/Hora,Sensor,Temperatura (\xB0C)\n";
|
|
29266
|
-
|
|
29636
|
+
state2.deviceData.forEach((dd) => {
|
|
29267
29637
|
dd.data.forEach((item) => {
|
|
29268
|
-
const date = new Date(item.ts).toLocaleString(
|
|
29638
|
+
const date = new Date(item.ts).toLocaleString(state2.locale);
|
|
29269
29639
|
const temp = Number(item.value).toFixed(2);
|
|
29270
29640
|
csvContent += `"${date}","${dd.device.label}",${temp}
|
|
29271
29641
|
`;
|
|
@@ -29373,10 +29743,10 @@ async function saveCustomerAttributes(customerId, token, minTemperature, maxTemp
|
|
|
29373
29743
|
throw new Error(`Failed to save attributes: ${response.status}`);
|
|
29374
29744
|
}
|
|
29375
29745
|
}
|
|
29376
|
-
function renderModal3(container,
|
|
29377
|
-
const colors = getColors(
|
|
29378
|
-
const minValue =
|
|
29379
|
-
const maxValue =
|
|
29746
|
+
function renderModal3(container, state2, modalId, onClose, onSave) {
|
|
29747
|
+
const colors = getColors(state2.theme);
|
|
29748
|
+
const minValue = state2.minTemperature !== null ? state2.minTemperature : "";
|
|
29749
|
+
const maxValue = state2.maxTemperature !== null ? state2.maxTemperature : "";
|
|
29380
29750
|
container.innerHTML = `
|
|
29381
29751
|
<style>
|
|
29382
29752
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
@@ -29641,23 +30011,23 @@ function renderModal3(container, state, modalId, onClose, onSave) {
|
|
|
29641
30011
|
</div>
|
|
29642
30012
|
|
|
29643
30013
|
<div class="modal-body">
|
|
29644
|
-
${
|
|
30014
|
+
${state2.isLoading ? `
|
|
29645
30015
|
<div class="loading-overlay">
|
|
29646
30016
|
<div class="loading-spinner"></div>
|
|
29647
30017
|
<div>Carregando configura\xE7\xF5es...</div>
|
|
29648
30018
|
</div>
|
|
29649
30019
|
` : `
|
|
29650
|
-
${
|
|
29651
|
-
<div class="message message-error">${
|
|
30020
|
+
${state2.error ? `
|
|
30021
|
+
<div class="message message-error">${state2.error}</div>
|
|
29652
30022
|
` : ""}
|
|
29653
30023
|
|
|
29654
|
-
${
|
|
29655
|
-
<div class="message message-success">${
|
|
30024
|
+
${state2.successMessage ? `
|
|
30025
|
+
<div class="message message-success">${state2.successMessage}</div>
|
|
29656
30026
|
` : ""}
|
|
29657
30027
|
|
|
29658
30028
|
<div class="customer-info">
|
|
29659
30029
|
<div class="customer-label">Shopping / Cliente</div>
|
|
29660
|
-
<div class="customer-name">${
|
|
30030
|
+
<div class="customer-name">${state2.customerName || "N\xE3o identificado"}</div>
|
|
29661
30031
|
</div>
|
|
29662
30032
|
|
|
29663
30033
|
<div class="form-group">
|
|
@@ -29701,11 +30071,11 @@ function renderModal3(container, state, modalId, onClose, onSave) {
|
|
|
29701
30071
|
`}
|
|
29702
30072
|
</div>
|
|
29703
30073
|
|
|
29704
|
-
${!
|
|
30074
|
+
${!state2.isLoading ? `
|
|
29705
30075
|
<div class="modal-footer">
|
|
29706
30076
|
<button class="btn btn-secondary" id="${modalId}-cancel">Cancelar</button>
|
|
29707
|
-
<button class="btn btn-primary" id="${modalId}-save" ${
|
|
29708
|
-
${
|
|
30077
|
+
<button class="btn btn-primary" id="${modalId}-save" ${state2.isSaving ? "disabled" : ""}>
|
|
30078
|
+
${state2.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
|
|
29709
30079
|
</button>
|
|
29710
30080
|
</div>
|
|
29711
30081
|
` : ""}
|
|
@@ -29742,18 +30112,18 @@ function renderModal3(container, state, modalId, onClose, onSave) {
|
|
|
29742
30112
|
const min = parseFloat(minInput.value);
|
|
29743
30113
|
const max = parseFloat(maxInput.value);
|
|
29744
30114
|
if (isNaN(min) || isNaN(max)) {
|
|
29745
|
-
|
|
29746
|
-
renderModal3(container,
|
|
30115
|
+
state2.error = "Por favor, preencha ambos os valores.";
|
|
30116
|
+
renderModal3(container, state2, modalId, onClose, onSave);
|
|
29747
30117
|
return;
|
|
29748
30118
|
}
|
|
29749
30119
|
if (min >= max) {
|
|
29750
|
-
|
|
29751
|
-
renderModal3(container,
|
|
30120
|
+
state2.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
|
|
30121
|
+
renderModal3(container, state2, modalId, onClose, onSave);
|
|
29752
30122
|
return;
|
|
29753
30123
|
}
|
|
29754
30124
|
if (min < 0 || max > 50) {
|
|
29755
|
-
|
|
29756
|
-
renderModal3(container,
|
|
30125
|
+
state2.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
|
|
30126
|
+
renderModal3(container, state2, modalId, onClose, onSave);
|
|
29757
30127
|
return;
|
|
29758
30128
|
}
|
|
29759
30129
|
await onSave(min, max);
|
|
@@ -29761,7 +30131,7 @@ function renderModal3(container, state, modalId, onClose, onSave) {
|
|
|
29761
30131
|
}
|
|
29762
30132
|
function openTemperatureSettingsModal(params) {
|
|
29763
30133
|
const modalId = `myio-temp-settings-${Date.now()}`;
|
|
29764
|
-
const
|
|
30134
|
+
const state2 = {
|
|
29765
30135
|
customerId: params.customerId,
|
|
29766
30136
|
customerName: params.customerName || "",
|
|
29767
30137
|
token: params.token,
|
|
@@ -29781,37 +30151,37 @@ function openTemperatureSettingsModal(params) {
|
|
|
29781
30151
|
params.onClose?.();
|
|
29782
30152
|
};
|
|
29783
30153
|
const handleSave = async (min, max) => {
|
|
29784
|
-
|
|
29785
|
-
|
|
29786
|
-
|
|
29787
|
-
renderModal3(container,
|
|
30154
|
+
state2.isSaving = true;
|
|
30155
|
+
state2.error = null;
|
|
30156
|
+
state2.successMessage = null;
|
|
30157
|
+
renderModal3(container, state2, modalId, destroy, handleSave);
|
|
29788
30158
|
try {
|
|
29789
|
-
await saveCustomerAttributes(
|
|
29790
|
-
|
|
29791
|
-
|
|
29792
|
-
|
|
29793
|
-
|
|
29794
|
-
renderModal3(container,
|
|
30159
|
+
await saveCustomerAttributes(state2.customerId, state2.token, min, max, params.onError);
|
|
30160
|
+
state2.minTemperature = min;
|
|
30161
|
+
state2.maxTemperature = max;
|
|
30162
|
+
state2.isSaving = false;
|
|
30163
|
+
state2.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
|
|
30164
|
+
renderModal3(container, state2, modalId, destroy, handleSave);
|
|
29795
30165
|
params.onSave?.({ minTemperature: min, maxTemperature: max });
|
|
29796
30166
|
setTimeout(() => {
|
|
29797
30167
|
destroy();
|
|
29798
30168
|
}, 1500);
|
|
29799
30169
|
} catch (error) {
|
|
29800
|
-
|
|
29801
|
-
|
|
29802
|
-
renderModal3(container,
|
|
30170
|
+
state2.isSaving = false;
|
|
30171
|
+
state2.error = `Erro ao salvar: ${error.message}`;
|
|
30172
|
+
renderModal3(container, state2, modalId, destroy, handleSave);
|
|
29803
30173
|
}
|
|
29804
30174
|
};
|
|
29805
|
-
renderModal3(container,
|
|
29806
|
-
fetchCustomerAttributes(
|
|
29807
|
-
|
|
29808
|
-
|
|
29809
|
-
|
|
29810
|
-
renderModal3(container,
|
|
30175
|
+
renderModal3(container, state2, modalId, destroy, handleSave);
|
|
30176
|
+
fetchCustomerAttributes(state2.customerId, state2.token, params.onError).then(({ minTemperature, maxTemperature }) => {
|
|
30177
|
+
state2.minTemperature = minTemperature;
|
|
30178
|
+
state2.maxTemperature = maxTemperature;
|
|
30179
|
+
state2.isLoading = false;
|
|
30180
|
+
renderModal3(container, state2, modalId, destroy, handleSave);
|
|
29811
30181
|
}).catch((error) => {
|
|
29812
|
-
|
|
29813
|
-
|
|
29814
|
-
renderModal3(container,
|
|
30182
|
+
state2.isLoading = false;
|
|
30183
|
+
state2.error = `Erro ao carregar: ${error.message}`;
|
|
30184
|
+
renderModal3(container, state2, modalId, destroy, handleSave);
|
|
29815
30185
|
});
|
|
29816
30186
|
return { destroy };
|
|
29817
30187
|
}
|
|
@@ -29847,11 +30217,13 @@ var ENERGY_SUMMARY_TOOLTIP_CSS = `
|
|
|
29847
30217
|
border: 1px solid #e2e8f0;
|
|
29848
30218
|
border-radius: 12px;
|
|
29849
30219
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
30220
|
+
min-width: 380px;
|
|
29850
30221
|
width: max-content;
|
|
29851
30222
|
max-width: 90vw;
|
|
29852
30223
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
29853
30224
|
font-size: 12px;
|
|
29854
30225
|
color: #1e293b;
|
|
30226
|
+
overflow: hidden;
|
|
29855
30227
|
}
|
|
29856
30228
|
|
|
29857
30229
|
.energy-summary-tooltip__header {
|
|
@@ -30157,8 +30529,7 @@ var ENERGY_SUMMARY_TOOLTIP_CSS = `
|
|
|
30157
30529
|
align-items: center;
|
|
30158
30530
|
padding: 10px 14px;
|
|
30159
30531
|
background: linear-gradient(135deg, #047857 0%, #059669 100%);
|
|
30160
|
-
|
|
30161
|
-
border-radius: 0 0 12px 12px;
|
|
30532
|
+
border-radius: 0 0 11px 11px;
|
|
30162
30533
|
}
|
|
30163
30534
|
|
|
30164
30535
|
.energy-summary-tooltip__total-label {
|
|
@@ -30301,7 +30672,7 @@ var EnergySummaryTooltip = {
|
|
|
30301
30672
|
{ key: "failure", label: "Falha", count: status.failure },
|
|
30302
30673
|
{ key: "standby", label: "Standby", count: status.standby },
|
|
30303
30674
|
{ key: "offline", label: "Offline", count: status.offline },
|
|
30304
|
-
{ key: "no-consumption", label: "Sem
|
|
30675
|
+
{ key: "no-consumption", label: "Sem Consumo", count: status.noConsumption }
|
|
30305
30676
|
];
|
|
30306
30677
|
return items.map((item) => `
|
|
30307
30678
|
<div class="energy-summary-tooltip__status-item ${item.key}">
|
|
@@ -30746,7 +31117,7 @@ var EnergySummaryTooltip = {
|
|
|
30746
31117
|
* Build summary data from TELEMETRY_INFO STATE
|
|
30747
31118
|
* This is called by the widget controller to get data for the tooltip
|
|
30748
31119
|
*/
|
|
30749
|
-
buildSummaryFromState(
|
|
31120
|
+
buildSummaryFromState(state2, receivedData) {
|
|
30750
31121
|
const summary = {
|
|
30751
31122
|
totalDevices: 0,
|
|
30752
31123
|
totalConsumption: 0,
|
|
@@ -30762,22 +31133,22 @@ var EnergySummaryTooltip = {
|
|
|
30762
31133
|
},
|
|
30763
31134
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
30764
31135
|
};
|
|
30765
|
-
if (!
|
|
31136
|
+
if (!state2) return summary;
|
|
30766
31137
|
const entrada = {
|
|
30767
31138
|
id: "entrada",
|
|
30768
31139
|
name: "Entrada",
|
|
30769
31140
|
icon: CATEGORY_ICONS.entrada,
|
|
30770
|
-
deviceCount:
|
|
30771
|
-
consumption:
|
|
31141
|
+
deviceCount: state2.entrada?.devices?.length || (receivedData?.entrada_total?.device_count || 0),
|
|
31142
|
+
consumption: state2.entrada?.total || 0,
|
|
30772
31143
|
percentage: 100
|
|
30773
31144
|
};
|
|
30774
31145
|
const lojas = {
|
|
30775
31146
|
id: "lojas",
|
|
30776
31147
|
name: "Lojas",
|
|
30777
31148
|
icon: CATEGORY_ICONS.lojas,
|
|
30778
|
-
deviceCount:
|
|
30779
|
-
consumption:
|
|
30780
|
-
percentage:
|
|
31149
|
+
deviceCount: state2.consumidores?.lojas?.devices?.length || (receivedData?.lojas_total?.device_count || 0),
|
|
31150
|
+
consumption: state2.consumidores?.lojas?.total || 0,
|
|
31151
|
+
percentage: state2.consumidores?.lojas?.perc || 0
|
|
30781
31152
|
};
|
|
30782
31153
|
const climatizacaoData = receivedData?.climatizacao || {};
|
|
30783
31154
|
const elevadoresData = receivedData?.elevadores || {};
|
|
@@ -30788,9 +31159,9 @@ var EnergySummaryTooltip = {
|
|
|
30788
31159
|
id: "climatizacao",
|
|
30789
31160
|
name: "Climatizacao",
|
|
30790
31161
|
icon: CATEGORY_ICONS.climatizacao,
|
|
30791
|
-
deviceCount: climatizacaoData.count ||
|
|
30792
|
-
consumption:
|
|
30793
|
-
percentage:
|
|
31162
|
+
deviceCount: climatizacaoData.count || state2.consumidores?.climatizacao?.devices?.length || 0,
|
|
31163
|
+
consumption: state2.consumidores?.climatizacao?.total || 0,
|
|
31164
|
+
percentage: state2.consumidores?.climatizacao?.perc || 0
|
|
30794
31165
|
};
|
|
30795
31166
|
if (climatizacaoData.subcategories) {
|
|
30796
31167
|
climatizacao.children = [];
|
|
@@ -30841,54 +31212,67 @@ var EnergySummaryTooltip = {
|
|
|
30841
31212
|
id: "elevadores",
|
|
30842
31213
|
name: "Elevadores",
|
|
30843
31214
|
icon: CATEGORY_ICONS.elevadores,
|
|
30844
|
-
deviceCount: elevadoresData.count ||
|
|
30845
|
-
consumption:
|
|
30846
|
-
percentage:
|
|
31215
|
+
deviceCount: elevadoresData.count || state2.consumidores?.elevadores?.devices?.length || 0,
|
|
31216
|
+
consumption: state2.consumidores?.elevadores?.total || 0,
|
|
31217
|
+
percentage: state2.consumidores?.elevadores?.perc || 0
|
|
30847
31218
|
});
|
|
30848
31219
|
areaComumChildren.push({
|
|
30849
31220
|
id: "escadasRolantes",
|
|
30850
31221
|
name: "Esc. Rolantes",
|
|
30851
31222
|
icon: CATEGORY_ICONS.escadas,
|
|
30852
|
-
deviceCount: escadasData.count ||
|
|
30853
|
-
consumption:
|
|
30854
|
-
percentage:
|
|
31223
|
+
deviceCount: escadasData.count || state2.consumidores?.escadasRolantes?.devices?.length || 0,
|
|
31224
|
+
consumption: state2.consumidores?.escadasRolantes?.total || 0,
|
|
31225
|
+
percentage: state2.consumidores?.escadasRolantes?.perc || 0
|
|
30855
31226
|
});
|
|
30856
31227
|
areaComumChildren.push({
|
|
30857
31228
|
id: "outros",
|
|
30858
31229
|
name: "Outros",
|
|
30859
31230
|
icon: CATEGORY_ICONS.outros,
|
|
30860
|
-
deviceCount: outrosData.count ||
|
|
30861
|
-
consumption:
|
|
30862
|
-
percentage:
|
|
31231
|
+
deviceCount: outrosData.count || state2.consumidores?.outros?.devices?.length || 0,
|
|
31232
|
+
consumption: state2.consumidores?.outros?.total || 0,
|
|
31233
|
+
percentage: state2.consumidores?.outros?.perc || 0
|
|
30863
31234
|
});
|
|
30864
31235
|
const areaComumDeviceCount = areaComumChildren.reduce((sum, c) => sum + c.deviceCount, 0);
|
|
30865
|
-
const areaComumConsumption =
|
|
31236
|
+
const areaComumConsumption = state2.consumidores?.areaComum?.total || areaComumChildren.reduce((sum, c) => sum + c.consumption, 0);
|
|
30866
31237
|
const areaComum = {
|
|
30867
31238
|
id: "areaComum",
|
|
30868
31239
|
name: "Area Comum",
|
|
30869
31240
|
icon: CATEGORY_ICONS.areaComum,
|
|
30870
31241
|
deviceCount: areaComumDeviceCount,
|
|
30871
31242
|
consumption: areaComumConsumption,
|
|
30872
|
-
percentage:
|
|
31243
|
+
percentage: state2.consumidores?.areaComum?.perc || 0,
|
|
30873
31244
|
children: areaComumChildren
|
|
30874
31245
|
};
|
|
30875
31246
|
summary.byCategory = [entrada, lojas, areaComum];
|
|
30876
31247
|
summary.totalDevices = entrada.deviceCount + lojas.deviceCount + areaComumDeviceCount;
|
|
30877
|
-
summary.totalConsumption =
|
|
31248
|
+
summary.totalConsumption = state2.grandTotal || entrada.consumption;
|
|
30878
31249
|
const totalDevices = summary.totalDevices;
|
|
30879
|
-
|
|
30880
|
-
|
|
30881
|
-
|
|
30882
|
-
|
|
30883
|
-
|
|
30884
|
-
|
|
30885
|
-
|
|
30886
|
-
|
|
30887
|
-
|
|
30888
|
-
|
|
30889
|
-
|
|
30890
|
-
|
|
30891
|
-
|
|
31250
|
+
const statusData = receivedData?.statusCounts || receivedData?.deviceStatus || null;
|
|
31251
|
+
if (statusData && typeof statusData === "object") {
|
|
31252
|
+
summary.byStatus = {
|
|
31253
|
+
normal: statusData.normal || 0,
|
|
31254
|
+
alert: statusData.alert || 0,
|
|
31255
|
+
failure: statusData.failure || 0,
|
|
31256
|
+
standby: statusData.standby || 0,
|
|
31257
|
+
offline: statusData.offline || 0,
|
|
31258
|
+
noConsumption: statusData.noConsumption || statusData.zeroConsumption || 0
|
|
31259
|
+
};
|
|
31260
|
+
} else {
|
|
31261
|
+
summary.byStatus = {
|
|
31262
|
+
normal: Math.floor(totalDevices * 0.75),
|
|
31263
|
+
// Estimate 75% normal (with consumption)
|
|
31264
|
+
alert: Math.floor(totalDevices * 0.06),
|
|
31265
|
+
// Estimate 6% alert
|
|
31266
|
+
failure: Math.floor(totalDevices * 0.02),
|
|
31267
|
+
// Estimate 2% failure
|
|
31268
|
+
standby: Math.floor(totalDevices * 0.02),
|
|
31269
|
+
// Estimate 2% standby
|
|
31270
|
+
offline: Math.floor(totalDevices * 0.03),
|
|
31271
|
+
// Estimate 3% offline
|
|
31272
|
+
noConsumption: Math.floor(totalDevices * 0.12)
|
|
31273
|
+
// Estimate 12% sem consumo
|
|
31274
|
+
};
|
|
31275
|
+
}
|
|
30892
31276
|
const statusSum = Object.values(summary.byStatus).reduce((a, b) => a + b, 0);
|
|
30893
31277
|
if (statusSum !== totalDevices && totalDevices > 0) {
|
|
30894
31278
|
summary.byStatus.normal += totalDevices - statusSum;
|
|
@@ -30897,6 +31281,654 @@ var EnergySummaryTooltip = {
|
|
|
30897
31281
|
}
|
|
30898
31282
|
};
|
|
30899
31283
|
|
|
31284
|
+
// src/utils/InfoTooltip.ts
|
|
31285
|
+
var INFO_TOOLTIP_CSS = `
|
|
31286
|
+
/* ============================================
|
|
31287
|
+
Info Tooltip (RFC-0105)
|
|
31288
|
+
Premium draggable tooltip with actions
|
|
31289
|
+
============================================ */
|
|
31290
|
+
|
|
31291
|
+
.myio-info-tooltip {
|
|
31292
|
+
position: fixed;
|
|
31293
|
+
z-index: 99999;
|
|
31294
|
+
pointer-events: none;
|
|
31295
|
+
opacity: 0;
|
|
31296
|
+
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
31297
|
+
transform: translateY(5px);
|
|
31298
|
+
}
|
|
31299
|
+
|
|
31300
|
+
.myio-info-tooltip.visible {
|
|
31301
|
+
opacity: 1;
|
|
31302
|
+
pointer-events: auto;
|
|
31303
|
+
transform: translateY(0);
|
|
31304
|
+
}
|
|
31305
|
+
|
|
31306
|
+
.myio-info-tooltip.closing {
|
|
31307
|
+
opacity: 0;
|
|
31308
|
+
transform: translateY(8px);
|
|
31309
|
+
transition: opacity 0.4s ease, transform 0.4s ease;
|
|
31310
|
+
}
|
|
31311
|
+
|
|
31312
|
+
.myio-info-tooltip.pinned {
|
|
31313
|
+
box-shadow: 0 0 0 2px #047857, 0 10px 40px rgba(0, 0, 0, 0.2);
|
|
31314
|
+
border-radius: 12px;
|
|
31315
|
+
}
|
|
31316
|
+
|
|
31317
|
+
.myio-info-tooltip.dragging {
|
|
31318
|
+
transition: none !important;
|
|
31319
|
+
cursor: move;
|
|
31320
|
+
}
|
|
31321
|
+
|
|
31322
|
+
.myio-info-tooltip.maximized {
|
|
31323
|
+
top: 20px !important;
|
|
31324
|
+
left: 20px !important;
|
|
31325
|
+
right: 20px !important;
|
|
31326
|
+
bottom: 20px !important;
|
|
31327
|
+
width: auto !important;
|
|
31328
|
+
max-width: none !important;
|
|
31329
|
+
}
|
|
31330
|
+
|
|
31331
|
+
.myio-info-tooltip.maximized .myio-info-tooltip__panel {
|
|
31332
|
+
width: 100%;
|
|
31333
|
+
height: 100%;
|
|
31334
|
+
max-width: none;
|
|
31335
|
+
display: flex;
|
|
31336
|
+
flex-direction: column;
|
|
31337
|
+
}
|
|
31338
|
+
|
|
31339
|
+
.myio-info-tooltip.maximized .myio-info-tooltip__content {
|
|
31340
|
+
flex: 1;
|
|
31341
|
+
overflow-y: auto;
|
|
31342
|
+
}
|
|
31343
|
+
|
|
31344
|
+
.myio-info-tooltip__panel {
|
|
31345
|
+
background: #ffffff;
|
|
31346
|
+
border: 1px solid #e2e8f0;
|
|
31347
|
+
border-radius: 12px;
|
|
31348
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12), 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
31349
|
+
min-width: 320px;
|
|
31350
|
+
max-width: 400px;
|
|
31351
|
+
font-size: 12px;
|
|
31352
|
+
color: #1e293b;
|
|
31353
|
+
overflow: hidden;
|
|
31354
|
+
font-family: Inter, system-ui, -apple-system, sans-serif;
|
|
31355
|
+
}
|
|
31356
|
+
|
|
31357
|
+
.myio-info-tooltip__header {
|
|
31358
|
+
display: flex;
|
|
31359
|
+
align-items: center;
|
|
31360
|
+
gap: 8px;
|
|
31361
|
+
padding: 12px 16px;
|
|
31362
|
+
background: linear-gradient(90deg, #f1f5f9 0%, #e2e8f0 100%);
|
|
31363
|
+
border-bottom: 1px solid #cbd5e1;
|
|
31364
|
+
cursor: move;
|
|
31365
|
+
user-select: none;
|
|
31366
|
+
}
|
|
31367
|
+
|
|
31368
|
+
.myio-info-tooltip__icon {
|
|
31369
|
+
font-size: 18px;
|
|
31370
|
+
}
|
|
31371
|
+
|
|
31372
|
+
.myio-info-tooltip__title {
|
|
31373
|
+
font-weight: 700;
|
|
31374
|
+
font-size: 14px;
|
|
31375
|
+
color: #475569;
|
|
31376
|
+
letter-spacing: 0.3px;
|
|
31377
|
+
flex: 1;
|
|
31378
|
+
}
|
|
31379
|
+
|
|
31380
|
+
.myio-info-tooltip__header-actions {
|
|
31381
|
+
display: flex;
|
|
31382
|
+
align-items: center;
|
|
31383
|
+
gap: 4px;
|
|
31384
|
+
}
|
|
31385
|
+
|
|
31386
|
+
.myio-info-tooltip__header-btn {
|
|
31387
|
+
width: 24px;
|
|
31388
|
+
height: 24px;
|
|
31389
|
+
border: none;
|
|
31390
|
+
background: rgba(255, 255, 255, 0.6);
|
|
31391
|
+
border-radius: 4px;
|
|
31392
|
+
cursor: pointer;
|
|
31393
|
+
display: flex;
|
|
31394
|
+
align-items: center;
|
|
31395
|
+
justify-content: center;
|
|
31396
|
+
transition: all 0.15s ease;
|
|
31397
|
+
color: #64748b;
|
|
31398
|
+
}
|
|
31399
|
+
|
|
31400
|
+
.myio-info-tooltip__header-btn:hover {
|
|
31401
|
+
background: rgba(255, 255, 255, 0.9);
|
|
31402
|
+
color: #1e293b;
|
|
31403
|
+
}
|
|
31404
|
+
|
|
31405
|
+
.myio-info-tooltip__header-btn.pinned {
|
|
31406
|
+
background: #047857;
|
|
31407
|
+
color: white;
|
|
31408
|
+
}
|
|
31409
|
+
|
|
31410
|
+
.myio-info-tooltip__header-btn.pinned:hover {
|
|
31411
|
+
background: #065f46;
|
|
31412
|
+
color: white;
|
|
31413
|
+
}
|
|
31414
|
+
|
|
31415
|
+
.myio-info-tooltip__header-btn svg {
|
|
31416
|
+
width: 14px;
|
|
31417
|
+
height: 14px;
|
|
31418
|
+
}
|
|
31419
|
+
|
|
31420
|
+
.myio-info-tooltip__content {
|
|
31421
|
+
padding: 16px;
|
|
31422
|
+
max-height: 500px;
|
|
31423
|
+
overflow-y: auto;
|
|
31424
|
+
}
|
|
31425
|
+
|
|
31426
|
+
/* Content styles */
|
|
31427
|
+
.myio-info-tooltip__section {
|
|
31428
|
+
margin-bottom: 14px;
|
|
31429
|
+
padding-bottom: 12px;
|
|
31430
|
+
border-bottom: 1px solid #f1f5f9;
|
|
31431
|
+
}
|
|
31432
|
+
|
|
31433
|
+
.myio-info-tooltip__section:last-child {
|
|
31434
|
+
margin-bottom: 0;
|
|
31435
|
+
padding-bottom: 0;
|
|
31436
|
+
border-bottom: none;
|
|
31437
|
+
}
|
|
31438
|
+
|
|
31439
|
+
.myio-info-tooltip__section-title {
|
|
31440
|
+
font-size: 11px;
|
|
31441
|
+
font-weight: 600;
|
|
31442
|
+
color: #64748b;
|
|
31443
|
+
text-transform: uppercase;
|
|
31444
|
+
letter-spacing: 0.8px;
|
|
31445
|
+
margin-bottom: 10px;
|
|
31446
|
+
display: flex;
|
|
31447
|
+
align-items: center;
|
|
31448
|
+
gap: 6px;
|
|
31449
|
+
}
|
|
31450
|
+
|
|
31451
|
+
.myio-info-tooltip__row {
|
|
31452
|
+
display: flex;
|
|
31453
|
+
justify-content: space-between;
|
|
31454
|
+
align-items: center;
|
|
31455
|
+
padding: 5px 0;
|
|
31456
|
+
gap: 12px;
|
|
31457
|
+
}
|
|
31458
|
+
|
|
31459
|
+
.myio-info-tooltip__label {
|
|
31460
|
+
color: #64748b;
|
|
31461
|
+
font-size: 12px;
|
|
31462
|
+
flex-shrink: 0;
|
|
31463
|
+
}
|
|
31464
|
+
|
|
31465
|
+
.myio-info-tooltip__value {
|
|
31466
|
+
color: #1e293b;
|
|
31467
|
+
font-weight: 600;
|
|
31468
|
+
text-align: right;
|
|
31469
|
+
}
|
|
31470
|
+
|
|
31471
|
+
.myio-info-tooltip__value--highlight {
|
|
31472
|
+
color: #10b981;
|
|
31473
|
+
font-weight: 700;
|
|
31474
|
+
font-size: 14px;
|
|
31475
|
+
}
|
|
31476
|
+
|
|
31477
|
+
.myio-info-tooltip__notice {
|
|
31478
|
+
display: flex;
|
|
31479
|
+
align-items: flex-start;
|
|
31480
|
+
gap: 10px;
|
|
31481
|
+
padding: 12px 14px;
|
|
31482
|
+
background: #f0fdf4;
|
|
31483
|
+
border: 1px solid #bbf7d0;
|
|
31484
|
+
border-radius: 8px;
|
|
31485
|
+
margin-top: 12px;
|
|
31486
|
+
}
|
|
31487
|
+
|
|
31488
|
+
.myio-info-tooltip__notice-icon {
|
|
31489
|
+
font-size: 14px;
|
|
31490
|
+
flex-shrink: 0;
|
|
31491
|
+
margin-top: 1px;
|
|
31492
|
+
}
|
|
31493
|
+
|
|
31494
|
+
.myio-info-tooltip__notice-text {
|
|
31495
|
+
font-size: 11px;
|
|
31496
|
+
color: #475569;
|
|
31497
|
+
line-height: 1.5;
|
|
31498
|
+
}
|
|
31499
|
+
|
|
31500
|
+
.myio-info-tooltip__notice-text strong {
|
|
31501
|
+
font-weight: 700;
|
|
31502
|
+
color: #334155;
|
|
31503
|
+
}
|
|
31504
|
+
|
|
31505
|
+
.myio-info-tooltip__category {
|
|
31506
|
+
display: flex;
|
|
31507
|
+
align-items: center;
|
|
31508
|
+
gap: 10px;
|
|
31509
|
+
padding: 8px 12px;
|
|
31510
|
+
background: #f8fafc;
|
|
31511
|
+
border-radius: 8px;
|
|
31512
|
+
margin-bottom: 6px;
|
|
31513
|
+
border-left: 3px solid #94a3b8;
|
|
31514
|
+
}
|
|
31515
|
+
|
|
31516
|
+
.myio-info-tooltip__category:last-child {
|
|
31517
|
+
margin-bottom: 0;
|
|
31518
|
+
}
|
|
31519
|
+
|
|
31520
|
+
.myio-info-tooltip__category--climatizacao {
|
|
31521
|
+
border-left-color: #00C896;
|
|
31522
|
+
background: #ecfdf5;
|
|
31523
|
+
}
|
|
31524
|
+
|
|
31525
|
+
.myio-info-tooltip__category--outros {
|
|
31526
|
+
border-left-color: #9C27B0;
|
|
31527
|
+
background: #fdf4ff;
|
|
31528
|
+
}
|
|
31529
|
+
|
|
31530
|
+
.myio-info-tooltip__category-icon {
|
|
31531
|
+
font-size: 14px;
|
|
31532
|
+
flex-shrink: 0;
|
|
31533
|
+
}
|
|
31534
|
+
|
|
31535
|
+
.myio-info-tooltip__category-info {
|
|
31536
|
+
flex: 1;
|
|
31537
|
+
}
|
|
31538
|
+
|
|
31539
|
+
.myio-info-tooltip__category-name {
|
|
31540
|
+
font-weight: 600;
|
|
31541
|
+
color: #334155;
|
|
31542
|
+
font-size: 12px;
|
|
31543
|
+
}
|
|
31544
|
+
|
|
31545
|
+
.myio-info-tooltip__category-desc {
|
|
31546
|
+
font-size: 10px;
|
|
31547
|
+
color: #64748b;
|
|
31548
|
+
margin-top: 2px;
|
|
31549
|
+
}
|
|
31550
|
+
|
|
31551
|
+
.myio-info-tooltip__category-value {
|
|
31552
|
+
font-weight: 700;
|
|
31553
|
+
color: #334155;
|
|
31554
|
+
font-size: 13px;
|
|
31555
|
+
}
|
|
31556
|
+
`;
|
|
31557
|
+
var cssInjected4 = false;
|
|
31558
|
+
function injectCSS4() {
|
|
31559
|
+
if (cssInjected4) return;
|
|
31560
|
+
if (typeof document === "undefined") return;
|
|
31561
|
+
const styleId = "myio-info-tooltip-styles";
|
|
31562
|
+
if (document.getElementById(styleId)) {
|
|
31563
|
+
cssInjected4 = true;
|
|
31564
|
+
return;
|
|
31565
|
+
}
|
|
31566
|
+
const style = document.createElement("style");
|
|
31567
|
+
style.id = styleId;
|
|
31568
|
+
style.textContent = INFO_TOOLTIP_CSS;
|
|
31569
|
+
document.head.appendChild(style);
|
|
31570
|
+
cssInjected4 = true;
|
|
31571
|
+
}
|
|
31572
|
+
var state = {
|
|
31573
|
+
hideTimer: null,
|
|
31574
|
+
isMouseOverTooltip: false,
|
|
31575
|
+
isMaximized: false,
|
|
31576
|
+
isDragging: false,
|
|
31577
|
+
dragOffset: { x: 0, y: 0 },
|
|
31578
|
+
savedPosition: null,
|
|
31579
|
+
pinnedCounter: 0
|
|
31580
|
+
};
|
|
31581
|
+
function generateHeaderHTML(icon, title) {
|
|
31582
|
+
return `
|
|
31583
|
+
<div class="myio-info-tooltip__header" data-drag-handle>
|
|
31584
|
+
<span class="myio-info-tooltip__icon">${icon}</span>
|
|
31585
|
+
<span class="myio-info-tooltip__title">${title}</span>
|
|
31586
|
+
<div class="myio-info-tooltip__header-actions">
|
|
31587
|
+
<button class="myio-info-tooltip__header-btn" data-action="pin" title="Fixar na tela">
|
|
31588
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
31589
|
+
<path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
|
|
31590
|
+
<line x1="12" y1="16" x2="12" y2="21"/>
|
|
31591
|
+
<line x1="8" y1="4" x2="16" y2="4"/>
|
|
31592
|
+
</svg>
|
|
31593
|
+
</button>
|
|
31594
|
+
<button class="myio-info-tooltip__header-btn" data-action="maximize" title="Maximizar">
|
|
31595
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
31596
|
+
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
|
31597
|
+
</svg>
|
|
31598
|
+
</button>
|
|
31599
|
+
<button class="myio-info-tooltip__header-btn" data-action="close" title="Fechar">
|
|
31600
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
31601
|
+
<path d="M18 6L6 18M6 6l12 12"/>
|
|
31602
|
+
</svg>
|
|
31603
|
+
</button>
|
|
31604
|
+
</div>
|
|
31605
|
+
</div>
|
|
31606
|
+
`;
|
|
31607
|
+
}
|
|
31608
|
+
function setupHoverListeners(container) {
|
|
31609
|
+
container.onmouseenter = () => {
|
|
31610
|
+
state.isMouseOverTooltip = true;
|
|
31611
|
+
if (state.hideTimer) {
|
|
31612
|
+
clearTimeout(state.hideTimer);
|
|
31613
|
+
state.hideTimer = null;
|
|
31614
|
+
}
|
|
31615
|
+
};
|
|
31616
|
+
container.onmouseleave = () => {
|
|
31617
|
+
state.isMouseOverTooltip = false;
|
|
31618
|
+
startDelayedHide();
|
|
31619
|
+
};
|
|
31620
|
+
}
|
|
31621
|
+
function setupButtonListeners(container) {
|
|
31622
|
+
const buttons = container.querySelectorAll("[data-action]");
|
|
31623
|
+
buttons.forEach((btn) => {
|
|
31624
|
+
btn.onclick = (e) => {
|
|
31625
|
+
e.stopPropagation();
|
|
31626
|
+
const action = btn.dataset.action;
|
|
31627
|
+
switch (action) {
|
|
31628
|
+
case "pin":
|
|
31629
|
+
createPinnedClone(container);
|
|
31630
|
+
break;
|
|
31631
|
+
case "maximize":
|
|
31632
|
+
toggleMaximize(container);
|
|
31633
|
+
break;
|
|
31634
|
+
case "close":
|
|
31635
|
+
InfoTooltip.close();
|
|
31636
|
+
break;
|
|
31637
|
+
}
|
|
31638
|
+
};
|
|
31639
|
+
});
|
|
31640
|
+
}
|
|
31641
|
+
function setupDragListeners(container) {
|
|
31642
|
+
const header = container.querySelector("[data-drag-handle]");
|
|
31643
|
+
if (!header) return;
|
|
31644
|
+
header.onmousedown = (e) => {
|
|
31645
|
+
if (e.target.closest("[data-action]")) return;
|
|
31646
|
+
if (state.isMaximized) return;
|
|
31647
|
+
state.isDragging = true;
|
|
31648
|
+
container.classList.add("dragging");
|
|
31649
|
+
const rect = container.getBoundingClientRect();
|
|
31650
|
+
state.dragOffset = {
|
|
31651
|
+
x: e.clientX - rect.left,
|
|
31652
|
+
y: e.clientY - rect.top
|
|
31653
|
+
};
|
|
31654
|
+
const onMouseMove = (e2) => {
|
|
31655
|
+
if (!state.isDragging) return;
|
|
31656
|
+
const newLeft = e2.clientX - state.dragOffset.x;
|
|
31657
|
+
const newTop = e2.clientY - state.dragOffset.y;
|
|
31658
|
+
const maxLeft = window.innerWidth - container.offsetWidth;
|
|
31659
|
+
const maxTop = window.innerHeight - container.offsetHeight;
|
|
31660
|
+
container.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
|
|
31661
|
+
container.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
|
|
31662
|
+
};
|
|
31663
|
+
const onMouseUp = () => {
|
|
31664
|
+
state.isDragging = false;
|
|
31665
|
+
container.classList.remove("dragging");
|
|
31666
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
31667
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
31668
|
+
};
|
|
31669
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
31670
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
31671
|
+
};
|
|
31672
|
+
}
|
|
31673
|
+
function createPinnedClone(container) {
|
|
31674
|
+
state.pinnedCounter++;
|
|
31675
|
+
const pinnedId = `myio-info-tooltip-pinned-${state.pinnedCounter}`;
|
|
31676
|
+
const clone = container.cloneNode(true);
|
|
31677
|
+
clone.id = pinnedId;
|
|
31678
|
+
clone.classList.add("pinned");
|
|
31679
|
+
clone.classList.remove("closing");
|
|
31680
|
+
const pinBtn = clone.querySelector('[data-action="pin"]');
|
|
31681
|
+
if (pinBtn) {
|
|
31682
|
+
pinBtn.classList.add("pinned");
|
|
31683
|
+
pinBtn.setAttribute("title", "Desafixar");
|
|
31684
|
+
pinBtn.innerHTML = `
|
|
31685
|
+
<svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1">
|
|
31686
|
+
<path d="M9 4v6l-2 4v2h10v-2l-2-4V4"/>
|
|
31687
|
+
<line x1="12" y1="16" x2="12" y2="21"/>
|
|
31688
|
+
<line x1="8" y1="4" x2="16" y2="4"/>
|
|
31689
|
+
</svg>
|
|
31690
|
+
`;
|
|
31691
|
+
}
|
|
31692
|
+
document.body.appendChild(clone);
|
|
31693
|
+
setupPinnedCloneListeners(clone, pinnedId);
|
|
31694
|
+
InfoTooltip.hide();
|
|
31695
|
+
}
|
|
31696
|
+
function setupPinnedCloneListeners(clone, cloneId) {
|
|
31697
|
+
let isMaximized = false;
|
|
31698
|
+
let savedPosition = null;
|
|
31699
|
+
const pinBtn = clone.querySelector('[data-action="pin"]');
|
|
31700
|
+
if (pinBtn) {
|
|
31701
|
+
pinBtn.onclick = (e) => {
|
|
31702
|
+
e.stopPropagation();
|
|
31703
|
+
closePinnedClone(cloneId);
|
|
31704
|
+
};
|
|
31705
|
+
}
|
|
31706
|
+
const closeBtn = clone.querySelector('[data-action="close"]');
|
|
31707
|
+
if (closeBtn) {
|
|
31708
|
+
closeBtn.onclick = (e) => {
|
|
31709
|
+
e.stopPropagation();
|
|
31710
|
+
closePinnedClone(cloneId);
|
|
31711
|
+
};
|
|
31712
|
+
}
|
|
31713
|
+
const maxBtn = clone.querySelector('[data-action="maximize"]');
|
|
31714
|
+
if (maxBtn) {
|
|
31715
|
+
maxBtn.onclick = (e) => {
|
|
31716
|
+
e.stopPropagation();
|
|
31717
|
+
isMaximized = !isMaximized;
|
|
31718
|
+
if (isMaximized) {
|
|
31719
|
+
savedPosition = { left: clone.style.left, top: clone.style.top };
|
|
31720
|
+
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>`;
|
|
31721
|
+
maxBtn.setAttribute("title", "Restaurar");
|
|
31722
|
+
} else {
|
|
31723
|
+
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>`;
|
|
31724
|
+
maxBtn.setAttribute("title", "Maximizar");
|
|
31725
|
+
if (savedPosition) {
|
|
31726
|
+
clone.style.left = savedPosition.left;
|
|
31727
|
+
clone.style.top = savedPosition.top;
|
|
31728
|
+
}
|
|
31729
|
+
}
|
|
31730
|
+
clone.classList.toggle("maximized", isMaximized);
|
|
31731
|
+
};
|
|
31732
|
+
}
|
|
31733
|
+
const header = clone.querySelector("[data-drag-handle]");
|
|
31734
|
+
if (header) {
|
|
31735
|
+
let isDragging = false;
|
|
31736
|
+
let dragOffset = { x: 0, y: 0 };
|
|
31737
|
+
header.onmousedown = (e) => {
|
|
31738
|
+
if (e.target.closest("[data-action]")) return;
|
|
31739
|
+
if (isMaximized) return;
|
|
31740
|
+
isDragging = true;
|
|
31741
|
+
clone.classList.add("dragging");
|
|
31742
|
+
const rect = clone.getBoundingClientRect();
|
|
31743
|
+
dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
31744
|
+
const onMouseMove = (e2) => {
|
|
31745
|
+
if (!isDragging) return;
|
|
31746
|
+
const newLeft = e2.clientX - dragOffset.x;
|
|
31747
|
+
const newTop = e2.clientY - dragOffset.y;
|
|
31748
|
+
const maxLeft = window.innerWidth - clone.offsetWidth;
|
|
31749
|
+
const maxTop = window.innerHeight - clone.offsetHeight;
|
|
31750
|
+
clone.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + "px";
|
|
31751
|
+
clone.style.top = Math.max(0, Math.min(newTop, maxTop)) + "px";
|
|
31752
|
+
};
|
|
31753
|
+
const onMouseUp = () => {
|
|
31754
|
+
isDragging = false;
|
|
31755
|
+
clone.classList.remove("dragging");
|
|
31756
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
31757
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
31758
|
+
};
|
|
31759
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
31760
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
31761
|
+
};
|
|
31762
|
+
}
|
|
31763
|
+
}
|
|
31764
|
+
function closePinnedClone(cloneId) {
|
|
31765
|
+
const clone = document.getElementById(cloneId);
|
|
31766
|
+
if (clone) {
|
|
31767
|
+
clone.classList.add("closing");
|
|
31768
|
+
setTimeout(() => clone.remove(), 400);
|
|
31769
|
+
}
|
|
31770
|
+
}
|
|
31771
|
+
function toggleMaximize(container) {
|
|
31772
|
+
state.isMaximized = !state.isMaximized;
|
|
31773
|
+
if (state.isMaximized) {
|
|
31774
|
+
state.savedPosition = {
|
|
31775
|
+
left: container.style.left,
|
|
31776
|
+
top: container.style.top
|
|
31777
|
+
};
|
|
31778
|
+
}
|
|
31779
|
+
container.classList.toggle("maximized", state.isMaximized);
|
|
31780
|
+
const maxBtn = container.querySelector('[data-action="maximize"]');
|
|
31781
|
+
if (maxBtn) {
|
|
31782
|
+
if (state.isMaximized) {
|
|
31783
|
+
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>`;
|
|
31784
|
+
maxBtn.setAttribute("title", "Restaurar");
|
|
31785
|
+
} else {
|
|
31786
|
+
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>`;
|
|
31787
|
+
maxBtn.setAttribute("title", "Maximizar");
|
|
31788
|
+
if (state.savedPosition) {
|
|
31789
|
+
container.style.left = state.savedPosition.left;
|
|
31790
|
+
container.style.top = state.savedPosition.top;
|
|
31791
|
+
}
|
|
31792
|
+
}
|
|
31793
|
+
}
|
|
31794
|
+
}
|
|
31795
|
+
function startDelayedHide() {
|
|
31796
|
+
if (state.isMouseOverTooltip) return;
|
|
31797
|
+
if (state.hideTimer) {
|
|
31798
|
+
clearTimeout(state.hideTimer);
|
|
31799
|
+
}
|
|
31800
|
+
state.hideTimer = setTimeout(() => {
|
|
31801
|
+
hideWithAnimation();
|
|
31802
|
+
}, 1500);
|
|
31803
|
+
}
|
|
31804
|
+
function hideWithAnimation() {
|
|
31805
|
+
const container = document.getElementById("myio-info-tooltip");
|
|
31806
|
+
if (container && container.classList.contains("visible")) {
|
|
31807
|
+
container.classList.add("closing");
|
|
31808
|
+
setTimeout(() => {
|
|
31809
|
+
container.classList.remove("visible", "closing");
|
|
31810
|
+
}, 400);
|
|
31811
|
+
}
|
|
31812
|
+
}
|
|
31813
|
+
function positionTooltip(container, triggerElement) {
|
|
31814
|
+
const rect = triggerElement.getBoundingClientRect();
|
|
31815
|
+
let left = rect.left;
|
|
31816
|
+
let top = rect.bottom + 8;
|
|
31817
|
+
const tooltipWidth = 380;
|
|
31818
|
+
if (left + tooltipWidth > window.innerWidth - 20) {
|
|
31819
|
+
left = window.innerWidth - tooltipWidth - 20;
|
|
31820
|
+
}
|
|
31821
|
+
if (left < 10) left = 10;
|
|
31822
|
+
if (top + 400 > window.innerHeight) {
|
|
31823
|
+
top = rect.top - 8 - 400;
|
|
31824
|
+
if (top < 10) top = 10;
|
|
31825
|
+
}
|
|
31826
|
+
container.style.left = left + "px";
|
|
31827
|
+
container.style.top = top + "px";
|
|
31828
|
+
}
|
|
31829
|
+
var InfoTooltip = {
|
|
31830
|
+
containerId: "myio-info-tooltip",
|
|
31831
|
+
/**
|
|
31832
|
+
* Get or create container
|
|
31833
|
+
*/
|
|
31834
|
+
getContainer() {
|
|
31835
|
+
injectCSS4();
|
|
31836
|
+
let container = document.getElementById(this.containerId);
|
|
31837
|
+
if (!container) {
|
|
31838
|
+
container = document.createElement("div");
|
|
31839
|
+
container.id = this.containerId;
|
|
31840
|
+
container.className = "myio-info-tooltip";
|
|
31841
|
+
document.body.appendChild(container);
|
|
31842
|
+
}
|
|
31843
|
+
return container;
|
|
31844
|
+
},
|
|
31845
|
+
/**
|
|
31846
|
+
* Show tooltip
|
|
31847
|
+
*/
|
|
31848
|
+
show(triggerElement, options) {
|
|
31849
|
+
if (state.hideTimer) {
|
|
31850
|
+
clearTimeout(state.hideTimer);
|
|
31851
|
+
state.hideTimer = null;
|
|
31852
|
+
}
|
|
31853
|
+
const container = this.getContainer();
|
|
31854
|
+
container.classList.remove("closing");
|
|
31855
|
+
container.innerHTML = `
|
|
31856
|
+
<div class="myio-info-tooltip__panel">
|
|
31857
|
+
${generateHeaderHTML(options.icon, options.title)}
|
|
31858
|
+
<div class="myio-info-tooltip__content">
|
|
31859
|
+
${options.content}
|
|
31860
|
+
</div>
|
|
31861
|
+
</div>
|
|
31862
|
+
`;
|
|
31863
|
+
positionTooltip(container, triggerElement);
|
|
31864
|
+
container.classList.add("visible");
|
|
31865
|
+
setupHoverListeners(container);
|
|
31866
|
+
setupButtonListeners(container);
|
|
31867
|
+
setupDragListeners(container);
|
|
31868
|
+
},
|
|
31869
|
+
/**
|
|
31870
|
+
* Start delayed hide
|
|
31871
|
+
*/
|
|
31872
|
+
startDelayedHide() {
|
|
31873
|
+
startDelayedHide();
|
|
31874
|
+
},
|
|
31875
|
+
/**
|
|
31876
|
+
* Hide immediately
|
|
31877
|
+
*/
|
|
31878
|
+
hide() {
|
|
31879
|
+
if (state.hideTimer) {
|
|
31880
|
+
clearTimeout(state.hideTimer);
|
|
31881
|
+
state.hideTimer = null;
|
|
31882
|
+
}
|
|
31883
|
+
state.isMouseOverTooltip = false;
|
|
31884
|
+
const container = document.getElementById(this.containerId);
|
|
31885
|
+
if (container) {
|
|
31886
|
+
container.classList.remove("visible", "closing");
|
|
31887
|
+
}
|
|
31888
|
+
},
|
|
31889
|
+
/**
|
|
31890
|
+
* Close and reset all states
|
|
31891
|
+
*/
|
|
31892
|
+
close() {
|
|
31893
|
+
state.isMaximized = false;
|
|
31894
|
+
state.isDragging = false;
|
|
31895
|
+
state.savedPosition = null;
|
|
31896
|
+
if (state.hideTimer) {
|
|
31897
|
+
clearTimeout(state.hideTimer);
|
|
31898
|
+
state.hideTimer = null;
|
|
31899
|
+
}
|
|
31900
|
+
state.isMouseOverTooltip = false;
|
|
31901
|
+
const container = document.getElementById(this.containerId);
|
|
31902
|
+
if (container) {
|
|
31903
|
+
container.classList.remove("visible", "pinned", "maximized", "dragging", "closing");
|
|
31904
|
+
}
|
|
31905
|
+
},
|
|
31906
|
+
/**
|
|
31907
|
+
* Attach tooltip to trigger element with hover behavior
|
|
31908
|
+
*/
|
|
31909
|
+
attach(triggerElement, getOptions) {
|
|
31910
|
+
const self = this;
|
|
31911
|
+
const handleMouseEnter = () => {
|
|
31912
|
+
if (state.hideTimer) {
|
|
31913
|
+
clearTimeout(state.hideTimer);
|
|
31914
|
+
state.hideTimer = null;
|
|
31915
|
+
}
|
|
31916
|
+
const options = getOptions();
|
|
31917
|
+
self.show(triggerElement, options);
|
|
31918
|
+
};
|
|
31919
|
+
const handleMouseLeave = () => {
|
|
31920
|
+
startDelayedHide();
|
|
31921
|
+
};
|
|
31922
|
+
triggerElement.addEventListener("mouseenter", handleMouseEnter);
|
|
31923
|
+
triggerElement.addEventListener("mouseleave", handleMouseLeave);
|
|
31924
|
+
return () => {
|
|
31925
|
+
triggerElement.removeEventListener("mouseenter", handleMouseEnter);
|
|
31926
|
+
triggerElement.removeEventListener("mouseleave", handleMouseLeave);
|
|
31927
|
+
self.hide();
|
|
31928
|
+
};
|
|
31929
|
+
}
|
|
31930
|
+
};
|
|
31931
|
+
|
|
30900
31932
|
// src/components/ModalHeader/index.ts
|
|
30901
31933
|
var DEFAULT_BG_COLOR = "#3e1a7d";
|
|
30902
31934
|
var DEFAULT_TEXT_COLOR = "white";
|
|
@@ -34676,6 +35708,7 @@ function createDistributionChartWidget(config) {
|
|
|
34676
35708
|
IMPORTANCE_COLORS,
|
|
34677
35709
|
IMPORTANCE_LABELS,
|
|
34678
35710
|
IMPORTANCE_LABELS_EN,
|
|
35711
|
+
InfoTooltip,
|
|
34679
35712
|
MyIOChartModal,
|
|
34680
35713
|
MyIODraggableCard,
|
|
34681
35714
|
MyIOSelectionStore,
|