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.
@@ -7426,7 +7426,10 @@ ${rangeText}`;
7426
7426
  if (!telemetryData || telemetryData.length === 0) continue;
7427
7427
  const latest = telemetryData[0];
7428
7428
  const config = TELEMETRY_CONFIG[key] || { label: key, unit: "", icon: "\u{1F4CA}", decimals: 2 };
7429
- const numValue = Number(latest.value) || 0;
7429
+ let numValue = Number(latest.value) || 0;
7430
+ if (key === "total_current" || key === "current") {
7431
+ numValue = numValue / 1e3;
7432
+ }
7430
7433
  const formatted = numValue.toFixed(config.decimals);
7431
7434
  values.push({
7432
7435
  key,
@@ -16898,13 +16901,30 @@ ${rangeText}`;
16898
16901
  jwtToken;
16899
16902
  tbBaseUrl;
16900
16903
  deviceType;
16904
+ deviceProfile;
16901
16905
  existingMapInstantaneousPower;
16902
16906
  constructor(jwtToken, apiConfig) {
16903
16907
  this.jwtToken = jwtToken;
16904
16908
  this.tbBaseUrl = apiConfig?.tbBaseUrl || window.location.origin;
16905
16909
  this.deviceType = apiConfig?.deviceType || "ELEVADOR";
16910
+ this.deviceProfile = apiConfig?.deviceProfile || null;
16906
16911
  this.existingMapInstantaneousPower = apiConfig?.mapInstantaneousPower || null;
16907
16912
  }
16913
+ /**
16914
+ * RFC-0086: Resolve effective device type
16915
+ * When deviceType is 3F_MEDIDOR, use deviceProfile as the actual type
16916
+ */
16917
+ getEffectiveDeviceType() {
16918
+ const normalizedType = (this.deviceType || "").toUpperCase();
16919
+ if (normalizedType === "3F_MEDIDOR") {
16920
+ const profile = (this.deviceProfile || "").toUpperCase();
16921
+ if (profile && profile !== "N/D" && profile.trim() !== "") {
16922
+ console.log(`[SettingsPersister] RFC-0086: Resolved 3F_MEDIDOR \u2192 ${profile}`);
16923
+ return profile;
16924
+ }
16925
+ }
16926
+ return normalizedType || "ELEVADOR";
16927
+ }
16908
16928
  async saveEntityLabel(deviceId, label) {
16909
16929
  try {
16910
16930
  const getRes = await fetch(`${this.tbBaseUrl}/api/device/${deviceId}`, {
@@ -16962,72 +16982,59 @@ ${rangeText}`;
16962
16982
  }
16963
16983
  }
16964
16984
  /**
16965
- * RFC-0080: Build mapInstantaneousPower JSON structure from form data
16966
- * Preserves existing structure and updates only the current device type
16985
+ * RFC-0086: Build mapInstantaneousPower JSON structure from form data
16986
+ * IMPORTANT: When saving to a DEVICE, only include entries for the specific deviceType
16987
+ * Uses getEffectiveDeviceType() to resolve 3F_MEDIDOR → deviceProfile
16967
16988
  */
16968
16989
  buildMapInstantaneousPower(formData) {
16969
- const result = this.existingMapInstantaneousPower ? JSON.parse(JSON.stringify(this.existingMapInstantaneousPower)) : {
16970
- version: "1.0.0",
16971
- limitsByInstantaneoustPowerType: []
16972
- };
16990
+ const effectiveDeviceType = this.getEffectiveDeviceType();
16973
16991
  const telemetryType = String(formData.telemetryType || "consumption");
16974
- let telemetryConfig = result.limitsByInstantaneoustPowerType.find(
16975
- (t) => t.telemetryType === telemetryType
16976
- );
16977
- if (!telemetryConfig) {
16978
- telemetryConfig = {
16979
- telemetryType,
16980
- itemsByDeviceType: []
16981
- };
16982
- result.limitsByInstantaneoustPowerType.push(telemetryConfig);
16983
- }
16984
- const deviceType = this.deviceType.toUpperCase();
16985
- let deviceConfig = telemetryConfig.itemsByDeviceType.find(
16986
- (d) => d.deviceType === deviceType
16987
- );
16988
- if (!deviceConfig) {
16989
- deviceConfig = {
16990
- deviceType,
16991
- name: `mapInstantaneousPower${this.formatDeviceTypeName(deviceType)}`,
16992
- description: `Limites de pot\xEAncia customizados para ${deviceType}`,
16993
- limitsByDeviceStatus: []
16994
- };
16995
- telemetryConfig.itemsByDeviceType.push(deviceConfig);
16996
- }
16997
- deviceConfig.limitsByDeviceStatus = [
16998
- {
16999
- deviceStatusName: "standBy",
17000
- limitsValues: {
17001
- baseValue: Number(formData.standbyLimitDownConsumption) || 0,
17002
- topValue: Number(formData.standbyLimitUpConsumption) || 0
17003
- }
17004
- },
17005
- {
17006
- deviceStatusName: "normal",
17007
- limitsValues: {
17008
- baseValue: Number(formData.normalLimitDownConsumption) || 0,
17009
- topValue: Number(formData.normalLimitUpConsumption) || 0
17010
- }
17011
- },
17012
- {
17013
- deviceStatusName: "alert",
17014
- limitsValues: {
17015
- baseValue: Number(formData.alertLimitDownConsumption) || 0,
17016
- topValue: Number(formData.alertLimitUpConsumption) || 0
17017
- }
17018
- },
17019
- {
17020
- deviceStatusName: "failure",
17021
- limitsValues: {
17022
- baseValue: Number(formData.failureLimitDownConsumption) || 0,
17023
- topValue: Number(formData.failureLimitUpConsumption) || 0
16992
+ const result = {
16993
+ version: "1.0.0",
16994
+ limitsByInstantaneoustPowerType: [
16995
+ {
16996
+ telemetryType,
16997
+ itemsByDeviceType: [
16998
+ {
16999
+ deviceType: effectiveDeviceType,
17000
+ name: `mapInstantaneousPower${this.formatDeviceTypeName(effectiveDeviceType)}`,
17001
+ description: formData.identifier ? `Limites customizados para ${formData.identifier}` : `Limites de pot\xEAncia customizados para ${effectiveDeviceType}`,
17002
+ limitsByDeviceStatus: [
17003
+ {
17004
+ deviceStatusName: "standBy",
17005
+ limitsValues: {
17006
+ baseValue: Number(formData.standbyLimitDownConsumption) || 0,
17007
+ topValue: Number(formData.standbyLimitUpConsumption) || 0
17008
+ }
17009
+ },
17010
+ {
17011
+ deviceStatusName: "normal",
17012
+ limitsValues: {
17013
+ baseValue: Number(formData.normalLimitDownConsumption) || 0,
17014
+ topValue: Number(formData.normalLimitUpConsumption) || 0
17015
+ }
17016
+ },
17017
+ {
17018
+ deviceStatusName: "alert",
17019
+ limitsValues: {
17020
+ baseValue: Number(formData.alertLimitDownConsumption) || 0,
17021
+ topValue: Number(formData.alertLimitUpConsumption) || 0
17022
+ }
17023
+ },
17024
+ {
17025
+ deviceStatusName: "failure",
17026
+ limitsValues: {
17027
+ baseValue: Number(formData.failureLimitDownConsumption) || 0,
17028
+ topValue: Number(formData.failureLimitUpConsumption) || 0
17029
+ }
17030
+ }
17031
+ ]
17032
+ }
17033
+ ]
17024
17034
  }
17025
- }
17026
- ];
17027
- if (formData.identifier) {
17028
- deviceConfig.description = `Limites customizados para ${formData.identifier}`;
17029
- }
17030
- console.log("[SettingsPersister] RFC-0080: Built mapInstantaneousPower:", result);
17035
+ ]
17036
+ };
17037
+ console.log(`[SettingsPersister] RFC-0086: Built mapInstantaneousPower for deviceType=${effectiveDeviceType}:`, result);
17031
17038
  return result;
17032
17039
  }
17033
17040
  /**
@@ -17266,6 +17273,8 @@ ${rangeText}`;
17266
17273
  const apiConfigWithDeviceInfo = {
17267
17274
  ...params.api,
17268
17275
  deviceType: params.deviceType,
17276
+ deviceProfile: params.deviceProfile,
17277
+ // RFC-0086: For 3F_MEDIDOR → deviceProfile resolution
17269
17278
  mapInstantaneousPower: params.mapInstantaneousPower
17270
17279
  };
17271
17280
  this.persister = params.persister || new DefaultSettingsPersister(params.jwtToken, apiConfigWithDeviceInfo);
@@ -17317,6 +17326,8 @@ ${rangeText}`;
17317
17326
  this.persister = new DefaultSettingsPersister(this.params.jwtToken, {
17318
17327
  ...this.params.api,
17319
17328
  deviceType: this.params.deviceType,
17329
+ deviceProfile: this.params.deviceProfile,
17330
+ // RFC-0086: For 3F_MEDIDOR → deviceProfile resolution
17320
17331
  mapInstantaneousPower: globalMap
17321
17332
  });
17322
17333
  this.view.updateMapInstantaneousPower(globalMap);
@@ -17972,13 +17983,13 @@ ${rangeText}`;
17972
17983
  function initializeModal() {
17973
17984
  if (data) {
17974
17985
  modalState.goalsData = data;
17975
- renderModal3();
17986
+ renderModal4();
17976
17987
  } else {
17977
- renderModal3();
17988
+ renderModal4();
17978
17989
  loadGoalsData();
17979
17990
  }
17980
17991
  }
17981
- function renderModal3() {
17992
+ function renderModal4() {
17982
17993
  const existing = document.getElementById("myio-goals-panel-modal");
17983
17994
  if (existing) {
17984
17995
  existing.remove();
@@ -19978,8 +19989,12 @@ ${rangeText}`;
19978
19989
  const paddingBottom = 55;
19979
19990
  const isPeriodsFiltered = state.selectedPeriods.length < 4 && state.selectedPeriods.length > 0;
19980
19991
  const values = chartData.map((d) => d.y);
19981
- const minY = Math.min(...values) - 1;
19982
- const maxY = Math.max(...values) + 1;
19992
+ const dataMin = Math.min(...values);
19993
+ const dataMax = Math.max(...values);
19994
+ const thresholdMin = state.temperatureMin !== null ? state.temperatureMin : dataMin;
19995
+ const thresholdMax = state.temperatureMax !== null ? state.temperatureMax : dataMax;
19996
+ const minY = Math.min(dataMin, thresholdMin) - 1;
19997
+ const maxY = Math.max(dataMax, thresholdMax) + 1;
19983
19998
  const chartWidth = width - paddingLeft - paddingRight;
19984
19999
  const chartHeight = height - paddingTop - paddingBottom;
19985
20000
  const scaleY = chartHeight / (maxY - minY || 1);
@@ -20320,8 +20335,10 @@ ${rangeText}`;
20320
20335
  deviceData: [],
20321
20336
  isLoading: true,
20322
20337
  dateRangePicker: null,
20323
- selectedPeriods: ["madrugada", "manha", "tarde", "noite"]
20338
+ selectedPeriods: ["madrugada", "manha", "tarde", "noite"],
20324
20339
  // All periods selected by default
20340
+ temperatureMin: params.temperatureMin ?? null,
20341
+ temperatureMax: params.temperatureMax ?? null
20325
20342
  };
20326
20343
  const savedGranularity = localStorage.getItem("myio-temp-comparison-granularity");
20327
20344
  const savedTheme = localStorage.getItem("myio-temp-comparison-theme");
@@ -20717,16 +20734,52 @@ ${rangeText}`;
20717
20734
  return;
20718
20735
  }
20719
20736
  const isPeriodsFiltered = state.selectedPeriods.length < 4 && state.selectedPeriods.length > 0;
20720
- let globalMinY = Infinity;
20721
- let globalMaxY = -Infinity;
20737
+ let dataMinY = Infinity;
20738
+ let dataMaxY = -Infinity;
20722
20739
  processedData.forEach(({ points }) => {
20723
20740
  points.forEach((point) => {
20724
- if (point.y < globalMinY) globalMinY = point.y;
20725
- if (point.y > globalMaxY) globalMaxY = point.y;
20741
+ if (point.y < dataMinY) dataMinY = point.y;
20742
+ if (point.y > dataMaxY) dataMaxY = point.y;
20743
+ });
20744
+ });
20745
+ const rangeMap = /* @__PURE__ */ new Map();
20746
+ state.deviceData.forEach((dd, index) => {
20747
+ const device = dd.device;
20748
+ const min = device.temperatureMin;
20749
+ const max = device.temperatureMax;
20750
+ if (min !== void 0 && min !== null && max !== void 0 && max !== null) {
20751
+ const key = `${min}-${max}`;
20752
+ if (!rangeMap.has(key)) {
20753
+ rangeMap.set(key, {
20754
+ min,
20755
+ max,
20756
+ customerName: device.customerName || "",
20757
+ color: CHART_COLORS[index % CHART_COLORS.length],
20758
+ deviceLabels: [device.label]
20759
+ });
20760
+ } else {
20761
+ rangeMap.get(key).deviceLabels.push(device.label);
20762
+ }
20763
+ }
20764
+ });
20765
+ if (rangeMap.size === 0 && state.temperatureMin !== null && state.temperatureMax !== null) {
20766
+ rangeMap.set("global", {
20767
+ min: state.temperatureMin,
20768
+ max: state.temperatureMax,
20769
+ customerName: "Global",
20770
+ color: colors.success,
20771
+ deviceLabels: []
20726
20772
  });
20773
+ }
20774
+ const temperatureRanges = Array.from(rangeMap.values());
20775
+ let thresholdMinY = dataMinY;
20776
+ let thresholdMaxY = dataMaxY;
20777
+ temperatureRanges.forEach((range) => {
20778
+ if (range.min < thresholdMinY) thresholdMinY = range.min;
20779
+ if (range.max > thresholdMaxY) thresholdMaxY = range.max;
20727
20780
  });
20728
- globalMinY = Math.floor(globalMinY) - 1;
20729
- globalMaxY = Math.ceil(globalMaxY) + 1;
20781
+ const globalMinY = Math.floor(Math.min(dataMinY, thresholdMinY)) - 1;
20782
+ const globalMaxY = Math.ceil(Math.max(dataMaxY, thresholdMaxY)) + 1;
20730
20783
  const chartWidth = width - paddingLeft - paddingRight;
20731
20784
  const chartHeight = height - paddingTop - paddingBottom;
20732
20785
  const scaleY = chartHeight / (globalMaxY - globalMinY || 1);
@@ -20766,6 +20819,41 @@ ${rangeText}`;
20766
20819
  ctx.lineTo(width - paddingRight, y);
20767
20820
  ctx.stroke();
20768
20821
  }
20822
+ const rangeColors = [
20823
+ { fill: "rgba(76, 175, 80, 0.12)", stroke: "#4CAF50" },
20824
+ // Green
20825
+ { fill: "rgba(33, 150, 243, 0.12)", stroke: "#2196F3" },
20826
+ // Blue
20827
+ { fill: "rgba(255, 152, 0, 0.12)", stroke: "#FF9800" },
20828
+ // Orange
20829
+ { fill: "rgba(156, 39, 176, 0.12)", stroke: "#9C27B0" }
20830
+ // Purple
20831
+ ];
20832
+ temperatureRanges.forEach((range, index) => {
20833
+ const rangeMinY = height - paddingBottom - (range.min - globalMinY) * scaleY;
20834
+ const rangeMaxY = height - paddingBottom - (range.max - globalMinY) * scaleY;
20835
+ const colorSet = rangeColors[index % rangeColors.length];
20836
+ ctx.fillStyle = colorSet.fill;
20837
+ ctx.fillRect(paddingLeft, rangeMaxY, chartWidth, rangeMinY - rangeMaxY);
20838
+ ctx.strokeStyle = colorSet.stroke;
20839
+ ctx.lineWidth = 1.5;
20840
+ ctx.setLineDash([6, 4]);
20841
+ ctx.beginPath();
20842
+ ctx.moveTo(paddingLeft, rangeMinY);
20843
+ ctx.lineTo(width - paddingRight, rangeMinY);
20844
+ ctx.moveTo(paddingLeft, rangeMaxY);
20845
+ ctx.lineTo(width - paddingRight, rangeMaxY);
20846
+ ctx.stroke();
20847
+ ctx.setLineDash([]);
20848
+ if (temperatureRanges.length > 1 || range.customerName) {
20849
+ ctx.fillStyle = colorSet.stroke;
20850
+ ctx.font = "10px system-ui, sans-serif";
20851
+ ctx.textAlign = "left";
20852
+ const labelY = (rangeMinY + rangeMaxY) / 2;
20853
+ const labelText = range.customerName || `${range.min}\xB0-${range.max}\xB0C`;
20854
+ ctx.fillText(labelText, width - paddingRight + 5, labelY + 3);
20855
+ }
20856
+ });
20769
20857
  processedData.forEach(({ device, points }) => {
20770
20858
  ctx.strokeStyle = device.color;
20771
20859
  ctx.lineWidth = 2.5;
@@ -21086,6 +21174,532 @@ ${rangeText}`;
21086
21174
  URL.revokeObjectURL(url);
21087
21175
  }
21088
21176
 
21177
+ // src/components/temperature/TemperatureSettingsModal.ts
21178
+ var DARK_THEME2 = {
21179
+ modalBg: "linear-gradient(180deg, #1e1e2e 0%, #151521 100%)",
21180
+ headerBg: "#3e1a7d",
21181
+ textPrimary: "#ffffff",
21182
+ textSecondary: "rgba(255, 255, 255, 0.7)",
21183
+ textMuted: "rgba(255, 255, 255, 0.5)",
21184
+ inputBg: "rgba(255, 255, 255, 0.08)",
21185
+ inputBorder: "rgba(255, 255, 255, 0.2)",
21186
+ inputText: "#ffffff",
21187
+ buttonPrimary: "#3e1a7d",
21188
+ buttonPrimaryHover: "#5a2da8",
21189
+ buttonSecondary: "rgba(255, 255, 255, 0.1)",
21190
+ success: "#4CAF50",
21191
+ error: "#f44336",
21192
+ overlay: "rgba(0, 0, 0, 0.85)"
21193
+ };
21194
+ var LIGHT_THEME2 = {
21195
+ modalBg: "#ffffff",
21196
+ headerBg: "#3e1a7d",
21197
+ textPrimary: "#1a1a2e",
21198
+ textSecondary: "rgba(0, 0, 0, 0.7)",
21199
+ textMuted: "rgba(0, 0, 0, 0.5)",
21200
+ inputBg: "#f5f5f5",
21201
+ inputBorder: "rgba(0, 0, 0, 0.2)",
21202
+ inputText: "#1a1a2e",
21203
+ buttonPrimary: "#3e1a7d",
21204
+ buttonPrimaryHover: "#5a2da8",
21205
+ buttonSecondary: "rgba(0, 0, 0, 0.05)",
21206
+ success: "#4CAF50",
21207
+ error: "#f44336",
21208
+ overlay: "rgba(0, 0, 0, 0.5)"
21209
+ };
21210
+ function getColors(theme) {
21211
+ return theme === "dark" ? DARK_THEME2 : LIGHT_THEME2;
21212
+ }
21213
+ async function fetchCustomerAttributes(customerId, token) {
21214
+ const url = `/api/plugins/telemetry/CUSTOMER/${customerId}/values/attributes/SERVER_SCOPE`;
21215
+ const response = await fetch(url, {
21216
+ method: "GET",
21217
+ headers: {
21218
+ "Content-Type": "application/json",
21219
+ "X-Authorization": `Bearer ${token}`
21220
+ }
21221
+ });
21222
+ if (!response.ok) {
21223
+ if (response.status === 404 || response.status === 400) {
21224
+ return { minTemperature: null, maxTemperature: null };
21225
+ }
21226
+ throw new Error(`Failed to fetch attributes: ${response.status}`);
21227
+ }
21228
+ const attributes = await response.json();
21229
+ let minTemperature = null;
21230
+ let maxTemperature = null;
21231
+ if (Array.isArray(attributes)) {
21232
+ for (const attr of attributes) {
21233
+ if (attr.key === "minTemperature") {
21234
+ minTemperature = Number(attr.value);
21235
+ } else if (attr.key === "maxTemperature") {
21236
+ maxTemperature = Number(attr.value);
21237
+ }
21238
+ }
21239
+ }
21240
+ return { minTemperature, maxTemperature };
21241
+ }
21242
+ async function saveCustomerAttributes(customerId, token, minTemperature, maxTemperature) {
21243
+ const url = `/api/plugins/telemetry/CUSTOMER/${customerId}/SERVER_SCOPE`;
21244
+ const attributes = {
21245
+ minTemperature,
21246
+ maxTemperature
21247
+ };
21248
+ const response = await fetch(url, {
21249
+ method: "POST",
21250
+ headers: {
21251
+ "Content-Type": "application/json",
21252
+ "X-Authorization": `Bearer ${token}`
21253
+ },
21254
+ body: JSON.stringify(attributes)
21255
+ });
21256
+ if (!response.ok) {
21257
+ throw new Error(`Failed to save attributes: ${response.status}`);
21258
+ }
21259
+ }
21260
+ function renderModal3(container, state, modalId, onClose, onSave) {
21261
+ const colors = getColors(state.theme);
21262
+ const minValue = state.minTemperature !== null ? state.minTemperature : "";
21263
+ const maxValue = state.maxTemperature !== null ? state.maxTemperature : "";
21264
+ container.innerHTML = `
21265
+ <style>
21266
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
21267
+ @keyframes slideIn { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
21268
+ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
21269
+
21270
+ #${modalId} {
21271
+ position: fixed;
21272
+ top: 0;
21273
+ left: 0;
21274
+ width: 100%;
21275
+ height: 100%;
21276
+ background: ${colors.overlay};
21277
+ z-index: 10000;
21278
+ display: flex;
21279
+ align-items: center;
21280
+ justify-content: center;
21281
+ animation: fadeIn 0.2s ease-out;
21282
+ }
21283
+
21284
+ #${modalId} .modal-content {
21285
+ background: ${colors.modalBg};
21286
+ border-radius: 16px;
21287
+ width: 90%;
21288
+ max-width: 480px;
21289
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
21290
+ border: 1px solid rgba(255, 255, 255, 0.1);
21291
+ animation: slideIn 0.3s ease-out;
21292
+ overflow: hidden;
21293
+ }
21294
+
21295
+ #${modalId} .modal-header {
21296
+ background: ${colors.headerBg};
21297
+ padding: 20px 24px;
21298
+ display: flex;
21299
+ align-items: center;
21300
+ justify-content: space-between;
21301
+ }
21302
+
21303
+ #${modalId} .modal-title {
21304
+ margin: 0;
21305
+ font-size: 18px;
21306
+ font-weight: 600;
21307
+ color: #fff;
21308
+ font-family: 'Roboto', sans-serif;
21309
+ display: flex;
21310
+ align-items: center;
21311
+ gap: 10px;
21312
+ }
21313
+
21314
+ #${modalId} .close-btn {
21315
+ width: 32px;
21316
+ height: 32px;
21317
+ background: rgba(255, 255, 255, 0.1);
21318
+ border: 1px solid rgba(255, 255, 255, 0.2);
21319
+ border-radius: 8px;
21320
+ cursor: pointer;
21321
+ display: flex;
21322
+ align-items: center;
21323
+ justify-content: center;
21324
+ transition: all 0.2s;
21325
+ color: #fff;
21326
+ font-size: 18px;
21327
+ }
21328
+
21329
+ #${modalId} .close-btn:hover {
21330
+ background: rgba(255, 68, 68, 0.25);
21331
+ border-color: rgba(255, 68, 68, 0.5);
21332
+ }
21333
+
21334
+ #${modalId} .modal-body {
21335
+ padding: 24px;
21336
+ }
21337
+
21338
+ #${modalId} .customer-info {
21339
+ margin-bottom: 24px;
21340
+ padding: 12px 16px;
21341
+ background: ${colors.inputBg};
21342
+ border-radius: 8px;
21343
+ border: 1px solid ${colors.inputBorder};
21344
+ }
21345
+
21346
+ #${modalId} .customer-label {
21347
+ font-size: 12px;
21348
+ color: ${colors.textMuted};
21349
+ margin-bottom: 4px;
21350
+ }
21351
+
21352
+ #${modalId} .customer-name {
21353
+ font-size: 16px;
21354
+ font-weight: 500;
21355
+ color: ${colors.textPrimary};
21356
+ }
21357
+
21358
+ #${modalId} .form-group {
21359
+ margin-bottom: 20px;
21360
+ }
21361
+
21362
+ #${modalId} .form-label {
21363
+ display: block;
21364
+ font-size: 14px;
21365
+ font-weight: 500;
21366
+ color: ${colors.textSecondary};
21367
+ margin-bottom: 8px;
21368
+ }
21369
+
21370
+ #${modalId} .form-input {
21371
+ width: 100%;
21372
+ padding: 12px 16px;
21373
+ font-size: 16px;
21374
+ background: ${colors.inputBg};
21375
+ border: 1px solid ${colors.inputBorder};
21376
+ border-radius: 8px;
21377
+ color: ${colors.inputText};
21378
+ outline: none;
21379
+ transition: border-color 0.2s;
21380
+ box-sizing: border-box;
21381
+ }
21382
+
21383
+ #${modalId} .form-input:focus {
21384
+ border-color: ${colors.buttonPrimary};
21385
+ }
21386
+
21387
+ #${modalId} .form-input::placeholder {
21388
+ color: ${colors.textMuted};
21389
+ }
21390
+
21391
+ #${modalId} .form-hint {
21392
+ font-size: 12px;
21393
+ color: ${colors.textMuted};
21394
+ margin-top: 6px;
21395
+ }
21396
+
21397
+ #${modalId} .temperature-range {
21398
+ display: grid;
21399
+ grid-template-columns: 1fr 1fr;
21400
+ gap: 16px;
21401
+ }
21402
+
21403
+ #${modalId} .range-preview {
21404
+ margin-top: 20px;
21405
+ padding: 16px;
21406
+ background: rgba(76, 175, 80, 0.1);
21407
+ border: 1px dashed ${colors.success};
21408
+ border-radius: 8px;
21409
+ text-align: center;
21410
+ }
21411
+
21412
+ #${modalId} .range-preview-label {
21413
+ font-size: 12px;
21414
+ color: ${colors.textMuted};
21415
+ margin-bottom: 8px;
21416
+ }
21417
+
21418
+ #${modalId} .range-preview-value {
21419
+ font-size: 24px;
21420
+ font-weight: 600;
21421
+ color: ${colors.success};
21422
+ }
21423
+
21424
+ #${modalId} .modal-footer {
21425
+ padding: 16px 24px;
21426
+ border-top: 1px solid ${colors.inputBorder};
21427
+ display: flex;
21428
+ justify-content: flex-end;
21429
+ gap: 12px;
21430
+ }
21431
+
21432
+ #${modalId} .btn {
21433
+ padding: 10px 24px;
21434
+ font-size: 14px;
21435
+ font-weight: 500;
21436
+ border-radius: 8px;
21437
+ cursor: pointer;
21438
+ transition: all 0.2s;
21439
+ border: none;
21440
+ display: flex;
21441
+ align-items: center;
21442
+ gap: 8px;
21443
+ }
21444
+
21445
+ #${modalId} .btn-secondary {
21446
+ background: ${colors.buttonSecondary};
21447
+ color: ${colors.textSecondary};
21448
+ border: 1px solid ${colors.inputBorder};
21449
+ }
21450
+
21451
+ #${modalId} .btn-secondary:hover {
21452
+ background: ${colors.inputBg};
21453
+ }
21454
+
21455
+ #${modalId} .btn-primary {
21456
+ background: ${colors.buttonPrimary};
21457
+ color: #fff;
21458
+ }
21459
+
21460
+ #${modalId} .btn-primary:hover {
21461
+ background: ${colors.buttonPrimaryHover};
21462
+ }
21463
+
21464
+ #${modalId} .btn-primary:disabled {
21465
+ opacity: 0.6;
21466
+ cursor: not-allowed;
21467
+ }
21468
+
21469
+ #${modalId} .spinner {
21470
+ width: 16px;
21471
+ height: 16px;
21472
+ border: 2px solid rgba(255,255,255,0.3);
21473
+ border-top-color: #fff;
21474
+ border-radius: 50%;
21475
+ animation: spin 1s linear infinite;
21476
+ }
21477
+
21478
+ #${modalId} .message {
21479
+ padding: 12px 16px;
21480
+ border-radius: 8px;
21481
+ margin-bottom: 16px;
21482
+ font-size: 14px;
21483
+ }
21484
+
21485
+ #${modalId} .message-error {
21486
+ background: rgba(244, 67, 54, 0.1);
21487
+ border: 1px solid ${colors.error};
21488
+ color: ${colors.error};
21489
+ }
21490
+
21491
+ #${modalId} .message-success {
21492
+ background: rgba(76, 175, 80, 0.1);
21493
+ border: 1px solid ${colors.success};
21494
+ color: ${colors.success};
21495
+ }
21496
+
21497
+ #${modalId} .loading-overlay {
21498
+ display: flex;
21499
+ flex-direction: column;
21500
+ align-items: center;
21501
+ justify-content: center;
21502
+ padding: 48px;
21503
+ color: ${colors.textSecondary};
21504
+ }
21505
+
21506
+ #${modalId} .loading-spinner {
21507
+ width: 40px;
21508
+ height: 40px;
21509
+ border: 3px solid ${colors.inputBorder};
21510
+ border-top-color: ${colors.buttonPrimary};
21511
+ border-radius: 50%;
21512
+ animation: spin 1s linear infinite;
21513
+ margin-bottom: 16px;
21514
+ }
21515
+ </style>
21516
+
21517
+ <div id="${modalId}" class="modal-overlay">
21518
+ <div class="modal-content">
21519
+ <div class="modal-header">
21520
+ <h2 class="modal-title">
21521
+ <span>\u{1F321}\uFE0F</span>
21522
+ Configurar Temperatura
21523
+ </h2>
21524
+ <button class="close-btn" id="${modalId}-close">&times;</button>
21525
+ </div>
21526
+
21527
+ <div class="modal-body">
21528
+ ${state.isLoading ? `
21529
+ <div class="loading-overlay">
21530
+ <div class="loading-spinner"></div>
21531
+ <div>Carregando configura\xE7\xF5es...</div>
21532
+ </div>
21533
+ ` : `
21534
+ ${state.error ? `
21535
+ <div class="message message-error">${state.error}</div>
21536
+ ` : ""}
21537
+
21538
+ ${state.successMessage ? `
21539
+ <div class="message message-success">${state.successMessage}</div>
21540
+ ` : ""}
21541
+
21542
+ <div class="customer-info">
21543
+ <div class="customer-label">Shopping / Cliente</div>
21544
+ <div class="customer-name">${state.customerName || "N\xE3o identificado"}</div>
21545
+ </div>
21546
+
21547
+ <div class="form-group">
21548
+ <label class="form-label">Faixa de Temperatura Ideal</label>
21549
+ <div class="temperature-range">
21550
+ <div>
21551
+ <input
21552
+ type="number"
21553
+ id="${modalId}-min"
21554
+ class="form-input"
21555
+ placeholder="M\xEDnima"
21556
+ value="${minValue}"
21557
+ step="0.5"
21558
+ min="0"
21559
+ max="50"
21560
+ />
21561
+ <div class="form-hint">Temperatura m\xEDnima (\xB0C)</div>
21562
+ </div>
21563
+ <div>
21564
+ <input
21565
+ type="number"
21566
+ id="${modalId}-max"
21567
+ class="form-input"
21568
+ placeholder="M\xE1xima"
21569
+ value="${maxValue}"
21570
+ step="0.5"
21571
+ min="0"
21572
+ max="50"
21573
+ />
21574
+ <div class="form-hint">Temperatura m\xE1xima (\xB0C)</div>
21575
+ </div>
21576
+ </div>
21577
+ </div>
21578
+
21579
+ <div class="range-preview" id="${modalId}-preview">
21580
+ <div class="range-preview-label">Faixa configurada</div>
21581
+ <div class="range-preview-value" id="${modalId}-preview-value">
21582
+ ${minValue && maxValue ? `${minValue}\xB0C - ${maxValue}\xB0C` : "N\xE3o definida"}
21583
+ </div>
21584
+ </div>
21585
+ `}
21586
+ </div>
21587
+
21588
+ ${!state.isLoading ? `
21589
+ <div class="modal-footer">
21590
+ <button class="btn btn-secondary" id="${modalId}-cancel">Cancelar</button>
21591
+ <button class="btn btn-primary" id="${modalId}-save" ${state.isSaving ? "disabled" : ""}>
21592
+ ${state.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
21593
+ </button>
21594
+ </div>
21595
+ ` : ""}
21596
+ </div>
21597
+ </div>
21598
+ `;
21599
+ const closeBtn = document.getElementById(`${modalId}-close`);
21600
+ const cancelBtn = document.getElementById(`${modalId}-cancel`);
21601
+ const saveBtn = document.getElementById(`${modalId}-save`);
21602
+ const minInput = document.getElementById(`${modalId}-min`);
21603
+ const maxInput = document.getElementById(`${modalId}-max`);
21604
+ const previewValue = document.getElementById(`${modalId}-preview-value`);
21605
+ const overlay = document.getElementById(modalId);
21606
+ closeBtn?.addEventListener("click", onClose);
21607
+ cancelBtn?.addEventListener("click", onClose);
21608
+ overlay?.addEventListener("click", (e) => {
21609
+ if (e.target === overlay) onClose();
21610
+ });
21611
+ const updatePreview = () => {
21612
+ if (previewValue && minInput && maxInput) {
21613
+ const min = minInput.value;
21614
+ const max = maxInput.value;
21615
+ if (min && max) {
21616
+ previewValue.textContent = `${min}\xB0C - ${max}\xB0C`;
21617
+ } else {
21618
+ previewValue.textContent = "N\xE3o definida";
21619
+ }
21620
+ }
21621
+ };
21622
+ minInput?.addEventListener("input", updatePreview);
21623
+ maxInput?.addEventListener("input", updatePreview);
21624
+ saveBtn?.addEventListener("click", async () => {
21625
+ if (!minInput || !maxInput) return;
21626
+ const min = parseFloat(minInput.value);
21627
+ const max = parseFloat(maxInput.value);
21628
+ if (isNaN(min) || isNaN(max)) {
21629
+ state.error = "Por favor, preencha ambos os valores.";
21630
+ renderModal3(container, state, modalId, onClose, onSave);
21631
+ return;
21632
+ }
21633
+ if (min >= max) {
21634
+ state.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
21635
+ renderModal3(container, state, modalId, onClose, onSave);
21636
+ return;
21637
+ }
21638
+ if (min < 0 || max > 50) {
21639
+ state.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
21640
+ renderModal3(container, state, modalId, onClose, onSave);
21641
+ return;
21642
+ }
21643
+ await onSave(min, max);
21644
+ });
21645
+ }
21646
+ function openTemperatureSettingsModal(params) {
21647
+ const modalId = `myio-temp-settings-${Date.now()}`;
21648
+ const state = {
21649
+ customerId: params.customerId,
21650
+ customerName: params.customerName || "",
21651
+ token: params.token,
21652
+ theme: params.theme || "dark",
21653
+ minTemperature: null,
21654
+ maxTemperature: null,
21655
+ isLoading: true,
21656
+ isSaving: false,
21657
+ error: null,
21658
+ successMessage: null
21659
+ };
21660
+ const container = document.createElement("div");
21661
+ container.id = `${modalId}-container`;
21662
+ document.body.appendChild(container);
21663
+ const destroy = () => {
21664
+ container.remove();
21665
+ params.onClose?.();
21666
+ };
21667
+ const handleSave = async (min, max) => {
21668
+ state.isSaving = true;
21669
+ state.error = null;
21670
+ state.successMessage = null;
21671
+ renderModal3(container, state, modalId, destroy, handleSave);
21672
+ try {
21673
+ await saveCustomerAttributes(state.customerId, state.token, min, max);
21674
+ state.minTemperature = min;
21675
+ state.maxTemperature = max;
21676
+ state.isSaving = false;
21677
+ state.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
21678
+ renderModal3(container, state, modalId, destroy, handleSave);
21679
+ params.onSave?.({ minTemperature: min, maxTemperature: max });
21680
+ setTimeout(() => {
21681
+ destroy();
21682
+ }, 1500);
21683
+ } catch (error) {
21684
+ state.isSaving = false;
21685
+ state.error = `Erro ao salvar: ${error.message}`;
21686
+ renderModal3(container, state, modalId, destroy, handleSave);
21687
+ }
21688
+ };
21689
+ renderModal3(container, state, modalId, destroy, handleSave);
21690
+ fetchCustomerAttributes(state.customerId, state.token).then(({ minTemperature, maxTemperature }) => {
21691
+ state.minTemperature = minTemperature;
21692
+ state.maxTemperature = maxTemperature;
21693
+ state.isLoading = false;
21694
+ renderModal3(container, state, modalId, destroy, handleSave);
21695
+ }).catch((error) => {
21696
+ state.isLoading = false;
21697
+ state.error = `Erro ao carregar: ${error.message}`;
21698
+ renderModal3(container, state, modalId, destroy, handleSave);
21699
+ });
21700
+ return { destroy };
21701
+ }
21702
+
21089
21703
  exports.CHART_COLORS = CHART_COLORS;
21090
21704
  exports.ConnectionStatusType = ConnectionStatusType;
21091
21705
  exports.DEFAULT_CLAMP_RANGE = DEFAULT_CLAMP_RANGE;
@@ -21172,6 +21786,7 @@ ${rangeText}`;
21172
21786
  exports.openRealTimeTelemetryModal = openRealTimeTelemetryModal;
21173
21787
  exports.openTemperatureComparisonModal = openTemperatureComparisonModal;
21174
21788
  exports.openTemperatureModal = openTemperatureModal;
21789
+ exports.openTemperatureSettingsModal = openTemperatureSettingsModal;
21175
21790
  exports.parseInputDateToDate = parseInputDateToDate;
21176
21791
  exports.renderCardComponent = renderCardComponent;
21177
21792
  exports.renderCardComponentEnhanced = renderCardComponent2;