myio-js-library 0.1.536 → 0.1.537
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 +1675 -416
- package/dist/index.d.cts +353 -32
- package/dist/index.d.ts +353 -32
- package/dist/index.js +1665 -416
- package/dist/myio-js-library.umd.js +1665 -416
- 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,
|
|
@@ -440,6 +447,8 @@ __export(src_exports, {
|
|
|
440
447
|
createFilterModalComponent: () => createFilterModalComponent,
|
|
441
448
|
createFooterComponent: () => createFooterComponent,
|
|
442
449
|
createFreshdeskTicket: () => createTicket,
|
|
450
|
+
createGcdrAnnotationsAuthStrategy: () => createAuthStrategy,
|
|
451
|
+
createGcdrAnnotationsClient: () => createGcdrAnnotationsClient,
|
|
443
452
|
createGoalsBarTooltip: () => createGoalsBarTooltip,
|
|
444
453
|
createGoalsMoneyClient: () => createGoalsMoneyClient,
|
|
445
454
|
createGranularitySelector: () => createGranularitySelector,
|
|
@@ -784,6 +793,7 @@ __export(src_exports, {
|
|
|
784
793
|
recalculateDeviceStatus: () => recalculateDeviceStatus,
|
|
785
794
|
recomputeDeviceGridPercentages: () => recomputePercentages,
|
|
786
795
|
refreshAlarmBadges: () => refreshAlarmBadges,
|
|
796
|
+
refreshIncidentBadges: () => refreshIncidentBadges,
|
|
787
797
|
refreshTicketBadges: () => refreshTicketBadges,
|
|
788
798
|
removeAlarmsNotificationsPanelStyles: () => removeAlarmsNotificationsPanelStyles,
|
|
789
799
|
removeBASDashboardStyles: () => removeBASDashboardStyles,
|
|
@@ -877,7 +887,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
877
887
|
// package.json
|
|
878
888
|
var package_default = {
|
|
879
889
|
name: "myio-js-library",
|
|
880
|
-
version: "0.1.
|
|
890
|
+
version: "0.1.537",
|
|
881
891
|
description: "A clean, standalone JS SDK for MYIO projects",
|
|
882
892
|
license: "MIT",
|
|
883
893
|
repository: "github:gh-myio/myio-js-library",
|
|
@@ -2274,7 +2284,8 @@ var DeviceIconType = {
|
|
|
2274
2284
|
TERMOSTATO: "TERMOSTATO",
|
|
2275
2285
|
COMPRESSOR: "COMPRESSOR",
|
|
2276
2286
|
VENTILADOR: "VENTILADOR",
|
|
2277
|
-
SOLENOIDE: "SOLENOIDE"
|
|
2287
|
+
SOLENOIDE: "SOLENOIDE",
|
|
2288
|
+
GATEWAY: "GATEWAY"
|
|
2278
2289
|
};
|
|
2279
2290
|
var deviceIcons = {
|
|
2280
2291
|
ESCADA_ROLANTE: "https://dashboard.myio-bas.com/api/images/public/EJ997iB2HD1AYYUHwIloyQOOszeqb2jp",
|
|
@@ -2299,7 +2310,10 @@ var deviceIcons = {
|
|
|
2299
2310
|
COMPRESSOR: "https://dashboard.myio-bas.com/api/images/public/4BWMuVIFHnsfqatiV86DmTrOB7IF0X8Y",
|
|
2300
2311
|
VENTILADOR: "https://dashboard.myio-bas.com/api/images/public/Rge8Q3t0CP5PW8XyTn9bBK9aVP6uzSTT",
|
|
2301
2312
|
// 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"
|
|
2313
|
+
SOLENOIDE: "https://dashboard.myio-bas.com/api/images/public/Tnq47Vd1TxhhqhYoHvzS73WVh1X84fPa",
|
|
2314
|
+
// GATEWAY: central/gateway hardware (OrangePi) — used by SettingsModalView's
|
|
2315
|
+
// "Central" tab identity card. Previously fell through to DEFAULT_DEVICE_ICON.
|
|
2316
|
+
GATEWAY: "https://dashboard.myio-bas.com/api/images/public/kNlazDO8Yy90R5O12i17EqkQUFsNj44b"
|
|
2303
2317
|
};
|
|
2304
2318
|
var deviceIconLabels = {
|
|
2305
2319
|
ESCADA_ROLANTE: "Escada Rolante",
|
|
@@ -2322,7 +2336,8 @@ var deviceIconLabels = {
|
|
|
2322
2336
|
TERMOSTATO: "Termostato",
|
|
2323
2337
|
COMPRESSOR: "Compressor",
|
|
2324
2338
|
VENTILADOR: "Ventilador",
|
|
2325
|
-
SOLENOIDE: "Solenoide"
|
|
2339
|
+
SOLENOIDE: "Solenoide",
|
|
2340
|
+
GATEWAY: "Gateway"
|
|
2326
2341
|
};
|
|
2327
2342
|
var DEFAULT_DEVICE_ICON = "https://dashboard.myio-bas.com/api/images/public/f9Ce4meybsdaAhAkUlAfy5ei3I4kcN4k";
|
|
2328
2343
|
function getDeviceIcon(deviceProfile) {
|
|
@@ -5097,11 +5112,19 @@ var MODAL_HEADER_CSS = `
|
|
|
5097
5112
|
}
|
|
5098
5113
|
|
|
5099
5114
|
.myio-modal-header__title {
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5115
|
+
/* Rendered as an <h2> (see generateHTML() below) \u2014 on a real ThingsBoard
|
|
5116
|
+
dashboard (Angular Material) a global rule like ".mat-typography h2"
|
|
5117
|
+
has HIGHER specificity (class+element) than this single-class selector,
|
|
5118
|
+
regardless of load order, and wins with Material's own much larger
|
|
5119
|
+
font-size/line-height/margin \u2014 the header balloons in height there while
|
|
5120
|
+
looking fine in an isolated showcase page with no Material CSS at all.
|
|
5121
|
+
Same class of bug, same !important remedy already used in
|
|
5122
|
+
WelcomeModalView.ts's title rules. */
|
|
5123
|
+
margin: 0 !important;
|
|
5124
|
+
font-size: 16px !important;
|
|
5125
|
+
font-weight: 600 !important;
|
|
5103
5126
|
color: white;
|
|
5104
|
-
line-height: 1.4;
|
|
5127
|
+
line-height: 1.4 !important;
|
|
5105
5128
|
white-space: nowrap;
|
|
5106
5129
|
overflow: hidden;
|
|
5107
5130
|
text-overflow: ellipsis;
|
|
@@ -8527,7 +8550,7 @@ function buildMyioIngestionAuth(config2) {
|
|
|
8527
8550
|
const skewMs = renewSkewSeconds * 1e3;
|
|
8528
8551
|
return now() >= cache.expiresAt - skewMs;
|
|
8529
8552
|
}
|
|
8530
|
-
async function
|
|
8553
|
+
async function sleep2(ms) {
|
|
8531
8554
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8532
8555
|
}
|
|
8533
8556
|
async function requestNewToken() {
|
|
@@ -8571,7 +8594,7 @@ function buildMyioIngestionAuth(config2) {
|
|
|
8571
8594
|
throw error;
|
|
8572
8595
|
}
|
|
8573
8596
|
const backoff = retryBaseMs * Math.pow(2, attempt - 1);
|
|
8574
|
-
await
|
|
8597
|
+
await sleep2(backoff);
|
|
8575
8598
|
}
|
|
8576
8599
|
}
|
|
8577
8600
|
}
|
|
@@ -9837,6 +9860,392 @@ async function createAnnotationIndicator(config2) {
|
|
|
9837
9860
|
return indicator;
|
|
9838
9861
|
}
|
|
9839
9862
|
|
|
9863
|
+
// src/components/gcdr-annotations/v1.0.0/authStrategies.ts
|
|
9864
|
+
var ApiKeyAuth = class {
|
|
9865
|
+
constructor(apiKey) {
|
|
9866
|
+
this.apiKey = apiKey;
|
|
9867
|
+
}
|
|
9868
|
+
apply(headers) {
|
|
9869
|
+
headers["X-API-Key"] = this.apiKey;
|
|
9870
|
+
}
|
|
9871
|
+
};
|
|
9872
|
+
var BearerAuth = class {
|
|
9873
|
+
constructor(token) {
|
|
9874
|
+
this.token = token;
|
|
9875
|
+
}
|
|
9876
|
+
async apply(headers) {
|
|
9877
|
+
const resolved = typeof this.token === "function" ? await this.token() : this.token;
|
|
9878
|
+
headers["Authorization"] = `Bearer ${resolved}`;
|
|
9879
|
+
}
|
|
9880
|
+
};
|
|
9881
|
+
function createAuthStrategy(auth) {
|
|
9882
|
+
const hasApiKey = !!auth.apiKey;
|
|
9883
|
+
const hasBearer = auth.bearerToken !== void 0 && auth.bearerToken !== null && auth.bearerToken !== "";
|
|
9884
|
+
if (hasApiKey && hasBearer) {
|
|
9885
|
+
throw new Error(
|
|
9886
|
+
"GcdrAnnotationsClient: provide exactly one of auth.apiKey or auth.bearerToken, not both."
|
|
9887
|
+
);
|
|
9888
|
+
}
|
|
9889
|
+
if (hasApiKey) return new ApiKeyAuth(auth.apiKey);
|
|
9890
|
+
if (hasBearer) return new BearerAuth(auth.bearerToken);
|
|
9891
|
+
throw new Error("GcdrAnnotationsClient: auth.apiKey or auth.bearerToken is required.");
|
|
9892
|
+
}
|
|
9893
|
+
|
|
9894
|
+
// src/components/gcdr-annotations/v1.0.0/adapters.ts
|
|
9895
|
+
var GCDR_TO_LEGACY_TYPE = {
|
|
9896
|
+
observation: "observation",
|
|
9897
|
+
pending: "pending",
|
|
9898
|
+
maintenance: "maintenance",
|
|
9899
|
+
activity: "activity"
|
|
9900
|
+
};
|
|
9901
|
+
var LEGACY_TO_GCDR_TYPE = {
|
|
9902
|
+
observation: "observation",
|
|
9903
|
+
pending: "pending",
|
|
9904
|
+
maintenance: "maintenance",
|
|
9905
|
+
activity: "activity"
|
|
9906
|
+
};
|
|
9907
|
+
var KNOWN_AUDIT_ACTIONS = /* @__PURE__ */ new Set([
|
|
9908
|
+
"created",
|
|
9909
|
+
"modified",
|
|
9910
|
+
"archived",
|
|
9911
|
+
"approved",
|
|
9912
|
+
"rejected",
|
|
9913
|
+
"commented",
|
|
9914
|
+
"acknowledged"
|
|
9915
|
+
]);
|
|
9916
|
+
function toUserInfo(u) {
|
|
9917
|
+
return { id: u?.id ?? "", email: u?.email ?? "", name: u?.name ?? "" };
|
|
9918
|
+
}
|
|
9919
|
+
function deriveLegacyStatus(a) {
|
|
9920
|
+
if (a.finalized && a.finalizedReason === "archived") return "archived";
|
|
9921
|
+
return a.version > 1 ? "modified" : "created";
|
|
9922
|
+
}
|
|
9923
|
+
function adaptResponse(r, annotationId) {
|
|
9924
|
+
return {
|
|
9925
|
+
id: r.id,
|
|
9926
|
+
annotationId,
|
|
9927
|
+
type: r.type,
|
|
9928
|
+
text: r.text ?? "",
|
|
9929
|
+
createdAt: r.createdAt,
|
|
9930
|
+
createdBy: toUserInfo(r.createdBy)
|
|
9931
|
+
};
|
|
9932
|
+
}
|
|
9933
|
+
function adaptEvent(e) {
|
|
9934
|
+
const action = KNOWN_AUDIT_ACTIONS.has(e.action) ? e.action : "modified";
|
|
9935
|
+
return {
|
|
9936
|
+
timestamp: e.at,
|
|
9937
|
+
userId: e.by?.id ?? "",
|
|
9938
|
+
userName: e.by?.name ?? "",
|
|
9939
|
+
userEmail: e.by?.email ?? "",
|
|
9940
|
+
action
|
|
9941
|
+
};
|
|
9942
|
+
}
|
|
9943
|
+
function adaptGcdrToLegacyAnnotation(g) {
|
|
9944
|
+
const detail = g;
|
|
9945
|
+
const responses = (detail.responses ?? []).map((r) => adaptResponse(r, g.id));
|
|
9946
|
+
const history = (detail.events ?? []).map(adaptEvent);
|
|
9947
|
+
const approvedOrRejected = responses.find((r) => r.type === "approved" || r.type === "rejected");
|
|
9948
|
+
return {
|
|
9949
|
+
id: g.id,
|
|
9950
|
+
version: g.version,
|
|
9951
|
+
text: g.text,
|
|
9952
|
+
type: GCDR_TO_LEGACY_TYPE[g.type] ?? "observation",
|
|
9953
|
+
importance: g.importance,
|
|
9954
|
+
status: deriveLegacyStatus(g),
|
|
9955
|
+
createdAt: g.createdAt,
|
|
9956
|
+
dueDate: g.dueDate ?? void 0,
|
|
9957
|
+
createdBy: toUserInfo(g.createdBy),
|
|
9958
|
+
acknowledged: !!approvedOrRejected,
|
|
9959
|
+
acknowledgedBy: approvedOrRejected?.createdBy,
|
|
9960
|
+
acknowledgedAt: approvedOrRejected?.createdAt,
|
|
9961
|
+
responses,
|
|
9962
|
+
history
|
|
9963
|
+
};
|
|
9964
|
+
}
|
|
9965
|
+
function adaptGcdrListToLegacyAnnotations(list) {
|
|
9966
|
+
return list.map(adaptGcdrToLegacyAnnotation);
|
|
9967
|
+
}
|
|
9968
|
+
function adaptLegacyToGcdrInput(data, entity) {
|
|
9969
|
+
return {
|
|
9970
|
+
entityType: entity.entityType,
|
|
9971
|
+
entityId: entity.entityId,
|
|
9972
|
+
customerId: entity.customerId,
|
|
9973
|
+
text: data.text,
|
|
9974
|
+
type: LEGACY_TO_GCDR_TYPE[data.type] ?? "observation",
|
|
9975
|
+
importance: data.importance,
|
|
9976
|
+
dueDate: data.dueDate
|
|
9977
|
+
};
|
|
9978
|
+
}
|
|
9979
|
+
|
|
9980
|
+
// src/components/gcdr-annotations/v1.0.0/types.ts
|
|
9981
|
+
var GcdrAnnotationsError = class extends Error {
|
|
9982
|
+
status;
|
|
9983
|
+
code;
|
|
9984
|
+
constructor(status, message, code) {
|
|
9985
|
+
super(message || `HTTP_${status}`);
|
|
9986
|
+
this.name = "GcdrAnnotationsError";
|
|
9987
|
+
this.status = status;
|
|
9988
|
+
this.code = code;
|
|
9989
|
+
}
|
|
9990
|
+
};
|
|
9991
|
+
var GcdrAnnotationsConflictError = class extends GcdrAnnotationsError {
|
|
9992
|
+
finalizedOrStale = true;
|
|
9993
|
+
constructor(message) {
|
|
9994
|
+
super(409, message, "CONFLICT");
|
|
9995
|
+
this.name = "GcdrAnnotationsConflictError";
|
|
9996
|
+
}
|
|
9997
|
+
};
|
|
9998
|
+
|
|
9999
|
+
// src/components/gcdr-annotations/v1.0.0/GcdrAnnotationsClient.ts
|
|
10000
|
+
var DEFAULT_MAX_PAGES = 50;
|
|
10001
|
+
var DEFAULT_LIMIT = 100;
|
|
10002
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
10003
|
+
var DEFAULT_RETRY_BASE_MS = 500;
|
|
10004
|
+
function normalizeDomainPath(domainPath) {
|
|
10005
|
+
return String(domainPath).replace(/\/+$/, "");
|
|
10006
|
+
}
|
|
10007
|
+
function isNetworkError(err) {
|
|
10008
|
+
if (!err || typeof err !== "object") return false;
|
|
10009
|
+
const name = err.name ?? "";
|
|
10010
|
+
const message = String(err.message ?? "").toLowerCase();
|
|
10011
|
+
return name === "TypeError" || message.includes("network") || message.includes("failed to fetch");
|
|
10012
|
+
}
|
|
10013
|
+
function sleep(ms) {
|
|
10014
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10015
|
+
}
|
|
10016
|
+
var GcdrAnnotationsClient = class {
|
|
10017
|
+
domainPath;
|
|
10018
|
+
authStrategy;
|
|
10019
|
+
fetchImpl;
|
|
10020
|
+
logger;
|
|
10021
|
+
cacheTtlMs;
|
|
10022
|
+
maxPages;
|
|
10023
|
+
cache = /* @__PURE__ */ new Map();
|
|
10024
|
+
constructor(params) {
|
|
10025
|
+
if (!params || !params.domainPath) {
|
|
10026
|
+
throw new Error("GcdrAnnotationsClient requires a domainPath.");
|
|
10027
|
+
}
|
|
10028
|
+
this.domainPath = normalizeDomainPath(params.domainPath);
|
|
10029
|
+
this.authStrategy = createAuthStrategy(params.auth);
|
|
10030
|
+
const injected = params.fetchImpl || globalThis.fetch;
|
|
10031
|
+
if (typeof injected !== "function") {
|
|
10032
|
+
throw new Error("GcdrAnnotationsClient requires a fetch implementation.");
|
|
10033
|
+
}
|
|
10034
|
+
this.fetchImpl = params.fetchImpl ? params.fetchImpl : injected.bind(globalThis);
|
|
10035
|
+
this.logger = params.logger ?? console;
|
|
10036
|
+
this.cacheTtlMs = params.cacheTtlMs ?? 0;
|
|
10037
|
+
this.maxPages = params.maxPages ?? DEFAULT_MAX_PAGES;
|
|
10038
|
+
}
|
|
10039
|
+
/** Drops every cached GET response. Called automatically after any write. */
|
|
10040
|
+
invalidate() {
|
|
10041
|
+
this.cache.clear();
|
|
10042
|
+
}
|
|
10043
|
+
// ── Reads ──────────────────────────────────────────────────────────────
|
|
10044
|
+
/** One page of `GET /annotations` with the given filters. */
|
|
10045
|
+
async list(params = {}) {
|
|
10046
|
+
const query = new URLSearchParams();
|
|
10047
|
+
if (params.customerId) query.set("customerId", params.customerId);
|
|
10048
|
+
if (params.entityType) query.set("entityType", params.entityType);
|
|
10049
|
+
if (params.entityId) query.set("entityId", params.entityId);
|
|
10050
|
+
if (params.type) query.set("type", params.type);
|
|
10051
|
+
if (params.status) query.set("status", params.status);
|
|
10052
|
+
if (params.importance !== void 0) query.set("importance", String(params.importance));
|
|
10053
|
+
if (params.includeArchived !== void 0) query.set("includeArchived", String(params.includeArchived));
|
|
10054
|
+
query.set("page", String(params.page ?? 1));
|
|
10055
|
+
query.set("limit", String(params.limit ?? DEFAULT_LIMIT));
|
|
10056
|
+
const raw = await this._requestJson(`/annotations?${query.toString()}`, { method: "GET" });
|
|
10057
|
+
return this._toListPage(raw, params.page ?? 1, params.limit ?? DEFAULT_LIMIT);
|
|
10058
|
+
}
|
|
10059
|
+
/** Every annotation of a customer, aggregated across as many pages as it takes (capped at `maxPages`). */
|
|
10060
|
+
async listByCustomer(customerId, params = {}) {
|
|
10061
|
+
return this._listAll({ ...params, customerId });
|
|
10062
|
+
}
|
|
10063
|
+
/** Every annotation attached to one entity (e.g. a device, or a central identified by its GCDR UUID). */
|
|
10064
|
+
async listByEntity(entityType, entityId, params = {}) {
|
|
10065
|
+
return this._listAll({ ...params, entityType, entityId });
|
|
10066
|
+
}
|
|
10067
|
+
/** Full detail — responses, events (audit trail), mentions, attachments. */
|
|
10068
|
+
async getById(id) {
|
|
10069
|
+
const raw = await this._requestJson(`/annotations/${encodeURIComponent(id)}`, { method: "GET" });
|
|
10070
|
+
return this._unwrap(raw);
|
|
10071
|
+
}
|
|
10072
|
+
/** Convenience bridge — same `Annotation[]` shape `AnnotationsTab`/orchestrators already consume. */
|
|
10073
|
+
toLegacyAnnotations(list) {
|
|
10074
|
+
return adaptGcdrListToLegacyAnnotations(list);
|
|
10075
|
+
}
|
|
10076
|
+
// ── Writes ─────────────────────────────────────────────────────────────
|
|
10077
|
+
async create(input) {
|
|
10078
|
+
const raw = await this._requestJson("/annotations", {
|
|
10079
|
+
method: "POST",
|
|
10080
|
+
body: JSON.stringify(input)
|
|
10081
|
+
});
|
|
10082
|
+
this.invalidate();
|
|
10083
|
+
return this._unwrap(raw);
|
|
10084
|
+
}
|
|
10085
|
+
/** Optimistic lock via `If-Match: "<version>"` — a stale version raises `GcdrAnnotationsConflictError`. */
|
|
10086
|
+
async patch(id, changes, version2) {
|
|
10087
|
+
const raw = await this._requestJson(`/annotations/${encodeURIComponent(id)}`, {
|
|
10088
|
+
method: "PATCH",
|
|
10089
|
+
body: JSON.stringify(changes),
|
|
10090
|
+
headers: { "If-Match": `"${version2}"` }
|
|
10091
|
+
});
|
|
10092
|
+
this.invalidate();
|
|
10093
|
+
return this._unwrap(raw);
|
|
10094
|
+
}
|
|
10095
|
+
async archive(id, version2) {
|
|
10096
|
+
const raw = await this._requestJson(`/annotations/${encodeURIComponent(id)}/archive`, {
|
|
10097
|
+
method: "POST",
|
|
10098
|
+
headers: { "If-Match": `"${version2}"` }
|
|
10099
|
+
});
|
|
10100
|
+
this.invalidate();
|
|
10101
|
+
return this._unwrap(raw);
|
|
10102
|
+
}
|
|
10103
|
+
/**
|
|
10104
|
+
* Posts a response (comment/approve/reject/archive). `text` is required for
|
|
10105
|
+
* every type except `approved` — mirrored client-side so a caller gets an
|
|
10106
|
+
* immediate, local error instead of a round trip (API guide §5 rule).
|
|
10107
|
+
*/
|
|
10108
|
+
async respond(id, input) {
|
|
10109
|
+
if (input.type !== "approved" && !input.text) {
|
|
10110
|
+
throw new GcdrAnnotationsError(400, `respond(): 'text' is required for response type '${input.type}'.`);
|
|
10111
|
+
}
|
|
10112
|
+
const raw = await this._requestJson(`/annotations/${encodeURIComponent(id)}/responses`, {
|
|
10113
|
+
method: "POST",
|
|
10114
|
+
body: JSON.stringify(input)
|
|
10115
|
+
});
|
|
10116
|
+
this.invalidate();
|
|
10117
|
+
return this._unwrap(raw);
|
|
10118
|
+
}
|
|
10119
|
+
async mention(id, userId) {
|
|
10120
|
+
await this._requestJson(`/annotations/${encodeURIComponent(id)}/mentions`, {
|
|
10121
|
+
method: "POST",
|
|
10122
|
+
body: JSON.stringify({ userId })
|
|
10123
|
+
});
|
|
10124
|
+
this.invalidate();
|
|
10125
|
+
}
|
|
10126
|
+
async attach(id, attachment) {
|
|
10127
|
+
await this._requestJson(`/annotations/${encodeURIComponent(id)}/attachments`, {
|
|
10128
|
+
method: "POST",
|
|
10129
|
+
body: JSON.stringify(attachment)
|
|
10130
|
+
});
|
|
10131
|
+
this.invalidate();
|
|
10132
|
+
}
|
|
10133
|
+
async detach(id, attachmentId) {
|
|
10134
|
+
await this._requestJson(
|
|
10135
|
+
`/annotations/${encodeURIComponent(id)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
10136
|
+
{ method: "DELETE" }
|
|
10137
|
+
);
|
|
10138
|
+
this.invalidate();
|
|
10139
|
+
}
|
|
10140
|
+
// ── Internals ────────────────────────────────────────────────────────────
|
|
10141
|
+
async _listAll(params) {
|
|
10142
|
+
const limit = params.limit ?? DEFAULT_LIMIT;
|
|
10143
|
+
const out = [];
|
|
10144
|
+
for (let page = 1; page <= this.maxPages; page++) {
|
|
10145
|
+
const result = await this.list({ ...params, page, limit });
|
|
10146
|
+
out.push(...result.items);
|
|
10147
|
+
if (!result.pagination.hasNext) return out;
|
|
10148
|
+
if (page === this.maxPages) {
|
|
10149
|
+
this.logger.warn(
|
|
10150
|
+
`[GcdrAnnotationsClient] hit hard page cap of ${this.maxPages} \u2014 some annotations may be missing.`
|
|
10151
|
+
);
|
|
10152
|
+
}
|
|
10153
|
+
}
|
|
10154
|
+
return out;
|
|
10155
|
+
}
|
|
10156
|
+
_toListPage(raw, page, limit) {
|
|
10157
|
+
const body = raw;
|
|
10158
|
+
if (Array.isArray(body)) {
|
|
10159
|
+
return { items: body, pagination: { page, limit, hasNext: false } };
|
|
10160
|
+
}
|
|
10161
|
+
const items = Array.isArray(body?.data) ? body.data : [];
|
|
10162
|
+
const pagination = body?.pagination ?? {
|
|
10163
|
+
page,
|
|
10164
|
+
limit,
|
|
10165
|
+
hasNext: items.length >= limit
|
|
10166
|
+
};
|
|
10167
|
+
return { items, pagination };
|
|
10168
|
+
}
|
|
10169
|
+
/** Unwraps the `{success, data}` envelope; tolerates an already-flat body (test doubles, future API versions). */
|
|
10170
|
+
_unwrap(raw) {
|
|
10171
|
+
const envelope = raw;
|
|
10172
|
+
if (envelope && typeof envelope === "object" && "data" in envelope) {
|
|
10173
|
+
return envelope.data;
|
|
10174
|
+
}
|
|
10175
|
+
return raw;
|
|
10176
|
+
}
|
|
10177
|
+
async _requestJson(path, init) {
|
|
10178
|
+
const url = `${this.domainPath}${path.startsWith("/") ? path : `/${path}`}`;
|
|
10179
|
+
const method = init.method ?? "GET";
|
|
10180
|
+
const cacheKey = `${method}::${url}`;
|
|
10181
|
+
if (method === "GET" && this.cacheTtlMs > 0) {
|
|
10182
|
+
const cached = this.cache.get(cacheKey);
|
|
10183
|
+
if (cached && Date.now() - cached.at < this.cacheTtlMs) {
|
|
10184
|
+
return cached.value;
|
|
10185
|
+
}
|
|
10186
|
+
}
|
|
10187
|
+
const result = await this._doRequest(url, init);
|
|
10188
|
+
if (method === "GET" && this.cacheTtlMs > 0) {
|
|
10189
|
+
this.cache.set(cacheKey, { at: Date.now(), value: result });
|
|
10190
|
+
}
|
|
10191
|
+
return result;
|
|
10192
|
+
}
|
|
10193
|
+
async _doRequest(url, init) {
|
|
10194
|
+
let attempt = 0;
|
|
10195
|
+
while (true) {
|
|
10196
|
+
const headers = { Accept: "application/json", ...init.headers || {} };
|
|
10197
|
+
if (init.body !== void 0) headers["Content-Type"] = "application/json";
|
|
10198
|
+
await this.authStrategy.apply(headers);
|
|
10199
|
+
try {
|
|
10200
|
+
const res = await this.fetchImpl(url, { ...init, headers });
|
|
10201
|
+
if ((res.status === 429 || res.status >= 500) && attempt < DEFAULT_MAX_RETRIES) {
|
|
10202
|
+
const backoffMs = DEFAULT_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
10203
|
+
this.logger.warn(
|
|
10204
|
+
`[GcdrAnnotationsClient] ${res.status} on ${url} \u2014 retry ${attempt + 1}/${DEFAULT_MAX_RETRIES} in ${backoffMs}ms`
|
|
10205
|
+
);
|
|
10206
|
+
attempt++;
|
|
10207
|
+
await sleep(backoffMs);
|
|
10208
|
+
continue;
|
|
10209
|
+
}
|
|
10210
|
+
const body = await this._readBody(res);
|
|
10211
|
+
if (res.status === 409) {
|
|
10212
|
+
const err = body && body.error;
|
|
10213
|
+
throw new GcdrAnnotationsConflictError(err?.message || res.statusText);
|
|
10214
|
+
}
|
|
10215
|
+
if (!res.ok) {
|
|
10216
|
+
const err = body && body.error;
|
|
10217
|
+
throw new GcdrAnnotationsError(res.status, err?.message || res.statusText, err?.code);
|
|
10218
|
+
}
|
|
10219
|
+
return body;
|
|
10220
|
+
} catch (err) {
|
|
10221
|
+
if (err instanceof GcdrAnnotationsError) throw err;
|
|
10222
|
+
if (isNetworkError(err) && attempt < DEFAULT_MAX_RETRIES) {
|
|
10223
|
+
const backoffMs = DEFAULT_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
10224
|
+
this.logger.warn(
|
|
10225
|
+
`[GcdrAnnotationsClient] network error on ${url} \u2014 retry ${attempt + 1}/${DEFAULT_MAX_RETRIES} in ${backoffMs}ms`
|
|
10226
|
+
);
|
|
10227
|
+
attempt++;
|
|
10228
|
+
await sleep(backoffMs);
|
|
10229
|
+
continue;
|
|
10230
|
+
}
|
|
10231
|
+
throw new GcdrAnnotationsError(0, err instanceof Error ? err.message : "network error");
|
|
10232
|
+
}
|
|
10233
|
+
}
|
|
10234
|
+
}
|
|
10235
|
+
async _readBody(res) {
|
|
10236
|
+
try {
|
|
10237
|
+
const text2 = await res.text();
|
|
10238
|
+
if (!text2) return null;
|
|
10239
|
+
return JSON.parse(text2);
|
|
10240
|
+
} catch {
|
|
10241
|
+
return null;
|
|
10242
|
+
}
|
|
10243
|
+
}
|
|
10244
|
+
};
|
|
10245
|
+
function createGcdrAnnotationsClient(params) {
|
|
10246
|
+
return new GcdrAnnotationsClient(params);
|
|
10247
|
+
}
|
|
10248
|
+
|
|
9840
10249
|
// src/utils/devices/deviceType.js
|
|
9841
10250
|
function normalize(str) {
|
|
9842
10251
|
if (typeof str !== "string") return "";
|
|
@@ -61047,9 +61456,273 @@ function createTicketsTab(config2) {
|
|
|
61047
61456
|
return { destroy: () => tab.destroy() };
|
|
61048
61457
|
}
|
|
61049
61458
|
|
|
61459
|
+
// src/components/createInputDateRangePickerInsideDIV.ts
|
|
61460
|
+
var PREMIUM_STYLES = `
|
|
61461
|
+
.myio-daterange-wrapper {
|
|
61462
|
+
font-family: 'Nunito', system-ui, sans-serif;
|
|
61463
|
+
background: #f9f9f9;
|
|
61464
|
+
padding: 20px;
|
|
61465
|
+
border-radius: 8px;
|
|
61466
|
+
margin-bottom: 20px;
|
|
61467
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
61468
|
+
transition: all 0.2s ease;
|
|
61469
|
+
}
|
|
61470
|
+
|
|
61471
|
+
.myio-daterange-wrapper:hover {
|
|
61472
|
+
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
61473
|
+
}
|
|
61474
|
+
|
|
61475
|
+
.myio-daterange-label {
|
|
61476
|
+
display: block;
|
|
61477
|
+
margin-bottom: 8px;
|
|
61478
|
+
font-weight: 500;
|
|
61479
|
+
color: #333;
|
|
61480
|
+
font-size: 14px;
|
|
61481
|
+
line-height: 1.4;
|
|
61482
|
+
}
|
|
61483
|
+
|
|
61484
|
+
.myio-daterange-input {
|
|
61485
|
+
width: 100%;
|
|
61486
|
+
max-width: 300px;
|
|
61487
|
+
padding: 8px 12px;
|
|
61488
|
+
border: 1px solid #ddd;
|
|
61489
|
+
border-radius: 4px;
|
|
61490
|
+
background: white;
|
|
61491
|
+
font-size: 14px;
|
|
61492
|
+
color: #333;
|
|
61493
|
+
cursor: pointer;
|
|
61494
|
+
transition: all 0.2s ease;
|
|
61495
|
+
font-family: inherit;
|
|
61496
|
+
line-height: 1.4;
|
|
61497
|
+
}
|
|
61498
|
+
|
|
61499
|
+
.myio-daterange-input:hover {
|
|
61500
|
+
border-color: #4A148C;
|
|
61501
|
+
box-shadow: 0 0 0 2px rgba(74, 20, 140, 0.1);
|
|
61502
|
+
}
|
|
61503
|
+
|
|
61504
|
+
.myio-daterange-input:focus {
|
|
61505
|
+
outline: none;
|
|
61506
|
+
border-color: #4A148C;
|
|
61507
|
+
box-shadow: 0 0 0 3px rgba(74, 20, 140, 0.2);
|
|
61508
|
+
}
|
|
61509
|
+
|
|
61510
|
+
.myio-daterange-input::placeholder {
|
|
61511
|
+
color: #999;
|
|
61512
|
+
opacity: 1;
|
|
61513
|
+
}
|
|
61514
|
+
|
|
61515
|
+
.myio-daterange-helper {
|
|
61516
|
+
font-size: 12px;
|
|
61517
|
+
color: #666;
|
|
61518
|
+
margin-top: 4px;
|
|
61519
|
+
line-height: 1.3;
|
|
61520
|
+
transition: color 0.2s ease;
|
|
61521
|
+
}
|
|
61522
|
+
|
|
61523
|
+
.myio-daterange-helper.success {
|
|
61524
|
+
color: #28a745;
|
|
61525
|
+
font-weight: 500;
|
|
61526
|
+
}
|
|
61527
|
+
|
|
61528
|
+
.myio-daterange-helper.error {
|
|
61529
|
+
color: #dc3545;
|
|
61530
|
+
font-weight: 500;
|
|
61531
|
+
}
|
|
61532
|
+
|
|
61533
|
+
/* Responsive design */
|
|
61534
|
+
@media (max-width: 768px) {
|
|
61535
|
+
.myio-daterange-wrapper {
|
|
61536
|
+
padding: 16px;
|
|
61537
|
+
margin-bottom: 16px;
|
|
61538
|
+
}
|
|
61539
|
+
|
|
61540
|
+
.myio-daterange-input {
|
|
61541
|
+
max-width: 100%;
|
|
61542
|
+
font-size: 16px; /* Prevents zoom on iOS */
|
|
61543
|
+
}
|
|
61544
|
+
}
|
|
61545
|
+
|
|
61546
|
+
/* High contrast mode support */
|
|
61547
|
+
@media (prefers-contrast: high) {
|
|
61548
|
+
.myio-daterange-wrapper {
|
|
61549
|
+
border: 2px solid #000;
|
|
61550
|
+
}
|
|
61551
|
+
|
|
61552
|
+
.myio-daterange-input {
|
|
61553
|
+
border: 2px solid #000;
|
|
61554
|
+
}
|
|
61555
|
+
}
|
|
61556
|
+
|
|
61557
|
+
/* Reduced motion support */
|
|
61558
|
+
@media (prefers-reduced-motion: reduce) {
|
|
61559
|
+
.myio-daterange-wrapper,
|
|
61560
|
+
.myio-daterange-input,
|
|
61561
|
+
.myio-daterange-helper {
|
|
61562
|
+
transition: none;
|
|
61563
|
+
}
|
|
61564
|
+
}
|
|
61565
|
+
`;
|
|
61566
|
+
function injectPremiumStyles() {
|
|
61567
|
+
const styleId = "myio-daterange-premium-styles";
|
|
61568
|
+
if (document.getElementById(styleId)) {
|
|
61569
|
+
return;
|
|
61570
|
+
}
|
|
61571
|
+
const styleEl = document.createElement("style");
|
|
61572
|
+
styleEl.id = styleId;
|
|
61573
|
+
styleEl.textContent = PREMIUM_STYLES;
|
|
61574
|
+
document.head.appendChild(styleEl);
|
|
61575
|
+
console.log("[MyIO] Premium date range styles injected");
|
|
61576
|
+
}
|
|
61577
|
+
function validateId(id, context) {
|
|
61578
|
+
if (!id || typeof id !== "string") {
|
|
61579
|
+
throw new Error(`[createInputDateRangePickerInsideDIV] ${context} must be a non-empty string`);
|
|
61580
|
+
}
|
|
61581
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(id)) {
|
|
61582
|
+
throw new Error(`[createInputDateRangePickerInsideDIV] ${context} '${id}' is not a valid HTML ID`);
|
|
61583
|
+
}
|
|
61584
|
+
}
|
|
61585
|
+
async function createInputDateRangePickerInsideDIV(params) {
|
|
61586
|
+
const {
|
|
61587
|
+
containerId,
|
|
61588
|
+
inputId,
|
|
61589
|
+
label = "Per\xEDodo de Datas",
|
|
61590
|
+
placeholder = "Clique para selecionar per\xEDodo",
|
|
61591
|
+
pickerOptions = {},
|
|
61592
|
+
classNames = {},
|
|
61593
|
+
injectStyles: injectStyles23 = true,
|
|
61594
|
+
showHelper = true
|
|
61595
|
+
} = params;
|
|
61596
|
+
validateId(containerId, "containerId");
|
|
61597
|
+
validateId(inputId, "inputId");
|
|
61598
|
+
const container = document.getElementById(containerId);
|
|
61599
|
+
if (!container) {
|
|
61600
|
+
throw new Error(`[createInputDateRangePickerInsideDIV] Container '#${containerId}' not found`);
|
|
61601
|
+
}
|
|
61602
|
+
if (injectStyles23) {
|
|
61603
|
+
injectPremiumStyles();
|
|
61604
|
+
}
|
|
61605
|
+
let inputEl = document.getElementById(inputId);
|
|
61606
|
+
if (inputEl && inputEl.tagName.toLowerCase() !== "input") {
|
|
61607
|
+
throw new Error(`[createInputDateRangePickerInsideDIV] Element '#${inputId}' exists but is not an input element`);
|
|
61608
|
+
}
|
|
61609
|
+
const wrapper = document.createElement("div");
|
|
61610
|
+
wrapper.className = classNames.wrapper || "myio-daterange-wrapper";
|
|
61611
|
+
wrapper.setAttribute("data-myio-component", "daterange-input");
|
|
61612
|
+
wrapper.setAttribute("data-version", "1.0.0");
|
|
61613
|
+
let labelEl = null;
|
|
61614
|
+
if (label) {
|
|
61615
|
+
labelEl = document.createElement("label");
|
|
61616
|
+
labelEl.className = classNames.label || "myio-daterange-label";
|
|
61617
|
+
labelEl.textContent = label;
|
|
61618
|
+
labelEl.setAttribute("for", inputId);
|
|
61619
|
+
wrapper.appendChild(labelEl);
|
|
61620
|
+
}
|
|
61621
|
+
if (!inputEl) {
|
|
61622
|
+
inputEl = document.createElement("input");
|
|
61623
|
+
inputEl.type = "text";
|
|
61624
|
+
inputEl.id = inputId;
|
|
61625
|
+
inputEl.name = inputId;
|
|
61626
|
+
}
|
|
61627
|
+
inputEl.className = classNames.input || "myio-daterange-input";
|
|
61628
|
+
inputEl.readOnly = true;
|
|
61629
|
+
inputEl.placeholder = placeholder;
|
|
61630
|
+
inputEl.autocomplete = "off";
|
|
61631
|
+
inputEl.setAttribute("aria-label", label || "Date range selector");
|
|
61632
|
+
if (showHelper) {
|
|
61633
|
+
inputEl.setAttribute("aria-describedby", `${inputId}-helper`);
|
|
61634
|
+
}
|
|
61635
|
+
let helperEl = null;
|
|
61636
|
+
if (showHelper) {
|
|
61637
|
+
helperEl = document.createElement("div");
|
|
61638
|
+
helperEl.id = `${inputId}-helper`;
|
|
61639
|
+
helperEl.className = classNames.helper || "myio-daterange-helper";
|
|
61640
|
+
helperEl.setAttribute("aria-live", "polite");
|
|
61641
|
+
helperEl.style.display = "flex";
|
|
61642
|
+
helperEl.style.alignItems = "center";
|
|
61643
|
+
}
|
|
61644
|
+
wrapper.appendChild(inputEl);
|
|
61645
|
+
if (helperEl) {
|
|
61646
|
+
wrapper.appendChild(helperEl);
|
|
61647
|
+
}
|
|
61648
|
+
const existingWrapper = container.querySelector('[data-myio-component="daterange-input"]');
|
|
61649
|
+
if (existingWrapper) {
|
|
61650
|
+
console.warn(`[createInputDateRangePickerInsideDIV] Replacing existing daterange input in container '#${containerId}'`);
|
|
61651
|
+
existingWrapper.remove();
|
|
61652
|
+
}
|
|
61653
|
+
container.appendChild(wrapper);
|
|
61654
|
+
const enhancedOptions = {
|
|
61655
|
+
maxRangeDays: 31,
|
|
61656
|
+
onApply: (result) => {
|
|
61657
|
+
if (helperEl) {
|
|
61658
|
+
const startDate = new Date(result.startISO);
|
|
61659
|
+
const endDate = new Date(result.endISO);
|
|
61660
|
+
const days = Math.ceil((endDate.getTime() - startDate.getTime()) / (1e3 * 60 * 60 * 24)) + 1;
|
|
61661
|
+
helperEl.textContent = `Per\xEDodo selecionado: ${days} dia${days !== 1 ? "s" : ""}`;
|
|
61662
|
+
helperEl.className = (classNames.helper || "myio-daterange-helper") + " success";
|
|
61663
|
+
setTimeout(() => {
|
|
61664
|
+
if (helperEl) {
|
|
61665
|
+
helperEl.className = classNames.helper || "myio-daterange-helper";
|
|
61666
|
+
}
|
|
61667
|
+
}, 3e3);
|
|
61668
|
+
}
|
|
61669
|
+
if (pickerOptions.onApply) {
|
|
61670
|
+
pickerOptions.onApply(result);
|
|
61671
|
+
}
|
|
61672
|
+
},
|
|
61673
|
+
...pickerOptions
|
|
61674
|
+
};
|
|
61675
|
+
let picker;
|
|
61676
|
+
try {
|
|
61677
|
+
picker = await createDateRangePicker2(inputEl, enhancedOptions);
|
|
61678
|
+
console.log(`[createInputDateRangePickerInsideDIV] Successfully initialized for input '#${inputId}'`);
|
|
61679
|
+
} catch (error) {
|
|
61680
|
+
wrapper.remove();
|
|
61681
|
+
throw new Error(`[createInputDateRangePickerInsideDIV] Failed to initialize date picker: ${error.message}`);
|
|
61682
|
+
}
|
|
61683
|
+
const controller = {
|
|
61684
|
+
input: inputEl,
|
|
61685
|
+
container,
|
|
61686
|
+
wrapper,
|
|
61687
|
+
picker,
|
|
61688
|
+
getDisplayValue: () => inputEl.value,
|
|
61689
|
+
getDates: () => picker.getDates(),
|
|
61690
|
+
setDates: (startISO, endISO) => {
|
|
61691
|
+
try {
|
|
61692
|
+
picker.setDates(startISO, endISO);
|
|
61693
|
+
} catch (error) {
|
|
61694
|
+
console.error(`[createInputDateRangePickerInsideDIV] Error setting dates:`, error);
|
|
61695
|
+
throw error;
|
|
61696
|
+
}
|
|
61697
|
+
},
|
|
61698
|
+
setHelperText: (text2, type = "default") => {
|
|
61699
|
+
if (helperEl) {
|
|
61700
|
+
helperEl.textContent = text2;
|
|
61701
|
+
const baseClass = classNames.helper || "myio-daterange-helper";
|
|
61702
|
+
helperEl.className = type === "default" ? baseClass : `${baseClass} ${type}`;
|
|
61703
|
+
}
|
|
61704
|
+
},
|
|
61705
|
+
destroy: () => {
|
|
61706
|
+
try {
|
|
61707
|
+
picker.destroy();
|
|
61708
|
+
console.log(`[createInputDateRangePickerInsideDIV] Date picker destroyed for input '#${inputId}'`);
|
|
61709
|
+
} catch (error) {
|
|
61710
|
+
console.warn(`[createInputDateRangePickerInsideDIV] Error destroying picker:`, error);
|
|
61711
|
+
}
|
|
61712
|
+
try {
|
|
61713
|
+
wrapper.remove();
|
|
61714
|
+
console.log(`[createInputDateRangePickerInsideDIV] Wrapper removed for input '#${inputId}'`);
|
|
61715
|
+
} catch (error) {
|
|
61716
|
+
console.warn(`[createInputDateRangePickerInsideDIV] Error removing wrapper:`, error);
|
|
61717
|
+
}
|
|
61718
|
+
}
|
|
61719
|
+
};
|
|
61720
|
+
return controller;
|
|
61721
|
+
}
|
|
61722
|
+
|
|
61050
61723
|
// src/components/premium-modals/settings/SettingsModalView.ts
|
|
61051
61724
|
var ALLOWED_EMAIL_DOMAIN = "@myio.com.br";
|
|
61052
|
-
var SettingsModalView = class {
|
|
61725
|
+
var SettingsModalView = class _SettingsModalView {
|
|
61053
61726
|
container;
|
|
61054
61727
|
modal;
|
|
61055
61728
|
form;
|
|
@@ -61064,14 +61737,24 @@ var SettingsModalView = class {
|
|
|
61064
61737
|
chamadosTabHandle = null;
|
|
61065
61738
|
// Exclusão de Grupos tab
|
|
61066
61739
|
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
61740
|
currentTab = "general";
|
|
61070
61741
|
currentUser = null;
|
|
61071
61742
|
permissions = null;
|
|
61072
61743
|
// RFC-0190: Exclude Groups Totals
|
|
61073
61744
|
excludeGroupsEnabled = false;
|
|
61074
61745
|
excludedGroups = [];
|
|
61746
|
+
// RFC-0232: Incidentes tab filters (search/devices/period) — client-side
|
|
61747
|
+
// state, all derived from `config.interpolatedSlots` (the host's one
|
|
61748
|
+
// pre-fetch). `incidentsIdSuffix` is only needed because
|
|
61749
|
+
// createInputDateRangePickerInsideDIV() does a global `document.getElementById`
|
|
61750
|
+
// internally — every other Incidentes element is found via `this.modal.querySelector`,
|
|
61751
|
+
// which is safely scoped even with duplicate ids across simultaneous instances.
|
|
61752
|
+
incidentsIdSuffix = Math.random().toString(36).slice(2, 10);
|
|
61753
|
+
incidentsAllSlots = [];
|
|
61754
|
+
incidentsSelectedDevices = /* @__PURE__ */ new Set();
|
|
61755
|
+
incidentsSearch = "";
|
|
61756
|
+
incidentsDateRange = { startISO: null, endISO: null };
|
|
61757
|
+
incidentsDateRangeController = null;
|
|
61075
61758
|
constructor(config2) {
|
|
61076
61759
|
this.config = config2;
|
|
61077
61760
|
this.createModal();
|
|
@@ -61132,6 +61815,10 @@ var SettingsModalView = class {
|
|
|
61132
61815
|
this.initAlarmsTab();
|
|
61133
61816
|
this.initChamadosTab();
|
|
61134
61817
|
this.initExclusionGroupsTab();
|
|
61818
|
+
if (this.config.isGateway && this.isSuperAdmin()) {
|
|
61819
|
+
this.bindIncidentsTabEvents();
|
|
61820
|
+
this.initIncidentsDateRange();
|
|
61821
|
+
}
|
|
61135
61822
|
this._updateTabBadges(initialData);
|
|
61136
61823
|
}
|
|
61137
61824
|
// RFC-0104: Initialize the Annotations Tab
|
|
@@ -61355,13 +62042,13 @@ var SettingsModalView = class {
|
|
|
61355
62042
|
const alarmsContent = this.modal.querySelector("#alarms-tab-content");
|
|
61356
62043
|
const chamadosContent = this.modal.querySelector("#chamados-tab-content");
|
|
61357
62044
|
const exclusionGroupsContent = this.modal.querySelector("#exclusion-groups-tab-content");
|
|
61358
|
-
const
|
|
62045
|
+
const incidentsContent = this.modal.querySelector("#incidents-tab-content");
|
|
61359
62046
|
if (generalContent) generalContent.style.display = tab === "general" ? "block" : "none";
|
|
61360
62047
|
if (annotationsContent) annotationsContent.style.display = tab === "annotations" ? "block" : "none";
|
|
61361
62048
|
if (alarmsContent) alarmsContent.style.display = tab === "alarms" ? "block" : "none";
|
|
61362
62049
|
if (chamadosContent) chamadosContent.style.display = tab === "chamados" ? "block" : "none";
|
|
61363
62050
|
if (exclusionGroupsContent) exclusionGroupsContent.style.display = tab === "exclusion-groups" ? "block" : "none";
|
|
61364
|
-
if (
|
|
62051
|
+
if (incidentsContent) incidentsContent.style.display = tab === "incidents" ? "block" : "none";
|
|
61365
62052
|
const saveBtn = this.modal.querySelector(".btn-save");
|
|
61366
62053
|
if (saveBtn) saveBtn.style.display = tab === "general" ? "inline-flex" : "none";
|
|
61367
62054
|
if (tab === "annotations" && this.annotationsTab) {
|
|
@@ -61386,6 +62073,10 @@ var SettingsModalView = class {
|
|
|
61386
62073
|
this.exclusionGroupsTab.destroy();
|
|
61387
62074
|
this.exclusionGroupsTab = null;
|
|
61388
62075
|
}
|
|
62076
|
+
if (this.incidentsDateRangeController) {
|
|
62077
|
+
this.incidentsDateRangeController.destroy();
|
|
62078
|
+
this.incidentsDateRangeController = null;
|
|
62079
|
+
}
|
|
61389
62080
|
if (this.originalActiveElement && "focus" in this.originalActiveElement) {
|
|
61390
62081
|
this.originalActiveElement.focus();
|
|
61391
62082
|
}
|
|
@@ -61552,15 +62243,15 @@ var SettingsModalView = class {
|
|
|
61552
62243
|
Excluir Grupos
|
|
61553
62244
|
<span class="modal-tab-badge modal-tab-badge--exclusion" id="tab-badge-exclusion-groups" style="display:none"></span>
|
|
61554
62245
|
</button>
|
|
61555
|
-
<!--
|
|
61556
|
-
${this.config.isGateway ? `<button type="button" class="modal-tab" data-tab="
|
|
62246
|
+
<!-- RFC-0232: Incidentes (interpola\xE7\xE3o) \u2014 central + admin MyIO only -->
|
|
62247
|
+
${this.config.isGateway && this.isSuperAdmin() ? `<button type="button" class="modal-tab" data-tab="incidents">
|
|
61557
62248
|
<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>
|
|
62249
|
+
<path d="M12 9v4"></path>
|
|
62250
|
+
<path d="M12 17h.01"></path>
|
|
62251
|
+
<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
62252
|
</svg>
|
|
61563
|
-
|
|
62253
|
+
Incidentes
|
|
62254
|
+
${(this.config.interpolatedSlots?.length ?? 0) > 0 ? `<span class="modal-tab-badge modal-tab-badge--incidents">${this.config.interpolatedSlots.length}</span>` : ""}
|
|
61564
62255
|
</button>` : ""}
|
|
61565
62256
|
</div>
|
|
61566
62257
|
<div class="modal-body">
|
|
@@ -61599,8 +62290,10 @@ var SettingsModalView = class {
|
|
|
61599
62290
|
<p>Carregando configura\xE7\xF5es de exclus\xE3o...</p>
|
|
61600
62291
|
</div>
|
|
61601
62292
|
</div>
|
|
61602
|
-
<!--
|
|
61603
|
-
|
|
62293
|
+
<!-- RFC-0232: Incidentes (interpola\xE7\xE3o) Tab Content \u2014 central + admin MyIO only.
|
|
62294
|
+
Rendered synchronously (data comes pre-fetched via config.interpolatedSlots,
|
|
62295
|
+
same as getGatewayInfoHTML() \u2014 no async loading state needed here). -->
|
|
62296
|
+
${this.config.isGateway && this.isSuperAdmin() ? `<div id="incidents-tab-content" class="tab-content" style="display: none;">${this.getInterpolationIncidentsHTML()}</div>` : ""}
|
|
61604
62297
|
</div>
|
|
61605
62298
|
<div class="modal-footer">
|
|
61606
62299
|
<button type="button" class="btn-cancel">Fechar</button>
|
|
@@ -61684,6 +62377,10 @@ var SettingsModalView = class {
|
|
|
61684
62377
|
</div>
|
|
61685
62378
|
</div>
|
|
61686
62379
|
|
|
62380
|
+
<!-- Central/gateway identity+telemetry \u2014 only for CentralSettingsModal (isGateway);
|
|
62381
|
+
rendered directly in Geral instead of its own tab, right after the identity card. -->
|
|
62382
|
+
${this.config.isGateway ? this.getGatewayInfoHTML() : ""}
|
|
62383
|
+
|
|
61687
62384
|
<!-- Bottom Row: Connection Info spanning full width -->
|
|
61688
62385
|
${this.getConnectionInfoHTML()}
|
|
61689
62386
|
|
|
@@ -61888,70 +62585,450 @@ var SettingsModalView = class {
|
|
|
61888
62585
|
parts.push(`${m}min`);
|
|
61889
62586
|
return parts.join(" ");
|
|
61890
62587
|
}
|
|
62588
|
+
/** pt-BR absolute timestamp WITH seconds, ISO-string input (mirrors the
|
|
62589
|
+
* seconds-precision formatting `getConnectionInfoHTML()` uses for its
|
|
62590
|
+
* disconnect-interval row). */
|
|
62591
|
+
formatIsoWithSeconds(iso) {
|
|
62592
|
+
if (!iso) return "\u2014";
|
|
62593
|
+
const ms = Date.parse(iso);
|
|
62594
|
+
if (Number.isNaN(ms)) return "\u2014";
|
|
62595
|
+
return new Date(ms).toLocaleString("pt-BR", {
|
|
62596
|
+
day: "2-digit",
|
|
62597
|
+
month: "2-digit",
|
|
62598
|
+
year: "numeric",
|
|
62599
|
+
hour: "2-digit",
|
|
62600
|
+
minute: "2-digit",
|
|
62601
|
+
second: "2-digit"
|
|
62602
|
+
});
|
|
62603
|
+
}
|
|
62604
|
+
/** "(Xd:YYhs:YYmins atrás)"-style relative time, ISO-string input — mirrors
|
|
62605
|
+
* the device "Conectado desde" pattern in `getConnectionInfoHTML()`. */
|
|
62606
|
+
formatIsoRelativeDetailed(iso) {
|
|
62607
|
+
const ms = iso ? Date.parse(iso) : NaN;
|
|
62608
|
+
if (Number.isNaN(ms)) return "";
|
|
62609
|
+
const diffMinutes = Math.floor((Date.now() - ms) / 6e4);
|
|
62610
|
+
const diffHours = Math.floor(diffMinutes / 60);
|
|
62611
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
62612
|
+
if (diffDays > 0) {
|
|
62613
|
+
return `(${diffDays}d:${String(diffHours % 24).padStart(2, "0")}hs:${String(diffMinutes % 60).padStart(2, "0")}mins atr\xE1s)`;
|
|
62614
|
+
}
|
|
62615
|
+
if (diffHours > 0) return `(${diffHours}hs:${String(diffMinutes % 60).padStart(2, "0")}mins atr\xE1s)`;
|
|
62616
|
+
if (diffMinutes > 0) return `(${diffMinutes}mins atr\xE1s)`;
|
|
62617
|
+
return "(agora)";
|
|
62618
|
+
}
|
|
62619
|
+
/** "(Xd/h/min atrás)"-style relative time, ISO-string input — mirrors the
|
|
62620
|
+
* device "Último check status" pattern in `getConnectionInfoHTML()`. */
|
|
62621
|
+
formatIsoRelativeSimple(iso) {
|
|
62622
|
+
const ms = iso ? Date.parse(iso) : NaN;
|
|
62623
|
+
if (Number.isNaN(ms)) return "";
|
|
62624
|
+
const diffMinutes = Math.floor((Date.now() - ms) / 6e4);
|
|
62625
|
+
const diffHours = Math.floor(diffMinutes / 60);
|
|
62626
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
62627
|
+
if (diffDays > 0) return `(${diffDays}d atr\xE1s)`;
|
|
62628
|
+
if (diffHours > 0) return `(${diffHours}h atr\xE1s)`;
|
|
62629
|
+
if (diffMinutes > 0) return `(${diffMinutes}min atr\xE1s)`;
|
|
62630
|
+
return "(agora)";
|
|
62631
|
+
}
|
|
61891
62632
|
/**
|
|
61892
62633
|
* Central/gateway identity + telemetry — read-only, sourced 1:1 from
|
|
61893
|
-
* GCDR's `GET /api/v1/centrals/:id`.
|
|
61894
|
-
*
|
|
61895
|
-
*
|
|
61896
|
-
*
|
|
61897
|
-
*
|
|
61898
|
-
|
|
62634
|
+
* GCDR's `GET /api/v1/centrals/:id`. Rendered inline in the Geral tab
|
|
62635
|
+
* (right after the identity card) rather than its own tab — a real device
|
|
62636
|
+
* never sets `isGateway`, so this is fully inert for the device settings
|
|
62637
|
+
* modal. Every value carries a stable `id="gwinfo-<key>"` so a host
|
|
62638
|
+
* (CentralSettingsModal) can patch `.textContent` after an async fetch
|
|
62639
|
+
* resolves — this method itself only runs once, at construction time
|
|
62640
|
+
* (createModal() is never re-invoked), so it can't pick up data that
|
|
62641
|
+
* arrives after the modal is already built.
|
|
62642
|
+
*/
|
|
62643
|
+
/**
|
|
62644
|
+
* "Status" text/color mapping for `GatewayInfo.connectionStatus`
|
|
62645
|
+
* (ONLINE|OFFLINE|DEGRADED|MAINTENANCE) — same visual vocabulary as
|
|
62646
|
+
* `getConnectionInfoHTML()`'s device `statusMap` (ONLINE/OFFLINE stay
|
|
62647
|
+
* ALL-CAPS, DEGRADED reads "Atenção"). Exposed as a static so
|
|
62648
|
+
* `CentralSettingsModal.patchGatewayInfoDom()` can mirror it exactly when
|
|
62649
|
+
* an async `onFetchSettings` re-renders this card's values.
|
|
62650
|
+
*/
|
|
62651
|
+
static GATEWAY_CONN_STATUS_MAP = {
|
|
62652
|
+
ONLINE: { text: "ONLINE", color: "#22c55e" },
|
|
62653
|
+
OFFLINE: { text: "OFFLINE", color: "#ef4444" },
|
|
62654
|
+
DEGRADED: { text: "Aten\xE7\xE3o", color: "#f59e0b" },
|
|
62655
|
+
MAINTENANCE: { text: "Manuten\xE7\xE3o", color: "#3b82f6" }
|
|
62656
|
+
};
|
|
61899
62657
|
getGatewayInfoHTML() {
|
|
61900
62658
|
const g = this.config.gatewayInfo || {};
|
|
61901
62659
|
const bool = (v) => v == null ? "\u2014" : v ? "Sim" : "N\xE3o";
|
|
61902
62660
|
const num = (v, suffix = "") => v == null ? "\u2014" : `${v}${suffix}`;
|
|
61903
62661
|
const txt = (v) => v == null || v === "" ? "\u2014" : v;
|
|
61904
|
-
const
|
|
61905
|
-
<
|
|
61906
|
-
<
|
|
62662
|
+
const infoRow = (label, key, valueHtml, fullWidth = false) => `<div class="info-row${fullWidth ? " full-width" : ""}">
|
|
62663
|
+
<span class="info-label">${label}</span>
|
|
62664
|
+
<span class="info-value" id="gwinfo-${key}">${valueHtml}</span>
|
|
61907
62665
|
</div>`;
|
|
62666
|
+
const connStatusInfo = _SettingsModalView.GATEWAY_CONN_STATUS_MAP[(g.connectionStatus || "").toUpperCase()] || {
|
|
62667
|
+
text: txt(g.connectionStatus),
|
|
62668
|
+
color: "#6b7280"
|
|
62669
|
+
};
|
|
62670
|
+
const isOnline = (g.connectionStatus || "").toUpperCase() === "ONLINE";
|
|
62671
|
+
const connectedSinceAbs = isOnline ? this.formatIso(g.lastGatewaySuccessCheckAt) : "\u2014";
|
|
62672
|
+
const connectedSinceRel = isOnline ? this.formatIsoRelativeDetailed(g.lastGatewaySuccessCheckAt) : "";
|
|
62673
|
+
const lastCheckAbs = this.formatIso(g.lastGatewayCheckAt);
|
|
62674
|
+
const lastCheckRel = this.formatIsoRelativeSimple(g.lastGatewayCheckAt);
|
|
62675
|
+
const lc = g.lastConsumptionTelemetry;
|
|
62676
|
+
const consumptionText = lc ? `${lc.value.toLocaleString("pt-BR")}${lc.unit ? ` ${lc.unit}` : ""} - ${this.formatIsoWithSeconds(
|
|
62677
|
+
lc.timestamp
|
|
62678
|
+
)} ${this.formatIsoRelativeSimple(lc.timestamp)}`.trim() : "\u2014";
|
|
62679
|
+
const connectionInfoCard = `
|
|
62680
|
+
<div class="form-card info-card-wide">
|
|
62681
|
+
<h4 class="section-title">
|
|
62682
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="vertical-align: text-bottom; margin-right: 6px;">
|
|
62683
|
+
<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"/>
|
|
62684
|
+
<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"/>
|
|
62685
|
+
</svg>
|
|
62686
|
+
Informa\xE7\xF5es de Conex\xE3o
|
|
62687
|
+
</h4>
|
|
62688
|
+
<div class="info-grid">
|
|
62689
|
+
${infoRow("Central:", "connCentral", txt(this.config.deviceLabel))}
|
|
62690
|
+
${infoRow(
|
|
62691
|
+
"Status:",
|
|
62692
|
+
"connectionStatus",
|
|
62693
|
+
`<span style="color: ${connStatusInfo.color}; font-weight: 600;">${connStatusInfo.text}</span>`
|
|
62694
|
+
)}
|
|
62695
|
+
${infoRow(
|
|
62696
|
+
"Conectado desde:",
|
|
62697
|
+
"lastGatewaySuccessCheckAt",
|
|
62698
|
+
`${connectedSinceAbs}${connectedSinceRel ? ` <span class="time-since">${connectedSinceRel}</span>` : ""}`
|
|
62699
|
+
)}
|
|
62700
|
+
${infoRow(
|
|
62701
|
+
"\xDAltimo check status:",
|
|
62702
|
+
"lastGatewayCheckAt",
|
|
62703
|
+
`${lastCheckAbs}${lastCheckRel ? ` <span class="time-since">${lastCheckRel}</span>` : ""}`
|
|
62704
|
+
)}
|
|
62705
|
+
${infoRow("\xDAltimo intervalo desconectado:", "disconnectInterval", "\u2014", true)}
|
|
62706
|
+
${infoRow("\xDAltima Telemetria de Consumo:", "lastConsumptionTelemetry", consumptionText, true)}
|
|
62707
|
+
${infoRow("Monitoramento habilitado:", "monitoringEnabled", bool(g.monitoringEnabled))}
|
|
62708
|
+
${infoRow("Lat\xEAncia:", "lastGatewayCheckLatencyMs", num(g.lastGatewayCheckLatencyMs, "ms"))}
|
|
62709
|
+
${infoRow("Resultado do probe:", "probeResult", txt(g.probeResult))}
|
|
62710
|
+
</div>
|
|
62711
|
+
</div>
|
|
62712
|
+
`;
|
|
61908
62713
|
return `
|
|
62714
|
+
${connectionInfoCard}
|
|
61909
62715
|
<div class="gateway-info-grid">
|
|
61910
|
-
<div class="form-card gateway-info-card">
|
|
62716
|
+
<div class="form-card gateway-info-card info-card-wide">
|
|
61911
62717
|
<h4 class="section-title">Identifica\xE7\xE3o</h4>
|
|
61912
|
-
<div class="
|
|
61913
|
-
${
|
|
61914
|
-
${
|
|
61915
|
-
${
|
|
61916
|
-
${
|
|
61917
|
-
${
|
|
61918
|
-
${
|
|
61919
|
-
${
|
|
61920
|
-
${
|
|
62718
|
+
<div class="info-grid">
|
|
62719
|
+
${infoRow("UUID:", "uuid", txt(this.config.deviceId))}
|
|
62720
|
+
${infoRow("Serial Number:", "serialNumber", txt(g.serialNumber))}
|
|
62721
|
+
${infoRow("Hardware ID:", "hardwareId", txt(g.hardwareId))}
|
|
62722
|
+
${infoRow("Tipo:", "type", txt(g.type))}
|
|
62723
|
+
${infoRow("Status (cadastro):", "status", txt(g.status))}
|
|
62724
|
+
${infoRow("Firmware:", "firmwareVersion", txt(g.firmwareVersion))}
|
|
62725
|
+
${infoRow("Software:", "softwareVersion", txt(g.softwareVersion))}
|
|
62726
|
+
${infoRow("Frequ\xEAncia (canal):", "frequency", num(g.frequency))}
|
|
61921
62727
|
</div>
|
|
61922
62728
|
</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">
|
|
62729
|
+
<div class="form-card gateway-info-card info-card-wide">
|
|
61935
62730
|
<h4 class="section-title">Estat\xEDsticas</h4>
|
|
61936
|
-
<div class="
|
|
61937
|
-
${
|
|
61938
|
-
${
|
|
61939
|
-
${
|
|
61940
|
-
${
|
|
61941
|
-
${
|
|
62731
|
+
<div class="info-grid">
|
|
62732
|
+
${infoRow("Dispositivos conectados:", "statsConnectedDevices", num(g.stats?.connectedDevices))}
|
|
62733
|
+
${infoRow("Regras ativas:", "statsActiveRules", num(g.stats?.activeRules))}
|
|
62734
|
+
${infoRow("Eventos de sync pendentes:", "statsPendingSyncEvents", num(g.stats?.pendingSyncEvents))}
|
|
62735
|
+
${infoRow("Uptime:", "statsUptimeSeconds", this.formatUptime(g.stats?.uptimeSeconds))}
|
|
62736
|
+
${infoRow("\xDAltimo heartbeat:", "statsLastHeartbeatAt", this.formatIso(g.stats?.lastHeartbeatAt))}
|
|
61942
62737
|
</div>
|
|
61943
62738
|
</div>
|
|
61944
|
-
<div class="form-card gateway-info-card">
|
|
62739
|
+
<div class="form-card gateway-info-card info-card-wide">
|
|
61945
62740
|
<h4 class="section-title">Metadados</h4>
|
|
61946
|
-
<div class="
|
|
61947
|
-
${
|
|
61948
|
-
${
|
|
61949
|
-
${
|
|
62741
|
+
<div class="info-grid">
|
|
62742
|
+
${infoRow("Criado em:", "createdAt", this.formatIso(g.createdAt))}
|
|
62743
|
+
${infoRow("Atualizado em:", "updatedAt", this.formatIso(g.updatedAt))}
|
|
62744
|
+
${infoRow("Vers\xE3o:", "version", num(g.version))}
|
|
61950
62745
|
</div>
|
|
61951
62746
|
</div>
|
|
61952
62747
|
</div>
|
|
61953
62748
|
`;
|
|
61954
62749
|
}
|
|
62750
|
+
escHtml(v) {
|
|
62751
|
+
return String(v ?? "").replace(
|
|
62752
|
+
/[&<>"']/g,
|
|
62753
|
+
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]
|
|
62754
|
+
);
|
|
62755
|
+
}
|
|
62756
|
+
incidentsDeviceKey(s) {
|
|
62757
|
+
return s.deviceName || `slave ${s.slaveId}`;
|
|
62758
|
+
}
|
|
62759
|
+
incidentsFmtHour(iso) {
|
|
62760
|
+
return new Date(iso).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo", hour: "2-digit", minute: "2-digit" });
|
|
62761
|
+
}
|
|
62762
|
+
incidentsFmtDay(iso) {
|
|
62763
|
+
return new Date(iso).toLocaleDateString("pt-BR", {
|
|
62764
|
+
timeZone: "America/Sao_Paulo",
|
|
62765
|
+
day: "2-digit",
|
|
62766
|
+
month: "2-digit",
|
|
62767
|
+
year: "numeric"
|
|
62768
|
+
});
|
|
62769
|
+
}
|
|
62770
|
+
incidentsFmtFull(iso) {
|
|
62771
|
+
return iso == null ? "\u2014" : new Date(iso).toLocaleString("pt-BR", {
|
|
62772
|
+
timeZone: "America/Sao_Paulo",
|
|
62773
|
+
day: "2-digit",
|
|
62774
|
+
month: "2-digit",
|
|
62775
|
+
hour: "2-digit",
|
|
62776
|
+
minute: "2-digit"
|
|
62777
|
+
});
|
|
62778
|
+
}
|
|
62779
|
+
/**
|
|
62780
|
+
* RFC-0232 — "Incidentes" tab (central + admin MyIO only): what the
|
|
62781
|
+
* No-Consumption Interpolation agent (`data-ingestion-prod`) fabricated for
|
|
62782
|
+
* THIS central. Inspired by that app's own ledger
|
|
62783
|
+
* (`InterpolationPanel.tsx`'s "Logs" tab, ~L450-548) but simplified for a
|
|
62784
|
+
* single-central context: that page groups by GATEWAY first (it shows every
|
|
62785
|
+
* central at once) — here there's only one central, so grouping starts one
|
|
62786
|
+
* level down, by DEVICE, then by DAY, each day showing its hour slots.
|
|
62787
|
+
* `config.interpolatedSlots` arrives pre-fetched from the host — nothing is
|
|
62788
|
+
* fetched in here, ever; the header controls (busca/dispositivos/período)
|
|
62789
|
+
* only filter that one array client-side. This method runs once, at
|
|
62790
|
+
* construction time, and builds the STATIC shell (filters header + KPI/body
|
|
62791
|
+
* mount points); `refreshIncidentsView()` re-renders the KPI/body pair on
|
|
62792
|
+
* every filter change via `this.modal.querySelector`.
|
|
62793
|
+
*/
|
|
62794
|
+
getInterpolationIncidentsHTML() {
|
|
62795
|
+
this.incidentsAllSlots = this.config.interpolatedSlots ?? [];
|
|
62796
|
+
const deviceKeys = Array.from(new Set(this.incidentsAllSlots.map((s) => this.incidentsDeviceKey(s)))).sort(
|
|
62797
|
+
(a, b) => a.localeCompare(b, "pt-BR")
|
|
62798
|
+
);
|
|
62799
|
+
this.incidentsSelectedDevices = new Set(deviceKeys);
|
|
62800
|
+
if (this.incidentsAllSlots.length === 0) {
|
|
62801
|
+
return `<div class="incidents-empty">Nenhuma interpola\xE7\xE3o registrada para esta central no per\xEDodo consultado pelo host.</div>`;
|
|
62802
|
+
}
|
|
62803
|
+
const devicesPanel = deviceKeys.map(
|
|
62804
|
+
(key) => `
|
|
62805
|
+
<label class="incidents-multiselect__item">
|
|
62806
|
+
<input type="checkbox" checked value="${this.escHtml(key)}" data-role="incidents-device-checkbox">
|
|
62807
|
+
<span>${this.escHtml(key)}</span>
|
|
62808
|
+
</label>`
|
|
62809
|
+
).join("");
|
|
62810
|
+
return `
|
|
62811
|
+
<div class="incidents-filters">
|
|
62812
|
+
<div class="incidents-filter incidents-filter--search">
|
|
62813
|
+
<label class="incidents-filter__label">Buscar dispositivo</label>
|
|
62814
|
+
<input type="text" class="incidents-search-input" placeholder="Nome do dispositivo\u2026" data-role="incidents-search-input">
|
|
62815
|
+
</div>
|
|
62816
|
+
<div class="incidents-filter incidents-filter--devices">
|
|
62817
|
+
<label class="incidents-filter__label">Dispositivos</label>
|
|
62818
|
+
<div class="incidents-multiselect">
|
|
62819
|
+
<button type="button" class="incidents-multiselect__toggle" data-role="incidents-devices-toggle">
|
|
62820
|
+
<span data-role="incidents-devices-summary">Todos (${deviceKeys.length})</span>
|
|
62821
|
+
<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>
|
|
62822
|
+
</button>
|
|
62823
|
+
<div class="incidents-multiselect__panel" data-role="incidents-devices-panel" hidden>
|
|
62824
|
+
<div class="incidents-multiselect__actions">
|
|
62825
|
+
<button type="button" data-role="incidents-devices-all">Marcar todos</button>
|
|
62826
|
+
<button type="button" data-role="incidents-devices-none">Desmarcar todos</button>
|
|
62827
|
+
</div>
|
|
62828
|
+
${devicesPanel}
|
|
62829
|
+
</div>
|
|
62830
|
+
</div>
|
|
62831
|
+
</div>
|
|
62832
|
+
<div class="incidents-filter incidents-filter--period">
|
|
62833
|
+
<label class="incidents-filter__label">Per\xEDodo</label>
|
|
62834
|
+
<div id="incidents-daterange-${this.incidentsIdSuffix}" class="incidents-daterange-mount"></div>
|
|
62835
|
+
</div>
|
|
62836
|
+
</div>
|
|
62837
|
+
<div class="incidents-kpis">${this.buildIncidentsKpisHtml(this.incidentsAllSlots)}</div>
|
|
62838
|
+
<div class="incidents-body">${this.buildIncidentsBodyHtml(this.incidentsAllSlots)}</div>
|
|
62839
|
+
`;
|
|
62840
|
+
}
|
|
62841
|
+
/** 4 small KPI cards summarizing the currently-filtered slot set. */
|
|
62842
|
+
buildIncidentsKpisHtml(slots) {
|
|
62843
|
+
if (slots.length === 0) {
|
|
62844
|
+
return `<div class="incidents-kpi-empty">Nenhuma interpola\xE7\xE3o corresponde aos filtros atuais.</div>`;
|
|
62845
|
+
}
|
|
62846
|
+
const deviceCount = new Set(slots.map((s) => this.incidentsDeviceKey(s))).size;
|
|
62847
|
+
const dayCount = new Set(slots.map((s) => this.incidentsFmtDay(s.hourStart))).size;
|
|
62848
|
+
const withIncident = slots.filter((s) => s.incidentRef).length;
|
|
62849
|
+
const pct = slots.length ? Math.round(withIncident / slots.length * 100) : 0;
|
|
62850
|
+
const kpi = (label, value, sub) => `
|
|
62851
|
+
<div class="incidents-kpi">
|
|
62852
|
+
<div class="incidents-kpi__value">${value}</div>
|
|
62853
|
+
<div class="incidents-kpi__label">${label}</div>
|
|
62854
|
+
${sub ? `<div class="incidents-kpi__sub">${sub}</div>` : ""}
|
|
62855
|
+
</div>`;
|
|
62856
|
+
return [
|
|
62857
|
+
kpi("Interpola\xE7\xF5es", String(slots.length)),
|
|
62858
|
+
kpi("Dispositivos", String(deviceCount)),
|
|
62859
|
+
kpi("Dias com atividade", String(dayCount)),
|
|
62860
|
+
kpi("Com incidente vinculado", String(withIncident), `${pct}% do total`)
|
|
62861
|
+
].join("");
|
|
62862
|
+
}
|
|
62863
|
+
/** Device → day → hour-slot table, for whatever slot subset the caller already filtered. */
|
|
62864
|
+
buildIncidentsBodyHtml(slots) {
|
|
62865
|
+
if (slots.length === 0) {
|
|
62866
|
+
return `<div class="incidents-empty">Nenhuma interpola\xE7\xE3o corresponde aos filtros atuais.</div>`;
|
|
62867
|
+
}
|
|
62868
|
+
const byDevice = /* @__PURE__ */ new Map();
|
|
62869
|
+
for (const s of slots) {
|
|
62870
|
+
const key = this.incidentsDeviceKey(s);
|
|
62871
|
+
(byDevice.get(key) ?? byDevice.set(key, []).get(key)).push(s);
|
|
62872
|
+
}
|
|
62873
|
+
const deviceNames = Array.from(byDevice.keys()).sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
62874
|
+
const deviceSections = deviceNames.map((deviceName) => {
|
|
62875
|
+
const deviceSlots = byDevice.get(deviceName).slice().sort((a, b) => a.hourStart.localeCompare(b.hourStart));
|
|
62876
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
62877
|
+
for (const s of deviceSlots) {
|
|
62878
|
+
const day = this.incidentsFmtDay(s.hourStart);
|
|
62879
|
+
(byDay.get(day) ?? byDay.set(day, []).get(day)).push(s);
|
|
62880
|
+
}
|
|
62881
|
+
const daySections = Array.from(byDay.entries()).map(([day, daySlots]) => {
|
|
62882
|
+
const rows = daySlots.map(
|
|
62883
|
+
(s) => `
|
|
62884
|
+
<tr>
|
|
62885
|
+
<td class="mono">${this.incidentsFmtHour(s.hourStart)}</td>
|
|
62886
|
+
<td class="num mono">${s.value.toFixed(1)}</td>
|
|
62887
|
+
<td class="mono muted">${this.incidentsFmtHour(s.sourceHour)}</td>
|
|
62888
|
+
<td class="mono muted" title="${this.escHtml(s.ruleId)}">${this.escHtml(s.ruleId.slice(0, 8))}</td>
|
|
62889
|
+
<td>${s.incidentRef ? `<code>${this.escHtml(s.incidentRef)}</code>` : '<span class="muted">\u2014</span>'}</td>
|
|
62890
|
+
<td class="muted">${this.incidentsFmtFull(s.createdAt)}</td>
|
|
62891
|
+
</tr>`
|
|
62892
|
+
).join("");
|
|
62893
|
+
return `
|
|
62894
|
+
<div class="incidents-day">
|
|
62895
|
+
<div class="incidents-day__header">
|
|
62896
|
+
<span>${this.escHtml(day)}</span>
|
|
62897
|
+
<span class="incidents-day__count">${daySlots.length} hora${daySlots.length === 1 ? "" : "s"}</span>
|
|
62898
|
+
</div>
|
|
62899
|
+
<table class="incidents-table">
|
|
62900
|
+
<thead>
|
|
62901
|
+
<tr><th>Hora</th><th>Valor</th><th>Fonte LOCF</th><th>Regra</th><th>Incidente</th><th>Gravado</th></tr>
|
|
62902
|
+
</thead>
|
|
62903
|
+
<tbody>${rows}</tbody>
|
|
62904
|
+
</table>
|
|
62905
|
+
</div>`;
|
|
62906
|
+
}).join("");
|
|
62907
|
+
return `
|
|
62908
|
+
<div class="incidents-device">
|
|
62909
|
+
<button type="button" class="incidents-device__header" data-role="toggle-incidents-device">
|
|
62910
|
+
<svg class="incidents-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
62911
|
+
<polyline points="9 18 15 12 9 6"></polyline>
|
|
62912
|
+
</svg>
|
|
62913
|
+
<span class="incidents-device__name">${this.escHtml(deviceName)}</span>
|
|
62914
|
+
<span class="incidents-device__count">${deviceSlots.length} hora${deviceSlots.length === 1 ? "" : "s"} \xB7 ${byDay.size} dia${byDay.size === 1 ? "" : "s"}</span>
|
|
62915
|
+
</button>
|
|
62916
|
+
<div class="incidents-device__body">${daySections}</div>
|
|
62917
|
+
</div>`;
|
|
62918
|
+
}).join("");
|
|
62919
|
+
return `
|
|
62920
|
+
<div class="incidents-summary">${slots.length} interpola\xE7\xE3o(\xF5es) \xB7 ${deviceNames.length} dispositivo(s)</div>
|
|
62921
|
+
<div class="incidents-wrap">${deviceSections}</div>
|
|
62922
|
+
`;
|
|
62923
|
+
}
|
|
62924
|
+
/** Applies search + device + period filters to `incidentsAllSlots`. */
|
|
62925
|
+
getFilteredIncidentsSlots() {
|
|
62926
|
+
const search = this.incidentsSearch.trim().toLowerCase();
|
|
62927
|
+
const { startISO, endISO } = this.incidentsDateRange;
|
|
62928
|
+
return this.incidentsAllSlots.filter((s) => {
|
|
62929
|
+
const key = this.incidentsDeviceKey(s);
|
|
62930
|
+
if (!this.incidentsSelectedDevices.has(key)) return false;
|
|
62931
|
+
if (search && !key.toLowerCase().includes(search)) return false;
|
|
62932
|
+
if (startISO && s.hourStart < startISO) return false;
|
|
62933
|
+
if (endISO && s.hourStart > endISO) return false;
|
|
62934
|
+
return true;
|
|
62935
|
+
});
|
|
62936
|
+
}
|
|
62937
|
+
/** Re-renders the KPI cards + device/day body after any filter change (search/devices/period). */
|
|
62938
|
+
refreshIncidentsView() {
|
|
62939
|
+
const filtered = this.getFilteredIncidentsSlots();
|
|
62940
|
+
const kpisEl = this.modal.querySelector(".incidents-kpis");
|
|
62941
|
+
const bodyEl = this.modal.querySelector(".incidents-body");
|
|
62942
|
+
if (kpisEl) kpisEl.innerHTML = this.buildIncidentsKpisHtml(filtered);
|
|
62943
|
+
if (bodyEl) bodyEl.innerHTML = this.buildIncidentsBodyHtml(filtered);
|
|
62944
|
+
const summaryEl = this.modal.querySelector('[data-role="incidents-devices-summary"]');
|
|
62945
|
+
if (summaryEl) {
|
|
62946
|
+
const total = this.incidentsAllSlots.length ? new Set(this.incidentsAllSlots.map((s) => this.incidentsDeviceKey(s))).size : 0;
|
|
62947
|
+
const selected = this.incidentsSelectedDevices.size;
|
|
62948
|
+
summaryEl.textContent = selected === total ? `Todos (${total})` : `${selected} de ${total}`;
|
|
62949
|
+
}
|
|
62950
|
+
}
|
|
62951
|
+
/**
|
|
62952
|
+
* Wires the Incidentes tab's static controls (search input, devices
|
|
62953
|
+
* multiselect dropdown, device-group collapse). Called once from `render()`
|
|
62954
|
+
* — all delegated on `this.modal`, so it survives the KPI/body innerHTML
|
|
62955
|
+
* swaps `refreshIncidentsView()` does (those never touch the header). The
|
|
62956
|
+
* date-range picker is wired separately in `initIncidentsDateRange()`
|
|
62957
|
+
* (async — see that method).
|
|
62958
|
+
*/
|
|
62959
|
+
bindIncidentsTabEvents() {
|
|
62960
|
+
this.modal.addEventListener("input", (event) => {
|
|
62961
|
+
const target = event.target;
|
|
62962
|
+
if (target.dataset.role !== "incidents-search-input") return;
|
|
62963
|
+
this.incidentsSearch = target.value;
|
|
62964
|
+
this.refreshIncidentsView();
|
|
62965
|
+
});
|
|
62966
|
+
this.modal.addEventListener("click", (event) => {
|
|
62967
|
+
const target = event.target;
|
|
62968
|
+
const deviceHeader = target.closest('[data-role="toggle-incidents-device"]');
|
|
62969
|
+
if (deviceHeader) {
|
|
62970
|
+
deviceHeader.closest(".incidents-device")?.classList.toggle("is-collapsed");
|
|
62971
|
+
return;
|
|
62972
|
+
}
|
|
62973
|
+
const panel = this.modal.querySelector('[data-role="incidents-devices-panel"]');
|
|
62974
|
+
const toggle = target.closest('[data-role="incidents-devices-toggle"]');
|
|
62975
|
+
if (toggle && panel) {
|
|
62976
|
+
panel.hidden = !panel.hidden;
|
|
62977
|
+
return;
|
|
62978
|
+
}
|
|
62979
|
+
if (target.dataset.role === "incidents-devices-all" || target.dataset.role === "incidents-devices-none") {
|
|
62980
|
+
const checkAll = target.dataset.role === "incidents-devices-all";
|
|
62981
|
+
this.modal.querySelectorAll('[data-role="incidents-device-checkbox"]').forEach((cb) => {
|
|
62982
|
+
cb.checked = checkAll;
|
|
62983
|
+
});
|
|
62984
|
+
this.incidentsSelectedDevices = checkAll ? new Set(this.incidentsAllSlots.map((s) => this.incidentsDeviceKey(s))) : /* @__PURE__ */ new Set();
|
|
62985
|
+
this.refreshIncidentsView();
|
|
62986
|
+
return;
|
|
62987
|
+
}
|
|
62988
|
+
if (panel && !panel.hidden && !target.closest('[data-role="incidents-devices-toggle"]') && !panel.contains(target)) {
|
|
62989
|
+
panel.hidden = true;
|
|
62990
|
+
}
|
|
62991
|
+
});
|
|
62992
|
+
this.modal.addEventListener("change", (event) => {
|
|
62993
|
+
const target = event.target;
|
|
62994
|
+
if (target.dataset.role !== "incidents-device-checkbox") return;
|
|
62995
|
+
const cb = target;
|
|
62996
|
+
if (cb.checked) this.incidentsSelectedDevices.add(cb.value);
|
|
62997
|
+
else this.incidentsSelectedDevices.delete(cb.value);
|
|
62998
|
+
this.refreshIncidentsView();
|
|
62999
|
+
});
|
|
63000
|
+
}
|
|
63001
|
+
/**
|
|
63002
|
+
* Mounts the period date-range picker into the Incidentes tab's header —
|
|
63003
|
+
* async (loads jQuery/moment/daterangepicker from CDN on first use, see
|
|
63004
|
+
* `createInputDateRangePickerInsideDIV`), so this runs from `render()`
|
|
63005
|
+
* AFTER the modal is attached to the DOM, same timing as
|
|
63006
|
+
* `initAnnotationsTab()`/`initAlarmsTab()`. No-ops when the tab isn't
|
|
63007
|
+
* rendered at all (non-admin / non-gateway).
|
|
63008
|
+
*/
|
|
63009
|
+
async initIncidentsDateRange() {
|
|
63010
|
+
if (!(this.config.isGateway && this.isSuperAdmin())) return;
|
|
63011
|
+
const mountId = `incidents-daterange-${this.incidentsIdSuffix}`;
|
|
63012
|
+
if (!document.getElementById(mountId)) return;
|
|
63013
|
+
try {
|
|
63014
|
+
this.incidentsDateRangeController = await createInputDateRangePickerInsideDIV({
|
|
63015
|
+
containerId: mountId,
|
|
63016
|
+
inputId: `${mountId}-input`,
|
|
63017
|
+
label: "",
|
|
63018
|
+
placeholder: "Todo o per\xEDodo",
|
|
63019
|
+
showHelper: false,
|
|
63020
|
+
pickerOptions: {
|
|
63021
|
+
includeTime: true,
|
|
63022
|
+
onApply: (result) => {
|
|
63023
|
+
this.incidentsDateRange = { startISO: result.startISO, endISO: result.endISO };
|
|
63024
|
+
this.refreshIncidentsView();
|
|
63025
|
+
}
|
|
63026
|
+
}
|
|
63027
|
+
});
|
|
63028
|
+
} catch (err) {
|
|
63029
|
+
console.warn("[SettingsModalView] Incidentes: failed to mount date-range picker:", err);
|
|
63030
|
+
}
|
|
63031
|
+
}
|
|
61955
63032
|
getDeviceImage(deviceType) {
|
|
61956
63033
|
let normalized = (deviceType || "").toUpperCase();
|
|
61957
63034
|
if (normalized === "3F_MEDIDOR") {
|
|
@@ -61960,8 +63037,7 @@ var SettingsModalView = class {
|
|
|
61960
63037
|
normalized = profile.toUpperCase();
|
|
61961
63038
|
}
|
|
61962
63039
|
}
|
|
61963
|
-
const
|
|
61964
|
-
const url = deviceIcons[normalized] || DEFAULT;
|
|
63040
|
+
const url = deviceIcons[normalized] || DEFAULT_DEVICE_ICON;
|
|
61965
63041
|
return `<img src="${url}" class="identity-device-image" alt="${normalized}" />`;
|
|
61966
63042
|
}
|
|
61967
63043
|
getConsumptionLimits() {
|
|
@@ -62364,7 +63440,19 @@ var SettingsModalView = class {
|
|
|
62364
63440
|
z-index: 9999999;
|
|
62365
63441
|
font-family: 'Nunito', system-ui, sans-serif;
|
|
62366
63442
|
}
|
|
62367
|
-
|
|
63443
|
+
|
|
63444
|
+
/* RFC-0232: the "Incidentes" period picker (createInputDateRangePickerInsideDIV
|
|
63445
|
+
-> jQuery daterangepicker) always appends its calendar dropdown to
|
|
63446
|
+
document.body, as a SIBLING of this overlay, not a descendant \u2014 so it
|
|
63447
|
+
can't be reached with a normal descendant selector. The plugin's own
|
|
63448
|
+
default z-index (~10000) is far below this overlay's 9999999 (chosen
|
|
63449
|
+
to beat ThingsBoard's Angular Material overlays in production), so
|
|
63450
|
+
without this override the calendar renders but is fully hidden behind
|
|
63451
|
+
the modal, making the input look like it "doesn't open". */
|
|
63452
|
+
.daterangepicker {
|
|
63453
|
+
z-index: 10000000 !important;
|
|
63454
|
+
}
|
|
63455
|
+
|
|
62368
63456
|
.myio-device-settings-modal {
|
|
62369
63457
|
background: white;
|
|
62370
63458
|
border-radius: 8px;
|
|
@@ -62466,11 +63554,277 @@ var SettingsModalView = class {
|
|
|
62466
63554
|
.modal-tab-badge--alarms { background: #dc2626; }
|
|
62467
63555
|
.modal-tab-badge--chamados { background: #0891b2; }
|
|
62468
63556
|
.modal-tab-badge--exclusion { background: #d97706; }
|
|
63557
|
+
.modal-tab-badge--incidents { background: #7C3AED; }
|
|
62469
63558
|
|
|
62470
63559
|
.tab-content {
|
|
62471
63560
|
min-height: 400px;
|
|
62472
63561
|
}
|
|
62473
63562
|
|
|
63563
|
+
/* RFC-0232: Incidentes (interpola\xE7\xE3o) tab \u2014 device \u2192 day \u2192 hour slots. */
|
|
63564
|
+
.incidents-filters {
|
|
63565
|
+
display: flex;
|
|
63566
|
+
flex-wrap: wrap;
|
|
63567
|
+
align-items: flex-end;
|
|
63568
|
+
gap: 16px;
|
|
63569
|
+
margin-bottom: 16px;
|
|
63570
|
+
padding-bottom: 16px;
|
|
63571
|
+
border-bottom: 1px solid #e5e7eb;
|
|
63572
|
+
}
|
|
63573
|
+
.incidents-filter {
|
|
63574
|
+
display: flex;
|
|
63575
|
+
flex-direction: column;
|
|
63576
|
+
gap: 4px;
|
|
63577
|
+
}
|
|
63578
|
+
.incidents-filter--search { flex: 1 1 123px; min-width: 100px; }
|
|
63579
|
+
.incidents-filter--devices { flex: 0 0 auto; }
|
|
63580
|
+
.incidents-filter--period { flex: 0 0 auto; }
|
|
63581
|
+
.incidents-filter__label {
|
|
63582
|
+
font: 700 11px 'Nunito', system-ui, sans-serif;
|
|
63583
|
+
color: #6b7280;
|
|
63584
|
+
text-transform: uppercase;
|
|
63585
|
+
letter-spacing: 0.03em;
|
|
63586
|
+
}
|
|
63587
|
+
.incidents-search-input {
|
|
63588
|
+
width: 100%;
|
|
63589
|
+
padding: 7px 10px;
|
|
63590
|
+
border: 1px solid #d1d5db;
|
|
63591
|
+
border-radius: 6px;
|
|
63592
|
+
font: 600 13px 'Nunito', system-ui, sans-serif;
|
|
63593
|
+
color: #1f2937;
|
|
63594
|
+
box-sizing: border-box;
|
|
63595
|
+
}
|
|
63596
|
+
.incidents-search-input:focus { outline: 2px solid #7C3AED; outline-offset: 1px; }
|
|
63597
|
+
.incidents-multiselect { position: relative; }
|
|
63598
|
+
.incidents-multiselect__toggle {
|
|
63599
|
+
display: flex;
|
|
63600
|
+
align-items: center;
|
|
63601
|
+
gap: 8px;
|
|
63602
|
+
padding: 7px 12px;
|
|
63603
|
+
min-width: 200px;
|
|
63604
|
+
border: 1px solid #d1d5db;
|
|
63605
|
+
border-radius: 6px;
|
|
63606
|
+
background: #fff;
|
|
63607
|
+
font: 600 13px 'Nunito', system-ui, sans-serif;
|
|
63608
|
+
color: #1f2937;
|
|
63609
|
+
cursor: pointer;
|
|
63610
|
+
white-space: nowrap;
|
|
63611
|
+
}
|
|
63612
|
+
.incidents-multiselect__toggle:hover { border-color: #7C3AED; }
|
|
63613
|
+
.incidents-multiselect__panel {
|
|
63614
|
+
position: absolute;
|
|
63615
|
+
top: calc(100% + 4px);
|
|
63616
|
+
left: 0;
|
|
63617
|
+
z-index: 20;
|
|
63618
|
+
min-width: 220px;
|
|
63619
|
+
max-height: 260px;
|
|
63620
|
+
overflow-y: auto;
|
|
63621
|
+
background: #fff;
|
|
63622
|
+
border: 1px solid #d1d5db;
|
|
63623
|
+
border-radius: 8px;
|
|
63624
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
63625
|
+
padding: 8px;
|
|
63626
|
+
}
|
|
63627
|
+
.incidents-multiselect__actions {
|
|
63628
|
+
display: flex;
|
|
63629
|
+
gap: 8px;
|
|
63630
|
+
padding-bottom: 6px;
|
|
63631
|
+
margin-bottom: 6px;
|
|
63632
|
+
border-bottom: 1px solid #f3f4f6;
|
|
63633
|
+
}
|
|
63634
|
+
.incidents-multiselect__actions button {
|
|
63635
|
+
flex: 1;
|
|
63636
|
+
padding: 4px 6px;
|
|
63637
|
+
border: none;
|
|
63638
|
+
border-radius: 4px;
|
|
63639
|
+
background: #f5f3ff;
|
|
63640
|
+
color: #5b21b6;
|
|
63641
|
+
font: 700 11px 'Nunito', system-ui, sans-serif;
|
|
63642
|
+
cursor: pointer;
|
|
63643
|
+
}
|
|
63644
|
+
.incidents-multiselect__actions button:hover { background: #ede9fe; }
|
|
63645
|
+
.incidents-multiselect__item {
|
|
63646
|
+
display: flex;
|
|
63647
|
+
align-items: center;
|
|
63648
|
+
gap: 8px;
|
|
63649
|
+
padding: 5px 4px;
|
|
63650
|
+
font: 600 13px 'Nunito', system-ui, sans-serif;
|
|
63651
|
+
color: #374151;
|
|
63652
|
+
cursor: pointer;
|
|
63653
|
+
border-radius: 4px;
|
|
63654
|
+
}
|
|
63655
|
+
.incidents-multiselect__item:hover { background: #f9fafb; }
|
|
63656
|
+
.incidents-daterange-mount .myio-daterange-wrapper {
|
|
63657
|
+
margin-bottom: 0;
|
|
63658
|
+
padding: 0;
|
|
63659
|
+
background: transparent;
|
|
63660
|
+
box-shadow: none;
|
|
63661
|
+
}
|
|
63662
|
+
.incidents-daterange-mount {
|
|
63663
|
+
min-width: 280px;
|
|
63664
|
+
}
|
|
63665
|
+
.incidents-daterange-mount .myio-daterange-input {
|
|
63666
|
+
padding: 7px 10px;
|
|
63667
|
+
font-size: 13px;
|
|
63668
|
+
width: 100%;
|
|
63669
|
+
min-width: 280px;
|
|
63670
|
+
max-width: 340px;
|
|
63671
|
+
}
|
|
63672
|
+
.incidents-kpis {
|
|
63673
|
+
display: grid;
|
|
63674
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
63675
|
+
gap: 12px;
|
|
63676
|
+
margin-bottom: 16px;
|
|
63677
|
+
}
|
|
63678
|
+
.incidents-kpi {
|
|
63679
|
+
padding: 12px 14px;
|
|
63680
|
+
border-radius: 8px;
|
|
63681
|
+
background: #f5f3ff;
|
|
63682
|
+
border: 1px solid #e9d5ff;
|
|
63683
|
+
}
|
|
63684
|
+
.incidents-kpi__value {
|
|
63685
|
+
font: 800 22px 'Nunito', system-ui, sans-serif;
|
|
63686
|
+
color: #5b21b6;
|
|
63687
|
+
}
|
|
63688
|
+
.incidents-kpi__label {
|
|
63689
|
+
margin-top: 2px;
|
|
63690
|
+
font: 700 11px 'Nunito', system-ui, sans-serif;
|
|
63691
|
+
color: #6b7280;
|
|
63692
|
+
text-transform: uppercase;
|
|
63693
|
+
letter-spacing: 0.02em;
|
|
63694
|
+
}
|
|
63695
|
+
.incidents-kpi__sub {
|
|
63696
|
+
margin-top: 2px;
|
|
63697
|
+
font-size: 11px;
|
|
63698
|
+
color: #9ca3af;
|
|
63699
|
+
}
|
|
63700
|
+
.incidents-kpi-empty {
|
|
63701
|
+
margin-bottom: 16px;
|
|
63702
|
+
padding: 10px 14px;
|
|
63703
|
+
border-radius: 8px;
|
|
63704
|
+
background: #f9fafb;
|
|
63705
|
+
color: #9ca3af;
|
|
63706
|
+
font-size: 12px;
|
|
63707
|
+
}
|
|
63708
|
+
.theme-dark .incidents-filters { border-color: #374151; }
|
|
63709
|
+
.theme-dark .incidents-filter__label { color: #9ca3af; }
|
|
63710
|
+
.theme-dark .incidents-search-input { background: #111827; border-color: #4b5563; color: #e5e7eb; }
|
|
63711
|
+
.theme-dark .incidents-multiselect__toggle { background: #111827; border-color: #4b5563; color: #e5e7eb; }
|
|
63712
|
+
.theme-dark .incidents-multiselect__panel { background: #1f2937; border-color: #374151; }
|
|
63713
|
+
.theme-dark .incidents-multiselect__item { color: #e5e7eb; }
|
|
63714
|
+
.theme-dark .incidents-multiselect__item:hover { background: #111827; }
|
|
63715
|
+
.theme-dark .incidents-kpi { background: #2e1065; border-color: #4c1d95; }
|
|
63716
|
+
.theme-dark .incidents-kpi__value { color: #d8b4fe; }
|
|
63717
|
+
.theme-dark .incidents-kpi-empty { background: #111827; color: #6b7280; }
|
|
63718
|
+
|
|
63719
|
+
.incidents-summary {
|
|
63720
|
+
margin-bottom: 14px;
|
|
63721
|
+
font-size: 13px;
|
|
63722
|
+
font-weight: 600;
|
|
63723
|
+
color: #6b7280;
|
|
63724
|
+
}
|
|
63725
|
+
.incidents-empty {
|
|
63726
|
+
padding: 40px 20px;
|
|
63727
|
+
text-align: center;
|
|
63728
|
+
color: #6b7280;
|
|
63729
|
+
font-size: 14px;
|
|
63730
|
+
border: 1px dashed #d1d5db;
|
|
63731
|
+
border-radius: 8px;
|
|
63732
|
+
}
|
|
63733
|
+
.incidents-wrap {
|
|
63734
|
+
display: flex;
|
|
63735
|
+
flex-direction: column;
|
|
63736
|
+
gap: 12px;
|
|
63737
|
+
}
|
|
63738
|
+
.incidents-device {
|
|
63739
|
+
border: 1px solid #e5e7eb;
|
|
63740
|
+
border-radius: 8px;
|
|
63741
|
+
overflow: hidden;
|
|
63742
|
+
}
|
|
63743
|
+
.incidents-device__header {
|
|
63744
|
+
display: flex;
|
|
63745
|
+
align-items: center;
|
|
63746
|
+
gap: 8px;
|
|
63747
|
+
width: 100%;
|
|
63748
|
+
padding: 10px 14px;
|
|
63749
|
+
background: #f5f3ff;
|
|
63750
|
+
border: none;
|
|
63751
|
+
cursor: pointer;
|
|
63752
|
+
font: 700 13px 'Nunito', system-ui, sans-serif;
|
|
63753
|
+
color: #5b21b6;
|
|
63754
|
+
text-align: left;
|
|
63755
|
+
}
|
|
63756
|
+
.incidents-device__header:hover { background: #ede9fe; }
|
|
63757
|
+
/* Chevron SVG points right (\u25B6); rotated 90\xB0 to point down (\u25BC) while
|
|
63758
|
+
expanded \u2014 the default state, since a group starts open. */
|
|
63759
|
+
.incidents-chevron { flex-shrink: 0; transition: transform 0.15s ease; transform: rotate(90deg); }
|
|
63760
|
+
.incidents-device.is-collapsed .incidents-chevron { transform: rotate(0deg); }
|
|
63761
|
+
.incidents-device.is-collapsed .incidents-device__body { display: none; }
|
|
63762
|
+
.incidents-device__name { flex: 1; }
|
|
63763
|
+
.incidents-device__count {
|
|
63764
|
+
font-size: 11px;
|
|
63765
|
+
font-weight: 600;
|
|
63766
|
+
color: #7c3aed;
|
|
63767
|
+
background: #fff;
|
|
63768
|
+
border-radius: 999px;
|
|
63769
|
+
padding: 2px 8px;
|
|
63770
|
+
}
|
|
63771
|
+
.incidents-device__body {
|
|
63772
|
+
display: flex;
|
|
63773
|
+
flex-direction: column;
|
|
63774
|
+
gap: 10px;
|
|
63775
|
+
padding: 10px 14px;
|
|
63776
|
+
}
|
|
63777
|
+
.incidents-day__header {
|
|
63778
|
+
display: flex;
|
|
63779
|
+
align-items: center;
|
|
63780
|
+
justify-content: space-between;
|
|
63781
|
+
padding: 4px 2px;
|
|
63782
|
+
font: 700 12px 'Nunito', system-ui, sans-serif;
|
|
63783
|
+
color: #374151;
|
|
63784
|
+
}
|
|
63785
|
+
.incidents-day__count {
|
|
63786
|
+
font-size: 11px;
|
|
63787
|
+
font-weight: 600;
|
|
63788
|
+
color: #6b7280;
|
|
63789
|
+
}
|
|
63790
|
+
.incidents-table {
|
|
63791
|
+
width: 100%;
|
|
63792
|
+
border-collapse: collapse;
|
|
63793
|
+
font-size: 12px;
|
|
63794
|
+
}
|
|
63795
|
+
.incidents-table th {
|
|
63796
|
+
text-align: left;
|
|
63797
|
+
padding: 6px 8px;
|
|
63798
|
+
background: #f9fafb;
|
|
63799
|
+
color: #6b7280;
|
|
63800
|
+
font-weight: 600;
|
|
63801
|
+
border-bottom: 1px solid #e5e7eb;
|
|
63802
|
+
}
|
|
63803
|
+
.incidents-table td {
|
|
63804
|
+
padding: 6px 8px;
|
|
63805
|
+
border-bottom: 1px solid #f3f4f6;
|
|
63806
|
+
color: #1f2937;
|
|
63807
|
+
}
|
|
63808
|
+
.incidents-table td.num { text-align: right; }
|
|
63809
|
+
.incidents-table td.mono { font-family: 'Courier New', Courier, monospace; }
|
|
63810
|
+
.incidents-table td.muted { color: #9ca3af; }
|
|
63811
|
+
.incidents-table code {
|
|
63812
|
+
color: #7c3aed;
|
|
63813
|
+
background: #f5f3ff;
|
|
63814
|
+
border-radius: 4px;
|
|
63815
|
+
padding: 1px 5px;
|
|
63816
|
+
font-size: 11px;
|
|
63817
|
+
}
|
|
63818
|
+
.theme-dark .incidents-empty { border-color: #4b5563; color: #9ca3af; }
|
|
63819
|
+
.theme-dark .incidents-device { border-color: #374151; }
|
|
63820
|
+
.theme-dark .incidents-device__header { background: #2e1065; color: #d8b4fe; }
|
|
63821
|
+
.theme-dark .incidents-device__header:hover { background: #3b0764; }
|
|
63822
|
+
.theme-dark .incidents-device__count { background: #111827; color: #c4b5fd; }
|
|
63823
|
+
.theme-dark .incidents-day__header { color: #e5e7eb; }
|
|
63824
|
+
.theme-dark .incidents-table th { background: #111827; color: #9ca3af; border-color: #374151; }
|
|
63825
|
+
.theme-dark .incidents-table td { color: #e5e7eb; border-color: #1f2937; }
|
|
63826
|
+
.theme-dark .incidents-table code { background: #2e1065; }
|
|
63827
|
+
|
|
62474
63828
|
/* Loading spinner for annotations tab */
|
|
62475
63829
|
.loading-spinner {
|
|
62476
63830
|
width: 32px;
|
|
@@ -62613,20 +63967,22 @@ var SettingsModalView = class {
|
|
|
62613
63967
|
height: fit-content;
|
|
62614
63968
|
}
|
|
62615
63969
|
|
|
62616
|
-
/*
|
|
62617
|
-
|
|
63970
|
+
/* Central/gateway identity+telemetry (isGateway), inline in Geral \u2014
|
|
63971
|
+
one section per line, same visual pattern as "Informa\xE7\xF5es de
|
|
63972
|
+
Conex\xE3o" (.info-card-wide + .info-grid/.info-row) for every card:
|
|
63973
|
+
Identifica\xE7\xE3o/Estat\xEDsticas/Metadados here, plus the Conectividade
|
|
63974
|
+
fieldset CentralSettingsModal injects after them.
|
|
63975
|
+
.gateway-info-grid is just a vertical stack \u2014 .gateway-info-card
|
|
63976
|
+
carries no sizing of its own any more (that lived here back when
|
|
63977
|
+
these were narrower, side-by-side flex-wrap cards); the full-width
|
|
63978
|
+
look now comes entirely from .info-card-wide. */
|
|
62618
63979
|
.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
63980
|
display: flex;
|
|
62628
63981
|
flex-direction: column;
|
|
62629
|
-
gap:
|
|
63982
|
+
gap: 16px;
|
|
63983
|
+
}
|
|
63984
|
+
.gateway-info-card {
|
|
63985
|
+
margin: 0;
|
|
62630
63986
|
}
|
|
62631
63987
|
|
|
62632
63988
|
/* RFC-0180: Identity card \u2014 2-column \xD7 6-row grid */
|
|
@@ -64266,6 +65622,7 @@ function mapToGatewayInfo(data) {
|
|
|
64266
65622
|
softwareVersion: data.softwareVersion ?? null,
|
|
64267
65623
|
frequency: data.frequency ?? null,
|
|
64268
65624
|
stats: data.stats ?? null,
|
|
65625
|
+
lastConsumptionTelemetry: data.lastConsumptionTelemetry ?? null,
|
|
64269
65626
|
createdAt: data.createdAt ?? null,
|
|
64270
65627
|
updatedAt: data.updatedAt ?? null,
|
|
64271
65628
|
version: data.version ?? null
|
|
@@ -64283,6 +65640,43 @@ function formatIsoPtBr(iso) {
|
|
|
64283
65640
|
minute: "2-digit"
|
|
64284
65641
|
});
|
|
64285
65642
|
}
|
|
65643
|
+
function formatIsoPtBrWithSeconds(iso) {
|
|
65644
|
+
if (!iso) return "\u2014";
|
|
65645
|
+
const ms = Date.parse(iso);
|
|
65646
|
+
if (Number.isNaN(ms)) return "\u2014";
|
|
65647
|
+
return new Date(ms).toLocaleString("pt-BR", {
|
|
65648
|
+
day: "2-digit",
|
|
65649
|
+
month: "2-digit",
|
|
65650
|
+
year: "numeric",
|
|
65651
|
+
hour: "2-digit",
|
|
65652
|
+
minute: "2-digit",
|
|
65653
|
+
second: "2-digit"
|
|
65654
|
+
});
|
|
65655
|
+
}
|
|
65656
|
+
function formatIsoRelativeDetailed(iso) {
|
|
65657
|
+
const ms = iso ? Date.parse(iso) : NaN;
|
|
65658
|
+
if (Number.isNaN(ms)) return "";
|
|
65659
|
+
const diffMinutes = Math.floor((Date.now() - ms) / 6e4);
|
|
65660
|
+
const diffHours = Math.floor(diffMinutes / 60);
|
|
65661
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
65662
|
+
if (diffDays > 0) {
|
|
65663
|
+
return `(${diffDays}d:${String(diffHours % 24).padStart(2, "0")}hs:${String(diffMinutes % 60).padStart(2, "0")}mins atr\xE1s)`;
|
|
65664
|
+
}
|
|
65665
|
+
if (diffHours > 0) return `(${diffHours}hs:${String(diffMinutes % 60).padStart(2, "0")}mins atr\xE1s)`;
|
|
65666
|
+
if (diffMinutes > 0) return `(${diffMinutes}mins atr\xE1s)`;
|
|
65667
|
+
return "(agora)";
|
|
65668
|
+
}
|
|
65669
|
+
function formatIsoRelativeSimple(iso) {
|
|
65670
|
+
const ms = iso ? Date.parse(iso) : NaN;
|
|
65671
|
+
if (Number.isNaN(ms)) return "";
|
|
65672
|
+
const diffMinutes = Math.floor((Date.now() - ms) / 6e4);
|
|
65673
|
+
const diffHours = Math.floor(diffMinutes / 60);
|
|
65674
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
65675
|
+
if (diffDays > 0) return `(${diffDays}d atr\xE1s)`;
|
|
65676
|
+
if (diffHours > 0) return `(${diffHours}h atr\xE1s)`;
|
|
65677
|
+
if (diffMinutes > 0) return `(${diffMinutes}min atr\xE1s)`;
|
|
65678
|
+
return "(agora)";
|
|
65679
|
+
}
|
|
64286
65680
|
function formatUptime(seconds) {
|
|
64287
65681
|
if (seconds == null || Number.isNaN(seconds)) return "\u2014";
|
|
64288
65682
|
const d = Math.floor(seconds / 86400);
|
|
@@ -64299,6 +65693,10 @@ function patchGatewayInfoDom(root, uuid, data) {
|
|
|
64299
65693
|
const el2 = root.querySelector(`#gwinfo-${key}`);
|
|
64300
65694
|
if (el2) el2.textContent = value;
|
|
64301
65695
|
};
|
|
65696
|
+
const setHtml = (key, html) => {
|
|
65697
|
+
const el2 = root.querySelector(`#gwinfo-${key}`);
|
|
65698
|
+
if (el2) el2.innerHTML = html;
|
|
65699
|
+
};
|
|
64302
65700
|
set("uuid", uuid || "\u2014");
|
|
64303
65701
|
set("serialNumber", data.serialNumber || "\u2014");
|
|
64304
65702
|
set("hardwareId", data.hardwareId || "\u2014");
|
|
@@ -64307,10 +65705,33 @@ function patchGatewayInfoDom(root, uuid, data) {
|
|
|
64307
65705
|
set("firmwareVersion", data.firmwareVersion || "\u2014");
|
|
64308
65706
|
set("softwareVersion", data.softwareVersion || "\u2014");
|
|
64309
65707
|
set("frequency", data.frequency != null ? String(data.frequency) : "\u2014");
|
|
64310
|
-
set("
|
|
65708
|
+
set("connCentral", data.name || "\u2014");
|
|
65709
|
+
const connStatusInfo = SettingsModalView.GATEWAY_CONN_STATUS_MAP[(data.connectionStatus || "").toUpperCase()] || {
|
|
65710
|
+
text: data.connectionStatus || "\u2014",
|
|
65711
|
+
color: "#6b7280"
|
|
65712
|
+
};
|
|
65713
|
+
setHtml("connectionStatus", `<span style="color: ${connStatusInfo.color}; font-weight: 600;">${connStatusInfo.text}</span>`);
|
|
65714
|
+
const isOnline = (data.connectionStatus || "").toUpperCase() === "ONLINE";
|
|
65715
|
+
const connectedSinceAbs = isOnline ? formatIsoPtBr(data.lastGatewaySuccessCheckAt) : "\u2014";
|
|
65716
|
+
const connectedSinceRel = isOnline ? formatIsoRelativeDetailed(data.lastGatewaySuccessCheckAt) : "";
|
|
65717
|
+
setHtml(
|
|
65718
|
+
"lastGatewaySuccessCheckAt",
|
|
65719
|
+
`${connectedSinceAbs}${connectedSinceRel ? ` <span class="time-since">${connectedSinceRel}</span>` : ""}`
|
|
65720
|
+
);
|
|
65721
|
+
const lastCheckAbs = formatIsoPtBr(data.lastGatewayCheckAt);
|
|
65722
|
+
const lastCheckRel = formatIsoRelativeSimple(data.lastGatewayCheckAt);
|
|
65723
|
+
setHtml(
|
|
65724
|
+
"lastGatewayCheckAt",
|
|
65725
|
+
`${lastCheckAbs}${lastCheckRel ? ` <span class="time-since">${lastCheckRel}</span>` : ""}`
|
|
65726
|
+
);
|
|
65727
|
+
const lc = data.lastConsumptionTelemetry;
|
|
65728
|
+
setHtml(
|
|
65729
|
+
"lastConsumptionTelemetry",
|
|
65730
|
+
lc ? `${lc.value.toLocaleString("pt-BR")}${lc.unit ? ` ${lc.unit}` : ""} - ${formatIsoPtBrWithSeconds(
|
|
65731
|
+
lc.timestamp
|
|
65732
|
+
)} ${formatIsoRelativeSimple(lc.timestamp)}`.trim() : "\u2014"
|
|
65733
|
+
);
|
|
64311
65734
|
set("monitoringEnabled", data.monitoringEnabled == null ? "\u2014" : data.monitoringEnabled ? "Sim" : "N\xE3o");
|
|
64312
|
-
set("lastGatewayCheckAt", formatIsoPtBr(data.lastGatewayCheckAt));
|
|
64313
|
-
set("lastGatewaySuccessCheckAt", formatIsoPtBr(data.lastGatewaySuccessCheckAt));
|
|
64314
65735
|
set("lastGatewayCheckLatencyMs", data.lastGatewayCheckLatencyMs != null ? `${data.lastGatewayCheckLatencyMs}ms` : "\u2014");
|
|
64315
65736
|
set("probeResult", data.probeResult || "\u2014");
|
|
64316
65737
|
set("statsConnectedDevices", data.stats?.connectedDevices != null ? String(data.stats.connectedDevices) : "\u2014");
|
|
@@ -64325,33 +65746,38 @@ function patchGatewayInfoDom(root, uuid, data) {
|
|
|
64325
65746
|
function connectivityFieldsetHtml(data, l, language, errors) {
|
|
64326
65747
|
const err = (field) => errors[field] ? `<span class="myio-csettings__field-error">${escAttr4(errors[field])}</span>` : "";
|
|
64327
65748
|
const errClass = (field) => errors[field] ? " myio-csettings__input--error" : "";
|
|
65749
|
+
const inputRow = (label, field, inputHtml) => `
|
|
65750
|
+
<div class="info-row">
|
|
65751
|
+
<span class="info-label">${escAttr4(label)}</span>
|
|
65752
|
+
<span class="info-value">${inputHtml}${err(field)}</span>
|
|
65753
|
+
</div>`;
|
|
64328
65754
|
return `
|
|
64329
|
-
<div class="form-card gateway-info-card myio-csettings-connectivity">
|
|
65755
|
+
<div class="form-card gateway-info-card info-card-wide myio-csettings-connectivity">
|
|
64330
65756
|
<h4 class="section-title">${escAttr4(
|
|
64331
65757
|
l.connectivitySection || (language === "en" ? "Connectivity Settings" : "Configura\xE7\xE3o de Conectividade")
|
|
64332
65758
|
)}</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
|
-
|
|
65759
|
+
<div class="info-grid">
|
|
65760
|
+
${inputRow(
|
|
65761
|
+
`${l.offlineGraceLabel || (language === "en" ? "Grace window (min)" : "Janela de gra\xE7a (min)")}:`,
|
|
65762
|
+
"offlineGraceMinutes",
|
|
65763
|
+
`<input type="number" min="0" step="1" name="offlineGraceMinutes" value="${data.offlineGraceMinutes}" class="myio-csettings__input${errClass(
|
|
65764
|
+
"offlineGraceMinutes"
|
|
65765
|
+
)}" />`
|
|
65766
|
+
)}
|
|
65767
|
+
${inputRow(
|
|
65768
|
+
`${l.blipToleranceLabel || (language === "en" ? "Blip tolerance (min)" : "Toler\xE2ncia de blip (min)")}:`,
|
|
65769
|
+
"blipToleranceMinutes",
|
|
65770
|
+
`<input type="number" min="0" step="1" name="blipToleranceMinutes" value="${data.blipToleranceMinutes}" class="myio-csettings__input${errClass(
|
|
65771
|
+
"blipToleranceMinutes"
|
|
65772
|
+
)}" />`
|
|
65773
|
+
)}
|
|
65774
|
+
${inputRow(
|
|
65775
|
+
`${l.offlineHardLabel || (language === "en" ? "Hard-offline threshold (min)" : "Limiar de hard-offline (min)")}:`,
|
|
65776
|
+
"offlineHardMinutes",
|
|
65777
|
+
`<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(
|
|
65778
|
+
"offlineHardMinutes"
|
|
65779
|
+
)}" />`
|
|
65780
|
+
)}
|
|
64355
65781
|
</div>
|
|
64356
65782
|
</div>
|
|
64357
65783
|
`;
|
|
@@ -64362,16 +65788,13 @@ function injectStyles9() {
|
|
|
64362
65788
|
const tag = document.createElement("style");
|
|
64363
65789
|
tag.id = STYLE_ID4;
|
|
64364
65790
|
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
65791
|
.myio-csettings__input{
|
|
64369
|
-
font:600 13px 'Nunito', system-ui, sans-serif;padding:
|
|
64370
|
-
border:1px solid #e5e7eb;background:#fff;color:#1f2937;width:
|
|
65792
|
+
font:600 13px 'Nunito', system-ui, sans-serif;padding:6px 8px;border-radius:6px;
|
|
65793
|
+
border:1px solid #e5e7eb;background:#fff;color:#1f2937;width:100px;box-sizing:border-box;
|
|
65794
|
+
text-align:right;
|
|
64371
65795
|
}
|
|
64372
65796
|
.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;}
|
|
65797
|
+
.myio-csettings__field-error{display:block;margin-top:4px;font:700 11px 'Nunito', system-ui, sans-serif;color:#dc2626;text-align:right;}
|
|
64375
65798
|
.theme-dark .myio-csettings__input{background:#111827;border-color:#4b5563;color:#e5e7eb;}
|
|
64376
65799
|
`;
|
|
64377
65800
|
document.head.appendChild(tag);
|
|
@@ -64403,6 +65826,12 @@ var CentralSettingsModal = class extends SettingsModalView {
|
|
|
64403
65826
|
deviceId: params.id,
|
|
64404
65827
|
deviceLabel: params.name,
|
|
64405
65828
|
deviceName: params.uuid ?? void 0,
|
|
65829
|
+
// RFC-0200: identity-card image (Geral tab) — without this, getDeviceImage()
|
|
65830
|
+
// sees an empty deviceType and falls back to DEFAULT_DEVICE_ICON (generic
|
|
65831
|
+
// 3F_MEDIDOR art) instead of deviceIcons.GATEWAY. A real device never sets
|
|
65832
|
+
// deviceType to this value (it's not a valid TB deviceProfile), so this is
|
|
65833
|
+
// fully inert for the device settings modal, same as isGateway above.
|
|
65834
|
+
deviceType: "GATEWAY",
|
|
64406
65835
|
// No domain concept for a central — suppresses the energy/water/
|
|
64407
65836
|
// temperature-only sections and removes the "Excluir Grupos" tab
|
|
64408
65837
|
// (gated on `domain === 'energy'` in the base class).
|
|
@@ -64412,11 +65841,15 @@ var CentralSettingsModal = class extends SettingsModalView {
|
|
|
64412
65841
|
jwtToken: "",
|
|
64413
65842
|
mapInstantaneousPower: {},
|
|
64414
65843
|
deviceMapInstaneousPower: {},
|
|
64415
|
-
//
|
|
64416
|
-
//
|
|
65844
|
+
// Inline gateway identity/telemetry in Geral (see file doc) — read-only
|
|
65845
|
+
// GCDR data. Real devices never set isGateway, so this is fully inert
|
|
64417
65846
|
// for the device settings modal.
|
|
64418
65847
|
isGateway: true,
|
|
64419
65848
|
gatewayInfo: mapToGatewayInfo(initialData),
|
|
65849
|
+
// RFC-0232: "Incidentes" tab gate + data — see CentralSettingsModalParams doc.
|
|
65850
|
+
userEmail: params.userEmail,
|
|
65851
|
+
superadmin: params.superadmin,
|
|
65852
|
+
interpolatedSlots: params.interpolatedSlots,
|
|
64420
65853
|
onSave: () => Promise.resolve(),
|
|
64421
65854
|
// rebound below once `this` exists (see class doc)
|
|
64422
65855
|
onClose: () => {
|
|
@@ -64436,7 +65869,7 @@ var CentralSettingsModal = class extends SettingsModalView {
|
|
|
64436
65869
|
}
|
|
64437
65870
|
/** Overrides the base's public `render()`: mounts the full device-modal
|
|
64438
65871
|
* shell via `super.render()`, then injects the Conectividade fieldset the
|
|
64439
|
-
* base has no concept of, and patches the
|
|
65872
|
+
* base has no concept of, and patches the Geral tab's inline gateway-info
|
|
64440
65873
|
* `#gwinfo-*` elements — its HTML was generated once at construction time
|
|
64441
65874
|
* (from whatever data was available then), so an async `onFetchSettings`
|
|
64442
65875
|
* resolving later needs this explicit patch to actually show up. */
|
|
@@ -64455,17 +65888,15 @@ var CentralSettingsModal = class extends SettingsModalView {
|
|
|
64455
65888
|
}
|
|
64456
65889
|
}
|
|
64457
65890
|
/**
|
|
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.
|
|
65891
|
+
* Injects the editable connectivity fieldset into the Geral tab's inline
|
|
65892
|
+
* gateway-info `.gateway-info-grid` (Identificação/Estatísticas/Metadados)
|
|
65893
|
+
* as a 4th `.form-card`, appended at the end. This grid is a descendant of
|
|
65894
|
+
* the Geral tab's own `<form>`, so the base's `new FormData(this.form)`
|
|
65895
|
+
* (used by the base's own `getFormData()`) picks up these inputs
|
|
65896
|
+
* automatically — no `getFormData()` override needed here.
|
|
64466
65897
|
*/
|
|
64467
65898
|
injectConnectivitySection(errors) {
|
|
64468
|
-
const grid = this.csRootEl?.querySelector("
|
|
65899
|
+
const grid = this.csRootEl?.querySelector(".gateway-info-grid");
|
|
64469
65900
|
if (!grid) return;
|
|
64470
65901
|
const html = connectivityFieldsetHtml(this.csCurrentData, this.csLabels, this.csLanguage, errors);
|
|
64471
65902
|
const existing = grid.querySelector(".myio-csettings-connectivity");
|
|
@@ -64473,30 +65904,7 @@ var CentralSettingsModal = class extends SettingsModalView {
|
|
|
64473
65904
|
existing.outerHTML = html;
|
|
64474
65905
|
return;
|
|
64475
65906
|
}
|
|
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 };
|
|
65907
|
+
grid.insertAdjacentHTML("beforeend", html);
|
|
64500
65908
|
}
|
|
64501
65909
|
async handleCentralSave(formData) {
|
|
64502
65910
|
const hardRaw = formData.offlineHardMinutes;
|
|
@@ -74322,270 +75730,6 @@ async function openDashboardPopupSettings(params) {
|
|
|
74322
75730
|
}
|
|
74323
75731
|
}
|
|
74324
75732
|
|
|
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
75733
|
// src/utils/goalsCoverage.ts
|
|
74590
75734
|
var MONTHS_PT = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"];
|
|
74591
75735
|
function formatCoverageRefPtBR(ref) {
|
|
@@ -101579,7 +102723,7 @@ async function openClearGcdrIdsModal(state6) {
|
|
|
101579
102723
|
if (!state6.selectedCustomer) return;
|
|
101580
102724
|
const customerId = getEntityId(state6.selectedCustomer);
|
|
101581
102725
|
const customerName = state6.selectedCustomer.name || state6.selectedCustomer.title || customerId;
|
|
101582
|
-
const
|
|
102726
|
+
const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
101583
102727
|
const overlay = document.createElement("div");
|
|
101584
102728
|
overlay.style.cssText = `
|
|
101585
102729
|
position:fixed; inset:0; z-index:99999;
|
|
@@ -101691,7 +102835,7 @@ async function openClearGcdrIdsModal(state6) {
|
|
|
101691
102835
|
const chunks = [];
|
|
101692
102836
|
for (let i = 0; i < items.length; i += 10) chunks.push(items.slice(i, i + 10));
|
|
101693
102837
|
for (let ci = 0; ci < chunks.length; ci++) {
|
|
101694
|
-
if (ci > 0) await
|
|
102838
|
+
if (ci > 0) await sleep2(800);
|
|
101695
102839
|
await Promise.all(chunks[ci].map(async (entity) => {
|
|
101696
102840
|
const tbId = entity.id?.id || "";
|
|
101697
102841
|
const name = entity.name || entity.label || tbId;
|
|
@@ -101741,7 +102885,7 @@ async function openClearGcdrIdsModal(state6) {
|
|
|
101741
102885
|
const chunks = [];
|
|
101742
102886
|
for (let i = 0; i < rows.length; i += 10) chunks.push(rows.slice(i, i + 10));
|
|
101743
102887
|
for (let ci = 0; ci < chunks.length; ci++) {
|
|
101744
|
-
if (ci > 0) await
|
|
102888
|
+
if (ci > 0) await sleep2(800);
|
|
101745
102889
|
await Promise.all(chunks[ci].map(async (row) => {
|
|
101746
102890
|
const keysToDelete = keys.filter((k) => row.present[k]);
|
|
101747
102891
|
try {
|
|
@@ -104348,6 +105492,7 @@ ${errors.slice(0, 5).join("\n")}` + (errors.length > 5 ? `
|
|
|
104348
105492
|
// src/components/card-badges/index.ts
|
|
104349
105493
|
var ALARM_STYLES_ID = "myio-alarm-badge-styles";
|
|
104350
105494
|
var TICKET_STYLES_ID = "myio-ticket-badge-styles";
|
|
105495
|
+
var INCIDENT_STYLES_ID = "myio-incident-badge-styles";
|
|
104351
105496
|
function injectAlarmBadgeStyles() {
|
|
104352
105497
|
if (document.getElementById(ALARM_STYLES_ID)) return;
|
|
104353
105498
|
const s = document.createElement("style");
|
|
@@ -104421,6 +105566,32 @@ function injectTicketBadgeStyles() {
|
|
|
104421
105566
|
`;
|
|
104422
105567
|
document.head.appendChild(s);
|
|
104423
105568
|
}
|
|
105569
|
+
function injectIncidentBadgeStyles() {
|
|
105570
|
+
if (document.getElementById(INCIDENT_STYLES_ID)) return;
|
|
105571
|
+
const s = document.createElement("style");
|
|
105572
|
+
s.id = INCIDENT_STYLES_ID;
|
|
105573
|
+
s.textContent = `
|
|
105574
|
+
/* Stacked directly under .myio-alarm-badge (same corner, same shape) */
|
|
105575
|
+
.myio-incident-badge {
|
|
105576
|
+
position: absolute;
|
|
105577
|
+
top: 28px;
|
|
105578
|
+
left: 6px;
|
|
105579
|
+
background: #7C3AED;
|
|
105580
|
+
color: #fff;
|
|
105581
|
+
border-radius: 10px;
|
|
105582
|
+
padding: 2px 5px;
|
|
105583
|
+
font-size: 10px;
|
|
105584
|
+
font-weight: 700;
|
|
105585
|
+
display: flex;
|
|
105586
|
+
align-items: center;
|
|
105587
|
+
gap: 2px;
|
|
105588
|
+
z-index: 10;
|
|
105589
|
+
pointer-events: none;
|
|
105590
|
+
line-height: 1.3;
|
|
105591
|
+
}
|
|
105592
|
+
`;
|
|
105593
|
+
document.head.appendChild(s);
|
|
105594
|
+
}
|
|
104424
105595
|
function defaultAlarmCount(gcdrDeviceId) {
|
|
104425
105596
|
const aso = window.AlarmServiceOrchestrator;
|
|
104426
105597
|
return aso?.getAlarmCountForDevice?.(gcdrDeviceId) ?? 0;
|
|
@@ -104431,6 +105602,13 @@ function alarmTitle(count) {
|
|
|
104431
105602
|
function ticketTitle(count) {
|
|
104432
105603
|
return count > 0 ? `${count} chamado${count !== 1 ? "s" : ""} aberto${count !== 1 ? "s" : ""}` : "Chamados";
|
|
104433
105604
|
}
|
|
105605
|
+
function defaultIncidentCount(gcdrDeviceId) {
|
|
105606
|
+
const iso = window.IncidentServiceOrchestrator;
|
|
105607
|
+
return iso?.getIncidentCountForDevice?.(gcdrDeviceId) ?? 0;
|
|
105608
|
+
}
|
|
105609
|
+
function incidentTitle(count) {
|
|
105610
|
+
return `${count} incidente${count !== 1 ? "s" : ""} de interpola\xE7\xE3o`;
|
|
105611
|
+
}
|
|
104434
105612
|
function addAlarmBadge(cardElement, gcdrDeviceId, opts) {
|
|
104435
105613
|
if (!cardElement || !gcdrDeviceId) return;
|
|
104436
105614
|
if (cardElement.querySelector(`[data-alarm-device-id="${gcdrDeviceId}"]`)) return;
|
|
@@ -104516,6 +105694,36 @@ function refreshTicketBadges(opts) {
|
|
|
104516
105694
|
}
|
|
104517
105695
|
});
|
|
104518
105696
|
}
|
|
105697
|
+
function addIncidentBadge(cardElement, gcdrDeviceId, opts) {
|
|
105698
|
+
if (!cardElement || !gcdrDeviceId) return;
|
|
105699
|
+
if (cardElement.querySelector(`[data-incident-device-id="${gcdrDeviceId}"]`)) return;
|
|
105700
|
+
const count = (opts?.getCount ?? defaultIncidentCount)(gcdrDeviceId);
|
|
105701
|
+
injectIncidentBadgeStyles();
|
|
105702
|
+
if (cardElement.style) cardElement.style.position = "relative";
|
|
105703
|
+
const badge = document.createElement("div");
|
|
105704
|
+
badge.className = "myio-incident-badge";
|
|
105705
|
+
badge.setAttribute("data-incident-device-id", gcdrDeviceId);
|
|
105706
|
+
badge.style.display = count > 0 ? "" : "none";
|
|
105707
|
+
badge.title = incidentTitle(count);
|
|
105708
|
+
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>`;
|
|
105709
|
+
cardElement.appendChild(badge);
|
|
105710
|
+
}
|
|
105711
|
+
function refreshIncidentBadges(opts) {
|
|
105712
|
+
const getCount = opts?.getCount ?? defaultIncidentCount;
|
|
105713
|
+
document.querySelectorAll(".myio-incident-badge[data-incident-device-id]").forEach((badge) => {
|
|
105714
|
+
const gcdrDeviceId = badge.getAttribute("data-incident-device-id");
|
|
105715
|
+
if (!gcdrDeviceId) return;
|
|
105716
|
+
const count = getCount(gcdrDeviceId);
|
|
105717
|
+
const span = badge.querySelector("span");
|
|
105718
|
+
if (count > 0) {
|
|
105719
|
+
badge.style.display = "";
|
|
105720
|
+
badge.title = incidentTitle(count);
|
|
105721
|
+
if (span) span.textContent = count > 99 ? "99+" : String(count);
|
|
105722
|
+
} else {
|
|
105723
|
+
badge.style.display = "none";
|
|
105724
|
+
}
|
|
105725
|
+
});
|
|
105726
|
+
}
|
|
104519
105727
|
|
|
104520
105728
|
// src/components/premium-modals/dialog/styles.ts
|
|
104521
105729
|
var FONT_LINK_ID = "myio-dialog-font-nunito";
|
|
@@ -163107,6 +164315,16 @@ var CSS4 = `
|
|
|
163107
164315
|
justify-content:center;gap:2px;line-height:1;cursor:default;box-shadow:0 1px 3px rgba(0,0,0,.25);
|
|
163108
164316
|
z-index:10;
|
|
163109
164317
|
}
|
|
164318
|
+
/* Incident (RFC-0232) sits at the vertical midpoint of the left border \u2014
|
|
164319
|
+
below the alarm badge, above the ticket badge \u2014 reading as a 3-badge
|
|
164320
|
+
stack on the same edge regardless of card height (card vs. compact). */
|
|
164321
|
+
.myio-cscard__incident-badge{
|
|
164322
|
+
position:absolute;top:50%;left:-14px;transform:translateY(-50%);
|
|
164323
|
+
background:#7C3AED;color:#fff;border-radius:10px;min-width:22px;height:22px;
|
|
164324
|
+
padding:0 6px;font:700 10px 'Nunito', system-ui, sans-serif;display:flex;align-items:center;
|
|
164325
|
+
justify-content:center;gap:2px;line-height:1;cursor:default;box-shadow:0 1px 3px rgba(0,0,0,.25);
|
|
164326
|
+
z-index:10;
|
|
164327
|
+
}
|
|
163110
164328
|
.myio-cscard__ticket-badge{
|
|
163111
164329
|
position:absolute;bottom:18%;left:-14px;transform:translateY(50%);width:24px;height:24px;
|
|
163112
164330
|
background:rgba(8,145,178,.12);color:#0891b2;border:1px solid rgba(8,145,178,.25);
|
|
@@ -163573,6 +164791,7 @@ var CentralStatusCard = class _CentralStatusCard {
|
|
|
163573
164791
|
detachTitleTooltip = null;
|
|
163574
164792
|
detachDivergenceTooltip = null;
|
|
163575
164793
|
detachAlarmTooltip = null;
|
|
164794
|
+
detachIncidentTooltip = null;
|
|
163576
164795
|
detachTicketTooltip = null;
|
|
163577
164796
|
detachAnnotationTooltips = [];
|
|
163578
164797
|
/** Latency from the PREVIOUS render, to derive the trend arrow next to
|
|
@@ -163637,6 +164856,7 @@ var CentralStatusCard = class _CentralStatusCard {
|
|
|
163637
164856
|
this.detachTitleTooltip?.();
|
|
163638
164857
|
this.detachDivergenceTooltip?.();
|
|
163639
164858
|
this.detachAlarmTooltip?.();
|
|
164859
|
+
this.detachIncidentTooltip?.();
|
|
163640
164860
|
this.detachTicketTooltip?.();
|
|
163641
164861
|
this.detachAnnotationTooltips.forEach((fn) => fn());
|
|
163642
164862
|
this.el.remove();
|
|
@@ -163957,6 +165177,12 @@ var CentralStatusCard = class _CentralStatusCard {
|
|
|
163957
165177
|
<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
165178
|
<span>${p.alarmCount > 99 ? "99+" : p.alarmCount}</span>
|
|
163959
165179
|
</div>` : "";
|
|
165180
|
+
const incidentBadgeHtml = p.incidentCount && p.incidentCount > 0 ? `<div class="myio-cscard__incident-badge" data-role="incident-badge" aria-label="${escAttr6(
|
|
165181
|
+
`${p.incidentCount} incidente${p.incidentCount !== 1 ? "s" : ""} de interpola\xE7\xE3o`
|
|
165182
|
+
)}">
|
|
165183
|
+
<svg viewBox="0 0 24 24" width="10" height="10" fill="currentColor" aria-hidden="true"><path d="M12 2L1 21h22L12 2z"/></svg>
|
|
165184
|
+
<span>${p.incidentCount > 99 ? "99+" : p.incidentCount}</span>
|
|
165185
|
+
</div>` : "";
|
|
163960
165186
|
const ticketBadgeHtml = p.ticketCount && p.ticketCount > 0 ? `<div class="myio-cscard__ticket-badge" data-role="ticket-badge" aria-label="${escAttr6(
|
|
163961
165187
|
`${p.ticketCount} chamado${p.ticketCount !== 1 ? "s" : ""} aberto${p.ticketCount !== 1 ? "s" : ""}`
|
|
163962
165188
|
)}">
|
|
@@ -163993,6 +165219,7 @@ var CentralStatusCard = class _CentralStatusCard {
|
|
|
163993
165219
|
const compactEvidenceLine = variant === "compact" ? `<div class="myio-cscard__evidence-line">${devicesText ? escAttr6(devicesText) : ""}${probeText ? (devicesText ? " \xB7 " : "") + probeText : ""}</div>` : "";
|
|
163994
165220
|
this.el.innerHTML = `
|
|
163995
165221
|
${alarmBadgeHtml}
|
|
165222
|
+
${incidentBadgeHtml}
|
|
163996
165223
|
${ticketBadgeHtml}
|
|
163997
165224
|
${annotationBadgesHtml}
|
|
163998
165225
|
<div class="myio-cscard__surface">
|
|
@@ -164096,6 +165323,8 @@ var CentralStatusCard = class _CentralStatusCard {
|
|
|
164096
165323
|
this.detachDivergenceTooltip = null;
|
|
164097
165324
|
this.detachAlarmTooltip?.();
|
|
164098
165325
|
this.detachAlarmTooltip = null;
|
|
165326
|
+
this.detachIncidentTooltip?.();
|
|
165327
|
+
this.detachIncidentTooltip = null;
|
|
164099
165328
|
this.detachTicketTooltip?.();
|
|
164100
165329
|
this.detachTicketTooltip = null;
|
|
164101
165330
|
this.detachAnnotationTooltips.forEach((fn) => fn());
|
|
@@ -164137,6 +165366,19 @@ var CentralStatusCard = class _CentralStatusCard {
|
|
|
164137
165366
|
}));
|
|
164138
165367
|
}
|
|
164139
165368
|
}
|
|
165369
|
+
if (p.incidentCount && p.incidentCount > 0) {
|
|
165370
|
+
const incidentBadge = this.el.querySelector('[data-role="incident-badge"]');
|
|
165371
|
+
if (incidentBadge) {
|
|
165372
|
+
const count = p.incidentCount;
|
|
165373
|
+
this.detachIncidentTooltip = InfoTooltip.attach(incidentBadge, () => ({
|
|
165374
|
+
icon: "\u{1F53A}",
|
|
165375
|
+
title: en ? "Incidents" : "Incidentes",
|
|
165376
|
+
content: escAttr6(
|
|
165377
|
+
en ? `${count} interpolated reading${count !== 1 ? "s" : ""}` : `${count} incidente${count !== 1 ? "s" : ""} de interpola\xE7\xE3o`
|
|
165378
|
+
)
|
|
165379
|
+
}));
|
|
165380
|
+
}
|
|
165381
|
+
}
|
|
164140
165382
|
if (p.ticketCount && p.ticketCount > 0) {
|
|
164141
165383
|
const ticketBadge = this.el.querySelector('[data-role="ticket-badge"]');
|
|
164142
165384
|
if (ticketBadge) {
|
|
@@ -164240,6 +165482,13 @@ var CentralStatusCard = class _CentralStatusCard {
|
|
|
164240
165482
|
this.params.onAlarmBadgeClick?.({ id: this.params.id, source: "central-status-card" });
|
|
164241
165483
|
});
|
|
164242
165484
|
}
|
|
165485
|
+
const incidentBadge = this.el.querySelector('[data-role="incident-badge"]');
|
|
165486
|
+
if (incidentBadge && this.params.onIncidentBadgeClick) {
|
|
165487
|
+
incidentBadge.addEventListener("click", (e) => {
|
|
165488
|
+
e.stopPropagation();
|
|
165489
|
+
this.params.onIncidentBadgeClick?.({ id: this.params.id, source: "central-status-card" });
|
|
165490
|
+
});
|
|
165491
|
+
}
|
|
164243
165492
|
const ticketBadge = this.el.querySelector('[data-role="ticket-badge"]');
|
|
164244
165493
|
if (ticketBadge) {
|
|
164245
165494
|
ticketBadge.addEventListener("click", (e) => {
|
|
@@ -164438,6 +165687,9 @@ var version = package_default.version || "0.0.0";
|
|
|
164438
165687
|
FreshdeskClient,
|
|
164439
165688
|
GRANULARITY_SELECTOR_CSS_PREFIX,
|
|
164440
165689
|
GRID_SORT_OPTIONS,
|
|
165690
|
+
GcdrAnnotationsClient,
|
|
165691
|
+
GcdrAnnotationsConflictError,
|
|
165692
|
+
GcdrAnnotationsError,
|
|
164441
165693
|
GoalsModal,
|
|
164442
165694
|
GoalsMoneyApiError,
|
|
164443
165695
|
GoalsMoneyClient,
|
|
@@ -164579,9 +165831,13 @@ var version = package_default.version || "0.0.0";
|
|
|
164579
165831
|
WELCOME_DEFAULT_PALETTE,
|
|
164580
165832
|
Water,
|
|
164581
165833
|
WaterSummaryTooltip,
|
|
165834
|
+
adaptGcdrListToLegacyAnnotations,
|
|
165835
|
+
adaptGcdrToLegacyAnnotation,
|
|
165836
|
+
adaptLegacyToGcdrInput,
|
|
164582
165837
|
addAlarmBadge,
|
|
164583
165838
|
addDetectionContext,
|
|
164584
165839
|
addFreshdeskTicketNote,
|
|
165840
|
+
addIncidentBadge,
|
|
164585
165841
|
addNamespace,
|
|
164586
165842
|
addTicketBadge,
|
|
164587
165843
|
aggregateByDay,
|
|
@@ -164688,6 +165944,8 @@ var version = package_default.version || "0.0.0";
|
|
|
164688
165944
|
createFilterModalComponent,
|
|
164689
165945
|
createFooterComponent,
|
|
164690
165946
|
createFreshdeskTicket,
|
|
165947
|
+
createGcdrAnnotationsAuthStrategy,
|
|
165948
|
+
createGcdrAnnotationsClient,
|
|
164691
165949
|
createGoalsBarTooltip,
|
|
164692
165950
|
createGoalsMoneyClient,
|
|
164693
165951
|
createGranularitySelector,
|
|
@@ -165032,6 +166290,7 @@ var version = package_default.version || "0.0.0";
|
|
|
165032
166290
|
recalculateDeviceStatus,
|
|
165033
166291
|
recomputeDeviceGridPercentages,
|
|
165034
166292
|
refreshAlarmBadges,
|
|
166293
|
+
refreshIncidentBadges,
|
|
165035
166294
|
refreshTicketBadges,
|
|
165036
166295
|
removeAlarmsNotificationsPanelStyles,
|
|
165037
166296
|
removeBASDashboardStyles,
|