myio-js-library 0.1.160 → 0.1.162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2121 -163
- package/dist/index.d.cts +889 -2
- package/dist/index.js +2100 -163
- package/dist/myio-js-library.umd.js +2099 -162
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
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,11 @@ __export(index_exports, {
|
|
|
595
604
|
classifyWaterLabels: () => classifyWaterLabels,
|
|
596
605
|
clearAllAuthCaches: () => clearAllAuthCaches,
|
|
597
606
|
connectionStatusIcons: () => connectionStatusIcons,
|
|
607
|
+
createConsumption7DaysChart: () => createConsumption7DaysChart,
|
|
608
|
+
createConsumptionModal: () => createConsumptionModal,
|
|
598
609
|
createDateRangePicker: () => createDateRangePicker2,
|
|
599
610
|
createInputDateRangePickerInsideDIV: () => createInputDateRangePickerInsideDIV,
|
|
611
|
+
createModalHeader: () => createModalHeader,
|
|
600
612
|
decodePayload: () => decodePayload,
|
|
601
613
|
decodePayloadBase64Xor: () => decodePayloadBase64Xor,
|
|
602
614
|
detectDeviceType: () => detectDeviceType,
|
|
@@ -610,6 +622,7 @@ __export(index_exports, {
|
|
|
610
622
|
fetchThingsboardCustomerAttrsFromStorage: () => fetchThingsboardCustomerAttrsFromStorage,
|
|
611
623
|
fetchThingsboardCustomerServerScopeAttrs: () => fetchThingsboardCustomerServerScopeAttrs,
|
|
612
624
|
findValue: () => findValue,
|
|
625
|
+
findValueWithDefault: () => findValueWithDefault,
|
|
613
626
|
fmtPerc: () => fmtPerc,
|
|
614
627
|
fmtPercLegacy: () => fmtPerc2,
|
|
615
628
|
formatAllInSameUnit: () => formatAllInSameUnit,
|
|
@@ -617,18 +630,24 @@ __export(index_exports, {
|
|
|
617
630
|
formatDateForInput: () => formatDateForInput,
|
|
618
631
|
formatDateToYMD: () => formatDateToYMD,
|
|
619
632
|
formatDateWithTimezoneOffset: () => formatDateWithTimezoneOffset,
|
|
633
|
+
formatDuration: () => formatDuration,
|
|
620
634
|
formatEnergy: () => formatEnergy,
|
|
621
635
|
formatNumberReadable: () => formatNumberReadable,
|
|
636
|
+
formatRelativeTime: () => formatRelativeTime,
|
|
622
637
|
formatTankHeadFromCm: () => formatTankHeadFromCm,
|
|
623
638
|
formatTemperature: () => formatTemperature2,
|
|
639
|
+
formatWater: () => formatWater,
|
|
624
640
|
formatWaterByGroup: () => formatWaterByGroup,
|
|
625
641
|
formatWaterVolumeM3: () => formatWaterVolumeM3,
|
|
642
|
+
formatarDuracao: () => formatarDuracao,
|
|
643
|
+
generateExportFilename: () => generateFilename,
|
|
626
644
|
getAuthCacheStats: () => getAuthCacheStats,
|
|
627
645
|
getAvailableContexts: () => getAvailableContexts,
|
|
628
646
|
getConnectionStatusIcon: () => getConnectionStatusIcon,
|
|
629
647
|
getDateRangeArray: () => getDateRangeArray,
|
|
630
648
|
getDeviceStatusIcon: () => getDeviceStatusIcon,
|
|
631
649
|
getDeviceStatusInfo: () => getDeviceStatusInfo,
|
|
650
|
+
getModalHeaderStyles: () => getModalHeaderStyles,
|
|
632
651
|
getSaoPauloISOString: () => getSaoPauloISOString,
|
|
633
652
|
getSaoPauloISOStringFixed: () => getSaoPauloISOStringFixed,
|
|
634
653
|
getValueByDatakey: () => getValueByDatakey,
|
|
@@ -640,8 +659,10 @@ __export(index_exports, {
|
|
|
640
659
|
isValidConnectionStatus: () => isValidConnectionStatus,
|
|
641
660
|
isValidDeviceStatus: () => isValidDeviceStatus,
|
|
642
661
|
isWaterCategory: () => isWaterCategory,
|
|
662
|
+
mapConnectionStatus: () => mapConnectionStatus,
|
|
643
663
|
mapDeviceStatusToCardStatus: () => mapDeviceStatusToCardStatus,
|
|
644
664
|
mapDeviceToConnectionStatus: () => mapDeviceToConnectionStatus,
|
|
665
|
+
myioExportData: () => myioExportData,
|
|
645
666
|
normalizeRecipients: () => normalizeRecipients,
|
|
646
667
|
numbers: () => numbers_exports,
|
|
647
668
|
openDashboardPopup: () => openDashboardPopup,
|
|
@@ -752,6 +773,10 @@ function formatNumberReadable(value, locale = "pt-BR", minimumFractionDigits = 2
|
|
|
752
773
|
}
|
|
753
774
|
|
|
754
775
|
// src/format/water.ts
|
|
776
|
+
function formatWater(value) {
|
|
777
|
+
const num = Number(value) || 0;
|
|
778
|
+
return `${num.toFixed(2)} m\xB3`;
|
|
779
|
+
}
|
|
755
780
|
function formatWaterVolumeM3(value, locale = "pt-BR") {
|
|
756
781
|
if (value === null || value === void 0 || isNaN(value)) {
|
|
757
782
|
return "-";
|
|
@@ -829,6 +854,76 @@ function formatAllInSameWaterUnit(values) {
|
|
|
829
854
|
};
|
|
830
855
|
}
|
|
831
856
|
|
|
857
|
+
// src/format/time.ts
|
|
858
|
+
function formatRelativeTime(timestamp) {
|
|
859
|
+
if (!timestamp || timestamp <= 0) {
|
|
860
|
+
return "\u2014";
|
|
861
|
+
}
|
|
862
|
+
const now = Date.now();
|
|
863
|
+
const diffSeconds = Math.round((now - timestamp) / 1e3);
|
|
864
|
+
if (diffSeconds < 10) {
|
|
865
|
+
return "agora";
|
|
866
|
+
}
|
|
867
|
+
if (diffSeconds < 60) {
|
|
868
|
+
return `h\xE1 ${diffSeconds}s`;
|
|
869
|
+
}
|
|
870
|
+
const diffMinutes = Math.round(diffSeconds / 60);
|
|
871
|
+
if (diffMinutes === 1) {
|
|
872
|
+
return "h\xE1 1 min";
|
|
873
|
+
}
|
|
874
|
+
if (diffMinutes < 60) {
|
|
875
|
+
return `h\xE1 ${diffMinutes} mins`;
|
|
876
|
+
}
|
|
877
|
+
const diffHours = Math.round(diffMinutes / 60);
|
|
878
|
+
if (diffHours === 1) {
|
|
879
|
+
return "h\xE1 1 hora";
|
|
880
|
+
}
|
|
881
|
+
if (diffHours < 24) {
|
|
882
|
+
return `h\xE1 ${diffHours} horas`;
|
|
883
|
+
}
|
|
884
|
+
const diffDays = Math.round(diffHours / 24);
|
|
885
|
+
if (diffDays === 1) {
|
|
886
|
+
return "ontem";
|
|
887
|
+
}
|
|
888
|
+
if (diffDays <= 30) {
|
|
889
|
+
return `h\xE1 ${diffDays} dias`;
|
|
890
|
+
}
|
|
891
|
+
return new Date(timestamp).toLocaleDateString("pt-BR");
|
|
892
|
+
}
|
|
893
|
+
function formatarDuracao(ms) {
|
|
894
|
+
if (typeof ms !== "number" || ms < 0 || !isFinite(ms)) {
|
|
895
|
+
return "0s";
|
|
896
|
+
}
|
|
897
|
+
if (ms === 0) {
|
|
898
|
+
return "0s";
|
|
899
|
+
}
|
|
900
|
+
const segundos = Math.floor(ms / 1e3 % 60);
|
|
901
|
+
const minutos = Math.floor(ms / (1e3 * 60) % 60);
|
|
902
|
+
const horas = Math.floor(ms / (1e3 * 60 * 60) % 24);
|
|
903
|
+
const dias = Math.floor(ms / (1e3 * 60 * 60 * 24));
|
|
904
|
+
const parts = [];
|
|
905
|
+
if (dias > 0) {
|
|
906
|
+
parts.push(`${dias}d`);
|
|
907
|
+
if (horas > 0) {
|
|
908
|
+
parts.push(`${horas}h`);
|
|
909
|
+
}
|
|
910
|
+
} else if (horas > 0) {
|
|
911
|
+
parts.push(`${horas}h`);
|
|
912
|
+
if (minutos > 0) {
|
|
913
|
+
parts.push(`${minutos}m`);
|
|
914
|
+
}
|
|
915
|
+
} else if (minutos > 0) {
|
|
916
|
+
parts.push(`${minutos}m`);
|
|
917
|
+
if (segundos > 0) {
|
|
918
|
+
parts.push(`${segundos}s`);
|
|
919
|
+
}
|
|
920
|
+
} else {
|
|
921
|
+
parts.push(`${segundos}s`);
|
|
922
|
+
}
|
|
923
|
+
return parts.length > 0 ? parts.join(" ") : "0s";
|
|
924
|
+
}
|
|
925
|
+
var formatDuration = formatarDuracao;
|
|
926
|
+
|
|
832
927
|
// src/date/ymd.ts
|
|
833
928
|
function formatDateToYMD(date) {
|
|
834
929
|
if (!date) {
|
|
@@ -1337,6 +1432,11 @@ function findValue(data, keyOrPath, legacyDataKey) {
|
|
|
1337
1432
|
}
|
|
1338
1433
|
return getValueByDatakey(data, keyOrPath);
|
|
1339
1434
|
}
|
|
1435
|
+
function findValueWithDefault(values, key, defaultValue = null) {
|
|
1436
|
+
if (!Array.isArray(values)) return defaultValue;
|
|
1437
|
+
const found = values.find((v) => v.key === key || v.dataType === key);
|
|
1438
|
+
return found ? found.value : defaultValue;
|
|
1439
|
+
}
|
|
1340
1440
|
|
|
1341
1441
|
// src/utils/deviceStatus.js
|
|
1342
1442
|
var DeviceStatusType = {
|
|
@@ -1393,6 +1493,16 @@ function mapDeviceToConnectionStatus(deviceStatus) {
|
|
|
1393
1493
|
}
|
|
1394
1494
|
return ConnectionStatusType.CONNECTED;
|
|
1395
1495
|
}
|
|
1496
|
+
function mapConnectionStatus(rawStatus) {
|
|
1497
|
+
const statusLower = String(rawStatus || "").toLowerCase().trim();
|
|
1498
|
+
if (statusLower === "online" || statusLower === "ok" || statusLower === "running") {
|
|
1499
|
+
return "online";
|
|
1500
|
+
}
|
|
1501
|
+
if (statusLower === "waiting" || statusLower === "connecting" || statusLower === "pending") {
|
|
1502
|
+
return "waiting";
|
|
1503
|
+
}
|
|
1504
|
+
return "offline";
|
|
1505
|
+
}
|
|
1396
1506
|
function mapDeviceStatusToCardStatus(deviceStatus) {
|
|
1397
1507
|
const statusMap = {
|
|
1398
1508
|
[DeviceStatusType.POWER_ON]: "ok",
|
|
@@ -2161,8 +2271,9 @@ var MyIOSelectionStoreClass = class _MyIOSelectionStoreClass {
|
|
|
2161
2271
|
lastValue: Number(entity.lastValue) || 0,
|
|
2162
2272
|
unit: entity.unit || "",
|
|
2163
2273
|
status: entity.status || "unknown",
|
|
2164
|
-
ingestionId: entity.ingestionId || entity.id
|
|
2274
|
+
ingestionId: entity.ingestionId || entity.id,
|
|
2165
2275
|
// ⭐ ADD: Store ingestionId for API calls
|
|
2276
|
+
customerName: entity.customerName || ""
|
|
2166
2277
|
};
|
|
2167
2278
|
this.entities.set(entity.id, normalizedEntity);
|
|
2168
2279
|
}
|
|
@@ -4790,6 +4901,13 @@ var Icons = {
|
|
|
4790
4901
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
|
4791
4902
|
viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false">
|
|
4792
4903
|
<circle cx="12" cy="12" r="5"/>
|
|
4904
|
+
</svg>`,
|
|
4905
|
+
waterDrop: `
|
|
4906
|
+
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
4907
|
+
<g id="water" transform="translate(-4 -2)">
|
|
4908
|
+
<path id="secondary" fill="#2ca9bc" d="M19,14A7,7,0,1,1,5,14C5,8,12,3,12,3S19,8,19,14Z"/>
|
|
4909
|
+
<path id="primary" d="M19,14A7,7,0,1,1,5,14C5,8,12,3,12,3S19,8,19,14Z" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
|
4910
|
+
</g>
|
|
4793
4911
|
</svg>`,
|
|
4794
4912
|
// ⋮ Kebab (for actions menu)
|
|
4795
4913
|
kebab: `
|
|
@@ -4818,6 +4936,7 @@ var ICON_MAP = {
|
|
|
4818
4936
|
var DEFAULT_I18N = {
|
|
4819
4937
|
// Status labels with icons
|
|
4820
4938
|
in_operation: "\u26A1 Normal",
|
|
4939
|
+
in_operation_water: "\u{1F4A7} Normal",
|
|
4821
4940
|
standby: "\u{1F4A4} Em standby",
|
|
4822
4941
|
alert: "\u26A0\uFE0F Alerta",
|
|
4823
4942
|
failure: "\u{1F6A8} Falha",
|
|
@@ -4874,7 +4993,13 @@ function normalizeParams(params) {
|
|
|
4874
4993
|
}
|
|
4875
4994
|
};
|
|
4876
4995
|
}
|
|
4877
|
-
function getIconSvg(deviceType) {
|
|
4996
|
+
function getIconSvg(deviceType, domain) {
|
|
4997
|
+
if (domain === "water") {
|
|
4998
|
+
return Icons.waterDrop;
|
|
4999
|
+
}
|
|
5000
|
+
if (domain === "temperature") {
|
|
5001
|
+
return Icons.thermometer;
|
|
5002
|
+
}
|
|
4878
5003
|
return ICON_MAP[deviceType] || ICON_MAP.DEFAULT;
|
|
4879
5004
|
}
|
|
4880
5005
|
function formatPower(valueInWatts) {
|
|
@@ -4914,9 +5039,23 @@ function calculateConsumptionPercentage(target, consumption) {
|
|
|
4914
5039
|
const percentage = numericConsumption / numericTarget * 100;
|
|
4915
5040
|
return percentage;
|
|
4916
5041
|
}
|
|
4917
|
-
function getStatusInfo(deviceStatus, i18n) {
|
|
5042
|
+
function getStatusInfo(deviceStatus, i18n, domain) {
|
|
4918
5043
|
switch (deviceStatus) {
|
|
5044
|
+
// --- Novos Status de Temperatura ---
|
|
5045
|
+
case "normal":
|
|
5046
|
+
return { chipClass: "chip--ok", label: "Normal" };
|
|
5047
|
+
// Verde/Azul
|
|
5048
|
+
case "cold":
|
|
5049
|
+
return { chipClass: "chip--standby", label: "Frio" };
|
|
5050
|
+
// Azul claro/Ciano
|
|
5051
|
+
case "hot":
|
|
5052
|
+
return { chipClass: "chip--alert", label: "Quente" };
|
|
5053
|
+
// Laranja/Amarelo
|
|
5054
|
+
// --- Status Existentes ---
|
|
4919
5055
|
case DeviceStatusType.POWER_ON:
|
|
5056
|
+
if (domain === "water") {
|
|
5057
|
+
return { chipClass: "chip--ok", label: i18n.in_operation_water };
|
|
5058
|
+
}
|
|
4920
5059
|
return { chipClass: "chip--ok", label: i18n.in_operation };
|
|
4921
5060
|
case DeviceStatusType.STANDBY:
|
|
4922
5061
|
return { chipClass: "chip--standby", label: i18n.standby };
|
|
@@ -4929,6 +5068,7 @@ function getStatusInfo(deviceStatus, i18n) {
|
|
|
4929
5068
|
return { chipClass: "chip--alert", label: i18n.maintenance };
|
|
4930
5069
|
case DeviceStatusType.NOT_INSTALLED:
|
|
4931
5070
|
return { chipClass: "chip--offline", label: i18n.not_installed };
|
|
5071
|
+
// Default (Cai aqui se não achar 'normal', 'hot' etc)
|
|
4932
5072
|
case DeviceStatusType.NO_INFO:
|
|
4933
5073
|
default:
|
|
4934
5074
|
return { chipClass: "chip--offline", label: i18n.offline };
|
|
@@ -4960,6 +5100,14 @@ function getCardStateClass(deviceStatus) {
|
|
|
4960
5100
|
}
|
|
4961
5101
|
function getStatusDotClass(deviceStatus) {
|
|
4962
5102
|
switch (deviceStatus) {
|
|
5103
|
+
// --- Novos Status de Temperatura ---
|
|
5104
|
+
case "normal":
|
|
5105
|
+
return "dot--ok";
|
|
5106
|
+
case "cold":
|
|
5107
|
+
return "dot--standby";
|
|
5108
|
+
case "hot":
|
|
5109
|
+
return "dot--alert";
|
|
5110
|
+
// --- Status Existentes ---
|
|
4963
5111
|
case DeviceStatusType.POWER_ON:
|
|
4964
5112
|
return "dot--ok";
|
|
4965
5113
|
case DeviceStatusType.STANDBY:
|
|
@@ -4987,7 +5135,7 @@ function buildDOM(state) {
|
|
|
4987
5135
|
header.className = "myio-ho-card__header";
|
|
4988
5136
|
const iconContainer = document.createElement("div");
|
|
4989
5137
|
iconContainer.className = "myio-ho-card__icon";
|
|
4990
|
-
iconContainer.innerHTML = getIconSvg(entityObject.deviceType);
|
|
5138
|
+
iconContainer.innerHTML = getIconSvg(entityObject.deviceType, entityObject.domain);
|
|
4991
5139
|
header.appendChild(iconContainer);
|
|
4992
5140
|
const titleSection = document.createElement("div");
|
|
4993
5141
|
titleSection.className = "myio-ho-card__title";
|
|
@@ -5109,7 +5257,11 @@ function buildDOM(state) {
|
|
|
5109
5257
|
powerMetric.appendChild(statusDot);
|
|
5110
5258
|
const powerLabel = document.createElement("div");
|
|
5111
5259
|
powerLabel.className = "label";
|
|
5112
|
-
|
|
5260
|
+
if (entityObject.domain === "water") {
|
|
5261
|
+
powerLabel.textContent = "Leitura";
|
|
5262
|
+
} else {
|
|
5263
|
+
powerLabel.textContent = i18n.instantaneous_power || "Pot\xEAncia";
|
|
5264
|
+
}
|
|
5113
5265
|
powerMetric.appendChild(powerLabel);
|
|
5114
5266
|
const powerVal = document.createElement("div");
|
|
5115
5267
|
powerVal.className = "val";
|
|
@@ -5133,7 +5285,7 @@ function verifyOfflineStatus(entityObject, delayTimeInMins = 15) {
|
|
|
5133
5285
|
return true;
|
|
5134
5286
|
}
|
|
5135
5287
|
function paint(root, state) {
|
|
5136
|
-
const { entityObject, i18n, delayTimeConnectionInMins } = state;
|
|
5288
|
+
const { entityObject, i18n, delayTimeConnectionInMins, isSelected } = state;
|
|
5137
5289
|
if (entityObject.connectionStatus) {
|
|
5138
5290
|
if (entityObject.connectionStatus === "offline") {
|
|
5139
5291
|
entityObject.deviceStatus = DeviceStatusType.NO_INFO;
|
|
@@ -5145,16 +5297,23 @@ function paint(root, state) {
|
|
|
5145
5297
|
}
|
|
5146
5298
|
const stateClass = getCardStateClass(entityObject.deviceStatus);
|
|
5147
5299
|
root.className = `myio-ho-card ${stateClass}`;
|
|
5148
|
-
const statusInfo = getStatusInfo(entityObject.deviceStatus, i18n);
|
|
5300
|
+
const statusInfo = getStatusInfo(entityObject.deviceStatus, i18n, entityObject.domain);
|
|
5149
5301
|
const chip = root.querySelector(".chip");
|
|
5150
5302
|
chip.className = `chip ${statusInfo.chipClass}`;
|
|
5151
|
-
chip.
|
|
5303
|
+
chip.innerHTML = statusInfo.label;
|
|
5152
5304
|
const primaryValue = formatValueByDomain(entityObject.val, entityObject.domain);
|
|
5153
5305
|
const numSpan = root.querySelector(".myio-ho-card__value .num");
|
|
5154
5306
|
const unitSpan = root.querySelector(".myio-ho-card__value .unit");
|
|
5155
5307
|
numSpan.textContent = primaryValue;
|
|
5156
5308
|
const barContainer = root.querySelector(".bar");
|
|
5157
5309
|
const effContainer = root.querySelector(".myio-ho-card__eff");
|
|
5310
|
+
if (state.enableSelection) {
|
|
5311
|
+
const checkbox = root.querySelector('.myio-ho-card__select input[type="checkbox"]');
|
|
5312
|
+
if (checkbox) {
|
|
5313
|
+
checkbox.checked = !!isSelected;
|
|
5314
|
+
}
|
|
5315
|
+
root.classList.toggle("is-selected", !!isSelected);
|
|
5316
|
+
}
|
|
5158
5317
|
const targetValue = entityObject.consumptionTargetValue;
|
|
5159
5318
|
if (targetValue) {
|
|
5160
5319
|
barContainer.style.display = "";
|
|
@@ -5176,9 +5335,14 @@ function paint(root, state) {
|
|
|
5176
5335
|
}
|
|
5177
5336
|
const powerVal = root.querySelector(".myio-ho-card__footer .metric:nth-child(2) .val");
|
|
5178
5337
|
if (powerVal) {
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5338
|
+
if (entityObject.domain === "water") {
|
|
5339
|
+
const pulses = entityObject.pulses ?? 0;
|
|
5340
|
+
powerVal.textContent = `${pulses} L`;
|
|
5341
|
+
} else {
|
|
5342
|
+
const instantPower = entityObject.instantaneousPower ?? entityObject.consumption_power ?? null;
|
|
5343
|
+
const powerFormatted = formatPower(instantPower);
|
|
5344
|
+
powerVal.textContent = instantPower !== null ? `${powerFormatted.num} ${powerFormatted.unit}` : "-";
|
|
5345
|
+
}
|
|
5182
5346
|
}
|
|
5183
5347
|
const statusDot = root.querySelector(".myio-ho-card__footer .metric:nth-child(2) .status-dot");
|
|
5184
5348
|
if (statusDot) {
|
|
@@ -5238,6 +5402,26 @@ function bindEvents(root, state, callbacks) {
|
|
|
5238
5402
|
callbacks.handleActionSettings(e, entityObject);
|
|
5239
5403
|
});
|
|
5240
5404
|
}
|
|
5405
|
+
const MyIOSelectionStore2 = window.MyIOLibrary?.MyIOSelectionStore || window.MyIOSelectionStore;
|
|
5406
|
+
if (MyIOSelectionStore2) {
|
|
5407
|
+
const onSelectionChange = () => {
|
|
5408
|
+
const selectedIds = MyIOSelectionStore2.getSelectedIds();
|
|
5409
|
+
const isSelected = selectedIds.includes(entityObject.entityId);
|
|
5410
|
+
if (state.isSelected !== isSelected) {
|
|
5411
|
+
state.isSelected = isSelected;
|
|
5412
|
+
paint(root, state);
|
|
5413
|
+
}
|
|
5414
|
+
};
|
|
5415
|
+
MyIOSelectionStore2.on("selection:change", onSelectionChange);
|
|
5416
|
+
root._selectionListener = onSelectionChange;
|
|
5417
|
+
}
|
|
5418
|
+
root._cleanup = () => {
|
|
5419
|
+
document.removeEventListener("click", closeMenu);
|
|
5420
|
+
document.removeEventListener("keydown", closeMenu);
|
|
5421
|
+
if (MyIOSelectionStore2 && root._selectionListener) {
|
|
5422
|
+
MyIOSelectionStore2.off("selection:change", root._selectionListener);
|
|
5423
|
+
}
|
|
5424
|
+
};
|
|
5241
5425
|
const checkbox = root.querySelector('.myio-ho-card__select input[type="checkbox"]');
|
|
5242
5426
|
if (checkbox && callbacks.handleSelect) {
|
|
5243
5427
|
checkbox.addEventListener("change", (e) => {
|
|
@@ -5305,6 +5489,7 @@ function renderCardComponentHeadOffice(containerEl, params) {
|
|
|
5305
5489
|
ensureCss();
|
|
5306
5490
|
const state = normalizeParams(params);
|
|
5307
5491
|
const root = buildDOM(state);
|
|
5492
|
+
state.isSelected = params.isSelected || false;
|
|
5308
5493
|
containerEl.appendChild(root);
|
|
5309
5494
|
bindEvents(root, state, state.callbacks);
|
|
5310
5495
|
paint(root, state);
|
|
@@ -7731,14 +7916,14 @@ async function openRealTimeTelemetryModal(params) {
|
|
|
7731
7916
|
return `${value.toFixed(config.decimals)} ${config.unit}`;
|
|
7732
7917
|
}
|
|
7733
7918
|
function initializeChart() {
|
|
7734
|
-
const
|
|
7735
|
-
if (!
|
|
7919
|
+
const Chart2 = window.Chart;
|
|
7920
|
+
if (!Chart2) {
|
|
7736
7921
|
console.warn("[RealTimeTelemetry] Chart.js not loaded");
|
|
7737
7922
|
return;
|
|
7738
7923
|
}
|
|
7739
7924
|
chartContainer.style.display = "block";
|
|
7740
7925
|
const config = TELEMETRY_CONFIG[selectedChartKey] || { label: selectedChartKey, unit: "" };
|
|
7741
|
-
chart = new
|
|
7926
|
+
chart = new Chart2(chartCanvas, {
|
|
7742
7927
|
type: "line",
|
|
7743
7928
|
data: {
|
|
7744
7929
|
datasets: [{
|
|
@@ -10507,7 +10692,7 @@ async function openDemandModal(params) {
|
|
|
10507
10692
|
params.timezoneOffset
|
|
10508
10693
|
);
|
|
10509
10694
|
if (!newChartData.isEmpty && chart && chartData) {
|
|
10510
|
-
const
|
|
10695
|
+
const Chart2 = window.Chart;
|
|
10511
10696
|
newChartData.series.forEach((newSeries, seriesIndex) => {
|
|
10512
10697
|
if (newSeries.points.length > 0 && chart.data.datasets[seriesIndex]) {
|
|
10513
10698
|
newSeries.points.forEach((point) => {
|
|
@@ -10736,8 +10921,8 @@ async function openDemandModal(params) {
|
|
|
10736
10921
|
peakEl.textContent = `${strings.maximum}: ${peak.formattedValue} kW ${peak.key ? `(${peak.key}) ` : ""}${strings.at} ${dateStr}`;
|
|
10737
10922
|
peakEl.style.display = "block";
|
|
10738
10923
|
}
|
|
10739
|
-
const
|
|
10740
|
-
|
|
10924
|
+
const Chart2 = window.Chart;
|
|
10925
|
+
Chart2.register(window.ChartZoom);
|
|
10741
10926
|
if (chart) {
|
|
10742
10927
|
chart.data.datasets = chartData.series.map((series) => ({
|
|
10743
10928
|
label: series.label,
|
|
@@ -10789,7 +10974,7 @@ async function openDemandModal(params) {
|
|
|
10789
10974
|
};
|
|
10790
10975
|
chart.update();
|
|
10791
10976
|
} else {
|
|
10792
|
-
chart = new
|
|
10977
|
+
chart = new Chart2(chartCanvas, {
|
|
10793
10978
|
type: "line",
|
|
10794
10979
|
data: {
|
|
10795
10980
|
datasets: chartData.series.map((series) => ({
|
|
@@ -11048,11 +11233,13 @@ var EnergyModalView = class {
|
|
|
11048
11233
|
console.log("[EnergyModalView] Bar mode toggled to:", this.currentBarMode);
|
|
11049
11234
|
}
|
|
11050
11235
|
/**
|
|
11051
|
-
|
|
11052
|
-
|
|
11236
|
+
* Applies the current theme to the modal and charts (Updated with #root override)
|
|
11237
|
+
*/
|
|
11053
11238
|
applyTheme() {
|
|
11054
11239
|
const themeToggleBtn = document.getElementById("theme-toggle-btn");
|
|
11055
|
-
const modalContent =
|
|
11240
|
+
const modalContent = this.container;
|
|
11241
|
+
const externalBody = this.container?.closest(".myio-modal-body") || document.querySelector(".myio-modal-body");
|
|
11242
|
+
const rootDiv = document.querySelector("#root > div");
|
|
11056
11243
|
if (themeToggleBtn) {
|
|
11057
11244
|
const sunIcon = themeToggleBtn.querySelector(".myio-theme-icon-sun");
|
|
11058
11245
|
const moonIcon = themeToggleBtn.querySelector(".myio-theme-icon-moon");
|
|
@@ -11065,6 +11252,14 @@ var EnergyModalView = class {
|
|
|
11065
11252
|
moonIcon.style.opacity = "0";
|
|
11066
11253
|
moonIcon.style.transform = "translate(-50%, -50%) rotate(90deg) scale(0)";
|
|
11067
11254
|
}
|
|
11255
|
+
if (externalBody) {
|
|
11256
|
+
externalBody.style.backgroundColor = "#ffffff";
|
|
11257
|
+
externalBody.style.color = "#1f2937";
|
|
11258
|
+
}
|
|
11259
|
+
if (rootDiv) {
|
|
11260
|
+
rootDiv.style.backgroundColor = "#ffffff";
|
|
11261
|
+
rootDiv.style.color = "#1f2937";
|
|
11262
|
+
}
|
|
11068
11263
|
} else {
|
|
11069
11264
|
if (sunIcon) {
|
|
11070
11265
|
sunIcon.style.opacity = "0";
|
|
@@ -11074,6 +11269,14 @@ var EnergyModalView = class {
|
|
|
11074
11269
|
moonIcon.style.opacity = "1";
|
|
11075
11270
|
moonIcon.style.transform = "translate(-50%, -50%) rotate(0deg) scale(1)";
|
|
11076
11271
|
}
|
|
11272
|
+
if (externalBody) {
|
|
11273
|
+
externalBody.style.backgroundColor = "#1f1f1f";
|
|
11274
|
+
externalBody.style.color = "#f3f4f6";
|
|
11275
|
+
}
|
|
11276
|
+
if (rootDiv) {
|
|
11277
|
+
rootDiv.style.backgroundColor = "#1f1f1f";
|
|
11278
|
+
rootDiv.style.color = "#f3f4f6";
|
|
11279
|
+
}
|
|
11077
11280
|
}
|
|
11078
11281
|
}
|
|
11079
11282
|
if (modalContent) {
|
|
@@ -11302,7 +11505,7 @@ var EnergyModalView = class {
|
|
|
11302
11505
|
${this.config.params.mode === "comparison" ? `
|
|
11303
11506
|
<!-- RFC-0097: Granularity Selector (only 1h and 1d supported) -->
|
|
11304
11507
|
<div class="myio-granularity-selector" style="display: flex; align-items: center; gap: 4px; margin-left: 8px; padding: 4px 8px; background: rgba(0,0,0,0.05); border-radius: 8px;">
|
|
11305
|
-
<span style="font-size: 11px;
|
|
11508
|
+
<span class="myio-label-secondary" style="font-size: 11px; margin-right: 4px; white-space: nowrap;">Granularidade:</span>
|
|
11306
11509
|
<button class="myio-btn myio-btn-granularity ${this.currentGranularity === "1h" ? "active" : ""}" data-granularity="1h" title="Hora">1h</button>
|
|
11307
11510
|
<button class="myio-btn myio-btn-granularity ${this.currentGranularity === "1d" ? "active" : ""}" data-granularity="1d" title="Dia">1d</button>
|
|
11308
11511
|
</div>
|
|
@@ -11984,93 +12187,159 @@ var EnergyModalView = class {
|
|
|
11984
12187
|
return i18n[key] || DEFAULT_I18N2[key];
|
|
11985
12188
|
}
|
|
11986
12189
|
/**
|
|
11987
|
-
|
|
11988
|
-
|
|
12190
|
+
* Gets modal styles with fixed Dark/Light contrast
|
|
12191
|
+
*/
|
|
12192
|
+
/**
|
|
12193
|
+
* Gets modal styles with fixed Chart Border for Light Mode
|
|
12194
|
+
*/
|
|
11989
12195
|
getModalStyles() {
|
|
11990
12196
|
const styles = this.config.params.styles || {};
|
|
12197
|
+
const defaultPrimary = styles.primaryColor || "#4A148C";
|
|
12198
|
+
const defaultFont = styles.fontFamily || '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
|
11991
12199
|
return `
|
|
12200
|
+
/* --- VARI\xC1VEIS DE TEMA (LIGHT MODE - PADR\xC3O) --- */
|
|
11992
12201
|
.myio-energy-modal-scope {
|
|
11993
|
-
--myio-energy-primary: ${
|
|
11994
|
-
--myio-energy-
|
|
11995
|
-
--myio-energy-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
--myio-energy-
|
|
12202
|
+
--myio-energy-primary: ${defaultPrimary};
|
|
12203
|
+
--myio-energy-font: ${defaultFont};
|
|
12204
|
+
--myio-energy-radius: 8px;
|
|
12205
|
+
|
|
12206
|
+
/* Cores Gerais (Light Mode) */
|
|
12207
|
+
--myio-energy-bg: #ffffff;
|
|
12208
|
+
--myio-energy-text: #1f2937;
|
|
12209
|
+
--myio-energy-text-secondary: #6b7280;
|
|
12210
|
+
|
|
12211
|
+
/* Borda GERAL (para bot\xF5es e inputs) - Cinza suave */
|
|
12212
|
+
--myio-energy-border: #e5e7eb;
|
|
12213
|
+
|
|
12214
|
+
/* Borda ESPEC\xCDFICA DO GR\xC1FICO (Aqui est\xE1 a corre\xE7\xE3o) */
|
|
12215
|
+
/* No Light Mode, definimos como transparente ou branco para "sumir" */
|
|
12216
|
+
--myio-chart-border: transparent;
|
|
12217
|
+
|
|
12218
|
+
--myio-energy-btn-bg: #f3f4f6;
|
|
12219
|
+
--myio-energy-btn-hover: #e5e7eb;
|
|
12220
|
+
--myio-energy-input-bg: #ffffff;
|
|
12221
|
+
--myio-granularity-bg: #f9fafb;
|
|
11999
12222
|
|
|
12000
12223
|
font-family: var(--myio-energy-font);
|
|
12224
|
+
background-color: var(--myio-energy-bg);
|
|
12001
12225
|
color: var(--myio-energy-text);
|
|
12002
12226
|
height: -webkit-fill-available;
|
|
12227
|
+
transition: background-color 0.3s ease, color 0.3s ease;
|
|
12003
12228
|
}
|
|
12004
12229
|
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
}
|
|
12230
|
+
/* --- DARK MODE OVERRIDES --- */
|
|
12231
|
+
.myio-energy-modal-scope[data-theme="dark"] {
|
|
12232
|
+
--myio-energy-bg: #1f1f1f;
|
|
12233
|
+
--myio-energy-text: #f3f4f6;
|
|
12234
|
+
--myio-energy-text-secondary: #9ca3af;
|
|
12235
|
+
|
|
12236
|
+
/* No Dark Mode, as bordas precisam aparecer */
|
|
12237
|
+
--myio-energy-border: #374151;
|
|
12238
|
+
--myio-chart-border: #374151; /* Borda vis\xEDvel no escuro */
|
|
12015
12239
|
|
|
12016
|
-
|
|
12017
|
-
|
|
12240
|
+
--myio-energy-btn-bg: #374151;
|
|
12241
|
+
--myio-energy-btn-hover: #4b5563;
|
|
12242
|
+
--myio-energy-input-bg: #111827;
|
|
12243
|
+
--myio-granularity-bg: #111827;
|
|
12018
12244
|
}
|
|
12019
12245
|
|
|
12020
|
-
|
|
12021
|
-
opacity: 0.5;
|
|
12022
|
-
cursor: not-allowed;
|
|
12023
|
-
}
|
|
12246
|
+
/* --- COMPONENTES --- */
|
|
12024
12247
|
|
|
12025
|
-
.myio-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12248
|
+
.myio-energy-chart-container {
|
|
12249
|
+
flex: 1 !important;
|
|
12250
|
+
min-height: 353px !important;
|
|
12251
|
+
height: 353px !important;
|
|
12252
|
+
background: var(--myio-energy-bg);
|
|
12253
|
+
border-radius: var(--myio-energy-radius);
|
|
12254
|
+
|
|
12255
|
+
/* USO DA VARI\xC1VEL ESPEC\xCDFICA AQUI */
|
|
12256
|
+
border: 1px solid var(--myio-chart-border);
|
|
12257
|
+
|
|
12258
|
+
padding: 10px !important;
|
|
12259
|
+
display: block !important;
|
|
12260
|
+
overflow: hidden !important;
|
|
12029
12261
|
}
|
|
12030
12262
|
|
|
12031
|
-
.myio-
|
|
12032
|
-
|
|
12263
|
+
.myio-chart-container {
|
|
12264
|
+
/* Aplica a mesma l\xF3gica para o gr\xE1fico de fallback */
|
|
12265
|
+
border: 1px solid var(--myio-chart-border);
|
|
12266
|
+
border-radius: var(--myio-energy-radius);
|
|
12267
|
+
overflow: hidden;
|
|
12033
12268
|
}
|
|
12034
12269
|
|
|
12035
|
-
.
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12270
|
+
/* --- Resto dos estilos (Bot\xF5es, Labels, etc.) --- */
|
|
12271
|
+
|
|
12272
|
+
.myio-label-secondary {
|
|
12273
|
+
color: var(--myio-energy-text-secondary);
|
|
12274
|
+
font-weight: 500;
|
|
12039
12275
|
}
|
|
12040
12276
|
|
|
12041
|
-
.myio-
|
|
12042
|
-
|
|
12277
|
+
.myio-granularity-selector {
|
|
12278
|
+
display: flex;
|
|
12279
|
+
align-items: center;
|
|
12280
|
+
gap: 4px;
|
|
12281
|
+
margin-left: 8px;
|
|
12282
|
+
padding: 4px 8px;
|
|
12283
|
+
border-radius: 8px;
|
|
12284
|
+
background: var(--myio-granularity-bg);
|
|
12285
|
+
border: 1px solid var(--myio-energy-border); /* Granularidade mant\xE9m borda suave */
|
|
12043
12286
|
}
|
|
12044
12287
|
|
|
12045
|
-
/* RFC-0097: Granularity selector buttons */
|
|
12046
12288
|
.myio-btn-granularity {
|
|
12047
12289
|
padding: 4px 10px;
|
|
12048
12290
|
font-size: 12px;
|
|
12049
12291
|
font-weight: 600;
|
|
12050
12292
|
border-radius: 6px;
|
|
12051
|
-
border: 1px solid var(--myio-energy-border);
|
|
12052
|
-
background: var(--myio-energy-bg);
|
|
12053
|
-
color: var(--myio-energy-text);
|
|
12054
12293
|
cursor: pointer;
|
|
12055
12294
|
transition: all 0.2s ease;
|
|
12056
12295
|
min-width: 36px;
|
|
12296
|
+
background: transparent;
|
|
12297
|
+
border: 1px solid transparent;
|
|
12298
|
+
color: var(--myio-energy-text-secondary);
|
|
12057
12299
|
}
|
|
12058
12300
|
|
|
12059
12301
|
.myio-btn-granularity:hover:not(.active) {
|
|
12060
|
-
background:
|
|
12061
|
-
|
|
12062
|
-
color: var(--myio-energy-primary);
|
|
12302
|
+
background: var(--myio-energy-btn-hover);
|
|
12303
|
+
color: var(--myio-energy-text);
|
|
12063
12304
|
}
|
|
12064
12305
|
|
|
12065
12306
|
.myio-btn-granularity.active {
|
|
12066
12307
|
background: var(--myio-energy-primary);
|
|
12067
12308
|
color: white;
|
|
12068
12309
|
border-color: var(--myio-energy-primary);
|
|
12069
|
-
box-shadow: 0 2px
|
|
12310
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
12070
12311
|
}
|
|
12071
12312
|
|
|
12072
|
-
.myio-
|
|
12313
|
+
.myio-btn {
|
|
12314
|
+
padding: 8px 16px;
|
|
12315
|
+
border-radius: var(--myio-energy-radius);
|
|
12073
12316
|
border: 1px solid var(--myio-energy-border);
|
|
12317
|
+
background: var(--myio-energy-btn-bg);
|
|
12318
|
+
color: var(--myio-energy-text);
|
|
12319
|
+
cursor: pointer;
|
|
12320
|
+
font-size: 14px;
|
|
12321
|
+
transition: all 0.2s;
|
|
12322
|
+
}
|
|
12323
|
+
|
|
12324
|
+
.myio-btn:hover:not(:disabled) {
|
|
12325
|
+
background: var(--myio-energy-btn-hover);
|
|
12326
|
+
border-color: var(--myio-energy-border);
|
|
12327
|
+
}
|
|
12328
|
+
|
|
12329
|
+
.myio-btn-primary {
|
|
12330
|
+
background: var(--myio-energy-primary);
|
|
12331
|
+
color: white;
|
|
12332
|
+
border-color: var(--myio-energy-primary);
|
|
12333
|
+
}
|
|
12334
|
+
|
|
12335
|
+
.myio-btn-primary:hover:not(:disabled) {
|
|
12336
|
+
opacity: 0.9;
|
|
12337
|
+
}
|
|
12338
|
+
|
|
12339
|
+
.myio-btn-secondary {
|
|
12340
|
+
background: var(--myio-energy-btn-bg);
|
|
12341
|
+
color: var(--myio-energy-text);
|
|
12342
|
+
border-color: var(--myio-energy-border);
|
|
12074
12343
|
}
|
|
12075
12344
|
|
|
12076
12345
|
.myio-modal-scope {
|
|
@@ -12079,42 +12348,30 @@ var EnergyModalView = class {
|
|
|
12079
12348
|
flex-direction: column !important;
|
|
12080
12349
|
}
|
|
12081
12350
|
|
|
12082
|
-
.myio-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
height: 353px !important;
|
|
12086
|
-
background: var(--myio-energy-bg);
|
|
12087
|
-
border-radius: var(--myio-energy-radius);
|
|
12088
|
-
border: 1px solid var(--myio-energy-border);
|
|
12089
|
-
padding: 10px !important;
|
|
12090
|
-
display: block !important;
|
|
12091
|
-
overflow: hidden !important;
|
|
12092
|
-
}
|
|
12093
|
-
|
|
12094
|
-
.myio-energy-chart-container > iframe {
|
|
12095
|
-
width: 100% !important;
|
|
12096
|
-
height: 100% !important;
|
|
12097
|
-
min-height: 408px !important;
|
|
12098
|
-
border: none !important;
|
|
12351
|
+
.myio-form-group {
|
|
12352
|
+
display: flex;
|
|
12353
|
+
flex-direction: column;
|
|
12099
12354
|
}
|
|
12100
12355
|
|
|
12101
|
-
.myio-
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
min-height: 408px !important;
|
|
12356
|
+
.myio-label {
|
|
12357
|
+
font-weight: 500;
|
|
12358
|
+
margin-bottom: 5px;
|
|
12359
|
+
color: var(--myio-energy-text);
|
|
12106
12360
|
}
|
|
12107
12361
|
|
|
12108
|
-
.myio-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12362
|
+
.myio-input {
|
|
12363
|
+
padding: 8px 12px;
|
|
12364
|
+
border: 1px solid var(--myio-energy-border);
|
|
12365
|
+
border-radius: var(--myio-energy-radius);
|
|
12366
|
+
font-size: 14px;
|
|
12367
|
+
background: var(--myio-energy-input-bg);
|
|
12368
|
+
color: var(--myio-energy-text);
|
|
12114
12369
|
}
|
|
12115
12370
|
|
|
12116
|
-
.myio-
|
|
12117
|
-
|
|
12371
|
+
.myio-input:focus {
|
|
12372
|
+
outline: none;
|
|
12373
|
+
border-color: var(--myio-energy-primary);
|
|
12374
|
+
box-shadow: 0 0 0 1px var(--myio-energy-primary);
|
|
12118
12375
|
}
|
|
12119
12376
|
|
|
12120
12377
|
.myio-spinner {
|
|
@@ -12126,78 +12383,40 @@ var EnergyModalView = class {
|
|
|
12126
12383
|
animation: spin 1s linear infinite;
|
|
12127
12384
|
margin: 0 auto 16px;
|
|
12128
12385
|
}
|
|
12129
|
-
|
|
12386
|
+
|
|
12130
12387
|
@keyframes spin {
|
|
12131
12388
|
0% { transform: rotate(0deg); }
|
|
12132
12389
|
100% { transform: rotate(360deg); }
|
|
12133
12390
|
}
|
|
12134
12391
|
|
|
12392
|
+
.myio-loading-state p {
|
|
12393
|
+
color: var(--myio-energy-text);
|
|
12394
|
+
}
|
|
12395
|
+
|
|
12135
12396
|
.myio-energy-error {
|
|
12136
|
-
background:
|
|
12137
|
-
border: 1px solid
|
|
12397
|
+
background: rgba(254, 202, 202, 0.15);
|
|
12398
|
+
border: 1px solid rgba(248, 113, 113, 0.5);
|
|
12138
12399
|
border-radius: var(--myio-energy-radius);
|
|
12139
12400
|
padding: 16px;
|
|
12140
12401
|
}
|
|
12141
|
-
|
|
12142
|
-
.myio-error-content {
|
|
12143
|
-
display: flex;
|
|
12144
|
-
align-items: center;
|
|
12145
|
-
gap: 12px;
|
|
12146
|
-
}
|
|
12147
|
-
|
|
12148
|
-
.myio-error-icon {
|
|
12149
|
-
font-size: 24px;
|
|
12150
|
-
}
|
|
12151
|
-
|
|
12402
|
+
|
|
12152
12403
|
.myio-error-message {
|
|
12153
|
-
color: #
|
|
12404
|
+
color: #ef4444;
|
|
12154
12405
|
font-weight: 500;
|
|
12155
12406
|
}
|
|
12156
12407
|
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
.myio-fallback-chart h4 {
|
|
12160
|
-
margin: 0 0 16px 0;
|
|
12161
|
-
text-align: center;
|
|
12162
|
-
}
|
|
12163
|
-
|
|
12164
|
-
.myio-chart-container {
|
|
12165
|
-
border: 1px solid var(--myio-energy-border);
|
|
12166
|
-
border-radius: var(--myio-energy-radius);
|
|
12167
|
-
overflow: hidden;
|
|
12168
|
-
}
|
|
12169
|
-
|
|
12170
12408
|
.myio-chart-note {
|
|
12171
12409
|
margin: 12px 0 0 0;
|
|
12172
12410
|
font-size: 12px;
|
|
12173
|
-
color: #666;
|
|
12174
|
-
text-align: center;
|
|
12175
|
-
}
|
|
12176
|
-
|
|
12177
|
-
.myio-form-group {
|
|
12178
|
-
display: flex;
|
|
12179
|
-
flex-direction: column;
|
|
12180
|
-
}
|
|
12181
|
-
|
|
12182
|
-
.myio-label {
|
|
12183
|
-
font-weight: 500;
|
|
12184
|
-
margin-bottom: 5px;
|
|
12185
|
-
color: var(--myio-energy-text);
|
|
12186
|
-
}
|
|
12187
|
-
|
|
12188
|
-
.myio-input {
|
|
12189
|
-
padding: 8px 12px;
|
|
12190
|
-
border: 1px solid var(--myio-energy-border);
|
|
12191
|
-
border-radius: var(--myio-energy-radius);
|
|
12192
|
-
font-size: 14px;
|
|
12193
|
-
background: var(--myio-energy-bg);
|
|
12194
12411
|
color: var(--myio-energy-text);
|
|
12412
|
+
opacity: 0.7;
|
|
12413
|
+
text-align: center;
|
|
12195
12414
|
}
|
|
12196
12415
|
|
|
12197
|
-
.myio-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12416
|
+
.myio-energy-chart-container > iframe {
|
|
12417
|
+
width: 100% !important;
|
|
12418
|
+
height: 100% !important;
|
|
12419
|
+
border: none !important;
|
|
12201
12420
|
}
|
|
12202
12421
|
|
|
12203
12422
|
@media (max-width: 768px) {
|
|
@@ -12205,12 +12424,6 @@ var EnergyModalView = class {
|
|
|
12205
12424
|
grid-template-columns: 1fr;
|
|
12206
12425
|
grid-template-rows: auto 1fr;
|
|
12207
12426
|
}
|
|
12208
|
-
|
|
12209
|
-
.myio-energy-header {
|
|
12210
|
-
flex-direction: column;
|
|
12211
|
-
gap: 12px;
|
|
12212
|
-
align-items: stretch;
|
|
12213
|
-
}
|
|
12214
12427
|
}
|
|
12215
12428
|
`;
|
|
12216
12429
|
}
|
|
@@ -18934,8 +19147,8 @@ function openGoalsPanel(params) {
|
|
|
18934
19147
|
<div class="myio-goals-progress-fill" style="width: ${Math.min(progress, 100)}%"></div>
|
|
18935
19148
|
</div>
|
|
18936
19149
|
<div class="myio-goals-progress-text">
|
|
18937
|
-
<span>${
|
|
18938
|
-
<span>${
|
|
19150
|
+
<span>${formatNumber3(monthlySum, locale)} ${annual.unit}</span>
|
|
19151
|
+
<span>${formatNumber3(annual.total, locale)} ${annual.unit}</span>
|
|
18939
19152
|
</div>
|
|
18940
19153
|
|
|
18941
19154
|
<!-- Monthly Grid -->
|
|
@@ -19009,7 +19222,7 @@ function openGoalsPanel(params) {
|
|
|
19009
19222
|
<span>${assetData.label || assetId}</span>
|
|
19010
19223
|
</div>
|
|
19011
19224
|
<div class="myio-goals-asset-total">
|
|
19012
|
-
${
|
|
19225
|
+
${formatNumber3(assetData.annual?.total || 0, locale)} ${assetData.annual?.unit || "kWh"}
|
|
19013
19226
|
</div>
|
|
19014
19227
|
<button class="myio-goals-btn-icon" data-action="delete-asset" data-asset-id="${assetId}" aria-label="${i18n.deleteAsset}">
|
|
19015
19228
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -19324,7 +19537,7 @@ function openGoalsPanel(params) {
|
|
|
19324
19537
|
monthlySum += value;
|
|
19325
19538
|
}
|
|
19326
19539
|
if (monthlySum > annualTotal && annualTotal > 0) {
|
|
19327
|
-
errors.push(`${i18n.errorMonthlyExceedsAnnual} (${
|
|
19540
|
+
errors.push(`${i18n.errorMonthlyExceedsAnnual} (${formatNumber3(monthlySum, locale)} > ${formatNumber3(annualTotal, locale)})`);
|
|
19328
19541
|
}
|
|
19329
19542
|
}
|
|
19330
19543
|
return errors;
|
|
@@ -19415,8 +19628,8 @@ function openGoalsPanel(params) {
|
|
|
19415
19628
|
}
|
|
19416
19629
|
if (progressTexts.length === 2) {
|
|
19417
19630
|
const unit = document.getElementById("unit-select")?.value || "kWh";
|
|
19418
|
-
progressTexts[0].textContent = `${
|
|
19419
|
-
progressTexts[1].textContent = `${
|
|
19631
|
+
progressTexts[0].textContent = `${formatNumber3(monthlySum, locale)} ${unit}`;
|
|
19632
|
+
progressTexts[1].textContent = `${formatNumber3(annualTotal, locale)} ${unit}`;
|
|
19420
19633
|
}
|
|
19421
19634
|
}
|
|
19422
19635
|
function updateMonthlyUnits(unit) {
|
|
@@ -19514,7 +19727,7 @@ function openGoalsPanel(params) {
|
|
|
19514
19727
|
modal.addEventListener("keydown", handleTab);
|
|
19515
19728
|
firstElement.focus();
|
|
19516
19729
|
}
|
|
19517
|
-
function
|
|
19730
|
+
function formatNumber3(value, locale2) {
|
|
19518
19731
|
return new Intl.NumberFormat(locale2, {
|
|
19519
19732
|
minimumFractionDigits: 0,
|
|
19520
19733
|
maximumFractionDigits: 2
|
|
@@ -22464,12 +22677,1743 @@ function openTemperatureSettingsModal(params) {
|
|
|
22464
22677
|
});
|
|
22465
22678
|
return { destroy };
|
|
22466
22679
|
}
|
|
22680
|
+
|
|
22681
|
+
// src/components/ModalHeader/index.ts
|
|
22682
|
+
var DEFAULT_BG_COLOR = "#3e1a7d";
|
|
22683
|
+
var DEFAULT_TEXT_COLOR = "white";
|
|
22684
|
+
var DEFAULT_BORDER_RADIUS = "10px 10px 0 0";
|
|
22685
|
+
var EXPORT_FORMAT_LABELS = {
|
|
22686
|
+
csv: "CSV",
|
|
22687
|
+
xls: "Excel (XLS)",
|
|
22688
|
+
pdf: "PDF"
|
|
22689
|
+
};
|
|
22690
|
+
var EXPORT_FORMAT_ICONS = {
|
|
22691
|
+
csv: "\u{1F4C4}",
|
|
22692
|
+
xls: "\u{1F4CA}",
|
|
22693
|
+
pdf: "\u{1F4D1}"
|
|
22694
|
+
};
|
|
22695
|
+
function createModalHeader(config) {
|
|
22696
|
+
let currentTheme = config.theme || "light";
|
|
22697
|
+
let currentIsMaximized = config.isMaximized || false;
|
|
22698
|
+
let currentTitle = config.title;
|
|
22699
|
+
let themeBtn = null;
|
|
22700
|
+
let maximizeBtn = null;
|
|
22701
|
+
let closeBtn = null;
|
|
22702
|
+
let exportBtn = null;
|
|
22703
|
+
let exportDropdown = null;
|
|
22704
|
+
const cleanupHandlers = [];
|
|
22705
|
+
const handleThemeClick = () => {
|
|
22706
|
+
currentTheme = currentTheme === "light" ? "dark" : "light";
|
|
22707
|
+
config.onThemeToggle?.(currentTheme);
|
|
22708
|
+
updateButtonIcons();
|
|
22709
|
+
};
|
|
22710
|
+
const handleMaximizeClick = () => {
|
|
22711
|
+
currentIsMaximized = !currentIsMaximized;
|
|
22712
|
+
config.onMaximize?.(currentIsMaximized);
|
|
22713
|
+
updateButtonIcons();
|
|
22714
|
+
};
|
|
22715
|
+
const handleCloseClick = () => {
|
|
22716
|
+
config.onClose?.();
|
|
22717
|
+
};
|
|
22718
|
+
const handleExportClick = (format) => {
|
|
22719
|
+
config.onExport?.(format);
|
|
22720
|
+
if (exportDropdown) {
|
|
22721
|
+
exportDropdown.style.display = "none";
|
|
22722
|
+
}
|
|
22723
|
+
};
|
|
22724
|
+
function updateButtonIcons() {
|
|
22725
|
+
if (themeBtn) {
|
|
22726
|
+
themeBtn.textContent = currentTheme === "dark" ? "\u2600\uFE0F" : "\u{1F319}";
|
|
22727
|
+
themeBtn.title = currentTheme === "dark" ? "Modo claro" : "Modo escuro";
|
|
22728
|
+
}
|
|
22729
|
+
if (maximizeBtn) {
|
|
22730
|
+
maximizeBtn.textContent = currentIsMaximized ? "\u{1F5D7}" : "\u{1F5D6}";
|
|
22731
|
+
maximizeBtn.title = currentIsMaximized ? "Restaurar" : "Maximizar";
|
|
22732
|
+
}
|
|
22733
|
+
}
|
|
22734
|
+
function getButtonStyle() {
|
|
22735
|
+
return `
|
|
22736
|
+
background: none;
|
|
22737
|
+
border: none;
|
|
22738
|
+
font-size: 16px;
|
|
22739
|
+
cursor: pointer;
|
|
22740
|
+
padding: 4px 8px;
|
|
22741
|
+
border-radius: 6px;
|
|
22742
|
+
color: rgba(255, 255, 255, 0.8);
|
|
22743
|
+
transition: background-color 0.2s, color 0.2s;
|
|
22744
|
+
`.replace(/\s+/g, " ").trim();
|
|
22745
|
+
}
|
|
22746
|
+
function renderExportDropdown() {
|
|
22747
|
+
const formats = config.exportFormats || [];
|
|
22748
|
+
if (formats.length === 0) return "";
|
|
22749
|
+
if (formats.length === 1) {
|
|
22750
|
+
return `
|
|
22751
|
+
<button id="${config.id}-export" title="Exportar ${EXPORT_FORMAT_LABELS[formats[0]]}" style="${getButtonStyle()}">
|
|
22752
|
+
\u{1F4E5}
|
|
22753
|
+
</button>
|
|
22754
|
+
`;
|
|
22755
|
+
}
|
|
22756
|
+
return `
|
|
22757
|
+
<div style="position: relative; display: inline-block;">
|
|
22758
|
+
<button id="${config.id}-export-btn" title="Exportar" style="${getButtonStyle()}">
|
|
22759
|
+
\u{1F4E5}
|
|
22760
|
+
</button>
|
|
22761
|
+
<div id="${config.id}-export-dropdown" style="
|
|
22762
|
+
display: none;
|
|
22763
|
+
position: absolute;
|
|
22764
|
+
top: 100%;
|
|
22765
|
+
right: 0;
|
|
22766
|
+
background: white;
|
|
22767
|
+
border-radius: 6px;
|
|
22768
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
22769
|
+
min-width: 140px;
|
|
22770
|
+
z-index: 10001;
|
|
22771
|
+
margin-top: 4px;
|
|
22772
|
+
overflow: hidden;
|
|
22773
|
+
">
|
|
22774
|
+
${formats.map((format) => `
|
|
22775
|
+
<button
|
|
22776
|
+
id="${config.id}-export-${format}"
|
|
22777
|
+
class="myio-export-option"
|
|
22778
|
+
data-format="${format}"
|
|
22779
|
+
style="
|
|
22780
|
+
display: flex;
|
|
22781
|
+
align-items: center;
|
|
22782
|
+
gap: 8px;
|
|
22783
|
+
width: 100%;
|
|
22784
|
+
padding: 10px 14px;
|
|
22785
|
+
border: none;
|
|
22786
|
+
background: white;
|
|
22787
|
+
cursor: pointer;
|
|
22788
|
+
font-size: 13px;
|
|
22789
|
+
color: #333;
|
|
22790
|
+
text-align: left;
|
|
22791
|
+
transition: background-color 0.2s;
|
|
22792
|
+
"
|
|
22793
|
+
>
|
|
22794
|
+
${EXPORT_FORMAT_ICONS[format]} ${EXPORT_FORMAT_LABELS[format]}
|
|
22795
|
+
</button>
|
|
22796
|
+
`).join("")}
|
|
22797
|
+
</div>
|
|
22798
|
+
</div>
|
|
22799
|
+
`;
|
|
22800
|
+
}
|
|
22801
|
+
const instance = {
|
|
22802
|
+
render() {
|
|
22803
|
+
const bgColor = config.backgroundColor || DEFAULT_BG_COLOR;
|
|
22804
|
+
const textColor = config.textColor || DEFAULT_TEXT_COLOR;
|
|
22805
|
+
const borderRadius = currentIsMaximized ? "0" : config.borderRadius || DEFAULT_BORDER_RADIUS;
|
|
22806
|
+
const showTheme = config.showThemeToggle !== false;
|
|
22807
|
+
const showMax = config.showMaximize !== false;
|
|
22808
|
+
const showClose = config.showClose !== false;
|
|
22809
|
+
const showExport = config.exportFormats && config.exportFormats.length > 0;
|
|
22810
|
+
const iconHtml = config.icon ? `<span style="margin-right: 8px;">${config.icon}</span>` : "";
|
|
22811
|
+
const buttonStyle = getButtonStyle();
|
|
22812
|
+
return `
|
|
22813
|
+
<div class="myio-modal-header" style="
|
|
22814
|
+
padding: 4px 8px;
|
|
22815
|
+
display: flex;
|
|
22816
|
+
align-items: center;
|
|
22817
|
+
justify-content: space-between;
|
|
22818
|
+
background: ${bgColor};
|
|
22819
|
+
color: ${textColor};
|
|
22820
|
+
border-radius: ${borderRadius};
|
|
22821
|
+
min-height: 20px;
|
|
22822
|
+
font-family: 'Roboto', Arial, sans-serif;
|
|
22823
|
+
">
|
|
22824
|
+
<h2 id="${config.id}-header-title" style="
|
|
22825
|
+
margin: 6px;
|
|
22826
|
+
font-size: 18px;
|
|
22827
|
+
font-weight: 600;
|
|
22828
|
+
color: ${textColor};
|
|
22829
|
+
line-height: 2;
|
|
22830
|
+
display: flex;
|
|
22831
|
+
align-items: center;
|
|
22832
|
+
">
|
|
22833
|
+
${iconHtml}${currentTitle}
|
|
22834
|
+
</h2>
|
|
22835
|
+
<div style="display: flex; gap: 4px; align-items: center;">
|
|
22836
|
+
${showExport ? renderExportDropdown() : ""}
|
|
22837
|
+
${showTheme ? `
|
|
22838
|
+
<button id="${config.id}-theme-toggle" title="${currentTheme === "dark" ? "Modo claro" : "Modo escuro"}" style="${buttonStyle}">
|
|
22839
|
+
${currentTheme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"}
|
|
22840
|
+
</button>
|
|
22841
|
+
` : ""}
|
|
22842
|
+
${showMax ? `
|
|
22843
|
+
<button id="${config.id}-maximize" title="${currentIsMaximized ? "Restaurar" : "Maximizar"}" style="${buttonStyle}">
|
|
22844
|
+
${currentIsMaximized ? "\u{1F5D7}" : "\u{1F5D6}"}
|
|
22845
|
+
</button>
|
|
22846
|
+
` : ""}
|
|
22847
|
+
${showClose ? `
|
|
22848
|
+
<button id="${config.id}-close" title="Fechar" style="${buttonStyle}; font-size: 20px;">
|
|
22849
|
+
\xD7
|
|
22850
|
+
</button>
|
|
22851
|
+
` : ""}
|
|
22852
|
+
</div>
|
|
22853
|
+
</div>
|
|
22854
|
+
`;
|
|
22855
|
+
},
|
|
22856
|
+
attachListeners() {
|
|
22857
|
+
themeBtn = document.getElementById(`${config.id}-theme-toggle`);
|
|
22858
|
+
maximizeBtn = document.getElementById(`${config.id}-maximize`);
|
|
22859
|
+
closeBtn = document.getElementById(`${config.id}-close`);
|
|
22860
|
+
const singleExportBtn = document.getElementById(`${config.id}-export`);
|
|
22861
|
+
if (singleExportBtn && config.exportFormats?.length === 1) {
|
|
22862
|
+
exportBtn = singleExportBtn;
|
|
22863
|
+
const format = config.exportFormats[0];
|
|
22864
|
+
const clickHandler = () => handleExportClick(format);
|
|
22865
|
+
exportBtn.addEventListener("click", clickHandler);
|
|
22866
|
+
cleanupHandlers.push(() => exportBtn?.removeEventListener("click", clickHandler));
|
|
22867
|
+
const enterHandler = () => {
|
|
22868
|
+
exportBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22869
|
+
};
|
|
22870
|
+
const leaveHandler = () => {
|
|
22871
|
+
exportBtn.style.backgroundColor = "transparent";
|
|
22872
|
+
};
|
|
22873
|
+
exportBtn.addEventListener("mouseenter", enterHandler);
|
|
22874
|
+
exportBtn.addEventListener("mouseleave", leaveHandler);
|
|
22875
|
+
cleanupHandlers.push(() => {
|
|
22876
|
+
exportBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22877
|
+
exportBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22878
|
+
});
|
|
22879
|
+
}
|
|
22880
|
+
const exportDropdownBtn = document.getElementById(`${config.id}-export-btn`);
|
|
22881
|
+
exportDropdown = document.getElementById(`${config.id}-export-dropdown`);
|
|
22882
|
+
if (exportDropdownBtn && exportDropdown) {
|
|
22883
|
+
exportBtn = exportDropdownBtn;
|
|
22884
|
+
const toggleHandler = (e) => {
|
|
22885
|
+
e.stopPropagation();
|
|
22886
|
+
if (exportDropdown) {
|
|
22887
|
+
exportDropdown.style.display = exportDropdown.style.display === "none" ? "block" : "none";
|
|
22888
|
+
}
|
|
22889
|
+
};
|
|
22890
|
+
exportDropdownBtn.addEventListener("click", toggleHandler);
|
|
22891
|
+
cleanupHandlers.push(() => exportDropdownBtn.removeEventListener("click", toggleHandler));
|
|
22892
|
+
const outsideClickHandler = (e) => {
|
|
22893
|
+
if (exportDropdown && !exportDropdown.contains(e.target) && e.target !== exportDropdownBtn) {
|
|
22894
|
+
exportDropdown.style.display = "none";
|
|
22895
|
+
}
|
|
22896
|
+
};
|
|
22897
|
+
document.addEventListener("click", outsideClickHandler);
|
|
22898
|
+
cleanupHandlers.push(() => document.removeEventListener("click", outsideClickHandler));
|
|
22899
|
+
config.exportFormats?.forEach((format) => {
|
|
22900
|
+
const btn = document.getElementById(`${config.id}-export-${format}`);
|
|
22901
|
+
if (btn) {
|
|
22902
|
+
const clickHandler = () => handleExportClick(format);
|
|
22903
|
+
btn.addEventListener("click", clickHandler);
|
|
22904
|
+
cleanupHandlers.push(() => btn.removeEventListener("click", clickHandler));
|
|
22905
|
+
const enterHandler2 = () => {
|
|
22906
|
+
btn.style.backgroundColor = "#f0f0f0";
|
|
22907
|
+
};
|
|
22908
|
+
const leaveHandler2 = () => {
|
|
22909
|
+
btn.style.backgroundColor = "white";
|
|
22910
|
+
};
|
|
22911
|
+
btn.addEventListener("mouseenter", enterHandler2);
|
|
22912
|
+
btn.addEventListener("mouseleave", leaveHandler2);
|
|
22913
|
+
cleanupHandlers.push(() => {
|
|
22914
|
+
btn.removeEventListener("mouseenter", enterHandler2);
|
|
22915
|
+
btn.removeEventListener("mouseleave", leaveHandler2);
|
|
22916
|
+
});
|
|
22917
|
+
}
|
|
22918
|
+
});
|
|
22919
|
+
const enterHandler = () => {
|
|
22920
|
+
exportDropdownBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22921
|
+
};
|
|
22922
|
+
const leaveHandler = () => {
|
|
22923
|
+
exportDropdownBtn.style.backgroundColor = "transparent";
|
|
22924
|
+
};
|
|
22925
|
+
exportDropdownBtn.addEventListener("mouseenter", enterHandler);
|
|
22926
|
+
exportDropdownBtn.addEventListener("mouseleave", leaveHandler);
|
|
22927
|
+
cleanupHandlers.push(() => {
|
|
22928
|
+
exportDropdownBtn.removeEventListener("mouseenter", enterHandler);
|
|
22929
|
+
exportDropdownBtn.removeEventListener("mouseleave", leaveHandler);
|
|
22930
|
+
});
|
|
22931
|
+
}
|
|
22932
|
+
if (themeBtn && config.showThemeToggle !== false) {
|
|
22933
|
+
themeBtn.addEventListener("click", handleThemeClick);
|
|
22934
|
+
cleanupHandlers.push(() => themeBtn?.removeEventListener("click", handleThemeClick));
|
|
22935
|
+
const enterHandler = () => {
|
|
22936
|
+
themeBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22937
|
+
};
|
|
22938
|
+
const leaveHandler = () => {
|
|
22939
|
+
themeBtn.style.backgroundColor = "transparent";
|
|
22940
|
+
};
|
|
22941
|
+
themeBtn.addEventListener("mouseenter", enterHandler);
|
|
22942
|
+
themeBtn.addEventListener("mouseleave", leaveHandler);
|
|
22943
|
+
cleanupHandlers.push(() => {
|
|
22944
|
+
themeBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22945
|
+
themeBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22946
|
+
});
|
|
22947
|
+
}
|
|
22948
|
+
if (maximizeBtn && config.showMaximize !== false) {
|
|
22949
|
+
maximizeBtn.addEventListener("click", handleMaximizeClick);
|
|
22950
|
+
cleanupHandlers.push(() => maximizeBtn?.removeEventListener("click", handleMaximizeClick));
|
|
22951
|
+
const enterHandler = () => {
|
|
22952
|
+
maximizeBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22953
|
+
};
|
|
22954
|
+
const leaveHandler = () => {
|
|
22955
|
+
maximizeBtn.style.backgroundColor = "transparent";
|
|
22956
|
+
};
|
|
22957
|
+
maximizeBtn.addEventListener("mouseenter", enterHandler);
|
|
22958
|
+
maximizeBtn.addEventListener("mouseleave", leaveHandler);
|
|
22959
|
+
cleanupHandlers.push(() => {
|
|
22960
|
+
maximizeBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22961
|
+
maximizeBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22962
|
+
});
|
|
22963
|
+
}
|
|
22964
|
+
if (closeBtn && config.showClose !== false) {
|
|
22965
|
+
closeBtn.addEventListener("click", handleCloseClick);
|
|
22966
|
+
cleanupHandlers.push(() => closeBtn?.removeEventListener("click", handleCloseClick));
|
|
22967
|
+
const enterHandler = () => {
|
|
22968
|
+
closeBtn.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
22969
|
+
};
|
|
22970
|
+
const leaveHandler = () => {
|
|
22971
|
+
closeBtn.style.backgroundColor = "transparent";
|
|
22972
|
+
};
|
|
22973
|
+
closeBtn.addEventListener("mouseenter", enterHandler);
|
|
22974
|
+
closeBtn.addEventListener("mouseleave", leaveHandler);
|
|
22975
|
+
cleanupHandlers.push(() => {
|
|
22976
|
+
closeBtn?.removeEventListener("mouseenter", enterHandler);
|
|
22977
|
+
closeBtn?.removeEventListener("mouseleave", leaveHandler);
|
|
22978
|
+
});
|
|
22979
|
+
}
|
|
22980
|
+
},
|
|
22981
|
+
update(updates) {
|
|
22982
|
+
if (updates.theme !== void 0) {
|
|
22983
|
+
currentTheme = updates.theme;
|
|
22984
|
+
updateButtonIcons();
|
|
22985
|
+
}
|
|
22986
|
+
if (updates.isMaximized !== void 0) {
|
|
22987
|
+
currentIsMaximized = updates.isMaximized;
|
|
22988
|
+
updateButtonIcons();
|
|
22989
|
+
}
|
|
22990
|
+
if (updates.title !== void 0) {
|
|
22991
|
+
currentTitle = updates.title;
|
|
22992
|
+
const titleEl = document.getElementById(`${config.id}-header-title`);
|
|
22993
|
+
if (titleEl) {
|
|
22994
|
+
const iconHtml = config.icon ? `<span style="margin-right: 8px;">${config.icon}</span>` : "";
|
|
22995
|
+
titleEl.innerHTML = `${iconHtml}${currentTitle}`;
|
|
22996
|
+
}
|
|
22997
|
+
}
|
|
22998
|
+
},
|
|
22999
|
+
getState() {
|
|
23000
|
+
return {
|
|
23001
|
+
theme: currentTheme,
|
|
23002
|
+
isMaximized: currentIsMaximized
|
|
23003
|
+
};
|
|
23004
|
+
},
|
|
23005
|
+
destroy() {
|
|
23006
|
+
cleanupHandlers.forEach((handler) => handler());
|
|
23007
|
+
cleanupHandlers.length = 0;
|
|
23008
|
+
themeBtn = null;
|
|
23009
|
+
maximizeBtn = null;
|
|
23010
|
+
closeBtn = null;
|
|
23011
|
+
exportBtn = null;
|
|
23012
|
+
exportDropdown = null;
|
|
23013
|
+
}
|
|
23014
|
+
};
|
|
23015
|
+
return instance;
|
|
23016
|
+
}
|
|
23017
|
+
function getModalHeaderStyles() {
|
|
23018
|
+
return `
|
|
23019
|
+
.myio-modal-header button:hover {
|
|
23020
|
+
background-color: rgba(255, 255, 255, 0.2) !important;
|
|
23021
|
+
}
|
|
23022
|
+
.myio-modal-header button:active {
|
|
23023
|
+
background-color: rgba(255, 255, 255, 0.3) !important;
|
|
23024
|
+
}
|
|
23025
|
+
.myio-export-option:hover {
|
|
23026
|
+
background-color: #f0f0f0 !important;
|
|
23027
|
+
}
|
|
23028
|
+
`;
|
|
23029
|
+
}
|
|
23030
|
+
|
|
23031
|
+
// src/components/Consumption7DaysChart/types.ts
|
|
23032
|
+
var DEFAULT_COLORS = {
|
|
23033
|
+
energy: {
|
|
23034
|
+
primary: "#2563eb",
|
|
23035
|
+
background: "rgba(37, 99, 235, 0.1)",
|
|
23036
|
+
gradient: ["#f0fdf4", "#dcfce7"],
|
|
23037
|
+
pointBackground: "#2563eb",
|
|
23038
|
+
pointBorder: "#ffffff"
|
|
23039
|
+
},
|
|
23040
|
+
water: {
|
|
23041
|
+
primary: "#0288d1",
|
|
23042
|
+
background: "rgba(2, 136, 209, 0.1)",
|
|
23043
|
+
gradient: ["#f0f9ff", "#bae6fd"],
|
|
23044
|
+
pointBackground: "#0288d1",
|
|
23045
|
+
pointBorder: "#ffffff"
|
|
23046
|
+
},
|
|
23047
|
+
gas: {
|
|
23048
|
+
primary: "#ea580c",
|
|
23049
|
+
background: "rgba(234, 88, 12, 0.1)",
|
|
23050
|
+
gradient: ["#fff7ed", "#fed7aa"],
|
|
23051
|
+
pointBackground: "#ea580c",
|
|
23052
|
+
pointBorder: "#ffffff"
|
|
23053
|
+
},
|
|
23054
|
+
temperature: {
|
|
23055
|
+
primary: "#dc2626",
|
|
23056
|
+
background: "rgba(220, 38, 38, 0.1)",
|
|
23057
|
+
gradient: ["#fef2f2", "#fecaca"],
|
|
23058
|
+
pointBackground: "#dc2626",
|
|
23059
|
+
pointBorder: "#ffffff"
|
|
23060
|
+
}
|
|
23061
|
+
};
|
|
23062
|
+
var THEME_COLORS = {
|
|
23063
|
+
light: {
|
|
23064
|
+
chartBackground: "#ffffff",
|
|
23065
|
+
text: "#1f2937",
|
|
23066
|
+
textMuted: "#6b7280",
|
|
23067
|
+
grid: "rgba(0, 0, 0, 0.1)",
|
|
23068
|
+
border: "#e5e7eb",
|
|
23069
|
+
tooltipBackground: "#ffffff",
|
|
23070
|
+
tooltipText: "#1f2937"
|
|
23071
|
+
},
|
|
23072
|
+
dark: {
|
|
23073
|
+
chartBackground: "#1f2937",
|
|
23074
|
+
text: "#f9fafb",
|
|
23075
|
+
textMuted: "#9ca3af",
|
|
23076
|
+
grid: "rgba(255, 255, 255, 0.1)",
|
|
23077
|
+
border: "#374151",
|
|
23078
|
+
tooltipBackground: "#374151",
|
|
23079
|
+
tooltipText: "#f9fafb"
|
|
23080
|
+
}
|
|
23081
|
+
};
|
|
23082
|
+
var DEFAULT_CONFIG = {
|
|
23083
|
+
defaultPeriod: 7,
|
|
23084
|
+
defaultChartType: "line",
|
|
23085
|
+
defaultVizMode: "total",
|
|
23086
|
+
defaultTheme: "light",
|
|
23087
|
+
cacheTTL: 3e5,
|
|
23088
|
+
// 5 minutes
|
|
23089
|
+
decimalPlaces: 1,
|
|
23090
|
+
lineTension: 0.4,
|
|
23091
|
+
pointRadius: 4,
|
|
23092
|
+
borderWidth: 2,
|
|
23093
|
+
fill: true,
|
|
23094
|
+
showLegend: false,
|
|
23095
|
+
enableExport: true
|
|
23096
|
+
};
|
|
23097
|
+
|
|
23098
|
+
// src/components/Consumption7DaysChart/createConsumption7DaysChart.ts
|
|
23099
|
+
function createConsumption7DaysChart(config) {
|
|
23100
|
+
let chartInstance = null;
|
|
23101
|
+
let cachedData = null;
|
|
23102
|
+
let currentPeriod = config.defaultPeriod ?? DEFAULT_CONFIG.defaultPeriod;
|
|
23103
|
+
let currentChartType = config.defaultChartType ?? DEFAULT_CONFIG.defaultChartType;
|
|
23104
|
+
let currentVizMode = config.defaultVizMode ?? DEFAULT_CONFIG.defaultVizMode;
|
|
23105
|
+
let currentTheme = config.theme ?? DEFAULT_CONFIG.defaultTheme;
|
|
23106
|
+
let currentIdealRange = config.idealRange ?? null;
|
|
23107
|
+
let isRendered = false;
|
|
23108
|
+
let autoRefreshTimer = null;
|
|
23109
|
+
const colors = {
|
|
23110
|
+
...DEFAULT_COLORS[config.domain] ?? DEFAULT_COLORS.energy,
|
|
23111
|
+
...config.colors
|
|
23112
|
+
};
|
|
23113
|
+
function $id(id) {
|
|
23114
|
+
if (config.$container && config.$container[0]) {
|
|
23115
|
+
return config.$container[0].querySelector(`#${id}`);
|
|
23116
|
+
}
|
|
23117
|
+
return document.getElementById(id);
|
|
23118
|
+
}
|
|
23119
|
+
function log(level, ...args) {
|
|
23120
|
+
const prefix = `[${config.domain.toUpperCase()}]`;
|
|
23121
|
+
console[level](prefix, ...args);
|
|
23122
|
+
}
|
|
23123
|
+
function calculateYAxisMax(values) {
|
|
23124
|
+
const maxValue = Math.max(...values, 0);
|
|
23125
|
+
if (config.domain === "temperature") {
|
|
23126
|
+
const tempConfig = config.temperatureConfig;
|
|
23127
|
+
if (tempConfig?.clampRange) {
|
|
23128
|
+
return tempConfig.clampRange.max;
|
|
23129
|
+
}
|
|
23130
|
+
const maxWithThreshold = Math.max(
|
|
23131
|
+
maxValue,
|
|
23132
|
+
tempConfig?.maxThreshold?.value ?? 0,
|
|
23133
|
+
tempConfig?.idealRange?.max ?? 0
|
|
23134
|
+
);
|
|
23135
|
+
return Math.ceil(maxWithThreshold + 5);
|
|
23136
|
+
}
|
|
23137
|
+
if (maxValue === 0) {
|
|
23138
|
+
return config.thresholdForLargeUnit ? config.thresholdForLargeUnit / 2 : 500;
|
|
23139
|
+
}
|
|
23140
|
+
let roundTo;
|
|
23141
|
+
if (config.thresholdForLargeUnit && maxValue >= config.thresholdForLargeUnit) {
|
|
23142
|
+
roundTo = config.thresholdForLargeUnit / 10;
|
|
23143
|
+
} else if (maxValue >= 1e3) {
|
|
23144
|
+
roundTo = 100;
|
|
23145
|
+
} else if (maxValue >= 100) {
|
|
23146
|
+
roundTo = 50;
|
|
23147
|
+
} else if (maxValue >= 10) {
|
|
23148
|
+
roundTo = 10;
|
|
23149
|
+
} else {
|
|
23150
|
+
roundTo = 5;
|
|
23151
|
+
}
|
|
23152
|
+
return Math.ceil(maxValue * 1.1 / roundTo) * roundTo;
|
|
23153
|
+
}
|
|
23154
|
+
function calculateYAxisMin(values) {
|
|
23155
|
+
if (config.domain !== "temperature") {
|
|
23156
|
+
return 0;
|
|
23157
|
+
}
|
|
23158
|
+
const tempConfig = config.temperatureConfig;
|
|
23159
|
+
if (tempConfig?.clampRange) {
|
|
23160
|
+
return tempConfig.clampRange.min;
|
|
23161
|
+
}
|
|
23162
|
+
const minValue = Math.min(...values);
|
|
23163
|
+
const minWithThreshold = Math.min(
|
|
23164
|
+
minValue,
|
|
23165
|
+
tempConfig?.minThreshold?.value ?? minValue,
|
|
23166
|
+
tempConfig?.idealRange?.min ?? minValue
|
|
23167
|
+
);
|
|
23168
|
+
return Math.floor(minWithThreshold - 5);
|
|
23169
|
+
}
|
|
23170
|
+
function buildTemperatureAnnotations() {
|
|
23171
|
+
const tempConfig = config.temperatureConfig;
|
|
23172
|
+
if (!tempConfig || config.domain !== "temperature") {
|
|
23173
|
+
return {};
|
|
23174
|
+
}
|
|
23175
|
+
const annotations = {};
|
|
23176
|
+
const createLineAnnotation = (line, id) => {
|
|
23177
|
+
const borderDash = line.lineStyle === "dashed" ? [6, 6] : line.lineStyle === "dotted" ? [2, 2] : [];
|
|
23178
|
+
return {
|
|
23179
|
+
type: "line",
|
|
23180
|
+
yMin: line.value,
|
|
23181
|
+
yMax: line.value,
|
|
23182
|
+
borderColor: line.color,
|
|
23183
|
+
borderWidth: line.lineWidth ?? 2,
|
|
23184
|
+
borderDash,
|
|
23185
|
+
label: {
|
|
23186
|
+
display: true,
|
|
23187
|
+
content: line.label,
|
|
23188
|
+
position: "end",
|
|
23189
|
+
backgroundColor: line.color,
|
|
23190
|
+
color: "#fff",
|
|
23191
|
+
font: { size: 10, weight: "bold" },
|
|
23192
|
+
padding: { x: 4, y: 2 }
|
|
23193
|
+
}
|
|
23194
|
+
};
|
|
23195
|
+
};
|
|
23196
|
+
if (tempConfig.minThreshold) {
|
|
23197
|
+
annotations["minThreshold"] = createLineAnnotation(tempConfig.minThreshold, "minThreshold");
|
|
23198
|
+
}
|
|
23199
|
+
if (tempConfig.maxThreshold) {
|
|
23200
|
+
annotations["maxThreshold"] = createLineAnnotation(tempConfig.maxThreshold, "maxThreshold");
|
|
23201
|
+
}
|
|
23202
|
+
if (tempConfig.idealRange) {
|
|
23203
|
+
annotations["idealRange"] = {
|
|
23204
|
+
type: "box",
|
|
23205
|
+
yMin: tempConfig.idealRange.min,
|
|
23206
|
+
yMax: tempConfig.idealRange.max,
|
|
23207
|
+
backgroundColor: tempConfig.idealRange.color,
|
|
23208
|
+
borderWidth: 0,
|
|
23209
|
+
label: tempConfig.idealRange.label ? {
|
|
23210
|
+
display: true,
|
|
23211
|
+
content: tempConfig.idealRange.label,
|
|
23212
|
+
position: { x: "start", y: "center" },
|
|
23213
|
+
color: "#666",
|
|
23214
|
+
font: { size: 10 }
|
|
23215
|
+
} : void 0
|
|
23216
|
+
};
|
|
23217
|
+
}
|
|
23218
|
+
return annotations;
|
|
23219
|
+
}
|
|
23220
|
+
function buildIdealRangeAnnotation() {
|
|
23221
|
+
if (!currentIdealRange) {
|
|
23222
|
+
return {};
|
|
23223
|
+
}
|
|
23224
|
+
const { min, max, enabled = true } = currentIdealRange;
|
|
23225
|
+
if (!enabled || min === 0 && max === 0 || min >= max) {
|
|
23226
|
+
return {};
|
|
23227
|
+
}
|
|
23228
|
+
const defaultColors = {
|
|
23229
|
+
temperature: { bg: "rgba(34, 197, 94, 0.15)", border: "rgba(34, 197, 94, 0.4)" },
|
|
23230
|
+
energy: { bg: "rgba(37, 99, 235, 0.1)", border: "rgba(37, 99, 235, 0.3)" },
|
|
23231
|
+
water: { bg: "rgba(2, 136, 209, 0.1)", border: "rgba(2, 136, 209, 0.3)" },
|
|
23232
|
+
gas: { bg: "rgba(234, 88, 12, 0.1)", border: "rgba(234, 88, 12, 0.3)" }
|
|
23233
|
+
};
|
|
23234
|
+
const domainDefaults = defaultColors[config.domain] || defaultColors.energy;
|
|
23235
|
+
return {
|
|
23236
|
+
idealRangeBox: {
|
|
23237
|
+
type: "box",
|
|
23238
|
+
yMin: min,
|
|
23239
|
+
yMax: max,
|
|
23240
|
+
backgroundColor: currentIdealRange.color || domainDefaults.bg,
|
|
23241
|
+
borderColor: currentIdealRange.borderColor || domainDefaults.border,
|
|
23242
|
+
borderWidth: 1,
|
|
23243
|
+
label: currentIdealRange.label ? {
|
|
23244
|
+
display: true,
|
|
23245
|
+
content: currentIdealRange.label,
|
|
23246
|
+
position: { x: "start", y: "center" },
|
|
23247
|
+
color: "#666",
|
|
23248
|
+
font: { size: 10, style: "italic" },
|
|
23249
|
+
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
|
23250
|
+
padding: { x: 4, y: 2 }
|
|
23251
|
+
} : void 0
|
|
23252
|
+
}
|
|
23253
|
+
};
|
|
23254
|
+
}
|
|
23255
|
+
function formatValue(value, includeUnit = true) {
|
|
23256
|
+
const decimals = config.decimalPlaces ?? DEFAULT_CONFIG.decimalPlaces;
|
|
23257
|
+
if (config.unitLarge && config.thresholdForLargeUnit && value >= config.thresholdForLargeUnit) {
|
|
23258
|
+
const converted = value / config.thresholdForLargeUnit;
|
|
23259
|
+
return includeUnit ? `${converted.toFixed(decimals)} ${config.unitLarge}` : converted.toFixed(decimals);
|
|
23260
|
+
}
|
|
23261
|
+
return includeUnit ? `${value.toFixed(decimals)} ${config.unit}` : value.toFixed(decimals);
|
|
23262
|
+
}
|
|
23263
|
+
function formatTickValue(value) {
|
|
23264
|
+
if (config.unitLarge && config.thresholdForLargeUnit && value >= config.thresholdForLargeUnit) {
|
|
23265
|
+
return `${(value / config.thresholdForLargeUnit).toFixed(1)}`;
|
|
23266
|
+
}
|
|
23267
|
+
return value.toFixed(0);
|
|
23268
|
+
}
|
|
23269
|
+
function buildChartConfig(data) {
|
|
23270
|
+
const yAxisMax = calculateYAxisMax(data.dailyTotals);
|
|
23271
|
+
const yAxisMin = calculateYAxisMin(data.dailyTotals);
|
|
23272
|
+
const tension = config.lineTension ?? DEFAULT_CONFIG.lineTension;
|
|
23273
|
+
const pointRadius = config.pointRadius ?? DEFAULT_CONFIG.pointRadius;
|
|
23274
|
+
const borderWidth = config.borderWidth ?? DEFAULT_CONFIG.borderWidth;
|
|
23275
|
+
const fill = config.fill ?? DEFAULT_CONFIG.fill;
|
|
23276
|
+
const showLegend = config.showLegend ?? DEFAULT_CONFIG.showLegend;
|
|
23277
|
+
const themeColors = THEME_COLORS[currentTheme];
|
|
23278
|
+
const isTemperature = config.domain === "temperature";
|
|
23279
|
+
let datasets;
|
|
23280
|
+
if (currentVizMode === "separate" && data.shoppingData && data.shoppingNames) {
|
|
23281
|
+
const shoppingColors = [
|
|
23282
|
+
"#2563eb",
|
|
23283
|
+
"#16a34a",
|
|
23284
|
+
"#ea580c",
|
|
23285
|
+
"#dc2626",
|
|
23286
|
+
"#8b5cf6",
|
|
23287
|
+
"#0891b2",
|
|
23288
|
+
"#65a30d",
|
|
23289
|
+
"#d97706",
|
|
23290
|
+
"#be185d",
|
|
23291
|
+
"#0d9488"
|
|
23292
|
+
];
|
|
23293
|
+
datasets = Object.entries(data.shoppingData).map(([shoppingId, values], index) => ({
|
|
23294
|
+
label: data.shoppingNames?.[shoppingId] || shoppingId,
|
|
23295
|
+
data: values,
|
|
23296
|
+
borderColor: shoppingColors[index % shoppingColors.length],
|
|
23297
|
+
backgroundColor: currentChartType === "line" ? `${shoppingColors[index % shoppingColors.length]}20` : shoppingColors[index % shoppingColors.length],
|
|
23298
|
+
fill: currentChartType === "line" && fill,
|
|
23299
|
+
tension,
|
|
23300
|
+
borderWidth,
|
|
23301
|
+
pointRadius: currentChartType === "line" ? pointRadius : 0,
|
|
23302
|
+
pointBackgroundColor: shoppingColors[index % shoppingColors.length],
|
|
23303
|
+
pointBorderColor: "#fff",
|
|
23304
|
+
pointBorderWidth: 2
|
|
23305
|
+
}));
|
|
23306
|
+
} else {
|
|
23307
|
+
const datasetLabel = isTemperature ? `Temperatura (${config.unit})` : `Consumo (${config.unit})`;
|
|
23308
|
+
datasets = [
|
|
23309
|
+
{
|
|
23310
|
+
label: datasetLabel,
|
|
23311
|
+
data: data.dailyTotals,
|
|
23312
|
+
borderColor: colors.primary,
|
|
23313
|
+
backgroundColor: currentChartType === "line" ? colors.background : colors.primary,
|
|
23314
|
+
fill: currentChartType === "line" && fill,
|
|
23315
|
+
tension,
|
|
23316
|
+
borderWidth,
|
|
23317
|
+
pointRadius: currentChartType === "line" ? pointRadius : 0,
|
|
23318
|
+
pointBackgroundColor: colors.pointBackground || colors.primary,
|
|
23319
|
+
pointBorderColor: colors.pointBorder || "#fff",
|
|
23320
|
+
pointBorderWidth: 2,
|
|
23321
|
+
borderRadius: currentChartType === "bar" ? 4 : 0
|
|
23322
|
+
}
|
|
23323
|
+
];
|
|
23324
|
+
}
|
|
23325
|
+
const temperatureAnnotations = buildTemperatureAnnotations();
|
|
23326
|
+
const idealRangeAnnotations = buildIdealRangeAnnotation();
|
|
23327
|
+
const allAnnotations = { ...temperatureAnnotations, ...idealRangeAnnotations };
|
|
23328
|
+
const yAxisLabel = config.unitLarge && config.thresholdForLargeUnit && yAxisMax >= config.thresholdForLargeUnit ? config.unitLarge : config.unit;
|
|
23329
|
+
return {
|
|
23330
|
+
type: currentChartType,
|
|
23331
|
+
data: {
|
|
23332
|
+
labels: data.labels,
|
|
23333
|
+
datasets
|
|
23334
|
+
},
|
|
23335
|
+
options: {
|
|
23336
|
+
responsive: true,
|
|
23337
|
+
maintainAspectRatio: false,
|
|
23338
|
+
animation: false,
|
|
23339
|
+
// CRITICAL: Prevents infinite growth bug
|
|
23340
|
+
plugins: {
|
|
23341
|
+
legend: {
|
|
23342
|
+
display: showLegend || currentVizMode === "separate",
|
|
23343
|
+
position: "bottom",
|
|
23344
|
+
labels: {
|
|
23345
|
+
color: themeColors.text
|
|
23346
|
+
}
|
|
23347
|
+
},
|
|
23348
|
+
tooltip: {
|
|
23349
|
+
backgroundColor: themeColors.tooltipBackground,
|
|
23350
|
+
titleColor: themeColors.tooltipText,
|
|
23351
|
+
bodyColor: themeColors.tooltipText,
|
|
23352
|
+
borderColor: themeColors.border,
|
|
23353
|
+
borderWidth: 1,
|
|
23354
|
+
callbacks: {
|
|
23355
|
+
label: function(context) {
|
|
23356
|
+
const value = context.parsed.y || 0;
|
|
23357
|
+
const label = context.dataset.label || "";
|
|
23358
|
+
return `${label}: ${formatValue(value)}`;
|
|
23359
|
+
}
|
|
23360
|
+
}
|
|
23361
|
+
},
|
|
23362
|
+
// Reference lines and ideal range (requires chartjs-plugin-annotation)
|
|
23363
|
+
annotation: Object.keys(allAnnotations).length > 0 ? {
|
|
23364
|
+
annotations: allAnnotations
|
|
23365
|
+
} : void 0
|
|
23366
|
+
},
|
|
23367
|
+
scales: {
|
|
23368
|
+
y: {
|
|
23369
|
+
beginAtZero: !isTemperature,
|
|
23370
|
+
// Temperature can have negative values
|
|
23371
|
+
min: yAxisMin,
|
|
23372
|
+
max: yAxisMax,
|
|
23373
|
+
// CRITICAL: Fixed max prevents animation loop
|
|
23374
|
+
grid: {
|
|
23375
|
+
color: themeColors.grid
|
|
23376
|
+
},
|
|
23377
|
+
title: {
|
|
23378
|
+
display: true,
|
|
23379
|
+
text: yAxisLabel,
|
|
23380
|
+
font: { size: 12 },
|
|
23381
|
+
color: themeColors.text
|
|
23382
|
+
},
|
|
23383
|
+
ticks: {
|
|
23384
|
+
font: { size: 11 },
|
|
23385
|
+
color: themeColors.textMuted,
|
|
23386
|
+
callback: function(value) {
|
|
23387
|
+
return formatTickValue(value);
|
|
23388
|
+
}
|
|
23389
|
+
}
|
|
23390
|
+
},
|
|
23391
|
+
x: {
|
|
23392
|
+
grid: {
|
|
23393
|
+
color: themeColors.grid
|
|
23394
|
+
},
|
|
23395
|
+
ticks: {
|
|
23396
|
+
font: { size: 11 },
|
|
23397
|
+
color: themeColors.textMuted
|
|
23398
|
+
}
|
|
23399
|
+
}
|
|
23400
|
+
}
|
|
23401
|
+
}
|
|
23402
|
+
};
|
|
23403
|
+
}
|
|
23404
|
+
function validateChartJs() {
|
|
23405
|
+
if (typeof Chart === "undefined") {
|
|
23406
|
+
log("error", "Chart.js not loaded. Cannot initialize chart.");
|
|
23407
|
+
config.onError?.(new Error("Chart.js not loaded"));
|
|
23408
|
+
return false;
|
|
23409
|
+
}
|
|
23410
|
+
return true;
|
|
23411
|
+
}
|
|
23412
|
+
function validateCanvas() {
|
|
23413
|
+
const canvas = $id(config.containerId);
|
|
23414
|
+
if (!canvas) {
|
|
23415
|
+
log("error", `Canvas #${config.containerId} not found`);
|
|
23416
|
+
config.onError?.(new Error(`Canvas #${config.containerId} not found`));
|
|
23417
|
+
return null;
|
|
23418
|
+
}
|
|
23419
|
+
return canvas;
|
|
23420
|
+
}
|
|
23421
|
+
function setupAutoRefresh() {
|
|
23422
|
+
if (config.autoRefreshInterval && config.autoRefreshInterval > 0) {
|
|
23423
|
+
if (autoRefreshTimer) {
|
|
23424
|
+
clearInterval(autoRefreshTimer);
|
|
23425
|
+
}
|
|
23426
|
+
autoRefreshTimer = setInterval(async () => {
|
|
23427
|
+
log("log", "Auto-refreshing data...");
|
|
23428
|
+
await instance.refresh(true);
|
|
23429
|
+
}, config.autoRefreshInterval);
|
|
23430
|
+
}
|
|
23431
|
+
}
|
|
23432
|
+
function cleanupAutoRefresh() {
|
|
23433
|
+
if (autoRefreshTimer) {
|
|
23434
|
+
clearInterval(autoRefreshTimer);
|
|
23435
|
+
autoRefreshTimer = null;
|
|
23436
|
+
}
|
|
23437
|
+
}
|
|
23438
|
+
function setupButtonHandlers() {
|
|
23439
|
+
if (config.settingsButtonId && config.onSettingsClick) {
|
|
23440
|
+
const settingsBtn = $id(config.settingsButtonId);
|
|
23441
|
+
if (settingsBtn) {
|
|
23442
|
+
settingsBtn.addEventListener("click", () => {
|
|
23443
|
+
log("log", "Settings button clicked");
|
|
23444
|
+
config.onSettingsClick?.();
|
|
23445
|
+
});
|
|
23446
|
+
log("log", "Settings button handler attached");
|
|
23447
|
+
}
|
|
23448
|
+
}
|
|
23449
|
+
if (config.maximizeButtonId && config.onMaximizeClick) {
|
|
23450
|
+
const maximizeBtn = $id(config.maximizeButtonId);
|
|
23451
|
+
if (maximizeBtn) {
|
|
23452
|
+
maximizeBtn.addEventListener("click", () => {
|
|
23453
|
+
log("log", "Maximize button clicked");
|
|
23454
|
+
config.onMaximizeClick?.();
|
|
23455
|
+
});
|
|
23456
|
+
log("log", "Maximize button handler attached");
|
|
23457
|
+
}
|
|
23458
|
+
}
|
|
23459
|
+
const enableExport = config.enableExport ?? DEFAULT_CONFIG.enableExport;
|
|
23460
|
+
if (enableExport && config.exportButtonId) {
|
|
23461
|
+
const exportBtn = $id(config.exportButtonId);
|
|
23462
|
+
if (exportBtn) {
|
|
23463
|
+
exportBtn.addEventListener("click", () => {
|
|
23464
|
+
log("log", "Export button clicked");
|
|
23465
|
+
if (config.onExportCSV && cachedData) {
|
|
23466
|
+
config.onExportCSV(cachedData);
|
|
23467
|
+
} else {
|
|
23468
|
+
instance.exportCSV();
|
|
23469
|
+
}
|
|
23470
|
+
});
|
|
23471
|
+
log("log", "Export button handler attached");
|
|
23472
|
+
}
|
|
23473
|
+
}
|
|
23474
|
+
}
|
|
23475
|
+
function generateCSVContent(data) {
|
|
23476
|
+
const rows = [];
|
|
23477
|
+
const decimals = config.decimalPlaces ?? DEFAULT_CONFIG.decimalPlaces;
|
|
23478
|
+
if (currentVizMode === "separate" && data.shoppingData && data.shoppingNames) {
|
|
23479
|
+
const shoppingHeaders = Object.keys(data.shoppingData).map(
|
|
23480
|
+
(id) => data.shoppingNames?.[id] || id
|
|
23481
|
+
);
|
|
23482
|
+
rows.push(["Data", ...shoppingHeaders, "Total"].join(";"));
|
|
23483
|
+
data.labels.forEach((label, index) => {
|
|
23484
|
+
const shoppingValues = Object.keys(data.shoppingData).map(
|
|
23485
|
+
(id) => data.shoppingData[id][index].toFixed(decimals)
|
|
23486
|
+
);
|
|
23487
|
+
rows.push([label, ...shoppingValues, data.dailyTotals[index].toFixed(decimals)].join(";"));
|
|
23488
|
+
});
|
|
23489
|
+
} else {
|
|
23490
|
+
rows.push(["Data", `Consumo (${config.unit})`].join(";"));
|
|
23491
|
+
data.labels.forEach((label, index) => {
|
|
23492
|
+
rows.push([label, data.dailyTotals[index].toFixed(decimals)].join(";"));
|
|
23493
|
+
});
|
|
23494
|
+
}
|
|
23495
|
+
const total = data.dailyTotals.reduce((sum, v) => sum + v, 0);
|
|
23496
|
+
const avg = total / data.dailyTotals.length;
|
|
23497
|
+
rows.push("");
|
|
23498
|
+
rows.push(["Total", total.toFixed(decimals)].join(";"));
|
|
23499
|
+
rows.push(["M\xE9dia", avg.toFixed(decimals)].join(";"));
|
|
23500
|
+
return rows.join("\n");
|
|
23501
|
+
}
|
|
23502
|
+
function downloadCSV(content, filename) {
|
|
23503
|
+
const BOM = "\uFEFF";
|
|
23504
|
+
const blob = new Blob([BOM + content], { type: "text/csv;charset=utf-8" });
|
|
23505
|
+
const url = URL.createObjectURL(blob);
|
|
23506
|
+
const link = document.createElement("a");
|
|
23507
|
+
link.href = url;
|
|
23508
|
+
link.download = `${filename}.csv`;
|
|
23509
|
+
document.body.appendChild(link);
|
|
23510
|
+
link.click();
|
|
23511
|
+
document.body.removeChild(link);
|
|
23512
|
+
URL.revokeObjectURL(url);
|
|
23513
|
+
log("log", `CSV exported: ${filename}.csv`);
|
|
23514
|
+
}
|
|
23515
|
+
function updateTitle() {
|
|
23516
|
+
if (config.titleElementId) {
|
|
23517
|
+
const titleEl = $id(config.titleElementId);
|
|
23518
|
+
if (titleEl) {
|
|
23519
|
+
if (currentPeriod === 0) {
|
|
23520
|
+
titleEl.textContent = `Consumo - Per\xEDodo Personalizado`;
|
|
23521
|
+
} else {
|
|
23522
|
+
titleEl.textContent = `Consumo dos \xFAltimos ${currentPeriod} dias`;
|
|
23523
|
+
}
|
|
23524
|
+
}
|
|
23525
|
+
}
|
|
23526
|
+
}
|
|
23527
|
+
const instance = {
|
|
23528
|
+
async render() {
|
|
23529
|
+
log("log", "Rendering chart...");
|
|
23530
|
+
if (!validateChartJs()) return;
|
|
23531
|
+
const canvas = validateCanvas();
|
|
23532
|
+
if (!canvas) return;
|
|
23533
|
+
try {
|
|
23534
|
+
log("log", `Fetching ${currentPeriod} days of data...`);
|
|
23535
|
+
cachedData = await config.fetchData(currentPeriod);
|
|
23536
|
+
cachedData.fetchTimestamp = Date.now();
|
|
23537
|
+
if (config.onBeforeRender) {
|
|
23538
|
+
cachedData = config.onBeforeRender(cachedData);
|
|
23539
|
+
}
|
|
23540
|
+
if (chartInstance) {
|
|
23541
|
+
chartInstance.destroy();
|
|
23542
|
+
chartInstance = null;
|
|
23543
|
+
}
|
|
23544
|
+
const ctx = canvas.getContext("2d");
|
|
23545
|
+
const chartConfig = buildChartConfig(cachedData);
|
|
23546
|
+
chartInstance = new Chart(ctx, chartConfig);
|
|
23547
|
+
isRendered = true;
|
|
23548
|
+
const yAxisMax = calculateYAxisMax(cachedData.dailyTotals);
|
|
23549
|
+
log("log", `Chart initialized with yAxisMax: ${yAxisMax}`);
|
|
23550
|
+
config.onDataLoaded?.(cachedData);
|
|
23551
|
+
config.onAfterRender?.(chartInstance);
|
|
23552
|
+
setupButtonHandlers();
|
|
23553
|
+
updateTitle();
|
|
23554
|
+
setupAutoRefresh();
|
|
23555
|
+
} catch (error) {
|
|
23556
|
+
log("error", "Failed to render chart:", error);
|
|
23557
|
+
config.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
23558
|
+
}
|
|
23559
|
+
},
|
|
23560
|
+
async update(data) {
|
|
23561
|
+
if (data) {
|
|
23562
|
+
cachedData = data;
|
|
23563
|
+
cachedData.fetchTimestamp = Date.now();
|
|
23564
|
+
}
|
|
23565
|
+
if (!chartInstance || !cachedData) {
|
|
23566
|
+
log("warn", "Cannot update: chart not initialized or no data");
|
|
23567
|
+
return;
|
|
23568
|
+
}
|
|
23569
|
+
let renderData = cachedData;
|
|
23570
|
+
if (config.onBeforeRender) {
|
|
23571
|
+
renderData = config.onBeforeRender(cachedData);
|
|
23572
|
+
}
|
|
23573
|
+
const chartConfig = buildChartConfig(renderData);
|
|
23574
|
+
chartInstance.data = chartConfig.data;
|
|
23575
|
+
chartInstance.options = chartConfig.options;
|
|
23576
|
+
chartInstance.update("none");
|
|
23577
|
+
log("log", "Chart updated");
|
|
23578
|
+
},
|
|
23579
|
+
setChartType(type) {
|
|
23580
|
+
if (currentChartType === type) return;
|
|
23581
|
+
log("log", `Changing chart type to: ${type}`);
|
|
23582
|
+
currentChartType = type;
|
|
23583
|
+
if (cachedData && chartInstance) {
|
|
23584
|
+
const canvas = validateCanvas();
|
|
23585
|
+
if (canvas) {
|
|
23586
|
+
chartInstance.destroy();
|
|
23587
|
+
const ctx = canvas.getContext("2d");
|
|
23588
|
+
chartInstance = new Chart(ctx, buildChartConfig(cachedData));
|
|
23589
|
+
}
|
|
23590
|
+
}
|
|
23591
|
+
},
|
|
23592
|
+
setVizMode(mode) {
|
|
23593
|
+
if (currentVizMode === mode) return;
|
|
23594
|
+
log("log", `Changing viz mode to: ${mode}`);
|
|
23595
|
+
currentVizMode = mode;
|
|
23596
|
+
if (cachedData) {
|
|
23597
|
+
this.update();
|
|
23598
|
+
}
|
|
23599
|
+
},
|
|
23600
|
+
async setPeriod(days) {
|
|
23601
|
+
if (currentPeriod === days) return;
|
|
23602
|
+
log("log", `Changing period to: ${days} days`);
|
|
23603
|
+
currentPeriod = days;
|
|
23604
|
+
updateTitle();
|
|
23605
|
+
await this.refresh(true);
|
|
23606
|
+
},
|
|
23607
|
+
async refresh(forceRefresh = false) {
|
|
23608
|
+
if (!forceRefresh && cachedData?.fetchTimestamp) {
|
|
23609
|
+
const age = Date.now() - cachedData.fetchTimestamp;
|
|
23610
|
+
const ttl = config.cacheTTL ?? DEFAULT_CONFIG.cacheTTL;
|
|
23611
|
+
if (age < ttl) {
|
|
23612
|
+
log("log", `Using cached data (age: ${Math.round(age / 1e3)}s)`);
|
|
23613
|
+
return;
|
|
23614
|
+
}
|
|
23615
|
+
}
|
|
23616
|
+
log("log", "Refreshing data...");
|
|
23617
|
+
await this.render();
|
|
23618
|
+
},
|
|
23619
|
+
destroy() {
|
|
23620
|
+
log("log", "Destroying chart...");
|
|
23621
|
+
cleanupAutoRefresh();
|
|
23622
|
+
if (chartInstance) {
|
|
23623
|
+
chartInstance.destroy();
|
|
23624
|
+
chartInstance = null;
|
|
23625
|
+
}
|
|
23626
|
+
cachedData = null;
|
|
23627
|
+
isRendered = false;
|
|
23628
|
+
},
|
|
23629
|
+
getChartInstance() {
|
|
23630
|
+
return chartInstance;
|
|
23631
|
+
},
|
|
23632
|
+
getCachedData() {
|
|
23633
|
+
return cachedData;
|
|
23634
|
+
},
|
|
23635
|
+
getState() {
|
|
23636
|
+
return {
|
|
23637
|
+
period: currentPeriod,
|
|
23638
|
+
chartType: currentChartType,
|
|
23639
|
+
vizMode: currentVizMode,
|
|
23640
|
+
theme: currentTheme,
|
|
23641
|
+
isRendered
|
|
23642
|
+
};
|
|
23643
|
+
},
|
|
23644
|
+
exportCSV(filename) {
|
|
23645
|
+
if (!cachedData) {
|
|
23646
|
+
log("warn", "Cannot export: no data available");
|
|
23647
|
+
return;
|
|
23648
|
+
}
|
|
23649
|
+
const defaultFilename = config.exportFilename || `${config.domain}-consumo-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
23650
|
+
const csvContent = generateCSVContent(cachedData);
|
|
23651
|
+
downloadCSV(csvContent, filename || defaultFilename);
|
|
23652
|
+
},
|
|
23653
|
+
setTheme(theme) {
|
|
23654
|
+
if (currentTheme === theme) return;
|
|
23655
|
+
log("log", `Changing theme to: ${theme}`);
|
|
23656
|
+
currentTheme = theme;
|
|
23657
|
+
if (cachedData && chartInstance) {
|
|
23658
|
+
const canvas = validateCanvas();
|
|
23659
|
+
if (canvas) {
|
|
23660
|
+
chartInstance.destroy();
|
|
23661
|
+
const ctx = canvas.getContext("2d");
|
|
23662
|
+
chartInstance = new Chart(ctx, buildChartConfig(cachedData));
|
|
23663
|
+
}
|
|
23664
|
+
}
|
|
23665
|
+
},
|
|
23666
|
+
setIdealRange(range) {
|
|
23667
|
+
const rangeChanged = JSON.stringify(currentIdealRange) !== JSON.stringify(range);
|
|
23668
|
+
if (!rangeChanged) return;
|
|
23669
|
+
if (range) {
|
|
23670
|
+
log("log", `Setting ideal range: ${range.min} - ${range.max}`);
|
|
23671
|
+
} else {
|
|
23672
|
+
log("log", "Clearing ideal range");
|
|
23673
|
+
}
|
|
23674
|
+
currentIdealRange = range;
|
|
23675
|
+
if (cachedData && chartInstance) {
|
|
23676
|
+
const canvas = validateCanvas();
|
|
23677
|
+
if (canvas) {
|
|
23678
|
+
chartInstance.destroy();
|
|
23679
|
+
const ctx = canvas.getContext("2d");
|
|
23680
|
+
chartInstance = new Chart(ctx, buildChartConfig(cachedData));
|
|
23681
|
+
}
|
|
23682
|
+
}
|
|
23683
|
+
},
|
|
23684
|
+
getIdealRange() {
|
|
23685
|
+
return currentIdealRange;
|
|
23686
|
+
}
|
|
23687
|
+
};
|
|
23688
|
+
return instance;
|
|
23689
|
+
}
|
|
23690
|
+
|
|
23691
|
+
// src/components/Consumption7DaysChart/createConsumptionModal.ts
|
|
23692
|
+
var DOMAIN_CONFIG3 = {
|
|
23693
|
+
energy: { name: "Energia", icon: "\u26A1" },
|
|
23694
|
+
water: { name: "\xC1gua", icon: "\u{1F4A7}" },
|
|
23695
|
+
gas: { name: "G\xE1s", icon: "\u{1F525}" },
|
|
23696
|
+
temperature: { name: "Temperatura", icon: "\u{1F321}\uFE0F" }
|
|
23697
|
+
};
|
|
23698
|
+
function createConsumptionModal(config) {
|
|
23699
|
+
const modalId = `myio-consumption-modal-${Date.now()}`;
|
|
23700
|
+
let modalElement = null;
|
|
23701
|
+
let chartInstance = null;
|
|
23702
|
+
let headerInstance = null;
|
|
23703
|
+
let currentTheme = config.theme ?? "light";
|
|
23704
|
+
let currentChartType = config.defaultChartType ?? "line";
|
|
23705
|
+
let currentVizMode = config.defaultVizMode ?? "total";
|
|
23706
|
+
let isMaximized = false;
|
|
23707
|
+
const domainCfg = DOMAIN_CONFIG3[config.domain] || { name: config.domain, icon: "\u{1F4CA}" };
|
|
23708
|
+
const title = config.title || `${domainCfg.name} - Hist\xF3rico de Consumo`;
|
|
23709
|
+
function getThemeColors2() {
|
|
23710
|
+
return THEME_COLORS[currentTheme];
|
|
23711
|
+
}
|
|
23712
|
+
function renderModal4() {
|
|
23713
|
+
const colors = getThemeColors2();
|
|
23714
|
+
const exportFormats = config.exportFormats || ["csv"];
|
|
23715
|
+
headerInstance = createModalHeader({
|
|
23716
|
+
id: modalId,
|
|
23717
|
+
title,
|
|
23718
|
+
icon: domainCfg.icon,
|
|
23719
|
+
theme: currentTheme,
|
|
23720
|
+
isMaximized,
|
|
23721
|
+
exportFormats,
|
|
23722
|
+
onExport: (format) => {
|
|
23723
|
+
if (config.onExport) {
|
|
23724
|
+
config.onExport(format);
|
|
23725
|
+
} else {
|
|
23726
|
+
if (format === "csv") {
|
|
23727
|
+
chartInstance?.exportCSV();
|
|
23728
|
+
} else {
|
|
23729
|
+
console.warn(`[ConsumptionModal] Export format "${format}" requires custom onExport handler`);
|
|
23730
|
+
}
|
|
23731
|
+
}
|
|
23732
|
+
},
|
|
23733
|
+
onThemeToggle: (theme) => {
|
|
23734
|
+
currentTheme = theme;
|
|
23735
|
+
chartInstance?.setTheme(currentTheme);
|
|
23736
|
+
updateModal();
|
|
23737
|
+
},
|
|
23738
|
+
onMaximize: (maximized) => {
|
|
23739
|
+
isMaximized = maximized;
|
|
23740
|
+
updateModal();
|
|
23741
|
+
},
|
|
23742
|
+
onClose: () => {
|
|
23743
|
+
instance.close();
|
|
23744
|
+
}
|
|
23745
|
+
});
|
|
23746
|
+
return `
|
|
23747
|
+
<div class="myio-consumption-modal-overlay" style="
|
|
23748
|
+
position: fixed;
|
|
23749
|
+
top: 0;
|
|
23750
|
+
left: 0;
|
|
23751
|
+
width: 100%;
|
|
23752
|
+
height: 100%;
|
|
23753
|
+
background: rgba(0, 0, 0, 0.5);
|
|
23754
|
+
backdrop-filter: blur(2px);
|
|
23755
|
+
z-index: 99998;
|
|
23756
|
+
display: flex;
|
|
23757
|
+
justify-content: center;
|
|
23758
|
+
align-items: center;
|
|
23759
|
+
">
|
|
23760
|
+
<div class="myio-consumption-modal-content" style="
|
|
23761
|
+
background: ${colors.chartBackground};
|
|
23762
|
+
border-radius: ${isMaximized ? "0" : "10px"};
|
|
23763
|
+
width: ${isMaximized ? "100%" : "95%"};
|
|
23764
|
+
max-width: ${isMaximized ? "100%" : "1200px"};
|
|
23765
|
+
height: ${isMaximized ? "100%" : "85vh"};
|
|
23766
|
+
display: flex;
|
|
23767
|
+
flex-direction: column;
|
|
23768
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
23769
|
+
overflow: hidden;
|
|
23770
|
+
">
|
|
23771
|
+
<!-- MyIO Premium Header (using ModalHeader component) -->
|
|
23772
|
+
${headerInstance.render()}
|
|
23773
|
+
|
|
23774
|
+
<!-- Controls Bar -->
|
|
23775
|
+
<div class="myio-consumption-modal-controls" style="
|
|
23776
|
+
display: flex;
|
|
23777
|
+
gap: 16px;
|
|
23778
|
+
padding: 12px 16px;
|
|
23779
|
+
background: ${currentTheme === "dark" ? "#374151" : "#f7f7f7"};
|
|
23780
|
+
border-bottom: 1px solid ${colors.border};
|
|
23781
|
+
align-items: center;
|
|
23782
|
+
flex-wrap: wrap;
|
|
23783
|
+
">
|
|
23784
|
+
<!-- Viz Mode Tabs -->
|
|
23785
|
+
<div style="display: flex; gap: 2px; background: ${currentTheme === "dark" ? "#4b5563" : "#e5e7eb"}; border-radius: 8px; padding: 2px;">
|
|
23786
|
+
<button id="${modalId}-viz-total" style="
|
|
23787
|
+
padding: 6px 12px;
|
|
23788
|
+
border: none;
|
|
23789
|
+
border-radius: 6px;
|
|
23790
|
+
font-size: 13px;
|
|
23791
|
+
cursor: pointer;
|
|
23792
|
+
transition: all 0.2s;
|
|
23793
|
+
background: ${currentVizMode === "total" ? "#3e1a7d" : "transparent"};
|
|
23794
|
+
color: ${currentVizMode === "total" ? "white" : colors.text};
|
|
23795
|
+
">Consolidado</button>
|
|
23796
|
+
<button id="${modalId}-viz-separate" style="
|
|
23797
|
+
padding: 6px 12px;
|
|
23798
|
+
border: none;
|
|
23799
|
+
border-radius: 6px;
|
|
23800
|
+
font-size: 13px;
|
|
23801
|
+
cursor: pointer;
|
|
23802
|
+
transition: all 0.2s;
|
|
23803
|
+
background: ${currentVizMode === "separate" ? "#3e1a7d" : "transparent"};
|
|
23804
|
+
color: ${currentVizMode === "separate" ? "white" : colors.text};
|
|
23805
|
+
">Por Shopping</button>
|
|
23806
|
+
</div>
|
|
23807
|
+
|
|
23808
|
+
<!-- Chart Type Tabs -->
|
|
23809
|
+
<div style="display: flex; gap: 2px; background: ${currentTheme === "dark" ? "#4b5563" : "#e5e7eb"}; border-radius: 8px; padding: 2px;">
|
|
23810
|
+
<button id="${modalId}-type-line" style="
|
|
23811
|
+
padding: 6px 12px;
|
|
23812
|
+
border: none;
|
|
23813
|
+
border-radius: 6px;
|
|
23814
|
+
font-size: 13px;
|
|
23815
|
+
cursor: pointer;
|
|
23816
|
+
transition: all 0.2s;
|
|
23817
|
+
background: ${currentChartType === "line" ? "#3e1a7d" : "transparent"};
|
|
23818
|
+
color: ${currentChartType === "line" ? "white" : colors.text};
|
|
23819
|
+
">Linhas</button>
|
|
23820
|
+
<button id="${modalId}-type-bar" style="
|
|
23821
|
+
padding: 6px 12px;
|
|
23822
|
+
border: none;
|
|
23823
|
+
border-radius: 6px;
|
|
23824
|
+
font-size: 13px;
|
|
23825
|
+
cursor: pointer;
|
|
23826
|
+
transition: all 0.2s;
|
|
23827
|
+
background: ${currentChartType === "bar" ? "#3e1a7d" : "transparent"};
|
|
23828
|
+
color: ${currentChartType === "bar" ? "white" : colors.text};
|
|
23829
|
+
">Barras</button>
|
|
23830
|
+
</div>
|
|
23831
|
+
</div>
|
|
23832
|
+
|
|
23833
|
+
<!-- Chart Container -->
|
|
23834
|
+
<div style="
|
|
23835
|
+
flex: 1;
|
|
23836
|
+
padding: 16px;
|
|
23837
|
+
min-height: 0;
|
|
23838
|
+
position: relative;
|
|
23839
|
+
background: ${colors.chartBackground};
|
|
23840
|
+
">
|
|
23841
|
+
<canvas id="${modalId}-chart" style="width: 100%; height: 100%;"></canvas>
|
|
23842
|
+
</div>
|
|
23843
|
+
</div>
|
|
23844
|
+
</div>
|
|
23845
|
+
`;
|
|
23846
|
+
}
|
|
23847
|
+
function setupListeners() {
|
|
23848
|
+
if (!modalElement) return;
|
|
23849
|
+
headerInstance?.attachListeners();
|
|
23850
|
+
document.getElementById(`${modalId}-viz-total`)?.addEventListener("click", () => {
|
|
23851
|
+
currentVizMode = "total";
|
|
23852
|
+
chartInstance?.setVizMode("total");
|
|
23853
|
+
updateControlStyles();
|
|
23854
|
+
});
|
|
23855
|
+
document.getElementById(`${modalId}-viz-separate`)?.addEventListener("click", () => {
|
|
23856
|
+
currentVizMode = "separate";
|
|
23857
|
+
chartInstance?.setVizMode("separate");
|
|
23858
|
+
updateControlStyles();
|
|
23859
|
+
});
|
|
23860
|
+
document.getElementById(`${modalId}-type-line`)?.addEventListener("click", () => {
|
|
23861
|
+
currentChartType = "line";
|
|
23862
|
+
chartInstance?.setChartType("line");
|
|
23863
|
+
updateControlStyles();
|
|
23864
|
+
});
|
|
23865
|
+
document.getElementById(`${modalId}-type-bar`)?.addEventListener("click", () => {
|
|
23866
|
+
currentChartType = "bar";
|
|
23867
|
+
chartInstance?.setChartType("bar");
|
|
23868
|
+
updateControlStyles();
|
|
23869
|
+
});
|
|
23870
|
+
modalElement.querySelector(".myio-consumption-modal-overlay")?.addEventListener("click", (e) => {
|
|
23871
|
+
if (e.target.classList.contains("myio-consumption-modal-overlay")) {
|
|
23872
|
+
instance.close();
|
|
23873
|
+
}
|
|
23874
|
+
});
|
|
23875
|
+
const handleKeydown = (e) => {
|
|
23876
|
+
if (e.key === "Escape") {
|
|
23877
|
+
instance.close();
|
|
23878
|
+
}
|
|
23879
|
+
};
|
|
23880
|
+
document.addEventListener("keydown", handleKeydown);
|
|
23881
|
+
modalElement.__handleKeydown = handleKeydown;
|
|
23882
|
+
}
|
|
23883
|
+
function updateControlStyles() {
|
|
23884
|
+
const colors = getThemeColors2();
|
|
23885
|
+
const vizTotalBtn = document.getElementById(`${modalId}-viz-total`);
|
|
23886
|
+
const vizSeparateBtn = document.getElementById(`${modalId}-viz-separate`);
|
|
23887
|
+
if (vizTotalBtn) {
|
|
23888
|
+
vizTotalBtn.style.background = currentVizMode === "total" ? "#3e1a7d" : "transparent";
|
|
23889
|
+
vizTotalBtn.style.color = currentVizMode === "total" ? "white" : colors.text;
|
|
23890
|
+
}
|
|
23891
|
+
if (vizSeparateBtn) {
|
|
23892
|
+
vizSeparateBtn.style.background = currentVizMode === "separate" ? "#3e1a7d" : "transparent";
|
|
23893
|
+
vizSeparateBtn.style.color = currentVizMode === "separate" ? "white" : colors.text;
|
|
23894
|
+
}
|
|
23895
|
+
const typeLineBtn = document.getElementById(`${modalId}-type-line`);
|
|
23896
|
+
const typeBarBtn = document.getElementById(`${modalId}-type-bar`);
|
|
23897
|
+
if (typeLineBtn) {
|
|
23898
|
+
typeLineBtn.style.background = currentChartType === "line" ? "#3e1a7d" : "transparent";
|
|
23899
|
+
typeLineBtn.style.color = currentChartType === "line" ? "white" : colors.text;
|
|
23900
|
+
}
|
|
23901
|
+
if (typeBarBtn) {
|
|
23902
|
+
typeBarBtn.style.background = currentChartType === "bar" ? "#3e1a7d" : "transparent";
|
|
23903
|
+
typeBarBtn.style.color = currentChartType === "bar" ? "white" : colors.text;
|
|
23904
|
+
}
|
|
23905
|
+
}
|
|
23906
|
+
function updateModal() {
|
|
23907
|
+
if (!modalElement) return;
|
|
23908
|
+
const cachedData = chartInstance?.getCachedData();
|
|
23909
|
+
headerInstance?.destroy();
|
|
23910
|
+
chartInstance?.destroy();
|
|
23911
|
+
modalElement.innerHTML = renderModal4();
|
|
23912
|
+
setupListeners();
|
|
23913
|
+
if (cachedData) {
|
|
23914
|
+
chartInstance = createConsumption7DaysChart({
|
|
23915
|
+
...config,
|
|
23916
|
+
containerId: `${modalId}-chart`,
|
|
23917
|
+
theme: currentTheme,
|
|
23918
|
+
defaultChartType: currentChartType,
|
|
23919
|
+
defaultVizMode: currentVizMode
|
|
23920
|
+
});
|
|
23921
|
+
chartInstance.update(cachedData);
|
|
23922
|
+
}
|
|
23923
|
+
}
|
|
23924
|
+
const instance = {
|
|
23925
|
+
async open() {
|
|
23926
|
+
modalElement = document.createElement("div");
|
|
23927
|
+
modalElement.id = modalId;
|
|
23928
|
+
modalElement.innerHTML = renderModal4();
|
|
23929
|
+
const container = config.container || document.body;
|
|
23930
|
+
container.appendChild(modalElement);
|
|
23931
|
+
setupListeners();
|
|
23932
|
+
chartInstance = createConsumption7DaysChart({
|
|
23933
|
+
...config,
|
|
23934
|
+
containerId: `${modalId}-chart`,
|
|
23935
|
+
theme: currentTheme,
|
|
23936
|
+
defaultChartType: currentChartType,
|
|
23937
|
+
defaultVizMode: currentVizMode
|
|
23938
|
+
});
|
|
23939
|
+
await chartInstance.render();
|
|
23940
|
+
},
|
|
23941
|
+
close() {
|
|
23942
|
+
if (modalElement) {
|
|
23943
|
+
const handleKeydown = modalElement.__handleKeydown;
|
|
23944
|
+
if (handleKeydown) {
|
|
23945
|
+
document.removeEventListener("keydown", handleKeydown);
|
|
23946
|
+
}
|
|
23947
|
+
headerInstance?.destroy();
|
|
23948
|
+
headerInstance = null;
|
|
23949
|
+
chartInstance?.destroy();
|
|
23950
|
+
chartInstance = null;
|
|
23951
|
+
modalElement.remove();
|
|
23952
|
+
modalElement = null;
|
|
23953
|
+
config.onClose?.();
|
|
23954
|
+
}
|
|
23955
|
+
},
|
|
23956
|
+
getChart() {
|
|
23957
|
+
return chartInstance;
|
|
23958
|
+
},
|
|
23959
|
+
destroy() {
|
|
23960
|
+
instance.close();
|
|
23961
|
+
}
|
|
23962
|
+
};
|
|
23963
|
+
return instance;
|
|
23964
|
+
}
|
|
23965
|
+
|
|
23966
|
+
// src/components/ExportData/index.ts
|
|
23967
|
+
var DEFAULT_COLORS3 = {
|
|
23968
|
+
primary: "#3e1a7d",
|
|
23969
|
+
// MyIO purple
|
|
23970
|
+
secondary: "#6b4c9a",
|
|
23971
|
+
// Light purple
|
|
23972
|
+
accent: "#00bcd4",
|
|
23973
|
+
// Cyan accent
|
|
23974
|
+
background: "#ffffff",
|
|
23975
|
+
// White
|
|
23976
|
+
text: "#333333",
|
|
23977
|
+
// Dark gray
|
|
23978
|
+
chartColors: ["#3e1a7d", "#00bcd4", "#4caf50", "#ff9800", "#e91e63", "#9c27b0"]
|
|
23979
|
+
};
|
|
23980
|
+
var DOMAIN_ICONS = {
|
|
23981
|
+
energy: "\u26A1",
|
|
23982
|
+
// Lightning bolt
|
|
23983
|
+
water: "\u{1F4A7}",
|
|
23984
|
+
// Water drop
|
|
23985
|
+
temperature: "\u{1F321}\uFE0F"
|
|
23986
|
+
// Thermometer
|
|
23987
|
+
};
|
|
23988
|
+
var DOMAIN_LABELS = {
|
|
23989
|
+
energy: "Energia",
|
|
23990
|
+
water: "\xC1gua",
|
|
23991
|
+
temperature: "Temperatura"
|
|
23992
|
+
};
|
|
23993
|
+
var DOMAIN_LABELS_EN = {
|
|
23994
|
+
energy: "Energy",
|
|
23995
|
+
water: "Water",
|
|
23996
|
+
temperature: "Temperature"
|
|
23997
|
+
};
|
|
23998
|
+
var DOMAIN_UNITS = {
|
|
23999
|
+
energy: "kWh",
|
|
24000
|
+
water: "m\xB3",
|
|
24001
|
+
temperature: "\xB0C"
|
|
24002
|
+
};
|
|
24003
|
+
var CSV_SEPARATORS = {
|
|
24004
|
+
"pt-BR": ";",
|
|
24005
|
+
"en-US": ",",
|
|
24006
|
+
"default": ";"
|
|
24007
|
+
};
|
|
24008
|
+
function formatDateForFilename(date) {
|
|
24009
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
24010
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
|
|
24011
|
+
}
|
|
24012
|
+
function sanitizeFilename(str) {
|
|
24013
|
+
return str.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, "_").substring(0, 50);
|
|
24014
|
+
}
|
|
24015
|
+
function generateFilename(data, config) {
|
|
24016
|
+
const timestamp = formatDateForFilename(/* @__PURE__ */ new Date());
|
|
24017
|
+
const domainLabel = DOMAIN_LABELS_EN[config.domain].toUpperCase();
|
|
24018
|
+
const ext = config.formatExport;
|
|
24019
|
+
let baseName = "export";
|
|
24020
|
+
if ("device" in data && data.device) {
|
|
24021
|
+
const device = data.device;
|
|
24022
|
+
const label = device.label || device.name || "device";
|
|
24023
|
+
const identifier = device.identifier ? `-${device.identifier}` : "";
|
|
24024
|
+
baseName = `${sanitizeFilename(label)}${identifier}`;
|
|
24025
|
+
} else if ("customer" in data && data.customer?.customerName) {
|
|
24026
|
+
baseName = sanitizeFilename(data.customer.customerName);
|
|
24027
|
+
} else if ("groupName" in data) {
|
|
24028
|
+
baseName = sanitizeFilename(data.groupName);
|
|
24029
|
+
}
|
|
24030
|
+
return `${baseName}-${domainLabel}-${timestamp}.${ext}`;
|
|
24031
|
+
}
|
|
24032
|
+
function normalizeTimestamp(ts) {
|
|
24033
|
+
if (ts instanceof Date) return ts;
|
|
24034
|
+
if (typeof ts === "number") return new Date(ts);
|
|
24035
|
+
return new Date(ts);
|
|
24036
|
+
}
|
|
24037
|
+
function calculateStats2(dataPoints) {
|
|
24038
|
+
if (dataPoints.length === 0) {
|
|
24039
|
+
return { min: 0, max: 0, average: 0, sum: 0, count: 0 };
|
|
24040
|
+
}
|
|
24041
|
+
const values = dataPoints.map((d) => d.value);
|
|
24042
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
24043
|
+
return {
|
|
24044
|
+
min: Math.min(...values),
|
|
24045
|
+
max: Math.max(...values),
|
|
24046
|
+
average: sum / values.length,
|
|
24047
|
+
sum,
|
|
24048
|
+
count: values.length
|
|
24049
|
+
};
|
|
24050
|
+
}
|
|
24051
|
+
function formatNumber2(value, locale, decimals = 2) {
|
|
24052
|
+
return new Intl.NumberFormat(locale, {
|
|
24053
|
+
minimumFractionDigits: decimals,
|
|
24054
|
+
maximumFractionDigits: decimals
|
|
24055
|
+
}).format(value);
|
|
24056
|
+
}
|
|
24057
|
+
function formatDate3(date, locale) {
|
|
24058
|
+
return new Intl.DateTimeFormat(locale, {
|
|
24059
|
+
year: "numeric",
|
|
24060
|
+
month: "2-digit",
|
|
24061
|
+
day: "2-digit",
|
|
24062
|
+
hour: "2-digit",
|
|
24063
|
+
minute: "2-digit"
|
|
24064
|
+
}).format(date);
|
|
24065
|
+
}
|
|
24066
|
+
function generateCSV(data, config) {
|
|
24067
|
+
const sep = CSV_SEPARATORS[config.locale] || CSV_SEPARATORS["default"];
|
|
24068
|
+
const rows = [];
|
|
24069
|
+
const escapeCSV = (val) => {
|
|
24070
|
+
const str = String(val ?? "");
|
|
24071
|
+
if (str.includes(sep) || str.includes('"') || str.includes("\n")) {
|
|
24072
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
24073
|
+
}
|
|
24074
|
+
return str;
|
|
24075
|
+
};
|
|
24076
|
+
const formatNumCSV = (val) => {
|
|
24077
|
+
return formatNumber2(val, config.locale);
|
|
24078
|
+
};
|
|
24079
|
+
if ("device" in data && "data" in data && Array.isArray(data.data)) {
|
|
24080
|
+
const deviceData = data;
|
|
24081
|
+
rows.push(["Timestamp", config.domainLabel, `Unit (${config.domainUnit})`]);
|
|
24082
|
+
for (const point of deviceData.data) {
|
|
24083
|
+
const ts = normalizeTimestamp(point.timestamp);
|
|
24084
|
+
rows.push([
|
|
24085
|
+
formatDate3(ts, config.locale),
|
|
24086
|
+
formatNumCSV(point.value),
|
|
24087
|
+
point.unit || config.domainUnit
|
|
24088
|
+
]);
|
|
24089
|
+
}
|
|
24090
|
+
if (config.includeStats) {
|
|
24091
|
+
const stats = calculateStats2(deviceData.data);
|
|
24092
|
+
rows.push([]);
|
|
24093
|
+
rows.push(["Statistics", "", ""]);
|
|
24094
|
+
rows.push(["Minimum", formatNumCSV(stats.min), config.domainUnit]);
|
|
24095
|
+
rows.push(["Maximum", formatNumCSV(stats.max), config.domainUnit]);
|
|
24096
|
+
rows.push(["Average", formatNumCSV(stats.average), config.domainUnit]);
|
|
24097
|
+
rows.push(["Total", formatNumCSV(stats.sum), config.domainUnit]);
|
|
24098
|
+
rows.push(["Count", String(stats.count), "points"]);
|
|
24099
|
+
}
|
|
24100
|
+
} else if ("devices" in data && Array.isArray(data.devices)) {
|
|
24101
|
+
const compData = data;
|
|
24102
|
+
const deviceHeaders = compData.devices.map(
|
|
24103
|
+
(d) => d.device.label || d.device.name || "Device"
|
|
24104
|
+
);
|
|
24105
|
+
rows.push(["Timestamp", ...deviceHeaders]);
|
|
24106
|
+
const allTimestamps = /* @__PURE__ */ new Set();
|
|
24107
|
+
compData.devices.forEach((d) => {
|
|
24108
|
+
d.data.forEach((point) => {
|
|
24109
|
+
allTimestamps.add(normalizeTimestamp(point.timestamp).getTime());
|
|
24110
|
+
});
|
|
24111
|
+
});
|
|
24112
|
+
const sortedTimestamps = Array.from(allTimestamps).sort((a, b) => a - b);
|
|
24113
|
+
for (const ts of sortedTimestamps) {
|
|
24114
|
+
const row = [formatDate3(new Date(ts), config.locale)];
|
|
24115
|
+
for (const device of compData.devices) {
|
|
24116
|
+
const point = device.data.find(
|
|
24117
|
+
(p) => normalizeTimestamp(p.timestamp).getTime() === ts
|
|
24118
|
+
);
|
|
24119
|
+
row.push(point ? formatNumCSV(point.value) : "");
|
|
24120
|
+
}
|
|
24121
|
+
rows.push(row);
|
|
24122
|
+
}
|
|
24123
|
+
}
|
|
24124
|
+
return rows.map((row) => row.map(escapeCSV).join(sep)).join("\r\n");
|
|
24125
|
+
}
|
|
24126
|
+
function generateXLSX(data, config) {
|
|
24127
|
+
return generateCSV(data, config);
|
|
24128
|
+
}
|
|
24129
|
+
function generatePDFContent(data, config) {
|
|
24130
|
+
const { colors, domainIcon, domainLabel, domainUnit, locale, includeStats, includeChart } = config;
|
|
24131
|
+
let deviceLabel = "Export";
|
|
24132
|
+
let customerName = "";
|
|
24133
|
+
let identifier = "";
|
|
24134
|
+
let dataPoints = [];
|
|
24135
|
+
if ("device" in data && "data" in data) {
|
|
24136
|
+
const deviceData = data;
|
|
24137
|
+
deviceLabel = deviceData.device.label || deviceData.device.name || "Device";
|
|
24138
|
+
identifier = deviceData.device.identifier || "";
|
|
24139
|
+
customerName = deviceData.customer?.customerName || "";
|
|
24140
|
+
dataPoints = deviceData.data;
|
|
24141
|
+
}
|
|
24142
|
+
const stats = calculateStats2(dataPoints);
|
|
24143
|
+
const tableRows = dataPoints.slice(0, 100).map((point) => {
|
|
24144
|
+
const ts = normalizeTimestamp(point.timestamp);
|
|
24145
|
+
return `
|
|
24146
|
+
<tr>
|
|
24147
|
+
<td style="padding: 8px; border-bottom: 1px solid #eee;">${formatDate3(ts, locale)}</td>
|
|
24148
|
+
<td style="padding: 8px; border-bottom: 1px solid #eee; text-align: right;">${formatNumber2(point.value, locale)}</td>
|
|
24149
|
+
<td style="padding: 8px; border-bottom: 1px solid #eee;">${point.unit || domainUnit}</td>
|
|
24150
|
+
</tr>
|
|
24151
|
+
`;
|
|
24152
|
+
}).join("");
|
|
24153
|
+
const statsSection = includeStats ? `
|
|
24154
|
+
<div style="margin-top: 24px; padding: 16px; background: #f5f5f5; border-radius: 8px;">
|
|
24155
|
+
<h3 style="margin: 0 0 12px 0; color: ${colors.primary};">Statistics</h3>
|
|
24156
|
+
<table style="width: 100%;">
|
|
24157
|
+
<tr>
|
|
24158
|
+
<td><strong>Minimum:</strong></td>
|
|
24159
|
+
<td>${formatNumber2(stats.min, locale)} ${domainUnit}</td>
|
|
24160
|
+
<td><strong>Maximum:</strong></td>
|
|
24161
|
+
<td>${formatNumber2(stats.max, locale)} ${domainUnit}</td>
|
|
24162
|
+
</tr>
|
|
24163
|
+
<tr>
|
|
24164
|
+
<td><strong>Average:</strong></td>
|
|
24165
|
+
<td>${formatNumber2(stats.average, locale)} ${domainUnit}</td>
|
|
24166
|
+
<td><strong>Total:</strong></td>
|
|
24167
|
+
<td>${formatNumber2(stats.sum, locale)} ${domainUnit}</td>
|
|
24168
|
+
</tr>
|
|
24169
|
+
</table>
|
|
24170
|
+
</div>
|
|
24171
|
+
` : "";
|
|
24172
|
+
return `
|
|
24173
|
+
<!DOCTYPE html>
|
|
24174
|
+
<html>
|
|
24175
|
+
<head>
|
|
24176
|
+
<meta charset="UTF-8">
|
|
24177
|
+
<title>${deviceLabel} - ${domainLabel} Report</title>
|
|
24178
|
+
<style>
|
|
24179
|
+
body {
|
|
24180
|
+
font-family: 'Roboto', Arial, sans-serif;
|
|
24181
|
+
margin: 0;
|
|
24182
|
+
padding: 24px;
|
|
24183
|
+
color: ${colors.text};
|
|
24184
|
+
background: ${colors.background};
|
|
24185
|
+
}
|
|
24186
|
+
.header {
|
|
24187
|
+
background: ${colors.primary};
|
|
24188
|
+
color: white;
|
|
24189
|
+
padding: 20px;
|
|
24190
|
+
border-radius: 8px;
|
|
24191
|
+
margin-bottom: 24px;
|
|
24192
|
+
}
|
|
24193
|
+
.header h1 {
|
|
24194
|
+
margin: 0;
|
|
24195
|
+
font-size: 24px;
|
|
24196
|
+
}
|
|
24197
|
+
.header .subtitle {
|
|
24198
|
+
opacity: 0.9;
|
|
24199
|
+
margin-top: 8px;
|
|
24200
|
+
}
|
|
24201
|
+
.device-info {
|
|
24202
|
+
display: flex;
|
|
24203
|
+
gap: 16px;
|
|
24204
|
+
margin-bottom: 16px;
|
|
24205
|
+
padding: 12px;
|
|
24206
|
+
background: #f5f5f5;
|
|
24207
|
+
border-radius: 8px;
|
|
24208
|
+
}
|
|
24209
|
+
.device-info span {
|
|
24210
|
+
padding: 4px 12px;
|
|
24211
|
+
background: ${colors.secondary};
|
|
24212
|
+
color: white;
|
|
24213
|
+
border-radius: 4px;
|
|
24214
|
+
font-size: 14px;
|
|
24215
|
+
}
|
|
24216
|
+
table {
|
|
24217
|
+
width: 100%;
|
|
24218
|
+
border-collapse: collapse;
|
|
24219
|
+
}
|
|
24220
|
+
th {
|
|
24221
|
+
background: ${colors.primary};
|
|
24222
|
+
color: white;
|
|
24223
|
+
padding: 12px 8px;
|
|
24224
|
+
text-align: left;
|
|
24225
|
+
}
|
|
24226
|
+
th:nth-child(2) {
|
|
24227
|
+
text-align: right;
|
|
24228
|
+
}
|
|
24229
|
+
.footer {
|
|
24230
|
+
margin-top: 24px;
|
|
24231
|
+
padding-top: 16px;
|
|
24232
|
+
border-top: 1px solid #eee;
|
|
24233
|
+
text-align: center;
|
|
24234
|
+
font-size: 12px;
|
|
24235
|
+
color: #999;
|
|
24236
|
+
}
|
|
24237
|
+
@media print {
|
|
24238
|
+
body { padding: 0; }
|
|
24239
|
+
.header { border-radius: 0; }
|
|
24240
|
+
}
|
|
24241
|
+
</style>
|
|
24242
|
+
</head>
|
|
24243
|
+
<body>
|
|
24244
|
+
<div class="header">
|
|
24245
|
+
<h1>${domainIcon} ${deviceLabel}</h1>
|
|
24246
|
+
<div class="subtitle">${domainLabel} Report - Generated ${formatDate3(/* @__PURE__ */ new Date(), locale)}</div>
|
|
24247
|
+
</div>
|
|
24248
|
+
|
|
24249
|
+
${customerName ? `<div class="customer-name" style="margin-bottom: 16px; font-size: 18px;"><strong>Customer:</strong> ${customerName}</div>` : ""}
|
|
24250
|
+
|
|
24251
|
+
${identifier ? `
|
|
24252
|
+
<div class="device-info">
|
|
24253
|
+
<span>ID: ${identifier}</span>
|
|
24254
|
+
<span>Domain: ${domainLabel}</span>
|
|
24255
|
+
<span>Unit: ${domainUnit}</span>
|
|
24256
|
+
</div>
|
|
24257
|
+
` : ""}
|
|
24258
|
+
|
|
24259
|
+
<table>
|
|
24260
|
+
<thead>
|
|
24261
|
+
<tr>
|
|
24262
|
+
<th>Timestamp</th>
|
|
24263
|
+
<th>${domainLabel} (${domainUnit})</th>
|
|
24264
|
+
<th>Unit</th>
|
|
24265
|
+
</tr>
|
|
24266
|
+
</thead>
|
|
24267
|
+
<tbody>
|
|
24268
|
+
${tableRows}
|
|
24269
|
+
${dataPoints.length > 100 ? `<tr><td colspan="3" style="text-align: center; padding: 16px; color: #999;">... and ${dataPoints.length - 100} more rows</td></tr>` : ""}
|
|
24270
|
+
</tbody>
|
|
24271
|
+
</table>
|
|
24272
|
+
|
|
24273
|
+
${statsSection}
|
|
24274
|
+
|
|
24275
|
+
<div class="footer">
|
|
24276
|
+
<p>${config.footerText || "Generated by MyIO Platform"}</p>
|
|
24277
|
+
</div>
|
|
24278
|
+
</body>
|
|
24279
|
+
</html>
|
|
24280
|
+
`;
|
|
24281
|
+
}
|
|
24282
|
+
function buildTemplateExport(params) {
|
|
24283
|
+
const {
|
|
24284
|
+
domain,
|
|
24285
|
+
formatExport,
|
|
24286
|
+
typeExport,
|
|
24287
|
+
colorsPallet,
|
|
24288
|
+
locale = "pt-BR",
|
|
24289
|
+
includeChart = formatExport === "pdf",
|
|
24290
|
+
includeStats = true,
|
|
24291
|
+
headerText,
|
|
24292
|
+
footerText
|
|
24293
|
+
} = params;
|
|
24294
|
+
const colors = {
|
|
24295
|
+
...DEFAULT_COLORS3,
|
|
24296
|
+
...colorsPallet,
|
|
24297
|
+
chartColors: colorsPallet?.chartColors || DEFAULT_COLORS3.chartColors
|
|
24298
|
+
};
|
|
24299
|
+
return {
|
|
24300
|
+
domain,
|
|
24301
|
+
formatExport,
|
|
24302
|
+
typeExport,
|
|
24303
|
+
colors,
|
|
24304
|
+
locale,
|
|
24305
|
+
includeChart,
|
|
24306
|
+
includeStats,
|
|
24307
|
+
headerText: headerText || `${DOMAIN_LABELS[domain]} Report`,
|
|
24308
|
+
footerText: footerText || "Generated by MyIO Platform",
|
|
24309
|
+
domainIcon: DOMAIN_ICONS[domain],
|
|
24310
|
+
domainLabel: DOMAIN_LABELS[domain],
|
|
24311
|
+
domainUnit: DOMAIN_UNITS[domain]
|
|
24312
|
+
};
|
|
24313
|
+
}
|
|
24314
|
+
function myioExportData(data, config, options) {
|
|
24315
|
+
const filename = generateFilename(data, config);
|
|
24316
|
+
let allDataPoints = [];
|
|
24317
|
+
if ("data" in data && Array.isArray(data.data)) {
|
|
24318
|
+
allDataPoints = data.data;
|
|
24319
|
+
} else if ("devices" in data && Array.isArray(data.devices)) {
|
|
24320
|
+
allDataPoints = data.devices.flatMap((d) => d.data);
|
|
24321
|
+
}
|
|
24322
|
+
const stats = calculateStats2(allDataPoints);
|
|
24323
|
+
const instance = {
|
|
24324
|
+
async export() {
|
|
24325
|
+
try {
|
|
24326
|
+
options?.onProgress?.(10, "Generating content...");
|
|
24327
|
+
let content;
|
|
24328
|
+
let mimeType;
|
|
24329
|
+
let finalFilename = filename;
|
|
24330
|
+
switch (config.formatExport) {
|
|
24331
|
+
case "csv":
|
|
24332
|
+
content = generateCSV(data, config);
|
|
24333
|
+
mimeType = "text/csv;charset=utf-8;";
|
|
24334
|
+
break;
|
|
24335
|
+
case "xlsx":
|
|
24336
|
+
content = generateXLSX(data, config);
|
|
24337
|
+
mimeType = "text/csv;charset=utf-8;";
|
|
24338
|
+
finalFilename = filename.replace(".xlsx", ".csv");
|
|
24339
|
+
break;
|
|
24340
|
+
case "pdf":
|
|
24341
|
+
content = generatePDFContent(data, config);
|
|
24342
|
+
mimeType = "text/html;charset=utf-8;";
|
|
24343
|
+
finalFilename = filename.replace(".pdf", ".html");
|
|
24344
|
+
break;
|
|
24345
|
+
default:
|
|
24346
|
+
throw new Error(`Unsupported format: ${config.formatExport}`);
|
|
24347
|
+
}
|
|
24348
|
+
options?.onProgress?.(80, "Creating file...");
|
|
24349
|
+
const bom = config.formatExport === "csv" ? "\uFEFF" : "";
|
|
24350
|
+
const blob = new Blob([bom + content], { type: mimeType });
|
|
24351
|
+
options?.onProgress?.(100, "Export complete");
|
|
24352
|
+
return {
|
|
24353
|
+
success: true,
|
|
24354
|
+
filename: finalFilename,
|
|
24355
|
+
blob,
|
|
24356
|
+
dataUrl: URL.createObjectURL(blob)
|
|
24357
|
+
};
|
|
24358
|
+
} catch (error) {
|
|
24359
|
+
return {
|
|
24360
|
+
success: false,
|
|
24361
|
+
filename,
|
|
24362
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
24363
|
+
};
|
|
24364
|
+
}
|
|
24365
|
+
},
|
|
24366
|
+
async download() {
|
|
24367
|
+
const result = await this.export();
|
|
24368
|
+
if (!result.success || !result.blob) {
|
|
24369
|
+
console.error("Export failed:", result.error);
|
|
24370
|
+
return;
|
|
24371
|
+
}
|
|
24372
|
+
const link = document.createElement("a");
|
|
24373
|
+
link.href = URL.createObjectURL(result.blob);
|
|
24374
|
+
link.download = result.filename;
|
|
24375
|
+
link.style.display = "none";
|
|
24376
|
+
document.body.appendChild(link);
|
|
24377
|
+
link.click();
|
|
24378
|
+
document.body.removeChild(link);
|
|
24379
|
+
URL.revokeObjectURL(link.href);
|
|
24380
|
+
},
|
|
24381
|
+
async preview() {
|
|
24382
|
+
if (config.formatExport !== "pdf") {
|
|
24383
|
+
return null;
|
|
24384
|
+
}
|
|
24385
|
+
const result = await this.export();
|
|
24386
|
+
return result.dataUrl || null;
|
|
24387
|
+
},
|
|
24388
|
+
getStats() {
|
|
24389
|
+
return stats;
|
|
24390
|
+
},
|
|
24391
|
+
getFilename() {
|
|
24392
|
+
return filename;
|
|
24393
|
+
}
|
|
24394
|
+
};
|
|
24395
|
+
if (options?.autoDownload) {
|
|
24396
|
+
instance.download();
|
|
24397
|
+
}
|
|
24398
|
+
return instance;
|
|
24399
|
+
}
|
|
24400
|
+
var EXPORT_DEFAULT_COLORS = DEFAULT_COLORS3;
|
|
24401
|
+
var EXPORT_DOMAIN_ICONS = DOMAIN_ICONS;
|
|
24402
|
+
var EXPORT_DOMAIN_LABELS = DOMAIN_LABELS;
|
|
24403
|
+
var EXPORT_DOMAIN_UNITS = DOMAIN_UNITS;
|
|
22467
24404
|
// Annotate the CommonJS export names for ESM import in node:
|
|
22468
24405
|
0 && (module.exports = {
|
|
22469
24406
|
CHART_COLORS,
|
|
24407
|
+
CONSUMPTION_CHART_COLORS,
|
|
24408
|
+
CONSUMPTION_CHART_DEFAULTS,
|
|
24409
|
+
CONSUMPTION_THEME_COLORS,
|
|
22470
24410
|
ConnectionStatusType,
|
|
22471
24411
|
DEFAULT_CLAMP_RANGE,
|
|
22472
24412
|
DeviceStatusType,
|
|
24413
|
+
EXPORT_DEFAULT_COLORS,
|
|
24414
|
+
EXPORT_DOMAIN_ICONS,
|
|
24415
|
+
EXPORT_DOMAIN_LABELS,
|
|
24416
|
+
EXPORT_DOMAIN_UNITS,
|
|
22473
24417
|
MyIOChartModal,
|
|
22474
24418
|
MyIODraggableCard,
|
|
22475
24419
|
MyIOSelectionStore,
|
|
@@ -22481,11 +24425,13 @@ function openTemperatureSettingsModal(params) {
|
|
|
22481
24425
|
averageByDay,
|
|
22482
24426
|
buildListItemsThingsboardByUniqueDatasource,
|
|
22483
24427
|
buildMyioIngestionAuth,
|
|
24428
|
+
buildTemplateExport,
|
|
22484
24429
|
buildWaterReportCSV,
|
|
22485
24430
|
buildWaterStoresCSV,
|
|
22486
24431
|
calcDeltaPercent,
|
|
22487
24432
|
calculateDeviceStatus,
|
|
22488
24433
|
calculateDeviceStatusWithRanges,
|
|
24434
|
+
calculateExportStats,
|
|
22489
24435
|
calculateStats,
|
|
22490
24436
|
clampTemperature,
|
|
22491
24437
|
classify,
|
|
@@ -22493,8 +24439,11 @@ function openTemperatureSettingsModal(params) {
|
|
|
22493
24439
|
classifyWaterLabels,
|
|
22494
24440
|
clearAllAuthCaches,
|
|
22495
24441
|
connectionStatusIcons,
|
|
24442
|
+
createConsumption7DaysChart,
|
|
24443
|
+
createConsumptionModal,
|
|
22496
24444
|
createDateRangePicker,
|
|
22497
24445
|
createInputDateRangePickerInsideDIV,
|
|
24446
|
+
createModalHeader,
|
|
22498
24447
|
decodePayload,
|
|
22499
24448
|
decodePayloadBase64Xor,
|
|
22500
24449
|
detectDeviceType,
|
|
@@ -22508,6 +24457,7 @@ function openTemperatureSettingsModal(params) {
|
|
|
22508
24457
|
fetchThingsboardCustomerAttrsFromStorage,
|
|
22509
24458
|
fetchThingsboardCustomerServerScopeAttrs,
|
|
22510
24459
|
findValue,
|
|
24460
|
+
findValueWithDefault,
|
|
22511
24461
|
fmtPerc,
|
|
22512
24462
|
fmtPercLegacy,
|
|
22513
24463
|
formatAllInSameUnit,
|
|
@@ -22515,18 +24465,24 @@ function openTemperatureSettingsModal(params) {
|
|
|
22515
24465
|
formatDateForInput,
|
|
22516
24466
|
formatDateToYMD,
|
|
22517
24467
|
formatDateWithTimezoneOffset,
|
|
24468
|
+
formatDuration,
|
|
22518
24469
|
formatEnergy,
|
|
22519
24470
|
formatNumberReadable,
|
|
24471
|
+
formatRelativeTime,
|
|
22520
24472
|
formatTankHeadFromCm,
|
|
22521
24473
|
formatTemperature,
|
|
24474
|
+
formatWater,
|
|
22522
24475
|
formatWaterByGroup,
|
|
22523
24476
|
formatWaterVolumeM3,
|
|
24477
|
+
formatarDuracao,
|
|
24478
|
+
generateExportFilename,
|
|
22524
24479
|
getAuthCacheStats,
|
|
22525
24480
|
getAvailableContexts,
|
|
22526
24481
|
getConnectionStatusIcon,
|
|
22527
24482
|
getDateRangeArray,
|
|
22528
24483
|
getDeviceStatusIcon,
|
|
22529
24484
|
getDeviceStatusInfo,
|
|
24485
|
+
getModalHeaderStyles,
|
|
22530
24486
|
getSaoPauloISOString,
|
|
22531
24487
|
getSaoPauloISOStringFixed,
|
|
22532
24488
|
getValueByDatakey,
|
|
@@ -22538,8 +24494,10 @@ function openTemperatureSettingsModal(params) {
|
|
|
22538
24494
|
isValidConnectionStatus,
|
|
22539
24495
|
isValidDeviceStatus,
|
|
22540
24496
|
isWaterCategory,
|
|
24497
|
+
mapConnectionStatus,
|
|
22541
24498
|
mapDeviceStatusToCardStatus,
|
|
22542
24499
|
mapDeviceToConnectionStatus,
|
|
24500
|
+
myioExportData,
|
|
22543
24501
|
normalizeRecipients,
|
|
22544
24502
|
numbers,
|
|
22545
24503
|
openDashboardPopup,
|