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 +1832 -417
- package/dist/index.d.cts +407 -34
- package/dist/index.d.ts +407 -34
- package/dist/index.js +1819 -417
- package/dist/myio-js-library.umd.js +1819 -417
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
<!--
|
|
61556
|
-
${this.config.isGateway ? `<button type="button" class="modal-tab" data-tab="
|
|
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
|
-
<
|
|
61559
|
-
<
|
|
61560
|
-
<
|
|
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
|
-
|
|
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
|
-
<!--
|
|
61603
|
-
|
|
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`.
|
|
61894
|
-
*
|
|
61895
|
-
*
|
|
61896
|
-
*
|
|
61897
|
-
*
|
|
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
|
|
61905
|
-
<
|
|
61906
|
-
<
|
|
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="
|
|
61913
|
-
${
|
|
61914
|
-
${
|
|
61915
|
-
${
|
|
61916
|
-
${
|
|
61917
|
-
${
|
|
61918
|
-
${
|
|
61919
|
-
${
|
|
61920
|
-
${
|
|
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="
|
|
61937
|
-
${
|
|
61938
|
-
${
|
|
61939
|
-
${
|
|
61940
|
-
${
|
|
61941
|
-
${
|
|
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="
|
|
61947
|
-
${
|
|
61948
|
-
${
|
|
61949
|
-
${
|
|
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) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[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
|
|
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
|
-
/*
|
|
62617
|
-
|
|
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:
|
|
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("
|
|
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="
|
|
64334
|
-
|
|
64335
|
-
|
|
64336
|
-
|
|
64337
|
-
"offlineGraceMinutes"
|
|
64338
|
-
|
|
64339
|
-
|
|
64340
|
-
|
|
64341
|
-
|
|
64342
|
-
|
|
64343
|
-
|
|
64344
|
-
"blipToleranceMinutes"
|
|
64345
|
-
|
|
64346
|
-
|
|
64347
|
-
|
|
64348
|
-
|
|
64349
|
-
|
|
64350
|
-
|
|
64351
|
-
"offlineHardMinutes"
|
|
64352
|
-
|
|
64353
|
-
|
|
64354
|
-
|
|
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:
|
|
64370
|
-
border:1px solid #e5e7eb;background:#fff;color:#1f2937;width:
|
|
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
|
-
//
|
|
64416
|
-
//
|
|
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
|
|
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
|
|
64459
|
-
*
|
|
64460
|
-
*
|
|
64461
|
-
*
|
|
64462
|
-
*
|
|
64463
|
-
*
|
|
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("
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|