myio-js-library 0.1.141 → 0.1.143
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 +691 -75
- package/dist/index.d.cts +44 -1
- package/dist/index.js +690 -75
- package/dist/myio-js-library.umd.js +690 -75
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7428,7 +7428,10 @@ async function openRealTimeTelemetryModal(params) {
|
|
|
7428
7428
|
if (!telemetryData || telemetryData.length === 0) continue;
|
|
7429
7429
|
const latest = telemetryData[0];
|
|
7430
7430
|
const config = TELEMETRY_CONFIG[key] || { label: key, unit: "", icon: "\u{1F4CA}", decimals: 2 };
|
|
7431
|
-
|
|
7431
|
+
let numValue = Number(latest.value) || 0;
|
|
7432
|
+
if (key === "total_current" || key === "current") {
|
|
7433
|
+
numValue = numValue / 1e3;
|
|
7434
|
+
}
|
|
7432
7435
|
const formatted = numValue.toFixed(config.decimals);
|
|
7433
7436
|
values.push({
|
|
7434
7437
|
key,
|
|
@@ -17074,13 +17077,30 @@ var DefaultSettingsPersister = class {
|
|
|
17074
17077
|
jwtToken;
|
|
17075
17078
|
tbBaseUrl;
|
|
17076
17079
|
deviceType;
|
|
17080
|
+
deviceProfile;
|
|
17077
17081
|
existingMapInstantaneousPower;
|
|
17078
17082
|
constructor(jwtToken, apiConfig) {
|
|
17079
17083
|
this.jwtToken = jwtToken;
|
|
17080
17084
|
this.tbBaseUrl = apiConfig?.tbBaseUrl || window.location.origin;
|
|
17081
17085
|
this.deviceType = apiConfig?.deviceType || "ELEVADOR";
|
|
17086
|
+
this.deviceProfile = apiConfig?.deviceProfile || null;
|
|
17082
17087
|
this.existingMapInstantaneousPower = apiConfig?.mapInstantaneousPower || null;
|
|
17083
17088
|
}
|
|
17089
|
+
/**
|
|
17090
|
+
* RFC-0086: Resolve effective device type
|
|
17091
|
+
* When deviceType is 3F_MEDIDOR, use deviceProfile as the actual type
|
|
17092
|
+
*/
|
|
17093
|
+
getEffectiveDeviceType() {
|
|
17094
|
+
const normalizedType = (this.deviceType || "").toUpperCase();
|
|
17095
|
+
if (normalizedType === "3F_MEDIDOR") {
|
|
17096
|
+
const profile = (this.deviceProfile || "").toUpperCase();
|
|
17097
|
+
if (profile && profile !== "N/D" && profile.trim() !== "") {
|
|
17098
|
+
console.log(`[SettingsPersister] RFC-0086: Resolved 3F_MEDIDOR \u2192 ${profile}`);
|
|
17099
|
+
return profile;
|
|
17100
|
+
}
|
|
17101
|
+
}
|
|
17102
|
+
return normalizedType || "ELEVADOR";
|
|
17103
|
+
}
|
|
17084
17104
|
async saveEntityLabel(deviceId, label) {
|
|
17085
17105
|
try {
|
|
17086
17106
|
const getRes = await fetch(`${this.tbBaseUrl}/api/device/${deviceId}`, {
|
|
@@ -17138,72 +17158,59 @@ var DefaultSettingsPersister = class {
|
|
|
17138
17158
|
}
|
|
17139
17159
|
}
|
|
17140
17160
|
/**
|
|
17141
|
-
* RFC-
|
|
17142
|
-
*
|
|
17161
|
+
* RFC-0086: Build mapInstantaneousPower JSON structure from form data
|
|
17162
|
+
* IMPORTANT: When saving to a DEVICE, only include entries for the specific deviceType
|
|
17163
|
+
* Uses getEffectiveDeviceType() to resolve 3F_MEDIDOR → deviceProfile
|
|
17143
17164
|
*/
|
|
17144
17165
|
buildMapInstantaneousPower(formData) {
|
|
17145
|
-
const
|
|
17146
|
-
version: "1.0.0",
|
|
17147
|
-
limitsByInstantaneoustPowerType: []
|
|
17148
|
-
};
|
|
17166
|
+
const effectiveDeviceType = this.getEffectiveDeviceType();
|
|
17149
17167
|
const telemetryType = String(formData.telemetryType || "consumption");
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
|
|
17170
|
-
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
|
|
17176
|
-
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17188
|
-
|
|
17189
|
-
|
|
17190
|
-
|
|
17191
|
-
|
|
17192
|
-
topValue: Number(formData.alertLimitUpConsumption) || 0
|
|
17193
|
-
}
|
|
17194
|
-
},
|
|
17195
|
-
{
|
|
17196
|
-
deviceStatusName: "failure",
|
|
17197
|
-
limitsValues: {
|
|
17198
|
-
baseValue: Number(formData.failureLimitDownConsumption) || 0,
|
|
17199
|
-
topValue: Number(formData.failureLimitUpConsumption) || 0
|
|
17168
|
+
const result = {
|
|
17169
|
+
version: "1.0.0",
|
|
17170
|
+
limitsByInstantaneoustPowerType: [
|
|
17171
|
+
{
|
|
17172
|
+
telemetryType,
|
|
17173
|
+
itemsByDeviceType: [
|
|
17174
|
+
{
|
|
17175
|
+
deviceType: effectiveDeviceType,
|
|
17176
|
+
name: `mapInstantaneousPower${this.formatDeviceTypeName(effectiveDeviceType)}`,
|
|
17177
|
+
description: formData.identifier ? `Limites customizados para ${formData.identifier}` : `Limites de pot\xEAncia customizados para ${effectiveDeviceType}`,
|
|
17178
|
+
limitsByDeviceStatus: [
|
|
17179
|
+
{
|
|
17180
|
+
deviceStatusName: "standBy",
|
|
17181
|
+
limitsValues: {
|
|
17182
|
+
baseValue: Number(formData.standbyLimitDownConsumption) || 0,
|
|
17183
|
+
topValue: Number(formData.standbyLimitUpConsumption) || 0
|
|
17184
|
+
}
|
|
17185
|
+
},
|
|
17186
|
+
{
|
|
17187
|
+
deviceStatusName: "normal",
|
|
17188
|
+
limitsValues: {
|
|
17189
|
+
baseValue: Number(formData.normalLimitDownConsumption) || 0,
|
|
17190
|
+
topValue: Number(formData.normalLimitUpConsumption) || 0
|
|
17191
|
+
}
|
|
17192
|
+
},
|
|
17193
|
+
{
|
|
17194
|
+
deviceStatusName: "alert",
|
|
17195
|
+
limitsValues: {
|
|
17196
|
+
baseValue: Number(formData.alertLimitDownConsumption) || 0,
|
|
17197
|
+
topValue: Number(formData.alertLimitUpConsumption) || 0
|
|
17198
|
+
}
|
|
17199
|
+
},
|
|
17200
|
+
{
|
|
17201
|
+
deviceStatusName: "failure",
|
|
17202
|
+
limitsValues: {
|
|
17203
|
+
baseValue: Number(formData.failureLimitDownConsumption) || 0,
|
|
17204
|
+
topValue: Number(formData.failureLimitUpConsumption) || 0
|
|
17205
|
+
}
|
|
17206
|
+
}
|
|
17207
|
+
]
|
|
17208
|
+
}
|
|
17209
|
+
]
|
|
17200
17210
|
}
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17204
|
-
deviceConfig.description = `Limites customizados para ${formData.identifier}`;
|
|
17205
|
-
}
|
|
17206
|
-
console.log("[SettingsPersister] RFC-0080: Built mapInstantaneousPower:", result);
|
|
17211
|
+
]
|
|
17212
|
+
};
|
|
17213
|
+
console.log(`[SettingsPersister] RFC-0086: Built mapInstantaneousPower for deviceType=${effectiveDeviceType}:`, result);
|
|
17207
17214
|
return result;
|
|
17208
17215
|
}
|
|
17209
17216
|
/**
|
|
@@ -17442,6 +17449,8 @@ var SettingsController = class {
|
|
|
17442
17449
|
const apiConfigWithDeviceInfo = {
|
|
17443
17450
|
...params.api,
|
|
17444
17451
|
deviceType: params.deviceType,
|
|
17452
|
+
deviceProfile: params.deviceProfile,
|
|
17453
|
+
// RFC-0086: For 3F_MEDIDOR → deviceProfile resolution
|
|
17445
17454
|
mapInstantaneousPower: params.mapInstantaneousPower
|
|
17446
17455
|
};
|
|
17447
17456
|
this.persister = params.persister || new DefaultSettingsPersister(params.jwtToken, apiConfigWithDeviceInfo);
|
|
@@ -17493,6 +17502,8 @@ var SettingsController = class {
|
|
|
17493
17502
|
this.persister = new DefaultSettingsPersister(this.params.jwtToken, {
|
|
17494
17503
|
...this.params.api,
|
|
17495
17504
|
deviceType: this.params.deviceType,
|
|
17505
|
+
deviceProfile: this.params.deviceProfile,
|
|
17506
|
+
// RFC-0086: For 3F_MEDIDOR → deviceProfile resolution
|
|
17496
17507
|
mapInstantaneousPower: globalMap
|
|
17497
17508
|
});
|
|
17498
17509
|
this.view.updateMapInstantaneousPower(globalMap);
|
|
@@ -18148,13 +18159,13 @@ function openGoalsPanel(params) {
|
|
|
18148
18159
|
function initializeModal() {
|
|
18149
18160
|
if (data) {
|
|
18150
18161
|
modalState.goalsData = data;
|
|
18151
|
-
|
|
18162
|
+
renderModal4();
|
|
18152
18163
|
} else {
|
|
18153
|
-
|
|
18164
|
+
renderModal4();
|
|
18154
18165
|
loadGoalsData();
|
|
18155
18166
|
}
|
|
18156
18167
|
}
|
|
18157
|
-
function
|
|
18168
|
+
function renderModal4() {
|
|
18158
18169
|
const existing = document.getElementById("myio-goals-panel-modal");
|
|
18159
18170
|
if (existing) {
|
|
18160
18171
|
existing.remove();
|
|
@@ -20154,8 +20165,12 @@ function drawChart(modalId, state) {
|
|
|
20154
20165
|
const paddingBottom = 55;
|
|
20155
20166
|
const isPeriodsFiltered = state.selectedPeriods.length < 4 && state.selectedPeriods.length > 0;
|
|
20156
20167
|
const values = chartData.map((d) => d.y);
|
|
20157
|
-
const
|
|
20158
|
-
const
|
|
20168
|
+
const dataMin = Math.min(...values);
|
|
20169
|
+
const dataMax = Math.max(...values);
|
|
20170
|
+
const thresholdMin = state.temperatureMin !== null ? state.temperatureMin : dataMin;
|
|
20171
|
+
const thresholdMax = state.temperatureMax !== null ? state.temperatureMax : dataMax;
|
|
20172
|
+
const minY = Math.min(dataMin, thresholdMin) - 1;
|
|
20173
|
+
const maxY = Math.max(dataMax, thresholdMax) + 1;
|
|
20159
20174
|
const chartWidth = width - paddingLeft - paddingRight;
|
|
20160
20175
|
const chartHeight = height - paddingTop - paddingBottom;
|
|
20161
20176
|
const scaleY = chartHeight / (maxY - minY || 1);
|
|
@@ -20496,8 +20511,10 @@ async function openTemperatureComparisonModal(params) {
|
|
|
20496
20511
|
deviceData: [],
|
|
20497
20512
|
isLoading: true,
|
|
20498
20513
|
dateRangePicker: null,
|
|
20499
|
-
selectedPeriods: ["madrugada", "manha", "tarde", "noite"]
|
|
20514
|
+
selectedPeriods: ["madrugada", "manha", "tarde", "noite"],
|
|
20500
20515
|
// All periods selected by default
|
|
20516
|
+
temperatureMin: params.temperatureMin ?? null,
|
|
20517
|
+
temperatureMax: params.temperatureMax ?? null
|
|
20501
20518
|
};
|
|
20502
20519
|
const savedGranularity = localStorage.getItem("myio-temp-comparison-granularity");
|
|
20503
20520
|
const savedTheme = localStorage.getItem("myio-temp-comparison-theme");
|
|
@@ -20893,16 +20910,52 @@ function drawComparisonChart(modalId, state) {
|
|
|
20893
20910
|
return;
|
|
20894
20911
|
}
|
|
20895
20912
|
const isPeriodsFiltered = state.selectedPeriods.length < 4 && state.selectedPeriods.length > 0;
|
|
20896
|
-
let
|
|
20897
|
-
let
|
|
20913
|
+
let dataMinY = Infinity;
|
|
20914
|
+
let dataMaxY = -Infinity;
|
|
20898
20915
|
processedData.forEach(({ points }) => {
|
|
20899
20916
|
points.forEach((point) => {
|
|
20900
|
-
if (point.y <
|
|
20901
|
-
if (point.y >
|
|
20917
|
+
if (point.y < dataMinY) dataMinY = point.y;
|
|
20918
|
+
if (point.y > dataMaxY) dataMaxY = point.y;
|
|
20919
|
+
});
|
|
20920
|
+
});
|
|
20921
|
+
const rangeMap = /* @__PURE__ */ new Map();
|
|
20922
|
+
state.deviceData.forEach((dd, index) => {
|
|
20923
|
+
const device = dd.device;
|
|
20924
|
+
const min = device.temperatureMin;
|
|
20925
|
+
const max = device.temperatureMax;
|
|
20926
|
+
if (min !== void 0 && min !== null && max !== void 0 && max !== null) {
|
|
20927
|
+
const key = `${min}-${max}`;
|
|
20928
|
+
if (!rangeMap.has(key)) {
|
|
20929
|
+
rangeMap.set(key, {
|
|
20930
|
+
min,
|
|
20931
|
+
max,
|
|
20932
|
+
customerName: device.customerName || "",
|
|
20933
|
+
color: CHART_COLORS[index % CHART_COLORS.length],
|
|
20934
|
+
deviceLabels: [device.label]
|
|
20935
|
+
});
|
|
20936
|
+
} else {
|
|
20937
|
+
rangeMap.get(key).deviceLabels.push(device.label);
|
|
20938
|
+
}
|
|
20939
|
+
}
|
|
20940
|
+
});
|
|
20941
|
+
if (rangeMap.size === 0 && state.temperatureMin !== null && state.temperatureMax !== null) {
|
|
20942
|
+
rangeMap.set("global", {
|
|
20943
|
+
min: state.temperatureMin,
|
|
20944
|
+
max: state.temperatureMax,
|
|
20945
|
+
customerName: "Global",
|
|
20946
|
+
color: colors.success,
|
|
20947
|
+
deviceLabels: []
|
|
20902
20948
|
});
|
|
20949
|
+
}
|
|
20950
|
+
const temperatureRanges = Array.from(rangeMap.values());
|
|
20951
|
+
let thresholdMinY = dataMinY;
|
|
20952
|
+
let thresholdMaxY = dataMaxY;
|
|
20953
|
+
temperatureRanges.forEach((range) => {
|
|
20954
|
+
if (range.min < thresholdMinY) thresholdMinY = range.min;
|
|
20955
|
+
if (range.max > thresholdMaxY) thresholdMaxY = range.max;
|
|
20903
20956
|
});
|
|
20904
|
-
globalMinY = Math.floor(
|
|
20905
|
-
globalMaxY = Math.ceil(
|
|
20957
|
+
const globalMinY = Math.floor(Math.min(dataMinY, thresholdMinY)) - 1;
|
|
20958
|
+
const globalMaxY = Math.ceil(Math.max(dataMaxY, thresholdMaxY)) + 1;
|
|
20906
20959
|
const chartWidth = width - paddingLeft - paddingRight;
|
|
20907
20960
|
const chartHeight = height - paddingTop - paddingBottom;
|
|
20908
20961
|
const scaleY = chartHeight / (globalMaxY - globalMinY || 1);
|
|
@@ -20942,6 +20995,41 @@ function drawComparisonChart(modalId, state) {
|
|
|
20942
20995
|
ctx.lineTo(width - paddingRight, y);
|
|
20943
20996
|
ctx.stroke();
|
|
20944
20997
|
}
|
|
20998
|
+
const rangeColors = [
|
|
20999
|
+
{ fill: "rgba(76, 175, 80, 0.12)", stroke: "#4CAF50" },
|
|
21000
|
+
// Green
|
|
21001
|
+
{ fill: "rgba(33, 150, 243, 0.12)", stroke: "#2196F3" },
|
|
21002
|
+
// Blue
|
|
21003
|
+
{ fill: "rgba(255, 152, 0, 0.12)", stroke: "#FF9800" },
|
|
21004
|
+
// Orange
|
|
21005
|
+
{ fill: "rgba(156, 39, 176, 0.12)", stroke: "#9C27B0" }
|
|
21006
|
+
// Purple
|
|
21007
|
+
];
|
|
21008
|
+
temperatureRanges.forEach((range, index) => {
|
|
21009
|
+
const rangeMinY = height - paddingBottom - (range.min - globalMinY) * scaleY;
|
|
21010
|
+
const rangeMaxY = height - paddingBottom - (range.max - globalMinY) * scaleY;
|
|
21011
|
+
const colorSet = rangeColors[index % rangeColors.length];
|
|
21012
|
+
ctx.fillStyle = colorSet.fill;
|
|
21013
|
+
ctx.fillRect(paddingLeft, rangeMaxY, chartWidth, rangeMinY - rangeMaxY);
|
|
21014
|
+
ctx.strokeStyle = colorSet.stroke;
|
|
21015
|
+
ctx.lineWidth = 1.5;
|
|
21016
|
+
ctx.setLineDash([6, 4]);
|
|
21017
|
+
ctx.beginPath();
|
|
21018
|
+
ctx.moveTo(paddingLeft, rangeMinY);
|
|
21019
|
+
ctx.lineTo(width - paddingRight, rangeMinY);
|
|
21020
|
+
ctx.moveTo(paddingLeft, rangeMaxY);
|
|
21021
|
+
ctx.lineTo(width - paddingRight, rangeMaxY);
|
|
21022
|
+
ctx.stroke();
|
|
21023
|
+
ctx.setLineDash([]);
|
|
21024
|
+
if (temperatureRanges.length > 1 || range.customerName) {
|
|
21025
|
+
ctx.fillStyle = colorSet.stroke;
|
|
21026
|
+
ctx.font = "10px system-ui, sans-serif";
|
|
21027
|
+
ctx.textAlign = "left";
|
|
21028
|
+
const labelY = (rangeMinY + rangeMaxY) / 2;
|
|
21029
|
+
const labelText = range.customerName || `${range.min}\xB0-${range.max}\xB0C`;
|
|
21030
|
+
ctx.fillText(labelText, width - paddingRight + 5, labelY + 3);
|
|
21031
|
+
}
|
|
21032
|
+
});
|
|
20945
21033
|
processedData.forEach(({ device, points }) => {
|
|
20946
21034
|
ctx.strokeStyle = device.color;
|
|
20947
21035
|
ctx.lineWidth = 2.5;
|
|
@@ -21261,6 +21349,532 @@ function exportComparisonCSV(state) {
|
|
|
21261
21349
|
document.body.removeChild(link);
|
|
21262
21350
|
URL.revokeObjectURL(url);
|
|
21263
21351
|
}
|
|
21352
|
+
|
|
21353
|
+
// src/components/temperature/TemperatureSettingsModal.ts
|
|
21354
|
+
var DARK_THEME2 = {
|
|
21355
|
+
modalBg: "linear-gradient(180deg, #1e1e2e 0%, #151521 100%)",
|
|
21356
|
+
headerBg: "#3e1a7d",
|
|
21357
|
+
textPrimary: "#ffffff",
|
|
21358
|
+
textSecondary: "rgba(255, 255, 255, 0.7)",
|
|
21359
|
+
textMuted: "rgba(255, 255, 255, 0.5)",
|
|
21360
|
+
inputBg: "rgba(255, 255, 255, 0.08)",
|
|
21361
|
+
inputBorder: "rgba(255, 255, 255, 0.2)",
|
|
21362
|
+
inputText: "#ffffff",
|
|
21363
|
+
buttonPrimary: "#3e1a7d",
|
|
21364
|
+
buttonPrimaryHover: "#5a2da8",
|
|
21365
|
+
buttonSecondary: "rgba(255, 255, 255, 0.1)",
|
|
21366
|
+
success: "#4CAF50",
|
|
21367
|
+
error: "#f44336",
|
|
21368
|
+
overlay: "rgba(0, 0, 0, 0.85)"
|
|
21369
|
+
};
|
|
21370
|
+
var LIGHT_THEME2 = {
|
|
21371
|
+
modalBg: "#ffffff",
|
|
21372
|
+
headerBg: "#3e1a7d",
|
|
21373
|
+
textPrimary: "#1a1a2e",
|
|
21374
|
+
textSecondary: "rgba(0, 0, 0, 0.7)",
|
|
21375
|
+
textMuted: "rgba(0, 0, 0, 0.5)",
|
|
21376
|
+
inputBg: "#f5f5f5",
|
|
21377
|
+
inputBorder: "rgba(0, 0, 0, 0.2)",
|
|
21378
|
+
inputText: "#1a1a2e",
|
|
21379
|
+
buttonPrimary: "#3e1a7d",
|
|
21380
|
+
buttonPrimaryHover: "#5a2da8",
|
|
21381
|
+
buttonSecondary: "rgba(0, 0, 0, 0.05)",
|
|
21382
|
+
success: "#4CAF50",
|
|
21383
|
+
error: "#f44336",
|
|
21384
|
+
overlay: "rgba(0, 0, 0, 0.5)"
|
|
21385
|
+
};
|
|
21386
|
+
function getColors(theme) {
|
|
21387
|
+
return theme === "dark" ? DARK_THEME2 : LIGHT_THEME2;
|
|
21388
|
+
}
|
|
21389
|
+
async function fetchCustomerAttributes(customerId, token) {
|
|
21390
|
+
const url = `/api/plugins/telemetry/CUSTOMER/${customerId}/values/attributes/SERVER_SCOPE`;
|
|
21391
|
+
const response = await fetch(url, {
|
|
21392
|
+
method: "GET",
|
|
21393
|
+
headers: {
|
|
21394
|
+
"Content-Type": "application/json",
|
|
21395
|
+
"X-Authorization": `Bearer ${token}`
|
|
21396
|
+
}
|
|
21397
|
+
});
|
|
21398
|
+
if (!response.ok) {
|
|
21399
|
+
if (response.status === 404 || response.status === 400) {
|
|
21400
|
+
return { minTemperature: null, maxTemperature: null };
|
|
21401
|
+
}
|
|
21402
|
+
throw new Error(`Failed to fetch attributes: ${response.status}`);
|
|
21403
|
+
}
|
|
21404
|
+
const attributes = await response.json();
|
|
21405
|
+
let minTemperature = null;
|
|
21406
|
+
let maxTemperature = null;
|
|
21407
|
+
if (Array.isArray(attributes)) {
|
|
21408
|
+
for (const attr of attributes) {
|
|
21409
|
+
if (attr.key === "minTemperature") {
|
|
21410
|
+
minTemperature = Number(attr.value);
|
|
21411
|
+
} else if (attr.key === "maxTemperature") {
|
|
21412
|
+
maxTemperature = Number(attr.value);
|
|
21413
|
+
}
|
|
21414
|
+
}
|
|
21415
|
+
}
|
|
21416
|
+
return { minTemperature, maxTemperature };
|
|
21417
|
+
}
|
|
21418
|
+
async function saveCustomerAttributes(customerId, token, minTemperature, maxTemperature) {
|
|
21419
|
+
const url = `/api/plugins/telemetry/CUSTOMER/${customerId}/SERVER_SCOPE`;
|
|
21420
|
+
const attributes = {
|
|
21421
|
+
minTemperature,
|
|
21422
|
+
maxTemperature
|
|
21423
|
+
};
|
|
21424
|
+
const response = await fetch(url, {
|
|
21425
|
+
method: "POST",
|
|
21426
|
+
headers: {
|
|
21427
|
+
"Content-Type": "application/json",
|
|
21428
|
+
"X-Authorization": `Bearer ${token}`
|
|
21429
|
+
},
|
|
21430
|
+
body: JSON.stringify(attributes)
|
|
21431
|
+
});
|
|
21432
|
+
if (!response.ok) {
|
|
21433
|
+
throw new Error(`Failed to save attributes: ${response.status}`);
|
|
21434
|
+
}
|
|
21435
|
+
}
|
|
21436
|
+
function renderModal3(container, state, modalId, onClose, onSave) {
|
|
21437
|
+
const colors = getColors(state.theme);
|
|
21438
|
+
const minValue = state.minTemperature !== null ? state.minTemperature : "";
|
|
21439
|
+
const maxValue = state.maxTemperature !== null ? state.maxTemperature : "";
|
|
21440
|
+
container.innerHTML = `
|
|
21441
|
+
<style>
|
|
21442
|
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
21443
|
+
@keyframes slideIn { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
21444
|
+
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
21445
|
+
|
|
21446
|
+
#${modalId} {
|
|
21447
|
+
position: fixed;
|
|
21448
|
+
top: 0;
|
|
21449
|
+
left: 0;
|
|
21450
|
+
width: 100%;
|
|
21451
|
+
height: 100%;
|
|
21452
|
+
background: ${colors.overlay};
|
|
21453
|
+
z-index: 10000;
|
|
21454
|
+
display: flex;
|
|
21455
|
+
align-items: center;
|
|
21456
|
+
justify-content: center;
|
|
21457
|
+
animation: fadeIn 0.2s ease-out;
|
|
21458
|
+
}
|
|
21459
|
+
|
|
21460
|
+
#${modalId} .modal-content {
|
|
21461
|
+
background: ${colors.modalBg};
|
|
21462
|
+
border-radius: 16px;
|
|
21463
|
+
width: 90%;
|
|
21464
|
+
max-width: 480px;
|
|
21465
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
21466
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
21467
|
+
animation: slideIn 0.3s ease-out;
|
|
21468
|
+
overflow: hidden;
|
|
21469
|
+
}
|
|
21470
|
+
|
|
21471
|
+
#${modalId} .modal-header {
|
|
21472
|
+
background: ${colors.headerBg};
|
|
21473
|
+
padding: 20px 24px;
|
|
21474
|
+
display: flex;
|
|
21475
|
+
align-items: center;
|
|
21476
|
+
justify-content: space-between;
|
|
21477
|
+
}
|
|
21478
|
+
|
|
21479
|
+
#${modalId} .modal-title {
|
|
21480
|
+
margin: 0;
|
|
21481
|
+
font-size: 18px;
|
|
21482
|
+
font-weight: 600;
|
|
21483
|
+
color: #fff;
|
|
21484
|
+
font-family: 'Roboto', sans-serif;
|
|
21485
|
+
display: flex;
|
|
21486
|
+
align-items: center;
|
|
21487
|
+
gap: 10px;
|
|
21488
|
+
}
|
|
21489
|
+
|
|
21490
|
+
#${modalId} .close-btn {
|
|
21491
|
+
width: 32px;
|
|
21492
|
+
height: 32px;
|
|
21493
|
+
background: rgba(255, 255, 255, 0.1);
|
|
21494
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
21495
|
+
border-radius: 8px;
|
|
21496
|
+
cursor: pointer;
|
|
21497
|
+
display: flex;
|
|
21498
|
+
align-items: center;
|
|
21499
|
+
justify-content: center;
|
|
21500
|
+
transition: all 0.2s;
|
|
21501
|
+
color: #fff;
|
|
21502
|
+
font-size: 18px;
|
|
21503
|
+
}
|
|
21504
|
+
|
|
21505
|
+
#${modalId} .close-btn:hover {
|
|
21506
|
+
background: rgba(255, 68, 68, 0.25);
|
|
21507
|
+
border-color: rgba(255, 68, 68, 0.5);
|
|
21508
|
+
}
|
|
21509
|
+
|
|
21510
|
+
#${modalId} .modal-body {
|
|
21511
|
+
padding: 24px;
|
|
21512
|
+
}
|
|
21513
|
+
|
|
21514
|
+
#${modalId} .customer-info {
|
|
21515
|
+
margin-bottom: 24px;
|
|
21516
|
+
padding: 12px 16px;
|
|
21517
|
+
background: ${colors.inputBg};
|
|
21518
|
+
border-radius: 8px;
|
|
21519
|
+
border: 1px solid ${colors.inputBorder};
|
|
21520
|
+
}
|
|
21521
|
+
|
|
21522
|
+
#${modalId} .customer-label {
|
|
21523
|
+
font-size: 12px;
|
|
21524
|
+
color: ${colors.textMuted};
|
|
21525
|
+
margin-bottom: 4px;
|
|
21526
|
+
}
|
|
21527
|
+
|
|
21528
|
+
#${modalId} .customer-name {
|
|
21529
|
+
font-size: 16px;
|
|
21530
|
+
font-weight: 500;
|
|
21531
|
+
color: ${colors.textPrimary};
|
|
21532
|
+
}
|
|
21533
|
+
|
|
21534
|
+
#${modalId} .form-group {
|
|
21535
|
+
margin-bottom: 20px;
|
|
21536
|
+
}
|
|
21537
|
+
|
|
21538
|
+
#${modalId} .form-label {
|
|
21539
|
+
display: block;
|
|
21540
|
+
font-size: 14px;
|
|
21541
|
+
font-weight: 500;
|
|
21542
|
+
color: ${colors.textSecondary};
|
|
21543
|
+
margin-bottom: 8px;
|
|
21544
|
+
}
|
|
21545
|
+
|
|
21546
|
+
#${modalId} .form-input {
|
|
21547
|
+
width: 100%;
|
|
21548
|
+
padding: 12px 16px;
|
|
21549
|
+
font-size: 16px;
|
|
21550
|
+
background: ${colors.inputBg};
|
|
21551
|
+
border: 1px solid ${colors.inputBorder};
|
|
21552
|
+
border-radius: 8px;
|
|
21553
|
+
color: ${colors.inputText};
|
|
21554
|
+
outline: none;
|
|
21555
|
+
transition: border-color 0.2s;
|
|
21556
|
+
box-sizing: border-box;
|
|
21557
|
+
}
|
|
21558
|
+
|
|
21559
|
+
#${modalId} .form-input:focus {
|
|
21560
|
+
border-color: ${colors.buttonPrimary};
|
|
21561
|
+
}
|
|
21562
|
+
|
|
21563
|
+
#${modalId} .form-input::placeholder {
|
|
21564
|
+
color: ${colors.textMuted};
|
|
21565
|
+
}
|
|
21566
|
+
|
|
21567
|
+
#${modalId} .form-hint {
|
|
21568
|
+
font-size: 12px;
|
|
21569
|
+
color: ${colors.textMuted};
|
|
21570
|
+
margin-top: 6px;
|
|
21571
|
+
}
|
|
21572
|
+
|
|
21573
|
+
#${modalId} .temperature-range {
|
|
21574
|
+
display: grid;
|
|
21575
|
+
grid-template-columns: 1fr 1fr;
|
|
21576
|
+
gap: 16px;
|
|
21577
|
+
}
|
|
21578
|
+
|
|
21579
|
+
#${modalId} .range-preview {
|
|
21580
|
+
margin-top: 20px;
|
|
21581
|
+
padding: 16px;
|
|
21582
|
+
background: rgba(76, 175, 80, 0.1);
|
|
21583
|
+
border: 1px dashed ${colors.success};
|
|
21584
|
+
border-radius: 8px;
|
|
21585
|
+
text-align: center;
|
|
21586
|
+
}
|
|
21587
|
+
|
|
21588
|
+
#${modalId} .range-preview-label {
|
|
21589
|
+
font-size: 12px;
|
|
21590
|
+
color: ${colors.textMuted};
|
|
21591
|
+
margin-bottom: 8px;
|
|
21592
|
+
}
|
|
21593
|
+
|
|
21594
|
+
#${modalId} .range-preview-value {
|
|
21595
|
+
font-size: 24px;
|
|
21596
|
+
font-weight: 600;
|
|
21597
|
+
color: ${colors.success};
|
|
21598
|
+
}
|
|
21599
|
+
|
|
21600
|
+
#${modalId} .modal-footer {
|
|
21601
|
+
padding: 16px 24px;
|
|
21602
|
+
border-top: 1px solid ${colors.inputBorder};
|
|
21603
|
+
display: flex;
|
|
21604
|
+
justify-content: flex-end;
|
|
21605
|
+
gap: 12px;
|
|
21606
|
+
}
|
|
21607
|
+
|
|
21608
|
+
#${modalId} .btn {
|
|
21609
|
+
padding: 10px 24px;
|
|
21610
|
+
font-size: 14px;
|
|
21611
|
+
font-weight: 500;
|
|
21612
|
+
border-radius: 8px;
|
|
21613
|
+
cursor: pointer;
|
|
21614
|
+
transition: all 0.2s;
|
|
21615
|
+
border: none;
|
|
21616
|
+
display: flex;
|
|
21617
|
+
align-items: center;
|
|
21618
|
+
gap: 8px;
|
|
21619
|
+
}
|
|
21620
|
+
|
|
21621
|
+
#${modalId} .btn-secondary {
|
|
21622
|
+
background: ${colors.buttonSecondary};
|
|
21623
|
+
color: ${colors.textSecondary};
|
|
21624
|
+
border: 1px solid ${colors.inputBorder};
|
|
21625
|
+
}
|
|
21626
|
+
|
|
21627
|
+
#${modalId} .btn-secondary:hover {
|
|
21628
|
+
background: ${colors.inputBg};
|
|
21629
|
+
}
|
|
21630
|
+
|
|
21631
|
+
#${modalId} .btn-primary {
|
|
21632
|
+
background: ${colors.buttonPrimary};
|
|
21633
|
+
color: #fff;
|
|
21634
|
+
}
|
|
21635
|
+
|
|
21636
|
+
#${modalId} .btn-primary:hover {
|
|
21637
|
+
background: ${colors.buttonPrimaryHover};
|
|
21638
|
+
}
|
|
21639
|
+
|
|
21640
|
+
#${modalId} .btn-primary:disabled {
|
|
21641
|
+
opacity: 0.6;
|
|
21642
|
+
cursor: not-allowed;
|
|
21643
|
+
}
|
|
21644
|
+
|
|
21645
|
+
#${modalId} .spinner {
|
|
21646
|
+
width: 16px;
|
|
21647
|
+
height: 16px;
|
|
21648
|
+
border: 2px solid rgba(255,255,255,0.3);
|
|
21649
|
+
border-top-color: #fff;
|
|
21650
|
+
border-radius: 50%;
|
|
21651
|
+
animation: spin 1s linear infinite;
|
|
21652
|
+
}
|
|
21653
|
+
|
|
21654
|
+
#${modalId} .message {
|
|
21655
|
+
padding: 12px 16px;
|
|
21656
|
+
border-radius: 8px;
|
|
21657
|
+
margin-bottom: 16px;
|
|
21658
|
+
font-size: 14px;
|
|
21659
|
+
}
|
|
21660
|
+
|
|
21661
|
+
#${modalId} .message-error {
|
|
21662
|
+
background: rgba(244, 67, 54, 0.1);
|
|
21663
|
+
border: 1px solid ${colors.error};
|
|
21664
|
+
color: ${colors.error};
|
|
21665
|
+
}
|
|
21666
|
+
|
|
21667
|
+
#${modalId} .message-success {
|
|
21668
|
+
background: rgba(76, 175, 80, 0.1);
|
|
21669
|
+
border: 1px solid ${colors.success};
|
|
21670
|
+
color: ${colors.success};
|
|
21671
|
+
}
|
|
21672
|
+
|
|
21673
|
+
#${modalId} .loading-overlay {
|
|
21674
|
+
display: flex;
|
|
21675
|
+
flex-direction: column;
|
|
21676
|
+
align-items: center;
|
|
21677
|
+
justify-content: center;
|
|
21678
|
+
padding: 48px;
|
|
21679
|
+
color: ${colors.textSecondary};
|
|
21680
|
+
}
|
|
21681
|
+
|
|
21682
|
+
#${modalId} .loading-spinner {
|
|
21683
|
+
width: 40px;
|
|
21684
|
+
height: 40px;
|
|
21685
|
+
border: 3px solid ${colors.inputBorder};
|
|
21686
|
+
border-top-color: ${colors.buttonPrimary};
|
|
21687
|
+
border-radius: 50%;
|
|
21688
|
+
animation: spin 1s linear infinite;
|
|
21689
|
+
margin-bottom: 16px;
|
|
21690
|
+
}
|
|
21691
|
+
</style>
|
|
21692
|
+
|
|
21693
|
+
<div id="${modalId}" class="modal-overlay">
|
|
21694
|
+
<div class="modal-content">
|
|
21695
|
+
<div class="modal-header">
|
|
21696
|
+
<h2 class="modal-title">
|
|
21697
|
+
<span>\u{1F321}\uFE0F</span>
|
|
21698
|
+
Configurar Temperatura
|
|
21699
|
+
</h2>
|
|
21700
|
+
<button class="close-btn" id="${modalId}-close">×</button>
|
|
21701
|
+
</div>
|
|
21702
|
+
|
|
21703
|
+
<div class="modal-body">
|
|
21704
|
+
${state.isLoading ? `
|
|
21705
|
+
<div class="loading-overlay">
|
|
21706
|
+
<div class="loading-spinner"></div>
|
|
21707
|
+
<div>Carregando configura\xE7\xF5es...</div>
|
|
21708
|
+
</div>
|
|
21709
|
+
` : `
|
|
21710
|
+
${state.error ? `
|
|
21711
|
+
<div class="message message-error">${state.error}</div>
|
|
21712
|
+
` : ""}
|
|
21713
|
+
|
|
21714
|
+
${state.successMessage ? `
|
|
21715
|
+
<div class="message message-success">${state.successMessage}</div>
|
|
21716
|
+
` : ""}
|
|
21717
|
+
|
|
21718
|
+
<div class="customer-info">
|
|
21719
|
+
<div class="customer-label">Shopping / Cliente</div>
|
|
21720
|
+
<div class="customer-name">${state.customerName || "N\xE3o identificado"}</div>
|
|
21721
|
+
</div>
|
|
21722
|
+
|
|
21723
|
+
<div class="form-group">
|
|
21724
|
+
<label class="form-label">Faixa de Temperatura Ideal</label>
|
|
21725
|
+
<div class="temperature-range">
|
|
21726
|
+
<div>
|
|
21727
|
+
<input
|
|
21728
|
+
type="number"
|
|
21729
|
+
id="${modalId}-min"
|
|
21730
|
+
class="form-input"
|
|
21731
|
+
placeholder="M\xEDnima"
|
|
21732
|
+
value="${minValue}"
|
|
21733
|
+
step="0.5"
|
|
21734
|
+
min="0"
|
|
21735
|
+
max="50"
|
|
21736
|
+
/>
|
|
21737
|
+
<div class="form-hint">Temperatura m\xEDnima (\xB0C)</div>
|
|
21738
|
+
</div>
|
|
21739
|
+
<div>
|
|
21740
|
+
<input
|
|
21741
|
+
type="number"
|
|
21742
|
+
id="${modalId}-max"
|
|
21743
|
+
class="form-input"
|
|
21744
|
+
placeholder="M\xE1xima"
|
|
21745
|
+
value="${maxValue}"
|
|
21746
|
+
step="0.5"
|
|
21747
|
+
min="0"
|
|
21748
|
+
max="50"
|
|
21749
|
+
/>
|
|
21750
|
+
<div class="form-hint">Temperatura m\xE1xima (\xB0C)</div>
|
|
21751
|
+
</div>
|
|
21752
|
+
</div>
|
|
21753
|
+
</div>
|
|
21754
|
+
|
|
21755
|
+
<div class="range-preview" id="${modalId}-preview">
|
|
21756
|
+
<div class="range-preview-label">Faixa configurada</div>
|
|
21757
|
+
<div class="range-preview-value" id="${modalId}-preview-value">
|
|
21758
|
+
${minValue && maxValue ? `${minValue}\xB0C - ${maxValue}\xB0C` : "N\xE3o definida"}
|
|
21759
|
+
</div>
|
|
21760
|
+
</div>
|
|
21761
|
+
`}
|
|
21762
|
+
</div>
|
|
21763
|
+
|
|
21764
|
+
${!state.isLoading ? `
|
|
21765
|
+
<div class="modal-footer">
|
|
21766
|
+
<button class="btn btn-secondary" id="${modalId}-cancel">Cancelar</button>
|
|
21767
|
+
<button class="btn btn-primary" id="${modalId}-save" ${state.isSaving ? "disabled" : ""}>
|
|
21768
|
+
${state.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
|
|
21769
|
+
</button>
|
|
21770
|
+
</div>
|
|
21771
|
+
` : ""}
|
|
21772
|
+
</div>
|
|
21773
|
+
</div>
|
|
21774
|
+
`;
|
|
21775
|
+
const closeBtn = document.getElementById(`${modalId}-close`);
|
|
21776
|
+
const cancelBtn = document.getElementById(`${modalId}-cancel`);
|
|
21777
|
+
const saveBtn = document.getElementById(`${modalId}-save`);
|
|
21778
|
+
const minInput = document.getElementById(`${modalId}-min`);
|
|
21779
|
+
const maxInput = document.getElementById(`${modalId}-max`);
|
|
21780
|
+
const previewValue = document.getElementById(`${modalId}-preview-value`);
|
|
21781
|
+
const overlay = document.getElementById(modalId);
|
|
21782
|
+
closeBtn?.addEventListener("click", onClose);
|
|
21783
|
+
cancelBtn?.addEventListener("click", onClose);
|
|
21784
|
+
overlay?.addEventListener("click", (e) => {
|
|
21785
|
+
if (e.target === overlay) onClose();
|
|
21786
|
+
});
|
|
21787
|
+
const updatePreview = () => {
|
|
21788
|
+
if (previewValue && minInput && maxInput) {
|
|
21789
|
+
const min = minInput.value;
|
|
21790
|
+
const max = maxInput.value;
|
|
21791
|
+
if (min && max) {
|
|
21792
|
+
previewValue.textContent = `${min}\xB0C - ${max}\xB0C`;
|
|
21793
|
+
} else {
|
|
21794
|
+
previewValue.textContent = "N\xE3o definida";
|
|
21795
|
+
}
|
|
21796
|
+
}
|
|
21797
|
+
};
|
|
21798
|
+
minInput?.addEventListener("input", updatePreview);
|
|
21799
|
+
maxInput?.addEventListener("input", updatePreview);
|
|
21800
|
+
saveBtn?.addEventListener("click", async () => {
|
|
21801
|
+
if (!minInput || !maxInput) return;
|
|
21802
|
+
const min = parseFloat(minInput.value);
|
|
21803
|
+
const max = parseFloat(maxInput.value);
|
|
21804
|
+
if (isNaN(min) || isNaN(max)) {
|
|
21805
|
+
state.error = "Por favor, preencha ambos os valores.";
|
|
21806
|
+
renderModal3(container, state, modalId, onClose, onSave);
|
|
21807
|
+
return;
|
|
21808
|
+
}
|
|
21809
|
+
if (min >= max) {
|
|
21810
|
+
state.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
|
|
21811
|
+
renderModal3(container, state, modalId, onClose, onSave);
|
|
21812
|
+
return;
|
|
21813
|
+
}
|
|
21814
|
+
if (min < 0 || max > 50) {
|
|
21815
|
+
state.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
|
|
21816
|
+
renderModal3(container, state, modalId, onClose, onSave);
|
|
21817
|
+
return;
|
|
21818
|
+
}
|
|
21819
|
+
await onSave(min, max);
|
|
21820
|
+
});
|
|
21821
|
+
}
|
|
21822
|
+
function openTemperatureSettingsModal(params) {
|
|
21823
|
+
const modalId = `myio-temp-settings-${Date.now()}`;
|
|
21824
|
+
const state = {
|
|
21825
|
+
customerId: params.customerId,
|
|
21826
|
+
customerName: params.customerName || "",
|
|
21827
|
+
token: params.token,
|
|
21828
|
+
theme: params.theme || "dark",
|
|
21829
|
+
minTemperature: null,
|
|
21830
|
+
maxTemperature: null,
|
|
21831
|
+
isLoading: true,
|
|
21832
|
+
isSaving: false,
|
|
21833
|
+
error: null,
|
|
21834
|
+
successMessage: null
|
|
21835
|
+
};
|
|
21836
|
+
const container = document.createElement("div");
|
|
21837
|
+
container.id = `${modalId}-container`;
|
|
21838
|
+
document.body.appendChild(container);
|
|
21839
|
+
const destroy = () => {
|
|
21840
|
+
container.remove();
|
|
21841
|
+
params.onClose?.();
|
|
21842
|
+
};
|
|
21843
|
+
const handleSave = async (min, max) => {
|
|
21844
|
+
state.isSaving = true;
|
|
21845
|
+
state.error = null;
|
|
21846
|
+
state.successMessage = null;
|
|
21847
|
+
renderModal3(container, state, modalId, destroy, handleSave);
|
|
21848
|
+
try {
|
|
21849
|
+
await saveCustomerAttributes(state.customerId, state.token, min, max);
|
|
21850
|
+
state.minTemperature = min;
|
|
21851
|
+
state.maxTemperature = max;
|
|
21852
|
+
state.isSaving = false;
|
|
21853
|
+
state.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
|
|
21854
|
+
renderModal3(container, state, modalId, destroy, handleSave);
|
|
21855
|
+
params.onSave?.({ minTemperature: min, maxTemperature: max });
|
|
21856
|
+
setTimeout(() => {
|
|
21857
|
+
destroy();
|
|
21858
|
+
}, 1500);
|
|
21859
|
+
} catch (error) {
|
|
21860
|
+
state.isSaving = false;
|
|
21861
|
+
state.error = `Erro ao salvar: ${error.message}`;
|
|
21862
|
+
renderModal3(container, state, modalId, destroy, handleSave);
|
|
21863
|
+
}
|
|
21864
|
+
};
|
|
21865
|
+
renderModal3(container, state, modalId, destroy, handleSave);
|
|
21866
|
+
fetchCustomerAttributes(state.customerId, state.token).then(({ minTemperature, maxTemperature }) => {
|
|
21867
|
+
state.minTemperature = minTemperature;
|
|
21868
|
+
state.maxTemperature = maxTemperature;
|
|
21869
|
+
state.isLoading = false;
|
|
21870
|
+
renderModal3(container, state, modalId, destroy, handleSave);
|
|
21871
|
+
}).catch((error) => {
|
|
21872
|
+
state.isLoading = false;
|
|
21873
|
+
state.error = `Erro ao carregar: ${error.message}`;
|
|
21874
|
+
renderModal3(container, state, modalId, destroy, handleSave);
|
|
21875
|
+
});
|
|
21876
|
+
return { destroy };
|
|
21877
|
+
}
|
|
21264
21878
|
export {
|
|
21265
21879
|
CHART_COLORS,
|
|
21266
21880
|
ConnectionStatusType,
|
|
@@ -21349,6 +21963,7 @@ export {
|
|
|
21349
21963
|
openRealTimeTelemetryModal,
|
|
21350
21964
|
openTemperatureComparisonModal,
|
|
21351
21965
|
openTemperatureModal,
|
|
21966
|
+
openTemperatureSettingsModal,
|
|
21352
21967
|
parseInputDateToDate,
|
|
21353
21968
|
renderCardComponent,
|
|
21354
21969
|
renderCardComponent2 as renderCardComponentEnhanced,
|