myio-js-library 0.1.161 → 0.1.162

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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 Chart = window.Chart;
7702
- if (!Chart) {
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 Chart(chartCanvas, {
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 Chart = window.Chart;
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 Chart = window.Chart;
10707
- Chart.register(window.ChartZoom);
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 Chart(chartCanvas, {
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>${formatNumber2(monthlySum, locale)} ${annual.unit}</span>
18933
- <span>${formatNumber2(annual.total, locale)} ${annual.unit}</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
- ${formatNumber2(assetData.annual?.total || 0, locale)} ${assetData.annual?.unit || "kWh"}
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} (${formatNumber2(monthlySum, locale)} > ${formatNumber2(annualTotal, locale)})`);
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 = `${formatNumber2(monthlySum, locale)} ${unit}`;
19414
- progressTexts[1].textContent = `${formatNumber2(annualTotal, locale)} ${unit}`;
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 formatNumber2(value, locale2) {
19601
+ function formatNumber3(value, locale2) {
19513
19602
  return new Intl.NumberFormat(locale2, {
19514
19603
  minimumFractionDigits: 0,
19515
19604
  maximumFractionDigits: 2
@@ -22459,11 +22548,1742 @@ 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 = [
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/ExportData/index.ts
23838
+ var DEFAULT_COLORS3 = {
23839
+ primary: "#3e1a7d",
23840
+ // MyIO purple
23841
+ secondary: "#6b4c9a",
23842
+ // Light purple
23843
+ accent: "#00bcd4",
23844
+ // Cyan accent
23845
+ background: "#ffffff",
23846
+ // White
23847
+ text: "#333333",
23848
+ // Dark gray
23849
+ chartColors: ["#3e1a7d", "#00bcd4", "#4caf50", "#ff9800", "#e91e63", "#9c27b0"]
23850
+ };
23851
+ var DOMAIN_ICONS = {
23852
+ energy: "\u26A1",
23853
+ // Lightning bolt
23854
+ water: "\u{1F4A7}",
23855
+ // Water drop
23856
+ temperature: "\u{1F321}\uFE0F"
23857
+ // Thermometer
23858
+ };
23859
+ var DOMAIN_LABELS = {
23860
+ energy: "Energia",
23861
+ water: "\xC1gua",
23862
+ temperature: "Temperatura"
23863
+ };
23864
+ var DOMAIN_LABELS_EN = {
23865
+ energy: "Energy",
23866
+ water: "Water",
23867
+ temperature: "Temperature"
23868
+ };
23869
+ var DOMAIN_UNITS = {
23870
+ energy: "kWh",
23871
+ water: "m\xB3",
23872
+ temperature: "\xB0C"
23873
+ };
23874
+ var CSV_SEPARATORS = {
23875
+ "pt-BR": ";",
23876
+ "en-US": ",",
23877
+ "default": ";"
23878
+ };
23879
+ function formatDateForFilename(date) {
23880
+ const pad = (n) => n.toString().padStart(2, "0");
23881
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
23882
+ }
23883
+ function sanitizeFilename(str) {
23884
+ return str.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, "_").substring(0, 50);
23885
+ }
23886
+ function generateFilename(data, config) {
23887
+ const timestamp = formatDateForFilename(/* @__PURE__ */ new Date());
23888
+ const domainLabel = DOMAIN_LABELS_EN[config.domain].toUpperCase();
23889
+ const ext = config.formatExport;
23890
+ let baseName = "export";
23891
+ if ("device" in data && data.device) {
23892
+ const device = data.device;
23893
+ const label = device.label || device.name || "device";
23894
+ const identifier = device.identifier ? `-${device.identifier}` : "";
23895
+ baseName = `${sanitizeFilename(label)}${identifier}`;
23896
+ } else if ("customer" in data && data.customer?.customerName) {
23897
+ baseName = sanitizeFilename(data.customer.customerName);
23898
+ } else if ("groupName" in data) {
23899
+ baseName = sanitizeFilename(data.groupName);
23900
+ }
23901
+ return `${baseName}-${domainLabel}-${timestamp}.${ext}`;
23902
+ }
23903
+ function normalizeTimestamp(ts) {
23904
+ if (ts instanceof Date) return ts;
23905
+ if (typeof ts === "number") return new Date(ts);
23906
+ return new Date(ts);
23907
+ }
23908
+ function calculateStats2(dataPoints) {
23909
+ if (dataPoints.length === 0) {
23910
+ return { min: 0, max: 0, average: 0, sum: 0, count: 0 };
23911
+ }
23912
+ const values = dataPoints.map((d) => d.value);
23913
+ const sum = values.reduce((a, b) => a + b, 0);
23914
+ return {
23915
+ min: Math.min(...values),
23916
+ max: Math.max(...values),
23917
+ average: sum / values.length,
23918
+ sum,
23919
+ count: values.length
23920
+ };
23921
+ }
23922
+ function formatNumber2(value, locale, decimals = 2) {
23923
+ return new Intl.NumberFormat(locale, {
23924
+ minimumFractionDigits: decimals,
23925
+ maximumFractionDigits: decimals
23926
+ }).format(value);
23927
+ }
23928
+ function formatDate3(date, locale) {
23929
+ return new Intl.DateTimeFormat(locale, {
23930
+ year: "numeric",
23931
+ month: "2-digit",
23932
+ day: "2-digit",
23933
+ hour: "2-digit",
23934
+ minute: "2-digit"
23935
+ }).format(date);
23936
+ }
23937
+ function generateCSV(data, config) {
23938
+ const sep = CSV_SEPARATORS[config.locale] || CSV_SEPARATORS["default"];
23939
+ const rows = [];
23940
+ const escapeCSV = (val) => {
23941
+ const str = String(val ?? "");
23942
+ if (str.includes(sep) || str.includes('"') || str.includes("\n")) {
23943
+ return `"${str.replace(/"/g, '""')}"`;
23944
+ }
23945
+ return str;
23946
+ };
23947
+ const formatNumCSV = (val) => {
23948
+ return formatNumber2(val, config.locale);
23949
+ };
23950
+ if ("device" in data && "data" in data && Array.isArray(data.data)) {
23951
+ const deviceData = data;
23952
+ rows.push(["Timestamp", config.domainLabel, `Unit (${config.domainUnit})`]);
23953
+ for (const point of deviceData.data) {
23954
+ const ts = normalizeTimestamp(point.timestamp);
23955
+ rows.push([
23956
+ formatDate3(ts, config.locale),
23957
+ formatNumCSV(point.value),
23958
+ point.unit || config.domainUnit
23959
+ ]);
23960
+ }
23961
+ if (config.includeStats) {
23962
+ const stats = calculateStats2(deviceData.data);
23963
+ rows.push([]);
23964
+ rows.push(["Statistics", "", ""]);
23965
+ rows.push(["Minimum", formatNumCSV(stats.min), config.domainUnit]);
23966
+ rows.push(["Maximum", formatNumCSV(stats.max), config.domainUnit]);
23967
+ rows.push(["Average", formatNumCSV(stats.average), config.domainUnit]);
23968
+ rows.push(["Total", formatNumCSV(stats.sum), config.domainUnit]);
23969
+ rows.push(["Count", String(stats.count), "points"]);
23970
+ }
23971
+ } else if ("devices" in data && Array.isArray(data.devices)) {
23972
+ const compData = data;
23973
+ const deviceHeaders = compData.devices.map(
23974
+ (d) => d.device.label || d.device.name || "Device"
23975
+ );
23976
+ rows.push(["Timestamp", ...deviceHeaders]);
23977
+ const allTimestamps = /* @__PURE__ */ new Set();
23978
+ compData.devices.forEach((d) => {
23979
+ d.data.forEach((point) => {
23980
+ allTimestamps.add(normalizeTimestamp(point.timestamp).getTime());
23981
+ });
23982
+ });
23983
+ const sortedTimestamps = Array.from(allTimestamps).sort((a, b) => a - b);
23984
+ for (const ts of sortedTimestamps) {
23985
+ const row = [formatDate3(new Date(ts), config.locale)];
23986
+ for (const device of compData.devices) {
23987
+ const point = device.data.find(
23988
+ (p) => normalizeTimestamp(p.timestamp).getTime() === ts
23989
+ );
23990
+ row.push(point ? formatNumCSV(point.value) : "");
23991
+ }
23992
+ rows.push(row);
23993
+ }
23994
+ }
23995
+ return rows.map((row) => row.map(escapeCSV).join(sep)).join("\r\n");
23996
+ }
23997
+ function generateXLSX(data, config) {
23998
+ return generateCSV(data, config);
23999
+ }
24000
+ function generatePDFContent(data, config) {
24001
+ const { colors, domainIcon, domainLabel, domainUnit, locale, includeStats, includeChart } = config;
24002
+ let deviceLabel = "Export";
24003
+ let customerName = "";
24004
+ let identifier = "";
24005
+ let dataPoints = [];
24006
+ if ("device" in data && "data" in data) {
24007
+ const deviceData = data;
24008
+ deviceLabel = deviceData.device.label || deviceData.device.name || "Device";
24009
+ identifier = deviceData.device.identifier || "";
24010
+ customerName = deviceData.customer?.customerName || "";
24011
+ dataPoints = deviceData.data;
24012
+ }
24013
+ const stats = calculateStats2(dataPoints);
24014
+ const tableRows = dataPoints.slice(0, 100).map((point) => {
24015
+ const ts = normalizeTimestamp(point.timestamp);
24016
+ return `
24017
+ <tr>
24018
+ <td style="padding: 8px; border-bottom: 1px solid #eee;">${formatDate3(ts, locale)}</td>
24019
+ <td style="padding: 8px; border-bottom: 1px solid #eee; text-align: right;">${formatNumber2(point.value, locale)}</td>
24020
+ <td style="padding: 8px; border-bottom: 1px solid #eee;">${point.unit || domainUnit}</td>
24021
+ </tr>
24022
+ `;
24023
+ }).join("");
24024
+ const statsSection = includeStats ? `
24025
+ <div style="margin-top: 24px; padding: 16px; background: #f5f5f5; border-radius: 8px;">
24026
+ <h3 style="margin: 0 0 12px 0; color: ${colors.primary};">Statistics</h3>
24027
+ <table style="width: 100%;">
24028
+ <tr>
24029
+ <td><strong>Minimum:</strong></td>
24030
+ <td>${formatNumber2(stats.min, locale)} ${domainUnit}</td>
24031
+ <td><strong>Maximum:</strong></td>
24032
+ <td>${formatNumber2(stats.max, locale)} ${domainUnit}</td>
24033
+ </tr>
24034
+ <tr>
24035
+ <td><strong>Average:</strong></td>
24036
+ <td>${formatNumber2(stats.average, locale)} ${domainUnit}</td>
24037
+ <td><strong>Total:</strong></td>
24038
+ <td>${formatNumber2(stats.sum, locale)} ${domainUnit}</td>
24039
+ </tr>
24040
+ </table>
24041
+ </div>
24042
+ ` : "";
24043
+ return `
24044
+ <!DOCTYPE html>
24045
+ <html>
24046
+ <head>
24047
+ <meta charset="UTF-8">
24048
+ <title>${deviceLabel} - ${domainLabel} Report</title>
24049
+ <style>
24050
+ body {
24051
+ font-family: 'Roboto', Arial, sans-serif;
24052
+ margin: 0;
24053
+ padding: 24px;
24054
+ color: ${colors.text};
24055
+ background: ${colors.background};
24056
+ }
24057
+ .header {
24058
+ background: ${colors.primary};
24059
+ color: white;
24060
+ padding: 20px;
24061
+ border-radius: 8px;
24062
+ margin-bottom: 24px;
24063
+ }
24064
+ .header h1 {
24065
+ margin: 0;
24066
+ font-size: 24px;
24067
+ }
24068
+ .header .subtitle {
24069
+ opacity: 0.9;
24070
+ margin-top: 8px;
24071
+ }
24072
+ .device-info {
24073
+ display: flex;
24074
+ gap: 16px;
24075
+ margin-bottom: 16px;
24076
+ padding: 12px;
24077
+ background: #f5f5f5;
24078
+ border-radius: 8px;
24079
+ }
24080
+ .device-info span {
24081
+ padding: 4px 12px;
24082
+ background: ${colors.secondary};
24083
+ color: white;
24084
+ border-radius: 4px;
24085
+ font-size: 14px;
24086
+ }
24087
+ table {
24088
+ width: 100%;
24089
+ border-collapse: collapse;
24090
+ }
24091
+ th {
24092
+ background: ${colors.primary};
24093
+ color: white;
24094
+ padding: 12px 8px;
24095
+ text-align: left;
24096
+ }
24097
+ th:nth-child(2) {
24098
+ text-align: right;
24099
+ }
24100
+ .footer {
24101
+ margin-top: 24px;
24102
+ padding-top: 16px;
24103
+ border-top: 1px solid #eee;
24104
+ text-align: center;
24105
+ font-size: 12px;
24106
+ color: #999;
24107
+ }
24108
+ @media print {
24109
+ body { padding: 0; }
24110
+ .header { border-radius: 0; }
24111
+ }
24112
+ </style>
24113
+ </head>
24114
+ <body>
24115
+ <div class="header">
24116
+ <h1>${domainIcon} ${deviceLabel}</h1>
24117
+ <div class="subtitle">${domainLabel} Report - Generated ${formatDate3(/* @__PURE__ */ new Date(), locale)}</div>
24118
+ </div>
24119
+
24120
+ ${customerName ? `<div class="customer-name" style="margin-bottom: 16px; font-size: 18px;"><strong>Customer:</strong> ${customerName}</div>` : ""}
24121
+
24122
+ ${identifier ? `
24123
+ <div class="device-info">
24124
+ <span>ID: ${identifier}</span>
24125
+ <span>Domain: ${domainLabel}</span>
24126
+ <span>Unit: ${domainUnit}</span>
24127
+ </div>
24128
+ ` : ""}
24129
+
24130
+ <table>
24131
+ <thead>
24132
+ <tr>
24133
+ <th>Timestamp</th>
24134
+ <th>${domainLabel} (${domainUnit})</th>
24135
+ <th>Unit</th>
24136
+ </tr>
24137
+ </thead>
24138
+ <tbody>
24139
+ ${tableRows}
24140
+ ${dataPoints.length > 100 ? `<tr><td colspan="3" style="text-align: center; padding: 16px; color: #999;">... and ${dataPoints.length - 100} more rows</td></tr>` : ""}
24141
+ </tbody>
24142
+ </table>
24143
+
24144
+ ${statsSection}
24145
+
24146
+ <div class="footer">
24147
+ <p>${config.footerText || "Generated by MyIO Platform"}</p>
24148
+ </div>
24149
+ </body>
24150
+ </html>
24151
+ `;
24152
+ }
24153
+ function buildTemplateExport(params) {
24154
+ const {
24155
+ domain,
24156
+ formatExport,
24157
+ typeExport,
24158
+ colorsPallet,
24159
+ locale = "pt-BR",
24160
+ includeChart = formatExport === "pdf",
24161
+ includeStats = true,
24162
+ headerText,
24163
+ footerText
24164
+ } = params;
24165
+ const colors = {
24166
+ ...DEFAULT_COLORS3,
24167
+ ...colorsPallet,
24168
+ chartColors: colorsPallet?.chartColors || DEFAULT_COLORS3.chartColors
24169
+ };
24170
+ return {
24171
+ domain,
24172
+ formatExport,
24173
+ typeExport,
24174
+ colors,
24175
+ locale,
24176
+ includeChart,
24177
+ includeStats,
24178
+ headerText: headerText || `${DOMAIN_LABELS[domain]} Report`,
24179
+ footerText: footerText || "Generated by MyIO Platform",
24180
+ domainIcon: DOMAIN_ICONS[domain],
24181
+ domainLabel: DOMAIN_LABELS[domain],
24182
+ domainUnit: DOMAIN_UNITS[domain]
24183
+ };
24184
+ }
24185
+ function myioExportData(data, config, options) {
24186
+ const filename = generateFilename(data, config);
24187
+ let allDataPoints = [];
24188
+ if ("data" in data && Array.isArray(data.data)) {
24189
+ allDataPoints = data.data;
24190
+ } else if ("devices" in data && Array.isArray(data.devices)) {
24191
+ allDataPoints = data.devices.flatMap((d) => d.data);
24192
+ }
24193
+ const stats = calculateStats2(allDataPoints);
24194
+ const instance = {
24195
+ async export() {
24196
+ try {
24197
+ options?.onProgress?.(10, "Generating content...");
24198
+ let content;
24199
+ let mimeType;
24200
+ let finalFilename = filename;
24201
+ switch (config.formatExport) {
24202
+ case "csv":
24203
+ content = generateCSV(data, config);
24204
+ mimeType = "text/csv;charset=utf-8;";
24205
+ break;
24206
+ case "xlsx":
24207
+ content = generateXLSX(data, config);
24208
+ mimeType = "text/csv;charset=utf-8;";
24209
+ finalFilename = filename.replace(".xlsx", ".csv");
24210
+ break;
24211
+ case "pdf":
24212
+ content = generatePDFContent(data, config);
24213
+ mimeType = "text/html;charset=utf-8;";
24214
+ finalFilename = filename.replace(".pdf", ".html");
24215
+ break;
24216
+ default:
24217
+ throw new Error(`Unsupported format: ${config.formatExport}`);
24218
+ }
24219
+ options?.onProgress?.(80, "Creating file...");
24220
+ const bom = config.formatExport === "csv" ? "\uFEFF" : "";
24221
+ const blob = new Blob([bom + content], { type: mimeType });
24222
+ options?.onProgress?.(100, "Export complete");
24223
+ return {
24224
+ success: true,
24225
+ filename: finalFilename,
24226
+ blob,
24227
+ dataUrl: URL.createObjectURL(blob)
24228
+ };
24229
+ } catch (error) {
24230
+ return {
24231
+ success: false,
24232
+ filename,
24233
+ error: error instanceof Error ? error.message : "Unknown error"
24234
+ };
24235
+ }
24236
+ },
24237
+ async download() {
24238
+ const result = await this.export();
24239
+ if (!result.success || !result.blob) {
24240
+ console.error("Export failed:", result.error);
24241
+ return;
24242
+ }
24243
+ const link = document.createElement("a");
24244
+ link.href = URL.createObjectURL(result.blob);
24245
+ link.download = result.filename;
24246
+ link.style.display = "none";
24247
+ document.body.appendChild(link);
24248
+ link.click();
24249
+ document.body.removeChild(link);
24250
+ URL.revokeObjectURL(link.href);
24251
+ },
24252
+ async preview() {
24253
+ if (config.formatExport !== "pdf") {
24254
+ return null;
24255
+ }
24256
+ const result = await this.export();
24257
+ return result.dataUrl || null;
24258
+ },
24259
+ getStats() {
24260
+ return stats;
24261
+ },
24262
+ getFilename() {
24263
+ return filename;
24264
+ }
24265
+ };
24266
+ if (options?.autoDownload) {
24267
+ instance.download();
24268
+ }
24269
+ return instance;
24270
+ }
24271
+ var EXPORT_DEFAULT_COLORS = DEFAULT_COLORS3;
24272
+ var EXPORT_DOMAIN_ICONS = DOMAIN_ICONS;
24273
+ var EXPORT_DOMAIN_LABELS = DOMAIN_LABELS;
24274
+ var EXPORT_DOMAIN_UNITS = DOMAIN_UNITS;
22462
24275
  export {
22463
24276
  CHART_COLORS,
24277
+ DEFAULT_COLORS as CONSUMPTION_CHART_COLORS,
24278
+ DEFAULT_CONFIG as CONSUMPTION_CHART_DEFAULTS,
24279
+ THEME_COLORS as CONSUMPTION_THEME_COLORS,
22464
24280
  ConnectionStatusType,
22465
24281
  DEFAULT_CLAMP_RANGE,
22466
24282
  DeviceStatusType,
24283
+ EXPORT_DEFAULT_COLORS,
24284
+ EXPORT_DOMAIN_ICONS,
24285
+ EXPORT_DOMAIN_LABELS,
24286
+ EXPORT_DOMAIN_UNITS,
22467
24287
  MyIOChartModal,
22468
24288
  MyIODraggableCard,
22469
24289
  MyIOSelectionStore,
@@ -22475,11 +24295,13 @@ export {
22475
24295
  averageByDay,
22476
24296
  buildListItemsThingsboardByUniqueDatasource,
22477
24297
  buildMyioIngestionAuth,
24298
+ buildTemplateExport,
22478
24299
  buildWaterReportCSV,
22479
24300
  buildWaterStoresCSV,
22480
24301
  calcDeltaPercent,
22481
24302
  calculateDeviceStatus,
22482
24303
  calculateDeviceStatusWithRanges,
24304
+ calculateStats2 as calculateExportStats,
22483
24305
  calculateStats,
22484
24306
  clampTemperature,
22485
24307
  classify,
@@ -22487,8 +24309,11 @@ export {
22487
24309
  classifyWaterLabels,
22488
24310
  clearAllAuthCaches,
22489
24311
  connectionStatusIcons,
24312
+ createConsumption7DaysChart,
24313
+ createConsumptionModal,
22490
24314
  createDateRangePicker2 as createDateRangePicker,
22491
24315
  createInputDateRangePickerInsideDIV,
24316
+ createModalHeader,
22492
24317
  decodePayload,
22493
24318
  decodePayloadBase64Xor,
22494
24319
  detectDeviceType,
@@ -22502,6 +24327,7 @@ export {
22502
24327
  fetchThingsboardCustomerAttrsFromStorage,
22503
24328
  fetchThingsboardCustomerServerScopeAttrs,
22504
24329
  findValue,
24330
+ findValueWithDefault,
22505
24331
  fmtPerc,
22506
24332
  fmtPerc2 as fmtPercLegacy,
22507
24333
  formatAllInSameUnit,
@@ -22509,18 +24335,24 @@ export {
22509
24335
  formatDateForInput,
22510
24336
  formatDateToYMD,
22511
24337
  formatDateWithTimezoneOffset,
24338
+ formatDuration,
22512
24339
  formatEnergy,
22513
24340
  formatNumberReadable,
24341
+ formatRelativeTime,
22514
24342
  formatTankHeadFromCm,
22515
24343
  formatTemperature2 as formatTemperature,
24344
+ formatWater,
22516
24345
  formatWaterByGroup,
22517
24346
  formatWaterVolumeM3,
24347
+ formatarDuracao,
24348
+ generateFilename as generateExportFilename,
22518
24349
  getAuthCacheStats,
22519
24350
  getAvailableContexts,
22520
24351
  getConnectionStatusIcon,
22521
24352
  getDateRangeArray,
22522
24353
  getDeviceStatusIcon,
22523
24354
  getDeviceStatusInfo,
24355
+ getModalHeaderStyles,
22524
24356
  getSaoPauloISOString,
22525
24357
  getSaoPauloISOStringFixed,
22526
24358
  getValueByDatakey,
@@ -22532,8 +24364,10 @@ export {
22532
24364
  isValidConnectionStatus,
22533
24365
  isValidDeviceStatus,
22534
24366
  isWaterCategory,
24367
+ mapConnectionStatus,
22535
24368
  mapDeviceStatusToCardStatus,
22536
24369
  mapDeviceToConnectionStatus,
24370
+ myioExportData,
22537
24371
  normalizeRecipients,
22538
24372
  numbers_exports as numbers,
22539
24373
  openDashboardPopup,