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.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
- const numValue = Number(latest.value) || 0;
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-0080: Build mapInstantaneousPower JSON structure from form data
17142
- * Preserves existing structure and updates only the current device type
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 result = this.existingMapInstantaneousPower ? JSON.parse(JSON.stringify(this.existingMapInstantaneousPower)) : {
17146
- version: "1.0.0",
17147
- limitsByInstantaneoustPowerType: []
17148
- };
17166
+ const effectiveDeviceType = this.getEffectiveDeviceType();
17149
17167
  const telemetryType = String(formData.telemetryType || "consumption");
17150
- let telemetryConfig = result.limitsByInstantaneoustPowerType.find(
17151
- (t) => t.telemetryType === telemetryType
17152
- );
17153
- if (!telemetryConfig) {
17154
- telemetryConfig = {
17155
- telemetryType,
17156
- itemsByDeviceType: []
17157
- };
17158
- result.limitsByInstantaneoustPowerType.push(telemetryConfig);
17159
- }
17160
- const deviceType = this.deviceType.toUpperCase();
17161
- let deviceConfig = telemetryConfig.itemsByDeviceType.find(
17162
- (d) => d.deviceType === deviceType
17163
- );
17164
- if (!deviceConfig) {
17165
- deviceConfig = {
17166
- deviceType,
17167
- name: `mapInstantaneousPower${this.formatDeviceTypeName(deviceType)}`,
17168
- description: `Limites de pot\xEAncia customizados para ${deviceType}`,
17169
- limitsByDeviceStatus: []
17170
- };
17171
- telemetryConfig.itemsByDeviceType.push(deviceConfig);
17172
- }
17173
- deviceConfig.limitsByDeviceStatus = [
17174
- {
17175
- deviceStatusName: "standBy",
17176
- limitsValues: {
17177
- baseValue: Number(formData.standbyLimitDownConsumption) || 0,
17178
- topValue: Number(formData.standbyLimitUpConsumption) || 0
17179
- }
17180
- },
17181
- {
17182
- deviceStatusName: "normal",
17183
- limitsValues: {
17184
- baseValue: Number(formData.normalLimitDownConsumption) || 0,
17185
- topValue: Number(formData.normalLimitUpConsumption) || 0
17186
- }
17187
- },
17188
- {
17189
- deviceStatusName: "alert",
17190
- limitsValues: {
17191
- baseValue: Number(formData.alertLimitDownConsumption) || 0,
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
- if (formData.identifier) {
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
- renderModal3();
18162
+ renderModal4();
18152
18163
  } else {
18153
- renderModal3();
18164
+ renderModal4();
18154
18165
  loadGoalsData();
18155
18166
  }
18156
18167
  }
18157
- function renderModal3() {
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 minY = Math.min(...values) - 1;
20158
- const maxY = Math.max(...values) + 1;
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 globalMinY = Infinity;
20897
- let globalMaxY = -Infinity;
20913
+ let dataMinY = Infinity;
20914
+ let dataMaxY = -Infinity;
20898
20915
  processedData.forEach(({ points }) => {
20899
20916
  points.forEach((point) => {
20900
- if (point.y < globalMinY) globalMinY = point.y;
20901
- if (point.y > globalMaxY) globalMaxY = 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(globalMinY) - 1;
20905
- globalMaxY = Math.ceil(globalMaxY) + 1;
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">&times;</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,