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 CHANGED
@@ -655,6 +655,7 @@ __export(index_exports, {
655
655
  openRealTimeTelemetryModal: () => openRealTimeTelemetryModal,
656
656
  openTemperatureComparisonModal: () => openTemperatureComparisonModal,
657
657
  openTemperatureModal: () => openTemperatureModal,
658
+ openTemperatureSettingsModal: () => openTemperatureSettingsModal,
658
659
  parseInputDateToDate: () => parseInputDateToDate,
659
660
  renderCardComponent: () => renderCardComponent,
660
661
  renderCardComponentEnhanced: () => renderCardComponent2,
@@ -7535,7 +7536,10 @@ async function openRealTimeTelemetryModal(params) {
7535
7536
  if (!telemetryData || telemetryData.length === 0) continue;
7536
7537
  const latest = telemetryData[0];
7537
7538
  const config = TELEMETRY_CONFIG[key] || { label: key, unit: "", icon: "\u{1F4CA}", decimals: 2 };
7538
- const numValue = Number(latest.value) || 0;
7539
+ let numValue = Number(latest.value) || 0;
7540
+ if (key === "total_current" || key === "current") {
7541
+ numValue = numValue / 1e3;
7542
+ }
7539
7543
  const formatted = numValue.toFixed(config.decimals);
7540
7544
  values.push({
7541
7545
  key,
@@ -17181,13 +17185,30 @@ var DefaultSettingsPersister = class {
17181
17185
  jwtToken;
17182
17186
  tbBaseUrl;
17183
17187
  deviceType;
17188
+ deviceProfile;
17184
17189
  existingMapInstantaneousPower;
17185
17190
  constructor(jwtToken, apiConfig) {
17186
17191
  this.jwtToken = jwtToken;
17187
17192
  this.tbBaseUrl = apiConfig?.tbBaseUrl || window.location.origin;
17188
17193
  this.deviceType = apiConfig?.deviceType || "ELEVADOR";
17194
+ this.deviceProfile = apiConfig?.deviceProfile || null;
17189
17195
  this.existingMapInstantaneousPower = apiConfig?.mapInstantaneousPower || null;
17190
17196
  }
17197
+ /**
17198
+ * RFC-0086: Resolve effective device type
17199
+ * When deviceType is 3F_MEDIDOR, use deviceProfile as the actual type
17200
+ */
17201
+ getEffectiveDeviceType() {
17202
+ const normalizedType = (this.deviceType || "").toUpperCase();
17203
+ if (normalizedType === "3F_MEDIDOR") {
17204
+ const profile = (this.deviceProfile || "").toUpperCase();
17205
+ if (profile && profile !== "N/D" && profile.trim() !== "") {
17206
+ console.log(`[SettingsPersister] RFC-0086: Resolved 3F_MEDIDOR \u2192 ${profile}`);
17207
+ return profile;
17208
+ }
17209
+ }
17210
+ return normalizedType || "ELEVADOR";
17211
+ }
17191
17212
  async saveEntityLabel(deviceId, label) {
17192
17213
  try {
17193
17214
  const getRes = await fetch(`${this.tbBaseUrl}/api/device/${deviceId}`, {
@@ -17245,72 +17266,59 @@ var DefaultSettingsPersister = class {
17245
17266
  }
17246
17267
  }
17247
17268
  /**
17248
- * RFC-0080: Build mapInstantaneousPower JSON structure from form data
17249
- * Preserves existing structure and updates only the current device type
17269
+ * RFC-0086: Build mapInstantaneousPower JSON structure from form data
17270
+ * IMPORTANT: When saving to a DEVICE, only include entries for the specific deviceType
17271
+ * Uses getEffectiveDeviceType() to resolve 3F_MEDIDOR → deviceProfile
17250
17272
  */
17251
17273
  buildMapInstantaneousPower(formData) {
17252
- const result = this.existingMapInstantaneousPower ? JSON.parse(JSON.stringify(this.existingMapInstantaneousPower)) : {
17253
- version: "1.0.0",
17254
- limitsByInstantaneoustPowerType: []
17255
- };
17274
+ const effectiveDeviceType = this.getEffectiveDeviceType();
17256
17275
  const telemetryType = String(formData.telemetryType || "consumption");
17257
- let telemetryConfig = result.limitsByInstantaneoustPowerType.find(
17258
- (t) => t.telemetryType === telemetryType
17259
- );
17260
- if (!telemetryConfig) {
17261
- telemetryConfig = {
17262
- telemetryType,
17263
- itemsByDeviceType: []
17264
- };
17265
- result.limitsByInstantaneoustPowerType.push(telemetryConfig);
17266
- }
17267
- const deviceType = this.deviceType.toUpperCase();
17268
- let deviceConfig = telemetryConfig.itemsByDeviceType.find(
17269
- (d) => d.deviceType === deviceType
17270
- );
17271
- if (!deviceConfig) {
17272
- deviceConfig = {
17273
- deviceType,
17274
- name: `mapInstantaneousPower${this.formatDeviceTypeName(deviceType)}`,
17275
- description: `Limites de pot\xEAncia customizados para ${deviceType}`,
17276
- limitsByDeviceStatus: []
17277
- };
17278
- telemetryConfig.itemsByDeviceType.push(deviceConfig);
17279
- }
17280
- deviceConfig.limitsByDeviceStatus = [
17281
- {
17282
- deviceStatusName: "standBy",
17283
- limitsValues: {
17284
- baseValue: Number(formData.standbyLimitDownConsumption) || 0,
17285
- topValue: Number(formData.standbyLimitUpConsumption) || 0
17286
- }
17287
- },
17288
- {
17289
- deviceStatusName: "normal",
17290
- limitsValues: {
17291
- baseValue: Number(formData.normalLimitDownConsumption) || 0,
17292
- topValue: Number(formData.normalLimitUpConsumption) || 0
17293
- }
17294
- },
17295
- {
17296
- deviceStatusName: "alert",
17297
- limitsValues: {
17298
- baseValue: Number(formData.alertLimitDownConsumption) || 0,
17299
- topValue: Number(formData.alertLimitUpConsumption) || 0
17300
- }
17301
- },
17302
- {
17303
- deviceStatusName: "failure",
17304
- limitsValues: {
17305
- baseValue: Number(formData.failureLimitDownConsumption) || 0,
17306
- topValue: Number(formData.failureLimitUpConsumption) || 0
17276
+ const result = {
17277
+ version: "1.0.0",
17278
+ limitsByInstantaneoustPowerType: [
17279
+ {
17280
+ telemetryType,
17281
+ itemsByDeviceType: [
17282
+ {
17283
+ deviceType: effectiveDeviceType,
17284
+ name: `mapInstantaneousPower${this.formatDeviceTypeName(effectiveDeviceType)}`,
17285
+ description: formData.identifier ? `Limites customizados para ${formData.identifier}` : `Limites de pot\xEAncia customizados para ${effectiveDeviceType}`,
17286
+ limitsByDeviceStatus: [
17287
+ {
17288
+ deviceStatusName: "standBy",
17289
+ limitsValues: {
17290
+ baseValue: Number(formData.standbyLimitDownConsumption) || 0,
17291
+ topValue: Number(formData.standbyLimitUpConsumption) || 0
17292
+ }
17293
+ },
17294
+ {
17295
+ deviceStatusName: "normal",
17296
+ limitsValues: {
17297
+ baseValue: Number(formData.normalLimitDownConsumption) || 0,
17298
+ topValue: Number(formData.normalLimitUpConsumption) || 0
17299
+ }
17300
+ },
17301
+ {
17302
+ deviceStatusName: "alert",
17303
+ limitsValues: {
17304
+ baseValue: Number(formData.alertLimitDownConsumption) || 0,
17305
+ topValue: Number(formData.alertLimitUpConsumption) || 0
17306
+ }
17307
+ },
17308
+ {
17309
+ deviceStatusName: "failure",
17310
+ limitsValues: {
17311
+ baseValue: Number(formData.failureLimitDownConsumption) || 0,
17312
+ topValue: Number(formData.failureLimitUpConsumption) || 0
17313
+ }
17314
+ }
17315
+ ]
17316
+ }
17317
+ ]
17307
17318
  }
17308
- }
17309
- ];
17310
- if (formData.identifier) {
17311
- deviceConfig.description = `Limites customizados para ${formData.identifier}`;
17312
- }
17313
- console.log("[SettingsPersister] RFC-0080: Built mapInstantaneousPower:", result);
17319
+ ]
17320
+ };
17321
+ console.log(`[SettingsPersister] RFC-0086: Built mapInstantaneousPower for deviceType=${effectiveDeviceType}:`, result);
17314
17322
  return result;
17315
17323
  }
17316
17324
  /**
@@ -17549,6 +17557,8 @@ var SettingsController = class {
17549
17557
  const apiConfigWithDeviceInfo = {
17550
17558
  ...params.api,
17551
17559
  deviceType: params.deviceType,
17560
+ deviceProfile: params.deviceProfile,
17561
+ // RFC-0086: For 3F_MEDIDOR → deviceProfile resolution
17552
17562
  mapInstantaneousPower: params.mapInstantaneousPower
17553
17563
  };
17554
17564
  this.persister = params.persister || new DefaultSettingsPersister(params.jwtToken, apiConfigWithDeviceInfo);
@@ -17600,6 +17610,8 @@ var SettingsController = class {
17600
17610
  this.persister = new DefaultSettingsPersister(this.params.jwtToken, {
17601
17611
  ...this.params.api,
17602
17612
  deviceType: this.params.deviceType,
17613
+ deviceProfile: this.params.deviceProfile,
17614
+ // RFC-0086: For 3F_MEDIDOR → deviceProfile resolution
17603
17615
  mapInstantaneousPower: globalMap
17604
17616
  });
17605
17617
  this.view.updateMapInstantaneousPower(globalMap);
@@ -18255,13 +18267,13 @@ function openGoalsPanel(params) {
18255
18267
  function initializeModal() {
18256
18268
  if (data) {
18257
18269
  modalState.goalsData = data;
18258
- renderModal3();
18270
+ renderModal4();
18259
18271
  } else {
18260
- renderModal3();
18272
+ renderModal4();
18261
18273
  loadGoalsData();
18262
18274
  }
18263
18275
  }
18264
- function renderModal3() {
18276
+ function renderModal4() {
18265
18277
  const existing = document.getElementById("myio-goals-panel-modal");
18266
18278
  if (existing) {
18267
18279
  existing.remove();
@@ -20261,8 +20273,12 @@ function drawChart(modalId, state) {
20261
20273
  const paddingBottom = 55;
20262
20274
  const isPeriodsFiltered = state.selectedPeriods.length < 4 && state.selectedPeriods.length > 0;
20263
20275
  const values = chartData.map((d) => d.y);
20264
- const minY = Math.min(...values) - 1;
20265
- const maxY = Math.max(...values) + 1;
20276
+ const dataMin = Math.min(...values);
20277
+ const dataMax = Math.max(...values);
20278
+ const thresholdMin = state.temperatureMin !== null ? state.temperatureMin : dataMin;
20279
+ const thresholdMax = state.temperatureMax !== null ? state.temperatureMax : dataMax;
20280
+ const minY = Math.min(dataMin, thresholdMin) - 1;
20281
+ const maxY = Math.max(dataMax, thresholdMax) + 1;
20266
20282
  const chartWidth = width - paddingLeft - paddingRight;
20267
20283
  const chartHeight = height - paddingTop - paddingBottom;
20268
20284
  const scaleY = chartHeight / (maxY - minY || 1);
@@ -20603,8 +20619,10 @@ async function openTemperatureComparisonModal(params) {
20603
20619
  deviceData: [],
20604
20620
  isLoading: true,
20605
20621
  dateRangePicker: null,
20606
- selectedPeriods: ["madrugada", "manha", "tarde", "noite"]
20622
+ selectedPeriods: ["madrugada", "manha", "tarde", "noite"],
20607
20623
  // All periods selected by default
20624
+ temperatureMin: params.temperatureMin ?? null,
20625
+ temperatureMax: params.temperatureMax ?? null
20608
20626
  };
20609
20627
  const savedGranularity = localStorage.getItem("myio-temp-comparison-granularity");
20610
20628
  const savedTheme = localStorage.getItem("myio-temp-comparison-theme");
@@ -21000,16 +21018,52 @@ function drawComparisonChart(modalId, state) {
21000
21018
  return;
21001
21019
  }
21002
21020
  const isPeriodsFiltered = state.selectedPeriods.length < 4 && state.selectedPeriods.length > 0;
21003
- let globalMinY = Infinity;
21004
- let globalMaxY = -Infinity;
21021
+ let dataMinY = Infinity;
21022
+ let dataMaxY = -Infinity;
21005
21023
  processedData.forEach(({ points }) => {
21006
21024
  points.forEach((point) => {
21007
- if (point.y < globalMinY) globalMinY = point.y;
21008
- if (point.y > globalMaxY) globalMaxY = point.y;
21025
+ if (point.y < dataMinY) dataMinY = point.y;
21026
+ if (point.y > dataMaxY) dataMaxY = point.y;
21027
+ });
21028
+ });
21029
+ const rangeMap = /* @__PURE__ */ new Map();
21030
+ state.deviceData.forEach((dd, index) => {
21031
+ const device = dd.device;
21032
+ const min = device.temperatureMin;
21033
+ const max = device.temperatureMax;
21034
+ if (min !== void 0 && min !== null && max !== void 0 && max !== null) {
21035
+ const key = `${min}-${max}`;
21036
+ if (!rangeMap.has(key)) {
21037
+ rangeMap.set(key, {
21038
+ min,
21039
+ max,
21040
+ customerName: device.customerName || "",
21041
+ color: CHART_COLORS[index % CHART_COLORS.length],
21042
+ deviceLabels: [device.label]
21043
+ });
21044
+ } else {
21045
+ rangeMap.get(key).deviceLabels.push(device.label);
21046
+ }
21047
+ }
21048
+ });
21049
+ if (rangeMap.size === 0 && state.temperatureMin !== null && state.temperatureMax !== null) {
21050
+ rangeMap.set("global", {
21051
+ min: state.temperatureMin,
21052
+ max: state.temperatureMax,
21053
+ customerName: "Global",
21054
+ color: colors.success,
21055
+ deviceLabels: []
21009
21056
  });
21057
+ }
21058
+ const temperatureRanges = Array.from(rangeMap.values());
21059
+ let thresholdMinY = dataMinY;
21060
+ let thresholdMaxY = dataMaxY;
21061
+ temperatureRanges.forEach((range) => {
21062
+ if (range.min < thresholdMinY) thresholdMinY = range.min;
21063
+ if (range.max > thresholdMaxY) thresholdMaxY = range.max;
21010
21064
  });
21011
- globalMinY = Math.floor(globalMinY) - 1;
21012
- globalMaxY = Math.ceil(globalMaxY) + 1;
21065
+ const globalMinY = Math.floor(Math.min(dataMinY, thresholdMinY)) - 1;
21066
+ const globalMaxY = Math.ceil(Math.max(dataMaxY, thresholdMaxY)) + 1;
21013
21067
  const chartWidth = width - paddingLeft - paddingRight;
21014
21068
  const chartHeight = height - paddingTop - paddingBottom;
21015
21069
  const scaleY = chartHeight / (globalMaxY - globalMinY || 1);
@@ -21049,6 +21103,41 @@ function drawComparisonChart(modalId, state) {
21049
21103
  ctx.lineTo(width - paddingRight, y);
21050
21104
  ctx.stroke();
21051
21105
  }
21106
+ const rangeColors = [
21107
+ { fill: "rgba(76, 175, 80, 0.12)", stroke: "#4CAF50" },
21108
+ // Green
21109
+ { fill: "rgba(33, 150, 243, 0.12)", stroke: "#2196F3" },
21110
+ // Blue
21111
+ { fill: "rgba(255, 152, 0, 0.12)", stroke: "#FF9800" },
21112
+ // Orange
21113
+ { fill: "rgba(156, 39, 176, 0.12)", stroke: "#9C27B0" }
21114
+ // Purple
21115
+ ];
21116
+ temperatureRanges.forEach((range, index) => {
21117
+ const rangeMinY = height - paddingBottom - (range.min - globalMinY) * scaleY;
21118
+ const rangeMaxY = height - paddingBottom - (range.max - globalMinY) * scaleY;
21119
+ const colorSet = rangeColors[index % rangeColors.length];
21120
+ ctx.fillStyle = colorSet.fill;
21121
+ ctx.fillRect(paddingLeft, rangeMaxY, chartWidth, rangeMinY - rangeMaxY);
21122
+ ctx.strokeStyle = colorSet.stroke;
21123
+ ctx.lineWidth = 1.5;
21124
+ ctx.setLineDash([6, 4]);
21125
+ ctx.beginPath();
21126
+ ctx.moveTo(paddingLeft, rangeMinY);
21127
+ ctx.lineTo(width - paddingRight, rangeMinY);
21128
+ ctx.moveTo(paddingLeft, rangeMaxY);
21129
+ ctx.lineTo(width - paddingRight, rangeMaxY);
21130
+ ctx.stroke();
21131
+ ctx.setLineDash([]);
21132
+ if (temperatureRanges.length > 1 || range.customerName) {
21133
+ ctx.fillStyle = colorSet.stroke;
21134
+ ctx.font = "10px system-ui, sans-serif";
21135
+ ctx.textAlign = "left";
21136
+ const labelY = (rangeMinY + rangeMaxY) / 2;
21137
+ const labelText = range.customerName || `${range.min}\xB0-${range.max}\xB0C`;
21138
+ ctx.fillText(labelText, width - paddingRight + 5, labelY + 3);
21139
+ }
21140
+ });
21052
21141
  processedData.forEach(({ device, points }) => {
21053
21142
  ctx.strokeStyle = device.color;
21054
21143
  ctx.lineWidth = 2.5;
@@ -21368,6 +21457,532 @@ function exportComparisonCSV(state) {
21368
21457
  document.body.removeChild(link);
21369
21458
  URL.revokeObjectURL(url);
21370
21459
  }
21460
+
21461
+ // src/components/temperature/TemperatureSettingsModal.ts
21462
+ var DARK_THEME2 = {
21463
+ modalBg: "linear-gradient(180deg, #1e1e2e 0%, #151521 100%)",
21464
+ headerBg: "#3e1a7d",
21465
+ textPrimary: "#ffffff",
21466
+ textSecondary: "rgba(255, 255, 255, 0.7)",
21467
+ textMuted: "rgba(255, 255, 255, 0.5)",
21468
+ inputBg: "rgba(255, 255, 255, 0.08)",
21469
+ inputBorder: "rgba(255, 255, 255, 0.2)",
21470
+ inputText: "#ffffff",
21471
+ buttonPrimary: "#3e1a7d",
21472
+ buttonPrimaryHover: "#5a2da8",
21473
+ buttonSecondary: "rgba(255, 255, 255, 0.1)",
21474
+ success: "#4CAF50",
21475
+ error: "#f44336",
21476
+ overlay: "rgba(0, 0, 0, 0.85)"
21477
+ };
21478
+ var LIGHT_THEME2 = {
21479
+ modalBg: "#ffffff",
21480
+ headerBg: "#3e1a7d",
21481
+ textPrimary: "#1a1a2e",
21482
+ textSecondary: "rgba(0, 0, 0, 0.7)",
21483
+ textMuted: "rgba(0, 0, 0, 0.5)",
21484
+ inputBg: "#f5f5f5",
21485
+ inputBorder: "rgba(0, 0, 0, 0.2)",
21486
+ inputText: "#1a1a2e",
21487
+ buttonPrimary: "#3e1a7d",
21488
+ buttonPrimaryHover: "#5a2da8",
21489
+ buttonSecondary: "rgba(0, 0, 0, 0.05)",
21490
+ success: "#4CAF50",
21491
+ error: "#f44336",
21492
+ overlay: "rgba(0, 0, 0, 0.5)"
21493
+ };
21494
+ function getColors(theme) {
21495
+ return theme === "dark" ? DARK_THEME2 : LIGHT_THEME2;
21496
+ }
21497
+ async function fetchCustomerAttributes(customerId, token) {
21498
+ const url = `/api/plugins/telemetry/CUSTOMER/${customerId}/values/attributes/SERVER_SCOPE`;
21499
+ const response = await fetch(url, {
21500
+ method: "GET",
21501
+ headers: {
21502
+ "Content-Type": "application/json",
21503
+ "X-Authorization": `Bearer ${token}`
21504
+ }
21505
+ });
21506
+ if (!response.ok) {
21507
+ if (response.status === 404 || response.status === 400) {
21508
+ return { minTemperature: null, maxTemperature: null };
21509
+ }
21510
+ throw new Error(`Failed to fetch attributes: ${response.status}`);
21511
+ }
21512
+ const attributes = await response.json();
21513
+ let minTemperature = null;
21514
+ let maxTemperature = null;
21515
+ if (Array.isArray(attributes)) {
21516
+ for (const attr of attributes) {
21517
+ if (attr.key === "minTemperature") {
21518
+ minTemperature = Number(attr.value);
21519
+ } else if (attr.key === "maxTemperature") {
21520
+ maxTemperature = Number(attr.value);
21521
+ }
21522
+ }
21523
+ }
21524
+ return { minTemperature, maxTemperature };
21525
+ }
21526
+ async function saveCustomerAttributes(customerId, token, minTemperature, maxTemperature) {
21527
+ const url = `/api/plugins/telemetry/CUSTOMER/${customerId}/SERVER_SCOPE`;
21528
+ const attributes = {
21529
+ minTemperature,
21530
+ maxTemperature
21531
+ };
21532
+ const response = await fetch(url, {
21533
+ method: "POST",
21534
+ headers: {
21535
+ "Content-Type": "application/json",
21536
+ "X-Authorization": `Bearer ${token}`
21537
+ },
21538
+ body: JSON.stringify(attributes)
21539
+ });
21540
+ if (!response.ok) {
21541
+ throw new Error(`Failed to save attributes: ${response.status}`);
21542
+ }
21543
+ }
21544
+ function renderModal3(container, state, modalId, onClose, onSave) {
21545
+ const colors = getColors(state.theme);
21546
+ const minValue = state.minTemperature !== null ? state.minTemperature : "";
21547
+ const maxValue = state.maxTemperature !== null ? state.maxTemperature : "";
21548
+ container.innerHTML = `
21549
+ <style>
21550
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
21551
+ @keyframes slideIn { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
21552
+ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
21553
+
21554
+ #${modalId} {
21555
+ position: fixed;
21556
+ top: 0;
21557
+ left: 0;
21558
+ width: 100%;
21559
+ height: 100%;
21560
+ background: ${colors.overlay};
21561
+ z-index: 10000;
21562
+ display: flex;
21563
+ align-items: center;
21564
+ justify-content: center;
21565
+ animation: fadeIn 0.2s ease-out;
21566
+ }
21567
+
21568
+ #${modalId} .modal-content {
21569
+ background: ${colors.modalBg};
21570
+ border-radius: 16px;
21571
+ width: 90%;
21572
+ max-width: 480px;
21573
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
21574
+ border: 1px solid rgba(255, 255, 255, 0.1);
21575
+ animation: slideIn 0.3s ease-out;
21576
+ overflow: hidden;
21577
+ }
21578
+
21579
+ #${modalId} .modal-header {
21580
+ background: ${colors.headerBg};
21581
+ padding: 20px 24px;
21582
+ display: flex;
21583
+ align-items: center;
21584
+ justify-content: space-between;
21585
+ }
21586
+
21587
+ #${modalId} .modal-title {
21588
+ margin: 0;
21589
+ font-size: 18px;
21590
+ font-weight: 600;
21591
+ color: #fff;
21592
+ font-family: 'Roboto', sans-serif;
21593
+ display: flex;
21594
+ align-items: center;
21595
+ gap: 10px;
21596
+ }
21597
+
21598
+ #${modalId} .close-btn {
21599
+ width: 32px;
21600
+ height: 32px;
21601
+ background: rgba(255, 255, 255, 0.1);
21602
+ border: 1px solid rgba(255, 255, 255, 0.2);
21603
+ border-radius: 8px;
21604
+ cursor: pointer;
21605
+ display: flex;
21606
+ align-items: center;
21607
+ justify-content: center;
21608
+ transition: all 0.2s;
21609
+ color: #fff;
21610
+ font-size: 18px;
21611
+ }
21612
+
21613
+ #${modalId} .close-btn:hover {
21614
+ background: rgba(255, 68, 68, 0.25);
21615
+ border-color: rgba(255, 68, 68, 0.5);
21616
+ }
21617
+
21618
+ #${modalId} .modal-body {
21619
+ padding: 24px;
21620
+ }
21621
+
21622
+ #${modalId} .customer-info {
21623
+ margin-bottom: 24px;
21624
+ padding: 12px 16px;
21625
+ background: ${colors.inputBg};
21626
+ border-radius: 8px;
21627
+ border: 1px solid ${colors.inputBorder};
21628
+ }
21629
+
21630
+ #${modalId} .customer-label {
21631
+ font-size: 12px;
21632
+ color: ${colors.textMuted};
21633
+ margin-bottom: 4px;
21634
+ }
21635
+
21636
+ #${modalId} .customer-name {
21637
+ font-size: 16px;
21638
+ font-weight: 500;
21639
+ color: ${colors.textPrimary};
21640
+ }
21641
+
21642
+ #${modalId} .form-group {
21643
+ margin-bottom: 20px;
21644
+ }
21645
+
21646
+ #${modalId} .form-label {
21647
+ display: block;
21648
+ font-size: 14px;
21649
+ font-weight: 500;
21650
+ color: ${colors.textSecondary};
21651
+ margin-bottom: 8px;
21652
+ }
21653
+
21654
+ #${modalId} .form-input {
21655
+ width: 100%;
21656
+ padding: 12px 16px;
21657
+ font-size: 16px;
21658
+ background: ${colors.inputBg};
21659
+ border: 1px solid ${colors.inputBorder};
21660
+ border-radius: 8px;
21661
+ color: ${colors.inputText};
21662
+ outline: none;
21663
+ transition: border-color 0.2s;
21664
+ box-sizing: border-box;
21665
+ }
21666
+
21667
+ #${modalId} .form-input:focus {
21668
+ border-color: ${colors.buttonPrimary};
21669
+ }
21670
+
21671
+ #${modalId} .form-input::placeholder {
21672
+ color: ${colors.textMuted};
21673
+ }
21674
+
21675
+ #${modalId} .form-hint {
21676
+ font-size: 12px;
21677
+ color: ${colors.textMuted};
21678
+ margin-top: 6px;
21679
+ }
21680
+
21681
+ #${modalId} .temperature-range {
21682
+ display: grid;
21683
+ grid-template-columns: 1fr 1fr;
21684
+ gap: 16px;
21685
+ }
21686
+
21687
+ #${modalId} .range-preview {
21688
+ margin-top: 20px;
21689
+ padding: 16px;
21690
+ background: rgba(76, 175, 80, 0.1);
21691
+ border: 1px dashed ${colors.success};
21692
+ border-radius: 8px;
21693
+ text-align: center;
21694
+ }
21695
+
21696
+ #${modalId} .range-preview-label {
21697
+ font-size: 12px;
21698
+ color: ${colors.textMuted};
21699
+ margin-bottom: 8px;
21700
+ }
21701
+
21702
+ #${modalId} .range-preview-value {
21703
+ font-size: 24px;
21704
+ font-weight: 600;
21705
+ color: ${colors.success};
21706
+ }
21707
+
21708
+ #${modalId} .modal-footer {
21709
+ padding: 16px 24px;
21710
+ border-top: 1px solid ${colors.inputBorder};
21711
+ display: flex;
21712
+ justify-content: flex-end;
21713
+ gap: 12px;
21714
+ }
21715
+
21716
+ #${modalId} .btn {
21717
+ padding: 10px 24px;
21718
+ font-size: 14px;
21719
+ font-weight: 500;
21720
+ border-radius: 8px;
21721
+ cursor: pointer;
21722
+ transition: all 0.2s;
21723
+ border: none;
21724
+ display: flex;
21725
+ align-items: center;
21726
+ gap: 8px;
21727
+ }
21728
+
21729
+ #${modalId} .btn-secondary {
21730
+ background: ${colors.buttonSecondary};
21731
+ color: ${colors.textSecondary};
21732
+ border: 1px solid ${colors.inputBorder};
21733
+ }
21734
+
21735
+ #${modalId} .btn-secondary:hover {
21736
+ background: ${colors.inputBg};
21737
+ }
21738
+
21739
+ #${modalId} .btn-primary {
21740
+ background: ${colors.buttonPrimary};
21741
+ color: #fff;
21742
+ }
21743
+
21744
+ #${modalId} .btn-primary:hover {
21745
+ background: ${colors.buttonPrimaryHover};
21746
+ }
21747
+
21748
+ #${modalId} .btn-primary:disabled {
21749
+ opacity: 0.6;
21750
+ cursor: not-allowed;
21751
+ }
21752
+
21753
+ #${modalId} .spinner {
21754
+ width: 16px;
21755
+ height: 16px;
21756
+ border: 2px solid rgba(255,255,255,0.3);
21757
+ border-top-color: #fff;
21758
+ border-radius: 50%;
21759
+ animation: spin 1s linear infinite;
21760
+ }
21761
+
21762
+ #${modalId} .message {
21763
+ padding: 12px 16px;
21764
+ border-radius: 8px;
21765
+ margin-bottom: 16px;
21766
+ font-size: 14px;
21767
+ }
21768
+
21769
+ #${modalId} .message-error {
21770
+ background: rgba(244, 67, 54, 0.1);
21771
+ border: 1px solid ${colors.error};
21772
+ color: ${colors.error};
21773
+ }
21774
+
21775
+ #${modalId} .message-success {
21776
+ background: rgba(76, 175, 80, 0.1);
21777
+ border: 1px solid ${colors.success};
21778
+ color: ${colors.success};
21779
+ }
21780
+
21781
+ #${modalId} .loading-overlay {
21782
+ display: flex;
21783
+ flex-direction: column;
21784
+ align-items: center;
21785
+ justify-content: center;
21786
+ padding: 48px;
21787
+ color: ${colors.textSecondary};
21788
+ }
21789
+
21790
+ #${modalId} .loading-spinner {
21791
+ width: 40px;
21792
+ height: 40px;
21793
+ border: 3px solid ${colors.inputBorder};
21794
+ border-top-color: ${colors.buttonPrimary};
21795
+ border-radius: 50%;
21796
+ animation: spin 1s linear infinite;
21797
+ margin-bottom: 16px;
21798
+ }
21799
+ </style>
21800
+
21801
+ <div id="${modalId}" class="modal-overlay">
21802
+ <div class="modal-content">
21803
+ <div class="modal-header">
21804
+ <h2 class="modal-title">
21805
+ <span>\u{1F321}\uFE0F</span>
21806
+ Configurar Temperatura
21807
+ </h2>
21808
+ <button class="close-btn" id="${modalId}-close">&times;</button>
21809
+ </div>
21810
+
21811
+ <div class="modal-body">
21812
+ ${state.isLoading ? `
21813
+ <div class="loading-overlay">
21814
+ <div class="loading-spinner"></div>
21815
+ <div>Carregando configura\xE7\xF5es...</div>
21816
+ </div>
21817
+ ` : `
21818
+ ${state.error ? `
21819
+ <div class="message message-error">${state.error}</div>
21820
+ ` : ""}
21821
+
21822
+ ${state.successMessage ? `
21823
+ <div class="message message-success">${state.successMessage}</div>
21824
+ ` : ""}
21825
+
21826
+ <div class="customer-info">
21827
+ <div class="customer-label">Shopping / Cliente</div>
21828
+ <div class="customer-name">${state.customerName || "N\xE3o identificado"}</div>
21829
+ </div>
21830
+
21831
+ <div class="form-group">
21832
+ <label class="form-label">Faixa de Temperatura Ideal</label>
21833
+ <div class="temperature-range">
21834
+ <div>
21835
+ <input
21836
+ type="number"
21837
+ id="${modalId}-min"
21838
+ class="form-input"
21839
+ placeholder="M\xEDnima"
21840
+ value="${minValue}"
21841
+ step="0.5"
21842
+ min="0"
21843
+ max="50"
21844
+ />
21845
+ <div class="form-hint">Temperatura m\xEDnima (\xB0C)</div>
21846
+ </div>
21847
+ <div>
21848
+ <input
21849
+ type="number"
21850
+ id="${modalId}-max"
21851
+ class="form-input"
21852
+ placeholder="M\xE1xima"
21853
+ value="${maxValue}"
21854
+ step="0.5"
21855
+ min="0"
21856
+ max="50"
21857
+ />
21858
+ <div class="form-hint">Temperatura m\xE1xima (\xB0C)</div>
21859
+ </div>
21860
+ </div>
21861
+ </div>
21862
+
21863
+ <div class="range-preview" id="${modalId}-preview">
21864
+ <div class="range-preview-label">Faixa configurada</div>
21865
+ <div class="range-preview-value" id="${modalId}-preview-value">
21866
+ ${minValue && maxValue ? `${minValue}\xB0C - ${maxValue}\xB0C` : "N\xE3o definida"}
21867
+ </div>
21868
+ </div>
21869
+ `}
21870
+ </div>
21871
+
21872
+ ${!state.isLoading ? `
21873
+ <div class="modal-footer">
21874
+ <button class="btn btn-secondary" id="${modalId}-cancel">Cancelar</button>
21875
+ <button class="btn btn-primary" id="${modalId}-save" ${state.isSaving ? "disabled" : ""}>
21876
+ ${state.isSaving ? '<div class="spinner"></div> Salvando...' : "Salvar"}
21877
+ </button>
21878
+ </div>
21879
+ ` : ""}
21880
+ </div>
21881
+ </div>
21882
+ `;
21883
+ const closeBtn = document.getElementById(`${modalId}-close`);
21884
+ const cancelBtn = document.getElementById(`${modalId}-cancel`);
21885
+ const saveBtn = document.getElementById(`${modalId}-save`);
21886
+ const minInput = document.getElementById(`${modalId}-min`);
21887
+ const maxInput = document.getElementById(`${modalId}-max`);
21888
+ const previewValue = document.getElementById(`${modalId}-preview-value`);
21889
+ const overlay = document.getElementById(modalId);
21890
+ closeBtn?.addEventListener("click", onClose);
21891
+ cancelBtn?.addEventListener("click", onClose);
21892
+ overlay?.addEventListener("click", (e) => {
21893
+ if (e.target === overlay) onClose();
21894
+ });
21895
+ const updatePreview = () => {
21896
+ if (previewValue && minInput && maxInput) {
21897
+ const min = minInput.value;
21898
+ const max = maxInput.value;
21899
+ if (min && max) {
21900
+ previewValue.textContent = `${min}\xB0C - ${max}\xB0C`;
21901
+ } else {
21902
+ previewValue.textContent = "N\xE3o definida";
21903
+ }
21904
+ }
21905
+ };
21906
+ minInput?.addEventListener("input", updatePreview);
21907
+ maxInput?.addEventListener("input", updatePreview);
21908
+ saveBtn?.addEventListener("click", async () => {
21909
+ if (!minInput || !maxInput) return;
21910
+ const min = parseFloat(minInput.value);
21911
+ const max = parseFloat(maxInput.value);
21912
+ if (isNaN(min) || isNaN(max)) {
21913
+ state.error = "Por favor, preencha ambos os valores.";
21914
+ renderModal3(container, state, modalId, onClose, onSave);
21915
+ return;
21916
+ }
21917
+ if (min >= max) {
21918
+ state.error = "A temperatura m\xEDnima deve ser menor que a m\xE1xima.";
21919
+ renderModal3(container, state, modalId, onClose, onSave);
21920
+ return;
21921
+ }
21922
+ if (min < 0 || max > 50) {
21923
+ state.error = "Os valores devem estar entre 0\xB0C e 50\xB0C.";
21924
+ renderModal3(container, state, modalId, onClose, onSave);
21925
+ return;
21926
+ }
21927
+ await onSave(min, max);
21928
+ });
21929
+ }
21930
+ function openTemperatureSettingsModal(params) {
21931
+ const modalId = `myio-temp-settings-${Date.now()}`;
21932
+ const state = {
21933
+ customerId: params.customerId,
21934
+ customerName: params.customerName || "",
21935
+ token: params.token,
21936
+ theme: params.theme || "dark",
21937
+ minTemperature: null,
21938
+ maxTemperature: null,
21939
+ isLoading: true,
21940
+ isSaving: false,
21941
+ error: null,
21942
+ successMessage: null
21943
+ };
21944
+ const container = document.createElement("div");
21945
+ container.id = `${modalId}-container`;
21946
+ document.body.appendChild(container);
21947
+ const destroy = () => {
21948
+ container.remove();
21949
+ params.onClose?.();
21950
+ };
21951
+ const handleSave = async (min, max) => {
21952
+ state.isSaving = true;
21953
+ state.error = null;
21954
+ state.successMessage = null;
21955
+ renderModal3(container, state, modalId, destroy, handleSave);
21956
+ try {
21957
+ await saveCustomerAttributes(state.customerId, state.token, min, max);
21958
+ state.minTemperature = min;
21959
+ state.maxTemperature = max;
21960
+ state.isSaving = false;
21961
+ state.successMessage = "Configura\xE7\xF5es salvas com sucesso!";
21962
+ renderModal3(container, state, modalId, destroy, handleSave);
21963
+ params.onSave?.({ minTemperature: min, maxTemperature: max });
21964
+ setTimeout(() => {
21965
+ destroy();
21966
+ }, 1500);
21967
+ } catch (error) {
21968
+ state.isSaving = false;
21969
+ state.error = `Erro ao salvar: ${error.message}`;
21970
+ renderModal3(container, state, modalId, destroy, handleSave);
21971
+ }
21972
+ };
21973
+ renderModal3(container, state, modalId, destroy, handleSave);
21974
+ fetchCustomerAttributes(state.customerId, state.token).then(({ minTemperature, maxTemperature }) => {
21975
+ state.minTemperature = minTemperature;
21976
+ state.maxTemperature = maxTemperature;
21977
+ state.isLoading = false;
21978
+ renderModal3(container, state, modalId, destroy, handleSave);
21979
+ }).catch((error) => {
21980
+ state.isLoading = false;
21981
+ state.error = `Erro ao carregar: ${error.message}`;
21982
+ renderModal3(container, state, modalId, destroy, handleSave);
21983
+ });
21984
+ return { destroy };
21985
+ }
21371
21986
  // Annotate the CommonJS export names for ESM import in node:
21372
21987
  0 && (module.exports = {
21373
21988
  CHART_COLORS,
@@ -21457,6 +22072,7 @@ function exportComparisonCSV(state) {
21457
22072
  openRealTimeTelemetryModal,
21458
22073
  openTemperatureComparisonModal,
21459
22074
  openTemperatureModal,
22075
+ openTemperatureSettingsModal,
21460
22076
  parseInputDateToDate,
21461
22077
  renderCardComponent,
21462
22078
  renderCardComponentEnhanced,