myio-js-library 0.1.536 → 0.1.538

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
@@ -190,6 +190,9 @@ __export(src_exports, {
190
190
  FreshdeskClient: () => FreshdeskClient_exports,
191
191
  GRANULARITY_SELECTOR_CSS_PREFIX: () => GRANULARITY_SELECTOR_CSS_PREFIX,
192
192
  GRID_SORT_OPTIONS: () => GRID_SORT_OPTIONS,
193
+ GcdrAnnotationsClient: () => GcdrAnnotationsClient,
194
+ GcdrAnnotationsConflictError: () => GcdrAnnotationsConflictError,
195
+ GcdrAnnotationsError: () => GcdrAnnotationsError,
193
196
  GoalsModal: () => GoalsModal,
194
197
  GoalsMoneyApiError: () => GoalsMoneyApiError,
195
198
  GoalsMoneyClient: () => GoalsMoneyClient,
@@ -331,9 +334,13 @@ __export(src_exports, {
331
334
  WELCOME_DEFAULT_PALETTE: () => DEFAULT_PALETTE,
332
335
  Water: () => Water,
333
336
  WaterSummaryTooltip: () => WaterSummaryTooltip,
337
+ adaptGcdrListToLegacyAnnotations: () => adaptGcdrListToLegacyAnnotations,
338
+ adaptGcdrToLegacyAnnotation: () => adaptGcdrToLegacyAnnotation,
339
+ adaptLegacyToGcdrInput: () => adaptLegacyToGcdrInput,
334
340
  addAlarmBadge: () => addAlarmBadge,
335
341
  addDetectionContext: () => addDetectionContext,
336
342
  addFreshdeskTicketNote: () => addTicketNote,
343
+ addIncidentBadge: () => addIncidentBadge,
337
344
  addNamespace: () => addNamespace,
338
345
  addTicketBadge: () => addTicketBadge,
339
346
  aggregateByDay: () => aggregateByDay,
@@ -356,6 +363,7 @@ __export(src_exports, {
356
363
  buildEquipmentCategoryDataForTooltip: () => buildEquipmentCategoryDataForTooltip,
357
364
  buildEquipmentCategorySummary: () => buildEquipmentCategorySummary,
358
365
  buildFinancialRowHTML: () => buildFinancialRowHTML,
366
+ buildGcdrEnergyRoot: () => buildGcdrEnergyRoot,
359
367
  buildListItemsThingsboardByUniqueDatasource: () => buildListItemsThingsboardByUniqueDatasource,
360
368
  buildMoneyVarianceHTML: () => buildMoneyVarianceHTML,
361
369
  buildMyioIngestionAuth: () => buildMyioIngestionAuth,
@@ -440,6 +448,8 @@ __export(src_exports, {
440
448
  createFilterModalComponent: () => createFilterModalComponent,
441
449
  createFooterComponent: () => createFooterComponent,
442
450
  createFreshdeskTicket: () => createTicket,
451
+ createGcdrAnnotationsAuthStrategy: () => createAuthStrategy,
452
+ createGcdrAnnotationsClient: () => createGcdrAnnotationsClient,
443
453
  createGoalsBarTooltip: () => createGoalsBarTooltip,
444
454
  createGoalsMoneyClient: () => createGoalsMoneyClient,
445
455
  createGranularitySelector: () => createGranularitySelector,
@@ -776,6 +786,8 @@ __export(src_exports, {
776
786
  panelDomainToWire: () => panelDomainToWire,
777
787
  panelTermToDeviceCategory: () => panelTermToDeviceCategory,
778
788
  parseClassificationEntities: () => parseClassificationEntities,
789
+ parseGcdrEnergyRoot: () => parseGcdrEnergyRoot,
790
+ parseGcdrEntityForest: () => parseGcdrEntityForest,
779
791
  parseInputDateToDate: () => parseInputDateToDate,
780
792
  parseLogAnnotations: () => parseLogAnnotations,
781
793
  periodKey: () => periodKey,
@@ -784,6 +796,7 @@ __export(src_exports, {
784
796
  recalculateDeviceStatus: () => recalculateDeviceStatus,
785
797
  recomputeDeviceGridPercentages: () => recomputePercentages,
786
798
  refreshAlarmBadges: () => refreshAlarmBadges,
799
+ refreshIncidentBadges: () => refreshIncidentBadges,
787
800
  refreshTicketBadges: () => refreshTicketBadges,
788
801
  removeAlarmsNotificationsPanelStyles: () => removeAlarmsNotificationsPanelStyles,
789
802
  removeBASDashboardStyles: () => removeBASDashboardStyles,
@@ -877,7 +890,7 @@ module.exports = __toCommonJS(src_exports);
877
890
  // package.json
878
891
  var package_default = {
879
892
  name: "myio-js-library",
880
- version: "0.1.536",
893
+ version: "0.1.538",
881
894
  description: "A clean, standalone JS SDK for MYIO projects",
882
895
  license: "MIT",
883
896
  repository: "github:gh-myio/myio-js-library",
@@ -2265,6 +2278,7 @@ var DeviceIconType = {
2265
2278
  RELOGIO: "RELOGIO",
2266
2279
  ENTRADA: "ENTRADA",
2267
2280
  SUBESTACAO: "SUBESTACAO",
2281
+ TRANSFORMADOR: "TRANSFORMADOR",
2268
2282
  FANCOIL: "FANCOIL",
2269
2283
  CHILLER: "CHILLER",
2270
2284
  HIDROMETRO: "HIDROMETRO",
@@ -2274,7 +2288,8 @@ var DeviceIconType = {
2274
2288
  TERMOSTATO: "TERMOSTATO",
2275
2289
  COMPRESSOR: "COMPRESSOR",
2276
2290
  VENTILADOR: "VENTILADOR",
2277
- SOLENOIDE: "SOLENOIDE"
2291
+ SOLENOIDE: "SOLENOIDE",
2292
+ GATEWAY: "GATEWAY"
2278
2293
  };
2279
2294
  var deviceIcons = {
2280
2295
  ESCADA_ROLANTE: "https://dashboard.myio-bas.com/api/images/public/EJ997iB2HD1AYYUHwIloyQOOszeqb2jp",
@@ -2288,6 +2303,10 @@ var deviceIcons = {
2288
2303
  RELOGIO: "https://dashboard.myio-bas.com/api/images/public/ljHZostWg0G5AfKiyM8oZixWRIIGRASB",
2289
2304
  ENTRADA: "https://dashboard.myio-bas.com/api/images/public/TQHPFqiejMW6lOSVsb8Pi85WtC0QKOLU",
2290
2305
  SUBESTACAO: "https://dashboard.myio-bas.com/api/images/public/TQHPFqiejMW6lOSVsb8Pi85WtC0QKOLU",
2306
+ // TRANSFORMADOR (RFC-0234): no dedicated art asset yet — shares ENTRADA/SUBESTACAO's
2307
+ // icon until a distinct one is uploaded. Explicit entry (not DEFAULT_DEVICE_ICON
2308
+ // fallback) so it's visually grouped with the meters it's electrically closest to.
2309
+ TRANSFORMADOR: "https://dashboard.myio-bas.com/api/images/public/TQHPFqiejMW6lOSVsb8Pi85WtC0QKOLU",
2291
2310
  FANCOIL: "https://dashboard.myio-bas.com/api/images/public/4BWMuVIFHnsfqatiV86DmTrOB7IF0X8Y",
2292
2311
  CHILLER: "https://dashboard.myio-bas.com/api/images/public/27Rvy9HbNoPz8KKWPa0SBDwu4kQ827VU",
2293
2312
  HIDROMETRO: "https://dashboard.myio-bas.com/api/images/public/aMQYFJbGHs9gQbQkMn6XseAlUZHanBR4",
@@ -2299,7 +2318,10 @@ var deviceIcons = {
2299
2318
  COMPRESSOR: "https://dashboard.myio-bas.com/api/images/public/4BWMuVIFHnsfqatiV86DmTrOB7IF0X8Y",
2300
2319
  VENTILADOR: "https://dashboard.myio-bas.com/api/images/public/Rge8Q3t0CP5PW8XyTn9bBK9aVP6uzSTT",
2301
2320
  // SOLENOIDE: single representative (on); dynamic on/off/offline lives in solenoid-control SOLENOID_IMAGES.
2302
- SOLENOIDE: "https://dashboard.myio-bas.com/api/images/public/Tnq47Vd1TxhhqhYoHvzS73WVh1X84fPa"
2321
+ SOLENOIDE: "https://dashboard.myio-bas.com/api/images/public/Tnq47Vd1TxhhqhYoHvzS73WVh1X84fPa",
2322
+ // GATEWAY: central/gateway hardware (OrangePi) — used by SettingsModalView's
2323
+ // "Central" tab identity card. Previously fell through to DEFAULT_DEVICE_ICON.
2324
+ GATEWAY: "https://dashboard.myio-bas.com/api/images/public/kNlazDO8Yy90R5O12i17EqkQUFsNj44b"
2303
2325
  };
2304
2326
  var deviceIconLabels = {
2305
2327
  ESCADA_ROLANTE: "Escada Rolante",
@@ -2313,6 +2335,7 @@ var deviceIconLabels = {
2313
2335
  RELOGIO: "Rel\xF3gio",
2314
2336
  ENTRADA: "Entrada",
2315
2337
  SUBESTACAO: "Subesta\xE7\xE3o",
2338
+ TRANSFORMADOR: "Transformador",
2316
2339
  FANCOIL: "Fancoil",
2317
2340
  CHILLER: "Chiller",
2318
2341
  HIDROMETRO: "Hidr\xF4metro",
@@ -2322,7 +2345,8 @@ var deviceIconLabels = {
2322
2345
  TERMOSTATO: "Termostato",
2323
2346
  COMPRESSOR: "Compressor",
2324
2347
  VENTILADOR: "Ventilador",
2325
- SOLENOIDE: "Solenoide"
2348
+ SOLENOIDE: "Solenoide",
2349
+ GATEWAY: "Gateway"
2326
2350
  };
2327
2351
  var DEFAULT_DEVICE_ICON = "https://dashboard.myio-bas.com/api/images/public/f9Ce4meybsdaAhAkUlAfy5ei3I4kcN4k";
2328
2352
  function getDeviceIcon(deviceProfile) {
@@ -3217,7 +3241,7 @@ var DEFAULT_DEVICE_CLASSIFICATION_PROFILE = {
3217
3241
  rules: [
3218
3242
  // RULE 1 — lojas: dp === '3F_MEDIDOR'
3219
3243
  { name: "lojas", deviceProfiles: ["3F_MEDIDOR"] },
3220
- // RULE 2 — entrada: ENTRADA_PROFILES.has(dp)
3244
+ // RULE 2 — entrada: ENTRADA_PROFILES.has(dp).
3221
3245
  { name: "entrada", deviceProfiles: ["TRAFO", "ENTRADA", "RELOGIO", "SUBESTACAO"] },
3222
3246
  // RULE 3 — areacomum: residual fallback
3223
3247
  { name: "areacomum", deviceProfiles: [], fallback: true }
@@ -3719,6 +3743,150 @@ function listGroups(profile = getActiveProfile(), domain = "") {
3719
3743
  const rules = dom?.groups?.rules ?? [];
3720
3744
  return rules.map((r) => ({ key: r.name, fallback: !!r.fallback }));
3721
3745
  }
3746
+ var KNOWN_ENERGY_GROUP_KEYS = {
3747
+ "energy-entry": "entrada",
3748
+ "energy-stores": "lojas",
3749
+ "energy-commonarea": "areacomum",
3750
+ "energy-transformers": "transformadores"
3751
+ };
3752
+ var ENERGY_GROUP_NAME_TO_KEY = Object.fromEntries(
3753
+ Object.entries(KNOWN_ENERGY_GROUP_KEYS).map(([k, v]) => [v, k])
3754
+ );
3755
+ var KNOWN_ENERGY_CATEGORY_KEYS = {
3756
+ climatizacao: "climatizacao",
3757
+ elevadores: "elevadores",
3758
+ "escada-rolante": "escadas_rolantes",
3759
+ "outros-equipamentos": "outros"
3760
+ };
3761
+ var ENERGY_CATEGORY_NAME_TO_KEY = Object.fromEntries(
3762
+ Object.entries(KNOWN_ENERGY_CATEGORY_KEYS).map(([k, v]) => [v, k])
3763
+ );
3764
+ function stripDomainPrefix(domain, key) {
3765
+ const prefix = `${domain}-`;
3766
+ return key.startsWith(prefix) ? key.slice(prefix.length) : key;
3767
+ }
3768
+ function deriveGroupName(domain, key) {
3769
+ return KNOWN_ENERGY_GROUP_KEYS[key] ?? stripDomainPrefix(domain, key);
3770
+ }
3771
+ function deriveGroupKey(domain, name) {
3772
+ return ENERGY_GROUP_NAME_TO_KEY[name] ?? `${domain}-${name}`;
3773
+ }
3774
+ function deriveCategoryName(key) {
3775
+ return KNOWN_ENERGY_CATEGORY_KEYS[key] ?? key;
3776
+ }
3777
+ function deriveCategoryKey(name) {
3778
+ return ENERGY_CATEGORY_NAME_TO_KEY[name] ?? name;
3779
+ }
3780
+ function isProfileLeafGroup(node) {
3781
+ const children = node.children ?? [];
3782
+ return children.length === 0 || children.every((c) => c.entityType === "PROFILE");
3783
+ }
3784
+ function humanize(name) {
3785
+ const s = name.replace(/[_-]+/g, " ").trim();
3786
+ return s.length ? s[0].toUpperCase() + s.slice(1) : s;
3787
+ }
3788
+ function parseGcdrEnergyRoot(energyRoot) {
3789
+ const domain = "energy";
3790
+ const children = (energyRoot.children ?? []).filter((c) => c.entityType === "GROUP");
3791
+ const rules = [];
3792
+ let containerChild = null;
3793
+ for (const child of children) {
3794
+ if (isProfileLeafGroup(child) && !containerChild) {
3795
+ rules.push({
3796
+ name: deriveGroupName(domain, child.entityKey),
3797
+ deviceProfiles: (child.children ?? []).filter((p) => p.entityType === "PROFILE").map((p) => p.entityKey)
3798
+ });
3799
+ } else if (!containerChild) {
3800
+ containerChild = child;
3801
+ } else {
3802
+ rules.push({
3803
+ name: deriveGroupName(domain, child.entityKey),
3804
+ deviceProfiles: (child.children ?? []).filter((p) => p.entityType === "PROFILE").map((p) => p.entityKey)
3805
+ });
3806
+ }
3807
+ }
3808
+ const fallbackName = containerChild ? deriveGroupName(domain, containerChild.entityKey) : "areacomum";
3809
+ rules.push({ name: fallbackName, deviceProfiles: [], fallback: true });
3810
+ const groups = { ocultosProfilePatterns: [], rules };
3811
+ const storeRule = rules.find((r) => r.name === "lojas");
3812
+ const storeDeviceProfile = storeRule?.deviceProfiles?.[0] || "3F_MEDIDOR";
3813
+ const categoryRules = [];
3814
+ for (const grandchild of containerChild?.children ?? []) {
3815
+ if (grandchild.entityType !== "GROUP") continue;
3816
+ const name = deriveCategoryName(grandchild.entityKey);
3817
+ if (name === "outros") continue;
3818
+ categoryRules.push({
3819
+ name,
3820
+ deviceProfiles: (grandchild.children ?? []).filter((p) => p.entityType === "PROFILE").map((p) => p.entityKey)
3821
+ });
3822
+ }
3823
+ return {
3824
+ caseInsensitive: true,
3825
+ groups,
3826
+ categories: {
3827
+ storeDeviceProfile,
3828
+ rules: categoryRules,
3829
+ fallback: { name: "outros" }
3830
+ }
3831
+ };
3832
+ }
3833
+ function parseGcdrEntityForest(roots) {
3834
+ const energyRoot = (roots ?? []).find(
3835
+ (r) => r.entityType === "GROUP" && r.entityKey === "energy"
3836
+ );
3837
+ return energyRoot ? { energy: parseGcdrEnergyRoot(energyRoot) } : {};
3838
+ }
3839
+ function buildGcdrEnergyRoot(domainProfile, previousRoot) {
3840
+ const domain = "energy";
3841
+ const prevChildByName = /* @__PURE__ */ new Map();
3842
+ for (const child of previousRoot?.children ?? []) {
3843
+ prevChildByName.set(deriveGroupName(domain, child.entityKey), child);
3844
+ }
3845
+ const prevGrandchildByName = /* @__PURE__ */ new Map();
3846
+ const prevContainer = [...prevChildByName.values()].find(
3847
+ (c) => !isProfileLeafGroup(c)
3848
+ );
3849
+ for (const gc of prevContainer?.children ?? []) {
3850
+ prevGrandchildByName.set(deriveCategoryName(gc.entityKey), gc);
3851
+ }
3852
+ const leafProfileNodes = (deviceProfiles) => deviceProfiles.map((dp) => ({
3853
+ entityType: "PROFILE",
3854
+ entityKey: dp,
3855
+ entityValue: dp,
3856
+ children: []
3857
+ }));
3858
+ const children = domainProfile.groups.rules.map((rule) => {
3859
+ const prev = prevChildByName.get(rule.name);
3860
+ if (rule.fallback && domainProfile.categories) {
3861
+ return {
3862
+ entityType: "GROUP",
3863
+ entityKey: prev?.entityKey ?? deriveGroupKey(domain, rule.name),
3864
+ entityValue: prev?.entityValue ?? humanize(rule.name),
3865
+ children: (domainProfile.categories.rules ?? []).map((cat) => {
3866
+ const prevCat = prevGrandchildByName.get(cat.name);
3867
+ return {
3868
+ entityType: "GROUP",
3869
+ entityKey: prevCat?.entityKey ?? deriveCategoryKey(cat.name),
3870
+ entityValue: prevCat?.entityValue ?? humanize(cat.name),
3871
+ children: leafProfileNodes(cat.deviceProfiles)
3872
+ };
3873
+ })
3874
+ };
3875
+ }
3876
+ return {
3877
+ entityType: "GROUP",
3878
+ entityKey: prev?.entityKey ?? deriveGroupKey(domain, rule.name),
3879
+ entityValue: prev?.entityValue ?? humanize(rule.name),
3880
+ children: leafProfileNodes(rule.deviceProfiles)
3881
+ };
3882
+ });
3883
+ return {
3884
+ entityType: "GROUP",
3885
+ entityKey: previousRoot?.entityKey ?? domain,
3886
+ entityValue: previousRoot?.entityValue ?? humanize(domain),
3887
+ children
3888
+ };
3889
+ }
3722
3890
 
3723
3891
  // src/utils/devices/classificationNodeTree.ts
3724
3892
  var up2 = (v) => String(v ?? "").toUpperCase();
@@ -5097,11 +5265,19 @@ var MODAL_HEADER_CSS = `
5097
5265
  }
5098
5266
 
5099
5267
  .myio-modal-header__title {
5100
- margin: 0;
5101
- font-size: 16px;
5102
- font-weight: 600;
5268
+ /* Rendered as an <h2> (see generateHTML() below) \u2014 on a real ThingsBoard
5269
+ dashboard (Angular Material) a global rule like ".mat-typography h2"
5270
+ has HIGHER specificity (class+element) than this single-class selector,
5271
+ regardless of load order, and wins with Material's own much larger
5272
+ font-size/line-height/margin \u2014 the header balloons in height there while
5273
+ looking fine in an isolated showcase page with no Material CSS at all.
5274
+ Same class of bug, same !important remedy already used in
5275
+ WelcomeModalView.ts's title rules. */
5276
+ margin: 0 !important;
5277
+ font-size: 16px !important;
5278
+ font-weight: 600 !important;
5103
5279
  color: white;
5104
- line-height: 1.4;
5280
+ line-height: 1.4 !important;
5105
5281
  white-space: nowrap;
5106
5282
  overflow: hidden;
5107
5283
  text-overflow: ellipsis;
@@ -8527,7 +8703,7 @@ function buildMyioIngestionAuth(config2) {
8527
8703
  const skewMs = renewSkewSeconds * 1e3;
8528
8704
  return now() >= cache.expiresAt - skewMs;
8529
8705
  }
8530
- async function sleep(ms) {
8706
+ async function sleep2(ms) {
8531
8707
  return new Promise((resolve) => setTimeout(resolve, ms));
8532
8708
  }
8533
8709
  async function requestNewToken() {
@@ -8571,7 +8747,7 @@ function buildMyioIngestionAuth(config2) {
8571
8747
  throw error;
8572
8748
  }
8573
8749
  const backoff = retryBaseMs * Math.pow(2, attempt - 1);
8574
- await sleep(backoff);
8750
+ await sleep2(backoff);
8575
8751
  }
8576
8752
  }
8577
8753
  }
@@ -9837,6 +10013,392 @@ async function createAnnotationIndicator(config2) {
9837
10013
  return indicator;
9838
10014
  }
9839
10015
 
10016
+ // src/components/gcdr-annotations/v1.0.0/authStrategies.ts
10017
+ var ApiKeyAuth = class {
10018
+ constructor(apiKey) {
10019
+ this.apiKey = apiKey;
10020
+ }
10021
+ apply(headers) {
10022
+ headers["X-API-Key"] = this.apiKey;
10023
+ }
10024
+ };
10025
+ var BearerAuth = class {
10026
+ constructor(token) {
10027
+ this.token = token;
10028
+ }
10029
+ async apply(headers) {
10030
+ const resolved = typeof this.token === "function" ? await this.token() : this.token;
10031
+ headers["Authorization"] = `Bearer ${resolved}`;
10032
+ }
10033
+ };
10034
+ function createAuthStrategy(auth) {
10035
+ const hasApiKey = !!auth.apiKey;
10036
+ const hasBearer = auth.bearerToken !== void 0 && auth.bearerToken !== null && auth.bearerToken !== "";
10037
+ if (hasApiKey && hasBearer) {
10038
+ throw new Error(
10039
+ "GcdrAnnotationsClient: provide exactly one of auth.apiKey or auth.bearerToken, not both."
10040
+ );
10041
+ }
10042
+ if (hasApiKey) return new ApiKeyAuth(auth.apiKey);
10043
+ if (hasBearer) return new BearerAuth(auth.bearerToken);
10044
+ throw new Error("GcdrAnnotationsClient: auth.apiKey or auth.bearerToken is required.");
10045
+ }
10046
+
10047
+ // src/components/gcdr-annotations/v1.0.0/adapters.ts
10048
+ var GCDR_TO_LEGACY_TYPE = {
10049
+ observation: "observation",
10050
+ pending: "pending",
10051
+ maintenance: "maintenance",
10052
+ activity: "activity"
10053
+ };
10054
+ var LEGACY_TO_GCDR_TYPE = {
10055
+ observation: "observation",
10056
+ pending: "pending",
10057
+ maintenance: "maintenance",
10058
+ activity: "activity"
10059
+ };
10060
+ var KNOWN_AUDIT_ACTIONS = /* @__PURE__ */ new Set([
10061
+ "created",
10062
+ "modified",
10063
+ "archived",
10064
+ "approved",
10065
+ "rejected",
10066
+ "commented",
10067
+ "acknowledged"
10068
+ ]);
10069
+ function toUserInfo(u) {
10070
+ return { id: u?.id ?? "", email: u?.email ?? "", name: u?.name ?? "" };
10071
+ }
10072
+ function deriveLegacyStatus(a) {
10073
+ if (a.finalized && a.finalizedReason === "archived") return "archived";
10074
+ return a.version > 1 ? "modified" : "created";
10075
+ }
10076
+ function adaptResponse(r, annotationId) {
10077
+ return {
10078
+ id: r.id,
10079
+ annotationId,
10080
+ type: r.type,
10081
+ text: r.text ?? "",
10082
+ createdAt: r.createdAt,
10083
+ createdBy: toUserInfo(r.createdBy)
10084
+ };
10085
+ }
10086
+ function adaptEvent(e) {
10087
+ const action = KNOWN_AUDIT_ACTIONS.has(e.action) ? e.action : "modified";
10088
+ return {
10089
+ timestamp: e.at,
10090
+ userId: e.by?.id ?? "",
10091
+ userName: e.by?.name ?? "",
10092
+ userEmail: e.by?.email ?? "",
10093
+ action
10094
+ };
10095
+ }
10096
+ function adaptGcdrToLegacyAnnotation(g) {
10097
+ const detail = g;
10098
+ const responses = (detail.responses ?? []).map((r) => adaptResponse(r, g.id));
10099
+ const history = (detail.events ?? []).map(adaptEvent);
10100
+ const approvedOrRejected = responses.find((r) => r.type === "approved" || r.type === "rejected");
10101
+ return {
10102
+ id: g.id,
10103
+ version: g.version,
10104
+ text: g.text,
10105
+ type: GCDR_TO_LEGACY_TYPE[g.type] ?? "observation",
10106
+ importance: g.importance,
10107
+ status: deriveLegacyStatus(g),
10108
+ createdAt: g.createdAt,
10109
+ dueDate: g.dueDate ?? void 0,
10110
+ createdBy: toUserInfo(g.createdBy),
10111
+ acknowledged: !!approvedOrRejected,
10112
+ acknowledgedBy: approvedOrRejected?.createdBy,
10113
+ acknowledgedAt: approvedOrRejected?.createdAt,
10114
+ responses,
10115
+ history
10116
+ };
10117
+ }
10118
+ function adaptGcdrListToLegacyAnnotations(list) {
10119
+ return list.map(adaptGcdrToLegacyAnnotation);
10120
+ }
10121
+ function adaptLegacyToGcdrInput(data, entity) {
10122
+ return {
10123
+ entityType: entity.entityType,
10124
+ entityId: entity.entityId,
10125
+ customerId: entity.customerId,
10126
+ text: data.text,
10127
+ type: LEGACY_TO_GCDR_TYPE[data.type] ?? "observation",
10128
+ importance: data.importance,
10129
+ dueDate: data.dueDate
10130
+ };
10131
+ }
10132
+
10133
+ // src/components/gcdr-annotations/v1.0.0/types.ts
10134
+ var GcdrAnnotationsError = class extends Error {
10135
+ status;
10136
+ code;
10137
+ constructor(status, message, code) {
10138
+ super(message || `HTTP_${status}`);
10139
+ this.name = "GcdrAnnotationsError";
10140
+ this.status = status;
10141
+ this.code = code;
10142
+ }
10143
+ };
10144
+ var GcdrAnnotationsConflictError = class extends GcdrAnnotationsError {
10145
+ finalizedOrStale = true;
10146
+ constructor(message) {
10147
+ super(409, message, "CONFLICT");
10148
+ this.name = "GcdrAnnotationsConflictError";
10149
+ }
10150
+ };
10151
+
10152
+ // src/components/gcdr-annotations/v1.0.0/GcdrAnnotationsClient.ts
10153
+ var DEFAULT_MAX_PAGES = 50;
10154
+ var DEFAULT_LIMIT = 100;
10155
+ var DEFAULT_MAX_RETRIES = 3;
10156
+ var DEFAULT_RETRY_BASE_MS = 500;
10157
+ function normalizeDomainPath(domainPath) {
10158
+ return String(domainPath).replace(/\/+$/, "");
10159
+ }
10160
+ function isNetworkError(err) {
10161
+ if (!err || typeof err !== "object") return false;
10162
+ const name = err.name ?? "";
10163
+ const message = String(err.message ?? "").toLowerCase();
10164
+ return name === "TypeError" || message.includes("network") || message.includes("failed to fetch");
10165
+ }
10166
+ function sleep(ms) {
10167
+ return new Promise((resolve) => setTimeout(resolve, ms));
10168
+ }
10169
+ var GcdrAnnotationsClient = class {
10170
+ domainPath;
10171
+ authStrategy;
10172
+ fetchImpl;
10173
+ logger;
10174
+ cacheTtlMs;
10175
+ maxPages;
10176
+ cache = /* @__PURE__ */ new Map();
10177
+ constructor(params) {
10178
+ if (!params || !params.domainPath) {
10179
+ throw new Error("GcdrAnnotationsClient requires a domainPath.");
10180
+ }
10181
+ this.domainPath = normalizeDomainPath(params.domainPath);
10182
+ this.authStrategy = createAuthStrategy(params.auth);
10183
+ const injected = params.fetchImpl || globalThis.fetch;
10184
+ if (typeof injected !== "function") {
10185
+ throw new Error("GcdrAnnotationsClient requires a fetch implementation.");
10186
+ }
10187
+ this.fetchImpl = params.fetchImpl ? params.fetchImpl : injected.bind(globalThis);
10188
+ this.logger = params.logger ?? console;
10189
+ this.cacheTtlMs = params.cacheTtlMs ?? 0;
10190
+ this.maxPages = params.maxPages ?? DEFAULT_MAX_PAGES;
10191
+ }
10192
+ /** Drops every cached GET response. Called automatically after any write. */
10193
+ invalidate() {
10194
+ this.cache.clear();
10195
+ }
10196
+ // ── Reads ──────────────────────────────────────────────────────────────
10197
+ /** One page of `GET /annotations` with the given filters. */
10198
+ async list(params = {}) {
10199
+ const query = new URLSearchParams();
10200
+ if (params.customerId) query.set("customerId", params.customerId);
10201
+ if (params.entityType) query.set("entityType", params.entityType);
10202
+ if (params.entityId) query.set("entityId", params.entityId);
10203
+ if (params.type) query.set("type", params.type);
10204
+ if (params.status) query.set("status", params.status);
10205
+ if (params.importance !== void 0) query.set("importance", String(params.importance));
10206
+ if (params.includeArchived !== void 0) query.set("includeArchived", String(params.includeArchived));
10207
+ query.set("page", String(params.page ?? 1));
10208
+ query.set("limit", String(params.limit ?? DEFAULT_LIMIT));
10209
+ const raw = await this._requestJson(`/annotations?${query.toString()}`, { method: "GET" });
10210
+ return this._toListPage(raw, params.page ?? 1, params.limit ?? DEFAULT_LIMIT);
10211
+ }
10212
+ /** Every annotation of a customer, aggregated across as many pages as it takes (capped at `maxPages`). */
10213
+ async listByCustomer(customerId, params = {}) {
10214
+ return this._listAll({ ...params, customerId });
10215
+ }
10216
+ /** Every annotation attached to one entity (e.g. a device, or a central identified by its GCDR UUID). */
10217
+ async listByEntity(entityType, entityId, params = {}) {
10218
+ return this._listAll({ ...params, entityType, entityId });
10219
+ }
10220
+ /** Full detail — responses, events (audit trail), mentions, attachments. */
10221
+ async getById(id) {
10222
+ const raw = await this._requestJson(`/annotations/${encodeURIComponent(id)}`, { method: "GET" });
10223
+ return this._unwrap(raw);
10224
+ }
10225
+ /** Convenience bridge — same `Annotation[]` shape `AnnotationsTab`/orchestrators already consume. */
10226
+ toLegacyAnnotations(list) {
10227
+ return adaptGcdrListToLegacyAnnotations(list);
10228
+ }
10229
+ // ── Writes ─────────────────────────────────────────────────────────────
10230
+ async create(input) {
10231
+ const raw = await this._requestJson("/annotations", {
10232
+ method: "POST",
10233
+ body: JSON.stringify(input)
10234
+ });
10235
+ this.invalidate();
10236
+ return this._unwrap(raw);
10237
+ }
10238
+ /** Optimistic lock via `If-Match: "<version>"` — a stale version raises `GcdrAnnotationsConflictError`. */
10239
+ async patch(id, changes, version2) {
10240
+ const raw = await this._requestJson(`/annotations/${encodeURIComponent(id)}`, {
10241
+ method: "PATCH",
10242
+ body: JSON.stringify(changes),
10243
+ headers: { "If-Match": `"${version2}"` }
10244
+ });
10245
+ this.invalidate();
10246
+ return this._unwrap(raw);
10247
+ }
10248
+ async archive(id, version2) {
10249
+ const raw = await this._requestJson(`/annotations/${encodeURIComponent(id)}/archive`, {
10250
+ method: "POST",
10251
+ headers: { "If-Match": `"${version2}"` }
10252
+ });
10253
+ this.invalidate();
10254
+ return this._unwrap(raw);
10255
+ }
10256
+ /**
10257
+ * Posts a response (comment/approve/reject/archive). `text` is required for
10258
+ * every type except `approved` — mirrored client-side so a caller gets an
10259
+ * immediate, local error instead of a round trip (API guide §5 rule).
10260
+ */
10261
+ async respond(id, input) {
10262
+ if (input.type !== "approved" && !input.text) {
10263
+ throw new GcdrAnnotationsError(400, `respond(): 'text' is required for response type '${input.type}'.`);
10264
+ }
10265
+ const raw = await this._requestJson(`/annotations/${encodeURIComponent(id)}/responses`, {
10266
+ method: "POST",
10267
+ body: JSON.stringify(input)
10268
+ });
10269
+ this.invalidate();
10270
+ return this._unwrap(raw);
10271
+ }
10272
+ async mention(id, userId) {
10273
+ await this._requestJson(`/annotations/${encodeURIComponent(id)}/mentions`, {
10274
+ method: "POST",
10275
+ body: JSON.stringify({ userId })
10276
+ });
10277
+ this.invalidate();
10278
+ }
10279
+ async attach(id, attachment) {
10280
+ await this._requestJson(`/annotations/${encodeURIComponent(id)}/attachments`, {
10281
+ method: "POST",
10282
+ body: JSON.stringify(attachment)
10283
+ });
10284
+ this.invalidate();
10285
+ }
10286
+ async detach(id, attachmentId) {
10287
+ await this._requestJson(
10288
+ `/annotations/${encodeURIComponent(id)}/attachments/${encodeURIComponent(attachmentId)}`,
10289
+ { method: "DELETE" }
10290
+ );
10291
+ this.invalidate();
10292
+ }
10293
+ // ── Internals ────────────────────────────────────────────────────────────
10294
+ async _listAll(params) {
10295
+ const limit = params.limit ?? DEFAULT_LIMIT;
10296
+ const out = [];
10297
+ for (let page = 1; page <= this.maxPages; page++) {
10298
+ const result = await this.list({ ...params, page, limit });
10299
+ out.push(...result.items);
10300
+ if (!result.pagination.hasNext) return out;
10301
+ if (page === this.maxPages) {
10302
+ this.logger.warn(
10303
+ `[GcdrAnnotationsClient] hit hard page cap of ${this.maxPages} \u2014 some annotations may be missing.`
10304
+ );
10305
+ }
10306
+ }
10307
+ return out;
10308
+ }
10309
+ _toListPage(raw, page, limit) {
10310
+ const body = raw;
10311
+ if (Array.isArray(body)) {
10312
+ return { items: body, pagination: { page, limit, hasNext: false } };
10313
+ }
10314
+ const items = Array.isArray(body?.data) ? body.data : [];
10315
+ const pagination = body?.pagination ?? {
10316
+ page,
10317
+ limit,
10318
+ hasNext: items.length >= limit
10319
+ };
10320
+ return { items, pagination };
10321
+ }
10322
+ /** Unwraps the `{success, data}` envelope; tolerates an already-flat body (test doubles, future API versions). */
10323
+ _unwrap(raw) {
10324
+ const envelope = raw;
10325
+ if (envelope && typeof envelope === "object" && "data" in envelope) {
10326
+ return envelope.data;
10327
+ }
10328
+ return raw;
10329
+ }
10330
+ async _requestJson(path, init) {
10331
+ const url = `${this.domainPath}${path.startsWith("/") ? path : `/${path}`}`;
10332
+ const method = init.method ?? "GET";
10333
+ const cacheKey = `${method}::${url}`;
10334
+ if (method === "GET" && this.cacheTtlMs > 0) {
10335
+ const cached = this.cache.get(cacheKey);
10336
+ if (cached && Date.now() - cached.at < this.cacheTtlMs) {
10337
+ return cached.value;
10338
+ }
10339
+ }
10340
+ const result = await this._doRequest(url, init);
10341
+ if (method === "GET" && this.cacheTtlMs > 0) {
10342
+ this.cache.set(cacheKey, { at: Date.now(), value: result });
10343
+ }
10344
+ return result;
10345
+ }
10346
+ async _doRequest(url, init) {
10347
+ let attempt = 0;
10348
+ while (true) {
10349
+ const headers = { Accept: "application/json", ...init.headers || {} };
10350
+ if (init.body !== void 0) headers["Content-Type"] = "application/json";
10351
+ await this.authStrategy.apply(headers);
10352
+ try {
10353
+ const res = await this.fetchImpl(url, { ...init, headers });
10354
+ if ((res.status === 429 || res.status >= 500) && attempt < DEFAULT_MAX_RETRIES) {
10355
+ const backoffMs = DEFAULT_RETRY_BASE_MS * Math.pow(2, attempt);
10356
+ this.logger.warn(
10357
+ `[GcdrAnnotationsClient] ${res.status} on ${url} \u2014 retry ${attempt + 1}/${DEFAULT_MAX_RETRIES} in ${backoffMs}ms`
10358
+ );
10359
+ attempt++;
10360
+ await sleep(backoffMs);
10361
+ continue;
10362
+ }
10363
+ const body = await this._readBody(res);
10364
+ if (res.status === 409) {
10365
+ const err = body && body.error;
10366
+ throw new GcdrAnnotationsConflictError(err?.message || res.statusText);
10367
+ }
10368
+ if (!res.ok) {
10369
+ const err = body && body.error;
10370
+ throw new GcdrAnnotationsError(res.status, err?.message || res.statusText, err?.code);
10371
+ }
10372
+ return body;
10373
+ } catch (err) {
10374
+ if (err instanceof GcdrAnnotationsError) throw err;
10375
+ if (isNetworkError(err) && attempt < DEFAULT_MAX_RETRIES) {
10376
+ const backoffMs = DEFAULT_RETRY_BASE_MS * Math.pow(2, attempt);
10377
+ this.logger.warn(
10378
+ `[GcdrAnnotationsClient] network error on ${url} \u2014 retry ${attempt + 1}/${DEFAULT_MAX_RETRIES} in ${backoffMs}ms`
10379
+ );
10380
+ attempt++;
10381
+ await sleep(backoffMs);
10382
+ continue;
10383
+ }
10384
+ throw new GcdrAnnotationsError(0, err instanceof Error ? err.message : "network error");
10385
+ }
10386
+ }
10387
+ }
10388
+ async _readBody(res) {
10389
+ try {
10390
+ const text2 = await res.text();
10391
+ if (!text2) return null;
10392
+ return JSON.parse(text2);
10393
+ } catch {
10394
+ return null;
10395
+ }
10396
+ }
10397
+ };
10398
+ function createGcdrAnnotationsClient(params) {
10399
+ return new GcdrAnnotationsClient(params);
10400
+ }
10401
+
9840
10402
  // src/utils/devices/deviceType.js
9841
10403
  function normalize(str) {
9842
10404
  if (typeof str !== "string") return "";
@@ -61047,9 +61609,273 @@ function createTicketsTab(config2) {
61047
61609
  return { destroy: () => tab.destroy() };
61048
61610
  }
61049
61611
 
61612
+ // src/components/createInputDateRangePickerInsideDIV.ts
61613
+ var PREMIUM_STYLES = `
61614
+ .myio-daterange-wrapper {
61615
+ font-family: 'Nunito', system-ui, sans-serif;
61616
+ background: #f9f9f9;
61617
+ padding: 20px;
61618
+ border-radius: 8px;
61619
+ margin-bottom: 20px;
61620
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
61621
+ transition: all 0.2s ease;
61622
+ }
61623
+
61624
+ .myio-daterange-wrapper:hover {
61625
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
61626
+ }
61627
+
61628
+ .myio-daterange-label {
61629
+ display: block;
61630
+ margin-bottom: 8px;
61631
+ font-weight: 500;
61632
+ color: #333;
61633
+ font-size: 14px;
61634
+ line-height: 1.4;
61635
+ }
61636
+
61637
+ .myio-daterange-input {
61638
+ width: 100%;
61639
+ max-width: 300px;
61640
+ padding: 8px 12px;
61641
+ border: 1px solid #ddd;
61642
+ border-radius: 4px;
61643
+ background: white;
61644
+ font-size: 14px;
61645
+ color: #333;
61646
+ cursor: pointer;
61647
+ transition: all 0.2s ease;
61648
+ font-family: inherit;
61649
+ line-height: 1.4;
61650
+ }
61651
+
61652
+ .myio-daterange-input:hover {
61653
+ border-color: #4A148C;
61654
+ box-shadow: 0 0 0 2px rgba(74, 20, 140, 0.1);
61655
+ }
61656
+
61657
+ .myio-daterange-input:focus {
61658
+ outline: none;
61659
+ border-color: #4A148C;
61660
+ box-shadow: 0 0 0 3px rgba(74, 20, 140, 0.2);
61661
+ }
61662
+
61663
+ .myio-daterange-input::placeholder {
61664
+ color: #999;
61665
+ opacity: 1;
61666
+ }
61667
+
61668
+ .myio-daterange-helper {
61669
+ font-size: 12px;
61670
+ color: #666;
61671
+ margin-top: 4px;
61672
+ line-height: 1.3;
61673
+ transition: color 0.2s ease;
61674
+ }
61675
+
61676
+ .myio-daterange-helper.success {
61677
+ color: #28a745;
61678
+ font-weight: 500;
61679
+ }
61680
+
61681
+ .myio-daterange-helper.error {
61682
+ color: #dc3545;
61683
+ font-weight: 500;
61684
+ }
61685
+
61686
+ /* Responsive design */
61687
+ @media (max-width: 768px) {
61688
+ .myio-daterange-wrapper {
61689
+ padding: 16px;
61690
+ margin-bottom: 16px;
61691
+ }
61692
+
61693
+ .myio-daterange-input {
61694
+ max-width: 100%;
61695
+ font-size: 16px; /* Prevents zoom on iOS */
61696
+ }
61697
+ }
61698
+
61699
+ /* High contrast mode support */
61700
+ @media (prefers-contrast: high) {
61701
+ .myio-daterange-wrapper {
61702
+ border: 2px solid #000;
61703
+ }
61704
+
61705
+ .myio-daterange-input {
61706
+ border: 2px solid #000;
61707
+ }
61708
+ }
61709
+
61710
+ /* Reduced motion support */
61711
+ @media (prefers-reduced-motion: reduce) {
61712
+ .myio-daterange-wrapper,
61713
+ .myio-daterange-input,
61714
+ .myio-daterange-helper {
61715
+ transition: none;
61716
+ }
61717
+ }
61718
+ `;
61719
+ function injectPremiumStyles() {
61720
+ const styleId = "myio-daterange-premium-styles";
61721
+ if (document.getElementById(styleId)) {
61722
+ return;
61723
+ }
61724
+ const styleEl = document.createElement("style");
61725
+ styleEl.id = styleId;
61726
+ styleEl.textContent = PREMIUM_STYLES;
61727
+ document.head.appendChild(styleEl);
61728
+ console.log("[MyIO] Premium date range styles injected");
61729
+ }
61730
+ function validateId(id, context) {
61731
+ if (!id || typeof id !== "string") {
61732
+ throw new Error(`[createInputDateRangePickerInsideDIV] ${context} must be a non-empty string`);
61733
+ }
61734
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(id)) {
61735
+ throw new Error(`[createInputDateRangePickerInsideDIV] ${context} '${id}' is not a valid HTML ID`);
61736
+ }
61737
+ }
61738
+ async function createInputDateRangePickerInsideDIV(params) {
61739
+ const {
61740
+ containerId,
61741
+ inputId,
61742
+ label = "Per\xEDodo de Datas",
61743
+ placeholder = "Clique para selecionar per\xEDodo",
61744
+ pickerOptions = {},
61745
+ classNames = {},
61746
+ injectStyles: injectStyles23 = true,
61747
+ showHelper = true
61748
+ } = params;
61749
+ validateId(containerId, "containerId");
61750
+ validateId(inputId, "inputId");
61751
+ const container = document.getElementById(containerId);
61752
+ if (!container) {
61753
+ throw new Error(`[createInputDateRangePickerInsideDIV] Container '#${containerId}' not found`);
61754
+ }
61755
+ if (injectStyles23) {
61756
+ injectPremiumStyles();
61757
+ }
61758
+ let inputEl = document.getElementById(inputId);
61759
+ if (inputEl && inputEl.tagName.toLowerCase() !== "input") {
61760
+ throw new Error(`[createInputDateRangePickerInsideDIV] Element '#${inputId}' exists but is not an input element`);
61761
+ }
61762
+ const wrapper = document.createElement("div");
61763
+ wrapper.className = classNames.wrapper || "myio-daterange-wrapper";
61764
+ wrapper.setAttribute("data-myio-component", "daterange-input");
61765
+ wrapper.setAttribute("data-version", "1.0.0");
61766
+ let labelEl = null;
61767
+ if (label) {
61768
+ labelEl = document.createElement("label");
61769
+ labelEl.className = classNames.label || "myio-daterange-label";
61770
+ labelEl.textContent = label;
61771
+ labelEl.setAttribute("for", inputId);
61772
+ wrapper.appendChild(labelEl);
61773
+ }
61774
+ if (!inputEl) {
61775
+ inputEl = document.createElement("input");
61776
+ inputEl.type = "text";
61777
+ inputEl.id = inputId;
61778
+ inputEl.name = inputId;
61779
+ }
61780
+ inputEl.className = classNames.input || "myio-daterange-input";
61781
+ inputEl.readOnly = true;
61782
+ inputEl.placeholder = placeholder;
61783
+ inputEl.autocomplete = "off";
61784
+ inputEl.setAttribute("aria-label", label || "Date range selector");
61785
+ if (showHelper) {
61786
+ inputEl.setAttribute("aria-describedby", `${inputId}-helper`);
61787
+ }
61788
+ let helperEl = null;
61789
+ if (showHelper) {
61790
+ helperEl = document.createElement("div");
61791
+ helperEl.id = `${inputId}-helper`;
61792
+ helperEl.className = classNames.helper || "myio-daterange-helper";
61793
+ helperEl.setAttribute("aria-live", "polite");
61794
+ helperEl.style.display = "flex";
61795
+ helperEl.style.alignItems = "center";
61796
+ }
61797
+ wrapper.appendChild(inputEl);
61798
+ if (helperEl) {
61799
+ wrapper.appendChild(helperEl);
61800
+ }
61801
+ const existingWrapper = container.querySelector('[data-myio-component="daterange-input"]');
61802
+ if (existingWrapper) {
61803
+ console.warn(`[createInputDateRangePickerInsideDIV] Replacing existing daterange input in container '#${containerId}'`);
61804
+ existingWrapper.remove();
61805
+ }
61806
+ container.appendChild(wrapper);
61807
+ const enhancedOptions = {
61808
+ maxRangeDays: 31,
61809
+ onApply: (result) => {
61810
+ if (helperEl) {
61811
+ const startDate = new Date(result.startISO);
61812
+ const endDate = new Date(result.endISO);
61813
+ const days = Math.ceil((endDate.getTime() - startDate.getTime()) / (1e3 * 60 * 60 * 24)) + 1;
61814
+ helperEl.textContent = `Per\xEDodo selecionado: ${days} dia${days !== 1 ? "s" : ""}`;
61815
+ helperEl.className = (classNames.helper || "myio-daterange-helper") + " success";
61816
+ setTimeout(() => {
61817
+ if (helperEl) {
61818
+ helperEl.className = classNames.helper || "myio-daterange-helper";
61819
+ }
61820
+ }, 3e3);
61821
+ }
61822
+ if (pickerOptions.onApply) {
61823
+ pickerOptions.onApply(result);
61824
+ }
61825
+ },
61826
+ ...pickerOptions
61827
+ };
61828
+ let picker;
61829
+ try {
61830
+ picker = await createDateRangePicker2(inputEl, enhancedOptions);
61831
+ console.log(`[createInputDateRangePickerInsideDIV] Successfully initialized for input '#${inputId}'`);
61832
+ } catch (error) {
61833
+ wrapper.remove();
61834
+ throw new Error(`[createInputDateRangePickerInsideDIV] Failed to initialize date picker: ${error.message}`);
61835
+ }
61836
+ const controller = {
61837
+ input: inputEl,
61838
+ container,
61839
+ wrapper,
61840
+ picker,
61841
+ getDisplayValue: () => inputEl.value,
61842
+ getDates: () => picker.getDates(),
61843
+ setDates: (startISO, endISO) => {
61844
+ try {
61845
+ picker.setDates(startISO, endISO);
61846
+ } catch (error) {
61847
+ console.error(`[createInputDateRangePickerInsideDIV] Error setting dates:`, error);
61848
+ throw error;
61849
+ }
61850
+ },
61851
+ setHelperText: (text2, type = "default") => {
61852
+ if (helperEl) {
61853
+ helperEl.textContent = text2;
61854
+ const baseClass = classNames.helper || "myio-daterange-helper";
61855
+ helperEl.className = type === "default" ? baseClass : `${baseClass} ${type}`;
61856
+ }
61857
+ },
61858
+ destroy: () => {
61859
+ try {
61860
+ picker.destroy();
61861
+ console.log(`[createInputDateRangePickerInsideDIV] Date picker destroyed for input '#${inputId}'`);
61862
+ } catch (error) {
61863
+ console.warn(`[createInputDateRangePickerInsideDIV] Error destroying picker:`, error);
61864
+ }
61865
+ try {
61866
+ wrapper.remove();
61867
+ console.log(`[createInputDateRangePickerInsideDIV] Wrapper removed for input '#${inputId}'`);
61868
+ } catch (error) {
61869
+ console.warn(`[createInputDateRangePickerInsideDIV] Error removing wrapper:`, error);
61870
+ }
61871
+ }
61872
+ };
61873
+ return controller;
61874
+ }
61875
+
61050
61876
  // src/components/premium-modals/settings/SettingsModalView.ts
61051
61877
  var ALLOWED_EMAIL_DOMAIN = "@myio.com.br";
61052
- var SettingsModalView = class {
61878
+ var SettingsModalView = class _SettingsModalView {
61053
61879
  container;
61054
61880
  modal;
61055
61881
  form;
@@ -61064,14 +61890,24 @@ var SettingsModalView = class {
61064
61890
  chamadosTabHandle = null;
61065
61891
  // Exclusão de Grupos tab
61066
61892
  exclusionGroupsTab = null;
61067
- // "gateway": read-only Central identity/telemetry tab, only rendered when
61068
- // config.isGateway (see getGatewayInfoHTML()) — a real device never has it.
61069
61893
  currentTab = "general";
61070
61894
  currentUser = null;
61071
61895
  permissions = null;
61072
61896
  // RFC-0190: Exclude Groups Totals
61073
61897
  excludeGroupsEnabled = false;
61074
61898
  excludedGroups = [];
61899
+ // RFC-0232: Incidentes tab filters (search/devices/period) — client-side
61900
+ // state, all derived from `config.interpolatedSlots` (the host's one
61901
+ // pre-fetch). `incidentsIdSuffix` is only needed because
61902
+ // createInputDateRangePickerInsideDIV() does a global `document.getElementById`
61903
+ // internally — every other Incidentes element is found via `this.modal.querySelector`,
61904
+ // which is safely scoped even with duplicate ids across simultaneous instances.
61905
+ incidentsIdSuffix = Math.random().toString(36).slice(2, 10);
61906
+ incidentsAllSlots = [];
61907
+ incidentsSelectedDevices = /* @__PURE__ */ new Set();
61908
+ incidentsSearch = "";
61909
+ incidentsDateRange = { startISO: null, endISO: null };
61910
+ incidentsDateRangeController = null;
61075
61911
  constructor(config2) {
61076
61912
  this.config = config2;
61077
61913
  this.createModal();
@@ -61132,6 +61968,10 @@ var SettingsModalView = class {
61132
61968
  this.initAlarmsTab();
61133
61969
  this.initChamadosTab();
61134
61970
  this.initExclusionGroupsTab();
61971
+ if (this.config.isGateway && this.isSuperAdmin()) {
61972
+ this.bindIncidentsTabEvents();
61973
+ this.initIncidentsDateRange();
61974
+ }
61135
61975
  this._updateTabBadges(initialData);
61136
61976
  }
61137
61977
  // RFC-0104: Initialize the Annotations Tab
@@ -61355,13 +62195,13 @@ var SettingsModalView = class {
61355
62195
  const alarmsContent = this.modal.querySelector("#alarms-tab-content");
61356
62196
  const chamadosContent = this.modal.querySelector("#chamados-tab-content");
61357
62197
  const exclusionGroupsContent = this.modal.querySelector("#exclusion-groups-tab-content");
61358
- const gatewayContent = this.modal.querySelector("#gateway-tab-content");
62198
+ const incidentsContent = this.modal.querySelector("#incidents-tab-content");
61359
62199
  if (generalContent) generalContent.style.display = tab === "general" ? "block" : "none";
61360
62200
  if (annotationsContent) annotationsContent.style.display = tab === "annotations" ? "block" : "none";
61361
62201
  if (alarmsContent) alarmsContent.style.display = tab === "alarms" ? "block" : "none";
61362
62202
  if (chamadosContent) chamadosContent.style.display = tab === "chamados" ? "block" : "none";
61363
62203
  if (exclusionGroupsContent) exclusionGroupsContent.style.display = tab === "exclusion-groups" ? "block" : "none";
61364
- if (gatewayContent) gatewayContent.style.display = tab === "gateway" ? "block" : "none";
62204
+ if (incidentsContent) incidentsContent.style.display = tab === "incidents" ? "block" : "none";
61365
62205
  const saveBtn = this.modal.querySelector(".btn-save");
61366
62206
  if (saveBtn) saveBtn.style.display = tab === "general" ? "inline-flex" : "none";
61367
62207
  if (tab === "annotations" && this.annotationsTab) {
@@ -61386,6 +62226,10 @@ var SettingsModalView = class {
61386
62226
  this.exclusionGroupsTab.destroy();
61387
62227
  this.exclusionGroupsTab = null;
61388
62228
  }
62229
+ if (this.incidentsDateRangeController) {
62230
+ this.incidentsDateRangeController.destroy();
62231
+ this.incidentsDateRangeController = null;
62232
+ }
61389
62233
  if (this.originalActiveElement && "focus" in this.originalActiveElement) {
61390
62234
  this.originalActiveElement.focus();
61391
62235
  }
@@ -61552,15 +62396,15 @@ var SettingsModalView = class {
61552
62396
  Excluir Grupos
61553
62397
  <span class="modal-tab-badge modal-tab-badge--exclusion" id="tab-badge-exclusion-groups" style="display:none"></span>
61554
62398
  </button>
61555
- <!-- Central/gateway identity+telemetry tab \u2014 only for CentralSettingsModal (isGateway) -->
61556
- ${this.config.isGateway ? `<button type="button" class="modal-tab" data-tab="gateway">
62399
+ <!-- RFC-0232: Incidentes (interpola\xE7\xE3o) \u2014 central + admin MyIO only -->
62400
+ ${this.config.isGateway && this.isSuperAdmin() ? `<button type="button" class="modal-tab" data-tab="incidents">
61557
62401
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
61558
- <rect x="2" y="4" width="20" height="6" rx="1"></rect>
61559
- <rect x="2" y="14" width="20" height="6" rx="1"></rect>
61560
- <line x1="6" y1="7" x2="6.01" y2="7"></line>
61561
- <line x1="6" y1="17" x2="6.01" y2="17"></line>
62402
+ <path d="M12 9v4"></path>
62403
+ <path d="M12 17h.01"></path>
62404
+ <path d="M10.29 3.86l-8.18 14.14A2 2 0 0 0 3.93 21h16.14a2 2 0 0 0 1.82-2.99L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
61562
62405
  </svg>
61563
- Central
62406
+ Incidentes
62407
+ ${(this.config.interpolatedSlots?.length ?? 0) > 0 ? `<span class="modal-tab-badge modal-tab-badge--incidents">${this.config.interpolatedSlots.length}</span>` : ""}
61564
62408
  </button>` : ""}
61565
62409
  </div>
61566
62410
  <div class="modal-body">
@@ -61599,8 +62443,10 @@ var SettingsModalView = class {
61599
62443
  <p>Carregando configura\xE7\xF5es de exclus\xE3o...</p>
61600
62444
  </div>
61601
62445
  </div>
61602
- <!-- Central/gateway identity+telemetry tab (read-only) -->
61603
- ${this.config.isGateway ? `<div id="gateway-tab-content" class="tab-content" style="display: none;">${this.getGatewayInfoHTML()}</div>` : ""}
62446
+ <!-- RFC-0232: Incidentes (interpola\xE7\xE3o) Tab Content \u2014 central + admin MyIO only.
62447
+ Rendered synchronously (data comes pre-fetched via config.interpolatedSlots,
62448
+ same as getGatewayInfoHTML() \u2014 no async loading state needed here). -->
62449
+ ${this.config.isGateway && this.isSuperAdmin() ? `<div id="incidents-tab-content" class="tab-content" style="display: none;">${this.getInterpolationIncidentsHTML()}</div>` : ""}
61604
62450
  </div>
61605
62451
  <div class="modal-footer">
61606
62452
  <button type="button" class="btn-cancel">Fechar</button>
@@ -61684,6 +62530,10 @@ var SettingsModalView = class {
61684
62530
  </div>
61685
62531
  </div>
61686
62532
 
62533
+ <!-- Central/gateway identity+telemetry \u2014 only for CentralSettingsModal (isGateway);
62534
+ rendered directly in Geral instead of its own tab, right after the identity card. -->
62535
+ ${this.config.isGateway ? this.getGatewayInfoHTML() : ""}
62536
+
61687
62537
  <!-- Bottom Row: Connection Info spanning full width -->
61688
62538
  ${this.getConnectionInfoHTML()}
61689
62539
 
@@ -61888,70 +62738,450 @@ var SettingsModalView = class {
61888
62738
  parts.push(`${m}min`);
61889
62739
  return parts.join(" ");
61890
62740
  }
62741
+ /** pt-BR absolute timestamp WITH seconds, ISO-string input (mirrors the
62742
+ * seconds-precision formatting `getConnectionInfoHTML()` uses for its
62743
+ * disconnect-interval row). */
62744
+ formatIsoWithSeconds(iso) {
62745
+ if (!iso) return "\u2014";
62746
+ const ms = Date.parse(iso);
62747
+ if (Number.isNaN(ms)) return "\u2014";
62748
+ return new Date(ms).toLocaleString("pt-BR", {
62749
+ day: "2-digit",
62750
+ month: "2-digit",
62751
+ year: "numeric",
62752
+ hour: "2-digit",
62753
+ minute: "2-digit",
62754
+ second: "2-digit"
62755
+ });
62756
+ }
62757
+ /** "(Xd:YYhs:YYmins atrás)"-style relative time, ISO-string input — mirrors
62758
+ * the device "Conectado desde" pattern in `getConnectionInfoHTML()`. */
62759
+ formatIsoRelativeDetailed(iso) {
62760
+ const ms = iso ? Date.parse(iso) : NaN;
62761
+ if (Number.isNaN(ms)) return "";
62762
+ const diffMinutes = Math.floor((Date.now() - ms) / 6e4);
62763
+ const diffHours = Math.floor(diffMinutes / 60);
62764
+ const diffDays = Math.floor(diffHours / 24);
62765
+ if (diffDays > 0) {
62766
+ return `(${diffDays}d:${String(diffHours % 24).padStart(2, "0")}hs:${String(diffMinutes % 60).padStart(2, "0")}mins atr\xE1s)`;
62767
+ }
62768
+ if (diffHours > 0) return `(${diffHours}hs:${String(diffMinutes % 60).padStart(2, "0")}mins atr\xE1s)`;
62769
+ if (diffMinutes > 0) return `(${diffMinutes}mins atr\xE1s)`;
62770
+ return "(agora)";
62771
+ }
62772
+ /** "(Xd/h/min atrás)"-style relative time, ISO-string input — mirrors the
62773
+ * device "Último check status" pattern in `getConnectionInfoHTML()`. */
62774
+ formatIsoRelativeSimple(iso) {
62775
+ const ms = iso ? Date.parse(iso) : NaN;
62776
+ if (Number.isNaN(ms)) return "";
62777
+ const diffMinutes = Math.floor((Date.now() - ms) / 6e4);
62778
+ const diffHours = Math.floor(diffMinutes / 60);
62779
+ const diffDays = Math.floor(diffHours / 24);
62780
+ if (diffDays > 0) return `(${diffDays}d atr\xE1s)`;
62781
+ if (diffHours > 0) return `(${diffHours}h atr\xE1s)`;
62782
+ if (diffMinutes > 0) return `(${diffMinutes}min atr\xE1s)`;
62783
+ return "(agora)";
62784
+ }
61891
62785
  /**
61892
62786
  * Central/gateway identity + telemetry — read-only, sourced 1:1 from
61893
- * GCDR's `GET /api/v1/centrals/:id`. Every value carries a stable
61894
- * `id="gwinfo-<key>"` so a host (CentralSettingsModal) can patch
61895
- * `.textContent` after an async fetch resolves — this method itself only
61896
- * runs once, at construction time (createModal() is never re-invoked),
61897
- * so it can't pick up data that arrives after the modal is already built.
61898
- */
62787
+ * GCDR's `GET /api/v1/centrals/:id`. Rendered inline in the Geral tab
62788
+ * (right after the identity card) rather than its own tab — a real device
62789
+ * never sets `isGateway`, so this is fully inert for the device settings
62790
+ * modal. Every value carries a stable `id="gwinfo-<key>"` so a host
62791
+ * (CentralSettingsModal) can patch `.textContent` after an async fetch
62792
+ * resolves — this method itself only runs once, at construction time
62793
+ * (createModal() is never re-invoked), so it can't pick up data that
62794
+ * arrives after the modal is already built.
62795
+ */
62796
+ /**
62797
+ * "Status" text/color mapping for `GatewayInfo.connectionStatus`
62798
+ * (ONLINE|OFFLINE|DEGRADED|MAINTENANCE) — same visual vocabulary as
62799
+ * `getConnectionInfoHTML()`'s device `statusMap` (ONLINE/OFFLINE stay
62800
+ * ALL-CAPS, DEGRADED reads "Atenção"). Exposed as a static so
62801
+ * `CentralSettingsModal.patchGatewayInfoDom()` can mirror it exactly when
62802
+ * an async `onFetchSettings` re-renders this card's values.
62803
+ */
62804
+ static GATEWAY_CONN_STATUS_MAP = {
62805
+ ONLINE: { text: "ONLINE", color: "#22c55e" },
62806
+ OFFLINE: { text: "OFFLINE", color: "#ef4444" },
62807
+ DEGRADED: { text: "Aten\xE7\xE3o", color: "#f59e0b" },
62808
+ MAINTENANCE: { text: "Manuten\xE7\xE3o", color: "#3b82f6" }
62809
+ };
61899
62810
  getGatewayInfoHTML() {
61900
62811
  const g = this.config.gatewayInfo || {};
61901
62812
  const bool = (v) => v == null ? "\u2014" : v ? "Sim" : "N\xE3o";
61902
62813
  const num = (v, suffix = "") => v == null ? "\u2014" : `${v}${suffix}`;
61903
62814
  const txt = (v) => v == null || v === "" ? "\u2014" : v;
61904
- const row = (label, key, value) => `<div class="identity-date-row">
61905
- <div class="identity-date-label">${label}</div>
61906
- <div class="identity-date-value" id="gwinfo-${key}">${value}</div>
62815
+ const infoRow = (label, key, valueHtml, fullWidth = false) => `<div class="info-row${fullWidth ? " full-width" : ""}">
62816
+ <span class="info-label">${label}</span>
62817
+ <span class="info-value" id="gwinfo-${key}">${valueHtml}</span>
61907
62818
  </div>`;
62819
+ const connStatusInfo = _SettingsModalView.GATEWAY_CONN_STATUS_MAP[(g.connectionStatus || "").toUpperCase()] || {
62820
+ text: txt(g.connectionStatus),
62821
+ color: "#6b7280"
62822
+ };
62823
+ const isOnline = (g.connectionStatus || "").toUpperCase() === "ONLINE";
62824
+ const connectedSinceAbs = isOnline ? this.formatIso(g.lastGatewaySuccessCheckAt) : "\u2014";
62825
+ const connectedSinceRel = isOnline ? this.formatIsoRelativeDetailed(g.lastGatewaySuccessCheckAt) : "";
62826
+ const lastCheckAbs = this.formatIso(g.lastGatewayCheckAt);
62827
+ const lastCheckRel = this.formatIsoRelativeSimple(g.lastGatewayCheckAt);
62828
+ const lc = g.lastConsumptionTelemetry;
62829
+ const consumptionText = lc ? `${lc.value.toLocaleString("pt-BR")}${lc.unit ? ` ${lc.unit}` : ""} - ${this.formatIsoWithSeconds(
62830
+ lc.timestamp
62831
+ )} ${this.formatIsoRelativeSimple(lc.timestamp)}`.trim() : "\u2014";
62832
+ const connectionInfoCard = `
62833
+ <div class="form-card info-card-wide">
62834
+ <h4 class="section-title">
62835
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="vertical-align: text-bottom; margin-right: 6px;">
62836
+ <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
62837
+ <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
62838
+ </svg>
62839
+ Informa\xE7\xF5es de Conex\xE3o
62840
+ </h4>
62841
+ <div class="info-grid">
62842
+ ${infoRow("Central:", "connCentral", txt(this.config.deviceLabel))}
62843
+ ${infoRow(
62844
+ "Status:",
62845
+ "connectionStatus",
62846
+ `<span style="color: ${connStatusInfo.color}; font-weight: 600;">${connStatusInfo.text}</span>`
62847
+ )}
62848
+ ${infoRow(
62849
+ "Conectado desde:",
62850
+ "lastGatewaySuccessCheckAt",
62851
+ `${connectedSinceAbs}${connectedSinceRel ? ` <span class="time-since">${connectedSinceRel}</span>` : ""}`
62852
+ )}
62853
+ ${infoRow(
62854
+ "\xDAltimo check status:",
62855
+ "lastGatewayCheckAt",
62856
+ `${lastCheckAbs}${lastCheckRel ? ` <span class="time-since">${lastCheckRel}</span>` : ""}`
62857
+ )}
62858
+ ${infoRow("\xDAltimo intervalo desconectado:", "disconnectInterval", "\u2014", true)}
62859
+ ${infoRow("\xDAltima Telemetria de Consumo:", "lastConsumptionTelemetry", consumptionText, true)}
62860
+ ${infoRow("Monitoramento habilitado:", "monitoringEnabled", bool(g.monitoringEnabled))}
62861
+ ${infoRow("Lat\xEAncia:", "lastGatewayCheckLatencyMs", num(g.lastGatewayCheckLatencyMs, "ms"))}
62862
+ ${infoRow("Resultado do probe:", "probeResult", txt(g.probeResult))}
62863
+ </div>
62864
+ </div>
62865
+ `;
61908
62866
  return `
62867
+ ${connectionInfoCard}
61909
62868
  <div class="gateway-info-grid">
61910
- <div class="form-card gateway-info-card">
62869
+ <div class="form-card gateway-info-card info-card-wide">
61911
62870
  <h4 class="section-title">Identifica\xE7\xE3o</h4>
61912
- <div class="gateway-info-rows">
61913
- ${row("UUID", "uuid", txt(this.config.deviceId))}
61914
- ${row("Serial Number", "serialNumber", txt(g.serialNumber))}
61915
- ${row("Hardware ID", "hardwareId", txt(g.hardwareId))}
61916
- ${row("Tipo", "type", txt(g.type))}
61917
- ${row("Status (cadastro)", "status", txt(g.status))}
61918
- ${row("Firmware", "firmwareVersion", txt(g.firmwareVersion))}
61919
- ${row("Software", "softwareVersion", txt(g.softwareVersion))}
61920
- ${row("Frequ\xEAncia (canal)", "frequency", num(g.frequency))}
62871
+ <div class="info-grid">
62872
+ ${infoRow("UUID:", "uuid", txt(this.config.deviceId))}
62873
+ ${infoRow("Serial Number:", "serialNumber", txt(g.serialNumber))}
62874
+ ${infoRow("Hardware ID:", "hardwareId", txt(g.hardwareId))}
62875
+ ${infoRow("Tipo:", "type", txt(g.type))}
62876
+ ${infoRow("Status (cadastro):", "status", txt(g.status))}
62877
+ ${infoRow("Firmware:", "firmwareVersion", txt(g.firmwareVersion))}
62878
+ ${infoRow("Software:", "softwareVersion", txt(g.softwareVersion))}
62879
+ ${infoRow("Frequ\xEAncia (canal):", "frequency", num(g.frequency))}
61921
62880
  </div>
61922
62881
  </div>
61923
- <div class="form-card gateway-info-card">
61924
- <h4 class="section-title">Conectividade (telemetria)</h4>
61925
- <div class="gateway-info-rows">
61926
- ${row("Status de conex\xE3o", "connectionStatus", txt(g.connectionStatus))}
61927
- ${row("Monitoramento habilitado", "monitoringEnabled", bool(g.monitoringEnabled))}
61928
- ${row("\xDAltima tentativa", "lastGatewayCheckAt", this.formatIso(g.lastGatewayCheckAt))}
61929
- ${row("\xDAltimo sucesso", "lastGatewaySuccessCheckAt", this.formatIso(g.lastGatewaySuccessCheckAt))}
61930
- ${row("Lat\xEAncia", "lastGatewayCheckLatencyMs", num(g.lastGatewayCheckLatencyMs, "ms"))}
61931
- ${row("Resultado do probe", "probeResult", txt(g.probeResult))}
61932
- </div>
61933
- </div>
61934
- <div class="form-card gateway-info-card">
62882
+ <div class="form-card gateway-info-card info-card-wide">
61935
62883
  <h4 class="section-title">Estat\xEDsticas</h4>
61936
- <div class="gateway-info-rows">
61937
- ${row("Dispositivos conectados", "statsConnectedDevices", num(g.stats?.connectedDevices))}
61938
- ${row("Regras ativas", "statsActiveRules", num(g.stats?.activeRules))}
61939
- ${row("Eventos de sync pendentes", "statsPendingSyncEvents", num(g.stats?.pendingSyncEvents))}
61940
- ${row("Uptime", "statsUptimeSeconds", this.formatUptime(g.stats?.uptimeSeconds))}
61941
- ${row("\xDAltimo heartbeat", "statsLastHeartbeatAt", this.formatIso(g.stats?.lastHeartbeatAt))}
62884
+ <div class="info-grid">
62885
+ ${infoRow("Dispositivos conectados:", "statsConnectedDevices", num(g.stats?.connectedDevices))}
62886
+ ${infoRow("Regras ativas:", "statsActiveRules", num(g.stats?.activeRules))}
62887
+ ${infoRow("Eventos de sync pendentes:", "statsPendingSyncEvents", num(g.stats?.pendingSyncEvents))}
62888
+ ${infoRow("Uptime:", "statsUptimeSeconds", this.formatUptime(g.stats?.uptimeSeconds))}
62889
+ ${infoRow("\xDAltimo heartbeat:", "statsLastHeartbeatAt", this.formatIso(g.stats?.lastHeartbeatAt))}
61942
62890
  </div>
61943
62891
  </div>
61944
- <div class="form-card gateway-info-card">
62892
+ <div class="form-card gateway-info-card info-card-wide">
61945
62893
  <h4 class="section-title">Metadados</h4>
61946
- <div class="gateway-info-rows">
61947
- ${row("Criado em", "createdAt", this.formatIso(g.createdAt))}
61948
- ${row("Atualizado em", "updatedAt", this.formatIso(g.updatedAt))}
61949
- ${row("Vers\xE3o", "version", num(g.version))}
62894
+ <div class="info-grid">
62895
+ ${infoRow("Criado em:", "createdAt", this.formatIso(g.createdAt))}
62896
+ ${infoRow("Atualizado em:", "updatedAt", this.formatIso(g.updatedAt))}
62897
+ ${infoRow("Vers\xE3o:", "version", num(g.version))}
61950
62898
  </div>
61951
62899
  </div>
61952
62900
  </div>
61953
62901
  `;
61954
62902
  }
62903
+ escHtml(v) {
62904
+ return String(v ?? "").replace(
62905
+ /[&<>"']/g,
62906
+ (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]
62907
+ );
62908
+ }
62909
+ incidentsDeviceKey(s) {
62910
+ return s.deviceName || `slave ${s.slaveId}`;
62911
+ }
62912
+ incidentsFmtHour(iso) {
62913
+ return new Date(iso).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo", hour: "2-digit", minute: "2-digit" });
62914
+ }
62915
+ incidentsFmtDay(iso) {
62916
+ return new Date(iso).toLocaleDateString("pt-BR", {
62917
+ timeZone: "America/Sao_Paulo",
62918
+ day: "2-digit",
62919
+ month: "2-digit",
62920
+ year: "numeric"
62921
+ });
62922
+ }
62923
+ incidentsFmtFull(iso) {
62924
+ return iso == null ? "\u2014" : new Date(iso).toLocaleString("pt-BR", {
62925
+ timeZone: "America/Sao_Paulo",
62926
+ day: "2-digit",
62927
+ month: "2-digit",
62928
+ hour: "2-digit",
62929
+ minute: "2-digit"
62930
+ });
62931
+ }
62932
+ /**
62933
+ * RFC-0232 — "Incidentes" tab (central + admin MyIO only): what the
62934
+ * No-Consumption Interpolation agent (`data-ingestion-prod`) fabricated for
62935
+ * THIS central. Inspired by that app's own ledger
62936
+ * (`InterpolationPanel.tsx`'s "Logs" tab, ~L450-548) but simplified for a
62937
+ * single-central context: that page groups by GATEWAY first (it shows every
62938
+ * central at once) — here there's only one central, so grouping starts one
62939
+ * level down, by DEVICE, then by DAY, each day showing its hour slots.
62940
+ * `config.interpolatedSlots` arrives pre-fetched from the host — nothing is
62941
+ * fetched in here, ever; the header controls (busca/dispositivos/período)
62942
+ * only filter that one array client-side. This method runs once, at
62943
+ * construction time, and builds the STATIC shell (filters header + KPI/body
62944
+ * mount points); `refreshIncidentsView()` re-renders the KPI/body pair on
62945
+ * every filter change via `this.modal.querySelector`.
62946
+ */
62947
+ getInterpolationIncidentsHTML() {
62948
+ this.incidentsAllSlots = this.config.interpolatedSlots ?? [];
62949
+ const deviceKeys = Array.from(new Set(this.incidentsAllSlots.map((s) => this.incidentsDeviceKey(s)))).sort(
62950
+ (a, b) => a.localeCompare(b, "pt-BR")
62951
+ );
62952
+ this.incidentsSelectedDevices = new Set(deviceKeys);
62953
+ if (this.incidentsAllSlots.length === 0) {
62954
+ return `<div class="incidents-empty">Nenhuma interpola\xE7\xE3o registrada para esta central no per\xEDodo consultado pelo host.</div>`;
62955
+ }
62956
+ const devicesPanel = deviceKeys.map(
62957
+ (key) => `
62958
+ <label class="incidents-multiselect__item">
62959
+ <input type="checkbox" checked value="${this.escHtml(key)}" data-role="incidents-device-checkbox">
62960
+ <span>${this.escHtml(key)}</span>
62961
+ </label>`
62962
+ ).join("");
62963
+ return `
62964
+ <div class="incidents-filters">
62965
+ <div class="incidents-filter incidents-filter--search">
62966
+ <label class="incidents-filter__label">Buscar dispositivo</label>
62967
+ <input type="text" class="incidents-search-input" placeholder="Nome do dispositivo\u2026" data-role="incidents-search-input">
62968
+ </div>
62969
+ <div class="incidents-filter incidents-filter--devices">
62970
+ <label class="incidents-filter__label">Dispositivos</label>
62971
+ <div class="incidents-multiselect">
62972
+ <button type="button" class="incidents-multiselect__toggle" data-role="incidents-devices-toggle">
62973
+ <span data-role="incidents-devices-summary">Todos (${deviceKeys.length})</span>
62974
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"></polyline></svg>
62975
+ </button>
62976
+ <div class="incidents-multiselect__panel" data-role="incidents-devices-panel" hidden>
62977
+ <div class="incidents-multiselect__actions">
62978
+ <button type="button" data-role="incidents-devices-all">Marcar todos</button>
62979
+ <button type="button" data-role="incidents-devices-none">Desmarcar todos</button>
62980
+ </div>
62981
+ ${devicesPanel}
62982
+ </div>
62983
+ </div>
62984
+ </div>
62985
+ <div class="incidents-filter incidents-filter--period">
62986
+ <label class="incidents-filter__label">Per\xEDodo</label>
62987
+ <div id="incidents-daterange-${this.incidentsIdSuffix}" class="incidents-daterange-mount"></div>
62988
+ </div>
62989
+ </div>
62990
+ <div class="incidents-kpis">${this.buildIncidentsKpisHtml(this.incidentsAllSlots)}</div>
62991
+ <div class="incidents-body">${this.buildIncidentsBodyHtml(this.incidentsAllSlots)}</div>
62992
+ `;
62993
+ }
62994
+ /** 4 small KPI cards summarizing the currently-filtered slot set. */
62995
+ buildIncidentsKpisHtml(slots) {
62996
+ if (slots.length === 0) {
62997
+ return `<div class="incidents-kpi-empty">Nenhuma interpola\xE7\xE3o corresponde aos filtros atuais.</div>`;
62998
+ }
62999
+ const deviceCount = new Set(slots.map((s) => this.incidentsDeviceKey(s))).size;
63000
+ const dayCount = new Set(slots.map((s) => this.incidentsFmtDay(s.hourStart))).size;
63001
+ const withIncident = slots.filter((s) => s.incidentRef).length;
63002
+ const pct = slots.length ? Math.round(withIncident / slots.length * 100) : 0;
63003
+ const kpi = (label, value, sub) => `
63004
+ <div class="incidents-kpi">
63005
+ <div class="incidents-kpi__value">${value}</div>
63006
+ <div class="incidents-kpi__label">${label}</div>
63007
+ ${sub ? `<div class="incidents-kpi__sub">${sub}</div>` : ""}
63008
+ </div>`;
63009
+ return [
63010
+ kpi("Interpola\xE7\xF5es", String(slots.length)),
63011
+ kpi("Dispositivos", String(deviceCount)),
63012
+ kpi("Dias com atividade", String(dayCount)),
63013
+ kpi("Com incidente vinculado", String(withIncident), `${pct}% do total`)
63014
+ ].join("");
63015
+ }
63016
+ /** Device → day → hour-slot table, for whatever slot subset the caller already filtered. */
63017
+ buildIncidentsBodyHtml(slots) {
63018
+ if (slots.length === 0) {
63019
+ return `<div class="incidents-empty">Nenhuma interpola\xE7\xE3o corresponde aos filtros atuais.</div>`;
63020
+ }
63021
+ const byDevice = /* @__PURE__ */ new Map();
63022
+ for (const s of slots) {
63023
+ const key = this.incidentsDeviceKey(s);
63024
+ (byDevice.get(key) ?? byDevice.set(key, []).get(key)).push(s);
63025
+ }
63026
+ const deviceNames = Array.from(byDevice.keys()).sort((a, b) => a.localeCompare(b, "pt-BR"));
63027
+ const deviceSections = deviceNames.map((deviceName) => {
63028
+ const deviceSlots = byDevice.get(deviceName).slice().sort((a, b) => a.hourStart.localeCompare(b.hourStart));
63029
+ const byDay = /* @__PURE__ */ new Map();
63030
+ for (const s of deviceSlots) {
63031
+ const day = this.incidentsFmtDay(s.hourStart);
63032
+ (byDay.get(day) ?? byDay.set(day, []).get(day)).push(s);
63033
+ }
63034
+ const daySections = Array.from(byDay.entries()).map(([day, daySlots]) => {
63035
+ const rows = daySlots.map(
63036
+ (s) => `
63037
+ <tr>
63038
+ <td class="mono">${this.incidentsFmtHour(s.hourStart)}</td>
63039
+ <td class="num mono">${s.value.toFixed(1)}</td>
63040
+ <td class="mono muted">${this.incidentsFmtHour(s.sourceHour)}</td>
63041
+ <td class="mono muted" title="${this.escHtml(s.ruleId)}">${this.escHtml(s.ruleId.slice(0, 8))}</td>
63042
+ <td>${s.incidentRef ? `<code>${this.escHtml(s.incidentRef)}</code>` : '<span class="muted">\u2014</span>'}</td>
63043
+ <td class="muted">${this.incidentsFmtFull(s.createdAt)}</td>
63044
+ </tr>`
63045
+ ).join("");
63046
+ return `
63047
+ <div class="incidents-day">
63048
+ <div class="incidents-day__header">
63049
+ <span>${this.escHtml(day)}</span>
63050
+ <span class="incidents-day__count">${daySlots.length} hora${daySlots.length === 1 ? "" : "s"}</span>
63051
+ </div>
63052
+ <table class="incidents-table">
63053
+ <thead>
63054
+ <tr><th>Hora</th><th>Valor</th><th>Fonte LOCF</th><th>Regra</th><th>Incidente</th><th>Gravado</th></tr>
63055
+ </thead>
63056
+ <tbody>${rows}</tbody>
63057
+ </table>
63058
+ </div>`;
63059
+ }).join("");
63060
+ return `
63061
+ <div class="incidents-device">
63062
+ <button type="button" class="incidents-device__header" data-role="toggle-incidents-device">
63063
+ <svg class="incidents-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
63064
+ <polyline points="9 18 15 12 9 6"></polyline>
63065
+ </svg>
63066
+ <span class="incidents-device__name">${this.escHtml(deviceName)}</span>
63067
+ <span class="incidents-device__count">${deviceSlots.length} hora${deviceSlots.length === 1 ? "" : "s"} \xB7 ${byDay.size} dia${byDay.size === 1 ? "" : "s"}</span>
63068
+ </button>
63069
+ <div class="incidents-device__body">${daySections}</div>
63070
+ </div>`;
63071
+ }).join("");
63072
+ return `
63073
+ <div class="incidents-summary">${slots.length} interpola\xE7\xE3o(\xF5es) \xB7 ${deviceNames.length} dispositivo(s)</div>
63074
+ <div class="incidents-wrap">${deviceSections}</div>
63075
+ `;
63076
+ }
63077
+ /** Applies search + device + period filters to `incidentsAllSlots`. */
63078
+ getFilteredIncidentsSlots() {
63079
+ const search = this.incidentsSearch.trim().toLowerCase();
63080
+ const { startISO, endISO } = this.incidentsDateRange;
63081
+ return this.incidentsAllSlots.filter((s) => {
63082
+ const key = this.incidentsDeviceKey(s);
63083
+ if (!this.incidentsSelectedDevices.has(key)) return false;
63084
+ if (search && !key.toLowerCase().includes(search)) return false;
63085
+ if (startISO && s.hourStart < startISO) return false;
63086
+ if (endISO && s.hourStart > endISO) return false;
63087
+ return true;
63088
+ });
63089
+ }
63090
+ /** Re-renders the KPI cards + device/day body after any filter change (search/devices/period). */
63091
+ refreshIncidentsView() {
63092
+ const filtered = this.getFilteredIncidentsSlots();
63093
+ const kpisEl = this.modal.querySelector(".incidents-kpis");
63094
+ const bodyEl = this.modal.querySelector(".incidents-body");
63095
+ if (kpisEl) kpisEl.innerHTML = this.buildIncidentsKpisHtml(filtered);
63096
+ if (bodyEl) bodyEl.innerHTML = this.buildIncidentsBodyHtml(filtered);
63097
+ const summaryEl = this.modal.querySelector('[data-role="incidents-devices-summary"]');
63098
+ if (summaryEl) {
63099
+ const total = this.incidentsAllSlots.length ? new Set(this.incidentsAllSlots.map((s) => this.incidentsDeviceKey(s))).size : 0;
63100
+ const selected = this.incidentsSelectedDevices.size;
63101
+ summaryEl.textContent = selected === total ? `Todos (${total})` : `${selected} de ${total}`;
63102
+ }
63103
+ }
63104
+ /**
63105
+ * Wires the Incidentes tab's static controls (search input, devices
63106
+ * multiselect dropdown, device-group collapse). Called once from `render()`
63107
+ * — all delegated on `this.modal`, so it survives the KPI/body innerHTML
63108
+ * swaps `refreshIncidentsView()` does (those never touch the header). The
63109
+ * date-range picker is wired separately in `initIncidentsDateRange()`
63110
+ * (async — see that method).
63111
+ */
63112
+ bindIncidentsTabEvents() {
63113
+ this.modal.addEventListener("input", (event) => {
63114
+ const target = event.target;
63115
+ if (target.dataset.role !== "incidents-search-input") return;
63116
+ this.incidentsSearch = target.value;
63117
+ this.refreshIncidentsView();
63118
+ });
63119
+ this.modal.addEventListener("click", (event) => {
63120
+ const target = event.target;
63121
+ const deviceHeader = target.closest('[data-role="toggle-incidents-device"]');
63122
+ if (deviceHeader) {
63123
+ deviceHeader.closest(".incidents-device")?.classList.toggle("is-collapsed");
63124
+ return;
63125
+ }
63126
+ const panel = this.modal.querySelector('[data-role="incidents-devices-panel"]');
63127
+ const toggle = target.closest('[data-role="incidents-devices-toggle"]');
63128
+ if (toggle && panel) {
63129
+ panel.hidden = !panel.hidden;
63130
+ return;
63131
+ }
63132
+ if (target.dataset.role === "incidents-devices-all" || target.dataset.role === "incidents-devices-none") {
63133
+ const checkAll = target.dataset.role === "incidents-devices-all";
63134
+ this.modal.querySelectorAll('[data-role="incidents-device-checkbox"]').forEach((cb) => {
63135
+ cb.checked = checkAll;
63136
+ });
63137
+ this.incidentsSelectedDevices = checkAll ? new Set(this.incidentsAllSlots.map((s) => this.incidentsDeviceKey(s))) : /* @__PURE__ */ new Set();
63138
+ this.refreshIncidentsView();
63139
+ return;
63140
+ }
63141
+ if (panel && !panel.hidden && !target.closest('[data-role="incidents-devices-toggle"]') && !panel.contains(target)) {
63142
+ panel.hidden = true;
63143
+ }
63144
+ });
63145
+ this.modal.addEventListener("change", (event) => {
63146
+ const target = event.target;
63147
+ if (target.dataset.role !== "incidents-device-checkbox") return;
63148
+ const cb = target;
63149
+ if (cb.checked) this.incidentsSelectedDevices.add(cb.value);
63150
+ else this.incidentsSelectedDevices.delete(cb.value);
63151
+ this.refreshIncidentsView();
63152
+ });
63153
+ }
63154
+ /**
63155
+ * Mounts the period date-range picker into the Incidentes tab's header —
63156
+ * async (loads jQuery/moment/daterangepicker from CDN on first use, see
63157
+ * `createInputDateRangePickerInsideDIV`), so this runs from `render()`
63158
+ * AFTER the modal is attached to the DOM, same timing as
63159
+ * `initAnnotationsTab()`/`initAlarmsTab()`. No-ops when the tab isn't
63160
+ * rendered at all (non-admin / non-gateway).
63161
+ */
63162
+ async initIncidentsDateRange() {
63163
+ if (!(this.config.isGateway && this.isSuperAdmin())) return;
63164
+ const mountId = `incidents-daterange-${this.incidentsIdSuffix}`;
63165
+ if (!document.getElementById(mountId)) return;
63166
+ try {
63167
+ this.incidentsDateRangeController = await createInputDateRangePickerInsideDIV({
63168
+ containerId: mountId,
63169
+ inputId: `${mountId}-input`,
63170
+ label: "",
63171
+ placeholder: "Todo o per\xEDodo",
63172
+ showHelper: false,
63173
+ pickerOptions: {
63174
+ includeTime: true,
63175
+ onApply: (result) => {
63176
+ this.incidentsDateRange = { startISO: result.startISO, endISO: result.endISO };
63177
+ this.refreshIncidentsView();
63178
+ }
63179
+ }
63180
+ });
63181
+ } catch (err) {
63182
+ console.warn("[SettingsModalView] Incidentes: failed to mount date-range picker:", err);
63183
+ }
63184
+ }
61955
63185
  getDeviceImage(deviceType) {
61956
63186
  let normalized = (deviceType || "").toUpperCase();
61957
63187
  if (normalized === "3F_MEDIDOR") {
@@ -61960,8 +63190,7 @@ var SettingsModalView = class {
61960
63190
  normalized = profile.toUpperCase();
61961
63191
  }
61962
63192
  }
61963
- const DEFAULT = "https://cdn-icons-png.flaticon.com/512/1178/1178428.png";
61964
- const url = deviceIcons[normalized] || DEFAULT;
63193
+ const url = deviceIcons[normalized] || DEFAULT_DEVICE_ICON;
61965
63194
  return `<img src="${url}" class="identity-device-image" alt="${normalized}" />`;
61966
63195
  }
61967
63196
  getConsumptionLimits() {
@@ -62364,7 +63593,19 @@ var SettingsModalView = class {
62364
63593
  z-index: 9999999;
62365
63594
  font-family: 'Nunito', system-ui, sans-serif;
62366
63595
  }
62367
-
63596
+
63597
+ /* RFC-0232: the "Incidentes" period picker (createInputDateRangePickerInsideDIV
63598
+ -> jQuery daterangepicker) always appends its calendar dropdown to
63599
+ document.body, as a SIBLING of this overlay, not a descendant \u2014 so it
63600
+ can't be reached with a normal descendant selector. The plugin's own
63601
+ default z-index (~10000) is far below this overlay's 9999999 (chosen
63602
+ to beat ThingsBoard's Angular Material overlays in production), so
63603
+ without this override the calendar renders but is fully hidden behind
63604
+ the modal, making the input look like it "doesn't open". */
63605
+ .daterangepicker {
63606
+ z-index: 10000000 !important;
63607
+ }
63608
+
62368
63609
  .myio-device-settings-modal {
62369
63610
  background: white;
62370
63611
  border-radius: 8px;
@@ -62466,11 +63707,277 @@ var SettingsModalView = class {
62466
63707
  .modal-tab-badge--alarms { background: #dc2626; }
62467
63708
  .modal-tab-badge--chamados { background: #0891b2; }
62468
63709
  .modal-tab-badge--exclusion { background: #d97706; }
63710
+ .modal-tab-badge--incidents { background: #7C3AED; }
62469
63711
 
62470
63712
  .tab-content {
62471
63713
  min-height: 400px;
62472
63714
  }
62473
63715
 
63716
+ /* RFC-0232: Incidentes (interpola\xE7\xE3o) tab \u2014 device \u2192 day \u2192 hour slots. */
63717
+ .incidents-filters {
63718
+ display: flex;
63719
+ flex-wrap: wrap;
63720
+ align-items: flex-end;
63721
+ gap: 16px;
63722
+ margin-bottom: 16px;
63723
+ padding-bottom: 16px;
63724
+ border-bottom: 1px solid #e5e7eb;
63725
+ }
63726
+ .incidents-filter {
63727
+ display: flex;
63728
+ flex-direction: column;
63729
+ gap: 4px;
63730
+ }
63731
+ .incidents-filter--search { flex: 1 1 123px; min-width: 100px; }
63732
+ .incidents-filter--devices { flex: 0 0 auto; }
63733
+ .incidents-filter--period { flex: 0 0 auto; }
63734
+ .incidents-filter__label {
63735
+ font: 700 11px 'Nunito', system-ui, sans-serif;
63736
+ color: #6b7280;
63737
+ text-transform: uppercase;
63738
+ letter-spacing: 0.03em;
63739
+ }
63740
+ .incidents-search-input {
63741
+ width: 100%;
63742
+ padding: 7px 10px;
63743
+ border: 1px solid #d1d5db;
63744
+ border-radius: 6px;
63745
+ font: 600 13px 'Nunito', system-ui, sans-serif;
63746
+ color: #1f2937;
63747
+ box-sizing: border-box;
63748
+ }
63749
+ .incidents-search-input:focus { outline: 2px solid #7C3AED; outline-offset: 1px; }
63750
+ .incidents-multiselect { position: relative; }
63751
+ .incidents-multiselect__toggle {
63752
+ display: flex;
63753
+ align-items: center;
63754
+ gap: 8px;
63755
+ padding: 7px 12px;
63756
+ min-width: 200px;
63757
+ border: 1px solid #d1d5db;
63758
+ border-radius: 6px;
63759
+ background: #fff;
63760
+ font: 600 13px 'Nunito', system-ui, sans-serif;
63761
+ color: #1f2937;
63762
+ cursor: pointer;
63763
+ white-space: nowrap;
63764
+ }
63765
+ .incidents-multiselect__toggle:hover { border-color: #7C3AED; }
63766
+ .incidents-multiselect__panel {
63767
+ position: absolute;
63768
+ top: calc(100% + 4px);
63769
+ left: 0;
63770
+ z-index: 20;
63771
+ min-width: 220px;
63772
+ max-height: 260px;
63773
+ overflow-y: auto;
63774
+ background: #fff;
63775
+ border: 1px solid #d1d5db;
63776
+ border-radius: 8px;
63777
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
63778
+ padding: 8px;
63779
+ }
63780
+ .incidents-multiselect__actions {
63781
+ display: flex;
63782
+ gap: 8px;
63783
+ padding-bottom: 6px;
63784
+ margin-bottom: 6px;
63785
+ border-bottom: 1px solid #f3f4f6;
63786
+ }
63787
+ .incidents-multiselect__actions button {
63788
+ flex: 1;
63789
+ padding: 4px 6px;
63790
+ border: none;
63791
+ border-radius: 4px;
63792
+ background: #f5f3ff;
63793
+ color: #5b21b6;
63794
+ font: 700 11px 'Nunito', system-ui, sans-serif;
63795
+ cursor: pointer;
63796
+ }
63797
+ .incidents-multiselect__actions button:hover { background: #ede9fe; }
63798
+ .incidents-multiselect__item {
63799
+ display: flex;
63800
+ align-items: center;
63801
+ gap: 8px;
63802
+ padding: 5px 4px;
63803
+ font: 600 13px 'Nunito', system-ui, sans-serif;
63804
+ color: #374151;
63805
+ cursor: pointer;
63806
+ border-radius: 4px;
63807
+ }
63808
+ .incidents-multiselect__item:hover { background: #f9fafb; }
63809
+ .incidents-daterange-mount .myio-daterange-wrapper {
63810
+ margin-bottom: 0;
63811
+ padding: 0;
63812
+ background: transparent;
63813
+ box-shadow: none;
63814
+ }
63815
+ .incidents-daterange-mount {
63816
+ min-width: 280px;
63817
+ }
63818
+ .incidents-daterange-mount .myio-daterange-input {
63819
+ padding: 7px 10px;
63820
+ font-size: 13px;
63821
+ width: 100%;
63822
+ min-width: 280px;
63823
+ max-width: 340px;
63824
+ }
63825
+ .incidents-kpis {
63826
+ display: grid;
63827
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
63828
+ gap: 12px;
63829
+ margin-bottom: 16px;
63830
+ }
63831
+ .incidents-kpi {
63832
+ padding: 12px 14px;
63833
+ border-radius: 8px;
63834
+ background: #f5f3ff;
63835
+ border: 1px solid #e9d5ff;
63836
+ }
63837
+ .incidents-kpi__value {
63838
+ font: 800 22px 'Nunito', system-ui, sans-serif;
63839
+ color: #5b21b6;
63840
+ }
63841
+ .incidents-kpi__label {
63842
+ margin-top: 2px;
63843
+ font: 700 11px 'Nunito', system-ui, sans-serif;
63844
+ color: #6b7280;
63845
+ text-transform: uppercase;
63846
+ letter-spacing: 0.02em;
63847
+ }
63848
+ .incidents-kpi__sub {
63849
+ margin-top: 2px;
63850
+ font-size: 11px;
63851
+ color: #9ca3af;
63852
+ }
63853
+ .incidents-kpi-empty {
63854
+ margin-bottom: 16px;
63855
+ padding: 10px 14px;
63856
+ border-radius: 8px;
63857
+ background: #f9fafb;
63858
+ color: #9ca3af;
63859
+ font-size: 12px;
63860
+ }
63861
+ .theme-dark .incidents-filters { border-color: #374151; }
63862
+ .theme-dark .incidents-filter__label { color: #9ca3af; }
63863
+ .theme-dark .incidents-search-input { background: #111827; border-color: #4b5563; color: #e5e7eb; }
63864
+ .theme-dark .incidents-multiselect__toggle { background: #111827; border-color: #4b5563; color: #e5e7eb; }
63865
+ .theme-dark .incidents-multiselect__panel { background: #1f2937; border-color: #374151; }
63866
+ .theme-dark .incidents-multiselect__item { color: #e5e7eb; }
63867
+ .theme-dark .incidents-multiselect__item:hover { background: #111827; }
63868
+ .theme-dark .incidents-kpi { background: #2e1065; border-color: #4c1d95; }
63869
+ .theme-dark .incidents-kpi__value { color: #d8b4fe; }
63870
+ .theme-dark .incidents-kpi-empty { background: #111827; color: #6b7280; }
63871
+
63872
+ .incidents-summary {
63873
+ margin-bottom: 14px;
63874
+ font-size: 13px;
63875
+ font-weight: 600;
63876
+ color: #6b7280;
63877
+ }
63878
+ .incidents-empty {
63879
+ padding: 40px 20px;
63880
+ text-align: center;
63881
+ color: #6b7280;
63882
+ font-size: 14px;
63883
+ border: 1px dashed #d1d5db;
63884
+ border-radius: 8px;
63885
+ }
63886
+ .incidents-wrap {
63887
+ display: flex;
63888
+ flex-direction: column;
63889
+ gap: 12px;
63890
+ }
63891
+ .incidents-device {
63892
+ border: 1px solid #e5e7eb;
63893
+ border-radius: 8px;
63894
+ overflow: hidden;
63895
+ }
63896
+ .incidents-device__header {
63897
+ display: flex;
63898
+ align-items: center;
63899
+ gap: 8px;
63900
+ width: 100%;
63901
+ padding: 10px 14px;
63902
+ background: #f5f3ff;
63903
+ border: none;
63904
+ cursor: pointer;
63905
+ font: 700 13px 'Nunito', system-ui, sans-serif;
63906
+ color: #5b21b6;
63907
+ text-align: left;
63908
+ }
63909
+ .incidents-device__header:hover { background: #ede9fe; }
63910
+ /* Chevron SVG points right (\u25B6); rotated 90\xB0 to point down (\u25BC) while
63911
+ expanded \u2014 the default state, since a group starts open. */
63912
+ .incidents-chevron { flex-shrink: 0; transition: transform 0.15s ease; transform: rotate(90deg); }
63913
+ .incidents-device.is-collapsed .incidents-chevron { transform: rotate(0deg); }
63914
+ .incidents-device.is-collapsed .incidents-device__body { display: none; }
63915
+ .incidents-device__name { flex: 1; }
63916
+ .incidents-device__count {
63917
+ font-size: 11px;
63918
+ font-weight: 600;
63919
+ color: #7c3aed;
63920
+ background: #fff;
63921
+ border-radius: 999px;
63922
+ padding: 2px 8px;
63923
+ }
63924
+ .incidents-device__body {
63925
+ display: flex;
63926
+ flex-direction: column;
63927
+ gap: 10px;
63928
+ padding: 10px 14px;
63929
+ }
63930
+ .incidents-day__header {
63931
+ display: flex;
63932
+ align-items: center;
63933
+ justify-content: space-between;
63934
+ padding: 4px 2px;
63935
+ font: 700 12px 'Nunito', system-ui, sans-serif;
63936
+ color: #374151;
63937
+ }
63938
+ .incidents-day__count {
63939
+ font-size: 11px;
63940
+ font-weight: 600;
63941
+ color: #6b7280;
63942
+ }
63943
+ .incidents-table {
63944
+ width: 100%;
63945
+ border-collapse: collapse;
63946
+ font-size: 12px;
63947
+ }
63948
+ .incidents-table th {
63949
+ text-align: left;
63950
+ padding: 6px 8px;
63951
+ background: #f9fafb;
63952
+ color: #6b7280;
63953
+ font-weight: 600;
63954
+ border-bottom: 1px solid #e5e7eb;
63955
+ }
63956
+ .incidents-table td {
63957
+ padding: 6px 8px;
63958
+ border-bottom: 1px solid #f3f4f6;
63959
+ color: #1f2937;
63960
+ }
63961
+ .incidents-table td.num { text-align: right; }
63962
+ .incidents-table td.mono { font-family: 'Courier New', Courier, monospace; }
63963
+ .incidents-table td.muted { color: #9ca3af; }
63964
+ .incidents-table code {
63965
+ color: #7c3aed;
63966
+ background: #f5f3ff;
63967
+ border-radius: 4px;
63968
+ padding: 1px 5px;
63969
+ font-size: 11px;
63970
+ }
63971
+ .theme-dark .incidents-empty { border-color: #4b5563; color: #9ca3af; }
63972
+ .theme-dark .incidents-device { border-color: #374151; }
63973
+ .theme-dark .incidents-device__header { background: #2e1065; color: #d8b4fe; }
63974
+ .theme-dark .incidents-device__header:hover { background: #3b0764; }
63975
+ .theme-dark .incidents-device__count { background: #111827; color: #c4b5fd; }
63976
+ .theme-dark .incidents-day__header { color: #e5e7eb; }
63977
+ .theme-dark .incidents-table th { background: #111827; color: #9ca3af; border-color: #374151; }
63978
+ .theme-dark .incidents-table td { color: #e5e7eb; border-color: #1f2937; }
63979
+ .theme-dark .incidents-table code { background: #2e1065; }
63980
+
62474
63981
  /* Loading spinner for annotations tab */
62475
63982
  .loading-spinner {
62476
63983
  width: 32px;
@@ -62613,20 +64120,22 @@ var SettingsModalView = class {
62613
64120
  height: fit-content;
62614
64121
  }
62615
64122
 
62616
- /* "Central" tab (isGateway) \u2014 read-only identity/telemetry, 4 form-cards
62617
- in a responsive grid, each a stack of identity-date-row pairs. */
64123
+ /* Central/gateway identity+telemetry (isGateway), inline in Geral \u2014
64124
+ one section per line, same visual pattern as "Informa\xE7\xF5es de
64125
+ Conex\xE3o" (.info-card-wide + .info-grid/.info-row) for every card:
64126
+ Identifica\xE7\xE3o/Estat\xEDsticas/Metadados here, plus the Conectividade
64127
+ fieldset CentralSettingsModal injects after them.
64128
+ .gateway-info-grid is just a vertical stack \u2014 .gateway-info-card
64129
+ carries no sizing of its own any more (that lived here back when
64130
+ these were narrower, side-by-side flex-wrap cards); the full-width
64131
+ look now comes entirely from .info-card-wide. */
62618
64132
  .gateway-info-grid {
62619
- display: grid;
62620
- grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
62621
- gap: 16px;
62622
- }
62623
- .gateway-info-card .section-title {
62624
- margin-bottom: 12px;
62625
- }
62626
- .gateway-info-rows {
62627
64133
  display: flex;
62628
64134
  flex-direction: column;
62629
- gap: 10px;
64135
+ gap: 16px;
64136
+ }
64137
+ .gateway-info-card {
64138
+ margin: 0;
62630
64139
  }
62631
64140
 
62632
64141
  /* RFC-0180: Identity card \u2014 2-column \xD7 6-row grid */
@@ -64266,6 +65775,7 @@ function mapToGatewayInfo(data) {
64266
65775
  softwareVersion: data.softwareVersion ?? null,
64267
65776
  frequency: data.frequency ?? null,
64268
65777
  stats: data.stats ?? null,
65778
+ lastConsumptionTelemetry: data.lastConsumptionTelemetry ?? null,
64269
65779
  createdAt: data.createdAt ?? null,
64270
65780
  updatedAt: data.updatedAt ?? null,
64271
65781
  version: data.version ?? null
@@ -64283,6 +65793,43 @@ function formatIsoPtBr(iso) {
64283
65793
  minute: "2-digit"
64284
65794
  });
64285
65795
  }
65796
+ function formatIsoPtBrWithSeconds(iso) {
65797
+ if (!iso) return "\u2014";
65798
+ const ms = Date.parse(iso);
65799
+ if (Number.isNaN(ms)) return "\u2014";
65800
+ return new Date(ms).toLocaleString("pt-BR", {
65801
+ day: "2-digit",
65802
+ month: "2-digit",
65803
+ year: "numeric",
65804
+ hour: "2-digit",
65805
+ minute: "2-digit",
65806
+ second: "2-digit"
65807
+ });
65808
+ }
65809
+ function formatIsoRelativeDetailed(iso) {
65810
+ const ms = iso ? Date.parse(iso) : NaN;
65811
+ if (Number.isNaN(ms)) return "";
65812
+ const diffMinutes = Math.floor((Date.now() - ms) / 6e4);
65813
+ const diffHours = Math.floor(diffMinutes / 60);
65814
+ const diffDays = Math.floor(diffHours / 24);
65815
+ if (diffDays > 0) {
65816
+ return `(${diffDays}d:${String(diffHours % 24).padStart(2, "0")}hs:${String(diffMinutes % 60).padStart(2, "0")}mins atr\xE1s)`;
65817
+ }
65818
+ if (diffHours > 0) return `(${diffHours}hs:${String(diffMinutes % 60).padStart(2, "0")}mins atr\xE1s)`;
65819
+ if (diffMinutes > 0) return `(${diffMinutes}mins atr\xE1s)`;
65820
+ return "(agora)";
65821
+ }
65822
+ function formatIsoRelativeSimple(iso) {
65823
+ const ms = iso ? Date.parse(iso) : NaN;
65824
+ if (Number.isNaN(ms)) return "";
65825
+ const diffMinutes = Math.floor((Date.now() - ms) / 6e4);
65826
+ const diffHours = Math.floor(diffMinutes / 60);
65827
+ const diffDays = Math.floor(diffHours / 24);
65828
+ if (diffDays > 0) return `(${diffDays}d atr\xE1s)`;
65829
+ if (diffHours > 0) return `(${diffHours}h atr\xE1s)`;
65830
+ if (diffMinutes > 0) return `(${diffMinutes}min atr\xE1s)`;
65831
+ return "(agora)";
65832
+ }
64286
65833
  function formatUptime(seconds) {
64287
65834
  if (seconds == null || Number.isNaN(seconds)) return "\u2014";
64288
65835
  const d = Math.floor(seconds / 86400);
@@ -64299,6 +65846,10 @@ function patchGatewayInfoDom(root, uuid, data) {
64299
65846
  const el2 = root.querySelector(`#gwinfo-${key}`);
64300
65847
  if (el2) el2.textContent = value;
64301
65848
  };
65849
+ const setHtml = (key, html) => {
65850
+ const el2 = root.querySelector(`#gwinfo-${key}`);
65851
+ if (el2) el2.innerHTML = html;
65852
+ };
64302
65853
  set("uuid", uuid || "\u2014");
64303
65854
  set("serialNumber", data.serialNumber || "\u2014");
64304
65855
  set("hardwareId", data.hardwareId || "\u2014");
@@ -64307,10 +65858,33 @@ function patchGatewayInfoDom(root, uuid, data) {
64307
65858
  set("firmwareVersion", data.firmwareVersion || "\u2014");
64308
65859
  set("softwareVersion", data.softwareVersion || "\u2014");
64309
65860
  set("frequency", data.frequency != null ? String(data.frequency) : "\u2014");
64310
- set("connectionStatus", data.connectionStatus || "\u2014");
65861
+ set("connCentral", data.name || "\u2014");
65862
+ const connStatusInfo = SettingsModalView.GATEWAY_CONN_STATUS_MAP[(data.connectionStatus || "").toUpperCase()] || {
65863
+ text: data.connectionStatus || "\u2014",
65864
+ color: "#6b7280"
65865
+ };
65866
+ setHtml("connectionStatus", `<span style="color: ${connStatusInfo.color}; font-weight: 600;">${connStatusInfo.text}</span>`);
65867
+ const isOnline = (data.connectionStatus || "").toUpperCase() === "ONLINE";
65868
+ const connectedSinceAbs = isOnline ? formatIsoPtBr(data.lastGatewaySuccessCheckAt) : "\u2014";
65869
+ const connectedSinceRel = isOnline ? formatIsoRelativeDetailed(data.lastGatewaySuccessCheckAt) : "";
65870
+ setHtml(
65871
+ "lastGatewaySuccessCheckAt",
65872
+ `${connectedSinceAbs}${connectedSinceRel ? ` <span class="time-since">${connectedSinceRel}</span>` : ""}`
65873
+ );
65874
+ const lastCheckAbs = formatIsoPtBr(data.lastGatewayCheckAt);
65875
+ const lastCheckRel = formatIsoRelativeSimple(data.lastGatewayCheckAt);
65876
+ setHtml(
65877
+ "lastGatewayCheckAt",
65878
+ `${lastCheckAbs}${lastCheckRel ? ` <span class="time-since">${lastCheckRel}</span>` : ""}`
65879
+ );
65880
+ const lc = data.lastConsumptionTelemetry;
65881
+ setHtml(
65882
+ "lastConsumptionTelemetry",
65883
+ lc ? `${lc.value.toLocaleString("pt-BR")}${lc.unit ? ` ${lc.unit}` : ""} - ${formatIsoPtBrWithSeconds(
65884
+ lc.timestamp
65885
+ )} ${formatIsoRelativeSimple(lc.timestamp)}`.trim() : "\u2014"
65886
+ );
64311
65887
  set("monitoringEnabled", data.monitoringEnabled == null ? "\u2014" : data.monitoringEnabled ? "Sim" : "N\xE3o");
64312
- set("lastGatewayCheckAt", formatIsoPtBr(data.lastGatewayCheckAt));
64313
- set("lastGatewaySuccessCheckAt", formatIsoPtBr(data.lastGatewaySuccessCheckAt));
64314
65888
  set("lastGatewayCheckLatencyMs", data.lastGatewayCheckLatencyMs != null ? `${data.lastGatewayCheckLatencyMs}ms` : "\u2014");
64315
65889
  set("probeResult", data.probeResult || "\u2014");
64316
65890
  set("statsConnectedDevices", data.stats?.connectedDevices != null ? String(data.stats.connectedDevices) : "\u2014");
@@ -64325,33 +65899,38 @@ function patchGatewayInfoDom(root, uuid, data) {
64325
65899
  function connectivityFieldsetHtml(data, l, language, errors) {
64326
65900
  const err = (field) => errors[field] ? `<span class="myio-csettings__field-error">${escAttr4(errors[field])}</span>` : "";
64327
65901
  const errClass = (field) => errors[field] ? " myio-csettings__input--error" : "";
65902
+ const inputRow = (label, field, inputHtml) => `
65903
+ <div class="info-row">
65904
+ <span class="info-label">${escAttr4(label)}</span>
65905
+ <span class="info-value">${inputHtml}${err(field)}</span>
65906
+ </div>`;
64328
65907
  return `
64329
- <div class="form-card gateway-info-card myio-csettings-connectivity">
65908
+ <div class="form-card gateway-info-card info-card-wide myio-csettings-connectivity">
64330
65909
  <h4 class="section-title">${escAttr4(
64331
65910
  l.connectivitySection || (language === "en" ? "Connectivity Settings" : "Configura\xE7\xE3o de Conectividade")
64332
65911
  )}</h4>
64333
- <div class="myio-csettings__fields">
64334
- <label class="myio-csettings__field">
64335
- <span>${escAttr4(l.offlineGraceLabel || (language === "en" ? "Grace window (min)" : "Janela de gra\xE7a (min)"))}</span>
64336
- <input type="number" min="0" step="1" name="offlineGraceMinutes" value="${data.offlineGraceMinutes}" class="myio-csettings__input${errClass(
64337
- "offlineGraceMinutes"
64338
- )}" />
64339
- ${err("offlineGraceMinutes")}
64340
- </label>
64341
- <label class="myio-csettings__field">
64342
- <span>${escAttr4(l.blipToleranceLabel || (language === "en" ? "Blip tolerance (min)" : "Toler\xE2ncia de blip (min)"))}</span>
64343
- <input type="number" min="0" step="1" name="blipToleranceMinutes" value="${data.blipToleranceMinutes}" class="myio-csettings__input${errClass(
64344
- "blipToleranceMinutes"
64345
- )}" />
64346
- ${err("blipToleranceMinutes")}
64347
- </label>
64348
- <label class="myio-csettings__field">
64349
- <span>${escAttr4(l.offlineHardLabel || (language === "en" ? "Hard-offline threshold (min)" : "Limiar de hard-offline (min)"))}</span>
64350
- <input type="number" min="0" step="1" name="offlineHardMinutes" value="${data.offlineHardMinutes ?? ""}" placeholder="${escAttr4(l.offlineHardHint || (language === "en" ? "empty = disabled" : "vazio = desativado"))}" class="myio-csettings__input${errClass(
64351
- "offlineHardMinutes"
64352
- )}" />
64353
- ${err("offlineHardMinutes")}
64354
- </label>
65912
+ <div class="info-grid">
65913
+ ${inputRow(
65914
+ `${l.offlineGraceLabel || (language === "en" ? "Grace window (min)" : "Janela de gra\xE7a (min)")}:`,
65915
+ "offlineGraceMinutes",
65916
+ `<input type="number" min="0" step="1" name="offlineGraceMinutes" value="${data.offlineGraceMinutes}" class="myio-csettings__input${errClass(
65917
+ "offlineGraceMinutes"
65918
+ )}" />`
65919
+ )}
65920
+ ${inputRow(
65921
+ `${l.blipToleranceLabel || (language === "en" ? "Blip tolerance (min)" : "Toler\xE2ncia de blip (min)")}:`,
65922
+ "blipToleranceMinutes",
65923
+ `<input type="number" min="0" step="1" name="blipToleranceMinutes" value="${data.blipToleranceMinutes}" class="myio-csettings__input${errClass(
65924
+ "blipToleranceMinutes"
65925
+ )}" />`
65926
+ )}
65927
+ ${inputRow(
65928
+ `${l.offlineHardLabel || (language === "en" ? "Hard-offline threshold (min)" : "Limiar de hard-offline (min)")}:`,
65929
+ "offlineHardMinutes",
65930
+ `<input type="number" min="0" step="1" name="offlineHardMinutes" value="${data.offlineHardMinutes ?? ""}" placeholder="${escAttr4(l.offlineHardHint || (language === "en" ? "empty = disabled" : "vazio = desativado"))}" class="myio-csettings__input${errClass(
65931
+ "offlineHardMinutes"
65932
+ )}" />`
65933
+ )}
64355
65934
  </div>
64356
65935
  </div>
64357
65936
  `;
@@ -64362,16 +65941,13 @@ function injectStyles9() {
64362
65941
  const tag = document.createElement("style");
64363
65942
  tag.id = STYLE_ID4;
64364
65943
  tag.textContent = `
64365
- .myio-csettings__fields{display:flex;flex-direction:column;gap:10px;}
64366
- .myio-csettings__field{display:flex;flex-direction:column;gap:4px;}
64367
- .myio-csettings__field span{font:700 12px 'Nunito', system-ui, sans-serif;color:#374151;}
64368
65944
  .myio-csettings__input{
64369
- font:600 13px 'Nunito', system-ui, sans-serif;padding:7px 10px;border-radius:8px;
64370
- border:1px solid #e5e7eb;background:#fff;color:#1f2937;width:100%;box-sizing:border-box;
65945
+ font:600 13px 'Nunito', system-ui, sans-serif;padding:6px 8px;border-radius:6px;
65946
+ border:1px solid #e5e7eb;background:#fff;color:#1f2937;width:100px;box-sizing:border-box;
65947
+ text-align:right;
64371
65948
  }
64372
65949
  .myio-csettings__input--error{border-color:#dc2626;}
64373
- .myio-csettings__field-error{font:700 11px 'Nunito', system-ui, sans-serif;color:#dc2626;}
64374
- .theme-dark .myio-csettings__field span{color:#d1d5db;}
65950
+ .myio-csettings__field-error{display:block;margin-top:4px;font:700 11px 'Nunito', system-ui, sans-serif;color:#dc2626;text-align:right;}
64375
65951
  .theme-dark .myio-csettings__input{background:#111827;border-color:#4b5563;color:#e5e7eb;}
64376
65952
  `;
64377
65953
  document.head.appendChild(tag);
@@ -64403,6 +65979,12 @@ var CentralSettingsModal = class extends SettingsModalView {
64403
65979
  deviceId: params.id,
64404
65980
  deviceLabel: params.name,
64405
65981
  deviceName: params.uuid ?? void 0,
65982
+ // RFC-0200: identity-card image (Geral tab) — without this, getDeviceImage()
65983
+ // sees an empty deviceType and falls back to DEFAULT_DEVICE_ICON (generic
65984
+ // 3F_MEDIDOR art) instead of deviceIcons.GATEWAY. A real device never sets
65985
+ // deviceType to this value (it's not a valid TB deviceProfile), so this is
65986
+ // fully inert for the device settings modal, same as isGateway above.
65987
+ deviceType: "GATEWAY",
64406
65988
  // No domain concept for a central — suppresses the energy/water/
64407
65989
  // temperature-only sections and removes the "Excluir Grupos" tab
64408
65990
  // (gated on `domain === 'energy'` in the base class).
@@ -64412,11 +65994,15 @@ var CentralSettingsModal = class extends SettingsModalView {
64412
65994
  jwtToken: "",
64413
65995
  mapInstantaneousPower: {},
64414
65996
  deviceMapInstaneousPower: {},
64415
- // Native "Central" tab (see file doc) — read-only GCDR identity/
64416
- // telemetry. Real devices never set isGateway, so this is fully inert
65997
+ // Inline gateway identity/telemetry in Geral (see file doc) — read-only
65998
+ // GCDR data. Real devices never set isGateway, so this is fully inert
64417
65999
  // for the device settings modal.
64418
66000
  isGateway: true,
64419
66001
  gatewayInfo: mapToGatewayInfo(initialData),
66002
+ // RFC-0232: "Incidentes" tab gate + data — see CentralSettingsModalParams doc.
66003
+ userEmail: params.userEmail,
66004
+ superadmin: params.superadmin,
66005
+ interpolatedSlots: params.interpolatedSlots,
64420
66006
  onSave: () => Promise.resolve(),
64421
66007
  // rebound below once `this` exists (see class doc)
64422
66008
  onClose: () => {
@@ -64436,7 +66022,7 @@ var CentralSettingsModal = class extends SettingsModalView {
64436
66022
  }
64437
66023
  /** Overrides the base's public `render()`: mounts the full device-modal
64438
66024
  * shell via `super.render()`, then injects the Conectividade fieldset the
64439
- * base has no concept of, and patches the native "Central" tab's
66025
+ * base has no concept of, and patches the Geral tab's inline gateway-info
64440
66026
  * `#gwinfo-*` elements — its HTML was generated once at construction time
64441
66027
  * (from whatever data was available then), so an async `onFetchSettings`
64442
66028
  * resolving later needs this explicit patch to actually show up. */
@@ -64455,17 +66041,15 @@ var CentralSettingsModal = class extends SettingsModalView {
64455
66041
  }
64456
66042
  }
64457
66043
  /**
64458
- * Injects the editable connectivity fieldset into the native "Central"
64459
- * tab's `.gateway-info-grid` — as a 5th `.form-card`, right after the
64460
- * read-only "Conectividade (telemetria)" card (found by its heading text,
64461
- * not a fixed position, so this stays correct even if the base ever
64462
- * reorders `getGatewayInfoHTML()`'s 4 cards). Lives outside `<form>`
64463
- * (the Central tab isn't form-wrapped, unlike Geral) — `getFormData()` is
64464
- * overridden below to read these fields manually since `new
64465
- * FormData(this.form)` can't see them.
66044
+ * Injects the editable connectivity fieldset into the Geral tab's inline
66045
+ * gateway-info `.gateway-info-grid` (Identificação/Estatísticas/Metadados)
66046
+ * as a 4th `.form-card`, appended at the end. This grid is a descendant of
66047
+ * the Geral tab's own `<form>`, so the base's `new FormData(this.form)`
66048
+ * (used by the base's own `getFormData()`) picks up these inputs
66049
+ * automatically — no `getFormData()` override needed here.
64466
66050
  */
64467
66051
  injectConnectivitySection(errors) {
64468
- const grid = this.csRootEl?.querySelector("#gateway-tab-content .gateway-info-grid");
66052
+ const grid = this.csRootEl?.querySelector(".gateway-info-grid");
64469
66053
  if (!grid) return;
64470
66054
  const html = connectivityFieldsetHtml(this.csCurrentData, this.csLabels, this.csLanguage, errors);
64471
66055
  const existing = grid.querySelector(".myio-csettings-connectivity");
@@ -64473,30 +66057,7 @@ var CentralSettingsModal = class extends SettingsModalView {
64473
66057
  existing.outerHTML = html;
64474
66058
  return;
64475
66059
  }
64476
- const telemetryCard = Array.from(grid.querySelectorAll(".gateway-info-card")).find(
64477
- (card) => card.querySelector(".section-title")?.textContent === "Conectividade (telemetria)"
64478
- );
64479
- if (telemetryCard) {
64480
- telemetryCard.insertAdjacentHTML("afterend", html);
64481
- } else {
64482
- grid.insertAdjacentHTML("beforeend", html);
64483
- }
64484
- }
64485
- /**
64486
- * Overrides the base's public `getFormData()` (called internally by the
64487
- * base's own submit/save-button handlers via `this.getFormData()`, which
64488
- * dispatches here through normal prototype resolution — no base edit
64489
- * needed). Merges the base's `new FormData(this.form)` read (Geral tab
64490
- * fields) with a manual read of the connectivity inputs, which live in the
64491
- * Central tab, outside `this.form`.
64492
- */
64493
- getFormData() {
64494
- const base = super.getFormData();
64495
- const extra = {};
64496
- this.csRootEl?.querySelectorAll(".myio-csettings-connectivity [name]").forEach((el2) => {
64497
- if (el2.name) extra[el2.name] = el2.value;
64498
- });
64499
- return { ...base, ...extra };
66060
+ grid.insertAdjacentHTML("beforeend", html);
64500
66061
  }
64501
66062
  async handleCentralSave(formData) {
64502
66063
  const hardRaw = formData.offlineHardMinutes;
@@ -74322,270 +75883,6 @@ async function openDashboardPopupSettings(params) {
74322
75883
  }
74323
75884
  }
74324
75885
 
74325
- // src/components/createInputDateRangePickerInsideDIV.ts
74326
- var PREMIUM_STYLES = `
74327
- .myio-daterange-wrapper {
74328
- font-family: 'Nunito', system-ui, sans-serif;
74329
- background: #f9f9f9;
74330
- padding: 20px;
74331
- border-radius: 8px;
74332
- margin-bottom: 20px;
74333
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
74334
- transition: all 0.2s ease;
74335
- }
74336
-
74337
- .myio-daterange-wrapper:hover {
74338
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
74339
- }
74340
-
74341
- .myio-daterange-label {
74342
- display: block;
74343
- margin-bottom: 8px;
74344
- font-weight: 500;
74345
- color: #333;
74346
- font-size: 14px;
74347
- line-height: 1.4;
74348
- }
74349
-
74350
- .myio-daterange-input {
74351
- width: 100%;
74352
- max-width: 300px;
74353
- padding: 8px 12px;
74354
- border: 1px solid #ddd;
74355
- border-radius: 4px;
74356
- background: white;
74357
- font-size: 14px;
74358
- color: #333;
74359
- cursor: pointer;
74360
- transition: all 0.2s ease;
74361
- font-family: inherit;
74362
- line-height: 1.4;
74363
- }
74364
-
74365
- .myio-daterange-input:hover {
74366
- border-color: #4A148C;
74367
- box-shadow: 0 0 0 2px rgba(74, 20, 140, 0.1);
74368
- }
74369
-
74370
- .myio-daterange-input:focus {
74371
- outline: none;
74372
- border-color: #4A148C;
74373
- box-shadow: 0 0 0 3px rgba(74, 20, 140, 0.2);
74374
- }
74375
-
74376
- .myio-daterange-input::placeholder {
74377
- color: #999;
74378
- opacity: 1;
74379
- }
74380
-
74381
- .myio-daterange-helper {
74382
- font-size: 12px;
74383
- color: #666;
74384
- margin-top: 4px;
74385
- line-height: 1.3;
74386
- transition: color 0.2s ease;
74387
- }
74388
-
74389
- .myio-daterange-helper.success {
74390
- color: #28a745;
74391
- font-weight: 500;
74392
- }
74393
-
74394
- .myio-daterange-helper.error {
74395
- color: #dc3545;
74396
- font-weight: 500;
74397
- }
74398
-
74399
- /* Responsive design */
74400
- @media (max-width: 768px) {
74401
- .myio-daterange-wrapper {
74402
- padding: 16px;
74403
- margin-bottom: 16px;
74404
- }
74405
-
74406
- .myio-daterange-input {
74407
- max-width: 100%;
74408
- font-size: 16px; /* Prevents zoom on iOS */
74409
- }
74410
- }
74411
-
74412
- /* High contrast mode support */
74413
- @media (prefers-contrast: high) {
74414
- .myio-daterange-wrapper {
74415
- border: 2px solid #000;
74416
- }
74417
-
74418
- .myio-daterange-input {
74419
- border: 2px solid #000;
74420
- }
74421
- }
74422
-
74423
- /* Reduced motion support */
74424
- @media (prefers-reduced-motion: reduce) {
74425
- .myio-daterange-wrapper,
74426
- .myio-daterange-input,
74427
- .myio-daterange-helper {
74428
- transition: none;
74429
- }
74430
- }
74431
- `;
74432
- function injectPremiumStyles() {
74433
- const styleId = "myio-daterange-premium-styles";
74434
- if (document.getElementById(styleId)) {
74435
- return;
74436
- }
74437
- const styleEl = document.createElement("style");
74438
- styleEl.id = styleId;
74439
- styleEl.textContent = PREMIUM_STYLES;
74440
- document.head.appendChild(styleEl);
74441
- console.log("[MyIO] Premium date range styles injected");
74442
- }
74443
- function validateId(id, context) {
74444
- if (!id || typeof id !== "string") {
74445
- throw new Error(`[createInputDateRangePickerInsideDIV] ${context} must be a non-empty string`);
74446
- }
74447
- if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(id)) {
74448
- throw new Error(`[createInputDateRangePickerInsideDIV] ${context} '${id}' is not a valid HTML ID`);
74449
- }
74450
- }
74451
- async function createInputDateRangePickerInsideDIV(params) {
74452
- const {
74453
- containerId,
74454
- inputId,
74455
- label = "Per\xEDodo de Datas",
74456
- placeholder = "Clique para selecionar per\xEDodo",
74457
- pickerOptions = {},
74458
- classNames = {},
74459
- injectStyles: injectStyles23 = true,
74460
- showHelper = true
74461
- } = params;
74462
- validateId(containerId, "containerId");
74463
- validateId(inputId, "inputId");
74464
- const container = document.getElementById(containerId);
74465
- if (!container) {
74466
- throw new Error(`[createInputDateRangePickerInsideDIV] Container '#${containerId}' not found`);
74467
- }
74468
- if (injectStyles23) {
74469
- injectPremiumStyles();
74470
- }
74471
- let inputEl = document.getElementById(inputId);
74472
- if (inputEl && inputEl.tagName.toLowerCase() !== "input") {
74473
- throw new Error(`[createInputDateRangePickerInsideDIV] Element '#${inputId}' exists but is not an input element`);
74474
- }
74475
- const wrapper = document.createElement("div");
74476
- wrapper.className = classNames.wrapper || "myio-daterange-wrapper";
74477
- wrapper.setAttribute("data-myio-component", "daterange-input");
74478
- wrapper.setAttribute("data-version", "1.0.0");
74479
- let labelEl = null;
74480
- if (label) {
74481
- labelEl = document.createElement("label");
74482
- labelEl.className = classNames.label || "myio-daterange-label";
74483
- labelEl.textContent = label;
74484
- labelEl.setAttribute("for", inputId);
74485
- wrapper.appendChild(labelEl);
74486
- }
74487
- if (!inputEl) {
74488
- inputEl = document.createElement("input");
74489
- inputEl.type = "text";
74490
- inputEl.id = inputId;
74491
- inputEl.name = inputId;
74492
- }
74493
- inputEl.className = classNames.input || "myio-daterange-input";
74494
- inputEl.readOnly = true;
74495
- inputEl.placeholder = placeholder;
74496
- inputEl.autocomplete = "off";
74497
- inputEl.setAttribute("aria-label", label || "Date range selector");
74498
- if (showHelper) {
74499
- inputEl.setAttribute("aria-describedby", `${inputId}-helper`);
74500
- }
74501
- let helperEl = null;
74502
- if (showHelper) {
74503
- helperEl = document.createElement("div");
74504
- helperEl.id = `${inputId}-helper`;
74505
- helperEl.className = classNames.helper || "myio-daterange-helper";
74506
- helperEl.setAttribute("aria-live", "polite");
74507
- helperEl.style.display = "flex";
74508
- helperEl.style.alignItems = "center";
74509
- }
74510
- wrapper.appendChild(inputEl);
74511
- if (helperEl) {
74512
- wrapper.appendChild(helperEl);
74513
- }
74514
- const existingWrapper = container.querySelector('[data-myio-component="daterange-input"]');
74515
- if (existingWrapper) {
74516
- console.warn(`[createInputDateRangePickerInsideDIV] Replacing existing daterange input in container '#${containerId}'`);
74517
- existingWrapper.remove();
74518
- }
74519
- container.appendChild(wrapper);
74520
- const enhancedOptions = {
74521
- maxRangeDays: 31,
74522
- onApply: (result) => {
74523
- if (helperEl) {
74524
- const startDate = new Date(result.startISO);
74525
- const endDate = new Date(result.endISO);
74526
- const days = Math.ceil((endDate.getTime() - startDate.getTime()) / (1e3 * 60 * 60 * 24)) + 1;
74527
- helperEl.textContent = `Per\xEDodo selecionado: ${days} dia${days !== 1 ? "s" : ""}`;
74528
- helperEl.className = (classNames.helper || "myio-daterange-helper") + " success";
74529
- setTimeout(() => {
74530
- if (helperEl) {
74531
- helperEl.className = classNames.helper || "myio-daterange-helper";
74532
- }
74533
- }, 3e3);
74534
- }
74535
- if (pickerOptions.onApply) {
74536
- pickerOptions.onApply(result);
74537
- }
74538
- },
74539
- ...pickerOptions
74540
- };
74541
- let picker;
74542
- try {
74543
- picker = await createDateRangePicker2(inputEl, enhancedOptions);
74544
- console.log(`[createInputDateRangePickerInsideDIV] Successfully initialized for input '#${inputId}'`);
74545
- } catch (error) {
74546
- wrapper.remove();
74547
- throw new Error(`[createInputDateRangePickerInsideDIV] Failed to initialize date picker: ${error.message}`);
74548
- }
74549
- const controller = {
74550
- input: inputEl,
74551
- container,
74552
- wrapper,
74553
- picker,
74554
- getDisplayValue: () => inputEl.value,
74555
- getDates: () => picker.getDates(),
74556
- setDates: (startISO, endISO) => {
74557
- try {
74558
- picker.setDates(startISO, endISO);
74559
- } catch (error) {
74560
- console.error(`[createInputDateRangePickerInsideDIV] Error setting dates:`, error);
74561
- throw error;
74562
- }
74563
- },
74564
- setHelperText: (text2, type = "default") => {
74565
- if (helperEl) {
74566
- helperEl.textContent = text2;
74567
- const baseClass = classNames.helper || "myio-daterange-helper";
74568
- helperEl.className = type === "default" ? baseClass : `${baseClass} ${type}`;
74569
- }
74570
- },
74571
- destroy: () => {
74572
- try {
74573
- picker.destroy();
74574
- console.log(`[createInputDateRangePickerInsideDIV] Date picker destroyed for input '#${inputId}'`);
74575
- } catch (error) {
74576
- console.warn(`[createInputDateRangePickerInsideDIV] Error destroying picker:`, error);
74577
- }
74578
- try {
74579
- wrapper.remove();
74580
- console.log(`[createInputDateRangePickerInsideDIV] Wrapper removed for input '#${inputId}'`);
74581
- } catch (error) {
74582
- console.warn(`[createInputDateRangePickerInsideDIV] Error removing wrapper:`, error);
74583
- }
74584
- }
74585
- };
74586
- return controller;
74587
- }
74588
-
74589
75886
  // src/utils/goalsCoverage.ts
74590
75887
  var MONTHS_PT = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"];
74591
75888
  function formatCoverageRefPtBR(ref) {
@@ -101579,7 +102876,7 @@ async function openClearGcdrIdsModal(state6) {
101579
102876
  if (!state6.selectedCustomer) return;
101580
102877
  const customerId = getEntityId(state6.selectedCustomer);
101581
102878
  const customerName = state6.selectedCustomer.name || state6.selectedCustomer.title || customerId;
101582
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
102879
+ const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
101583
102880
  const overlay = document.createElement("div");
101584
102881
  overlay.style.cssText = `
101585
102882
  position:fixed; inset:0; z-index:99999;
@@ -101691,7 +102988,7 @@ async function openClearGcdrIdsModal(state6) {
101691
102988
  const chunks = [];
101692
102989
  for (let i = 0; i < items.length; i += 10) chunks.push(items.slice(i, i + 10));
101693
102990
  for (let ci = 0; ci < chunks.length; ci++) {
101694
- if (ci > 0) await sleep(800);
102991
+ if (ci > 0) await sleep2(800);
101695
102992
  await Promise.all(chunks[ci].map(async (entity) => {
101696
102993
  const tbId = entity.id?.id || "";
101697
102994
  const name = entity.name || entity.label || tbId;
@@ -101741,7 +103038,7 @@ async function openClearGcdrIdsModal(state6) {
101741
103038
  const chunks = [];
101742
103039
  for (let i = 0; i < rows.length; i += 10) chunks.push(rows.slice(i, i + 10));
101743
103040
  for (let ci = 0; ci < chunks.length; ci++) {
101744
- if (ci > 0) await sleep(800);
103041
+ if (ci > 0) await sleep2(800);
101745
103042
  await Promise.all(chunks[ci].map(async (row) => {
101746
103043
  const keysToDelete = keys.filter((k) => row.present[k]);
101747
103044
  try {
@@ -104348,6 +105645,7 @@ ${errors.slice(0, 5).join("\n")}` + (errors.length > 5 ? `
104348
105645
  // src/components/card-badges/index.ts
104349
105646
  var ALARM_STYLES_ID = "myio-alarm-badge-styles";
104350
105647
  var TICKET_STYLES_ID = "myio-ticket-badge-styles";
105648
+ var INCIDENT_STYLES_ID = "myio-incident-badge-styles";
104351
105649
  function injectAlarmBadgeStyles() {
104352
105650
  if (document.getElementById(ALARM_STYLES_ID)) return;
104353
105651
  const s = document.createElement("style");
@@ -104421,6 +105719,32 @@ function injectTicketBadgeStyles() {
104421
105719
  `;
104422
105720
  document.head.appendChild(s);
104423
105721
  }
105722
+ function injectIncidentBadgeStyles() {
105723
+ if (document.getElementById(INCIDENT_STYLES_ID)) return;
105724
+ const s = document.createElement("style");
105725
+ s.id = INCIDENT_STYLES_ID;
105726
+ s.textContent = `
105727
+ /* Stacked directly under .myio-alarm-badge (same corner, same shape) */
105728
+ .myio-incident-badge {
105729
+ position: absolute;
105730
+ top: 28px;
105731
+ left: 6px;
105732
+ background: #7C3AED;
105733
+ color: #fff;
105734
+ border-radius: 10px;
105735
+ padding: 2px 5px;
105736
+ font-size: 10px;
105737
+ font-weight: 700;
105738
+ display: flex;
105739
+ align-items: center;
105740
+ gap: 2px;
105741
+ z-index: 10;
105742
+ pointer-events: none;
105743
+ line-height: 1.3;
105744
+ }
105745
+ `;
105746
+ document.head.appendChild(s);
105747
+ }
104424
105748
  function defaultAlarmCount(gcdrDeviceId) {
104425
105749
  const aso = window.AlarmServiceOrchestrator;
104426
105750
  return aso?.getAlarmCountForDevice?.(gcdrDeviceId) ?? 0;
@@ -104431,6 +105755,13 @@ function alarmTitle(count) {
104431
105755
  function ticketTitle(count) {
104432
105756
  return count > 0 ? `${count} chamado${count !== 1 ? "s" : ""} aberto${count !== 1 ? "s" : ""}` : "Chamados";
104433
105757
  }
105758
+ function defaultIncidentCount(gcdrDeviceId) {
105759
+ const iso = window.IncidentServiceOrchestrator;
105760
+ return iso?.getIncidentCountForDevice?.(gcdrDeviceId) ?? 0;
105761
+ }
105762
+ function incidentTitle(count) {
105763
+ return `${count} incidente${count !== 1 ? "s" : ""} de interpola\xE7\xE3o`;
105764
+ }
104434
105765
  function addAlarmBadge(cardElement, gcdrDeviceId, opts) {
104435
105766
  if (!cardElement || !gcdrDeviceId) return;
104436
105767
  if (cardElement.querySelector(`[data-alarm-device-id="${gcdrDeviceId}"]`)) return;
@@ -104516,6 +105847,36 @@ function refreshTicketBadges(opts) {
104516
105847
  }
104517
105848
  });
104518
105849
  }
105850
+ function addIncidentBadge(cardElement, gcdrDeviceId, opts) {
105851
+ if (!cardElement || !gcdrDeviceId) return;
105852
+ if (cardElement.querySelector(`[data-incident-device-id="${gcdrDeviceId}"]`)) return;
105853
+ const count = (opts?.getCount ?? defaultIncidentCount)(gcdrDeviceId);
105854
+ injectIncidentBadgeStyles();
105855
+ if (cardElement.style) cardElement.style.position = "relative";
105856
+ const badge = document.createElement("div");
105857
+ badge.className = "myio-incident-badge";
105858
+ badge.setAttribute("data-incident-device-id", gcdrDeviceId);
105859
+ badge.style.display = count > 0 ? "" : "none";
105860
+ badge.title = incidentTitle(count);
105861
+ badge.innerHTML = `<svg viewBox="0 0 24 24" width="10" height="10" fill="currentColor" aria-hidden="true"><path d="M12 2L1 21h22L12 2z"/></svg><span>${count > 99 ? "99+" : count}</span>`;
105862
+ cardElement.appendChild(badge);
105863
+ }
105864
+ function refreshIncidentBadges(opts) {
105865
+ const getCount = opts?.getCount ?? defaultIncidentCount;
105866
+ document.querySelectorAll(".myio-incident-badge[data-incident-device-id]").forEach((badge) => {
105867
+ const gcdrDeviceId = badge.getAttribute("data-incident-device-id");
105868
+ if (!gcdrDeviceId) return;
105869
+ const count = getCount(gcdrDeviceId);
105870
+ const span = badge.querySelector("span");
105871
+ if (count > 0) {
105872
+ badge.style.display = "";
105873
+ badge.title = incidentTitle(count);
105874
+ if (span) span.textContent = count > 99 ? "99+" : String(count);
105875
+ } else {
105876
+ badge.style.display = "none";
105877
+ }
105878
+ });
105879
+ }
104519
105880
 
104520
105881
  // src/components/premium-modals/dialog/styles.ts
104521
105882
  var FONT_LINK_ID = "myio-dialog-font-nunito";
@@ -163107,6 +164468,16 @@ var CSS4 = `
163107
164468
  justify-content:center;gap:2px;line-height:1;cursor:default;box-shadow:0 1px 3px rgba(0,0,0,.25);
163108
164469
  z-index:10;
163109
164470
  }
164471
+ /* Incident (RFC-0232) sits at the vertical midpoint of the left border \u2014
164472
+ below the alarm badge, above the ticket badge \u2014 reading as a 3-badge
164473
+ stack on the same edge regardless of card height (card vs. compact). */
164474
+ .myio-cscard__incident-badge{
164475
+ position:absolute;top:50%;left:-14px;transform:translateY(-50%);
164476
+ background:#7C3AED;color:#fff;border-radius:10px;min-width:22px;height:22px;
164477
+ padding:0 6px;font:700 10px 'Nunito', system-ui, sans-serif;display:flex;align-items:center;
164478
+ justify-content:center;gap:2px;line-height:1;cursor:default;box-shadow:0 1px 3px rgba(0,0,0,.25);
164479
+ z-index:10;
164480
+ }
163110
164481
  .myio-cscard__ticket-badge{
163111
164482
  position:absolute;bottom:18%;left:-14px;transform:translateY(50%);width:24px;height:24px;
163112
164483
  background:rgba(8,145,178,.12);color:#0891b2;border:1px solid rgba(8,145,178,.25);
@@ -163573,6 +164944,7 @@ var CentralStatusCard = class _CentralStatusCard {
163573
164944
  detachTitleTooltip = null;
163574
164945
  detachDivergenceTooltip = null;
163575
164946
  detachAlarmTooltip = null;
164947
+ detachIncidentTooltip = null;
163576
164948
  detachTicketTooltip = null;
163577
164949
  detachAnnotationTooltips = [];
163578
164950
  /** Latency from the PREVIOUS render, to derive the trend arrow next to
@@ -163637,6 +165009,7 @@ var CentralStatusCard = class _CentralStatusCard {
163637
165009
  this.detachTitleTooltip?.();
163638
165010
  this.detachDivergenceTooltip?.();
163639
165011
  this.detachAlarmTooltip?.();
165012
+ this.detachIncidentTooltip?.();
163640
165013
  this.detachTicketTooltip?.();
163641
165014
  this.detachAnnotationTooltips.forEach((fn) => fn());
163642
165015
  this.el.remove();
@@ -163957,6 +165330,12 @@ var CentralStatusCard = class _CentralStatusCard {
163957
165330
  <svg viewBox="0 0 24 24" width="10" height="10" fill="currentColor" aria-hidden="true"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6V11c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/></svg>
163958
165331
  <span>${p.alarmCount > 99 ? "99+" : p.alarmCount}</span>
163959
165332
  </div>` : "";
165333
+ const incidentBadgeHtml = p.incidentCount && p.incidentCount > 0 ? `<div class="myio-cscard__incident-badge" data-role="incident-badge" aria-label="${escAttr6(
165334
+ `${p.incidentCount} incidente${p.incidentCount !== 1 ? "s" : ""} de interpola\xE7\xE3o`
165335
+ )}">
165336
+ <svg viewBox="0 0 24 24" width="10" height="10" fill="currentColor" aria-hidden="true"><path d="M12 2L1 21h22L12 2z"/></svg>
165337
+ <span>${p.incidentCount > 99 ? "99+" : p.incidentCount}</span>
165338
+ </div>` : "";
163960
165339
  const ticketBadgeHtml = p.ticketCount && p.ticketCount > 0 ? `<div class="myio-cscard__ticket-badge" data-role="ticket-badge" aria-label="${escAttr6(
163961
165340
  `${p.ticketCount} chamado${p.ticketCount !== 1 ? "s" : ""} aberto${p.ticketCount !== 1 ? "s" : ""}`
163962
165341
  )}">
@@ -163993,6 +165372,7 @@ var CentralStatusCard = class _CentralStatusCard {
163993
165372
  const compactEvidenceLine = variant === "compact" ? `<div class="myio-cscard__evidence-line">${devicesText ? escAttr6(devicesText) : ""}${probeText ? (devicesText ? " \xB7 " : "") + probeText : ""}</div>` : "";
163994
165373
  this.el.innerHTML = `
163995
165374
  ${alarmBadgeHtml}
165375
+ ${incidentBadgeHtml}
163996
165376
  ${ticketBadgeHtml}
163997
165377
  ${annotationBadgesHtml}
163998
165378
  <div class="myio-cscard__surface">
@@ -164096,6 +165476,8 @@ var CentralStatusCard = class _CentralStatusCard {
164096
165476
  this.detachDivergenceTooltip = null;
164097
165477
  this.detachAlarmTooltip?.();
164098
165478
  this.detachAlarmTooltip = null;
165479
+ this.detachIncidentTooltip?.();
165480
+ this.detachIncidentTooltip = null;
164099
165481
  this.detachTicketTooltip?.();
164100
165482
  this.detachTicketTooltip = null;
164101
165483
  this.detachAnnotationTooltips.forEach((fn) => fn());
@@ -164137,6 +165519,19 @@ var CentralStatusCard = class _CentralStatusCard {
164137
165519
  }));
164138
165520
  }
164139
165521
  }
165522
+ if (p.incidentCount && p.incidentCount > 0) {
165523
+ const incidentBadge = this.el.querySelector('[data-role="incident-badge"]');
165524
+ if (incidentBadge) {
165525
+ const count = p.incidentCount;
165526
+ this.detachIncidentTooltip = InfoTooltip.attach(incidentBadge, () => ({
165527
+ icon: "\u{1F53A}",
165528
+ title: en ? "Incidents" : "Incidentes",
165529
+ content: escAttr6(
165530
+ en ? `${count} interpolated reading${count !== 1 ? "s" : ""}` : `${count} incidente${count !== 1 ? "s" : ""} de interpola\xE7\xE3o`
165531
+ )
165532
+ }));
165533
+ }
165534
+ }
164140
165535
  if (p.ticketCount && p.ticketCount > 0) {
164141
165536
  const ticketBadge = this.el.querySelector('[data-role="ticket-badge"]');
164142
165537
  if (ticketBadge) {
@@ -164240,6 +165635,13 @@ var CentralStatusCard = class _CentralStatusCard {
164240
165635
  this.params.onAlarmBadgeClick?.({ id: this.params.id, source: "central-status-card" });
164241
165636
  });
164242
165637
  }
165638
+ const incidentBadge = this.el.querySelector('[data-role="incident-badge"]');
165639
+ if (incidentBadge && this.params.onIncidentBadgeClick) {
165640
+ incidentBadge.addEventListener("click", (e) => {
165641
+ e.stopPropagation();
165642
+ this.params.onIncidentBadgeClick?.({ id: this.params.id, source: "central-status-card" });
165643
+ });
165644
+ }
164243
165645
  const ticketBadge = this.el.querySelector('[data-role="ticket-badge"]');
164244
165646
  if (ticketBadge) {
164245
165647
  ticketBadge.addEventListener("click", (e) => {
@@ -164438,6 +165840,9 @@ var version = package_default.version || "0.0.0";
164438
165840
  FreshdeskClient,
164439
165841
  GRANULARITY_SELECTOR_CSS_PREFIX,
164440
165842
  GRID_SORT_OPTIONS,
165843
+ GcdrAnnotationsClient,
165844
+ GcdrAnnotationsConflictError,
165845
+ GcdrAnnotationsError,
164441
165846
  GoalsModal,
164442
165847
  GoalsMoneyApiError,
164443
165848
  GoalsMoneyClient,
@@ -164579,9 +165984,13 @@ var version = package_default.version || "0.0.0";
164579
165984
  WELCOME_DEFAULT_PALETTE,
164580
165985
  Water,
164581
165986
  WaterSummaryTooltip,
165987
+ adaptGcdrListToLegacyAnnotations,
165988
+ adaptGcdrToLegacyAnnotation,
165989
+ adaptLegacyToGcdrInput,
164582
165990
  addAlarmBadge,
164583
165991
  addDetectionContext,
164584
165992
  addFreshdeskTicketNote,
165993
+ addIncidentBadge,
164585
165994
  addNamespace,
164586
165995
  addTicketBadge,
164587
165996
  aggregateByDay,
@@ -164604,6 +166013,7 @@ var version = package_default.version || "0.0.0";
164604
166013
  buildEquipmentCategoryDataForTooltip,
164605
166014
  buildEquipmentCategorySummary,
164606
166015
  buildFinancialRowHTML,
166016
+ buildGcdrEnergyRoot,
164607
166017
  buildListItemsThingsboardByUniqueDatasource,
164608
166018
  buildMoneyVarianceHTML,
164609
166019
  buildMyioIngestionAuth,
@@ -164688,6 +166098,8 @@ var version = package_default.version || "0.0.0";
164688
166098
  createFilterModalComponent,
164689
166099
  createFooterComponent,
164690
166100
  createFreshdeskTicket,
166101
+ createGcdrAnnotationsAuthStrategy,
166102
+ createGcdrAnnotationsClient,
164691
166103
  createGoalsBarTooltip,
164692
166104
  createGoalsMoneyClient,
164693
166105
  createGranularitySelector,
@@ -165024,6 +166436,8 @@ var version = package_default.version || "0.0.0";
165024
166436
  panelDomainToWire,
165025
166437
  panelTermToDeviceCategory,
165026
166438
  parseClassificationEntities,
166439
+ parseGcdrEnergyRoot,
166440
+ parseGcdrEntityForest,
165027
166441
  parseInputDateToDate,
165028
166442
  parseLogAnnotations,
165029
166443
  periodKey,
@@ -165032,6 +166446,7 @@ var version = package_default.version || "0.0.0";
165032
166446
  recalculateDeviceStatus,
165033
166447
  recomputeDeviceGridPercentages,
165034
166448
  refreshAlarmBadges,
166449
+ refreshIncidentBadges,
165035
166450
  refreshTicketBadges,
165036
166451
  removeAlarmsNotificationsPanelStyles,
165037
166452
  removeBASDashboardStyles,