myio-js-library 0.1.556 → 0.1.558

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
@@ -908,7 +908,7 @@ module.exports = __toCommonJS(src_exports);
908
908
  // package.json
909
909
  var package_default = {
910
910
  name: "myio-js-library",
911
- version: "0.1.556",
911
+ version: "0.1.558",
912
912
  description: "A clean, standalone JS SDK for MYIO projects",
913
913
  license: "MIT",
914
914
  repository: "github:gh-myio/myio-js-library",
@@ -12855,6 +12855,26 @@ var CSS_STRING = `
12855
12855
  margin-left: 4px;
12856
12856
  }
12857
12857
 
12858
+ /* Consumo ainda carregando (spinner no lugar do valor) */
12859
+ .myio-ho-card__value.is-loading .num {
12860
+ display: inline-flex;
12861
+ align-items: center;
12862
+ gap: 8px;
12863
+ font-size: 14px;
12864
+ font-weight: 600;
12865
+ color: var(--myio-muted, #9ca3af);
12866
+ }
12867
+ .myio-ho-card__value-spinner {
12868
+ width: 14px;
12869
+ height: 14px;
12870
+ border: 2px solid rgba(124, 58, 237, 0.2);
12871
+ border-top-color: var(--myio-brand-700, #7c3aed);
12872
+ border-radius: 50%;
12873
+ animation: myio-consumption-spin 0.8s linear infinite;
12874
+ flex-shrink: 0;
12875
+ }
12876
+ @keyframes myio-consumption-spin { to { transform: rotate(360deg); } }
12877
+
12858
12878
  /* Efficiency bar */
12859
12879
  .myio-ho-card__eff {
12860
12880
  display: flex;
@@ -16527,7 +16547,20 @@ function paint(root, state6) {
16527
16547
  const primaryValue = formatValueByDomain(entityObject.val, entityObject.domain);
16528
16548
  const numSpan = root.querySelector(".myio-ho-card__value .num");
16529
16549
  const unitSpan = root.querySelector(".myio-ho-card__value .unit");
16530
- numSpan.textContent = primaryValue;
16550
+ const valueBox = root.querySelector(".myio-ho-card__value");
16551
+ const dataLoading = !!entityObject.dataLoading && entityObject.domain !== "temperature";
16552
+ if (valueBox) {
16553
+ valueBox.classList.toggle("is-loading", dataLoading);
16554
+ if (dataLoading) valueBox.setAttribute("aria-busy", "true");
16555
+ else valueBox.removeAttribute("aria-busy");
16556
+ }
16557
+ if (dataLoading) {
16558
+ numSpan.innerHTML = '<span class="myio-ho-card__value-spinner" aria-hidden="true"></span>Carregando\u2026';
16559
+ numSpan.title = "Carregando consumo do per\xEDodo";
16560
+ } else {
16561
+ numSpan.textContent = primaryValue;
16562
+ numSpan.removeAttribute("title");
16563
+ }
16531
16564
  const barContainer = root.querySelector(".bar");
16532
16565
  const effContainer = root.querySelector(".myio-ho-card__eff");
16533
16566
  if (state6.enableSelection) {
@@ -53080,6 +53113,444 @@ function escapeHtml4(text2) {
53080
53113
  return div.innerHTML;
53081
53114
  }
53082
53115
 
53116
+ // src/services/alarm/AlarmApiClient.ts
53117
+ var AlarmApiClient = class {
53118
+ baseUrl = "";
53119
+ apiKey = "";
53120
+ configure(baseUrl, apiKey) {
53121
+ if (baseUrl) this.baseUrl = baseUrl;
53122
+ if (apiKey) this.apiKey = apiKey;
53123
+ }
53124
+ get headers() {
53125
+ return {
53126
+ "Content-Type": "application/json",
53127
+ "X-API-Key": this.apiKey
53128
+ };
53129
+ }
53130
+ async request(url, options) {
53131
+ const response = await fetch(url, {
53132
+ method: "GET",
53133
+ headers: this.headers,
53134
+ ...options
53135
+ });
53136
+ if (!response.ok) {
53137
+ throw new Error(`Alarms API error: ${response.status} ${response.statusText}`);
53138
+ }
53139
+ return response.json();
53140
+ }
53141
+ // Some endpoints return { success: true, data: T[] } — unwrap safely
53142
+ unwrapArray(raw) {
53143
+ if (Array.isArray(raw)) return raw;
53144
+ if (raw !== null && typeof raw === "object" && "data" in raw) {
53145
+ const d = raw.data;
53146
+ return Array.isArray(d) ? d : [];
53147
+ }
53148
+ return [];
53149
+ }
53150
+ // -----------------------------------------------------------------------
53151
+ // Availability (existing endpoint)
53152
+ // -----------------------------------------------------------------------
53153
+ async getAvailability(params) {
53154
+ const query = new URLSearchParams({
53155
+ customerId: params.customerId,
53156
+ startAt: params.startAt,
53157
+ endAt: params.endAt,
53158
+ includeByDevice: String(params.includeByDevice ?? true)
53159
+ });
53160
+ if (params.deviceType) query.set("deviceType", params.deviceType);
53161
+ if (params.deviceIds) query.set("deviceIds", params.deviceIds);
53162
+ const raw = await this.request(
53163
+ `${this.baseUrl}/alarms/stats/availability?${query.toString()}`
53164
+ );
53165
+ return raw?.data ?? raw;
53166
+ }
53167
+ // -----------------------------------------------------------------------
53168
+ // Alarm list (GET /alarms)
53169
+ // -----------------------------------------------------------------------
53170
+ async getAlarms(params = {}) {
53171
+ const query = new URLSearchParams();
53172
+ if (params.state?.length) query.set("state", params.state.join(","));
53173
+ if (params.severity?.length) query.set("severity", params.severity.join(","));
53174
+ if (params.alarmType) query.set("alarmType", params.alarmType);
53175
+ if (params.from) query.set("from", params.from);
53176
+ if (params.to) query.set("to", params.to);
53177
+ if (params.customerId) query.set("customerId", params.customerId);
53178
+ query.set("limit", String(params.limit ?? 100));
53179
+ if (params.page && params.page > 1) query.set("page", String(params.page));
53180
+ const qs = query.toString();
53181
+ return this.request(
53182
+ `${this.baseUrl}/alarms${qs ? `?${qs}` : ""}`
53183
+ );
53184
+ }
53185
+ // -----------------------------------------------------------------------
53186
+ // Stats (GET /api/v1/alarms/stats)
53187
+ // -----------------------------------------------------------------------
53188
+ async getAlarmStats(tenantId, period) {
53189
+ const query = new URLSearchParams({ tenantId, period });
53190
+ const raw = await this.request(
53191
+ `${this.baseUrl}/alarms/stats?${query.toString()}`
53192
+ );
53193
+ return raw?.data ?? raw;
53194
+ }
53195
+ // -----------------------------------------------------------------------
53196
+ // Trend (GET /api/v1/alarms/stats/trend)
53197
+ // -----------------------------------------------------------------------
53198
+ async getAlarmTrend(tenantId, period, groupBy) {
53199
+ const query = new URLSearchParams({ tenantId, period, groupBy });
53200
+ const raw = await this.request(
53201
+ `${this.baseUrl}/alarms/stats/trend?${query.toString()}`
53202
+ );
53203
+ return this.unwrapArray(raw);
53204
+ }
53205
+ // -----------------------------------------------------------------------
53206
+ // Top offenders (GET /api/v1/alarms/stats/top-offenders)
53207
+ // -----------------------------------------------------------------------
53208
+ async getTopOffenders(tenantId, limit = 5) {
53209
+ const query = new URLSearchParams({ tenantId, limit: String(limit) });
53210
+ const raw = await this.request(
53211
+ `${this.baseUrl}/alarms/stats/top-offenders?${query.toString()}`
53212
+ );
53213
+ return this.unwrapArray(raw);
53214
+ }
53215
+ // -----------------------------------------------------------------------
53216
+ // Per-device stats (GET /api/v1/alarms/stats/by-device)
53217
+ // -----------------------------------------------------------------------
53218
+ async getAlarmStatsByDevice(tenantId) {
53219
+ const query = new URLSearchParams({ tenantId });
53220
+ const raw = await this.request(
53221
+ `${this.baseUrl}/alarms/stats/by-device?${query.toString()}`
53222
+ );
53223
+ return this.unwrapArray(raw);
53224
+ }
53225
+ // -----------------------------------------------------------------------
53226
+ // Alarm history (GET /alarms/:id/history)
53227
+ // -----------------------------------------------------------------------
53228
+ async getAlarmHistory(id) {
53229
+ const raw = await this.request(`${this.baseUrl}/alarms/${encodeURIComponent(id)}/history`);
53230
+ return this.unwrapArray(raw);
53231
+ }
53232
+ // -----------------------------------------------------------------------
53233
+ // Alarm actions (POST /alarms/:id/...)
53234
+ // -----------------------------------------------------------------------
53235
+ async acknowledgeAlarm(id, acknowledgedBy, note) {
53236
+ await this.request(`${this.baseUrl}/alarms/${id}/ack`, {
53237
+ method: "POST",
53238
+ body: JSON.stringify({ acknowledgedBy, note })
53239
+ });
53240
+ }
53241
+ async silenceAlarm(id, silencedBy, duration, reason) {
53242
+ await this.request(`${this.baseUrl}/alarms/${id}/silence`, {
53243
+ method: "POST",
53244
+ body: JSON.stringify({ silencedBy, duration, reason })
53245
+ });
53246
+ }
53247
+ async escalateAlarm(id, escalatedBy, reason) {
53248
+ await this.request(`${this.baseUrl}/alarms/${id}/escalate`, {
53249
+ method: "POST",
53250
+ body: JSON.stringify({ escalatedBy, reason })
53251
+ });
53252
+ }
53253
+ async closeAlarm(id, closedBy, resolution) {
53254
+ await this.request(`${this.baseUrl}/alarms/${id}/close`, {
53255
+ method: "POST",
53256
+ body: JSON.stringify({ closedBy, resolution })
53257
+ });
53258
+ }
53259
+ // -----------------------------------------------------------------------
53260
+ // Batch alarm actions (POST /alarms/batch/*)
53261
+ // Up to 100 IDs per call; response contains succeeded/failed arrays.
53262
+ // -----------------------------------------------------------------------
53263
+ async batchAcknowledge(alarmIds, acknowledgedBy) {
53264
+ const res = await this.request(
53265
+ `${this.baseUrl}/alarms/batch/ack`,
53266
+ { method: "POST", body: JSON.stringify({ alarmIds, acknowledgedBy }) }
53267
+ );
53268
+ return res.data;
53269
+ }
53270
+ async batchSilence(alarmIds, silencedBy, duration) {
53271
+ const res = await this.request(
53272
+ `${this.baseUrl}/alarms/batch/silence`,
53273
+ { method: "POST", body: JSON.stringify({ alarmIds, silencedBy, duration }) }
53274
+ );
53275
+ return res.data;
53276
+ }
53277
+ async batchEscalate(alarmIds, escalatedBy) {
53278
+ const res = await this.request(
53279
+ `${this.baseUrl}/alarms/batch/escalate`,
53280
+ { method: "POST", body: JSON.stringify({ alarmIds, escalatedBy }) }
53281
+ );
53282
+ return res.data;
53283
+ }
53284
+ async batchClose(alarmIds, closedBy, resolution) {
53285
+ const res = await this.request(
53286
+ `${this.baseUrl}/alarms/batch/close`,
53287
+ { method: "POST", body: JSON.stringify({ alarmIds, closedBy, resolution }) }
53288
+ );
53289
+ return res.data;
53290
+ }
53291
+ };
53292
+
53293
+ // src/services/alarm/AlarmService.ts
53294
+ function isFresh(entry, ttlMs) {
53295
+ return !!entry && Date.now() - entry.timestamp < ttlMs;
53296
+ }
53297
+ function mapApiAlarm(api, customerMap) {
53298
+ return {
53299
+ id: api.id,
53300
+ customerId: api.customerId ?? api.tenantId,
53301
+ customerName: customerMap?.get(api.customerId ?? "") ?? "",
53302
+ source: api.deviceName || api.deviceId,
53303
+ severity: api.severity,
53304
+ state: api.state,
53305
+ title: api.title,
53306
+ description: api.description ?? "",
53307
+ tags: {
53308
+ alarmType: api.alarmType,
53309
+ deviceType: api.deviceType
53310
+ },
53311
+ firstOccurrence: api.raisedAt || api.raised_at || api.createdAt || void 0,
53312
+ lastOccurrence: api.lastUpdatedAt || api.updatedAt || api.updated_at || void 0,
53313
+ occurrenceCount: api.occurrenceCount ?? api.dispatchCount ?? 1,
53314
+ acknowledgedAt: api.acknowledgedAt || void 0,
53315
+ acknowledgedBy: api.acknowledgedBy,
53316
+ snoozedUntil: api.silencedUntil || api.snoozedUntil || void 0,
53317
+ closedAt: api.closedAt || void 0,
53318
+ closedBy: api.closedBy,
53319
+ closedReason: api.resolution,
53320
+ triggerValue: api.metadata?.value,
53321
+ centralId: api.centralId || void 0,
53322
+ deviceId: api.deviceId || void 0
53323
+ };
53324
+ }
53325
+ function mapApiStats(api) {
53326
+ return {
53327
+ total: api.total,
53328
+ bySeverity: {
53329
+ CRITICAL: api.bySeverity?.CRITICAL ?? 0,
53330
+ HIGH: api.bySeverity?.HIGH ?? 0,
53331
+ MEDIUM: api.bySeverity?.MEDIUM ?? 0,
53332
+ LOW: api.bySeverity?.LOW ?? 0,
53333
+ INFO: api.bySeverity?.INFO ?? 0
53334
+ },
53335
+ byState: {
53336
+ OPEN: api.byState?.OPEN ?? 0,
53337
+ ACK: api.byState?.ACK ?? 0,
53338
+ SNOOZED: api.byState?.SNOOZED ?? 0,
53339
+ ESCALATED: api.byState?.ESCALATED ?? 0,
53340
+ CLOSED: api.byState?.CLOSED ?? 0
53341
+ },
53342
+ openCritical: api.openCritical,
53343
+ openHigh: api.openHigh,
53344
+ last24Hours: api.last24Hours
53345
+ };
53346
+ }
53347
+ function mapApiTrend(apiTrend) {
53348
+ return (apiTrend || []).map((point) => ({
53349
+ label: point.label || point.period,
53350
+ timestamp: point.timestamp || new Date(point.period).getTime() || 0,
53351
+ total: point.total ?? point.count,
53352
+ bySeverity: point.bySeverity ? {
53353
+ CRITICAL: point.bySeverity.CRITICAL ?? 0,
53354
+ HIGH: point.bySeverity.HIGH ?? 0,
53355
+ MEDIUM: point.bySeverity.MEDIUM ?? 0,
53356
+ LOW: point.bySeverity.LOW ?? 0,
53357
+ INFO: point.bySeverity.INFO ?? 0
53358
+ } : void 0
53359
+ }));
53360
+ }
53361
+ function mapApiTopOffenders(items, deviceNameMap) {
53362
+ return (items || []).map((item) => ({
53363
+ name: deviceNameMap.get(item.deviceId) ?? item.deviceName ?? item.deviceId,
53364
+ location: item.customerName ?? "",
53365
+ downtime: item.downtimeHours ?? (item.totalDowntimeMinutes ?? 0) / 60,
53366
+ percentage: 0
53367
+ // computed by caller if needed
53368
+ }));
53369
+ }
53370
+ var AlarmServiceClass = class {
53371
+ client = new AlarmApiClient();
53372
+ cacheTtlMs = 3 * 60 * 1e3;
53373
+ // 3 min default; overridable via configure()
53374
+ availabilityCache = /* @__PURE__ */ new Map();
53375
+ alarmsCache = /* @__PURE__ */ new Map();
53376
+ statsCache = /* @__PURE__ */ new Map();
53377
+ trendCache = /* @__PURE__ */ new Map();
53378
+ // -----------------------------------------------------------------------
53379
+ // Availability (MTBF / MTTR per device)
53380
+ // -----------------------------------------------------------------------
53381
+ /**
53382
+ * Fetches MTBF, MTTR, and Availability metrics per device and fleet-wide.
53383
+ * Results are cached for 60 seconds.
53384
+ */
53385
+ async getAvailability(customerId, startAt, endAt) {
53386
+ if (!customerId) {
53387
+ throw new Error("AlarmService.getAvailability: customerId is required");
53388
+ }
53389
+ const cacheKey = `avail:${customerId}:${startAt}:${endAt}`;
53390
+ const cached = this.availabilityCache.get(cacheKey);
53391
+ if (isFresh(cached, this.cacheTtlMs)) return cached.data;
53392
+ const data = await this.client.getAvailability({
53393
+ customerId,
53394
+ startAt,
53395
+ endAt,
53396
+ includeByDevice: true
53397
+ });
53398
+ this.availabilityCache.set(cacheKey, { data, timestamp: Date.now() });
53399
+ return data;
53400
+ }
53401
+ // -----------------------------------------------------------------------
53402
+ // Alarm list
53403
+ // -----------------------------------------------------------------------
53404
+ /**
53405
+ * Fetch alarm list with optional filters, auto-paginating until hasMore is false.
53406
+ * Returns { data: Alarm[], summary: AlarmListSummary } with all pages merged.
53407
+ * The summary comes from page 1 (reflects full dataset totals).
53408
+ */
53409
+ async getAlarms(params = {}, customerMap) {
53410
+ const cacheKey = `alarms:${JSON.stringify(params)}`;
53411
+ const cached = this.alarmsCache.get(cacheKey);
53412
+ if (isFresh(cached, this.cacheTtlMs)) return cached.data;
53413
+ let allData = [];
53414
+ let summary;
53415
+ let totalPages = 1;
53416
+ let page = 1;
53417
+ while (page <= totalPages) {
53418
+ const response = await this.client.getAlarms({ ...params, page });
53419
+ const pageData = (response.data || []).map((a) => mapApiAlarm(a, customerMap));
53420
+ allData = allData.concat(pageData);
53421
+ if (page === 1) {
53422
+ summary = response.summary;
53423
+ totalPages = response.pagination?.totalPages ?? 1;
53424
+ }
53425
+ page++;
53426
+ }
53427
+ const result = { data: allData, summary };
53428
+ this.alarmsCache.set(cacheKey, { data: result, timestamp: Date.now() });
53429
+ return result;
53430
+ }
53431
+ // -----------------------------------------------------------------------
53432
+ // Alarm statistics
53433
+ // -----------------------------------------------------------------------
53434
+ /**
53435
+ * Fetch aggregated alarm statistics for a tenant and time period.
53436
+ * Returns mapped AlarmStats (from src/types/alarm.ts).
53437
+ */
53438
+ async getAlarmStats(tenantId, period) {
53439
+ const cacheKey = `stats:${tenantId}:${period}`;
53440
+ const cached = this.statsCache.get(cacheKey);
53441
+ if (isFresh(cached, this.cacheTtlMs)) return cached.data;
53442
+ const raw = await this.client.getAlarmStats(tenantId, period);
53443
+ const data = mapApiStats(raw);
53444
+ this.statsCache.set(cacheKey, { data, timestamp: Date.now() });
53445
+ return data;
53446
+ }
53447
+ // -----------------------------------------------------------------------
53448
+ // Alarm trend
53449
+ // -----------------------------------------------------------------------
53450
+ /**
53451
+ * Fetch alarm trend data points.
53452
+ * Returns mapped AlarmTrendDataPoint[] (from src/types/alarm.ts).
53453
+ */
53454
+ async getAlarmTrend(tenantId, period, groupBy) {
53455
+ const cacheKey = `trend:${tenantId}:${period}:${groupBy}`;
53456
+ const cached = this.trendCache.get(cacheKey);
53457
+ if (isFresh(cached, this.cacheTtlMs)) return cached.data;
53458
+ const raw = await this.client.getAlarmTrend(tenantId, period, groupBy);
53459
+ const data = mapApiTrend(raw);
53460
+ this.trendCache.set(cacheKey, { data, timestamp: Date.now() });
53461
+ return data;
53462
+ }
53463
+ // -----------------------------------------------------------------------
53464
+ // Top downtime / offenders
53465
+ // -----------------------------------------------------------------------
53466
+ /**
53467
+ * Fetch top devices by alarm count / downtime.
53468
+ * Returns mapped DowntimeEntry[] (from src/types/operational.ts).
53469
+ *
53470
+ * @param deviceNameMap - Optional map of deviceId → friendly name from ThingsBoard cache
53471
+ */
53472
+ async getTopDowntime(tenantId, deviceNameMap = /* @__PURE__ */ new Map(), limit = 5) {
53473
+ const items = await this.client.getTopOffenders(tenantId, limit);
53474
+ return mapApiTopOffenders(items, deviceNameMap);
53475
+ }
53476
+ // -----------------------------------------------------------------------
53477
+ // Per-device alarm counts
53478
+ // -----------------------------------------------------------------------
53479
+ /**
53480
+ * Fetch alarm counts per device.
53481
+ * Returns a Map<deviceId, alarmCount>.
53482
+ */
53483
+ async getDeviceAlarmCounts(tenantId) {
53484
+ const items = await this.client.getAlarmStatsByDevice(tenantId);
53485
+ const map = /* @__PURE__ */ new Map();
53486
+ (items || []).forEach((item) => map.set(item.deviceId, item.alarmCount));
53487
+ return map;
53488
+ }
53489
+ /**
53490
+ * Histórico de transições do alarme (abertura/reabertura/fechamento/ack…).
53491
+ * Falha de rede/API devolve [] — o detalhe do alarme degrada para só a abertura.
53492
+ */
53493
+ async getAlarmHistory(id) {
53494
+ try {
53495
+ return await this.client.getAlarmHistory(id);
53496
+ } catch {
53497
+ return [];
53498
+ }
53499
+ }
53500
+ // -----------------------------------------------------------------------
53501
+ // Alarm actions
53502
+ // -----------------------------------------------------------------------
53503
+ async acknowledgeAlarm(id, userEmail, note) {
53504
+ return this.client.acknowledgeAlarm(id, userEmail, note);
53505
+ }
53506
+ async silenceAlarm(id, userEmail, duration, reason) {
53507
+ return this.client.silenceAlarm(id, userEmail, duration, reason);
53508
+ }
53509
+ async escalateAlarm(id, userEmail, reason) {
53510
+ return this.client.escalateAlarm(id, userEmail, reason);
53511
+ }
53512
+ async closeAlarm(id, userEmail, resolution) {
53513
+ return this.client.closeAlarm(id, userEmail, resolution);
53514
+ }
53515
+ // -----------------------------------------------------------------------
53516
+ // Batch alarm actions (POST /alarms/batch/*)
53517
+ // -----------------------------------------------------------------------
53518
+ async batchAcknowledge(alarmIds, userEmail) {
53519
+ return this.client.batchAcknowledge(alarmIds, userEmail);
53520
+ }
53521
+ async batchSilence(alarmIds, userEmail, duration) {
53522
+ return this.client.batchSilence(alarmIds, userEmail, duration);
53523
+ }
53524
+ async batchEscalate(alarmIds, userEmail) {
53525
+ return this.client.batchEscalate(alarmIds, userEmail);
53526
+ }
53527
+ async batchClose(alarmIds, userEmail, resolution) {
53528
+ return this.client.batchClose(alarmIds, userEmail, resolution);
53529
+ }
53530
+ // -----------------------------------------------------------------------
53531
+ // Configuration
53532
+ // -----------------------------------------------------------------------
53533
+ /**
53534
+ * Set the Alarms API base URL (e.g. from MAIN_VIEW orchestrator settings).
53535
+ * Clears the cache so subsequent calls use the new endpoint.
53536
+ */
53537
+ configure(baseUrl, cacheTtlMs, apiKey) {
53538
+ this.client.configure(baseUrl ?? "", apiKey);
53539
+ if (cacheTtlMs && cacheTtlMs > 0) this.cacheTtlMs = cacheTtlMs;
53540
+ this.clearCache();
53541
+ }
53542
+ // -----------------------------------------------------------------------
53543
+ // Cache management
53544
+ // -----------------------------------------------------------------------
53545
+ clearCache() {
53546
+ this.availabilityCache.clear();
53547
+ this.alarmsCache.clear();
53548
+ this.statsCache.clear();
53549
+ this.trendCache.clear();
53550
+ }
53551
+ };
53552
+ var AlarmService = new AlarmServiceClass();
53553
+
53083
53554
  // src/components/AlarmsNotificationsPanel/AlarmDetailsModal.ts
53084
53555
  function fmt(isoString) {
53085
53556
  if (!isoString) return "-";
@@ -53109,112 +53580,155 @@ function escHtml2(s) {
53109
53580
  if (s == null) return "";
53110
53581
  return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53111
53582
  }
53112
- function seededRand(seed) {
53113
- let s = (seed ^ 3735928559) >>> 0;
53114
- return () => {
53115
- s = Math.imul(s ^ s >>> 16, 73244475);
53116
- s = Math.imul(s ^ s >>> 16, 73244475);
53117
- s = (s ^ s >>> 16) >>> 0;
53118
- return s / 4294967295;
53583
+ var EVENT_LABEL = {
53584
+ raised: "Abertura",
53585
+ reopened: "Reabertura",
53586
+ closed: "Fechamento",
53587
+ acknowledged: "Reconhecido",
53588
+ silenced: "Adiado",
53589
+ escalated: "Escalado",
53590
+ other: "Atualiza\xE7\xE3o"
53591
+ };
53592
+ function classifyHistoryEvent(ev) {
53593
+ const action = String(ev.action || "").toUpperCase();
53594
+ const next = String(ev.newState || "").toUpperCase();
53595
+ const prev = String(ev.previousState || "").toUpperCase();
53596
+ if (next === "OPEN") return prev && prev !== "OPEN" ? prev === "CLOSED" ? "reopened" : "other" : "raised";
53597
+ if (next === "CLOSED" || /CLOSE/.test(action)) return "closed";
53598
+ if (next === "ACK" || next === "ACKNOWLEDGED" || /ACK/.test(action)) return "acknowledged";
53599
+ if (next === "SNOOZED" || next === "SILENCED" || /SILENC|SNOOZ/.test(action)) return "silenced";
53600
+ if (next === "ESCALATED" || /ESCALAT/.test(action)) return "escalated";
53601
+ if (/CREAT|RAISE/.test(action)) return "raised";
53602
+ return "other";
53603
+ }
53604
+ async function loadAlarmEvents(alarm) {
53605
+ const ids = alarm._groupAlarmIds?.length ? alarm._groupAlarmIds : [alarm.id];
53606
+ const aso = window.AlarmServiceOrchestrator?.alarms || [];
53607
+ const byId = new Map(aso.map((a) => [a?.id, a]));
53608
+ const single = ids.length === 1;
53609
+ const events = [];
53610
+ await Promise.all(
53611
+ ids.slice(0, 50).map(async (id) => {
53612
+ const raw = byId.get(id);
53613
+ const device = raw?.deviceName || alarm.source || "";
53614
+ const title = raw?.title || alarm.title;
53615
+ const raisedMs = new Date(raw?.raisedAt || (single ? alarm.firstOccurrence : "") || "").getTime();
53616
+ const hasRaise = Number.isFinite(raisedMs) && raisedMs > 0;
53617
+ if (hasRaise) events.push({ ts: raisedMs, kind: "raised", alarmId: id, device, title, occurrence: true });
53618
+ const hist = await AlarmService.getAlarmHistory(id);
53619
+ for (const ev of hist) {
53620
+ const ts = new Date(ev.timestamp).getTime();
53621
+ if (!Number.isFinite(ts)) continue;
53622
+ const kind = classifyHistoryEvent(ev);
53623
+ if (kind === "raised" && hasRaise && Math.abs(ts - raisedMs) < 6e4) continue;
53624
+ events.push({
53625
+ ts,
53626
+ kind,
53627
+ alarmId: id,
53628
+ device,
53629
+ title,
53630
+ actor: ev.actor,
53631
+ occurrence: kind === "raised" || kind === "reopened"
53632
+ });
53633
+ }
53634
+ })
53635
+ );
53636
+ if (!events.some((e) => e.occurrence)) {
53637
+ const ms = new Date(alarm.firstOccurrence).getTime();
53638
+ if (Number.isFinite(ms) && ms > 0) {
53639
+ events.push({ ts: ms, kind: "raised", alarmId: alarm.id, device: alarm.source, title: alarm.title, occurrence: true });
53640
+ }
53641
+ }
53642
+ return events.sort((a, b) => a.ts - b.ts);
53643
+ }
53644
+ function bucketEvents(occ, devices, period, fromMs, toMs) {
53645
+ const start = new Date(fromMs);
53646
+ if (period === "hora") start.setMinutes(0, 0, 0);
53647
+ else start.setHours(0, 0, 0, 0);
53648
+ if (period === "semana") start.setDate(start.getDate() - start.getDay());
53649
+ if (period === "mes") start.setDate(1);
53650
+ const keys = [];
53651
+ const cursor = new Date(start);
53652
+ while (cursor.getTime() <= toMs && keys.length < 5e3) {
53653
+ keys.push(cursor.getTime());
53654
+ if (period === "hora") cursor.setHours(cursor.getHours() + 1);
53655
+ else if (period === "dia") cursor.setDate(cursor.getDate() + 1);
53656
+ else if (period === "semana") cursor.setDate(cursor.getDate() + 7);
53657
+ else cursor.setMonth(cursor.getMonth() + 1);
53658
+ }
53659
+ const maxBuckets = period === "hora" ? 72 : period === "dia" ? 92 : period === "semana" ? 104 : 36;
53660
+ const ks = keys.slice(-maxBuckets);
53661
+ const months = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"];
53662
+ const dm = (d) => `${String(d.getDate()).padStart(2, "0")}/${String(d.getMonth() + 1).padStart(2, "0")}`;
53663
+ const label = (ms) => {
53664
+ const d = new Date(ms);
53665
+ if (period === "hora") return `${dm(d)} ${String(d.getHours()).padStart(2, "0")}h`;
53666
+ if (period === "mes") return `${months[d.getMonth()]}/${String(d.getFullYear()).slice(2)}`;
53667
+ return dm(d);
53119
53668
  };
53669
+ const buckets = ks.map((k) => ({ label: label(k), total: 0, byDevice: {} }));
53670
+ for (const e of occ) {
53671
+ let idx = -1;
53672
+ for (let i = ks.length - 1; i >= 0; i--) {
53673
+ if (ks[i] <= e.ts) {
53674
+ idx = i;
53675
+ break;
53676
+ }
53677
+ }
53678
+ if (idx < 0) continue;
53679
+ buckets[idx].total++;
53680
+ const dev = devices.includes(e.device) ? e.device : devices[0] ?? e.device;
53681
+ buckets[idx].byDevice[dev] = (buckets[idx].byDevice[dev] || 0) + 1;
53682
+ }
53683
+ return buckets;
53120
53684
  }
53121
- function hashStr(s) {
53122
- let h3 = 0;
53123
- for (let i = 0; i < s.length; i++) h3 = Math.imul(31, h3) + s.charCodeAt(i) | 0;
53124
- return Math.abs(h3);
53685
+ function dowFromEvents(occ) {
53686
+ const v = new Array(7).fill(0);
53687
+ occ.forEach((e) => v[new Date(e.ts).getDay()]++);
53688
+ return v;
53125
53689
  }
53126
- function generateBuckets(alarm, devices, period = "mes") {
53127
- const count = alarm.occurrenceCount || 1;
53128
- let firstMs = new Date(alarm.firstOccurrence).getTime();
53129
- let lastMs = new Date(alarm.lastOccurrence).getTime();
53130
- if (isNaN(firstMs) || isNaN(lastMs) || firstMs <= 0 || lastMs <= 0) {
53131
- lastMs = Date.now();
53132
- firstMs = lastMs - 7 * 864e5;
53133
- }
53134
- const durMs = Math.max(lastMs - firstMs, 36e5);
53135
- let numBuckets;
53136
- let bucketMs;
53137
- let labelFn;
53138
- const nowMs = Date.now();
53139
- if (period === "hora") {
53140
- bucketMs = 36e5;
53141
- numBuckets = Math.max(2, Math.min(24, Math.ceil(durMs / bucketMs)));
53142
- labelFn = (ms) => `${String(new Date(ms).getHours()).padStart(2, "0")}h`;
53143
- } else if (period === "dia") {
53144
- bucketMs = 864e5;
53145
- numBuckets = Math.max(1, Math.min(31, Math.ceil(durMs / bucketMs)));
53146
- labelFn = (ms) => {
53147
- const d = new Date(ms);
53148
- return `${String(d.getDate()).padStart(2, "0")}/${String(d.getMonth() + 1).padStart(2, "0")}`;
53149
- };
53150
- } else if (period === "semana") {
53151
- bucketMs = 7 * 864e5;
53152
- numBuckets = Math.max(1, Math.min(26, Math.ceil(durMs / bucketMs)));
53153
- labelFn = (ms) => {
53154
- const d = new Date(ms);
53155
- return `${String(d.getDate()).padStart(2, "0")}/${String(d.getMonth() + 1).padStart(2, "0")}`;
53156
- };
53157
- } else {
53158
- bucketMs = 30 * 864e5;
53159
- numBuckets = Math.max(1, Math.min(24, Math.ceil(durMs / bucketMs)));
53160
- const months = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"];
53161
- labelFn = (ms) => months[new Date(ms).getMonth()];
53162
- }
53163
- while (numBuckets > 1 && firstMs + (numBuckets - 1) * bucketMs > nowMs) numBuckets--;
53164
- const seed = hashStr(alarm.id + period);
53165
- const rand = seededRand(seed);
53166
- const weights = Array.from({ length: numBuckets }, (_, i) => {
53167
- const center = (numBuckets - 1) / 2;
53168
- const bell = 1 - 0.45 * Math.abs(i - center) / (center || 1);
53169
- return Math.max(0.1, bell * (0.6 + rand() * 0.8));
53170
- });
53171
- const wSum = weights.reduce((a, b) => a + b, 0);
53172
- const totals = new Array(numBuckets).fill(0);
53173
- let remaining = count;
53174
- for (let i = 0; i < numBuckets - 1; i++) {
53175
- const v = Math.round(weights[i] / wSum * count);
53176
- totals[i] = Math.min(v, remaining);
53177
- remaining -= totals[i];
53178
- }
53179
- totals[numBuckets - 1] = Math.max(0, remaining);
53180
- return Array.from({ length: numBuckets }, (_, i) => {
53181
- const ts = firstMs + i * bucketMs;
53182
- const total = totals[i];
53183
- const byDevice = {};
53184
- if (devices.length === 1) {
53185
- byDevice[devices[0]] = total;
53186
- } else {
53187
- let rem = total;
53188
- for (let d = 0; d < devices.length - 1; d++) {
53189
- const share = Math.round(rand() * rem * (1 / (devices.length - d)));
53190
- byDevice[devices[d]] = share;
53191
- rem -= share;
53690
+ function hodFromEvents(occ) {
53691
+ const v = new Array(24).fill(0);
53692
+ occ.forEach((e) => v[new Date(e.ts).getHours()]++);
53693
+ return v;
53694
+ }
53695
+ function resolveDeviceIdentity(alarm, src) {
53696
+ const orch = window.MyIOOrchestrator;
53697
+ const nameToLabel = orch?.entityNameToLabelMap;
53698
+ const gcdrToLabel = orch?.gcdrDeviceNameMap;
53699
+ let label = null;
53700
+ if (alarm.deviceId && parseDevices(alarm.source).length <= 1) label = gcdrToLabel?.get?.(alarm.deviceId) || null;
53701
+ if (!label && nameToLabel?.has?.(src)) label = nameToLabel.get(src) || null;
53702
+ if (!label) label = src;
53703
+ let name = nameToLabel?.has?.(src) ? src : null;
53704
+ if (!name && nameToLabel) {
53705
+ for (const [n, l] of nameToLabel) {
53706
+ if (l === label) {
53707
+ name = n;
53708
+ break;
53192
53709
  }
53193
- byDevice[devices[devices.length - 1]] = Math.max(0, rem);
53194
53710
  }
53195
- return { label: labelFn(ts), total, byDevice };
53196
- });
53711
+ }
53712
+ return { label, name: name && name !== label ? name : null };
53197
53713
  }
53198
- function generateDowDistribution(alarm) {
53199
- const count = alarm.occurrenceCount || 1;
53200
- const rand = seededRand(hashStr(alarm.id + "dow"));
53201
- const weights = [0.2, 1, 0.95, 0.9, 0.85, 0.8, 0.3];
53202
- const wSum = weights.reduce((a, b) => a + b, 0);
53203
- const vals = weights.map((w) => Math.round(w / wSum * count * (0.7 + rand() * 0.6)));
53204
- return vals;
53714
+ function resolveDeviceLabel(alarm, src) {
53715
+ return resolveDeviceIdentity(alarm, src).label;
53205
53716
  }
53206
- function generateHodDistribution(alarm) {
53207
- const count = alarm.occurrenceCount || 1;
53208
- const rand = seededRand(hashStr(alarm.id + "hod"));
53209
- const weights = Array.from({ length: 24 }, (_, h3) => {
53210
- if (h3 < 6) return 0.05 + rand() * 0.05;
53211
- if (h3 < 10) return 0.3 + rand() * 0.3;
53212
- if (h3 <= 19) return 0.7 + rand() * 0.6;
53213
- if (h3 <= 22) return 0.3 + rand() * 0.3;
53214
- return 0.05 + rand() * 0.05;
53215
- });
53216
- const wSum = weights.reduce((a, b) => a + b, 0);
53217
- return weights.map((w) => Math.max(0, Math.round(w / wSum * count)));
53717
+ function brandColor(fallback = "#7c3aed") {
53718
+ const theme = window.MyIOUtils?.theme;
53719
+ const vars = typeof theme?.cssVars === "function" ? theme.cssVars() : theme;
53720
+ const c = vars?.["--myio-brand-700"];
53721
+ return typeof c === "string" && /^#[0-9a-f]{6}$/i.test(c.trim()) ? c.trim() : fallback;
53722
+ }
53723
+ var COPY_SVG = '<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>';
53724
+ function deviceIdentityHtml(alarm, src) {
53725
+ const { label, name } = resolveDeviceIdentity(alarm, src);
53726
+ const copyValue = name || src;
53727
+ return `<span class="adm-dev-id">
53728
+ <span class="adm-dev-label">${escHtml2(label)}</span>
53729
+ ${name ? `<span class="adm-dev-name">${escHtml2(name)}</span>` : ""}
53730
+ <button type="button" class="adm-copy-btn" data-copy="${escHtml2(copyValue)}" title="Copiar nome do dispositivo">${COPY_SVG}</button>
53731
+ </span>`;
53218
53732
  }
53219
53733
  var DEV_COLORS = [
53220
53734
  "#7c3aed",
@@ -53304,8 +53818,8 @@ function createDowChart(canvas, vals) {
53304
53818
  datasets: [{
53305
53819
  label: "Ocorr\xEAncias",
53306
53820
  data: vals,
53307
- backgroundColor: "#8b5cf6bb",
53308
- borderColor: "#7c3aed",
53821
+ backgroundColor: brandColor("#8b5cf6") + "bb",
53822
+ borderColor: brandColor(),
53309
53823
  borderWidth: 1,
53310
53824
  borderRadius: 3
53311
53825
  }]
@@ -53347,8 +53861,8 @@ function createHodChart(canvas, vals) {
53347
53861
  datasets: [{
53348
53862
  label: "Ocorr\xEAncias",
53349
53863
  data: vals,
53350
- backgroundColor: "#3b82f6bb",
53351
- borderColor: "#2563eb",
53864
+ backgroundColor: brandColor("#3b82f6") + "bb",
53865
+ borderColor: brandColor("#2563eb"),
53352
53866
  borderWidth: 1,
53353
53867
  borderRadius: 2
53354
53868
  }]
@@ -53409,12 +53923,12 @@ var HIDE_ANNOTATIONS_TAB = true;
53409
53923
  function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction) {
53410
53924
  const sev = SEVERITY_CONFIG[alarm.severity];
53411
53925
  const st = STATE_CONFIG[alarm.state];
53412
- const count = alarm.occurrenceCount || 1;
53413
53926
  const firstMs = new Date(alarm.firstOccurrence).getTime();
53414
53927
  const lastMs = new Date(alarm.lastOccurrence).getTime();
53415
- const durMs = isNaN(firstMs) || isNaN(lastMs) ? 0 : Math.max(0, lastMs - firstMs);
53416
- const durLabel = durationLabel(durMs);
53417
- const avgFreq = count > 1 ? durationLabel(durMs / (count - 1)) + " entre ocorr\xEAncias" : "Evento \xFAnico";
53928
+ const isClosedState = alarm.state === "CLOSED";
53929
+ const closedMs = alarm.closedAt ? new Date(alarm.closedAt).getTime() : NaN;
53930
+ const ageMs = isNaN(firstMs) ? NaN : isClosedState ? (Number.isFinite(closedMs) ? closedMs : lastMs) - firstMs : Date.now() - firstMs;
53931
+ const ageKpiLabel = isClosedState ? "Dura\xE7\xE3o" : "Aberto h\xE1";
53418
53932
  const devices = parseDevices(alarm.source);
53419
53933
  const deviceCount = devices.length;
53420
53934
  const row = (label, value) => value ? `<div class="adm-row"><span class="adm-row-label">${label}</span><span class="adm-row-value">${escHtml2(value)}</span></div>` : "";
@@ -53423,48 +53937,30 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53423
53937
  <div class="adm-device-row">
53424
53938
  <span class="adm-device-row-index">${i + 1}</span>
53425
53939
  <div class="adm-device-icon-sm">
53426
- <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#7c3aed" stroke-width="2">
53940
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2">
53427
53941
  <rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>
53428
53942
  </svg>
53429
53943
  </div>
53430
- <span class="adm-device-row-name">${escHtml2(dev)}</span>
53944
+ <span class="adm-device-row-name">${deviceIdentityHtml(alarm, dev)}</span>
53431
53945
  <span class="adm-device-row-sub">${escHtml2(alarm.customerName || "-")}</span>
53432
53946
  </div>`
53433
53947
  ).join("");
53434
- const initialPeriod = durMs < 864e5 ? "hora" : durMs < 7 * 864e5 ? "dia" : durMs < 30 * 864e5 ? "semana" : "mes";
53435
- const dowVals = generateDowDistribution(alarm);
53436
- const hodVals = generateHodDistribution(alarm);
53948
+ const spanMs = Number.isFinite(ageMs) ? ageMs : 0;
53949
+ const initialPeriod = spanMs < 3 * 864e5 ? "hora" : spanMs < 45 * 864e5 ? "dia" : spanMs < 180 * 864e5 ? "semana" : "mes";
53437
53950
  const chartSingleColor = sev.text || "#7c3aed";
53951
+ let events = null;
53952
+ const occEvents = () => (events || []).filter((e) => e.occurrence);
53953
+ const onEventsReady = [];
53438
53954
  const annotCount = getActiveAnnotationCount(alarm.id);
53439
- const sourceDisplay = deviceCount > 1 ? `${deviceCount} dispositivos` : escHtml2(alarm.source);
53955
+ const sourceDisplay = deviceCount > 1 ? `${deviceCount} dispositivos` : escHtml2(resolveDeviceLabel(alarm, alarm.source) || alarm.source);
53440
53956
  const overlay = document.createElement("div");
53441
53957
  overlay.className = "adm-overlay";
53442
53958
  overlay.setAttribute("data-theme", themeMode);
53443
53959
  overlay.innerHTML = `
53444
53960
  <div class="adm-drawer" role="dialog" aria-modal="true" aria-label="Detalhes do alarme">
53445
53961
 
53446
- <!-- \u2500\u2500 Header \u2500\u2500 -->
53962
+ <!-- \u2500\u2500 Header (t\xEDtulo/maximizar/fechar v\xEAm da moldura padr\xE3o; aqui s\xF3 os badges) \u2500\u2500 -->
53447
53963
  <div class="adm-header">
53448
- <div class="adm-header-top">
53449
- <div class="adm-title" title="${escHtml2(alarm.title)}">${escHtml2(alarm.title)}</div>
53450
- <div class="adm-header-actions">
53451
- <button class="adm-header-btn" id="admMaximize" title="Maximizar">
53452
- <svg class="adm-icon-expand" viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
53453
- <polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/>
53454
- <line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/>
53455
- </svg>
53456
- <svg class="adm-icon-compress" viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
53457
- <polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/>
53458
- <line x1="10" y1="14" x2="3" y2="21"/><line x1="21" y1="3" x2="14" y2="10"/>
53459
- </svg>
53460
- </button>
53461
- <button class="adm-header-btn adm-close" id="admClose" title="Fechar (Esc)">
53462
- <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.5">
53463
- <path d="M18 6L6 18M6 6l12 12"/>
53464
- </svg>
53465
- </button>
53466
- </div>
53467
- </div>
53468
53964
  <div class="adm-badges">
53469
53965
  <span class="adm-badge-severity" style="background:${sev.bg};color:${sev.text}">${sev.icon} ${sev.label}</span>
53470
53966
  <span class="adm-badge-state" style="color:${st.color}">${st.label}</span>
@@ -53480,7 +53976,7 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53480
53976
  </button>
53481
53977
  <button class="adm-tab" data-panel="timeline">
53482
53978
  <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.22" y1="4.22" x2="7.05" y2="7.05"/><line x1="16.95" y1="16.95" x2="19.78" y2="19.78"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.22" y1="19.78" x2="7.05" y2="16.95"/><line x1="16.95" y1="7.05" x2="19.78" y2="4.22"/></svg>
53483
- Timeline <span class="adm-tab-badge">${count}</span>
53979
+ Timeline <span class="adm-tab-badge" id="admTlTabBadge">\u2026</span>
53484
53980
  </button>
53485
53981
  <button class="adm-tab" data-panel="dispositivos">
53486
53982
  <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor"><path d="M20 3H4a1 1 0 00-1 1v12a1 1 0 001 1h8v2H8v2h8v-2h-4v-2h8a1 1 0 001-1V4a1 1 0 00-1-1zm-1 12H5V5h14v10z"/></svg>
@@ -53502,17 +53998,17 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53502
53998
  <!-- RESUMO -->
53503
53999
  <div class="adm-panel is-active" data-panel="resumo">
53504
54000
  ${groupMode !== "separado" ? `<div class="adm-kpi-grid">
53505
- <div class="adm-kpi">
53506
- <div class="adm-kpi-value" style="color:${sev.text}">${count}</div>
54001
+ <div class="adm-kpi" title="Abertura + reaberturas registradas no hist\xF3rico do alarme">
54002
+ <div class="adm-kpi-value" id="admKpiOcc" style="color:${sev.text}">\u2026</div>
53507
54003
  <div class="adm-kpi-label">Ocorr\xEAncias</div>
53508
54004
  </div>
53509
54005
  <div class="adm-kpi">
53510
54006
  <div class="adm-kpi-value">${deviceCount}</div>
53511
54007
  <div class="adm-kpi-label">Dispositivos</div>
53512
54008
  </div>
53513
- <div class="adm-kpi">
53514
- <div class="adm-kpi-value">${durLabel}</div>
53515
- <div class="adm-kpi-label">Dura\xE7\xE3o</div>
54009
+ <div class="adm-kpi" title="${isClosedState ? "Da abertura ao fechamento" : "Desde a abertura do alarme at\xE9 agora"}">
54010
+ <div class="adm-kpi-value">${Number.isFinite(ageMs) ? durationLabel(ageMs) : "-"}</div>
54011
+ <div class="adm-kpi-label">${ageKpiLabel}</div>
53516
54012
  </div>
53517
54013
  </div>` : ""}
53518
54014
 
@@ -53529,15 +54025,16 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53529
54025
  <div class="adm-section">
53530
54026
  <div class="adm-section-title">Identifica\xE7\xE3o</div>
53531
54027
  ${row("Shopping", alarm.customerName)}
53532
- ${row("Dispositivo(s)", alarm.source)}
54028
+ ${devices.length ? `<div class="adm-row"><span class="adm-row-label">Dispositivo(s)</span><span class="adm-row-value adm-row-value--devices">${devices.map((d) => deviceIdentityHtml(alarm, d)).join("")}</span></div>` : ""}
53533
54029
  ${alarm.triggerValue != null ? row("Valor do disparo", String(alarm.triggerValue)) : ""}
53534
54030
  ${groupMode === "separado" ? row("ID", alarm.id) : ""}
53535
54031
  </div>
53536
54032
 
53537
54033
  <div class="adm-section">
53538
54034
  <div class="adm-section-title">Datas</div>
53539
- ${row("Primeira ocorr\xEAncia", fmt(alarm.firstOccurrence))}
53540
- ${row("\xDAltima ocorr\xEAncia", fmt(alarm.lastOccurrence))}
54035
+ ${row("Aberto em", fmt(alarm.firstOccurrence))}
54036
+ <div id="admReopenRows"></div>
54037
+ ${row("\xDAltima atualiza\xE7\xE3o", fmt(alarm.lastOccurrence))}
53541
54038
  ${alarm.acknowledgedAt ? row("Reconhecido em", fmt(alarm.acknowledgedAt)) : ""}
53542
54039
  ${alarm.acknowledgedBy ? row("Reconhecido por", alarm.acknowledgedBy) : ""}
53543
54040
  ${alarm.snoozedUntil ? row("Adiado at\xE9", fmt(alarm.snoozedUntil)) : ""}
@@ -53562,7 +54059,7 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53562
54059
  <div class="adm-section-title">IDs de Alarme (${alarm._groupAlarmIds.length})</div>
53563
54060
  <div class="adm-alarm-ids-list" style="display:flex;flex-wrap:wrap;gap:6px;">
53564
54061
  ${alarm._groupAlarmIds.map(
53565
- (rawId) => `<span class="adm-alarm-id-chip" title="Clique para copiar" data-copy-id="${escHtml2(rawId)}" style="display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid rgba(124,58,237,0.25);background:rgba(124,58,237,0.08);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:#4c1d95;cursor:pointer;user-select:all;">${escHtml2(rawId)}</span>`
54062
+ (rawId) => `<span class="adm-alarm-id-chip" title="Clique para copiar" data-copy-id="${escHtml2(rawId)}" style="display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid color-mix(in srgb, var(--myio-brand-700, #7c3aed) 25%, transparent);background:color-mix(in srgb, var(--myio-brand-700, #7c3aed) 8%, transparent);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--myio-brand-700, #4c1d95);cursor:pointer;user-select:all;">${escHtml2(rawId)}</span>`
53566
54063
  ).join("")}
53567
54064
  </div>
53568
54065
  </div>` : ""}
@@ -53584,7 +54081,7 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53584
54081
  <div class="adm-panel" data-panel="timeline">
53585
54082
  <div class="adm-tl-header" id="admTlHeader">
53586
54083
  <div class="adm-tl-toolbar">
53587
- <span class="adm-tl-summary" id="admTlSummary">${count} ocorr\xEAncia${count !== 1 ? "s" : ""}</span>
54084
+ <span class="adm-tl-summary" id="admTlSummary">Carregando hist\xF3rico\u2026</span>
53588
54085
  <button class="adm-tl-batch-toggle" id="admTlBatchToggle" title="A\xE7\xF5es em lote">
53589
54086
  <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="4" height="4" rx="1"/><path d="M10 7h11"/><rect x="3" y="12" width="4" height="4" rx="1"/><path d="M10 14h11"/><rect x="3" y="19" width="4" height="4" rx="1"/><path d="M10 21h11"/></svg>
53590
54087
  A\xE7\xE3o em lote
@@ -53610,7 +54107,7 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53610
54107
  <label class="adm-report-label">De</label>
53611
54108
  <input type="date" class="adm-report-date-input" id="admRptStart" value="${fmtDateInput(firstMs)}">
53612
54109
  <label class="adm-report-label">At\xE9</label>
53613
- <input type="date" class="adm-report-date-input" id="admRptEnd" value="${fmtDateInput(lastMs)}">
54110
+ <input type="date" class="adm-report-date-input" id="admRptEnd" value="${fmtDateInput(Date.now())}">
53614
54111
  <button class="adm-report-emit-btn" id="admRptEmit">
53615
54112
  <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
53616
54113
  Carregar
@@ -53661,7 +54158,8 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53661
54158
  </div>
53662
54159
 
53663
54160
  <div class="adm-section">
53664
- <div class="adm-section-title">Tend\xEAncia de ocorr\xEAncias no per\xEDodo</div>
54161
+ <div class="adm-section-title">Ocorr\xEAncias no per\xEDodo</div>
54162
+ <div class="adm-chart-note" id="admChartNote">Carregando hist\xF3rico\u2026</div>
53665
54163
  <div class="adm-chart-wrapper">
53666
54164
  <canvas id="admMainChart" class="adm-chart-canvas"></canvas>
53667
54165
  </div>
@@ -53719,21 +54217,45 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53719
54217
  let activeChartType = "bar";
53720
54218
  let activeVizMode = "total";
53721
54219
  let activePeriod = initialPeriod;
54220
+ const noteEl = graficoPanel.querySelector("#admChartNote");
54221
+ const updateNote = () => {
54222
+ if (!noteEl) return;
54223
+ if (!events) {
54224
+ noteEl.textContent = "Carregando hist\xF3rico\u2026";
54225
+ return;
54226
+ }
54227
+ const n = occEvents().length;
54228
+ noteEl.textContent = `${n} ocorr\xEAncia${n !== 1 ? "s" : ""} registrada${n !== 1 ? "s" : ""} (abertura + reaberturas do hist\xF3rico do alarme).` + (n < 3 ? " Poucos eventos para indicar tend\xEAncia." : "");
54229
+ };
53722
54230
  const buildMain = () => {
53723
- const nb = generateBuckets(alarm, devices, activePeriod);
54231
+ updateNote();
54232
+ if (!events) return;
53724
54233
  const canvas = graficoPanel.querySelector("#admMainChart");
53725
54234
  if (!canvas) return;
54235
+ const occ = occEvents();
54236
+ const fromMs = occ.length ? occ[0].ts : Number.isFinite(firstMs) ? firstMs : Date.now();
54237
+ const nb = bucketEvents(occ, devices, activePeriod, fromMs, Date.now());
53726
54238
  if (chartInstances[0]) {
53727
54239
  chartInstances[0].destroy();
53728
54240
  chartInstances[0] = null;
53729
54241
  }
53730
54242
  chartInstances[0] = createMainChart(canvas, nb, devices, activeChartType, activeVizMode, chartSingleColor);
53731
54243
  };
53732
- const initSecondary = () => {
54244
+ const buildSecondary = () => {
54245
+ if (!events) return;
54246
+ const occ = occEvents();
53733
54247
  const dowCanvas = graficoPanel.querySelector("#admDowChart");
53734
- if (dowCanvas && !chartInstances[1]) chartInstances[1] = createDowChart(dowCanvas, dowVals);
54248
+ if (chartInstances[1]) {
54249
+ chartInstances[1].destroy();
54250
+ chartInstances[1] = null;
54251
+ }
54252
+ if (dowCanvas) chartInstances[1] = createDowChart(dowCanvas, dowFromEvents(occ));
53735
54253
  const hodCanvas = graficoPanel.querySelector("#admHodChart");
53736
- if (hodCanvas && !chartInstances[2]) chartInstances[2] = createHodChart(hodCanvas, hodVals);
54254
+ if (chartInstances[2]) {
54255
+ chartInstances[2].destroy();
54256
+ chartInstances[2] = null;
54257
+ }
54258
+ if (hodCanvas) chartInstances[2] = createHodChart(hodCanvas, hodFromEvents(occ));
53737
54259
  };
53738
54260
  graficoPanel.querySelectorAll("button[data-chart-type]").forEach((btn) => {
53739
54261
  btn.addEventListener("click", () => {
@@ -53759,11 +54281,16 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53759
54281
  buildMain();
53760
54282
  });
53761
54283
  });
54284
+ const renderCharts = () => {
54285
+ buildMain();
54286
+ buildSecondary();
54287
+ };
53762
54288
  overlay.querySelectorAll('.adm-tab[data-panel="grafico"]').forEach((tabBtn) => {
53763
- tabBtn.addEventListener("click", () => setTimeout(() => {
53764
- buildMain();
53765
- initSecondary();
53766
- }, 30));
54289
+ tabBtn.addEventListener("click", () => setTimeout(renderCharts, 30));
54290
+ });
54291
+ onEventsReady.push(() => {
54292
+ if (graficoPanel.classList.contains("is-active")) renderCharts();
54293
+ else updateNote();
53767
54294
  });
53768
54295
  }
53769
54296
  const rptPanel = overlay.querySelector('.adm-panel[data-panel="timeline"]');
@@ -53779,159 +54306,126 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53779
54306
  const _svgAck = `<svg viewBox="0 0 24 24" width="11" height="11" fill="currentColor" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>`;
53780
54307
  const _svgSnooze = `<svg viewBox="0 0 24 24" width="11" height="11" fill="currentColor" aria-hidden="true"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67V7z"/></svg>`;
53781
54308
  const _svgEscalate = `<svg viewBox="0 0 24 24" width="11" height="11" fill="currentColor" aria-hidden="true"><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>`;
53782
- const buildOccurrenceSection = (occFirst, occLast) => {
53783
- const intervalMs = count > 1 ? (occLast - occFirst) / (count - 1) : 0;
53784
- const trigStr = alarm.triggerValue != null ? String(alarm.triggerValue) : null;
53785
- const hasTrigger = trigStr != null;
54309
+ const buildOccurrenceSection = (startMs, endMs) => {
54310
+ if (!events) {
54311
+ return { html: '<div class="adm-report-empty-hint">Carregando hist\xF3rico do alarme\u2026</div>', csvRows: [] };
54312
+ }
53786
54313
  const showDevice = !isSeparado && devices.length > 0;
54314
+ const showAlarm = groupMode === "porDispositivo" && (alarm._groupAlarmIds?.length || 0) > 1;
53787
54315
  const isActive = alarm.state === "OPEN" || alarm.state === "ESCALATED";
53788
54316
  const isClosed = alarm.state === "CLOSED";
53789
- const idxStyle = (isSingle, isLatest, isFirst) => {
53790
- if (isSingle) return "color:#7c3aed;font-weight:700";
53791
- if (isLatest) return "color:#3b82f6;font-weight:700";
53792
- if (isFirst) return "color:#ef4444;font-weight:700";
53793
- return "";
53794
- };
53795
- const csvHeader = ["#", "Data/Hora"];
53796
- if (showDevice) csvHeader.push("Dispositivo");
53797
- if (hasTrigger) csvHeader.push("Trigger");
54317
+ const occIndex = /* @__PURE__ */ new Map();
54318
+ occEvents().forEach((e, i) => occIndex.set(e, i + 1));
54319
+ const inRange = events.filter((e) => e.ts >= startMs && e.ts <= endMs).sort((a, b) => b.ts - a.ts || (occIndex.get(b) || 0) - (occIndex.get(a) || 0));
54320
+ const csvHeader = ["#", "Data/Hora", "Evento"];
54321
+ if (showAlarm) csvHeader.push("Alarme");
54322
+ if (showDevice) csvHeader.push("Dispositivo", "Name (TB)");
54323
+ csvHeader.push("Por");
53798
54324
  const rows = [csvHeader];
53799
- const tableRows = [];
53800
- if (groupMode === "porDispositivo" && alarm._alarmTypeGroups?.length) {
53801
- const groups = alarm._alarmTypeGroups;
53802
- const groupHtml = groups.map((grp, gi) => {
53803
- const gCount = grp.occurrenceCount || 1;
53804
- const gFirstMs = new Date(grp.firstOccurrence).getTime();
53805
- const gLastMs = new Date(grp.lastOccurrence).getTime();
53806
- const gInterval = gCount > 1 ? (gLastMs - gFirstMs) / (gCount - 1) : 0;
53807
- const gTrig = grp.triggerValue != null ? String(grp.triggerValue) : trigStr;
53808
- const gHasTrig = gTrig != null;
53809
- const gThead = `<thead><tr>
53810
- <th class="adm-rpt-th adm-rpt-th--idx">#</th>
53811
- <th class="adm-rpt-th">Data/Hora</th>
53812
- ${gHasTrig ? `<th class="adm-rpt-th adm-rpt-th--num">Trigger</th>` : ""}
53813
- </tr></thead>`;
53814
- const gRows = [];
53815
- const MAX = 10;
53816
- for (let i = gCount; i >= 1; i--) {
53817
- const tsMs = gFirstMs + gInterval * (i - 1);
53818
- const isSingle = gCount === 1;
53819
- const isLatest = !isSingle && i === gCount;
53820
- const isFirst = !isSingle && i === 1;
53821
- const isEst = !isSingle && !isLatest && !isFirst;
53822
- const timeStr = (isEst ? "~" : "") + fmt(tsMs);
53823
- const style = idxStyle(isSingle, isLatest, isFirst);
53824
- gRows.push(`<tr>
53825
- <td class="adm-rpt-cell adm-rpt-cell--idx"${style ? ` style="${style}"` : ""}>#${i}</td>
53826
- <td class="adm-rpt-cell">${timeStr}</td>
53827
- ${gHasTrig ? `<td class="adm-rpt-cell adm-rpt-cell--num">${escHtml2(gTrig)}</td>` : ""}
53828
- </tr>`);
53829
- const csvRow = [`#${i}`, fmt(tsMs)];
53830
- if (gHasTrig) csvRow.push(gTrig);
53831
- rows.push(csvRow);
53832
- if (gRows.length >= MAX) break;
53833
- }
53834
- const sepStyle = gi > 0 ? ' style="margin-top:12px;padding-top:12px;border-top:1px dashed #e5e7eb"' : "";
53835
- return `<div${sepStyle}>
53836
- <div class="adm-timeline-group-header">
53837
- <label class="adm-tl-cb-wrap" data-cb-wrap><input type="checkbox" class="adm-tl-row-cb" data-item-id="${alarm.id}__grp${gi}"></label>
53838
- <span class="adm-tl-group-title">${escHtml2(grp.title)}</span>
53839
- <span class="adm-timeline-group-count">${gCount}</span>
53840
- <div class="adm-tl-row-btns adm-tl-row-btns--group" data-row-btns>
53841
- ${isActive ? `<button class="adm-tl-row-btn adm-tl-row-btn--ack" data-action="acknowledge" data-item-id="${alarm.id}" title="Reconhecer">${_svgAck}</button>` : ""}
53842
- ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--snooze" data-action="snooze" data-item-id="${alarm.id}" title="Adiar">${_svgSnooze}</button>` : ""}
53843
- ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--escalate" data-action="escalate" data-item-id="${alarm.id}" title="Escalar">${_svgEscalate}</button>` : ""}
53844
- </div>
53845
- </div>
53846
- <div class="adm-rpt-table-wrap" style="margin-bottom:0">
53847
- <table class="adm-rpt-table">${gThead}<tbody>${gRows.join("")}</tbody></table>
53848
- </div>
53849
- </div>`;
53850
- }).join("");
53851
- return { html: groupHtml, csvRows: rows };
53852
- }
53853
- const deviceTokens = alarm.source ? alarm.source.split(/[,;]+/).map((s) => s.trim()).filter(Boolean) : [];
54325
+ const kindStyle = {
54326
+ raised: "color:#ef4444;font-weight:700",
54327
+ reopened: "color:#d97706;font-weight:700",
54328
+ closed: "color:#16a34a;font-weight:700",
54329
+ acknowledged: "color:var(--myio-brand-700, #7c3aed);font-weight:600",
54330
+ silenced: "color:#6b7280;font-weight:600",
54331
+ escalated: "color:#dc2626;font-weight:600",
54332
+ other: "color:#6b7280"
54333
+ };
53854
54334
  const thead = `<thead><tr>
53855
54335
  <th class="adm-rpt-th adm-rpt-th--idx">#</th>
53856
54336
  <th class="adm-rpt-th">Data/Hora</th>
54337
+ <th class="adm-rpt-th">Evento</th>
54338
+ ${showAlarm ? `<th class="adm-rpt-th">Alarme</th>` : ""}
53857
54339
  ${showDevice ? `<th class="adm-rpt-th">Dispositivo</th>` : ""}
53858
- ${hasTrigger ? `<th class="adm-rpt-th adm-rpt-th--num">Trigger</th>` : ""}
54340
+ <th class="adm-rpt-th">Por</th>
53859
54341
  <th class="adm-rpt-th adm-tl-actions-th">A\xE7\xF5es</th>
53860
54342
  </tr></thead>`;
53861
- const MAX_SHOWN = 30;
53862
- for (let i = count; i >= 1; i--) {
53863
- const tsMs = occFirst + intervalMs * (i - 1);
53864
- const isSingle = count === 1;
53865
- const isLatest = !isSingle && i === count;
53866
- const isFirst = !isSingle && i === 1;
53867
- const isEst = !isSingle && !isLatest && !isFirst;
53868
- const timeStr = (isEst ? "~" : "") + fmt(tsMs);
53869
- const device = showDevice ? deviceTokens[(i - 1) % deviceTokens.length] : "";
53870
- const style = idxStyle(isSingle, isLatest, isFirst);
53871
- tableRows.push(`<tr>
53872
- <td class="adm-rpt-cell adm-rpt-cell--idx"${style ? ` style="${style}"` : ""}>#${i}</td>
53873
- <td class="adm-rpt-cell">${timeStr}</td>
53874
- ${showDevice ? `<td class="adm-rpt-cell adm-rpt-cell--dev">${escHtml2(device)}</td>` : ""}
53875
- ${hasTrigger ? `<td class="adm-rpt-cell adm-rpt-cell--num">${escHtml2(trigStr)}</td>` : ""}
54343
+ const MAX_SHOWN = 200;
54344
+ const tableRows = inRange.slice(0, MAX_SHOWN).map((e, ri) => {
54345
+ const n = occIndex.get(e);
54346
+ const ident = showDevice ? resolveDeviceIdentity(alarm, e.device) : null;
54347
+ const actor = e.kind === "raised" ? "regra" : e.actor === "system" ? "sistema" : e.actor || "-";
54348
+ const csvRow = [n ? `#${n}` : "", fmt(e.ts), EVENT_LABEL[e.kind]];
54349
+ if (showAlarm) csvRow.push(e.title);
54350
+ if (showDevice) csvRow.push(ident.label, ident.name || e.device);
54351
+ csvRow.push(actor);
54352
+ rows.push(csvRow);
54353
+ const itemId = `${e.alarmId}__ev${ri}`;
54354
+ return `<tr>
54355
+ <td class="adm-rpt-cell adm-rpt-cell--idx">${n ? `#${n}` : ""}</td>
54356
+ <td class="adm-rpt-cell">${fmt(e.ts)}</td>
54357
+ <td class="adm-rpt-cell" style="${kindStyle[e.kind]}">${EVENT_LABEL[e.kind]}</td>
54358
+ ${showAlarm ? `<td class="adm-rpt-cell">${escHtml2(e.title)}</td>` : ""}
54359
+ ${showDevice ? `<td class="adm-rpt-cell adm-rpt-cell--dev" title="${escHtml2(ident.name || e.device)}">${escHtml2(ident.label)}</td>` : ""}
54360
+ <td class="adm-rpt-cell">${escHtml2(actor)}</td>
53876
54361
  <td class="adm-rpt-cell adm-tl-action-cell">
53877
- <label class="adm-tl-cb-wrap" data-cb-wrap><input type="checkbox" class="adm-tl-row-cb" data-item-id="${alarm.id}__row${i}"></label>
54362
+ ${e.occurrence ? `<label class="adm-tl-cb-wrap" data-cb-wrap><input type="checkbox" class="adm-tl-row-cb" data-item-id="${escHtml2(itemId)}"></label>
53878
54363
  <div class="adm-tl-row-btns" data-row-btns>
53879
- ${isActive ? `<button class="adm-tl-row-btn adm-tl-row-btn--ack" data-action="acknowledge" data-item-id="${alarm.id}" title="Reconhecer">${_svgAck}</button>` : ""}
53880
- ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--snooze" data-action="snooze" data-item-id="${alarm.id}" title="Adiar">${_svgSnooze}</button>` : ""}
53881
- ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--escalate" data-action="escalate" data-item-id="${alarm.id}" title="Escalar">${_svgEscalate}</button>` : ""}
53882
- </div>
54364
+ ${isActive ? `<button class="adm-tl-row-btn adm-tl-row-btn--ack" data-action="acknowledge" data-item-id="${escHtml2(itemId)}" title="Reconhecer">${_svgAck}</button>` : ""}
54365
+ ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--snooze" data-action="snooze" data-item-id="${escHtml2(itemId)}" title="Adiar">${_svgSnooze}</button>` : ""}
54366
+ ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--escalate" data-action="escalate" data-item-id="${escHtml2(itemId)}" title="Escalar">${_svgEscalate}</button>` : ""}
54367
+ </div>` : ""}
53883
54368
  </td>
53884
- </tr>`);
53885
- const csvRow = [`#${i}`, fmt(tsMs)];
53886
- if (showDevice) csvRow.push(device);
53887
- if (hasTrigger) csvRow.push(trigStr);
53888
- rows.push(csvRow);
53889
- if (tableRows.length >= MAX_SHOWN) break;
53890
- }
53891
- const html = `<div class="adm-rpt-table-wrap" id="admRptTableWrap" style="max-height:340px;overflow-y:auto">
54369
+ </tr>`;
54370
+ });
54371
+ const occInRange = inRange.filter((e) => e.occurrence).length;
54372
+ const colspan = 4 + (showAlarm ? 1 : 0) + (showDevice ? 1 : 0);
54373
+ const html = inRange.length ? `<div class="adm-rpt-table-wrap" id="admRptTableWrap" style="max-height:340px;overflow-y:auto">
53892
54374
  <table class="adm-rpt-table">
53893
54375
  ${thead}
53894
54376
  <tbody>${tableRows.join("")}</tbody>
53895
54377
  <tfoot><tr>
53896
- <td class="adm-rpt-cell adm-rpt-cell--total" colspan="${showDevice ? hasTrigger ? 3 : 2 : hasTrigger ? 2 : 1}">Total</td>
53897
- <td class="adm-rpt-cell adm-rpt-cell--num adm-rpt-cell--total">${count} ocorr\xEAncia${count !== 1 ? "s" : ""}</td>
54378
+ <td class="adm-rpt-cell adm-rpt-cell--total" colspan="${colspan}">Total: ${occInRange} ocorr\xEAncia${occInRange !== 1 ? "s" : ""} \xB7 ${inRange.length} evento${inRange.length !== 1 ? "s" : ""}</td>
53898
54379
  <td class="adm-rpt-cell adm-rpt-cell--total"></td>
53899
54380
  </tr></tfoot>
53900
54381
  </table>
53901
- </div>`;
54382
+ </div>` : '<div class="adm-report-empty-hint">Nenhum evento no per\xEDodo selecionado.</div>';
53902
54383
  return { html, csvRows: rows };
53903
54384
  };
53904
54385
  const buildReportTable = () => {
53905
54386
  const startInput = rptPanel.querySelector("#admRptStart");
53906
54387
  const endInput = rptPanel.querySelector("#admRptEnd");
53907
- const startMs = startInput?.value ? new Date(startInput.value).getTime() : firstMs;
53908
- const endMs = endInput?.value ? new Date(endInput.value).getTime() + 864e5 - 1 : lastMs;
53909
- const occFirst = Math.max(firstMs, startMs);
53910
- const occLast = Math.min(lastMs, endMs);
54388
+ const toLocalDay = (v) => {
54389
+ const [y, m, d] = v.split("-").map(Number);
54390
+ return new Date(y, (m || 1) - 1, d || 1).getTime();
54391
+ };
54392
+ const startMs = startInput?.value ? toLocalDay(startInput.value) : 0;
54393
+ const endMs = endInput?.value ? toLocalDay(endInput.value) + 864e5 - 1 : Date.now();
53911
54394
  const headerHtml = isSeparado ? `<div class="adm-rpt-device-header">
53912
- <span class="adm-rpt-device-name">${escHtml2(alarm.source)}</span>
54395
+ <span class="adm-rpt-device-name" title="${escHtml2(alarm.source)}">${escHtml2(resolveDeviceLabel(alarm, alarm.source) || alarm.source)}</span>
53913
54396
  <span class="adm-rpt-device-sep">\xB7</span>
53914
54397
  <span class="adm-rpt-alarm-type">${escHtml2(alarm.title)}</span>
53915
54398
  </div>` : "";
53916
- const sectionTitle = groupMode === "porDispositivo" && alarm._alarmTypeGroups?.length ? `${alarm._alarmTypeGroups.length} tipo${alarm._alarmTypeGroups.length !== 1 ? "s" : ""} \xB7 ${count} ocorr\xEAncia${count !== 1 ? "s" : ""} \xB7 ${durLabel} de janela` : `${count} ocorr\xEAncia${count !== 1 ? "s" : ""} \xB7 ${durLabel} de janela`;
53917
- const { html: occHtml, csvRows } = buildOccurrenceSection(occFirst, occLast);
54399
+ const { html: occHtml, csvRows } = buildOccurrenceSection(startMs, endMs);
53918
54400
  lastCsvRows = csvRows;
53919
- const statsHtml = count > 1 || alarm.triggerValue != null ? `<div class="adm-section">
54401
+ const occ = occEvents();
54402
+ const occN = occ.length;
54403
+ const sectionTitle = events ? `${occN} ocorr\xEAncia${occN !== 1 ? "s" : ""} \xB7 ${events.length} evento${events.length !== 1 ? "s" : ""} no hist\xF3rico` : "Hist\xF3rico do alarme";
54404
+ const reopenN = occ.filter((e) => e.kind === "reopened").length;
54405
+ const statsHtml = events && (occN > 1 || alarm.triggerValue != null) ? `<div class="adm-section">
53920
54406
  <div class="adm-section-title">Estat\xEDsticas de recorr\xEAncia</div>
53921
- ${alarm.triggerValue != null ? row("Valor do disparo", String(alarm.triggerValue)) : ""}
53922
- ${count > 1 ? row("Frequ\xEAncia m\xE9dia", avgFreq) : ""}
53923
- ${count > 1 ? row("Janela total", durLabel) : ""}
53924
- ${count > 1 ? row("Primeira ocorr\xEAncia", fmt(alarm.firstOccurrence)) : ""}
53925
- ${count > 1 ? row("\xDAltima ocorr\xEAncia", fmt(alarm.lastOccurrence)) : ""}
54407
+ ${alarm.triggerValue != null ? row("Valor do \xFAltimo disparo", String(alarm.triggerValue)) : ""}
54408
+ ${occN > 1 ? row("Reaberturas", String(reopenN)) : ""}
54409
+ ${occN > 1 ? row("Intervalo m\xE9dio entre ocorr\xEAncias", durationLabel((occ[occN - 1].ts - occ[0].ts) / (occN - 1))) : ""}
54410
+ ${occN > 1 ? row("Primeira ocorr\xEAncia", fmt(occ[0].ts)) : ""}
54411
+ ${occN > 1 ? row("\xDAltima ocorr\xEAncia", fmt(occ[occN - 1].ts)) : ""}
53926
54412
  </div>` : "";
53927
54413
  let summaryHtml = "";
53928
- if (!isSeparado && devices.length > 0) {
53929
- const devRows = devices.map((dev) => `<tr>
53930
- <td class="adm-rpt-cell">${escHtml2(dev)}</td>
53931
- <td class="adm-rpt-cell adm-rpt-cell--num">${count}</td>
53932
- <td class="adm-rpt-cell">${fmt(occFirst)}</td>
53933
- <td class="adm-rpt-cell">${fmt(occLast)}</td>
53934
- </tr>`).join("");
54414
+ if (events && !isSeparado && devices.length > 0) {
54415
+ const byDev = /* @__PURE__ */ new Map();
54416
+ occ.forEach((e) => {
54417
+ const k = devices.includes(e.device) ? e.device : devices[0];
54418
+ byDev.set(k, [...byDev.get(k) || [], e]);
54419
+ });
54420
+ const devRows = devices.map((dev) => {
54421
+ const list = byDev.get(dev) || [];
54422
+ return `<tr>
54423
+ <td class="adm-rpt-cell" title="${escHtml2(dev)}">${escHtml2(resolveDeviceLabel(alarm, dev) || dev)}</td>
54424
+ <td class="adm-rpt-cell adm-rpt-cell--num">${list.length}</td>
54425
+ <td class="adm-rpt-cell">${list.length ? fmt(list[0].ts) : "-"}</td>
54426
+ <td class="adm-rpt-cell">${list.length ? fmt(list[list.length - 1].ts) : "-"}</td>
54427
+ </tr>`;
54428
+ }).join("");
53935
54429
  summaryHtml = `
53936
54430
  <div class="adm-rpt-section-title">Sum\xE1rio por dispositivo</div>
53937
54431
  <div class="adm-rpt-table-wrap adm-rpt-table-wrap--summary">
@@ -53955,8 +54449,10 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53955
54449
  </div>
53956
54450
  ${statsHtml}
53957
54451
  ${summaryHtml}`;
53958
- csvBtn?.removeAttribute("disabled");
53959
- pdfBtn?.removeAttribute("disabled");
54452
+ if (events) {
54453
+ csvBtn?.removeAttribute("disabled");
54454
+ pdfBtn?.removeAttribute("disabled");
54455
+ }
53960
54456
  };
53961
54457
  csvBtn?.addEventListener("click", () => {
53962
54458
  if (!lastCsvRows.length) return;
@@ -53987,14 +54483,14 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53987
54483
  tfoot td{font-weight:700;background:#ede9fe;}
53988
54484
  </style></head><body>
53989
54485
  <h2>${escHtml2(alarm.title)}</h2>
53990
- <p>${escHtml2(alarm.id)} \xB7 ${escHtml2(alarm.customerName || "")} \xB7 ${count} ocorr\xEAncia${count !== 1 ? "s" : ""} \xB7 Emitido em ${(/* @__PURE__ */ new Date()).toLocaleDateString("pt-BR")}</p>
53991
- <h3>Ocorr\xEAncias</h3>${tbl}
54486
+ <p>${escHtml2(alarm.id)} \xB7 ${escHtml2(alarm.customerName || "")} \xB7 ${occEvents().length} ocorr\xEAncia${occEvents().length !== 1 ? "s" : ""} \xB7 Emitido em ${(/* @__PURE__ */ new Date()).toLocaleDateString("pt-BR")}</p>
54487
+ <h3>Hist\xF3rico</h3>${tbl}
53992
54488
  ${sumTbl ? `<h3>Sum\xE1rio por dispositivo</h3>${sumTbl}` : ""}
53993
54489
  <script>window.onload=function(){window.print();window.close();}</script>
53994
54490
  </body></html>`);
53995
54491
  win2.document.close();
53996
54492
  });
53997
- emitBtn?.addEventListener("click", () => {
54493
+ const refreshReport = () => {
53998
54494
  buildReportTable();
53999
54495
  if (batchActive && grid) {
54000
54496
  grid.querySelectorAll("[data-cb-wrap]").forEach((el2) => {
@@ -54004,8 +54500,10 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
54004
54500
  el2.style.display = "none";
54005
54501
  });
54006
54502
  }
54007
- });
54503
+ };
54504
+ emitBtn?.addEventListener("click", refreshReport);
54008
54505
  buildReportTable();
54506
+ onEventsReady.push(refreshReport);
54009
54507
  const batchToggleBtn = rptPanel.querySelector("#admTlBatchToggle");
54010
54508
  const batchBar = rptPanel.querySelector("#admTlBatchBar");
54011
54509
  const selAllCb = rptPanel.querySelector("#admTlSelAll");
@@ -54110,17 +54608,48 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
54110
54608
  }
54111
54609
  }, tbCfg);
54112
54610
  }
54611
+ const applyEvents = () => {
54612
+ const occ = occEvents();
54613
+ const occText = events ? String(occ.length || "-") : "\u2026";
54614
+ const kpi = overlay.querySelector("#admKpiOcc");
54615
+ if (kpi) kpi.textContent = occText;
54616
+ const badge = overlay.querySelector("#admTlTabBadge");
54617
+ if (badge) badge.textContent = occText;
54618
+ const summary = overlay.querySelector("#admTlSummary");
54619
+ if (summary && events) summary.textContent = `${occ.length} ocorr\xEAncia${occ.length !== 1 ? "s" : ""}`;
54620
+ const reopenEl = overlay.querySelector("#admReopenRows");
54621
+ const reopens = occ.filter((e) => e.kind === "reopened");
54622
+ if (reopenEl) {
54623
+ reopenEl.innerHTML = reopens.length ? row("\xDAltima reabertura", fmt(reopens[reopens.length - 1].ts)) + (reopens.length > 1 ? row("Reaberturas", String(reopens.length)) : "") : "";
54624
+ }
54625
+ onEventsReady.forEach((fn) => {
54626
+ try {
54627
+ fn();
54628
+ } catch (_) {
54629
+ }
54630
+ });
54631
+ };
54632
+ loadAlarmEvents(alarm).then((ev) => {
54633
+ events = ev;
54634
+ }).catch(() => {
54635
+ events = [];
54636
+ }).then(() => {
54637
+ if (overlay.isConnected) applyEvents();
54638
+ });
54113
54639
  const drawer = overlay.querySelector(".adm-drawer");
54114
- const maximizeBtn = overlay.querySelector("#admMaximize");
54115
- const iconExpand = maximizeBtn?.querySelector(".adm-icon-expand");
54116
- const iconCompress = maximizeBtn?.querySelector(".adm-icon-compress");
54117
- maximizeBtn?.addEventListener("click", () => {
54118
- const isMax = drawer?.classList.toggle("adm-drawer--maximized");
54119
- if (iconExpand) iconExpand.style.display = isMax ? "none" : "";
54120
- if (iconCompress) iconCompress.style.display = isMax ? "" : "none";
54121
- maximizeBtn.title = isMax ? "Restaurar (Esc)" : "Maximizar";
54640
+ const chrome = createPremiumModalChrome({
54641
+ modalId: "adm-modal",
54642
+ icon: "\u{1F514}",
54643
+ title: escHtml2(alarm.title),
54644
+ modalEl: drawer,
54645
+ theme: themeMode,
54646
+ maximizedClass: "adm-drawer--maximized",
54647
+ headerRadius: "18px 18px 0 0",
54648
+ onClose: () => closeModal2(),
54649
+ footer: alarm.customerName ? { customerName: alarm.customerName } : {}
54122
54650
  });
54123
54651
  const closeModal2 = () => {
54652
+ chrome.destroy();
54124
54653
  overlay.classList.remove("adm-overlay--visible");
54125
54654
  setTimeout(() => {
54126
54655
  overlay.remove();
@@ -54137,7 +54666,34 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
54137
54666
  if (e.key === "Escape") closeModal2();
54138
54667
  };
54139
54668
  document.addEventListener("keydown", onKey);
54140
- overlay.querySelector("#admClose")?.addEventListener("click", closeModal2);
54669
+ overlay.addEventListener("click", (e) => {
54670
+ const btn = e.target.closest(".adm-copy-btn");
54671
+ if (!btn) return;
54672
+ e.stopPropagation();
54673
+ const text2 = btn.getAttribute("data-copy") || "";
54674
+ const done = () => {
54675
+ btn.classList.add("is-copied");
54676
+ btn.title = "Copiado!";
54677
+ setTimeout(() => {
54678
+ btn.classList.remove("is-copied");
54679
+ btn.title = "Copiar nome do dispositivo";
54680
+ }, 1200);
54681
+ };
54682
+ if (navigator.clipboard?.writeText) navigator.clipboard.writeText(text2).then(done).catch(() => {
54683
+ });
54684
+ else {
54685
+ const ta = document.createElement("textarea");
54686
+ ta.value = text2;
54687
+ document.body.appendChild(ta);
54688
+ ta.select();
54689
+ try {
54690
+ document.execCommand("copy");
54691
+ done();
54692
+ } catch (_) {
54693
+ }
54694
+ ta.remove();
54695
+ }
54696
+ });
54141
54697
  overlay.addEventListener("click", (e) => {
54142
54698
  if (e.target === overlay) closeModal2();
54143
54699
  });
@@ -54159,7 +54715,7 @@ function buildOccurrenceMatrix(alarm, devices) {
54159
54715
  }
54160
54716
  const deviceChip = (dev) => `<span class="adm-device-chip">
54161
54717
  <svg viewBox="0 0 24 24" width="8" height="8" fill="currentColor"><circle cx="12" cy="12" r="10"/></svg>
54162
- ${escHtml2(dev)}
54718
+ ${escHtml2(resolveDeviceLabel(alarm, dev) || dev)}
54163
54719
  </span>`;
54164
54720
  return show.map((item) => {
54165
54721
  const devIndex = (item.n - 1) % devices.length;
@@ -56357,6 +56913,46 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56357
56913
  transform: translateY(0) scale(1);
56358
56914
  }
56359
56915
 
56916
+ /* ---- Nota do gr\xE1fico (base real de ocorr\xEAncias) ---- */
56917
+ .adm-chart-note { font-size: 11px; color: #6b7280; margin: -2px 0 8px; }
56918
+ .adm-overlay[data-theme="dark"] .adm-chart-note { color: #94a3b8; }
56919
+
56920
+ /* ---- Device identity (label + name discreto + copiar) ---- */
56921
+ .adm-dev-id {
56922
+ display: inline-flex;
56923
+ align-items: baseline;
56924
+ gap: 6px;
56925
+ min-width: 0;
56926
+ flex-wrap: wrap;
56927
+ }
56928
+ .adm-dev-label { font-weight: 600; color: inherit; font-family: 'Nunito', system-ui, sans-serif; font-size: 13px; }
56929
+ .adm-dev-name {
56930
+ font-family: ui-monospace, 'SFMono-Regular', Consolas, monospace;
56931
+ font-size: 11px;
56932
+ color: #9ca3af;
56933
+ }
56934
+ .adm-copy-btn {
56935
+ display: inline-flex;
56936
+ align-items: center;
56937
+ justify-content: center;
56938
+ width: 20px;
56939
+ height: 20px;
56940
+ padding: 0;
56941
+ border: 1px solid #e5e7eb;
56942
+ border-radius: 4px;
56943
+ background: transparent;
56944
+ color: #9ca3af;
56945
+ cursor: pointer;
56946
+ align-self: center;
56947
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
56948
+ }
56949
+ .adm-copy-btn:hover { color: var(--myio-brand-700, #7c3aed); border-color: var(--myio-brand-700, #7c3aed); }
56950
+ .adm-copy-btn.is-copied { color: #16a34a; border-color: #16a34a; background: #f0fdf4; }
56951
+ .adm-row-value--devices { display: flex; flex-direction: column; gap: 4px; }
56952
+ .adm-device-icon-sm svg { color: var(--myio-brand-700, #7c3aed); }
56953
+ .adm-overlay[data-theme="dark"] .adm-dev-name { color: #64748b; }
56954
+ .adm-overlay[data-theme="dark"] .adm-copy-btn { border-color: #2a2b3d; color: #64748b; }
56955
+
56360
56956
  /* ---- Maximized state ---- */
56361
56957
  .adm-drawer--maximized {
56362
56958
  width: 100% !important;
@@ -56368,7 +56964,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56368
56964
 
56369
56965
  /* ---- Header ---- */
56370
56966
  .adm-header {
56371
- padding: 18px 22px 14px;
56967
+ padding: 12px 22px;
56372
56968
  border-bottom: 1px solid #e5e7eb;
56373
56969
  flex-shrink: 0;
56374
56970
  }
@@ -56514,8 +57110,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56514
57110
  .adm-tab:hover { color: #111827; }
56515
57111
 
56516
57112
  .adm-tab.is-active {
56517
- color: #7c3aed;
56518
- border-bottom-color: #7c3aed;
57113
+ color: var(--myio-brand-700, #7c3aed);
57114
+ border-bottom-color: var(--myio-brand-700, #7c3aed);
56519
57115
  font-weight: 700;
56520
57116
  }
56521
57117
 
@@ -56527,8 +57123,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56527
57123
  height: 18px;
56528
57124
  padding: 0 4px;
56529
57125
  border-radius: 9px;
56530
- background: #ede9fe;
56531
- color: #7c3aed;
57126
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, #ffffff);
57127
+ color: var(--myio-brand-700, #7c3aed);
56532
57128
  font-size: 10px;
56533
57129
  font-weight: 700;
56534
57130
  }
@@ -56629,9 +57225,9 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56629
57225
  padding: 3px 8px;
56630
57226
  font-size: 10px;
56631
57227
  font-weight: 500;
56632
- background: rgba(139, 92, 246, 0.12);
56633
- color: #7c3aed;
56634
- border: 1px solid rgba(139, 92, 246, 0.3);
57228
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, transparent);
57229
+ color: var(--myio-brand-700, #7c3aed);
57230
+ border: 1px solid color-mix(in srgb, var(--myio-brand-700, #7c3aed) 30%, transparent);
56635
57231
  border-radius: 4px;
56636
57232
  }
56637
57233
 
@@ -56676,7 +57272,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56676
57272
  }
56677
57273
 
56678
57274
  .adm-tag-key {
56679
- color: #7c3aed;
57275
+ color: var(--myio-brand-700, #7c3aed);
56680
57276
  font-weight: 700;
56681
57277
  }
56682
57278
 
@@ -56724,15 +57320,15 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56724
57320
  width: 12px;
56725
57321
  height: 12px;
56726
57322
  border-radius: 50%;
56727
- background: #8b5cf6;
57323
+ background: var(--myio-brand-700, #8b5cf6);
56728
57324
  border: 2px solid #fff;
56729
- box-shadow: 0 0 0 2px #8b5cf6;
57325
+ box-shadow: 0 0 0 2px var(--myio-brand-700, #8b5cf6);
56730
57326
  flex-shrink: 0;
56731
57327
  }
56732
57328
 
56733
57329
  .adm-timeline-item.is-last .adm-timeline-dot { background: #3b82f6; box-shadow: 0 0 0 2px #3b82f6; }
56734
57330
  .adm-timeline-item.is-first .adm-timeline-dot { background: #ef4444; box-shadow: 0 0 0 2px #ef4444; }
56735
- .adm-timeline-item.is-single .adm-timeline-dot { background: #7c3aed; box-shadow: 0 0 0 2px #7c3aed; }
57331
+ .adm-timeline-item.is-single .adm-timeline-dot { background: var(--myio-brand-700, #7c3aed); box-shadow: 0 0 0 2px var(--myio-brand-700, #7c3aed); }
56736
57332
 
56737
57333
  .adm-timeline-num {
56738
57334
  font-size: 11px;
@@ -56818,7 +57414,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56818
57414
  gap: 6px;
56819
57415
  font-size: 10px;
56820
57416
  font-weight: 700;
56821
- color: #7c3aed;
57417
+ color: var(--myio-brand-700, #7c3aed);
56822
57418
  margin-bottom: 6px;
56823
57419
  padding: 3px 6px;
56824
57420
  background: rgba(139, 92, 246, 0.08);
@@ -56829,7 +57425,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56829
57425
  font-size: 9px;
56830
57426
  font-weight: 500;
56831
57427
  background: rgba(139, 92, 246, 0.2);
56832
- color: #7c3aed;
57428
+ color: var(--myio-brand-700, #7c3aed);
56833
57429
  padding: 1px 5px;
56834
57430
  border-radius: 8px;
56835
57431
  }
@@ -56867,14 +57463,14 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56867
57463
  transition: border-color 0.15s, color 0.15s, background 0.15s;
56868
57464
  }
56869
57465
  .adm-tl-batch-toggle:hover {
56870
- border-color: #7c3aed;
56871
- color: #7c3aed;
57466
+ border-color: var(--myio-brand-700, #7c3aed);
57467
+ color: var(--myio-brand-700, #7c3aed);
56872
57468
  background: rgba(124, 58, 237, 0.04);
56873
57469
  }
56874
57470
  .adm-tl-batch-toggle.is-active {
56875
- border-color: #7c3aed;
57471
+ border-color: var(--myio-brand-700, #7c3aed);
56876
57472
  background: rgba(124, 58, 237, 0.08);
56877
- color: #7c3aed;
57473
+ color: var(--myio-brand-700, #7c3aed);
56878
57474
  }
56879
57475
  .adm-tl-batch-bar {
56880
57476
  display: none;
@@ -56894,7 +57490,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56894
57490
  }
56895
57491
  .adm-tl-sel-count {
56896
57492
  font-size: 11px;
56897
- color: #7c3aed;
57493
+ color: var(--myio-brand-700, #7c3aed);
56898
57494
  font-weight: 600;
56899
57495
  min-width: 86px;
56900
57496
  }
@@ -56909,7 +57505,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56909
57505
  }
56910
57506
  .adm-tl-exec-btn {
56911
57507
  padding: 4px 12px;
56912
- background: #7c3aed;
57508
+ background: var(--myio-brand-700, #7c3aed);
56913
57509
  color: #fff;
56914
57510
  border: none;
56915
57511
  border-radius: 6px;
@@ -56919,7 +57515,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56919
57515
  transition: background 0.15s;
56920
57516
  }
56921
57517
  .adm-tl-exec-btn:disabled { background: #d1d5db; cursor: not-allowed; }
56922
- .adm-tl-exec-btn:not(:disabled):hover { background: #6d28d9; }
57518
+ .adm-tl-exec-btn:not(:disabled):hover { background: var(--myio-brand-600, #6d28d9); }
56923
57519
  .adm-tl-cancel-btn {
56924
57520
  padding: 4px 10px;
56925
57521
  background: transparent;
@@ -57005,7 +57601,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57005
57601
  justify-content: center;
57006
57602
  width: 22px;
57007
57603
  height: 22px;
57008
- background: #ede9fe;
57604
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, #ffffff);
57009
57605
  border-radius: 5px;
57010
57606
  flex-shrink: 0;
57011
57607
  }
@@ -57078,7 +57674,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57078
57674
 
57079
57675
  .adm-matrix-n {
57080
57676
  font-weight: 700;
57081
- color: #7c3aed;
57677
+ color: var(--myio-brand-700, #7c3aed);
57082
57678
  }
57083
57679
 
57084
57680
  .adm-matrix-ts {
@@ -57106,8 +57702,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57106
57702
  align-items: center;
57107
57703
  gap: 4px;
57108
57704
  padding: 2px 6px;
57109
- background: #ede9fe;
57110
- color: #6d28d9;
57705
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, #ffffff);
57706
+ color: var(--myio-brand-600, #6d28d9);
57111
57707
  border-radius: 4px;
57112
57708
  font-size: 10px;
57113
57709
  font-family: 'SF Mono', Monaco, monospace;
@@ -57159,8 +57755,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57159
57755
  }
57160
57756
 
57161
57757
  .adm-report-date-input:focus {
57162
- border-color: #7c3aed;
57163
- box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.12);
57758
+ border-color: var(--myio-brand-700, #7c3aed);
57759
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, transparent);
57164
57760
  }
57165
57761
 
57166
57762
  .adm-report-emit-btn {
@@ -57168,7 +57764,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57168
57764
  align-items: center;
57169
57765
  gap: 6px;
57170
57766
  padding: 8px 16px;
57171
- background: #7c3aed;
57767
+ background: var(--myio-brand-700, #7c3aed);
57172
57768
  color: #fff;
57173
57769
  border: none;
57174
57770
  border-radius: 8px;
@@ -57179,8 +57775,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57179
57775
  align-self: flex-start;
57180
57776
  }
57181
57777
 
57182
- .adm-report-emit-btn:hover { background: #6d28d9; }
57183
- .adm-report-emit-btn:active { background: #5b21b6; }
57778
+ .adm-report-emit-btn:hover { background: var(--myio-brand-600, #6d28d9); }
57779
+ .adm-report-emit-btn:active { background: var(--myio-brand-600, #6d28d9); }
57184
57780
 
57185
57781
  .adm-report-empty-hint {
57186
57782
  font-size: 13px;
@@ -57190,7 +57786,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57190
57786
  font-style: italic;
57191
57787
  }
57192
57788
 
57193
- .adm-report-empty-hint strong { color: #7c3aed; font-style: normal; }
57789
+ .adm-report-empty-hint strong { color: var(--myio-brand-700, #7c3aed); font-style: normal; }
57194
57790
 
57195
57791
  .adm-rpt-table-wrap {
57196
57792
  overflow-x: auto;
@@ -57229,7 +57825,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57229
57825
  font-size: 11px;
57230
57826
  }
57231
57827
 
57232
- .adm-rpt-cell--num { text-align: right; font-weight: 700; color: #7c3aed; }
57828
+ .adm-rpt-cell--num { text-align: right; font-weight: 700; color: var(--myio-brand-700, #7c3aed); }
57233
57829
 
57234
57830
  .adm-rpt-cell--dev {
57235
57831
  font-family: inherit;
@@ -57244,7 +57840,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57244
57840
  .adm-rpt-cell--total {
57245
57841
  font-weight: 700;
57246
57842
  color: #111827;
57247
- background: #ede9fe;
57843
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, #ffffff);
57248
57844
  }
57249
57845
 
57250
57846
  .adm-rpt-table tbody tr:nth-child(even) td { background: #fafafa; }
@@ -57256,7 +57852,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57256
57852
  font-weight: 700;
57257
57853
  text-transform: uppercase;
57258
57854
  letter-spacing: 0.5px;
57259
- color: #7c3aed;
57855
+ color: var(--myio-brand-700, #7c3aed);
57260
57856
  margin: 12px 0 6px;
57261
57857
  }
57262
57858
  .adm-rpt-section-title:first-child { margin-top: 0; }
@@ -57289,7 +57885,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57289
57885
  .adm-rpt-device-sep { color: #9ca3af; font-size: 11px; }
57290
57886
  .adm-rpt-alarm-type {
57291
57887
  font-size: 11px;
57292
- color: #7c3aed;
57888
+ color: var(--myio-brand-700, #7c3aed);
57293
57889
  font-weight: 600;
57294
57890
  }
57295
57891
 
@@ -57370,7 +57966,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57370
57966
  }
57371
57967
 
57372
57968
  .adm-chart-btn.is-active {
57373
- background: #7c3aed;
57969
+ background: var(--myio-brand-700, #7c3aed);
57374
57970
  color: #fff;
57375
57971
  }
57376
57972
 
@@ -57470,7 +58066,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57470
58066
 
57471
58067
  .adm-dow-bar {
57472
58068
  height: 100%;
57473
- background: #7c3aed;
58069
+ background: var(--myio-brand-700, #7c3aed);
57474
58070
  border-radius: 3px;
57475
58071
  }
57476
58072
 
@@ -57502,7 +58098,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57502
58098
 
57503
58099
  .adm-hod-bar {
57504
58100
  width: 100%;
57505
- background: #7c3aed;
58101
+ background: var(--myio-brand-700, #7c3aed);
57506
58102
  border-radius: 2px 2px 0 0;
57507
58103
  min-height: 4px;
57508
58104
  }
@@ -57533,7 +58129,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57533
58129
  align-items: center;
57534
58130
  gap: 5px;
57535
58131
  padding: 7px 14px;
57536
- background: #7c3aed;
58132
+ background: var(--myio-brand-700, #7c3aed);
57537
58133
  color: #fff;
57538
58134
  border: none;
57539
58135
  border-radius: 8px;
@@ -57542,8 +58138,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57542
58138
  cursor: pointer;
57543
58139
  transition: background 0.15s;
57544
58140
  }
57545
- .adm-annot-create-btn:hover { background: #6d28d9; }
57546
- .adm-annot-create-btn:active { background: #5b21b6; }
58141
+ .adm-annot-create-btn:hover { background: var(--myio-brand-600, #6d28d9); }
58142
+ .adm-annot-create-btn:active { background: var(--myio-brand-600, #6d28d9); }
57547
58143
 
57548
58144
  .adm-annot-form-wrap { margin-bottom: 14px; }
57549
58145
 
@@ -57576,8 +58172,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57576
58172
  transition: border-color 0.15s;
57577
58173
  }
57578
58174
  .adm-annot-textarea:focus {
57579
- border-color: #7c3aed;
57580
- box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.12);
58175
+ border-color: var(--myio-brand-700, #7c3aed);
58176
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, transparent);
57581
58177
  }
57582
58178
 
57583
58179
  .adm-annot-char-count {
@@ -57664,7 +58260,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57664
58260
  cursor: pointer;
57665
58261
  transition: border-color 0.15s;
57666
58262
  }
57667
- .adm-annot-date-input:focus { border-color: #7c3aed; }
58263
+ .adm-annot-date-input:focus { border-color: var(--myio-brand-700, #7c3aed); }
57668
58264
 
57669
58265
  /* Form action buttons */
57670
58266
  .adm-annot-form-actions {
@@ -57685,8 +58281,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57685
58281
  }
57686
58282
  .adm-annot-form-btn--cancel { background: #e5e7eb; color: #374151; }
57687
58283
  .adm-annot-form-btn--cancel:hover { background: #d1d5db; }
57688
- .adm-annot-form-btn--save { background: #7c3aed; color: #fff; }
57689
- .adm-annot-form-btn--save:hover { background: #6d28d9; }
58284
+ .adm-annot-form-btn--save { background: var(--myio-brand-700, #7c3aed); color: #fff; }
58285
+ .adm-annot-form-btn--save:hover { background: var(--myio-brand-600, #6d28d9); }
57690
58286
  .adm-annot-form-btn--save:disabled { opacity: 0.4; cursor: not-allowed; }
57691
58287
 
57692
58288
  /* Empty state */
@@ -57786,8 +58382,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57786
58382
  width: 20px;
57787
58383
  height: 20px;
57788
58384
  border-radius: 50%;
57789
- background: #ede9fe;
57790
- color: #7c3aed;
58385
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, #ffffff);
58386
+ color: var(--myio-brand-700, #7c3aed);
57791
58387
  font-size: 8px;
57792
58388
  font-weight: 700;
57793
58389
  flex-shrink: 0;
@@ -57890,13 +58486,13 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57890
58486
  }
57891
58487
 
57892
58488
  .adm-overlay[data-theme="dark"] .adm-tab.is-active {
57893
- color: #a78bfa;
57894
- border-bottom-color: #a78bfa;
58489
+ color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
58490
+ border-bottom-color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
57895
58491
  }
57896
58492
 
57897
58493
  .adm-overlay[data-theme="dark"] .adm-tab-badge {
57898
- background: #2d1b69;
57899
- color: #a78bfa;
58494
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 35%, #1e1f2e);
58495
+ color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
57900
58496
  }
57901
58497
 
57902
58498
  .adm-overlay[data-theme="dark"] .adm-body {
@@ -57968,13 +58564,13 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57968
58564
  }
57969
58565
 
57970
58566
  .adm-overlay[data-theme="dark"] .adm-timeline-group-header {
57971
- color: #a78bfa;
57972
- background: rgba(139, 92, 246, 0.12);
58567
+ color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
58568
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, transparent);
57973
58569
  }
57974
58570
 
57975
58571
  .adm-overlay[data-theme="dark"] .adm-timeline-group-count {
57976
- background: rgba(139, 92, 246, 0.25);
57977
- color: #a78bfa;
58572
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 25%, transparent);
58573
+ color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
57978
58574
  }
57979
58575
 
57980
58576
  .adm-overlay[data-theme="dark"] .adm-tl-header { border-color: #334155; }
@@ -57982,10 +58578,10 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57982
58578
  .adm-overlay[data-theme="dark"] .adm-tl-summary { color: #94a3b8; }
57983
58579
  .adm-overlay[data-theme="dark"] .adm-tl-batch-toggle { border-color: #475569; background: #1e293b; color: #e2e8f0; }
57984
58580
  .adm-overlay[data-theme="dark"] .adm-tl-batch-toggle:hover,
57985
- .adm-overlay[data-theme="dark"] .adm-tl-batch-toggle.is-active { border-color: #a78bfa; color: #a78bfa; }
58581
+ .adm-overlay[data-theme="dark"] .adm-tl-batch-toggle.is-active { border-color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff); color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff); }
57986
58582
  .adm-overlay[data-theme="dark"] .adm-tl-batch-bar { border-color: #334155; }
57987
58583
  .adm-overlay[data-theme="dark"] .adm-tl-sel-all-label { color: #e2e8f0; }
57988
- .adm-overlay[data-theme="dark"] .adm-tl-sel-count { color: #a78bfa; }
58584
+ .adm-overlay[data-theme="dark"] .adm-tl-sel-count { color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff); }
57989
58585
  .adm-overlay[data-theme="dark"] .adm-tl-action-sel { background: #1e293b; border-color: #475569; color: #e2e8f0; }
57990
58586
  .adm-overlay[data-theme="dark"] .adm-tl-cancel-btn { border-color: #475569; color: #94a3b8; }
57991
58587
  .adm-overlay[data-theme="dark"] .adm-tl-cancel-btn:hover { color: #e2e8f0; }
@@ -58016,9 +58612,9 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
58016
58612
  }
58017
58613
 
58018
58614
  .adm-overlay[data-theme="dark"] .adm-chart-btn.is-active {
58019
- background: #2d1b69;
58020
- border-color: #6d28d9;
58021
- color: #a78bfa;
58615
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 35%, #1e1f2e);
58616
+ border-color: var(--myio-brand-600, #6d28d9);
58617
+ color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
58022
58618
  }
58023
58619
 
58024
58620
  .adm-overlay[data-theme="dark"] .adm-chart-btn:hover:not(.is-active) {
@@ -58082,13 +58678,13 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
58082
58678
  background: #252636;
58083
58679
  }
58084
58680
 
58085
- .adm-overlay[data-theme="dark"] .adm-rpt-section-title { color: #a78bfa; }
58681
+ .adm-overlay[data-theme="dark"] .adm-rpt-section-title { color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff); }
58086
58682
  .adm-overlay[data-theme="dark"] .adm-rpt-device-header {
58087
58683
  background: rgba(139, 92, 246, 0.1);
58088
- border-color: rgba(139, 92, 246, 0.25);
58684
+ border-color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 25%, transparent);
58089
58685
  }
58090
58686
  .adm-overlay[data-theme="dark"] .adm-rpt-device-name { color: #e2e8f0; }
58091
- .adm-overlay[data-theme="dark"] .adm-rpt-alarm-type { color: #a78bfa; }
58687
+ .adm-overlay[data-theme="dark"] .adm-rpt-alarm-type { color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff); }
58092
58688
  .adm-overlay[data-theme="dark"] .adm-rpt-cell--total { background: rgba(139, 92, 246, 0.15); color: #e2e8f0; }
58093
58689
 
58094
58690
  .adm-overlay[data-theme="dark"] .adm-annot-card {
@@ -71146,6 +71742,25 @@ var HeaderView = class {
71146
71742
  transition: color 0.2s ease;
71147
71743
  }
71148
71744
 
71745
+ /* Consumo ainda carregando */
71746
+ .${HEADER_CSS_PREFIX}-kpi-loading {
71747
+ display: inline-flex;
71748
+ align-items: center;
71749
+ gap: 10px;
71750
+ font-size: 1rem;
71751
+ font-weight: 600;
71752
+ opacity: 0.85;
71753
+ }
71754
+ .${HEADER_CSS_PREFIX}-kpi-spinner {
71755
+ width: 18px;
71756
+ height: 18px;
71757
+ border: 2.5px solid rgba(255, 255, 255, 0.3);
71758
+ border-top-color: currentColor;
71759
+ border-radius: 50%;
71760
+ animation: myio-consumption-spin 0.8s linear infinite;
71761
+ }
71762
+ @keyframes myio-consumption-spin { to { transform: rotate(360deg); } }
71763
+
71149
71764
  .${HEADER_CSS_PREFIX}-card__subrow {
71150
71765
  display: flex;
71151
71766
  align-items: center;
@@ -71584,7 +72199,28 @@ var HeaderView = class {
71584
72199
  /**
71585
72200
  * Update energy card
71586
72201
  */
72202
+ /** Spinner nos KPIs de consumo (energia e água) enquanto o período carrega */
72203
+ setConsumptionLoading(loading) {
72204
+ if (!loading) {
72205
+ [this.energyKpiEl, this.waterKpiEl].forEach((el2) => {
72206
+ if (el2?.querySelector(`.${HEADER_CSS_PREFIX}-kpi-loading`)) el2.textContent = "-";
72207
+ });
72208
+ return;
72209
+ }
72210
+ const html = `<span class="${HEADER_CSS_PREFIX}-kpi-loading" aria-busy="true"><span class="${HEADER_CSS_PREFIX}-kpi-spinner" aria-hidden="true"></span>Carregando\u2026</span>`;
72211
+ if (this.energyKpiEl) this.energyKpiEl.innerHTML = html;
72212
+ if (this.waterKpiEl) this.waterKpiEl.innerHTML = html;
72213
+ if (this.energyTrendEl) this.energyTrendEl.textContent = "";
72214
+ if (this.waterTrendEl) this.waterTrendEl.textContent = "";
72215
+ }
71587
72216
  updateEnergyCard(data) {
72217
+ if (data.loading) {
72218
+ if (this.energyKpiEl) {
72219
+ this.energyKpiEl.innerHTML = `<span class="${HEADER_CSS_PREFIX}-kpi-loading" aria-busy="true"><span class="${HEADER_CSS_PREFIX}-kpi-spinner" aria-hidden="true"></span>Carregando\u2026</span>`;
72220
+ }
72221
+ if (this.energyTrendEl) this.energyTrendEl.textContent = "";
72222
+ return;
72223
+ }
71588
72224
  if (this.energyKpiEl) {
71589
72225
  if (data.isFiltered && data.unfilteredTotal > 0) {
71590
72226
  const filteredFormatted = this.formatEnergy(data.customerTotal);
@@ -71633,6 +72269,13 @@ var HeaderView = class {
71633
72269
  * Update water card
71634
72270
  */
71635
72271
  updateWaterCard(data) {
72272
+ if (data.loading) {
72273
+ if (this.waterKpiEl) {
72274
+ this.waterKpiEl.innerHTML = `<span class="${HEADER_CSS_PREFIX}-kpi-loading" aria-busy="true"><span class="${HEADER_CSS_PREFIX}-kpi-spinner" aria-hidden="true"></span>Carregando\u2026</span>`;
72275
+ }
72276
+ if (this.waterTrendEl) this.waterTrendEl.textContent = "";
72277
+ return;
72278
+ }
71636
72279
  if (this.waterKpiEl) {
71637
72280
  if (data.isFiltered && data.unfilteredTotal > 0) {
71638
72281
  const filteredFormatted = this.formatWater(data.filteredTotal);
@@ -72618,6 +73261,9 @@ function createHeaderComponent(params) {
72618
73261
  tooltipData.temperature = data;
72619
73262
  view.updateTemperatureCard(data);
72620
73263
  }
73264
+ function setConsumptionLoading(loading) {
73265
+ view.setConsumptionLoading(loading);
73266
+ }
72621
73267
  function updateWaterKPI(data) {
72622
73268
  tooltipData.water = data;
72623
73269
  view.updateWaterCard(data);
@@ -72673,6 +73319,7 @@ function createHeaderComponent(params) {
72673
73319
  updateEnergyKPI,
72674
73320
  updateTemperatureKPI,
72675
73321
  updateWaterKPI,
73322
+ setConsumptionLoading,
72676
73323
  updateTooltipData,
72677
73324
  // Shopping/Filter
72678
73325
  updateShoppings,
@@ -123159,6 +123806,20 @@ var HEADER_DEVICES_GRID_STYLES = `
123159
123806
  body.light-mode .telemetry-grid-wrap.maximized .telemetry-grid-header {
123160
123807
  background: #f8fafc;
123161
123808
  }
123809
+
123810
+ /* Consumo ainda carregando */
123811
+ .hdg-value-spinner {
123812
+ display: inline-block;
123813
+ width: 13px;
123814
+ height: 13px;
123815
+ margin-right: 6px;
123816
+ vertical-align: -2px;
123817
+ border: 2px solid rgba(124, 58, 237, 0.2);
123818
+ border-top-color: var(--myio-brand-700, #7c3aed);
123819
+ border-radius: 50%;
123820
+ animation: myio-consumption-spin 0.8s linear infinite;
123821
+ }
123822
+ @keyframes myio-consumption-spin { to { transform: rotate(360deg); } }
123162
123823
  `;
123163
123824
  function injectHeaderDevicesGridStyles() {
123164
123825
  const styleId = "hdg-styles";
@@ -123176,14 +123837,15 @@ var DOMAIN_CONFIG7 = {
123176
123837
  consumptionLabel: "Consumo Total",
123177
123838
  zeroLabel: "Sem Consumo",
123178
123839
  unit: "kWh",
123179
- formatValue: (value) => formatEnergy(value, void 0, 2)
123840
+ // 3 casas: mesmo padrão do dashboard de shopping (RFC-0108, measurementSettings default)
123841
+ formatValue: (value) => formatEnergy(value, void 0, 3)
123180
123842
  },
123181
123843
  water: {
123182
123844
  totalLabel: "Total Hidr\xF4metros",
123183
123845
  consumptionLabel: "Consumo Total",
123184
123846
  zeroLabel: "Sem Consumo",
123185
123847
  unit: "m\xB3",
123186
- formatValue: (value) => formatWater(value)
123848
+ formatValue: (value) => `${(Number(value) || 0).toLocaleString("pt-BR", { minimumFractionDigits: 3, maximumFractionDigits: 3 })} m\xB3`
123187
123849
  },
123188
123850
  temperature: {
123189
123851
  totalLabel: "Total Sensores",
@@ -123351,6 +124013,16 @@ var HeaderDevicesGridView = class {
123351
124013
  const percentage = total > 0 ? (online / total * 100).toFixed(1) : "0.0";
123352
124014
  connectivityEl.textContent = `${online}/${total} (${percentage}%)`;
123353
124015
  totalEl.textContent = total.toString();
124016
+ if (stats.loading) {
124017
+ const spin = '<span class="hdg-value-spinner" aria-hidden="true"></span>';
124018
+ consumptionEl.innerHTML = `${spin}Carregando\u2026`;
124019
+ zeroEl.innerHTML = spin;
124020
+ consumptionEl.setAttribute("aria-busy", "true");
124021
+ zeroEl.setAttribute("aria-busy", "true");
124022
+ return;
124023
+ }
124024
+ consumptionEl.removeAttribute("aria-busy");
124025
+ zeroEl.removeAttribute("aria-busy");
123354
124026
  consumptionEl.textContent = this.domainConfig.formatValue(consumption);
123355
124027
  zeroEl.textContent = zeroCount.toString();
123356
124028
  }
@@ -123457,7 +124129,8 @@ var HeaderDevicesGridController = class {
123457
124129
  online,
123458
124130
  total: devices.length,
123459
124131
  consumption: displayValue,
123460
- zeroCount
124132
+ zeroCount,
124133
+ loading: this.domain !== "temperature" && devices.some((d) => d.dataLoading)
123461
124134
  });
123462
124135
  }
123463
124136
  setThemeMode(mode) {
@@ -142202,425 +142875,6 @@ function createDeviceOperationalCardGridComponent(params) {
142202
142875
  };
142203
142876
  }
142204
142877
 
142205
- // src/services/alarm/AlarmApiClient.ts
142206
- var AlarmApiClient = class {
142207
- baseUrl = "";
142208
- apiKey = "";
142209
- configure(baseUrl, apiKey) {
142210
- if (baseUrl) this.baseUrl = baseUrl;
142211
- if (apiKey) this.apiKey = apiKey;
142212
- }
142213
- get headers() {
142214
- return {
142215
- "Content-Type": "application/json",
142216
- "X-API-Key": this.apiKey
142217
- };
142218
- }
142219
- async request(url, options) {
142220
- const response = await fetch(url, {
142221
- method: "GET",
142222
- headers: this.headers,
142223
- ...options
142224
- });
142225
- if (!response.ok) {
142226
- throw new Error(`Alarms API error: ${response.status} ${response.statusText}`);
142227
- }
142228
- return response.json();
142229
- }
142230
- // Some endpoints return { success: true, data: T[] } — unwrap safely
142231
- unwrapArray(raw) {
142232
- if (Array.isArray(raw)) return raw;
142233
- if (raw !== null && typeof raw === "object" && "data" in raw) {
142234
- const d = raw.data;
142235
- return Array.isArray(d) ? d : [];
142236
- }
142237
- return [];
142238
- }
142239
- // -----------------------------------------------------------------------
142240
- // Availability (existing endpoint)
142241
- // -----------------------------------------------------------------------
142242
- async getAvailability(params) {
142243
- const query = new URLSearchParams({
142244
- customerId: params.customerId,
142245
- startAt: params.startAt,
142246
- endAt: params.endAt,
142247
- includeByDevice: String(params.includeByDevice ?? true)
142248
- });
142249
- if (params.deviceType) query.set("deviceType", params.deviceType);
142250
- if (params.deviceIds) query.set("deviceIds", params.deviceIds);
142251
- const raw = await this.request(
142252
- `${this.baseUrl}/alarms/stats/availability?${query.toString()}`
142253
- );
142254
- return raw?.data ?? raw;
142255
- }
142256
- // -----------------------------------------------------------------------
142257
- // Alarm list (GET /alarms)
142258
- // -----------------------------------------------------------------------
142259
- async getAlarms(params = {}) {
142260
- const query = new URLSearchParams();
142261
- if (params.state?.length) query.set("state", params.state.join(","));
142262
- if (params.severity?.length) query.set("severity", params.severity.join(","));
142263
- if (params.alarmType) query.set("alarmType", params.alarmType);
142264
- if (params.from) query.set("from", params.from);
142265
- if (params.to) query.set("to", params.to);
142266
- if (params.customerId) query.set("customerId", params.customerId);
142267
- query.set("limit", String(params.limit ?? 100));
142268
- if (params.page && params.page > 1) query.set("page", String(params.page));
142269
- const qs = query.toString();
142270
- return this.request(
142271
- `${this.baseUrl}/alarms${qs ? `?${qs}` : ""}`
142272
- );
142273
- }
142274
- // -----------------------------------------------------------------------
142275
- // Stats (GET /api/v1/alarms/stats)
142276
- // -----------------------------------------------------------------------
142277
- async getAlarmStats(tenantId, period) {
142278
- const query = new URLSearchParams({ tenantId, period });
142279
- const raw = await this.request(
142280
- `${this.baseUrl}/alarms/stats?${query.toString()}`
142281
- );
142282
- return raw?.data ?? raw;
142283
- }
142284
- // -----------------------------------------------------------------------
142285
- // Trend (GET /api/v1/alarms/stats/trend)
142286
- // -----------------------------------------------------------------------
142287
- async getAlarmTrend(tenantId, period, groupBy) {
142288
- const query = new URLSearchParams({ tenantId, period, groupBy });
142289
- const raw = await this.request(
142290
- `${this.baseUrl}/alarms/stats/trend?${query.toString()}`
142291
- );
142292
- return this.unwrapArray(raw);
142293
- }
142294
- // -----------------------------------------------------------------------
142295
- // Top offenders (GET /api/v1/alarms/stats/top-offenders)
142296
- // -----------------------------------------------------------------------
142297
- async getTopOffenders(tenantId, limit = 5) {
142298
- const query = new URLSearchParams({ tenantId, limit: String(limit) });
142299
- const raw = await this.request(
142300
- `${this.baseUrl}/alarms/stats/top-offenders?${query.toString()}`
142301
- );
142302
- return this.unwrapArray(raw);
142303
- }
142304
- // -----------------------------------------------------------------------
142305
- // Per-device stats (GET /api/v1/alarms/stats/by-device)
142306
- // -----------------------------------------------------------------------
142307
- async getAlarmStatsByDevice(tenantId) {
142308
- const query = new URLSearchParams({ tenantId });
142309
- const raw = await this.request(
142310
- `${this.baseUrl}/alarms/stats/by-device?${query.toString()}`
142311
- );
142312
- return this.unwrapArray(raw);
142313
- }
142314
- // -----------------------------------------------------------------------
142315
- // Alarm actions (POST /alarms/:id/...)
142316
- // -----------------------------------------------------------------------
142317
- async acknowledgeAlarm(id, acknowledgedBy, note) {
142318
- await this.request(`${this.baseUrl}/alarms/${id}/ack`, {
142319
- method: "POST",
142320
- body: JSON.stringify({ acknowledgedBy, note })
142321
- });
142322
- }
142323
- async silenceAlarm(id, silencedBy, duration, reason) {
142324
- await this.request(`${this.baseUrl}/alarms/${id}/silence`, {
142325
- method: "POST",
142326
- body: JSON.stringify({ silencedBy, duration, reason })
142327
- });
142328
- }
142329
- async escalateAlarm(id, escalatedBy, reason) {
142330
- await this.request(`${this.baseUrl}/alarms/${id}/escalate`, {
142331
- method: "POST",
142332
- body: JSON.stringify({ escalatedBy, reason })
142333
- });
142334
- }
142335
- async closeAlarm(id, closedBy, resolution) {
142336
- await this.request(`${this.baseUrl}/alarms/${id}/close`, {
142337
- method: "POST",
142338
- body: JSON.stringify({ closedBy, resolution })
142339
- });
142340
- }
142341
- // -----------------------------------------------------------------------
142342
- // Batch alarm actions (POST /alarms/batch/*)
142343
- // Up to 100 IDs per call; response contains succeeded/failed arrays.
142344
- // -----------------------------------------------------------------------
142345
- async batchAcknowledge(alarmIds, acknowledgedBy) {
142346
- const res = await this.request(
142347
- `${this.baseUrl}/alarms/batch/ack`,
142348
- { method: "POST", body: JSON.stringify({ alarmIds, acknowledgedBy }) }
142349
- );
142350
- return res.data;
142351
- }
142352
- async batchSilence(alarmIds, silencedBy, duration) {
142353
- const res = await this.request(
142354
- `${this.baseUrl}/alarms/batch/silence`,
142355
- { method: "POST", body: JSON.stringify({ alarmIds, silencedBy, duration }) }
142356
- );
142357
- return res.data;
142358
- }
142359
- async batchEscalate(alarmIds, escalatedBy) {
142360
- const res = await this.request(
142361
- `${this.baseUrl}/alarms/batch/escalate`,
142362
- { method: "POST", body: JSON.stringify({ alarmIds, escalatedBy }) }
142363
- );
142364
- return res.data;
142365
- }
142366
- async batchClose(alarmIds, closedBy, resolution) {
142367
- const res = await this.request(
142368
- `${this.baseUrl}/alarms/batch/close`,
142369
- { method: "POST", body: JSON.stringify({ alarmIds, closedBy, resolution }) }
142370
- );
142371
- return res.data;
142372
- }
142373
- };
142374
-
142375
- // src/services/alarm/AlarmService.ts
142376
- function isFresh(entry, ttlMs) {
142377
- return !!entry && Date.now() - entry.timestamp < ttlMs;
142378
- }
142379
- function mapApiAlarm(api, customerMap) {
142380
- return {
142381
- id: api.id,
142382
- customerId: api.customerId ?? api.tenantId,
142383
- customerName: customerMap?.get(api.customerId ?? "") ?? "",
142384
- source: api.deviceName || api.deviceId,
142385
- severity: api.severity,
142386
- state: api.state,
142387
- title: api.title,
142388
- description: api.description ?? "",
142389
- tags: {
142390
- alarmType: api.alarmType,
142391
- deviceType: api.deviceType
142392
- },
142393
- firstOccurrence: api.raisedAt || api.raised_at || api.createdAt || void 0,
142394
- lastOccurrence: api.lastUpdatedAt || api.updatedAt || api.updated_at || void 0,
142395
- occurrenceCount: api.occurrenceCount ?? api.dispatchCount ?? 1,
142396
- acknowledgedAt: api.acknowledgedAt || void 0,
142397
- acknowledgedBy: api.acknowledgedBy,
142398
- snoozedUntil: api.silencedUntil || api.snoozedUntil || void 0,
142399
- closedAt: api.closedAt || void 0,
142400
- closedBy: api.closedBy,
142401
- closedReason: api.resolution,
142402
- triggerValue: api.metadata?.value,
142403
- centralId: api.centralId || void 0
142404
- };
142405
- }
142406
- function mapApiStats(api) {
142407
- return {
142408
- total: api.total,
142409
- bySeverity: {
142410
- CRITICAL: api.bySeverity?.CRITICAL ?? 0,
142411
- HIGH: api.bySeverity?.HIGH ?? 0,
142412
- MEDIUM: api.bySeverity?.MEDIUM ?? 0,
142413
- LOW: api.bySeverity?.LOW ?? 0,
142414
- INFO: api.bySeverity?.INFO ?? 0
142415
- },
142416
- byState: {
142417
- OPEN: api.byState?.OPEN ?? 0,
142418
- ACK: api.byState?.ACK ?? 0,
142419
- SNOOZED: api.byState?.SNOOZED ?? 0,
142420
- ESCALATED: api.byState?.ESCALATED ?? 0,
142421
- CLOSED: api.byState?.CLOSED ?? 0
142422
- },
142423
- openCritical: api.openCritical,
142424
- openHigh: api.openHigh,
142425
- last24Hours: api.last24Hours
142426
- };
142427
- }
142428
- function mapApiTrend(apiTrend) {
142429
- return (apiTrend || []).map((point) => ({
142430
- label: point.label || point.period,
142431
- timestamp: point.timestamp || new Date(point.period).getTime() || 0,
142432
- total: point.total ?? point.count,
142433
- bySeverity: point.bySeverity ? {
142434
- CRITICAL: point.bySeverity.CRITICAL ?? 0,
142435
- HIGH: point.bySeverity.HIGH ?? 0,
142436
- MEDIUM: point.bySeverity.MEDIUM ?? 0,
142437
- LOW: point.bySeverity.LOW ?? 0,
142438
- INFO: point.bySeverity.INFO ?? 0
142439
- } : void 0
142440
- }));
142441
- }
142442
- function mapApiTopOffenders(items, deviceNameMap) {
142443
- return (items || []).map((item) => ({
142444
- name: deviceNameMap.get(item.deviceId) ?? item.deviceName ?? item.deviceId,
142445
- location: item.customerName ?? "",
142446
- downtime: item.downtimeHours ?? (item.totalDowntimeMinutes ?? 0) / 60,
142447
- percentage: 0
142448
- // computed by caller if needed
142449
- }));
142450
- }
142451
- var AlarmServiceClass = class {
142452
- client = new AlarmApiClient();
142453
- cacheTtlMs = 3 * 60 * 1e3;
142454
- // 3 min default; overridable via configure()
142455
- availabilityCache = /* @__PURE__ */ new Map();
142456
- alarmsCache = /* @__PURE__ */ new Map();
142457
- statsCache = /* @__PURE__ */ new Map();
142458
- trendCache = /* @__PURE__ */ new Map();
142459
- // -----------------------------------------------------------------------
142460
- // Availability (MTBF / MTTR per device)
142461
- // -----------------------------------------------------------------------
142462
- /**
142463
- * Fetches MTBF, MTTR, and Availability metrics per device and fleet-wide.
142464
- * Results are cached for 60 seconds.
142465
- */
142466
- async getAvailability(customerId, startAt, endAt) {
142467
- if (!customerId) {
142468
- throw new Error("AlarmService.getAvailability: customerId is required");
142469
- }
142470
- const cacheKey = `avail:${customerId}:${startAt}:${endAt}`;
142471
- const cached = this.availabilityCache.get(cacheKey);
142472
- if (isFresh(cached, this.cacheTtlMs)) return cached.data;
142473
- const data = await this.client.getAvailability({
142474
- customerId,
142475
- startAt,
142476
- endAt,
142477
- includeByDevice: true
142478
- });
142479
- this.availabilityCache.set(cacheKey, { data, timestamp: Date.now() });
142480
- return data;
142481
- }
142482
- // -----------------------------------------------------------------------
142483
- // Alarm list
142484
- // -----------------------------------------------------------------------
142485
- /**
142486
- * Fetch alarm list with optional filters, auto-paginating until hasMore is false.
142487
- * Returns { data: Alarm[], summary: AlarmListSummary } with all pages merged.
142488
- * The summary comes from page 1 (reflects full dataset totals).
142489
- */
142490
- async getAlarms(params = {}, customerMap) {
142491
- const cacheKey = `alarms:${JSON.stringify(params)}`;
142492
- const cached = this.alarmsCache.get(cacheKey);
142493
- if (isFresh(cached, this.cacheTtlMs)) return cached.data;
142494
- let allData = [];
142495
- let summary;
142496
- let totalPages = 1;
142497
- let page = 1;
142498
- while (page <= totalPages) {
142499
- const response = await this.client.getAlarms({ ...params, page });
142500
- const pageData = (response.data || []).map((a) => mapApiAlarm(a, customerMap));
142501
- allData = allData.concat(pageData);
142502
- if (page === 1) {
142503
- summary = response.summary;
142504
- totalPages = response.pagination?.totalPages ?? 1;
142505
- }
142506
- page++;
142507
- }
142508
- const result = { data: allData, summary };
142509
- this.alarmsCache.set(cacheKey, { data: result, timestamp: Date.now() });
142510
- return result;
142511
- }
142512
- // -----------------------------------------------------------------------
142513
- // Alarm statistics
142514
- // -----------------------------------------------------------------------
142515
- /**
142516
- * Fetch aggregated alarm statistics for a tenant and time period.
142517
- * Returns mapped AlarmStats (from src/types/alarm.ts).
142518
- */
142519
- async getAlarmStats(tenantId, period) {
142520
- const cacheKey = `stats:${tenantId}:${period}`;
142521
- const cached = this.statsCache.get(cacheKey);
142522
- if (isFresh(cached, this.cacheTtlMs)) return cached.data;
142523
- const raw = await this.client.getAlarmStats(tenantId, period);
142524
- const data = mapApiStats(raw);
142525
- this.statsCache.set(cacheKey, { data, timestamp: Date.now() });
142526
- return data;
142527
- }
142528
- // -----------------------------------------------------------------------
142529
- // Alarm trend
142530
- // -----------------------------------------------------------------------
142531
- /**
142532
- * Fetch alarm trend data points.
142533
- * Returns mapped AlarmTrendDataPoint[] (from src/types/alarm.ts).
142534
- */
142535
- async getAlarmTrend(tenantId, period, groupBy) {
142536
- const cacheKey = `trend:${tenantId}:${period}:${groupBy}`;
142537
- const cached = this.trendCache.get(cacheKey);
142538
- if (isFresh(cached, this.cacheTtlMs)) return cached.data;
142539
- const raw = await this.client.getAlarmTrend(tenantId, period, groupBy);
142540
- const data = mapApiTrend(raw);
142541
- this.trendCache.set(cacheKey, { data, timestamp: Date.now() });
142542
- return data;
142543
- }
142544
- // -----------------------------------------------------------------------
142545
- // Top downtime / offenders
142546
- // -----------------------------------------------------------------------
142547
- /**
142548
- * Fetch top devices by alarm count / downtime.
142549
- * Returns mapped DowntimeEntry[] (from src/types/operational.ts).
142550
- *
142551
- * @param deviceNameMap - Optional map of deviceId → friendly name from ThingsBoard cache
142552
- */
142553
- async getTopDowntime(tenantId, deviceNameMap = /* @__PURE__ */ new Map(), limit = 5) {
142554
- const items = await this.client.getTopOffenders(tenantId, limit);
142555
- return mapApiTopOffenders(items, deviceNameMap);
142556
- }
142557
- // -----------------------------------------------------------------------
142558
- // Per-device alarm counts
142559
- // -----------------------------------------------------------------------
142560
- /**
142561
- * Fetch alarm counts per device.
142562
- * Returns a Map<deviceId, alarmCount>.
142563
- */
142564
- async getDeviceAlarmCounts(tenantId) {
142565
- const items = await this.client.getAlarmStatsByDevice(tenantId);
142566
- const map = /* @__PURE__ */ new Map();
142567
- (items || []).forEach((item) => map.set(item.deviceId, item.alarmCount));
142568
- return map;
142569
- }
142570
- // -----------------------------------------------------------------------
142571
- // Alarm actions
142572
- // -----------------------------------------------------------------------
142573
- async acknowledgeAlarm(id, userEmail, note) {
142574
- return this.client.acknowledgeAlarm(id, userEmail, note);
142575
- }
142576
- async silenceAlarm(id, userEmail, duration, reason) {
142577
- return this.client.silenceAlarm(id, userEmail, duration, reason);
142578
- }
142579
- async escalateAlarm(id, userEmail, reason) {
142580
- return this.client.escalateAlarm(id, userEmail, reason);
142581
- }
142582
- async closeAlarm(id, userEmail, resolution) {
142583
- return this.client.closeAlarm(id, userEmail, resolution);
142584
- }
142585
- // -----------------------------------------------------------------------
142586
- // Batch alarm actions (POST /alarms/batch/*)
142587
- // -----------------------------------------------------------------------
142588
- async batchAcknowledge(alarmIds, userEmail) {
142589
- return this.client.batchAcknowledge(alarmIds, userEmail);
142590
- }
142591
- async batchSilence(alarmIds, userEmail, duration) {
142592
- return this.client.batchSilence(alarmIds, userEmail, duration);
142593
- }
142594
- async batchEscalate(alarmIds, userEmail) {
142595
- return this.client.batchEscalate(alarmIds, userEmail);
142596
- }
142597
- async batchClose(alarmIds, userEmail, resolution) {
142598
- return this.client.batchClose(alarmIds, userEmail, resolution);
142599
- }
142600
- // -----------------------------------------------------------------------
142601
- // Configuration
142602
- // -----------------------------------------------------------------------
142603
- /**
142604
- * Set the Alarms API base URL (e.g. from MAIN_VIEW orchestrator settings).
142605
- * Clears the cache so subsequent calls use the new endpoint.
142606
- */
142607
- configure(baseUrl, cacheTtlMs, apiKey) {
142608
- this.client.configure(baseUrl ?? "", apiKey);
142609
- if (cacheTtlMs && cacheTtlMs > 0) this.cacheTtlMs = cacheTtlMs;
142610
- this.clearCache();
142611
- }
142612
- // -----------------------------------------------------------------------
142613
- // Cache management
142614
- // -----------------------------------------------------------------------
142615
- clearCache() {
142616
- this.availabilityCache.clear();
142617
- this.alarmsCache.clear();
142618
- this.statsCache.clear();
142619
- this.trendCache.clear();
142620
- }
142621
- };
142622
- var AlarmService = new AlarmServiceClass();
142623
-
142624
142878
  // src/components/AlarmsNotificationsPanel/AlarmsNotificationsPanelController.ts
142625
142879
  var AlarmsNotificationsPanelController = class {
142626
142880
  state;
@@ -143821,16 +144075,36 @@ var AlarmsNotificationsPanelView = class {
143821
144075
  * Render dashboard tab content.
143822
144076
  * Trend data is fetched asynchronously on first open and injected when ready.
143823
144077
  */
144078
+ /** KPIs do Dashboard calculados sobre uma lista de alarmes (a mesma exibida na Lista). */
144079
+ statsFromAlarms(alarms) {
144080
+ const bySeverity = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0, INFO: 0 };
144081
+ const byState = { OPEN: 0, ACK: 0, SNOOZED: 0, ESCALATED: 0, CLOSED: 0 };
144082
+ let openCritical = 0;
144083
+ let openHigh = 0;
144084
+ let last24Hours = 0;
144085
+ const since = Date.now() - 24 * 60 * 60 * 1e3;
144086
+ for (const a of alarms) {
144087
+ if (a.severity in bySeverity) bySeverity[a.severity]++;
144088
+ if (a.state in byState) byState[a.state]++;
144089
+ const open = a.state !== "CLOSED";
144090
+ if (open && a.severity === "CRITICAL") openCritical++;
144091
+ if (open && a.severity === "HIGH") openHigh++;
144092
+ const first = new Date(a.firstOccurrence || a.lastOccurrence || 0).getTime();
144093
+ if (first >= since) last24Hours++;
144094
+ }
144095
+ return { total: alarms.length, bySeverity, byState, openCritical, openHigh, last24Hours };
144096
+ }
143824
144097
  renderDashboardContent(state6) {
143825
144098
  if (!this.root) return;
143826
144099
  const container = this.root.querySelector("#dashboardContent");
143827
144100
  if (!container) return;
144101
+ const stats = state6.allAlarms.length > 0 ? this.statsFromAlarms(state6.filteredAlarms) : state6.stats;
143828
144102
  if (container.children.length > 0) {
143829
- updateDashboard(container, state6.stats, this.trendData);
144103
+ updateDashboard(container, stats, this.trendData);
143830
144104
  } else {
143831
- container.innerHTML = renderDashboard(state6.stats, this.trendData);
144105
+ container.innerHTML = renderDashboard(stats, this.trendData);
143832
144106
  }
143833
- this.emit("stats-updated", state6.stats);
144107
+ this.emit("stats-updated", stats);
143834
144108
  if (!this.trendFetched && this.params.tenantId) {
143835
144109
  this.trendFetched = true;
143836
144110
  AlarmService.getAlarmTrend(this.params.tenantId, "week", "day").then((data) => {
@@ -163286,6 +163560,11 @@ var STYLES3 = `
163286
163560
  flex-direction: column;
163287
163561
  overflow: hidden;
163288
163562
  }
163563
+ #${MODAL_ID5} .abm-card.is-maximized {
163564
+ width: 100vw !important; max-width: 100vw !important;
163565
+ height: 100vh !important; max-height: 100vh !important; border-radius: 0;
163566
+ }
163567
+ #${MODAL_ID5} .myio-premium-chrome-header .abm-view-toggle { margin-left: 0; margin-right: 6px; }
163289
163568
  #${MODAL_ID5} .abm-header {
163290
163569
  background: ${TEAL2};
163291
163570
  color: #fff;
@@ -164284,7 +164563,11 @@ async function openAlarmBundleMapModal(params) {
164284
164563
  card.className = "abm-card";
164285
164564
  overlay.appendChild(card);
164286
164565
  document.body.appendChild(overlay);
164566
+ let chrome = null;
164567
+ let maximized = false;
164287
164568
  const closeModal2 = () => {
164569
+ chrome?.destroy();
164570
+ chrome = null;
164288
164571
  overlay.remove();
164289
164572
  params.onClose?.();
164290
164573
  };
@@ -164310,12 +164593,29 @@ async function openAlarmBundleMapModal(params) {
164310
164593
  <button class="abm-close-btn" id="abm-close-btn" title="Fechar">\u2715</button>
164311
164594
  </div>
164312
164595
  <div class="abm-body">${bodyHtml}</div>
164313
- <div class="abm-footer">
164314
- <button class="abm-btn abm-btn-secondary" id="abm-close-btn-footer">Fechar</button>
164315
- </div>
164316
164596
  `;
164317
164597
  card.querySelector("#abm-close-btn")?.addEventListener("click", closeModal2);
164318
- card.querySelector("#abm-close-btn-footer")?.addEventListener("click", closeModal2);
164598
+ const toggleEl = card.querySelector(".abm-view-toggle");
164599
+ chrome?.destroy();
164600
+ chrome = createPremiumModalChrome({
164601
+ modalId: "abm-modal",
164602
+ icon: "\u{1F517}",
164603
+ title: `Mapa de Alarmes GCDR${subtitle ? ` \u2014 ${escHtml5(subtitle)}` : ""}`,
164604
+ modalEl: card,
164605
+ headerSlot: card.querySelector(".abm-header"),
164606
+ maximized,
164607
+ headerRadius: "16px 16px 0 0",
164608
+ onMaximizeChange: (m) => {
164609
+ maximized = m;
164610
+ placeToggle();
164611
+ },
164612
+ onClose: closeModal2
164613
+ });
164614
+ const placeToggle = () => {
164615
+ const actions = chrome?.header.lastElementChild;
164616
+ if (toggleEl && actions) actions.insertBefore(toggleEl, actions.firstChild);
164617
+ };
164618
+ placeToggle();
164319
164619
  }
164320
164620
  render3(`<div class="abm-loading">\u23F3 Carregando mapa de alarmes...</div>`);
164321
164621
  try {