myio-js-library 0.1.499 → 0.1.501

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
@@ -1162,7 +1162,7 @@ module.exports = __toCommonJS(index_exports);
1162
1162
  // package.json
1163
1163
  var package_default = {
1164
1164
  name: "myio-js-library",
1165
- version: "0.1.499",
1165
+ version: "0.1.501",
1166
1166
  description: "A clean, standalone JS SDK for MYIO projects",
1167
1167
  license: "MIT",
1168
1168
  repository: "github:gh-myio/myio-js-library",
@@ -15633,6 +15633,57 @@ function renderCardComponentV5({
15633
15633
  `;
15634
15634
  document.head.appendChild(layoutStyle);
15635
15635
  }
15636
+ if (!document.getElementById("myio-card-alert-styles")) {
15637
+ const alertStyle = document.createElement("style");
15638
+ alertStyle.id = "myio-card-alert-styles";
15639
+ alertStyle.textContent = `
15640
+ .myio-alert-overlay {
15641
+ position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 100000;
15642
+ display: flex; align-items: center; justify-content: center;
15643
+ background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);
15644
+ -webkit-backdrop-filter: blur(4px);
15645
+ animation: myio-fadeIn 0.2s ease-out;
15646
+ }
15647
+ @keyframes myio-fadeIn { from { opacity: 0; } to { opacity: 1; } }
15648
+ .myio-alert-box {
15649
+ position: relative; max-width: 480px; width: 90%; padding: 32px;
15650
+ background: #ffffff; border: 1px solid rgba(0,0,0,0.1); border-radius: 20px;
15651
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
15652
+ animation: myio-slideUp 0.3s cubic-bezier(0.4,0,0.2,1);
15653
+ }
15654
+ @keyframes myio-slideUp {
15655
+ from { opacity: 0; transform: translateY(40px) scale(0.95); }
15656
+ to { opacity: 1; transform: translateY(0) scale(1); }
15657
+ }
15658
+ .myio-alert-icon {
15659
+ width: 64px; height: 64px; margin: 0 auto 20px;
15660
+ display: flex; align-items: center; justify-content: center;
15661
+ background: linear-gradient(135deg, #3E1A7D 0%, #2D1359 100%);
15662
+ border: 2px solid #3E1A7D; border-radius: 50%; color: #ffffff; font-size: 32px;
15663
+ }
15664
+ .myio-alert-title {
15665
+ margin: 0 0 12px; font-size: 24px; font-weight: 700; color: #000000;
15666
+ text-align: center; letter-spacing: -0.02em;
15667
+ }
15668
+ .myio-alert-message {
15669
+ margin: 0 0 28px; font-size: 16px; font-weight: 500; color: #000000;
15670
+ text-align: center; line-height: 1.6;
15671
+ }
15672
+ .myio-alert-button {
15673
+ width: 100%; height: 48px; font-size: 15px; font-weight: 700;
15674
+ text-transform: uppercase;
15675
+ background: linear-gradient(135deg, #3E1A7D 0%, #2D1359 100%);
15676
+ border: none; border-radius: 12px; color: #ffffff; cursor: pointer;
15677
+ box-shadow: 0 4px 16px rgba(62,26,125,0.4);
15678
+ transition: all 0.2s cubic-bezier(0.4,0,0.2,1);
15679
+ }
15680
+ .myio-alert-button:hover {
15681
+ background: linear-gradient(135deg, #4E2A9D 0%, #3E1A7D 100%);
15682
+ box-shadow: 0 6px 24px rgba(62,26,125,0.5); transform: translateY(-2px);
15683
+ }
15684
+ `;
15685
+ document.head.appendChild(alertStyle);
15686
+ }
15636
15687
  const actionsContainer = document.createElement("div");
15637
15688
  actionsContainer.className = "card-actions";
15638
15689
  if (typeof handleActionDashboard === "function") {
@@ -15681,6 +15732,44 @@ function renderCardComponentV5({
15681
15732
  if (enhancedCardElement && actionsContainer.children.length > 0) {
15682
15733
  enhancedCardElement.insertBefore(actionsContainer, enhancedCardElement.firstChild);
15683
15734
  }
15735
+ let _cardAlertOverlay = null;
15736
+ function hideCardAlert() {
15737
+ if (_cardAlertOverlay && _cardAlertOverlay.parentNode) {
15738
+ _cardAlertOverlay.remove();
15739
+ _cardAlertOverlay = null;
15740
+ }
15741
+ }
15742
+ function showCardLimitAlert() {
15743
+ if (_cardAlertOverlay) hideCardAlert();
15744
+ const maxAllowed = MyIOSelectionStore?.MAX_SELECTION ?? 20;
15745
+ const overlay = document.createElement("div");
15746
+ overlay.className = "myio-alert-overlay";
15747
+ overlay.innerHTML = `
15748
+ <div class="myio-alert-box">
15749
+ <div class="myio-alert-icon">\u26A0</div>
15750
+ <h2 class="myio-alert-title">Limite Atingido</h2>
15751
+ <p class="myio-alert-message">
15752
+ Voc\xEA pode selecionar no m\xE1ximo <strong>${maxAllowed} dispositivos</strong> para compara\xE7\xE3o.
15753
+ Remova um dispositivo antes de adicionar outro.
15754
+ </p>
15755
+ <button class="myio-alert-button">FECHAR</button>
15756
+ </div>`;
15757
+ document.body.appendChild(overlay);
15758
+ _cardAlertOverlay = overlay;
15759
+ const closeBtn = overlay.querySelector(".myio-alert-button");
15760
+ const close = () => {
15761
+ document.removeEventListener("keydown", handleEscape);
15762
+ hideCardAlert();
15763
+ };
15764
+ const handleEscape = (e) => {
15765
+ if (e.key === "Escape") close();
15766
+ };
15767
+ closeBtn.addEventListener("click", close);
15768
+ overlay.addEventListener("click", (e) => {
15769
+ if (e.target === overlay) close();
15770
+ });
15771
+ document.addEventListener("keydown", handleEscape);
15772
+ }
15684
15773
  if (enableSelection && MyIOSelectionStore) {
15685
15774
  const checkbox = enhancedCardElement.querySelector(".card-checkbox");
15686
15775
  if (checkbox) {
@@ -15691,10 +15780,10 @@ function renderCardComponentV5({
15691
15780
  const selectedEntities = MyIOSelectionStore.getSelectedEntities();
15692
15781
  console.log("selectedEntities", selectedEntities);
15693
15782
  const isTryingToAdd = e.target.checked;
15694
- if (isTryingToAdd && currentCount >= 6) {
15783
+ if (isTryingToAdd && currentCount >= (MyIOSelectionStore.MAX_SELECTION ?? 20)) {
15695
15784
  e.preventDefault();
15696
15785
  e.target.checked = false;
15697
- MyIOToast2.show("N\xE3o \xE9 poss\xEDvel selecionar mais de 6 itens.", "warning");
15786
+ showCardLimitAlert();
15698
15787
  return;
15699
15788
  }
15700
15789
  MyIOSelectionStore.add(entityId);
@@ -17936,6 +18025,95 @@ function renderCardComponentV6({
17936
18025
  if (enhancedCardElement && actionsContainer.children.length > 0) {
17937
18026
  enhancedCardElement.insertBefore(actionsContainer, enhancedCardElement.firstChild);
17938
18027
  }
18028
+ let _cardAlertOverlay = null;
18029
+ function hideCardAlert() {
18030
+ if (_cardAlertOverlay && _cardAlertOverlay.parentNode) {
18031
+ _cardAlertOverlay.remove();
18032
+ _cardAlertOverlay = null;
18033
+ }
18034
+ }
18035
+ function showCardLimitAlert() {
18036
+ if (_cardAlertOverlay) hideCardAlert();
18037
+ if (!document.getElementById("myio-card-alert-styles")) {
18038
+ const alertStyle = document.createElement("style");
18039
+ alertStyle.id = "myio-card-alert-styles";
18040
+ alertStyle.textContent = `
18041
+ .myio-alert-overlay {
18042
+ position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 100000;
18043
+ display: flex; align-items: center; justify-content: center;
18044
+ background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);
18045
+ -webkit-backdrop-filter: blur(4px);
18046
+ animation: myio-fadeIn 0.2s ease-out;
18047
+ }
18048
+ @keyframes myio-fadeIn { from { opacity: 0; } to { opacity: 1; } }
18049
+ .myio-alert-box {
18050
+ position: relative; max-width: 480px; width: 90%; padding: 32px;
18051
+ background: #ffffff; border: 1px solid rgba(0,0,0,0.1); border-radius: 20px;
18052
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
18053
+ animation: myio-slideUp 0.3s cubic-bezier(0.4,0,0.2,1);
18054
+ }
18055
+ @keyframes myio-slideUp {
18056
+ from { opacity: 0; transform: translateY(40px) scale(0.95); }
18057
+ to { opacity: 1; transform: translateY(0) scale(1); }
18058
+ }
18059
+ .myio-alert-icon {
18060
+ width: 64px; height: 64px; margin: 0 auto 20px;
18061
+ display: flex; align-items: center; justify-content: center;
18062
+ background: linear-gradient(135deg, #3E1A7D 0%, #2D1359 100%);
18063
+ border: 2px solid #3E1A7D; border-radius: 50%; color: #ffffff; font-size: 32px;
18064
+ }
18065
+ .myio-alert-title {
18066
+ margin: 0 0 12px; font-size: 24px; font-weight: 700; color: #000000;
18067
+ text-align: center; letter-spacing: -0.02em;
18068
+ }
18069
+ .myio-alert-message {
18070
+ margin: 0 0 28px; font-size: 16px; font-weight: 500; color: #000000;
18071
+ text-align: center; line-height: 1.6;
18072
+ }
18073
+ .myio-alert-button {
18074
+ width: 100%; height: 48px; font-size: 15px; font-weight: 700;
18075
+ text-transform: uppercase;
18076
+ background: linear-gradient(135deg, #3E1A7D 0%, #2D1359 100%);
18077
+ border: none; border-radius: 12px; color: #ffffff; cursor: pointer;
18078
+ box-shadow: 0 4px 16px rgba(62,26,125,0.4);
18079
+ transition: all 0.2s cubic-bezier(0.4,0,0.2,1);
18080
+ }
18081
+ .myio-alert-button:hover {
18082
+ background: linear-gradient(135deg, #4E2A9D 0%, #3E1A7D 100%);
18083
+ box-shadow: 0 6px 24px rgba(62,26,125,0.5); transform: translateY(-2px);
18084
+ }
18085
+ `;
18086
+ document.head.appendChild(alertStyle);
18087
+ }
18088
+ const maxAllowed = MyIOSelectionStore?.MAX_SELECTION ?? 20;
18089
+ const overlay = document.createElement("div");
18090
+ overlay.className = "myio-alert-overlay";
18091
+ overlay.innerHTML = `
18092
+ <div class="myio-alert-box">
18093
+ <div class="myio-alert-icon">\u26A0</div>
18094
+ <h2 class="myio-alert-title">Limite Atingido</h2>
18095
+ <p class="myio-alert-message">
18096
+ Voc\xEA pode selecionar no m\xE1ximo <strong>${maxAllowed} dispositivos</strong> para compara\xE7\xE3o.
18097
+ Remova um dispositivo antes de adicionar outro.
18098
+ </p>
18099
+ <button class="myio-alert-button">FECHAR</button>
18100
+ </div>`;
18101
+ document.body.appendChild(overlay);
18102
+ _cardAlertOverlay = overlay;
18103
+ const closeBtn = overlay.querySelector(".myio-alert-button");
18104
+ const close = () => {
18105
+ document.removeEventListener("keydown", handleEscape);
18106
+ hideCardAlert();
18107
+ };
18108
+ const handleEscape = (e) => {
18109
+ if (e.key === "Escape") close();
18110
+ };
18111
+ closeBtn.addEventListener("click", close);
18112
+ overlay.addEventListener("click", (e) => {
18113
+ if (e.target === overlay) close();
18114
+ });
18115
+ document.addEventListener("keydown", handleEscape);
18116
+ }
17939
18117
  if (enableSelection && MyIOSelectionStore) {
17940
18118
  const checkbox = enhancedCardElement.querySelector(".card-checkbox");
17941
18119
  if (checkbox) {
@@ -17943,10 +18121,10 @@ function renderCardComponentV6({
17943
18121
  e.stopPropagation();
17944
18122
  if (e.target.checked) {
17945
18123
  const currentCount = MyIOSelectionStore.getSelectedEntities().length;
17946
- if (currentCount >= 6) {
18124
+ if (currentCount >= (MyIOSelectionStore.MAX_SELECTION ?? 20)) {
17947
18125
  e.preventDefault();
17948
18126
  e.target.checked = false;
17949
- MyIOToast2.show("N\xE3o \xE9 poss\xEDvel selecionar mais de 6 itens.", "warning");
18127
+ showCardLimitAlert();
17950
18128
  return;
17951
18129
  }
17952
18130
  MyIOSelectionStore.add(entityId);
@@ -43692,7 +43870,7 @@ function bindEvents3(container, modalId, state6, onClose) {
43692
43870
  }
43693
43871
 
43694
43872
  // src/components/footer/ComparisonHandler.ts
43695
- var DEFAULT_CHARTS_BASE_URL = "https://graphs.staging.apps.myio-bas.com";
43873
+ var DEFAULT_CHARTS_BASE_URL = "https://graphs.apps.myio-bas.com";
43696
43874
  function createLogger(debug) {
43697
43875
  const prefix = "[ComparisonHandler]";
43698
43876
  return {
@@ -52816,6 +52994,8 @@ function createAlarmCardElement(alarm, params) {
52816
52994
  const scrollRight = card.querySelector(".alarm-type-scroll-btn--right");
52817
52995
  if (typeList && scrollLeft && scrollRight) {
52818
52996
  const updateArrows = () => {
52997
+ const hasOverflow = typeList.scrollWidth - typeList.clientWidth > 1;
52998
+ typeList.classList.toggle("is-overflowing", hasOverflow);
52819
52999
  const canLeft = typeList.scrollLeft > 0;
52820
53000
  const canRight = typeList.scrollLeft < typeList.scrollWidth - typeList.clientWidth - 1;
52821
53001
  scrollLeft.style.opacity = canLeft ? "1" : "0";
@@ -53207,6 +53387,7 @@ function resolveDeviceTbId(alarm) {
53207
53387
  }
53208
53388
  return null;
53209
53389
  }
53390
+ var HIDE_ANNOTATIONS_TAB = true;
53210
53391
  function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction) {
53211
53392
  const sev = SEVERITY_CONFIG[alarm.severity];
53212
53393
  const st = STATE_CONFIG[alarm.state];
@@ -53291,10 +53472,10 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53291
53472
  <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 20 9 12 14 16 19 6"/><polyline points="19 6 19 10"/><polyline points="19 6 15 6"/></svg>
53292
53473
  Gr\xE1fico
53293
53474
  </button>
53294
- <button class="adm-tab" data-panel="anotacoes">
53475
+ ${HIDE_ANNOTATIONS_TAB ? "" : `<button class="adm-tab" data-panel="anotacoes">
53295
53476
  <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3v4a1 1 0 001 1h4"/><path d="M17 21H7a2 2 0 01-2-2V5a2 2 0 012-2h7l5 5v11a2 2 0 01-2 2z"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/></svg>
53296
53477
  Anota\xE7\xF5es${annotCount > 0 ? ` <span class="adm-tab-badge">${annotCount}</span>` : ""}
53297
- </button>
53478
+ </button>`}
53298
53479
  </nav>
53299
53480
 
53300
53481
  <!-- \u2500\u2500 Body \u2500\u2500 -->
@@ -53332,7 +53513,7 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53332
53513
  ${row("Shopping", alarm.customerName)}
53333
53514
  ${row("Dispositivo(s)", alarm.source)}
53334
53515
  ${alarm.triggerValue != null ? row("Valor do disparo", String(alarm.triggerValue)) : ""}
53335
- ${row("ID", alarm.id)}
53516
+ ${groupMode === "separado" ? row("ID", alarm.id) : ""}
53336
53517
  </div>
53337
53518
 
53338
53519
  <div class="adm-section">
@@ -53358,6 +53539,16 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53358
53539
  </div>
53359
53540
  </div>
53360
53541
 
53542
+ ${groupMode !== "separado" && alarm._groupAlarmIds && alarm._groupAlarmIds.length > 0 ? `
53543
+ <div class="adm-section">
53544
+ <div class="adm-section-title">IDs de Alarme (${alarm._groupAlarmIds.length})</div>
53545
+ <div class="adm-alarm-ids-list" style="display:flex;flex-wrap:wrap;gap:6px;">
53546
+ ${alarm._groupAlarmIds.map(
53547
+ (rawId) => `<span class="adm-alarm-id-chip" title="Clique para copiar" data-copy-id="${escHtml(rawId)}" style="display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid rgba(124,58,237,0.25);background:rgba(124,58,237,0.08);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:#4c1d95;cursor:pointer;user-select:all;">${escHtml(rawId)}</span>`
53548
+ ).join("")}
53549
+ </div>
53550
+ </div>` : ""}
53551
+
53361
53552
  <!-- Occurrence \xD7 Device matrix \u2014 disabled -->
53362
53553
  <!-- <div class="adm-section" style="display:none">
53363
53554
  <div class="adm-section-title">Mapa ocorr\xEAncias \xD7 dispositivos</div>
@@ -53471,9 +53662,9 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53471
53662
  </div>
53472
53663
 
53473
53664
  <!-- ANOTA\xC7\xD5ES -->
53474
- <div class="adm-panel" data-panel="anotacoes">
53665
+ ${HIDE_ANNOTATIONS_TAB ? "" : `<div class="adm-panel" data-panel="anotacoes">
53475
53666
  ${buildAnnotationsPanelHtml(alarm.id)}
53476
- </div>
53667
+ </div>`}
53477
53668
 
53478
53669
  </div><!-- /adm-body -->
53479
53670
  </div><!-- /adm-drawer -->
@@ -53489,6 +53680,21 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53489
53680
  overlay.querySelector(`.adm-panel[data-panel="${panel}"]`)?.classList.add("is-active");
53490
53681
  });
53491
53682
  });
53683
+ overlay.querySelectorAll(".adm-alarm-id-chip[data-copy-id]").forEach((chip) => {
53684
+ chip.addEventListener("click", async () => {
53685
+ const id = chip.getAttribute("data-copy-id") || "";
53686
+ if (!id) return;
53687
+ try {
53688
+ await navigator.clipboard?.writeText(id);
53689
+ const prev = chip.textContent;
53690
+ chip.textContent = "\u2713 copiado";
53691
+ setTimeout(() => {
53692
+ chip.textContent = prev;
53693
+ }, 900);
53694
+ } catch {
53695
+ }
53696
+ });
53697
+ });
53492
53698
  const chartInstances = [];
53493
53699
  const graficoPanel = overlay.querySelector('.adm-panel[data-panel="grafico"]');
53494
53700
  if (graficoPanel) {
@@ -54884,6 +55090,11 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
54884
55090
  align-items: center;
54885
55091
  gap: 4px;
54886
55092
  flex-wrap: wrap;
55093
+ /* Always dock to the right, even when the header has no checkbox/device-badge
55094
+ * siblings (consolidado / porDispositivo modes). With justify-content:
55095
+ * space-between on the parent and a single child, the child would otherwise
55096
+ * collapse to flex-start. margin-left: auto makes it flex-end regardless. */
55097
+ margin-left: auto;
54887
55098
  }
54888
55099
 
54889
55100
  .alarm-severity-badge {
@@ -55163,6 +55374,11 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
55163
55374
  .alarm-card-type-list {
55164
55375
  display: flex !important;
55165
55376
  flex-wrap: nowrap;
55377
+ /* Default: center chips when they all fit (common case of 1\u20133 chips).
55378
+ * When content overflows, JS toggles .is-overflowing so we switch to
55379
+ * flex-start \u2014 otherwise browsers anchor at center and the left edge
55380
+ * becomes unreachable via scroll. */
55381
+ justify-content: center;
55166
55382
  gap: 3px;
55167
55383
  overflow-x: auto;
55168
55384
  scrollbar-width: none;
@@ -55170,6 +55386,10 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
55170
55386
  min-width: 0;
55171
55387
  }
55172
55388
 
55389
+ .alarm-card-type-list.is-overflowing {
55390
+ justify-content: flex-start;
55391
+ }
55392
+
55173
55393
  .alarm-card-type-list::-webkit-scrollbar {
55174
55394
  display: none;
55175
55395
  }
@@ -58256,12 +58476,18 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
58256
58476
  .atbl-cell--num { text-align: center; font-weight: 700; }
58257
58477
  .atbl-cell--date { font-size: 10px; white-space: nowrap; color: var(--alarms-text-muted); }
58258
58478
 
58479
+ /* First (primary) column in consolidado (Por Tipo) and porDispositivo modes.
58480
+ * Matches .atbl-cell--device-primary so the typography is consistent across
58481
+ * all three groupModes \u2014 only the semantic label differs. */
58259
58482
  .atbl-cell--title {
58260
- max-width: 160px;
58483
+ min-width: 160px;
58484
+ max-width: 220px;
58261
58485
  white-space: nowrap;
58262
58486
  overflow: hidden;
58263
58487
  text-overflow: ellipsis;
58264
- font-weight: 600;
58488
+ font-size: 12px;
58489
+ font-weight: 700;
58490
+ color: var(--alarms-text-primary);
58265
58491
  }
58266
58492
 
58267
58493
  .atbl-cell--device {
@@ -117614,6 +117840,9 @@ var AlarmsNotificationsPanelController = class {
117614
117840
  }
117615
117841
  };
117616
117842
 
117843
+ // src/components/AlarmsNotificationsPanel/AlarmsNotificationsPanelView.ts
117844
+ var import_jspdf2 = require("jspdf");
117845
+
117617
117846
  // src/components/AlarmsNotificationsPanel/AlarmDashboard.ts
117618
117847
  function renderKPICards(stats) {
117619
117848
  return `
@@ -118897,6 +119126,65 @@ var AlarmsNotificationsPanelView = class {
118897
119126
  close();
118898
119127
  });
118899
119128
  }
119129
+ // =====================================================================
119130
+ // Export helpers
119131
+ // =====================================================================
119132
+ /** Returns the single customer name shared by the filtered alarms, or null
119133
+ * when the selection spans multiple shoppings. */
119134
+ _exportCustomerName() {
119135
+ const alarms = this.groupMode === "separado" ? this.controller.getState().filteredAlarms : this.groupedAlarms;
119136
+ const names = new Set(alarms.map((a) => a.customerName).filter(Boolean));
119137
+ if (names.size === 1) return [...names][0];
119138
+ return null;
119139
+ }
119140
+ /** Returns "DD/MM/AAAA — DD/MM/AAAA" from the active filters, or null. */
119141
+ _exportPeriodLabel() {
119142
+ const f = this.controller.getFilters();
119143
+ const fromISO = f.fromDate;
119144
+ const toISO = f.toDate;
119145
+ if (!fromISO && !toISO) return null;
119146
+ const fmt2 = (iso) => {
119147
+ if (!iso) return "";
119148
+ const d = new Date(iso);
119149
+ return isNaN(d.getTime()) ? "" : d.toLocaleDateString("pt-BR");
119150
+ };
119151
+ if (fromISO && toISO) return `${fmt2(fromISO)} \u2014 ${fmt2(toISO)}`;
119152
+ return fmt2(fromISO || toISO || "");
119153
+ }
119154
+ _exportDatestamp() {
119155
+ const d = /* @__PURE__ */ new Date();
119156
+ const p = (n) => String(n).padStart(2, "0");
119157
+ return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}`;
119158
+ }
119159
+ _exportSlugify(s) {
119160
+ return s.normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^\w]+/g, "-").toLowerCase().replace(/^-+|-+$/g, "").slice(0, 40);
119161
+ }
119162
+ /** Builds `alarmes-[customer-]YYYYMMDD-HHmm` for export filenames. */
119163
+ _exportFilenameBase() {
119164
+ const customer = this._exportCustomerName();
119165
+ const parts = ["alarmes"];
119166
+ if (customer) {
119167
+ const slug = this._exportSlugify(customer);
119168
+ if (slug) parts.push(slug);
119169
+ }
119170
+ parts.push(this._exportDatestamp());
119171
+ return parts.join("-");
119172
+ }
119173
+ /**
119174
+ * Rows for tabular exports (CSV / XLS / PDF). Schema tuned per groupMode:
119175
+ *
119176
+ * - separado (Disp. + Tipo): one row per (device × alarm type). Drops the
119177
+ * legacy "Fechado em" / "Motivo" columns that only made sense for the
119178
+ * closed-history path and cluttered normal exports.
119179
+ *
119180
+ * - consolidado (Por Tipo): one row per alarm type. The "Fonte" column is
119181
+ * renamed to "Dispositivos afetados" — that's semantically what
119182
+ * alarm.source carries here (comma-separated list of devices).
119183
+ *
119184
+ * - porDispositivo (Por Disp.): one row per device. Adds a "Tipos de Alarme"
119185
+ * column with the same list rendered as chips on the cards; drops the
119186
+ * redundant "Fonte" column (device already identifies the row).
119187
+ */
118900
119188
  getCsvRows() {
118901
119189
  const fmtDt3 = (iso) => {
118902
119190
  if (!iso) return "";
@@ -118906,28 +119194,66 @@ var AlarmsNotificationsPanelView = class {
118906
119194
  if (this.groupMode === "separado") {
118907
119195
  const rawAlarms = this.controller.getState().filteredAlarms;
118908
119196
  const hasCustomer2 = rawAlarms.some((a) => !!a.customerName);
118909
- const header2 = ["Tipo", "Severidade", "Estado", ...hasCustomer2 ? ["Cliente"] : [], "Dispositivo", "1a Ocorr\xEAncia", "\xDAltimo Evento", "Fechado em", "Motivo"];
119197
+ const header2 = [
119198
+ "Dispositivo",
119199
+ "Tipo",
119200
+ "Severidade",
119201
+ "Estado",
119202
+ ...hasCustomer2 ? ["Cliente"] : [],
119203
+ "1a Ocorr\xEAncia",
119204
+ "\xDAltima Ocorr\xEAncia"
119205
+ ];
118910
119206
  const rows = [header2];
118911
119207
  for (const alarm of rawAlarms) {
118912
119208
  const devices = alarm.source ? alarm.source.split(",").map((s) => s.trim()).filter(Boolean) : [""];
118913
119209
  for (const device of devices) {
118914
119210
  rows.push([
119211
+ device,
118915
119212
  alarm.title || "",
118916
119213
  alarm.severity,
118917
119214
  alarm.state,
118918
119215
  ...hasCustomer2 ? [alarm.customerName || ""] : [],
118919
- device,
118920
119216
  fmtDt3(alarm.firstOccurrence),
118921
- fmtDt3(alarm.lastOccurrence),
118922
- fmtDt3(alarm.closedAt),
118923
- alarm.closedReason || ""
119217
+ fmtDt3(alarm.lastOccurrence)
118924
119218
  ]);
118925
119219
  }
118926
119220
  }
118927
119221
  return rows;
118928
119222
  }
118929
119223
  const hasCustomer = this.groupedAlarms.some((a) => !!a.customerName);
118930
- const header = ["Tipo", "Severidade", "Estado", ...hasCustomer ? ["Cliente"] : [], "Fonte", "Qte.", "1a Ocorr\xEAncia", "\xDAlt. Ocorr\xEAncia"];
119224
+ if (this.groupMode === "porDispositivo") {
119225
+ const header2 = [
119226
+ "Dispositivo",
119227
+ "Tipos de Alarme",
119228
+ "Severidade",
119229
+ "Estado",
119230
+ ...hasCustomer ? ["Cliente"] : [],
119231
+ "Qte.",
119232
+ "1a Ocorr\xEAncia",
119233
+ "\xDAltima Ocorr\xEAncia"
119234
+ ];
119235
+ const dataRows2 = this.groupedAlarms.map((a) => [
119236
+ a.title || a.source || "",
119237
+ (a._alarmTypes || []).join(", "),
119238
+ a.severity,
119239
+ a.state,
119240
+ ...hasCustomer ? [a.customerName || ""] : [],
119241
+ String(a.occurrenceCount || 1),
119242
+ fmtDt3(a.firstOccurrence),
119243
+ fmtDt3(a.lastOccurrence)
119244
+ ]);
119245
+ return [header2, ...dataRows2];
119246
+ }
119247
+ const header = [
119248
+ "Tipo",
119249
+ "Severidade",
119250
+ "Estado",
119251
+ ...hasCustomer ? ["Cliente"] : [],
119252
+ "Dispositivos afetados",
119253
+ "Qte.",
119254
+ "1a Ocorr\xEAncia",
119255
+ "\xDAltima Ocorr\xEAncia"
119256
+ ];
118931
119257
  const dataRows = this.groupedAlarms.map((a) => [
118932
119258
  a.title || "",
118933
119259
  a.severity,
@@ -118943,36 +119269,153 @@ var AlarmsNotificationsPanelView = class {
118943
119269
  exportToCsv() {
118944
119270
  const rows = this.getCsvRows();
118945
119271
  const BOM = "\uFEFF";
118946
- const csv = BOM + rows.map((r) => r.map((c) => `"${c.replace(/"/g, '""')}"`).join(",")).join("\r\n");
118947
- this.triggerDownload(csv, "alarmes.csv", "text/csv;charset=utf-8;");
119272
+ const customer = this._exportCustomerName();
119273
+ const period = this._exportPeriodLabel();
119274
+ const meta = ['"Relat\xF3rio de Alarmes"'];
119275
+ if (customer) meta.push(`"Cliente";"${customer.replace(/"/g, '""')}"`);
119276
+ if (period) meta.push(`"Per\xEDodo";"${period}"`);
119277
+ meta.push(`"Gerado em";"${(/* @__PURE__ */ new Date()).toLocaleString("pt-BR")}"`);
119278
+ meta.push("");
119279
+ const body = rows.map((r) => r.map((c) => `"${c.replace(/"/g, '""')}"`).join(",")).join("\r\n");
119280
+ this.triggerDownload(
119281
+ BOM + meta.join("\r\n") + "\r\n" + body,
119282
+ `${this._exportFilenameBase()}.csv`,
119283
+ "text/csv;charset=utf-8;"
119284
+ );
118948
119285
  }
118949
119286
  exportToExcel() {
118950
119287
  const rows = this.getCsvRows();
119288
+ const customer = this._exportCustomerName();
119289
+ const period = this._exportPeriodLabel();
119290
+ const span = Math.max(1, rows[0]?.length || 1);
119291
+ const metaRow = (k, v) => `<tr><td style="background:#F0EDF9;font-weight:700;border:1px solid #ccc;padding:4px 8px;font-size:11px;">${k}</td><td colspan="${span - 1}" style="border:1px solid #ccc;padding:4px 8px;font-size:11px;">${v}</td></tr>`;
119292
+ const metaBlock = metaRow("Relat\xF3rio", "Alarmes") + (customer ? metaRow("Cliente", customer) : "") + (period ? metaRow("Per\xEDodo", period) : "") + metaRow("Gerado em", (/* @__PURE__ */ new Date()).toLocaleString("pt-BR")) + `<tr><td colspan="${span}" style="border:0;padding:2px;"></td></tr>`;
118951
119293
  const htmlTable = `<html xmlns:x="urn:schemas-microsoft-com:office:excel">
118952
- <head><meta charset="UTF-8"><style>td,th{border:1px solid #ccc;padding:4px 8px;font-size:11px;}th{background:#7c3aed;color:#fff;}</style></head>
118953
- <body><table>${rows.map((r, i) => `<tr>${r.map((c) => i === 0 ? `<th>${c}</th>` : `<td>${c}</td>`).join("")}</tr>`).join("")}</table></body>
119294
+ <head><meta charset="UTF-8"><style>td,th{border:1px solid #ccc;padding:4px 8px;font-size:11px;}th{background:#3E1A7D;color:#fff;}</style></head>
119295
+ <body><table>${metaBlock}${rows.map((r, i) => `<tr>${r.map((c) => i === 0 ? `<th>${c}</th>` : `<td>${c}</td>`).join("")}</tr>`).join("")}</table></body>
118954
119296
  </html>`;
118955
- this.triggerDownload(htmlTable, "alarmes.xls", "application/vnd.ms-excel;charset=utf-8;");
119297
+ this.triggerDownload(
119298
+ htmlTable,
119299
+ `${this._exportFilenameBase()}.xls`,
119300
+ "application/vnd.ms-excel;charset=utf-8;"
119301
+ );
118956
119302
  }
119303
+ /**
119304
+ * Premium PDF export (landscape A4) — mirrors the design used by
119305
+ * `src/components/telemetry-grid-shopping/export.ts`: purple top band
119306
+ * with title, right-side metadata line (Período · Gerado em · # alarmes ·
119307
+ * Pág.), purple column-header row, alternating row shading, purple
119308
+ * footer band. Saves directly via `doc.save()` — no window.open detour.
119309
+ */
118957
119310
  exportToPdf() {
118958
119311
  const rows = this.getCsvRows();
118959
- const tableHtml = `<table style="width:100%;border-collapse:collapse;font-size:11px;">
118960
- ${rows.map((r, i) => `<tr>${r.map(
118961
- (c) => i === 0 ? `<th style="background:#7c3aed;color:#fff;padding:5px 8px;border:1px solid #555;">${c}</th>` : `<td style="padding:4px 8px;border:1px solid #ddd;">${c}</td>`
118962
- ).join("")}</tr>`).join("")}
118963
- </table>`;
118964
- const win = window.open("", "_blank");
118965
- if (!win) return;
118966
- win.document.write(`<!DOCTYPE html><html><head><title>Alarmes</title>
118967
- <style>body{font-family:sans-serif;padding:16px;}h2{font-size:14px;margin-bottom:8px;}@media print{button{display:none}}</style>
118968
- </head><body>
118969
- <h2>Relat\xF3rio de Alarmes</h2>
118970
- ${tableHtml}
118971
- <br><button onclick="window.print()">Imprimir / Salvar PDF</button>
118972
- </body></html>`);
118973
- win.document.close();
118974
- win.focus();
118975
- setTimeout(() => win.print(), 400);
119312
+ if (rows.length < 1) return;
119313
+ const headerRow = rows[0];
119314
+ const dataRows = rows.slice(1);
119315
+ const customer = this._exportCustomerName();
119316
+ const period = this._exportPeriodLabel();
119317
+ const generatedAt = (/* @__PURE__ */ new Date()).toLocaleString("pt-BR");
119318
+ const title = customer ? `${customer} \u2014 Relat\xF3rio de Alarmes` : "Relat\xF3rio de Alarmes";
119319
+ const doc = new import_jspdf2.jsPDF({ orientation: "landscape", unit: "mm", format: "a4" });
119320
+ const PW = doc.internal.pageSize.getWidth();
119321
+ const PH = doc.internal.pageSize.getHeight();
119322
+ const MARGIN = 10;
119323
+ const HDR_H = 13;
119324
+ const FTR_H = 10;
119325
+ const ROW_H = 7;
119326
+ const HEAD_H = 8;
119327
+ const TABLE_Y = HDR_H + MARGIN;
119328
+ const MAX_Y = PH - FTR_H - MARGIN;
119329
+ const TABLE_W = PW - MARGIN * 2;
119330
+ const colWeights = headerRow.map((label) => {
119331
+ const l = label.toLowerCase();
119332
+ if (l.includes("tipos") || l.includes("afetados")) return 4;
119333
+ if (l.includes("dispositivo") || l === "tipo" || l === "cliente") return 3;
119334
+ if (l.includes("ocorr\xEAncia") || l === "estado" || l === "severidade") return 2;
119335
+ return 1.2;
119336
+ });
119337
+ const weightSum = colWeights.reduce((s, w) => s + w, 0);
119338
+ const colWidths = colWeights.map((w) => w / weightSum * TABLE_W);
119339
+ const colX = (ci) => MARGIN + colWidths.slice(0, ci).reduce((s, w) => s + w, 0);
119340
+ const truncate2 = (s, max) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
119341
+ const drawHeader2 = (pageNo2) => {
119342
+ doc.setFillColor(62, 26, 125);
119343
+ doc.rect(0, 0, PW, HDR_H, "F");
119344
+ doc.setTextColor(255, 255, 255);
119345
+ doc.setFont("helvetica", "bold");
119346
+ doc.setFontSize(10);
119347
+ doc.text(title, MARGIN, HDR_H / 2 + 1.5);
119348
+ doc.setFont("helvetica", "normal");
119349
+ doc.setFontSize(8);
119350
+ const parts = [];
119351
+ if (period) parts.push(`Per\xEDodo: ${period}`);
119352
+ parts.push(`Gerado em: ${generatedAt}`);
119353
+ parts.push(`${dataRows.length} alarme${dataRows.length !== 1 ? "s" : ""}`);
119354
+ parts.push(`P\xE1g. ${pageNo2}`);
119355
+ doc.text(parts.join(" \u2022 "), PW - MARGIN, HDR_H / 2 + 1.5, { align: "right" });
119356
+ };
119357
+ const drawColumnHeaders = (y) => {
119358
+ doc.setFillColor(240, 237, 250);
119359
+ doc.rect(MARGIN, y, TABLE_W, HEAD_H, "F");
119360
+ doc.setTextColor(62, 26, 125);
119361
+ doc.setFont("helvetica", "bold");
119362
+ doc.setFontSize(7);
119363
+ headerRow.forEach((label, ci) => {
119364
+ const x = colX(ci) + 1.5;
119365
+ doc.text(label, x, y + HEAD_H / 2 + 2.5);
119366
+ });
119367
+ doc.setDrawColor(200, 195, 220);
119368
+ doc.setLineWidth(0.2);
119369
+ doc.rect(MARGIN, y, TABLE_W, HEAD_H);
119370
+ };
119371
+ const drawDataRow = (cells, y, even) => {
119372
+ if (even) {
119373
+ doc.setFillColor(250, 249, 255);
119374
+ doc.rect(MARGIN, y, TABLE_W, ROW_H, "F");
119375
+ }
119376
+ doc.setTextColor(40, 40, 40);
119377
+ doc.setFont("helvetica", "normal");
119378
+ doc.setFontSize(6.5);
119379
+ cells.forEach((cell, ci) => {
119380
+ const x = colX(ci) + 1.5;
119381
+ const maxChars = Math.floor(colWidths[ci] / 1.8);
119382
+ doc.text(truncate2(String(cell ?? ""), maxChars), x, y + ROW_H / 2 + 2.2);
119383
+ });
119384
+ doc.setDrawColor(230, 228, 240);
119385
+ doc.setLineWidth(0.1);
119386
+ doc.line(MARGIN, y + ROW_H, MARGIN + TABLE_W, y + ROW_H);
119387
+ };
119388
+ const drawFooter2 = () => {
119389
+ doc.setFillColor(250, 249, 255);
119390
+ doc.rect(0, PH - FTR_H, PW, FTR_H, "F");
119391
+ doc.setDrawColor(210, 205, 230);
119392
+ doc.setLineWidth(0.2);
119393
+ doc.line(MARGIN, PH - FTR_H + 0.5, PW - MARGIN, PH - FTR_H + 0.5);
119394
+ doc.setTextColor(120, 110, 150);
119395
+ doc.setFont("helvetica", "normal");
119396
+ doc.setFontSize(7);
119397
+ doc.text(`Gerado em ${generatedAt} \u2014 MyIO`, MARGIN, PH - FTR_H + 6);
119398
+ };
119399
+ let pageNo = 1;
119400
+ let currentY = TABLE_Y;
119401
+ drawHeader2(pageNo);
119402
+ drawColumnHeaders(currentY);
119403
+ currentY += HEAD_H;
119404
+ dataRows.forEach((row, i) => {
119405
+ if (currentY + ROW_H > MAX_Y) {
119406
+ drawFooter2();
119407
+ doc.addPage();
119408
+ pageNo++;
119409
+ currentY = TABLE_Y;
119410
+ drawHeader2(pageNo);
119411
+ drawColumnHeaders(currentY);
119412
+ currentY += HEAD_H;
119413
+ }
119414
+ drawDataRow(row, currentY, i % 2 === 0);
119415
+ currentY += ROW_H;
119416
+ });
119417
+ drawFooter2();
119418
+ doc.save(`${this._exportFilenameBase()}.pdf`);
118976
119419
  }
118977
119420
  triggerDownload(content, filename, mimeType) {
118978
119421
  const blob = new Blob([content], { type: mimeType });
@@ -138543,7 +138986,7 @@ var ProvisioningApiClient = class {
138543
138986
  };
138544
138987
 
138545
138988
  // src/components/PresetupGateway/utils/pdf.ts
138546
- var import_jspdf2 = require("jspdf");
138989
+ var import_jspdf3 = require("jspdf");
138547
138990
  var import_qrious = __toESM(require("qrious"), 1);
138548
138991
  async function makeQrDataUrl(value) {
138549
138992
  return new Promise((resolve) => {
@@ -138662,7 +139105,7 @@ async function drawTag(doc, x, y, w, h, device, gateway, allDevices) {
138662
139105
  }
138663
139106
  async function exportDeviceTagsPdf(devices, gateway, layout = "grid_4x7") {
138664
139107
  if (!devices.length) throw new Error("Nenhum dispositivo para exportar");
138665
- const doc = new import_jspdf2.jsPDF({ orientation: "portrait", unit: "mm", format: "a4" });
139108
+ const doc = new import_jspdf3.jsPDF({ orientation: "portrait", unit: "mm", format: "a4" });
138666
139109
  const pageW = doc.internal.pageSize.getWidth();
138667
139110
  const pageH = doc.internal.pageSize.getHeight();
138668
139111
  const headerH = 16;