myio-js-library 0.1.430 → 0.1.433

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
@@ -1075,6 +1075,7 @@ __export(index_exports, {
1075
1075
  openTemperatureSettingsModal: () => openTemperatureSettingsModal,
1076
1076
  openTutorialModal: () => openTutorialModal,
1077
1077
  openUpsellModal: () => openUpsellModal,
1078
+ openUserManagementModal: () => openUserManagementModal,
1078
1079
  openWelcomeModal: () => openWelcomeModal,
1079
1080
  parseInputDateToDate: () => parseInputDateToDate,
1080
1081
  periodKey: () => periodKey,
@@ -1137,7 +1138,7 @@ module.exports = __toCommonJS(index_exports);
1137
1138
  // package.json
1138
1139
  var package_default = {
1139
1140
  name: "myio-js-library",
1140
- version: "0.1.430",
1141
+ version: "0.1.432",
1141
1142
  description: "A clean, standalone JS SDK for MYIO projects",
1142
1143
  license: "MIT",
1143
1144
  repository: "github:gh-myio/myio-js-library",
@@ -24519,7 +24520,8 @@ var EnergyDataFetcher = class {
24519
24520
  */
24520
24521
  buildEnergyApiUrl(params) {
24521
24522
  const baseUrl = this.config.dataApiHost;
24522
- const endpoint = `/api/v1/telemetry/devices/${params.ingestionId}/energy`;
24523
+ const segment = params.readingType === "water" || params.readingType === "tank" ? "water" : "energy";
24524
+ const endpoint = `/api/v1/telemetry/devices/${params.ingestionId}/${segment}`;
24523
24525
  const queryParams = new URLSearchParams({
24524
24526
  startTime: params.startISO,
24525
24527
  endTime: params.endISO,
@@ -28084,14 +28086,17 @@ var EnergyModalView = class {
28084
28086
  const totalConsumption = this.currentEnergyData.consumption.reduce((sum, item) => sum + item.value, 0);
28085
28087
  const now = /* @__PURE__ */ new Date();
28086
28088
  const timestamp = now.toLocaleDateString("pt-BR") + " - " + now.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
28089
+ const rt = this.config.params.readingType;
28090
+ const unit = rt === "water" || rt === "tank" ? "m\xB3" : rt === "temperature" ? "\xB0C" : "kWh";
28091
+ const reportTitle = rt === "water" || rt === "tank" ? "WATER REPORT" : rt === "temperature" ? "TEMPERATURE REPORT" : "ENERGY REPORT";
28087
28092
  const csvData = [
28088
- ["ENERGY REPORT - DEVICE DETAILS", "", ""],
28093
+ [`${reportTitle} - DEVICE DETAILS`, "", ""],
28089
28094
  ["Device", device.label, ""],
28090
28095
  ["Device ID", device.id, ""],
28091
28096
  ["Export Date", timestamp, ""],
28092
- ["Total Consumption", formatNumber(totalConsumption), "kWh"],
28097
+ ["Total Consumption", formatNumber(totalConsumption), unit],
28093
28098
  ["", "", ""],
28094
- ["Date", "Consumption (kWh)", ""],
28099
+ ["Date", `Consumption (${unit})`, ""],
28095
28100
  ...this.currentEnergyData.consumption.map((row) => [
28096
28101
  formatDate(row.timestamp),
28097
28102
  formatNumber(row.value),
@@ -28099,7 +28104,7 @@ var EnergyModalView = class {
28099
28104
  ])
28100
28105
  ];
28101
28106
  const csvContent = toCsv(csvData);
28102
- this.downloadCSV(csvContent, `energy-report-${device.id}-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`);
28107
+ this.downloadCSV(csvContent, `${rt || "energy"}-report-${device.id}-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`);
28103
28108
  }
28104
28109
  /**
28105
28110
  * Downloads CSV file
@@ -28239,7 +28244,7 @@ var EnergyModalView = class {
28239
28244
  endDate,
28240
28245
  label: this.config.params.deviceLabel || "Dispositivo",
28241
28246
  locale: "pt-BR",
28242
- readingType: this.config.params.readingType || "energy",
28247
+ readingType: (this.config.params.readingType === "tank" ? "water" : this.config.params.readingType) || "energy",
28243
28248
  enableRealTimeMode: true,
28244
28249
  realTimeInterval: 8e3,
28245
28250
  realTimeAutoScroll: true
@@ -28854,7 +28859,8 @@ var EnergyModal = class {
28854
28859
  ingestionId: this.context.resolved.ingestionId,
28855
28860
  startISO,
28856
28861
  endISO,
28857
- granularity: this.params.granularity || "1d"
28862
+ granularity: this.params.granularity || "1d",
28863
+ readingType: this.params.readingType
28858
28864
  });
28859
28865
  console.log("[EnergyModal] Energy data loaded:", {
28860
28866
  dataPoints: energyData.consumption.length,
@@ -28936,7 +28942,8 @@ var EnergyModal = class {
28936
28942
  ingestionId: this.context.resolved.ingestionId,
28937
28943
  startISO,
28938
28944
  endISO,
28939
- granularity: this.params.granularity || "1d"
28945
+ granularity: this.params.granularity || "1d",
28946
+ readingType: this.params.readingType
28940
28947
  });
28941
28948
  console.log("[EnergyModal] Energy data reloaded:", {
28942
28949
  dataPoints: energyData.consumption.length,
@@ -29201,9 +29208,10 @@ var DeviceReportModal = class {
29201
29208
  <label class="myio-label" for="date-range">Per\xEDodo</label>
29202
29209
  <input type="text" id="date-range" class="myio-input" readonly placeholder="Selecione o per\xEDodo" style="width: 300px;">
29203
29210
  </div>
29204
- <div id="granularity-toggle" role="group" aria-label="Granularidade" style="display:inline-flex;align-items:center;border:1px solid var(--myio-border,#e5e7eb);border-radius:8px;overflow:hidden;background:#f3f4f6;flex-shrink:0;height:36px;">
29205
- <button type="button" data-gran="1d" style="height:36px;padding:0 12px;font-size:13px;font-weight:600;border:none;background:var(--myio-primary,#1565c0);color:#fff;cursor:pointer;white-space:nowrap;">1d</button>
29206
- <button type="button" data-gran="1h" style="height:36px;padding:0 12px;font-size:13px;font-weight:600;border:none;background:transparent;color:#6b7280;cursor:pointer;white-space:nowrap;">1h</button>
29211
+ <!-- granularity-toggle hidden: 1h not yet supported, default stays 1d -->
29212
+ <div id="granularity-toggle" role="group" aria-label="Granularidade" style="display:none;">
29213
+ <button type="button" data-gran="1d">1d</button>
29214
+ <button type="button" data-gran="1h">1h</button>
29207
29215
  </div>
29208
29216
  <button id="load-btn" class="myio-btn myio-btn-primary">
29209
29217
  <span class="myio-spinner" id="load-spinner" style="display: none;"></span>
@@ -30456,9 +30464,10 @@ var AllReportModal = class {
30456
30464
  <label class="myio-label" for="date-range">Per\xEDodo</label>
30457
30465
  <input type="text" id="date-range" class="myio-input" readonly placeholder="Selecione o per\xEDodo" style="width: 300px;">
30458
30466
  </div>
30459
- <div id="granularity-toggle" role="group" aria-label="Granularidade" style="display:inline-flex;align-items:center;border:1px solid var(--myio-border,#e5e7eb);border-radius:8px;overflow:hidden;background:#f3f4f6;flex-shrink:0;height:36px;">
30460
- <button type="button" data-gran="1d" style="height:36px;padding:0 12px;font-size:13px;font-weight:600;border:none;background:var(--myio-primary,#1565c0);color:#fff;cursor:pointer;white-space:nowrap;">1d</button>
30461
- <button type="button" data-gran="1h" style="height:36px;padding:0 12px;font-size:13px;font-weight:600;border:none;background:transparent;color:#6b7280;cursor:pointer;white-space:nowrap;">1h</button>
30467
+ <!-- granularity-toggle hidden: 1h not yet supported, default stays 1d -->
30468
+ <div id="granularity-toggle" role="group" aria-label="Granularidade" style="display:none;">
30469
+ <button type="button" data-gran="1d">1d</button>
30470
+ <button type="button" data-gran="1h">1h</button>
30462
30471
  </div>
30463
30472
  <button id="load-btn" class="myio-btn myio-btn-primary">
30464
30473
  <span class="myio-spinner" id="load-spinner" style="display: none;"></span>
@@ -31007,14 +31016,11 @@ var AllReportModal = class {
31007
31016
  totalMappedConsumption += consumption;
31008
31017
  rows.push(result);
31009
31018
  }
31010
- this.debugLog(
31011
- "[AllReportModal] API-driven filter \u2014 matched:",
31012
- rows.length,
31013
- "| discarded:",
31014
- apiArray.length - rows.length,
31015
- "| total consumption:",
31016
- totalMappedConsumption
31017
- );
31019
+ this.debugLog("[AllReportModal] API-driven filter", {
31020
+ matched: rows.length,
31021
+ discarded: apiArray.length - rows.length,
31022
+ totalConsumption: totalMappedConsumption
31023
+ });
31018
31024
  return rows;
31019
31025
  }
31020
31026
  parseConsumptionValue(item) {
@@ -49587,7 +49593,7 @@ function bindAnnotationsPanelEvents(panel, alarmId, currentUser, onCountChange,
49587
49593
  }
49588
49594
 
49589
49595
  // src/utils/AnnotationTooltip.ts
49590
- var CSS = `
49596
+ var CSS2 = `
49591
49597
  .annot-tooltip {
49592
49598
  position: fixed;
49593
49599
  z-index: 99999;
@@ -49779,7 +49785,7 @@ function injectCSS7() {
49779
49785
  }
49780
49786
  const style = document.createElement("style");
49781
49787
  style.id = id;
49782
- style.textContent = CSS;
49788
+ style.textContent = CSS2;
49783
49789
  document.head.appendChild(style);
49784
49790
  _cssInjected = true;
49785
49791
  }
@@ -55677,9 +55683,6 @@ function removeAlarmsNotificationsPanelStyles() {
55677
55683
  }
55678
55684
 
55679
55685
  // src/components/premium-modals/settings/alarms/AlarmsTab.ts
55680
- var GCDR_INTEGRATION_API_KEY = "gcdr_cust_tb_integration_key_2026";
55681
- var GCDR_DEFAULT_BASE_URL = "https://gcdr-api.a.myio-bas.com";
55682
- var ALARMS_DEFAULT_BASE_URL = "https://alarms-api.a.myio-bas.com";
55683
55686
  var PRIORITY_ORDER = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
55684
55687
  var PRIORITY_COLORS = {
55685
55688
  CRITICAL: "#dc2626",
@@ -55700,6 +55703,7 @@ var AlarmsTab = class {
55700
55703
  activeAlarms = [];
55701
55704
  initialCheckedRuleIds = /* @__PURE__ */ new Set();
55702
55705
  alarmsUpdatedHandler = null;
55706
+ rulesFetchError = null;
55703
55707
  constructor(config) {
55704
55708
  this.config = config;
55705
55709
  }
@@ -55707,30 +55711,35 @@ var AlarmsTab = class {
55707
55711
  const { container } = this.config;
55708
55712
  this.injectStyles();
55709
55713
  container.innerHTML = this.getLoadingHTML();
55714
+ const orch = this.orch;
55715
+ if (!orch?.gcdrFetchCustomerRules) {
55716
+ this.showToast("AlarmsTab: MyIOOrchestrator n\xE3o inicializado");
55717
+ container.innerHTML = this.getErrorHTML("MyIOOrchestrator n\xE3o inicializado com API GCDR");
55718
+ return;
55719
+ }
55720
+ this.activeAlarms = this.readAlarmsFromASO();
55721
+ let rulesFetchError = null;
55710
55722
  try {
55711
- const gcdrBaseUrl = this.config.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
55712
- const alarms = this.readAlarmsFromASO();
55713
- const rules = this.config.prefetchedRules != null ? this.config.prefetchedRules : await this.fetchCustomerRules(gcdrBaseUrl);
55714
- this.activeAlarms = alarms;
55715
- this.customerRules = rules;
55723
+ this.customerRules = this.config.prefetchedRules != null ? this.config.prefetchedRules : await this.orch.gcdrFetchCustomerRules();
55716
55724
  for (const rule of this.customerRules) {
55717
55725
  if (rule.scope?.entityIds?.includes(this.config.gcdrDeviceId)) {
55718
55726
  this.initialCheckedRuleIds.add(rule.id);
55719
55727
  }
55720
55728
  }
55721
- container.innerHTML = this.renderTab();
55722
- this.populateAlarmsGrid();
55723
- this.attachTabListeners();
55724
- this.alarmsUpdatedHandler = () => {
55725
- const fresh = this.readAlarmsFromASO();
55726
- this.activeAlarms = fresh;
55727
- this.refreshAlarmsGridFromCurrentData();
55728
- };
55729
- window.addEventListener("myio:alarms-updated", this.alarmsUpdatedHandler);
55730
55729
  } catch (err) {
55731
- const msg = err instanceof Error ? err.message : String(err);
55732
- container.innerHTML = this.getErrorHTML(msg);
55733
- }
55730
+ rulesFetchError = this.classifyRulesError(err);
55731
+ this.customerRules = [];
55732
+ }
55733
+ this.rulesFetchError = rulesFetchError;
55734
+ container.innerHTML = this.renderTab();
55735
+ this.populateAlarmsGrid();
55736
+ this.attachTabListeners();
55737
+ this.alarmsUpdatedHandler = () => {
55738
+ const fresh = this.readAlarmsFromASO();
55739
+ this.activeAlarms = fresh;
55740
+ this.refreshAlarmsGridFromCurrentData();
55741
+ };
55742
+ window.addEventListener("myio:alarms-updated", this.alarmsUpdatedHandler);
55734
55743
  }
55735
55744
  destroy() {
55736
55745
  if (this.alarmsUpdatedHandler) {
@@ -55749,9 +55758,7 @@ var AlarmsTab = class {
55749
55758
  }
55750
55759
  const prefetched = this.config.prefetchedAlarms;
55751
55760
  if (prefetched != null) {
55752
- return prefetched.filter(
55753
- (a) => a.deviceId === this.config.gcdrDeviceId
55754
- );
55761
+ return prefetched.filter((a) => a.deviceId === this.config.gcdrDeviceId);
55755
55762
  }
55756
55763
  return [];
55757
55764
  }
@@ -55765,7 +55772,7 @@ var AlarmsTab = class {
55765
55772
  mapAlarmToCard(alarm) {
55766
55773
  return {
55767
55774
  id: alarm.id,
55768
- customerId: this.config.gcdrCustomerId,
55775
+ customerId: this.orch?.gcdrCustomerId ?? "",
55769
55776
  customerName: "",
55770
55777
  source: alarm.deviceName || "",
55771
55778
  severity: alarm.severity || "LOW",
@@ -55779,121 +55786,12 @@ var AlarmsTab = class {
55779
55786
  };
55780
55787
  }
55781
55788
  // ============================================================================
55782
- // Data fetching
55783
- // ============================================================================
55784
- async fetchActiveAlarms(baseUrl) {
55785
- const url = `${baseUrl}/api/v1/alarms?deviceId=${encodeURIComponent(this.config.gcdrDeviceId)}&state=OPEN,ACK,ESCALATED,SNOOZED&limit=100&page=1`;
55786
- const response = await fetch(url, {
55787
- method: "GET",
55788
- headers: {
55789
- "X-API-Key": this.config.alarmsApiKey ?? GCDR_INTEGRATION_API_KEY,
55790
- "X-Tenant-ID": this.config.gcdrTenantId,
55791
- Accept: "application/json"
55792
- }
55793
- });
55794
- if (response.status === 404) return [];
55795
- if (!response.ok) {
55796
- throw new Error(`Alarms API error (${response.status}): ${response.statusText}`);
55797
- }
55798
- const json = await response.json();
55799
- if (Array.isArray(json.data)) return json.data;
55800
- return json.items ?? json.data?.items ?? [];
55801
- }
55802
- async fetchCustomerRules(baseUrl) {
55803
- const url = `${baseUrl}/api/v1/customers/${encodeURIComponent(this.config.gcdrCustomerId)}/rules`;
55804
- const response = await fetch(url, {
55805
- method: "GET",
55806
- headers: {
55807
- "X-API-Key": this.config.gcdrApiKey ?? GCDR_INTEGRATION_API_KEY,
55808
- "X-Tenant-ID": this.config.gcdrTenantId,
55809
- Accept: "application/json"
55810
- }
55811
- });
55812
- if (response.status === 404) return [];
55813
- if (!response.ok) {
55814
- throw new Error(`GCDR error fetching rules (${response.status}): ${response.statusText}`);
55815
- }
55816
- const json = await response.json();
55817
- return json.items ?? json.data?.items ?? [];
55818
- }
55819
- async postAlarmAction(baseUrl, alarmId, action) {
55820
- try {
55821
- const response = await fetch(`${baseUrl}/api/v1/alarms/${encodeURIComponent(alarmId)}/${action}`, {
55822
- method: "POST",
55823
- headers: {
55824
- "X-API-Key": this.config.alarmsApiKey ?? GCDR_INTEGRATION_API_KEY,
55825
- "X-Tenant-ID": this.config.gcdrTenantId
55826
- }
55827
- });
55828
- return response.ok;
55829
- } catch {
55830
- return false;
55831
- }
55832
- }
55833
- async patchRuleScope(baseUrl, ruleId, entityIds) {
55834
- try {
55835
- const url = `${baseUrl}/api/v1/rules/${encodeURIComponent(ruleId)}`;
55836
- const resolvedKey = this.config.gcdrApiKey ?? GCDR_INTEGRATION_API_KEY;
55837
- const body = { scope: { type: "DEVICE", entityIds } };
55838
- console.group("[AlarmsTab] patchRuleScope");
55839
- console.log("url :", url);
55840
- console.log("ruleId :", ruleId);
55841
- console.log("apiKey (used):", resolvedKey);
55842
- console.log("tenantId :", this.config.gcdrTenantId);
55843
- console.log("body :", body);
55844
- console.groupEnd();
55845
- const response = await fetch(url, {
55846
- method: "PATCH",
55847
- headers: {
55848
- "X-API-Key": resolvedKey,
55849
- "X-Tenant-ID": this.config.gcdrTenantId,
55850
- "Content-Type": "application/json"
55851
- },
55852
- body: JSON.stringify(body)
55853
- });
55854
- console.log(`[AlarmsTab] patchRuleScope response: ${response.status} ${response.statusText} ok=${response.ok}`);
55855
- return response.ok;
55856
- } catch (err) {
55857
- console.error("[AlarmsTab] patchRuleScope error:", err);
55858
- return false;
55859
- }
55860
- }
55861
- async patchRuleValue(baseUrl, ruleId, alarmConfig) {
55862
- try {
55863
- const url = `${baseUrl}/api/v1/rules/${encodeURIComponent(ruleId)}`;
55864
- const resolvedKey = this.config.gcdrApiKey ?? GCDR_INTEGRATION_API_KEY;
55865
- const body = { alarmConfig };
55866
- console.group("[AlarmsTab] patchRuleValue");
55867
- console.log("url :", url);
55868
- console.log("ruleId :", ruleId);
55869
- console.log("apiKey (used):", resolvedKey);
55870
- console.log("tenantId :", this.config.gcdrTenantId);
55871
- console.log("body :", body);
55872
- console.groupEnd();
55873
- const response = await fetch(url, {
55874
- method: "PATCH",
55875
- headers: {
55876
- "X-API-Key": resolvedKey,
55877
- "X-Tenant-ID": this.config.gcdrTenantId,
55878
- "Content-Type": "application/json"
55879
- },
55880
- body: JSON.stringify(body)
55881
- });
55882
- console.log(`[AlarmsTab] patchRuleValue response: ${response.status} ${response.statusText} ok=${response.ok}`);
55883
- return response.ok;
55884
- } catch (err) {
55885
- console.error("[AlarmsTab] patchRuleValue error:", err);
55886
- return false;
55887
- }
55888
- }
55889
- // ============================================================================
55890
55789
  // Alarm grid population
55891
55790
  // ============================================================================
55892
55791
  populateAlarmsGrid() {
55893
55792
  const grid = this.config.container.querySelector("#at-alarms-grid");
55894
55793
  if (!grid) return;
55895
55794
  grid.innerHTML = "";
55896
- const alarmsBaseUrl = this.config.alarmsApiBaseUrl || ALARMS_DEFAULT_BASE_URL;
55897
55795
  const AlarmService2 = window.MyIOLibrary?.AlarmService;
55898
55796
  const userEmail = window.MyIOUtils?.currentUserEmail || "";
55899
55797
  for (const rawAlarm of this.activeAlarms) {
@@ -55904,22 +55802,22 @@ var AlarmsTab = class {
55904
55802
  if (AlarmService2?.batchAcknowledge) {
55905
55803
  await AlarmService2.batchAcknowledge([alarmId], userEmail);
55906
55804
  } else {
55907
- await this.postAlarmAction(alarmsBaseUrl, alarmId, "acknowledge");
55805
+ await this.orch.gcdrPostAlarmAction(alarmId, "acknowledge");
55908
55806
  }
55909
55807
  } else if (action === "snooze") {
55910
55808
  if (AlarmService2?.batchSilence) {
55911
55809
  await AlarmService2.batchSilence([alarmId], userEmail, "4h");
55912
55810
  } else {
55913
- await this.postAlarmAction(alarmsBaseUrl, alarmId, "snooze");
55811
+ await this.orch.gcdrPostAlarmAction(alarmId, "snooze");
55914
55812
  }
55915
55813
  } else if (action === "escalate") {
55916
55814
  if (AlarmService2?.batchEscalate) {
55917
55815
  await AlarmService2.batchEscalate([alarmId], userEmail);
55918
55816
  } else {
55919
- await this.postAlarmAction(alarmsBaseUrl, alarmId, "escalate");
55817
+ await this.orch.gcdrPostAlarmAction(alarmId, "escalate");
55920
55818
  }
55921
55819
  }
55922
- await this.refreshAlarmsGrid(alarmsBaseUrl);
55820
+ await this.refreshAlarmsGrid();
55923
55821
  };
55924
55822
  doAction().catch(() => {
55925
55823
  });
@@ -55933,25 +55831,25 @@ var AlarmsTab = class {
55933
55831
  if (AlarmService2?.batchAcknowledge) {
55934
55832
  await AlarmService2.batchAcknowledge([rawAlarm.id], userEmail);
55935
55833
  } else {
55936
- await this.postAlarmAction(alarmsBaseUrl, rawAlarm.id, "acknowledge");
55834
+ await this.orch.gcdrPostAlarmAction(rawAlarm.id, "acknowledge");
55937
55835
  }
55938
- await this.refreshAlarmsGrid(alarmsBaseUrl);
55836
+ await this.refreshAlarmsGrid();
55939
55837
  },
55940
55838
  onSnooze: async () => {
55941
55839
  if (AlarmService2?.batchSilence) {
55942
55840
  await AlarmService2.batchSilence([rawAlarm.id], userEmail, "4h");
55943
55841
  } else {
55944
- await this.postAlarmAction(alarmsBaseUrl, rawAlarm.id, "snooze");
55842
+ await this.orch.gcdrPostAlarmAction(rawAlarm.id, "snooze");
55945
55843
  }
55946
- await this.refreshAlarmsGrid(alarmsBaseUrl);
55844
+ await this.refreshAlarmsGrid();
55947
55845
  },
55948
55846
  onEscalate: async () => {
55949
55847
  if (AlarmService2?.batchEscalate) {
55950
55848
  await AlarmService2.batchEscalate([rawAlarm.id], userEmail);
55951
55849
  } else {
55952
- await this.postAlarmAction(alarmsBaseUrl, rawAlarm.id, "escalate");
55850
+ await this.orch.gcdrPostAlarmAction(rawAlarm.id, "escalate");
55953
55851
  }
55954
- await this.refreshAlarmsGrid(alarmsBaseUrl);
55852
+ await this.refreshAlarmsGrid();
55955
55853
  },
55956
55854
  onDetails: () => {
55957
55855
  openAlarmDetailsModal(alarm, "light", "separado", onAction);
@@ -55966,7 +55864,7 @@ var AlarmsTab = class {
55966
55864
  * The myio:alarms-updated event will update this component automatically.
55967
55865
  * Also updates the grid immediately from the post-action ASO data.
55968
55866
  */
55969
- async refreshAlarmsGrid(_alarmsBaseUrl) {
55867
+ async refreshAlarmsGrid() {
55970
55868
  const aso = window.AlarmServiceOrchestrator;
55971
55869
  if (aso) {
55972
55870
  await aso.refresh().catch(() => {
@@ -56042,7 +55940,32 @@ var AlarmsTab = class {
56042
55940
  }
56043
55941
  // ---------- Section 2: Parametrize rules (multi-select + save) ----------
56044
55942
  renderSection2() {
56045
- const sorted = [...this.customerRules].sort(
55943
+ if (this.rulesFetchError) {
55944
+ return `
55945
+ <div class="at-section">
55946
+ <div class="at-section-header">
55947
+ <span class="at-section-icon">\u2699\uFE0F</span>
55948
+ <div>
55949
+ <div class="at-section-title">Parametrizar Regras de Alarme</div>
55950
+ <div class="at-section-sub">N\xE3o foi poss\xEDvel carregar as regras</div>
55951
+ </div>
55952
+ </div>
55953
+ <div class="at-rules-error">
55954
+ <div class="at-rules-error-icon">\u26A0\uFE0F</div>
55955
+ <div class="at-rules-error-body">
55956
+ <div class="at-rules-error-title">${this.esc(this.rulesFetchError)}</div>
55957
+ <div class="at-rules-error-hint">Verifique a conex\xE3o com o servidor e tente reabrir este painel.</div>
55958
+ </div>
55959
+ </div>
55960
+ </div>
55961
+ `;
55962
+ }
55963
+ const deviceProfile = (this.config.deviceProfile ?? "").toUpperCase();
55964
+ const compatible = this.customerRules.filter(
55965
+ (rule) => rule.scopeProfiles && rule.scopeProfiles.length > 0 && rule.scopeProfiles.some((p) => p.toUpperCase() === deviceProfile)
55966
+ );
55967
+ const filtered = compatible.length > 0 ? compatible : this.customerRules;
55968
+ const sorted = [...filtered].sort(
56046
55969
  (a, b) => (PRIORITY_ORDER[a.priority] ?? 99) - (PRIORITY_ORDER[b.priority] ?? 99)
56047
55970
  );
56048
55971
  return `
@@ -56066,6 +55989,26 @@ var AlarmsTab = class {
56066
55989
  </div>
56067
55990
  `;
56068
55991
  }
55992
+ /**
55993
+ * Translates a raw rules-fetch error into a user-friendly message.
55994
+ * Hides internal HTTP codes and stack traces from the operator.
55995
+ */
55996
+ classifyRulesError(err) {
55997
+ const raw = err instanceof Error ? err.message : String(err);
55998
+ if (/HTTP 5\d\d/i.test(raw)) {
55999
+ return "O servidor de regras est\xE1 temporariamente indispon\xEDvel. Tente novamente em instantes.";
56000
+ }
56001
+ if (/HTTP 401|HTTP 403|unauthorized|forbidden/i.test(raw)) {
56002
+ return "Sem permiss\xE3o para carregar as regras de alarme deste cliente.";
56003
+ }
56004
+ if (/HTTP 404/i.test(raw)) {
56005
+ return "Nenhuma regra de alarme encontrada para este cliente.";
56006
+ }
56007
+ if (/network|failed to fetch|net::/i.test(raw)) {
56008
+ return "Erro de rede ao carregar as regras. Verifique a conex\xE3o e tente novamente.";
56009
+ }
56010
+ return "N\xE3o foi poss\xEDvel carregar as regras de alarme.";
56011
+ }
56069
56012
  renderCustomerRuleSelectable(rule) {
56070
56013
  const checked = this.initialCheckedRuleIds.has(rule.id);
56071
56014
  const color = PRIORITY_COLORS[rule.priority] ?? "#6b7280";
@@ -56135,7 +56078,6 @@ var AlarmsTab = class {
56135
56078
  if (chipWrap.querySelector(".at-rule-edit-inline")) return;
56136
56079
  const { metric, operator, value, valueHigh } = rule.alarmConfig;
56137
56080
  const opLabel = OPERATOR_LABELS[operator] ?? operator;
56138
- const baseUrl = this.config.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
56139
56081
  const statusId = `at-edit-status-${ruleId}`;
56140
56082
  chipWrap.innerHTML = `
56141
56083
  <div class="at-rule-edit-inline">
@@ -56156,7 +56098,9 @@ var AlarmsTab = class {
56156
56098
  const confirmBtn = chipWrap.querySelector(".at-rule-edit-confirm");
56157
56099
  const cancelBtn = chipWrap.querySelector(".at-rule-edit-cancel");
56158
56100
  const statusEl = chipWrap.querySelector(`#${statusId}`);
56159
- const inputLow = chipWrap.querySelector(".at-rule-edit-input:not(.at-rule-edit-input--high)");
56101
+ const inputLow = chipWrap.querySelector(
56102
+ ".at-rule-edit-input:not(.at-rule-edit-input--high)"
56103
+ );
56160
56104
  const inputHigh = chipWrap.querySelector(".at-rule-edit-input--high");
56161
56105
  const restoreChip = (v, vh) => {
56162
56106
  const vhStr = vh != null ? ` \u2013 ${vh}` : "";
@@ -56184,7 +56128,7 @@ var AlarmsTab = class {
56184
56128
  value: newVal,
56185
56129
  ...newValHigh !== void 0 && !isNaN(newValHigh) ? { valueHigh: newValHigh } : {}
56186
56130
  };
56187
- const ok = await this.patchRuleValue(baseUrl, ruleId, updatedConfig);
56131
+ const ok = await this.orch.gcdrPatchRuleValue(ruleId, updatedConfig);
56188
56132
  if (ok) {
56189
56133
  rule.alarmConfig = updatedConfig;
56190
56134
  restoreChip(newVal, updatedConfig.valueHigh ?? null);
@@ -56203,7 +56147,6 @@ var AlarmsTab = class {
56203
56147
  const container = this.config.container;
56204
56148
  const saveBtn = container.querySelector("#at-save-btn");
56205
56149
  const msgEl = container.querySelector("#at-save-msg");
56206
- const baseUrl = this.config.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
56207
56150
  if (saveBtn) {
56208
56151
  saveBtn.disabled = true;
56209
56152
  saveBtn.textContent = "Salvando\u2026";
@@ -56230,7 +56173,7 @@ var AlarmsTab = class {
56230
56173
  if (!rule) continue;
56231
56174
  const ids = [...rule.scope?.entityIds ?? []];
56232
56175
  if (!ids.includes(this.config.gcdrDeviceId)) ids.push(this.config.gcdrDeviceId);
56233
- const ok = await this.patchRuleScope(baseUrl, ruleId, ids);
56176
+ const ok = await this.orch.gcdrPatchRuleScope(ruleId, ids);
56234
56177
  if (ok) {
56235
56178
  rule.scope = { ...rule.scope, entityIds: ids };
56236
56179
  } else {
@@ -56240,8 +56183,17 @@ var AlarmsTab = class {
56240
56183
  for (const ruleId of toRemove) {
56241
56184
  const rule = ruleMap.get(ruleId);
56242
56185
  if (!rule) continue;
56186
+ const confirmed = await this.confirmRuleUnassign(rule);
56187
+ if (!confirmed) {
56188
+ this.restoreRuleCheckbox(ruleId);
56189
+ continue;
56190
+ }
56191
+ const enqueued = await this.orch.gcdrEnqueueCloseAlarms(ruleId, this.config.gcdrDeviceId);
56192
+ if (!enqueued) {
56193
+ console.warn("[AlarmsTab] RFC-0191: enqueue-close failed for rule", ruleId);
56194
+ }
56243
56195
  const ids = (rule.scope?.entityIds ?? []).filter((id) => id !== this.config.gcdrDeviceId);
56244
- const ok = await this.patchRuleScope(baseUrl, ruleId, ids);
56196
+ const ok = await this.orch.gcdrPatchRuleScope(ruleId, ids);
56245
56197
  if (ok) {
56246
56198
  rule.scope = { ...rule.scope, entityIds: ids };
56247
56199
  } else {
@@ -56268,9 +56220,89 @@ var AlarmsTab = class {
56268
56220
  // ============================================================================
56269
56221
  // Utilities
56270
56222
  // ============================================================================
56223
+ /** Typed accessor to window.MyIOOrchestrator GCDR API methods. */
56224
+ get orch() {
56225
+ return window.MyIOOrchestrator;
56226
+ }
56271
56227
  esc(str) {
56272
56228
  return String(str ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
56273
56229
  }
56230
+ // ============================================================================
56231
+ // RFC-0191: Confirmation modal + checkbox restore
56232
+ // ============================================================================
56233
+ /**
56234
+ * Renders a premium confirmation modal and waits for the user's decision.
56235
+ * Resolves true = confirmed (proceed with unassignment), false = cancelled.
56236
+ */
56237
+ confirmRuleUnassign(rule) {
56238
+ return new Promise((resolve) => {
56239
+ const PRIORITY_COLORS_LOCAL = {
56240
+ CRITICAL: "#dc2626",
56241
+ HIGH: "#f59e0b",
56242
+ MEDIUM: "#3b82f6",
56243
+ LOW: "#6b7280"
56244
+ };
56245
+ const color = PRIORITY_COLORS_LOCAL[rule.priority] ?? "#6b7280";
56246
+ const overlay = document.createElement("div");
56247
+ overlay.className = "at-confirm-overlay";
56248
+ overlay.innerHTML = `
56249
+ <div class="at-confirm-modal">
56250
+ <div class="at-confirm-warning-bar"></div>
56251
+ <div class="at-confirm-body">
56252
+ <div class="at-confirm-icon">\u26A0\uFE0F</div>
56253
+ <div class="at-confirm-content">
56254
+ <div class="at-confirm-title">Aten\xE7\xE3o \u2014 Remo\xE7\xE3o de Regra de Alarme</div>
56255
+ <div class="at-confirm-text">
56256
+ Voc\xEA est\xE1 removendo este dispositivo da regra de alarme:
56257
+ </div>
56258
+ <div class="at-confirm-rule-name">
56259
+ ${this.esc(rule.name)}
56260
+ <span class="at-confirm-priority-badge"
56261
+ style="background:${color}20;color:${color};">${this.esc(rule.priority)}</span>
56262
+ </div>
56263
+ <div class="at-confirm-text at-confirm-text--sub">
56264
+ Esta a\xE7\xE3o pode fechar alarmes abertos ou na fila associados a esta regra neste
56265
+ dispositivo. O motor de alarmes ir\xE1 enfileirar o fechamento; o hist\xF3rico e os
56266
+ registros de auditoria s\xE3o preservados.
56267
+ </div>
56268
+ </div>
56269
+ </div>
56270
+ <div class="at-confirm-footer">
56271
+ <button type="button" class="at-confirm-btn at-confirm-btn--cancel">Cancelar</button>
56272
+ <button type="button" class="at-confirm-btn at-confirm-btn--confirm">Confirmar e Remover Regra</button>
56273
+ </div>
56274
+ </div>
56275
+ `;
56276
+ const close = (result) => {
56277
+ overlay.remove();
56278
+ resolve(result);
56279
+ };
56280
+ overlay.querySelector(".at-confirm-btn--cancel").addEventListener("click", () => close(false));
56281
+ overlay.querySelector(".at-confirm-btn--confirm").addEventListener("click", () => close(true));
56282
+ overlay.addEventListener("click", (e) => {
56283
+ if (e.target === overlay) close(false);
56284
+ });
56285
+ document.body.appendChild(overlay);
56286
+ });
56287
+ }
56288
+ /**
56289
+ * Restores the checkbox of a rule to checked after the user cancels the confirmation.
56290
+ */
56291
+ restoreRuleCheckbox(ruleId) {
56292
+ const cb = this.config.container.querySelector(
56293
+ `.at-rule-check[data-rule-id="${CSS.escape(ruleId)}"]`
56294
+ );
56295
+ if (!cb) return;
56296
+ cb.checked = true;
56297
+ cb.closest(".at-rule-row")?.classList.add("at-rule-row--checked");
56298
+ }
56299
+ showToast(msg) {
56300
+ const el2 = document.createElement("div");
56301
+ el2.style.cssText = "position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:#dc2626;color:#fff;font-size:13px;font-weight:600;padding:10px 20px;border-radius:8px;z-index:99999;box-shadow:0 4px 12px rgba(0,0,0,0.25);pointer-events:none;font-family:Inter,system-ui,-apple-system,sans-serif;white-space:nowrap;";
56302
+ el2.textContent = `\u26A0\uFE0F ${msg}`;
56303
+ document.body.appendChild(el2);
56304
+ setTimeout(() => el2.remove(), 4e3);
56305
+ }
56274
56306
  getLoadingHTML() {
56275
56307
  return `
56276
56308
  <div style="padding:32px;text-align:center;color:#6c757d;">
@@ -56502,6 +56534,28 @@ var AlarmsTab = class {
56502
56534
  .at-rule-edit-cancel:hover { background: #fecaca; }
56503
56535
  .at-rule-edit-status { font-size: 11px; white-space: nowrap; }
56504
56536
 
56537
+ /* ===== Rules fetch error (Section 2) ===== */
56538
+ .at-rules-error {
56539
+ display: flex;
56540
+ align-items: flex-start;
56541
+ gap: 12px;
56542
+ padding: 16px 20px;
56543
+ }
56544
+ .at-rules-error-icon { font-size: 20px; flex-shrink: 0; line-height: 1.4; }
56545
+ .at-rules-error-body { flex: 1; min-width: 0; }
56546
+ .at-rules-error-title {
56547
+ font-size: 13px;
56548
+ font-weight: 500;
56549
+ color: #92400e;
56550
+ line-height: 1.5;
56551
+ }
56552
+ .at-rules-error-hint {
56553
+ font-size: 12px;
56554
+ color: #6b7280;
56555
+ margin-top: 4px;
56556
+ line-height: 1.4;
56557
+ }
56558
+
56505
56559
  /* ===== Footer / save ===== */
56506
56560
  .at-footer {
56507
56561
  padding: 12px 20px;
@@ -56538,6 +56592,104 @@ var AlarmsTab = class {
56538
56592
  margin: 0 auto 12px;
56539
56593
  }
56540
56594
  @keyframes at-spin { to { transform: rotate(360deg); } }
56595
+
56596
+ /* ===== RFC-0191: Rule unassign confirmation modal ===== */
56597
+ .at-confirm-overlay {
56598
+ position: fixed;
56599
+ inset: 0;
56600
+ background: rgba(0,0,0,0.55);
56601
+ display: flex;
56602
+ align-items: center;
56603
+ justify-content: center;
56604
+ z-index: 99999;
56605
+ }
56606
+ .at-confirm-modal {
56607
+ background: #fff;
56608
+ border-radius: 10px;
56609
+ box-shadow: 0 8px 32px rgba(0,0,0,0.22);
56610
+ max-width: 440px;
56611
+ width: calc(100% - 32px);
56612
+ overflow: hidden;
56613
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
56614
+ }
56615
+ .at-confirm-warning-bar {
56616
+ height: 4px;
56617
+ background: #f59e0b;
56618
+ }
56619
+ .at-confirm-body {
56620
+ display: flex;
56621
+ gap: 14px;
56622
+ padding: 20px 20px 16px;
56623
+ }
56624
+ .at-confirm-icon { font-size: 22px; flex-shrink: 0; line-height: 1.3; }
56625
+ .at-confirm-content { flex: 1; min-width: 0; }
56626
+ .at-confirm-title {
56627
+ font-size: 14px;
56628
+ font-weight: 700;
56629
+ color: #1a1a1a;
56630
+ margin-bottom: 8px;
56631
+ }
56632
+ .at-confirm-text {
56633
+ font-size: 13px;
56634
+ color: #374151;
56635
+ line-height: 1.5;
56636
+ margin-bottom: 8px;
56637
+ }
56638
+ .at-confirm-text--sub {
56639
+ color: #6b7280;
56640
+ font-size: 12px;
56641
+ margin-bottom: 0;
56642
+ }
56643
+ .at-confirm-rule-name {
56644
+ display: flex;
56645
+ align-items: center;
56646
+ gap: 8px;
56647
+ font-size: 13px;
56648
+ font-weight: 600;
56649
+ color: #1a1a1a;
56650
+ background: #f8f9fa;
56651
+ border-radius: 6px;
56652
+ padding: 6px 10px;
56653
+ margin-bottom: 10px;
56654
+ flex-wrap: wrap;
56655
+ }
56656
+ .at-confirm-priority-badge {
56657
+ font-size: 10px;
56658
+ font-weight: 700;
56659
+ padding: 2px 7px;
56660
+ border-radius: 4px;
56661
+ text-transform: uppercase;
56662
+ letter-spacing: 0.05em;
56663
+ flex-shrink: 0;
56664
+ }
56665
+ .at-confirm-footer {
56666
+ display: flex;
56667
+ align-items: center;
56668
+ justify-content: flex-end;
56669
+ gap: 10px;
56670
+ padding: 12px 20px;
56671
+ border-top: 1px solid #e9ecef;
56672
+ background: #fafafa;
56673
+ }
56674
+ .at-confirm-btn {
56675
+ border: none;
56676
+ border-radius: 6px;
56677
+ padding: 8px 16px;
56678
+ font-size: 13px;
56679
+ font-weight: 600;
56680
+ cursor: pointer;
56681
+ transition: background 0.15s, opacity 0.15s;
56682
+ }
56683
+ .at-confirm-btn--cancel {
56684
+ background: #f1f3f5;
56685
+ color: #374151;
56686
+ }
56687
+ .at-confirm-btn--cancel:hover { background: #e2e8f0; }
56688
+ .at-confirm-btn--confirm {
56689
+ background: #dc2626;
56690
+ color: #fff;
56691
+ }
56692
+ .at-confirm-btn--confirm:hover { background: #b91c1c; }
56541
56693
  `;
56542
56694
  document.head.appendChild(style);
56543
56695
  }
@@ -56655,22 +56807,42 @@ var SettingsModalView = class {
56655
56807
  async initAlarmsTab() {
56656
56808
  const container = this.modal.querySelector("#alarms-tab-content");
56657
56809
  if (!container) return;
56658
- const { gcdrDeviceId, gcdrCustomerId, gcdrTenantId, gcdrApiBaseUrl, prefetchedBundle, prefetchedAlarms, deviceId, jwtToken } = this.config;
56659
- if (!gcdrDeviceId || !gcdrCustomerId || !gcdrTenantId) {
56660
- container.innerHTML = '<p style="color:#6c757d;padding:20px;text-align:center;font-style:italic;">Alarm data not available (missing GCDR identifiers).</p>';
56810
+ const { gcdrDeviceId, prefetchedBundle, prefetchedAlarms, prefetchedRules, deviceId, jwtToken } = this.config;
56811
+ if (!gcdrDeviceId) {
56812
+ const tabBtn = this.modal.querySelector('.modal-tab[data-tab="alarms"]');
56813
+ if (tabBtn) tabBtn.classList.add("locked");
56814
+ container.innerHTML = `
56815
+ <div style="
56816
+ display:flex; flex-direction:column; align-items:center; justify-content:center;
56817
+ min-height:320px; padding:40px 24px; text-align:center;
56818
+ ">
56819
+ <div style="
56820
+ width:72px; height:72px; border-radius:50%;
56821
+ background:#f3f4f6; border:1.5px solid #e5e7eb;
56822
+ display:flex; align-items:center; justify-content:center;
56823
+ font-size:32px; margin-bottom:20px; opacity:0.7;
56824
+ ">\u{1F512}</div>
56825
+ <div style="font-size:15px; font-weight:600; color:#374151; margin-bottom:8px;">
56826
+ Alarmes n\xE3o dispon\xEDveis
56827
+ </div>
56828
+ <div style="font-size:13px; color:#9ca3af; max-width:320px; line-height:1.6;">
56829
+ Este dispositivo n\xE3o est\xE1 vinculado ao sistema GCDR.<br>
56830
+ O identificador <code style="font-size:11px;background:#f3f4f6;padding:1px 5px;border-radius:3px;">gcdrDeviceId</code>
56831
+ n\xE3o foi encontrado nos atributos do servidor.
56832
+ </div>
56833
+ </div>`;
56661
56834
  return;
56662
56835
  }
56663
56836
  try {
56664
56837
  this.alarmsTab = new AlarmsTab({
56665
56838
  container,
56666
56839
  gcdrDeviceId,
56667
- gcdrCustomerId,
56668
- gcdrTenantId,
56669
- gcdrApiBaseUrl,
56670
56840
  tbDeviceId: deviceId ?? "",
56671
56841
  jwtToken: jwtToken ?? "",
56672
56842
  prefetchedBundle: prefetchedBundle ?? null,
56673
- prefetchedAlarms: prefetchedAlarms ?? null
56843
+ prefetchedAlarms: prefetchedAlarms ?? null,
56844
+ prefetchedRules: prefetchedRules ?? null,
56845
+ deviceProfile: this.config.deviceProfile
56674
56846
  });
56675
56847
  await this.alarmsTab.init();
56676
56848
  console.log("[SettingsModalView] RFC-0180: Alarms tab initialized");
@@ -57677,6 +57849,12 @@ var SettingsModalView = class {
57677
57849
  background: #fff;
57678
57850
  }
57679
57851
 
57852
+ .modal-tab.locked {
57853
+ opacity: 0.5;
57854
+ cursor: not-allowed;
57855
+ pointer-events: none;
57856
+ }
57857
+
57680
57858
  .modal-tab svg {
57681
57859
  width: 16px;
57682
57860
  height: 16px;
@@ -59567,6 +59745,8 @@ var SettingsController = class {
59567
59745
  gcdrTenantId: params.gcdrTenantId,
59568
59746
  gcdrApiBaseUrl: params.gcdrApiBaseUrl,
59569
59747
  prefetchedBundle: params.prefetchedBundle,
59748
+ prefetchedAlarms: params.prefetchedAlarms,
59749
+ prefetchedRules: params.prefetchedRules,
59570
59750
  // Device timestamps
59571
59751
  createdTime: params.createdTime ?? null,
59572
59752
  lastActivityTime: params.lastActivityTime ?? null
@@ -78314,7 +78494,8 @@ function openUpsellModal(params) {
78314
78494
  label: 120,
78315
78495
  type: 140,
78316
78496
  createdTime: 90,
78317
- relationTo: 110,
78497
+ relationTo: 120,
78498
+ relationFrom: 110,
78318
78499
  centralId: 80,
78319
78500
  slaveId: 60,
78320
78501
  deviceType: 80,
@@ -78330,7 +78511,9 @@ function openUpsellModal(params) {
78330
78511
  telemetryLoadedCount: 0,
78331
78512
  deviceRelations: [],
78332
78513
  allRelations: [],
78333
- deviceToAssetMap: /* @__PURE__ */ new Map(),
78514
+ deviceRelToMap: /* @__PURE__ */ new Map(),
78515
+ deviceRelFromMap: /* @__PURE__ */ new Map(),
78516
+ relationsDetailModal: { open: false, deviceId: "", deviceName: "", direction: "to", relations: [], removing: null },
78334
78517
  relationsLoaded: false,
78335
78518
  relationsLoading: false,
78336
78519
  deviceProfiles: [],
@@ -79557,11 +79740,13 @@ function renderStep2(state6, modalId, colors2, t) {
79557
79740
  </div>`;
79558
79741
  }
79559
79742
  return `
79743
+ <div style="overflow-x:auto; overflow-y:visible;">
79560
79744
  <div id="${modalId}-grid-header" style="
79561
79745
  display:flex; align-items:center; gap:0; padding:8px 12px;
79562
79746
  background:${colors2.cardBg}; border:1px solid ${colors2.border};
79563
79747
  border-bottom:none; border-radius:8px 8px 0 0; font-size:9px;
79564
79748
  font-weight:600; color:${colors2.textMuted}; text-transform:uppercase;
79749
+ min-width:max-content;
79565
79750
  ">
79566
79751
  ${multiCol ? `<div style="width:28px; text-align:center;">\u2611</div>` : ""}
79567
79752
  <div style="width:28px;"></div>
@@ -79572,6 +79757,9 @@ function renderStep2(state6, modalId, colors2, t) {
79572
79757
  <div style="width:${state6.columnWidths.relationTo}px; padding:0 6px; text-align:center; border-right:1px solid ${colors2.border};">
79573
79758
  relTO ${!state6.relationsLoaded ? "\u23F3" : ""}
79574
79759
  </div>
79760
+ <div style="width:${state6.columnWidths.relationFrom}px; padding:0 6px; text-align:center; border-right:1px solid ${colors2.border};">
79761
+ relFROM ${!state6.relationsLoaded ? "\u23F3" : ""}
79762
+ </div>
79575
79763
  ${renderSortableHeader("centralId", "centralId" + (!state6.deviceAttrsLoaded ? " \u{1F512}" : ""), "centralId", state6.columnWidths.centralId)}
79576
79764
  ${renderSortableHeader("slaveId", "slaveId" + (!state6.deviceAttrsLoaded ? " \u{1F512}" : ""), "slaveId", state6.columnWidths.slaveId)}
79577
79765
  <div style="width:${state6.columnWidths.deviceType}px; padding:0 6px; text-align:center; border-right:1px solid ${colors2.border};">
@@ -79589,10 +79777,11 @@ function renderStep2(state6, modalId, colors2, t) {
79589
79777
  <div style="width:24px;"></div>
79590
79778
  </div>
79591
79779
  <div style="
79592
- max-height:${gridHeight}; overflow-y:auto; border:1px solid ${colors2.border};
79593
- border-radius:0 0 8px 8px; background:${colors2.surface};
79780
+ max-height:${gridHeight}; overflow-y:auto; overflow-x:visible; border:1px solid ${colors2.border};
79781
+ border-radius:0 0 8px 8px; background:${colors2.surface}; min-width:max-content;
79594
79782
  " id="${modalId}-device-list">
79595
79783
  ${sortedDevices.length === 0 ? `<div style="padding:40px; text-align:center; color:${colors2.textMuted};">${t.noResults}</div>` : sortedDevices.map((device) => renderDeviceRow(device, state6, modalId, colors2)).join("")}
79784
+ </div>
79596
79785
  </div>`;
79597
79786
  })()}
79598
79787
  `;
@@ -79752,11 +79941,24 @@ function renderDeviceRow(device, state6, modalId, colors2) {
79752
79941
  <div style="width: ${state6.columnWidths.createdTime}px; padding: 0 6px; text-align: center; flex-shrink: 0;">
79753
79942
  <span style="font-size: 9px; color: ${colors2.textMuted};">${createdTimeStr}</span>
79754
79943
  </div>
79755
- <div style="width: ${state6.columnWidths.relationTo}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
79944
+ <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;">
79945
+ ${(() => {
79946
+ if (!state6.relationsLoaded) return `<span style="font-size: 8px; color: ${colors2.textMuted}; font-style: italic;">\u2014</span>`;
79947
+ const rels = state6.deviceRelToMap.get(deviceId) || [];
79948
+ if (rels.length === 0) return `<span style="font-size: 9px; color: ${colors2.textMuted};">\u2014</span>`;
79949
+ const first = rels[0];
79950
+ const more = rels.length - 1;
79951
+ 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>` : ""}`;
79952
+ })()}
79953
+ </div>
79954
+ <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;">
79756
79955
  ${(() => {
79757
79956
  if (!state6.relationsLoaded) return `<span style="font-size: 8px; color: ${colors2.textMuted}; font-style: italic;">\u2014</span>`;
79758
- const assetName = state6.deviceToAssetMap.get(deviceId);
79759
- return assetName ? `<span style="font-size: 9px; color: ${colors2.text};" title="${assetName}">${assetName}</span>` : `<span style="font-size: 9px; color: ${colors2.textMuted};">\u2014</span>`;
79957
+ const rels = state6.deviceRelFromMap.get(deviceId) || [];
79958
+ if (rels.length === 0) return `<span style="font-size: 9px; color: ${colors2.textMuted};">\u2014</span>`;
79959
+ const first = rels[0];
79960
+ const more = rels.length - 1;
79961
+ 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>` : ""}`;
79760
79962
  })()}
79761
79963
  </div>
79762
79964
  <div style="width: ${state6.columnWidths.centralId}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
@@ -80316,6 +80518,109 @@ function renderEntityLabelRow(currentLabel, deviceName, colors2, modalId, t) {
80316
80518
  </div>
80317
80519
  `;
80318
80520
  }
80521
+ function openRelationsDetailPanel(deviceId, deviceName, direction, rels, state6, t) {
80522
+ document.getElementById("myio-rel-detail-panel")?.remove();
80523
+ const isDark = state6.theme === "dark";
80524
+ const colors2 = {
80525
+ bg: isDark ? "#1f2937" : "#ffffff",
80526
+ border: isDark ? "#374151" : "#e5e7eb",
80527
+ text: isDark ? "#f9fafb" : "#111827",
80528
+ muted: isDark ? "#9ca3af" : "#6b7280",
80529
+ surface: isDark ? "#111827" : "#f9fafb",
80530
+ danger: "#dc2626",
80531
+ dangerBg: "#fee2e2"
80532
+ };
80533
+ const dirLabel = direction === "to" ? "relTO" : "relFROM";
80534
+ const dirDesc = direction === "to" ? "Entidades que apontam para este dispositivo" : "Entidades que este dispositivo aponta para";
80535
+ const typeColor = (et) => ({
80536
+ ASSET: ["#dbeafe", "#1e40af"],
80537
+ CUSTOMER: ["#d1fae5", "#065f46"],
80538
+ DEVICE: ["#ede9ff", "#4c1d95"]
80539
+ })[et] || ["#f3f4f6", "#374151"];
80540
+ let currentRels = [...rels];
80541
+ function renderPanel() {
80542
+ document.getElementById("myio-rel-detail-panel")?.remove();
80543
+ const panel = document.createElement("div");
80544
+ panel.id = "myio-rel-detail-panel";
80545
+ panel.style.cssText = `
80546
+ position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
80547
+ z-index:99999; background:${colors2.bg}; border:1px solid ${colors2.border};
80548
+ border-radius:12px; box-shadow:0 20px 60px rgba(0,0,0,.35);
80549
+ min-width:380px; max-width:500px; width:90vw; max-height:70vh;
80550
+ display:flex; flex-direction:column; font-family:system-ui,sans-serif;
80551
+ `;
80552
+ const rows = currentRels.map((rel, idx) => {
80553
+ const [tcBg, tcColor] = typeColor(rel.entityType);
80554
+ return `
80555
+ <div style="display:flex;align-items:center;gap:8px;padding:10px 16px;border-bottom:1px solid ${colors2.border}">
80556
+ <span style="font-size:9px;background:${tcBg};color:${tcColor};padding:2px 6px;border-radius:3px;font-weight:700;white-space:nowrap;flex-shrink:0">${rel.entityType}</span>
80557
+ <div style="flex:1;overflow:hidden">
80558
+ <div style="font-size:12px;font-weight:600;color:${colors2.text};overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${rel.name}</div>
80559
+ <div style="font-size:10px;color:${colors2.muted};font-family:monospace">${rel.id}</div>
80560
+ <div style="font-size:10px;color:${colors2.muted}">tipo: <em>${rel.relationType}</em></div>
80561
+ </div>
80562
+ <button data-rel-remove="${idx}" style="flex-shrink:0;font-size:11px;background:${colors2.dangerBg};color:${colors2.danger};border:1px solid ${colors2.danger};border-radius:5px;padding:3px 8px;cursor:pointer;font-weight:600">
80563
+ Remover
80564
+ </button>
80565
+ </div>`;
80566
+ }).join("");
80567
+ panel.innerHTML = `
80568
+ <div style="padding:14px 16px 10px;border-bottom:1px solid ${colors2.border};display:flex;align-items:flex-start;justify-content:space-between;gap:8px">
80569
+ <div>
80570
+ <div style="font-size:13px;font-weight:700;color:${colors2.text}">${dirLabel} \u2014 ${deviceName}</div>
80571
+ <div style="font-size:11px;color:${colors2.muted};margin-top:2px">${dirDesc}</div>
80572
+ </div>
80573
+ <button id="myio-rel-panel-close" style="font-size:16px;background:none;border:none;cursor:pointer;color:${colors2.muted};padding:0 4px;line-height:1">\u2715</button>
80574
+ </div>
80575
+ <div style="overflow-y:auto;flex:1">
80576
+ ${currentRels.length === 0 ? `<div style="padding:24px;text-align:center;color:${colors2.muted};font-size:12px">Nenhuma rela\xE7\xE3o</div>` : rows}
80577
+ </div>
80578
+ `;
80579
+ const overlay = document.createElement("div");
80580
+ overlay.id = "myio-rel-detail-overlay";
80581
+ overlay.style.cssText = "position:fixed;inset:0;z-index:99998;background:rgba(0,0,0,.3)";
80582
+ overlay.addEventListener("click", () => {
80583
+ panel.remove();
80584
+ overlay.remove();
80585
+ });
80586
+ document.body.appendChild(overlay);
80587
+ document.body.appendChild(panel);
80588
+ panel.querySelector("#myio-rel-panel-close")?.addEventListener("click", () => {
80589
+ panel.remove();
80590
+ overlay.remove();
80591
+ });
80592
+ panel.querySelectorAll("[data-rel-remove]").forEach((btn) => {
80593
+ btn.addEventListener("click", async () => {
80594
+ const idx = parseInt(btn.dataset.relRemove || "0", 10);
80595
+ const rel = currentRels[idx];
80596
+ if (!rel) return;
80597
+ btn.disabled = true;
80598
+ btn.textContent = "\u2026";
80599
+ try {
80600
+ const fromId = direction === "to" ? rel.id : deviceId;
80601
+ const fromType = direction === "to" ? rel.entityType : "DEVICE";
80602
+ const toId = direction === "to" ? deviceId : rel.id;
80603
+ const toType = direction === "to" ? "DEVICE" : rel.entityType;
80604
+ const token = state6.token;
80605
+ const base = state6.tbApiBase || "";
80606
+ await fetch(
80607
+ `${base}/api/relation?fromId=${fromId}&fromType=${fromType}&toId=${toId}&toType=${toType}&relationType=${encodeURIComponent(rel.relationType)}&typeGroup=COMMON`,
80608
+ { method: "DELETE", headers: { "X-Authorization": `Bearer ${token}` } }
80609
+ );
80610
+ currentRels = currentRels.filter((_, i) => i !== idx);
80611
+ const map = direction === "to" ? state6.deviceRelToMap : state6.deviceRelFromMap;
80612
+ map.set(deviceId, [...currentRels]);
80613
+ renderPanel();
80614
+ } catch {
80615
+ btn.disabled = false;
80616
+ btn.textContent = "Remover";
80617
+ }
80618
+ });
80619
+ });
80620
+ }
80621
+ renderPanel();
80622
+ void t;
80623
+ }
80319
80624
  function setupEventListeners3(container, state6, modalId, t, onClose) {
80320
80625
  const closeHandler = () => closeModal(container, onClose);
80321
80626
  const overlay = container.querySelector(".myio-upsell-modal-overlay");
@@ -80518,6 +80823,18 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
80518
80823
  document.getElementById(`${modalId}-load-relations`)?.addEventListener("click", () => {
80519
80824
  void loadDeviceRelations(state6, container, modalId, t, onClose);
80520
80825
  });
80826
+ container.addEventListener("click", (e) => {
80827
+ const target = e.target;
80828
+ const btn = target.closest("[data-show-relto],[data-show-relfrom]");
80829
+ if (!btn) return;
80830
+ e.stopPropagation();
80831
+ const isTo = !!btn.dataset.showRelto;
80832
+ const deviceId = isTo ? btn.dataset.showRelto : btn.dataset.showRelfrom;
80833
+ const deviceName = decodeURIComponent(btn.dataset.deviceName || "");
80834
+ const direction = isTo ? "to" : "from";
80835
+ const rels = (isTo ? state6.deviceRelToMap : state6.deviceRelFromMap).get(deviceId) || [];
80836
+ openRelationsDetailPanel(deviceId, deviceName, direction, rels, state6, t);
80837
+ });
80521
80838
  document.getElementById(`${modalId}-load-attrs`)?.addEventListener("click", async () => {
80522
80839
  if (state6.attrsLoading) return;
80523
80840
  console.log(
@@ -81892,7 +82209,9 @@ async function loadDevices(state6, container, modalId, t, onClose) {
81892
82209
  state6.telemetryLoadedCount = 0;
81893
82210
  state6.relationsLoaded = false;
81894
82211
  state6.relationsLoading = false;
81895
- state6.deviceToAssetMap = /* @__PURE__ */ new Map();
82212
+ state6.deviceRelToMap = /* @__PURE__ */ new Map();
82213
+ state6.deviceRelFromMap = /* @__PURE__ */ new Map();
82214
+ state6.relationsDetailModal = { open: false, deviceId: "", deviceName: "", direction: "to", relations: [], removing: null };
81896
82215
  state6.isLoading = false;
81897
82216
  renderModal4(container, state6, modalId, t);
81898
82217
  setupEventListeners3(container, state6, modalId, t, onClose);
@@ -81915,8 +82234,9 @@ async function loadDeviceRelations(state6, container, modalId, t, onClose) {
81915
82234
  }
81916
82235
  const assets = state6.customerAssets;
81917
82236
  const BATCH_SIZE = 20;
81918
- const deviceToAssetMap = /* @__PURE__ */ new Map();
81919
- showBusyProgress("Carregando rela\xE7\xF5es...", assets.length);
82237
+ const deviceRelToMap = /* @__PURE__ */ new Map();
82238
+ const deviceRelFromMap = /* @__PURE__ */ new Map();
82239
+ showBusyProgress("Carregando rela\xE7\xF5es (relTO)...", assets.length);
81920
82240
  for (let i = 0; i < assets.length; i += BATCH_SIZE) {
81921
82241
  const batch = assets.slice(i, i + BATCH_SIZE);
81922
82242
  await Promise.all(
@@ -81928,7 +82248,10 @@ async function loadDeviceRelations(state6, container, modalId, t, onClose) {
81928
82248
  );
81929
82249
  rels.forEach((rel) => {
81930
82250
  if (rel.to.entityType === "DEVICE") {
81931
- deviceToAssetMap.set(rel.to.id, asset.name);
82251
+ const entry = { id: asset.id, name: asset.name, entityType: "ASSET", relationType: rel.type || "Contains" };
82252
+ const list = deviceRelToMap.get(rel.to.id) || [];
82253
+ list.push(entry);
82254
+ deviceRelToMap.set(rel.to.id, list);
81932
82255
  }
81933
82256
  });
81934
82257
  } catch {
@@ -81937,7 +82260,34 @@ async function loadDeviceRelations(state6, container, modalId, t, onClose) {
81937
82260
  );
81938
82261
  updateBusyProgress(Math.min(i + BATCH_SIZE, assets.length));
81939
82262
  }
81940
- state6.deviceToAssetMap = deviceToAssetMap;
82263
+ const allDeviceIds = state6.devices.map((d) => getEntityId(d));
82264
+ showBusyProgress("Carregando rela\xE7\xF5es (relFROM)...", allDeviceIds.length);
82265
+ for (let i = 0; i < allDeviceIds.length; i += BATCH_SIZE) {
82266
+ const batch = allDeviceIds.slice(i, i + BATCH_SIZE);
82267
+ await Promise.all(
82268
+ batch.map(async (devId) => {
82269
+ try {
82270
+ const rels = await tbFetch(
82271
+ state6,
82272
+ `/api/relations?fromId=${devId}&fromType=DEVICE`
82273
+ );
82274
+ if (rels.length > 0) {
82275
+ const entries = rels.map((rel) => ({
82276
+ id: rel.to.id,
82277
+ name: rel.toName || rel.to.id,
82278
+ entityType: rel.to.entityType,
82279
+ relationType: rel.type || "Contains"
82280
+ }));
82281
+ deviceRelFromMap.set(devId, entries);
82282
+ }
82283
+ } catch {
82284
+ }
82285
+ })
82286
+ );
82287
+ updateBusyProgress(Math.min(i + BATCH_SIZE, allDeviceIds.length));
82288
+ }
82289
+ state6.deviceRelToMap = deviceRelToMap;
82290
+ state6.deviceRelFromMap = deviceRelFromMap;
81941
82291
  state6.relationsLoaded = true;
81942
82292
  state6.relationsLoading = false;
81943
82293
  hideBusyProgress();
@@ -82413,6 +82763,1151 @@ ${errors.slice(0, 5).join("\n")}` + (errors.length > 5 ? `
82413
82763
  renderModal4(container, state6, modalId, t);
82414
82764
  }
82415
82765
 
82766
+ // src/components/premium-modals/user-management/tabs/UserListTab.ts
82767
+ var UserListTab = class {
82768
+ config;
82769
+ callbacks;
82770
+ el;
82771
+ currentPage = 0;
82772
+ totalPages = 0;
82773
+ searchQuery = "";
82774
+ searchTimer = null;
82775
+ loading = false;
82776
+ users = [];
82777
+ highlightedUserId = null;
82778
+ constructor(config, callbacks) {
82779
+ this.config = config;
82780
+ this.callbacks = callbacks;
82781
+ }
82782
+ render() {
82783
+ this.el = document.createElement("div");
82784
+ this.el.className = "um-tab-content um-user-list";
82785
+ this.el.innerHTML = `
82786
+ <div class="um-list-toolbar">
82787
+ <div class="um-search-wrap">
82788
+ <svg class="um-search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none"
82789
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
82790
+ <circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
82791
+ </svg>
82792
+ <input type="text" class="um-search-input" placeholder="Buscar por nome ou e-mail..." />
82793
+ </div>
82794
+ <button class="um-btn um-btn--primary um-btn--sm um-new-user-btn">+ Novo Usu\xE1rio</button>
82795
+ </div>
82796
+ <div class="um-table-wrap">
82797
+ <table class="um-table">
82798
+ <thead>
82799
+ <tr>
82800
+ <th>Nome</th>
82801
+ <th>E-mail</th>
82802
+ <th>Perfil</th>
82803
+ <th class="um-col-actions">A\xE7\xF5es</th>
82804
+ </tr>
82805
+ </thead>
82806
+ <tbody class="um-tbody"></tbody>
82807
+ </table>
82808
+ <div class="um-list-empty" style="display:none">Nenhum usu\xE1rio encontrado.</div>
82809
+ <div class="um-list-loading" style="display:none">
82810
+ <span class="um-spinner"></span> Carregando...
82811
+ </div>
82812
+ </div>
82813
+ <div class="um-pagination" style="display:none">
82814
+ <button class="um-btn um-btn--ghost um-btn--sm um-prev-btn">\u2190 Anterior</button>
82815
+ <span class="um-page-info"></span>
82816
+ <button class="um-btn um-btn--ghost um-btn--sm um-next-btn">Pr\xF3xima \u2192</button>
82817
+ </div>
82818
+ `;
82819
+ const searchInput = this.el.querySelector(".um-search-input");
82820
+ searchInput.addEventListener("input", () => {
82821
+ if (this.searchTimer) clearTimeout(this.searchTimer);
82822
+ this.searchTimer = setTimeout(() => {
82823
+ this.searchQuery = searchInput.value.trim();
82824
+ this.currentPage = 0;
82825
+ this.fetchUsers();
82826
+ }, 300);
82827
+ });
82828
+ this.el.querySelector(".um-new-user-btn").addEventListener("click", () => {
82829
+ this.callbacks.onSwitchToNewUser();
82830
+ });
82831
+ this.el.querySelector(".um-prev-btn").addEventListener("click", () => {
82832
+ if (this.currentPage > 0) {
82833
+ this.currentPage--;
82834
+ this.fetchUsers();
82835
+ }
82836
+ });
82837
+ this.el.querySelector(".um-next-btn").addEventListener("click", () => {
82838
+ if (this.currentPage < this.totalPages - 1) {
82839
+ this.currentPage++;
82840
+ this.fetchUsers();
82841
+ }
82842
+ });
82843
+ this.fetchUsers();
82844
+ return this.el;
82845
+ }
82846
+ /** Called by NewUserTab/UserDetailTab after a mutation so we refresh the list */
82847
+ refresh(highlightUserId) {
82848
+ if (highlightUserId) this.highlightedUserId = highlightUserId;
82849
+ this.fetchUsers();
82850
+ }
82851
+ async fetchUsers() {
82852
+ if (this.loading) return;
82853
+ this.loading = true;
82854
+ this.setLoading(true);
82855
+ try {
82856
+ const { tbBaseUrl, jwtToken, customerId } = this.config;
82857
+ const q = encodeURIComponent(this.searchQuery);
82858
+ const url = `${tbBaseUrl}/api/customer/${customerId}/users?pageSize=20&page=${this.currentPage}${q ? `&textSearch=${q}` : ""}`;
82859
+ const res = await fetch(url, {
82860
+ headers: { "X-Authorization": `Bearer ${jwtToken}` }
82861
+ });
82862
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
82863
+ const page = await res.json();
82864
+ this.users = page.data;
82865
+ this.totalPages = page.totalPages;
82866
+ this.renderRows();
82867
+ this.renderPagination(page);
82868
+ } catch (err) {
82869
+ console.error("[UserListTab] fetchUsers error", err);
82870
+ this.callbacks.showToast("Erro ao carregar usu\xE1rios. Tente novamente.", "error");
82871
+ } finally {
82872
+ this.loading = false;
82873
+ this.setLoading(false);
82874
+ }
82875
+ }
82876
+ renderRows() {
82877
+ const tbody = this.el.querySelector(".um-tbody");
82878
+ const empty = this.el.querySelector(".um-list-empty");
82879
+ tbody.innerHTML = "";
82880
+ if (this.users.length === 0) {
82881
+ empty.style.display = "";
82882
+ return;
82883
+ }
82884
+ empty.style.display = "none";
82885
+ for (const user of this.users) {
82886
+ const tr = document.createElement("tr");
82887
+ tr.dataset.userId = user.id.id;
82888
+ if (this.highlightedUserId === user.id.id) {
82889
+ tr.classList.add("um-row--highlight");
82890
+ this.highlightedUserId = null;
82891
+ }
82892
+ const name = [user.firstName, user.lastName].filter(Boolean).join(" ") || "\u2014";
82893
+ const role = user.authority === "TENANT_ADMIN" ? "Admin" : "Usu\xE1rio";
82894
+ tr.innerHTML = `
82895
+ <td>${this.esc(name)}</td>
82896
+ <td>${this.esc(user.email)}</td>
82897
+ <td><span class="um-badge um-badge--${user.authority === "TENANT_ADMIN" ? "admin" : "user"}">${role}</span></td>
82898
+ <td class="um-col-actions">
82899
+ <button class="um-icon-btn um-edit-btn" title="Editar">
82900
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
82901
+ stroke-linecap="round" stroke-linejoin="round">
82902
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
82903
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
82904
+ </svg>
82905
+ </button>
82906
+ <button class="um-icon-btn um-view-btn" title="Ver Detalhes">
82907
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
82908
+ stroke-linecap="round" stroke-linejoin="round">
82909
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
82910
+ <circle cx="12" cy="12" r="3"/>
82911
+ </svg>
82912
+ </button>
82913
+ </td>
82914
+ `;
82915
+ tr.querySelector(".um-edit-btn").addEventListener("click", () => this.callbacks.onOpenUserDetail(user, true));
82916
+ tr.querySelector(".um-view-btn").addEventListener("click", () => this.callbacks.onOpenUserDetail(user, false));
82917
+ tbody.appendChild(tr);
82918
+ }
82919
+ }
82920
+ renderPagination(page) {
82921
+ const pag = this.el.querySelector(".um-pagination");
82922
+ const info = this.el.querySelector(".um-page-info");
82923
+ const prevBtn = this.el.querySelector(".um-prev-btn");
82924
+ const nextBtn = this.el.querySelector(".um-next-btn");
82925
+ if (page.totalPages <= 1) {
82926
+ pag.style.display = "none";
82927
+ return;
82928
+ }
82929
+ pag.style.display = "";
82930
+ info.textContent = `P\xE1gina ${this.currentPage + 1} de ${page.totalPages}`;
82931
+ prevBtn.disabled = this.currentPage === 0;
82932
+ nextBtn.disabled = !page.hasNext;
82933
+ }
82934
+ setLoading(on) {
82935
+ const loadEl = this.el?.querySelector(".um-list-loading");
82936
+ const tableWrap = this.el?.querySelector(".um-table-wrap");
82937
+ if (loadEl) loadEl.style.display = on ? "" : "none";
82938
+ if (tableWrap) tableWrap.style.opacity = on ? "0.5" : "1";
82939
+ }
82940
+ esc(s) {
82941
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
82942
+ }
82943
+ };
82944
+
82945
+ // src/components/premium-modals/user-management/tabs/NewUserTab.ts
82946
+ var NewUserTab = class {
82947
+ config;
82948
+ callbacks;
82949
+ el;
82950
+ submitting = false;
82951
+ constructor(config, callbacks) {
82952
+ this.config = config;
82953
+ this.callbacks = callbacks;
82954
+ }
82955
+ render() {
82956
+ this.el = document.createElement("div");
82957
+ this.el.className = "um-tab-content um-new-user";
82958
+ this.el.innerHTML = `
82959
+ <form class="um-form" novalidate>
82960
+ <div class="um-form-row">
82961
+ <div class="um-form-group">
82962
+ <label class="um-label">Nome <span class="um-req">*</span></label>
82963
+ <input type="text" class="um-input" name="firstName" autocomplete="off" />
82964
+ <span class="um-field-error" data-for="firstName"></span>
82965
+ </div>
82966
+ <div class="um-form-group">
82967
+ <label class="um-label">Sobrenome <span class="um-req">*</span></label>
82968
+ <input type="text" class="um-input" name="lastName" autocomplete="off" />
82969
+ <span class="um-field-error" data-for="lastName"></span>
82970
+ </div>
82971
+ </div>
82972
+ <div class="um-form-group">
82973
+ <label class="um-label">E-mail <span class="um-req">*</span></label>
82974
+ <input type="email" class="um-input" name="email" autocomplete="off" />
82975
+ <span class="um-field-error" data-for="email"></span>
82976
+ </div>
82977
+ <div class="um-form-group">
82978
+ <label class="um-label">Telefone</label>
82979
+ <input type="text" class="um-input" name="phone" autocomplete="off" />
82980
+ </div>
82981
+ <div class="um-form-group">
82982
+ <label class="um-label">Descri\xE7\xE3o</label>
82983
+ <textarea class="um-input um-textarea" name="description" rows="2"></textarea>
82984
+ </div>
82985
+ <div class="um-form-group um-form-group--check">
82986
+ <label class="um-check-label">
82987
+ <input type="checkbox" name="sendActivationMail" checked />
82988
+ Enviar e-mail de ativa\xE7\xE3o
82989
+ </label>
82990
+ </div>
82991
+ <div class="um-form-actions">
82992
+ <button type="button" class="um-btn um-btn--ghost um-cancel-btn">Cancelar</button>
82993
+ <button type="submit" class="um-btn um-btn--primary um-submit-btn">Criar Usu\xE1rio</button>
82994
+ </div>
82995
+ </form>
82996
+ `;
82997
+ const form = this.el.querySelector(".um-form");
82998
+ form.addEventListener("submit", (e) => {
82999
+ e.preventDefault();
83000
+ this.handleSubmit();
83001
+ });
83002
+ this.el.querySelector(".um-cancel-btn").addEventListener("click", () => this.callbacks.onCancel());
83003
+ return this.el;
83004
+ }
83005
+ reset() {
83006
+ const form = this.el?.querySelector(".um-form");
83007
+ if (form) {
83008
+ form.reset();
83009
+ this.el.querySelectorAll(".um-field-error").forEach((el2) => el2.textContent = "");
83010
+ }
83011
+ }
83012
+ validate(data) {
83013
+ const errors = {};
83014
+ if (!data.firstName.trim()) errors.firstName = "Nome \xE9 obrigat\xF3rio.";
83015
+ if (!data.lastName.trim()) errors.lastName = "Sobrenome \xE9 obrigat\xF3rio.";
83016
+ if (!data.email.trim()) {
83017
+ errors.email = "E-mail \xE9 obrigat\xF3rio.";
83018
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
83019
+ errors.email = "E-mail inv\xE1lido.";
83020
+ }
83021
+ return errors;
83022
+ }
83023
+ showErrors(errors) {
83024
+ this.el.querySelectorAll("[data-for]").forEach((el2) => {
83025
+ el2.textContent = errors[el2.dataset.for] || "";
83026
+ });
83027
+ }
83028
+ async handleSubmit() {
83029
+ if (this.submitting) return;
83030
+ const form = this.el.querySelector(".um-form");
83031
+ const fd = new FormData(form);
83032
+ const data = {
83033
+ firstName: fd.get("firstName") || "",
83034
+ lastName: fd.get("lastName") || "",
83035
+ email: fd.get("email") || "",
83036
+ phone: fd.get("phone") || "",
83037
+ description: fd.get("description") || ""
83038
+ };
83039
+ const sendActivation = fd.get("sendActivationMail") === "on";
83040
+ const errors = this.validate(data);
83041
+ this.showErrors(errors);
83042
+ if (Object.keys(errors).length > 0) return;
83043
+ this.submitting = true;
83044
+ const submitBtn = this.el.querySelector(".um-submit-btn");
83045
+ submitBtn.disabled = true;
83046
+ submitBtn.textContent = "Criando...";
83047
+ try {
83048
+ const { tbBaseUrl, jwtToken, customerId, tenantId } = this.config;
83049
+ const url = `${tbBaseUrl}/api/user?sendActivationMail=${sendActivation}`;
83050
+ const body = {
83051
+ email: data.email,
83052
+ firstName: data.firstName,
83053
+ lastName: data.lastName,
83054
+ phone: data.phone || void 0,
83055
+ authority: "CUSTOMER_USER",
83056
+ customerId: { id: customerId, entityType: "CUSTOMER" },
83057
+ tenantId: { id: tenantId, entityType: "TENANT" },
83058
+ additionalInfo: data.description ? { description: data.description } : void 0
83059
+ };
83060
+ const res = await fetch(url, {
83061
+ method: "POST",
83062
+ headers: {
83063
+ "X-Authorization": `Bearer ${jwtToken}`,
83064
+ "Content-Type": "application/json"
83065
+ },
83066
+ body: JSON.stringify(body)
83067
+ });
83068
+ if (!res.ok) {
83069
+ const errText = await res.text().catch(() => "");
83070
+ throw new Error(`HTTP ${res.status}${errText ? ": " + errText.slice(0, 120) : ""}`);
83071
+ }
83072
+ const created = await res.json();
83073
+ this.callbacks.showToast(`Usu\xE1rio ${data.firstName} ${data.lastName} criado com sucesso!`, "success");
83074
+ this.reset();
83075
+ this.callbacks.onCreated(created?.id?.id || "");
83076
+ } catch (err) {
83077
+ console.error("[NewUserTab] create user error", err);
83078
+ this.callbacks.showToast("Erro ao criar usu\xE1rio. Verifique os dados e tente novamente.", "error");
83079
+ } finally {
83080
+ this.submitting = false;
83081
+ submitBtn.disabled = false;
83082
+ submitBtn.textContent = "Criar Usu\xE1rio";
83083
+ }
83084
+ }
83085
+ };
83086
+
83087
+ // src/components/premium-modals/user-management/tabs/ProfileManagementTab.ts
83088
+ var ProfileManagementTab = class {
83089
+ config;
83090
+ el;
83091
+ constructor(config) {
83092
+ this.config = config;
83093
+ }
83094
+ render() {
83095
+ this.el = document.createElement("div");
83096
+ this.el.className = "um-tab-content um-profiles";
83097
+ this.el.innerHTML = `
83098
+ <div class="um-profiles-header">
83099
+ <h3 class="um-profiles-title">Perfis configurados para este cliente</h3>
83100
+ </div>
83101
+ <div class="um-profiles-body">
83102
+ <div class="um-profiles-loading">
83103
+ <span class="um-spinner"></span> Carregando perfis...
83104
+ </div>
83105
+ <table class="um-table um-profiles-table" style="display:none">
83106
+ <thead>
83107
+ <tr>
83108
+ <th>Nome do Grupo</th>
83109
+ <th>Tipo</th>
83110
+ <th>Membros</th>
83111
+ </tr>
83112
+ </thead>
83113
+ <tbody class="um-profiles-tbody"></tbody>
83114
+ </table>
83115
+ <div class="um-profiles-empty" style="display:none">Nenhum perfil encontrado.</div>
83116
+ <div class="um-profiles-error" style="display:none"></div>
83117
+ </div>
83118
+ <div class="um-profiles-notice">
83119
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
83120
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
83121
+ <circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/>
83122
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
83123
+ </svg>
83124
+ Edi\xE7\xE3o de perfis estar\xE1 dispon\xEDvel em vers\xE3o futura.
83125
+ </div>
83126
+ `;
83127
+ this.loadGroups();
83128
+ return this.el;
83129
+ }
83130
+ async loadGroups() {
83131
+ const { tbBaseUrl, jwtToken, customerId } = this.config;
83132
+ const loadingEl = this.el.querySelector(".um-profiles-loading");
83133
+ const tableEl = this.el.querySelector(".um-profiles-table");
83134
+ const emptyEl = this.el.querySelector(".um-profiles-empty");
83135
+ const errorEl = this.el.querySelector(".um-profiles-error");
83136
+ const tbody = this.el.querySelector(".um-profiles-tbody");
83137
+ try {
83138
+ const url = `${tbBaseUrl}/api/entityGroups/CUSTOMER/${customerId}/USER`;
83139
+ const res = await fetch(url, {
83140
+ headers: { "X-Authorization": `Bearer ${jwtToken}` }
83141
+ });
83142
+ loadingEl.style.display = "none";
83143
+ if (!res.ok) {
83144
+ if (res.status === 403 || res.status === 404) {
83145
+ emptyEl.style.display = "";
83146
+ emptyEl.textContent = "Gest\xE3o de grupos n\xE3o dispon\xEDvel nesta edi\xE7\xE3o do ThingsBoard.";
83147
+ return;
83148
+ }
83149
+ throw new Error(`HTTP ${res.status}`);
83150
+ }
83151
+ const groups = await res.json();
83152
+ if (!groups || groups.length === 0) {
83153
+ emptyEl.style.display = "";
83154
+ return;
83155
+ }
83156
+ for (const g of groups) {
83157
+ const tr = document.createElement("tr");
83158
+ const memberCount = g.additionalInfo?.memberCount ?? "\u2014";
83159
+ tr.innerHTML = `
83160
+ <td>${this.esc(g.name || "\u2014")}</td>
83161
+ <td>USER_GROUP</td>
83162
+ <td>${memberCount}</td>
83163
+ `;
83164
+ tbody.appendChild(tr);
83165
+ }
83166
+ tableEl.style.display = "";
83167
+ } catch (err) {
83168
+ console.warn("[ProfileManagementTab] loadGroups error", err);
83169
+ loadingEl.style.display = "none";
83170
+ errorEl.style.display = "";
83171
+ errorEl.textContent = "N\xE3o foi poss\xEDvel carregar os perfis neste momento.";
83172
+ }
83173
+ }
83174
+ esc(s) {
83175
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
83176
+ }
83177
+ };
83178
+
83179
+ // src/components/premium-modals/user-management/types.ts
83180
+ function buildUserTabLabel(user) {
83181
+ const first = (user.firstName || user.email.split("@")[0] || "?").toUpperCase();
83182
+ const lastInitial = user.lastName ? user.lastName[0].toUpperCase() + "." : "";
83183
+ return lastInitial ? `${first} ${lastInitial}` : first;
83184
+ }
83185
+
83186
+ // src/components/premium-modals/user-management/tabs/UserDetailTab.ts
83187
+ var UserDetailTab = class {
83188
+ config;
83189
+ callbacks;
83190
+ user;
83191
+ el;
83192
+ mode = "view";
83193
+ saving = false;
83194
+ constructor(config, user, callbacks) {
83195
+ this.config = config;
83196
+ this.user = user;
83197
+ this.callbacks = callbacks;
83198
+ }
83199
+ get tabLabel() {
83200
+ return buildUserTabLabel(this.user);
83201
+ }
83202
+ render() {
83203
+ this.el = document.createElement("div");
83204
+ this.el.className = "um-tab-content um-user-detail";
83205
+ this.renderContent();
83206
+ return this.el;
83207
+ }
83208
+ /** Called after tab is re-activated when user already has an open tab */
83209
+ focus() {
83210
+ this.el?.querySelector(".um-detail-card")?.scrollIntoView({ behavior: "smooth", block: "start" });
83211
+ }
83212
+ renderContent() {
83213
+ this.el.innerHTML = "";
83214
+ if (this.mode === "view") {
83215
+ this.el.appendChild(this.buildViewMode());
83216
+ } else {
83217
+ this.el.appendChild(this.buildEditMode());
83218
+ }
83219
+ }
83220
+ buildViewMode() {
83221
+ const u = this.user;
83222
+ const name = [u.firstName, u.lastName].filter(Boolean).join(" ") || "\u2014";
83223
+ const role = u.authority === "TENANT_ADMIN" ? "Admin" : "Usu\xE1rio";
83224
+ const createdAt = u.createdTime ? new Date(u.createdTime).toLocaleDateString("pt-BR") : "\u2014";
83225
+ const card = document.createElement("div");
83226
+ card.className = "um-detail-card";
83227
+ card.innerHTML = `
83228
+ <div class="um-detail-section">
83229
+ <div class="um-detail-row"><span class="um-detail-label">Nome</span><span class="um-detail-value">${this.esc(name)}</span></div>
83230
+ <div class="um-detail-row"><span class="um-detail-label">E-mail</span><span class="um-detail-value">${this.esc(u.email)}</span></div>
83231
+ <div class="um-detail-row"><span class="um-detail-label">Telefone</span><span class="um-detail-value">${this.esc(u.phone || "\u2014")}</span></div>
83232
+ <div class="um-detail-row"><span class="um-detail-label">Perfil</span><span class="um-detail-value">
83233
+ <span class="um-badge um-badge--${u.authority === "TENANT_ADMIN" ? "admin" : "user"}">${role}</span>
83234
+ </span></div>
83235
+ <div class="um-detail-row"><span class="um-detail-label">Criado em</span><span class="um-detail-value">${createdAt}</span></div>
83236
+ ${u.additionalInfo?.description ? `<div class="um-detail-row"><span class="um-detail-label">Descri\xE7\xE3o</span><span class="um-detail-value">${this.esc(String(u.additionalInfo.description))}</span></div>` : ""}
83237
+ </div>
83238
+ <div class="um-detail-actions">
83239
+ <button class="um-btn um-btn--secondary um-detail-edit-btn">Editar</button>
83240
+ <button class="um-btn um-btn--ghost um-detail-reset-btn">Redefinir Senha</button>
83241
+ <button class="um-btn um-btn--danger um-detail-delete-btn">Excluir</button>
83242
+ </div>
83243
+ `;
83244
+ card.querySelector(".um-detail-edit-btn").addEventListener("click", () => {
83245
+ this.mode = "edit";
83246
+ this.renderContent();
83247
+ });
83248
+ card.querySelector(".um-detail-reset-btn").addEventListener("click", () => this.handleResetPassword());
83249
+ card.querySelector(".um-detail-delete-btn").addEventListener("click", () => this.handleDelete());
83250
+ return card;
83251
+ }
83252
+ buildEditMode() {
83253
+ const u = this.user;
83254
+ const card = document.createElement("div");
83255
+ card.className = "um-detail-card um-detail-card--edit";
83256
+ card.innerHTML = `
83257
+ <form class="um-form" novalidate>
83258
+ <div class="um-form-row">
83259
+ <div class="um-form-group">
83260
+ <label class="um-label">Nome <span class="um-req">*</span></label>
83261
+ <input type="text" class="um-input" name="firstName" value="${this.esc(u.firstName || "")}" autocomplete="off" />
83262
+ <span class="um-field-error" data-for="firstName"></span>
83263
+ </div>
83264
+ <div class="um-form-group">
83265
+ <label class="um-label">Sobrenome</label>
83266
+ <input type="text" class="um-input" name="lastName" value="${this.esc(u.lastName || "")}" autocomplete="off" />
83267
+ </div>
83268
+ </div>
83269
+ <div class="um-form-group">
83270
+ <label class="um-label">E-mail <span class="um-req">*</span></label>
83271
+ <input type="email" class="um-input" name="email" value="${this.esc(u.email)}" autocomplete="off" />
83272
+ <span class="um-field-error" data-for="email"></span>
83273
+ </div>
83274
+ <div class="um-form-group">
83275
+ <label class="um-label">Telefone</label>
83276
+ <input type="text" class="um-input" name="phone" value="${this.esc(u.phone || "")}" autocomplete="off" />
83277
+ </div>
83278
+ <div class="um-form-group">
83279
+ <label class="um-label">Descri\xE7\xE3o</label>
83280
+ <textarea class="um-input um-textarea" name="description" rows="2">${this.esc(String(u.additionalInfo?.description || ""))}</textarea>
83281
+ </div>
83282
+ <div class="um-form-actions">
83283
+ <button type="button" class="um-btn um-btn--ghost um-cancel-edit-btn">Cancelar</button>
83284
+ <button type="submit" class="um-btn um-btn--primary um-save-btn">Salvar Altera\xE7\xF5es</button>
83285
+ </div>
83286
+ </form>
83287
+ `;
83288
+ card.querySelector(".um-form").addEventListener("submit", (e) => {
83289
+ e.preventDefault();
83290
+ this.handleSave(card);
83291
+ });
83292
+ card.querySelector(".um-cancel-edit-btn").addEventListener("click", () => {
83293
+ this.mode = "view";
83294
+ this.renderContent();
83295
+ });
83296
+ return card;
83297
+ }
83298
+ async handleSave(card) {
83299
+ if (this.saving) return;
83300
+ const form = card.querySelector(".um-form");
83301
+ const fd = new FormData(form);
83302
+ const firstName = (fd.get("firstName") || "").trim();
83303
+ const email = (fd.get("email") || "").trim();
83304
+ const errors = {};
83305
+ if (!firstName) errors.firstName = "Nome \xE9 obrigat\xF3rio.";
83306
+ if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errors.email = "E-mail inv\xE1lido.";
83307
+ card.querySelectorAll("[data-for]").forEach((el2) => {
83308
+ el2.textContent = errors[el2.dataset.for] || "";
83309
+ });
83310
+ if (Object.keys(errors).length > 0) return;
83311
+ this.saving = true;
83312
+ const saveBtn = card.querySelector(".um-save-btn");
83313
+ saveBtn.disabled = true;
83314
+ saveBtn.textContent = "Salvando...";
83315
+ try {
83316
+ const { tbBaseUrl, jwtToken } = this.config;
83317
+ const description = (fd.get("description") || "").trim();
83318
+ const updatedUser = {
83319
+ ...this.user,
83320
+ firstName,
83321
+ lastName: (fd.get("lastName") || "").trim() || void 0,
83322
+ email,
83323
+ phone: (fd.get("phone") || "").trim() || void 0,
83324
+ additionalInfo: {
83325
+ ...this.user.additionalInfo,
83326
+ description: description || void 0
83327
+ }
83328
+ };
83329
+ const res = await fetch(`${tbBaseUrl}/api/user`, {
83330
+ method: "POST",
83331
+ headers: {
83332
+ "X-Authorization": `Bearer ${jwtToken}`,
83333
+ "Content-Type": "application/json"
83334
+ },
83335
+ body: JSON.stringify(updatedUser)
83336
+ });
83337
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
83338
+ const saved = await res.json();
83339
+ this.user = saved;
83340
+ this.mode = "view";
83341
+ this.renderContent();
83342
+ this.callbacks.showToast("Usu\xE1rio atualizado com sucesso!", "success");
83343
+ this.callbacks.onUpdated(saved);
83344
+ } catch (err) {
83345
+ console.error("[UserDetailTab] handleSave error", err);
83346
+ this.callbacks.showToast("Erro ao salvar. Tente novamente.", "error");
83347
+ } finally {
83348
+ this.saving = false;
83349
+ saveBtn.disabled = false;
83350
+ saveBtn.textContent = "Salvar Altera\xE7\xF5es";
83351
+ }
83352
+ }
83353
+ handleResetPassword() {
83354
+ this.showConfirmDialog({
83355
+ title: "Redefinir Senha",
83356
+ message: `Enviar e-mail de redefini\xE7\xE3o de senha para <strong>${this.esc(this.user.email)}</strong>?`,
83357
+ confirmLabel: "Enviar E-mail",
83358
+ confirmClass: "um-btn--secondary",
83359
+ onConfirm: async () => {
83360
+ try {
83361
+ const { tbBaseUrl, jwtToken } = this.config;
83362
+ const res = await fetch(`${tbBaseUrl}/api/noauth/resetPasswordByEmail`, {
83363
+ method: "POST",
83364
+ headers: {
83365
+ "X-Authorization": `Bearer ${jwtToken}`,
83366
+ "Content-Type": "application/json"
83367
+ },
83368
+ body: JSON.stringify({ email: this.user.email })
83369
+ });
83370
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
83371
+ this.callbacks.showToast("E-mail de redefini\xE7\xE3o de senha enviado!", "success");
83372
+ } catch (err) {
83373
+ console.error("[UserDetailTab] resetPassword error", err);
83374
+ this.callbacks.showToast("Erro ao enviar e-mail de redefini\xE7\xE3o.", "error");
83375
+ }
83376
+ }
83377
+ });
83378
+ }
83379
+ handleDelete() {
83380
+ const name = [this.user.firstName, this.user.lastName].filter(Boolean).join(" ") || this.user.email;
83381
+ this.showConfirmDialog({
83382
+ title: "Excluir Usu\xE1rio",
83383
+ message: `Tem certeza que deseja excluir o usu\xE1rio <strong>${this.esc(name)}</strong>? Esta a\xE7\xE3o n\xE3o pode ser desfeita.`,
83384
+ confirmLabel: "Excluir",
83385
+ confirmClass: "um-btn--danger",
83386
+ onConfirm: async () => {
83387
+ try {
83388
+ const { tbBaseUrl, jwtToken } = this.config;
83389
+ const res = await fetch(`${tbBaseUrl}/api/user/${this.user.id.id}`, {
83390
+ method: "DELETE",
83391
+ headers: { "X-Authorization": `Bearer ${jwtToken}` }
83392
+ });
83393
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
83394
+ this.callbacks.showToast(`Usu\xE1rio ${name} exclu\xEDdo.`, "success");
83395
+ this.callbacks.onDeleted();
83396
+ } catch (err) {
83397
+ console.error("[UserDetailTab] delete error", err);
83398
+ this.callbacks.showToast("Erro ao excluir usu\xE1rio. Verifique as permiss\xF5es.", "error");
83399
+ }
83400
+ }
83401
+ });
83402
+ }
83403
+ showConfirmDialog(opts) {
83404
+ const overlay = document.createElement("div");
83405
+ overlay.className = "um-confirm-overlay";
83406
+ overlay.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:100001;";
83407
+ const modal = document.createElement("div");
83408
+ modal.className = "um-confirm-modal";
83409
+ modal.style.cssText = "background:#1e2433;border:1px solid #3a4160;border-radius:12px;padding:24px;max-width:440px;width:90%;box-shadow:0 24px 64px rgba(0,0,0,0.5);";
83410
+ modal.innerHTML = `
83411
+ <h4 style="margin:0 0 12px;font-size:16px;font-weight:600;color:#e2e8f0;">${opts.title}</h4>
83412
+ <p style="margin:0 0 20px;font-size:14px;color:#94a3b8;line-height:1.5;">${opts.message}</p>
83413
+ <div style="display:flex;gap:8px;justify-content:flex-end;">
83414
+ <button class="um-btn um-btn--ghost um-confirm-cancel">Cancelar</button>
83415
+ <button class="um-btn ${opts.confirmClass} um-confirm-ok">${opts.confirmLabel}</button>
83416
+ </div>
83417
+ `;
83418
+ overlay.appendChild(modal);
83419
+ document.body.appendChild(overlay);
83420
+ const close = () => overlay.remove();
83421
+ overlay.addEventListener("click", (e) => {
83422
+ if (e.target === overlay) close();
83423
+ });
83424
+ modal.querySelector(".um-confirm-cancel").addEventListener("click", close);
83425
+ modal.querySelector(".um-confirm-ok").addEventListener("click", async () => {
83426
+ const btn = modal.querySelector(".um-confirm-ok");
83427
+ btn.disabled = true;
83428
+ btn.textContent = "...";
83429
+ close();
83430
+ await opts.onConfirm();
83431
+ });
83432
+ }
83433
+ esc(s) {
83434
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
83435
+ }
83436
+ };
83437
+
83438
+ // src/components/premium-modals/user-management/UserManagementModalView.ts
83439
+ var UserManagementModalView = class {
83440
+ config;
83441
+ backdrop;
83442
+ modalEl;
83443
+ tabBarEl;
83444
+ contentEl;
83445
+ toastEl;
83446
+ tabs = [];
83447
+ activeTabKey = "user-list";
83448
+ userListTab;
83449
+ newUserTab;
83450
+ constructor(config) {
83451
+ this.config = config;
83452
+ }
83453
+ render() {
83454
+ this.injectStyles();
83455
+ this.buildDOM();
83456
+ this.buildTabs();
83457
+ this.activateTab("user-list");
83458
+ document.body.appendChild(this.backdrop);
83459
+ }
83460
+ destroy() {
83461
+ this.backdrop?.remove();
83462
+ }
83463
+ showToast(msg, type = "success") {
83464
+ if (!this.toastEl) return;
83465
+ this.toastEl.textContent = msg;
83466
+ this.toastEl.className = `um-toast um-toast--${type} um-toast--visible`;
83467
+ setTimeout(() => {
83468
+ this.toastEl.classList.remove("um-toast--visible");
83469
+ }, 3500);
83470
+ }
83471
+ // ── DOM ──────────────────────────────────────────────────────────────────────
83472
+ buildDOM() {
83473
+ this.backdrop = document.createElement("div");
83474
+ this.backdrop.className = "um-backdrop";
83475
+ this.modalEl = document.createElement("div");
83476
+ this.modalEl.className = "um-modal";
83477
+ const customerName = this.config.customerName || "";
83478
+ const header = document.createElement("div");
83479
+ header.className = "um-modal-header";
83480
+ header.innerHTML = `
83481
+ <div class="um-modal-header-left">
83482
+ <span class="um-modal-icon">
83483
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
83484
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
83485
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
83486
+ <circle cx="9" cy="7" r="4"/>
83487
+ <path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
83488
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
83489
+ </svg>
83490
+ </span>
83491
+ <span class="um-modal-title">Gest\xE3o de Usu\xE1rios</span>
83492
+ ${customerName ? `<span class="um-modal-subtitle">\xB7 ${this.esc(customerName)}</span>` : ""}
83493
+ </div>
83494
+ <button class="um-modal-close" title="Fechar">
83495
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
83496
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
83497
+ <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
83498
+ </svg>
83499
+ </button>
83500
+ `;
83501
+ header.querySelector(".um-modal-close").addEventListener("click", () => this.close());
83502
+ this.tabBarEl = document.createElement("div");
83503
+ this.tabBarEl.className = "um-tab-bar";
83504
+ this.contentEl = document.createElement("div");
83505
+ this.contentEl.className = "um-modal-content";
83506
+ this.toastEl = document.createElement("div");
83507
+ this.toastEl.className = "um-toast";
83508
+ this.modalEl.appendChild(header);
83509
+ this.modalEl.appendChild(this.tabBarEl);
83510
+ this.modalEl.appendChild(this.contentEl);
83511
+ this.modalEl.appendChild(this.toastEl);
83512
+ this.backdrop.appendChild(this.modalEl);
83513
+ this.backdrop.addEventListener("click", (e) => {
83514
+ if (e.target === this.backdrop) this.close();
83515
+ });
83516
+ }
83517
+ close() {
83518
+ this.config.onClose();
83519
+ this.destroy();
83520
+ }
83521
+ // ── Tabs ─────────────────────────────────────────────────────────────────────
83522
+ buildTabs() {
83523
+ const config = this.config;
83524
+ this.userListTab = new UserListTab(config, {
83525
+ onOpenUserDetail: (user, editMode) => this.openUserDetailTab(user, editMode),
83526
+ onSwitchToNewUser: () => this.activateTab("new-user"),
83527
+ showToast: (msg, type) => this.showToast(msg, type)
83528
+ });
83529
+ this.tabs.push({
83530
+ key: "user-list",
83531
+ label: "Usu\xE1rios",
83532
+ closeable: false,
83533
+ getEl: () => this.userListTab.render()
83534
+ });
83535
+ this.newUserTab = new NewUserTab(config, {
83536
+ onCreated: (userId) => {
83537
+ this.activateTab("user-list");
83538
+ this.userListTab.refresh(userId);
83539
+ },
83540
+ onCancel: () => this.activateTab("user-list"),
83541
+ showToast: (msg, type) => this.showToast(msg, type)
83542
+ });
83543
+ this.tabs.push({
83544
+ key: "new-user",
83545
+ label: "Novo Usu\xE1rio",
83546
+ closeable: false,
83547
+ getEl: () => this.newUserTab.render(),
83548
+ onActivate: () => this.newUserTab.reset()
83549
+ });
83550
+ const profileTab = new ProfileManagementTab(config);
83551
+ this.tabs.push({
83552
+ key: "profiles",
83553
+ label: "Perfis",
83554
+ closeable: false,
83555
+ getEl: () => profileTab.render()
83556
+ });
83557
+ this.rebuildTabBar();
83558
+ }
83559
+ openUserDetailTab(user, editMode = false) {
83560
+ const existingKey = `user-detail-${user.id.id}`;
83561
+ const existing = this.tabs.find((t) => t.key === existingKey);
83562
+ if (existing) {
83563
+ this.activateTab(existingKey);
83564
+ existing.detailTab?.focus();
83565
+ return;
83566
+ }
83567
+ const detailTab = new UserDetailTab(this.config, user, {
83568
+ onDeleted: () => {
83569
+ this.removeTab(existingKey);
83570
+ this.userListTab.refresh();
83571
+ },
83572
+ onUpdated: (updated) => {
83573
+ this.userListTab.refresh(updated.id.id);
83574
+ const entry = this.tabs.find((t) => t.key === existingKey);
83575
+ if (entry) {
83576
+ entry.label = detailTab.tabLabel;
83577
+ this.rebuildTabBar();
83578
+ }
83579
+ },
83580
+ showToast: (msg, type) => this.showToast(msg, type)
83581
+ });
83582
+ let el2 = null;
83583
+ this.tabs.push({
83584
+ key: existingKey,
83585
+ label: detailTab.tabLabel,
83586
+ closeable: true,
83587
+ userId: user.id.id,
83588
+ detailTab,
83589
+ getEl: () => {
83590
+ el2 = detailTab.render();
83591
+ if (editMode) {
83592
+ setTimeout(() => el2?.querySelector(".um-detail-edit-btn")?.click(), 0);
83593
+ }
83594
+ return el2;
83595
+ }
83596
+ });
83597
+ this.rebuildTabBar();
83598
+ this.activateTab(existingKey);
83599
+ }
83600
+ removeTab(key) {
83601
+ const idx = this.tabs.findIndex((t) => t.key === key);
83602
+ if (idx === -1) return;
83603
+ this.tabs.splice(idx, 1);
83604
+ if (this.activeTabKey === key) {
83605
+ this.activateTab("user-list");
83606
+ } else {
83607
+ this.rebuildTabBar();
83608
+ }
83609
+ }
83610
+ activateTab(key) {
83611
+ const entry = this.tabs.find((t) => t.key === key);
83612
+ if (!entry) return;
83613
+ this.activeTabKey = key;
83614
+ entry.onActivate?.();
83615
+ this.contentEl.innerHTML = "";
83616
+ this.contentEl.appendChild(entry.getEl());
83617
+ this.rebuildTabBar();
83618
+ }
83619
+ rebuildTabBar() {
83620
+ this.tabBarEl.innerHTML = "";
83621
+ for (const tab of this.tabs) {
83622
+ const btn = document.createElement("button");
83623
+ btn.className = `um-tab-btn${tab.key === this.activeTabKey ? " um-tab-btn--active" : ""}`;
83624
+ btn.textContent = tab.label;
83625
+ btn.addEventListener("click", () => this.activateTab(tab.key));
83626
+ if (tab.closeable) {
83627
+ const close = document.createElement("span");
83628
+ close.className = "um-tab-close";
83629
+ close.innerHTML = "\u2715";
83630
+ close.title = "Fechar aba";
83631
+ close.addEventListener("click", (e) => {
83632
+ e.stopPropagation();
83633
+ this.removeTab(tab.key);
83634
+ });
83635
+ btn.appendChild(close);
83636
+ }
83637
+ this.tabBarEl.appendChild(btn);
83638
+ }
83639
+ }
83640
+ // ── Styles ───────────────────────────────────────────────────────────────────
83641
+ injectStyles() {
83642
+ if (document.getElementById("um-styles")) return;
83643
+ const style = document.createElement("style");
83644
+ style.id = "um-styles";
83645
+ style.textContent = `
83646
+ /* === UserManagement Modal === */
83647
+ .um-backdrop {
83648
+ position: fixed; inset: 0;
83649
+ background: rgba(0,0,0,0.55);
83650
+ backdrop-filter: blur(4px);
83651
+ display: flex; align-items: center; justify-content: center;
83652
+ z-index: 99999;
83653
+ padding: 16px;
83654
+ }
83655
+ .um-modal {
83656
+ background: #131929;
83657
+ border: 1px solid #2a3352;
83658
+ border-radius: 14px;
83659
+ width: 92vw; max-width: 960px;
83660
+ aspect-ratio: 16/9;
83661
+ display: flex; flex-direction: column;
83662
+ box-shadow: 0 32px 80px rgba(0,0,0,0.6);
83663
+ overflow: hidden;
83664
+ position: relative;
83665
+ }
83666
+ @media (max-height: 600px) { .um-modal { aspect-ratio: unset; height: 90vh; } }
83667
+
83668
+ /* Header */
83669
+ .um-modal-header {
83670
+ display: flex; align-items: center; justify-content: space-between;
83671
+ padding: 14px 20px; border-bottom: 1px solid #1e2d4a;
83672
+ flex-shrink: 0;
83673
+ }
83674
+ .um-modal-header-left { display: flex; align-items: center; gap: 10px; }
83675
+ .um-modal-icon { color: #60a5fa; display: flex; }
83676
+ .um-modal-title { font-size: 15px; font-weight: 600; color: #e2e8f0; }
83677
+ .um-modal-subtitle { font-size: 13px; color: #64748b; }
83678
+ .um-modal-close {
83679
+ background: none; border: none; cursor: pointer;
83680
+ color: #64748b; padding: 4px; border-radius: 6px;
83681
+ display: flex; align-items: center; justify-content: center;
83682
+ transition: color 0.15s, background 0.15s;
83683
+ }
83684
+ .um-modal-close:hover { color: #e2e8f0; background: #1e2d4a; }
83685
+
83686
+ /* Tab bar */
83687
+ .um-tab-bar {
83688
+ display: flex; gap: 2px; padding: 0 16px;
83689
+ border-bottom: 1px solid #1e2d4a; flex-shrink: 0;
83690
+ overflow-x: auto; scrollbar-width: none;
83691
+ }
83692
+ .um-tab-bar::-webkit-scrollbar { display: none; }
83693
+ .um-tab-btn {
83694
+ background: none; border: none; cursor: pointer;
83695
+ color: #64748b; font-size: 12px; font-weight: 500;
83696
+ padding: 10px 14px; border-bottom: 2px solid transparent;
83697
+ white-space: nowrap; display: flex; align-items: center; gap: 6px;
83698
+ transition: color 0.15s, border-color 0.15s;
83699
+ }
83700
+ .um-tab-btn:hover { color: #94a3b8; }
83701
+ .um-tab-btn--active { color: #60a5fa; border-bottom-color: #60a5fa; }
83702
+ .um-tab-close {
83703
+ font-size: 9px; color: #475569; padding: 2px 3px;
83704
+ border-radius: 3px; line-height: 1; cursor: pointer;
83705
+ transition: background 0.15s, color 0.15s;
83706
+ }
83707
+ .um-tab-close:hover { background: #3a4160; color: #e2e8f0; }
83708
+
83709
+ /* Content */
83710
+ .um-modal-content {
83711
+ flex: 1; overflow-y: auto; padding: 20px;
83712
+ scrollbar-width: thin; scrollbar-color: #2a3352 transparent;
83713
+ }
83714
+ .um-modal-content::-webkit-scrollbar { width: 6px; }
83715
+ .um-modal-content::-webkit-scrollbar-thumb { background: #2a3352; border-radius: 3px; }
83716
+
83717
+ /* Toast */
83718
+ .um-toast {
83719
+ position: absolute; bottom: 16px; right: 16px;
83720
+ background: #1e3a2e; border: 1px solid #22c55e;
83721
+ color: #4ade80; font-size: 12px; font-weight: 500;
83722
+ padding: 10px 16px; border-radius: 8px;
83723
+ opacity: 0; transform: translateY(8px);
83724
+ transition: opacity 0.2s, transform 0.2s;
83725
+ pointer-events: none; z-index: 10;
83726
+ max-width: 300px;
83727
+ }
83728
+ .um-toast--error { background: #3b1a1a; border-color: #ef4444; color: #f87171; }
83729
+ .um-toast--visible { opacity: 1; transform: translateY(0); }
83730
+
83731
+ /* Tab content */
83732
+ .um-tab-content { min-height: 100%; }
83733
+
83734
+ /* Toolbar */
83735
+ .um-list-toolbar {
83736
+ display: flex; align-items: center; justify-content: space-between;
83737
+ gap: 12px; margin-bottom: 16px;
83738
+ }
83739
+ .um-search-wrap {
83740
+ position: relative; flex: 1; max-width: 360px;
83741
+ }
83742
+ .um-search-icon {
83743
+ position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
83744
+ color: #475569; pointer-events: none;
83745
+ }
83746
+ .um-search-input {
83747
+ width: 100%; background: #1a2235; border: 1px solid #2a3352;
83748
+ border-radius: 8px; padding: 7px 12px 7px 32px;
83749
+ color: #e2e8f0; font-size: 13px; outline: none;
83750
+ transition: border-color 0.15s;
83751
+ }
83752
+ .um-search-input:focus { border-color: #3b5bdb; }
83753
+ .um-search-input::placeholder { color: #475569; }
83754
+
83755
+ /* Table */
83756
+ .um-table-wrap { position: relative; overflow-x: auto; }
83757
+ .um-table {
83758
+ width: 100%; border-collapse: collapse; font-size: 13px;
83759
+ }
83760
+ .um-table th {
83761
+ text-align: left; padding: 8px 12px;
83762
+ color: #64748b; font-weight: 500; font-size: 11px;
83763
+ text-transform: uppercase; letter-spacing: 0.04em;
83764
+ border-bottom: 1px solid #1e2d4a;
83765
+ }
83766
+ .um-table td {
83767
+ padding: 10px 12px; color: #c4ccd9;
83768
+ border-bottom: 1px solid #0f1829;
83769
+ }
83770
+ .um-table tr:hover td { background: #0f1829; }
83771
+ .um-col-actions { width: 80px; text-align: center; }
83772
+ .um-row--highlight td { background: #0e2a1e !important; }
83773
+
83774
+ /* List states */
83775
+ .um-list-empty, .um-list-loading, .um-profiles-loading,
83776
+ .um-profiles-empty, .um-profiles-error {
83777
+ text-align: center; padding: 32px 16px; color: #475569; font-size: 13px;
83778
+ }
83779
+
83780
+ /* Pagination */
83781
+ .um-pagination {
83782
+ display: flex; align-items: center; justify-content: center;
83783
+ gap: 16px; margin-top: 16px; font-size: 12px; color: #64748b;
83784
+ }
83785
+
83786
+ /* Icon buttons */
83787
+ .um-icon-btn {
83788
+ background: none; border: none; cursor: pointer;
83789
+ color: #475569; padding: 5px; border-radius: 6px;
83790
+ display: inline-flex; align-items: center; justify-content: center;
83791
+ transition: color 0.15s, background 0.15s;
83792
+ }
83793
+ .um-icon-btn:hover { color: #60a5fa; background: #1a2a45; }
83794
+
83795
+ /* Buttons */
83796
+ .um-btn {
83797
+ border: none; cursor: pointer; font-size: 13px; font-weight: 500;
83798
+ padding: 8px 16px; border-radius: 8px; transition: opacity 0.15s, background 0.15s;
83799
+ }
83800
+ .um-btn:disabled { opacity: 0.5; cursor: not-allowed; }
83801
+ .um-btn--primary { background: #3b5bdb; color: #fff; }
83802
+ .um-btn--primary:hover:not(:disabled) { background: #4c6ef5; }
83803
+ .um-btn--secondary { background: #1e2d4a; color: #93c5fd; border: 1px solid #2a3b60; }
83804
+ .um-btn--secondary:hover:not(:disabled) { background: #253456; }
83805
+ .um-btn--ghost { background: transparent; color: #64748b; border: 1px solid #2a3352; }
83806
+ .um-btn--ghost:hover:not(:disabled) { background: #1a2235; color: #94a3b8; }
83807
+ .um-btn--danger { background: #7f1d1d; color: #fca5a5; border: 1px solid #991b1b; }
83808
+ .um-btn--danger:hover:not(:disabled) { background: #991b1b; }
83809
+ .um-btn--sm { padding: 6px 12px; font-size: 12px; }
83810
+
83811
+ /* Badges */
83812
+ .um-badge {
83813
+ display: inline-block; font-size: 10px; font-weight: 600;
83814
+ padding: 2px 8px; border-radius: 9999px;
83815
+ }
83816
+ .um-badge--admin { background: #1e3a5f; color: #60a5fa; }
83817
+ .um-badge--user { background: #1a2d24; color: #4ade80; }
83818
+
83819
+ /* Forms */
83820
+ .um-form { display: flex; flex-direction: column; gap: 14px; max-width: 560px; }
83821
+ .um-form-row { display: flex; gap: 12px; }
83822
+ .um-form-row .um-form-group { flex: 1; }
83823
+ .um-form-group { display: flex; flex-direction: column; gap: 5px; }
83824
+ .um-form-group--check { flex-direction: row; align-items: center; }
83825
+ .um-label { font-size: 12px; font-weight: 500; color: #94a3b8; }
83826
+ .um-req { color: #f87171; }
83827
+ .um-input {
83828
+ background: #1a2235; border: 1px solid #2a3352;
83829
+ border-radius: 8px; padding: 8px 12px;
83830
+ color: #e2e8f0; font-size: 13px; outline: none; width: 100%;
83831
+ transition: border-color 0.15s; box-sizing: border-box;
83832
+ }
83833
+ .um-input:focus { border-color: #3b5bdb; }
83834
+ .um-textarea { resize: vertical; min-height: 64px; }
83835
+ .um-field-error { font-size: 11px; color: #f87171; min-height: 14px; }
83836
+ .um-check-label { font-size: 13px; color: #94a3b8; cursor: pointer; display: flex; align-items: center; gap: 8px; }
83837
+ .um-form-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }
83838
+
83839
+ /* Detail card */
83840
+ .um-detail-card { max-width: 560px; }
83841
+ .um-detail-section { border: 1px solid #1e2d4a; border-radius: 10px; overflow: hidden; margin-bottom: 20px; }
83842
+ .um-detail-row {
83843
+ display: flex; align-items: flex-start; gap: 16px;
83844
+ padding: 11px 16px; border-bottom: 1px solid #0f1829;
83845
+ }
83846
+ .um-detail-row:last-child { border-bottom: none; }
83847
+ .um-detail-label { width: 100px; font-size: 12px; color: #475569; font-weight: 500; flex-shrink: 0; padding-top: 1px; }
83848
+ .um-detail-value { font-size: 13px; color: #c4ccd9; flex: 1; }
83849
+ .um-detail-actions { display: flex; gap: 8px; flex-wrap: wrap; }
83850
+
83851
+ /* Profiles */
83852
+ .um-profiles-header { margin-bottom: 16px; }
83853
+ .um-profiles-title { font-size: 14px; font-weight: 600; color: #c4ccd9; margin: 0; }
83854
+ .um-profiles-notice {
83855
+ display: flex; align-items: center; gap: 8px;
83856
+ margin-top: 16px; padding: 10px 14px;
83857
+ background: #1a2537; border: 1px solid #2a3b60; border-radius: 8px;
83858
+ font-size: 12px; color: #64748b;
83859
+ }
83860
+
83861
+ /* Spinner */
83862
+ .um-spinner {
83863
+ display: inline-block; width: 14px; height: 14px;
83864
+ border: 2px solid #2a3352; border-top-color: #60a5fa;
83865
+ border-radius: 50%; animation: um-spin 0.7s linear infinite;
83866
+ }
83867
+ @keyframes um-spin { to { transform: rotate(360deg); } }
83868
+ `;
83869
+ document.head.appendChild(style);
83870
+ }
83871
+ esc(s) {
83872
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
83873
+ }
83874
+ };
83875
+
83876
+ // src/components/premium-modals/user-management/UserManagementController.ts
83877
+ var UserManagementController = class {
83878
+ view;
83879
+ constructor(params) {
83880
+ const config = {
83881
+ customerId: params.customerId,
83882
+ tenantId: params.tenantId,
83883
+ customerName: params.customerName || "",
83884
+ jwtToken: params.jwtToken,
83885
+ tbBaseUrl: params.tbBaseUrl.replace(/\/$/, ""),
83886
+ currentUser: params.currentUser,
83887
+ onClose: () => {
83888
+ }
83889
+ };
83890
+ this.view = new UserManagementModalView(config);
83891
+ }
83892
+ show() {
83893
+ this.view.render();
83894
+ }
83895
+ };
83896
+
83897
+ // src/components/premium-modals/user-management/openUserManagementModal.ts
83898
+ function openUserManagementModal(params) {
83899
+ if (!params.jwtToken) {
83900
+ console.warn("[openUserManagementModal] jwtToken is required");
83901
+ return;
83902
+ }
83903
+ if (!params.customerId) {
83904
+ console.warn("[openUserManagementModal] customerId is required");
83905
+ return;
83906
+ }
83907
+ const controller = new UserManagementController(params);
83908
+ controller.show();
83909
+ }
83910
+
82416
83911
  // src/components/menu/types.ts
82417
83912
  var DEFAULT_LIGHT_THEME3 = {
82418
83913
  // Tabs
@@ -125426,8 +126921,8 @@ async function openGCDRSyncModal(params) {
125426
126921
 
125427
126922
  // src/components/premium-modals/alarm-bundle-map/openAlarmBundleMapModal.ts
125428
126923
  var MODAL_ID3 = "myio-alarm-bundle-map-modal";
125429
- var GCDR_INTEGRATION_API_KEY2 = "gcdr_cust_tb_integration_key_2026";
125430
- var GCDR_DEFAULT_BASE_URL2 = "https://gcdr-api.a.myio-bas.com";
126924
+ var GCDR_INTEGRATION_API_KEY = "gcdr_cust_tb_integration_key_2026";
126925
+ var GCDR_DEFAULT_BASE_URL = "https://gcdr-api.a.myio-bas.com";
125431
126926
  var TEAL2 = "#0a6d5e";
125432
126927
  var TEAL_DARK2 = "#084f44";
125433
126928
  var STYLES3 = `
@@ -125726,7 +127221,7 @@ async function fetchBundle(customerTB_ID, gcdrTenantId, baseUrl) {
125726
127221
  const response = await fetch(url, {
125727
127222
  method: "GET",
125728
127223
  headers: {
125729
- "X-API-Key": GCDR_INTEGRATION_API_KEY2,
127224
+ "X-API-Key": GCDR_INTEGRATION_API_KEY,
125730
127225
  "X-Tenant-ID": gcdrTenantId,
125731
127226
  "Accept": "application/json"
125732
127227
  }
@@ -125757,7 +127252,7 @@ async function openAlarmBundleMapModal(params) {
125757
127252
  alert("GCDR Tenant ID n\xE3o encontrado. Configure o atributo gcdrTenantId no cliente ThingsBoard.");
125758
127253
  return;
125759
127254
  }
125760
- const baseUrl = params.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL2;
127255
+ const baseUrl = params.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
125761
127256
  const overlay = document.createElement("div");
125762
127257
  overlay.id = MODAL_ID3;
125763
127258
  const card = document.createElement("div");
@@ -127378,6 +128873,7 @@ var version = package_default.version || "0.0.0";
127378
128873
  openTemperatureSettingsModal,
127379
128874
  openTutorialModal,
127380
128875
  openUpsellModal,
128876
+ openUserManagementModal,
127381
128877
  openWelcomeModal,
127382
128878
  parseInputDateToDate,
127383
128879
  periodKey,