myio-js-library 0.1.486 → 0.1.489

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
@@ -1159,7 +1159,7 @@ module.exports = __toCommonJS(index_exports);
1159
1159
  // package.json
1160
1160
  var package_default = {
1161
1161
  name: "myio-js-library",
1162
- version: "0.1.486",
1162
+ version: "0.1.489",
1163
1163
  description: "A clean, standalone JS SDK for MYIO projects",
1164
1164
  license: "MIT",
1165
1165
  repository: "github:gh-myio/myio-js-library",
@@ -62014,7 +62014,7 @@ var SettingsModalView = class {
62014
62014
  <div class="customer-name-container">
62015
62015
  <div class="customer-info-row">
62016
62016
  <div class="device-type-icon-wrapper">
62017
- ${this.getDeviceTypeIcon(deviceType)}
62017
+ ${this.getDeviceTypeIcon(deviceType ?? "")}
62018
62018
  </div>
62019
62019
  <div class="customer-info-content">
62020
62020
  <div class="customer-name-label">Shopping</div>
@@ -62620,7 +62620,7 @@ var SettingsModalView = class {
62620
62620
  not_installed: { text: "N\xE3o instalado", color: "#94a3b8" },
62621
62621
  unknown: { text: "Sem informa\xE7\xE3o", color: "#94a3b8" }
62622
62622
  };
62623
- const statusInfo = statusMap[mapDeviceStatusToCardStatus(deviceStatus) || ""] || {
62623
+ const statusInfo = statusMap[mapDeviceStatusToCardStatus(deviceStatus ?? "") || ""] || {
62624
62624
  text: "Desconhecido",
62625
62625
  color: "#6b7280"
62626
62626
  };
@@ -62713,6 +62713,13 @@ var SettingsModalView = class {
62713
62713
  }
62714
62714
 
62715
62715
  /* Header handled by ModalHeader (RFC-0121) */
62716
+ .myio-device-settings-modal .myio-modal-header__title {
62717
+ font-size: 14px;
62718
+ font-weight: 600;
62719
+ }
62720
+ .myio-device-settings-modal .myio-modal-header__icon {
62721
+ font-size: 15px;
62722
+ }
62716
62723
 
62717
62724
  .myio-device-settings-modal.is-maximized {
62718
62725
  width: 100vw !important;
@@ -88352,6 +88359,9 @@ var UserListTab = class {
88352
88359
  loading = false;
88353
88360
  users = [];
88354
88361
  highlightedUserId = null;
88362
+ gcdrConfigs = /* @__PURE__ */ new Map();
88363
+ gcdrSyncing = /* @__PURE__ */ new Set();
88364
+ syncTooltipEl = null;
88355
88365
  constructor(config, callbacks) {
88356
88366
  this.config = config;
88357
88367
  this.callbacks = callbacks;
@@ -88377,6 +88387,8 @@ var UserListTab = class {
88377
88387
  <th>Nome</th>
88378
88388
  <th>E-mail</th>
88379
88389
  <th>Perfil</th>
88390
+ <th>Status</th>
88391
+ <th class="um-col-gcdr">GCDR</th>
88380
88392
  <th class="um-col-actions">A\xE7\xF5es</th>
88381
88393
  </tr>
88382
88394
  </thead>
@@ -88442,6 +88454,7 @@ var UserListTab = class {
88442
88454
  this.totalPages = page.totalPages;
88443
88455
  this.renderRows();
88444
88456
  this.renderPagination(page);
88457
+ this.fetchGcdrConfigsBatch();
88445
88458
  } catch (err) {
88446
88459
  console.error("[UserListTab] fetchUsers error", err);
88447
88460
  this.callbacks.showToast("Erro ao carregar usu\xE1rios. Tente novamente.", "error");
@@ -88468,11 +88481,36 @@ var UserListTab = class {
88468
88481
  }
88469
88482
  const name = [user.firstName, user.lastName].filter(Boolean).join(" ") || "\u2014";
88470
88483
  const role = user.authority === "TENANT_ADMIN" ? "Admin" : "Usu\xE1rio";
88484
+ const statusBadge = this.buildStatusBadge(user);
88485
+ const uid2 = user.id.id;
88471
88486
  tr.innerHTML = `
88472
88487
  <td>${this.esc(name)}</td>
88473
88488
  <td>${this.esc(user.email)}</td>
88474
88489
  <td><span class="um-badge um-badge--${user.authority === "TENANT_ADMIN" ? "admin" : "user"}">${role}</span></td>
88490
+ <td>${statusBadge}</td>
88491
+ <td class="um-col-gcdr">
88492
+ <span class="um-sync-icon um-sync-icon--loading" data-sync-uid="${uid2}">
88493
+ <span class="um-spinner" style="width:12px;height:12px;border-width:1.5px;display:block;margin:0 auto;"></span>
88494
+ </span>
88495
+ <button class="um-icon-btn um-force-sync-btn" title="Sincronizar com GCDR" style="margin-left:2px;">
88496
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
88497
+ stroke-linecap="round" stroke-linejoin="round">
88498
+ <polyline points="23 4 23 10 17 10"/>
88499
+ <polyline points="1 20 1 14 7 14"/>
88500
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
88501
+ </svg>
88502
+ </button>
88503
+ </td>
88475
88504
  <td class="um-col-actions">
88505
+ <button class="um-icon-btn um-assign-btn" title="Ver Fun\xE7\xF5es / Pap\xE9is" style="margin-right:4px;">
88506
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
88507
+ stroke-linecap="round" stroke-linejoin="round">
88508
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
88509
+ <circle cx="9" cy="7" r="4"/>
88510
+ <path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
88511
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
88512
+ </svg>
88513
+ </button>
88476
88514
  <button class="um-icon-btn um-detail-btn" title="Ver Detalhes">
88477
88515
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
88478
88516
  stroke-linecap="round" stroke-linejoin="round">
@@ -88482,6 +88520,11 @@ var UserListTab = class {
88482
88520
  </td>
88483
88521
  `;
88484
88522
  tr.querySelector(".um-detail-btn").addEventListener("click", () => this.callbacks.onOpenUserDetail(user, false));
88523
+ tr.querySelector(".um-assign-btn").addEventListener("click", (e) => this.showAssignmentsPopup(user, e.currentTarget));
88524
+ tr.querySelector(".um-force-sync-btn").addEventListener("click", () => this.syncUserToGCDR(user));
88525
+ const syncIcon = tr.querySelector(".um-sync-icon");
88526
+ syncIcon.addEventListener("mouseenter", (e) => this.showSyncTooltip(user, e.currentTarget));
88527
+ syncIcon.addEventListener("mouseleave", () => this.hideSyncTooltip());
88485
88528
  tbody.appendChild(tr);
88486
88529
  }
88487
88530
  }
@@ -88505,9 +88548,321 @@ var UserListTab = class {
88505
88548
  if (loadEl) loadEl.style.display = on ? "" : "none";
88506
88549
  if (tableWrap) tableWrap.style.opacity = on ? "0.5" : "1";
88507
88550
  }
88551
+ buildStatusBadge(user) {
88552
+ const enabled = user.additionalInfo?.userCredentialsEnabled;
88553
+ if (enabled === false) {
88554
+ return `<span class="um-badge um-badge--blocked">Bloqueado</span>`;
88555
+ }
88556
+ return `<span class="um-badge um-badge--active">Ativo</span>`;
88557
+ }
88558
+ gcdrHeaders() {
88559
+ const orch = window.MyIOOrchestrator;
88560
+ return {
88561
+ "Content-Type": "application/json",
88562
+ "X-API-Key": orch?.gcdrApiKey || "",
88563
+ "X-Tenant-ID": orch?.gcdrTenantId || ""
88564
+ };
88565
+ }
88566
+ gcdrBase() {
88567
+ return window.MyIOOrchestrator?.gcdrApiBaseUrl || "";
88568
+ }
88569
+ unwrapList(json) {
88570
+ if (Array.isArray(json)) return json;
88571
+ const j = json;
88572
+ if (j?.data && typeof j.data === "object") {
88573
+ const d = j.data;
88574
+ if (Array.isArray(d.items)) return d.items;
88575
+ if (Array.isArray(d)) return d;
88576
+ }
88577
+ if (Array.isArray(j?.items)) return j.items;
88578
+ return [];
88579
+ }
88580
+ async showAssignmentsPopup(user, anchor) {
88581
+ document.querySelector(".um-assign-popup")?.remove();
88582
+ const popup = document.createElement("div");
88583
+ popup.className = "um-assign-popup";
88584
+ popup.setAttribute("data-theme", this.config.theme || "light");
88585
+ const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || user.email;
88586
+ popup.innerHTML = `
88587
+ <div class="um-assign-popup-header">
88588
+ <span class="um-assign-popup-title">\u{1F511} Fun\xE7\xF5es \u2014 ${this.esc(displayName)}</span>
88589
+ <button class="um-assign-popup-close" type="button">\u2715</button>
88590
+ </div>
88591
+ <div class="um-assign-popup-body">
88592
+ <div class="um-assign-popup-loading" style="font-size:12px;color:var(--um-text-muted);padding:8px 0;">
88593
+ <span class="um-spinner" style="display:inline-block;"></span> Carregando...
88594
+ </div>
88595
+ </div>
88596
+ `;
88597
+ document.body.appendChild(popup);
88598
+ const rect = anchor.getBoundingClientRect();
88599
+ const popupW = 320;
88600
+ const left = Math.min(rect.right + 8, window.innerWidth - popupW - 12);
88601
+ popup.style.top = `${Math.max(rect.top - 10, 8)}px`;
88602
+ popup.style.left = `${left}px`;
88603
+ const close = () => popup.remove();
88604
+ popup.querySelector(".um-assign-popup-close").addEventListener("click", close);
88605
+ const outsideHandler = (e) => {
88606
+ if (!popup.contains(e.target) && e.target !== anchor) {
88607
+ close();
88608
+ document.removeEventListener("click", outsideHandler, true);
88609
+ }
88610
+ };
88611
+ setTimeout(() => document.addEventListener("click", outsideHandler, true), 50);
88612
+ const body = popup.querySelector(".um-assign-popup-body");
88613
+ const base = this.gcdrBase();
88614
+ if (!base) {
88615
+ body.innerHTML = `<div style="font-size:12px;color:var(--um-toast-err-text);padding:8px 0;">GCDR n\xE3o configurado.</div>`;
88616
+ return;
88617
+ }
88618
+ try {
88619
+ const res = await fetch(`${base}/authorization/users/${user.id.id}/assignments`, { headers: this.gcdrHeaders() });
88620
+ let assignments = [];
88621
+ if (res.ok) {
88622
+ const json = await res.json();
88623
+ assignments = Array.isArray(json) ? json : json.assignments ?? [];
88624
+ }
88625
+ if (assignments.length === 0) {
88626
+ body.innerHTML = `<div style="font-size:12px;color:var(--um-text-faint);padding:8px 0;">Nenhuma fun\xE7\xE3o atribu\xEDda.</div>`;
88627
+ return;
88628
+ }
88629
+ const statusColors = {
88630
+ active: "var(--um-badge-active-text)",
88631
+ expired: "var(--um-badge-blocked-text)",
88632
+ inactive: "var(--um-text-faint)"
88633
+ };
88634
+ body.innerHTML = assignments.map((a) => {
88635
+ const scopeLabel = a.scope === "*" ? "* (global)" : a.scope.startsWith("customer:") ? `Cliente (${a.scope.replace("customer:", "").slice(0, 8)}...)` : a.scope.startsWith("asset:") ? `Asset (${a.scope.replace("asset:", "").slice(0, 8)}...)` : this.esc(a.scope);
88636
+ const expires = a.expiresAt ? new Date(a.expiresAt).toLocaleDateString("pt-BR") : null;
88637
+ const color = statusColors[a.status] || "var(--um-text-faint)";
88638
+ return `
88639
+ <div class="um-assign-row">
88640
+ <span class="um-assign-role">${this.esc(a.roleDisplayName || a.roleKey)}</span>
88641
+ <span class="um-assign-meta">
88642
+ Escopo: ${scopeLabel}
88643
+ \xB7 <span style="color:${color};font-weight:600;">${a.status}</span>
88644
+ ${expires ? ` \xB7 Expira ${expires}` : ""}
88645
+ </span>
88646
+ </div>`;
88647
+ }).join("");
88648
+ } catch {
88649
+ body.innerHTML = `<div style="font-size:12px;color:var(--um-toast-err-text);padding:8px 0;">Erro ao carregar.</div>`;
88650
+ }
88651
+ }
88508
88652
  esc(s) {
88509
88653
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
88510
88654
  }
88655
+ // ── GCDR Sync Column ─────────────────────────────────────────────────────────
88656
+ async fetchGcdrConfigsBatch() {
88657
+ if (this.users.length === 0) return;
88658
+ const { tbBaseUrl, jwtToken } = this.config;
88659
+ await Promise.allSettled(this.users.map(async (user) => {
88660
+ const uid2 = user.id.id;
88661
+ try {
88662
+ const res = await fetch(
88663
+ `${tbBaseUrl}/api/plugins/telemetry/USER/${uid2}/values/attributes/SERVER_SCOPE?keys=gcdrUserConfigs`,
88664
+ { headers: { "X-Authorization": `Bearer ${jwtToken}` } }
88665
+ );
88666
+ let cfg = null;
88667
+ if (res.ok) {
88668
+ const attrs = await res.json();
88669
+ const entry = attrs.find((a) => a.key === "gcdrUserConfigs");
88670
+ if (entry?.value && typeof entry.value === "object") {
88671
+ cfg = entry.value;
88672
+ }
88673
+ }
88674
+ this.gcdrConfigs.set(uid2, cfg);
88675
+ this.updateSyncCell(uid2, cfg ? cfg.lastSyncResult ?? "none" : "none", cfg);
88676
+ } catch {
88677
+ this.updateSyncCell(uid2, "none", null);
88678
+ }
88679
+ }));
88680
+ }
88681
+ updateSyncCell(userId, state6, cfg = null) {
88682
+ const iconEl = this.el?.querySelector(`.um-sync-icon[data-sync-uid="${userId}"]`);
88683
+ if (!iconEl) return;
88684
+ iconEl.className = "um-sync-icon";
88685
+ if (state6 === "loading" || state6 === "syncing") {
88686
+ iconEl.innerHTML = `<span class="um-spinner" style="width:12px;height:12px;border-width:1.5px;display:block;margin:0 auto;"></span>`;
88687
+ return;
88688
+ }
88689
+ const gcdrStatus = cfg?.gcdrStatus;
88690
+ let dotClass = "um-sync-dot--none";
88691
+ let dotTitle = "Nunca sincronizado";
88692
+ if (state6 === "success") {
88693
+ dotClass = gcdrStatus === "ACTIVE" ? "um-sync-dot--active" : gcdrStatus === "INACTIVE" || gcdrStatus === "LOCKED" ? "um-sync-dot--warn" : "um-sync-dot--ok";
88694
+ dotTitle = `GCDR: ${gcdrStatus ?? "OK"}`;
88695
+ } else if (state6 === "error") {
88696
+ dotClass = "um-sync-dot--err";
88697
+ dotTitle = cfg?.lastError ?? "Erro";
88698
+ }
88699
+ iconEl.innerHTML = `<span class="um-sync-dot ${dotClass}" title="${this.esc(dotTitle)}"></span>`;
88700
+ }
88701
+ showSyncTooltip(user, anchor) {
88702
+ this.hideSyncTooltip();
88703
+ const uid2 = user.id.id;
88704
+ const cfg = this.gcdrConfigs.get(uid2);
88705
+ const tooltip = document.createElement("div");
88706
+ tooltip.className = "um-sync-tooltip";
88707
+ tooltip.setAttribute("data-theme", this.config.theme || "light");
88708
+ this.syncTooltipEl = tooltip;
88709
+ const syncing = this.gcdrSyncing.has(uid2);
88710
+ const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || user.email;
88711
+ let bodyHtml;
88712
+ if (syncing) {
88713
+ bodyHtml = `<div class="um-sync-tooltip-row" style="justify-content:center;padding:8px 0;">
88714
+ <span class="um-spinner" style="width:13px;height:13px;"></span>&nbsp;Sincronizando...
88715
+ </div>`;
88716
+ } else if (!cfg) {
88717
+ bodyHtml = `<div class="um-sync-tooltip-row um-sync-tooltip-row--muted">Nunca sincronizado com GCDR.</div>`;
88718
+ } else {
88719
+ const syncedAt = cfg.syncedAt ? new Date(cfg.syncedAt).toLocaleString("pt-BR") : "\u2014";
88720
+ const updatedAt = cfg.updatedAt ? new Date(cfg.updatedAt).toLocaleString("pt-BR") : "\u2014";
88721
+ const resultColor = cfg.lastSyncResult === "success" ? "var(--um-badge-active-text)" : "var(--um-badge-blocked-text)";
88722
+ bodyHtml = `
88723
+ <div class="um-sync-tooltip-row">
88724
+ <span class="um-sync-tooltip-label">GCDR ID</span>
88725
+ <span class="um-sync-tooltip-value" style="font-family:monospace;font-size:10px;">${cfg.gcdrUserId ? cfg.gcdrUserId.slice(0, 16) + "\u2026" : "\u2014"}</span>
88726
+ </div>
88727
+ <div class="um-sync-tooltip-row">
88728
+ <span class="um-sync-tooltip-label">Status GCDR</span>
88729
+ <span class="um-sync-tooltip-value">${cfg.gcdrStatus ?? "\u2014"}</span>
88730
+ </div>
88731
+ <div class="um-sync-tooltip-row">
88732
+ <span class="um-sync-tooltip-label">\xDAltimo sync</span>
88733
+ <span class="um-sync-tooltip-value">${syncedAt}</span>
88734
+ </div>
88735
+ <div class="um-sync-tooltip-row">
88736
+ <span class="um-sync-tooltip-label">Atualizado</span>
88737
+ <span class="um-sync-tooltip-value">${updatedAt}</span>
88738
+ </div>
88739
+ <div class="um-sync-tooltip-row">
88740
+ <span class="um-sync-tooltip-label">Qtd syncs</span>
88741
+ <span class="um-sync-tooltip-value">${cfg.syncCount ?? 0}</span>
88742
+ </div>
88743
+ <div class="um-sync-tooltip-row">
88744
+ <span class="um-sync-tooltip-label">Resultado</span>
88745
+ <span class="um-sync-tooltip-value" style="color:${resultColor};font-weight:700;">${cfg.lastSyncResult === "success" ? "\u2713 Sucesso" : "\u2717 Erro"}</span>
88746
+ </div>
88747
+ ${cfg.lastError ? `<div class="um-sync-tooltip-row um-sync-tooltip-row--error">
88748
+ <span class="um-sync-tooltip-label">Erro</span>
88749
+ <span class="um-sync-tooltip-value">${this.esc(cfg.lastError)}</span>
88750
+ </div>` : ""}
88751
+ `;
88752
+ }
88753
+ tooltip.innerHTML = `
88754
+ <div class="um-sync-tooltip-header">
88755
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
88756
+ stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;">
88757
+ <polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/>
88758
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
88759
+ </svg>
88760
+ <span>GCDR Sync \u2014 ${this.esc(displayName)}</span>
88761
+ </div>
88762
+ <div class="um-sync-tooltip-body">${bodyHtml}</div>
88763
+ `;
88764
+ const backdrop = this.el.closest(".um-backdrop") ?? document.body;
88765
+ backdrop.appendChild(tooltip);
88766
+ const rect = anchor.getBoundingClientRect();
88767
+ const ttW = 260;
88768
+ let left = rect.right + 8;
88769
+ if (left + ttW > window.innerWidth - 8) left = rect.left - ttW - 8;
88770
+ tooltip.style.left = `${Math.max(4, left)}px`;
88771
+ tooltip.style.top = `${Math.max(4, rect.top - 10)}px`;
88772
+ }
88773
+ hideSyncTooltip() {
88774
+ this.syncTooltipEl?.remove();
88775
+ this.syncTooltipEl = null;
88776
+ }
88777
+ async syncUserToGCDR(user) {
88778
+ const uid2 = user.id.id;
88779
+ if (this.gcdrSyncing.has(uid2)) return;
88780
+ const base = this.gcdrBase();
88781
+ if (!base) {
88782
+ this.callbacks.showToast("GCDR n\xE3o configurado.", "error");
88783
+ return;
88784
+ }
88785
+ this.gcdrSyncing.add(uid2);
88786
+ this.updateSyncCell(uid2, "syncing");
88787
+ const prev = this.gcdrConfigs.get(uid2) ?? null;
88788
+ const syncCount = (prev?.syncCount ?? 0) + 1;
88789
+ const now = (/* @__PURE__ */ new Date()).toISOString();
88790
+ try {
88791
+ const searchRes = await fetch(
88792
+ `${base}/api/v1/users?search=${encodeURIComponent(user.email)}&customerId=${encodeURIComponent(this.config.customerId)}&limit=10`,
88793
+ { headers: this.gcdrHeaders() }
88794
+ );
88795
+ let gcdrUser = null;
88796
+ if (searchRes.ok) {
88797
+ const data = await searchRes.json();
88798
+ const items = Array.isArray(data) ? data : data?.data?.items ?? data?.items ?? [];
88799
+ gcdrUser = items.find((u) => u.email?.toLowerCase() === user.email.toLowerCase()) ?? null;
88800
+ }
88801
+ if (!gcdrUser) {
88802
+ const createRes = await fetch(`${base}/api/v1/users`, {
88803
+ method: "POST",
88804
+ headers: this.gcdrHeaders(),
88805
+ body: JSON.stringify({
88806
+ email: user.email,
88807
+ firstName: user.firstName || "",
88808
+ lastName: user.lastName || "",
88809
+ customerId: this.config.customerId,
88810
+ type: "CUSTOMER"
88811
+ })
88812
+ });
88813
+ if (!createRes.ok) throw new Error(`Criar usu\xE1rio GCDR: HTTP ${createRes.status}`);
88814
+ gcdrUser = await createRes.json();
88815
+ }
88816
+ const configs = {
88817
+ gcdrUserId: gcdrUser.id,
88818
+ gcdrStatus: gcdrUser.status,
88819
+ gcdrType: gcdrUser.type,
88820
+ syncedAt: now,
88821
+ syncCount,
88822
+ lastSyncResult: "success",
88823
+ lastError: null,
88824
+ createdAt: prev?.createdAt ?? now,
88825
+ updatedAt: now
88826
+ };
88827
+ await this.saveTBAttribute(uid2, configs);
88828
+ this.gcdrConfigs.set(uid2, configs);
88829
+ this.updateSyncCell(uid2, "success", configs);
88830
+ this.callbacks.showToast(`Sync GCDR conclu\xEDdo \u2014 ${user.email}`, "success");
88831
+ } catch (err) {
88832
+ const configs = {
88833
+ ...prev ?? {},
88834
+ syncedAt: now,
88835
+ syncCount,
88836
+ lastSyncResult: "error",
88837
+ lastError: String(err?.message ?? "Erro desconhecido"),
88838
+ updatedAt: now
88839
+ };
88840
+ try {
88841
+ await this.saveTBAttribute(uid2, configs);
88842
+ } catch {
88843
+ }
88844
+ this.gcdrConfigs.set(uid2, configs);
88845
+ this.updateSyncCell(uid2, "error", configs);
88846
+ this.callbacks.showToast(`Erro sync GCDR: ${err?.message || "Falha"}`, "error");
88847
+ } finally {
88848
+ this.gcdrSyncing.delete(uid2);
88849
+ }
88850
+ }
88851
+ async saveTBAttribute(userId, configs) {
88852
+ const { tbBaseUrl, jwtToken } = this.config;
88853
+ const res = await fetch(
88854
+ `${tbBaseUrl}/api/plugins/telemetry/USER/${userId}/attributes/SERVER_SCOPE`,
88855
+ {
88856
+ method: "POST",
88857
+ headers: {
88858
+ "X-Authorization": `Bearer ${jwtToken}`,
88859
+ "Content-Type": "application/json"
88860
+ },
88861
+ body: JSON.stringify({ gcdrUserConfigs: configs })
88862
+ }
88863
+ );
88864
+ if (!res.ok) throw new Error(`Salvar atributo TB: HTTP ${res.status}`);
88865
+ }
88511
88866
  };
88512
88867
 
88513
88868
  // src/components/premium-modals/user-management/tabs/NewUserTab.ts
@@ -89971,10 +90326,10 @@ var UserDetailTab = class {
89971
90326
  section.style.cssText = "margin-top:20px;border:1px solid var(--um-border);border-radius:10px;overflow:hidden;";
89972
90327
  const sectionHeader = document.createElement("div");
89973
90328
  sectionHeader.style.cssText = "display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--um-accent);border-bottom:1px solid var(--um-btn-2-border);";
89974
- sectionHeader.innerHTML = `<span style="font-size:13px;font-weight:600;color:#fff;">\u{1F511} Atribui\xE7\xF5es de Fun\xE7\xF5es</span>`;
90329
+ sectionHeader.innerHTML = `<span style="font-size:13px;font-weight:600;color:#fff;">\u{1F511} Fun\xE7\xF5es / Pap\xE9is</span>`;
89975
90330
  const addBtn = document.createElement("button");
89976
90331
  addBtn.className = "um-btn um-btn--secondary um-btn--sm";
89977
- addBtn.textContent = "+ Atribuir Fun\xE7\xE3o";
90332
+ addBtn.textContent = "+ Adicionar";
89978
90333
  addBtn.addEventListener("click", () => this.showAssignForm());
89979
90334
  sectionHeader.appendChild(addBtn);
89980
90335
  section.appendChild(sectionHeader);
@@ -89994,7 +90349,12 @@ var UserDetailTab = class {
89994
90349
  fetch(`${this.gcdrBase()}/roles?limit=100`, { headers: this.gcdrHeaders() }),
89995
90350
  fetch(`${this.gcdrBase()}/policies?limit=100`, { headers: this.gcdrHeaders() })
89996
90351
  ]);
89997
- this.assignments = assignRes.ok ? this.unwrapList(await assignRes.json()) : [];
90352
+ if (assignRes.ok) {
90353
+ const assignJson = await assignRes.json();
90354
+ this.assignments = Array.isArray(assignJson) ? assignJson : assignJson.assignments ?? [];
90355
+ } else {
90356
+ this.assignments = [];
90357
+ }
89998
90358
  this.availableRoles = rolesRes.ok ? this.unwrapList(await rolesRes.json()) : [];
89999
90359
  this.availablePolicies = policiesRes.ok ? this.unwrapList(await policiesRes.json()) : [];
90000
90360
  this.renderAssignments();
@@ -90021,11 +90381,12 @@ var UserDetailTab = class {
90021
90381
  const tbody = document.createElement("tbody");
90022
90382
  this.assignments.forEach((a) => {
90023
90383
  const tr = document.createElement("tr");
90024
- const statusColor = a.status === "active" ? "var(--um-badge-user-text)" : a.status === "expired" ? "var(--um-btn-danger-text)" : "var(--um-text-faint)";
90384
+ const statusColor = a.status === "active" ? "var(--um-badge-active-text)" : a.status === "expired" ? "var(--um-badge-blocked-text)" : "var(--um-text-faint)";
90025
90385
  const expiresAt = a.expiresAt ? new Date(a.expiresAt).toLocaleDateString("pt-BR") : "\u2014";
90386
+ const scopeLabel = a.scope === "*" ? "* (global)" : a.scope.startsWith("customer:") ? `Cliente (${a.scope.replace("customer:", "").slice(0, 8)}...)` : a.scope.startsWith("asset:") ? `Asset (${a.scope.replace("asset:", "").slice(0, 8)}...)` : a.scope;
90026
90387
  tr.innerHTML = `
90027
90388
  <td style="font-weight:500;">${this.esc(a.roleDisplayName || a.roleKey)}</td>
90028
- <td><code style="font-size:10px;">${this.esc(a.scope)}</code></td>
90389
+ <td style="font-size:12px;">${this.esc(scopeLabel)}</td>
90029
90390
  <td><span style="color:${statusColor};font-weight:600;">${a.status}</span></td>
90030
90391
  <td>${expiresAt}</td>
90031
90392
  <td style="text-align:center;"><button class="um-btn um-btn--danger um-btn--sm revoke-btn">Revogar</button></td>
@@ -90044,14 +90405,19 @@ var UserDetailTab = class {
90044
90405
  overlay.style.zIndex = "100001";
90045
90406
  const modal = document.createElement("div");
90046
90407
  modal.className = "um-modal";
90047
- modal.style.cssText = "padding: 24px; width: min(480px, 92vw); max-height: 80vh; height: auto; aspect-ratio: unset; overflow-y: auto; display: block;";
90408
+ modal.style.cssText = "width: min(820px, 92vw); max-height: 85vh; height: auto; aspect-ratio: unset; overflow: hidden; display: flex; flex-direction: column;";
90048
90409
  const gcdrCid = window.MyIOOrchestrator?.gcdrCustomerId || "";
90049
90410
  const scopeOptions = [
90050
- { value: "*", label: "* (global)" },
90051
- ...gcdrCid ? [{ value: `customer:${gcdrCid}`, label: `customer:${gcdrCid}` }] : []
90411
+ { value: "*", label: "* (global \u2014 todos os clientes)" },
90412
+ ...gcdrCid ? [{ value: `customer:${gcdrCid}`, label: `Cliente atual (${gcdrCid.slice(0, 8)}...)` }] : []
90052
90413
  ];
90053
90414
  modal.innerHTML = `
90054
- <h4 style="margin:0 0 16px;font-size:15px;font-weight:600;color:var(--um-text-primary,#e2e8f0);">Atribuir Fun\xE7\xE3o</h4>
90415
+ <div style="display:flex;align-items:center;gap:10px;padding:14px 20px;background:var(--um-accent);border-bottom:1px solid var(--um-btn-2-border);flex-shrink:0;">
90416
+ <span style="font-size:15px;">\u{1F511}</span>
90417
+ <span style="flex:1;font-size:14px;font-weight:600;color:#fff;">Atribuir Fun\xE7\xE3o / Papel</span>
90418
+ <button type="button" class="assign-close" style="background:none;border:none;color:rgba(255,255,255,0.8);font-size:18px;cursor:pointer;padding:2px 6px;border-radius:4px;line-height:1;">\u2715</button>
90419
+ </div>
90420
+ <div style="padding:20px 24px;overflow-y:auto;flex:1;">
90055
90421
  <div class="um-form" style="max-width:100%;">
90056
90422
  <div class="um-form-group">
90057
90423
  <label class="um-label">Fun\xE7\xE3o <span class="um-req">*</span></label>
@@ -90084,6 +90450,7 @@ var UserDetailTab = class {
90084
90450
  <button class="um-btn um-btn--primary assign-save">Atribuir</button>
90085
90451
  </div>
90086
90452
  </div>
90453
+ </div>
90087
90454
  `;
90088
90455
  overlay.appendChild(modal);
90089
90456
  document.body.appendChild(overlay);
@@ -90114,6 +90481,7 @@ var UserDetailTab = class {
90114
90481
  overlay.addEventListener("click", (e) => {
90115
90482
  if (e.target === overlay) close();
90116
90483
  });
90484
+ modal.querySelector(".assign-close").addEventListener("click", close);
90117
90485
  modal.querySelector(".assign-cancel").addEventListener("click", close);
90118
90486
  modal.querySelector(".assign-save").addEventListener("click", async () => {
90119
90487
  const roleId = modal.querySelector("[name=roleId]").value.trim();
@@ -90128,13 +90496,14 @@ var UserDetailTab = class {
90128
90496
  const expiresAt = expiresAtRaw ? new Date(expiresAtRaw).toISOString() : null;
90129
90497
  const reason = modal.querySelector("[name=reason]").value.trim() || null;
90130
90498
  const role = this.availableRoles.find((r) => r.id === roleId);
90499
+ const roleKey = role?.key || role?.id || roleId;
90131
90500
  const btn = modal.querySelector(".assign-save");
90132
90501
  btn.disabled = true;
90133
90502
  btn.textContent = "...";
90134
90503
  try {
90135
90504
  const body = {
90136
90505
  userId: this.user.id.id,
90137
- roleId,
90506
+ roleKey,
90138
90507
  scope,
90139
90508
  expiresAt,
90140
90509
  reason
@@ -91146,6 +91515,13 @@ var UserManagementModalView = class {
91146
91515
  }
91147
91516
  // ── Styles ───────────────────────────────────────────────────────────────────
91148
91517
  injectStyles() {
91518
+ if (!document.getElementById("um-font-nunito")) {
91519
+ const link = document.createElement("link");
91520
+ link.id = "um-font-nunito";
91521
+ link.rel = "stylesheet";
91522
+ link.href = "https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700&display=swap";
91523
+ document.head.appendChild(link);
91524
+ }
91149
91525
  if (document.getElementById("um-styles")) return;
91150
91526
  const style = document.createElement("style");
91151
91527
  style.id = "um-styles";
@@ -91181,6 +91557,12 @@ var UserManagementModalView = class {
91181
91557
  --um-badge-admin-text: #3b5bdb;
91182
91558
  --um-badge-user-bg: #f0fdf4;
91183
91559
  --um-badge-user-text: #16a34a;
91560
+ --um-badge-active-bg: #dcfce7;
91561
+ --um-badge-active-text: #15803d;
91562
+ --um-badge-blocked-bg: #fee2e2;
91563
+ --um-badge-blocked-text: #b91c1c;
91564
+ --um-badge-pending-bg: #fef9c3;
91565
+ --um-badge-pending-text: #854d0e;
91184
91566
  --um-toast-ok-bg: #f0fdf4;
91185
91567
  --um-toast-ok-border: #22c55e;
91186
91568
  --um-toast-ok-text: #16a34a;
@@ -91241,6 +91623,12 @@ var UserManagementModalView = class {
91241
91623
  --um-badge-admin-text: #60a5fa;
91242
91624
  --um-badge-user-bg: #1a2d24;
91243
91625
  --um-badge-user-text: #4ade80;
91626
+ --um-badge-active-bg: #14532d;
91627
+ --um-badge-active-text: #86efac;
91628
+ --um-badge-blocked-bg: #450a0a;
91629
+ --um-badge-blocked-text: #fca5a5;
91630
+ --um-badge-pending-bg: #422006;
91631
+ --um-badge-pending-text: #fde68a;
91244
91632
  --um-toast-ok-bg: #1e3a2e;
91245
91633
  --um-toast-ok-border: #22c55e;
91246
91634
  --um-toast-ok-text: #4ade80;
@@ -91269,14 +91657,22 @@ var UserManagementModalView = class {
91269
91657
  /* \u2500\u2500 Component styles (theme-agnostic via variables) \u2500\u2500 */
91270
91658
  .um-modal {
91271
91659
  background: var(--um-modal-bg);
91272
- border-radius: 14px; width: 92vw; max-width: 960px;
91660
+ font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
91661
+ border-radius: 14px; width: 96vw; max-width: 1200px;
91273
91662
  --modal-header-radius: 14px 14px 0 0;
91274
- aspect-ratio: 16/9; display: flex; flex-direction: column;
91663
+ height: 84vh; display: flex; flex-direction: column;
91275
91664
  box-shadow: var(--um-shadow); overflow: hidden; position: relative;
91276
91665
  }
91277
- @media (max-height: 600px) { .um-modal { aspect-ratio: unset; height: 90vh; } }
91666
+ @media (max-height: 600px) { .um-modal { height: 96vh; } }
91278
91667
 
91279
91668
  /* Header handled by ModalHeader (RFC-0121) */
91669
+ .um-modal .myio-modal-header__title {
91670
+ font-size: 14px !important;
91671
+ font-weight: 600 !important;
91672
+ }
91673
+ .um-modal .myio-modal-header__icon {
91674
+ font-size: 15px !important;
91675
+ }
91280
91676
 
91281
91677
  /* Force MyIO purple header regardless of light/dark theme \u2014 overrides myio-modal-header--light */
91282
91678
  .um-modal .myio-modal-header--light {
@@ -91375,16 +91771,83 @@ var UserManagementModalView = class {
91375
91771
  .um-table { width: 100%; border-collapse: collapse; font-size: 13px; }
91376
91772
  .um-table th {
91377
91773
  text-align: left; padding: 8px 12px;
91378
- color: var(--um-text-muted); font-weight: 500; font-size: 11px;
91774
+ color: var(--um-text-muted); font-weight: 600; font-size: 11px;
91379
91775
  text-transform: uppercase; letter-spacing: 0.04em;
91380
91776
  border-bottom: 1px solid var(--um-border);
91381
91777
  }
91382
91778
  .um-table td {
91383
- padding: 10px 12px; color: var(--um-text-secondary);
91779
+ padding: 10px 12px; color: var(--um-text-primary); font-weight: 500;
91384
91780
  border-bottom: 1px solid var(--um-border-sub);
91385
91781
  }
91386
91782
  .um-table tr:hover td { background: var(--um-bg-surface); }
91387
- .um-col-actions { width: 80px; text-align: center; }
91783
+ .um-col-actions { width: 100px; text-align: center; }
91784
+ .um-col-gcdr { width: 72px; text-align: center; }
91785
+
91786
+ /* Sync status dot */
91787
+ .um-sync-icon { display: inline-flex; align-items: center; justify-content: center; }
91788
+ .um-sync-dot {
91789
+ display: inline-block; width: 10px; height: 10px; border-radius: 50%;
91790
+ flex-shrink: 0;
91791
+ }
91792
+ .um-sync-dot--none { background: var(--um-text-faint); opacity: 0.4; }
91793
+ .um-sync-dot--ok,
91794
+ .um-sync-dot--active { background: var(--um-badge-active-text); }
91795
+ .um-sync-dot--warn { background: var(--um-badge-pending-text); }
91796
+ .um-sync-dot--err { background: var(--um-badge-blocked-text); }
91797
+
91798
+ /* Premium sync tooltip */
91799
+ .um-sync-tooltip {
91800
+ position: fixed; z-index: 100020;
91801
+ background: var(--um-modal-bg); border: 1px solid var(--um-border);
91802
+ border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.2);
91803
+ width: 260px; pointer-events: none;
91804
+ font-family: 'Nunito', -apple-system, sans-serif;
91805
+ }
91806
+ .um-sync-tooltip-header {
91807
+ display: flex; align-items: center; gap: 7px;
91808
+ padding: 8px 12px; background: var(--um-accent);
91809
+ border-radius: 10px 10px 0 0;
91810
+ font-size: 11px; font-weight: 700; color: #fff;
91811
+ }
91812
+ .um-sync-tooltip-body {
91813
+ padding: 10px 12px; display: flex; flex-direction: column; gap: 4px;
91814
+ }
91815
+ .um-sync-tooltip-row {
91816
+ display: flex; justify-content: space-between; align-items: flex-start;
91817
+ font-size: 11px; gap: 8px;
91818
+ }
91819
+ .um-sync-tooltip-row--muted { color: var(--um-text-faint); justify-content: center; padding: 4px 0; }
91820
+ .um-sync-tooltip-row--error .um-sync-tooltip-value { color: var(--um-badge-blocked-text); word-break: break-all; }
91821
+ .um-sync-tooltip-label { color: var(--um-text-muted); flex-shrink: 0; }
91822
+ .um-sync-tooltip-value { color: var(--um-text-primary); font-weight: 600; text-align: right; }
91823
+
91824
+ /* Assignments quick-view popup */
91825
+ .um-assign-popup {
91826
+ position: fixed; z-index: 100010;
91827
+ background: var(--um-modal-bg); border: 1px solid var(--um-border);
91828
+ border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.18);
91829
+ width: 320px; max-height: 340px; overflow-y: auto;
91830
+ font-family: 'Nunito', -apple-system, sans-serif;
91831
+ }
91832
+ .um-assign-popup-header {
91833
+ display: flex; align-items: center; justify-content: space-between;
91834
+ padding: 10px 14px; background: var(--um-accent);
91835
+ border-radius: 10px 10px 0 0;
91836
+ }
91837
+ .um-assign-popup-title { font-size: 12px; font-weight: 700; color: #fff; }
91838
+ .um-assign-popup-close {
91839
+ background: none; border: none; color: rgba(255,255,255,0.8);
91840
+ font-size: 14px; cursor: pointer; padding: 0 4px; line-height: 1;
91841
+ }
91842
+ .um-assign-popup-body { padding: 10px 14px; }
91843
+ .um-assign-row {
91844
+ display: flex; flex-direction: column; gap: 2px;
91845
+ padding: 7px 0; border-bottom: 1px solid var(--um-border-sub);
91846
+ font-size: 12px;
91847
+ }
91848
+ .um-assign-row:last-child { border-bottom: none; }
91849
+ .um-assign-role { font-weight: 600; color: var(--um-text-primary); }
91850
+ .um-assign-meta { color: var(--um-text-muted); font-size: 11px; }
91388
91851
  .um-row--highlight td { background: var(--um-row-highlight) !important; }
91389
91852
 
91390
91853
  .um-list-empty, .um-list-loading, .um-profiles-loading,
@@ -91420,8 +91883,11 @@ var UserManagementModalView = class {
91420
91883
  .um-btn--sm { padding: 6px 12px; font-size: 12px; }
91421
91884
 
91422
91885
  .um-badge { display: inline-block; font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 9999px; }
91423
- .um-badge--admin { background: var(--um-badge-admin-bg); color: var(--um-badge-admin-text); }
91424
- .um-badge--user { background: var(--um-badge-user-bg); color: var(--um-badge-user-text); }
91886
+ .um-badge--admin { background: var(--um-badge-admin-bg); color: var(--um-badge-admin-text); }
91887
+ .um-badge--user { background: var(--um-badge-user-bg); color: var(--um-badge-user-text); }
91888
+ .um-badge--active { background: var(--um-badge-active-bg); color: var(--um-badge-active-text); }
91889
+ .um-badge--blocked { background: var(--um-badge-blocked-bg); color: var(--um-badge-blocked-text); }
91890
+ .um-badge--pending { background: var(--um-badge-pending-bg); color: var(--um-badge-pending-text); }
91425
91891
 
91426
91892
  .um-form { display: flex; flex-direction: column; gap: 14px;}
91427
91893
  .um-form-row { display: flex; gap: 12px; }
@@ -91442,7 +91908,7 @@ var UserManagementModalView = class {
91442
91908
  .um-check-label { font-size: 13px; color: var(--um-text-secondary); cursor: pointer; display: flex; align-items: center; gap: 8px; }
91443
91909
  .um-form-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }
91444
91910
 
91445
- .um-detail-card { max-width: 560px; }
91911
+ .um-detail-card { width: 100%; }
91446
91912
  .um-detail-section { border: 1px solid var(--um-border); border-radius: 10px; overflow: hidden; margin-bottom: 20px; }
91447
91913
  .um-detail-row {
91448
91914
  display: flex; align-items: flex-start; gap: 16px;
@@ -136064,7 +136530,7 @@ function bindFilterBar(card, bundle, getViewMode) {
136064
136530
  const lower = text.toLowerCase();
136065
136531
  const types = /* @__PURE__ */ new Set();
136066
136532
  bundle.devices.forEach((d) => {
136067
- const name = (d.displayName || d.name).toLowerCase();
136533
+ const name = (d.entityLabel || d.displayName || d.name).toLowerCase();
136068
136534
  if (!lower || name.includes(lower) || d.type.toLowerCase().includes(lower)) {
136069
136535
  types.add(d.type);
136070
136536
  }
@@ -136140,7 +136606,7 @@ function renderDevice(device, rules, viewMode = "granular") {
136140
136606
  <div class="abm-device-header">
136141
136607
  <span class="abm-chevron">\u25BE</span>
136142
136608
  <span style="font-size:16px;">\u{1F4E1}</span>
136143
- <span class="abm-device-name">${escHtml3(device.displayName || device.name)}</span>
136609
+ <span class="abm-device-name">${escHtml3(device.entityLabel || device.displayName || device.name)}</span>
136144
136610
  ${_alarmBadgeHtml(alarmCount)}
136145
136611
  <span class="abm-device-type">${escHtml3(device.type)}</span>
136146
136612
  </div>
@@ -136190,7 +136656,7 @@ function renderByRuleView(bundle) {
136190
136656
  return `
136191
136657
  <li class="abm-rule-group-device" data-device-id="${escHtml3(d.id)}">
136192
136658
  <span style="font-size:13px;">\u{1F4E1}</span>
136193
- <span>${escHtml3(d.displayName || d.name)}</span>
136659
+ <span>${escHtml3(d.entityLabel || d.displayName || d.name)}</span>
136194
136660
  <span style="font-size:11px;color:#888;">${escHtml3(d.type)}</span>
136195
136661
  ${overridePart}
136196
136662
  </li>`;