myio-js-library 0.1.501 → 0.1.503

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
@@ -583,6 +583,7 @@ __export(index_exports, {
583
583
  CONSUMPTION_CHART_DEFAULTS: () => DEFAULT_CONFIG,
584
584
  CONSUMPTION_THEME_COLORS: () => THEME_COLORS,
585
585
  CardGridPanel: () => CardGridPanel,
586
+ ColumnSummaryTooltip: () => ColumnSummaryTooltip,
586
587
  ConnectionStatusType: () => ConnectionStatusType,
587
588
  ContractSummaryTooltip: () => ContractSummaryTooltip,
588
589
  CustomerCardV1: () => CustomerCardV1,
@@ -1162,7 +1163,7 @@ module.exports = __toCommonJS(index_exports);
1162
1163
  // package.json
1163
1164
  var package_default = {
1164
1165
  name: "myio-js-library",
1165
- version: "0.1.501",
1166
+ version: "0.1.503",
1166
1167
  description: "A clean, standalone JS SDK for MYIO projects",
1167
1168
  license: "MIT",
1168
1169
  repository: "github:gh-myio/myio-js-library",
@@ -27305,8 +27306,8 @@ var EnergyDataFetcher = class {
27305
27306
  // src/components/premium-modals/internal/engines/CsvExporter.ts
27306
27307
  var toCsv = (rows, locale = "pt-BR", sep = ";") => {
27307
27308
  const fmt2 = new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
27308
- const esc3 = (v) => (typeof v === "number" ? fmt2.format(v) : String(v)).replace(/"/g, '""');
27309
- return rows.map((r) => r.map((c) => `"${esc3(c)}"`).join(sep)).join("\r\n");
27309
+ const esc4 = (v) => (typeof v === "number" ? fmt2.format(v) : String(v)).replace(/"/g, '""');
27310
+ return rows.map((r) => r.map((c) => `"${esc4(c)}"`).join(sep)).join("\r\n");
27310
27311
  };
27311
27312
 
27312
27313
  // src/utils/telemetryUtils.ts
@@ -32992,6 +32993,13 @@ var AllReportModal = class {
32992
32993
  domainConfig;
32993
32994
  // Granularity: '1d' (daily) | '1h' (hourly)
32994
32995
  granularity = "1d";
32996
+ // When true, devices flagged via `exclude_groups_totals` for this group are dropped
32997
+ // from the report so its total reconciles with the dashboard KPIs. Toggleable in the UI.
32998
+ considerExclusion = true;
32999
+ // Raw API response kept so the exclusion toggle can re-map without a new fetch.
33000
+ lastApiResponse = null;
33001
+ // Cleanup for the InfoTooltip attached to the exclusion-flag info icon.
33002
+ exclusionTooltipCleanup = null;
32995
33003
  // Debug logging helper
32996
33004
  debugLog(message, data) {
32997
33005
  if (this.debugEnabled) {
@@ -33000,7 +33008,7 @@ var AllReportModal = class {
33000
33008
  }
33001
33009
  // Helper: normalize identifiers (upper, strip spaces and non-alphanum)
33002
33010
  normalizeId(v) {
33003
- return (v || "").toString().normalize("NFKC").toUpperCase().replace(/\s+/g, "").replace(/[^A-Z0-9]/g, "");
33011
+ return (v || "").toString().normalize("NFKC").toUpperCase().replace(/\s+/g, "").replace(/[0300-036f]/g, "");
33004
33012
  }
33005
33013
  // Helper: extract store identifier from API item
33006
33014
  // Priority: assetName -> parse from name (last token or token after space) -> null
@@ -33069,6 +33077,10 @@ var AllReportModal = class {
33069
33077
  this.dateRangePicker.destroy();
33070
33078
  this.dateRangePicker = null;
33071
33079
  }
33080
+ if (this.exclusionTooltipCleanup) {
33081
+ this.exclusionTooltipCleanup();
33082
+ this.exclusionTooltipCleanup = null;
33083
+ }
33072
33084
  if (this.filterModal) {
33073
33085
  this.filterModal.destroy();
33074
33086
  this.filterModal = null;
@@ -33107,6 +33119,20 @@ var AllReportModal = class {
33107
33119
  <button id="filter-btn" class="myio-btn myio-btn-secondary" style="background: var(--myio-brand-700); color: white;">
33108
33120
  \u{1F50D} Filtros & Ordena\xE7\xE3o
33109
33121
  </button>
33122
+ <div class="myio-form-group" style="margin-bottom: 0; display: flex; align-items: center; gap: 6px; align-self: flex-end; padding-bottom: 8px;">
33123
+ <label for="consider-exclusion" style="display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; color: var(--myio-text, #374151); white-space: nowrap;">
33124
+ <input type="checkbox" id="consider-exclusion" checked style="cursor: pointer; width: 15px; height: 15px; accent-color: var(--myio-brand-700, #5b2c9d);">
33125
+ Considerar exclus\xE3o de totais
33126
+ </label>
33127
+ <span id="exclusion-info" aria-label="Sobre a exclus\xE3o de totais" style="
33128
+ display: inline-flex; align-items: center; justify-content: center;
33129
+ width: 16px; height: 16px; border-radius: 50%;
33130
+ background: var(--myio-brand-700, #5b2c9d); color: #fff;
33131
+ font-size: 11px; font-weight: 700; font-style: italic;
33132
+ font-family: Georgia, 'Times New Roman', serif; cursor: help;
33133
+ user-select: none;
33134
+ ">i</span>
33135
+ </div>
33110
33136
  <div class="myio-form-group" style="margin-bottom: 0; margin-left: auto;">
33111
33137
  <label class="myio-label" for="search-input">Busca r\xE1pida</label>
33112
33138
  <input type="text" id="search-input" class="myio-input" placeholder="Digite para filtrar..." style="width: 200px;">
@@ -33165,6 +33191,20 @@ var AllReportModal = class {
33165
33191
  this.renderTable();
33166
33192
  });
33167
33193
  }
33194
+ const exclusionCheckbox = document.getElementById("consider-exclusion");
33195
+ exclusionCheckbox?.addEventListener("change", () => {
33196
+ this.considerExclusion = exclusionCheckbox.checked;
33197
+ this.remapAndRender();
33198
+ });
33199
+ const exclusionInfo = document.getElementById("exclusion-info");
33200
+ if (exclusionInfo) {
33201
+ this.exclusionTooltipCleanup?.();
33202
+ this.exclusionTooltipCleanup = InfoTooltip.attach(exclusionInfo, () => ({
33203
+ icon: "\u2139\uFE0F",
33204
+ title: "Exclus\xE3o de totais",
33205
+ content: this.buildExclusionTooltipContent()
33206
+ }));
33207
+ }
33168
33208
  try {
33169
33209
  this.dateRangePicker = await attach(dateRangeInput, {
33170
33210
  presetStart: this.getDefaultStartDate(),
@@ -33207,6 +33247,7 @@ var AllReportModal = class {
33207
33247
  const customerTotalsData = await this.fetchCustomerTotals(startISO, endISO);
33208
33248
  this.debugLog("\u2705 API response received", customerTotalsData);
33209
33249
  this.debugLog("\u{1F504} Processing API response...");
33250
+ this.lastApiResponse = customerTotalsData;
33210
33251
  this.data = this.mapCustomerTotalsResponse(customerTotalsData);
33211
33252
  this.debugLog("\u2705 Data mapping completed", {
33212
33253
  mappedDataLength: this.data.length,
@@ -33426,48 +33467,6 @@ var AllReportModal = class {
33426
33467
  });
33427
33468
  }
33428
33469
  renderPagination() {
33429
- return;
33430
- const container = document.getElementById("pagination-container");
33431
- if (!container) return;
33432
- const filteredData = this.getFilteredData();
33433
- const totalPages = Math.ceil(filteredData.length / this.itemsPerPage);
33434
- if (totalPages <= 1) {
33435
- container.style.display = "none";
33436
- return;
33437
- }
33438
- const startItem = (this.currentPage - 1) * this.itemsPerPage + 1;
33439
- const endItem = Math.min(this.currentPage * this.itemsPerPage, filteredData.length);
33440
- container.innerHTML = `
33441
- <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;">
33442
- <div style="color: var(--myio-text-muted);">
33443
- Mostrando ${startItem}-${endItem} de ${filteredData.length} lojas
33444
- </div>
33445
- <div style="display: flex; gap: 8px; align-items: center;">
33446
- <button id="prev-page" class="myio-btn myio-btn-outline" ${this.currentPage === 1 ? "disabled" : ""}>
33447
- Anterior
33448
- </button>
33449
- <span style="padding: 0 12px; font-weight: bold;">
33450
- ${this.currentPage} / ${totalPages}
33451
- </span>
33452
- <button id="next-page" class="myio-btn myio-btn-outline" ${this.currentPage === totalPages ? "disabled" : ""}>
33453
- Pr\xF3ximo
33454
- </button>
33455
- </div>
33456
- </div>
33457
- `;
33458
- document.getElementById("prev-page")?.addEventListener("click", () => {
33459
- if (this.currentPage > 1) {
33460
- this.currentPage--;
33461
- this.renderTable();
33462
- }
33463
- });
33464
- document.getElementById("next-page")?.addEventListener("click", () => {
33465
- if (this.currentPage < totalPages) {
33466
- this.currentPage++;
33467
- this.renderTable();
33468
- }
33469
- });
33470
- container.style.display = "block";
33471
33470
  }
33472
33471
  calculateTotalConsumption() {
33473
33472
  return this.data.reduce((sum, row) => sum + row.consumption, 0);
@@ -33511,7 +33510,7 @@ var AllReportModal = class {
33511
33510
  }
33512
33511
  generateStoreId(storeName) {
33513
33512
  const name = (storeName || "SEM-ID").toString();
33514
- return name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
33513
+ return name.toLowerCase().replace(/\s+/g, "-").replace(/[0300-036f]/g, "");
33515
33514
  }
33516
33515
  applyFiltersAndSort(selectedIds, sortMode) {
33517
33516
  this.selectedStoreIds = new Set(selectedIds);
@@ -33560,8 +33559,10 @@ var AllReportModal = class {
33560
33559
  async fetchCustomerTotals(startISO, endISO) {
33561
33560
  if (this.params.fetcher) {
33562
33561
  const token2 = this.params.api.ingestionToken || await this.authClient.getBearer();
33562
+ const baseUrl2 = this.params.api.dataApiBaseUrl;
33563
+ if (!baseUrl2) throw new Error("dataApiBaseUrl n\xE3o configurado.");
33563
33564
  return await this.params.fetcher({
33564
- baseUrl: this.params.api.dataApiBaseUrl,
33565
+ baseUrl: baseUrl2,
33565
33566
  token: token2,
33566
33567
  customerId: this.params.customerId,
33567
33568
  startISO,
@@ -33598,6 +33599,83 @@ var AllReportModal = class {
33598
33599
  this.debugLog("[AllReportModal] Customer totals response:", data);
33599
33600
  return data;
33600
33601
  }
33602
+ // Re-map the cached API response under the current exclusion flag and refresh the UI.
33603
+ // No-op until data has been loaded at least once.
33604
+ remapAndRender() {
33605
+ if (!this.lastApiResponse) return;
33606
+ this.data = this.mapCustomerTotalsResponse(this.lastApiResponse);
33607
+ this.selectedStoreIds = new Set(this.data.map((s) => this.generateStoreId(s.identifier)));
33608
+ this.currentPage = 1;
33609
+ this.renderSummary();
33610
+ this.renderTable();
33611
+ }
33612
+ // Premium tooltip content for the exclusion-flag info icon. Uses the library
33613
+ // InfoTooltip CSS classes (myio-info-tooltip__*) — injected by InfoTooltip itself.
33614
+ buildExclusionTooltipContent() {
33615
+ const p = "margin:0 0 8px;font-size:11px;line-height:1.5;color:#475569;";
33616
+ const pLast = "margin:0;font-size:11px;line-height:1.5;color:#475569;";
33617
+ return `
33618
+ <div class="myio-info-tooltip__section" style="max-width:280px;">
33619
+ <p style="${p}">
33620
+ Alguns dispositivos t\xEAm o atributo <strong>exclude_groups_totals</strong> e s\xE3o
33621
+ propositalmente removidos dos totais do dashboard (ex.: medidor de locat\xE1rio que
33622
+ n\xE3o \xE9 consumo operacional do shopping).
33623
+ </p>
33624
+ <p style="${p}">
33625
+ <strong>Ligado</strong> (padr\xE3o): esses dispositivos s\xE3o omitidos do relat\xF3rio \u2014
33626
+ o total bate com os cards do dashboard.
33627
+ </p>
33628
+ <p style="${pLast}">
33629
+ <strong>Desligado</strong>: todos os dispositivos do grupo entram \u2014 mostra o
33630
+ consumo bruto, inclusive os exclu\xEDdos.
33631
+ </p>
33632
+ </div>
33633
+ <div class="myio-info-tooltip__notice">
33634
+ <span class="myio-info-tooltip__notice-icon">\u{1F4A1}</span>
33635
+ <span>N\xE3o altera nenhum dado \u2014 apenas o que o relat\xF3rio soma e lista.</span>
33636
+ </div>
33637
+ `;
33638
+ }
33639
+ // Resolve the canonical `exclude_groups_totals.groups` key for the report's current group.
33640
+ // Returns null for groupings that don't map to a single exclusion key (climatizavel, etc.).
33641
+ resolveExclusionGroupKey(item) {
33642
+ const g = String(this.params.group || "").toLowerCase();
33643
+ if (g === "entrada" || g === "lojas" || g === "area_comum") return g;
33644
+ if (g === "todos") {
33645
+ const gl = String(item.groupLabel || "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();
33646
+ if (gl === "entrada") return "entrada";
33647
+ if (gl === "lojas") return "lojas";
33648
+ if (gl === "area comum" || gl === "areacomum") return "area_comum";
33649
+ if (gl === "climatizacao") return "climatizacao";
33650
+ if (gl === "elevadores") return "elevadores";
33651
+ if (gl === "escadas rolantes" || gl === "esc. rolantes") return "escadas_rolantes";
33652
+ if (gl === "outros" || gl === "outros equipamentos") return "outros";
33653
+ }
33654
+ return null;
33655
+ }
33656
+ // Mirrors getValorEfetivo (MAIN_VIEW): a device flagged in exclude_groups_totals for the
33657
+ // report's group is dropped, so the report total reconciles with the dashboard KPI card.
33658
+ isExcludedFromTotals(item) {
33659
+ const raw = item.excludeGroupsTotals;
33660
+ if (!raw) return false;
33661
+ let parsed;
33662
+ try {
33663
+ parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
33664
+ } catch {
33665
+ return false;
33666
+ }
33667
+ if (!parsed || parsed.enabled !== true) return false;
33668
+ const key = this.resolveExclusionGroupKey(item);
33669
+ if (!key) return false;
33670
+ if (parsed.groups && typeof parsed.groups === "object") {
33671
+ return parsed.groups[key] === true;
33672
+ }
33673
+ if (Array.isArray(parsed.excludedGroups)) {
33674
+ const ex = parsed.excludedGroups.map((x) => String(x).toLowerCase());
33675
+ return ex.includes(key) || ex.includes("all");
33676
+ }
33677
+ return false;
33678
+ }
33601
33679
  mapCustomerTotalsResponse(apiResponse) {
33602
33680
  this.debugLog("\u{1F50D} Starting mapCustomerTotalsResponse", { apiResponse });
33603
33681
  const apiArray = Array.isArray(apiResponse?.data) ? apiResponse.data : Array.isArray(apiResponse) ? apiResponse : [];
@@ -33635,6 +33713,10 @@ var AllReportModal = class {
33635
33713
  const apiId = String(apiItem?.id || "");
33636
33714
  if (!apiId || !orchIdSet.has(apiId)) continue;
33637
33715
  const meta = orchMeta.get(apiId);
33716
+ if (this.considerExclusion && meta && this.isExcludedFromTotals(meta)) {
33717
+ this.debugLog("[AllReportModal] device excluded via exclude_groups_totals:", meta.label);
33718
+ continue;
33719
+ }
33638
33720
  const consumption = Math.round(this.pickConsumption(apiItem) * 100) / 100;
33639
33721
  const result = {
33640
33722
  identifier: meta?.identifier || apiItem.name || apiId,
@@ -73761,6 +73843,185 @@ var WaterSummaryTooltip = {
73761
73843
  }
73762
73844
  };
73763
73845
 
73846
+ // src/utils/ColumnSummaryTooltip.ts
73847
+ var COLUMN_SUMMARY_CSS = `
73848
+ .myio-col-summary {
73849
+ max-width: 300px;
73850
+ font-family: 'Nunito', 'Segoe UI', system-ui, sans-serif;
73851
+ }
73852
+ .myio-col-summary__kpis {
73853
+ display: flex; flex-direction: column; gap: 4px;
73854
+ padding: 8px 10px; margin-bottom: 10px;
73855
+ background: #faf8ff; border: 1px solid #e3d9f3; border-radius: 8px;
73856
+ }
73857
+ .myio-col-summary__kpi {
73858
+ display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
73859
+ }
73860
+ .myio-col-summary__kpi-label {
73861
+ font-size: 10px; font-weight: 700; letter-spacing: 0.3px;
73862
+ text-transform: uppercase; color: #64748b;
73863
+ }
73864
+ .myio-col-summary__kpi-value {
73865
+ font-size: 12px; font-weight: 700; color: #1e293b; text-align: right;
73866
+ }
73867
+ .myio-col-summary__kpi-value--accent {
73868
+ font-size: 14px; color: #3e1a7d;
73869
+ }
73870
+ .myio-col-summary__group {
73871
+ display: flex; flex-direction: column; gap: 1px; margin-top: 8px;
73872
+ }
73873
+ .myio-col-summary__group-label {
73874
+ font-size: 10px; font-weight: 800; letter-spacing: 0.3px;
73875
+ text-transform: uppercase; color: #64748b; margin-bottom: 3px;
73876
+ }
73877
+ .myio-col-summary__row {
73878
+ display: flex; align-items: center; gap: 8px; padding: 2px 0;
73879
+ font-size: 11px; color: #1e293b;
73880
+ }
73881
+ .myio-col-summary__name {
73882
+ flex: 1 1 auto; min-width: 0;
73883
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
73884
+ }
73885
+ .myio-col-summary__val {
73886
+ flex: 0 0 auto; font-weight: 700; color: #16a34a; text-align: right;
73887
+ }
73888
+ .myio-col-summary__pct {
73889
+ flex: 0 0 auto; min-width: 44px; text-align: right;
73890
+ font-size: 10px; font-weight: 600; color: #64748b;
73891
+ }
73892
+ .myio-col-summary__empty {
73893
+ padding: 14px 0; text-align: center; font-style: italic;
73894
+ font-size: 11px; color: #94a3b8;
73895
+ }
73896
+ `;
73897
+ var _cssInjected2 = false;
73898
+ function injectCSS10() {
73899
+ if (_cssInjected2 || typeof document === "undefined") return;
73900
+ const STYLE_ID4 = "myio-column-summary-tooltip-css";
73901
+ if (document.getElementById(STYLE_ID4)) {
73902
+ _cssInjected2 = true;
73903
+ return;
73904
+ }
73905
+ const style = document.createElement("style");
73906
+ style.id = STYLE_ID4;
73907
+ style.textContent = COLUMN_SUMMARY_CSS;
73908
+ document.head.appendChild(style);
73909
+ _cssInjected2 = true;
73910
+ }
73911
+ function esc3(value) {
73912
+ return String(value == null ? "" : value).replace(
73913
+ /[&<>"]/g,
73914
+ (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]
73915
+ );
73916
+ }
73917
+ function defaultFormatter(unit) {
73918
+ const nf = new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 2 });
73919
+ return (v) => nf.format(Number(v) || 0) + (unit ? " " + unit : "");
73920
+ }
73921
+ function fmtPct(value, total) {
73922
+ const p = total > 0 ? (Number(value) || 0) / total * 100 : 0;
73923
+ return p.toFixed(1).replace(".", ",") + "%";
73924
+ }
73925
+ function buildContent(data) {
73926
+ const devices = Array.isArray(data.devices) ? data.devices.slice() : [];
73927
+ const fmt2 = data.formatValue || defaultFormatter(data.unit || "");
73928
+ const count = devices.length;
73929
+ const total = devices.reduce((s, d) => s + (Number(d.value) || 0), 0);
73930
+ const avg = count ? total / count : 0;
73931
+ const periodRow = data.periodLabel ? `<div class="myio-col-summary__kpi">
73932
+ <span class="myio-col-summary__kpi-label">Per\xEDodo</span>
73933
+ <span class="myio-col-summary__kpi-value">${esc3(data.periodLabel)}</span>
73934
+ </div>` : "";
73935
+ if (!count) {
73936
+ return `<div class="myio-col-summary">
73937
+ <div class="myio-col-summary__kpis">
73938
+ ${periodRow}
73939
+ <div class="myio-col-summary__kpi">
73940
+ <span class="myio-col-summary__kpi-label">Dispositivos</span>
73941
+ <span class="myio-col-summary__kpi-value">0</span>
73942
+ </div>
73943
+ </div>
73944
+ <div class="myio-col-summary__empty">Nenhum dispositivo.</div>
73945
+ </div>`;
73946
+ }
73947
+ const desc = devices.slice().sort((a, b) => (Number(b.value) || 0) - (Number(a.value) || 0));
73948
+ const top3 = desc.slice(0, 3);
73949
+ const bottom3 = desc.slice(-3).reverse();
73950
+ const near3 = devices.slice().sort(
73951
+ (a, b) => Math.abs((Number(a.value) || 0) - avg) - Math.abs((Number(b.value) || 0) - avg)
73952
+ ).slice(0, 3);
73953
+ const row = (d) => `
73954
+ <div class="myio-col-summary__row">
73955
+ <span class="myio-col-summary__name" title="${esc3(d.name)}">${esc3(d.name)}</span>
73956
+ <span class="myio-col-summary__val">${esc3(fmt2(Number(d.value) || 0))}</span>
73957
+ <span class="myio-col-summary__pct">${fmtPct(Number(d.value) || 0, total)}</span>
73958
+ </div>`;
73959
+ const group = (label, list) => list.length ? `<div class="myio-col-summary__group">
73960
+ <span class="myio-col-summary__group-label">${label}</span>
73961
+ ${list.map(row).join("")}
73962
+ </div>` : "";
73963
+ return `<div class="myio-col-summary">
73964
+ <div class="myio-col-summary__kpis">
73965
+ ${periodRow}
73966
+ <div class="myio-col-summary__kpi">
73967
+ <span class="myio-col-summary__kpi-label">Dispositivos</span>
73968
+ <span class="myio-col-summary__kpi-value">${count}</span>
73969
+ </div>
73970
+ <div class="myio-col-summary__kpi">
73971
+ <span class="myio-col-summary__kpi-label">Consumo m\xE9dio</span>
73972
+ <span class="myio-col-summary__kpi-value myio-col-summary__kpi-value--accent">${esc3(
73973
+ fmt2(avg)
73974
+ )}</span>
73975
+ </div>
73976
+ <div class="myio-col-summary__kpi">
73977
+ <span class="myio-col-summary__kpi-label">Consumo total</span>
73978
+ <span class="myio-col-summary__kpi-value">${esc3(fmt2(total))}</span>
73979
+ </div>
73980
+ </div>
73981
+ ${group("\u25B2 3 maiores", top3)}
73982
+ ${group("\u25BC 3 menores", bottom3)}
73983
+ ${group("\u25CF 3 na m\xE9dia", near3)}
73984
+ </div>`;
73985
+ }
73986
+ var ColumnSummaryTooltip = {
73987
+ /** Shows the column summary tooltip anchored to the trigger element. */
73988
+ show(triggerElement, data) {
73989
+ injectCSS10();
73990
+ InfoTooltip.show(triggerElement, {
73991
+ icon: "\u{1F4CA}",
73992
+ title: data.title ? `Resumo \u2014 ${data.title}` : "Resumo da Coluna",
73993
+ content: buildContent(data)
73994
+ });
73995
+ },
73996
+ /** Hides the tooltip immediately. */
73997
+ hide() {
73998
+ InfoTooltip.hide();
73999
+ },
74000
+ /** Starts the delayed hide (use on mouseleave). */
74001
+ startDelayedHide() {
74002
+ InfoTooltip.startDelayedHide();
74003
+ },
74004
+ /**
74005
+ * Attaches hover behavior to a trigger element. `getData` is called on each
74006
+ * hover so the summary always reflects the latest data. Returns a cleanup fn.
74007
+ */
74008
+ attach(triggerElement, getData) {
74009
+ injectCSS10();
74010
+ const handleEnter = () => {
74011
+ ColumnSummaryTooltip.show(triggerElement, getData());
74012
+ };
74013
+ const handleLeave = () => {
74014
+ InfoTooltip.startDelayedHide();
74015
+ };
74016
+ triggerElement.addEventListener("mouseenter", handleEnter);
74017
+ triggerElement.addEventListener("mouseleave", handleLeave);
74018
+ return () => {
74019
+ triggerElement.removeEventListener("mouseenter", handleEnter);
74020
+ triggerElement.removeEventListener("mouseleave", handleLeave);
74021
+ };
74022
+ }
74023
+ };
74024
+
73764
74025
  // src/utils/TempSensorSummaryTooltip.ts
73765
74026
  var TEMP_SENSOR_TOOLTIP_CSS = `
73766
74027
  /* ============================================
@@ -74251,7 +74512,7 @@ var TEMP_SENSOR_TOOLTIP_CSS = `
74251
74512
  }
74252
74513
  `;
74253
74514
  var cssInjected9 = false;
74254
- function injectCSS10() {
74515
+ function injectCSS11() {
74255
74516
  if (cssInjected9) return;
74256
74517
  if (typeof document === "undefined") return;
74257
74518
  const styleId = "myio-temp-sensor-tooltip-styles";
@@ -74638,7 +74899,7 @@ var TempSensorSummaryTooltip = {
74638
74899
  * Get or create container
74639
74900
  */
74640
74901
  getContainer() {
74641
- injectCSS10();
74902
+ injectCSS11();
74642
74903
  let container = document.getElementById(this.containerId);
74643
74904
  if (!container) {
74644
74905
  container = document.createElement("div");
@@ -75232,7 +75493,7 @@ var CONTRACT_SUMMARY_TOOLTIP_CSS = `
75232
75493
  }
75233
75494
  `;
75234
75495
  var cssInjected10 = false;
75235
- function injectCSS11() {
75496
+ function injectCSS12() {
75236
75497
  if (cssInjected10) return;
75237
75498
  if (typeof document === "undefined") return;
75238
75499
  const styleId = "myio-contract-summary-tooltip-styles";
@@ -75675,7 +75936,7 @@ var ContractSummaryTooltip = {
75675
75936
  * Get or create container
75676
75937
  */
75677
75938
  getContainer() {
75678
- injectCSS11();
75939
+ injectCSS12();
75679
75940
  let container = document.getElementById(this.containerId);
75680
75941
  if (!container) {
75681
75942
  container = document.createElement("div");
@@ -76161,7 +76422,7 @@ var USERS_SUMMARY_TOOLTIP_CSS = `
76161
76422
  }
76162
76423
  `;
76163
76424
  var cssInjected11 = false;
76164
- function injectCSS12() {
76425
+ function injectCSS13() {
76165
76426
  if (cssInjected11) return;
76166
76427
  if (typeof document === "undefined") return;
76167
76428
  const styleId = "myio-users-summary-tooltip-styles";
@@ -76192,7 +76453,7 @@ var UsersSummaryTooltip = {
76192
76453
  * Create or get the tooltip container
76193
76454
  */
76194
76455
  getContainer() {
76195
- injectCSS12();
76456
+ injectCSS13();
76196
76457
  let container = document.getElementById(this.containerId);
76197
76458
  if (!container) {
76198
76459
  container = document.createElement("div");
@@ -76869,7 +77130,7 @@ var ALARMS_SUMMARY_TOOLTIP_CSS = `
76869
77130
  }
76870
77131
  `;
76871
77132
  var cssInjected12 = false;
76872
- function injectCSS13() {
77133
+ function injectCSS14() {
76873
77134
  if (cssInjected12) return;
76874
77135
  if (typeof document === "undefined") return;
76875
77136
  const styleId = "myio-alarms-summary-tooltip-styles";
@@ -76900,7 +77161,7 @@ var AlarmsSummaryTooltip = {
76900
77161
  * Create or get the tooltip container
76901
77162
  */
76902
77163
  getContainer() {
76903
- injectCSS13();
77164
+ injectCSS14();
76904
77165
  let container = document.getElementById(this.containerId);
76905
77166
  if (!container) {
76906
77167
  container = document.createElement("div");
@@ -77315,7 +77576,7 @@ var NOTIFICATIONS_SUMMARY_TOOLTIP_CSS = `
77315
77576
  }
77316
77577
  `;
77317
77578
  var cssInjected13 = false;
77318
- function injectCSS14() {
77579
+ function injectCSS15() {
77319
77580
  if (cssInjected13) return;
77320
77581
  if (typeof document === "undefined") return;
77321
77582
  const styleId = "myio-notifications-summary-tooltip-styles";
@@ -77346,7 +77607,7 @@ var NotificationsSummaryTooltip = {
77346
77607
  * Create or get the tooltip container
77347
77608
  */
77348
77609
  getContainer() {
77349
- injectCSS14();
77610
+ injectCSS15();
77350
77611
  let container = document.getElementById(this.containerId);
77351
77612
  if (!container) {
77352
77613
  container = document.createElement("div");
@@ -84311,7 +84572,7 @@ async function fetchIngestionDevicesAllPaged(customerId) {
84311
84572
  function findIngestionDeviceByCentralSlaveId(devices, centralId, slaveId) {
84312
84573
  const slaveIdNum = typeof slaveId === "string" ? parseInt(slaveId, 10) : slaveId;
84313
84574
  for (const device of devices) {
84314
- const deviceGatewayId = device.gatewayId || device.gateway?.id;
84575
+ const deviceGatewayId = device.gateway?.hardwareUuid || device.gatewayId || device.gateway?.id;
84315
84576
  if (deviceGatewayId === centralId && device.slaveId === slaveIdNum) {
84316
84577
  console.log(
84317
84578
  "[UpsellModal] Found matching device:",
@@ -84329,8 +84590,8 @@ function findIngestionDeviceByCentralSlaveId(devices, centralId, slaveId) {
84329
84590
  console.log(
84330
84591
  `[UpsellModal] Sample device ${i}:`,
84331
84592
  d.name,
84332
- "gatewayId:",
84333
- d.gatewayId || d.gateway?.id,
84593
+ "gateway(hwUuid|id):",
84594
+ d.gateway?.hardwareUuid || d.gatewayId || d.gateway?.id,
84334
84595
  "slaveId:",
84335
84596
  d.slaveId
84336
84597
  );
@@ -84515,7 +84776,7 @@ function openUpsellModal(params) {
84515
84776
  selectedDevices: [],
84516
84777
  bulkAttributeModal: { open: false, attribute: "deviceType", value: "", saving: false },
84517
84778
  bulkProfileModal: { open: false, selectedProfileId: "", saving: false },
84518
- bulkOwnerModal: { open: false, saving: false },
84779
+ bulkOwnerModal: { open: false, saving: false, targetCustomerId: "" },
84519
84780
  columnWidths: {
84520
84781
  name: 180,
84521
84782
  label: 120,
@@ -84552,6 +84813,7 @@ function openUpsellModal(params) {
84552
84813
  lojasDeviceData: [],
84553
84814
  lojasDataLoading: false,
84554
84815
  lojasConfig: null,
84816
+ lojasApplyRelation: true,
84555
84817
  customModeModal: { open: false },
84556
84818
  bulkRelationModal: { open: false, target: "CUSTOMER", selectedAssetId: "", selectedAssetName: "", search: "", newAssetName: "", assetsLoaded: false, overrideCustomerId: "", overrideCustomerName: "", customerSearch: "", customerPickerOpen: false },
84557
84819
  checkFixLoading: false,
@@ -84716,6 +84978,12 @@ function renderModal4(container, state6, modalId, t, error) {
84716
84978
  font-size: 14px; font-weight: 500; font-family: 'Roboto', Arial, sans-serif;
84717
84979
  display: flex; align-items: center; gap: 6px;
84718
84980
  " ${!state6.selectedCustomer ? 'disabled title="Selecione um Customer primeiro"' : ""}>\u{1F504} Sync Ingestion ID (${state6.selectedDevices.length})</button>
84981
+ <button id="${modalId}-bulk-delete" style="
84982
+ background: #b91c1c; color: white; border: 1px solid #7f1d1d;
84983
+ padding: 8px 16px; border-radius: 6px; cursor: pointer;
84984
+ font-size: 14px; font-weight: 600; font-family: 'Roboto', Arial, sans-serif;
84985
+ display: flex; align-items: center; gap: 6px;
84986
+ " title="Deletar permanentemente os dispositivos selecionados (irrevers\xEDvel)">\u{1F5D1}\uFE0F Deletar (${state6.selectedDevices.length})</button>
84719
84987
  ` : ""}
84720
84988
  ${state6.currentStep === 3 && state6.lojasMode ? `
84721
84989
  <button id="${modalId}-lojas-sync" style="
@@ -84954,7 +85222,18 @@ function renderModal4(container, state6, modalId, t, error) {
84954
85222
  </div>
84955
85223
  ` : ""}
84956
85224
 
84957
- ${state6.bulkOwnerModal.open ? `
85225
+ ${state6.bulkOwnerModal.open ? (() => {
85226
+ const effId = state6.bulkOwnerModal.targetCustomerId || state6.selectedCustomer?.id?.id || "";
85227
+ const effCustomer = state6.customers.find((c) => c.id?.id === effId) || state6.selectedCustomer;
85228
+ const effName = effCustomer?.name || effCustomer?.title || "N\xE3o selecionado";
85229
+ const customerOptions = state6.customers.length === 0 ? `<option value="${effId}">${effName}</option>` : [...state6.customers].sort(
85230
+ (a, b) => (a.name || a.title || "").localeCompare(b.name || b.title || "", "pt-BR")
85231
+ ).map((c) => {
85232
+ const cid = c.id?.id || "";
85233
+ const cname = c.name || c.title || cid;
85234
+ return `<option value="${cid}" ${cid === effId ? "selected" : ""}>${cname}</option>`;
85235
+ }).join("");
85236
+ return `
84958
85237
  <!-- Bulk Owner Modal -->
84959
85238
  <div class="myio-bulk-owner-overlay" style="
84960
85239
  position: fixed; top: 0; left: 0; right: 0; bottom: 0;
@@ -84980,17 +85259,25 @@ function renderModal4(container, state6, modalId, t, error) {
84980
85259
  <div style="font-size: 14px; color: ${colors2.text}; font-weight: 500;">${state6.selectedDevices.length} dispositivos</div>
84981
85260
  </div>
84982
85261
 
84983
- <div style="margin-bottom: 16px; padding: 12px; background: ${colors2.success}20; border-radius: 8px; border: 1px solid ${colors2.success}40;">
84984
- <div style="font-size: 12px; color: ${colors2.textMuted}; margin-bottom: 4px;">Novo Owner (Customer):</div>
84985
- <div style="font-size: 14px; color: ${colors2.success}; font-weight: 600;">${state6.selectedCustomer?.name || state6.selectedCustomer?.title || "N\xE3o selecionado"}</div>
85262
+ <div style="margin-bottom: 16px;">
85263
+ <div style="font-size: 12px; color: ${colors2.textMuted}; margin-bottom: 6px;">
85264
+ Novo Owner (Customer):
85265
+ </div>
85266
+ <select id="${modalId}-bulk-owner-customer" style="
85267
+ width: 100%; padding: 9px 10px; border-radius: 8px;
85268
+ border: 1px solid ${colors2.border}; background: ${colors2.inputBg};
85269
+ color: ${colors2.text}; font-size: 13px; cursor: pointer;
85270
+ ">
85271
+ ${customerOptions}
85272
+ </select>
84986
85273
  <div style="font-size: 11px; color: ${colors2.textMuted}; margin-top: 4px;">
84987
- ID: ${state6.selectedCustomer?.id?.id || "N/A"}
85274
+ ID: ${effId || "N/A"}${state6.customers.length === 0 ? " \xB7 carregando lista de clientes\u2026" : ""}
84988
85275
  </div>
84989
85276
  </div>
84990
85277
 
84991
85278
  <div style="margin-bottom: 16px; padding: 12px; background: ${colors2.warning}20; border-radius: 8px; border: 1px solid ${colors2.warning}40;">
84992
85279
  <div style="font-size: 12px; color: ${colors2.warning}; font-weight: 500;">
84993
- \u26A0\uFE0F Aten\xE7\xE3o: Esta a\xE7\xE3o ir\xE1 atribuir todos os ${state6.selectedDevices.length} devices selecionados ao customer "${state6.selectedCustomer?.name || state6.selectedCustomer?.title}".
85280
+ \u26A0\uFE0F Aten\xE7\xE3o: Esta a\xE7\xE3o ir\xE1 atribuir todos os ${state6.selectedDevices.length} devices selecionados ao customer "${effName}".
84994
85281
  </div>
84995
85282
  </div>
84996
85283
 
@@ -85004,13 +85291,14 @@ function renderModal4(container, state6, modalId, t, error) {
85004
85291
  background: #10b981; color: white; border: none;
85005
85292
  padding: 10px 20px; border-radius: 6px; cursor: pointer;
85006
85293
  font-size: 14px; font-weight: 500;
85007
- " ${state6.bulkOwnerModal.saving || !state6.selectedCustomer ? "disabled" : ""}>
85294
+ " ${state6.bulkOwnerModal.saving || !effId ? "disabled" : ""}>
85008
85295
  ${state6.bulkOwnerModal.saving ? "Salvando..." : "Atribuir Owner para " + state6.selectedDevices.length + " devices"}
85009
85296
  </button>
85010
85297
  </div>
85011
85298
  </div>
85012
85299
  </div>
85013
- ` : ""}
85300
+ `;
85301
+ })() : ""}
85014
85302
 
85015
85303
  ${state6.customModeModal.open ? `
85016
85304
  <!-- CUSTOM Mode Picker Modal -->
@@ -85230,6 +85518,7 @@ function renderModal4(container, state6, modalId, t, error) {
85230
85518
  `;
85231
85519
  })() : ""}
85232
85520
  `;
85521
+ delete container.dataset.upsellListenersBound;
85233
85522
  setupEventListeners3(container, state6, modalId, t);
85234
85523
  }
85235
85524
  function renderStepIndicator(step, label, currentStep, colors2) {
@@ -85433,6 +85722,8 @@ function renderCheckFixRow(r, state6, modalId, colors2, dupPairIds, dupIngestion
85433
85722
  };
85434
85723
  const valStr = Object.entries(r.telemetry.values).map(([k, v]) => v != null ? `${k}:<b>${v}${unit[k] ?? ""}</b>` : null).filter(Boolean).join(" \xB7 ") || "\u2014";
85435
85724
  const connColor = r.connStatus ? CONN_COLOR[r.connStatus] || colors2.textMuted : colors2.textMuted;
85725
+ const valCopy = Object.entries(r.telemetry.values).filter(([, v]) => v != null).map(([k, v]) => `${k}: ${v}${unit[k] ?? ""}`).join(" \xB7 ");
85726
+ const copyAttr = (v) => `class="myio-copy-cell" data-copy="${encodeURIComponent(String(v ?? ""))}"`;
85436
85727
  return `
85437
85728
  <tr class="myio-list-item ${isSelected ? "selected" : ""}" data-device-id="${r.deviceId}"
85438
85729
  style="border-bottom:1px solid ${colors2.border}; cursor:pointer;">
@@ -85441,21 +85732,21 @@ function renderCheckFixRow(r, state6, modalId, colors2, dupPairIds, dupIngestion
85441
85732
  <input type="checkbox" class="myio-device-checkbox" data-device-id="${r.deviceId}"
85442
85733
  ${isSelectedMulti ? "checked" : ""} style="width:14px;height:14px;cursor:pointer;accent-color:${MYIO_PURPLE};"/>
85443
85734
  </td>` : ""}
85444
- <td style="${cell()} max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${r.deviceName}">${r.deviceName}</td>
85445
- <td style="${cell()} max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:${colors2.textMuted};" title="${r.deviceLabel}">${r.deviceLabel || dash}</td>
85446
- <td style="${cell(typeActWrong ? "ok" : "none", true)}">${r.inferred.deviceType}</td>
85447
- <td style="${cell(typeActWrong ? r.typeEqualsProfile ? "warn" : "bad" : "none", true)}" title="${typeActWrong ? `esperado: ${r.inferred.deviceType}` : ""}">${r.actual.type || dash}</td>
85448
- <td style="${cell(devTypeWrong ? "ok" : "none", true)}">${r.inferred.deviceType}</td>
85449
- <td style="${cell(devTypeWrong ? "bad" : "none", true)}" title="${devTypeWrong ? `esperado: ${r.inferred.deviceType}` : ""}">${r.actual.deviceType || dash}</td>
85450
- <td style="${cell(devProfWrong ? "ok" : "none", true)}">${r.inferred.deviceProfile}</td>
85451
- <td style="${cell(devProfWrong ? "bad" : "none", true)}" title="${devProfWrong ? `esperado: ${r.inferred.deviceProfile}` : ""}">${r.actual.deviceProfile || dash}</td>
85452
- <td style="${cell()} white-space:nowrap; color:${colors2.textMuted}; font-size:9px;">${tsStr}</td>
85453
- <td style="${cell()} font-size:9px;">${valStr}</td>
85454
- <td style="${cell()} color:${connColor}; font-weight:600; white-space:nowrap;">${r.connStatus || dash}</td>
85455
- <td style="${cell()} font-size:9px; font-family:monospace; color:${colors2.textMuted}; max-width:110px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${r.gcdrDeviceId || ""}">${r.gcdrDeviceId || dash}</td>
85456
- <td style="${cell(dupIngestionIds.has(r.deviceId) ? "bad" : "none")} font-size:9px; font-family:monospace; max-width:110px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${r.ingestionId || ""}">${r.ingestionId || dash}</td>
85457
- <td style="${cell(dupPairIds.has(r.deviceId) ? "bad" : "none")} font-size:9px; font-family:monospace; max-width:80px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${r.centralId || ""}">${r.centralId || dash}</td>
85458
- <td style="${cell(dupPairIds.has(r.deviceId) ? "bad" : "none")} font-size:9px; font-family:monospace; max-width:50px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${r.slaveId || dash}</td>
85735
+ <td ${copyAttr(r.deviceName)} style="${cell()} max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${r.deviceName}">${r.deviceName}</td>
85736
+ <td ${copyAttr(r.deviceLabel)} style="${cell()} max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:${colors2.textMuted};" title="${r.deviceLabel}">${r.deviceLabel || dash}</td>
85737
+ <td ${copyAttr(r.inferred.deviceType)} style="${cell(typeActWrong ? "ok" : "none", true)}">${r.inferred.deviceType}</td>
85738
+ <td ${copyAttr(r.actual.type ?? "")} style="${cell(typeActWrong ? r.typeEqualsProfile ? "warn" : "bad" : "none", true)}" title="${typeActWrong ? `esperado: ${r.inferred.deviceType}` : ""}">${r.actual.type || dash}</td>
85739
+ <td ${copyAttr(r.inferred.deviceType)} style="${cell(devTypeWrong ? "ok" : "none", true)}">${r.inferred.deviceType}</td>
85740
+ <td ${copyAttr(r.actual.deviceType ?? "")} style="${cell(devTypeWrong ? "bad" : "none", true)}" title="${devTypeWrong ? `esperado: ${r.inferred.deviceType}` : ""}">${r.actual.deviceType || dash}</td>
85741
+ <td ${copyAttr(r.inferred.deviceProfile)} style="${cell(devProfWrong ? "ok" : "none", true)}">${r.inferred.deviceProfile}</td>
85742
+ <td ${copyAttr(r.actual.deviceProfile ?? "")} style="${cell(devProfWrong ? "bad" : "none", true)}" title="${devProfWrong ? `esperado: ${r.inferred.deviceProfile}` : ""}">${r.actual.deviceProfile || dash}</td>
85743
+ <td ${copyAttr(r.telemetry.ts ? tsStr : "")} style="${cell()} white-space:nowrap; color:${colors2.textMuted}; font-size:9px;">${tsStr}</td>
85744
+ <td ${copyAttr(valCopy)} style="${cell()} font-size:9px;">${valStr}</td>
85745
+ <td ${copyAttr(r.connStatus ?? "")} style="${cell()} color:${connColor}; font-weight:600; white-space:nowrap;">${r.connStatus || dash}</td>
85746
+ <td ${copyAttr(r.gcdrDeviceId ?? "")} style="${cell()} font-size:9px; font-family:monospace; color:${colors2.textMuted}; max-width:110px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${r.gcdrDeviceId || ""}">${r.gcdrDeviceId || dash}</td>
85747
+ <td ${copyAttr(r.ingestionId ?? "")} style="${cell(dupIngestionIds.has(r.deviceId) ? "bad" : "none")} font-size:9px; font-family:monospace; max-width:110px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${r.ingestionId || ""}">${r.ingestionId || dash}</td>
85748
+ <td ${copyAttr(r.centralId ?? "")} style="${cell(dupPairIds.has(r.deviceId) ? "bad" : "none")} font-size:9px; font-family:monospace; max-width:80px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${r.centralId || ""}">${r.centralId || dash}</td>
85749
+ <td ${copyAttr(r.slaveId ?? "")} style="${cell(dupPairIds.has(r.deviceId) ? "bad" : "none")} font-size:9px; font-family:monospace; max-width:50px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${r.slaveId || dash}</td>
85459
85750
  <td style="padding:4px 6px; font-size:10px; color:${STATUS_COLOR[r.status]}; font-weight:700; white-space:nowrap; cursor:${r.status !== "ok" ? "help" : "default"};"
85460
85751
  ${r.status !== "ok" ? `data-cf-status="${r.status}" data-cf-device="${encodeURIComponent(r.deviceName)}" data-cf-detail="${encodeURIComponent(_buildCfStatusDetail(r))}"` : ""}>
85461
85752
  ${STATUS_ICON[r.status]} ${r.status}
@@ -85501,16 +85792,7 @@ function renderStep2(state6, modalId, colors2, t) {
85501
85792
  }
85502
85793
  return true;
85503
85794
  });
85504
- const searchFilteredDevices = searchTerm ? filteredDevices.filter((d) => {
85505
- const name = (d.name || "").toLowerCase();
85506
- const label = (d.label || "").toLowerCase();
85507
- const type = (d.type || "").toLowerCase();
85508
- const deviceType = (d.serverAttrs?.deviceType || "").toLowerCase();
85509
- const deviceProfile = (d.serverAttrs?.deviceProfile || "").toLowerCase();
85510
- const slaveId = String(d.serverAttrs?.slaveId ?? "").toLowerCase();
85511
- const status = (d.latestTelemetry?.connectionStatus?.value || "").toLowerCase();
85512
- return name.includes(searchTerm) || label.includes(searchTerm) || type.includes(searchTerm) || deviceType.includes(searchTerm) || deviceProfile.includes(searchTerm) || slaveId.includes(searchTerm) || status.includes(searchTerm);
85513
- }) : filteredDevices;
85795
+ const searchFilteredDevices = searchTerm ? filteredDevices.filter((d) => buildDeviceSearchHaystack(d, state6).includes(searchTerm)) : filteredDevices;
85514
85796
  const sortedDevices = sortDevices2(searchFilteredDevices, sortField, sortOrder);
85515
85797
  const gridHeight = state6.isMaximized ? "calc(100vh - 340px)" : "360px";
85516
85798
  const hasActiveFilters = filterTypes.length > 0 || filterDeviceTypes.length > 0 || filterDeviceProfiles.length > 0 || filterStatuses.length > 0 || filterTelemetryKeys.length > 0;
@@ -85978,6 +86260,95 @@ function renderStep2(state6, modalId, colors2, t) {
85978
86260
  })()}
85979
86261
  `;
85980
86262
  }
86263
+ function buildDeviceSearchHaystack(d, state6) {
86264
+ const id = getEntityId(d);
86265
+ const relTo = (state6.deviceRelToMap.get(id) || []).map((r) => r.name || "").join(" ");
86266
+ const relFrom = (state6.deviceRelFromMap.get(id) || []).map((r) => r.name || "").join(" ");
86267
+ const tel = d.latestTelemetry || {};
86268
+ const a = d.serverAttrs || {};
86269
+ return [
86270
+ d.name,
86271
+ d.label,
86272
+ d.type,
86273
+ d.createdTime ? formatDate6(d.createdTime, state6.locale) : "",
86274
+ relTo,
86275
+ relFrom,
86276
+ a.centralId,
86277
+ a.slaveId,
86278
+ a.deviceType,
86279
+ a.deviceProfile,
86280
+ tel.pulses?.value,
86281
+ tel.consumption?.value,
86282
+ tel.temperature?.value,
86283
+ tel.connectionStatus?.value
86284
+ ].map((v) => String(v ?? "").toLowerCase()).join("");
86285
+ }
86286
+ function getGridVisibleDevices(state6) {
86287
+ const {
86288
+ types: filterTypes,
86289
+ deviceTypes: filterDeviceTypes,
86290
+ deviceProfiles: filterDeviceProfiles,
86291
+ statuses: filterStatuses,
86292
+ telemetryKeys: filterTelemetryKeys
86293
+ } = state6.deviceFilters;
86294
+ const searchTerm = state6.deviceSearchTerm.toLowerCase();
86295
+ let result = state6.devices.filter((d) => {
86296
+ if (filterTypes.length > 0 && !filterTypes.includes(d.type || "")) return false;
86297
+ if (filterDeviceTypes.length > 0 && !filterDeviceTypes.includes(d.serverAttrs?.deviceType || ""))
86298
+ return false;
86299
+ if (filterDeviceProfiles.length > 0 && !filterDeviceProfiles.includes(d.serverAttrs?.deviceProfile || ""))
86300
+ return false;
86301
+ if (filterStatuses.length > 0) {
86302
+ const status = d.latestTelemetry?.connectionStatus?.value || "offline";
86303
+ if (!filterStatuses.includes(status)) return false;
86304
+ }
86305
+ if (filterTelemetryKeys.length > 0) {
86306
+ const telem = d.latestTelemetry;
86307
+ const hasMatch = filterTelemetryKeys.some((k) => {
86308
+ if (k === "pulses") return telem?.pulses != null;
86309
+ if (k === "consumption") return telem?.consumption != null;
86310
+ return false;
86311
+ });
86312
+ if (!hasMatch) return false;
86313
+ }
86314
+ return true;
86315
+ });
86316
+ if (searchTerm) {
86317
+ result = result.filter((d) => buildDeviceSearchHaystack(d, state6).includes(searchTerm));
86318
+ }
86319
+ if (state6.checkFixReport) {
86320
+ const idToRecord = new Map(state6.checkFixReport.records.map((r) => [r.deviceId, r]));
86321
+ const af = state6.checkFixAdvancedFilter;
86322
+ result = result.filter((d) => {
86323
+ const r = idToRecord.get(getEntityId(d));
86324
+ if (!r) return false;
86325
+ if (!(state6.checkFixFilter === "all" || r.status === state6.checkFixFilter)) return false;
86326
+ if (af.statuses.length > 0 && !af.statuses.includes(r.status)) return false;
86327
+ if (af.connStatuses.length > 0 && !af.connStatuses.includes(r.connStatus || "null")) return false;
86328
+ if (af.domains.length > 0 && !af.domains.includes(r.domain || "null")) return false;
86329
+ if (af.missingIngestionId && r.ingestionId) return false;
86330
+ if (af.missingCentralSlave && r.centralId && r.slaveId != null) return false;
86331
+ return true;
86332
+ });
86333
+ }
86334
+ return result;
86335
+ }
86336
+ function copyCellValue(text, el2) {
86337
+ if (!text) return;
86338
+ const flash = (ok) => {
86339
+ const prev = el2.style.backgroundColor;
86340
+ el2.style.transition = "background-color 0.15s ease";
86341
+ el2.style.backgroundColor = ok ? "rgba(34,197,94,0.35)" : "rgba(239,68,68,0.35)";
86342
+ setTimeout(() => {
86343
+ el2.style.backgroundColor = prev;
86344
+ }, 350);
86345
+ };
86346
+ if (navigator.clipboard?.writeText) {
86347
+ navigator.clipboard.writeText(text).then(() => flash(true), () => flash(false));
86348
+ } else {
86349
+ flash(false);
86350
+ }
86351
+ }
85981
86352
  function renderDeviceRow(device, state6, modalId, colors2) {
85982
86353
  const deviceId = getEntityId(device);
85983
86354
  const isSelectedSingle = state6.deviceSelectionMode === "single" && getEntityId(state6.selectedDevice) === deviceId;
@@ -86092,6 +86463,10 @@ function renderDeviceRow(device, state6, modalId, colors2) {
86092
86463
  " title="${statusTs}">(+)</span>` : ""}
86093
86464
  `;
86094
86465
  };
86466
+ const relToNames = state6.relationsLoaded ? (state6.deviceRelToMap.get(deviceId) || []).map((r) => r.name || "").filter(Boolean).join(", ") : "";
86467
+ const relFromNames = state6.relationsLoaded ? (state6.deviceRelFromMap.get(deviceId) || []).map((r) => r.name || "").filter(Boolean).join(", ") : "";
86468
+ const telemetryCopy = telemetryItems.map((it) => `${it.label}: ${it.value}${it.unit}`).join("; ");
86469
+ const copyAttr = (v) => `class="myio-copy-cell" data-copy="${encodeURIComponent(String(v ?? ""))}" title="Clique para copiar valor"`;
86095
86470
  return `
86096
86471
  <div class="myio-list-item ${isSelected ? "selected" : ""}"
86097
86472
  data-device-id="${deviceId}" style="
@@ -86107,8 +86482,8 @@ function renderDeviceRow(device, state6, modalId, colors2) {
86107
86482
  " />
86108
86483
  </div>
86109
86484
  ` : ""}
86110
- <div style="width: 28px; font-size: 16px; flex-shrink: 0;">${getDeviceIcon3(device.type)}</div>
86111
- <div style="width: ${state6.columnWidths.name}px; padding: 0 6px; overflow: hidden; display: flex; align-items: center; gap: 4px;">
86485
+ <div style="width: 28px; font-size: 16px; flex-shrink: 0; cursor: pointer;" title="Clique para selecionar o dispositivo">${getDeviceIcon3(device.type)}</div>
86486
+ <div ${copyAttr(device.name)} style="width: ${state6.columnWidths.name}px; padding: 0 6px; overflow: hidden; display: flex; align-items: center; gap: 4px; cursor: pointer;">
86112
86487
  <div style="font-weight: 600; color: ${colors2.text}; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1;" title="${device.name}">
86113
86488
  ${device.name}
86114
86489
  </div>
@@ -86118,22 +86493,22 @@ function renderDeviceRow(device, state6, modalId, colors2) {
86118
86493
  display: flex; align-items: center; justify-content: center; border: 1px solid ${colors2.border};
86119
86494
  " title="Ver detalhes">\u24D8</span>
86120
86495
  </div>
86121
- <div style="width: ${state6.columnWidths.label}px; padding: 0 6px; overflow: hidden; flex-shrink: 0;">
86496
+ <div ${copyAttr(device.label ?? "")} style="width: ${state6.columnWidths.label}px; padding: 0 6px; overflow: hidden; flex-shrink: 0; cursor: pointer;">
86122
86497
  <div style="font-size: 10px; color: ${colors2.textMuted}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${device.label ?? ""}">
86123
86498
  ${device.label ?? ""}
86124
86499
  </div>
86125
86500
  </div>
86126
- <div style="width: ${state6.columnWidths.type}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden;">
86501
+ <div ${copyAttr(device.type ?? "")} style="width: ${state6.columnWidths.type}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; cursor: pointer;">
86127
86502
  <div style="font-size: 9px; padding: 2px 4px; border-radius: 3px; display: inline-block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
86128
86503
  background: ${device.type?.includes("HIDRO") ? "#dbeafe" : "#fef3c7"};
86129
86504
  color: ${device.type?.includes("HIDRO") ? "#1e40af" : "#92400e"};" title="${device.type || ""}">
86130
86505
  ${device.type || "\u2014"}
86131
86506
  </div>
86132
86507
  </div>
86133
- <div style="width: ${state6.columnWidths.createdTime}px; padding: 0 6px; text-align: center; flex-shrink: 0;">
86508
+ <div ${copyAttr(device.createdTime ? createdTimeStr : "")} style="width: ${state6.columnWidths.createdTime}px; padding: 0 6px; text-align: center; flex-shrink: 0; cursor: pointer;">
86134
86509
  <span style="font-size: 9px; color: ${colors2.textMuted};">${createdTimeStr}</span>
86135
86510
  </div>
86136
- <div style="width: ${state6.columnWidths.relationTo}px; padding: 0 6px; flex-shrink: 0; overflow: hidden; display:flex; align-items:center; justify-content:center; gap:3px;">
86511
+ <div ${copyAttr(relToNames)} style="width: ${state6.columnWidths.relationTo}px; padding: 0 6px; flex-shrink: 0; overflow: hidden; display:flex; align-items:center; justify-content:center; gap:3px; cursor: pointer;">
86137
86512
  ${(() => {
86138
86513
  if (!state6.relationsLoaded) return `<span style="font-size: 8px; color: ${colors2.textMuted}; font-style: italic;">\u2014</span>`;
86139
86514
  const rels = state6.deviceRelToMap.get(deviceId) || [];
@@ -86143,7 +86518,7 @@ function renderDeviceRow(device, state6, modalId, colors2) {
86143
86518
  return `<span style="font-size:9px;color:${colors2.text};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:${more > 0 ? 68 : 108}px" title="${first.name}">${first.name}</span>${more > 0 ? `<button data-show-relto="${deviceId}" data-device-name="${encodeURIComponent(device.name || "")}" style="flex-shrink:0;font-size:9px;background:#ede9ff;color:#4c1d95;border:none;border-radius:3px;padding:1px 4px;cursor:pointer;font-weight:700;line-height:1.4">+${more}</button>` : ""}`;
86144
86519
  })()}
86145
86520
  </div>
86146
- <div style="width: ${state6.columnWidths.relationFrom}px; padding: 0 6px; flex-shrink: 0; overflow: hidden; display:flex; align-items:center; justify-content:center; gap:3px;">
86521
+ <div ${copyAttr(relFromNames)} style="width: ${state6.columnWidths.relationFrom}px; padding: 0 6px; flex-shrink: 0; overflow: hidden; display:flex; align-items:center; justify-content:center; gap:3px; cursor: pointer;">
86147
86522
  ${(() => {
86148
86523
  if (!state6.relationsLoaded) return `<span style="font-size: 8px; color: ${colors2.textMuted}; font-style: italic;">\u2014</span>`;
86149
86524
  const rels = state6.deviceRelFromMap.get(deviceId) || [];
@@ -86153,25 +86528,25 @@ function renderDeviceRow(device, state6, modalId, colors2) {
86153
86528
  return `<span style="font-size:9px;color:${colors2.text};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:${more > 0 ? 68 : 98}px" title="${first.name}">${first.name}</span>${more > 0 ? `<button data-show-relfrom="${deviceId}" data-device-name="${encodeURIComponent(device.name || "")}" style="flex-shrink:0;font-size:9px;background:#dbeafe;color:#1e40af;border:none;border-radius:3px;padding:1px 4px;cursor:pointer;font-weight:700;line-height:1.4">+${more}</button>` : ""}`;
86154
86529
  })()}
86155
86530
  </div>
86156
- <div style="width: ${state6.columnWidths.centralId}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
86531
+ <div ${copyAttr(attrs.centralId ?? "")} style="width: ${state6.columnWidths.centralId}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; cursor: pointer;">
86157
86532
  ${!state6.deviceAttrsLoaded ? `<span style="font-size: 8px; color: ${colors2.textMuted}; font-style: italic;">\u2014</span>` : attrs.centralId ? `<span style="font-size: 9px; color: ${colors2.text};" title="${attrs.centralId}">${attrs.centralId}</span>` : `<span style="font-size: 9px; color: ${colors2.textMuted};">\u2014</span>`}
86158
86533
  </div>
86159
- <div style="width: ${state6.columnWidths.slaveId}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
86534
+ <div ${copyAttr(attrs.slaveId ?? "")} style="width: ${state6.columnWidths.slaveId}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; cursor: pointer;">
86160
86535
  ${!state6.deviceAttrsLoaded ? `<span style="font-size: 8px; color: ${colors2.textMuted}; font-style: italic;">\u2014</span>` : attrs.slaveId != null && attrs.slaveId !== "" ? `<span style="font-size: 9px; color: ${colors2.text};" title="${attrs.slaveId}">${attrs.slaveId}</span>` : `<span style="font-size: 9px; color: ${colors2.textMuted};">\u2014</span>`}
86161
86536
  </div>
86162
- <div style="width: ${state6.columnWidths.deviceType}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
86537
+ <div ${copyAttr(state6.deviceAttrsLoaded ? attrs.deviceType ?? "" : "")} style="width: ${state6.columnWidths.deviceType}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; cursor: pointer;">
86163
86538
  ${renderDeviceTypeValue()}
86164
86539
  </div>
86165
- <div style="width: ${state6.columnWidths.deviceProfile}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
86540
+ <div ${copyAttr(state6.deviceAttrsLoaded ? attrs.deviceProfile ?? "" : "")} style="width: ${state6.columnWidths.deviceProfile}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; cursor: pointer;">
86166
86541
  ${renderDeviceProfileValue()}
86167
86542
  </div>
86168
- <div style="width: ${state6.columnWidths.telemetry}px; padding: 0 6px; text-align: center; flex-shrink: 0; display: flex; align-items: center; justify-content: center; gap: 4px; flex-wrap: wrap;">
86543
+ <div ${copyAttr(telemetryCopy)} style="width: ${state6.columnWidths.telemetry}px; padding: 0 6px; text-align: center; flex-shrink: 0; display: flex; align-items: center; justify-content: center; gap: 4px; flex-wrap: wrap; cursor: pointer;">
86169
86544
  ${renderTelemetryValue()}
86170
86545
  </div>
86171
- <div style="width: ${state6.columnWidths.status}px; padding: 0 6px; text-align: center; flex-shrink: 0; display: flex; align-items: center; justify-content: center; gap: 2px;">
86546
+ <div ${copyAttr(connStatus ?? "")} style="width: ${state6.columnWidths.status}px; padding: 0 6px; text-align: center; flex-shrink: 0; display: flex; align-items: center; justify-content: center; gap: 2px; cursor: pointer;">
86172
86547
  ${renderStatusValue()}
86173
86548
  </div>
86174
- <div style="width: 24px; flex-shrink: 0; text-align: center;">
86549
+ <div style="width: 24px; flex-shrink: 0; text-align: center; cursor: pointer;" title="Clique para selecionar o dispositivo">
86175
86550
  ${isSelected ? `<span style="color: ${colors2.success}; font-size: 14px;">\u2713</span>` : ""}
86176
86551
  </div>
86177
86552
  </div>
@@ -86553,7 +86928,11 @@ function renderLojasStep3(state6, modalId, colors2, t) {
86553
86928
  <span>Profile alvo: <strong style="color: ${colors2.text};">3F_MEDIDOR</strong></span>
86554
86929
  <span>deviceType: <strong style="color: ${colors2.text};">3F_MEDIDOR</strong></span>
86555
86930
  <span>deviceProfile: <strong style="color: ${colors2.text};">3F_MEDIDOR</strong></span>
86556
- <span>Rela\xE7\xE3o: <strong style="color: ${colors2.text};">CUSTOMER \u2192 DEVICE (Contains)</strong></span>
86931
+ <label style="display: flex; align-items: center; gap: 6px; cursor: pointer;"
86932
+ title="Marcado: for\xE7a a rela\xE7\xE3o Customer \u2192 Device. Desmarcado: n\xE3o altera rela\xE7\xF5es.">
86933
+ <input type="checkbox" id="${modalId}-lojas-apply-relation" ${state6.lojasApplyRelation ? "checked" : ""} style="accent-color: ${MYIO_PURPLE}; cursor: pointer;" />
86934
+ <span>Rela\xE7\xE3o: <strong style="color: ${colors2.text};">CUSTOMER \u2192 DEVICE (Contains)</strong></span>
86935
+ </label>
86557
86936
  </div>
86558
86937
  </div>
86559
86938
  `;
@@ -87117,6 +87496,8 @@ async function openClearGcdrIdsModal(state6) {
87117
87496
  }
87118
87497
  }
87119
87498
  function setupEventListeners3(container, state6, modalId, t, onClose) {
87499
+ if (container.dataset.upsellListenersBound === "true") return;
87500
+ container.dataset.upsellListenersBound = "true";
87120
87501
  const closeHandler = () => closeModal(container, onClose);
87121
87502
  const overlay = container.querySelector(".myio-upsell-modal-overlay");
87122
87503
  if (overlay) {
@@ -87232,10 +87613,18 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
87232
87613
  });
87233
87614
  });
87234
87615
  });
87616
+ container.querySelectorAll(".myio-copy-cell").forEach((cell) => {
87617
+ cell.addEventListener("click", (e) => {
87618
+ e.stopPropagation();
87619
+ const target = e.target;
87620
+ if (target.closest("button, input, .myio-info-btn, .myio-ts-btn")) return;
87621
+ copyCellValue(decodeURIComponent(cell.dataset.copy || ""), cell);
87622
+ });
87623
+ });
87235
87624
  document.getElementById(`${modalId}-device-search`)?.addEventListener("input", (e) => {
87236
87625
  const search = e.target.value.toLowerCase();
87237
87626
  state6.deviceSearchTerm = e.target.value;
87238
- filterDeviceListVisual(container, state6.devices, search, state6.deviceFilters, state6.deviceSort);
87627
+ filterDeviceListVisual(container, state6.devices, search, state6.deviceFilters, state6.deviceSort, state6);
87239
87628
  });
87240
87629
  document.getElementById(`${modalId}-device-type-filter`)?.addEventListener("change", (e) => {
87241
87630
  const select = e.target;
@@ -87457,15 +87846,17 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
87457
87846
  renderModal4(container, state6, modalId, t);
87458
87847
  setupEventListeners3(container, state6, modalId, t, onClose);
87459
87848
  });
87460
- document.getElementById(`${modalId}-check-fix`)?.addEventListener("click", async () => {
87849
+ document.getElementById(`${modalId}-check-fix`)?.addEventListener("click", () => {
87461
87850
  if (state6.checkFixLoading) return;
87462
- state6.checkFixLoading = true;
87463
- renderModal4(container, state6, modalId, t);
87464
- setupEventListeners3(container, state6, modalId, t, onClose);
87465
- await runCheckFixRoutine(state6, container, modalId, t, onClose);
87466
- state6.checkFixLoading = false;
87467
- renderModal4(container, state6, modalId, t);
87468
- setupEventListeners3(container, state6, modalId, t, onClose);
87851
+ openCheckFixScopeDialog(state6, async (scopeDevices) => {
87852
+ state6.checkFixLoading = true;
87853
+ renderModal4(container, state6, modalId, t);
87854
+ setupEventListeners3(container, state6, modalId, t, onClose);
87855
+ await runCheckFixRoutine(state6, container, modalId, t, onClose, scopeDevices);
87856
+ state6.checkFixLoading = false;
87857
+ renderModal4(container, state6, modalId, t);
87858
+ setupEventListeners3(container, state6, modalId, t, onClose);
87859
+ });
87469
87860
  });
87470
87861
  document.getElementById(`${modalId}-checkfix-filter`)?.addEventListener("change", (e) => {
87471
87862
  state6.checkFixFilter = e.target.value;
@@ -87563,35 +87954,7 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
87563
87954
  }
87564
87955
  });
87565
87956
  document.getElementById(`${modalId}-select-all`)?.addEventListener("click", () => {
87566
- const {
87567
- types: filterTypes,
87568
- deviceTypes: filterDeviceTypes,
87569
- deviceProfiles: filterDeviceProfiles,
87570
- statuses: filterStatuses,
87571
- telemetryKeys: filterTelemetryKeys
87572
- } = state6.deviceFilters;
87573
- let filteredDevices = state6.devices.filter((d) => {
87574
- if (filterTypes.length > 0 && !filterTypes.includes(d.type || "")) return false;
87575
- if (filterDeviceTypes.length > 0 && !filterDeviceTypes.includes(d.serverAttrs?.deviceType || ""))
87576
- return false;
87577
- if (filterDeviceProfiles.length > 0 && !filterDeviceProfiles.includes(d.serverAttrs?.deviceProfile || ""))
87578
- return false;
87579
- if (filterStatuses.length > 0) {
87580
- const status = d.latestTelemetry?.connectionStatus?.value || "offline";
87581
- if (!filterStatuses.includes(status)) return false;
87582
- }
87583
- if (filterTelemetryKeys.length > 0) {
87584
- const telem = d.latestTelemetry;
87585
- const hasMatch = filterTelemetryKeys.some((k) => {
87586
- if (k === "pulses") return telem?.pulses != null;
87587
- if (k === "consumption") return telem?.consumption != null;
87588
- return false;
87589
- });
87590
- if (!hasMatch) return false;
87591
- }
87592
- return true;
87593
- });
87594
- state6.selectedDevices = [...filteredDevices];
87957
+ state6.selectedDevices = [...getGridVisibleDevices(state6)];
87595
87958
  const listEl = document.getElementById(`${modalId}-device-list`);
87596
87959
  const savedScroll = listEl ? listEl.scrollTop : 0;
87597
87960
  renderModal4(container, state6, modalId, t);
@@ -87707,12 +88070,27 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
87707
88070
  document.getElementById(`${modalId}-bulk-profile-save`)?.addEventListener("click", async () => {
87708
88071
  await saveBulkProfile(state6, container, modalId, t, onClose);
87709
88072
  });
87710
- document.getElementById(`${modalId}-bulk-owner`)?.addEventListener("click", () => {
88073
+ document.getElementById(`${modalId}-bulk-owner`)?.addEventListener("click", async () => {
87711
88074
  if (!state6.selectedCustomer) {
87712
88075
  alert("Selecione um Customer primeiro no Step 1");
87713
88076
  return;
87714
88077
  }
87715
88078
  state6.bulkOwnerModal.open = true;
88079
+ if (!state6.bulkOwnerModal.targetCustomerId) {
88080
+ state6.bulkOwnerModal.targetCustomerId = state6.selectedCustomer.id?.id || "";
88081
+ }
88082
+ renderModal4(container, state6, modalId, t);
88083
+ setupEventListeners3(container, state6, modalId, t, onClose);
88084
+ if (state6.customers.length === 0) {
88085
+ await loadCustomers(state6, container, modalId, t, onClose);
88086
+ if (state6.bulkOwnerModal.open) {
88087
+ renderModal4(container, state6, modalId, t);
88088
+ setupEventListeners3(container, state6, modalId, t, onClose);
88089
+ }
88090
+ }
88091
+ });
88092
+ document.getElementById(`${modalId}-bulk-owner-customer`)?.addEventListener("change", (e) => {
88093
+ state6.bulkOwnerModal.targetCustomerId = e.target.value;
87716
88094
  renderModal4(container, state6, modalId, t);
87717
88095
  setupEventListeners3(container, state6, modalId, t, onClose);
87718
88096
  });
@@ -87757,6 +88135,10 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
87757
88135
  if (!state6.selectedCustomer || state6.selectedDevices.length === 0) return;
87758
88136
  await handleBulkSyncIngestionId(state6, container, modalId, t, onClose);
87759
88137
  });
88138
+ document.getElementById(`${modalId}-bulk-delete`)?.addEventListener("click", async () => {
88139
+ if (state6.selectedDevices.length === 0) return;
88140
+ await handleBulkDeleteDevices(state6, container, modalId, t, onClose);
88141
+ });
87760
88142
  document.getElementById(`${modalId}-clear-gcdr-ids`)?.addEventListener("click", () => {
87761
88143
  if (!state6.selectedCustomer) {
87762
88144
  alert("Selecione um Customer primeiro no Step 1");
@@ -87886,6 +88268,14 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
87886
88268
  });
87887
88269
  }
87888
88270
  });
88271
+ const applyRelCb = document.getElementById(
88272
+ `${modalId}-lojas-apply-relation`
88273
+ );
88274
+ if (applyRelCb) {
88275
+ applyRelCb.addEventListener("change", () => {
88276
+ state6.lojasApplyRelation = applyRelCb.checked;
88277
+ });
88278
+ }
87889
88279
  }
87890
88280
  document.getElementById(`${modalId}-change-owner`)?.addEventListener("click", () => {
87891
88281
  const form = document.getElementById(`${modalId}-change-owner-form`);
@@ -88107,7 +88497,8 @@ Total de devices no customer: ${ingestionDevices.length}`
88107
88497
  state6.devices,
88108
88498
  state6.deviceSearchTerm.toLowerCase(),
88109
88499
  state6.deviceFilters,
88110
- state6.deviceSort
88500
+ state6.deviceSort,
88501
+ state6
88111
88502
  );
88112
88503
  }
88113
88504
  }
@@ -88247,7 +88638,7 @@ function filterCustomerList(container, customers, search, selected, sort) {
88247
88638
  item.style.display = matches ? "flex" : "none";
88248
88639
  });
88249
88640
  }
88250
- function filterDeviceListVisual(container, devices, search, filters, sort) {
88641
+ function filterDeviceListVisual(container, devices, search, filters, sort, state6) {
88251
88642
  const listContainer = container.querySelector('[id$="-device-list"]');
88252
88643
  if (!listContainer) return;
88253
88644
  let filtered = devices.filter((d) => {
@@ -88267,7 +88658,7 @@ function filterDeviceListVisual(container, devices, search, filters, sort) {
88267
88658
  item.style.display = "none";
88268
88659
  return;
88269
88660
  }
88270
- const matchesSearch = !search || device.name?.toLowerCase().includes(search) || device.label?.toLowerCase().includes(search) || device.type?.toLowerCase().includes(search) || device.serverAttrs?.deviceType?.toLowerCase().includes(search) || device.serverAttrs?.deviceProfile?.toLowerCase().includes(search);
88661
+ const matchesSearch = !search || buildDeviceSearchHaystack(device, state6).includes(search);
88271
88662
  const el2 = item;
88272
88663
  const isTableRow = el2.tagName === "TR";
88273
88664
  el2.style.display = matchesSearch ? isTableRow ? "" : "flex" : "none";
@@ -88618,6 +89009,56 @@ async function loadLojasData(state6, container, modalId, t, onClose) {
88618
89009
  setupEventListeners3(container, state6, modalId, t, onClose);
88619
89010
  }
88620
89011
  }
89012
+ async function handleBulkDeleteDevices(state6, container, modalId, t, onClose) {
89013
+ const devices = [...state6.selectedDevices];
89014
+ if (devices.length === 0) return;
89015
+ const preview = devices.slice(0, 8).map((d) => `\u2022 ${d.name || d.label || getEntityId(d)}`).join("\n");
89016
+ const more = devices.length > 8 ? `
89017
+ \u2026 e mais ${devices.length - 8}` : "";
89018
+ const confirm1 = `\u26A0\uFE0F DELETAR ${devices.length} dispositivo(s) do ThingsBoard?
89019
+
89020
+ ${preview}${more}
89021
+
89022
+ Esta a\xE7\xE3o \xE9 IRREVERS\xCDVEL \u2014 os dispositivos, suas rela\xE7\xF5es e telemetria ser\xE3o removidos permanentemente.`;
89023
+ if (!confirm(confirm1)) return;
89024
+ if (!confirm(
89025
+ `Confirma\xE7\xE3o final: deletar ${devices.length} dispositivo(s)?
89026
+ Esta a\xE7\xE3o N\xC3O pode ser desfeita.`
89027
+ ))
89028
+ return;
89029
+ showBusyProgress(`Deletando ${devices.length} dispositivos...`, devices.length);
89030
+ let okCount = 0;
89031
+ let failCount = 0;
89032
+ const errors = [];
89033
+ const deletedIds = /* @__PURE__ */ new Set();
89034
+ for (let i = 0; i < devices.length; i++) {
89035
+ const d = devices[i];
89036
+ const id = getEntityId(d);
89037
+ try {
89038
+ await tbDelete(state6, `/api/device/${id}`);
89039
+ deletedIds.add(id);
89040
+ okCount++;
89041
+ } catch (err) {
89042
+ failCount++;
89043
+ errors.push(`${d.name || id}: ${err.message}`);
89044
+ }
89045
+ updateBusyProgress(i + 1);
89046
+ }
89047
+ hideBusyProgress();
89048
+ state6.devices = state6.devices.filter((d) => !deletedIds.has(getEntityId(d)));
89049
+ state6.selectedDevices = state6.selectedDevices.filter((d) => !deletedIds.has(getEntityId(d)));
89050
+ if (state6.selectedDevice && deletedIds.has(getEntityId(state6.selectedDevice))) {
89051
+ state6.selectedDevice = null;
89052
+ }
89053
+ renderModal4(container, state6, modalId, t);
89054
+ setupEventListeners3(container, state6, modalId, t, onClose);
89055
+ alert(
89056
+ `Dispositivos deletados: ${okCount}` + (failCount > 0 ? `
89057
+ Falhas: ${failCount}
89058
+ ${errors.slice(0, 5).join("\n")}` + (errors.length > 5 ? `
89059
+ \u2026 e mais ${errors.length - 5} erros` : "") : "")
89060
+ );
89061
+ }
88621
89062
  async function handleBulkSyncIngestionId(state6, container, modalId, t, onClose) {
88622
89063
  if (!state6.selectedCustomer || state6.selectedDevices.length === 0) return;
88623
89064
  const customerId = getEntityId(state6.selectedCustomer);
@@ -88764,15 +89205,17 @@ async function handleLojasApply(state6, container, modalId, t, onClose) {
88764
89205
  if (identifierInput) data[i].identifier = identifierInput.value;
88765
89206
  }
88766
89207
  const activeConfig = state6.lojasConfig ?? CUSTOM_MODES[0];
89208
+ const applyRelation = state6.lojasApplyRelation;
88767
89209
  const confirmMsg = `Aplicar configura\xE7\xE3o "${activeConfig.label}" para ${data.length} dispositivos?
88768
89210
 
88769
89211
  Cada device receber\xE1:
88770
89212
  - Label atualizado (etiqueta)
88771
89213
  - Profile: ${activeConfig.deviceProfile}
88772
89214
  - deviceType/deviceProfile: ${activeConfig.deviceType} / ${activeConfig.deviceProfile}
88773
- - Rela\xE7\xF5es existentes removidas
89215
+ ` + (applyRelation ? `- Rela\xE7\xF5es existentes removidas
88774
89216
  - Nova rela\xE7\xE3o: Customer \u2192 Device (Contains)
88775
-
89217
+ ` : `- Rela\xE7\xF5es N\xC3O ser\xE3o alteradas (checkbox de rela\xE7\xE3o desmarcado)
89218
+ `) + `
88776
89219
  Deseja continuar?`;
88777
89220
  if (!confirm(confirmMsg)) return;
88778
89221
  showBusyProgress(`Aplicando ${activeConfig.label}...`, data.length);
@@ -88800,31 +89243,33 @@ Deseja continuar?`;
88800
89243
  attrs.ingestionId = d.ingestionId;
88801
89244
  }
88802
89245
  await tbPost(state6, `/api/plugins/telemetry/DEVICE/${d.deviceId}/attributes/SERVER_SCOPE`, attrs);
88803
- if (d.currentRelations.length > 0) {
88804
- updateBusyProgress(i + 1, `[${i + 1}/${data.length}] ${d.name}: Removendo rela\xE7\xF5es...`);
88805
- for (const rel of d.currentRelations) {
88806
- try {
88807
- const params = new URLSearchParams({
88808
- fromId: rel.from.id,
88809
- fromType: rel.from.entityType,
88810
- toId: d.deviceId,
88811
- toType: "DEVICE",
88812
- relationType: rel.type || "Contains",
88813
- relationTypeGroup: rel.typeGroup || "COMMON"
88814
- });
88815
- await tbDelete(state6, `/api/relation?${params.toString()}`);
88816
- } catch (e) {
88817
- console.warn("[UpsellModal] Error deleting relation for LOJAS:", e);
89246
+ if (applyRelation) {
89247
+ if (d.currentRelations.length > 0) {
89248
+ updateBusyProgress(i + 1, `[${i + 1}/${data.length}] ${d.name}: Removendo rela\xE7\xF5es...`);
89249
+ for (const rel of d.currentRelations) {
89250
+ try {
89251
+ const params = new URLSearchParams({
89252
+ fromId: rel.from.id,
89253
+ fromType: rel.from.entityType,
89254
+ toId: d.deviceId,
89255
+ toType: "DEVICE",
89256
+ relationType: rel.type || "Contains",
89257
+ relationTypeGroup: rel.typeGroup || "COMMON"
89258
+ });
89259
+ await tbDelete(state6, `/api/relation?${params.toString()}`);
89260
+ } catch (e) {
89261
+ console.warn("[UpsellModal] Error deleting relation for LOJAS:", e);
89262
+ }
88818
89263
  }
88819
89264
  }
89265
+ updateBusyProgress(i + 1, `[${i + 1}/${data.length}] ${d.name}: Criando rela\xE7\xE3o...`);
89266
+ await tbPost(state6, "/api/relation", {
89267
+ from: { entityType: "CUSTOMER", id: customerId },
89268
+ to: { entityType: "DEVICE", id: d.deviceId },
89269
+ type: "Contains",
89270
+ typeGroup: "COMMON"
89271
+ });
88820
89272
  }
88821
- updateBusyProgress(i + 1, `[${i + 1}/${data.length}] ${d.name}: Criando rela\xE7\xE3o...`);
88822
- await tbPost(state6, "/api/relation", {
88823
- from: { entityType: "CUSTOMER", id: customerId },
88824
- to: { entityType: "DEVICE", id: d.deviceId },
88825
- type: "Contains",
88826
- typeGroup: "COMMON"
88827
- });
88828
89273
  successCount++;
88829
89274
  } catch (error) {
88830
89275
  errorCount++;
@@ -89112,8 +89557,111 @@ async function loadDeviceTelemetryInBatch(state6, container, modalId, t, onClose
89112
89557
  hideBusyProgress();
89113
89558
  }
89114
89559
  }
89115
- async function runCheckFixRoutine(state6, container, modalId, t, onClose) {
89116
- const devices = state6.devices;
89560
+ function openCheckFixScopeDialog(state6, onConfirm) {
89561
+ const DIALOG_ID = "myio-upsell-cf-scope-dialog";
89562
+ document.getElementById(DIALOG_ID)?.remove();
89563
+ const c = getThemeColors5(state6.theme);
89564
+ const allDevices = state6.devices;
89565
+ const allTypes = [...new Set(allDevices.map((d) => d.type).filter(Boolean))].sort();
89566
+ let namePattern = "";
89567
+ let caseSensitive = false;
89568
+ const selectedTypes = /* @__PURE__ */ new Set();
89569
+ function computeSubset() {
89570
+ let result = allDevices;
89571
+ if (namePattern) {
89572
+ const pat = caseSensitive ? namePattern : namePattern.toLowerCase();
89573
+ result = result.filter((d) => {
89574
+ const n = caseSensitive ? d.name || "" : (d.name || "").toLowerCase();
89575
+ return n.includes(pat);
89576
+ });
89577
+ }
89578
+ if (selectedTypes.size > 0) {
89579
+ result = result.filter((d) => selectedTypes.has(d.type || ""));
89580
+ }
89581
+ return result;
89582
+ }
89583
+ const overlay = document.createElement("div");
89584
+ overlay.id = DIALOG_ID;
89585
+ overlay.style.cssText = "position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,0.55);display:flex;align-items:center;justify-content:center;font-family:Roboto,Inter,system-ui,sans-serif;";
89586
+ overlay.innerHTML = `
89587
+ <div style="background:${c.surface};border-radius:12px;width:480px;max-width:92vw;max-height:88vh;
89588
+ overflow:hidden;display:flex;flex-direction:column;box-shadow:0 12px 40px rgba(0,0,0,0.4);">
89589
+ <div style="background:${MYIO_PURPLE};color:#fff;padding:12px 16px;font-size:14px;font-weight:700;">
89590
+ \u{1F52C} Escopo do CHECK &amp; FIX
89591
+ </div>
89592
+ <div style="padding:16px;overflow-y:auto;display:flex;flex-direction:column;gap:14px;">
89593
+ <div style="font-size:11px;color:${c.textMuted};">
89594
+ Os filtros abaixo se <strong>combinam (E)</strong>. Deixe ambos vazios para
89595
+ diagnosticar todos os ${allDevices.length} dispositivos.
89596
+ </div>
89597
+
89598
+ <div style="display:flex;flex-direction:column;gap:8px;">
89599
+ <span style="font-size:12px;font-weight:600;color:${c.text};">Filtro por nome</span>
89600
+ <input id="cf-scope-name-input" type="text" placeholder="nome cont\xE9m\u2026 (ex.: TEMP.)" style="
89601
+ font-size:12px;padding:7px 9px;border-radius:6px;border:1px solid ${c.border};
89602
+ background:${c.inputBg};color:${c.text};" />
89603
+ <label style="display:flex;gap:6px;align-items:center;font-size:11px;color:${c.textMuted};cursor:pointer;">
89604
+ <input id="cf-scope-case" type="checkbox" style="accent-color:${MYIO_PURPLE};" />
89605
+ Diferenciar mai\xFAsculas/min\xFAsculas (case-sensitive)
89606
+ </label>
89607
+ </div>
89608
+
89609
+ <div style="display:flex;flex-direction:column;gap:8px;">
89610
+ <span style="font-size:12px;font-weight:600;color:${c.text};">Filtro por tipo (device.type)</span>
89611
+ <div style="display:flex;flex-wrap:wrap;gap:6px;max-height:160px;overflow-y:auto;">
89612
+ ${allTypes.length === 0 ? `<span style="font-size:11px;color:${c.textMuted};">Nenhum tipo dispon\xEDvel.</span>` : allTypes.map(
89613
+ (tp) => `
89614
+ <label style="display:flex;gap:5px;align-items:center;font-size:11px;color:${c.text};
89615
+ cursor:pointer;border:1px solid ${c.border};border-radius:6px;padding:3px 8px;background:${c.cardBg};">
89616
+ <input type="checkbox" class="cf-scope-type-cb" value="${tp}" style="accent-color:${MYIO_PURPLE};" />
89617
+ ${tp}
89618
+ </label>`
89619
+ ).join("")}
89620
+ </div>
89621
+ </div>
89622
+ </div>
89623
+ <div style="padding:12px 16px;border-top:1px solid ${c.border};display:flex;justify-content:flex-end;gap:8px;">
89624
+ <button id="cf-scope-cancel" style="font-size:12px;font-weight:600;padding:8px 14px;border-radius:6px;
89625
+ border:1px solid ${c.border};background:${c.cardBg};color:${c.text};cursor:pointer;">Cancelar</button>
89626
+ <button id="cf-scope-run" style="font-size:12px;font-weight:700;padding:8px 14px;border-radius:6px;
89627
+ border:none;background:${MYIO_PURPLE};color:#fff;cursor:pointer;">Executar diagn\xF3stico (${allDevices.length})</button>
89628
+ </div>
89629
+ </div>`;
89630
+ document.body.appendChild(overlay);
89631
+ const runBtn = overlay.querySelector("#cf-scope-run");
89632
+ function refresh() {
89633
+ const n = computeSubset().length;
89634
+ runBtn.textContent = `Executar diagn\xF3stico (${n})`;
89635
+ runBtn.disabled = n === 0;
89636
+ runBtn.style.opacity = n === 0 ? "0.5" : "1";
89637
+ runBtn.style.cursor = n === 0 ? "not-allowed" : "pointer";
89638
+ }
89639
+ overlay.querySelector("#cf-scope-name-input").addEventListener("input", (e) => {
89640
+ namePattern = e.target.value;
89641
+ refresh();
89642
+ });
89643
+ overlay.querySelector("#cf-scope-case").addEventListener("change", (e) => {
89644
+ caseSensitive = e.target.checked;
89645
+ refresh();
89646
+ });
89647
+ overlay.querySelectorAll(".cf-scope-type-cb").forEach((cb) => {
89648
+ cb.addEventListener("change", () => {
89649
+ if (cb.checked) selectedTypes.add(cb.value);
89650
+ else selectedTypes.delete(cb.value);
89651
+ refresh();
89652
+ });
89653
+ });
89654
+ overlay.querySelector("#cf-scope-cancel").addEventListener("click", () => overlay.remove());
89655
+ runBtn.addEventListener("click", () => {
89656
+ const subset = computeSubset();
89657
+ if (subset.length === 0) return;
89658
+ overlay.remove();
89659
+ onConfirm(subset);
89660
+ });
89661
+ refresh();
89662
+ }
89663
+ async function runCheckFixRoutine(state6, container, modalId, t, onClose, scopeDevices) {
89664
+ const devices = scopeDevices ?? state6.devices;
89117
89665
  if (devices.length === 0) return;
89118
89666
  const BATCH_SIZE = 5;
89119
89667
  const BATCH_DELAY_MS = 1500;
@@ -89396,10 +89944,11 @@ ${errors.slice(0, 5).join("\n")}` + (errors.length > 5 ? `
89396
89944
  }
89397
89945
  async function saveBulkOwner(state6, container, modalId, t, onClose) {
89398
89946
  const devices = state6.selectedDevices;
89399
- const newCustomerId = state6.selectedCustomer?.id?.id;
89400
- const customerName = state6.selectedCustomer?.name || state6.selectedCustomer?.title || "Unknown";
89947
+ const newCustomerId = state6.bulkOwnerModal.targetCustomerId || state6.selectedCustomer?.id?.id;
89948
+ const targetCustomer = state6.customers.find((c) => c.id?.id === newCustomerId) || state6.selectedCustomer;
89949
+ const customerName = targetCustomer?.name || targetCustomer?.title || "Unknown";
89401
89950
  if (!newCustomerId) {
89402
- alert("Por favor, selecione um Customer no Step 1 primeiro.");
89951
+ alert("Por favor, selecione um Customer de destino.");
89403
89952
  return;
89404
89953
  }
89405
89954
  if (devices.length === 0) {
@@ -118810,10 +119359,10 @@ var AlarmsNotificationsPanelView = class {
118810
119359
  const renderListPanel = (group, items, sel) => {
118811
119360
  if (items.length === 0) return '<div class="afm-empty">Nenhum item encontrado</div>';
118812
119361
  return items.map((item) => {
118813
- const esc3 = this.esc(item);
119362
+ const esc4 = this.esc(item);
118814
119363
  return `<label class="afm-check-item">
118815
- <input type="checkbox" class="afm-checkbox" data-group="${group}" data-value="${esc3}"${sel.has(item) ? " checked" : ""}>
118816
- <span class="afm-check-label">${esc3}</span>
119364
+ <input type="checkbox" class="afm-checkbox" data-group="${group}" data-value="${esc4}"${sel.has(item) ? " checked" : ""}>
119365
+ <span class="afm-check-label">${esc4}</span>
118817
119366
  </label>`;
118818
119367
  }).join("");
118819
119368
  };
@@ -141096,6 +141645,7 @@ var version = package_default.version || "0.0.0";
141096
141645
  CONSUMPTION_CHART_DEFAULTS,
141097
141646
  CONSUMPTION_THEME_COLORS,
141098
141647
  CardGridPanel,
141648
+ ColumnSummaryTooltip,
141099
141649
  ConnectionStatusType,
141100
141650
  ContractSummaryTooltip,
141101
141651
  CustomerCardV1,