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