myio-js-library 0.1.161 → 0.1.163
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 +3138 -14
- package/dist/index.d.cts +962 -2
- package/dist/index.js +3116 -14
- package/dist/myio-js-library.umd.js +3113 -13
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -644,6 +644,10 @@ function formatNumberReadable(value, locale = "pt-BR", minimumFractionDigits = 2
|
|
|
644
644
|
}
|
|
645
645
|
|
|
646
646
|
// src/format/water.ts
|
|
647
|
+
function formatWater(value) {
|
|
648
|
+
const num = Number(value) || 0;
|
|
649
|
+
return `${num.toFixed(2)} m\xB3`;
|
|
650
|
+
}
|
|
647
651
|
function formatWaterVolumeM3(value, locale = "pt-BR") {
|
|
648
652
|
if (value === null || value === void 0 || isNaN(value)) {
|
|
649
653
|
return "-";
|
|
@@ -721,6 +725,76 @@ function formatAllInSameWaterUnit(values) {
|
|
|
721
725
|
};
|
|
722
726
|
}
|
|
723
727
|
|
|
728
|
+
// src/format/time.ts
|
|
729
|
+
function formatRelativeTime(timestamp) {
|
|
730
|
+
if (!timestamp || timestamp <= 0) {
|
|
731
|
+
return "\u2014";
|
|
732
|
+
}
|
|
733
|
+
const now = Date.now();
|
|
734
|
+
const diffSeconds = Math.round((now - timestamp) / 1e3);
|
|
735
|
+
if (diffSeconds < 10) {
|
|
736
|
+
return "agora";
|
|
737
|
+
}
|
|
738
|
+
if (diffSeconds < 60) {
|
|
739
|
+
return `h\xE1 ${diffSeconds}s`;
|
|
740
|
+
}
|
|
741
|
+
const diffMinutes = Math.round(diffSeconds / 60);
|
|
742
|
+
if (diffMinutes === 1) {
|
|
743
|
+
return "h\xE1 1 min";
|
|
744
|
+
}
|
|
745
|
+
if (diffMinutes < 60) {
|
|
746
|
+
return `h\xE1 ${diffMinutes} mins`;
|
|
747
|
+
}
|
|
748
|
+
const diffHours = Math.round(diffMinutes / 60);
|
|
749
|
+
if (diffHours === 1) {
|
|
750
|
+
return "h\xE1 1 hora";
|
|
751
|
+
}
|
|
752
|
+
if (diffHours < 24) {
|
|
753
|
+
return `h\xE1 ${diffHours} horas`;
|
|
754
|
+
}
|
|
755
|
+
const diffDays = Math.round(diffHours / 24);
|
|
756
|
+
if (diffDays === 1) {
|
|
757
|
+
return "ontem";
|
|
758
|
+
}
|
|
759
|
+
if (diffDays <= 30) {
|
|
760
|
+
return `h\xE1 ${diffDays} dias`;
|
|
761
|
+
}
|
|
762
|
+
return new Date(timestamp).toLocaleDateString("pt-BR");
|
|
763
|
+
}
|
|
764
|
+
function formatarDuracao(ms) {
|
|
765
|
+
if (typeof ms !== "number" || ms < 0 || !isFinite(ms)) {
|
|
766
|
+
return "0s";
|
|
767
|
+
}
|
|
768
|
+
if (ms === 0) {
|
|
769
|
+
return "0s";
|
|
770
|
+
}
|
|
771
|
+
const segundos = Math.floor(ms / 1e3 % 60);
|
|
772
|
+
const minutos = Math.floor(ms / (1e3 * 60) % 60);
|
|
773
|
+
const horas = Math.floor(ms / (1e3 * 60 * 60) % 24);
|
|
774
|
+
const dias = Math.floor(ms / (1e3 * 60 * 60 * 24));
|
|
775
|
+
const parts = [];
|
|
776
|
+
if (dias > 0) {
|
|
777
|
+
parts.push(`${dias}d`);
|
|
778
|
+
if (horas > 0) {
|
|
779
|
+
parts.push(`${horas}h`);
|
|
780
|
+
}
|
|
781
|
+
} else if (horas > 0) {
|
|
782
|
+
parts.push(`${horas}h`);
|
|
783
|
+
if (minutos > 0) {
|
|
784
|
+
parts.push(`${minutos}m`);
|
|
785
|
+
}
|
|
786
|
+
} else if (minutos > 0) {
|
|
787
|
+
parts.push(`${minutos}m`);
|
|
788
|
+
if (segundos > 0) {
|
|
789
|
+
parts.push(`${segundos}s`);
|
|
790
|
+
}
|
|
791
|
+
} else {
|
|
792
|
+
parts.push(`${segundos}s`);
|
|
793
|
+
}
|
|
794
|
+
return parts.length > 0 ? parts.join(" ") : "0s";
|
|
795
|
+
}
|
|
796
|
+
var formatDuration = formatarDuracao;
|
|
797
|
+
|
|
724
798
|
// src/date/ymd.ts
|
|
725
799
|
function formatDateToYMD(date) {
|
|
726
800
|
if (!date) {
|
|
@@ -1229,6 +1303,11 @@ function findValue(data, keyOrPath, legacyDataKey) {
|
|
|
1229
1303
|
}
|
|
1230
1304
|
return getValueByDatakey(data, keyOrPath);
|
|
1231
1305
|
}
|
|
1306
|
+
function findValueWithDefault(values, key, defaultValue = null) {
|
|
1307
|
+
if (!Array.isArray(values)) return defaultValue;
|
|
1308
|
+
const found = values.find((v) => v.key === key || v.dataType === key);
|
|
1309
|
+
return found ? found.value : defaultValue;
|
|
1310
|
+
}
|
|
1232
1311
|
|
|
1233
1312
|
// src/utils/deviceStatus.js
|
|
1234
1313
|
var DeviceStatusType = {
|
|
@@ -1285,6 +1364,16 @@ function mapDeviceToConnectionStatus(deviceStatus) {
|
|
|
1285
1364
|
}
|
|
1286
1365
|
return ConnectionStatusType.CONNECTED;
|
|
1287
1366
|
}
|
|
1367
|
+
function mapConnectionStatus(rawStatus) {
|
|
1368
|
+
const statusLower = String(rawStatus || "").toLowerCase().trim();
|
|
1369
|
+
if (statusLower === "online" || statusLower === "ok" || statusLower === "running") {
|
|
1370
|
+
return "online";
|
|
1371
|
+
}
|
|
1372
|
+
if (statusLower === "waiting" || statusLower === "connecting" || statusLower === "pending") {
|
|
1373
|
+
return "waiting";
|
|
1374
|
+
}
|
|
1375
|
+
return "offline";
|
|
1376
|
+
}
|
|
1288
1377
|
function mapDeviceStatusToCardStatus(deviceStatus) {
|
|
1289
1378
|
const statusMap = {
|
|
1290
1379
|
[DeviceStatusType.POWER_ON]: "ok",
|
|
@@ -7698,14 +7787,14 @@ async function openRealTimeTelemetryModal(params) {
|
|
|
7698
7787
|
return `${value.toFixed(config.decimals)} ${config.unit}`;
|
|
7699
7788
|
}
|
|
7700
7789
|
function initializeChart() {
|
|
7701
|
-
const
|
|
7702
|
-
if (!
|
|
7790
|
+
const Chart2 = window.Chart;
|
|
7791
|
+
if (!Chart2) {
|
|
7703
7792
|
console.warn("[RealTimeTelemetry] Chart.js not loaded");
|
|
7704
7793
|
return;
|
|
7705
7794
|
}
|
|
7706
7795
|
chartContainer.style.display = "block";
|
|
7707
7796
|
const config = TELEMETRY_CONFIG[selectedChartKey] || { label: selectedChartKey, unit: "" };
|
|
7708
|
-
chart = new
|
|
7797
|
+
chart = new Chart2(chartCanvas, {
|
|
7709
7798
|
type: "line",
|
|
7710
7799
|
data: {
|
|
7711
7800
|
datasets: [{
|
|
@@ -10474,7 +10563,7 @@ async function openDemandModal(params) {
|
|
|
10474
10563
|
params.timezoneOffset
|
|
10475
10564
|
);
|
|
10476
10565
|
if (!newChartData.isEmpty && chart && chartData) {
|
|
10477
|
-
const
|
|
10566
|
+
const Chart2 = window.Chart;
|
|
10478
10567
|
newChartData.series.forEach((newSeries, seriesIndex) => {
|
|
10479
10568
|
if (newSeries.points.length > 0 && chart.data.datasets[seriesIndex]) {
|
|
10480
10569
|
newSeries.points.forEach((point) => {
|
|
@@ -10703,8 +10792,8 @@ async function openDemandModal(params) {
|
|
|
10703
10792
|
peakEl.textContent = `${strings.maximum}: ${peak.formattedValue} kW ${peak.key ? `(${peak.key}) ` : ""}${strings.at} ${dateStr}`;
|
|
10704
10793
|
peakEl.style.display = "block";
|
|
10705
10794
|
}
|
|
10706
|
-
const
|
|
10707
|
-
|
|
10795
|
+
const Chart2 = window.Chart;
|
|
10796
|
+
Chart2.register(window.ChartZoom);
|
|
10708
10797
|
if (chart) {
|
|
10709
10798
|
chart.data.datasets = chartData.series.map((series) => ({
|
|
10710
10799
|
label: series.label,
|
|
@@ -10756,7 +10845,7 @@ async function openDemandModal(params) {
|
|
|
10756
10845
|
};
|
|
10757
10846
|
chart.update();
|
|
10758
10847
|
} else {
|
|
10759
|
-
chart = new
|
|
10848
|
+
chart = new Chart2(chartCanvas, {
|
|
10760
10849
|
type: "line",
|
|
10761
10850
|
data: {
|
|
10762
10851
|
datasets: chartData.series.map((series) => ({
|
|
@@ -18929,8 +19018,8 @@ function openGoalsPanel(params) {
|
|
|
18929
19018
|
<div class="myio-goals-progress-fill" style="width: ${Math.min(progress, 100)}%"></div>
|
|
18930
19019
|
</div>
|
|
18931
19020
|
<div class="myio-goals-progress-text">
|
|
18932
|
-
<span>${
|
|
18933
|
-
<span>${
|
|
19021
|
+
<span>${formatNumber3(monthlySum, locale)} ${annual.unit}</span>
|
|
19022
|
+
<span>${formatNumber3(annual.total, locale)} ${annual.unit}</span>
|
|
18934
19023
|
</div>
|
|
18935
19024
|
|
|
18936
19025
|
<!-- Monthly Grid -->
|
|
@@ -19004,7 +19093,7 @@ function openGoalsPanel(params) {
|
|
|
19004
19093
|
<span>${assetData.label || assetId}</span>
|
|
19005
19094
|
</div>
|
|
19006
19095
|
<div class="myio-goals-asset-total">
|
|
19007
|
-
${
|
|
19096
|
+
${formatNumber3(assetData.annual?.total || 0, locale)} ${assetData.annual?.unit || "kWh"}
|
|
19008
19097
|
</div>
|
|
19009
19098
|
<button class="myio-goals-btn-icon" data-action="delete-asset" data-asset-id="${assetId}" aria-label="${i18n.deleteAsset}">
|
|
19010
19099
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -19319,7 +19408,7 @@ function openGoalsPanel(params) {
|
|
|
19319
19408
|
monthlySum += value;
|
|
19320
19409
|
}
|
|
19321
19410
|
if (monthlySum > annualTotal && annualTotal > 0) {
|
|
19322
|
-
errors.push(`${i18n.errorMonthlyExceedsAnnual} (${
|
|
19411
|
+
errors.push(`${i18n.errorMonthlyExceedsAnnual} (${formatNumber3(monthlySum, locale)} > ${formatNumber3(annualTotal, locale)})`);
|
|
19323
19412
|
}
|
|
19324
19413
|
}
|
|
19325
19414
|
return errors;
|
|
@@ -19410,8 +19499,8 @@ function openGoalsPanel(params) {
|
|
|
19410
19499
|
}
|
|
19411
19500
|
if (progressTexts.length === 2) {
|
|
19412
19501
|
const unit = document.getElementById("unit-select")?.value || "kWh";
|
|
19413
|
-
progressTexts[0].textContent = `${
|
|
19414
|
-
progressTexts[1].textContent = `${
|
|
19502
|
+
progressTexts[0].textContent = `${formatNumber3(monthlySum, locale)} ${unit}`;
|
|
19503
|
+
progressTexts[1].textContent = `${formatNumber3(annualTotal, locale)} ${unit}`;
|
|
19415
19504
|
}
|
|
19416
19505
|
}
|
|
19417
19506
|
function updateMonthlyUnits(unit) {
|
|
@@ -19509,7 +19598,7 @@ function openGoalsPanel(params) {
|
|
|
19509
19598
|
modal.addEventListener("keydown", handleTab);
|
|
19510
19599
|
firstElement.focus();
|
|
19511
19600
|
}
|
|
19512
|
-
function
|
|
19601
|
+
function formatNumber3(value, locale2) {
|
|
19513
19602
|
return new Intl.NumberFormat(locale2, {
|
|
19514
19603
|
minimumFractionDigits: 0,
|
|
19515
19604
|
maximumFractionDigits: 2
|
|
@@ -22459,11 +22548,3009 @@ function openTemperatureSettingsModal(params) {
|
|
|
22459
22548
|
});
|
|
22460
22549
|
return { destroy };
|
|
22461
22550
|
}
|
|
22551
|
+
|
|
22552
|
+
// src/components/ModalHeader/index.ts
|
|
22553
|
+
var DEFAULT_BG_COLOR = "#3e1a7d";
|
|
22554
|
+
var DEFAULT_TEXT_COLOR = "white";
|
|
22555
|
+
var DEFAULT_BORDER_RADIUS = "10px 10px 0 0";
|
|
22556
|
+
var EXPORT_FORMAT_LABELS = {
|
|
22557
|
+
csv: "CSV",
|
|
22558
|
+
xls: "Excel (XLS)",
|
|
22559
|
+
pdf: "PDF"
|
|
22560
|
+
};
|
|
22561
|
+
var EXPORT_FORMAT_ICONS = {
|
|
22562
|
+
csv: "\u{1F4C4}",
|
|
22563
|
+
xls: "\u{1F4CA}",
|
|
22564
|
+
pdf: "\u{1F4D1}"
|
|
22565
|
+
};
|
|
22566
|
+
function createModalHeader(config) {
|
|
22567
|
+
let currentTheme = config.theme || "light";
|
|
22568
|
+
let currentIsMaximized = config.isMaximized || false;
|
|
22569
|
+
let currentTitle = config.title;
|
|
22570
|
+
let themeBtn = null;
|
|
22571
|
+
let maximizeBtn = null;
|
|
22572
|
+
let closeBtn = null;
|
|
22573
|
+
let exportBtn = null;
|
|
22574
|
+
let exportDropdown = null;
|
|
22575
|
+
const cleanupHandlers = [];
|
|
22576
|
+
const handleThemeClick = () => {
|
|
22577
|
+
currentTheme = currentTheme === "light" ? "dark" : "light";
|
|
22578
|
+
config.onThemeToggle?.(currentTheme);
|
|
22579
|
+
updateButtonIcons();
|
|
22580
|
+
};
|
|
22581
|
+
const handleMaximizeClick = () => {
|
|
22582
|
+
currentIsMaximized = !currentIsMaximized;
|
|
22583
|
+
config.onMaximize?.(currentIsMaximized);
|
|
22584
|
+
updateButtonIcons();
|
|
22585
|
+
};
|
|
22586
|
+
const handleCloseClick = () => {
|
|
22587
|
+
config.onClose?.();
|
|
22588
|
+
};
|
|
22589
|
+
const handleExportClick = (format) => {
|
|
22590
|
+
config.onExport?.(format);
|
|
22591
|
+
if (exportDropdown) {
|
|
22592
|
+
exportDropdown.style.display = "none";
|
|
22593
|
+
}
|
|
22594
|
+
};
|
|
22595
|
+
function updateButtonIcons() {
|
|
22596
|
+
if (themeBtn) {
|
|
22597
|
+
themeBtn.textContent = currentTheme === "dark" ? "\u2600\uFE0F" : "\u{1F319}";
|
|
22598
|
+
themeBtn.title = currentTheme === "dark" ? "Modo claro" : "Modo escuro";
|
|
22599
|
+
}
|
|
22600
|
+
if (maximizeBtn) {
|
|
22601
|
+
maximizeBtn.textContent = currentIsMaximized ? "\u{1F5D7}" : "\u{1F5D6}";
|
|
22602
|
+
maximizeBtn.title = currentIsMaximized ? "Restaurar" : "Maximizar";
|
|
22603
|
+
}
|
|
22604
|
+
}
|
|
22605
|
+
function getButtonStyle() {
|
|
22606
|
+
return `
|
|
22607
|
+
background: none;
|
|
22608
|
+
border: none;
|
|
22609
|
+
font-size: 16px;
|
|
22610
|
+
cursor: pointer;
|
|
22611
|
+
padding: 4px 8px;
|
|
22612
|
+
border-radius: 6px;
|
|
22613
|
+
color: rgba(255, 255, 255, 0.8);
|
|
22614
|
+
transition: background-color 0.2s, color 0.2s;
|
|
22615
|
+
`.replace(/\s+/g, " ").trim();
|
|
22616
|
+
}
|
|
22617
|
+
function renderExportDropdown() {
|
|
22618
|
+
const formats = config.exportFormats || [];
|
|
22619
|
+
if (formats.length === 0) return "";
|
|
22620
|
+
if (formats.length === 1) {
|
|
22621
|
+
return `
|
|
22622
|
+
<button id="${config.id}-export" title="Exportar ${EXPORT_FORMAT_LABELS[formats[0]]}" style="${getButtonStyle()}">
|
|
22623
|
+
\u{1F4E5}
|
|
22624
|
+
</button>
|
|
22625
|
+
`;
|
|
22626
|
+
}
|
|
22627
|
+
return `
|
|
22628
|
+
<div style="position: relative; display: inline-block;">
|
|
22629
|
+
<button id="${config.id}-export-btn" title="Exportar" style="${getButtonStyle()}">
|
|
22630
|
+
\u{1F4E5}
|
|
22631
|
+
</button>
|
|
22632
|
+
<div id="${config.id}-export-dropdown" style="
|
|
22633
|
+
display: none;
|
|
22634
|
+
position: absolute;
|
|
22635
|
+
top: 100%;
|
|
22636
|
+
right: 0;
|
|
22637
|
+
background: white;
|
|
22638
|
+
border-radius: 6px;
|
|
22639
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
22640
|
+
min-width: 140px;
|
|
22641
|
+
z-index: 10001;
|
|
22642
|
+
margin-top: 4px;
|
|
22643
|
+
overflow: hidden;
|
|
22644
|
+
">
|
|
22645
|
+
${formats.map((format) => `
|
|
22646
|
+
<button
|
|
22647
|
+
id="${config.id}-export-${format}"
|
|
22648
|
+
class="myio-export-option"
|
|
22649
|
+
data-format="${format}"
|
|
22650
|
+
style="
|
|
22651
|
+
display: flex;
|
|
22652
|
+
align-items: center;
|
|
22653
|
+
gap: 8px;
|
|
22654
|
+
width: 100%;
|
|
22655
|
+
padding: 10px 14px;
|
|
22656
|
+
border: none;
|
|
22657
|
+
background: white;
|
|
22658
|
+
cursor: pointer;
|
|
22659
|
+
font-size: 13px;
|
|
22660
|
+
color: #333;
|
|
22661
|
+
text-align: left;
|
|
22662
|
+
transition: background-color 0.2s;
|
|
22663
|
+
"
|
|
22664
|
+
>
|
|
22665
|
+
${EXPORT_FORMAT_ICONS[format]} ${EXPORT_FORMAT_LABELS[format]}
|
|
22666
|
+
</button>
|
|
22667
|
+
`).join("")}
|
|
22668
|
+
</div>
|
|
22669
|
+
</div>
|
|
22670
|
+
`;
|
|
22671
|
+
}
|
|
22672
|
+
const instance = {
|
|
22673
|
+
render() {
|
|
22674
|
+
const bgColor = config.backgroundColor || DEFAULT_BG_COLOR;
|
|
22675
|
+
const textColor = config.textColor || DEFAULT_TEXT_COLOR;
|
|
22676
|
+
const borderRadius = currentIsMaximized ? "0" : config.borderRadius || DEFAULT_BORDER_RADIUS;
|
|
22677
|
+
const showTheme = config.showThemeToggle !== false;
|
|
22678
|
+
const showMax = config.showMaximize !== false;
|
|
22679
|
+
const showClose = config.showClose !== false;
|
|
22680
|
+
const showExport = config.exportFormats && config.exportFormats.length > 0;
|
|
22681
|
+
const iconHtml = config.icon ? `<span style="margin-right: 8px;">${config.icon}</span>` : "";
|
|
22682
|
+
const buttonStyle = getButtonStyle();
|
|
22683
|
+
return `
|
|
22684
|
+
<div class="myio-modal-header" style="
|
|
22685
|
+
padding: 4px 8px;
|
|
22686
|
+
display: flex;
|
|
22687
|
+
align-items: center;
|
|
22688
|
+
justify-content: space-between;
|
|
22689
|
+
background: ${bgColor};
|
|
22690
|
+
color: ${textColor};
|
|
22691
|
+
border-radius: ${borderRadius};
|
|
22692
|
+
min-height: 20px;
|
|
22693
|
+
font-family: 'Roboto', Arial, sans-serif;
|
|
22694
|
+
">
|
|
22695
|
+
<h2 id="${config.id}-header-title" style="
|
|
22696
|
+
margin: 6px;
|
|
22697
|
+
font-size: 18px;
|
|
22698
|
+
font-weight: 600;
|
|
22699
|
+
color: ${textColor};
|
|
22700
|
+
line-height: 2;
|
|
22701
|
+
display: flex;
|
|
22702
|
+
align-items: center;
|
|
22703
|
+
">
|
|
22704
|
+
${iconHtml}${currentTitle}
|
|
22705
|
+
</h2>
|
|
22706
|
+
<div style="display: flex; gap: 4px; align-items: center;">
|
|
22707
|
+
${showExport ? renderExportDropdown() : ""}
|
|
22708
|
+
${showTheme ? `
|
|
22709
|
+
<button id="${config.id}-theme-toggle" title="${currentTheme === "dark" ? "Modo claro" : "Modo escuro"}" style="${buttonStyle}">
|
|
22710
|
+
${currentTheme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}
|
|
22711
|
+
</button>
|
|
22712
|
+
` : ""}
|
|
22713
|
+
${showMax ? `
|
|
22714
|
+
<button id="${config.id}-maximize" title="${currentIsMaximized ? "Restaurar" : "Maximizar"}" style="${buttonStyle}">
|
|
22715
|
+
${currentIsMaximized ? "\u{1F5D7}" : "\u{1F5D6}"}
|
|
22716
|
+
</button>
|
|
22717
|
+
` : ""}
|
|
22718
|
+
${showClose ? `
|
|
22719
|
+
<button id="${config.id}-close" title="Fechar" style="${buttonStyle}; font-size: 20px;">
|
|
22720
|
+
\xD7
|
|
22721
|
+
</button>
|
|
22722
|
+
` : ""}
|
|
22723
|
+
</div>
|
|
22724
|
+
</div>
|
|
22725
|
+
`;
|
|
22726
|
+
},
|
|
22727
|
+
attachListeners() {
|
|
22728
|
+
themeBtn = document.getElementById(`${config.id}-theme-toggle`);
|
|
22729
|
+
maximizeBtn = document.getElementById(`${config.id}-maximize`);
|
|
22730
|
+
closeBtn = document.getElementById(`${config.id}-close`);
|
|
22731
|
+
const singleExportBtn = document.getElementById(`${config.id}-export`);
|
|
22732
|
+
if (singleExportBtn && config.exportFormats?.length === 1) {
|
|
22733
|
+
exportBtn = singleExportBtn;
|
|
22734
|
+
const format = config.exportFormats[0];
|
|
22735
|
+
const clickHandler = () => handleExportClick(format);
|
|
22736
|
+
exportBtn.addEventListener("click", clickHandler);
|
|
22737
|
+
cleanupHandlers.push(() => exportBtn?.removeEventListener("click", clickHandler));
|
|
22738
|
+
const enterHandler = () => {
|
|
22739
|
+
exportBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22740
|
+
};
|
|
22741
|
+
const leaveHandler = () => {
|
|
22742
|
+
exportBtn.style.backgroundColor = "transparent";
|
|
22743
|
+
};
|
|
22744
|
+
exportBtn.addEventListener("mouseenter", enterHandler);
|
|
22745
|
+
exportBtn.addEventListener("mouseleave", leaveHandler);
|
|
22746
|
+
cleanupHandlers.push(() => {
|
|
22747
|
+
exportBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22748
|
+
exportBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22749
|
+
});
|
|
22750
|
+
}
|
|
22751
|
+
const exportDropdownBtn = document.getElementById(`${config.id}-export-btn`);
|
|
22752
|
+
exportDropdown = document.getElementById(`${config.id}-export-dropdown`);
|
|
22753
|
+
if (exportDropdownBtn && exportDropdown) {
|
|
22754
|
+
exportBtn = exportDropdownBtn;
|
|
22755
|
+
const toggleHandler = (e) => {
|
|
22756
|
+
e.stopPropagation();
|
|
22757
|
+
if (exportDropdown) {
|
|
22758
|
+
exportDropdown.style.display = exportDropdown.style.display === "none" ? "block" : "none";
|
|
22759
|
+
}
|
|
22760
|
+
};
|
|
22761
|
+
exportDropdownBtn.addEventListener("click", toggleHandler);
|
|
22762
|
+
cleanupHandlers.push(() => exportDropdownBtn.removeEventListener("click", toggleHandler));
|
|
22763
|
+
const outsideClickHandler = (e) => {
|
|
22764
|
+
if (exportDropdown && !exportDropdown.contains(e.target) && e.target !== exportDropdownBtn) {
|
|
22765
|
+
exportDropdown.style.display = "none";
|
|
22766
|
+
}
|
|
22767
|
+
};
|
|
22768
|
+
document.addEventListener("click", outsideClickHandler);
|
|
22769
|
+
cleanupHandlers.push(() => document.removeEventListener("click", outsideClickHandler));
|
|
22770
|
+
config.exportFormats?.forEach((format) => {
|
|
22771
|
+
const btn = document.getElementById(`${config.id}-export-${format}`);
|
|
22772
|
+
if (btn) {
|
|
22773
|
+
const clickHandler = () => handleExportClick(format);
|
|
22774
|
+
btn.addEventListener("click", clickHandler);
|
|
22775
|
+
cleanupHandlers.push(() => btn.removeEventListener("click", clickHandler));
|
|
22776
|
+
const enterHandler2 = () => {
|
|
22777
|
+
btn.style.backgroundColor = "#f0f0f0";
|
|
22778
|
+
};
|
|
22779
|
+
const leaveHandler2 = () => {
|
|
22780
|
+
btn.style.backgroundColor = "white";
|
|
22781
|
+
};
|
|
22782
|
+
btn.addEventListener("mouseenter", enterHandler2);
|
|
22783
|
+
btn.addEventListener("mouseleave", leaveHandler2);
|
|
22784
|
+
cleanupHandlers.push(() => {
|
|
22785
|
+
btn.removeEventListener("mouseenter", enterHandler2);
|
|
22786
|
+
btn.removeEventListener("mouseleave", leaveHandler2);
|
|
22787
|
+
});
|
|
22788
|
+
}
|
|
22789
|
+
});
|
|
22790
|
+
const enterHandler = () => {
|
|
22791
|
+
exportDropdownBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22792
|
+
};
|
|
22793
|
+
const leaveHandler = () => {
|
|
22794
|
+
exportDropdownBtn.style.backgroundColor = "transparent";
|
|
22795
|
+
};
|
|
22796
|
+
exportDropdownBtn.addEventListener("mouseenter", enterHandler);
|
|
22797
|
+
exportDropdownBtn.addEventListener("mouseleave", leaveHandler);
|
|
22798
|
+
cleanupHandlers.push(() => {
|
|
22799
|
+
exportDropdownBtn.removeEventListener("mouseenter", enterHandler);
|
|
22800
|
+
exportDropdownBtn.removeEventListener("mouseleave", leaveHandler);
|
|
22801
|
+
});
|
|
22802
|
+
}
|
|
22803
|
+
if (themeBtn && config.showThemeToggle !== false) {
|
|
22804
|
+
themeBtn.addEventListener("click", handleThemeClick);
|
|
22805
|
+
cleanupHandlers.push(() => themeBtn?.removeEventListener("click", handleThemeClick));
|
|
22806
|
+
const enterHandler = () => {
|
|
22807
|
+
themeBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22808
|
+
};
|
|
22809
|
+
const leaveHandler = () => {
|
|
22810
|
+
themeBtn.style.backgroundColor = "transparent";
|
|
22811
|
+
};
|
|
22812
|
+
themeBtn.addEventListener("mouseenter", enterHandler);
|
|
22813
|
+
themeBtn.addEventListener("mouseleave", leaveHandler);
|
|
22814
|
+
cleanupHandlers.push(() => {
|
|
22815
|
+
themeBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22816
|
+
themeBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22817
|
+
});
|
|
22818
|
+
}
|
|
22819
|
+
if (maximizeBtn && config.showMaximize !== false) {
|
|
22820
|
+
maximizeBtn.addEventListener("click", handleMaximizeClick);
|
|
22821
|
+
cleanupHandlers.push(() => maximizeBtn?.removeEventListener("click", handleMaximizeClick));
|
|
22822
|
+
const enterHandler = () => {
|
|
22823
|
+
maximizeBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22824
|
+
};
|
|
22825
|
+
const leaveHandler = () => {
|
|
22826
|
+
maximizeBtn.style.backgroundColor = "transparent";
|
|
22827
|
+
};
|
|
22828
|
+
maximizeBtn.addEventListener("mouseenter", enterHandler);
|
|
22829
|
+
maximizeBtn.addEventListener("mouseleave", leaveHandler);
|
|
22830
|
+
cleanupHandlers.push(() => {
|
|
22831
|
+
maximizeBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22832
|
+
maximizeBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22833
|
+
});
|
|
22834
|
+
}
|
|
22835
|
+
if (closeBtn && config.showClose !== false) {
|
|
22836
|
+
closeBtn.addEventListener("click", handleCloseClick);
|
|
22837
|
+
cleanupHandlers.push(() => closeBtn?.removeEventListener("click", handleCloseClick));
|
|
22838
|
+
const enterHandler = () => {
|
|
22839
|
+
closeBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22840
|
+
};
|
|
22841
|
+
const leaveHandler = () => {
|
|
22842
|
+
closeBtn.style.backgroundColor = "transparent";
|
|
22843
|
+
};
|
|
22844
|
+
closeBtn.addEventListener("mouseenter", enterHandler);
|
|
22845
|
+
closeBtn.addEventListener("mouseleave", leaveHandler);
|
|
22846
|
+
cleanupHandlers.push(() => {
|
|
22847
|
+
closeBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22848
|
+
closeBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22849
|
+
});
|
|
22850
|
+
}
|
|
22851
|
+
},
|
|
22852
|
+
update(updates) {
|
|
22853
|
+
if (updates.theme !== void 0) {
|
|
22854
|
+
currentTheme = updates.theme;
|
|
22855
|
+
updateButtonIcons();
|
|
22856
|
+
}
|
|
22857
|
+
if (updates.isMaximized !== void 0) {
|
|
22858
|
+
currentIsMaximized = updates.isMaximized;
|
|
22859
|
+
updateButtonIcons();
|
|
22860
|
+
}
|
|
22861
|
+
if (updates.title !== void 0) {
|
|
22862
|
+
currentTitle = updates.title;
|
|
22863
|
+
const titleEl = document.getElementById(`${config.id}-header-title`);
|
|
22864
|
+
if (titleEl) {
|
|
22865
|
+
const iconHtml = config.icon ? `<span style="margin-right: 8px;">${config.icon}</span>` : "";
|
|
22866
|
+
titleEl.innerHTML = `${iconHtml}${currentTitle}`;
|
|
22867
|
+
}
|
|
22868
|
+
}
|
|
22869
|
+
},
|
|
22870
|
+
getState() {
|
|
22871
|
+
return {
|
|
22872
|
+
theme: currentTheme,
|
|
22873
|
+
isMaximized: currentIsMaximized
|
|
22874
|
+
};
|
|
22875
|
+
},
|
|
22876
|
+
destroy() {
|
|
22877
|
+
cleanupHandlers.forEach((handler) => handler());
|
|
22878
|
+
cleanupHandlers.length = 0;
|
|
22879
|
+
themeBtn = null;
|
|
22880
|
+
maximizeBtn = null;
|
|
22881
|
+
closeBtn = null;
|
|
22882
|
+
exportBtn = null;
|
|
22883
|
+
exportDropdown = null;
|
|
22884
|
+
}
|
|
22885
|
+
};
|
|
22886
|
+
return instance;
|
|
22887
|
+
}
|
|
22888
|
+
function getModalHeaderStyles() {
|
|
22889
|
+
return `
|
|
22890
|
+
.myio-modal-header button:hover {
|
|
22891
|
+
background-color: rgba(255, 255, 255, 0.2) !important;
|
|
22892
|
+
}
|
|
22893
|
+
.myio-modal-header button:active {
|
|
22894
|
+
background-color: rgba(255, 255, 255, 0.3) !important;
|
|
22895
|
+
}
|
|
22896
|
+
.myio-export-option:hover {
|
|
22897
|
+
background-color: #f0f0f0 !important;
|
|
22898
|
+
}
|
|
22899
|
+
`;
|
|
22900
|
+
}
|
|
22901
|
+
|
|
22902
|
+
// src/components/Consumption7DaysChart/types.ts
|
|
22903
|
+
var DEFAULT_COLORS = {
|
|
22904
|
+
energy: {
|
|
22905
|
+
primary: "#2563eb",
|
|
22906
|
+
background: "rgba(37, 99, 235, 0.1)",
|
|
22907
|
+
gradient: ["#f0fdf4", "#dcfce7"],
|
|
22908
|
+
pointBackground: "#2563eb",
|
|
22909
|
+
pointBorder: "#ffffff"
|
|
22910
|
+
},
|
|
22911
|
+
water: {
|
|
22912
|
+
primary: "#0288d1",
|
|
22913
|
+
background: "rgba(2, 136, 209, 0.1)",
|
|
22914
|
+
gradient: ["#f0f9ff", "#bae6fd"],
|
|
22915
|
+
pointBackground: "#0288d1",
|
|
22916
|
+
pointBorder: "#ffffff"
|
|
22917
|
+
},
|
|
22918
|
+
gas: {
|
|
22919
|
+
primary: "#ea580c",
|
|
22920
|
+
background: "rgba(234, 88, 12, 0.1)",
|
|
22921
|
+
gradient: ["#fff7ed", "#fed7aa"],
|
|
22922
|
+
pointBackground: "#ea580c",
|
|
22923
|
+
pointBorder: "#ffffff"
|
|
22924
|
+
},
|
|
22925
|
+
temperature: {
|
|
22926
|
+
primary: "#dc2626",
|
|
22927
|
+
background: "rgba(220, 38, 38, 0.1)",
|
|
22928
|
+
gradient: ["#fef2f2", "#fecaca"],
|
|
22929
|
+
pointBackground: "#dc2626",
|
|
22930
|
+
pointBorder: "#ffffff"
|
|
22931
|
+
}
|
|
22932
|
+
};
|
|
22933
|
+
var THEME_COLORS = {
|
|
22934
|
+
light: {
|
|
22935
|
+
chartBackground: "#ffffff",
|
|
22936
|
+
text: "#1f2937",
|
|
22937
|
+
textMuted: "#6b7280",
|
|
22938
|
+
grid: "rgba(0, 0, 0, 0.1)",
|
|
22939
|
+
border: "#e5e7eb",
|
|
22940
|
+
tooltipBackground: "#ffffff",
|
|
22941
|
+
tooltipText: "#1f2937"
|
|
22942
|
+
},
|
|
22943
|
+
dark: {
|
|
22944
|
+
chartBackground: "#1f2937",
|
|
22945
|
+
text: "#f9fafb",
|
|
22946
|
+
textMuted: "#9ca3af",
|
|
22947
|
+
grid: "rgba(255, 255, 255, 0.1)",
|
|
22948
|
+
border: "#374151",
|
|
22949
|
+
tooltipBackground: "#374151",
|
|
22950
|
+
tooltipText: "#f9fafb"
|
|
22951
|
+
}
|
|
22952
|
+
};
|
|
22953
|
+
var DEFAULT_CONFIG = {
|
|
22954
|
+
defaultPeriod: 7,
|
|
22955
|
+
defaultChartType: "line",
|
|
22956
|
+
defaultVizMode: "total",
|
|
22957
|
+
defaultTheme: "light",
|
|
22958
|
+
cacheTTL: 3e5,
|
|
22959
|
+
// 5 minutes
|
|
22960
|
+
decimalPlaces: 1,
|
|
22961
|
+
lineTension: 0.4,
|
|
22962
|
+
pointRadius: 4,
|
|
22963
|
+
borderWidth: 2,
|
|
22964
|
+
fill: true,
|
|
22965
|
+
showLegend: false,
|
|
22966
|
+
enableExport: true
|
|
22967
|
+
};
|
|
22968
|
+
|
|
22969
|
+
// src/components/Consumption7DaysChart/createConsumption7DaysChart.ts
|
|
22970
|
+
function createConsumption7DaysChart(config) {
|
|
22971
|
+
let chartInstance = null;
|
|
22972
|
+
let cachedData = null;
|
|
22973
|
+
let currentPeriod = config.defaultPeriod ?? DEFAULT_CONFIG.defaultPeriod;
|
|
22974
|
+
let currentChartType = config.defaultChartType ?? DEFAULT_CONFIG.defaultChartType;
|
|
22975
|
+
let currentVizMode = config.defaultVizMode ?? DEFAULT_CONFIG.defaultVizMode;
|
|
22976
|
+
let currentTheme = config.theme ?? DEFAULT_CONFIG.defaultTheme;
|
|
22977
|
+
let currentIdealRange = config.idealRange ?? null;
|
|
22978
|
+
let isRendered = false;
|
|
22979
|
+
let autoRefreshTimer = null;
|
|
22980
|
+
const colors = {
|
|
22981
|
+
...DEFAULT_COLORS[config.domain] ?? DEFAULT_COLORS.energy,
|
|
22982
|
+
...config.colors
|
|
22983
|
+
};
|
|
22984
|
+
function $id(id) {
|
|
22985
|
+
if (config.$container && config.$container[0]) {
|
|
22986
|
+
return config.$container[0].querySelector(`#${id}`);
|
|
22987
|
+
}
|
|
22988
|
+
return document.getElementById(id);
|
|
22989
|
+
}
|
|
22990
|
+
function log(level, ...args) {
|
|
22991
|
+
const prefix = `[${config.domain.toUpperCase()}]`;
|
|
22992
|
+
console[level](prefix, ...args);
|
|
22993
|
+
}
|
|
22994
|
+
function calculateYAxisMax(values) {
|
|
22995
|
+
const maxValue = Math.max(...values, 0);
|
|
22996
|
+
if (config.domain === "temperature") {
|
|
22997
|
+
const tempConfig = config.temperatureConfig;
|
|
22998
|
+
if (tempConfig?.clampRange) {
|
|
22999
|
+
return tempConfig.clampRange.max;
|
|
23000
|
+
}
|
|
23001
|
+
const maxWithThreshold = Math.max(
|
|
23002
|
+
maxValue,
|
|
23003
|
+
tempConfig?.maxThreshold?.value ?? 0,
|
|
23004
|
+
tempConfig?.idealRange?.max ?? 0
|
|
23005
|
+
);
|
|
23006
|
+
return Math.ceil(maxWithThreshold + 5);
|
|
23007
|
+
}
|
|
23008
|
+
if (maxValue === 0) {
|
|
23009
|
+
return config.thresholdForLargeUnit ? config.thresholdForLargeUnit / 2 : 500;
|
|
23010
|
+
}
|
|
23011
|
+
let roundTo;
|
|
23012
|
+
if (config.thresholdForLargeUnit && maxValue >= config.thresholdForLargeUnit) {
|
|
23013
|
+
roundTo = config.thresholdForLargeUnit / 10;
|
|
23014
|
+
} else if (maxValue >= 1e3) {
|
|
23015
|
+
roundTo = 100;
|
|
23016
|
+
} else if (maxValue >= 100) {
|
|
23017
|
+
roundTo = 50;
|
|
23018
|
+
} else if (maxValue >= 10) {
|
|
23019
|
+
roundTo = 10;
|
|
23020
|
+
} else {
|
|
23021
|
+
roundTo = 5;
|
|
23022
|
+
}
|
|
23023
|
+
return Math.ceil(maxValue * 1.1 / roundTo) * roundTo;
|
|
23024
|
+
}
|
|
23025
|
+
function calculateYAxisMin(values) {
|
|
23026
|
+
if (config.domain !== "temperature") {
|
|
23027
|
+
return 0;
|
|
23028
|
+
}
|
|
23029
|
+
const tempConfig = config.temperatureConfig;
|
|
23030
|
+
if (tempConfig?.clampRange) {
|
|
23031
|
+
return tempConfig.clampRange.min;
|
|
23032
|
+
}
|
|
23033
|
+
const minValue = Math.min(...values);
|
|
23034
|
+
const minWithThreshold = Math.min(
|
|
23035
|
+
minValue,
|
|
23036
|
+
tempConfig?.minThreshold?.value ?? minValue,
|
|
23037
|
+
tempConfig?.idealRange?.min ?? minValue
|
|
23038
|
+
);
|
|
23039
|
+
return Math.floor(minWithThreshold - 5);
|
|
23040
|
+
}
|
|
23041
|
+
function buildTemperatureAnnotations() {
|
|
23042
|
+
const tempConfig = config.temperatureConfig;
|
|
23043
|
+
if (!tempConfig || config.domain !== "temperature") {
|
|
23044
|
+
return {};
|
|
23045
|
+
}
|
|
23046
|
+
const annotations = {};
|
|
23047
|
+
const createLineAnnotation = (line, id) => {
|
|
23048
|
+
const borderDash = line.lineStyle === "dashed" ? [6, 6] : line.lineStyle === "dotted" ? [2, 2] : [];
|
|
23049
|
+
return {
|
|
23050
|
+
type: "line",
|
|
23051
|
+
yMin: line.value,
|
|
23052
|
+
yMax: line.value,
|
|
23053
|
+
borderColor: line.color,
|
|
23054
|
+
borderWidth: line.lineWidth ?? 2,
|
|
23055
|
+
borderDash,
|
|
23056
|
+
label: {
|
|
23057
|
+
display: true,
|
|
23058
|
+
content: line.label,
|
|
23059
|
+
position: "end",
|
|
23060
|
+
backgroundColor: line.color,
|
|
23061
|
+
color: "#fff",
|
|
23062
|
+
font: { size: 10, weight: "bold" },
|
|
23063
|
+
padding: { x: 4, y: 2 }
|
|
23064
|
+
}
|
|
23065
|
+
};
|
|
23066
|
+
};
|
|
23067
|
+
if (tempConfig.minThreshold) {
|
|
23068
|
+
annotations["minThreshold"] = createLineAnnotation(tempConfig.minThreshold, "minThreshold");
|
|
23069
|
+
}
|
|
23070
|
+
if (tempConfig.maxThreshold) {
|
|
23071
|
+
annotations["maxThreshold"] = createLineAnnotation(tempConfig.maxThreshold, "maxThreshold");
|
|
23072
|
+
}
|
|
23073
|
+
if (tempConfig.idealRange) {
|
|
23074
|
+
annotations["idealRange"] = {
|
|
23075
|
+
type: "box",
|
|
23076
|
+
yMin: tempConfig.idealRange.min,
|
|
23077
|
+
yMax: tempConfig.idealRange.max,
|
|
23078
|
+
backgroundColor: tempConfig.idealRange.color,
|
|
23079
|
+
borderWidth: 0,
|
|
23080
|
+
label: tempConfig.idealRange.label ? {
|
|
23081
|
+
display: true,
|
|
23082
|
+
content: tempConfig.idealRange.label,
|
|
23083
|
+
position: { x: "start", y: "center" },
|
|
23084
|
+
color: "#666",
|
|
23085
|
+
font: { size: 10 }
|
|
23086
|
+
} : void 0
|
|
23087
|
+
};
|
|
23088
|
+
}
|
|
23089
|
+
return annotations;
|
|
23090
|
+
}
|
|
23091
|
+
function buildIdealRangeAnnotation() {
|
|
23092
|
+
if (!currentIdealRange) {
|
|
23093
|
+
return {};
|
|
23094
|
+
}
|
|
23095
|
+
const { min, max, enabled = true } = currentIdealRange;
|
|
23096
|
+
if (!enabled || min === 0 && max === 0 || min >= max) {
|
|
23097
|
+
return {};
|
|
23098
|
+
}
|
|
23099
|
+
const defaultColors = {
|
|
23100
|
+
temperature: { bg: "rgba(34, 197, 94, 0.15)", border: "rgba(34, 197, 94, 0.4)" },
|
|
23101
|
+
energy: { bg: "rgba(37, 99, 235, 0.1)", border: "rgba(37, 99, 235, 0.3)" },
|
|
23102
|
+
water: { bg: "rgba(2, 136, 209, 0.1)", border: "rgba(2, 136, 209, 0.3)" },
|
|
23103
|
+
gas: { bg: "rgba(234, 88, 12, 0.1)", border: "rgba(234, 88, 12, 0.3)" }
|
|
23104
|
+
};
|
|
23105
|
+
const domainDefaults = defaultColors[config.domain] || defaultColors.energy;
|
|
23106
|
+
return {
|
|
23107
|
+
idealRangeBox: {
|
|
23108
|
+
type: "box",
|
|
23109
|
+
yMin: min,
|
|
23110
|
+
yMax: max,
|
|
23111
|
+
backgroundColor: currentIdealRange.color || domainDefaults.bg,
|
|
23112
|
+
borderColor: currentIdealRange.borderColor || domainDefaults.border,
|
|
23113
|
+
borderWidth: 1,
|
|
23114
|
+
label: currentIdealRange.label ? {
|
|
23115
|
+
display: true,
|
|
23116
|
+
content: currentIdealRange.label,
|
|
23117
|
+
position: { x: "start", y: "center" },
|
|
23118
|
+
color: "#666",
|
|
23119
|
+
font: { size: 10, style: "italic" },
|
|
23120
|
+
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
|
23121
|
+
padding: { x: 4, y: 2 }
|
|
23122
|
+
} : void 0
|
|
23123
|
+
}
|
|
23124
|
+
};
|
|
23125
|
+
}
|
|
23126
|
+
function formatValue(value, includeUnit = true) {
|
|
23127
|
+
const decimals = config.decimalPlaces ?? DEFAULT_CONFIG.decimalPlaces;
|
|
23128
|
+
if (config.unitLarge && config.thresholdForLargeUnit && value >= config.thresholdForLargeUnit) {
|
|
23129
|
+
const converted = value / config.thresholdForLargeUnit;
|
|
23130
|
+
return includeUnit ? `${converted.toFixed(decimals)} ${config.unitLarge}` : converted.toFixed(decimals);
|
|
23131
|
+
}
|
|
23132
|
+
return includeUnit ? `${value.toFixed(decimals)} ${config.unit}` : value.toFixed(decimals);
|
|
23133
|
+
}
|
|
23134
|
+
function formatTickValue(value) {
|
|
23135
|
+
if (config.unitLarge && config.thresholdForLargeUnit && value >= config.thresholdForLargeUnit) {
|
|
23136
|
+
return `${(value / config.thresholdForLargeUnit).toFixed(1)}`;
|
|
23137
|
+
}
|
|
23138
|
+
return value.toFixed(0);
|
|
23139
|
+
}
|
|
23140
|
+
function buildChartConfig(data) {
|
|
23141
|
+
const yAxisMax = calculateYAxisMax(data.dailyTotals);
|
|
23142
|
+
const yAxisMin = calculateYAxisMin(data.dailyTotals);
|
|
23143
|
+
const tension = config.lineTension ?? DEFAULT_CONFIG.lineTension;
|
|
23144
|
+
const pointRadius = config.pointRadius ?? DEFAULT_CONFIG.pointRadius;
|
|
23145
|
+
const borderWidth = config.borderWidth ?? DEFAULT_CONFIG.borderWidth;
|
|
23146
|
+
const fill = config.fill ?? DEFAULT_CONFIG.fill;
|
|
23147
|
+
const showLegend = config.showLegend ?? DEFAULT_CONFIG.showLegend;
|
|
23148
|
+
const themeColors = THEME_COLORS[currentTheme];
|
|
23149
|
+
const isTemperature = config.domain === "temperature";
|
|
23150
|
+
let datasets;
|
|
23151
|
+
if (currentVizMode === "separate" && data.shoppingData && data.shoppingNames) {
|
|
23152
|
+
const shoppingColors = colors.shoppingColors || [
|
|
23153
|
+
"#2563eb",
|
|
23154
|
+
"#16a34a",
|
|
23155
|
+
"#ea580c",
|
|
23156
|
+
"#dc2626",
|
|
23157
|
+
"#8b5cf6",
|
|
23158
|
+
"#0891b2",
|
|
23159
|
+
"#65a30d",
|
|
23160
|
+
"#d97706",
|
|
23161
|
+
"#be185d",
|
|
23162
|
+
"#0d9488"
|
|
23163
|
+
];
|
|
23164
|
+
datasets = Object.entries(data.shoppingData).map(([shoppingId, values], index) => ({
|
|
23165
|
+
label: data.shoppingNames?.[shoppingId] || shoppingId,
|
|
23166
|
+
data: values,
|
|
23167
|
+
borderColor: shoppingColors[index % shoppingColors.length],
|
|
23168
|
+
backgroundColor: currentChartType === "line" ? `${shoppingColors[index % shoppingColors.length]}20` : shoppingColors[index % shoppingColors.length],
|
|
23169
|
+
fill: currentChartType === "line" && fill,
|
|
23170
|
+
tension,
|
|
23171
|
+
borderWidth,
|
|
23172
|
+
pointRadius: currentChartType === "line" ? pointRadius : 0,
|
|
23173
|
+
pointBackgroundColor: shoppingColors[index % shoppingColors.length],
|
|
23174
|
+
pointBorderColor: "#fff",
|
|
23175
|
+
pointBorderWidth: 2
|
|
23176
|
+
}));
|
|
23177
|
+
} else {
|
|
23178
|
+
const datasetLabel = isTemperature ? `Temperatura (${config.unit})` : `Consumo (${config.unit})`;
|
|
23179
|
+
datasets = [
|
|
23180
|
+
{
|
|
23181
|
+
label: datasetLabel,
|
|
23182
|
+
data: data.dailyTotals,
|
|
23183
|
+
borderColor: colors.primary,
|
|
23184
|
+
backgroundColor: currentChartType === "line" ? colors.background : colors.primary,
|
|
23185
|
+
fill: currentChartType === "line" && fill,
|
|
23186
|
+
tension,
|
|
23187
|
+
borderWidth,
|
|
23188
|
+
pointRadius: currentChartType === "line" ? pointRadius : 0,
|
|
23189
|
+
pointBackgroundColor: colors.pointBackground || colors.primary,
|
|
23190
|
+
pointBorderColor: colors.pointBorder || "#fff",
|
|
23191
|
+
pointBorderWidth: 2,
|
|
23192
|
+
borderRadius: currentChartType === "bar" ? 4 : 0
|
|
23193
|
+
}
|
|
23194
|
+
];
|
|
23195
|
+
}
|
|
23196
|
+
const temperatureAnnotations = buildTemperatureAnnotations();
|
|
23197
|
+
const idealRangeAnnotations = buildIdealRangeAnnotation();
|
|
23198
|
+
const allAnnotations = { ...temperatureAnnotations, ...idealRangeAnnotations };
|
|
23199
|
+
const yAxisLabel = config.unitLarge && config.thresholdForLargeUnit && yAxisMax >= config.thresholdForLargeUnit ? config.unitLarge : config.unit;
|
|
23200
|
+
return {
|
|
23201
|
+
type: currentChartType,
|
|
23202
|
+
data: {
|
|
23203
|
+
labels: data.labels,
|
|
23204
|
+
datasets
|
|
23205
|
+
},
|
|
23206
|
+
options: {
|
|
23207
|
+
responsive: true,
|
|
23208
|
+
maintainAspectRatio: false,
|
|
23209
|
+
animation: false,
|
|
23210
|
+
// CRITICAL: Prevents infinite growth bug
|
|
23211
|
+
plugins: {
|
|
23212
|
+
legend: {
|
|
23213
|
+
display: showLegend || currentVizMode === "separate",
|
|
23214
|
+
position: "bottom",
|
|
23215
|
+
labels: {
|
|
23216
|
+
color: themeColors.text
|
|
23217
|
+
}
|
|
23218
|
+
},
|
|
23219
|
+
tooltip: {
|
|
23220
|
+
backgroundColor: themeColors.tooltipBackground,
|
|
23221
|
+
titleColor: themeColors.tooltipText,
|
|
23222
|
+
bodyColor: themeColors.tooltipText,
|
|
23223
|
+
borderColor: themeColors.border,
|
|
23224
|
+
borderWidth: 1,
|
|
23225
|
+
callbacks: {
|
|
23226
|
+
label: function(context) {
|
|
23227
|
+
const value = context.parsed.y || 0;
|
|
23228
|
+
const label = context.dataset.label || "";
|
|
23229
|
+
return `${label}: ${formatValue(value)}`;
|
|
23230
|
+
}
|
|
23231
|
+
}
|
|
23232
|
+
},
|
|
23233
|
+
// Reference lines and ideal range (requires chartjs-plugin-annotation)
|
|
23234
|
+
annotation: Object.keys(allAnnotations).length > 0 ? {
|
|
23235
|
+
annotations: allAnnotations
|
|
23236
|
+
} : void 0
|
|
23237
|
+
},
|
|
23238
|
+
scales: {
|
|
23239
|
+
y: {
|
|
23240
|
+
beginAtZero: !isTemperature,
|
|
23241
|
+
// Temperature can have negative values
|
|
23242
|
+
min: yAxisMin,
|
|
23243
|
+
max: yAxisMax,
|
|
23244
|
+
// CRITICAL: Fixed max prevents animation loop
|
|
23245
|
+
grid: {
|
|
23246
|
+
color: themeColors.grid
|
|
23247
|
+
},
|
|
23248
|
+
title: {
|
|
23249
|
+
display: true,
|
|
23250
|
+
text: yAxisLabel,
|
|
23251
|
+
font: { size: 12 },
|
|
23252
|
+
color: themeColors.text
|
|
23253
|
+
},
|
|
23254
|
+
ticks: {
|
|
23255
|
+
font: { size: 11 },
|
|
23256
|
+
color: themeColors.textMuted,
|
|
23257
|
+
callback: function(value) {
|
|
23258
|
+
return formatTickValue(value);
|
|
23259
|
+
}
|
|
23260
|
+
}
|
|
23261
|
+
},
|
|
23262
|
+
x: {
|
|
23263
|
+
grid: {
|
|
23264
|
+
color: themeColors.grid
|
|
23265
|
+
},
|
|
23266
|
+
ticks: {
|
|
23267
|
+
font: { size: 11 },
|
|
23268
|
+
color: themeColors.textMuted
|
|
23269
|
+
}
|
|
23270
|
+
}
|
|
23271
|
+
}
|
|
23272
|
+
}
|
|
23273
|
+
};
|
|
23274
|
+
}
|
|
23275
|
+
function validateChartJs() {
|
|
23276
|
+
if (typeof Chart === "undefined") {
|
|
23277
|
+
log("error", "Chart.js not loaded. Cannot initialize chart.");
|
|
23278
|
+
config.onError?.(new Error("Chart.js not loaded"));
|
|
23279
|
+
return false;
|
|
23280
|
+
}
|
|
23281
|
+
return true;
|
|
23282
|
+
}
|
|
23283
|
+
function validateCanvas() {
|
|
23284
|
+
const canvas = $id(config.containerId);
|
|
23285
|
+
if (!canvas) {
|
|
23286
|
+
log("error", `Canvas #${config.containerId} not found`);
|
|
23287
|
+
config.onError?.(new Error(`Canvas #${config.containerId} not found`));
|
|
23288
|
+
return null;
|
|
23289
|
+
}
|
|
23290
|
+
return canvas;
|
|
23291
|
+
}
|
|
23292
|
+
function setupAutoRefresh() {
|
|
23293
|
+
if (config.autoRefreshInterval && config.autoRefreshInterval > 0) {
|
|
23294
|
+
if (autoRefreshTimer) {
|
|
23295
|
+
clearInterval(autoRefreshTimer);
|
|
23296
|
+
}
|
|
23297
|
+
autoRefreshTimer = setInterval(async () => {
|
|
23298
|
+
log("log", "Auto-refreshing data...");
|
|
23299
|
+
await instance.refresh(true);
|
|
23300
|
+
}, config.autoRefreshInterval);
|
|
23301
|
+
}
|
|
23302
|
+
}
|
|
23303
|
+
function cleanupAutoRefresh() {
|
|
23304
|
+
if (autoRefreshTimer) {
|
|
23305
|
+
clearInterval(autoRefreshTimer);
|
|
23306
|
+
autoRefreshTimer = null;
|
|
23307
|
+
}
|
|
23308
|
+
}
|
|
23309
|
+
function setupButtonHandlers() {
|
|
23310
|
+
if (config.settingsButtonId && config.onSettingsClick) {
|
|
23311
|
+
const settingsBtn = $id(config.settingsButtonId);
|
|
23312
|
+
if (settingsBtn) {
|
|
23313
|
+
settingsBtn.addEventListener("click", () => {
|
|
23314
|
+
log("log", "Settings button clicked");
|
|
23315
|
+
config.onSettingsClick?.();
|
|
23316
|
+
});
|
|
23317
|
+
log("log", "Settings button handler attached");
|
|
23318
|
+
}
|
|
23319
|
+
}
|
|
23320
|
+
if (config.maximizeButtonId && config.onMaximizeClick) {
|
|
23321
|
+
const maximizeBtn = $id(config.maximizeButtonId);
|
|
23322
|
+
if (maximizeBtn) {
|
|
23323
|
+
maximizeBtn.addEventListener("click", () => {
|
|
23324
|
+
log("log", "Maximize button clicked");
|
|
23325
|
+
config.onMaximizeClick?.();
|
|
23326
|
+
});
|
|
23327
|
+
log("log", "Maximize button handler attached");
|
|
23328
|
+
}
|
|
23329
|
+
}
|
|
23330
|
+
const enableExport = config.enableExport ?? DEFAULT_CONFIG.enableExport;
|
|
23331
|
+
if (enableExport && config.exportButtonId) {
|
|
23332
|
+
const exportBtn = $id(config.exportButtonId);
|
|
23333
|
+
if (exportBtn) {
|
|
23334
|
+
exportBtn.addEventListener("click", () => {
|
|
23335
|
+
log("log", "Export button clicked");
|
|
23336
|
+
if (config.onExportCSV && cachedData) {
|
|
23337
|
+
config.onExportCSV(cachedData);
|
|
23338
|
+
} else {
|
|
23339
|
+
instance.exportCSV();
|
|
23340
|
+
}
|
|
23341
|
+
});
|
|
23342
|
+
log("log", "Export button handler attached");
|
|
23343
|
+
}
|
|
23344
|
+
}
|
|
23345
|
+
}
|
|
23346
|
+
function generateCSVContent(data) {
|
|
23347
|
+
const rows = [];
|
|
23348
|
+
const decimals = config.decimalPlaces ?? DEFAULT_CONFIG.decimalPlaces;
|
|
23349
|
+
if (currentVizMode === "separate" && data.shoppingData && data.shoppingNames) {
|
|
23350
|
+
const shoppingHeaders = Object.keys(data.shoppingData).map(
|
|
23351
|
+
(id) => data.shoppingNames?.[id] || id
|
|
23352
|
+
);
|
|
23353
|
+
rows.push(["Data", ...shoppingHeaders, "Total"].join(";"));
|
|
23354
|
+
data.labels.forEach((label, index) => {
|
|
23355
|
+
const shoppingValues = Object.keys(data.shoppingData).map(
|
|
23356
|
+
(id) => data.shoppingData[id][index].toFixed(decimals)
|
|
23357
|
+
);
|
|
23358
|
+
rows.push([label, ...shoppingValues, data.dailyTotals[index].toFixed(decimals)].join(";"));
|
|
23359
|
+
});
|
|
23360
|
+
} else {
|
|
23361
|
+
rows.push(["Data", `Consumo (${config.unit})`].join(";"));
|
|
23362
|
+
data.labels.forEach((label, index) => {
|
|
23363
|
+
rows.push([label, data.dailyTotals[index].toFixed(decimals)].join(";"));
|
|
23364
|
+
});
|
|
23365
|
+
}
|
|
23366
|
+
const total = data.dailyTotals.reduce((sum, v) => sum + v, 0);
|
|
23367
|
+
const avg = total / data.dailyTotals.length;
|
|
23368
|
+
rows.push("");
|
|
23369
|
+
rows.push(["Total", total.toFixed(decimals)].join(";"));
|
|
23370
|
+
rows.push(["M\xE9dia", avg.toFixed(decimals)].join(";"));
|
|
23371
|
+
return rows.join("\n");
|
|
23372
|
+
}
|
|
23373
|
+
function downloadCSV(content, filename) {
|
|
23374
|
+
const BOM = "\uFEFF";
|
|
23375
|
+
const blob = new Blob([BOM + content], { type: "text/csv;charset=utf-8" });
|
|
23376
|
+
const url = URL.createObjectURL(blob);
|
|
23377
|
+
const link = document.createElement("a");
|
|
23378
|
+
link.href = url;
|
|
23379
|
+
link.download = `${filename}.csv`;
|
|
23380
|
+
document.body.appendChild(link);
|
|
23381
|
+
link.click();
|
|
23382
|
+
document.body.removeChild(link);
|
|
23383
|
+
URL.revokeObjectURL(url);
|
|
23384
|
+
log("log", `CSV exported: ${filename}.csv`);
|
|
23385
|
+
}
|
|
23386
|
+
function updateTitle() {
|
|
23387
|
+
if (config.titleElementId) {
|
|
23388
|
+
const titleEl = $id(config.titleElementId);
|
|
23389
|
+
if (titleEl) {
|
|
23390
|
+
if (currentPeriod === 0) {
|
|
23391
|
+
titleEl.textContent = `Consumo - Per\xEDodo Personalizado`;
|
|
23392
|
+
} else {
|
|
23393
|
+
titleEl.textContent = `Consumo dos \xFAltimos ${currentPeriod} dias`;
|
|
23394
|
+
}
|
|
23395
|
+
}
|
|
23396
|
+
}
|
|
23397
|
+
}
|
|
23398
|
+
const instance = {
|
|
23399
|
+
async render() {
|
|
23400
|
+
log("log", "Rendering chart...");
|
|
23401
|
+
if (!validateChartJs()) return;
|
|
23402
|
+
const canvas = validateCanvas();
|
|
23403
|
+
if (!canvas) return;
|
|
23404
|
+
try {
|
|
23405
|
+
log("log", `Fetching ${currentPeriod} days of data...`);
|
|
23406
|
+
cachedData = await config.fetchData(currentPeriod);
|
|
23407
|
+
cachedData.fetchTimestamp = Date.now();
|
|
23408
|
+
if (config.onBeforeRender) {
|
|
23409
|
+
cachedData = config.onBeforeRender(cachedData);
|
|
23410
|
+
}
|
|
23411
|
+
if (chartInstance) {
|
|
23412
|
+
chartInstance.destroy();
|
|
23413
|
+
chartInstance = null;
|
|
23414
|
+
}
|
|
23415
|
+
const ctx = canvas.getContext("2d");
|
|
23416
|
+
const chartConfig = buildChartConfig(cachedData);
|
|
23417
|
+
chartInstance = new Chart(ctx, chartConfig);
|
|
23418
|
+
isRendered = true;
|
|
23419
|
+
const yAxisMax = calculateYAxisMax(cachedData.dailyTotals);
|
|
23420
|
+
log("log", `Chart initialized with yAxisMax: ${yAxisMax}`);
|
|
23421
|
+
config.onDataLoaded?.(cachedData);
|
|
23422
|
+
config.onAfterRender?.(chartInstance);
|
|
23423
|
+
setupButtonHandlers();
|
|
23424
|
+
updateTitle();
|
|
23425
|
+
setupAutoRefresh();
|
|
23426
|
+
} catch (error) {
|
|
23427
|
+
log("error", "Failed to render chart:", error);
|
|
23428
|
+
config.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
23429
|
+
}
|
|
23430
|
+
},
|
|
23431
|
+
async update(data) {
|
|
23432
|
+
if (data) {
|
|
23433
|
+
cachedData = data;
|
|
23434
|
+
cachedData.fetchTimestamp = Date.now();
|
|
23435
|
+
}
|
|
23436
|
+
if (!chartInstance || !cachedData) {
|
|
23437
|
+
log("warn", "Cannot update: chart not initialized or no data");
|
|
23438
|
+
return;
|
|
23439
|
+
}
|
|
23440
|
+
let renderData = cachedData;
|
|
23441
|
+
if (config.onBeforeRender) {
|
|
23442
|
+
renderData = config.onBeforeRender(cachedData);
|
|
23443
|
+
}
|
|
23444
|
+
const chartConfig = buildChartConfig(renderData);
|
|
23445
|
+
chartInstance.data = chartConfig.data;
|
|
23446
|
+
chartInstance.options = chartConfig.options;
|
|
23447
|
+
chartInstance.update("none");
|
|
23448
|
+
log("log", "Chart updated");
|
|
23449
|
+
},
|
|
23450
|
+
setChartType(type) {
|
|
23451
|
+
if (currentChartType === type) return;
|
|
23452
|
+
log("log", `Changing chart type to: ${type}`);
|
|
23453
|
+
currentChartType = type;
|
|
23454
|
+
if (cachedData && chartInstance) {
|
|
23455
|
+
const canvas = validateCanvas();
|
|
23456
|
+
if (canvas) {
|
|
23457
|
+
chartInstance.destroy();
|
|
23458
|
+
const ctx = canvas.getContext("2d");
|
|
23459
|
+
chartInstance = new Chart(ctx, buildChartConfig(cachedData));
|
|
23460
|
+
}
|
|
23461
|
+
}
|
|
23462
|
+
},
|
|
23463
|
+
setVizMode(mode) {
|
|
23464
|
+
if (currentVizMode === mode) return;
|
|
23465
|
+
log("log", `Changing viz mode to: ${mode}`);
|
|
23466
|
+
currentVizMode = mode;
|
|
23467
|
+
if (cachedData) {
|
|
23468
|
+
this.update();
|
|
23469
|
+
}
|
|
23470
|
+
},
|
|
23471
|
+
async setPeriod(days) {
|
|
23472
|
+
if (currentPeriod === days) return;
|
|
23473
|
+
log("log", `Changing period to: ${days} days`);
|
|
23474
|
+
currentPeriod = days;
|
|
23475
|
+
updateTitle();
|
|
23476
|
+
await this.refresh(true);
|
|
23477
|
+
},
|
|
23478
|
+
async refresh(forceRefresh = false) {
|
|
23479
|
+
if (!forceRefresh && cachedData?.fetchTimestamp) {
|
|
23480
|
+
const age = Date.now() - cachedData.fetchTimestamp;
|
|
23481
|
+
const ttl = config.cacheTTL ?? DEFAULT_CONFIG.cacheTTL;
|
|
23482
|
+
if (age < ttl) {
|
|
23483
|
+
log("log", `Using cached data (age: ${Math.round(age / 1e3)}s)`);
|
|
23484
|
+
return;
|
|
23485
|
+
}
|
|
23486
|
+
}
|
|
23487
|
+
log("log", "Refreshing data...");
|
|
23488
|
+
await this.render();
|
|
23489
|
+
},
|
|
23490
|
+
destroy() {
|
|
23491
|
+
log("log", "Destroying chart...");
|
|
23492
|
+
cleanupAutoRefresh();
|
|
23493
|
+
if (chartInstance) {
|
|
23494
|
+
chartInstance.destroy();
|
|
23495
|
+
chartInstance = null;
|
|
23496
|
+
}
|
|
23497
|
+
cachedData = null;
|
|
23498
|
+
isRendered = false;
|
|
23499
|
+
},
|
|
23500
|
+
getChartInstance() {
|
|
23501
|
+
return chartInstance;
|
|
23502
|
+
},
|
|
23503
|
+
getCachedData() {
|
|
23504
|
+
return cachedData;
|
|
23505
|
+
},
|
|
23506
|
+
getState() {
|
|
23507
|
+
return {
|
|
23508
|
+
period: currentPeriod,
|
|
23509
|
+
chartType: currentChartType,
|
|
23510
|
+
vizMode: currentVizMode,
|
|
23511
|
+
theme: currentTheme,
|
|
23512
|
+
isRendered
|
|
23513
|
+
};
|
|
23514
|
+
},
|
|
23515
|
+
exportCSV(filename) {
|
|
23516
|
+
if (!cachedData) {
|
|
23517
|
+
log("warn", "Cannot export: no data available");
|
|
23518
|
+
return;
|
|
23519
|
+
}
|
|
23520
|
+
const defaultFilename = config.exportFilename || `${config.domain}-consumo-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
23521
|
+
const csvContent = generateCSVContent(cachedData);
|
|
23522
|
+
downloadCSV(csvContent, filename || defaultFilename);
|
|
23523
|
+
},
|
|
23524
|
+
setTheme(theme) {
|
|
23525
|
+
if (currentTheme === theme) return;
|
|
23526
|
+
log("log", `Changing theme to: ${theme}`);
|
|
23527
|
+
currentTheme = theme;
|
|
23528
|
+
if (cachedData && chartInstance) {
|
|
23529
|
+
const canvas = validateCanvas();
|
|
23530
|
+
if (canvas) {
|
|
23531
|
+
chartInstance.destroy();
|
|
23532
|
+
const ctx = canvas.getContext("2d");
|
|
23533
|
+
chartInstance = new Chart(ctx, buildChartConfig(cachedData));
|
|
23534
|
+
}
|
|
23535
|
+
}
|
|
23536
|
+
},
|
|
23537
|
+
setIdealRange(range) {
|
|
23538
|
+
const rangeChanged = JSON.stringify(currentIdealRange) !== JSON.stringify(range);
|
|
23539
|
+
if (!rangeChanged) return;
|
|
23540
|
+
if (range) {
|
|
23541
|
+
log("log", `Setting ideal range: ${range.min} - ${range.max}`);
|
|
23542
|
+
} else {
|
|
23543
|
+
log("log", "Clearing ideal range");
|
|
23544
|
+
}
|
|
23545
|
+
currentIdealRange = range;
|
|
23546
|
+
if (cachedData && chartInstance) {
|
|
23547
|
+
const canvas = validateCanvas();
|
|
23548
|
+
if (canvas) {
|
|
23549
|
+
chartInstance.destroy();
|
|
23550
|
+
const ctx = canvas.getContext("2d");
|
|
23551
|
+
chartInstance = new Chart(ctx, buildChartConfig(cachedData));
|
|
23552
|
+
}
|
|
23553
|
+
}
|
|
23554
|
+
},
|
|
23555
|
+
getIdealRange() {
|
|
23556
|
+
return currentIdealRange;
|
|
23557
|
+
}
|
|
23558
|
+
};
|
|
23559
|
+
return instance;
|
|
23560
|
+
}
|
|
23561
|
+
|
|
23562
|
+
// src/components/Consumption7DaysChart/createConsumptionModal.ts
|
|
23563
|
+
var DOMAIN_CONFIG3 = {
|
|
23564
|
+
energy: { name: "Energia", icon: "\u26A1" },
|
|
23565
|
+
water: { name: "\xC1gua", icon: "\u{1F4A7}" },
|
|
23566
|
+
gas: { name: "G\xE1s", icon: "\u{1F525}" },
|
|
23567
|
+
temperature: { name: "Temperatura", icon: "\u{1F321}\uFE0F" }
|
|
23568
|
+
};
|
|
23569
|
+
function createConsumptionModal(config) {
|
|
23570
|
+
const modalId = `myio-consumption-modal-${Date.now()}`;
|
|
23571
|
+
let modalElement = null;
|
|
23572
|
+
let chartInstance = null;
|
|
23573
|
+
let headerInstance = null;
|
|
23574
|
+
let currentTheme = config.theme ?? "light";
|
|
23575
|
+
let currentChartType = config.defaultChartType ?? "line";
|
|
23576
|
+
let currentVizMode = config.defaultVizMode ?? "total";
|
|
23577
|
+
let isMaximized = false;
|
|
23578
|
+
const domainCfg = DOMAIN_CONFIG3[config.domain] || { name: config.domain, icon: "\u{1F4CA}" };
|
|
23579
|
+
const title = config.title || `${domainCfg.name} - Hist\xF3rico de Consumo`;
|
|
23580
|
+
function getThemeColors2() {
|
|
23581
|
+
return THEME_COLORS[currentTheme];
|
|
23582
|
+
}
|
|
23583
|
+
function renderModal4() {
|
|
23584
|
+
const colors = getThemeColors2();
|
|
23585
|
+
const exportFormats = config.exportFormats || ["csv"];
|
|
23586
|
+
headerInstance = createModalHeader({
|
|
23587
|
+
id: modalId,
|
|
23588
|
+
title,
|
|
23589
|
+
icon: domainCfg.icon,
|
|
23590
|
+
theme: currentTheme,
|
|
23591
|
+
isMaximized,
|
|
23592
|
+
exportFormats,
|
|
23593
|
+
onExport: (format) => {
|
|
23594
|
+
if (config.onExport) {
|
|
23595
|
+
config.onExport(format);
|
|
23596
|
+
} else {
|
|
23597
|
+
if (format === "csv") {
|
|
23598
|
+
chartInstance?.exportCSV();
|
|
23599
|
+
} else {
|
|
23600
|
+
console.warn(`[ConsumptionModal] Export format "${format}" requires custom onExport handler`);
|
|
23601
|
+
}
|
|
23602
|
+
}
|
|
23603
|
+
},
|
|
23604
|
+
onThemeToggle: (theme) => {
|
|
23605
|
+
currentTheme = theme;
|
|
23606
|
+
chartInstance?.setTheme(currentTheme);
|
|
23607
|
+
updateModal();
|
|
23608
|
+
},
|
|
23609
|
+
onMaximize: (maximized) => {
|
|
23610
|
+
isMaximized = maximized;
|
|
23611
|
+
updateModal();
|
|
23612
|
+
},
|
|
23613
|
+
onClose: () => {
|
|
23614
|
+
instance.close();
|
|
23615
|
+
}
|
|
23616
|
+
});
|
|
23617
|
+
return `
|
|
23618
|
+
<div class="myio-consumption-modal-overlay" style="
|
|
23619
|
+
position: fixed;
|
|
23620
|
+
top: 0;
|
|
23621
|
+
left: 0;
|
|
23622
|
+
width: 100%;
|
|
23623
|
+
height: 100%;
|
|
23624
|
+
background: rgba(0, 0, 0, 0.5);
|
|
23625
|
+
backdrop-filter: blur(2px);
|
|
23626
|
+
z-index: 99998;
|
|
23627
|
+
display: flex;
|
|
23628
|
+
justify-content: center;
|
|
23629
|
+
align-items: center;
|
|
23630
|
+
">
|
|
23631
|
+
<div class="myio-consumption-modal-content" style="
|
|
23632
|
+
background: ${colors.chartBackground};
|
|
23633
|
+
border-radius: ${isMaximized ? "0" : "10px"};
|
|
23634
|
+
width: ${isMaximized ? "100%" : "95%"};
|
|
23635
|
+
max-width: ${isMaximized ? "100%" : "1200px"};
|
|
23636
|
+
height: ${isMaximized ? "100%" : "85vh"};
|
|
23637
|
+
display: flex;
|
|
23638
|
+
flex-direction: column;
|
|
23639
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
23640
|
+
overflow: hidden;
|
|
23641
|
+
">
|
|
23642
|
+
<!-- MyIO Premium Header (using ModalHeader component) -->
|
|
23643
|
+
${headerInstance.render()}
|
|
23644
|
+
|
|
23645
|
+
<!-- Controls Bar -->
|
|
23646
|
+
<div class="myio-consumption-modal-controls" style="
|
|
23647
|
+
display: flex;
|
|
23648
|
+
gap: 16px;
|
|
23649
|
+
padding: 12px 16px;
|
|
23650
|
+
background: ${currentTheme === "dark" ? "#374151" : "#f7f7f7"};
|
|
23651
|
+
border-bottom: 1px solid ${colors.border};
|
|
23652
|
+
align-items: center;
|
|
23653
|
+
flex-wrap: wrap;
|
|
23654
|
+
">
|
|
23655
|
+
<!-- Viz Mode Tabs -->
|
|
23656
|
+
<div style="display: flex; gap: 2px; background: ${currentTheme === "dark" ? "#4b5563" : "#e5e7eb"}; border-radius: 8px; padding: 2px;">
|
|
23657
|
+
<button id="${modalId}-viz-total" style="
|
|
23658
|
+
padding: 6px 12px;
|
|
23659
|
+
border: none;
|
|
23660
|
+
border-radius: 6px;
|
|
23661
|
+
font-size: 13px;
|
|
23662
|
+
cursor: pointer;
|
|
23663
|
+
transition: all 0.2s;
|
|
23664
|
+
background: ${currentVizMode === "total" ? "#3e1a7d" : "transparent"};
|
|
23665
|
+
color: ${currentVizMode === "total" ? "white" : colors.text};
|
|
23666
|
+
">Consolidado</button>
|
|
23667
|
+
<button id="${modalId}-viz-separate" style="
|
|
23668
|
+
padding: 6px 12px;
|
|
23669
|
+
border: none;
|
|
23670
|
+
border-radius: 6px;
|
|
23671
|
+
font-size: 13px;
|
|
23672
|
+
cursor: pointer;
|
|
23673
|
+
transition: all 0.2s;
|
|
23674
|
+
background: ${currentVizMode === "separate" ? "#3e1a7d" : "transparent"};
|
|
23675
|
+
color: ${currentVizMode === "separate" ? "white" : colors.text};
|
|
23676
|
+
">Por Shopping</button>
|
|
23677
|
+
</div>
|
|
23678
|
+
|
|
23679
|
+
<!-- Chart Type Tabs -->
|
|
23680
|
+
<div style="display: flex; gap: 2px; background: ${currentTheme === "dark" ? "#4b5563" : "#e5e7eb"}; border-radius: 8px; padding: 2px;">
|
|
23681
|
+
<button id="${modalId}-type-line" style="
|
|
23682
|
+
padding: 6px 12px;
|
|
23683
|
+
border: none;
|
|
23684
|
+
border-radius: 6px;
|
|
23685
|
+
font-size: 13px;
|
|
23686
|
+
cursor: pointer;
|
|
23687
|
+
transition: all 0.2s;
|
|
23688
|
+
background: ${currentChartType === "line" ? "#3e1a7d" : "transparent"};
|
|
23689
|
+
color: ${currentChartType === "line" ? "white" : colors.text};
|
|
23690
|
+
">Linhas</button>
|
|
23691
|
+
<button id="${modalId}-type-bar" style="
|
|
23692
|
+
padding: 6px 12px;
|
|
23693
|
+
border: none;
|
|
23694
|
+
border-radius: 6px;
|
|
23695
|
+
font-size: 13px;
|
|
23696
|
+
cursor: pointer;
|
|
23697
|
+
transition: all 0.2s;
|
|
23698
|
+
background: ${currentChartType === "bar" ? "#3e1a7d" : "transparent"};
|
|
23699
|
+
color: ${currentChartType === "bar" ? "white" : colors.text};
|
|
23700
|
+
">Barras</button>
|
|
23701
|
+
</div>
|
|
23702
|
+
</div>
|
|
23703
|
+
|
|
23704
|
+
<!-- Chart Container -->
|
|
23705
|
+
<div style="
|
|
23706
|
+
flex: 1;
|
|
23707
|
+
padding: 16px;
|
|
23708
|
+
min-height: 0;
|
|
23709
|
+
position: relative;
|
|
23710
|
+
background: ${colors.chartBackground};
|
|
23711
|
+
">
|
|
23712
|
+
<canvas id="${modalId}-chart" style="width: 100%; height: 100%;"></canvas>
|
|
23713
|
+
</div>
|
|
23714
|
+
</div>
|
|
23715
|
+
</div>
|
|
23716
|
+
`;
|
|
23717
|
+
}
|
|
23718
|
+
function setupListeners() {
|
|
23719
|
+
if (!modalElement) return;
|
|
23720
|
+
headerInstance?.attachListeners();
|
|
23721
|
+
document.getElementById(`${modalId}-viz-total`)?.addEventListener("click", () => {
|
|
23722
|
+
currentVizMode = "total";
|
|
23723
|
+
chartInstance?.setVizMode("total");
|
|
23724
|
+
updateControlStyles();
|
|
23725
|
+
});
|
|
23726
|
+
document.getElementById(`${modalId}-viz-separate`)?.addEventListener("click", () => {
|
|
23727
|
+
currentVizMode = "separate";
|
|
23728
|
+
chartInstance?.setVizMode("separate");
|
|
23729
|
+
updateControlStyles();
|
|
23730
|
+
});
|
|
23731
|
+
document.getElementById(`${modalId}-type-line`)?.addEventListener("click", () => {
|
|
23732
|
+
currentChartType = "line";
|
|
23733
|
+
chartInstance?.setChartType("line");
|
|
23734
|
+
updateControlStyles();
|
|
23735
|
+
});
|
|
23736
|
+
document.getElementById(`${modalId}-type-bar`)?.addEventListener("click", () => {
|
|
23737
|
+
currentChartType = "bar";
|
|
23738
|
+
chartInstance?.setChartType("bar");
|
|
23739
|
+
updateControlStyles();
|
|
23740
|
+
});
|
|
23741
|
+
modalElement.querySelector(".myio-consumption-modal-overlay")?.addEventListener("click", (e) => {
|
|
23742
|
+
if (e.target.classList.contains("myio-consumption-modal-overlay")) {
|
|
23743
|
+
instance.close();
|
|
23744
|
+
}
|
|
23745
|
+
});
|
|
23746
|
+
const handleKeydown = (e) => {
|
|
23747
|
+
if (e.key === "Escape") {
|
|
23748
|
+
instance.close();
|
|
23749
|
+
}
|
|
23750
|
+
};
|
|
23751
|
+
document.addEventListener("keydown", handleKeydown);
|
|
23752
|
+
modalElement.__handleKeydown = handleKeydown;
|
|
23753
|
+
}
|
|
23754
|
+
function updateControlStyles() {
|
|
23755
|
+
const colors = getThemeColors2();
|
|
23756
|
+
const vizTotalBtn = document.getElementById(`${modalId}-viz-total`);
|
|
23757
|
+
const vizSeparateBtn = document.getElementById(`${modalId}-viz-separate`);
|
|
23758
|
+
if (vizTotalBtn) {
|
|
23759
|
+
vizTotalBtn.style.background = currentVizMode === "total" ? "#3e1a7d" : "transparent";
|
|
23760
|
+
vizTotalBtn.style.color = currentVizMode === "total" ? "white" : colors.text;
|
|
23761
|
+
}
|
|
23762
|
+
if (vizSeparateBtn) {
|
|
23763
|
+
vizSeparateBtn.style.background = currentVizMode === "separate" ? "#3e1a7d" : "transparent";
|
|
23764
|
+
vizSeparateBtn.style.color = currentVizMode === "separate" ? "white" : colors.text;
|
|
23765
|
+
}
|
|
23766
|
+
const typeLineBtn = document.getElementById(`${modalId}-type-line`);
|
|
23767
|
+
const typeBarBtn = document.getElementById(`${modalId}-type-bar`);
|
|
23768
|
+
if (typeLineBtn) {
|
|
23769
|
+
typeLineBtn.style.background = currentChartType === "line" ? "#3e1a7d" : "transparent";
|
|
23770
|
+
typeLineBtn.style.color = currentChartType === "line" ? "white" : colors.text;
|
|
23771
|
+
}
|
|
23772
|
+
if (typeBarBtn) {
|
|
23773
|
+
typeBarBtn.style.background = currentChartType === "bar" ? "#3e1a7d" : "transparent";
|
|
23774
|
+
typeBarBtn.style.color = currentChartType === "bar" ? "white" : colors.text;
|
|
23775
|
+
}
|
|
23776
|
+
}
|
|
23777
|
+
function updateModal() {
|
|
23778
|
+
if (!modalElement) return;
|
|
23779
|
+
const cachedData = chartInstance?.getCachedData();
|
|
23780
|
+
headerInstance?.destroy();
|
|
23781
|
+
chartInstance?.destroy();
|
|
23782
|
+
modalElement.innerHTML = renderModal4();
|
|
23783
|
+
setupListeners();
|
|
23784
|
+
if (cachedData) {
|
|
23785
|
+
chartInstance = createConsumption7DaysChart({
|
|
23786
|
+
...config,
|
|
23787
|
+
containerId: `${modalId}-chart`,
|
|
23788
|
+
theme: currentTheme,
|
|
23789
|
+
defaultChartType: currentChartType,
|
|
23790
|
+
defaultVizMode: currentVizMode
|
|
23791
|
+
});
|
|
23792
|
+
chartInstance.update(cachedData);
|
|
23793
|
+
}
|
|
23794
|
+
}
|
|
23795
|
+
const instance = {
|
|
23796
|
+
async open() {
|
|
23797
|
+
modalElement = document.createElement("div");
|
|
23798
|
+
modalElement.id = modalId;
|
|
23799
|
+
modalElement.innerHTML = renderModal4();
|
|
23800
|
+
const container = config.container || document.body;
|
|
23801
|
+
container.appendChild(modalElement);
|
|
23802
|
+
setupListeners();
|
|
23803
|
+
chartInstance = createConsumption7DaysChart({
|
|
23804
|
+
...config,
|
|
23805
|
+
containerId: `${modalId}-chart`,
|
|
23806
|
+
theme: currentTheme,
|
|
23807
|
+
defaultChartType: currentChartType,
|
|
23808
|
+
defaultVizMode: currentVizMode
|
|
23809
|
+
});
|
|
23810
|
+
await chartInstance.render();
|
|
23811
|
+
},
|
|
23812
|
+
close() {
|
|
23813
|
+
if (modalElement) {
|
|
23814
|
+
const handleKeydown = modalElement.__handleKeydown;
|
|
23815
|
+
if (handleKeydown) {
|
|
23816
|
+
document.removeEventListener("keydown", handleKeydown);
|
|
23817
|
+
}
|
|
23818
|
+
headerInstance?.destroy();
|
|
23819
|
+
headerInstance = null;
|
|
23820
|
+
chartInstance?.destroy();
|
|
23821
|
+
chartInstance = null;
|
|
23822
|
+
modalElement.remove();
|
|
23823
|
+
modalElement = null;
|
|
23824
|
+
config.onClose?.();
|
|
23825
|
+
}
|
|
23826
|
+
},
|
|
23827
|
+
getChart() {
|
|
23828
|
+
return chartInstance;
|
|
23829
|
+
},
|
|
23830
|
+
destroy() {
|
|
23831
|
+
instance.close();
|
|
23832
|
+
}
|
|
23833
|
+
};
|
|
23834
|
+
return instance;
|
|
23835
|
+
}
|
|
23836
|
+
|
|
23837
|
+
// src/components/Consumption7DaysChart/createConsumptionChartWidget.ts
|
|
23838
|
+
var DOMAIN_CONFIG4 = {
|
|
23839
|
+
energy: {
|
|
23840
|
+
name: "Energia",
|
|
23841
|
+
icon: "\u26A1",
|
|
23842
|
+
color: "#6c2fbf",
|
|
23843
|
+
colors: ["#2563eb", "#16a34a", "#8b5cf6", "#ea580c", "#dc2626"]
|
|
23844
|
+
},
|
|
23845
|
+
water: {
|
|
23846
|
+
name: "\xC1gua",
|
|
23847
|
+
icon: "\u{1F4A7}",
|
|
23848
|
+
color: "#0288d1",
|
|
23849
|
+
colors: ["#0288d1", "#06b6d4", "#0891b2", "#22d3ee", "#67e8f9"]
|
|
23850
|
+
},
|
|
23851
|
+
gas: {
|
|
23852
|
+
name: "G\xE1s",
|
|
23853
|
+
icon: "\u{1F525}",
|
|
23854
|
+
color: "#ea580c",
|
|
23855
|
+
colors: ["#ea580c", "#f97316", "#fb923c", "#fdba74", "#fed7aa"]
|
|
23856
|
+
},
|
|
23857
|
+
temperature: {
|
|
23858
|
+
name: "Temperatura",
|
|
23859
|
+
icon: "\u{1F321}\uFE0F",
|
|
23860
|
+
color: "#e65100",
|
|
23861
|
+
colors: ["#dc2626", "#059669", "#0ea5e9", "#f59e0b", "#8b5cf6"]
|
|
23862
|
+
}
|
|
23863
|
+
};
|
|
23864
|
+
function getWidgetStyles(theme, primaryColor) {
|
|
23865
|
+
const colors = THEME_COLORS[theme];
|
|
23866
|
+
return `
|
|
23867
|
+
.myio-chart-widget {
|
|
23868
|
+
font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
|
|
23869
|
+
background: ${colors.chartBackground};
|
|
23870
|
+
border: 1px solid ${colors.border};
|
|
23871
|
+
border-radius: 16px;
|
|
23872
|
+
overflow: hidden;
|
|
23873
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
23874
|
+
}
|
|
23875
|
+
|
|
23876
|
+
.myio-chart-widget.dark {
|
|
23877
|
+
background: ${THEME_COLORS.dark.chartBackground};
|
|
23878
|
+
border-color: ${THEME_COLORS.dark.border};
|
|
23879
|
+
}
|
|
23880
|
+
|
|
23881
|
+
.myio-chart-widget-header {
|
|
23882
|
+
display: flex;
|
|
23883
|
+
justify-content: space-between;
|
|
23884
|
+
align-items: center;
|
|
23885
|
+
padding: 16px 20px;
|
|
23886
|
+
border-bottom: 1px solid ${colors.border};
|
|
23887
|
+
flex-wrap: wrap;
|
|
23888
|
+
gap: 12px;
|
|
23889
|
+
}
|
|
23890
|
+
|
|
23891
|
+
.myio-chart-widget-title-group {
|
|
23892
|
+
display: flex;
|
|
23893
|
+
align-items: center;
|
|
23894
|
+
gap: 10px;
|
|
23895
|
+
}
|
|
23896
|
+
|
|
23897
|
+
.myio-chart-widget-title {
|
|
23898
|
+
margin: 0;
|
|
23899
|
+
font-size: 16px;
|
|
23900
|
+
font-weight: 600;
|
|
23901
|
+
color: ${colors.text};
|
|
23902
|
+
}
|
|
23903
|
+
|
|
23904
|
+
.myio-chart-widget-controls {
|
|
23905
|
+
display: flex;
|
|
23906
|
+
align-items: center;
|
|
23907
|
+
gap: 12px;
|
|
23908
|
+
flex-wrap: wrap;
|
|
23909
|
+
}
|
|
23910
|
+
|
|
23911
|
+
.myio-chart-widget-tabs {
|
|
23912
|
+
display: flex;
|
|
23913
|
+
gap: 2px;
|
|
23914
|
+
background: ${theme === "dark" ? "#374151" : "#f3f4f6"};
|
|
23915
|
+
padding: 3px;
|
|
23916
|
+
border-radius: 8px;
|
|
23917
|
+
}
|
|
23918
|
+
|
|
23919
|
+
.myio-chart-widget-tab {
|
|
23920
|
+
padding: 6px 14px;
|
|
23921
|
+
font-size: 12px;
|
|
23922
|
+
font-weight: 500;
|
|
23923
|
+
border: none;
|
|
23924
|
+
background: transparent;
|
|
23925
|
+
color: ${colors.textMuted};
|
|
23926
|
+
cursor: pointer;
|
|
23927
|
+
border-radius: 6px;
|
|
23928
|
+
transition: all 0.2s;
|
|
23929
|
+
white-space: nowrap;
|
|
23930
|
+
}
|
|
23931
|
+
|
|
23932
|
+
.myio-chart-widget-tab:hover {
|
|
23933
|
+
color: ${colors.text};
|
|
23934
|
+
background: ${theme === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
|
|
23935
|
+
}
|
|
23936
|
+
|
|
23937
|
+
.myio-chart-widget-tab.active {
|
|
23938
|
+
background: ${primaryColor};
|
|
23939
|
+
color: white;
|
|
23940
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
23941
|
+
}
|
|
23942
|
+
|
|
23943
|
+
.myio-chart-widget-btn {
|
|
23944
|
+
background: transparent;
|
|
23945
|
+
border: 1px solid ${colors.border};
|
|
23946
|
+
font-size: 16px;
|
|
23947
|
+
cursor: pointer;
|
|
23948
|
+
padding: 6px 10px;
|
|
23949
|
+
border-radius: 6px;
|
|
23950
|
+
transition: all 0.2s;
|
|
23951
|
+
color: ${colors.text};
|
|
23952
|
+
}
|
|
23953
|
+
|
|
23954
|
+
.myio-chart-widget-btn:hover {
|
|
23955
|
+
background: ${primaryColor};
|
|
23956
|
+
border-color: ${primaryColor};
|
|
23957
|
+
color: white;
|
|
23958
|
+
}
|
|
23959
|
+
|
|
23960
|
+
.myio-chart-widget-body {
|
|
23961
|
+
position: relative;
|
|
23962
|
+
padding: 16px 20px;
|
|
23963
|
+
}
|
|
23964
|
+
|
|
23965
|
+
.myio-chart-widget-canvas-container {
|
|
23966
|
+
position: relative;
|
|
23967
|
+
width: 100%;
|
|
23968
|
+
}
|
|
23969
|
+
|
|
23970
|
+
.myio-chart-widget-loading {
|
|
23971
|
+
position: absolute;
|
|
23972
|
+
top: 0;
|
|
23973
|
+
left: 0;
|
|
23974
|
+
right: 0;
|
|
23975
|
+
bottom: 0;
|
|
23976
|
+
background: rgba(255, 255, 255, 0.9);
|
|
23977
|
+
display: flex;
|
|
23978
|
+
align-items: center;
|
|
23979
|
+
justify-content: center;
|
|
23980
|
+
z-index: 10;
|
|
23981
|
+
border-radius: 8px;
|
|
23982
|
+
}
|
|
23983
|
+
|
|
23984
|
+
.myio-chart-widget.dark .myio-chart-widget-loading {
|
|
23985
|
+
background: rgba(31, 41, 55, 0.9);
|
|
23986
|
+
}
|
|
23987
|
+
|
|
23988
|
+
.myio-chart-widget-spinner {
|
|
23989
|
+
width: 32px;
|
|
23990
|
+
height: 32px;
|
|
23991
|
+
border: 3px solid ${colors.border};
|
|
23992
|
+
border-top-color: ${primaryColor};
|
|
23993
|
+
border-radius: 50%;
|
|
23994
|
+
animation: myio-spin 1s linear infinite;
|
|
23995
|
+
}
|
|
23996
|
+
|
|
23997
|
+
@keyframes myio-spin {
|
|
23998
|
+
to { transform: rotate(360deg); }
|
|
23999
|
+
}
|
|
24000
|
+
|
|
24001
|
+
.myio-chart-widget-footer {
|
|
24002
|
+
display: flex;
|
|
24003
|
+
justify-content: space-around;
|
|
24004
|
+
padding: 16px 20px;
|
|
24005
|
+
border-top: 1px solid ${colors.border};
|
|
24006
|
+
gap: 16px;
|
|
24007
|
+
flex-wrap: wrap;
|
|
24008
|
+
}
|
|
24009
|
+
|
|
24010
|
+
.myio-chart-widget-stat {
|
|
24011
|
+
display: flex;
|
|
24012
|
+
flex-direction: column;
|
|
24013
|
+
align-items: center;
|
|
24014
|
+
text-align: center;
|
|
24015
|
+
min-width: 100px;
|
|
24016
|
+
}
|
|
24017
|
+
|
|
24018
|
+
.myio-chart-widget-stat-label {
|
|
24019
|
+
font-size: 11px;
|
|
24020
|
+
font-weight: 500;
|
|
24021
|
+
color: ${colors.textMuted};
|
|
24022
|
+
text-transform: uppercase;
|
|
24023
|
+
letter-spacing: 0.5px;
|
|
24024
|
+
margin-bottom: 4px;
|
|
24025
|
+
}
|
|
24026
|
+
|
|
24027
|
+
.myio-chart-widget-stat-value {
|
|
24028
|
+
font-size: 20px;
|
|
24029
|
+
font-weight: 700;
|
|
24030
|
+
color: ${colors.text};
|
|
24031
|
+
}
|
|
24032
|
+
|
|
24033
|
+
.myio-chart-widget-stat-value.primary {
|
|
24034
|
+
color: ${primaryColor};
|
|
24035
|
+
}
|
|
24036
|
+
|
|
24037
|
+
.myio-chart-widget-stat-sub {
|
|
24038
|
+
font-size: 11px;
|
|
24039
|
+
color: ${colors.textMuted};
|
|
24040
|
+
margin-top: 2px;
|
|
24041
|
+
}
|
|
24042
|
+
|
|
24043
|
+
/* Settings Modal Overlay */
|
|
24044
|
+
.myio-settings-overlay {
|
|
24045
|
+
position: fixed;
|
|
24046
|
+
inset: 0;
|
|
24047
|
+
background: rgba(0, 0, 0, 0.6);
|
|
24048
|
+
display: flex;
|
|
24049
|
+
align-items: center;
|
|
24050
|
+
justify-content: center;
|
|
24051
|
+
z-index: 99999;
|
|
24052
|
+
backdrop-filter: blur(4px);
|
|
24053
|
+
}
|
|
24054
|
+
|
|
24055
|
+
.myio-settings-overlay.hidden {
|
|
24056
|
+
display: none;
|
|
24057
|
+
}
|
|
24058
|
+
|
|
24059
|
+
.myio-settings-card {
|
|
24060
|
+
background: ${colors.chartBackground};
|
|
24061
|
+
border-radius: 10px;
|
|
24062
|
+
width: 90%;
|
|
24063
|
+
max-width: 600px;
|
|
24064
|
+
max-height: 90vh;
|
|
24065
|
+
display: flex;
|
|
24066
|
+
flex-direction: column;
|
|
24067
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
|
24068
|
+
overflow: hidden;
|
|
24069
|
+
}
|
|
24070
|
+
|
|
24071
|
+
.myio-settings-card .myio-modal-header {
|
|
24072
|
+
border-radius: 10px 10px 0 0;
|
|
24073
|
+
}
|
|
24074
|
+
|
|
24075
|
+
.myio-settings-body {
|
|
24076
|
+
padding: 20px;
|
|
24077
|
+
overflow-y: auto;
|
|
24078
|
+
display: flex;
|
|
24079
|
+
flex-direction: column;
|
|
24080
|
+
gap: 20px;
|
|
24081
|
+
}
|
|
24082
|
+
|
|
24083
|
+
.myio-settings-section {
|
|
24084
|
+
background: ${theme === "dark" ? "rgba(255,255,255,0.05)" : "#f8fafc"};
|
|
24085
|
+
border-radius: 10px;
|
|
24086
|
+
padding: 16px;
|
|
24087
|
+
border: 1px solid ${theme === "dark" ? "rgba(255,255,255,0.1)" : "#e2e8f0"};
|
|
24088
|
+
}
|
|
24089
|
+
|
|
24090
|
+
.myio-settings-section-label {
|
|
24091
|
+
font-size: 13px;
|
|
24092
|
+
font-weight: 600;
|
|
24093
|
+
color: ${colors.text};
|
|
24094
|
+
margin-bottom: 12px;
|
|
24095
|
+
display: flex;
|
|
24096
|
+
align-items: center;
|
|
24097
|
+
gap: 8px;
|
|
24098
|
+
}
|
|
24099
|
+
|
|
24100
|
+
.myio-settings-row {
|
|
24101
|
+
display: flex;
|
|
24102
|
+
gap: 16px;
|
|
24103
|
+
flex-wrap: wrap;
|
|
24104
|
+
align-items: flex-end;
|
|
24105
|
+
}
|
|
24106
|
+
|
|
24107
|
+
.myio-settings-field {
|
|
24108
|
+
display: flex;
|
|
24109
|
+
flex-direction: column;
|
|
24110
|
+
gap: 6px;
|
|
24111
|
+
flex: 1;
|
|
24112
|
+
min-width: 120px;
|
|
24113
|
+
}
|
|
24114
|
+
|
|
24115
|
+
.myio-settings-field-label {
|
|
24116
|
+
font-size: 12px;
|
|
24117
|
+
font-weight: 500;
|
|
24118
|
+
color: ${colors.textMuted};
|
|
24119
|
+
}
|
|
24120
|
+
|
|
24121
|
+
.myio-settings-input,
|
|
24122
|
+
.myio-settings-select {
|
|
24123
|
+
padding: 10px 14px;
|
|
24124
|
+
border: 1px solid ${colors.border};
|
|
24125
|
+
border-radius: 8px;
|
|
24126
|
+
font-size: 14px;
|
|
24127
|
+
background: ${colors.chartBackground};
|
|
24128
|
+
color: ${colors.text};
|
|
24129
|
+
width: 100%;
|
|
24130
|
+
}
|
|
24131
|
+
|
|
24132
|
+
.myio-settings-input:focus,
|
|
24133
|
+
.myio-settings-select:focus {
|
|
24134
|
+
outline: 2px solid ${primaryColor};
|
|
24135
|
+
outline-offset: 1px;
|
|
24136
|
+
}
|
|
24137
|
+
|
|
24138
|
+
.myio-settings-tabs {
|
|
24139
|
+
display: flex;
|
|
24140
|
+
gap: 2px;
|
|
24141
|
+
background: ${theme === "dark" ? "#374151" : "#e5e7eb"};
|
|
24142
|
+
padding: 3px;
|
|
24143
|
+
border-radius: 8px;
|
|
24144
|
+
}
|
|
24145
|
+
|
|
24146
|
+
.myio-settings-tab {
|
|
24147
|
+
flex: 1;
|
|
24148
|
+
padding: 8px 12px;
|
|
24149
|
+
font-size: 12px;
|
|
24150
|
+
font-weight: 500;
|
|
24151
|
+
border: none;
|
|
24152
|
+
background: transparent;
|
|
24153
|
+
color: ${colors.textMuted};
|
|
24154
|
+
cursor: pointer;
|
|
24155
|
+
border-radius: 6px;
|
|
24156
|
+
transition: all 0.2s;
|
|
24157
|
+
white-space: nowrap;
|
|
24158
|
+
}
|
|
24159
|
+
|
|
24160
|
+
.myio-settings-tab:hover {
|
|
24161
|
+
color: ${colors.text};
|
|
24162
|
+
background: ${theme === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
|
|
24163
|
+
}
|
|
24164
|
+
|
|
24165
|
+
.myio-settings-tab.active {
|
|
24166
|
+
background: ${primaryColor};
|
|
24167
|
+
color: white;
|
|
24168
|
+
}
|
|
24169
|
+
|
|
24170
|
+
.myio-settings-footer {
|
|
24171
|
+
display: flex;
|
|
24172
|
+
gap: 12px;
|
|
24173
|
+
justify-content: flex-end;
|
|
24174
|
+
padding: 16px 20px;
|
|
24175
|
+
border-top: 1px solid ${colors.border};
|
|
24176
|
+
background: ${theme === "dark" ? "rgba(0,0,0,0.2)" : "#fafafa"};
|
|
24177
|
+
}
|
|
24178
|
+
|
|
24179
|
+
.myio-settings-btn {
|
|
24180
|
+
padding: 10px 20px;
|
|
24181
|
+
border-radius: 8px;
|
|
24182
|
+
font-size: 14px;
|
|
24183
|
+
font-weight: 500;
|
|
24184
|
+
cursor: pointer;
|
|
24185
|
+
transition: all 0.2s;
|
|
24186
|
+
}
|
|
24187
|
+
|
|
24188
|
+
.myio-settings-btn-secondary {
|
|
24189
|
+
background: transparent;
|
|
24190
|
+
border: 1px solid ${colors.border};
|
|
24191
|
+
color: ${colors.text};
|
|
24192
|
+
}
|
|
24193
|
+
|
|
24194
|
+
.myio-settings-btn-secondary:hover {
|
|
24195
|
+
background: ${theme === "dark" ? "rgba(255,255,255,0.1)" : "#f3f4f6"};
|
|
24196
|
+
}
|
|
24197
|
+
|
|
24198
|
+
.myio-settings-btn-primary {
|
|
24199
|
+
background: ${primaryColor};
|
|
24200
|
+
border: none;
|
|
24201
|
+
color: white;
|
|
24202
|
+
}
|
|
24203
|
+
|
|
24204
|
+
.myio-settings-btn-primary:hover {
|
|
24205
|
+
filter: brightness(1.1);
|
|
24206
|
+
}
|
|
24207
|
+
|
|
24208
|
+
.myio-settings-hint {
|
|
24209
|
+
font-size: 11px;
|
|
24210
|
+
color: ${colors.textMuted};
|
|
24211
|
+
font-weight: normal;
|
|
24212
|
+
}
|
|
24213
|
+
|
|
24214
|
+
.myio-settings-context-group {
|
|
24215
|
+
margin-bottom: 8px;
|
|
24216
|
+
}
|
|
24217
|
+
|
|
24218
|
+
.myio-settings-context-group:last-child {
|
|
24219
|
+
margin-bottom: 0;
|
|
24220
|
+
}
|
|
24221
|
+
|
|
24222
|
+
/* Dropdown styles */
|
|
24223
|
+
.myio-settings-dropdown-container {
|
|
24224
|
+
position: relative;
|
|
24225
|
+
}
|
|
24226
|
+
|
|
24227
|
+
.myio-settings-dropdown-btn {
|
|
24228
|
+
padding: 10px 14px;
|
|
24229
|
+
border: 1px solid ${colors.border};
|
|
24230
|
+
border-radius: 8px;
|
|
24231
|
+
font-size: 14px;
|
|
24232
|
+
background: ${colors.chartBackground};
|
|
24233
|
+
color: ${colors.text};
|
|
24234
|
+
cursor: pointer;
|
|
24235
|
+
min-width: 180px;
|
|
24236
|
+
display: flex;
|
|
24237
|
+
align-items: center;
|
|
24238
|
+
justify-content: space-between;
|
|
24239
|
+
gap: 8px;
|
|
24240
|
+
width: 100%;
|
|
24241
|
+
}
|
|
24242
|
+
|
|
24243
|
+
.myio-settings-dropdown-btn:hover {
|
|
24244
|
+
border-color: ${primaryColor};
|
|
24245
|
+
}
|
|
24246
|
+
|
|
24247
|
+
.myio-settings-dropdown-arrow {
|
|
24248
|
+
font-size: 10px;
|
|
24249
|
+
color: ${colors.textMuted};
|
|
24250
|
+
}
|
|
24251
|
+
|
|
24252
|
+
.myio-settings-dropdown {
|
|
24253
|
+
position: absolute;
|
|
24254
|
+
top: calc(100% + 4px);
|
|
24255
|
+
left: 0;
|
|
24256
|
+
z-index: 100001;
|
|
24257
|
+
background: ${colors.chartBackground};
|
|
24258
|
+
border: 1px solid ${colors.border};
|
|
24259
|
+
border-radius: 8px;
|
|
24260
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
24261
|
+
min-width: 220px;
|
|
24262
|
+
padding: 8px 0;
|
|
24263
|
+
}
|
|
24264
|
+
|
|
24265
|
+
.myio-settings-dropdown.hidden {
|
|
24266
|
+
display: none;
|
|
24267
|
+
}
|
|
24268
|
+
|
|
24269
|
+
.myio-settings-dropdown-option {
|
|
24270
|
+
display: flex;
|
|
24271
|
+
align-items: center;
|
|
24272
|
+
gap: 10px;
|
|
24273
|
+
padding: 10px 14px;
|
|
24274
|
+
cursor: pointer;
|
|
24275
|
+
font-size: 13px;
|
|
24276
|
+
color: ${colors.text};
|
|
24277
|
+
transition: background 0.15s;
|
|
24278
|
+
}
|
|
24279
|
+
|
|
24280
|
+
.myio-settings-dropdown-option:hover {
|
|
24281
|
+
background: ${theme === "dark" ? "rgba(255,255,255,0.1)" : "#f3f4f6"};
|
|
24282
|
+
}
|
|
24283
|
+
|
|
24284
|
+
.myio-settings-dropdown-option input {
|
|
24285
|
+
width: 16px;
|
|
24286
|
+
height: 16px;
|
|
24287
|
+
cursor: pointer;
|
|
24288
|
+
accent-color: ${primaryColor};
|
|
24289
|
+
}
|
|
24290
|
+
|
|
24291
|
+
.myio-settings-dropdown-actions {
|
|
24292
|
+
border-top: 1px solid ${colors.border};
|
|
24293
|
+
margin-top: 8px;
|
|
24294
|
+
padding: 8px;
|
|
24295
|
+
display: flex;
|
|
24296
|
+
flex-direction: column;
|
|
24297
|
+
gap: 6px;
|
|
24298
|
+
}
|
|
24299
|
+
|
|
24300
|
+
.myio-settings-dropdown-actions button {
|
|
24301
|
+
width: 100%;
|
|
24302
|
+
padding: 8px;
|
|
24303
|
+
background: ${theme === "dark" ? "rgba(255,255,255,0.1)" : "#f3f4f6"};
|
|
24304
|
+
border: none;
|
|
24305
|
+
border-radius: 6px;
|
|
24306
|
+
cursor: pointer;
|
|
24307
|
+
font-size: 12px;
|
|
24308
|
+
color: ${colors.text};
|
|
24309
|
+
transition: background 0.15s;
|
|
24310
|
+
}
|
|
24311
|
+
|
|
24312
|
+
.myio-settings-dropdown-actions button:hover {
|
|
24313
|
+
background: ${theme === "dark" ? "rgba(255,255,255,0.15)" : "#e5e7eb"};
|
|
24314
|
+
}
|
|
24315
|
+
|
|
24316
|
+
/* Suggestion icon styles */
|
|
24317
|
+
.myio-settings-section-label span[id$="-settings-suggestion"] {
|
|
24318
|
+
transition: opacity 0.2s, transform 0.2s;
|
|
24319
|
+
}
|
|
24320
|
+
|
|
24321
|
+
.myio-settings-section-label span[id$="-settings-suggestion"]:hover {
|
|
24322
|
+
opacity: 1 !important;
|
|
24323
|
+
transform: scale(1.2);
|
|
24324
|
+
}
|
|
24325
|
+
`;
|
|
24326
|
+
}
|
|
24327
|
+
function createConsumptionChartWidget(config) {
|
|
24328
|
+
const widgetId = `myio-widget-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
24329
|
+
let containerElement = null;
|
|
24330
|
+
let chartInstance = null;
|
|
24331
|
+
let styleElement = null;
|
|
24332
|
+
let settingsModalElement = null;
|
|
24333
|
+
let settingsHeaderInstance = null;
|
|
24334
|
+
let currentTheme = config.theme ?? "light";
|
|
24335
|
+
let currentChartType = config.defaultChartType ?? "line";
|
|
24336
|
+
let currentVizMode = config.defaultVizMode ?? "total";
|
|
24337
|
+
let currentPeriod = config.defaultPeriod ?? 7;
|
|
24338
|
+
let currentIdealRange = config.idealRange ?? null;
|
|
24339
|
+
let isLoading = false;
|
|
24340
|
+
let tempPeriod = currentPeriod;
|
|
24341
|
+
let tempChartType = currentChartType;
|
|
24342
|
+
let tempVizMode = currentVizMode;
|
|
24343
|
+
let tempTheme = currentTheme;
|
|
24344
|
+
let tempIdealRange = currentIdealRange;
|
|
24345
|
+
let currentSuggestion = null;
|
|
24346
|
+
const domainCfg = DOMAIN_CONFIG4[config.domain] || DOMAIN_CONFIG4.energy;
|
|
24347
|
+
const primaryColor = config.colors?.primary || domainCfg.color;
|
|
24348
|
+
const domainColors = config.colors?.shoppingColors || domainCfg.colors;
|
|
24349
|
+
const showSettingsButton = config.showSettingsButton ?? true;
|
|
24350
|
+
const showMaximizeButton = config.showMaximizeButton ?? true;
|
|
24351
|
+
const showVizModeTabs = config.showVizModeTabs ?? true;
|
|
24352
|
+
const showChartTypeTabs = config.showChartTypeTabs ?? true;
|
|
24353
|
+
const chartHeight = typeof config.chartHeight === "number" ? `${config.chartHeight}px` : config.chartHeight ?? "300px";
|
|
24354
|
+
function getTitle() {
|
|
24355
|
+
if (config.title) return config.title;
|
|
24356
|
+
const domainName = config.domain === "temperature" ? "Temperatura" : "Consumo";
|
|
24357
|
+
return `${domainName} dos \xFAltimos ${currentPeriod} dias`;
|
|
24358
|
+
}
|
|
24359
|
+
function renderHTML() {
|
|
24360
|
+
return `
|
|
24361
|
+
<div id="${widgetId}" class="myio-chart-widget ${currentTheme === "dark" ? "dark" : ""} ${config.className || ""}">
|
|
24362
|
+
<div class="myio-chart-widget-header">
|
|
24363
|
+
<div class="myio-chart-widget-title-group">
|
|
24364
|
+
${showSettingsButton ? `
|
|
24365
|
+
<button id="${widgetId}-settings-btn" class="myio-chart-widget-btn" title="Configura\xE7\xF5es">\u2699\uFE0F</button>
|
|
24366
|
+
` : ""}
|
|
24367
|
+
<h4 id="${widgetId}-title" class="myio-chart-widget-title">${getTitle()}</h4>
|
|
24368
|
+
</div>
|
|
24369
|
+
<div class="myio-chart-widget-controls">
|
|
24370
|
+
${showVizModeTabs ? `
|
|
24371
|
+
<div class="myio-chart-widget-tabs" id="${widgetId}-viz-tabs">
|
|
24372
|
+
<button class="myio-chart-widget-tab ${currentVizMode === "total" ? "active" : ""}" data-viz="total">Consolidado</button>
|
|
24373
|
+
<button class="myio-chart-widget-tab ${currentVizMode === "separate" ? "active" : ""}" data-viz="separate">Por Shopping</button>
|
|
24374
|
+
</div>
|
|
24375
|
+
` : ""}
|
|
24376
|
+
${showChartTypeTabs ? `
|
|
24377
|
+
<div class="myio-chart-widget-tabs" id="${widgetId}-type-tabs">
|
|
24378
|
+
<button class="myio-chart-widget-tab ${currentChartType === "line" ? "active" : ""}" data-type="line">Linhas</button>
|
|
24379
|
+
<button class="myio-chart-widget-tab ${currentChartType === "bar" ? "active" : ""}" data-type="bar">Barras</button>
|
|
24380
|
+
</div>
|
|
24381
|
+
` : ""}
|
|
24382
|
+
${showMaximizeButton ? `
|
|
24383
|
+
<button id="${widgetId}-maximize-btn" class="myio-chart-widget-btn" title="Maximizar">\u26F6</button>
|
|
24384
|
+
` : ""}
|
|
24385
|
+
</div>
|
|
24386
|
+
</div>
|
|
24387
|
+
<div class="myio-chart-widget-body">
|
|
24388
|
+
<div id="${widgetId}-loading" class="myio-chart-widget-loading" style="display: none;">
|
|
24389
|
+
<div class="myio-chart-widget-spinner"></div>
|
|
24390
|
+
</div>
|
|
24391
|
+
<div class="myio-chart-widget-canvas-container" style="height: ${chartHeight};">
|
|
24392
|
+
<canvas id="${widgetId}-canvas"></canvas>
|
|
24393
|
+
</div>
|
|
24394
|
+
</div>
|
|
24395
|
+
<div class="myio-chart-widget-footer" id="${widgetId}-footer">
|
|
24396
|
+
<div class="myio-chart-widget-stat">
|
|
24397
|
+
<span class="myio-chart-widget-stat-label">Total Per\xEDodo</span>
|
|
24398
|
+
<span id="${widgetId}-stat-total" class="myio-chart-widget-stat-value primary">--</span>
|
|
24399
|
+
</div>
|
|
24400
|
+
<div class="myio-chart-widget-stat">
|
|
24401
|
+
<span class="myio-chart-widget-stat-label">M\xE9dia Di\xE1ria</span>
|
|
24402
|
+
<span id="${widgetId}-stat-avg" class="myio-chart-widget-stat-value">--</span>
|
|
24403
|
+
</div>
|
|
24404
|
+
<div class="myio-chart-widget-stat">
|
|
24405
|
+
<span class="myio-chart-widget-stat-label">Dia de Pico</span>
|
|
24406
|
+
<span id="${widgetId}-stat-peak" class="myio-chart-widget-stat-value">--</span>
|
|
24407
|
+
<span id="${widgetId}-stat-peak-date" class="myio-chart-widget-stat-sub"></span>
|
|
24408
|
+
</div>
|
|
24409
|
+
</div>
|
|
24410
|
+
</div>
|
|
24411
|
+
`;
|
|
24412
|
+
}
|
|
24413
|
+
function renderSettingsModal() {
|
|
24414
|
+
const unit = config.unit ?? "";
|
|
24415
|
+
const isTemperature = config.domain === "temperature";
|
|
24416
|
+
settingsHeaderInstance = createModalHeader({
|
|
24417
|
+
id: `${widgetId}-settings`,
|
|
24418
|
+
title: "Configura\xE7\xF5es",
|
|
24419
|
+
icon: "\u2699\uFE0F",
|
|
24420
|
+
theme: tempTheme,
|
|
24421
|
+
backgroundColor: primaryColor,
|
|
24422
|
+
showThemeToggle: false,
|
|
24423
|
+
showMaximize: false,
|
|
24424
|
+
showClose: true,
|
|
24425
|
+
onClose: () => closeSettingsModal()
|
|
24426
|
+
});
|
|
24427
|
+
return `
|
|
24428
|
+
<div id="${widgetId}-settings-overlay" class="myio-settings-overlay hidden">
|
|
24429
|
+
<div class="myio-settings-card">
|
|
24430
|
+
${settingsHeaderInstance.render()}
|
|
24431
|
+
<div class="myio-settings-body">
|
|
24432
|
+
<!-- CONTEXT 1: Per\xEDodo e Dados -->
|
|
24433
|
+
<div class="myio-settings-context-group">
|
|
24434
|
+
<!-- Per\xEDodo -->
|
|
24435
|
+
<div class="myio-settings-section">
|
|
24436
|
+
<div class="myio-settings-section-label">\u{1F4C5} Per\xEDodo</div>
|
|
24437
|
+
<div class="myio-settings-row">
|
|
24438
|
+
<div class="myio-settings-field" style="flex: 1;">
|
|
24439
|
+
<select id="${widgetId}-settings-period" class="myio-settings-select">
|
|
24440
|
+
<option value="7" ${tempPeriod === 7 ? "selected" : ""}>\xDAltimos 7 dias</option>
|
|
24441
|
+
<option value="14" ${tempPeriod === 14 ? "selected" : ""}>\xDAltimos 14 dias</option>
|
|
24442
|
+
<option value="30" ${tempPeriod === 30 ? "selected" : ""}>\xDAltimos 30 dias</option>
|
|
24443
|
+
<option value="60" ${tempPeriod === 60 ? "selected" : ""}>\xDAltimos 60 dias</option>
|
|
24444
|
+
<option value="90" ${tempPeriod === 90 ? "selected" : ""}>\xDAltimos 90 dias</option>
|
|
24445
|
+
</select>
|
|
24446
|
+
</div>
|
|
24447
|
+
</div>
|
|
24448
|
+
</div>
|
|
24449
|
+
|
|
24450
|
+
<!-- Dados -->
|
|
24451
|
+
<div class="myio-settings-section">
|
|
24452
|
+
<div class="myio-settings-section-label">\u{1F4CA} Dados</div>
|
|
24453
|
+
<div class="myio-settings-row">
|
|
24454
|
+
<!-- Granularity Select -->
|
|
24455
|
+
<div class="myio-settings-field">
|
|
24456
|
+
<label class="myio-settings-field-label">Granularidade</label>
|
|
24457
|
+
<select id="${widgetId}-settings-granularity" class="myio-settings-select">
|
|
24458
|
+
<option value="1d" selected>\u{1F4C6} Por Dia</option>
|
|
24459
|
+
<option value="1h">\u{1F550} Por Hora</option>
|
|
24460
|
+
</select>
|
|
24461
|
+
</div>
|
|
24462
|
+
|
|
24463
|
+
<!-- Weekday Filter -->
|
|
24464
|
+
<div class="myio-settings-field">
|
|
24465
|
+
<label class="myio-settings-field-label">Dias da Semana</label>
|
|
24466
|
+
<div class="myio-settings-dropdown-container">
|
|
24467
|
+
<button type="button" id="${widgetId}-settings-weekday-btn" class="myio-settings-dropdown-btn">
|
|
24468
|
+
<span id="${widgetId}-settings-weekday-label">Todos os dias</span>
|
|
24469
|
+
<span class="myio-settings-dropdown-arrow">\u25BC</span>
|
|
24470
|
+
</button>
|
|
24471
|
+
<div id="${widgetId}-settings-weekday-dropdown" class="myio-settings-dropdown hidden">
|
|
24472
|
+
<label class="myio-settings-dropdown-option">
|
|
24473
|
+
<input type="checkbox" name="${widgetId}-weekday" value="dom" checked /> Domingo
|
|
24474
|
+
</label>
|
|
24475
|
+
<label class="myio-settings-dropdown-option">
|
|
24476
|
+
<input type="checkbox" name="${widgetId}-weekday" value="seg" checked /> Segunda-feira
|
|
24477
|
+
</label>
|
|
24478
|
+
<label class="myio-settings-dropdown-option">
|
|
24479
|
+
<input type="checkbox" name="${widgetId}-weekday" value="ter" checked /> Ter\xE7a-feira
|
|
24480
|
+
</label>
|
|
24481
|
+
<label class="myio-settings-dropdown-option">
|
|
24482
|
+
<input type="checkbox" name="${widgetId}-weekday" value="qua" checked /> Quarta-feira
|
|
24483
|
+
</label>
|
|
24484
|
+
<label class="myio-settings-dropdown-option">
|
|
24485
|
+
<input type="checkbox" name="${widgetId}-weekday" value="qui" checked /> Quinta-feira
|
|
24486
|
+
</label>
|
|
24487
|
+
<label class="myio-settings-dropdown-option">
|
|
24488
|
+
<input type="checkbox" name="${widgetId}-weekday" value="sex" checked /> Sexta-feira
|
|
24489
|
+
</label>
|
|
24490
|
+
<label class="myio-settings-dropdown-option">
|
|
24491
|
+
<input type="checkbox" name="${widgetId}-weekday" value="sab" checked /> S\xE1bado
|
|
24492
|
+
</label>
|
|
24493
|
+
<div class="myio-settings-dropdown-actions">
|
|
24494
|
+
<button type="button" id="${widgetId}-settings-weekday-all">Selecionar Todos</button>
|
|
24495
|
+
<button type="button" id="${widgetId}-settings-weekday-clear">Limpar</button>
|
|
24496
|
+
</div>
|
|
24497
|
+
</div>
|
|
24498
|
+
</div>
|
|
24499
|
+
</div>
|
|
24500
|
+
|
|
24501
|
+
<!-- Day Period Filter (only visible when hourly) -->
|
|
24502
|
+
<div class="myio-settings-field" id="${widgetId}-settings-dayperiod-field" style="display: none;">
|
|
24503
|
+
<label class="myio-settings-field-label">Per\xEDodos do Dia</label>
|
|
24504
|
+
<div class="myio-settings-dropdown-container">
|
|
24505
|
+
<button type="button" id="${widgetId}-settings-dayperiod-btn" class="myio-settings-dropdown-btn">
|
|
24506
|
+
<span id="${widgetId}-settings-dayperiod-label">Todos os per\xEDodos</span>
|
|
24507
|
+
<span class="myio-settings-dropdown-arrow">\u25BC</span>
|
|
24508
|
+
</button>
|
|
24509
|
+
<div id="${widgetId}-settings-dayperiod-dropdown" class="myio-settings-dropdown hidden">
|
|
24510
|
+
<label class="myio-settings-dropdown-option">
|
|
24511
|
+
<input type="checkbox" name="${widgetId}-dayperiod" value="madrugada" checked /> Madrugada (00h-06h)
|
|
24512
|
+
</label>
|
|
24513
|
+
<label class="myio-settings-dropdown-option">
|
|
24514
|
+
<input type="checkbox" name="${widgetId}-dayperiod" value="manha" checked /> Manh\xE3 (06h-12h)
|
|
24515
|
+
</label>
|
|
24516
|
+
<label class="myio-settings-dropdown-option">
|
|
24517
|
+
<input type="checkbox" name="${widgetId}-dayperiod" value="tarde" checked /> Tarde (12h-18h)
|
|
24518
|
+
</label>
|
|
24519
|
+
<label class="myio-settings-dropdown-option">
|
|
24520
|
+
<input type="checkbox" name="${widgetId}-dayperiod" value="noite" checked /> Noite (18h-24h)
|
|
24521
|
+
</label>
|
|
24522
|
+
<div class="myio-settings-dropdown-actions">
|
|
24523
|
+
<button type="button" id="${widgetId}-settings-dayperiod-all">Selecionar Todos</button>
|
|
24524
|
+
<button type="button" id="${widgetId}-settings-dayperiod-clear">Limpar</button>
|
|
24525
|
+
</div>
|
|
24526
|
+
</div>
|
|
24527
|
+
</div>
|
|
24528
|
+
</div>
|
|
24529
|
+
</div>
|
|
24530
|
+
</div>
|
|
24531
|
+
</div>
|
|
24532
|
+
|
|
24533
|
+
<!-- CONTEXT 2: Faixa Ideal -->
|
|
24534
|
+
<div class="myio-settings-context-group">
|
|
24535
|
+
<div class="myio-settings-section">
|
|
24536
|
+
<div class="myio-settings-section-label">
|
|
24537
|
+
\u{1F3AF} Faixa Ideal
|
|
24538
|
+
<span class="myio-settings-hint" id="${widgetId}-settings-range-hint">(opcional - deixe zerado para n\xE3o exibir)</span>
|
|
24539
|
+
<span
|
|
24540
|
+
id="${widgetId}-settings-suggestion"
|
|
24541
|
+
title=""
|
|
24542
|
+
style="cursor: pointer; font-size: 16px; opacity: 0.7; transition: opacity 0.2s; margin-left: 4px;"
|
|
24543
|
+
>\u{1F4A1}</span>
|
|
24544
|
+
</div>
|
|
24545
|
+
<div class="myio-settings-row">
|
|
24546
|
+
<div class="myio-settings-field" style="min-width: 100px;">
|
|
24547
|
+
<label class="myio-settings-field-label">M\xEDnimo (${unit})</label>
|
|
24548
|
+
<input type="number" id="${widgetId}-settings-range-min" class="myio-settings-input"
|
|
24549
|
+
value="${tempIdealRange?.min ?? ""}" placeholder="0" step="0.1">
|
|
24550
|
+
</div>
|
|
24551
|
+
<div class="myio-settings-field" style="min-width: 100px;">
|
|
24552
|
+
<label class="myio-settings-field-label">M\xE1ximo (${unit})</label>
|
|
24553
|
+
<input type="number" id="${widgetId}-settings-range-max" class="myio-settings-input"
|
|
24554
|
+
value="${tempIdealRange?.max ?? ""}" placeholder="0" step="0.1">
|
|
24555
|
+
</div>
|
|
24556
|
+
<div class="myio-settings-field" style="flex: 1;">
|
|
24557
|
+
<label class="myio-settings-field-label">R\xF3tulo</label>
|
|
24558
|
+
<input type="text" id="${widgetId}-settings-range-label" class="myio-settings-input"
|
|
24559
|
+
value="${tempIdealRange?.label ?? ""}" placeholder="${isTemperature ? "Faixa Ideal" : "Meta de Consumo"}">
|
|
24560
|
+
</div>
|
|
24561
|
+
</div>
|
|
24562
|
+
</div>
|
|
24563
|
+
</div>
|
|
24564
|
+
|
|
24565
|
+
<!-- CONTEXT 3: Visualiza\xE7\xE3o -->
|
|
24566
|
+
<div class="myio-settings-context-group">
|
|
24567
|
+
<div class="myio-settings-section">
|
|
24568
|
+
<div class="myio-settings-section-label">\u{1F3A8} Visualiza\xE7\xE3o</div>
|
|
24569
|
+
<div class="myio-settings-row" style="gap: 20px; flex-wrap: wrap;">
|
|
24570
|
+
<!-- Chart Type -->
|
|
24571
|
+
<div class="myio-settings-field" style="flex: 1; min-width: 180px;">
|
|
24572
|
+
<label class="myio-settings-field-label">Tipo de Gr\xE1fico</label>
|
|
24573
|
+
<div class="myio-settings-tabs" id="${widgetId}-settings-chart-type">
|
|
24574
|
+
<button class="myio-settings-tab ${tempChartType === "line" ? "active" : ""}" data-type="line">\u{1F4C8} Linhas</button>
|
|
24575
|
+
<button class="myio-settings-tab ${tempChartType === "bar" ? "active" : ""}" data-type="bar">\u{1F4CA} Barras</button>
|
|
24576
|
+
</div>
|
|
24577
|
+
</div>
|
|
24578
|
+
|
|
24579
|
+
<!-- Viz Mode -->
|
|
24580
|
+
<div class="myio-settings-field" style="flex: 1; min-width: 200px;">
|
|
24581
|
+
<label class="myio-settings-field-label">Agrupamento</label>
|
|
24582
|
+
<div class="myio-settings-tabs" id="${widgetId}-settings-viz-mode">
|
|
24583
|
+
<button class="myio-settings-tab ${tempVizMode === "total" ? "active" : ""}" data-viz="total">\u{1F517} Consolidado</button>
|
|
24584
|
+
<button class="myio-settings-tab ${tempVizMode === "separate" ? "active" : ""}" data-viz="separate">\u{1F3EC} Por Shopping</button>
|
|
24585
|
+
</div>
|
|
24586
|
+
</div>
|
|
24587
|
+
|
|
24588
|
+
<!-- Theme -->
|
|
24589
|
+
<div class="myio-settings-field" style="flex: 1; min-width: 160px;">
|
|
24590
|
+
<label class="myio-settings-field-label">Tema</label>
|
|
24591
|
+
<div class="myio-settings-tabs" id="${widgetId}-settings-theme">
|
|
24592
|
+
<button class="myio-settings-tab ${tempTheme === "light" ? "active" : ""}" data-theme="light">\u2600\uFE0F Light</button>
|
|
24593
|
+
<button class="myio-settings-tab ${tempTheme === "dark" ? "active" : ""}" data-theme="dark">\u{1F319} Dark</button>
|
|
24594
|
+
</div>
|
|
24595
|
+
</div>
|
|
24596
|
+
</div>
|
|
24597
|
+
</div>
|
|
24598
|
+
</div>
|
|
24599
|
+
</div>
|
|
24600
|
+
<div class="myio-settings-footer">
|
|
24601
|
+
<button id="${widgetId}-settings-reset" class="myio-settings-btn myio-settings-btn-secondary">Resetar</button>
|
|
24602
|
+
<button id="${widgetId}-settings-apply" class="myio-settings-btn myio-settings-btn-primary">Carregar</button>
|
|
24603
|
+
</div>
|
|
24604
|
+
</div>
|
|
24605
|
+
</div>
|
|
24606
|
+
`;
|
|
24607
|
+
}
|
|
24608
|
+
function injectStyles() {
|
|
24609
|
+
if (styleElement) return;
|
|
24610
|
+
styleElement = document.createElement("style");
|
|
24611
|
+
styleElement.id = `${widgetId}-styles`;
|
|
24612
|
+
styleElement.textContent = getWidgetStyles(currentTheme, primaryColor);
|
|
24613
|
+
document.head.appendChild(styleElement);
|
|
24614
|
+
}
|
|
24615
|
+
function updateStyles() {
|
|
24616
|
+
if (styleElement) {
|
|
24617
|
+
styleElement.textContent = getWidgetStyles(currentTheme, primaryColor);
|
|
24618
|
+
}
|
|
24619
|
+
}
|
|
24620
|
+
function setupListeners() {
|
|
24621
|
+
if (showSettingsButton) {
|
|
24622
|
+
document.getElementById(`${widgetId}-settings-btn`)?.addEventListener("click", () => {
|
|
24623
|
+
openSettingsModal();
|
|
24624
|
+
config.onSettingsClick?.();
|
|
24625
|
+
});
|
|
24626
|
+
}
|
|
24627
|
+
if (showMaximizeButton && config.onMaximizeClick) {
|
|
24628
|
+
document.getElementById(`${widgetId}-maximize-btn`)?.addEventListener("click", () => {
|
|
24629
|
+
config.onMaximizeClick?.();
|
|
24630
|
+
});
|
|
24631
|
+
}
|
|
24632
|
+
if (showVizModeTabs) {
|
|
24633
|
+
document.getElementById(`${widgetId}-viz-tabs`)?.addEventListener("click", (e) => {
|
|
24634
|
+
const target = e.target;
|
|
24635
|
+
if (target.classList.contains("myio-chart-widget-tab")) {
|
|
24636
|
+
const mode = target.dataset.viz;
|
|
24637
|
+
if (mode) {
|
|
24638
|
+
instance.setVizMode(mode);
|
|
24639
|
+
}
|
|
24640
|
+
}
|
|
24641
|
+
});
|
|
24642
|
+
}
|
|
24643
|
+
if (showChartTypeTabs) {
|
|
24644
|
+
document.getElementById(`${widgetId}-type-tabs`)?.addEventListener("click", (e) => {
|
|
24645
|
+
const target = e.target;
|
|
24646
|
+
if (target.classList.contains("myio-chart-widget-tab")) {
|
|
24647
|
+
const type = target.dataset.type;
|
|
24648
|
+
if (type) {
|
|
24649
|
+
instance.setChartType(type);
|
|
24650
|
+
}
|
|
24651
|
+
}
|
|
24652
|
+
});
|
|
24653
|
+
}
|
|
24654
|
+
}
|
|
24655
|
+
function updateTabStates() {
|
|
24656
|
+
document.querySelectorAll(`#${widgetId}-viz-tabs .myio-chart-widget-tab`).forEach((tab) => {
|
|
24657
|
+
const btn = tab;
|
|
24658
|
+
btn.classList.toggle("active", btn.dataset.viz === currentVizMode);
|
|
24659
|
+
});
|
|
24660
|
+
document.querySelectorAll(`#${widgetId}-type-tabs .myio-chart-widget-tab`).forEach((tab) => {
|
|
24661
|
+
const btn = tab;
|
|
24662
|
+
btn.classList.toggle("active", btn.dataset.type === currentChartType);
|
|
24663
|
+
});
|
|
24664
|
+
}
|
|
24665
|
+
function updateTitle() {
|
|
24666
|
+
const titleEl = document.getElementById(`${widgetId}-title`);
|
|
24667
|
+
if (titleEl) {
|
|
24668
|
+
titleEl.textContent = getTitle();
|
|
24669
|
+
}
|
|
24670
|
+
}
|
|
24671
|
+
function setLoading(loading) {
|
|
24672
|
+
isLoading = loading;
|
|
24673
|
+
const loadingEl = document.getElementById(`${widgetId}-loading`);
|
|
24674
|
+
if (loadingEl) {
|
|
24675
|
+
loadingEl.style.display = loading ? "flex" : "none";
|
|
24676
|
+
}
|
|
24677
|
+
}
|
|
24678
|
+
function formatValue(value) {
|
|
24679
|
+
const unit = config.unit ?? "";
|
|
24680
|
+
const unitLarge = config.unitLarge;
|
|
24681
|
+
const threshold = config.thresholdForLargeUnit ?? 1e3;
|
|
24682
|
+
if (unitLarge && Math.abs(value) >= threshold) {
|
|
24683
|
+
return `${(value / threshold).toFixed(2)} ${unitLarge}`;
|
|
24684
|
+
}
|
|
24685
|
+
return `${value.toFixed(2)} ${unit}`;
|
|
24686
|
+
}
|
|
24687
|
+
function updateFooterStats(data) {
|
|
24688
|
+
const totalEl = document.getElementById(`${widgetId}-stat-total`);
|
|
24689
|
+
const avgEl = document.getElementById(`${widgetId}-stat-avg`);
|
|
24690
|
+
const peakEl = document.getElementById(`${widgetId}-stat-peak`);
|
|
24691
|
+
const peakDateEl = document.getElementById(`${widgetId}-stat-peak-date`);
|
|
24692
|
+
if (!data.dailyTotals || data.dailyTotals.length === 0) {
|
|
24693
|
+
if (totalEl) totalEl.textContent = "--";
|
|
24694
|
+
if (avgEl) avgEl.textContent = "--";
|
|
24695
|
+
if (peakEl) peakEl.textContent = "--";
|
|
24696
|
+
if (peakDateEl) peakDateEl.textContent = "";
|
|
24697
|
+
return;
|
|
24698
|
+
}
|
|
24699
|
+
const isTemperature = config.domain === "temperature";
|
|
24700
|
+
const totals = data.dailyTotals;
|
|
24701
|
+
const labels = data.labels ?? [];
|
|
24702
|
+
const total = totals.reduce((a, b) => a + b, 0);
|
|
24703
|
+
const avg = total / totals.length;
|
|
24704
|
+
const peakValue = Math.max(...totals);
|
|
24705
|
+
const peakIndex = totals.indexOf(peakValue);
|
|
24706
|
+
const peakDate = labels[peakIndex] ?? "";
|
|
24707
|
+
if (totalEl) {
|
|
24708
|
+
if (isTemperature) {
|
|
24709
|
+
totalEl.textContent = formatValue(avg);
|
|
24710
|
+
const labelEl = totalEl.previousElementSibling;
|
|
24711
|
+
if (labelEl) labelEl.textContent = "M\xE9dia Per\xEDodo";
|
|
24712
|
+
} else {
|
|
24713
|
+
totalEl.textContent = formatValue(total);
|
|
24714
|
+
}
|
|
24715
|
+
}
|
|
24716
|
+
if (avgEl) {
|
|
24717
|
+
avgEl.textContent = formatValue(avg);
|
|
24718
|
+
}
|
|
24719
|
+
if (peakEl) {
|
|
24720
|
+
peakEl.textContent = formatValue(peakValue);
|
|
24721
|
+
}
|
|
24722
|
+
if (peakDateEl) {
|
|
24723
|
+
peakDateEl.textContent = peakDate;
|
|
24724
|
+
}
|
|
24725
|
+
}
|
|
24726
|
+
function openSettingsModal() {
|
|
24727
|
+
tempPeriod = currentPeriod;
|
|
24728
|
+
tempChartType = currentChartType;
|
|
24729
|
+
tempVizMode = currentVizMode;
|
|
24730
|
+
tempTheme = currentTheme;
|
|
24731
|
+
tempIdealRange = currentIdealRange ? { ...currentIdealRange } : null;
|
|
24732
|
+
if (!settingsModalElement) {
|
|
24733
|
+
settingsModalElement = document.createElement("div");
|
|
24734
|
+
settingsModalElement.innerHTML = renderSettingsModal();
|
|
24735
|
+
document.body.appendChild(settingsModalElement.firstElementChild);
|
|
24736
|
+
settingsModalElement = document.getElementById(`${widgetId}-settings-overlay`);
|
|
24737
|
+
setupSettingsModalListeners();
|
|
24738
|
+
}
|
|
24739
|
+
updateSettingsModalValues();
|
|
24740
|
+
updateIdealRangeSuggestionTooltip();
|
|
24741
|
+
settingsModalElement?.classList.remove("hidden");
|
|
24742
|
+
}
|
|
24743
|
+
function closeSettingsModal() {
|
|
24744
|
+
settingsModalElement?.classList.add("hidden");
|
|
24745
|
+
}
|
|
24746
|
+
function updateSettingsModalValues() {
|
|
24747
|
+
const periodSelect = document.getElementById(`${widgetId}-settings-period`);
|
|
24748
|
+
if (periodSelect) periodSelect.value = String(tempPeriod);
|
|
24749
|
+
const minInput = document.getElementById(`${widgetId}-settings-range-min`);
|
|
24750
|
+
const maxInput = document.getElementById(`${widgetId}-settings-range-max`);
|
|
24751
|
+
const labelInput = document.getElementById(`${widgetId}-settings-range-label`);
|
|
24752
|
+
if (minInput) minInput.value = tempIdealRange?.min?.toString() ?? "";
|
|
24753
|
+
if (maxInput) maxInput.value = tempIdealRange?.max?.toString() ?? "";
|
|
24754
|
+
if (labelInput) labelInput.value = tempIdealRange?.label ?? "";
|
|
24755
|
+
updateSettingsModalTabs();
|
|
24756
|
+
}
|
|
24757
|
+
function updateSettingsModalTabs() {
|
|
24758
|
+
document.querySelectorAll(`#${widgetId}-settings-chart-type .myio-settings-tab`).forEach((tab) => {
|
|
24759
|
+
const btn = tab;
|
|
24760
|
+
btn.classList.toggle("active", btn.dataset.type === tempChartType);
|
|
24761
|
+
});
|
|
24762
|
+
document.querySelectorAll(`#${widgetId}-settings-viz-mode .myio-settings-tab`).forEach((tab) => {
|
|
24763
|
+
const btn = tab;
|
|
24764
|
+
btn.classList.toggle("active", btn.dataset.viz === tempVizMode);
|
|
24765
|
+
});
|
|
24766
|
+
document.querySelectorAll(`#${widgetId}-settings-theme .myio-settings-tab`).forEach((tab) => {
|
|
24767
|
+
const btn = tab;
|
|
24768
|
+
btn.classList.toggle("active", btn.dataset.theme === tempTheme);
|
|
24769
|
+
});
|
|
24770
|
+
}
|
|
24771
|
+
function updateWeekdayLabel() {
|
|
24772
|
+
const checkboxes = document.querySelectorAll(`input[name="${widgetId}-weekday"]`);
|
|
24773
|
+
const checked = Array.from(checkboxes).filter((cb) => cb.checked);
|
|
24774
|
+
const label = document.getElementById(`${widgetId}-settings-weekday-label`);
|
|
24775
|
+
if (label) {
|
|
24776
|
+
if (checked.length === 0) {
|
|
24777
|
+
label.textContent = "Nenhum dia";
|
|
24778
|
+
} else if (checked.length === checkboxes.length) {
|
|
24779
|
+
label.textContent = "Todos os dias";
|
|
24780
|
+
} else {
|
|
24781
|
+
label.textContent = `${checked.length} dias selecionados`;
|
|
24782
|
+
}
|
|
24783
|
+
}
|
|
24784
|
+
}
|
|
24785
|
+
function updateDayPeriodLabel() {
|
|
24786
|
+
const checkboxes = document.querySelectorAll(`input[name="${widgetId}-dayperiod"]`);
|
|
24787
|
+
const checked = Array.from(checkboxes).filter((cb) => cb.checked);
|
|
24788
|
+
const label = document.getElementById(`${widgetId}-settings-dayperiod-label`);
|
|
24789
|
+
if (label) {
|
|
24790
|
+
if (checked.length === 0) {
|
|
24791
|
+
label.textContent = "Nenhum per\xEDodo";
|
|
24792
|
+
} else if (checked.length === checkboxes.length) {
|
|
24793
|
+
label.textContent = "Todos os per\xEDodos";
|
|
24794
|
+
} else {
|
|
24795
|
+
label.textContent = `${checked.length} per\xEDodos selecionados`;
|
|
24796
|
+
}
|
|
24797
|
+
}
|
|
24798
|
+
}
|
|
24799
|
+
function calculateIdealRangeSuggestion() {
|
|
24800
|
+
const data = chartInstance?.getCachedData();
|
|
24801
|
+
if (!data || !data.dailyTotals || data.dailyTotals.length === 0) {
|
|
24802
|
+
return { min: 0, max: 0, avg: 0 };
|
|
24803
|
+
}
|
|
24804
|
+
const total = data.dailyTotals.reduce((a, b) => a + b, 0);
|
|
24805
|
+
const avg = total / data.dailyTotals.length;
|
|
24806
|
+
const min = avg * 0.85;
|
|
24807
|
+
const max = avg * 1.15;
|
|
24808
|
+
return {
|
|
24809
|
+
min: Math.round(min * 10) / 10,
|
|
24810
|
+
max: Math.round(max * 10) / 10,
|
|
24811
|
+
avg: Math.round(avg * 10) / 10
|
|
24812
|
+
};
|
|
24813
|
+
}
|
|
24814
|
+
function updateIdealRangeSuggestionTooltip() {
|
|
24815
|
+
const suggestion = calculateIdealRangeSuggestion();
|
|
24816
|
+
const suggestionEl = document.getElementById(`${widgetId}-settings-suggestion`);
|
|
24817
|
+
const hintEl = document.getElementById(`${widgetId}-settings-range-hint`);
|
|
24818
|
+
const unit = config.unit ?? "";
|
|
24819
|
+
const isTemperature = config.domain === "temperature";
|
|
24820
|
+
currentSuggestion = suggestion;
|
|
24821
|
+
if (hintEl) {
|
|
24822
|
+
if (isTemperature) {
|
|
24823
|
+
hintEl.textContent = "(valores carregados do cliente)";
|
|
24824
|
+
} else {
|
|
24825
|
+
hintEl.textContent = "(opcional - deixe zerado para n\xE3o exibir)";
|
|
24826
|
+
}
|
|
24827
|
+
}
|
|
24828
|
+
if (suggestionEl) {
|
|
24829
|
+
if (suggestion.avg > 0) {
|
|
24830
|
+
const tooltipText = `Sugest\xE3o: ${suggestion.min} - ${suggestion.max} ${unit} (m\xE9dia \xB115%). Clique para aplicar.`;
|
|
24831
|
+
suggestionEl.title = tooltipText;
|
|
24832
|
+
suggestionEl.style.display = "inline";
|
|
24833
|
+
} else {
|
|
24834
|
+
suggestionEl.style.display = "none";
|
|
24835
|
+
}
|
|
24836
|
+
}
|
|
24837
|
+
}
|
|
24838
|
+
function applyIdealRangeSuggestion() {
|
|
24839
|
+
if (!currentSuggestion || currentSuggestion.min === 0 && currentSuggestion.max === 0) {
|
|
24840
|
+
return;
|
|
24841
|
+
}
|
|
24842
|
+
const minInput = document.getElementById(`${widgetId}-settings-range-min`);
|
|
24843
|
+
const maxInput = document.getElementById(`${widgetId}-settings-range-max`);
|
|
24844
|
+
const labelInput = document.getElementById(`${widgetId}-settings-range-label`);
|
|
24845
|
+
if (minInput) minInput.value = String(currentSuggestion.min);
|
|
24846
|
+
if (maxInput) maxInput.value = String(currentSuggestion.max);
|
|
24847
|
+
if (labelInput) labelInput.value = "Faixa Sugerida";
|
|
24848
|
+
tempIdealRange = {
|
|
24849
|
+
min: currentSuggestion.min,
|
|
24850
|
+
max: currentSuggestion.max,
|
|
24851
|
+
label: "Faixa Sugerida"
|
|
24852
|
+
};
|
|
24853
|
+
}
|
|
24854
|
+
function setupSettingsModalListeners() {
|
|
24855
|
+
settingsHeaderInstance?.attachListeners();
|
|
24856
|
+
document.getElementById(`${widgetId}-settings-overlay`)?.addEventListener("click", (e) => {
|
|
24857
|
+
if (e.target.classList.contains("myio-settings-overlay")) {
|
|
24858
|
+
closeSettingsModal();
|
|
24859
|
+
}
|
|
24860
|
+
});
|
|
24861
|
+
document.getElementById(`${widgetId}-settings-granularity`)?.addEventListener("change", (e) => {
|
|
24862
|
+
const select = e.target;
|
|
24863
|
+
const dayPeriodField = document.getElementById(`${widgetId}-settings-dayperiod-field`);
|
|
24864
|
+
if (dayPeriodField) {
|
|
24865
|
+
dayPeriodField.style.display = select.value === "1h" ? "block" : "none";
|
|
24866
|
+
}
|
|
24867
|
+
});
|
|
24868
|
+
document.getElementById(`${widgetId}-settings-suggestion`)?.addEventListener("click", () => {
|
|
24869
|
+
applyIdealRangeSuggestion();
|
|
24870
|
+
});
|
|
24871
|
+
document.getElementById(`${widgetId}-settings-weekday-btn`)?.addEventListener("click", (e) => {
|
|
24872
|
+
e.stopPropagation();
|
|
24873
|
+
const dropdown = document.getElementById(`${widgetId}-settings-weekday-dropdown`);
|
|
24874
|
+
dropdown?.classList.toggle("hidden");
|
|
24875
|
+
});
|
|
24876
|
+
document.querySelectorAll(`input[name="${widgetId}-weekday"]`).forEach((cb) => {
|
|
24877
|
+
cb.addEventListener("change", updateWeekdayLabel);
|
|
24878
|
+
});
|
|
24879
|
+
document.getElementById(`${widgetId}-settings-weekday-all`)?.addEventListener("click", () => {
|
|
24880
|
+
document.querySelectorAll(`input[name="${widgetId}-weekday"]`).forEach((cb) => {
|
|
24881
|
+
cb.checked = true;
|
|
24882
|
+
});
|
|
24883
|
+
updateWeekdayLabel();
|
|
24884
|
+
});
|
|
24885
|
+
document.getElementById(`${widgetId}-settings-weekday-clear`)?.addEventListener("click", () => {
|
|
24886
|
+
document.querySelectorAll(`input[name="${widgetId}-weekday"]`).forEach((cb) => {
|
|
24887
|
+
cb.checked = false;
|
|
24888
|
+
});
|
|
24889
|
+
updateWeekdayLabel();
|
|
24890
|
+
});
|
|
24891
|
+
document.getElementById(`${widgetId}-settings-dayperiod-btn`)?.addEventListener("click", (e) => {
|
|
24892
|
+
e.stopPropagation();
|
|
24893
|
+
const dropdown = document.getElementById(`${widgetId}-settings-dayperiod-dropdown`);
|
|
24894
|
+
dropdown?.classList.toggle("hidden");
|
|
24895
|
+
});
|
|
24896
|
+
document.querySelectorAll(`input[name="${widgetId}-dayperiod"]`).forEach((cb) => {
|
|
24897
|
+
cb.addEventListener("change", updateDayPeriodLabel);
|
|
24898
|
+
});
|
|
24899
|
+
document.getElementById(`${widgetId}-settings-dayperiod-all`)?.addEventListener("click", () => {
|
|
24900
|
+
document.querySelectorAll(`input[name="${widgetId}-dayperiod"]`).forEach((cb) => {
|
|
24901
|
+
cb.checked = true;
|
|
24902
|
+
});
|
|
24903
|
+
updateDayPeriodLabel();
|
|
24904
|
+
});
|
|
24905
|
+
document.getElementById(`${widgetId}-settings-dayperiod-clear`)?.addEventListener("click", () => {
|
|
24906
|
+
document.querySelectorAll(`input[name="${widgetId}-dayperiod"]`).forEach((cb) => {
|
|
24907
|
+
cb.checked = false;
|
|
24908
|
+
});
|
|
24909
|
+
updateDayPeriodLabel();
|
|
24910
|
+
});
|
|
24911
|
+
document.addEventListener("click", (e) => {
|
|
24912
|
+
const target = e.target;
|
|
24913
|
+
const weekdayDropdown = document.getElementById(`${widgetId}-settings-weekday-dropdown`);
|
|
24914
|
+
const dayperiodDropdown = document.getElementById(`${widgetId}-settings-dayperiod-dropdown`);
|
|
24915
|
+
if (weekdayDropdown && !target.closest(`#${widgetId}-settings-weekday-btn`) && !target.closest(`#${widgetId}-settings-weekday-dropdown`)) {
|
|
24916
|
+
weekdayDropdown.classList.add("hidden");
|
|
24917
|
+
}
|
|
24918
|
+
if (dayperiodDropdown && !target.closest(`#${widgetId}-settings-dayperiod-btn`) && !target.closest(`#${widgetId}-settings-dayperiod-dropdown`)) {
|
|
24919
|
+
dayperiodDropdown.classList.add("hidden");
|
|
24920
|
+
}
|
|
24921
|
+
});
|
|
24922
|
+
document.getElementById(`${widgetId}-settings-chart-type`)?.addEventListener("click", (e) => {
|
|
24923
|
+
const target = e.target;
|
|
24924
|
+
if (target.classList.contains("myio-settings-tab")) {
|
|
24925
|
+
tempChartType = target.dataset.type;
|
|
24926
|
+
updateSettingsModalTabs();
|
|
24927
|
+
}
|
|
24928
|
+
});
|
|
24929
|
+
document.getElementById(`${widgetId}-settings-viz-mode`)?.addEventListener("click", (e) => {
|
|
24930
|
+
const target = e.target;
|
|
24931
|
+
if (target.classList.contains("myio-settings-tab")) {
|
|
24932
|
+
tempVizMode = target.dataset.viz;
|
|
24933
|
+
updateSettingsModalTabs();
|
|
24934
|
+
}
|
|
24935
|
+
});
|
|
24936
|
+
document.getElementById(`${widgetId}-settings-theme`)?.addEventListener("click", (e) => {
|
|
24937
|
+
const target = e.target;
|
|
24938
|
+
if (target.classList.contains("myio-settings-tab")) {
|
|
24939
|
+
tempTheme = target.dataset.theme;
|
|
24940
|
+
updateSettingsModalTabs();
|
|
24941
|
+
}
|
|
24942
|
+
});
|
|
24943
|
+
document.getElementById(`${widgetId}-settings-reset`)?.addEventListener("click", () => {
|
|
24944
|
+
tempPeriod = config.defaultPeriod ?? 7;
|
|
24945
|
+
tempChartType = config.defaultChartType ?? "line";
|
|
24946
|
+
tempVizMode = config.defaultVizMode ?? "total";
|
|
24947
|
+
tempTheme = config.theme ?? "light";
|
|
24948
|
+
tempIdealRange = config.idealRange ?? null;
|
|
24949
|
+
const granularitySelect = document.getElementById(`${widgetId}-settings-granularity`);
|
|
24950
|
+
if (granularitySelect) granularitySelect.value = "1d";
|
|
24951
|
+
const dayPeriodField = document.getElementById(`${widgetId}-settings-dayperiod-field`);
|
|
24952
|
+
if (dayPeriodField) dayPeriodField.style.display = "none";
|
|
24953
|
+
document.querySelectorAll(`input[name="${widgetId}-weekday"]`).forEach((cb) => {
|
|
24954
|
+
cb.checked = true;
|
|
24955
|
+
});
|
|
24956
|
+
updateWeekdayLabel();
|
|
24957
|
+
document.querySelectorAll(`input[name="${widgetId}-dayperiod"]`).forEach((cb) => {
|
|
24958
|
+
cb.checked = true;
|
|
24959
|
+
});
|
|
24960
|
+
updateDayPeriodLabel();
|
|
24961
|
+
updateSettingsModalValues();
|
|
24962
|
+
});
|
|
24963
|
+
document.getElementById(`${widgetId}-settings-apply`)?.addEventListener("click", async () => {
|
|
24964
|
+
const minInput = document.getElementById(`${widgetId}-settings-range-min`);
|
|
24965
|
+
const maxInput = document.getElementById(`${widgetId}-settings-range-max`);
|
|
24966
|
+
const labelInput = document.getElementById(`${widgetId}-settings-range-label`);
|
|
24967
|
+
const periodSelect = document.getElementById(`${widgetId}-settings-period`);
|
|
24968
|
+
const min = parseFloat(minInput?.value || "0");
|
|
24969
|
+
const max = parseFloat(maxInput?.value || "0");
|
|
24970
|
+
const label = labelInput?.value || "";
|
|
24971
|
+
tempPeriod = parseInt(periodSelect?.value || "7", 10);
|
|
24972
|
+
if (min > 0 || max > 0) {
|
|
24973
|
+
tempIdealRange = { min, max, label };
|
|
24974
|
+
} else {
|
|
24975
|
+
tempIdealRange = null;
|
|
24976
|
+
}
|
|
24977
|
+
closeSettingsModal();
|
|
24978
|
+
if (tempTheme !== currentTheme) {
|
|
24979
|
+
instance.setTheme(tempTheme);
|
|
24980
|
+
}
|
|
24981
|
+
if (tempChartType !== currentChartType) {
|
|
24982
|
+
instance.setChartType(tempChartType);
|
|
24983
|
+
}
|
|
24984
|
+
if (tempVizMode !== currentVizMode) {
|
|
24985
|
+
instance.setVizMode(tempVizMode);
|
|
24986
|
+
}
|
|
24987
|
+
if (JSON.stringify(tempIdealRange) !== JSON.stringify(currentIdealRange)) {
|
|
24988
|
+
instance.setIdealRange(tempIdealRange);
|
|
24989
|
+
}
|
|
24990
|
+
if (tempPeriod !== currentPeriod) {
|
|
24991
|
+
await instance.setPeriod(tempPeriod);
|
|
24992
|
+
}
|
|
24993
|
+
});
|
|
24994
|
+
}
|
|
24995
|
+
const instance = {
|
|
24996
|
+
async render() {
|
|
24997
|
+
containerElement = document.getElementById(config.containerId);
|
|
24998
|
+
if (!containerElement) {
|
|
24999
|
+
console.error(`[ConsumptionWidget] Container #${config.containerId} not found`);
|
|
25000
|
+
return;
|
|
25001
|
+
}
|
|
25002
|
+
injectStyles();
|
|
25003
|
+
containerElement.innerHTML = renderHTML();
|
|
25004
|
+
setupListeners();
|
|
25005
|
+
setLoading(true);
|
|
25006
|
+
chartInstance = createConsumption7DaysChart({
|
|
25007
|
+
...config,
|
|
25008
|
+
containerId: `${widgetId}-canvas`,
|
|
25009
|
+
theme: currentTheme,
|
|
25010
|
+
defaultChartType: currentChartType,
|
|
25011
|
+
defaultVizMode: currentVizMode,
|
|
25012
|
+
defaultPeriod: currentPeriod,
|
|
25013
|
+
idealRange: currentIdealRange,
|
|
25014
|
+
colors: {
|
|
25015
|
+
primary: primaryColor,
|
|
25016
|
+
background: `${primaryColor}20`,
|
|
25017
|
+
shoppingColors: domainColors,
|
|
25018
|
+
...config.colors
|
|
25019
|
+
},
|
|
25020
|
+
onDataLoaded: (data) => {
|
|
25021
|
+
setLoading(false);
|
|
25022
|
+
updateFooterStats(data);
|
|
25023
|
+
config.onDataLoaded?.(data);
|
|
25024
|
+
},
|
|
25025
|
+
onError: (error) => {
|
|
25026
|
+
setLoading(false);
|
|
25027
|
+
config.onError?.(error);
|
|
25028
|
+
}
|
|
25029
|
+
});
|
|
25030
|
+
await chartInstance.render();
|
|
25031
|
+
setLoading(false);
|
|
25032
|
+
},
|
|
25033
|
+
async refresh(forceRefresh = false) {
|
|
25034
|
+
if (!chartInstance) return;
|
|
25035
|
+
setLoading(true);
|
|
25036
|
+
await chartInstance.refresh(forceRefresh);
|
|
25037
|
+
setLoading(false);
|
|
25038
|
+
},
|
|
25039
|
+
setChartType(type) {
|
|
25040
|
+
if (currentChartType === type) return;
|
|
25041
|
+
currentChartType = type;
|
|
25042
|
+
chartInstance?.setChartType(type);
|
|
25043
|
+
updateTabStates();
|
|
25044
|
+
},
|
|
25045
|
+
setVizMode(mode) {
|
|
25046
|
+
if (currentVizMode === mode) return;
|
|
25047
|
+
currentVizMode = mode;
|
|
25048
|
+
chartInstance?.setVizMode(mode);
|
|
25049
|
+
updateTabStates();
|
|
25050
|
+
},
|
|
25051
|
+
setTheme(theme) {
|
|
25052
|
+
if (currentTheme === theme) return;
|
|
25053
|
+
currentTheme = theme;
|
|
25054
|
+
chartInstance?.setTheme(theme);
|
|
25055
|
+
const widget = document.getElementById(widgetId);
|
|
25056
|
+
if (widget) {
|
|
25057
|
+
widget.classList.toggle("dark", theme === "dark");
|
|
25058
|
+
}
|
|
25059
|
+
updateStyles();
|
|
25060
|
+
},
|
|
25061
|
+
async setPeriod(days) {
|
|
25062
|
+
if (currentPeriod === days) return;
|
|
25063
|
+
currentPeriod = days;
|
|
25064
|
+
updateTitle();
|
|
25065
|
+
setLoading(true);
|
|
25066
|
+
await chartInstance?.setPeriod(days);
|
|
25067
|
+
setLoading(false);
|
|
25068
|
+
},
|
|
25069
|
+
setIdealRange(range) {
|
|
25070
|
+
currentIdealRange = range;
|
|
25071
|
+
chartInstance?.setIdealRange(range);
|
|
25072
|
+
},
|
|
25073
|
+
getChart() {
|
|
25074
|
+
return chartInstance;
|
|
25075
|
+
},
|
|
25076
|
+
getCachedData() {
|
|
25077
|
+
return chartInstance?.getCachedData() ?? null;
|
|
25078
|
+
},
|
|
25079
|
+
exportCSV(filename) {
|
|
25080
|
+
chartInstance?.exportCSV(filename);
|
|
25081
|
+
},
|
|
25082
|
+
destroy() {
|
|
25083
|
+
chartInstance?.destroy();
|
|
25084
|
+
chartInstance = null;
|
|
25085
|
+
if (styleElement) {
|
|
25086
|
+
styleElement.remove();
|
|
25087
|
+
styleElement = null;
|
|
25088
|
+
}
|
|
25089
|
+
settingsHeaderInstance?.destroy();
|
|
25090
|
+
settingsHeaderInstance = null;
|
|
25091
|
+
if (settingsModalElement) {
|
|
25092
|
+
settingsModalElement.remove();
|
|
25093
|
+
settingsModalElement = null;
|
|
25094
|
+
}
|
|
25095
|
+
if (containerElement) {
|
|
25096
|
+
containerElement.innerHTML = "";
|
|
25097
|
+
containerElement = null;
|
|
25098
|
+
}
|
|
25099
|
+
}
|
|
25100
|
+
};
|
|
25101
|
+
return instance;
|
|
25102
|
+
}
|
|
25103
|
+
|
|
25104
|
+
// src/components/ExportData/index.ts
|
|
25105
|
+
var DEFAULT_COLORS4 = {
|
|
25106
|
+
primary: "#3e1a7d",
|
|
25107
|
+
// MyIO purple
|
|
25108
|
+
secondary: "#6b4c9a",
|
|
25109
|
+
// Light purple
|
|
25110
|
+
accent: "#00bcd4",
|
|
25111
|
+
// Cyan accent
|
|
25112
|
+
background: "#ffffff",
|
|
25113
|
+
// White
|
|
25114
|
+
text: "#333333",
|
|
25115
|
+
// Dark gray
|
|
25116
|
+
chartColors: ["#3e1a7d", "#00bcd4", "#4caf50", "#ff9800", "#e91e63", "#9c27b0"]
|
|
25117
|
+
};
|
|
25118
|
+
var DOMAIN_ICONS = {
|
|
25119
|
+
energy: "\u26A1",
|
|
25120
|
+
// Lightning bolt
|
|
25121
|
+
water: "\u{1F4A7}",
|
|
25122
|
+
// Water drop
|
|
25123
|
+
temperature: "\u{1F321}\uFE0F"
|
|
25124
|
+
// Thermometer
|
|
25125
|
+
};
|
|
25126
|
+
var DOMAIN_LABELS = {
|
|
25127
|
+
energy: "Energia",
|
|
25128
|
+
water: "\xC1gua",
|
|
25129
|
+
temperature: "Temperatura"
|
|
25130
|
+
};
|
|
25131
|
+
var DOMAIN_LABELS_EN = {
|
|
25132
|
+
energy: "Energy",
|
|
25133
|
+
water: "Water",
|
|
25134
|
+
temperature: "Temperature"
|
|
25135
|
+
};
|
|
25136
|
+
var DOMAIN_UNITS = {
|
|
25137
|
+
energy: "kWh",
|
|
25138
|
+
water: "m\xB3",
|
|
25139
|
+
temperature: "\xB0C"
|
|
25140
|
+
};
|
|
25141
|
+
var CSV_SEPARATORS = {
|
|
25142
|
+
"pt-BR": ";",
|
|
25143
|
+
"en-US": ",",
|
|
25144
|
+
"default": ";"
|
|
25145
|
+
};
|
|
25146
|
+
function formatDateForFilename(date) {
|
|
25147
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
25148
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
|
|
25149
|
+
}
|
|
25150
|
+
function sanitizeFilename(str) {
|
|
25151
|
+
return str.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, "_").substring(0, 50);
|
|
25152
|
+
}
|
|
25153
|
+
function generateFilename(data, config) {
|
|
25154
|
+
const timestamp = formatDateForFilename(/* @__PURE__ */ new Date());
|
|
25155
|
+
const domainLabel = DOMAIN_LABELS_EN[config.domain].toUpperCase();
|
|
25156
|
+
const ext = config.formatExport;
|
|
25157
|
+
let baseName = "export";
|
|
25158
|
+
if ("device" in data && data.device) {
|
|
25159
|
+
const device = data.device;
|
|
25160
|
+
const label = device.label || device.name || "device";
|
|
25161
|
+
const identifier = device.identifier ? `-${device.identifier}` : "";
|
|
25162
|
+
baseName = `${sanitizeFilename(label)}${identifier}`;
|
|
25163
|
+
} else if ("customer" in data && data.customer?.customerName) {
|
|
25164
|
+
baseName = sanitizeFilename(data.customer.customerName);
|
|
25165
|
+
} else if ("groupName" in data) {
|
|
25166
|
+
baseName = sanitizeFilename(data.groupName);
|
|
25167
|
+
}
|
|
25168
|
+
return `${baseName}-${domainLabel}-${timestamp}.${ext}`;
|
|
25169
|
+
}
|
|
25170
|
+
function normalizeTimestamp(ts) {
|
|
25171
|
+
if (ts instanceof Date) return ts;
|
|
25172
|
+
if (typeof ts === "number") return new Date(ts);
|
|
25173
|
+
return new Date(ts);
|
|
25174
|
+
}
|
|
25175
|
+
function calculateStats2(dataPoints) {
|
|
25176
|
+
if (dataPoints.length === 0) {
|
|
25177
|
+
return { min: 0, max: 0, average: 0, sum: 0, count: 0 };
|
|
25178
|
+
}
|
|
25179
|
+
const values = dataPoints.map((d) => d.value);
|
|
25180
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
25181
|
+
return {
|
|
25182
|
+
min: Math.min(...values),
|
|
25183
|
+
max: Math.max(...values),
|
|
25184
|
+
average: sum / values.length,
|
|
25185
|
+
sum,
|
|
25186
|
+
count: values.length
|
|
25187
|
+
};
|
|
25188
|
+
}
|
|
25189
|
+
function formatNumber2(value, locale, decimals = 2) {
|
|
25190
|
+
return new Intl.NumberFormat(locale, {
|
|
25191
|
+
minimumFractionDigits: decimals,
|
|
25192
|
+
maximumFractionDigits: decimals
|
|
25193
|
+
}).format(value);
|
|
25194
|
+
}
|
|
25195
|
+
function formatDate3(date, locale) {
|
|
25196
|
+
return new Intl.DateTimeFormat(locale, {
|
|
25197
|
+
year: "numeric",
|
|
25198
|
+
month: "2-digit",
|
|
25199
|
+
day: "2-digit",
|
|
25200
|
+
hour: "2-digit",
|
|
25201
|
+
minute: "2-digit"
|
|
25202
|
+
}).format(date);
|
|
25203
|
+
}
|
|
25204
|
+
function generateCSV(data, config) {
|
|
25205
|
+
const sep = CSV_SEPARATORS[config.locale] || CSV_SEPARATORS["default"];
|
|
25206
|
+
const rows = [];
|
|
25207
|
+
const escapeCSV = (val) => {
|
|
25208
|
+
const str = String(val ?? "");
|
|
25209
|
+
if (str.includes(sep) || str.includes('"') || str.includes("\n")) {
|
|
25210
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
25211
|
+
}
|
|
25212
|
+
return str;
|
|
25213
|
+
};
|
|
25214
|
+
const formatNumCSV = (val) => {
|
|
25215
|
+
return formatNumber2(val, config.locale);
|
|
25216
|
+
};
|
|
25217
|
+
if ("device" in data && "data" in data && Array.isArray(data.data)) {
|
|
25218
|
+
const deviceData = data;
|
|
25219
|
+
rows.push(["Timestamp", config.domainLabel, `Unit (${config.domainUnit})`]);
|
|
25220
|
+
for (const point of deviceData.data) {
|
|
25221
|
+
const ts = normalizeTimestamp(point.timestamp);
|
|
25222
|
+
rows.push([
|
|
25223
|
+
formatDate3(ts, config.locale),
|
|
25224
|
+
formatNumCSV(point.value),
|
|
25225
|
+
point.unit || config.domainUnit
|
|
25226
|
+
]);
|
|
25227
|
+
}
|
|
25228
|
+
if (config.includeStats) {
|
|
25229
|
+
const stats = calculateStats2(deviceData.data);
|
|
25230
|
+
rows.push([]);
|
|
25231
|
+
rows.push(["Statistics", "", ""]);
|
|
25232
|
+
rows.push(["Minimum", formatNumCSV(stats.min), config.domainUnit]);
|
|
25233
|
+
rows.push(["Maximum", formatNumCSV(stats.max), config.domainUnit]);
|
|
25234
|
+
rows.push(["Average", formatNumCSV(stats.average), config.domainUnit]);
|
|
25235
|
+
rows.push(["Total", formatNumCSV(stats.sum), config.domainUnit]);
|
|
25236
|
+
rows.push(["Count", String(stats.count), "points"]);
|
|
25237
|
+
}
|
|
25238
|
+
} else if ("devices" in data && Array.isArray(data.devices)) {
|
|
25239
|
+
const compData = data;
|
|
25240
|
+
const deviceHeaders = compData.devices.map(
|
|
25241
|
+
(d) => d.device.label || d.device.name || "Device"
|
|
25242
|
+
);
|
|
25243
|
+
rows.push(["Timestamp", ...deviceHeaders]);
|
|
25244
|
+
const allTimestamps = /* @__PURE__ */ new Set();
|
|
25245
|
+
compData.devices.forEach((d) => {
|
|
25246
|
+
d.data.forEach((point) => {
|
|
25247
|
+
allTimestamps.add(normalizeTimestamp(point.timestamp).getTime());
|
|
25248
|
+
});
|
|
25249
|
+
});
|
|
25250
|
+
const sortedTimestamps = Array.from(allTimestamps).sort((a, b) => a - b);
|
|
25251
|
+
for (const ts of sortedTimestamps) {
|
|
25252
|
+
const row = [formatDate3(new Date(ts), config.locale)];
|
|
25253
|
+
for (const device of compData.devices) {
|
|
25254
|
+
const point = device.data.find(
|
|
25255
|
+
(p) => normalizeTimestamp(p.timestamp).getTime() === ts
|
|
25256
|
+
);
|
|
25257
|
+
row.push(point ? formatNumCSV(point.value) : "");
|
|
25258
|
+
}
|
|
25259
|
+
rows.push(row);
|
|
25260
|
+
}
|
|
25261
|
+
}
|
|
25262
|
+
return rows.map((row) => row.map(escapeCSV).join(sep)).join("\r\n");
|
|
25263
|
+
}
|
|
25264
|
+
function generateXLSX(data, config) {
|
|
25265
|
+
return generateCSV(data, config);
|
|
25266
|
+
}
|
|
25267
|
+
function generatePDFContent(data, config) {
|
|
25268
|
+
const { colors, domainIcon, domainLabel, domainUnit, locale, includeStats, includeChart } = config;
|
|
25269
|
+
let deviceLabel = "Export";
|
|
25270
|
+
let customerName = "";
|
|
25271
|
+
let identifier = "";
|
|
25272
|
+
let dataPoints = [];
|
|
25273
|
+
if ("device" in data && "data" in data) {
|
|
25274
|
+
const deviceData = data;
|
|
25275
|
+
deviceLabel = deviceData.device.label || deviceData.device.name || "Device";
|
|
25276
|
+
identifier = deviceData.device.identifier || "";
|
|
25277
|
+
customerName = deviceData.customer?.customerName || "";
|
|
25278
|
+
dataPoints = deviceData.data;
|
|
25279
|
+
}
|
|
25280
|
+
const stats = calculateStats2(dataPoints);
|
|
25281
|
+
const tableRows = dataPoints.slice(0, 100).map((point) => {
|
|
25282
|
+
const ts = normalizeTimestamp(point.timestamp);
|
|
25283
|
+
return `
|
|
25284
|
+
<tr>
|
|
25285
|
+
<td style="padding: 8px; border-bottom: 1px solid #eee;">${formatDate3(ts, locale)}</td>
|
|
25286
|
+
<td style="padding: 8px; border-bottom: 1px solid #eee; text-align: right;">${formatNumber2(point.value, locale)}</td>
|
|
25287
|
+
<td style="padding: 8px; border-bottom: 1px solid #eee;">${point.unit || domainUnit}</td>
|
|
25288
|
+
</tr>
|
|
25289
|
+
`;
|
|
25290
|
+
}).join("");
|
|
25291
|
+
const statsSection = includeStats ? `
|
|
25292
|
+
<div style="margin-top: 24px; padding: 16px; background: #f5f5f5; border-radius: 8px;">
|
|
25293
|
+
<h3 style="margin: 0 0 12px 0; color: ${colors.primary};">Statistics</h3>
|
|
25294
|
+
<table style="width: 100%;">
|
|
25295
|
+
<tr>
|
|
25296
|
+
<td><strong>Minimum:</strong></td>
|
|
25297
|
+
<td>${formatNumber2(stats.min, locale)} ${domainUnit}</td>
|
|
25298
|
+
<td><strong>Maximum:</strong></td>
|
|
25299
|
+
<td>${formatNumber2(stats.max, locale)} ${domainUnit}</td>
|
|
25300
|
+
</tr>
|
|
25301
|
+
<tr>
|
|
25302
|
+
<td><strong>Average:</strong></td>
|
|
25303
|
+
<td>${formatNumber2(stats.average, locale)} ${domainUnit}</td>
|
|
25304
|
+
<td><strong>Total:</strong></td>
|
|
25305
|
+
<td>${formatNumber2(stats.sum, locale)} ${domainUnit}</td>
|
|
25306
|
+
</tr>
|
|
25307
|
+
</table>
|
|
25308
|
+
</div>
|
|
25309
|
+
` : "";
|
|
25310
|
+
return `
|
|
25311
|
+
<!DOCTYPE html>
|
|
25312
|
+
<html>
|
|
25313
|
+
<head>
|
|
25314
|
+
<meta charset="UTF-8">
|
|
25315
|
+
<title>${deviceLabel} - ${domainLabel} Report</title>
|
|
25316
|
+
<style>
|
|
25317
|
+
body {
|
|
25318
|
+
font-family: 'Roboto', Arial, sans-serif;
|
|
25319
|
+
margin: 0;
|
|
25320
|
+
padding: 24px;
|
|
25321
|
+
color: ${colors.text};
|
|
25322
|
+
background: ${colors.background};
|
|
25323
|
+
}
|
|
25324
|
+
.header {
|
|
25325
|
+
background: ${colors.primary};
|
|
25326
|
+
color: white;
|
|
25327
|
+
padding: 20px;
|
|
25328
|
+
border-radius: 8px;
|
|
25329
|
+
margin-bottom: 24px;
|
|
25330
|
+
}
|
|
25331
|
+
.header h1 {
|
|
25332
|
+
margin: 0;
|
|
25333
|
+
font-size: 24px;
|
|
25334
|
+
}
|
|
25335
|
+
.header .subtitle {
|
|
25336
|
+
opacity: 0.9;
|
|
25337
|
+
margin-top: 8px;
|
|
25338
|
+
}
|
|
25339
|
+
.device-info {
|
|
25340
|
+
display: flex;
|
|
25341
|
+
gap: 16px;
|
|
25342
|
+
margin-bottom: 16px;
|
|
25343
|
+
padding: 12px;
|
|
25344
|
+
background: #f5f5f5;
|
|
25345
|
+
border-radius: 8px;
|
|
25346
|
+
}
|
|
25347
|
+
.device-info span {
|
|
25348
|
+
padding: 4px 12px;
|
|
25349
|
+
background: ${colors.secondary};
|
|
25350
|
+
color: white;
|
|
25351
|
+
border-radius: 4px;
|
|
25352
|
+
font-size: 14px;
|
|
25353
|
+
}
|
|
25354
|
+
table {
|
|
25355
|
+
width: 100%;
|
|
25356
|
+
border-collapse: collapse;
|
|
25357
|
+
}
|
|
25358
|
+
th {
|
|
25359
|
+
background: ${colors.primary};
|
|
25360
|
+
color: white;
|
|
25361
|
+
padding: 12px 8px;
|
|
25362
|
+
text-align: left;
|
|
25363
|
+
}
|
|
25364
|
+
th:nth-child(2) {
|
|
25365
|
+
text-align: right;
|
|
25366
|
+
}
|
|
25367
|
+
.footer {
|
|
25368
|
+
margin-top: 24px;
|
|
25369
|
+
padding-top: 16px;
|
|
25370
|
+
border-top: 1px solid #eee;
|
|
25371
|
+
text-align: center;
|
|
25372
|
+
font-size: 12px;
|
|
25373
|
+
color: #999;
|
|
25374
|
+
}
|
|
25375
|
+
@media print {
|
|
25376
|
+
body { padding: 0; }
|
|
25377
|
+
.header { border-radius: 0; }
|
|
25378
|
+
}
|
|
25379
|
+
</style>
|
|
25380
|
+
</head>
|
|
25381
|
+
<body>
|
|
25382
|
+
<div class="header">
|
|
25383
|
+
<h1>${domainIcon} ${deviceLabel}</h1>
|
|
25384
|
+
<div class="subtitle">${domainLabel} Report - Generated ${formatDate3(/* @__PURE__ */ new Date(), locale)}</div>
|
|
25385
|
+
</div>
|
|
25386
|
+
|
|
25387
|
+
${customerName ? `<div class="customer-name" style="margin-bottom: 16px; font-size: 18px;"><strong>Customer:</strong> ${customerName}</div>` : ""}
|
|
25388
|
+
|
|
25389
|
+
${identifier ? `
|
|
25390
|
+
<div class="device-info">
|
|
25391
|
+
<span>ID: ${identifier}</span>
|
|
25392
|
+
<span>Domain: ${domainLabel}</span>
|
|
25393
|
+
<span>Unit: ${domainUnit}</span>
|
|
25394
|
+
</div>
|
|
25395
|
+
` : ""}
|
|
25396
|
+
|
|
25397
|
+
<table>
|
|
25398
|
+
<thead>
|
|
25399
|
+
<tr>
|
|
25400
|
+
<th>Timestamp</th>
|
|
25401
|
+
<th>${domainLabel} (${domainUnit})</th>
|
|
25402
|
+
<th>Unit</th>
|
|
25403
|
+
</tr>
|
|
25404
|
+
</thead>
|
|
25405
|
+
<tbody>
|
|
25406
|
+
${tableRows}
|
|
25407
|
+
${dataPoints.length > 100 ? `<tr><td colspan="3" style="text-align: center; padding: 16px; color: #999;">... and ${dataPoints.length - 100} more rows</td></tr>` : ""}
|
|
25408
|
+
</tbody>
|
|
25409
|
+
</table>
|
|
25410
|
+
|
|
25411
|
+
${statsSection}
|
|
25412
|
+
|
|
25413
|
+
<div class="footer">
|
|
25414
|
+
<p>${config.footerText || "Generated by MyIO Platform"}</p>
|
|
25415
|
+
</div>
|
|
25416
|
+
</body>
|
|
25417
|
+
</html>
|
|
25418
|
+
`;
|
|
25419
|
+
}
|
|
25420
|
+
function buildTemplateExport(params) {
|
|
25421
|
+
const {
|
|
25422
|
+
domain,
|
|
25423
|
+
formatExport,
|
|
25424
|
+
typeExport,
|
|
25425
|
+
colorsPallet,
|
|
25426
|
+
locale = "pt-BR",
|
|
25427
|
+
includeChart = formatExport === "pdf",
|
|
25428
|
+
includeStats = true,
|
|
25429
|
+
headerText,
|
|
25430
|
+
footerText
|
|
25431
|
+
} = params;
|
|
25432
|
+
const colors = {
|
|
25433
|
+
...DEFAULT_COLORS4,
|
|
25434
|
+
...colorsPallet,
|
|
25435
|
+
chartColors: colorsPallet?.chartColors || DEFAULT_COLORS4.chartColors
|
|
25436
|
+
};
|
|
25437
|
+
return {
|
|
25438
|
+
domain,
|
|
25439
|
+
formatExport,
|
|
25440
|
+
typeExport,
|
|
25441
|
+
colors,
|
|
25442
|
+
locale,
|
|
25443
|
+
includeChart,
|
|
25444
|
+
includeStats,
|
|
25445
|
+
headerText: headerText || `${DOMAIN_LABELS[domain]} Report`,
|
|
25446
|
+
footerText: footerText || "Generated by MyIO Platform",
|
|
25447
|
+
domainIcon: DOMAIN_ICONS[domain],
|
|
25448
|
+
domainLabel: DOMAIN_LABELS[domain],
|
|
25449
|
+
domainUnit: DOMAIN_UNITS[domain]
|
|
25450
|
+
};
|
|
25451
|
+
}
|
|
25452
|
+
function myioExportData(data, config, options) {
|
|
25453
|
+
const filename = generateFilename(data, config);
|
|
25454
|
+
let allDataPoints = [];
|
|
25455
|
+
if ("data" in data && Array.isArray(data.data)) {
|
|
25456
|
+
allDataPoints = data.data;
|
|
25457
|
+
} else if ("devices" in data && Array.isArray(data.devices)) {
|
|
25458
|
+
allDataPoints = data.devices.flatMap((d) => d.data);
|
|
25459
|
+
}
|
|
25460
|
+
const stats = calculateStats2(allDataPoints);
|
|
25461
|
+
const instance = {
|
|
25462
|
+
async export() {
|
|
25463
|
+
try {
|
|
25464
|
+
options?.onProgress?.(10, "Generating content...");
|
|
25465
|
+
let content;
|
|
25466
|
+
let mimeType;
|
|
25467
|
+
let finalFilename = filename;
|
|
25468
|
+
switch (config.formatExport) {
|
|
25469
|
+
case "csv":
|
|
25470
|
+
content = generateCSV(data, config);
|
|
25471
|
+
mimeType = "text/csv;charset=utf-8;";
|
|
25472
|
+
break;
|
|
25473
|
+
case "xlsx":
|
|
25474
|
+
content = generateXLSX(data, config);
|
|
25475
|
+
mimeType = "text/csv;charset=utf-8;";
|
|
25476
|
+
finalFilename = filename.replace(".xlsx", ".csv");
|
|
25477
|
+
break;
|
|
25478
|
+
case "pdf":
|
|
25479
|
+
content = generatePDFContent(data, config);
|
|
25480
|
+
mimeType = "text/html;charset=utf-8;";
|
|
25481
|
+
finalFilename = filename.replace(".pdf", ".html");
|
|
25482
|
+
break;
|
|
25483
|
+
default:
|
|
25484
|
+
throw new Error(`Unsupported format: ${config.formatExport}`);
|
|
25485
|
+
}
|
|
25486
|
+
options?.onProgress?.(80, "Creating file...");
|
|
25487
|
+
const bom = config.formatExport === "csv" ? "\uFEFF" : "";
|
|
25488
|
+
const blob = new Blob([bom + content], { type: mimeType });
|
|
25489
|
+
options?.onProgress?.(100, "Export complete");
|
|
25490
|
+
return {
|
|
25491
|
+
success: true,
|
|
25492
|
+
filename: finalFilename,
|
|
25493
|
+
blob,
|
|
25494
|
+
dataUrl: URL.createObjectURL(blob)
|
|
25495
|
+
};
|
|
25496
|
+
} catch (error) {
|
|
25497
|
+
return {
|
|
25498
|
+
success: false,
|
|
25499
|
+
filename,
|
|
25500
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
25501
|
+
};
|
|
25502
|
+
}
|
|
25503
|
+
},
|
|
25504
|
+
async download() {
|
|
25505
|
+
const result = await this.export();
|
|
25506
|
+
if (!result.success || !result.blob) {
|
|
25507
|
+
console.error("Export failed:", result.error);
|
|
25508
|
+
return;
|
|
25509
|
+
}
|
|
25510
|
+
const link = document.createElement("a");
|
|
25511
|
+
link.href = URL.createObjectURL(result.blob);
|
|
25512
|
+
link.download = result.filename;
|
|
25513
|
+
link.style.display = "none";
|
|
25514
|
+
document.body.appendChild(link);
|
|
25515
|
+
link.click();
|
|
25516
|
+
document.body.removeChild(link);
|
|
25517
|
+
URL.revokeObjectURL(link.href);
|
|
25518
|
+
},
|
|
25519
|
+
async preview() {
|
|
25520
|
+
if (config.formatExport !== "pdf") {
|
|
25521
|
+
return null;
|
|
25522
|
+
}
|
|
25523
|
+
const result = await this.export();
|
|
25524
|
+
return result.dataUrl || null;
|
|
25525
|
+
},
|
|
25526
|
+
getStats() {
|
|
25527
|
+
return stats;
|
|
25528
|
+
},
|
|
25529
|
+
getFilename() {
|
|
25530
|
+
return filename;
|
|
25531
|
+
}
|
|
25532
|
+
};
|
|
25533
|
+
if (options?.autoDownload) {
|
|
25534
|
+
instance.download();
|
|
25535
|
+
}
|
|
25536
|
+
return instance;
|
|
25537
|
+
}
|
|
25538
|
+
var EXPORT_DEFAULT_COLORS = DEFAULT_COLORS4;
|
|
25539
|
+
var EXPORT_DOMAIN_ICONS = DOMAIN_ICONS;
|
|
25540
|
+
var EXPORT_DOMAIN_LABELS = DOMAIN_LABELS;
|
|
25541
|
+
var EXPORT_DOMAIN_UNITS = DOMAIN_UNITS;
|
|
22462
25542
|
export {
|
|
22463
25543
|
CHART_COLORS,
|
|
25544
|
+
DEFAULT_COLORS as CONSUMPTION_CHART_COLORS,
|
|
25545
|
+
DEFAULT_CONFIG as CONSUMPTION_CHART_DEFAULTS,
|
|
25546
|
+
THEME_COLORS as CONSUMPTION_THEME_COLORS,
|
|
22464
25547
|
ConnectionStatusType,
|
|
22465
25548
|
DEFAULT_CLAMP_RANGE,
|
|
22466
25549
|
DeviceStatusType,
|
|
25550
|
+
EXPORT_DEFAULT_COLORS,
|
|
25551
|
+
EXPORT_DOMAIN_ICONS,
|
|
25552
|
+
EXPORT_DOMAIN_LABELS,
|
|
25553
|
+
EXPORT_DOMAIN_UNITS,
|
|
22467
25554
|
MyIOChartModal,
|
|
22468
25555
|
MyIODraggableCard,
|
|
22469
25556
|
MyIOSelectionStore,
|
|
@@ -22475,11 +25562,13 @@ export {
|
|
|
22475
25562
|
averageByDay,
|
|
22476
25563
|
buildListItemsThingsboardByUniqueDatasource,
|
|
22477
25564
|
buildMyioIngestionAuth,
|
|
25565
|
+
buildTemplateExport,
|
|
22478
25566
|
buildWaterReportCSV,
|
|
22479
25567
|
buildWaterStoresCSV,
|
|
22480
25568
|
calcDeltaPercent,
|
|
22481
25569
|
calculateDeviceStatus,
|
|
22482
25570
|
calculateDeviceStatusWithRanges,
|
|
25571
|
+
calculateStats2 as calculateExportStats,
|
|
22483
25572
|
calculateStats,
|
|
22484
25573
|
clampTemperature,
|
|
22485
25574
|
classify,
|
|
@@ -22487,8 +25576,12 @@ export {
|
|
|
22487
25576
|
classifyWaterLabels,
|
|
22488
25577
|
clearAllAuthCaches,
|
|
22489
25578
|
connectionStatusIcons,
|
|
25579
|
+
createConsumption7DaysChart,
|
|
25580
|
+
createConsumptionChartWidget,
|
|
25581
|
+
createConsumptionModal,
|
|
22490
25582
|
createDateRangePicker2 as createDateRangePicker,
|
|
22491
25583
|
createInputDateRangePickerInsideDIV,
|
|
25584
|
+
createModalHeader,
|
|
22492
25585
|
decodePayload,
|
|
22493
25586
|
decodePayloadBase64Xor,
|
|
22494
25587
|
detectDeviceType,
|
|
@@ -22502,6 +25595,7 @@ export {
|
|
|
22502
25595
|
fetchThingsboardCustomerAttrsFromStorage,
|
|
22503
25596
|
fetchThingsboardCustomerServerScopeAttrs,
|
|
22504
25597
|
findValue,
|
|
25598
|
+
findValueWithDefault,
|
|
22505
25599
|
fmtPerc,
|
|
22506
25600
|
fmtPerc2 as fmtPercLegacy,
|
|
22507
25601
|
formatAllInSameUnit,
|
|
@@ -22509,18 +25603,24 @@ export {
|
|
|
22509
25603
|
formatDateForInput,
|
|
22510
25604
|
formatDateToYMD,
|
|
22511
25605
|
formatDateWithTimezoneOffset,
|
|
25606
|
+
formatDuration,
|
|
22512
25607
|
formatEnergy,
|
|
22513
25608
|
formatNumberReadable,
|
|
25609
|
+
formatRelativeTime,
|
|
22514
25610
|
formatTankHeadFromCm,
|
|
22515
25611
|
formatTemperature2 as formatTemperature,
|
|
25612
|
+
formatWater,
|
|
22516
25613
|
formatWaterByGroup,
|
|
22517
25614
|
formatWaterVolumeM3,
|
|
25615
|
+
formatarDuracao,
|
|
25616
|
+
generateFilename as generateExportFilename,
|
|
22518
25617
|
getAuthCacheStats,
|
|
22519
25618
|
getAvailableContexts,
|
|
22520
25619
|
getConnectionStatusIcon,
|
|
22521
25620
|
getDateRangeArray,
|
|
22522
25621
|
getDeviceStatusIcon,
|
|
22523
25622
|
getDeviceStatusInfo,
|
|
25623
|
+
getModalHeaderStyles,
|
|
22524
25624
|
getSaoPauloISOString,
|
|
22525
25625
|
getSaoPauloISOStringFixed,
|
|
22526
25626
|
getValueByDatakey,
|
|
@@ -22532,8 +25632,10 @@ export {
|
|
|
22532
25632
|
isValidConnectionStatus,
|
|
22533
25633
|
isValidDeviceStatus,
|
|
22534
25634
|
isWaterCategory,
|
|
25635
|
+
mapConnectionStatus,
|
|
22535
25636
|
mapDeviceStatusToCardStatus,
|
|
22536
25637
|
mapDeviceToConnectionStatus,
|
|
25638
|
+
myioExportData,
|
|
22537
25639
|
normalizeRecipients,
|
|
22538
25640
|
numbers_exports as numbers,
|
|
22539
25641
|
openDashboardPopup,
|