myio-js-library 0.1.181 → 0.1.182

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.
@@ -5910,185 +5910,253 @@
5910
5910
  };
5911
5911
  }
5912
5912
 
5913
- // src/thingsboard/main-dashboard-shopping/v-4.0.0/card/head-office/card-head-office.js
5914
- var LABEL_CHAR_LIMIT = 18;
5915
- var DEFAUL_DELAY_TIME_CONNECTION_IN_MINS = 1440;
5916
- var CSS_TAG = "head-office-card-v1";
5917
- function ensureCss() {
5918
- if (!document.querySelector(`style[data-myio-css="${CSS_TAG}"]`)) {
5919
- const style = document.createElement("style");
5920
- style.setAttribute("data-myio-css", CSS_TAG);
5921
- style.textContent = CSS_STRING;
5922
- document.head.appendChild(style);
5923
- }
5924
- }
5925
- function normalizeParams(params) {
5926
- if (!params || !params.entityObject) {
5927
- throw new Error("renderCardCompenteHeadOffice: entityObject is required");
5928
- }
5929
- const LogHelper2 = createLogHelper(params.debugActive ?? false);
5930
- const entityObject = params.entityObject;
5931
- if (!entityObject.entityId) {
5932
- LogHelper2.warn("[CardHeadOffice] entityId is missing, generating temporary ID");
5933
- entityObject.entityId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
5934
- }
5935
- if (!params.delayTimeConnectionInMins) {
5936
- LogHelper2.warn(
5937
- `[CardHeadOffice] delayTimeConnectionInMins is missing, defaulting to ${DEFAUL_DELAY_TIME_CONNECTION_IN_MINS} mins`
5938
- );
5939
- }
5940
- return {
5941
- entityObject,
5942
- i18n: { ...DEFAULT_I18N, ...params.i18n || {} },
5943
- enableSelection: Boolean(params.enableSelection),
5944
- enableDragDrop: Boolean(params.enableDragDrop),
5945
- useNewComponents: Boolean(params.useNewComponents),
5946
- // RFC-0091: Configurable delay time for connection status check (default 15 minutes)
5947
- delayTimeConnectionInMins: params.delayTimeConnectionInMins ?? DEFAUL_DELAY_TIME_CONNECTION_IN_MINS,
5948
- // Debug options
5949
- debugActive: params.debugActive ?? false,
5950
- activeTooltipDebug: params.activeTooltipDebug ?? false,
5951
- // LogHelper instance for this card
5952
- LogHelper: LogHelper2,
5953
- callbacks: {
5954
- handleActionDashboard: params.handleActionDashboard,
5955
- handleActionReport: params.handleActionReport,
5956
- handleActionSettings: params.handleActionSettings,
5957
- handleSelect: params.handleSelect,
5958
- handInfo: params.handInfo,
5959
- handleClickCard: params.handleClickCard
5960
- }
5961
- };
5962
- }
5963
- function getIconSvg(deviceType, domain) {
5964
- if (domain === "water") {
5965
- return Icons.waterDrop;
5966
- }
5967
- if (domain === "temperature") {
5968
- return Icons.thermometer;
5969
- }
5970
- return ICON_MAP[deviceType] || ICON_MAP.DEFAULT;
5971
- }
5972
- function formatPower(valueInWatts) {
5973
- if (valueInWatts === null || valueInWatts === void 0 || isNaN(valueInWatts)) {
5974
- return { num: "-", unit: "" };
5975
- }
5976
- const val = Number(valueInWatts);
5977
- if (val >= 1e3) {
5978
- const kw = Math.ceil(val / 1e3 * 100) / 100;
5979
- return { num: kw.toFixed(2), unit: "kW" };
5980
- } else {
5981
- const w = Math.ceil(val);
5982
- return { num: w.toString(), unit: "W" };
5983
- }
5984
- }
5985
- function formatValueByDomain(value, domain) {
5986
- if (domain === "water") {
5987
- return formatWaterVolumeM3(value);
5988
- }
5989
- if (domain === "temperature") {
5990
- return formatTemperature(value, 0);
5991
- }
5992
- return formatEnergy(value);
5993
- }
5994
- function formatRelativeTime2(timestamp) {
5995
- if (!timestamp || isNaN(timestamp)) return "\u2014";
5996
- const date = new Date(timestamp);
5997
- const hours = String(date.getHours()).padStart(2, "0");
5998
- const minutes = String(date.getMinutes()).padStart(2, "0");
5999
- const day = String(date.getDate()).padStart(2, "0");
6000
- const month = String(date.getMonth() + 1).padStart(2, "0");
6001
- const year = date.getFullYear();
6002
- return `${hours}:${minutes} ${day}/${month}/${year}`;
6003
- }
6004
- function calculateConsumptionPercentage(target, consumption) {
6005
- const numericTarget = Number(target);
6006
- const numericConsumption = Number(consumption);
6007
- if (isNaN(numericTarget) || isNaN(numericConsumption) || numericTarget <= 0) {
6008
- return 0;
6009
- }
6010
- const percentage = numericConsumption / numericTarget * 100;
6011
- return percentage;
5913
+ // src/utils/TempRangeTooltip.ts
5914
+ var TOOLTIP_STYLES = `
5915
+ .temp-range-tooltip {
5916
+ position: fixed;
5917
+ z-index: 99999;
5918
+ pointer-events: none;
5919
+ opacity: 0;
5920
+ transition: opacity 0.2s ease, transform 0.2s ease;
5921
+ transform: translateY(5px);
5922
+ }
5923
+
5924
+ .temp-range-tooltip.visible {
5925
+ opacity: 1;
5926
+ pointer-events: auto;
5927
+ transform: translateY(0);
5928
+ }
5929
+
5930
+ .temp-range-tooltip__content {
5931
+ background: #ffffff;
5932
+ border: 1px solid #e2e8f0;
5933
+ border-radius: 12px;
5934
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 2px 10px rgba(0, 0, 0, 0.08);
5935
+ min-width: 280px;
5936
+ max-width: 320px;
5937
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5938
+ font-size: 12px;
5939
+ color: #1e293b;
5940
+ overflow: hidden;
5941
+ }
5942
+
5943
+ .temp-range-tooltip__header {
5944
+ display: flex;
5945
+ align-items: center;
5946
+ gap: 8px;
5947
+ padding: 12px 16px;
5948
+ background: linear-gradient(90deg, #fff7ed 0%, #fed7aa 100%);
5949
+ border-bottom: 1px solid #fdba74;
5950
+ }
5951
+
5952
+ .temp-range-tooltip__icon {
5953
+ font-size: 18px;
5954
+ }
5955
+
5956
+ .temp-range-tooltip__title {
5957
+ font-weight: 700;
5958
+ font-size: 13px;
5959
+ color: #c2410c;
5960
+ }
5961
+
5962
+ .temp-range-tooltip__body {
5963
+ padding: 16px;
5964
+ }
5965
+
5966
+ .temp-range-tooltip__value-row {
5967
+ display: flex;
5968
+ justify-content: space-between;
5969
+ align-items: center;
5970
+ margin-bottom: 16px;
5971
+ }
5972
+
5973
+ .temp-range-tooltip__current {
5974
+ font-size: 28px;
5975
+ font-weight: 700;
5976
+ color: #1e293b;
5977
+ }
5978
+
5979
+ .temp-range-tooltip__current sup {
5980
+ font-size: 14px;
5981
+ color: #64748b;
5982
+ }
5983
+
5984
+ .temp-range-tooltip__deviation {
5985
+ text-align: right;
5986
+ }
5987
+
5988
+ .temp-range-tooltip__deviation-value {
5989
+ font-size: 16px;
5990
+ font-weight: 700;
5991
+ }
5992
+
5993
+ .temp-range-tooltip__deviation-value.cold {
5994
+ color: #2563eb;
5995
+ }
5996
+
5997
+ .temp-range-tooltip__deviation-value.ok {
5998
+ color: #16a34a;
5999
+ }
6000
+
6001
+ .temp-range-tooltip__deviation-value.hot {
6002
+ color: #dc2626;
6003
+ }
6004
+
6005
+ .temp-range-tooltip__deviation-label {
6006
+ font-size: 10px;
6007
+ color: #64748b;
6008
+ text-transform: uppercase;
6009
+ letter-spacing: 0.5px;
6010
+ }
6011
+
6012
+ .temp-range-tooltip__ruler {
6013
+ position: relative;
6014
+ height: 32px;
6015
+ margin: 12px 0;
6016
+ border-radius: 8px;
6017
+ overflow: visible;
6018
+ }
6019
+
6020
+ .temp-range-tooltip__ruler-track {
6021
+ position: absolute;
6022
+ top: 12px;
6023
+ left: 0;
6024
+ right: 0;
6025
+ height: 8px;
6026
+ background: linear-gradient(90deg, #dbeafe 0%, #dcfce7 50%, #fee2e2 100%);
6027
+ border-radius: 4px;
6028
+ border: 1px solid #e2e8f0;
6029
+ }
6030
+
6031
+ .temp-range-tooltip__ruler-range {
6032
+ position: absolute;
6033
+ top: 12px;
6034
+ height: 8px;
6035
+ background: #22c55e;
6036
+ border-radius: 4px;
6037
+ opacity: 0.6;
6038
+ }
6039
+
6040
+ .temp-range-tooltip__ruler-marker {
6041
+ position: absolute;
6042
+ top: 4px;
6043
+ width: 4px;
6044
+ height: 24px;
6045
+ background: #1e293b;
6046
+ border-radius: 2px;
6047
+ transform: translateX(-50%);
6048
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
6049
+ }
6050
+
6051
+ .temp-range-tooltip__ruler-marker::after {
6052
+ content: '';
6053
+ position: absolute;
6054
+ top: -4px;
6055
+ left: 50%;
6056
+ transform: translateX(-50%);
6057
+ width: 12px;
6058
+ height: 12px;
6059
+ background: #1e293b;
6060
+ border-radius: 50%;
6061
+ border: 2px solid #fff;
6062
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
6063
+ }
6064
+
6065
+ .temp-range-tooltip__ruler-labels {
6066
+ display: flex;
6067
+ justify-content: space-between;
6068
+ margin-top: 8px;
6069
+ font-size: 10px;
6070
+ color: #64748b;
6071
+ }
6072
+
6073
+ .temp-range-tooltip__ruler-min,
6074
+ .temp-range-tooltip__ruler-max {
6075
+ font-weight: 600;
6076
+ }
6077
+
6078
+ .temp-range-tooltip__range-info {
6079
+ display: flex;
6080
+ justify-content: space-between;
6081
+ padding: 10px 12px;
6082
+ background: #f8fafc;
6083
+ border-radius: 8px;
6084
+ margin-top: 12px;
6085
+ }
6086
+
6087
+ .temp-range-tooltip__range-item {
6088
+ text-align: center;
6089
+ }
6090
+
6091
+ .temp-range-tooltip__range-label {
6092
+ font-size: 10px;
6093
+ color: #64748b;
6094
+ text-transform: uppercase;
6095
+ letter-spacing: 0.3px;
6096
+ margin-bottom: 2px;
6097
+ }
6098
+
6099
+ .temp-range-tooltip__range-value {
6100
+ font-size: 14px;
6101
+ font-weight: 600;
6102
+ color: #334155;
6103
+ }
6104
+
6105
+ .temp-range-tooltip__status {
6106
+ display: flex;
6107
+ align-items: center;
6108
+ justify-content: center;
6109
+ gap: 6px;
6110
+ margin-top: 12px;
6111
+ padding: 8px 12px;
6112
+ border-radius: 6px;
6113
+ font-size: 11px;
6114
+ font-weight: 600;
6115
+ }
6116
+
6117
+ .temp-range-tooltip__status.cold {
6118
+ background: #dbeafe;
6119
+ color: #1d4ed8;
6120
+ border: 1px solid #93c5fd;
6121
+ }
6122
+
6123
+ .temp-range-tooltip__status.ok {
6124
+ background: #dcfce7;
6125
+ color: #15803d;
6126
+ border: 1px solid #86efac;
6127
+ }
6128
+
6129
+ .temp-range-tooltip__status.hot {
6130
+ background: #fee2e2;
6131
+ color: #b91c1c;
6132
+ border: 1px solid #fca5a5;
6133
+ }
6134
+
6135
+ .temp-range-tooltip__status.unknown {
6136
+ background: #f3f4f6;
6137
+ color: #6b7280;
6138
+ border: 1px solid #d1d5db;
6139
+ }
6140
+ `;
6141
+ function injectStyles() {
6142
+ if (typeof document === "undefined") return;
6143
+ const styleId = "myio-temp-range-tooltip-styles";
6144
+ if (document.getElementById(styleId)) return;
6145
+ const style = document.createElement("style");
6146
+ style.id = styleId;
6147
+ style.textContent = TOOLTIP_STYLES;
6148
+ document.head.appendChild(style);
6012
6149
  }
6013
- function getStatusInfo(deviceStatus, i18n, domain) {
6014
- switch (deviceStatus) {
6015
- // --- Novos Status de Temperatura ---
6016
- case "normal":
6017
- return { chipClass: "chip--ok", label: "Normal" };
6018
- case "cold":
6019
- return { chipClass: "chip--standby", label: "Frio" };
6020
- case "hot":
6021
- return { chipClass: "chip--alert", label: "Quente" };
6022
- // --- Status Existentes (aligned with getCardStateClass) ---
6023
- case DeviceStatusType.POWER_ON:
6024
- if (domain === "water") {
6025
- return { chipClass: "chip--power-on", label: i18n.in_operation_water };
6026
- }
6027
- return { chipClass: "chip--power-on", label: i18n.in_operation };
6028
- case DeviceStatusType.STANDBY:
6029
- return { chipClass: "chip--standby", label: i18n.standby };
6030
- case DeviceStatusType.WARNING:
6031
- return { chipClass: "chip--warning", label: i18n.alert };
6032
- case DeviceStatusType.MAINTENANCE:
6033
- return { chipClass: "chip--maintenance", label: i18n.maintenance };
6034
- case DeviceStatusType.FAILURE:
6035
- return { chipClass: "chip--failure", label: i18n.failure };
6036
- case DeviceStatusType.POWER_OFF:
6037
- return { chipClass: "chip--power-off", label: i18n.power_off || i18n.failure };
6038
- case DeviceStatusType.OFFLINE:
6039
- return { chipClass: "chip--offline", label: i18n.offline };
6040
- case DeviceStatusType.NO_INFO:
6041
- return { chipClass: "chip--no-info", label: i18n.no_info || i18n.offline };
6042
- case DeviceStatusType.NOT_INSTALLED:
6043
- return { chipClass: "chip--not-installed", label: i18n.not_installed };
6044
- default:
6045
- return { chipClass: "chip--offline", label: i18n.offline };
6046
- }
6150
+ function extractTemperature(entityData) {
6151
+ return Number(entityData.val ?? entityData.currentTemperature ?? entityData.temperature) || 0;
6047
6152
  }
6048
- function getCardStateClass(deviceStatus) {
6049
- switch (deviceStatus) {
6050
- case DeviceStatusType.POWER_ON:
6051
- return "is-power-on";
6052
- // Blue border
6053
- case DeviceStatusType.STANDBY:
6054
- return "is-standby";
6055
- // Green border
6056
- case DeviceStatusType.WARNING:
6057
- return "is-warning";
6058
- // Yellow border
6059
- case DeviceStatusType.MAINTENANCE:
6060
- return "is-maintenance";
6061
- // Yellow border
6062
- case DeviceStatusType.FAILURE:
6063
- return "is-failure";
6064
- // Dark Red border
6065
- case DeviceStatusType.POWER_OFF:
6066
- return "is-power-off";
6067
- // Light Red border
6068
- case DeviceStatusType.OFFLINE:
6069
- return "is-offline";
6070
- // Dark Gray border
6071
- case DeviceStatusType.NO_INFO:
6072
- return "is-no-info";
6073
- // Dark Orange border
6074
- case DeviceStatusType.NOT_INSTALLED:
6075
- return "is-not-installed";
6076
- // Purple border
6077
- default:
6078
- return "";
6079
- }
6153
+ function extractRange(entityData) {
6154
+ const tempMin = entityData.temperatureMin ?? entityData.minTemperature ?? null;
6155
+ const tempMax = entityData.temperatureMax ?? entityData.maxTemperature ?? null;
6156
+ return { tempMin, tempMax };
6080
6157
  }
6081
- function getTempRangeClass(entityObject) {
6082
- if (entityObject.domain !== "temperature") return "";
6083
- const currentTemp = Number(entityObject.val ?? entityObject.currentTemperature ?? entityObject.temperature) || 0;
6084
- const tempMin = entityObject.temperatureMin ?? entityObject.minTemperature;
6085
- const tempMax = entityObject.temperatureMax ?? entityObject.maxTemperature;
6086
- if (tempMin === void 0 || tempMax === void 0 || tempMin === null || tempMax === null) {
6087
- return "";
6088
- }
6089
- if (currentTemp > tempMax) return "is-temp-hot";
6090
- if (currentTemp < tempMin) return "is-temp-cold";
6091
- return "is-temp-ok";
6158
+ function extractLabel(entityData) {
6159
+ return entityData.labelOrName || entityData.name || entityData.label || "Sensor";
6092
6160
  }
6093
6161
  var TempRangeTooltip = {
6094
6162
  containerId: "myio-temp-range-tooltip",
@@ -6096,6 +6164,7 @@
6096
6164
  * Create or get the tooltip container
6097
6165
  */
6098
6166
  getContainer() {
6167
+ injectStyles();
6099
6168
  let container = document.getElementById(this.containerId);
6100
6169
  if (!container) {
6101
6170
  container = document.createElement("div");
@@ -6143,20 +6212,20 @@
6143
6212
  },
6144
6213
  /**
6145
6214
  * Show tooltip for a temperature card
6146
- * @param {HTMLElement} triggerElement - The card element
6147
- * @param {Object} entityObject - Entity data
6148
- * @param {MouseEvent} event - Mouse event for cursor position
6215
+ * @param triggerElement - The card element
6216
+ * @param entityData - Entity data with temperature info
6217
+ * @param event - Mouse event for cursor position
6149
6218
  */
6150
- show(triggerElement, entityObject, event) {
6219
+ show(triggerElement, entityData, event) {
6151
6220
  const container = this.getContainer();
6152
- const currentTemp = Number(entityObject.val ?? entityObject.currentTemperature ?? entityObject.temperature) || 0;
6153
- const tempMin = entityObject.temperatureMin ?? entityObject.minTemperature;
6154
- const tempMax = entityObject.temperatureMax ?? entityObject.maxTemperature;
6221
+ const currentTemp = extractTemperature(entityData);
6222
+ const { tempMin, tempMax } = extractRange(entityData);
6223
+ const label = extractLabel(entityData);
6155
6224
  const hasRange = tempMin != null && tempMax != null;
6156
- const { status, deviation, deviationPercent } = this.calculateStatus(currentTemp, tempMin, tempMax);
6225
+ const { status, deviationPercent } = this.calculateStatus(currentTemp, tempMin, tempMax);
6157
6226
  const markerPos = this.calculateMarkerPosition(currentTemp, tempMin, tempMax);
6158
6227
  let rangeLeft = 0, rangeWidth = 100;
6159
- if (hasRange) {
6228
+ if (hasRange && tempMin != null && tempMax != null) {
6160
6229
  const rangeSize = tempMax - tempMin;
6161
6230
  const extension = rangeSize * 0.3;
6162
6231
  const visibleMin = tempMin - extension;
@@ -6165,23 +6234,23 @@
6165
6234
  rangeLeft = (tempMin - visibleMin) / visibleRange * 100;
6166
6235
  rangeWidth = rangeSize / visibleRange * 100;
6167
6236
  }
6168
- let deviationClass = status;
6169
- if (status === "cold") {
6170
- `${Math.abs(deviation).toFixed(1)}\xB0C abaixo`;
6171
- } else if (status === "hot") {
6172
- `+${deviation.toFixed(1)}\xB0C acima`;
6173
- } else ;
6174
6237
  const statusLabels = {
6175
6238
  cold: "\u2744\uFE0F Abaixo da Faixa Ideal",
6176
6239
  ok: "\u2714\uFE0F Dentro da Faixa Ideal",
6177
6240
  hot: "\u{1F525} Acima da Faixa Ideal",
6178
6241
  unknown: "\u2753 Faixa N\xE3o Configurada"
6179
6242
  };
6243
+ const statusColors = {
6244
+ cold: "#2563eb",
6245
+ ok: "#16a34a",
6246
+ hot: "#dc2626",
6247
+ unknown: "#64748b"
6248
+ };
6180
6249
  container.innerHTML = `
6181
6250
  <div class="temp-range-tooltip__content">
6182
6251
  <div class="temp-range-tooltip__header">
6183
6252
  <span class="temp-range-tooltip__icon">\u{1F321}\uFE0F</span>
6184
- <span class="temp-range-tooltip__title">${entityObject.labelOrName || entityObject.name || "Sensor"}</span>
6253
+ <span class="temp-range-tooltip__title">${label}</span>
6185
6254
  </div>
6186
6255
  <div class="temp-range-tooltip__body">
6187
6256
  <div class="temp-range-tooltip__value-row">
@@ -6189,79 +6258,392 @@
6189
6258
  ${currentTemp.toFixed(1)}<sup>\xB0C</sup>
6190
6259
  </div>
6191
6260
  <div class="temp-range-tooltip__deviation">
6192
- <div class="temp-range-tooltip__deviation-value ${deviationClass}">
6261
+ <div class="temp-range-tooltip__deviation-value ${status}">
6193
6262
  ${status === "ok" ? "\u2713" : status === "cold" ? "\u2193" : status === "hot" ? "\u2191" : "?"} ${Math.abs(deviationPercent || 0).toFixed(0)}%
6194
6263
  </div>
6195
6264
  <div class="temp-range-tooltip__deviation-label">Desvio</div>
6196
6265
  </div>
6197
6266
  </div>
6198
6267
 
6199
- ${hasRange ? `
6200
- <div class="temp-range-tooltip__ruler">
6201
- <div class="temp-range-tooltip__ruler-track"></div>
6202
- <div class="temp-range-tooltip__ruler-range" style="left: ${rangeLeft}%; width: ${rangeWidth}%;"></div>
6203
- <div class="temp-range-tooltip__ruler-marker" style="left: ${markerPos}%;"></div>
6204
- </div>
6205
- <div class="temp-range-tooltip__ruler-labels">
6206
- <span class="temp-range-tooltip__ruler-min">${tempMin}\xB0C</span>
6207
- <span style="color: #22c55e; font-weight: 600;">Faixa Ideal</span>
6208
- <span class="temp-range-tooltip__ruler-max">${tempMax}\xB0C</span>
6209
- </div>
6210
- ` : ""}
6268
+ ${hasRange ? `
6269
+ <div class="temp-range-tooltip__ruler">
6270
+ <div class="temp-range-tooltip__ruler-track"></div>
6271
+ <div class="temp-range-tooltip__ruler-range" style="left: ${rangeLeft}%; width: ${rangeWidth}%;"></div>
6272
+ <div class="temp-range-tooltip__ruler-marker" style="left: ${markerPos}%;"></div>
6273
+ </div>
6274
+ <div class="temp-range-tooltip__ruler-labels">
6275
+ <span class="temp-range-tooltip__ruler-min">${tempMin}\xB0C</span>
6276
+ <span style="color: #22c55e; font-weight: 600;">Faixa Ideal</span>
6277
+ <span class="temp-range-tooltip__ruler-max">${tempMax}\xB0C</span>
6278
+ </div>
6279
+ ` : ""}
6280
+
6281
+ <div class="temp-range-tooltip__range-info">
6282
+ <div class="temp-range-tooltip__range-item">
6283
+ <div class="temp-range-tooltip__range-label">M\xEDnimo</div>
6284
+ <div class="temp-range-tooltip__range-value">${hasRange ? tempMin + "\xB0C" : "--"}</div>
6285
+ </div>
6286
+ <div class="temp-range-tooltip__range-item">
6287
+ <div class="temp-range-tooltip__range-label">Atual</div>
6288
+ <div class="temp-range-tooltip__range-value" style="color: ${statusColors[status]}">${currentTemp.toFixed(1)}\xB0C</div>
6289
+ </div>
6290
+ <div class="temp-range-tooltip__range-item">
6291
+ <div class="temp-range-tooltip__range-label">M\xE1ximo</div>
6292
+ <div class="temp-range-tooltip__range-value">${hasRange ? tempMax + "\xB0C" : "--"}</div>
6293
+ </div>
6294
+ </div>
6295
+
6296
+ <div class="temp-range-tooltip__status ${status}">
6297
+ ${statusLabels[status]}
6298
+ </div>
6299
+ </div>
6300
+ </div>
6301
+ `;
6302
+ let left, top;
6303
+ if (event && event.clientX && event.clientY) {
6304
+ left = event.clientX + 15;
6305
+ top = event.clientY + 15;
6306
+ } else {
6307
+ const rect = triggerElement.getBoundingClientRect();
6308
+ left = rect.left + rect.width / 2 - 150;
6309
+ top = rect.bottom + 8;
6310
+ }
6311
+ const tooltipWidth = 300;
6312
+ const tooltipHeight = 350;
6313
+ if (left + tooltipWidth > window.innerWidth - 10) {
6314
+ left = (event?.clientX || left) - tooltipWidth - 15;
6315
+ }
6316
+ if (left < 10) left = 10;
6317
+ if (top + tooltipHeight > window.innerHeight - 10) {
6318
+ top = (event?.clientY || top) - tooltipHeight - 15;
6319
+ }
6320
+ if (top < 10) top = 10;
6321
+ container.style.left = left + "px";
6322
+ container.style.top = top + "px";
6323
+ container.classList.add("visible");
6324
+ },
6325
+ /**
6326
+ * Hide tooltip
6327
+ */
6328
+ hide() {
6329
+ const container = document.getElementById(this.containerId);
6330
+ if (container) {
6331
+ container.classList.remove("visible");
6332
+ }
6333
+ },
6334
+ /**
6335
+ * Attach tooltip to an element
6336
+ * Returns cleanup function
6337
+ */
6338
+ attach(element, entityData) {
6339
+ const showHandler = (e) => this.show(element, entityData, e);
6340
+ const hideHandler = () => this.hide();
6341
+ element.style.cursor = "help";
6342
+ element.addEventListener("mouseenter", showHandler);
6343
+ element.addEventListener("mouseleave", hideHandler);
6344
+ return () => {
6345
+ element.removeEventListener("mouseenter", showHandler);
6346
+ element.removeEventListener("mouseleave", hideHandler);
6347
+ element.style.cursor = "";
6348
+ this.hide();
6349
+ };
6350
+ }
6351
+ };
6352
+
6353
+ // src/utils/EnergyRangeTooltip.ts
6354
+ var ENERGY_RANGE_TOOLTIP_CSS = `
6355
+ /* ============================================
6356
+ Energy Range Tooltip (for domain=energy)
6357
+ Shows power ruler with current position and status ranges
6358
+ ============================================ */
6359
+ .energy-range-tooltip {
6360
+ position: fixed;
6361
+ z-index: 99999;
6362
+ pointer-events: none;
6363
+ opacity: 0;
6364
+ transition: opacity 0.2s ease, transform 0.2s ease;
6365
+ transform: translateY(5px);
6366
+ }
6367
+
6368
+ .energy-range-tooltip.visible {
6369
+ opacity: 1;
6370
+ pointer-events: auto;
6371
+ transform: translateY(0);
6372
+ }
6373
+
6374
+ .energy-range-tooltip__content {
6375
+ background: #ffffff;
6376
+ border: 1px solid #e2e8f0;
6377
+ border-radius: 12px;
6378
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 2px 10px rgba(0, 0, 0, 0.08);
6379
+ min-width: 300px;
6380
+ max-width: 360px;
6381
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
6382
+ font-size: 12px;
6383
+ color: #1e293b;
6384
+ overflow: hidden;
6385
+ }
6386
+
6387
+ .energy-range-tooltip__header {
6388
+ display: flex;
6389
+ align-items: center;
6390
+ gap: 8px;
6391
+ padding: 12px 16px;
6392
+ background: linear-gradient(90deg, #ecfdf5 0%, #d1fae5 100%);
6393
+ border-bottom: 1px solid #6ee7b7;
6394
+ }
6395
+
6396
+ .energy-range-tooltip__icon {
6397
+ font-size: 18px;
6398
+ }
6399
+
6400
+ .energy-range-tooltip__title {
6401
+ font-weight: 700;
6402
+ font-size: 13px;
6403
+ color: #047857;
6404
+ }
6405
+
6406
+ .energy-range-tooltip__body {
6407
+ padding: 16px;
6408
+ }
6409
+
6410
+ /* Power value display */
6411
+ .energy-range-tooltip__value-row {
6412
+ display: flex;
6413
+ justify-content: space-between;
6414
+ align-items: center;
6415
+ margin-bottom: 16px;
6416
+ }
6417
+
6418
+ .energy-range-tooltip__current {
6419
+ font-size: 28px;
6420
+ font-weight: 700;
6421
+ color: #1e293b;
6422
+ }
6211
6423
 
6212
- <div class="temp-range-tooltip__range-info">
6213
- <div class="temp-range-tooltip__range-item">
6214
- <div class="temp-range-tooltip__range-label">M\xEDnimo</div>
6215
- <div class="temp-range-tooltip__range-value">${hasRange ? tempMin + "\xB0C" : "--"}</div>
6216
- </div>
6217
- <div class="temp-range-tooltip__range-item">
6218
- <div class="temp-range-tooltip__range-label">Atual</div>
6219
- <div class="temp-range-tooltip__range-value" style="color: ${status === "ok" ? "#16a34a" : status === "cold" ? "#2563eb" : "#dc2626"}">${currentTemp.toFixed(1)}\xB0C</div>
6220
- </div>
6221
- <div class="temp-range-tooltip__range-item">
6222
- <div class="temp-range-tooltip__range-label">M\xE1ximo</div>
6223
- <div class="temp-range-tooltip__range-value">${hasRange ? tempMax + "\xB0C" : "--"}</div>
6224
- </div>
6225
- </div>
6424
+ .energy-range-tooltip__current sup {
6425
+ font-size: 14px;
6426
+ color: #64748b;
6427
+ }
6226
6428
 
6227
- <div class="temp-range-tooltip__status ${status}">
6228
- ${statusLabels[status]}
6229
- </div>
6230
- </div>
6231
- </div>
6232
- `;
6233
- let left, top;
6234
- if (event && event.clientX && event.clientY) {
6235
- left = event.clientX + 15;
6236
- top = event.clientY + 15;
6237
- } else {
6238
- const rect = triggerElement.getBoundingClientRect();
6239
- left = rect.left + rect.width / 2 - 150;
6240
- top = rect.bottom + 8;
6241
- }
6242
- const tooltipWidth = 300;
6243
- const tooltipHeight = 350;
6244
- if (left + tooltipWidth > window.innerWidth - 10) {
6245
- left = (event?.clientX || left) - tooltipWidth - 15;
6246
- }
6247
- if (left < 10) left = 10;
6248
- if (top + tooltipHeight > window.innerHeight - 10) {
6249
- top = (event?.clientY || top) - tooltipHeight - 15;
6250
- }
6251
- if (top < 10) top = 10;
6252
- container.style.left = left + "px";
6253
- container.style.top = top + "px";
6254
- container.classList.add("visible");
6255
- },
6256
- /**
6257
- * Hide tooltip
6258
- */
6259
- hide() {
6260
- const container = document.getElementById(this.containerId);
6261
- if (container) {
6262
- container.classList.remove("visible");
6263
- }
6429
+ .energy-range-tooltip__status-badge {
6430
+ text-align: right;
6431
+ }
6432
+
6433
+ .energy-range-tooltip__status-value {
6434
+ font-size: 14px;
6435
+ font-weight: 700;
6436
+ padding: 4px 10px;
6437
+ border-radius: 6px;
6438
+ }
6439
+
6440
+ .energy-range-tooltip__status-value.standby {
6441
+ background: #dbeafe;
6442
+ color: #1d4ed8;
6443
+ }
6444
+
6445
+ .energy-range-tooltip__status-value.normal {
6446
+ background: #dcfce7;
6447
+ color: #15803d;
6448
+ }
6449
+
6450
+ .energy-range-tooltip__status-value.alert {
6451
+ background: #fef3c7;
6452
+ color: #b45309;
6453
+ }
6454
+
6455
+ .energy-range-tooltip__status-value.failure {
6456
+ background: #fee2e2;
6457
+ color: #b91c1c;
6458
+ }
6459
+
6460
+ .energy-range-tooltip__status-value.offline {
6461
+ background: #f3f4f6;
6462
+ color: #6b7280;
6463
+ }
6464
+
6465
+ /* Power ruler/gauge */
6466
+ .energy-range-tooltip__ruler {
6467
+ position: relative;
6468
+ height: 40px;
6469
+ margin: 12px 0;
6470
+ border-radius: 8px;
6471
+ overflow: visible;
6472
+ }
6473
+
6474
+ .energy-range-tooltip__ruler-track {
6475
+ position: absolute;
6476
+ top: 16px;
6477
+ left: 0;
6478
+ right: 0;
6479
+ height: 8px;
6480
+ display: flex;
6481
+ border-radius: 4px;
6482
+ overflow: hidden;
6483
+ border: 1px solid #e2e8f0;
6484
+ }
6485
+
6486
+ .energy-range-tooltip__ruler-segment {
6487
+ height: 100%;
6488
+ }
6489
+
6490
+ .energy-range-tooltip__ruler-segment.standby {
6491
+ background: #dbeafe;
6492
+ }
6493
+
6494
+ .energy-range-tooltip__ruler-segment.normal {
6495
+ background: #dcfce7;
6496
+ }
6497
+
6498
+ .energy-range-tooltip__ruler-segment.alert {
6499
+ background: #fef3c7;
6500
+ }
6501
+
6502
+ .energy-range-tooltip__ruler-segment.failure {
6503
+ background: #fee2e2;
6504
+ }
6505
+
6506
+ .energy-range-tooltip__ruler-marker {
6507
+ position: absolute;
6508
+ top: 8px;
6509
+ width: 4px;
6510
+ height: 24px;
6511
+ background: #1e293b;
6512
+ border-radius: 2px;
6513
+ transform: translateX(-50%);
6514
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
6515
+ }
6516
+
6517
+ .energy-range-tooltip__ruler-marker::after {
6518
+ content: '';
6519
+ position: absolute;
6520
+ top: -4px;
6521
+ left: 50%;
6522
+ transform: translateX(-50%);
6523
+ width: 12px;
6524
+ height: 12px;
6525
+ background: #1e293b;
6526
+ border-radius: 50%;
6527
+ border: 2px solid #fff;
6528
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
6529
+ }
6530
+
6531
+ /* Range info grid */
6532
+ .energy-range-tooltip__ranges {
6533
+ display: grid;
6534
+ grid-template-columns: repeat(4, 1fr);
6535
+ gap: 8px;
6536
+ margin-top: 16px;
6537
+ }
6538
+
6539
+ .energy-range-tooltip__range-item {
6540
+ text-align: center;
6541
+ padding: 8px 4px;
6542
+ border-radius: 6px;
6543
+ background: #f8fafc;
6544
+ }
6545
+
6546
+ .energy-range-tooltip__range-item.standby {
6547
+ border-left: 3px solid #3b82f6;
6548
+ }
6549
+
6550
+ .energy-range-tooltip__range-item.normal {
6551
+ border-left: 3px solid #22c55e;
6552
+ }
6553
+
6554
+ .energy-range-tooltip__range-item.alert {
6555
+ border-left: 3px solid #f59e0b;
6556
+ }
6557
+
6558
+ .energy-range-tooltip__range-item.failure {
6559
+ border-left: 3px solid #ef4444;
6560
+ }
6561
+
6562
+ .energy-range-tooltip__range-label {
6563
+ font-size: 9px;
6564
+ color: #64748b;
6565
+ text-transform: uppercase;
6566
+ letter-spacing: 0.3px;
6567
+ margin-bottom: 2px;
6568
+ }
6569
+
6570
+ .energy-range-tooltip__range-value {
6571
+ font-size: 11px;
6572
+ font-weight: 600;
6573
+ color: #334155;
6574
+ }
6575
+
6576
+ /* Status info */
6577
+ .energy-range-tooltip__status-info {
6578
+ display: flex;
6579
+ align-items: center;
6580
+ justify-content: center;
6581
+ gap: 6px;
6582
+ margin-top: 12px;
6583
+ padding: 8px 12px;
6584
+ border-radius: 6px;
6585
+ font-size: 11px;
6586
+ font-weight: 600;
6587
+ }
6588
+
6589
+ .energy-range-tooltip__status-info.standby {
6590
+ background: #dbeafe;
6591
+ color: #1d4ed8;
6592
+ border: 1px solid #93c5fd;
6593
+ }
6594
+
6595
+ .energy-range-tooltip__status-info.normal {
6596
+ background: #dcfce7;
6597
+ color: #15803d;
6598
+ border: 1px solid #86efac;
6599
+ }
6600
+
6601
+ .energy-range-tooltip__status-info.alert {
6602
+ background: #fef3c7;
6603
+ color: #b45309;
6604
+ border: 1px solid #fcd34d;
6605
+ }
6606
+
6607
+ .energy-range-tooltip__status-info.failure {
6608
+ background: #fee2e2;
6609
+ color: #b91c1c;
6610
+ border: 1px solid #fca5a5;
6611
+ }
6612
+
6613
+ .energy-range-tooltip__status-info.offline {
6614
+ background: #f3f4f6;
6615
+ color: #6b7280;
6616
+ border: 1px solid #d1d5db;
6617
+ }
6618
+ `;
6619
+ var cssInjected = false;
6620
+ function injectCSS() {
6621
+ if (cssInjected) return;
6622
+ if (typeof document === "undefined") return;
6623
+ const styleId = "myio-energy-range-tooltip-styles";
6624
+ if (document.getElementById(styleId)) {
6625
+ cssInjected = true;
6626
+ return;
6264
6627
  }
6628
+ const style = document.createElement("style");
6629
+ style.id = styleId;
6630
+ style.textContent = ENERGY_RANGE_TOOLTIP_CSS;
6631
+ document.head.appendChild(style);
6632
+ cssInjected = true;
6633
+ }
6634
+ var STATUS_LABELS = {
6635
+ standby: "Standby",
6636
+ normal: "Operacao Normal",
6637
+ alert: "Alerta",
6638
+ failure: "Falha",
6639
+ offline: "Fora da faixa"
6640
+ };
6641
+ var STATUS_INFO_LABELS = {
6642
+ standby: "\u{1F535} Standby",
6643
+ normal: "\u2705 Operacao Normal",
6644
+ alert: "\u26A0\uFE0F Alerta",
6645
+ failure: "\u{1F534} Falha",
6646
+ offline: "\u26AB Offline / Sem dados"
6265
6647
  };
6266
6648
  var EnergyRangeTooltip = {
6267
6649
  containerId: "myio-energy-range-tooltip",
@@ -6269,6 +6651,7 @@
6269
6651
  * Create or get the tooltip container
6270
6652
  */
6271
6653
  getContainer() {
6654
+ injectCSS();
6272
6655
  let container = document.getElementById(this.containerId);
6273
6656
  if (!container) {
6274
6657
  container = document.createElement("div");
@@ -6288,18 +6671,18 @@
6288
6671
  const power = Number(powerValue) || 0;
6289
6672
  const { standbyRange, normalRange, alertRange, failureRange } = ranges;
6290
6673
  if (standbyRange && power >= standbyRange.down && power <= standbyRange.up) {
6291
- return { status: "standby", label: "Standby" };
6674
+ return { status: "standby", label: STATUS_LABELS.standby };
6292
6675
  }
6293
6676
  if (normalRange && power >= normalRange.down && power <= normalRange.up) {
6294
- return { status: "normal", label: "Normal" };
6677
+ return { status: "normal", label: STATUS_LABELS.normal };
6295
6678
  }
6296
6679
  if (alertRange && power >= alertRange.down && power <= alertRange.up) {
6297
- return { status: "alert", label: "Alerta" };
6680
+ return { status: "alert", label: STATUS_LABELS.alert };
6298
6681
  }
6299
6682
  if (failureRange && power >= failureRange.down && power <= failureRange.up) {
6300
- return { status: "failure", label: "Falha" };
6683
+ return { status: "failure", label: STATUS_LABELS.failure };
6301
6684
  }
6302
- return { status: "offline", label: "Fora da faixa" };
6685
+ return { status: "offline", label: STATUS_LABELS.offline };
6303
6686
  },
6304
6687
  /**
6305
6688
  * Calculate marker position on ruler (0-100%)
@@ -6330,7 +6713,7 @@
6330
6713
  * Format power value for display
6331
6714
  */
6332
6715
  formatPower(value) {
6333
- if (value == null || isNaN(value)) return "-";
6716
+ if (value == null || isNaN(Number(value))) return "-";
6334
6717
  const num = Number(value);
6335
6718
  if (num >= 1e3) {
6336
6719
  return `${(num / 1e3).toFixed(2)} kW`;
@@ -6348,13 +6731,40 @@
6348
6731
  const { status, label } = this.calculateStatus(powerValue, ranges);
6349
6732
  const markerPos = this.calculateMarkerPosition(powerValue, ranges);
6350
6733
  const segmentWidths = this.calculateSegmentWidths(ranges);
6351
- const statusLabels = {
6352
- standby: "\u{1F535} Standby",
6353
- normal: "\u2705 Opera\xE7\xE3o Normal",
6354
- alert: "\u26A0\uFE0F Alerta",
6355
- failure: "\u{1F534} Falha",
6356
- offline: "\u26AB Offline / Sem dados"
6357
- };
6734
+ const rangesHtml = hasRanges && ranges ? `
6735
+ <div class="energy-range-tooltip__ruler">
6736
+ <div class="energy-range-tooltip__ruler-track">
6737
+ <div class="energy-range-tooltip__ruler-segment standby" style="width: ${segmentWidths.standby}%"></div>
6738
+ <div class="energy-range-tooltip__ruler-segment normal" style="width: ${segmentWidths.normal}%"></div>
6739
+ <div class="energy-range-tooltip__ruler-segment alert" style="width: ${segmentWidths.alert}%"></div>
6740
+ <div class="energy-range-tooltip__ruler-segment failure" style="width: ${segmentWidths.failure}%"></div>
6741
+ </div>
6742
+ <div class="energy-range-tooltip__ruler-marker" style="left: ${markerPos}%;"></div>
6743
+ </div>
6744
+
6745
+ <div class="energy-range-tooltip__ranges">
6746
+ <div class="energy-range-tooltip__range-item standby">
6747
+ <div class="energy-range-tooltip__range-label">Standby</div>
6748
+ <div class="energy-range-tooltip__range-value">${ranges.standbyRange?.down || 0}-${ranges.standbyRange?.up || 0}W</div>
6749
+ </div>
6750
+ <div class="energy-range-tooltip__range-item normal">
6751
+ <div class="energy-range-tooltip__range-label">Normal</div>
6752
+ <div class="energy-range-tooltip__range-value">${ranges.normalRange?.down || 0}-${ranges.normalRange?.up || 0}W</div>
6753
+ </div>
6754
+ <div class="energy-range-tooltip__range-item alert">
6755
+ <div class="energy-range-tooltip__range-label">Alerta</div>
6756
+ <div class="energy-range-tooltip__range-value">${ranges.alertRange?.down || 0}-${ranges.alertRange?.up || 0}W</div>
6757
+ </div>
6758
+ <div class="energy-range-tooltip__range-item failure">
6759
+ <div class="energy-range-tooltip__range-label">Falha</div>
6760
+ <div class="energy-range-tooltip__range-value">&gt;${ranges.failureRange?.down || 0}W</div>
6761
+ </div>
6762
+ </div>
6763
+ ` : `
6764
+ <div style="text-align: center; padding: 16px; color: #64748b; font-size: 12px;">
6765
+ Ranges de potencia nao configurados
6766
+ </div>
6767
+ `;
6358
6768
  container.innerHTML = `
6359
6769
  <div class="energy-range-tooltip__content">
6360
6770
  <div class="energy-range-tooltip__header">
@@ -6371,51 +6781,18 @@
6371
6781
  </div>
6372
6782
  </div>
6373
6783
 
6374
- ${hasRanges ? `
6375
- <div class="energy-range-tooltip__ruler">
6376
- <div class="energy-range-tooltip__ruler-track">
6377
- <div class="energy-range-tooltip__ruler-segment standby" style="width: ${segmentWidths.standby}%"></div>
6378
- <div class="energy-range-tooltip__ruler-segment normal" style="width: ${segmentWidths.normal}%"></div>
6379
- <div class="energy-range-tooltip__ruler-segment alert" style="width: ${segmentWidths.alert}%"></div>
6380
- <div class="energy-range-tooltip__ruler-segment failure" style="width: ${segmentWidths.failure}%"></div>
6381
- </div>
6382
- <div class="energy-range-tooltip__ruler-marker" style="left: ${markerPos}%;"></div>
6383
- </div>
6384
-
6385
- <div class="energy-range-tooltip__ranges">
6386
- <div class="energy-range-tooltip__range-item standby">
6387
- <div class="energy-range-tooltip__range-label">Standby</div>
6388
- <div class="energy-range-tooltip__range-value">${ranges.standbyRange?.down || 0}-${ranges.standbyRange?.up || 0}W</div>
6389
- </div>
6390
- <div class="energy-range-tooltip__range-item normal">
6391
- <div class="energy-range-tooltip__range-label">Normal</div>
6392
- <div class="energy-range-tooltip__range-value">${ranges.normalRange?.down || 0}-${ranges.normalRange?.up || 0}W</div>
6393
- </div>
6394
- <div class="energy-range-tooltip__range-item alert">
6395
- <div class="energy-range-tooltip__range-label">Alerta</div>
6396
- <div class="energy-range-tooltip__range-value">${ranges.alertRange?.down || 0}-${ranges.alertRange?.up || 0}W</div>
6397
- </div>
6398
- <div class="energy-range-tooltip__range-item failure">
6399
- <div class="energy-range-tooltip__range-label">Falha</div>
6400
- <div class="energy-range-tooltip__range-value">&gt;${ranges.failureRange?.down || 0}W</div>
6401
- </div>
6402
- </div>
6403
- ` : `
6404
- <div style="text-align: center; padding: 16px; color: #64748b; font-size: 12px;">
6405
- Ranges de pot\xEAncia n\xE3o configurados
6406
- </div>
6407
- `}
6784
+ ${rangesHtml}
6408
6785
 
6409
6786
  <div class="energy-range-tooltip__status-info ${status}">
6410
- ${statusLabels[status] || statusLabels.offline}
6787
+ ${STATUS_INFO_LABELS[status] || STATUS_INFO_LABELS.offline}
6411
6788
  </div>
6412
6789
  </div>
6413
6790
  </div>
6414
6791
  `;
6415
6792
  let left, top;
6416
6793
  if (event && event.clientX && event.clientY) {
6417
- left = event.clientX + 15;
6418
- top = event.clientY + 15;
6794
+ left = event.clientX + 8;
6795
+ top = event.clientY + 8;
6419
6796
  } else {
6420
6797
  const rect = triggerElement.getBoundingClientRect();
6421
6798
  left = rect.left + rect.width / 2 - 150;
@@ -6424,11 +6801,11 @@
6424
6801
  const tooltipWidth = 320;
6425
6802
  const tooltipHeight = 380;
6426
6803
  if (left + tooltipWidth > window.innerWidth - 10) {
6427
- left = (event?.clientX || left) - tooltipWidth - 15;
6804
+ left = (event?.clientX || left) - tooltipWidth - 8;
6428
6805
  }
6429
6806
  if (left < 10) left = 10;
6430
6807
  if (top + tooltipHeight > window.innerHeight - 10) {
6431
- top = (event?.clientY || top) - tooltipHeight - 15;
6808
+ top = (event?.clientY || top) - tooltipHeight - 8;
6432
6809
  }
6433
6810
  if (top < 10) top = 10;
6434
6811
  container.style.left = left + "px";
@@ -6443,8 +6820,229 @@
6443
6820
  if (container) {
6444
6821
  container.classList.remove("visible");
6445
6822
  }
6823
+ },
6824
+ /**
6825
+ * Attach tooltip to an element with automatic show/hide on hover
6826
+ * Returns cleanup function to remove event listeners
6827
+ */
6828
+ attach(element, entityData) {
6829
+ const handleMouseEnter = (e) => {
6830
+ this.show(element, entityData, e);
6831
+ };
6832
+ const handleMouseLeave = () => {
6833
+ this.hide();
6834
+ };
6835
+ const handleMouseMove = (e) => {
6836
+ const container = document.getElementById(this.containerId);
6837
+ if (container && container.classList.contains("visible")) {
6838
+ let left = e.clientX + 8;
6839
+ let top = e.clientY + 8;
6840
+ const tooltipWidth = 320;
6841
+ const tooltipHeight = 380;
6842
+ if (left + tooltipWidth > window.innerWidth - 10) {
6843
+ left = e.clientX - tooltipWidth - 8;
6844
+ }
6845
+ if (left < 10) left = 10;
6846
+ if (top + tooltipHeight > window.innerHeight - 10) {
6847
+ top = e.clientY - tooltipHeight - 8;
6848
+ }
6849
+ if (top < 10) top = 10;
6850
+ container.style.left = left + "px";
6851
+ container.style.top = top + "px";
6852
+ }
6853
+ };
6854
+ element.addEventListener("mouseenter", handleMouseEnter);
6855
+ element.addEventListener("mouseleave", handleMouseLeave);
6856
+ element.addEventListener("mousemove", handleMouseMove);
6857
+ return () => {
6858
+ element.removeEventListener("mouseenter", handleMouseEnter);
6859
+ element.removeEventListener("mouseleave", handleMouseLeave);
6860
+ element.removeEventListener("mousemove", handleMouseMove);
6861
+ this.hide();
6862
+ };
6446
6863
  }
6447
6864
  };
6865
+
6866
+ // src/thingsboard/main-dashboard-shopping/v-4.0.0/card/head-office/card-head-office.js
6867
+ var LABEL_CHAR_LIMIT = 18;
6868
+ var DEFAUL_DELAY_TIME_CONNECTION_IN_MINS = 1440;
6869
+ var CSS_TAG = "head-office-card-v1";
6870
+ function ensureCss() {
6871
+ if (!document.querySelector(`style[data-myio-css="${CSS_TAG}"]`)) {
6872
+ const style = document.createElement("style");
6873
+ style.setAttribute("data-myio-css", CSS_TAG);
6874
+ style.textContent = CSS_STRING;
6875
+ document.head.appendChild(style);
6876
+ }
6877
+ }
6878
+ function normalizeParams(params) {
6879
+ if (!params || !params.entityObject) {
6880
+ throw new Error("renderCardCompenteHeadOffice: entityObject is required");
6881
+ }
6882
+ const LogHelper2 = createLogHelper(params.debugActive ?? false);
6883
+ const entityObject = params.entityObject;
6884
+ if (!entityObject.entityId) {
6885
+ LogHelper2.warn("[CardHeadOffice] entityId is missing, generating temporary ID");
6886
+ entityObject.entityId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
6887
+ }
6888
+ if (!params.delayTimeConnectionInMins) {
6889
+ LogHelper2.warn(
6890
+ `[CardHeadOffice] delayTimeConnectionInMins is missing, defaulting to ${DEFAUL_DELAY_TIME_CONNECTION_IN_MINS} mins`
6891
+ );
6892
+ }
6893
+ return {
6894
+ entityObject,
6895
+ i18n: { ...DEFAULT_I18N, ...params.i18n || {} },
6896
+ enableSelection: Boolean(params.enableSelection),
6897
+ enableDragDrop: Boolean(params.enableDragDrop),
6898
+ useNewComponents: Boolean(params.useNewComponents),
6899
+ // RFC-0091: Configurable delay time for connection status check (default 15 minutes)
6900
+ delayTimeConnectionInMins: params.delayTimeConnectionInMins ?? DEFAUL_DELAY_TIME_CONNECTION_IN_MINS,
6901
+ // Debug options
6902
+ debugActive: params.debugActive ?? false,
6903
+ activeTooltipDebug: params.activeTooltipDebug ?? false,
6904
+ // LogHelper instance for this card
6905
+ LogHelper: LogHelper2,
6906
+ callbacks: {
6907
+ handleActionDashboard: params.handleActionDashboard,
6908
+ handleActionReport: params.handleActionReport,
6909
+ handleActionSettings: params.handleActionSettings,
6910
+ handleSelect: params.handleSelect,
6911
+ handInfo: params.handInfo,
6912
+ handleClickCard: params.handleClickCard
6913
+ }
6914
+ };
6915
+ }
6916
+ function getIconSvg(deviceType, domain) {
6917
+ if (domain === "water") {
6918
+ return Icons.waterDrop;
6919
+ }
6920
+ if (domain === "temperature") {
6921
+ return Icons.thermometer;
6922
+ }
6923
+ return ICON_MAP[deviceType] || ICON_MAP.DEFAULT;
6924
+ }
6925
+ function formatPower(valueInWatts) {
6926
+ if (valueInWatts === null || valueInWatts === void 0 || isNaN(valueInWatts)) {
6927
+ return { num: "-", unit: "" };
6928
+ }
6929
+ const val = Number(valueInWatts);
6930
+ if (val >= 1e3) {
6931
+ const kw = Math.ceil(val / 1e3 * 100) / 100;
6932
+ return { num: kw.toFixed(2), unit: "kW" };
6933
+ } else {
6934
+ const w = Math.ceil(val);
6935
+ return { num: w.toString(), unit: "W" };
6936
+ }
6937
+ }
6938
+ function formatValueByDomain(value, domain) {
6939
+ if (domain === "water") {
6940
+ return formatWaterVolumeM3(value);
6941
+ }
6942
+ if (domain === "temperature") {
6943
+ return formatTemperature(value, 0);
6944
+ }
6945
+ return formatEnergy(value);
6946
+ }
6947
+ function formatRelativeTime2(timestamp) {
6948
+ if (!timestamp || isNaN(timestamp)) return "\u2014";
6949
+ const date = new Date(timestamp);
6950
+ const hours = String(date.getHours()).padStart(2, "0");
6951
+ const minutes = String(date.getMinutes()).padStart(2, "0");
6952
+ const day = String(date.getDate()).padStart(2, "0");
6953
+ const month = String(date.getMonth() + 1).padStart(2, "0");
6954
+ const year = date.getFullYear();
6955
+ return `${hours}:${minutes} ${day}/${month}/${year}`;
6956
+ }
6957
+ function calculateConsumptionPercentage(target, consumption) {
6958
+ const numericTarget = Number(target);
6959
+ const numericConsumption = Number(consumption);
6960
+ if (isNaN(numericTarget) || isNaN(numericConsumption) || numericTarget <= 0) {
6961
+ return 0;
6962
+ }
6963
+ const percentage = numericConsumption / numericTarget * 100;
6964
+ return percentage;
6965
+ }
6966
+ function getStatusInfo(deviceStatus, i18n, domain) {
6967
+ switch (deviceStatus) {
6968
+ // --- Novos Status de Temperatura ---
6969
+ case "normal":
6970
+ return { chipClass: "chip--ok", label: "Normal" };
6971
+ case "cold":
6972
+ return { chipClass: "chip--standby", label: "Frio" };
6973
+ case "hot":
6974
+ return { chipClass: "chip--alert", label: "Quente" };
6975
+ // --- Status Existentes (aligned with getCardStateClass) ---
6976
+ case DeviceStatusType.POWER_ON:
6977
+ if (domain === "water") {
6978
+ return { chipClass: "chip--power-on", label: i18n.in_operation_water };
6979
+ }
6980
+ return { chipClass: "chip--power-on", label: i18n.in_operation };
6981
+ case DeviceStatusType.STANDBY:
6982
+ return { chipClass: "chip--standby", label: i18n.standby };
6983
+ case DeviceStatusType.WARNING:
6984
+ return { chipClass: "chip--warning", label: i18n.alert };
6985
+ case DeviceStatusType.MAINTENANCE:
6986
+ return { chipClass: "chip--maintenance", label: i18n.maintenance };
6987
+ case DeviceStatusType.FAILURE:
6988
+ return { chipClass: "chip--failure", label: i18n.failure };
6989
+ case DeviceStatusType.POWER_OFF:
6990
+ return { chipClass: "chip--power-off", label: i18n.power_off || i18n.failure };
6991
+ case DeviceStatusType.OFFLINE:
6992
+ return { chipClass: "chip--offline", label: i18n.offline };
6993
+ case DeviceStatusType.NO_INFO:
6994
+ return { chipClass: "chip--no-info", label: i18n.no_info || i18n.offline };
6995
+ case DeviceStatusType.NOT_INSTALLED:
6996
+ return { chipClass: "chip--not-installed", label: i18n.not_installed };
6997
+ default:
6998
+ return { chipClass: "chip--offline", label: i18n.offline };
6999
+ }
7000
+ }
7001
+ function getCardStateClass(deviceStatus) {
7002
+ switch (deviceStatus) {
7003
+ case DeviceStatusType.POWER_ON:
7004
+ return "is-power-on";
7005
+ // Blue border
7006
+ case DeviceStatusType.STANDBY:
7007
+ return "is-standby";
7008
+ // Green border
7009
+ case DeviceStatusType.WARNING:
7010
+ return "is-warning";
7011
+ // Yellow border
7012
+ case DeviceStatusType.MAINTENANCE:
7013
+ return "is-maintenance";
7014
+ // Yellow border
7015
+ case DeviceStatusType.FAILURE:
7016
+ return "is-failure";
7017
+ // Dark Red border
7018
+ case DeviceStatusType.POWER_OFF:
7019
+ return "is-power-off";
7020
+ // Light Red border
7021
+ case DeviceStatusType.OFFLINE:
7022
+ return "is-offline";
7023
+ // Dark Gray border
7024
+ case DeviceStatusType.NO_INFO:
7025
+ return "is-no-info";
7026
+ // Dark Orange border
7027
+ case DeviceStatusType.NOT_INSTALLED:
7028
+ return "is-not-installed";
7029
+ // Purple border
7030
+ default:
7031
+ return "";
7032
+ }
7033
+ }
7034
+ function getTempRangeClass(entityObject) {
7035
+ if (entityObject.domain !== "temperature") return "";
7036
+ const currentTemp = Number(entityObject.val ?? entityObject.currentTemperature ?? entityObject.temperature) || 0;
7037
+ const tempMin = entityObject.temperatureMin ?? entityObject.minTemperature;
7038
+ const tempMax = entityObject.temperatureMax ?? entityObject.maxTemperature;
7039
+ if (tempMin === void 0 || tempMax === void 0 || tempMin === null || tempMax === null) {
7040
+ return "";
7041
+ }
7042
+ if (currentTemp > tempMax) return "is-temp-hot";
7043
+ if (currentTemp < tempMin) return "is-temp-cold";
7044
+ return "is-temp-ok";
7045
+ }
6448
7046
  function getStatusDotClass(deviceStatus) {
6449
7047
  switch (deviceStatus) {
6450
7048
  // --- Novos Status de Temperatura ---
@@ -7565,6 +8163,7 @@
7565
8163
  };
7566
8164
  const isTankDevice = deviceType === "TANK" || deviceType === "CAIXA_DAGUA";
7567
8165
  const isTermostatoDevice = deviceType?.toUpperCase() === "TERMOSTATO";
8166
+ const isEnergyDeviceFlag = isEnergyDevice(deviceType);
7568
8167
  const percentageForDisplay = isTankDevice ? (waterPercentage || 0) * 100 : perc;
7569
8168
  const calculateTempStatus = () => {
7570
8169
  if (temperatureStatus) return temperatureStatus;
@@ -7581,33 +8180,6 @@
7581
8180
  tempStatus,
7582
8181
  isOffline
7583
8182
  });
7584
- const getTemperatureTooltip = () => {
7585
- if (!isTermostatoDevice) return "";
7586
- const currentTemp = Number(val) || 0;
7587
- const hasRange = temperatureMin !== void 0 && temperatureMax !== void 0 && temperatureMin !== null && temperatureMax !== null;
7588
- if (isOffline) {
7589
- return "Dispositivo offline";
7590
- }
7591
- if (!hasRange) {
7592
- return `Temperatura atual: ${currentTemp.toFixed(1)}\xB0C
7593
- Faixa n\xE3o configurada`;
7594
- }
7595
- const rangeText = `Faixa ideal: ${temperatureMin}\xB0C a ${temperatureMax}\xB0C`;
7596
- if (tempStatus === "above") {
7597
- return `ACIMA da faixa ideal
7598
- Temperatura atual: ${currentTemp.toFixed(1)}\xB0C
7599
- ${rangeText}`;
7600
- } else if (tempStatus === "below") {
7601
- return `ABAIXO da faixa ideal
7602
- Temperatura atual: ${currentTemp.toFixed(1)}\xB0C
7603
- ${rangeText}`;
7604
- } else {
7605
- return `DENTRO da faixa ideal
7606
- Temperatura atual: ${currentTemp.toFixed(1)}\xB0C
7607
- ${rangeText}`;
7608
- }
7609
- };
7610
- const temperatureTooltip = getTemperatureTooltip();
7611
8183
  const cardHTML = `
7612
8184
  <div class="device-card-centered clickable ${cardEntity.status === "offline" ? "offline" : ""}"
7613
8185
  data-entity-id="${entityId}"
@@ -7633,7 +8205,7 @@ ${rangeText}`;
7633
8205
  ` : ""}
7634
8206
  </div>
7635
8207
 
7636
- <img class="device-image" src="${deviceImageUrl}" alt="${deviceType}" ${isTermostatoDevice && temperatureTooltip ? `title="${temperatureTooltip}"` : ""} style="${isTermostatoDevice ? "cursor: help;" : ""}" />
8208
+ <img class="device-image ${isTermostatoDevice ? "temp-tooltip-trigger" : ""}${isEnergyDeviceFlag ? " energy-tooltip-trigger" : ""}" src="${deviceImageUrl}" alt="${deviceType}" style="${isTermostatoDevice || isEnergyDeviceFlag ? "cursor: help;" : ""}" />
7637
8209
 
7638
8210
 
7639
8211
  ${customerName && String(customerName).trim() !== "" ? `
@@ -8197,6 +8769,33 @@ ${rangeText}`;
8197
8769
  }
8198
8770
  });
8199
8771
  }
8772
+ const tempTooltipTrigger = enhancedCardElement.querySelector(".temp-tooltip-trigger");
8773
+ let tempTooltipCleanup = null;
8774
+ if (tempTooltipTrigger && isTermostatoDevice) {
8775
+ const tooltipEntityData = {
8776
+ val,
8777
+ temperatureMin,
8778
+ temperatureMax,
8779
+ labelOrName: cardEntity.name,
8780
+ name: cardEntity.name
8781
+ };
8782
+ tempTooltipCleanup = TempRangeTooltip.attach(tempTooltipTrigger, tooltipEntityData);
8783
+ }
8784
+ const energyTooltipTrigger = enhancedCardElement.querySelector(".energy-tooltip-trigger");
8785
+ let energyTooltipCleanup = null;
8786
+ if (energyTooltipTrigger && isEnergyDeviceFlag) {
8787
+ const energyTooltipData = {
8788
+ labelOrName: cardEntity.name,
8789
+ name: cardEntity.name,
8790
+ instantaneousPower: entityObject.instantaneousPower ?? entityObject.consumption_power ?? val,
8791
+ powerRanges: entityObject.powerRanges || entityObject.ranges
8792
+ };
8793
+ energyTooltipCleanup = EnergyRangeTooltip.attach(energyTooltipTrigger, energyTooltipData);
8794
+ }
8795
+ container._cleanup = () => {
8796
+ if (tempTooltipCleanup) tempTooltipCleanup();
8797
+ if (energyTooltipCleanup) energyTooltipCleanup();
8798
+ };
8200
8799
  const jQueryLikeObject = {
8201
8800
  get: (index) => index === 0 ? container : void 0,
8202
8801
  0: container,
@@ -11178,7 +11777,7 @@ ${rangeText}`;
11178
11777
  var zoomPluginLoaded = false;
11179
11778
  var jsPdfLoaded = false;
11180
11779
  var _jspdfPromise = null;
11181
- var cssInjected = false;
11780
+ var cssInjected2 = false;
11182
11781
  var STRINGS2 = {
11183
11782
  "pt-BR": {
11184
11783
  title: "Demanda",
@@ -11354,8 +11953,8 @@ ${rangeText}`;
11354
11953
  }
11355
11954
  return nextY;
11356
11955
  }
11357
- function injectCSS(styles) {
11358
- if (cssInjected) return;
11956
+ function injectCSS2(styles) {
11957
+ if (cssInjected2) return;
11359
11958
  const css = `
11360
11959
  .myio-demand-modal-overlay {
11361
11960
  position: fixed;
@@ -11750,7 +12349,7 @@ ${rangeText}`;
11750
12349
  const style = document.createElement("style");
11751
12350
  style.textContent = css;
11752
12351
  document.head.appendChild(style);
11753
- cssInjected = true;
12352
+ cssInjected2 = true;
11754
12353
  }
11755
12354
  function formatDate2(date, locale) {
11756
12355
  return date.toLocaleDateString(locale, {
@@ -11932,7 +12531,7 @@ ${rangeText}`;
11932
12531
  const locale = params.locale || "pt-BR";
11933
12532
  const strings = STRINGS2[locale] || STRINGS2["pt-BR"];
11934
12533
  await loadExternalLibraries();
11935
- injectCSS(styles);
12534
+ injectCSS2(styles);
11936
12535
  const container = typeof params.container === "string" ? document.querySelector(params.container) : params.container || document.body;
11937
12536
  if (!container) {
11938
12537
  throw new Error("Container element not found");
@@ -21410,7 +22009,7 @@ ${rangeText}`;
21410
22009
  placeholder = "Clique para selecionar per\xEDodo",
21411
22010
  pickerOptions = {},
21412
22011
  classNames = {},
21413
- injectStyles = true,
22012
+ injectStyles: injectStyles2 = true,
21414
22013
  showHelper = true
21415
22014
  } = params;
21416
22015
  validateId(containerId, "containerId");
@@ -21419,7 +22018,7 @@ ${rangeText}`;
21419
22018
  if (!container) {
21420
22019
  throw new Error(`[createInputDateRangePickerInsideDIV] Container '#${containerId}' not found`);
21421
22020
  }
21422
- if (injectStyles) {
22021
+ if (injectStyles2) {
21423
22022
  injectPremiumStyles();
21424
22023
  }
21425
22024
  let inputEl = document.getElementById(inputId);
@@ -21614,7 +22213,7 @@ ${rangeText}`;
21614
22213
  modal.setAttribute("aria-labelledby", "goals-modal-title");
21615
22214
  modal.innerHTML = generateModalHTML();
21616
22215
  document.body.appendChild(modal);
21617
- injectStyles();
22216
+ injectStyles2();
21618
22217
  attachEventListeners();
21619
22218
  trapFocus(modal);
21620
22219
  renderTabContent();
@@ -22367,7 +22966,7 @@ ${rangeText}`;
22367
22966
  maximumFractionDigits: 2
22368
22967
  }).format(value);
22369
22968
  }
22370
- function injectStyles() {
22969
+ function injectStyles2() {
22371
22970
  const styleId = "myio-goals-panel-styles";
22372
22971
  if (document.getElementById(styleId)) return;
22373
22972
  const style = document.createElement("style");
@@ -24624,7 +25223,7 @@ ${rangeText}`;
24624
25223
  function getColors(theme) {
24625
25224
  return theme === "dark" ? DARK_THEME2 : LIGHT_THEME2;
24626
25225
  }
24627
- async function fetchCustomerAttributes(customerId, token) {
25226
+ async function fetchCustomerAttributes(customerId, token, onError) {
24628
25227
  const url = `/api/plugins/telemetry/CUSTOMER/${customerId}/values/attributes/SERVER_SCOPE`;
24629
25228
  const response = await fetch(url, {
24630
25229
  method: "GET",
@@ -24637,6 +25236,10 @@ ${rangeText}`;
24637
25236
  if (response.status === 404 || response.status === 400) {
24638
25237
  return { minTemperature: null, maxTemperature: null };
24639
25238
  }
25239
+ if (onError) {
25240
+ onError({ status: response.status, message: `Failed to fetch attributes: ${response.status}` });
25241
+ return { minTemperature: null, maxTemperature: null };
25242
+ }
24640
25243
  throw new Error(`Failed to fetch attributes: ${response.status}`);
24641
25244
  }
24642
25245
  const attributes = await response.json();
@@ -24653,7 +25256,7 @@ ${rangeText}`;
24653
25256
  }
24654
25257
  return { minTemperature, maxTemperature };
24655
25258
  }
24656
- async function saveCustomerAttributes(customerId, token, minTemperature, maxTemperature) {
25259
+ async function saveCustomerAttributes(customerId, token, minTemperature, maxTemperature, onError) {
24657
25260
  const url = `/api/plugins/telemetry/CUSTOMER/${customerId}/SERVER_SCOPE`;
24658
25261
  const attributes = {
24659
25262
  minTemperature,
@@ -24668,6 +25271,10 @@ ${rangeText}`;
24668
25271
  body: JSON.stringify(attributes)
24669
25272
  });
24670
25273
  if (!response.ok) {
25274
+ if (onError) {
25275
+ onError({ status: response.status, message: `Failed to save attributes: ${response.status}` });
25276
+ return;
25277
+ }
24671
25278
  throw new Error(`Failed to save attributes: ${response.status}`);
24672
25279
  }
24673
25280
  }
@@ -25084,7 +25691,7 @@ ${rangeText}`;
25084
25691
  state.successMessage = null;
25085
25692
  renderModal3(container, state, modalId, destroy, handleSave);
25086
25693
  try {
25087
- await saveCustomerAttributes(state.customerId, state.token, min, max);
25694
+ await saveCustomerAttributes(state.customerId, state.token, min, max, params.onError);
25088
25695
  state.minTemperature = min;
25089
25696
  state.maxTemperature = max;
25090
25697
  state.isSaving = false;
@@ -25101,7 +25708,7 @@ ${rangeText}`;
25101
25708
  }
25102
25709
  };
25103
25710
  renderModal3(container, state, modalId, destroy, handleSave);
25104
- fetchCustomerAttributes(state.customerId, state.token).then(({ minTemperature, maxTemperature }) => {
25711
+ fetchCustomerAttributes(state.customerId, state.token, params.onError).then(({ minTemperature, maxTemperature }) => {
25105
25712
  state.minTemperature = minTemperature;
25106
25713
  state.maxTemperature = maxTemperature;
25107
25714
  state.isLoading = false;
@@ -27260,7 +27867,7 @@ ${rangeText}`;
27260
27867
  </div>
27261
27868
  `;
27262
27869
  }
27263
- function injectStyles() {
27870
+ function injectStyles2() {
27264
27871
  if (styleElement) return;
27265
27872
  styleElement = document.createElement("style");
27266
27873
  styleElement.id = `${widgetId}-styles`;
@@ -27741,7 +28348,7 @@ ${rangeText}`;
27741
28348
  console.error(`[ConsumptionWidget] Container #${config.containerId} not found`);
27742
28349
  return;
27743
28350
  }
27744
- injectStyles();
28351
+ injectStyles2();
27745
28352
  containerElement.innerHTML = renderHTML();
27746
28353
  setupListeners();
27747
28354
  setLoading(true);
@@ -28878,6 +29485,7 @@ ${rangeText}`;
28878
29485
  exports.EXPORT_DOMAIN_ICONS = EXPORT_DOMAIN_ICONS;
28879
29486
  exports.EXPORT_DOMAIN_LABELS = EXPORT_DOMAIN_LABELS;
28880
29487
  exports.EXPORT_DOMAIN_UNITS = EXPORT_DOMAIN_UNITS;
29488
+ exports.EnergyRangeTooltip = EnergyRangeTooltip;
28881
29489
  exports.MyIOChartModal = MyIOChartModal;
28882
29490
  exports.MyIODraggableCard = MyIODraggableCard;
28883
29491
  exports.MyIOSelectionStoreClass = MyIOSelectionStoreClass;
@@ -28885,6 +29493,7 @@ ${rangeText}`;
28885
29493
  exports.POWER_LIMITS_DEVICE_TYPES = DEVICE_TYPES;
28886
29494
  exports.POWER_LIMITS_STATUS_CONFIG = STATUS_CONFIG;
28887
29495
  exports.POWER_LIMITS_TELEMETRY_TYPES = TELEMETRY_TYPES2;
29496
+ exports.TempRangeTooltip = TempRangeTooltip;
28888
29497
  exports.addDetectionContext = addDetectionContext;
28889
29498
  exports.addNamespace = addNamespace;
28890
29499
  exports.aggregateByDay = aggregateByDay;