myio-js-library 0.1.161 → 0.1.162
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 +1869 -14
- package/dist/index.d.cts +889 -2
- package/dist/index.js +1848 -14
- package/dist/myio-js-library.umd.js +1847 -13
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
|
@@ -650,6 +650,10 @@
|
|
|
650
650
|
}
|
|
651
651
|
|
|
652
652
|
// src/format/water.ts
|
|
653
|
+
function formatWater(value) {
|
|
654
|
+
const num = Number(value) || 0;
|
|
655
|
+
return `${num.toFixed(2)} m\xB3`;
|
|
656
|
+
}
|
|
653
657
|
function formatWaterVolumeM3(value, locale = "pt-BR") {
|
|
654
658
|
if (value === null || value === void 0 || isNaN(value)) {
|
|
655
659
|
return "-";
|
|
@@ -727,6 +731,76 @@
|
|
|
727
731
|
};
|
|
728
732
|
}
|
|
729
733
|
|
|
734
|
+
// src/format/time.ts
|
|
735
|
+
function formatRelativeTime(timestamp) {
|
|
736
|
+
if (!timestamp || timestamp <= 0) {
|
|
737
|
+
return "\u2014";
|
|
738
|
+
}
|
|
739
|
+
const now = Date.now();
|
|
740
|
+
const diffSeconds = Math.round((now - timestamp) / 1e3);
|
|
741
|
+
if (diffSeconds < 10) {
|
|
742
|
+
return "agora";
|
|
743
|
+
}
|
|
744
|
+
if (diffSeconds < 60) {
|
|
745
|
+
return `h\xE1 ${diffSeconds}s`;
|
|
746
|
+
}
|
|
747
|
+
const diffMinutes = Math.round(diffSeconds / 60);
|
|
748
|
+
if (diffMinutes === 1) {
|
|
749
|
+
return "h\xE1 1 min";
|
|
750
|
+
}
|
|
751
|
+
if (diffMinutes < 60) {
|
|
752
|
+
return `h\xE1 ${diffMinutes} mins`;
|
|
753
|
+
}
|
|
754
|
+
const diffHours = Math.round(diffMinutes / 60);
|
|
755
|
+
if (diffHours === 1) {
|
|
756
|
+
return "h\xE1 1 hora";
|
|
757
|
+
}
|
|
758
|
+
if (diffHours < 24) {
|
|
759
|
+
return `h\xE1 ${diffHours} horas`;
|
|
760
|
+
}
|
|
761
|
+
const diffDays = Math.round(diffHours / 24);
|
|
762
|
+
if (diffDays === 1) {
|
|
763
|
+
return "ontem";
|
|
764
|
+
}
|
|
765
|
+
if (diffDays <= 30) {
|
|
766
|
+
return `h\xE1 ${diffDays} dias`;
|
|
767
|
+
}
|
|
768
|
+
return new Date(timestamp).toLocaleDateString("pt-BR");
|
|
769
|
+
}
|
|
770
|
+
function formatarDuracao(ms) {
|
|
771
|
+
if (typeof ms !== "number" || ms < 0 || !isFinite(ms)) {
|
|
772
|
+
return "0s";
|
|
773
|
+
}
|
|
774
|
+
if (ms === 0) {
|
|
775
|
+
return "0s";
|
|
776
|
+
}
|
|
777
|
+
const segundos = Math.floor(ms / 1e3 % 60);
|
|
778
|
+
const minutos = Math.floor(ms / (1e3 * 60) % 60);
|
|
779
|
+
const horas = Math.floor(ms / (1e3 * 60 * 60) % 24);
|
|
780
|
+
const dias = Math.floor(ms / (1e3 * 60 * 60 * 24));
|
|
781
|
+
const parts = [];
|
|
782
|
+
if (dias > 0) {
|
|
783
|
+
parts.push(`${dias}d`);
|
|
784
|
+
if (horas > 0) {
|
|
785
|
+
parts.push(`${horas}h`);
|
|
786
|
+
}
|
|
787
|
+
} else if (horas > 0) {
|
|
788
|
+
parts.push(`${horas}h`);
|
|
789
|
+
if (minutos > 0) {
|
|
790
|
+
parts.push(`${minutos}m`);
|
|
791
|
+
}
|
|
792
|
+
} else if (minutos > 0) {
|
|
793
|
+
parts.push(`${minutos}m`);
|
|
794
|
+
if (segundos > 0) {
|
|
795
|
+
parts.push(`${segundos}s`);
|
|
796
|
+
}
|
|
797
|
+
} else {
|
|
798
|
+
parts.push(`${segundos}s`);
|
|
799
|
+
}
|
|
800
|
+
return parts.length > 0 ? parts.join(" ") : "0s";
|
|
801
|
+
}
|
|
802
|
+
var formatDuration = formatarDuracao;
|
|
803
|
+
|
|
730
804
|
// src/date/ymd.ts
|
|
731
805
|
function formatDateToYMD(date) {
|
|
732
806
|
if (!date) {
|
|
@@ -1235,6 +1309,11 @@
|
|
|
1235
1309
|
}
|
|
1236
1310
|
return getValueByDatakey(data, keyOrPath);
|
|
1237
1311
|
}
|
|
1312
|
+
function findValueWithDefault(values, key, defaultValue = null) {
|
|
1313
|
+
if (!Array.isArray(values)) return defaultValue;
|
|
1314
|
+
const found = values.find((v) => v.key === key || v.dataType === key);
|
|
1315
|
+
return found ? found.value : defaultValue;
|
|
1316
|
+
}
|
|
1238
1317
|
|
|
1239
1318
|
// src/utils/deviceStatus.js
|
|
1240
1319
|
var DeviceStatusType = {
|
|
@@ -1291,6 +1370,16 @@
|
|
|
1291
1370
|
}
|
|
1292
1371
|
return ConnectionStatusType.CONNECTED;
|
|
1293
1372
|
}
|
|
1373
|
+
function mapConnectionStatus(rawStatus) {
|
|
1374
|
+
const statusLower = String(rawStatus || "").toLowerCase().trim();
|
|
1375
|
+
if (statusLower === "online" || statusLower === "ok" || statusLower === "running") {
|
|
1376
|
+
return "online";
|
|
1377
|
+
}
|
|
1378
|
+
if (statusLower === "waiting" || statusLower === "connecting" || statusLower === "pending") {
|
|
1379
|
+
return "waiting";
|
|
1380
|
+
}
|
|
1381
|
+
return "offline";
|
|
1382
|
+
}
|
|
1294
1383
|
function mapDeviceStatusToCardStatus(deviceStatus) {
|
|
1295
1384
|
const statusMap = {
|
|
1296
1385
|
[DeviceStatusType.POWER_ON]: "ok",
|
|
@@ -7690,14 +7779,14 @@ ${rangeText}`;
|
|
|
7690
7779
|
return `${value.toFixed(config.decimals)} ${config.unit}`;
|
|
7691
7780
|
}
|
|
7692
7781
|
function initializeChart() {
|
|
7693
|
-
const
|
|
7694
|
-
if (!
|
|
7782
|
+
const Chart2 = window.Chart;
|
|
7783
|
+
if (!Chart2) {
|
|
7695
7784
|
console.warn("[RealTimeTelemetry] Chart.js not loaded");
|
|
7696
7785
|
return;
|
|
7697
7786
|
}
|
|
7698
7787
|
chartContainer.style.display = "block";
|
|
7699
7788
|
const config = TELEMETRY_CONFIG[selectedChartKey] || { label: selectedChartKey, unit: "" };
|
|
7700
|
-
chart = new
|
|
7789
|
+
chart = new Chart2(chartCanvas, {
|
|
7701
7790
|
type: "line",
|
|
7702
7791
|
data: {
|
|
7703
7792
|
datasets: [{
|
|
@@ -10562,8 +10651,8 @@ ${rangeText}`;
|
|
|
10562
10651
|
peakEl.textContent = `${strings.maximum}: ${peak.formattedValue} kW ${peak.key ? `(${peak.key}) ` : ""}${strings.at} ${dateStr}`;
|
|
10563
10652
|
peakEl.style.display = "block";
|
|
10564
10653
|
}
|
|
10565
|
-
const
|
|
10566
|
-
|
|
10654
|
+
const Chart2 = window.Chart;
|
|
10655
|
+
Chart2.register(window.ChartZoom);
|
|
10567
10656
|
if (chart) {
|
|
10568
10657
|
chart.data.datasets = chartData.series.map((series) => ({
|
|
10569
10658
|
label: series.label,
|
|
@@ -10615,7 +10704,7 @@ ${rangeText}`;
|
|
|
10615
10704
|
};
|
|
10616
10705
|
chart.update();
|
|
10617
10706
|
} else {
|
|
10618
|
-
chart = new
|
|
10707
|
+
chart = new Chart2(chartCanvas, {
|
|
10619
10708
|
type: "line",
|
|
10620
10709
|
data: {
|
|
10621
10710
|
datasets: chartData.series.map((series) => ({
|
|
@@ -18747,8 +18836,8 @@ ${rangeText}`;
|
|
|
18747
18836
|
<div class="myio-goals-progress-fill" style="width: ${Math.min(progress, 100)}%"></div>
|
|
18748
18837
|
</div>
|
|
18749
18838
|
<div class="myio-goals-progress-text">
|
|
18750
|
-
<span>${
|
|
18751
|
-
<span>${
|
|
18839
|
+
<span>${formatNumber3(monthlySum, locale)} ${annual.unit}</span>
|
|
18840
|
+
<span>${formatNumber3(annual.total, locale)} ${annual.unit}</span>
|
|
18752
18841
|
</div>
|
|
18753
18842
|
|
|
18754
18843
|
<!-- Monthly Grid -->
|
|
@@ -18822,7 +18911,7 @@ ${rangeText}`;
|
|
|
18822
18911
|
<span>${assetData.label || assetId}</span>
|
|
18823
18912
|
</div>
|
|
18824
18913
|
<div class="myio-goals-asset-total">
|
|
18825
|
-
${
|
|
18914
|
+
${formatNumber3(assetData.annual?.total || 0, locale)} ${assetData.annual?.unit || "kWh"}
|
|
18826
18915
|
</div>
|
|
18827
18916
|
<button class="myio-goals-btn-icon" data-action="delete-asset" data-asset-id="${assetId}" aria-label="${i18n.deleteAsset}">
|
|
18828
18917
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -19137,7 +19226,7 @@ ${rangeText}`;
|
|
|
19137
19226
|
monthlySum += value;
|
|
19138
19227
|
}
|
|
19139
19228
|
if (monthlySum > annualTotal && annualTotal > 0) {
|
|
19140
|
-
errors.push(`${i18n.errorMonthlyExceedsAnnual} (${
|
|
19229
|
+
errors.push(`${i18n.errorMonthlyExceedsAnnual} (${formatNumber3(monthlySum, locale)} > ${formatNumber3(annualTotal, locale)})`);
|
|
19141
19230
|
}
|
|
19142
19231
|
}
|
|
19143
19232
|
return errors;
|
|
@@ -19228,8 +19317,8 @@ ${rangeText}`;
|
|
|
19228
19317
|
}
|
|
19229
19318
|
if (progressTexts.length === 2) {
|
|
19230
19319
|
const unit = document.getElementById("unit-select")?.value || "kWh";
|
|
19231
|
-
progressTexts[0].textContent = `${
|
|
19232
|
-
progressTexts[1].textContent = `${
|
|
19320
|
+
progressTexts[0].textContent = `${formatNumber3(monthlySum, locale)} ${unit}`;
|
|
19321
|
+
progressTexts[1].textContent = `${formatNumber3(annualTotal, locale)} ${unit}`;
|
|
19233
19322
|
}
|
|
19234
19323
|
}
|
|
19235
19324
|
function updateMonthlyUnits(unit) {
|
|
@@ -19327,7 +19416,7 @@ ${rangeText}`;
|
|
|
19327
19416
|
modal.addEventListener("keydown", handleTab);
|
|
19328
19417
|
firstElement.focus();
|
|
19329
19418
|
}
|
|
19330
|
-
function
|
|
19419
|
+
function formatNumber3(value, locale2) {
|
|
19331
19420
|
return new Intl.NumberFormat(locale2, {
|
|
19332
19421
|
minimumFractionDigits: 0,
|
|
19333
19422
|
maximumFractionDigits: 2
|
|
@@ -22278,10 +22367,1741 @@ ${rangeText}`;
|
|
|
22278
22367
|
return { destroy };
|
|
22279
22368
|
}
|
|
22280
22369
|
|
|
22370
|
+
// src/components/ModalHeader/index.ts
|
|
22371
|
+
var DEFAULT_BG_COLOR = "#3e1a7d";
|
|
22372
|
+
var DEFAULT_TEXT_COLOR = "white";
|
|
22373
|
+
var DEFAULT_BORDER_RADIUS = "10px 10px 0 0";
|
|
22374
|
+
var EXPORT_FORMAT_LABELS = {
|
|
22375
|
+
csv: "CSV",
|
|
22376
|
+
xls: "Excel (XLS)",
|
|
22377
|
+
pdf: "PDF"
|
|
22378
|
+
};
|
|
22379
|
+
var EXPORT_FORMAT_ICONS = {
|
|
22380
|
+
csv: "\u{1F4C4}",
|
|
22381
|
+
xls: "\u{1F4CA}",
|
|
22382
|
+
pdf: "\u{1F4D1}"
|
|
22383
|
+
};
|
|
22384
|
+
function createModalHeader(config) {
|
|
22385
|
+
let currentTheme = config.theme || "light";
|
|
22386
|
+
let currentIsMaximized = config.isMaximized || false;
|
|
22387
|
+
let currentTitle = config.title;
|
|
22388
|
+
let themeBtn = null;
|
|
22389
|
+
let maximizeBtn = null;
|
|
22390
|
+
let closeBtn = null;
|
|
22391
|
+
let exportBtn = null;
|
|
22392
|
+
let exportDropdown = null;
|
|
22393
|
+
const cleanupHandlers = [];
|
|
22394
|
+
const handleThemeClick = () => {
|
|
22395
|
+
currentTheme = currentTheme === "light" ? "dark" : "light";
|
|
22396
|
+
config.onThemeToggle?.(currentTheme);
|
|
22397
|
+
updateButtonIcons();
|
|
22398
|
+
};
|
|
22399
|
+
const handleMaximizeClick = () => {
|
|
22400
|
+
currentIsMaximized = !currentIsMaximized;
|
|
22401
|
+
config.onMaximize?.(currentIsMaximized);
|
|
22402
|
+
updateButtonIcons();
|
|
22403
|
+
};
|
|
22404
|
+
const handleCloseClick = () => {
|
|
22405
|
+
config.onClose?.();
|
|
22406
|
+
};
|
|
22407
|
+
const handleExportClick = (format) => {
|
|
22408
|
+
config.onExport?.(format);
|
|
22409
|
+
if (exportDropdown) {
|
|
22410
|
+
exportDropdown.style.display = "none";
|
|
22411
|
+
}
|
|
22412
|
+
};
|
|
22413
|
+
function updateButtonIcons() {
|
|
22414
|
+
if (themeBtn) {
|
|
22415
|
+
themeBtn.textContent = currentTheme === "dark" ? "\u2600\uFE0F" : "\u{1F319}";
|
|
22416
|
+
themeBtn.title = currentTheme === "dark" ? "Modo claro" : "Modo escuro";
|
|
22417
|
+
}
|
|
22418
|
+
if (maximizeBtn) {
|
|
22419
|
+
maximizeBtn.textContent = currentIsMaximized ? "\u{1F5D7}" : "\u{1F5D6}";
|
|
22420
|
+
maximizeBtn.title = currentIsMaximized ? "Restaurar" : "Maximizar";
|
|
22421
|
+
}
|
|
22422
|
+
}
|
|
22423
|
+
function getButtonStyle() {
|
|
22424
|
+
return `
|
|
22425
|
+
background: none;
|
|
22426
|
+
border: none;
|
|
22427
|
+
font-size: 16px;
|
|
22428
|
+
cursor: pointer;
|
|
22429
|
+
padding: 4px 8px;
|
|
22430
|
+
border-radius: 6px;
|
|
22431
|
+
color: rgba(255, 255, 255, 0.8);
|
|
22432
|
+
transition: background-color 0.2s, color 0.2s;
|
|
22433
|
+
`.replace(/\s+/g, " ").trim();
|
|
22434
|
+
}
|
|
22435
|
+
function renderExportDropdown() {
|
|
22436
|
+
const formats = config.exportFormats || [];
|
|
22437
|
+
if (formats.length === 0) return "";
|
|
22438
|
+
if (formats.length === 1) {
|
|
22439
|
+
return `
|
|
22440
|
+
<button id="${config.id}-export" title="Exportar ${EXPORT_FORMAT_LABELS[formats[0]]}" style="${getButtonStyle()}">
|
|
22441
|
+
\u{1F4E5}
|
|
22442
|
+
</button>
|
|
22443
|
+
`;
|
|
22444
|
+
}
|
|
22445
|
+
return `
|
|
22446
|
+
<div style="position: relative; display: inline-block;">
|
|
22447
|
+
<button id="${config.id}-export-btn" title="Exportar" style="${getButtonStyle()}">
|
|
22448
|
+
\u{1F4E5}
|
|
22449
|
+
</button>
|
|
22450
|
+
<div id="${config.id}-export-dropdown" style="
|
|
22451
|
+
display: none;
|
|
22452
|
+
position: absolute;
|
|
22453
|
+
top: 100%;
|
|
22454
|
+
right: 0;
|
|
22455
|
+
background: white;
|
|
22456
|
+
border-radius: 6px;
|
|
22457
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
22458
|
+
min-width: 140px;
|
|
22459
|
+
z-index: 10001;
|
|
22460
|
+
margin-top: 4px;
|
|
22461
|
+
overflow: hidden;
|
|
22462
|
+
">
|
|
22463
|
+
${formats.map((format) => `
|
|
22464
|
+
<button
|
|
22465
|
+
id="${config.id}-export-${format}"
|
|
22466
|
+
class="myio-export-option"
|
|
22467
|
+
data-format="${format}"
|
|
22468
|
+
style="
|
|
22469
|
+
display: flex;
|
|
22470
|
+
align-items: center;
|
|
22471
|
+
gap: 8px;
|
|
22472
|
+
width: 100%;
|
|
22473
|
+
padding: 10px 14px;
|
|
22474
|
+
border: none;
|
|
22475
|
+
background: white;
|
|
22476
|
+
cursor: pointer;
|
|
22477
|
+
font-size: 13px;
|
|
22478
|
+
color: #333;
|
|
22479
|
+
text-align: left;
|
|
22480
|
+
transition: background-color 0.2s;
|
|
22481
|
+
"
|
|
22482
|
+
>
|
|
22483
|
+
${EXPORT_FORMAT_ICONS[format]} ${EXPORT_FORMAT_LABELS[format]}
|
|
22484
|
+
</button>
|
|
22485
|
+
`).join("")}
|
|
22486
|
+
</div>
|
|
22487
|
+
</div>
|
|
22488
|
+
`;
|
|
22489
|
+
}
|
|
22490
|
+
const instance = {
|
|
22491
|
+
render() {
|
|
22492
|
+
const bgColor = config.backgroundColor || DEFAULT_BG_COLOR;
|
|
22493
|
+
const textColor = config.textColor || DEFAULT_TEXT_COLOR;
|
|
22494
|
+
const borderRadius = currentIsMaximized ? "0" : config.borderRadius || DEFAULT_BORDER_RADIUS;
|
|
22495
|
+
const showTheme = config.showThemeToggle !== false;
|
|
22496
|
+
const showMax = config.showMaximize !== false;
|
|
22497
|
+
const showClose = config.showClose !== false;
|
|
22498
|
+
const showExport = config.exportFormats && config.exportFormats.length > 0;
|
|
22499
|
+
const iconHtml = config.icon ? `<span style="margin-right: 8px;">${config.icon}</span>` : "";
|
|
22500
|
+
const buttonStyle = getButtonStyle();
|
|
22501
|
+
return `
|
|
22502
|
+
<div class="myio-modal-header" style="
|
|
22503
|
+
padding: 4px 8px;
|
|
22504
|
+
display: flex;
|
|
22505
|
+
align-items: center;
|
|
22506
|
+
justify-content: space-between;
|
|
22507
|
+
background: ${bgColor};
|
|
22508
|
+
color: ${textColor};
|
|
22509
|
+
border-radius: ${borderRadius};
|
|
22510
|
+
min-height: 20px;
|
|
22511
|
+
font-family: 'Roboto', Arial, sans-serif;
|
|
22512
|
+
">
|
|
22513
|
+
<h2 id="${config.id}-header-title" style="
|
|
22514
|
+
margin: 6px;
|
|
22515
|
+
font-size: 18px;
|
|
22516
|
+
font-weight: 600;
|
|
22517
|
+
color: ${textColor};
|
|
22518
|
+
line-height: 2;
|
|
22519
|
+
display: flex;
|
|
22520
|
+
align-items: center;
|
|
22521
|
+
">
|
|
22522
|
+
${iconHtml}${currentTitle}
|
|
22523
|
+
</h2>
|
|
22524
|
+
<div style="display: flex; gap: 4px; align-items: center;">
|
|
22525
|
+
${showExport ? renderExportDropdown() : ""}
|
|
22526
|
+
${showTheme ? `
|
|
22527
|
+
<button id="${config.id}-theme-toggle" title="${currentTheme === "dark" ? "Modo claro" : "Modo escuro"}" style="${buttonStyle}">
|
|
22528
|
+
${currentTheme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}
|
|
22529
|
+
</button>
|
|
22530
|
+
` : ""}
|
|
22531
|
+
${showMax ? `
|
|
22532
|
+
<button id="${config.id}-maximize" title="${currentIsMaximized ? "Restaurar" : "Maximizar"}" style="${buttonStyle}">
|
|
22533
|
+
${currentIsMaximized ? "\u{1F5D7}" : "\u{1F5D6}"}
|
|
22534
|
+
</button>
|
|
22535
|
+
` : ""}
|
|
22536
|
+
${showClose ? `
|
|
22537
|
+
<button id="${config.id}-close" title="Fechar" style="${buttonStyle}; font-size: 20px;">
|
|
22538
|
+
\xD7
|
|
22539
|
+
</button>
|
|
22540
|
+
` : ""}
|
|
22541
|
+
</div>
|
|
22542
|
+
</div>
|
|
22543
|
+
`;
|
|
22544
|
+
},
|
|
22545
|
+
attachListeners() {
|
|
22546
|
+
themeBtn = document.getElementById(`${config.id}-theme-toggle`);
|
|
22547
|
+
maximizeBtn = document.getElementById(`${config.id}-maximize`);
|
|
22548
|
+
closeBtn = document.getElementById(`${config.id}-close`);
|
|
22549
|
+
const singleExportBtn = document.getElementById(`${config.id}-export`);
|
|
22550
|
+
if (singleExportBtn && config.exportFormats?.length === 1) {
|
|
22551
|
+
exportBtn = singleExportBtn;
|
|
22552
|
+
const format = config.exportFormats[0];
|
|
22553
|
+
const clickHandler = () => handleExportClick(format);
|
|
22554
|
+
exportBtn.addEventListener("click", clickHandler);
|
|
22555
|
+
cleanupHandlers.push(() => exportBtn?.removeEventListener("click", clickHandler));
|
|
22556
|
+
const enterHandler = () => {
|
|
22557
|
+
exportBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22558
|
+
};
|
|
22559
|
+
const leaveHandler = () => {
|
|
22560
|
+
exportBtn.style.backgroundColor = "transparent";
|
|
22561
|
+
};
|
|
22562
|
+
exportBtn.addEventListener("mouseenter", enterHandler);
|
|
22563
|
+
exportBtn.addEventListener("mouseleave", leaveHandler);
|
|
22564
|
+
cleanupHandlers.push(() => {
|
|
22565
|
+
exportBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22566
|
+
exportBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22567
|
+
});
|
|
22568
|
+
}
|
|
22569
|
+
const exportDropdownBtn = document.getElementById(`${config.id}-export-btn`);
|
|
22570
|
+
exportDropdown = document.getElementById(`${config.id}-export-dropdown`);
|
|
22571
|
+
if (exportDropdownBtn && exportDropdown) {
|
|
22572
|
+
exportBtn = exportDropdownBtn;
|
|
22573
|
+
const toggleHandler = (e) => {
|
|
22574
|
+
e.stopPropagation();
|
|
22575
|
+
if (exportDropdown) {
|
|
22576
|
+
exportDropdown.style.display = exportDropdown.style.display === "none" ? "block" : "none";
|
|
22577
|
+
}
|
|
22578
|
+
};
|
|
22579
|
+
exportDropdownBtn.addEventListener("click", toggleHandler);
|
|
22580
|
+
cleanupHandlers.push(() => exportDropdownBtn.removeEventListener("click", toggleHandler));
|
|
22581
|
+
const outsideClickHandler = (e) => {
|
|
22582
|
+
if (exportDropdown && !exportDropdown.contains(e.target) && e.target !== exportDropdownBtn) {
|
|
22583
|
+
exportDropdown.style.display = "none";
|
|
22584
|
+
}
|
|
22585
|
+
};
|
|
22586
|
+
document.addEventListener("click", outsideClickHandler);
|
|
22587
|
+
cleanupHandlers.push(() => document.removeEventListener("click", outsideClickHandler));
|
|
22588
|
+
config.exportFormats?.forEach((format) => {
|
|
22589
|
+
const btn = document.getElementById(`${config.id}-export-${format}`);
|
|
22590
|
+
if (btn) {
|
|
22591
|
+
const clickHandler = () => handleExportClick(format);
|
|
22592
|
+
btn.addEventListener("click", clickHandler);
|
|
22593
|
+
cleanupHandlers.push(() => btn.removeEventListener("click", clickHandler));
|
|
22594
|
+
const enterHandler2 = () => {
|
|
22595
|
+
btn.style.backgroundColor = "#f0f0f0";
|
|
22596
|
+
};
|
|
22597
|
+
const leaveHandler2 = () => {
|
|
22598
|
+
btn.style.backgroundColor = "white";
|
|
22599
|
+
};
|
|
22600
|
+
btn.addEventListener("mouseenter", enterHandler2);
|
|
22601
|
+
btn.addEventListener("mouseleave", leaveHandler2);
|
|
22602
|
+
cleanupHandlers.push(() => {
|
|
22603
|
+
btn.removeEventListener("mouseenter", enterHandler2);
|
|
22604
|
+
btn.removeEventListener("mouseleave", leaveHandler2);
|
|
22605
|
+
});
|
|
22606
|
+
}
|
|
22607
|
+
});
|
|
22608
|
+
const enterHandler = () => {
|
|
22609
|
+
exportDropdownBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22610
|
+
};
|
|
22611
|
+
const leaveHandler = () => {
|
|
22612
|
+
exportDropdownBtn.style.backgroundColor = "transparent";
|
|
22613
|
+
};
|
|
22614
|
+
exportDropdownBtn.addEventListener("mouseenter", enterHandler);
|
|
22615
|
+
exportDropdownBtn.addEventListener("mouseleave", leaveHandler);
|
|
22616
|
+
cleanupHandlers.push(() => {
|
|
22617
|
+
exportDropdownBtn.removeEventListener("mouseenter", enterHandler);
|
|
22618
|
+
exportDropdownBtn.removeEventListener("mouseleave", leaveHandler);
|
|
22619
|
+
});
|
|
22620
|
+
}
|
|
22621
|
+
if (themeBtn && config.showThemeToggle !== false) {
|
|
22622
|
+
themeBtn.addEventListener("click", handleThemeClick);
|
|
22623
|
+
cleanupHandlers.push(() => themeBtn?.removeEventListener("click", handleThemeClick));
|
|
22624
|
+
const enterHandler = () => {
|
|
22625
|
+
themeBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22626
|
+
};
|
|
22627
|
+
const leaveHandler = () => {
|
|
22628
|
+
themeBtn.style.backgroundColor = "transparent";
|
|
22629
|
+
};
|
|
22630
|
+
themeBtn.addEventListener("mouseenter", enterHandler);
|
|
22631
|
+
themeBtn.addEventListener("mouseleave", leaveHandler);
|
|
22632
|
+
cleanupHandlers.push(() => {
|
|
22633
|
+
themeBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22634
|
+
themeBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22635
|
+
});
|
|
22636
|
+
}
|
|
22637
|
+
if (maximizeBtn && config.showMaximize !== false) {
|
|
22638
|
+
maximizeBtn.addEventListener("click", handleMaximizeClick);
|
|
22639
|
+
cleanupHandlers.push(() => maximizeBtn?.removeEventListener("click", handleMaximizeClick));
|
|
22640
|
+
const enterHandler = () => {
|
|
22641
|
+
maximizeBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22642
|
+
};
|
|
22643
|
+
const leaveHandler = () => {
|
|
22644
|
+
maximizeBtn.style.backgroundColor = "transparent";
|
|
22645
|
+
};
|
|
22646
|
+
maximizeBtn.addEventListener("mouseenter", enterHandler);
|
|
22647
|
+
maximizeBtn.addEventListener("mouseleave", leaveHandler);
|
|
22648
|
+
cleanupHandlers.push(() => {
|
|
22649
|
+
maximizeBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22650
|
+
maximizeBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22651
|
+
});
|
|
22652
|
+
}
|
|
22653
|
+
if (closeBtn && config.showClose !== false) {
|
|
22654
|
+
closeBtn.addEventListener("click", handleCloseClick);
|
|
22655
|
+
cleanupHandlers.push(() => closeBtn?.removeEventListener("click", handleCloseClick));
|
|
22656
|
+
const enterHandler = () => {
|
|
22657
|
+
closeBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22658
|
+
};
|
|
22659
|
+
const leaveHandler = () => {
|
|
22660
|
+
closeBtn.style.backgroundColor = "transparent";
|
|
22661
|
+
};
|
|
22662
|
+
closeBtn.addEventListener("mouseenter", enterHandler);
|
|
22663
|
+
closeBtn.addEventListener("mouseleave", leaveHandler);
|
|
22664
|
+
cleanupHandlers.push(() => {
|
|
22665
|
+
closeBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22666
|
+
closeBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22667
|
+
});
|
|
22668
|
+
}
|
|
22669
|
+
},
|
|
22670
|
+
update(updates) {
|
|
22671
|
+
if (updates.theme !== void 0) {
|
|
22672
|
+
currentTheme = updates.theme;
|
|
22673
|
+
updateButtonIcons();
|
|
22674
|
+
}
|
|
22675
|
+
if (updates.isMaximized !== void 0) {
|
|
22676
|
+
currentIsMaximized = updates.isMaximized;
|
|
22677
|
+
updateButtonIcons();
|
|
22678
|
+
}
|
|
22679
|
+
if (updates.title !== void 0) {
|
|
22680
|
+
currentTitle = updates.title;
|
|
22681
|
+
const titleEl = document.getElementById(`${config.id}-header-title`);
|
|
22682
|
+
if (titleEl) {
|
|
22683
|
+
const iconHtml = config.icon ? `<span style="margin-right: 8px;">${config.icon}</span>` : "";
|
|
22684
|
+
titleEl.innerHTML = `${iconHtml}${currentTitle}`;
|
|
22685
|
+
}
|
|
22686
|
+
}
|
|
22687
|
+
},
|
|
22688
|
+
getState() {
|
|
22689
|
+
return {
|
|
22690
|
+
theme: currentTheme,
|
|
22691
|
+
isMaximized: currentIsMaximized
|
|
22692
|
+
};
|
|
22693
|
+
},
|
|
22694
|
+
destroy() {
|
|
22695
|
+
cleanupHandlers.forEach((handler) => handler());
|
|
22696
|
+
cleanupHandlers.length = 0;
|
|
22697
|
+
themeBtn = null;
|
|
22698
|
+
maximizeBtn = null;
|
|
22699
|
+
closeBtn = null;
|
|
22700
|
+
exportBtn = null;
|
|
22701
|
+
exportDropdown = null;
|
|
22702
|
+
}
|
|
22703
|
+
};
|
|
22704
|
+
return instance;
|
|
22705
|
+
}
|
|
22706
|
+
function getModalHeaderStyles() {
|
|
22707
|
+
return `
|
|
22708
|
+
.myio-modal-header button:hover {
|
|
22709
|
+
background-color: rgba(255, 255, 255, 0.2) !important;
|
|
22710
|
+
}
|
|
22711
|
+
.myio-modal-header button:active {
|
|
22712
|
+
background-color: rgba(255, 255, 255, 0.3) !important;
|
|
22713
|
+
}
|
|
22714
|
+
.myio-export-option:hover {
|
|
22715
|
+
background-color: #f0f0f0 !important;
|
|
22716
|
+
}
|
|
22717
|
+
`;
|
|
22718
|
+
}
|
|
22719
|
+
|
|
22720
|
+
// src/components/Consumption7DaysChart/types.ts
|
|
22721
|
+
var DEFAULT_COLORS = {
|
|
22722
|
+
energy: {
|
|
22723
|
+
primary: "#2563eb",
|
|
22724
|
+
background: "rgba(37, 99, 235, 0.1)",
|
|
22725
|
+
gradient: ["#f0fdf4", "#dcfce7"],
|
|
22726
|
+
pointBackground: "#2563eb",
|
|
22727
|
+
pointBorder: "#ffffff"
|
|
22728
|
+
},
|
|
22729
|
+
water: {
|
|
22730
|
+
primary: "#0288d1",
|
|
22731
|
+
background: "rgba(2, 136, 209, 0.1)",
|
|
22732
|
+
gradient: ["#f0f9ff", "#bae6fd"],
|
|
22733
|
+
pointBackground: "#0288d1",
|
|
22734
|
+
pointBorder: "#ffffff"
|
|
22735
|
+
},
|
|
22736
|
+
gas: {
|
|
22737
|
+
primary: "#ea580c",
|
|
22738
|
+
background: "rgba(234, 88, 12, 0.1)",
|
|
22739
|
+
gradient: ["#fff7ed", "#fed7aa"],
|
|
22740
|
+
pointBackground: "#ea580c",
|
|
22741
|
+
pointBorder: "#ffffff"
|
|
22742
|
+
},
|
|
22743
|
+
temperature: {
|
|
22744
|
+
primary: "#dc2626",
|
|
22745
|
+
background: "rgba(220, 38, 38, 0.1)",
|
|
22746
|
+
gradient: ["#fef2f2", "#fecaca"],
|
|
22747
|
+
pointBackground: "#dc2626",
|
|
22748
|
+
pointBorder: "#ffffff"
|
|
22749
|
+
}
|
|
22750
|
+
};
|
|
22751
|
+
var THEME_COLORS = {
|
|
22752
|
+
light: {
|
|
22753
|
+
chartBackground: "#ffffff",
|
|
22754
|
+
text: "#1f2937",
|
|
22755
|
+
textMuted: "#6b7280",
|
|
22756
|
+
grid: "rgba(0, 0, 0, 0.1)",
|
|
22757
|
+
border: "#e5e7eb",
|
|
22758
|
+
tooltipBackground: "#ffffff",
|
|
22759
|
+
tooltipText: "#1f2937"
|
|
22760
|
+
},
|
|
22761
|
+
dark: {
|
|
22762
|
+
chartBackground: "#1f2937",
|
|
22763
|
+
text: "#f9fafb",
|
|
22764
|
+
textMuted: "#9ca3af",
|
|
22765
|
+
grid: "rgba(255, 255, 255, 0.1)",
|
|
22766
|
+
border: "#374151",
|
|
22767
|
+
tooltipBackground: "#374151",
|
|
22768
|
+
tooltipText: "#f9fafb"
|
|
22769
|
+
}
|
|
22770
|
+
};
|
|
22771
|
+
var DEFAULT_CONFIG = {
|
|
22772
|
+
defaultPeriod: 7,
|
|
22773
|
+
defaultChartType: "line",
|
|
22774
|
+
defaultVizMode: "total",
|
|
22775
|
+
defaultTheme: "light",
|
|
22776
|
+
cacheTTL: 3e5,
|
|
22777
|
+
// 5 minutes
|
|
22778
|
+
decimalPlaces: 1,
|
|
22779
|
+
lineTension: 0.4,
|
|
22780
|
+
pointRadius: 4,
|
|
22781
|
+
borderWidth: 2,
|
|
22782
|
+
fill: true,
|
|
22783
|
+
showLegend: false,
|
|
22784
|
+
enableExport: true
|
|
22785
|
+
};
|
|
22786
|
+
|
|
22787
|
+
// src/components/Consumption7DaysChart/createConsumption7DaysChart.ts
|
|
22788
|
+
function createConsumption7DaysChart(config) {
|
|
22789
|
+
let chartInstance = null;
|
|
22790
|
+
let cachedData = null;
|
|
22791
|
+
let currentPeriod = config.defaultPeriod ?? DEFAULT_CONFIG.defaultPeriod;
|
|
22792
|
+
let currentChartType = config.defaultChartType ?? DEFAULT_CONFIG.defaultChartType;
|
|
22793
|
+
let currentVizMode = config.defaultVizMode ?? DEFAULT_CONFIG.defaultVizMode;
|
|
22794
|
+
let currentTheme = config.theme ?? DEFAULT_CONFIG.defaultTheme;
|
|
22795
|
+
let currentIdealRange = config.idealRange ?? null;
|
|
22796
|
+
let isRendered = false;
|
|
22797
|
+
let autoRefreshTimer = null;
|
|
22798
|
+
const colors = {
|
|
22799
|
+
...DEFAULT_COLORS[config.domain] ?? DEFAULT_COLORS.energy,
|
|
22800
|
+
...config.colors
|
|
22801
|
+
};
|
|
22802
|
+
function $id(id) {
|
|
22803
|
+
if (config.$container && config.$container[0]) {
|
|
22804
|
+
return config.$container[0].querySelector(`#${id}`);
|
|
22805
|
+
}
|
|
22806
|
+
return document.getElementById(id);
|
|
22807
|
+
}
|
|
22808
|
+
function log(level, ...args) {
|
|
22809
|
+
const prefix = `[${config.domain.toUpperCase()}]`;
|
|
22810
|
+
console[level](prefix, ...args);
|
|
22811
|
+
}
|
|
22812
|
+
function calculateYAxisMax(values) {
|
|
22813
|
+
const maxValue = Math.max(...values, 0);
|
|
22814
|
+
if (config.domain === "temperature") {
|
|
22815
|
+
const tempConfig = config.temperatureConfig;
|
|
22816
|
+
if (tempConfig?.clampRange) {
|
|
22817
|
+
return tempConfig.clampRange.max;
|
|
22818
|
+
}
|
|
22819
|
+
const maxWithThreshold = Math.max(
|
|
22820
|
+
maxValue,
|
|
22821
|
+
tempConfig?.maxThreshold?.value ?? 0,
|
|
22822
|
+
tempConfig?.idealRange?.max ?? 0
|
|
22823
|
+
);
|
|
22824
|
+
return Math.ceil(maxWithThreshold + 5);
|
|
22825
|
+
}
|
|
22826
|
+
if (maxValue === 0) {
|
|
22827
|
+
return config.thresholdForLargeUnit ? config.thresholdForLargeUnit / 2 : 500;
|
|
22828
|
+
}
|
|
22829
|
+
let roundTo;
|
|
22830
|
+
if (config.thresholdForLargeUnit && maxValue >= config.thresholdForLargeUnit) {
|
|
22831
|
+
roundTo = config.thresholdForLargeUnit / 10;
|
|
22832
|
+
} else if (maxValue >= 1e3) {
|
|
22833
|
+
roundTo = 100;
|
|
22834
|
+
} else if (maxValue >= 100) {
|
|
22835
|
+
roundTo = 50;
|
|
22836
|
+
} else if (maxValue >= 10) {
|
|
22837
|
+
roundTo = 10;
|
|
22838
|
+
} else {
|
|
22839
|
+
roundTo = 5;
|
|
22840
|
+
}
|
|
22841
|
+
return Math.ceil(maxValue * 1.1 / roundTo) * roundTo;
|
|
22842
|
+
}
|
|
22843
|
+
function calculateYAxisMin(values) {
|
|
22844
|
+
if (config.domain !== "temperature") {
|
|
22845
|
+
return 0;
|
|
22846
|
+
}
|
|
22847
|
+
const tempConfig = config.temperatureConfig;
|
|
22848
|
+
if (tempConfig?.clampRange) {
|
|
22849
|
+
return tempConfig.clampRange.min;
|
|
22850
|
+
}
|
|
22851
|
+
const minValue = Math.min(...values);
|
|
22852
|
+
const minWithThreshold = Math.min(
|
|
22853
|
+
minValue,
|
|
22854
|
+
tempConfig?.minThreshold?.value ?? minValue,
|
|
22855
|
+
tempConfig?.idealRange?.min ?? minValue
|
|
22856
|
+
);
|
|
22857
|
+
return Math.floor(minWithThreshold - 5);
|
|
22858
|
+
}
|
|
22859
|
+
function buildTemperatureAnnotations() {
|
|
22860
|
+
const tempConfig = config.temperatureConfig;
|
|
22861
|
+
if (!tempConfig || config.domain !== "temperature") {
|
|
22862
|
+
return {};
|
|
22863
|
+
}
|
|
22864
|
+
const annotations = {};
|
|
22865
|
+
const createLineAnnotation = (line, id) => {
|
|
22866
|
+
const borderDash = line.lineStyle === "dashed" ? [6, 6] : line.lineStyle === "dotted" ? [2, 2] : [];
|
|
22867
|
+
return {
|
|
22868
|
+
type: "line",
|
|
22869
|
+
yMin: line.value,
|
|
22870
|
+
yMax: line.value,
|
|
22871
|
+
borderColor: line.color,
|
|
22872
|
+
borderWidth: line.lineWidth ?? 2,
|
|
22873
|
+
borderDash,
|
|
22874
|
+
label: {
|
|
22875
|
+
display: true,
|
|
22876
|
+
content: line.label,
|
|
22877
|
+
position: "end",
|
|
22878
|
+
backgroundColor: line.color,
|
|
22879
|
+
color: "#fff",
|
|
22880
|
+
font: { size: 10, weight: "bold" },
|
|
22881
|
+
padding: { x: 4, y: 2 }
|
|
22882
|
+
}
|
|
22883
|
+
};
|
|
22884
|
+
};
|
|
22885
|
+
if (tempConfig.minThreshold) {
|
|
22886
|
+
annotations["minThreshold"] = createLineAnnotation(tempConfig.minThreshold);
|
|
22887
|
+
}
|
|
22888
|
+
if (tempConfig.maxThreshold) {
|
|
22889
|
+
annotations["maxThreshold"] = createLineAnnotation(tempConfig.maxThreshold);
|
|
22890
|
+
}
|
|
22891
|
+
if (tempConfig.idealRange) {
|
|
22892
|
+
annotations["idealRange"] = {
|
|
22893
|
+
type: "box",
|
|
22894
|
+
yMin: tempConfig.idealRange.min,
|
|
22895
|
+
yMax: tempConfig.idealRange.max,
|
|
22896
|
+
backgroundColor: tempConfig.idealRange.color,
|
|
22897
|
+
borderWidth: 0,
|
|
22898
|
+
label: tempConfig.idealRange.label ? {
|
|
22899
|
+
display: true,
|
|
22900
|
+
content: tempConfig.idealRange.label,
|
|
22901
|
+
position: { x: "start", y: "center" },
|
|
22902
|
+
color: "#666",
|
|
22903
|
+
font: { size: 10 }
|
|
22904
|
+
} : void 0
|
|
22905
|
+
};
|
|
22906
|
+
}
|
|
22907
|
+
return annotations;
|
|
22908
|
+
}
|
|
22909
|
+
function buildIdealRangeAnnotation() {
|
|
22910
|
+
if (!currentIdealRange) {
|
|
22911
|
+
return {};
|
|
22912
|
+
}
|
|
22913
|
+
const { min, max, enabled = true } = currentIdealRange;
|
|
22914
|
+
if (!enabled || min === 0 && max === 0 || min >= max) {
|
|
22915
|
+
return {};
|
|
22916
|
+
}
|
|
22917
|
+
const defaultColors = {
|
|
22918
|
+
temperature: { bg: "rgba(34, 197, 94, 0.15)", border: "rgba(34, 197, 94, 0.4)" },
|
|
22919
|
+
energy: { bg: "rgba(37, 99, 235, 0.1)", border: "rgba(37, 99, 235, 0.3)" },
|
|
22920
|
+
water: { bg: "rgba(2, 136, 209, 0.1)", border: "rgba(2, 136, 209, 0.3)" },
|
|
22921
|
+
gas: { bg: "rgba(234, 88, 12, 0.1)", border: "rgba(234, 88, 12, 0.3)" }
|
|
22922
|
+
};
|
|
22923
|
+
const domainDefaults = defaultColors[config.domain] || defaultColors.energy;
|
|
22924
|
+
return {
|
|
22925
|
+
idealRangeBox: {
|
|
22926
|
+
type: "box",
|
|
22927
|
+
yMin: min,
|
|
22928
|
+
yMax: max,
|
|
22929
|
+
backgroundColor: currentIdealRange.color || domainDefaults.bg,
|
|
22930
|
+
borderColor: currentIdealRange.borderColor || domainDefaults.border,
|
|
22931
|
+
borderWidth: 1,
|
|
22932
|
+
label: currentIdealRange.label ? {
|
|
22933
|
+
display: true,
|
|
22934
|
+
content: currentIdealRange.label,
|
|
22935
|
+
position: { x: "start", y: "center" },
|
|
22936
|
+
color: "#666",
|
|
22937
|
+
font: { size: 10, style: "italic" },
|
|
22938
|
+
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
|
22939
|
+
padding: { x: 4, y: 2 }
|
|
22940
|
+
} : void 0
|
|
22941
|
+
}
|
|
22942
|
+
};
|
|
22943
|
+
}
|
|
22944
|
+
function formatValue(value, includeUnit = true) {
|
|
22945
|
+
const decimals = config.decimalPlaces ?? DEFAULT_CONFIG.decimalPlaces;
|
|
22946
|
+
if (config.unitLarge && config.thresholdForLargeUnit && value >= config.thresholdForLargeUnit) {
|
|
22947
|
+
const converted = value / config.thresholdForLargeUnit;
|
|
22948
|
+
return includeUnit ? `${converted.toFixed(decimals)} ${config.unitLarge}` : converted.toFixed(decimals);
|
|
22949
|
+
}
|
|
22950
|
+
return includeUnit ? `${value.toFixed(decimals)} ${config.unit}` : value.toFixed(decimals);
|
|
22951
|
+
}
|
|
22952
|
+
function formatTickValue(value) {
|
|
22953
|
+
if (config.unitLarge && config.thresholdForLargeUnit && value >= config.thresholdForLargeUnit) {
|
|
22954
|
+
return `${(value / config.thresholdForLargeUnit).toFixed(1)}`;
|
|
22955
|
+
}
|
|
22956
|
+
return value.toFixed(0);
|
|
22957
|
+
}
|
|
22958
|
+
function buildChartConfig(data) {
|
|
22959
|
+
const yAxisMax = calculateYAxisMax(data.dailyTotals);
|
|
22960
|
+
const yAxisMin = calculateYAxisMin(data.dailyTotals);
|
|
22961
|
+
const tension = config.lineTension ?? DEFAULT_CONFIG.lineTension;
|
|
22962
|
+
const pointRadius = config.pointRadius ?? DEFAULT_CONFIG.pointRadius;
|
|
22963
|
+
const borderWidth = config.borderWidth ?? DEFAULT_CONFIG.borderWidth;
|
|
22964
|
+
const fill = config.fill ?? DEFAULT_CONFIG.fill;
|
|
22965
|
+
const showLegend = config.showLegend ?? DEFAULT_CONFIG.showLegend;
|
|
22966
|
+
const themeColors = THEME_COLORS[currentTheme];
|
|
22967
|
+
const isTemperature = config.domain === "temperature";
|
|
22968
|
+
let datasets;
|
|
22969
|
+
if (currentVizMode === "separate" && data.shoppingData && data.shoppingNames) {
|
|
22970
|
+
const shoppingColors = [
|
|
22971
|
+
"#2563eb",
|
|
22972
|
+
"#16a34a",
|
|
22973
|
+
"#ea580c",
|
|
22974
|
+
"#dc2626",
|
|
22975
|
+
"#8b5cf6",
|
|
22976
|
+
"#0891b2",
|
|
22977
|
+
"#65a30d",
|
|
22978
|
+
"#d97706",
|
|
22979
|
+
"#be185d",
|
|
22980
|
+
"#0d9488"
|
|
22981
|
+
];
|
|
22982
|
+
datasets = Object.entries(data.shoppingData).map(([shoppingId, values], index) => ({
|
|
22983
|
+
label: data.shoppingNames?.[shoppingId] || shoppingId,
|
|
22984
|
+
data: values,
|
|
22985
|
+
borderColor: shoppingColors[index % shoppingColors.length],
|
|
22986
|
+
backgroundColor: currentChartType === "line" ? `${shoppingColors[index % shoppingColors.length]}20` : shoppingColors[index % shoppingColors.length],
|
|
22987
|
+
fill: currentChartType === "line" && fill,
|
|
22988
|
+
tension,
|
|
22989
|
+
borderWidth,
|
|
22990
|
+
pointRadius: currentChartType === "line" ? pointRadius : 0,
|
|
22991
|
+
pointBackgroundColor: shoppingColors[index % shoppingColors.length],
|
|
22992
|
+
pointBorderColor: "#fff",
|
|
22993
|
+
pointBorderWidth: 2
|
|
22994
|
+
}));
|
|
22995
|
+
} else {
|
|
22996
|
+
const datasetLabel = isTemperature ? `Temperatura (${config.unit})` : `Consumo (${config.unit})`;
|
|
22997
|
+
datasets = [
|
|
22998
|
+
{
|
|
22999
|
+
label: datasetLabel,
|
|
23000
|
+
data: data.dailyTotals,
|
|
23001
|
+
borderColor: colors.primary,
|
|
23002
|
+
backgroundColor: currentChartType === "line" ? colors.background : colors.primary,
|
|
23003
|
+
fill: currentChartType === "line" && fill,
|
|
23004
|
+
tension,
|
|
23005
|
+
borderWidth,
|
|
23006
|
+
pointRadius: currentChartType === "line" ? pointRadius : 0,
|
|
23007
|
+
pointBackgroundColor: colors.pointBackground || colors.primary,
|
|
23008
|
+
pointBorderColor: colors.pointBorder || "#fff",
|
|
23009
|
+
pointBorderWidth: 2,
|
|
23010
|
+
borderRadius: currentChartType === "bar" ? 4 : 0
|
|
23011
|
+
}
|
|
23012
|
+
];
|
|
23013
|
+
}
|
|
23014
|
+
const temperatureAnnotations = buildTemperatureAnnotations();
|
|
23015
|
+
const idealRangeAnnotations = buildIdealRangeAnnotation();
|
|
23016
|
+
const allAnnotations = { ...temperatureAnnotations, ...idealRangeAnnotations };
|
|
23017
|
+
const yAxisLabel = config.unitLarge && config.thresholdForLargeUnit && yAxisMax >= config.thresholdForLargeUnit ? config.unitLarge : config.unit;
|
|
23018
|
+
return {
|
|
23019
|
+
type: currentChartType,
|
|
23020
|
+
data: {
|
|
23021
|
+
labels: data.labels,
|
|
23022
|
+
datasets
|
|
23023
|
+
},
|
|
23024
|
+
options: {
|
|
23025
|
+
responsive: true,
|
|
23026
|
+
maintainAspectRatio: false,
|
|
23027
|
+
animation: false,
|
|
23028
|
+
// CRITICAL: Prevents infinite growth bug
|
|
23029
|
+
plugins: {
|
|
23030
|
+
legend: {
|
|
23031
|
+
display: showLegend || currentVizMode === "separate",
|
|
23032
|
+
position: "bottom",
|
|
23033
|
+
labels: {
|
|
23034
|
+
color: themeColors.text
|
|
23035
|
+
}
|
|
23036
|
+
},
|
|
23037
|
+
tooltip: {
|
|
23038
|
+
backgroundColor: themeColors.tooltipBackground,
|
|
23039
|
+
titleColor: themeColors.tooltipText,
|
|
23040
|
+
bodyColor: themeColors.tooltipText,
|
|
23041
|
+
borderColor: themeColors.border,
|
|
23042
|
+
borderWidth: 1,
|
|
23043
|
+
callbacks: {
|
|
23044
|
+
label: function(context) {
|
|
23045
|
+
const value = context.parsed.y || 0;
|
|
23046
|
+
const label = context.dataset.label || "";
|
|
23047
|
+
return `${label}: ${formatValue(value)}`;
|
|
23048
|
+
}
|
|
23049
|
+
}
|
|
23050
|
+
},
|
|
23051
|
+
// Reference lines and ideal range (requires chartjs-plugin-annotation)
|
|
23052
|
+
annotation: Object.keys(allAnnotations).length > 0 ? {
|
|
23053
|
+
annotations: allAnnotations
|
|
23054
|
+
} : void 0
|
|
23055
|
+
},
|
|
23056
|
+
scales: {
|
|
23057
|
+
y: {
|
|
23058
|
+
beginAtZero: !isTemperature,
|
|
23059
|
+
// Temperature can have negative values
|
|
23060
|
+
min: yAxisMin,
|
|
23061
|
+
max: yAxisMax,
|
|
23062
|
+
// CRITICAL: Fixed max prevents animation loop
|
|
23063
|
+
grid: {
|
|
23064
|
+
color: themeColors.grid
|
|
23065
|
+
},
|
|
23066
|
+
title: {
|
|
23067
|
+
display: true,
|
|
23068
|
+
text: yAxisLabel,
|
|
23069
|
+
font: { size: 12 },
|
|
23070
|
+
color: themeColors.text
|
|
23071
|
+
},
|
|
23072
|
+
ticks: {
|
|
23073
|
+
font: { size: 11 },
|
|
23074
|
+
color: themeColors.textMuted,
|
|
23075
|
+
callback: function(value) {
|
|
23076
|
+
return formatTickValue(value);
|
|
23077
|
+
}
|
|
23078
|
+
}
|
|
23079
|
+
},
|
|
23080
|
+
x: {
|
|
23081
|
+
grid: {
|
|
23082
|
+
color: themeColors.grid
|
|
23083
|
+
},
|
|
23084
|
+
ticks: {
|
|
23085
|
+
font: { size: 11 },
|
|
23086
|
+
color: themeColors.textMuted
|
|
23087
|
+
}
|
|
23088
|
+
}
|
|
23089
|
+
}
|
|
23090
|
+
}
|
|
23091
|
+
};
|
|
23092
|
+
}
|
|
23093
|
+
function validateChartJs() {
|
|
23094
|
+
if (typeof Chart === "undefined") {
|
|
23095
|
+
log("error", "Chart.js not loaded. Cannot initialize chart.");
|
|
23096
|
+
config.onError?.(new Error("Chart.js not loaded"));
|
|
23097
|
+
return false;
|
|
23098
|
+
}
|
|
23099
|
+
return true;
|
|
23100
|
+
}
|
|
23101
|
+
function validateCanvas() {
|
|
23102
|
+
const canvas = $id(config.containerId);
|
|
23103
|
+
if (!canvas) {
|
|
23104
|
+
log("error", `Canvas #${config.containerId} not found`);
|
|
23105
|
+
config.onError?.(new Error(`Canvas #${config.containerId} not found`));
|
|
23106
|
+
return null;
|
|
23107
|
+
}
|
|
23108
|
+
return canvas;
|
|
23109
|
+
}
|
|
23110
|
+
function setupAutoRefresh() {
|
|
23111
|
+
if (config.autoRefreshInterval && config.autoRefreshInterval > 0) {
|
|
23112
|
+
if (autoRefreshTimer) {
|
|
23113
|
+
clearInterval(autoRefreshTimer);
|
|
23114
|
+
}
|
|
23115
|
+
autoRefreshTimer = setInterval(async () => {
|
|
23116
|
+
log("log", "Auto-refreshing data...");
|
|
23117
|
+
await instance.refresh(true);
|
|
23118
|
+
}, config.autoRefreshInterval);
|
|
23119
|
+
}
|
|
23120
|
+
}
|
|
23121
|
+
function cleanupAutoRefresh() {
|
|
23122
|
+
if (autoRefreshTimer) {
|
|
23123
|
+
clearInterval(autoRefreshTimer);
|
|
23124
|
+
autoRefreshTimer = null;
|
|
23125
|
+
}
|
|
23126
|
+
}
|
|
23127
|
+
function setupButtonHandlers() {
|
|
23128
|
+
if (config.settingsButtonId && config.onSettingsClick) {
|
|
23129
|
+
const settingsBtn = $id(config.settingsButtonId);
|
|
23130
|
+
if (settingsBtn) {
|
|
23131
|
+
settingsBtn.addEventListener("click", () => {
|
|
23132
|
+
log("log", "Settings button clicked");
|
|
23133
|
+
config.onSettingsClick?.();
|
|
23134
|
+
});
|
|
23135
|
+
log("log", "Settings button handler attached");
|
|
23136
|
+
}
|
|
23137
|
+
}
|
|
23138
|
+
if (config.maximizeButtonId && config.onMaximizeClick) {
|
|
23139
|
+
const maximizeBtn = $id(config.maximizeButtonId);
|
|
23140
|
+
if (maximizeBtn) {
|
|
23141
|
+
maximizeBtn.addEventListener("click", () => {
|
|
23142
|
+
log("log", "Maximize button clicked");
|
|
23143
|
+
config.onMaximizeClick?.();
|
|
23144
|
+
});
|
|
23145
|
+
log("log", "Maximize button handler attached");
|
|
23146
|
+
}
|
|
23147
|
+
}
|
|
23148
|
+
const enableExport = config.enableExport ?? DEFAULT_CONFIG.enableExport;
|
|
23149
|
+
if (enableExport && config.exportButtonId) {
|
|
23150
|
+
const exportBtn = $id(config.exportButtonId);
|
|
23151
|
+
if (exportBtn) {
|
|
23152
|
+
exportBtn.addEventListener("click", () => {
|
|
23153
|
+
log("log", "Export button clicked");
|
|
23154
|
+
if (config.onExportCSV && cachedData) {
|
|
23155
|
+
config.onExportCSV(cachedData);
|
|
23156
|
+
} else {
|
|
23157
|
+
instance.exportCSV();
|
|
23158
|
+
}
|
|
23159
|
+
});
|
|
23160
|
+
log("log", "Export button handler attached");
|
|
23161
|
+
}
|
|
23162
|
+
}
|
|
23163
|
+
}
|
|
23164
|
+
function generateCSVContent(data) {
|
|
23165
|
+
const rows = [];
|
|
23166
|
+
const decimals = config.decimalPlaces ?? DEFAULT_CONFIG.decimalPlaces;
|
|
23167
|
+
if (currentVizMode === "separate" && data.shoppingData && data.shoppingNames) {
|
|
23168
|
+
const shoppingHeaders = Object.keys(data.shoppingData).map(
|
|
23169
|
+
(id) => data.shoppingNames?.[id] || id
|
|
23170
|
+
);
|
|
23171
|
+
rows.push(["Data", ...shoppingHeaders, "Total"].join(";"));
|
|
23172
|
+
data.labels.forEach((label, index) => {
|
|
23173
|
+
const shoppingValues = Object.keys(data.shoppingData).map(
|
|
23174
|
+
(id) => data.shoppingData[id][index].toFixed(decimals)
|
|
23175
|
+
);
|
|
23176
|
+
rows.push([label, ...shoppingValues, data.dailyTotals[index].toFixed(decimals)].join(";"));
|
|
23177
|
+
});
|
|
23178
|
+
} else {
|
|
23179
|
+
rows.push(["Data", `Consumo (${config.unit})`].join(";"));
|
|
23180
|
+
data.labels.forEach((label, index) => {
|
|
23181
|
+
rows.push([label, data.dailyTotals[index].toFixed(decimals)].join(";"));
|
|
23182
|
+
});
|
|
23183
|
+
}
|
|
23184
|
+
const total = data.dailyTotals.reduce((sum, v) => sum + v, 0);
|
|
23185
|
+
const avg = total / data.dailyTotals.length;
|
|
23186
|
+
rows.push("");
|
|
23187
|
+
rows.push(["Total", total.toFixed(decimals)].join(";"));
|
|
23188
|
+
rows.push(["M\xE9dia", avg.toFixed(decimals)].join(";"));
|
|
23189
|
+
return rows.join("\n");
|
|
23190
|
+
}
|
|
23191
|
+
function downloadCSV(content, filename) {
|
|
23192
|
+
const BOM = "\uFEFF";
|
|
23193
|
+
const blob = new Blob([BOM + content], { type: "text/csv;charset=utf-8" });
|
|
23194
|
+
const url = URL.createObjectURL(blob);
|
|
23195
|
+
const link = document.createElement("a");
|
|
23196
|
+
link.href = url;
|
|
23197
|
+
link.download = `${filename}.csv`;
|
|
23198
|
+
document.body.appendChild(link);
|
|
23199
|
+
link.click();
|
|
23200
|
+
document.body.removeChild(link);
|
|
23201
|
+
URL.revokeObjectURL(url);
|
|
23202
|
+
log("log", `CSV exported: ${filename}.csv`);
|
|
23203
|
+
}
|
|
23204
|
+
function updateTitle() {
|
|
23205
|
+
if (config.titleElementId) {
|
|
23206
|
+
const titleEl = $id(config.titleElementId);
|
|
23207
|
+
if (titleEl) {
|
|
23208
|
+
if (currentPeriod === 0) {
|
|
23209
|
+
titleEl.textContent = `Consumo - Per\xEDodo Personalizado`;
|
|
23210
|
+
} else {
|
|
23211
|
+
titleEl.textContent = `Consumo dos \xFAltimos ${currentPeriod} dias`;
|
|
23212
|
+
}
|
|
23213
|
+
}
|
|
23214
|
+
}
|
|
23215
|
+
}
|
|
23216
|
+
const instance = {
|
|
23217
|
+
async render() {
|
|
23218
|
+
log("log", "Rendering chart...");
|
|
23219
|
+
if (!validateChartJs()) return;
|
|
23220
|
+
const canvas = validateCanvas();
|
|
23221
|
+
if (!canvas) return;
|
|
23222
|
+
try {
|
|
23223
|
+
log("log", `Fetching ${currentPeriod} days of data...`);
|
|
23224
|
+
cachedData = await config.fetchData(currentPeriod);
|
|
23225
|
+
cachedData.fetchTimestamp = Date.now();
|
|
23226
|
+
if (config.onBeforeRender) {
|
|
23227
|
+
cachedData = config.onBeforeRender(cachedData);
|
|
23228
|
+
}
|
|
23229
|
+
if (chartInstance) {
|
|
23230
|
+
chartInstance.destroy();
|
|
23231
|
+
chartInstance = null;
|
|
23232
|
+
}
|
|
23233
|
+
const ctx = canvas.getContext("2d");
|
|
23234
|
+
const chartConfig = buildChartConfig(cachedData);
|
|
23235
|
+
chartInstance = new Chart(ctx, chartConfig);
|
|
23236
|
+
isRendered = true;
|
|
23237
|
+
const yAxisMax = calculateYAxisMax(cachedData.dailyTotals);
|
|
23238
|
+
log("log", `Chart initialized with yAxisMax: ${yAxisMax}`);
|
|
23239
|
+
config.onDataLoaded?.(cachedData);
|
|
23240
|
+
config.onAfterRender?.(chartInstance);
|
|
23241
|
+
setupButtonHandlers();
|
|
23242
|
+
updateTitle();
|
|
23243
|
+
setupAutoRefresh();
|
|
23244
|
+
} catch (error) {
|
|
23245
|
+
log("error", "Failed to render chart:", error);
|
|
23246
|
+
config.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
23247
|
+
}
|
|
23248
|
+
},
|
|
23249
|
+
async update(data) {
|
|
23250
|
+
if (data) {
|
|
23251
|
+
cachedData = data;
|
|
23252
|
+
cachedData.fetchTimestamp = Date.now();
|
|
23253
|
+
}
|
|
23254
|
+
if (!chartInstance || !cachedData) {
|
|
23255
|
+
log("warn", "Cannot update: chart not initialized or no data");
|
|
23256
|
+
return;
|
|
23257
|
+
}
|
|
23258
|
+
let renderData = cachedData;
|
|
23259
|
+
if (config.onBeforeRender) {
|
|
23260
|
+
renderData = config.onBeforeRender(cachedData);
|
|
23261
|
+
}
|
|
23262
|
+
const chartConfig = buildChartConfig(renderData);
|
|
23263
|
+
chartInstance.data = chartConfig.data;
|
|
23264
|
+
chartInstance.options = chartConfig.options;
|
|
23265
|
+
chartInstance.update("none");
|
|
23266
|
+
log("log", "Chart updated");
|
|
23267
|
+
},
|
|
23268
|
+
setChartType(type) {
|
|
23269
|
+
if (currentChartType === type) return;
|
|
23270
|
+
log("log", `Changing chart type to: ${type}`);
|
|
23271
|
+
currentChartType = type;
|
|
23272
|
+
if (cachedData && chartInstance) {
|
|
23273
|
+
const canvas = validateCanvas();
|
|
23274
|
+
if (canvas) {
|
|
23275
|
+
chartInstance.destroy();
|
|
23276
|
+
const ctx = canvas.getContext("2d");
|
|
23277
|
+
chartInstance = new Chart(ctx, buildChartConfig(cachedData));
|
|
23278
|
+
}
|
|
23279
|
+
}
|
|
23280
|
+
},
|
|
23281
|
+
setVizMode(mode) {
|
|
23282
|
+
if (currentVizMode === mode) return;
|
|
23283
|
+
log("log", `Changing viz mode to: ${mode}`);
|
|
23284
|
+
currentVizMode = mode;
|
|
23285
|
+
if (cachedData) {
|
|
23286
|
+
this.update();
|
|
23287
|
+
}
|
|
23288
|
+
},
|
|
23289
|
+
async setPeriod(days) {
|
|
23290
|
+
if (currentPeriod === days) return;
|
|
23291
|
+
log("log", `Changing period to: ${days} days`);
|
|
23292
|
+
currentPeriod = days;
|
|
23293
|
+
updateTitle();
|
|
23294
|
+
await this.refresh(true);
|
|
23295
|
+
},
|
|
23296
|
+
async refresh(forceRefresh = false) {
|
|
23297
|
+
if (!forceRefresh && cachedData?.fetchTimestamp) {
|
|
23298
|
+
const age = Date.now() - cachedData.fetchTimestamp;
|
|
23299
|
+
const ttl = config.cacheTTL ?? DEFAULT_CONFIG.cacheTTL;
|
|
23300
|
+
if (age < ttl) {
|
|
23301
|
+
log("log", `Using cached data (age: ${Math.round(age / 1e3)}s)`);
|
|
23302
|
+
return;
|
|
23303
|
+
}
|
|
23304
|
+
}
|
|
23305
|
+
log("log", "Refreshing data...");
|
|
23306
|
+
await this.render();
|
|
23307
|
+
},
|
|
23308
|
+
destroy() {
|
|
23309
|
+
log("log", "Destroying chart...");
|
|
23310
|
+
cleanupAutoRefresh();
|
|
23311
|
+
if (chartInstance) {
|
|
23312
|
+
chartInstance.destroy();
|
|
23313
|
+
chartInstance = null;
|
|
23314
|
+
}
|
|
23315
|
+
cachedData = null;
|
|
23316
|
+
isRendered = false;
|
|
23317
|
+
},
|
|
23318
|
+
getChartInstance() {
|
|
23319
|
+
return chartInstance;
|
|
23320
|
+
},
|
|
23321
|
+
getCachedData() {
|
|
23322
|
+
return cachedData;
|
|
23323
|
+
},
|
|
23324
|
+
getState() {
|
|
23325
|
+
return {
|
|
23326
|
+
period: currentPeriod,
|
|
23327
|
+
chartType: currentChartType,
|
|
23328
|
+
vizMode: currentVizMode,
|
|
23329
|
+
theme: currentTheme,
|
|
23330
|
+
isRendered
|
|
23331
|
+
};
|
|
23332
|
+
},
|
|
23333
|
+
exportCSV(filename) {
|
|
23334
|
+
if (!cachedData) {
|
|
23335
|
+
log("warn", "Cannot export: no data available");
|
|
23336
|
+
return;
|
|
23337
|
+
}
|
|
23338
|
+
const defaultFilename = config.exportFilename || `${config.domain}-consumo-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
23339
|
+
const csvContent = generateCSVContent(cachedData);
|
|
23340
|
+
downloadCSV(csvContent, filename || defaultFilename);
|
|
23341
|
+
},
|
|
23342
|
+
setTheme(theme) {
|
|
23343
|
+
if (currentTheme === theme) return;
|
|
23344
|
+
log("log", `Changing theme to: ${theme}`);
|
|
23345
|
+
currentTheme = theme;
|
|
23346
|
+
if (cachedData && chartInstance) {
|
|
23347
|
+
const canvas = validateCanvas();
|
|
23348
|
+
if (canvas) {
|
|
23349
|
+
chartInstance.destroy();
|
|
23350
|
+
const ctx = canvas.getContext("2d");
|
|
23351
|
+
chartInstance = new Chart(ctx, buildChartConfig(cachedData));
|
|
23352
|
+
}
|
|
23353
|
+
}
|
|
23354
|
+
},
|
|
23355
|
+
setIdealRange(range) {
|
|
23356
|
+
const rangeChanged = JSON.stringify(currentIdealRange) !== JSON.stringify(range);
|
|
23357
|
+
if (!rangeChanged) return;
|
|
23358
|
+
if (range) {
|
|
23359
|
+
log("log", `Setting ideal range: ${range.min} - ${range.max}`);
|
|
23360
|
+
} else {
|
|
23361
|
+
log("log", "Clearing ideal range");
|
|
23362
|
+
}
|
|
23363
|
+
currentIdealRange = range;
|
|
23364
|
+
if (cachedData && chartInstance) {
|
|
23365
|
+
const canvas = validateCanvas();
|
|
23366
|
+
if (canvas) {
|
|
23367
|
+
chartInstance.destroy();
|
|
23368
|
+
const ctx = canvas.getContext("2d");
|
|
23369
|
+
chartInstance = new Chart(ctx, buildChartConfig(cachedData));
|
|
23370
|
+
}
|
|
23371
|
+
}
|
|
23372
|
+
},
|
|
23373
|
+
getIdealRange() {
|
|
23374
|
+
return currentIdealRange;
|
|
23375
|
+
}
|
|
23376
|
+
};
|
|
23377
|
+
return instance;
|
|
23378
|
+
}
|
|
23379
|
+
|
|
23380
|
+
// src/components/Consumption7DaysChart/createConsumptionModal.ts
|
|
23381
|
+
var DOMAIN_CONFIG3 = {
|
|
23382
|
+
energy: { name: "Energia", icon: "\u26A1" },
|
|
23383
|
+
water: { name: "\xC1gua", icon: "\u{1F4A7}" },
|
|
23384
|
+
gas: { name: "G\xE1s", icon: "\u{1F525}" },
|
|
23385
|
+
temperature: { name: "Temperatura", icon: "\u{1F321}\uFE0F" }
|
|
23386
|
+
};
|
|
23387
|
+
function createConsumptionModal(config) {
|
|
23388
|
+
const modalId = `myio-consumption-modal-${Date.now()}`;
|
|
23389
|
+
let modalElement = null;
|
|
23390
|
+
let chartInstance = null;
|
|
23391
|
+
let headerInstance = null;
|
|
23392
|
+
let currentTheme = config.theme ?? "light";
|
|
23393
|
+
let currentChartType = config.defaultChartType ?? "line";
|
|
23394
|
+
let currentVizMode = config.defaultVizMode ?? "total";
|
|
23395
|
+
let isMaximized = false;
|
|
23396
|
+
const domainCfg = DOMAIN_CONFIG3[config.domain] || { name: config.domain, icon: "\u{1F4CA}" };
|
|
23397
|
+
const title = config.title || `${domainCfg.name} - Hist\xF3rico de Consumo`;
|
|
23398
|
+
function getThemeColors2() {
|
|
23399
|
+
return THEME_COLORS[currentTheme];
|
|
23400
|
+
}
|
|
23401
|
+
function renderModal4() {
|
|
23402
|
+
const colors = getThemeColors2();
|
|
23403
|
+
const exportFormats = config.exportFormats || ["csv"];
|
|
23404
|
+
headerInstance = createModalHeader({
|
|
23405
|
+
id: modalId,
|
|
23406
|
+
title,
|
|
23407
|
+
icon: domainCfg.icon,
|
|
23408
|
+
theme: currentTheme,
|
|
23409
|
+
isMaximized,
|
|
23410
|
+
exportFormats,
|
|
23411
|
+
onExport: (format) => {
|
|
23412
|
+
if (config.onExport) {
|
|
23413
|
+
config.onExport(format);
|
|
23414
|
+
} else {
|
|
23415
|
+
if (format === "csv") {
|
|
23416
|
+
chartInstance?.exportCSV();
|
|
23417
|
+
} else {
|
|
23418
|
+
console.warn(`[ConsumptionModal] Export format "${format}" requires custom onExport handler`);
|
|
23419
|
+
}
|
|
23420
|
+
}
|
|
23421
|
+
},
|
|
23422
|
+
onThemeToggle: (theme) => {
|
|
23423
|
+
currentTheme = theme;
|
|
23424
|
+
chartInstance?.setTheme(currentTheme);
|
|
23425
|
+
updateModal();
|
|
23426
|
+
},
|
|
23427
|
+
onMaximize: (maximized) => {
|
|
23428
|
+
isMaximized = maximized;
|
|
23429
|
+
updateModal();
|
|
23430
|
+
},
|
|
23431
|
+
onClose: () => {
|
|
23432
|
+
instance.close();
|
|
23433
|
+
}
|
|
23434
|
+
});
|
|
23435
|
+
return `
|
|
23436
|
+
<div class="myio-consumption-modal-overlay" style="
|
|
23437
|
+
position: fixed;
|
|
23438
|
+
top: 0;
|
|
23439
|
+
left: 0;
|
|
23440
|
+
width: 100%;
|
|
23441
|
+
height: 100%;
|
|
23442
|
+
background: rgba(0, 0, 0, 0.5);
|
|
23443
|
+
backdrop-filter: blur(2px);
|
|
23444
|
+
z-index: 99998;
|
|
23445
|
+
display: flex;
|
|
23446
|
+
justify-content: center;
|
|
23447
|
+
align-items: center;
|
|
23448
|
+
">
|
|
23449
|
+
<div class="myio-consumption-modal-content" style="
|
|
23450
|
+
background: ${colors.chartBackground};
|
|
23451
|
+
border-radius: ${isMaximized ? "0" : "10px"};
|
|
23452
|
+
width: ${isMaximized ? "100%" : "95%"};
|
|
23453
|
+
max-width: ${isMaximized ? "100%" : "1200px"};
|
|
23454
|
+
height: ${isMaximized ? "100%" : "85vh"};
|
|
23455
|
+
display: flex;
|
|
23456
|
+
flex-direction: column;
|
|
23457
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
23458
|
+
overflow: hidden;
|
|
23459
|
+
">
|
|
23460
|
+
<!-- MyIO Premium Header (using ModalHeader component) -->
|
|
23461
|
+
${headerInstance.render()}
|
|
23462
|
+
|
|
23463
|
+
<!-- Controls Bar -->
|
|
23464
|
+
<div class="myio-consumption-modal-controls" style="
|
|
23465
|
+
display: flex;
|
|
23466
|
+
gap: 16px;
|
|
23467
|
+
padding: 12px 16px;
|
|
23468
|
+
background: ${currentTheme === "dark" ? "#374151" : "#f7f7f7"};
|
|
23469
|
+
border-bottom: 1px solid ${colors.border};
|
|
23470
|
+
align-items: center;
|
|
23471
|
+
flex-wrap: wrap;
|
|
23472
|
+
">
|
|
23473
|
+
<!-- Viz Mode Tabs -->
|
|
23474
|
+
<div style="display: flex; gap: 2px; background: ${currentTheme === "dark" ? "#4b5563" : "#e5e7eb"}; border-radius: 8px; padding: 2px;">
|
|
23475
|
+
<button id="${modalId}-viz-total" style="
|
|
23476
|
+
padding: 6px 12px;
|
|
23477
|
+
border: none;
|
|
23478
|
+
border-radius: 6px;
|
|
23479
|
+
font-size: 13px;
|
|
23480
|
+
cursor: pointer;
|
|
23481
|
+
transition: all 0.2s;
|
|
23482
|
+
background: ${currentVizMode === "total" ? "#3e1a7d" : "transparent"};
|
|
23483
|
+
color: ${currentVizMode === "total" ? "white" : colors.text};
|
|
23484
|
+
">Consolidado</button>
|
|
23485
|
+
<button id="${modalId}-viz-separate" style="
|
|
23486
|
+
padding: 6px 12px;
|
|
23487
|
+
border: none;
|
|
23488
|
+
border-radius: 6px;
|
|
23489
|
+
font-size: 13px;
|
|
23490
|
+
cursor: pointer;
|
|
23491
|
+
transition: all 0.2s;
|
|
23492
|
+
background: ${currentVizMode === "separate" ? "#3e1a7d" : "transparent"};
|
|
23493
|
+
color: ${currentVizMode === "separate" ? "white" : colors.text};
|
|
23494
|
+
">Por Shopping</button>
|
|
23495
|
+
</div>
|
|
23496
|
+
|
|
23497
|
+
<!-- Chart Type Tabs -->
|
|
23498
|
+
<div style="display: flex; gap: 2px; background: ${currentTheme === "dark" ? "#4b5563" : "#e5e7eb"}; border-radius: 8px; padding: 2px;">
|
|
23499
|
+
<button id="${modalId}-type-line" style="
|
|
23500
|
+
padding: 6px 12px;
|
|
23501
|
+
border: none;
|
|
23502
|
+
border-radius: 6px;
|
|
23503
|
+
font-size: 13px;
|
|
23504
|
+
cursor: pointer;
|
|
23505
|
+
transition: all 0.2s;
|
|
23506
|
+
background: ${currentChartType === "line" ? "#3e1a7d" : "transparent"};
|
|
23507
|
+
color: ${currentChartType === "line" ? "white" : colors.text};
|
|
23508
|
+
">Linhas</button>
|
|
23509
|
+
<button id="${modalId}-type-bar" style="
|
|
23510
|
+
padding: 6px 12px;
|
|
23511
|
+
border: none;
|
|
23512
|
+
border-radius: 6px;
|
|
23513
|
+
font-size: 13px;
|
|
23514
|
+
cursor: pointer;
|
|
23515
|
+
transition: all 0.2s;
|
|
23516
|
+
background: ${currentChartType === "bar" ? "#3e1a7d" : "transparent"};
|
|
23517
|
+
color: ${currentChartType === "bar" ? "white" : colors.text};
|
|
23518
|
+
">Barras</button>
|
|
23519
|
+
</div>
|
|
23520
|
+
</div>
|
|
23521
|
+
|
|
23522
|
+
<!-- Chart Container -->
|
|
23523
|
+
<div style="
|
|
23524
|
+
flex: 1;
|
|
23525
|
+
padding: 16px;
|
|
23526
|
+
min-height: 0;
|
|
23527
|
+
position: relative;
|
|
23528
|
+
background: ${colors.chartBackground};
|
|
23529
|
+
">
|
|
23530
|
+
<canvas id="${modalId}-chart" style="width: 100%; height: 100%;"></canvas>
|
|
23531
|
+
</div>
|
|
23532
|
+
</div>
|
|
23533
|
+
</div>
|
|
23534
|
+
`;
|
|
23535
|
+
}
|
|
23536
|
+
function setupListeners() {
|
|
23537
|
+
if (!modalElement) return;
|
|
23538
|
+
headerInstance?.attachListeners();
|
|
23539
|
+
document.getElementById(`${modalId}-viz-total`)?.addEventListener("click", () => {
|
|
23540
|
+
currentVizMode = "total";
|
|
23541
|
+
chartInstance?.setVizMode("total");
|
|
23542
|
+
updateControlStyles();
|
|
23543
|
+
});
|
|
23544
|
+
document.getElementById(`${modalId}-viz-separate`)?.addEventListener("click", () => {
|
|
23545
|
+
currentVizMode = "separate";
|
|
23546
|
+
chartInstance?.setVizMode("separate");
|
|
23547
|
+
updateControlStyles();
|
|
23548
|
+
});
|
|
23549
|
+
document.getElementById(`${modalId}-type-line`)?.addEventListener("click", () => {
|
|
23550
|
+
currentChartType = "line";
|
|
23551
|
+
chartInstance?.setChartType("line");
|
|
23552
|
+
updateControlStyles();
|
|
23553
|
+
});
|
|
23554
|
+
document.getElementById(`${modalId}-type-bar`)?.addEventListener("click", () => {
|
|
23555
|
+
currentChartType = "bar";
|
|
23556
|
+
chartInstance?.setChartType("bar");
|
|
23557
|
+
updateControlStyles();
|
|
23558
|
+
});
|
|
23559
|
+
modalElement.querySelector(".myio-consumption-modal-overlay")?.addEventListener("click", (e) => {
|
|
23560
|
+
if (e.target.classList.contains("myio-consumption-modal-overlay")) {
|
|
23561
|
+
instance.close();
|
|
23562
|
+
}
|
|
23563
|
+
});
|
|
23564
|
+
const handleKeydown = (e) => {
|
|
23565
|
+
if (e.key === "Escape") {
|
|
23566
|
+
instance.close();
|
|
23567
|
+
}
|
|
23568
|
+
};
|
|
23569
|
+
document.addEventListener("keydown", handleKeydown);
|
|
23570
|
+
modalElement.__handleKeydown = handleKeydown;
|
|
23571
|
+
}
|
|
23572
|
+
function updateControlStyles() {
|
|
23573
|
+
const colors = getThemeColors2();
|
|
23574
|
+
const vizTotalBtn = document.getElementById(`${modalId}-viz-total`);
|
|
23575
|
+
const vizSeparateBtn = document.getElementById(`${modalId}-viz-separate`);
|
|
23576
|
+
if (vizTotalBtn) {
|
|
23577
|
+
vizTotalBtn.style.background = currentVizMode === "total" ? "#3e1a7d" : "transparent";
|
|
23578
|
+
vizTotalBtn.style.color = currentVizMode === "total" ? "white" : colors.text;
|
|
23579
|
+
}
|
|
23580
|
+
if (vizSeparateBtn) {
|
|
23581
|
+
vizSeparateBtn.style.background = currentVizMode === "separate" ? "#3e1a7d" : "transparent";
|
|
23582
|
+
vizSeparateBtn.style.color = currentVizMode === "separate" ? "white" : colors.text;
|
|
23583
|
+
}
|
|
23584
|
+
const typeLineBtn = document.getElementById(`${modalId}-type-line`);
|
|
23585
|
+
const typeBarBtn = document.getElementById(`${modalId}-type-bar`);
|
|
23586
|
+
if (typeLineBtn) {
|
|
23587
|
+
typeLineBtn.style.background = currentChartType === "line" ? "#3e1a7d" : "transparent";
|
|
23588
|
+
typeLineBtn.style.color = currentChartType === "line" ? "white" : colors.text;
|
|
23589
|
+
}
|
|
23590
|
+
if (typeBarBtn) {
|
|
23591
|
+
typeBarBtn.style.background = currentChartType === "bar" ? "#3e1a7d" : "transparent";
|
|
23592
|
+
typeBarBtn.style.color = currentChartType === "bar" ? "white" : colors.text;
|
|
23593
|
+
}
|
|
23594
|
+
}
|
|
23595
|
+
function updateModal() {
|
|
23596
|
+
if (!modalElement) return;
|
|
23597
|
+
const cachedData = chartInstance?.getCachedData();
|
|
23598
|
+
headerInstance?.destroy();
|
|
23599
|
+
chartInstance?.destroy();
|
|
23600
|
+
modalElement.innerHTML = renderModal4();
|
|
23601
|
+
setupListeners();
|
|
23602
|
+
if (cachedData) {
|
|
23603
|
+
chartInstance = createConsumption7DaysChart({
|
|
23604
|
+
...config,
|
|
23605
|
+
containerId: `${modalId}-chart`,
|
|
23606
|
+
theme: currentTheme,
|
|
23607
|
+
defaultChartType: currentChartType,
|
|
23608
|
+
defaultVizMode: currentVizMode
|
|
23609
|
+
});
|
|
23610
|
+
chartInstance.update(cachedData);
|
|
23611
|
+
}
|
|
23612
|
+
}
|
|
23613
|
+
const instance = {
|
|
23614
|
+
async open() {
|
|
23615
|
+
modalElement = document.createElement("div");
|
|
23616
|
+
modalElement.id = modalId;
|
|
23617
|
+
modalElement.innerHTML = renderModal4();
|
|
23618
|
+
const container = config.container || document.body;
|
|
23619
|
+
container.appendChild(modalElement);
|
|
23620
|
+
setupListeners();
|
|
23621
|
+
chartInstance = createConsumption7DaysChart({
|
|
23622
|
+
...config,
|
|
23623
|
+
containerId: `${modalId}-chart`,
|
|
23624
|
+
theme: currentTheme,
|
|
23625
|
+
defaultChartType: currentChartType,
|
|
23626
|
+
defaultVizMode: currentVizMode
|
|
23627
|
+
});
|
|
23628
|
+
await chartInstance.render();
|
|
23629
|
+
},
|
|
23630
|
+
close() {
|
|
23631
|
+
if (modalElement) {
|
|
23632
|
+
const handleKeydown = modalElement.__handleKeydown;
|
|
23633
|
+
if (handleKeydown) {
|
|
23634
|
+
document.removeEventListener("keydown", handleKeydown);
|
|
23635
|
+
}
|
|
23636
|
+
headerInstance?.destroy();
|
|
23637
|
+
headerInstance = null;
|
|
23638
|
+
chartInstance?.destroy();
|
|
23639
|
+
chartInstance = null;
|
|
23640
|
+
modalElement.remove();
|
|
23641
|
+
modalElement = null;
|
|
23642
|
+
config.onClose?.();
|
|
23643
|
+
}
|
|
23644
|
+
},
|
|
23645
|
+
getChart() {
|
|
23646
|
+
return chartInstance;
|
|
23647
|
+
},
|
|
23648
|
+
destroy() {
|
|
23649
|
+
instance.close();
|
|
23650
|
+
}
|
|
23651
|
+
};
|
|
23652
|
+
return instance;
|
|
23653
|
+
}
|
|
23654
|
+
|
|
23655
|
+
// src/components/ExportData/index.ts
|
|
23656
|
+
var DEFAULT_COLORS3 = {
|
|
23657
|
+
primary: "#3e1a7d",
|
|
23658
|
+
// MyIO purple
|
|
23659
|
+
secondary: "#6b4c9a",
|
|
23660
|
+
// Light purple
|
|
23661
|
+
accent: "#00bcd4",
|
|
23662
|
+
// Cyan accent
|
|
23663
|
+
background: "#ffffff",
|
|
23664
|
+
// White
|
|
23665
|
+
text: "#333333",
|
|
23666
|
+
// Dark gray
|
|
23667
|
+
chartColors: ["#3e1a7d", "#00bcd4", "#4caf50", "#ff9800", "#e91e63", "#9c27b0"]
|
|
23668
|
+
};
|
|
23669
|
+
var DOMAIN_ICONS = {
|
|
23670
|
+
energy: "\u26A1",
|
|
23671
|
+
// Lightning bolt
|
|
23672
|
+
water: "\u{1F4A7}",
|
|
23673
|
+
// Water drop
|
|
23674
|
+
temperature: "\u{1F321}\uFE0F"
|
|
23675
|
+
// Thermometer
|
|
23676
|
+
};
|
|
23677
|
+
var DOMAIN_LABELS = {
|
|
23678
|
+
energy: "Energia",
|
|
23679
|
+
water: "\xC1gua",
|
|
23680
|
+
temperature: "Temperatura"
|
|
23681
|
+
};
|
|
23682
|
+
var DOMAIN_LABELS_EN = {
|
|
23683
|
+
energy: "Energy",
|
|
23684
|
+
water: "Water",
|
|
23685
|
+
temperature: "Temperature"
|
|
23686
|
+
};
|
|
23687
|
+
var DOMAIN_UNITS = {
|
|
23688
|
+
energy: "kWh",
|
|
23689
|
+
water: "m\xB3",
|
|
23690
|
+
temperature: "\xB0C"
|
|
23691
|
+
};
|
|
23692
|
+
var CSV_SEPARATORS = {
|
|
23693
|
+
"pt-BR": ";",
|
|
23694
|
+
"en-US": ",",
|
|
23695
|
+
"default": ";"
|
|
23696
|
+
};
|
|
23697
|
+
function formatDateForFilename(date) {
|
|
23698
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
23699
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
|
|
23700
|
+
}
|
|
23701
|
+
function sanitizeFilename(str) {
|
|
23702
|
+
return str.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, "_").substring(0, 50);
|
|
23703
|
+
}
|
|
23704
|
+
function generateFilename(data, config) {
|
|
23705
|
+
const timestamp = formatDateForFilename(/* @__PURE__ */ new Date());
|
|
23706
|
+
const domainLabel = DOMAIN_LABELS_EN[config.domain].toUpperCase();
|
|
23707
|
+
const ext = config.formatExport;
|
|
23708
|
+
let baseName = "export";
|
|
23709
|
+
if ("device" in data && data.device) {
|
|
23710
|
+
const device = data.device;
|
|
23711
|
+
const label = device.label || device.name || "device";
|
|
23712
|
+
const identifier = device.identifier ? `-${device.identifier}` : "";
|
|
23713
|
+
baseName = `${sanitizeFilename(label)}${identifier}`;
|
|
23714
|
+
} else if ("customer" in data && data.customer?.customerName) {
|
|
23715
|
+
baseName = sanitizeFilename(data.customer.customerName);
|
|
23716
|
+
} else if ("groupName" in data) {
|
|
23717
|
+
baseName = sanitizeFilename(data.groupName);
|
|
23718
|
+
}
|
|
23719
|
+
return `${baseName}-${domainLabel}-${timestamp}.${ext}`;
|
|
23720
|
+
}
|
|
23721
|
+
function normalizeTimestamp(ts) {
|
|
23722
|
+
if (ts instanceof Date) return ts;
|
|
23723
|
+
if (typeof ts === "number") return new Date(ts);
|
|
23724
|
+
return new Date(ts);
|
|
23725
|
+
}
|
|
23726
|
+
function calculateStats2(dataPoints) {
|
|
23727
|
+
if (dataPoints.length === 0) {
|
|
23728
|
+
return { min: 0, max: 0, average: 0, sum: 0, count: 0 };
|
|
23729
|
+
}
|
|
23730
|
+
const values = dataPoints.map((d) => d.value);
|
|
23731
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
23732
|
+
return {
|
|
23733
|
+
min: Math.min(...values),
|
|
23734
|
+
max: Math.max(...values),
|
|
23735
|
+
average: sum / values.length,
|
|
23736
|
+
sum,
|
|
23737
|
+
count: values.length
|
|
23738
|
+
};
|
|
23739
|
+
}
|
|
23740
|
+
function formatNumber2(value, locale, decimals = 2) {
|
|
23741
|
+
return new Intl.NumberFormat(locale, {
|
|
23742
|
+
minimumFractionDigits: decimals,
|
|
23743
|
+
maximumFractionDigits: decimals
|
|
23744
|
+
}).format(value);
|
|
23745
|
+
}
|
|
23746
|
+
function formatDate3(date, locale) {
|
|
23747
|
+
return new Intl.DateTimeFormat(locale, {
|
|
23748
|
+
year: "numeric",
|
|
23749
|
+
month: "2-digit",
|
|
23750
|
+
day: "2-digit",
|
|
23751
|
+
hour: "2-digit",
|
|
23752
|
+
minute: "2-digit"
|
|
23753
|
+
}).format(date);
|
|
23754
|
+
}
|
|
23755
|
+
function generateCSV(data, config) {
|
|
23756
|
+
const sep = CSV_SEPARATORS[config.locale] || CSV_SEPARATORS["default"];
|
|
23757
|
+
const rows = [];
|
|
23758
|
+
const escapeCSV = (val) => {
|
|
23759
|
+
const str = String(val ?? "");
|
|
23760
|
+
if (str.includes(sep) || str.includes('"') || str.includes("\n")) {
|
|
23761
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
23762
|
+
}
|
|
23763
|
+
return str;
|
|
23764
|
+
};
|
|
23765
|
+
const formatNumCSV = (val) => {
|
|
23766
|
+
return formatNumber2(val, config.locale);
|
|
23767
|
+
};
|
|
23768
|
+
if ("device" in data && "data" in data && Array.isArray(data.data)) {
|
|
23769
|
+
const deviceData = data;
|
|
23770
|
+
rows.push(["Timestamp", config.domainLabel, `Unit (${config.domainUnit})`]);
|
|
23771
|
+
for (const point of deviceData.data) {
|
|
23772
|
+
const ts = normalizeTimestamp(point.timestamp);
|
|
23773
|
+
rows.push([
|
|
23774
|
+
formatDate3(ts, config.locale),
|
|
23775
|
+
formatNumCSV(point.value),
|
|
23776
|
+
point.unit || config.domainUnit
|
|
23777
|
+
]);
|
|
23778
|
+
}
|
|
23779
|
+
if (config.includeStats) {
|
|
23780
|
+
const stats = calculateStats2(deviceData.data);
|
|
23781
|
+
rows.push([]);
|
|
23782
|
+
rows.push(["Statistics", "", ""]);
|
|
23783
|
+
rows.push(["Minimum", formatNumCSV(stats.min), config.domainUnit]);
|
|
23784
|
+
rows.push(["Maximum", formatNumCSV(stats.max), config.domainUnit]);
|
|
23785
|
+
rows.push(["Average", formatNumCSV(stats.average), config.domainUnit]);
|
|
23786
|
+
rows.push(["Total", formatNumCSV(stats.sum), config.domainUnit]);
|
|
23787
|
+
rows.push(["Count", String(stats.count), "points"]);
|
|
23788
|
+
}
|
|
23789
|
+
} else if ("devices" in data && Array.isArray(data.devices)) {
|
|
23790
|
+
const compData = data;
|
|
23791
|
+
const deviceHeaders = compData.devices.map(
|
|
23792
|
+
(d) => d.device.label || d.device.name || "Device"
|
|
23793
|
+
);
|
|
23794
|
+
rows.push(["Timestamp", ...deviceHeaders]);
|
|
23795
|
+
const allTimestamps = /* @__PURE__ */ new Set();
|
|
23796
|
+
compData.devices.forEach((d) => {
|
|
23797
|
+
d.data.forEach((point) => {
|
|
23798
|
+
allTimestamps.add(normalizeTimestamp(point.timestamp).getTime());
|
|
23799
|
+
});
|
|
23800
|
+
});
|
|
23801
|
+
const sortedTimestamps = Array.from(allTimestamps).sort((a, b) => a - b);
|
|
23802
|
+
for (const ts of sortedTimestamps) {
|
|
23803
|
+
const row = [formatDate3(new Date(ts), config.locale)];
|
|
23804
|
+
for (const device of compData.devices) {
|
|
23805
|
+
const point = device.data.find(
|
|
23806
|
+
(p) => normalizeTimestamp(p.timestamp).getTime() === ts
|
|
23807
|
+
);
|
|
23808
|
+
row.push(point ? formatNumCSV(point.value) : "");
|
|
23809
|
+
}
|
|
23810
|
+
rows.push(row);
|
|
23811
|
+
}
|
|
23812
|
+
}
|
|
23813
|
+
return rows.map((row) => row.map(escapeCSV).join(sep)).join("\r\n");
|
|
23814
|
+
}
|
|
23815
|
+
function generateXLSX(data, config) {
|
|
23816
|
+
return generateCSV(data, config);
|
|
23817
|
+
}
|
|
23818
|
+
function generatePDFContent(data, config) {
|
|
23819
|
+
const { colors, domainIcon, domainLabel, domainUnit, locale, includeStats, includeChart } = config;
|
|
23820
|
+
let deviceLabel = "Export";
|
|
23821
|
+
let customerName = "";
|
|
23822
|
+
let identifier = "";
|
|
23823
|
+
let dataPoints = [];
|
|
23824
|
+
if ("device" in data && "data" in data) {
|
|
23825
|
+
const deviceData = data;
|
|
23826
|
+
deviceLabel = deviceData.device.label || deviceData.device.name || "Device";
|
|
23827
|
+
identifier = deviceData.device.identifier || "";
|
|
23828
|
+
customerName = deviceData.customer?.customerName || "";
|
|
23829
|
+
dataPoints = deviceData.data;
|
|
23830
|
+
}
|
|
23831
|
+
const stats = calculateStats2(dataPoints);
|
|
23832
|
+
const tableRows = dataPoints.slice(0, 100).map((point) => {
|
|
23833
|
+
const ts = normalizeTimestamp(point.timestamp);
|
|
23834
|
+
return `
|
|
23835
|
+
<tr>
|
|
23836
|
+
<td style="padding: 8px; border-bottom: 1px solid #eee;">${formatDate3(ts, locale)}</td>
|
|
23837
|
+
<td style="padding: 8px; border-bottom: 1px solid #eee; text-align: right;">${formatNumber2(point.value, locale)}</td>
|
|
23838
|
+
<td style="padding: 8px; border-bottom: 1px solid #eee;">${point.unit || domainUnit}</td>
|
|
23839
|
+
</tr>
|
|
23840
|
+
`;
|
|
23841
|
+
}).join("");
|
|
23842
|
+
const statsSection = includeStats ? `
|
|
23843
|
+
<div style="margin-top: 24px; padding: 16px; background: #f5f5f5; border-radius: 8px;">
|
|
23844
|
+
<h3 style="margin: 0 0 12px 0; color: ${colors.primary};">Statistics</h3>
|
|
23845
|
+
<table style="width: 100%;">
|
|
23846
|
+
<tr>
|
|
23847
|
+
<td><strong>Minimum:</strong></td>
|
|
23848
|
+
<td>${formatNumber2(stats.min, locale)} ${domainUnit}</td>
|
|
23849
|
+
<td><strong>Maximum:</strong></td>
|
|
23850
|
+
<td>${formatNumber2(stats.max, locale)} ${domainUnit}</td>
|
|
23851
|
+
</tr>
|
|
23852
|
+
<tr>
|
|
23853
|
+
<td><strong>Average:</strong></td>
|
|
23854
|
+
<td>${formatNumber2(stats.average, locale)} ${domainUnit}</td>
|
|
23855
|
+
<td><strong>Total:</strong></td>
|
|
23856
|
+
<td>${formatNumber2(stats.sum, locale)} ${domainUnit}</td>
|
|
23857
|
+
</tr>
|
|
23858
|
+
</table>
|
|
23859
|
+
</div>
|
|
23860
|
+
` : "";
|
|
23861
|
+
return `
|
|
23862
|
+
<!DOCTYPE html>
|
|
23863
|
+
<html>
|
|
23864
|
+
<head>
|
|
23865
|
+
<meta charset="UTF-8">
|
|
23866
|
+
<title>${deviceLabel} - ${domainLabel} Report</title>
|
|
23867
|
+
<style>
|
|
23868
|
+
body {
|
|
23869
|
+
font-family: 'Roboto', Arial, sans-serif;
|
|
23870
|
+
margin: 0;
|
|
23871
|
+
padding: 24px;
|
|
23872
|
+
color: ${colors.text};
|
|
23873
|
+
background: ${colors.background};
|
|
23874
|
+
}
|
|
23875
|
+
.header {
|
|
23876
|
+
background: ${colors.primary};
|
|
23877
|
+
color: white;
|
|
23878
|
+
padding: 20px;
|
|
23879
|
+
border-radius: 8px;
|
|
23880
|
+
margin-bottom: 24px;
|
|
23881
|
+
}
|
|
23882
|
+
.header h1 {
|
|
23883
|
+
margin: 0;
|
|
23884
|
+
font-size: 24px;
|
|
23885
|
+
}
|
|
23886
|
+
.header .subtitle {
|
|
23887
|
+
opacity: 0.9;
|
|
23888
|
+
margin-top: 8px;
|
|
23889
|
+
}
|
|
23890
|
+
.device-info {
|
|
23891
|
+
display: flex;
|
|
23892
|
+
gap: 16px;
|
|
23893
|
+
margin-bottom: 16px;
|
|
23894
|
+
padding: 12px;
|
|
23895
|
+
background: #f5f5f5;
|
|
23896
|
+
border-radius: 8px;
|
|
23897
|
+
}
|
|
23898
|
+
.device-info span {
|
|
23899
|
+
padding: 4px 12px;
|
|
23900
|
+
background: ${colors.secondary};
|
|
23901
|
+
color: white;
|
|
23902
|
+
border-radius: 4px;
|
|
23903
|
+
font-size: 14px;
|
|
23904
|
+
}
|
|
23905
|
+
table {
|
|
23906
|
+
width: 100%;
|
|
23907
|
+
border-collapse: collapse;
|
|
23908
|
+
}
|
|
23909
|
+
th {
|
|
23910
|
+
background: ${colors.primary};
|
|
23911
|
+
color: white;
|
|
23912
|
+
padding: 12px 8px;
|
|
23913
|
+
text-align: left;
|
|
23914
|
+
}
|
|
23915
|
+
th:nth-child(2) {
|
|
23916
|
+
text-align: right;
|
|
23917
|
+
}
|
|
23918
|
+
.footer {
|
|
23919
|
+
margin-top: 24px;
|
|
23920
|
+
padding-top: 16px;
|
|
23921
|
+
border-top: 1px solid #eee;
|
|
23922
|
+
text-align: center;
|
|
23923
|
+
font-size: 12px;
|
|
23924
|
+
color: #999;
|
|
23925
|
+
}
|
|
23926
|
+
@media print {
|
|
23927
|
+
body { padding: 0; }
|
|
23928
|
+
.header { border-radius: 0; }
|
|
23929
|
+
}
|
|
23930
|
+
</style>
|
|
23931
|
+
</head>
|
|
23932
|
+
<body>
|
|
23933
|
+
<div class="header">
|
|
23934
|
+
<h1>${domainIcon} ${deviceLabel}</h1>
|
|
23935
|
+
<div class="subtitle">${domainLabel} Report - Generated ${formatDate3(/* @__PURE__ */ new Date(), locale)}</div>
|
|
23936
|
+
</div>
|
|
23937
|
+
|
|
23938
|
+
${customerName ? `<div class="customer-name" style="margin-bottom: 16px; font-size: 18px;"><strong>Customer:</strong> ${customerName}</div>` : ""}
|
|
23939
|
+
|
|
23940
|
+
${identifier ? `
|
|
23941
|
+
<div class="device-info">
|
|
23942
|
+
<span>ID: ${identifier}</span>
|
|
23943
|
+
<span>Domain: ${domainLabel}</span>
|
|
23944
|
+
<span>Unit: ${domainUnit}</span>
|
|
23945
|
+
</div>
|
|
23946
|
+
` : ""}
|
|
23947
|
+
|
|
23948
|
+
<table>
|
|
23949
|
+
<thead>
|
|
23950
|
+
<tr>
|
|
23951
|
+
<th>Timestamp</th>
|
|
23952
|
+
<th>${domainLabel} (${domainUnit})</th>
|
|
23953
|
+
<th>Unit</th>
|
|
23954
|
+
</tr>
|
|
23955
|
+
</thead>
|
|
23956
|
+
<tbody>
|
|
23957
|
+
${tableRows}
|
|
23958
|
+
${dataPoints.length > 100 ? `<tr><td colspan="3" style="text-align: center; padding: 16px; color: #999;">... and ${dataPoints.length - 100} more rows</td></tr>` : ""}
|
|
23959
|
+
</tbody>
|
|
23960
|
+
</table>
|
|
23961
|
+
|
|
23962
|
+
${statsSection}
|
|
23963
|
+
|
|
23964
|
+
<div class="footer">
|
|
23965
|
+
<p>${config.footerText || "Generated by MyIO Platform"}</p>
|
|
23966
|
+
</div>
|
|
23967
|
+
</body>
|
|
23968
|
+
</html>
|
|
23969
|
+
`;
|
|
23970
|
+
}
|
|
23971
|
+
function buildTemplateExport(params) {
|
|
23972
|
+
const {
|
|
23973
|
+
domain,
|
|
23974
|
+
formatExport,
|
|
23975
|
+
typeExport,
|
|
23976
|
+
colorsPallet,
|
|
23977
|
+
locale = "pt-BR",
|
|
23978
|
+
includeChart = formatExport === "pdf",
|
|
23979
|
+
includeStats = true,
|
|
23980
|
+
headerText,
|
|
23981
|
+
footerText
|
|
23982
|
+
} = params;
|
|
23983
|
+
const colors = {
|
|
23984
|
+
...DEFAULT_COLORS3,
|
|
23985
|
+
...colorsPallet,
|
|
23986
|
+
chartColors: colorsPallet?.chartColors || DEFAULT_COLORS3.chartColors
|
|
23987
|
+
};
|
|
23988
|
+
return {
|
|
23989
|
+
domain,
|
|
23990
|
+
formatExport,
|
|
23991
|
+
typeExport,
|
|
23992
|
+
colors,
|
|
23993
|
+
locale,
|
|
23994
|
+
includeChart,
|
|
23995
|
+
includeStats,
|
|
23996
|
+
headerText: headerText || `${DOMAIN_LABELS[domain]} Report`,
|
|
23997
|
+
footerText: footerText || "Generated by MyIO Platform",
|
|
23998
|
+
domainIcon: DOMAIN_ICONS[domain],
|
|
23999
|
+
domainLabel: DOMAIN_LABELS[domain],
|
|
24000
|
+
domainUnit: DOMAIN_UNITS[domain]
|
|
24001
|
+
};
|
|
24002
|
+
}
|
|
24003
|
+
function myioExportData(data, config, options) {
|
|
24004
|
+
const filename = generateFilename(data, config);
|
|
24005
|
+
let allDataPoints = [];
|
|
24006
|
+
if ("data" in data && Array.isArray(data.data)) {
|
|
24007
|
+
allDataPoints = data.data;
|
|
24008
|
+
} else if ("devices" in data && Array.isArray(data.devices)) {
|
|
24009
|
+
allDataPoints = data.devices.flatMap((d) => d.data);
|
|
24010
|
+
}
|
|
24011
|
+
const stats = calculateStats2(allDataPoints);
|
|
24012
|
+
const instance = {
|
|
24013
|
+
async export() {
|
|
24014
|
+
try {
|
|
24015
|
+
options?.onProgress?.(10, "Generating content...");
|
|
24016
|
+
let content;
|
|
24017
|
+
let mimeType;
|
|
24018
|
+
let finalFilename = filename;
|
|
24019
|
+
switch (config.formatExport) {
|
|
24020
|
+
case "csv":
|
|
24021
|
+
content = generateCSV(data, config);
|
|
24022
|
+
mimeType = "text/csv;charset=utf-8;";
|
|
24023
|
+
break;
|
|
24024
|
+
case "xlsx":
|
|
24025
|
+
content = generateXLSX(data, config);
|
|
24026
|
+
mimeType = "text/csv;charset=utf-8;";
|
|
24027
|
+
finalFilename = filename.replace(".xlsx", ".csv");
|
|
24028
|
+
break;
|
|
24029
|
+
case "pdf":
|
|
24030
|
+
content = generatePDFContent(data, config);
|
|
24031
|
+
mimeType = "text/html;charset=utf-8;";
|
|
24032
|
+
finalFilename = filename.replace(".pdf", ".html");
|
|
24033
|
+
break;
|
|
24034
|
+
default:
|
|
24035
|
+
throw new Error(`Unsupported format: ${config.formatExport}`);
|
|
24036
|
+
}
|
|
24037
|
+
options?.onProgress?.(80, "Creating file...");
|
|
24038
|
+
const bom = config.formatExport === "csv" ? "\uFEFF" : "";
|
|
24039
|
+
const blob = new Blob([bom + content], { type: mimeType });
|
|
24040
|
+
options?.onProgress?.(100, "Export complete");
|
|
24041
|
+
return {
|
|
24042
|
+
success: true,
|
|
24043
|
+
filename: finalFilename,
|
|
24044
|
+
blob,
|
|
24045
|
+
dataUrl: URL.createObjectURL(blob)
|
|
24046
|
+
};
|
|
24047
|
+
} catch (error) {
|
|
24048
|
+
return {
|
|
24049
|
+
success: false,
|
|
24050
|
+
filename,
|
|
24051
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
24052
|
+
};
|
|
24053
|
+
}
|
|
24054
|
+
},
|
|
24055
|
+
async download() {
|
|
24056
|
+
const result = await this.export();
|
|
24057
|
+
if (!result.success || !result.blob) {
|
|
24058
|
+
console.error("Export failed:", result.error);
|
|
24059
|
+
return;
|
|
24060
|
+
}
|
|
24061
|
+
const link = document.createElement("a");
|
|
24062
|
+
link.href = URL.createObjectURL(result.blob);
|
|
24063
|
+
link.download = result.filename;
|
|
24064
|
+
link.style.display = "none";
|
|
24065
|
+
document.body.appendChild(link);
|
|
24066
|
+
link.click();
|
|
24067
|
+
document.body.removeChild(link);
|
|
24068
|
+
URL.revokeObjectURL(link.href);
|
|
24069
|
+
},
|
|
24070
|
+
async preview() {
|
|
24071
|
+
if (config.formatExport !== "pdf") {
|
|
24072
|
+
return null;
|
|
24073
|
+
}
|
|
24074
|
+
const result = await this.export();
|
|
24075
|
+
return result.dataUrl || null;
|
|
24076
|
+
},
|
|
24077
|
+
getStats() {
|
|
24078
|
+
return stats;
|
|
24079
|
+
},
|
|
24080
|
+
getFilename() {
|
|
24081
|
+
return filename;
|
|
24082
|
+
}
|
|
24083
|
+
};
|
|
24084
|
+
if (options?.autoDownload) {
|
|
24085
|
+
instance.download();
|
|
24086
|
+
}
|
|
24087
|
+
return instance;
|
|
24088
|
+
}
|
|
24089
|
+
var EXPORT_DEFAULT_COLORS = DEFAULT_COLORS3;
|
|
24090
|
+
var EXPORT_DOMAIN_ICONS = DOMAIN_ICONS;
|
|
24091
|
+
var EXPORT_DOMAIN_LABELS = DOMAIN_LABELS;
|
|
24092
|
+
var EXPORT_DOMAIN_UNITS = DOMAIN_UNITS;
|
|
24093
|
+
|
|
22281
24094
|
exports.CHART_COLORS = CHART_COLORS;
|
|
24095
|
+
exports.CONSUMPTION_CHART_COLORS = DEFAULT_COLORS;
|
|
24096
|
+
exports.CONSUMPTION_CHART_DEFAULTS = DEFAULT_CONFIG;
|
|
24097
|
+
exports.CONSUMPTION_THEME_COLORS = THEME_COLORS;
|
|
22282
24098
|
exports.ConnectionStatusType = ConnectionStatusType;
|
|
22283
24099
|
exports.DEFAULT_CLAMP_RANGE = DEFAULT_CLAMP_RANGE;
|
|
22284
24100
|
exports.DeviceStatusType = DeviceStatusType;
|
|
24101
|
+
exports.EXPORT_DEFAULT_COLORS = EXPORT_DEFAULT_COLORS;
|
|
24102
|
+
exports.EXPORT_DOMAIN_ICONS = EXPORT_DOMAIN_ICONS;
|
|
24103
|
+
exports.EXPORT_DOMAIN_LABELS = EXPORT_DOMAIN_LABELS;
|
|
24104
|
+
exports.EXPORT_DOMAIN_UNITS = EXPORT_DOMAIN_UNITS;
|
|
22285
24105
|
exports.MyIOChartModal = MyIOChartModal;
|
|
22286
24106
|
exports.MyIODraggableCard = MyIODraggableCard;
|
|
22287
24107
|
exports.MyIOSelectionStoreClass = MyIOSelectionStoreClass;
|
|
@@ -22292,11 +24112,13 @@ ${rangeText}`;
|
|
|
22292
24112
|
exports.averageByDay = averageByDay;
|
|
22293
24113
|
exports.buildListItemsThingsboardByUniqueDatasource = buildListItemsThingsboardByUniqueDatasource;
|
|
22294
24114
|
exports.buildMyioIngestionAuth = buildMyioIngestionAuth;
|
|
24115
|
+
exports.buildTemplateExport = buildTemplateExport;
|
|
22295
24116
|
exports.buildWaterReportCSV = buildWaterReportCSV;
|
|
22296
24117
|
exports.buildWaterStoresCSV = buildWaterStoresCSV;
|
|
22297
24118
|
exports.calcDeltaPercent = calcDeltaPercent;
|
|
22298
24119
|
exports.calculateDeviceStatus = calculateDeviceStatus;
|
|
22299
24120
|
exports.calculateDeviceStatusWithRanges = calculateDeviceStatusWithRanges;
|
|
24121
|
+
exports.calculateExportStats = calculateStats2;
|
|
22300
24122
|
exports.calculateStats = calculateStats;
|
|
22301
24123
|
exports.clampTemperature = clampTemperature;
|
|
22302
24124
|
exports.classify = classify;
|
|
@@ -22304,8 +24126,11 @@ ${rangeText}`;
|
|
|
22304
24126
|
exports.classifyWaterLabels = classifyWaterLabels;
|
|
22305
24127
|
exports.clearAllAuthCaches = clearAllAuthCaches;
|
|
22306
24128
|
exports.connectionStatusIcons = connectionStatusIcons;
|
|
24129
|
+
exports.createConsumption7DaysChart = createConsumption7DaysChart;
|
|
24130
|
+
exports.createConsumptionModal = createConsumptionModal;
|
|
22307
24131
|
exports.createDateRangePicker = createDateRangePicker2;
|
|
22308
24132
|
exports.createInputDateRangePickerInsideDIV = createInputDateRangePickerInsideDIV;
|
|
24133
|
+
exports.createModalHeader = createModalHeader;
|
|
22309
24134
|
exports.decodePayload = decodePayload;
|
|
22310
24135
|
exports.decodePayloadBase64Xor = decodePayloadBase64Xor;
|
|
22311
24136
|
exports.detectDeviceType = detectDeviceType;
|
|
@@ -22319,6 +24144,7 @@ ${rangeText}`;
|
|
|
22319
24144
|
exports.fetchThingsboardCustomerAttrsFromStorage = fetchThingsboardCustomerAttrsFromStorage;
|
|
22320
24145
|
exports.fetchThingsboardCustomerServerScopeAttrs = fetchThingsboardCustomerServerScopeAttrs;
|
|
22321
24146
|
exports.findValue = findValue;
|
|
24147
|
+
exports.findValueWithDefault = findValueWithDefault;
|
|
22322
24148
|
exports.fmtPerc = fmtPerc;
|
|
22323
24149
|
exports.fmtPercLegacy = fmtPerc2;
|
|
22324
24150
|
exports.formatAllInSameUnit = formatAllInSameUnit;
|
|
@@ -22326,18 +24152,24 @@ ${rangeText}`;
|
|
|
22326
24152
|
exports.formatDateForInput = formatDateForInput;
|
|
22327
24153
|
exports.formatDateToYMD = formatDateToYMD;
|
|
22328
24154
|
exports.formatDateWithTimezoneOffset = formatDateWithTimezoneOffset;
|
|
24155
|
+
exports.formatDuration = formatDuration;
|
|
22329
24156
|
exports.formatEnergy = formatEnergy;
|
|
22330
24157
|
exports.formatNumberReadable = formatNumberReadable;
|
|
24158
|
+
exports.formatRelativeTime = formatRelativeTime;
|
|
22331
24159
|
exports.formatTankHeadFromCm = formatTankHeadFromCm;
|
|
22332
24160
|
exports.formatTemperature = formatTemperature2;
|
|
24161
|
+
exports.formatWater = formatWater;
|
|
22333
24162
|
exports.formatWaterByGroup = formatWaterByGroup;
|
|
22334
24163
|
exports.formatWaterVolumeM3 = formatWaterVolumeM3;
|
|
24164
|
+
exports.formatarDuracao = formatarDuracao;
|
|
24165
|
+
exports.generateExportFilename = generateFilename;
|
|
22335
24166
|
exports.getAuthCacheStats = getAuthCacheStats;
|
|
22336
24167
|
exports.getAvailableContexts = getAvailableContexts;
|
|
22337
24168
|
exports.getConnectionStatusIcon = getConnectionStatusIcon;
|
|
22338
24169
|
exports.getDateRangeArray = getDateRangeArray;
|
|
22339
24170
|
exports.getDeviceStatusIcon = getDeviceStatusIcon;
|
|
22340
24171
|
exports.getDeviceStatusInfo = getDeviceStatusInfo;
|
|
24172
|
+
exports.getModalHeaderStyles = getModalHeaderStyles;
|
|
22341
24173
|
exports.getSaoPauloISOString = getSaoPauloISOString;
|
|
22342
24174
|
exports.getSaoPauloISOStringFixed = getSaoPauloISOStringFixed;
|
|
22343
24175
|
exports.getValueByDatakey = getValueByDatakey;
|
|
@@ -22349,8 +24181,10 @@ ${rangeText}`;
|
|
|
22349
24181
|
exports.isValidConnectionStatus = isValidConnectionStatus;
|
|
22350
24182
|
exports.isValidDeviceStatus = isValidDeviceStatus;
|
|
22351
24183
|
exports.isWaterCategory = isWaterCategory;
|
|
24184
|
+
exports.mapConnectionStatus = mapConnectionStatus;
|
|
22352
24185
|
exports.mapDeviceStatusToCardStatus = mapDeviceStatusToCardStatus;
|
|
22353
24186
|
exports.mapDeviceToConnectionStatus = mapDeviceToConnectionStatus;
|
|
24187
|
+
exports.myioExportData = myioExportData;
|
|
22354
24188
|
exports.normalizeRecipients = normalizeRecipients;
|
|
22355
24189
|
exports.numbers = numbers_exports;
|
|
22356
24190
|
exports.openDashboardPopup = openDashboardPopup;
|