myio-js-library 0.1.508 → 0.1.510
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 +2619 -17
- package/dist/index.d.cts +593 -2
- package/dist/index.js +2590 -17
- package/dist/myio-js-library.umd.js +2587 -17
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -563,9 +563,12 @@ __export(index_exports, {
|
|
|
563
563
|
ALARM_STATE_CONFIG: () => STATE_CONFIG,
|
|
564
564
|
AMBIENTE_GROUP_CSS_PREFIX: () => AMBIENTE_GROUP_CSS_PREFIX,
|
|
565
565
|
AMBIENTE_MODAL_CSS_PREFIX: () => AMBIENTE_MODAL_CSS_PREFIX,
|
|
566
|
+
ANNOTATION_CSV_COLUMNS: () => CSV_COLUMNS,
|
|
567
|
+
ANNOTATION_SORT_OPTIONS: () => SORT_OPTIONS,
|
|
566
568
|
ANNOTATION_TYPE_COLORS: () => ANNOTATION_TYPE_COLORS,
|
|
567
569
|
ANNOTATION_TYPE_LABELS: () => ANNOTATION_TYPE_LABELS,
|
|
568
570
|
ANNOTATION_TYPE_LABELS_EN: () => ANNOTATION_TYPE_LABELS_EN,
|
|
571
|
+
ANNOTATION_VIRTUAL_THRESHOLD: () => VIRTUAL_SCROLL_THRESHOLD,
|
|
569
572
|
ActionButtonController: () => ActionButtonController,
|
|
570
573
|
ActionButtonView: () => ActionButtonView,
|
|
571
574
|
AlarmService: () => AlarmService,
|
|
@@ -588,6 +591,7 @@ __export(index_exports, {
|
|
|
588
591
|
ContractSummaryTooltip: () => ContractSummaryTooltip,
|
|
589
592
|
CustomerCardV1: () => CustomerCardV1,
|
|
590
593
|
CustomerCardV2: () => CustomerCardV2,
|
|
594
|
+
CustomerDeviceService: () => CustomerDeviceService,
|
|
591
595
|
DAY_LABELS: () => DAY_LABELS,
|
|
592
596
|
DAY_LABELS_FULL: () => DAY_LABELS_FULL,
|
|
593
597
|
DECIMAL_OPTIONS: () => DECIMAL_OPTIONS,
|
|
@@ -595,6 +599,7 @@ __export(index_exports, {
|
|
|
595
599
|
DEFAULT_ALARM_FILTERS: () => DEFAULT_ALARM_FILTERS,
|
|
596
600
|
DEFAULT_ALARM_FILTER_TABS: () => DEFAULT_ALARM_FILTER_TABS,
|
|
597
601
|
DEFAULT_ALARM_STATS: () => DEFAULT_ALARM_STATS,
|
|
602
|
+
DEFAULT_ANNOTATION_SORT: () => DEFAULT_SORT,
|
|
598
603
|
DEFAULT_BAS_SETTINGS: () => DEFAULT_BAS_SETTINGS,
|
|
599
604
|
DEFAULT_CLAMP_RANGE: () => DEFAULT_CLAMP_RANGE,
|
|
600
605
|
DEFAULT_DASHBOARD_KPIS: () => DEFAULT_DASHBOARD_KPIS,
|
|
@@ -692,6 +697,7 @@ __export(index_exports, {
|
|
|
692
697
|
HEADER_STYLE_DEFAULT: () => HEADER_STYLE_DEFAULT,
|
|
693
698
|
HEADER_STYLE_PREMIUM_GREEN: () => HEADER_STYLE_PREMIUM_GREEN,
|
|
694
699
|
HEADER_STYLE_SLIM: () => HEADER_STYLE_SLIM,
|
|
700
|
+
HeaderAnnotationsPanel: () => HeaderAnnotationsPanel,
|
|
695
701
|
HeaderDevicesGridController: () => HeaderDevicesGridController,
|
|
696
702
|
HeaderDevicesGridView: () => HeaderDevicesGridView,
|
|
697
703
|
HeaderFilterModal: () => HeaderFilterModal,
|
|
@@ -790,6 +796,7 @@ __export(index_exports, {
|
|
|
790
796
|
TempRangeTooltip: () => TempRangeTooltip,
|
|
791
797
|
TempSensorSummaryTooltip: () => TempSensorSummaryTooltip,
|
|
792
798
|
UsersSummaryTooltip: () => UsersSummaryTooltip,
|
|
799
|
+
VirtualList: () => VirtualList,
|
|
793
800
|
WAITING_STATUSES: () => WAITING_STATUSES,
|
|
794
801
|
WATER_DEVICE_CATEGORIES: () => WATER_DEVICE_CATEGORIES,
|
|
795
802
|
WATER_SORT_OPTIONS: () => WATER_SORT_OPTIONS,
|
|
@@ -806,6 +813,9 @@ __export(index_exports, {
|
|
|
806
813
|
assignShoppingColors: () => assignShoppingColors,
|
|
807
814
|
averageByDay: () => averageByDay,
|
|
808
815
|
buildAmbienteGroupData: () => buildAmbienteGroupData,
|
|
816
|
+
buildAnnotationServiceOrchestrator: () => buildAnnotationServiceOrchestrator,
|
|
817
|
+
buildAnnotationsCsv: () => buildAnnotationsCsv,
|
|
818
|
+
buildAnnotationsExportFilename: () => buildExportFilename,
|
|
809
819
|
buildDeviceGridEntityObject: () => buildEntityObject,
|
|
810
820
|
buildEquipmentCategoryDataForTooltip: () => buildEquipmentCategoryDataForTooltip,
|
|
811
821
|
buildEquipmentCategorySummary: () => buildEquipmentCategorySummary,
|
|
@@ -840,6 +850,7 @@ __export(index_exports, {
|
|
|
840
850
|
classifyWaterLabels: () => classifyWaterLabels,
|
|
841
851
|
clearAllAuthCaches: () => clearAllAuthCaches,
|
|
842
852
|
clearFreshdeskTicketsOnTB: () => clearFreshdeskTicketsOnTB,
|
|
853
|
+
closeAnnotationsExportModal: () => closeExportModal,
|
|
843
854
|
connectionStatusIcons: () => connectionStatusIcons,
|
|
844
855
|
createActionButton: () => createActionButton,
|
|
845
856
|
createAlarmCardElement: () => createAlarmCardElement,
|
|
@@ -856,6 +867,7 @@ __export(index_exports, {
|
|
|
856
867
|
createCustomerCardV2: () => createCustomerCardV2,
|
|
857
868
|
createDateRangePicker: () => createDateRangePicker2,
|
|
858
869
|
createDaysGrid: () => createDaysGrid,
|
|
870
|
+
createDefaultAnnotationFilter: () => createDefaultFilter,
|
|
859
871
|
createDeviceGridBusyModal: () => createBusyModal,
|
|
860
872
|
createDeviceGridState: () => createState,
|
|
861
873
|
createDeviceGridV6: () => createDeviceGridV6,
|
|
@@ -904,6 +916,7 @@ __export(index_exports, {
|
|
|
904
916
|
createToggleSwitch: () => createToggleSwitch,
|
|
905
917
|
createWaterPanelComponent: () => createWaterPanelComponent,
|
|
906
918
|
createWidgetController: () => createWidgetController,
|
|
919
|
+
csvEscapeAnnotation: () => csvEscape,
|
|
907
920
|
decodePayload: () => decodePayload,
|
|
908
921
|
decodePayloadBase64Xor: () => decodePayloadBase64Xor,
|
|
909
922
|
deleteFreshdeskTicket: () => deleteTicket,
|
|
@@ -915,6 +928,10 @@ __export(index_exports, {
|
|
|
915
928
|
determineInterval: () => determineInterval,
|
|
916
929
|
deviceStatusIcons: () => deviceStatusIcons,
|
|
917
930
|
doSchedulesOverlap: () => doSchedulesOverlap,
|
|
931
|
+
downloadAnnotationsTextFile: () => downloadTextFile,
|
|
932
|
+
escapeAnnotationHtml: () => escapeHtml6,
|
|
933
|
+
exportAnnotationsCsv: () => exportAnnotationsCsv,
|
|
934
|
+
exportAnnotationsPdf: () => exportAnnotationsPdf,
|
|
918
935
|
exportGridCsv: () => exportGridCsv,
|
|
919
936
|
exportGridPdf: () => exportGridPdf,
|
|
920
937
|
exportGridXls: () => exportGridXls,
|
|
@@ -939,6 +956,7 @@ __export(index_exports, {
|
|
|
939
956
|
formatAlarmRelativeTime: () => formatAlarmRelativeTime,
|
|
940
957
|
formatAllInSameUnit: () => formatAllInSameUnit,
|
|
941
958
|
formatAllInSameWaterUnit: () => formatAllInSameWaterUnit,
|
|
959
|
+
formatAnnotationRelativeTime: () => formatRelative,
|
|
942
960
|
formatDashboardPercentage: () => formatPercentage4,
|
|
943
961
|
formatDateForInput: () => formatDateForInput,
|
|
944
962
|
formatDateToYMD: () => formatDateToYMD,
|
|
@@ -987,6 +1005,7 @@ __export(index_exports, {
|
|
|
987
1005
|
getFirstDayOfMonthFor: () => getFirstDayOfMonthFor,
|
|
988
1006
|
getGroupColor: () => getGroupColor,
|
|
989
1007
|
getHashColor: () => getHashColor,
|
|
1008
|
+
getHeaderAnnotationsPanel: () => getHeaderAnnotationsPanel,
|
|
990
1009
|
getImageByConsumption: () => getImageByConsumption,
|
|
991
1010
|
getLastDayOfMonth: () => getLastDayOfMonth,
|
|
992
1011
|
getModalHeaderStyles: () => getModalHeaderStyles,
|
|
@@ -1010,6 +1029,7 @@ __export(index_exports, {
|
|
|
1010
1029
|
groupByDay: () => groupByDay,
|
|
1011
1030
|
handleDeviceType: () => handleDeviceType,
|
|
1012
1031
|
hasSelectedDays: () => hasSelectedDays,
|
|
1032
|
+
highlightAnnotationMatches: () => highlightMatches,
|
|
1013
1033
|
initMyIOAuthContext: () => initMyIOAuthContext,
|
|
1014
1034
|
initOnOffTimelineTooltips: () => initOnOffTimelineTooltips,
|
|
1015
1035
|
injectActionButtonStyles: () => injectActionButtonStyles,
|
|
@@ -1023,6 +1043,7 @@ __export(index_exports, {
|
|
|
1023
1043
|
injectDeviceOperationalCardGridStyles: () => injectDeviceOperationalCardGridStyles,
|
|
1024
1044
|
injectDeviceOperationalCardStyles: () => injectDeviceOperationalCardStyles,
|
|
1025
1045
|
injectFancoilRemoteStyles: () => injectFancoilRemoteStyles,
|
|
1046
|
+
injectHeaderAnnotationsStyles: () => injectStylesOnce,
|
|
1026
1047
|
injectHeaderDevicesGridStyles: () => injectHeaderDevicesGridStyles,
|
|
1027
1048
|
injectHeaderShoppingStyles: () => injectHeaderShoppingStyles,
|
|
1028
1049
|
injectMenuShoppingStyles: () => injectMenuShoppingStyles,
|
|
@@ -1041,6 +1062,7 @@ __export(index_exports, {
|
|
|
1041
1062
|
injectSwitchControlStyles: () => injectSwitchControlStyles,
|
|
1042
1063
|
interpolateTemperature: () => interpolateTemperature,
|
|
1043
1064
|
isAlarmActive: () => isAlarmActive,
|
|
1065
|
+
isAnnotationOverdue: () => isOverdue,
|
|
1044
1066
|
isConnectionStale: () => isConnectionStale,
|
|
1045
1067
|
isDeviceOffline: () => isDeviceOffline,
|
|
1046
1068
|
isEndAfterStart: () => isEndAfterStart,
|
|
@@ -1063,6 +1085,7 @@ __export(index_exports, {
|
|
|
1063
1085
|
mapDeviceStatusToCardStatus: () => mapDeviceStatusToCardStatus,
|
|
1064
1086
|
mapDeviceToConnectionStatus: () => mapDeviceToConnectionStatus,
|
|
1065
1087
|
myioExportData: () => myioExportData,
|
|
1088
|
+
nfdNormalizeAnnotationSearch: () => nfdNormalize,
|
|
1066
1089
|
normalizeConnectionStatus: () => normalizeConnectionStatus,
|
|
1067
1090
|
normalizeRecipients: () => normalizeRecipients,
|
|
1068
1091
|
numbers: () => numbers_exports,
|
|
@@ -1071,6 +1094,7 @@ __export(index_exports, {
|
|
|
1071
1094
|
openAlarmDetailsModal: () => openAlarmDetailsModal,
|
|
1072
1095
|
openAmbienteDetailModal: () => openAmbienteDetailModal,
|
|
1073
1096
|
openAmbienteGroupModal: () => openAmbienteGroupModal,
|
|
1097
|
+
openAnnotationsExportModal: () => openExportModal,
|
|
1074
1098
|
openContractDevicesModal: () => openContractDevicesModal,
|
|
1075
1099
|
openDashboardPopup: () => openDashboardPopup,
|
|
1076
1100
|
openDashboardPopupAllReport: () => openDashboardPopupAllReport,
|
|
@@ -1097,6 +1121,7 @@ __export(index_exports, {
|
|
|
1097
1121
|
openUserManagementModal: () => openUserManagementModal,
|
|
1098
1122
|
openWelcomeModal: () => openWelcomeModal,
|
|
1099
1123
|
parseInputDateToDate: () => parseInputDateToDate,
|
|
1124
|
+
parseLogAnnotations: () => parseLogAnnotations,
|
|
1100
1125
|
periodKey: () => periodKey,
|
|
1101
1126
|
readFreshdeskTicketsFromTB: () => readFreshdeskTicketsFromTB,
|
|
1102
1127
|
recalculateDeviceStatus: () => recalculateDeviceStatus,
|
|
@@ -1110,6 +1135,7 @@ __export(index_exports, {
|
|
|
1110
1135
|
removeOperationalHeaderDevicesGridStyles: () => removeOperationalHeaderDevicesGridStyles,
|
|
1111
1136
|
removeSchedulingSharedStyles: () => removeSchedulingSharedStyles,
|
|
1112
1137
|
renderAlarmCard: () => renderAlarmCard,
|
|
1138
|
+
renderAnnotationItemCard: () => renderAnnotationItemCard,
|
|
1113
1139
|
renderCardAmbienteV6: () => renderCardAmbienteV6,
|
|
1114
1140
|
renderCardComponent: () => renderCardComponent,
|
|
1115
1141
|
renderCardComponentEnhanced: () => renderCardComponent2,
|
|
@@ -1140,6 +1166,8 @@ __export(index_exports, {
|
|
|
1140
1166
|
schedShowConfirmModal: () => showConfirmModal,
|
|
1141
1167
|
schedShowNotificationModal: () => showNotificationModal,
|
|
1142
1168
|
shouldFlashIcon: () => shouldFlashIcon,
|
|
1169
|
+
shouldVirtualizeAnnotationList: () => shouldVirtualize,
|
|
1170
|
+
sortAnnotationGroups: () => sortGroups,
|
|
1143
1171
|
sortDeviceGridDevices: () => sortDevices,
|
|
1144
1172
|
strings: () => strings_exports,
|
|
1145
1173
|
telemetryInfoFormatEnergy: () => formatEnergy2,
|
|
@@ -1151,6 +1179,7 @@ __export(index_exports, {
|
|
|
1151
1179
|
toCSV: () => toCSV,
|
|
1152
1180
|
toFixedSafe: () => toFixedSafe,
|
|
1153
1181
|
toFreshdeskTicketSummary: () => toSummary,
|
|
1182
|
+
truncateAnnotationText: () => truncate2,
|
|
1154
1183
|
updateDeviceGridStats: () => updateStats,
|
|
1155
1184
|
updateFreshdeskTicket: () => updateTicket,
|
|
1156
1185
|
upsertAlarmAnnotation: () => upsertAlarmAnnotation,
|
|
@@ -1164,7 +1193,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
1164
1193
|
// package.json
|
|
1165
1194
|
var package_default = {
|
|
1166
1195
|
name: "myio-js-library",
|
|
1167
|
-
version: "0.1.
|
|
1196
|
+
version: "0.1.510",
|
|
1168
1197
|
description: "A clean, standalone JS SDK for MYIO projects",
|
|
1169
1198
|
license: "MIT",
|
|
1170
1199
|
repository: "github:gh-myio/myio-js-library",
|
|
@@ -16574,16 +16603,18 @@ function hideWithAnimation3() {
|
|
|
16574
16603
|
}
|
|
16575
16604
|
function positionTooltip3(container, triggerElement) {
|
|
16576
16605
|
const rect = triggerElement.getBoundingClientRect();
|
|
16606
|
+
const margin = 12;
|
|
16607
|
+
const vw = window.innerWidth;
|
|
16608
|
+
const vh = window.innerHeight;
|
|
16609
|
+
const cw = container.offsetWidth || 450;
|
|
16610
|
+
const ch = container.offsetHeight || 420;
|
|
16577
16611
|
let left = rect.left;
|
|
16612
|
+
if (left + cw > vw - margin) left = vw - cw - margin;
|
|
16613
|
+
if (left < margin) left = margin;
|
|
16578
16614
|
let top = rect.bottom + 8;
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
}
|
|
16583
|
-
if (left < 10) left = 10;
|
|
16584
|
-
if (top + 400 > window.innerHeight) {
|
|
16585
|
-
top = rect.top - 8 - 400;
|
|
16586
|
-
if (top < 10) top = 10;
|
|
16615
|
+
if (top + ch > vh - margin) {
|
|
16616
|
+
const above = rect.top - 8 - ch;
|
|
16617
|
+
top = above >= margin ? above : Math.max(margin, vh - ch - margin);
|
|
16587
16618
|
}
|
|
16588
16619
|
container.style.left = left + "px";
|
|
16589
16620
|
container.style.top = top + "px";
|
|
@@ -49868,6 +49899,11 @@ var AnnotationsTab = class {
|
|
|
49868
49899
|
return false;
|
|
49869
49900
|
}
|
|
49870
49901
|
this.onAnnotationChange?.(this.annotations);
|
|
49902
|
+
window.dispatchEvent(
|
|
49903
|
+
new CustomEvent("myio:annotation-changed", {
|
|
49904
|
+
detail: { deviceId: this.deviceId, action: "save", timestamp: Date.now() }
|
|
49905
|
+
})
|
|
49906
|
+
);
|
|
49871
49907
|
return true;
|
|
49872
49908
|
} catch (error) {
|
|
49873
49909
|
console.error("[AnnotationsTab] Error saving annotations:", error);
|
|
@@ -66462,7 +66498,7 @@ async function createInputDateRangePickerInsideDIV(params) {
|
|
|
66462
66498
|
placeholder = "Clique para selecionar per\xEDodo",
|
|
66463
66499
|
pickerOptions = {},
|
|
66464
66500
|
classNames = {},
|
|
66465
|
-
injectStyles:
|
|
66501
|
+
injectStyles: injectStyles16 = true,
|
|
66466
66502
|
showHelper = true
|
|
66467
66503
|
} = params;
|
|
66468
66504
|
validateId(containerId, "containerId");
|
|
@@ -66471,7 +66507,7 @@ async function createInputDateRangePickerInsideDIV(params) {
|
|
|
66471
66507
|
if (!container) {
|
|
66472
66508
|
throw new Error(`[createInputDateRangePickerInsideDIV] Container '#${containerId}' not found`);
|
|
66473
66509
|
}
|
|
66474
|
-
if (
|
|
66510
|
+
if (injectStyles16) {
|
|
66475
66511
|
injectPremiumStyles();
|
|
66476
66512
|
}
|
|
66477
66513
|
let inputEl = document.getElementById(inputId);
|
|
@@ -66670,7 +66706,7 @@ function openGoalsPanel(params) {
|
|
|
66670
66706
|
modal.setAttribute("aria-labelledby", "goals-modal-title");
|
|
66671
66707
|
modal.innerHTML = generateModalHTML();
|
|
66672
66708
|
document.body.appendChild(modal);
|
|
66673
|
-
|
|
66709
|
+
injectStyles16();
|
|
66674
66710
|
attachEventListeners();
|
|
66675
66711
|
trapFocus(modal);
|
|
66676
66712
|
renderTabContent();
|
|
@@ -67432,7 +67468,7 @@ function openGoalsPanel(params) {
|
|
|
67432
67468
|
maximumFractionDigits: 2
|
|
67433
67469
|
}).format(value);
|
|
67434
67470
|
}
|
|
67435
|
-
function
|
|
67471
|
+
function injectStyles16() {
|
|
67436
67472
|
const styleId = "myio-goals-panel-styles";
|
|
67437
67473
|
if (document.getElementById(styleId)) return;
|
|
67438
67474
|
const style = document.createElement("style");
|
|
@@ -80626,7 +80662,7 @@ function createConsumptionChartWidget(config) {
|
|
|
80626
80662
|
</div>
|
|
80627
80663
|
`;
|
|
80628
80664
|
}
|
|
80629
|
-
function
|
|
80665
|
+
function injectStyles16() {
|
|
80630
80666
|
if (styleElement) return;
|
|
80631
80667
|
styleElement = document.createElement("style");
|
|
80632
80668
|
styleElement.id = `${widgetId}-styles`;
|
|
@@ -81120,7 +81156,7 @@ function createConsumptionChartWidget(config) {
|
|
|
81120
81156
|
console.error(`[ConsumptionWidget] Container #${config.containerId} not found`);
|
|
81121
81157
|
return;
|
|
81122
81158
|
}
|
|
81123
|
-
|
|
81159
|
+
injectStyles16();
|
|
81124
81160
|
containerElement.innerHTML = renderHTML();
|
|
81125
81161
|
setupListeners();
|
|
81126
81162
|
setLoading(true);
|
|
@@ -120215,7 +120251,7 @@ var AlarmsNotificationsPanelView = class {
|
|
|
120215
120251
|
const weightSum = colWeights.reduce((s, w) => s + w, 0);
|
|
120216
120252
|
const colWidths = colWeights.map((w) => w / weightSum * TABLE_W);
|
|
120217
120253
|
const colX = (ci) => MARGIN + colWidths.slice(0, ci).reduce((s, w) => s + w, 0);
|
|
120218
|
-
const
|
|
120254
|
+
const truncate3 = (s, max) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
120219
120255
|
const drawHeader2 = (pageNo2) => {
|
|
120220
120256
|
doc.setFillColor(62, 26, 125);
|
|
120221
120257
|
doc.rect(0, 0, PW, HDR_H, "F");
|
|
@@ -120257,7 +120293,7 @@ var AlarmsNotificationsPanelView = class {
|
|
|
120257
120293
|
cells.forEach((cell, ci) => {
|
|
120258
120294
|
const x = colX(ci) + 1.5;
|
|
120259
120295
|
const maxChars = Math.floor(colWidths[ci] / 1.8);
|
|
120260
|
-
doc.text(
|
|
120296
|
+
doc.text(truncate3(String(cell ?? ""), maxChars), x, y + ROW_H / 2 + 2.2);
|
|
120261
120297
|
});
|
|
120262
120298
|
doc.setDrawColor(230, 228, 240);
|
|
120263
120299
|
doc.setLineWidth(0.1);
|
|
@@ -141943,6 +141979,2543 @@ function _buildMap(tickets) {
|
|
|
141943
141979
|
return map;
|
|
141944
141980
|
}
|
|
141945
141981
|
|
|
141982
|
+
// src/services/annotations/CustomerDeviceService.ts
|
|
141983
|
+
var CustomerDeviceService = class {
|
|
141984
|
+
customerId;
|
|
141985
|
+
tbHost;
|
|
141986
|
+
jwt;
|
|
141987
|
+
pageSize;
|
|
141988
|
+
concurrency;
|
|
141989
|
+
chunkDelayMs;
|
|
141990
|
+
maxRetries;
|
|
141991
|
+
logger;
|
|
141992
|
+
constructor(cfg) {
|
|
141993
|
+
this.customerId = cfg.customerId;
|
|
141994
|
+
this.tbHost = cfg.tbHost.replace(/\/+$/, "");
|
|
141995
|
+
this.jwt = cfg.jwt;
|
|
141996
|
+
this.pageSize = cfg.pageSize ?? 1e3;
|
|
141997
|
+
this.concurrency = cfg.concurrency ?? 5;
|
|
141998
|
+
this.chunkDelayMs = cfg.chunkDelayMs ?? 50;
|
|
141999
|
+
this.maxRetries = cfg.maxRetries ?? 3;
|
|
142000
|
+
const raw = cfg.logger ?? console;
|
|
142001
|
+
const noop = () => {
|
|
142002
|
+
};
|
|
142003
|
+
const log = raw.log || noop;
|
|
142004
|
+
this.logger = {
|
|
142005
|
+
debug: raw.debug || log,
|
|
142006
|
+
info: raw.info || log,
|
|
142007
|
+
warn: raw.warn || log,
|
|
142008
|
+
error: raw.error || log
|
|
142009
|
+
};
|
|
142010
|
+
}
|
|
142011
|
+
get headers() {
|
|
142012
|
+
return {
|
|
142013
|
+
"Content-Type": "application/json",
|
|
142014
|
+
"X-Authorization": `Bearer ${this.jwt}`
|
|
142015
|
+
};
|
|
142016
|
+
}
|
|
142017
|
+
/**
|
|
142018
|
+
* Fetches ALL devices of the customer using TB's paginated `deviceInfos`
|
|
142019
|
+
* endpoint. Hard limit: 10 pages (i.e. 10× pageSize devices) — beyond that
|
|
142020
|
+
* we log a warning and stop, to protect the dashboard from runaway fetches.
|
|
142021
|
+
*
|
|
142022
|
+
* AC-9
|
|
142023
|
+
*/
|
|
142024
|
+
async fetchAllCustomerDevices() {
|
|
142025
|
+
const HARD_PAGE_CAP = 10;
|
|
142026
|
+
const out = [];
|
|
142027
|
+
for (let page = 0; page < HARD_PAGE_CAP; page++) {
|
|
142028
|
+
const url = `${this.tbHost}/api/customer/${encodeURIComponent(this.customerId)}/deviceInfos?pageSize=${this.pageSize}&page=${page}`;
|
|
142029
|
+
const data = await this._requestJson(url);
|
|
142030
|
+
const flat = (data?.data ?? []).map(this._flatten);
|
|
142031
|
+
out.push(...flat);
|
|
142032
|
+
if (!data?.hasNext) break;
|
|
142033
|
+
if (page === HARD_PAGE_CAP - 1) {
|
|
142034
|
+
this.logger.warn(
|
|
142035
|
+
`[CustomerDeviceService] hit hard page cap of ${HARD_PAGE_CAP} for customer ${this.customerId}; some devices may be missing.`
|
|
142036
|
+
);
|
|
142037
|
+
}
|
|
142038
|
+
}
|
|
142039
|
+
this.logger.debug(
|
|
142040
|
+
`[CustomerDeviceService] fetched ${out.length} devices for customer ${this.customerId}`
|
|
142041
|
+
);
|
|
142042
|
+
return out;
|
|
142043
|
+
}
|
|
142044
|
+
/**
|
|
142045
|
+
* Fetches SERVER_SCOPE attributes for a batch of devices. Splits into chunks
|
|
142046
|
+
* of `concurrency` (default 5), sequenced with a 50ms delay between chunks.
|
|
142047
|
+
* Returns a Map<deviceId, Record<key, value>> of attribute values.
|
|
142048
|
+
*
|
|
142049
|
+
* Failures per device are logged and skipped — the rest still returns.
|
|
142050
|
+
*
|
|
142051
|
+
* AC-10
|
|
142052
|
+
*/
|
|
142053
|
+
async fetchAttributesBatch(deviceIds, keys) {
|
|
142054
|
+
const result = /* @__PURE__ */ new Map();
|
|
142055
|
+
if (deviceIds.length === 0 || keys.length === 0) return result;
|
|
142056
|
+
const keysParam = keys.map(encodeURIComponent).join(",");
|
|
142057
|
+
for (let i = 0; i < deviceIds.length; i += this.concurrency) {
|
|
142058
|
+
const slice = deviceIds.slice(i, i + this.concurrency);
|
|
142059
|
+
const settled = await Promise.allSettled(
|
|
142060
|
+
slice.map(
|
|
142061
|
+
(deviceId) => this._fetchAttributesForDevice(deviceId, keysParam).then(
|
|
142062
|
+
(attrs) => ({ deviceId, attrs })
|
|
142063
|
+
)
|
|
142064
|
+
)
|
|
142065
|
+
);
|
|
142066
|
+
for (const r of settled) {
|
|
142067
|
+
if (r.status === "fulfilled") {
|
|
142068
|
+
result.set(r.value.deviceId, r.value.attrs);
|
|
142069
|
+
} else {
|
|
142070
|
+
this.logger.warn(
|
|
142071
|
+
"[CustomerDeviceService] attribute fetch failed for one device:",
|
|
142072
|
+
r.reason
|
|
142073
|
+
);
|
|
142074
|
+
}
|
|
142075
|
+
}
|
|
142076
|
+
if (i + this.concurrency < deviceIds.length) {
|
|
142077
|
+
await _sleep(this.chunkDelayMs);
|
|
142078
|
+
}
|
|
142079
|
+
}
|
|
142080
|
+
return result;
|
|
142081
|
+
}
|
|
142082
|
+
// ── Internals ────────────────────────────────────────────────────────────
|
|
142083
|
+
_flatten(d) {
|
|
142084
|
+
const id = typeof d.id === "object" && d.id?.id ? d.id.id : String(d.id ?? "");
|
|
142085
|
+
return {
|
|
142086
|
+
id,
|
|
142087
|
+
name: d.name ?? "",
|
|
142088
|
+
label: d.label ?? d.name ?? "",
|
|
142089
|
+
deviceType: d.type ?? d.deviceProfileName ?? ""
|
|
142090
|
+
};
|
|
142091
|
+
}
|
|
142092
|
+
async _fetchAttributesForDevice(deviceId, keysParam) {
|
|
142093
|
+
const url = `${this.tbHost}/api/plugins/telemetry/DEVICE/${encodeURIComponent(deviceId)}/values/attributes/SERVER_SCOPE?keys=${keysParam}`;
|
|
142094
|
+
const list = await this._requestJson(url);
|
|
142095
|
+
const flat = {};
|
|
142096
|
+
for (const kv of list ?? []) {
|
|
142097
|
+
if (kv && typeof kv.key === "string") flat[kv.key] = kv.value;
|
|
142098
|
+
}
|
|
142099
|
+
return flat;
|
|
142100
|
+
}
|
|
142101
|
+
async _requestJson(url) {
|
|
142102
|
+
let attempt = 0;
|
|
142103
|
+
while (true) {
|
|
142104
|
+
try {
|
|
142105
|
+
const res = await fetch(url, { method: "GET", headers: this.headers });
|
|
142106
|
+
if (res.status === 429 || res.status >= 500) {
|
|
142107
|
+
if (attempt < this.maxRetries) {
|
|
142108
|
+
const backoffMs = Math.pow(2, attempt) * 1e3;
|
|
142109
|
+
this.logger.warn(
|
|
142110
|
+
`[CustomerDeviceService] ${res.status} on ${url} \u2014 retry ${attempt + 1}/${this.maxRetries} in ${backoffMs}ms`
|
|
142111
|
+
);
|
|
142112
|
+
attempt++;
|
|
142113
|
+
await _sleep(backoffMs);
|
|
142114
|
+
continue;
|
|
142115
|
+
}
|
|
142116
|
+
}
|
|
142117
|
+
if (!res.ok) {
|
|
142118
|
+
throw new Error(`HTTP ${res.status} on ${url}`);
|
|
142119
|
+
}
|
|
142120
|
+
return await res.json();
|
|
142121
|
+
} catch (err) {
|
|
142122
|
+
if (attempt < this.maxRetries && _isNetworkError(err)) {
|
|
142123
|
+
const backoffMs = Math.pow(2, attempt) * 1e3;
|
|
142124
|
+
this.logger.warn(
|
|
142125
|
+
`[CustomerDeviceService] network error on ${url} \u2014 retry ${attempt + 1}/${this.maxRetries} in ${backoffMs}ms`
|
|
142126
|
+
);
|
|
142127
|
+
attempt++;
|
|
142128
|
+
await _sleep(backoffMs);
|
|
142129
|
+
continue;
|
|
142130
|
+
}
|
|
142131
|
+
throw err;
|
|
142132
|
+
}
|
|
142133
|
+
}
|
|
142134
|
+
}
|
|
142135
|
+
};
|
|
142136
|
+
function _sleep(ms) {
|
|
142137
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
142138
|
+
}
|
|
142139
|
+
function _isNetworkError(err) {
|
|
142140
|
+
if (!err || typeof err !== "object") return false;
|
|
142141
|
+
const name = err.name ?? "";
|
|
142142
|
+
const message = String(err.message ?? "").toLowerCase();
|
|
142143
|
+
return name === "TypeError" || message.includes("network") || message.includes("failed to fetch");
|
|
142144
|
+
}
|
|
142145
|
+
|
|
142146
|
+
// src/services/annotations/parseLogAnnotations.ts
|
|
142147
|
+
function parseLogAnnotations(raw, ctx, logger = console) {
|
|
142148
|
+
if (raw == null || raw === "") return [];
|
|
142149
|
+
let value = raw;
|
|
142150
|
+
if (typeof value === "string") {
|
|
142151
|
+
try {
|
|
142152
|
+
value = JSON.parse(value);
|
|
142153
|
+
} catch (err) {
|
|
142154
|
+
logger.warn(
|
|
142155
|
+
`[parseLogAnnotations] JSON parse failed${ctx ? ` (${ctx})` : ""}:`,
|
|
142156
|
+
err?.message ?? err
|
|
142157
|
+
);
|
|
142158
|
+
return [];
|
|
142159
|
+
}
|
|
142160
|
+
}
|
|
142161
|
+
if (Array.isArray(value)) {
|
|
142162
|
+
return _filterArray(value, ctx, logger);
|
|
142163
|
+
}
|
|
142164
|
+
if (value !== null && typeof value === "object") {
|
|
142165
|
+
const obj = value;
|
|
142166
|
+
if (Array.isArray(obj.annotations)) {
|
|
142167
|
+
return _filterArray(obj.annotations, ctx, logger);
|
|
142168
|
+
}
|
|
142169
|
+
}
|
|
142170
|
+
logger.warn(
|
|
142171
|
+
`[parseLogAnnotations] Unknown shape${ctx ? ` (${ctx})` : ""}; expected array, object{annotations}, or JSON string. Got:`,
|
|
142172
|
+
typeof value
|
|
142173
|
+
);
|
|
142174
|
+
return [];
|
|
142175
|
+
}
|
|
142176
|
+
function _filterArray(arr, ctx, logger) {
|
|
142177
|
+
const kept = [];
|
|
142178
|
+
let dropped = 0;
|
|
142179
|
+
for (const item of arr) {
|
|
142180
|
+
if (item !== null && typeof item === "object" && typeof item.id === "string" && typeof item.text === "string" && typeof item.type === "string" && typeof item.status === "string") {
|
|
142181
|
+
kept.push(item);
|
|
142182
|
+
} else {
|
|
142183
|
+
dropped++;
|
|
142184
|
+
}
|
|
142185
|
+
}
|
|
142186
|
+
if (dropped > 0) {
|
|
142187
|
+
logger.warn(
|
|
142188
|
+
`[parseLogAnnotations] dropped ${dropped} malformed entries${ctx ? ` (${ctx})` : ""}`
|
|
142189
|
+
);
|
|
142190
|
+
}
|
|
142191
|
+
return kept;
|
|
142192
|
+
}
|
|
142193
|
+
|
|
142194
|
+
// src/services/annotations/AnnotationServiceOrchestrator.ts
|
|
142195
|
+
var DOMAIN_ICONS2 = {
|
|
142196
|
+
energy: "\u26A1",
|
|
142197
|
+
water: "\u{1F4A7}",
|
|
142198
|
+
temperature: "\u{1F321}\uFE0F",
|
|
142199
|
+
unknown: "\xB7"
|
|
142200
|
+
};
|
|
142201
|
+
var DOMAIN_LABELS2 = {
|
|
142202
|
+
energy: "Energia",
|
|
142203
|
+
water: "\xC1gua",
|
|
142204
|
+
temperature: "Temperatura",
|
|
142205
|
+
unknown: "Indeterminado"
|
|
142206
|
+
};
|
|
142207
|
+
var BUCKET_NO_IDENTIFIER = "Sem Identificador";
|
|
142208
|
+
function _normalizeLogger(raw) {
|
|
142209
|
+
const noop = () => {
|
|
142210
|
+
};
|
|
142211
|
+
const log = raw && raw.log || noop;
|
|
142212
|
+
return {
|
|
142213
|
+
debug: raw?.debug || log,
|
|
142214
|
+
info: raw?.info || log,
|
|
142215
|
+
warn: raw?.warn || log,
|
|
142216
|
+
error: raw?.error || log
|
|
142217
|
+
};
|
|
142218
|
+
}
|
|
142219
|
+
async function buildAnnotationServiceOrchestrator(params) {
|
|
142220
|
+
const logger = _normalizeLogger(params.logger ?? console);
|
|
142221
|
+
const cacheTtlMs = params.cacheTtlMs ?? 6e4;
|
|
142222
|
+
const client = new CustomerDeviceService({
|
|
142223
|
+
customerId: params.customerId,
|
|
142224
|
+
tbHost: params.tbHost,
|
|
142225
|
+
jwt: params.jwt,
|
|
142226
|
+
logger
|
|
142227
|
+
});
|
|
142228
|
+
const state6 = {
|
|
142229
|
+
devices: [],
|
|
142230
|
+
byIdentifier: /* @__PURE__ */ new Map(),
|
|
142231
|
+
byDeviceId: /* @__PURE__ */ new Map(),
|
|
142232
|
+
byDomain: /* @__PURE__ */ new Map(),
|
|
142233
|
+
fetchedAt: 0,
|
|
142234
|
+
stale: false
|
|
142235
|
+
};
|
|
142236
|
+
async function _fetchAndIndex() {
|
|
142237
|
+
const t0 = Date.now();
|
|
142238
|
+
const flatDevices = await client.fetchAllCustomerDevices();
|
|
142239
|
+
const deviceIds = flatDevices.map((d) => d.id);
|
|
142240
|
+
const attrs = await client.fetchAttributesBatch(deviceIds, [
|
|
142241
|
+
"log_annotations",
|
|
142242
|
+
"identifier"
|
|
142243
|
+
]);
|
|
142244
|
+
const annotated = flatDevices.map((d) => {
|
|
142245
|
+
const a = attrs.get(d.id) ?? {};
|
|
142246
|
+
const rawAnnotations = a["log_annotations"];
|
|
142247
|
+
const identifierRaw = a["identifier"];
|
|
142248
|
+
return {
|
|
142249
|
+
deviceId: d.id,
|
|
142250
|
+
name: d.name,
|
|
142251
|
+
label: d.label,
|
|
142252
|
+
identifier: typeof identifierRaw === "string" && identifierRaw.length > 0 ? identifierRaw : null,
|
|
142253
|
+
domain: _classifyDomain(d.deviceType),
|
|
142254
|
+
deviceType: d.deviceType,
|
|
142255
|
+
annotations: parseLogAnnotations(rawAnnotations, d.id, logger)
|
|
142256
|
+
};
|
|
142257
|
+
});
|
|
142258
|
+
state6.devices = annotated;
|
|
142259
|
+
state6.byIdentifier = _indexByIdentifier(annotated);
|
|
142260
|
+
state6.byDeviceId = _indexByDeviceId(annotated);
|
|
142261
|
+
state6.byDomain = _indexByDomain(annotated);
|
|
142262
|
+
state6.fetchedAt = Date.now();
|
|
142263
|
+
state6.stale = false;
|
|
142264
|
+
const durationMs = state6.fetchedAt - t0;
|
|
142265
|
+
logger.debug(
|
|
142266
|
+
`[AnnotationServiceOrchestrator] built \u2014 ${annotated.length} devices, ${_totalAnnotations(annotated)} annotations, ${durationMs}ms`
|
|
142267
|
+
);
|
|
142268
|
+
}
|
|
142269
|
+
await _fetchAndIndex();
|
|
142270
|
+
if (typeof window !== "undefined") {
|
|
142271
|
+
window.addEventListener("myio:annotation-changed", (ev) => {
|
|
142272
|
+
logger.debug(
|
|
142273
|
+
"[AnnotationServiceOrchestrator] myio:annotation-changed received:",
|
|
142274
|
+
ev.detail
|
|
142275
|
+
);
|
|
142276
|
+
state6.stale = true;
|
|
142277
|
+
orchestrator.refresh().catch((err) => {
|
|
142278
|
+
logger.warn("[AnnotationServiceOrchestrator] refresh after annotation-changed failed:", err);
|
|
142279
|
+
});
|
|
142280
|
+
});
|
|
142281
|
+
}
|
|
142282
|
+
const orchestrator = {
|
|
142283
|
+
get devices() {
|
|
142284
|
+
return state6.devices;
|
|
142285
|
+
},
|
|
142286
|
+
get byIdentifier() {
|
|
142287
|
+
return state6.byIdentifier;
|
|
142288
|
+
},
|
|
142289
|
+
get byDeviceId() {
|
|
142290
|
+
return state6.byDeviceId;
|
|
142291
|
+
},
|
|
142292
|
+
get byDomain() {
|
|
142293
|
+
return state6.byDomain;
|
|
142294
|
+
},
|
|
142295
|
+
get fetchedAt() {
|
|
142296
|
+
return state6.fetchedAt;
|
|
142297
|
+
},
|
|
142298
|
+
// Queries
|
|
142299
|
+
getAll: () => state6.devices.slice(),
|
|
142300
|
+
getByIdentifier: (identifier) => state6.byIdentifier.get(identifier)?.slice() ?? [],
|
|
142301
|
+
getByDevice: (deviceId) => state6.byDeviceId.get(deviceId) ?? null,
|
|
142302
|
+
getByDomain: (domain) => state6.byDomain.get(domain)?.slice() ?? [],
|
|
142303
|
+
getGroups: (groupBy, filter) => _buildGroups(state6, groupBy, filter),
|
|
142304
|
+
// Counts
|
|
142305
|
+
getTotalCount: () => _countNonArchived(state6.devices),
|
|
142306
|
+
getPendingCount: () => _countWhere(state6.devices, (a) => a.status !== "archived" && a.type === "pending"),
|
|
142307
|
+
getOverdueCount: () => _countWhere(state6.devices, (a) => a.status !== "archived" && _isOverdue(a)),
|
|
142308
|
+
// Lifecycle — AC-13
|
|
142309
|
+
refresh: async () => {
|
|
142310
|
+
await _fetchAndIndex();
|
|
142311
|
+
if (typeof window !== "undefined") {
|
|
142312
|
+
window.dispatchEvent(
|
|
142313
|
+
new CustomEvent("myio:annotations-refreshed", {
|
|
142314
|
+
detail: {
|
|
142315
|
+
totalCount: _countNonArchived(state6.devices),
|
|
142316
|
+
durationMs: 0
|
|
142317
|
+
// already logged inside _fetchAndIndex
|
|
142318
|
+
}
|
|
142319
|
+
})
|
|
142320
|
+
);
|
|
142321
|
+
}
|
|
142322
|
+
},
|
|
142323
|
+
// AC-12 helper — TTL-based staleness
|
|
142324
|
+
invalidate: () => {
|
|
142325
|
+
state6.stale = true;
|
|
142326
|
+
state6.fetchedAt = Math.min(state6.fetchedAt, Date.now() - cacheTtlMs - 1);
|
|
142327
|
+
}
|
|
142328
|
+
};
|
|
142329
|
+
return orchestrator;
|
|
142330
|
+
}
|
|
142331
|
+
function _indexByIdentifier(devices) {
|
|
142332
|
+
const map = /* @__PURE__ */ new Map();
|
|
142333
|
+
for (const d of devices) {
|
|
142334
|
+
const key = d.identifier;
|
|
142335
|
+
const arr = map.get(key);
|
|
142336
|
+
if (arr) arr.push(d);
|
|
142337
|
+
else map.set(key, [d]);
|
|
142338
|
+
}
|
|
142339
|
+
return map;
|
|
142340
|
+
}
|
|
142341
|
+
function _indexByDeviceId(devices) {
|
|
142342
|
+
const map = /* @__PURE__ */ new Map();
|
|
142343
|
+
for (const d of devices) map.set(d.deviceId, d);
|
|
142344
|
+
return map;
|
|
142345
|
+
}
|
|
142346
|
+
function _indexByDomain(devices) {
|
|
142347
|
+
const map = /* @__PURE__ */ new Map();
|
|
142348
|
+
for (const d of devices) {
|
|
142349
|
+
const arr = map.get(d.domain);
|
|
142350
|
+
if (arr) arr.push(d);
|
|
142351
|
+
else map.set(d.domain, [d]);
|
|
142352
|
+
}
|
|
142353
|
+
return map;
|
|
142354
|
+
}
|
|
142355
|
+
function _classifyDomain(deviceType) {
|
|
142356
|
+
const t = (deviceType || "").toUpperCase();
|
|
142357
|
+
if (!t) return "unknown";
|
|
142358
|
+
if (t.includes("HIDROMETRO") || t.includes("CAIXA_DAGUA") || t === "TANK") return "water";
|
|
142359
|
+
if (t.includes("TERMOSTATO") || t.includes("TEMP")) return "temperature";
|
|
142360
|
+
if (t.includes("3F_MEDIDOR") || t.includes("ENTRADA") || t.includes("RELOGIO") || t.includes("TRAFO") || t.includes("SUBESTACAO") || t.includes("MEDIDOR") || t.includes("CHILLER") || t.includes("FANCOIL") || t.includes("HVAC") || t.includes("AR_CONDICIONADO") || t.includes("BOMBA") || t.includes("ELEVADOR") || t.includes("ESCADA")) {
|
|
142361
|
+
return "energy";
|
|
142362
|
+
}
|
|
142363
|
+
return "unknown";
|
|
142364
|
+
}
|
|
142365
|
+
function _buildGroups(state6, groupBy, filter) {
|
|
142366
|
+
const filtered = state6.devices.map((d) => _applyFilterToDevice(d, filter)).filter(
|
|
142367
|
+
(d) => d !== null
|
|
142368
|
+
);
|
|
142369
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
142370
|
+
for (const d of filtered) {
|
|
142371
|
+
let key;
|
|
142372
|
+
if (groupBy === "identifier") key = d.identifier ?? BUCKET_NO_IDENTIFIER;
|
|
142373
|
+
else if (groupBy === "device") key = d.deviceId;
|
|
142374
|
+
else key = d.domain;
|
|
142375
|
+
const arr = buckets.get(key);
|
|
142376
|
+
if (arr) arr.push(d);
|
|
142377
|
+
else buckets.set(key, [d]);
|
|
142378
|
+
}
|
|
142379
|
+
const groups = [];
|
|
142380
|
+
for (const [key, devs] of buckets) {
|
|
142381
|
+
const annotations = devs.flatMap((d) => d.annotations);
|
|
142382
|
+
groups.push({
|
|
142383
|
+
key,
|
|
142384
|
+
label: _labelForGroup(key, groupBy, devs[0]),
|
|
142385
|
+
icon: _iconForGroup(key, groupBy),
|
|
142386
|
+
devices: devs,
|
|
142387
|
+
totalAnnotations: annotations.length,
|
|
142388
|
+
maxImportance: annotations.reduce((acc, a) => Math.max(acc, a.importance || 0), 0),
|
|
142389
|
+
mostRecentAt: _maxCreatedAt(annotations)
|
|
142390
|
+
});
|
|
142391
|
+
}
|
|
142392
|
+
return groups;
|
|
142393
|
+
}
|
|
142394
|
+
function _applyFilterToDevice(device, filter) {
|
|
142395
|
+
if (!filter) {
|
|
142396
|
+
const visible2 = device.annotations.filter((a) => a.status !== "archived");
|
|
142397
|
+
if (visible2.length === 0) return null;
|
|
142398
|
+
return { ...device, annotations: visible2 };
|
|
142399
|
+
}
|
|
142400
|
+
const now = Date.now();
|
|
142401
|
+
const sevenDaysMs = 7 * 24 * 60 * 60 * 1e3;
|
|
142402
|
+
const visible = device.annotations.filter((a) => {
|
|
142403
|
+
if (filter.statuses.size > 0) {
|
|
142404
|
+
if (!filter.statuses.has(a.status)) return false;
|
|
142405
|
+
} else if (a.status === "archived") {
|
|
142406
|
+
return false;
|
|
142407
|
+
}
|
|
142408
|
+
if (filter.types.size > 0 && !filter.types.has(a.type)) return false;
|
|
142409
|
+
if (filter.importance.size > 0 && !filter.importance.has(a.importance)) return false;
|
|
142410
|
+
if (filter.actionableOnly) {
|
|
142411
|
+
const isPending = a.type === "pending" && a.status !== "archived";
|
|
142412
|
+
if (!isPending) return false;
|
|
142413
|
+
const hasDue = !!a.dueDate;
|
|
142414
|
+
if (hasDue) {
|
|
142415
|
+
const dueMs = new Date(a.dueDate).getTime();
|
|
142416
|
+
if (!(dueMs <= now + sevenDaysMs)) return false;
|
|
142417
|
+
}
|
|
142418
|
+
}
|
|
142419
|
+
if (filter.searchTerm) {
|
|
142420
|
+
const needle = _nfd(filter.searchTerm);
|
|
142421
|
+
const haystack = _nfd(
|
|
142422
|
+
[a.text, device.identifier ?? "", device.name, device.label].join(" ")
|
|
142423
|
+
);
|
|
142424
|
+
if (!haystack.includes(needle)) return false;
|
|
142425
|
+
}
|
|
142426
|
+
return true;
|
|
142427
|
+
});
|
|
142428
|
+
if (visible.length === 0) return null;
|
|
142429
|
+
return { ...device, annotations: visible };
|
|
142430
|
+
}
|
|
142431
|
+
function _labelForGroup(key, groupBy, sample) {
|
|
142432
|
+
if (groupBy === "identifier") return key;
|
|
142433
|
+
if (groupBy === "device") return sample?.label || sample?.name || key;
|
|
142434
|
+
return DOMAIN_LABELS2[key] ?? key;
|
|
142435
|
+
}
|
|
142436
|
+
function _iconForGroup(key, groupBy) {
|
|
142437
|
+
if (groupBy === "domain") return DOMAIN_ICONS2[key];
|
|
142438
|
+
return void 0;
|
|
142439
|
+
}
|
|
142440
|
+
function _maxCreatedAt(annotations) {
|
|
142441
|
+
if (annotations.length === 0) return null;
|
|
142442
|
+
let max = annotations[0].createdAt;
|
|
142443
|
+
for (let i = 1; i < annotations.length; i++) {
|
|
142444
|
+
if (annotations[i].createdAt > max) max = annotations[i].createdAt;
|
|
142445
|
+
}
|
|
142446
|
+
return max;
|
|
142447
|
+
}
|
|
142448
|
+
function _countNonArchived(devices) {
|
|
142449
|
+
let n = 0;
|
|
142450
|
+
for (const d of devices) {
|
|
142451
|
+
for (const a of d.annotations) if (a.status !== "archived") n++;
|
|
142452
|
+
}
|
|
142453
|
+
return n;
|
|
142454
|
+
}
|
|
142455
|
+
function _countWhere(devices, predicate) {
|
|
142456
|
+
let n = 0;
|
|
142457
|
+
for (const d of devices) {
|
|
142458
|
+
for (const a of d.annotations) if (predicate(a)) n++;
|
|
142459
|
+
}
|
|
142460
|
+
return n;
|
|
142461
|
+
}
|
|
142462
|
+
function _totalAnnotations(devices) {
|
|
142463
|
+
let n = 0;
|
|
142464
|
+
for (const d of devices) n += d.annotations.length;
|
|
142465
|
+
return n;
|
|
142466
|
+
}
|
|
142467
|
+
function _isOverdue(a) {
|
|
142468
|
+
if (!a.dueDate || a.type !== "pending") return false;
|
|
142469
|
+
const due = new Date(a.dueDate).getTime();
|
|
142470
|
+
if (isNaN(due)) return false;
|
|
142471
|
+
return due < Date.now();
|
|
142472
|
+
}
|
|
142473
|
+
function _nfd(s) {
|
|
142474
|
+
return s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
|
|
142475
|
+
}
|
|
142476
|
+
|
|
142477
|
+
// src/components/header-annotations-panel/styles.ts
|
|
142478
|
+
var HEADER_ANNOTATIONS_STYLES_ID = "myio-annotations-panel-styles";
|
|
142479
|
+
var HEADER_ANNOTATIONS_STYLES = `
|
|
142480
|
+
/* Container \u2014 anchored under the HEADER button by default */
|
|
142481
|
+
.myio-annotations-panel {
|
|
142482
|
+
position: fixed;
|
|
142483
|
+
z-index: 99998;
|
|
142484
|
+
width: min(720px, 90vw);
|
|
142485
|
+
max-height: min(80vh, 720px);
|
|
142486
|
+
background: #fff;
|
|
142487
|
+
border: 1px solid #e2e8f0;
|
|
142488
|
+
border-radius: 12px;
|
|
142489
|
+
box-shadow: 0 20px 50px rgba(15, 23, 42, 0.18);
|
|
142490
|
+
display: flex;
|
|
142491
|
+
flex-direction: column;
|
|
142492
|
+
font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
142493
|
+
color: #1e293b;
|
|
142494
|
+
overflow: hidden;
|
|
142495
|
+
animation: myio-anno-pop 0.16s ease-out;
|
|
142496
|
+
}
|
|
142497
|
+
@keyframes myio-anno-pop {
|
|
142498
|
+
from { opacity: 0; transform: translateY(-6px); }
|
|
142499
|
+
to { opacity: 1; transform: translateY(0); }
|
|
142500
|
+
}
|
|
142501
|
+
|
|
142502
|
+
.myio-annotations-panel.maximized {
|
|
142503
|
+
width: 90vw !important;
|
|
142504
|
+
height: 90vh !important;
|
|
142505
|
+
max-height: 90vh !important;
|
|
142506
|
+
top: 5vh !important;
|
|
142507
|
+
left: 5vw !important;
|
|
142508
|
+
}
|
|
142509
|
+
.myio-annotations-panel.is-dragging {
|
|
142510
|
+
cursor: grabbing;
|
|
142511
|
+
user-select: none;
|
|
142512
|
+
}
|
|
142513
|
+
.myio-annotations-panel.is-dragging * {
|
|
142514
|
+
pointer-events: none;
|
|
142515
|
+
}
|
|
142516
|
+
.myio-annotations-panel-header[data-drag-handle] {
|
|
142517
|
+
cursor: grab;
|
|
142518
|
+
}
|
|
142519
|
+
.myio-annotations-panel-header[data-drag-handle]:active {
|
|
142520
|
+
cursor: grabbing;
|
|
142521
|
+
}
|
|
142522
|
+
.myio-annotations-panel-action.is-active {
|
|
142523
|
+
background: rgba(108, 92, 231, 0.18);
|
|
142524
|
+
color: #4c3aac;
|
|
142525
|
+
}
|
|
142526
|
+
|
|
142527
|
+
/* Virtual list bookkeeping styles (RFC-0203 M6) */
|
|
142528
|
+
.myio-vlist-container { /* container is .myio-annotations-body in practice */ }
|
|
142529
|
+
.myio-vlist-viewport { will-change: transform; }
|
|
142530
|
+
|
|
142531
|
+
/* Header */
|
|
142532
|
+
.myio-annotations-panel-header {
|
|
142533
|
+
display: flex;
|
|
142534
|
+
align-items: center;
|
|
142535
|
+
gap: 8px;
|
|
142536
|
+
padding: 12px 16px;
|
|
142537
|
+
border-bottom: 1px solid #e2e8f0;
|
|
142538
|
+
background: linear-gradient(135deg, rgba(108, 92, 231, 0.05), rgba(108, 92, 231, 0.02));
|
|
142539
|
+
}
|
|
142540
|
+
.myio-annotations-panel-title {
|
|
142541
|
+
flex: 1;
|
|
142542
|
+
margin: 0;
|
|
142543
|
+
font-size: 15px;
|
|
142544
|
+
font-weight: 700;
|
|
142545
|
+
color: #4c3aac;
|
|
142546
|
+
letter-spacing: -0.01em;
|
|
142547
|
+
}
|
|
142548
|
+
.myio-annotations-panel-title .myio-annotations-icon {
|
|
142549
|
+
margin-right: 6px;
|
|
142550
|
+
font-size: 16px;
|
|
142551
|
+
}
|
|
142552
|
+
.myio-annotations-panel-meta {
|
|
142553
|
+
font-size: 11px;
|
|
142554
|
+
font-weight: 500;
|
|
142555
|
+
color: #64748b;
|
|
142556
|
+
}
|
|
142557
|
+
.myio-annotations-panel-actions {
|
|
142558
|
+
display: flex;
|
|
142559
|
+
align-items: center;
|
|
142560
|
+
gap: 4px;
|
|
142561
|
+
}
|
|
142562
|
+
.myio-annotations-panel-action {
|
|
142563
|
+
width: 28px;
|
|
142564
|
+
height: 28px;
|
|
142565
|
+
display: inline-flex;
|
|
142566
|
+
align-items: center;
|
|
142567
|
+
justify-content: center;
|
|
142568
|
+
border: 1px solid transparent;
|
|
142569
|
+
border-radius: 6px;
|
|
142570
|
+
background: transparent;
|
|
142571
|
+
color: #64748b;
|
|
142572
|
+
cursor: pointer;
|
|
142573
|
+
font-size: 14px;
|
|
142574
|
+
transition: background 0.15s, color 0.15s;
|
|
142575
|
+
}
|
|
142576
|
+
.myio-annotations-panel-action:hover {
|
|
142577
|
+
background: rgba(108, 92, 231, 0.1);
|
|
142578
|
+
color: #4c3aac;
|
|
142579
|
+
}
|
|
142580
|
+
.myio-annotations-panel-action:focus-visible {
|
|
142581
|
+
outline: 2px solid #6c5ce7;
|
|
142582
|
+
outline-offset: 2px;
|
|
142583
|
+
}
|
|
142584
|
+
|
|
142585
|
+
/* Tabs */
|
|
142586
|
+
.myio-annotations-tabs {
|
|
142587
|
+
display: flex;
|
|
142588
|
+
gap: 0;
|
|
142589
|
+
padding: 0 8px;
|
|
142590
|
+
border-bottom: 1px solid #e2e8f0;
|
|
142591
|
+
background: #fafbff;
|
|
142592
|
+
flex-shrink: 0;
|
|
142593
|
+
}
|
|
142594
|
+
.myio-annotations-tab {
|
|
142595
|
+
flex: 1;
|
|
142596
|
+
padding: 10px 12px;
|
|
142597
|
+
font: inherit;
|
|
142598
|
+
font-size: 13px;
|
|
142599
|
+
font-weight: 600;
|
|
142600
|
+
color: #64748b;
|
|
142601
|
+
background: transparent;
|
|
142602
|
+
border: none;
|
|
142603
|
+
border-bottom: 2px solid transparent;
|
|
142604
|
+
cursor: pointer;
|
|
142605
|
+
transition: color 0.15s, border-color 0.15s;
|
|
142606
|
+
white-space: nowrap;
|
|
142607
|
+
}
|
|
142608
|
+
.myio-annotations-tab:hover {
|
|
142609
|
+
color: #4c3aac;
|
|
142610
|
+
}
|
|
142611
|
+
.myio-annotations-tab[aria-selected="true"] {
|
|
142612
|
+
color: #4c3aac;
|
|
142613
|
+
border-bottom-color: #6c5ce7;
|
|
142614
|
+
}
|
|
142615
|
+
.myio-annotations-tab:focus-visible {
|
|
142616
|
+
outline: 2px solid #6c5ce7;
|
|
142617
|
+
outline-offset: -2px;
|
|
142618
|
+
border-radius: 4px;
|
|
142619
|
+
}
|
|
142620
|
+
|
|
142621
|
+
/* Toolbar (RFC-0203 M5) */
|
|
142622
|
+
.myio-annotations-toolbar {
|
|
142623
|
+
display: flex;
|
|
142624
|
+
flex-direction: column;
|
|
142625
|
+
gap: 6px;
|
|
142626
|
+
padding: 8px 12px 0 12px;
|
|
142627
|
+
background: #fff;
|
|
142628
|
+
border-bottom: 1px solid #f1f5f9;
|
|
142629
|
+
flex-shrink: 0;
|
|
142630
|
+
}
|
|
142631
|
+
.myio-annotations-toolbar-row {
|
|
142632
|
+
display: flex;
|
|
142633
|
+
align-items: center;
|
|
142634
|
+
gap: 6px;
|
|
142635
|
+
}
|
|
142636
|
+
.myio-annotations-toolbar-meta {
|
|
142637
|
+
font-size: 11px;
|
|
142638
|
+
color: #64748b;
|
|
142639
|
+
padding-bottom: 6px;
|
|
142640
|
+
}
|
|
142641
|
+
.myio-annotations-toolbar-count { font-weight: 600; }
|
|
142642
|
+
|
|
142643
|
+
.myio-annotations-toolbar-search {
|
|
142644
|
+
flex: 1;
|
|
142645
|
+
display: inline-flex;
|
|
142646
|
+
align-items: center;
|
|
142647
|
+
gap: 6px;
|
|
142648
|
+
height: 32px;
|
|
142649
|
+
padding: 0 10px;
|
|
142650
|
+
border: 1px solid #e2e8f0;
|
|
142651
|
+
border-radius: 6px;
|
|
142652
|
+
background: #fff;
|
|
142653
|
+
}
|
|
142654
|
+
.myio-annotations-toolbar-search:focus-within {
|
|
142655
|
+
border-color: #6c5ce7;
|
|
142656
|
+
box-shadow: 0 0 0 2px rgba(108, 92, 231, 0.15);
|
|
142657
|
+
}
|
|
142658
|
+
.myio-annotations-toolbar-search input {
|
|
142659
|
+
flex: 1;
|
|
142660
|
+
border: none;
|
|
142661
|
+
outline: none;
|
|
142662
|
+
font: inherit;
|
|
142663
|
+
font-size: 13px;
|
|
142664
|
+
color: #1e293b;
|
|
142665
|
+
background: transparent;
|
|
142666
|
+
}
|
|
142667
|
+
.myio-annotations-search-icon { font-size: 13px; color: #94a3b8; }
|
|
142668
|
+
|
|
142669
|
+
.myio-annotations-toolbar-sort {
|
|
142670
|
+
height: 32px;
|
|
142671
|
+
padding: 0 8px;
|
|
142672
|
+
border: 1px solid #e2e8f0;
|
|
142673
|
+
border-radius: 6px;
|
|
142674
|
+
background: #fff;
|
|
142675
|
+
font: inherit;
|
|
142676
|
+
font-size: 12px;
|
|
142677
|
+
color: #1e293b;
|
|
142678
|
+
cursor: pointer;
|
|
142679
|
+
}
|
|
142680
|
+
.myio-annotations-toolbar-sort:focus-visible {
|
|
142681
|
+
border-color: #6c5ce7;
|
|
142682
|
+
outline: 2px solid rgba(108, 92, 231, 0.3);
|
|
142683
|
+
outline-offset: 1px;
|
|
142684
|
+
}
|
|
142685
|
+
|
|
142686
|
+
.myio-annotations-toolbar-filter-btn {
|
|
142687
|
+
height: 32px;
|
|
142688
|
+
padding: 0 10px;
|
|
142689
|
+
border: 1px solid #e2e8f0;
|
|
142690
|
+
border-radius: 6px;
|
|
142691
|
+
background: #fff;
|
|
142692
|
+
font: inherit;
|
|
142693
|
+
font-size: 12px;
|
|
142694
|
+
font-weight: 600;
|
|
142695
|
+
color: #4c3aac;
|
|
142696
|
+
cursor: pointer;
|
|
142697
|
+
display: inline-flex;
|
|
142698
|
+
align-items: center;
|
|
142699
|
+
gap: 6px;
|
|
142700
|
+
}
|
|
142701
|
+
.myio-annotations-toolbar-filter-btn:hover {
|
|
142702
|
+
background: rgba(108, 92, 231, 0.06);
|
|
142703
|
+
border-color: rgba(108, 92, 231, 0.35);
|
|
142704
|
+
}
|
|
142705
|
+
.myio-annotations-toolbar-filter-btn[aria-expanded="true"] {
|
|
142706
|
+
background: rgba(108, 92, 231, 0.12);
|
|
142707
|
+
border-color: #6c5ce7;
|
|
142708
|
+
}
|
|
142709
|
+
|
|
142710
|
+
/* Filter dropdown panel */
|
|
142711
|
+
.myio-annotations-filters {
|
|
142712
|
+
padding: 8px 0 12px 0;
|
|
142713
|
+
border-top: 1px dashed #e2e8f0;
|
|
142714
|
+
margin-top: 6px;
|
|
142715
|
+
}
|
|
142716
|
+
.myio-annotations-filter-section { margin-bottom: 8px; }
|
|
142717
|
+
.myio-annotations-filter-section-title {
|
|
142718
|
+
font-size: 11px;
|
|
142719
|
+
font-weight: 700;
|
|
142720
|
+
color: #64748b;
|
|
142721
|
+
text-transform: uppercase;
|
|
142722
|
+
letter-spacing: 0.04em;
|
|
142723
|
+
margin-bottom: 4px;
|
|
142724
|
+
}
|
|
142725
|
+
.myio-annotations-filter-chips {
|
|
142726
|
+
display: flex;
|
|
142727
|
+
flex-wrap: wrap;
|
|
142728
|
+
gap: 6px;
|
|
142729
|
+
}
|
|
142730
|
+
.myio-annotations-filter-chip {
|
|
142731
|
+
display: inline-flex;
|
|
142732
|
+
align-items: center;
|
|
142733
|
+
gap: 4px;
|
|
142734
|
+
padding: 4px 10px;
|
|
142735
|
+
border: 1px solid #e2e8f0;
|
|
142736
|
+
border-radius: 999px;
|
|
142737
|
+
background: #f8fafc;
|
|
142738
|
+
font-size: 12px;
|
|
142739
|
+
color: #334155;
|
|
142740
|
+
cursor: pointer;
|
|
142741
|
+
user-select: none;
|
|
142742
|
+
transition: background 0.12s, border-color 0.12s;
|
|
142743
|
+
}
|
|
142744
|
+
.myio-annotations-filter-chip:hover {
|
|
142745
|
+
border-color: rgba(108, 92, 231, 0.4);
|
|
142746
|
+
}
|
|
142747
|
+
.myio-annotations-filter-chip input {
|
|
142748
|
+
position: absolute;
|
|
142749
|
+
opacity: 0;
|
|
142750
|
+
pointer-events: none;
|
|
142751
|
+
}
|
|
142752
|
+
.myio-annotations-filter-chip.is-on {
|
|
142753
|
+
background: rgba(108, 92, 231, 0.12);
|
|
142754
|
+
border-color: #6c5ce7;
|
|
142755
|
+
color: #4c3aac;
|
|
142756
|
+
font-weight: 600;
|
|
142757
|
+
}
|
|
142758
|
+
.myio-annotations-filter-actions {
|
|
142759
|
+
display: flex;
|
|
142760
|
+
justify-content: flex-end;
|
|
142761
|
+
margin-top: 6px;
|
|
142762
|
+
}
|
|
142763
|
+
|
|
142764
|
+
/* Search highlight */
|
|
142765
|
+
.myio-annotations-item mark {
|
|
142766
|
+
background: rgba(245, 158, 11, 0.35);
|
|
142767
|
+
color: inherit;
|
|
142768
|
+
padding: 0 1px;
|
|
142769
|
+
border-radius: 2px;
|
|
142770
|
+
}
|
|
142771
|
+
|
|
142772
|
+
/* Body */
|
|
142773
|
+
.myio-annotations-body {
|
|
142774
|
+
flex: 1;
|
|
142775
|
+
overflow: auto;
|
|
142776
|
+
padding: 8px 12px 12px 12px;
|
|
142777
|
+
background: #fff;
|
|
142778
|
+
}
|
|
142779
|
+
|
|
142780
|
+
/* Group */
|
|
142781
|
+
.myio-annotations-group {
|
|
142782
|
+
margin-top: 8px;
|
|
142783
|
+
border: 1px solid #e2e8f0;
|
|
142784
|
+
border-radius: 8px;
|
|
142785
|
+
overflow: hidden;
|
|
142786
|
+
background: #fff;
|
|
142787
|
+
}
|
|
142788
|
+
.myio-annotations-group-header {
|
|
142789
|
+
display: flex;
|
|
142790
|
+
align-items: center;
|
|
142791
|
+
gap: 8px;
|
|
142792
|
+
padding: 8px 12px;
|
|
142793
|
+
background: #f8fafc;
|
|
142794
|
+
border-bottom: 1px solid #e2e8f0;
|
|
142795
|
+
font-size: 12px;
|
|
142796
|
+
font-weight: 700;
|
|
142797
|
+
color: #334155;
|
|
142798
|
+
}
|
|
142799
|
+
.myio-annotations-group-icon { font-size: 14px; }
|
|
142800
|
+
.myio-annotations-group-label { flex: 1; }
|
|
142801
|
+
.myio-annotations-group-count {
|
|
142802
|
+
font-size: 11px;
|
|
142803
|
+
font-weight: 600;
|
|
142804
|
+
color: #64748b;
|
|
142805
|
+
padding: 2px 8px;
|
|
142806
|
+
background: rgba(108, 92, 231, 0.1);
|
|
142807
|
+
border-radius: 10px;
|
|
142808
|
+
}
|
|
142809
|
+
.myio-annotations-group--no-id .myio-annotations-group-header {
|
|
142810
|
+
background: #fef3c7;
|
|
142811
|
+
color: #92400e;
|
|
142812
|
+
border-bottom-color: #fde68a;
|
|
142813
|
+
}
|
|
142814
|
+
|
|
142815
|
+
/* Item */
|
|
142816
|
+
.myio-annotations-item {
|
|
142817
|
+
display: grid;
|
|
142818
|
+
grid-template-columns: 22px 1fr auto;
|
|
142819
|
+
gap: 10px;
|
|
142820
|
+
padding: 10px 12px;
|
|
142821
|
+
border-bottom: 1px solid #f1f5f9;
|
|
142822
|
+
cursor: pointer;
|
|
142823
|
+
transition: background 0.12s;
|
|
142824
|
+
}
|
|
142825
|
+
.myio-annotations-item:last-child { border-bottom: none; }
|
|
142826
|
+
.myio-annotations-item:hover { background: rgba(108, 92, 231, 0.05); }
|
|
142827
|
+
.myio-annotations-item:focus-visible {
|
|
142828
|
+
outline: 2px solid #6c5ce7;
|
|
142829
|
+
outline-offset: -2px;
|
|
142830
|
+
}
|
|
142831
|
+
|
|
142832
|
+
.myio-annotations-item-icon {
|
|
142833
|
+
font-size: 16px;
|
|
142834
|
+
line-height: 22px;
|
|
142835
|
+
text-align: center;
|
|
142836
|
+
}
|
|
142837
|
+
.myio-annotations-item-body { min-width: 0; }
|
|
142838
|
+
.myio-annotations-item-text {
|
|
142839
|
+
font-size: 13px;
|
|
142840
|
+
font-weight: 500;
|
|
142841
|
+
color: #1e293b;
|
|
142842
|
+
margin: 0 0 4px 0;
|
|
142843
|
+
line-height: 1.4;
|
|
142844
|
+
display: -webkit-box;
|
|
142845
|
+
-webkit-line-clamp: 2;
|
|
142846
|
+
-webkit-box-orient: vertical;
|
|
142847
|
+
overflow: hidden;
|
|
142848
|
+
}
|
|
142849
|
+
.myio-annotations-item-meta {
|
|
142850
|
+
font-size: 11px;
|
|
142851
|
+
color: #64748b;
|
|
142852
|
+
display: flex;
|
|
142853
|
+
flex-wrap: wrap;
|
|
142854
|
+
gap: 6px 10px;
|
|
142855
|
+
}
|
|
142856
|
+
.myio-annotations-item-device {
|
|
142857
|
+
font-weight: 600;
|
|
142858
|
+
color: #4c3aac;
|
|
142859
|
+
}
|
|
142860
|
+
|
|
142861
|
+
.myio-annotations-item-side {
|
|
142862
|
+
display: flex;
|
|
142863
|
+
flex-direction: column;
|
|
142864
|
+
align-items: flex-end;
|
|
142865
|
+
gap: 4px;
|
|
142866
|
+
}
|
|
142867
|
+
.myio-annotations-importance-badge {
|
|
142868
|
+
display: inline-block;
|
|
142869
|
+
min-width: 18px;
|
|
142870
|
+
padding: 2px 6px;
|
|
142871
|
+
border-radius: 10px;
|
|
142872
|
+
font-size: 10px;
|
|
142873
|
+
font-weight: 700;
|
|
142874
|
+
color: #fff;
|
|
142875
|
+
text-align: center;
|
|
142876
|
+
line-height: 1.2;
|
|
142877
|
+
}
|
|
142878
|
+
.myio-annotations-importance-1 { background: #94a3b8; }
|
|
142879
|
+
.myio-annotations-importance-2 { background: #64748b; }
|
|
142880
|
+
.myio-annotations-importance-3 { background: #6c5ce7; }
|
|
142881
|
+
.myio-annotations-importance-4 { background: #f59e0b; }
|
|
142882
|
+
.myio-annotations-importance-5 { background: #dc2626; }
|
|
142883
|
+
|
|
142884
|
+
.myio-annotations-overdue {
|
|
142885
|
+
font-size: 10px;
|
|
142886
|
+
font-weight: 700;
|
|
142887
|
+
color: #dc2626;
|
|
142888
|
+
text-transform: uppercase;
|
|
142889
|
+
}
|
|
142890
|
+
|
|
142891
|
+
/* Empty state */
|
|
142892
|
+
.myio-annotations-empty {
|
|
142893
|
+
padding: 32px 16px;
|
|
142894
|
+
text-align: center;
|
|
142895
|
+
color: #94a3b8;
|
|
142896
|
+
font-size: 13px;
|
|
142897
|
+
}
|
|
142898
|
+
.myio-annotations-empty-icon {
|
|
142899
|
+
font-size: 28px;
|
|
142900
|
+
margin-bottom: 8px;
|
|
142901
|
+
opacity: 0.5;
|
|
142902
|
+
}
|
|
142903
|
+
|
|
142904
|
+
/* Loading state */
|
|
142905
|
+
.myio-annotations-loading {
|
|
142906
|
+
padding: 24px;
|
|
142907
|
+
text-align: center;
|
|
142908
|
+
color: #64748b;
|
|
142909
|
+
font-size: 13px;
|
|
142910
|
+
}
|
|
142911
|
+
|
|
142912
|
+
/* Footer */
|
|
142913
|
+
.myio-annotations-panel-footer {
|
|
142914
|
+
display: flex;
|
|
142915
|
+
align-items: center;
|
|
142916
|
+
justify-content: space-between;
|
|
142917
|
+
padding: 8px 16px;
|
|
142918
|
+
border-top: 1px solid #e2e8f0;
|
|
142919
|
+
background: #fafbff;
|
|
142920
|
+
font-size: 11px;
|
|
142921
|
+
color: #64748b;
|
|
142922
|
+
flex-shrink: 0;
|
|
142923
|
+
}
|
|
142924
|
+
.myio-annotations-panel-footer-meta {
|
|
142925
|
+
flex: 1;
|
|
142926
|
+
text-align: center;
|
|
142927
|
+
font-size: 11px;
|
|
142928
|
+
color: #94a3b8;
|
|
142929
|
+
}
|
|
142930
|
+
.myio-annotations-panel-footer-action {
|
|
142931
|
+
font: inherit;
|
|
142932
|
+
font-size: 12px;
|
|
142933
|
+
font-weight: 600;
|
|
142934
|
+
color: #4c3aac;
|
|
142935
|
+
background: transparent;
|
|
142936
|
+
border: 1px solid rgba(108, 92, 231, 0.3);
|
|
142937
|
+
border-radius: 6px;
|
|
142938
|
+
padding: 4px 10px;
|
|
142939
|
+
cursor: pointer;
|
|
142940
|
+
transition: background 0.15s, border-color 0.15s;
|
|
142941
|
+
}
|
|
142942
|
+
.myio-annotations-panel-footer-action:hover {
|
|
142943
|
+
background: rgba(108, 92, 231, 0.08);
|
|
142944
|
+
border-color: rgba(108, 92, 231, 0.5);
|
|
142945
|
+
}
|
|
142946
|
+
.myio-annotations-panel-footer-action:focus-visible {
|
|
142947
|
+
outline: 2px solid #6c5ce7;
|
|
142948
|
+
outline-offset: 1px;
|
|
142949
|
+
}
|
|
142950
|
+
`;
|
|
142951
|
+
function injectStylesOnce() {
|
|
142952
|
+
if (typeof document === "undefined") return;
|
|
142953
|
+
if (document.getElementById(HEADER_ANNOTATIONS_STYLES_ID)) return;
|
|
142954
|
+
const style = document.createElement("style");
|
|
142955
|
+
style.id = HEADER_ANNOTATIONS_STYLES_ID;
|
|
142956
|
+
style.textContent = HEADER_ANNOTATIONS_STYLES;
|
|
142957
|
+
document.head.appendChild(style);
|
|
142958
|
+
}
|
|
142959
|
+
|
|
142960
|
+
// src/components/header-annotations-panel/searchSortFilter.ts
|
|
142961
|
+
function nfdNormalize(s) {
|
|
142962
|
+
if (!s) return "";
|
|
142963
|
+
return s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
|
|
142964
|
+
}
|
|
142965
|
+
function highlightMatches(escapedText, term) {
|
|
142966
|
+
if (!term) return escapedText;
|
|
142967
|
+
const needle = nfdNormalize(term);
|
|
142968
|
+
if (!needle) return escapedText;
|
|
142969
|
+
const lowered = nfdNormalize(escapedText);
|
|
142970
|
+
let result = "";
|
|
142971
|
+
let i = 0;
|
|
142972
|
+
while (i < escapedText.length) {
|
|
142973
|
+
const found = lowered.indexOf(needle, i);
|
|
142974
|
+
if (found === -1) {
|
|
142975
|
+
result += escapedText.slice(i);
|
|
142976
|
+
break;
|
|
142977
|
+
}
|
|
142978
|
+
result += escapedText.slice(i, found);
|
|
142979
|
+
result += "<mark>" + escapedText.slice(found, found + needle.length) + "</mark>";
|
|
142980
|
+
i = found + needle.length;
|
|
142981
|
+
}
|
|
142982
|
+
return result;
|
|
142983
|
+
}
|
|
142984
|
+
var SORT_OPTIONS = [
|
|
142985
|
+
{ key: "alpha-asc", label: "Alfab\xE9tica (A \u2192 Z)" },
|
|
142986
|
+
{ key: "alpha-desc", label: "Alfab\xE9tica (Z \u2192 A)" },
|
|
142987
|
+
{ key: "count-desc", label: "Mais anota\xE7\xF5es" },
|
|
142988
|
+
{ key: "count-asc", label: "Menos anota\xE7\xF5es" },
|
|
142989
|
+
{ key: "importance-desc", label: "Maior import\xE2ncia" },
|
|
142990
|
+
{ key: "recent-desc", label: "Mais recente" }
|
|
142991
|
+
];
|
|
142992
|
+
var DEFAULT_SORT = "alpha-asc";
|
|
142993
|
+
function sortGroups(groups, key) {
|
|
142994
|
+
const arr = groups.slice();
|
|
142995
|
+
const cmp = _comparator(key);
|
|
142996
|
+
arr.sort((a, b) => cmp(a, b));
|
|
142997
|
+
return arr;
|
|
142998
|
+
}
|
|
142999
|
+
function _comparator(key) {
|
|
143000
|
+
switch (key) {
|
|
143001
|
+
case "alpha-asc":
|
|
143002
|
+
return (a, b) => a.label.localeCompare(b.label, "pt-BR", { sensitivity: "base" });
|
|
143003
|
+
case "alpha-desc":
|
|
143004
|
+
return (a, b) => b.label.localeCompare(a.label, "pt-BR", { sensitivity: "base" });
|
|
143005
|
+
case "count-desc":
|
|
143006
|
+
return (a, b) => b.totalAnnotations - a.totalAnnotations || a.label.localeCompare(b.label, "pt-BR");
|
|
143007
|
+
case "count-asc":
|
|
143008
|
+
return (a, b) => a.totalAnnotations - b.totalAnnotations || a.label.localeCompare(b.label, "pt-BR");
|
|
143009
|
+
case "importance-desc":
|
|
143010
|
+
return (a, b) => b.maxImportance - a.maxImportance || b.totalAnnotations - a.totalAnnotations;
|
|
143011
|
+
case "recent-desc":
|
|
143012
|
+
return (a, b) => _compareIsoDesc(a.mostRecentAt, b.mostRecentAt);
|
|
143013
|
+
default:
|
|
143014
|
+
return () => 0;
|
|
143015
|
+
}
|
|
143016
|
+
}
|
|
143017
|
+
function _compareIsoDesc(a, b) {
|
|
143018
|
+
if (a === b) return 0;
|
|
143019
|
+
if (!a) return 1;
|
|
143020
|
+
if (!b) return -1;
|
|
143021
|
+
return b.localeCompare(a);
|
|
143022
|
+
}
|
|
143023
|
+
function createDefaultFilter() {
|
|
143024
|
+
return {
|
|
143025
|
+
types: /* @__PURE__ */ new Set(),
|
|
143026
|
+
statuses: /* @__PURE__ */ new Set(),
|
|
143027
|
+
importance: /* @__PURE__ */ new Set(),
|
|
143028
|
+
actionableOnly: false,
|
|
143029
|
+
searchTerm: ""
|
|
143030
|
+
};
|
|
143031
|
+
}
|
|
143032
|
+
var FILTER_TYPE_OPTIONS = [
|
|
143033
|
+
{ id: "observation", label: "Observa\xE7\xE3o", icon: "\u{1F4DD}" },
|
|
143034
|
+
{ id: "pending", label: "Pend\xEAncia", icon: "\u26A0\uFE0F" },
|
|
143035
|
+
{ id: "maintenance", label: "Manuten\xE7\xE3o", icon: "\u{1F527}" },
|
|
143036
|
+
{ id: "activity", label: "Atividade", icon: "\u2713" }
|
|
143037
|
+
];
|
|
143038
|
+
var FILTER_STATUS_OPTIONS = [
|
|
143039
|
+
{ id: "created", label: "Criada" },
|
|
143040
|
+
{ id: "modified", label: "Modificada" },
|
|
143041
|
+
{ id: "archived", label: "Arquivada" }
|
|
143042
|
+
// AC-27: off by default; toggle exposes it
|
|
143043
|
+
];
|
|
143044
|
+
function countAnnotationsInGroups(groups) {
|
|
143045
|
+
let n = 0;
|
|
143046
|
+
for (const g of groups) n += g.totalAnnotations;
|
|
143047
|
+
return n;
|
|
143048
|
+
}
|
|
143049
|
+
function toggleInSet(set, value) {
|
|
143050
|
+
const next = new Set(set);
|
|
143051
|
+
if (next.has(value)) next.delete(value);
|
|
143052
|
+
else next.add(value);
|
|
143053
|
+
return next;
|
|
143054
|
+
}
|
|
143055
|
+
function withSearchTerm(filter, term) {
|
|
143056
|
+
return { ...filter, searchTerm: term };
|
|
143057
|
+
}
|
|
143058
|
+
|
|
143059
|
+
// src/components/header-annotations-panel/AnnotationItemCard.ts
|
|
143060
|
+
var DOMAIN_ICONS3 = {
|
|
143061
|
+
energy: "\u26A1",
|
|
143062
|
+
water: "\u{1F4A7}",
|
|
143063
|
+
temperature: "\u{1F321}\uFE0F",
|
|
143064
|
+
unknown: "\xB7"
|
|
143065
|
+
};
|
|
143066
|
+
var TYPE_ICONS = {
|
|
143067
|
+
observation: "\u{1F4DD}",
|
|
143068
|
+
pending: "\u26A0\uFE0F",
|
|
143069
|
+
maintenance: "\u{1F527}",
|
|
143070
|
+
activity: "\u2713"
|
|
143071
|
+
};
|
|
143072
|
+
var ITEM_TEXT_MAX = 120;
|
|
143073
|
+
function escapeHtml6(input) {
|
|
143074
|
+
if (input == null) return "";
|
|
143075
|
+
return String(input).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
143076
|
+
}
|
|
143077
|
+
function truncate2(input, max) {
|
|
143078
|
+
const s = String(input ?? "");
|
|
143079
|
+
if (s.length <= max) return s;
|
|
143080
|
+
return s.slice(0, max - 1).trimEnd() + "\u2026";
|
|
143081
|
+
}
|
|
143082
|
+
function formatRelative(iso, now = Date.now()) {
|
|
143083
|
+
if (!iso) return "";
|
|
143084
|
+
const t = new Date(iso).getTime();
|
|
143085
|
+
if (isNaN(t)) return "";
|
|
143086
|
+
const diffMs = now - t;
|
|
143087
|
+
const sec = Math.round(diffMs / 1e3);
|
|
143088
|
+
if (sec < 60) return "agora h\xE1 pouco";
|
|
143089
|
+
const min = Math.round(sec / 60);
|
|
143090
|
+
if (min < 60) return `h\xE1 ${min} min`;
|
|
143091
|
+
const hr = Math.round(min / 60);
|
|
143092
|
+
if (hr < 24) return `h\xE1 ${hr} h`;
|
|
143093
|
+
const day = Math.round(hr / 24);
|
|
143094
|
+
if (day < 30) return `h\xE1 ${day} d`;
|
|
143095
|
+
const mo = Math.round(day / 30);
|
|
143096
|
+
if (mo < 12) return `h\xE1 ${mo} m\xEAs${mo > 1 ? "es" : ""}`;
|
|
143097
|
+
const yr = Math.round(mo / 12);
|
|
143098
|
+
return `h\xE1 ${yr} ano${yr > 1 ? "s" : ""}`;
|
|
143099
|
+
}
|
|
143100
|
+
function isOverdue(annotation, now = Date.now()) {
|
|
143101
|
+
if (annotation.type !== "pending") return false;
|
|
143102
|
+
if (!annotation.dueDate) return false;
|
|
143103
|
+
const due = new Date(annotation.dueDate).getTime();
|
|
143104
|
+
if (isNaN(due)) return false;
|
|
143105
|
+
return due < now;
|
|
143106
|
+
}
|
|
143107
|
+
function renderAnnotationItemCard(device, annotation, searchTerm) {
|
|
143108
|
+
const domainIcon = DOMAIN_ICONS3[device.domain] ?? DOMAIN_ICONS3.unknown;
|
|
143109
|
+
const typeIcon = TYPE_ICONS[annotation.type] ?? "\xB7";
|
|
143110
|
+
const importance = Math.max(1, Math.min(5, annotation.importance || 1));
|
|
143111
|
+
const textEscaped = escapeHtml6(truncate2(annotation.text || "", ITEM_TEXT_MAX));
|
|
143112
|
+
const deviceLabelEscaped = escapeHtml6(device.label || device.name || device.deviceId);
|
|
143113
|
+
const author = escapeHtml6(annotation.createdBy?.name || "sem autor");
|
|
143114
|
+
const when = escapeHtml6(formatRelative(annotation.createdAt));
|
|
143115
|
+
const overdueTag = isOverdue(annotation) ? '<span class="myio-annotations-overdue">Vencida</span>' : "";
|
|
143116
|
+
const text = searchTerm ? highlightMatches(textEscaped, searchTerm) : textEscaped;
|
|
143117
|
+
const deviceLabel = searchTerm ? highlightMatches(deviceLabelEscaped, searchTerm) : deviceLabelEscaped;
|
|
143118
|
+
const identifierEscaped = device.identifier ? escapeHtml6(device.identifier) : "";
|
|
143119
|
+
const identifierHighlighted = searchTerm && identifierEscaped ? highlightMatches(identifierEscaped, searchTerm) : identifierEscaped;
|
|
143120
|
+
const identifierTag = device.identifier ? `<span class="myio-annotations-item-device">${identifierHighlighted}</span>` : "";
|
|
143121
|
+
return `
|
|
143122
|
+
<button
|
|
143123
|
+
class="myio-annotations-item"
|
|
143124
|
+
type="button"
|
|
143125
|
+
data-device-id="${escapeHtml6(device.deviceId)}"
|
|
143126
|
+
data-annotation-id="${escapeHtml6(annotation.id)}"
|
|
143127
|
+
tabindex="0"
|
|
143128
|
+
aria-label="${escapeHtml6(annotation.text)} \u2014 ${deviceLabel}"
|
|
143129
|
+
>
|
|
143130
|
+
<span class="myio-annotations-item-icon" aria-hidden="true">${typeIcon}</span>
|
|
143131
|
+
<div class="myio-annotations-item-body">
|
|
143132
|
+
<p class="myio-annotations-item-text">${text}</p>
|
|
143133
|
+
<div class="myio-annotations-item-meta">
|
|
143134
|
+
<span aria-hidden="true">${domainIcon}</span>
|
|
143135
|
+
${identifierTag}
|
|
143136
|
+
<span>${deviceLabel}</span>
|
|
143137
|
+
<span>\xB7</span>
|
|
143138
|
+
<span>${author}</span>
|
|
143139
|
+
<span>\xB7</span>
|
|
143140
|
+
<span>${when}</span>
|
|
143141
|
+
</div>
|
|
143142
|
+
</div>
|
|
143143
|
+
<div class="myio-annotations-item-side">
|
|
143144
|
+
<span class="myio-annotations-importance-badge myio-annotations-importance-${importance}" title="Import\xE2ncia ${importance}">${importance}</span>
|
|
143145
|
+
${overdueTag}
|
|
143146
|
+
</div>
|
|
143147
|
+
</button>
|
|
143148
|
+
`.trim();
|
|
143149
|
+
}
|
|
143150
|
+
|
|
143151
|
+
// src/components/header-annotations-panel/VirtualList.ts
|
|
143152
|
+
var VIRTUAL_SCROLL_THRESHOLD = 100;
|
|
143153
|
+
function shouldVirtualize(itemCount) {
|
|
143154
|
+
return itemCount > VIRTUAL_SCROLL_THRESHOLD;
|
|
143155
|
+
}
|
|
143156
|
+
var VirtualList = class {
|
|
143157
|
+
container;
|
|
143158
|
+
rows;
|
|
143159
|
+
overscan;
|
|
143160
|
+
spacer;
|
|
143161
|
+
viewport;
|
|
143162
|
+
/** Cumulative pixel offset for each row (offsets[i] = top of row i). */
|
|
143163
|
+
offsets;
|
|
143164
|
+
/** Total content height. */
|
|
143165
|
+
totalHeight;
|
|
143166
|
+
_onScroll;
|
|
143167
|
+
_rafScheduled = false;
|
|
143168
|
+
_destroyed = false;
|
|
143169
|
+
constructor(opts) {
|
|
143170
|
+
this.container = opts.container;
|
|
143171
|
+
this.rows = opts.rows;
|
|
143172
|
+
this.overscan = opts.overscan ?? 6;
|
|
143173
|
+
this.offsets = new Array(this.rows.length);
|
|
143174
|
+
let acc = 0;
|
|
143175
|
+
for (let i = 0; i < this.rows.length; i++) {
|
|
143176
|
+
this.offsets[i] = acc;
|
|
143177
|
+
acc += this.rows[i].height;
|
|
143178
|
+
}
|
|
143179
|
+
this.totalHeight = acc;
|
|
143180
|
+
this.container.classList.add("myio-vlist-container");
|
|
143181
|
+
this.container.style.position = "relative";
|
|
143182
|
+
this.container.style.overflowY = "auto";
|
|
143183
|
+
this.spacer = document.createElement("div");
|
|
143184
|
+
this.spacer.className = "myio-vlist-spacer";
|
|
143185
|
+
this.spacer.style.position = "relative";
|
|
143186
|
+
this.spacer.style.width = "100%";
|
|
143187
|
+
this.spacer.style.height = `${this.totalHeight}px`;
|
|
143188
|
+
this.viewport = document.createElement("div");
|
|
143189
|
+
this.viewport.className = "myio-vlist-viewport";
|
|
143190
|
+
this.viewport.style.position = "absolute";
|
|
143191
|
+
this.viewport.style.top = "0px";
|
|
143192
|
+
this.viewport.style.left = "0";
|
|
143193
|
+
this.viewport.style.right = "0";
|
|
143194
|
+
this.spacer.appendChild(this.viewport);
|
|
143195
|
+
this.container.innerHTML = "";
|
|
143196
|
+
this.container.appendChild(this.spacer);
|
|
143197
|
+
this._onScroll = () => this._scheduleRender();
|
|
143198
|
+
this.container.addEventListener("scroll", this._onScroll, { passive: true });
|
|
143199
|
+
this._renderVisible();
|
|
143200
|
+
}
|
|
143201
|
+
/** Re-render the visible window (e.g. after a DOM resize). Idempotent. */
|
|
143202
|
+
refresh() {
|
|
143203
|
+
if (this._destroyed) return;
|
|
143204
|
+
this._renderVisible();
|
|
143205
|
+
}
|
|
143206
|
+
/** Remove the spacer/viewport and detach listeners. */
|
|
143207
|
+
destroy() {
|
|
143208
|
+
if (this._destroyed) return;
|
|
143209
|
+
this._destroyed = true;
|
|
143210
|
+
this.container.removeEventListener("scroll", this._onScroll);
|
|
143211
|
+
this.container.classList.remove("myio-vlist-container");
|
|
143212
|
+
this.container.innerHTML = "";
|
|
143213
|
+
}
|
|
143214
|
+
// ── Internals ───────────────────────────────────────────────────────────
|
|
143215
|
+
_scheduleRender() {
|
|
143216
|
+
if (this._rafScheduled) return;
|
|
143217
|
+
this._rafScheduled = true;
|
|
143218
|
+
requestAnimationFrame(() => {
|
|
143219
|
+
this._rafScheduled = false;
|
|
143220
|
+
this._renderVisible();
|
|
143221
|
+
});
|
|
143222
|
+
}
|
|
143223
|
+
_renderVisible() {
|
|
143224
|
+
if (this._destroyed) return;
|
|
143225
|
+
const scrollTop = this.container.scrollTop;
|
|
143226
|
+
const viewportH = this.container.clientHeight || 1;
|
|
143227
|
+
const startIdx = Math.max(0, this._findRowAt(scrollTop) - this.overscan);
|
|
143228
|
+
const endIdx = Math.min(
|
|
143229
|
+
this.rows.length - 1,
|
|
143230
|
+
this._findRowAt(scrollTop + viewportH) + this.overscan
|
|
143231
|
+
);
|
|
143232
|
+
const html = [];
|
|
143233
|
+
for (let i = startIdx; i <= endIdx; i++) {
|
|
143234
|
+
html.push(this.rows[i].render());
|
|
143235
|
+
}
|
|
143236
|
+
const topOffset = this.offsets[startIdx] ?? 0;
|
|
143237
|
+
this.viewport.style.transform = `translateY(${topOffset}px)`;
|
|
143238
|
+
this.viewport.innerHTML = html.join("");
|
|
143239
|
+
}
|
|
143240
|
+
/** Binary search for the row whose offset contains `y`. */
|
|
143241
|
+
_findRowAt(y) {
|
|
143242
|
+
if (this.rows.length === 0) return 0;
|
|
143243
|
+
let lo = 0;
|
|
143244
|
+
let hi = this.rows.length - 1;
|
|
143245
|
+
while (lo < hi) {
|
|
143246
|
+
const mid = lo + hi + 1 >> 1;
|
|
143247
|
+
if (this.offsets[mid] <= y) lo = mid;
|
|
143248
|
+
else hi = mid - 1;
|
|
143249
|
+
}
|
|
143250
|
+
return lo;
|
|
143251
|
+
}
|
|
143252
|
+
};
|
|
143253
|
+
|
|
143254
|
+
// src/components/header-annotations-panel/ExportModal.ts
|
|
143255
|
+
var MODAL_DOM_ID = "myio-annotations-export-modal";
|
|
143256
|
+
var STYLES_ID3 = "myio-annotations-export-modal-styles";
|
|
143257
|
+
var STYLES4 = `
|
|
143258
|
+
.${MODAL_DOM_ID}-backdrop {
|
|
143259
|
+
position: fixed; inset: 0; z-index: 99999;
|
|
143260
|
+
background: rgba(15, 23, 42, 0.55);
|
|
143261
|
+
display: flex; align-items: center; justify-content: center;
|
|
143262
|
+
animation: myio-anno-export-fade 0.12s ease-out;
|
|
143263
|
+
}
|
|
143264
|
+
@keyframes myio-anno-export-fade {
|
|
143265
|
+
from { opacity: 0; } to { opacity: 1; }
|
|
143266
|
+
}
|
|
143267
|
+
.${MODAL_DOM_ID} {
|
|
143268
|
+
width: min(420px, 92vw);
|
|
143269
|
+
background: #fff;
|
|
143270
|
+
border-radius: 12px;
|
|
143271
|
+
box-shadow: 0 20px 50px rgba(15, 23, 42, 0.3);
|
|
143272
|
+
padding: 18px 20px;
|
|
143273
|
+
font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
143274
|
+
color: #1e293b;
|
|
143275
|
+
}
|
|
143276
|
+
.${MODAL_DOM_ID} h3 {
|
|
143277
|
+
margin: 0 0 12px 0;
|
|
143278
|
+
font-size: 16px;
|
|
143279
|
+
color: #4c3aac;
|
|
143280
|
+
}
|
|
143281
|
+
.${MODAL_DOM_ID} fieldset {
|
|
143282
|
+
border: none;
|
|
143283
|
+
margin: 0 0 12px 0;
|
|
143284
|
+
padding: 0;
|
|
143285
|
+
}
|
|
143286
|
+
.${MODAL_DOM_ID} legend {
|
|
143287
|
+
font-size: 12px;
|
|
143288
|
+
font-weight: 700;
|
|
143289
|
+
color: #64748b;
|
|
143290
|
+
text-transform: uppercase;
|
|
143291
|
+
letter-spacing: 0.04em;
|
|
143292
|
+
margin-bottom: 4px;
|
|
143293
|
+
}
|
|
143294
|
+
.${MODAL_DOM_ID} label {
|
|
143295
|
+
display: block;
|
|
143296
|
+
font-size: 13px;
|
|
143297
|
+
padding: 3px 0;
|
|
143298
|
+
cursor: pointer;
|
|
143299
|
+
}
|
|
143300
|
+
.${MODAL_DOM_ID} input[type="radio"],
|
|
143301
|
+
.${MODAL_DOM_ID} input[type="checkbox"] {
|
|
143302
|
+
margin-right: 6px;
|
|
143303
|
+
accent-color: #6c5ce7;
|
|
143304
|
+
}
|
|
143305
|
+
.${MODAL_DOM_ID} input[disabled] + span { color: #94a3b8; }
|
|
143306
|
+
.${MODAL_DOM_ID}-actions {
|
|
143307
|
+
display: flex; gap: 8px; justify-content: flex-end;
|
|
143308
|
+
margin-top: 8px;
|
|
143309
|
+
}
|
|
143310
|
+
.${MODAL_DOM_ID}-actions button {
|
|
143311
|
+
font: inherit; font-size: 13px; font-weight: 600;
|
|
143312
|
+
padding: 6px 14px;
|
|
143313
|
+
border-radius: 6px;
|
|
143314
|
+
cursor: pointer;
|
|
143315
|
+
}
|
|
143316
|
+
.${MODAL_DOM_ID}-cancel {
|
|
143317
|
+
background: #fff;
|
|
143318
|
+
border: 1px solid #cbd5e1;
|
|
143319
|
+
color: #475569;
|
|
143320
|
+
}
|
|
143321
|
+
.${MODAL_DOM_ID}-confirm {
|
|
143322
|
+
background: #6c5ce7;
|
|
143323
|
+
border: 1px solid #6c5ce7;
|
|
143324
|
+
color: #fff;
|
|
143325
|
+
}
|
|
143326
|
+
.${MODAL_DOM_ID}-confirm:disabled {
|
|
143327
|
+
background: #cbd5e1; border-color: #cbd5e1; cursor: not-allowed;
|
|
143328
|
+
}
|
|
143329
|
+
`;
|
|
143330
|
+
function injectStyles15() {
|
|
143331
|
+
if (typeof document === "undefined") return;
|
|
143332
|
+
if (document.getElementById(STYLES_ID3)) return;
|
|
143333
|
+
const el2 = document.createElement("style");
|
|
143334
|
+
el2.id = STYLES_ID3;
|
|
143335
|
+
el2.textContent = STYLES4;
|
|
143336
|
+
document.head.appendChild(el2);
|
|
143337
|
+
}
|
|
143338
|
+
var _activeBackdrop = null;
|
|
143339
|
+
function openExportModal(options) {
|
|
143340
|
+
injectStyles15();
|
|
143341
|
+
closeExportModal();
|
|
143342
|
+
const backdrop = document.createElement("div");
|
|
143343
|
+
backdrop.className = `${MODAL_DOM_ID}-backdrop`;
|
|
143344
|
+
backdrop.setAttribute("role", "presentation");
|
|
143345
|
+
backdrop.innerHTML = `
|
|
143346
|
+
<div class="${MODAL_DOM_ID}" role="dialog" aria-modal="true" aria-labelledby="${MODAL_DOM_ID}-title">
|
|
143347
|
+
<h3 id="${MODAL_DOM_ID}-title">Exportar anota\xE7\xF5es</h3>
|
|
143348
|
+
|
|
143349
|
+
<fieldset>
|
|
143350
|
+
<legend>Formato</legend>
|
|
143351
|
+
<label><input type="radio" name="fmt" value="pdf" checked /><span>PDF</span></label>
|
|
143352
|
+
<label><input type="radio" name="fmt" value="csv" /><span>CSV</span></label>
|
|
143353
|
+
</fieldset>
|
|
143354
|
+
|
|
143355
|
+
<fieldset data-fmt-pdf-only>
|
|
143356
|
+
<legend>N\xEDveis (PDF apenas)</legend>
|
|
143357
|
+
<label><input type="checkbox" name="lvl-summary" checked /><span>Sum\xE1rio (totais + KPIs)</span></label>
|
|
143358
|
+
<label><input type="checkbox" name="lvl-consolidated" checked /><span>Consolidado (por device)</span></label>
|
|
143359
|
+
<label><input type="checkbox" name="lvl-detailed" /><span>Detalhado (anota\xE7\xE3o por anota\xE7\xE3o)</span></label>
|
|
143360
|
+
</fieldset>
|
|
143361
|
+
|
|
143362
|
+
<fieldset>
|
|
143363
|
+
<legend>Escopo</legend>
|
|
143364
|
+
<label><input type="radio" name="scope" value="current-tab" checked /><span>Aba atual</span></label>
|
|
143365
|
+
<label><input type="radio" name="scope" value="all" /><span>Todas as anota\xE7\xF5es</span></label>
|
|
143366
|
+
<label><input type="radio" name="scope" value="filtered" ${options.hasActiveFilter ? "" : "disabled"} /><span>Resultado filtrado / buscado</span></label>
|
|
143367
|
+
</fieldset>
|
|
143368
|
+
|
|
143369
|
+
<div class="${MODAL_DOM_ID}-actions">
|
|
143370
|
+
<button type="button" class="${MODAL_DOM_ID}-cancel">Cancelar</button>
|
|
143371
|
+
<button type="button" class="${MODAL_DOM_ID}-confirm">Exportar</button>
|
|
143372
|
+
</div>
|
|
143373
|
+
</div>
|
|
143374
|
+
`;
|
|
143375
|
+
document.body.appendChild(backdrop);
|
|
143376
|
+
_activeBackdrop = backdrop;
|
|
143377
|
+
const modal = backdrop.querySelector(`.${MODAL_DOM_ID}`);
|
|
143378
|
+
const fmtPdfOnly = modal.querySelector("[data-fmt-pdf-only]");
|
|
143379
|
+
const confirmBtn = modal.querySelector(`.${MODAL_DOM_ID}-confirm`);
|
|
143380
|
+
const cancelBtn = modal.querySelector(`.${MODAL_DOM_ID}-cancel`);
|
|
143381
|
+
const fmtRadios = Array.from(modal.querySelectorAll('input[name="fmt"]'));
|
|
143382
|
+
const updatePdfFieldset = () => {
|
|
143383
|
+
const isPdf = fmtRadios.find((r) => r.checked)?.value === "pdf";
|
|
143384
|
+
if (fmtPdfOnly) {
|
|
143385
|
+
const inputs = Array.from(fmtPdfOnly.querySelectorAll("input"));
|
|
143386
|
+
inputs.forEach((i) => {
|
|
143387
|
+
i.disabled = !isPdf;
|
|
143388
|
+
});
|
|
143389
|
+
fmtPdfOnly.style.opacity = isPdf ? "1" : "0.5";
|
|
143390
|
+
}
|
|
143391
|
+
const enabled = !isPdf || modal.querySelector('input[name="lvl-summary"]')?.checked || modal.querySelector('input[name="lvl-consolidated"]')?.checked || modal.querySelector('input[name="lvl-detailed"]')?.checked;
|
|
143392
|
+
confirmBtn.disabled = !enabled;
|
|
143393
|
+
};
|
|
143394
|
+
fmtRadios.forEach((r) => r.addEventListener("change", updatePdfFieldset));
|
|
143395
|
+
modal.querySelectorAll('input[name^="lvl-"]').forEach((cb) => cb.addEventListener("change", updatePdfFieldset));
|
|
143396
|
+
updatePdfFieldset();
|
|
143397
|
+
const close = () => closeExportModal();
|
|
143398
|
+
cancelBtn.addEventListener("click", close);
|
|
143399
|
+
backdrop.addEventListener("click", (e) => {
|
|
143400
|
+
if (e.target === backdrop) close();
|
|
143401
|
+
});
|
|
143402
|
+
const onEsc = (e) => {
|
|
143403
|
+
if (e.key === "Escape") {
|
|
143404
|
+
close();
|
|
143405
|
+
window.removeEventListener("keydown", onEsc);
|
|
143406
|
+
}
|
|
143407
|
+
};
|
|
143408
|
+
window.addEventListener("keydown", onEsc);
|
|
143409
|
+
confirmBtn.addEventListener("click", () => {
|
|
143410
|
+
const fmt2 = fmtRadios.find((r) => r.checked)?.value;
|
|
143411
|
+
const scope = modal.querySelector('input[name="scope"]:checked')?.value ?? "current-tab";
|
|
143412
|
+
const levels = [];
|
|
143413
|
+
if (fmt2 === "pdf") {
|
|
143414
|
+
if (modal.querySelector('input[name="lvl-summary"]')?.checked) levels.push("summary");
|
|
143415
|
+
if (modal.querySelector('input[name="lvl-consolidated"]')?.checked) levels.push("consolidated");
|
|
143416
|
+
if (modal.querySelector('input[name="lvl-detailed"]')?.checked) levels.push("detailed");
|
|
143417
|
+
}
|
|
143418
|
+
const opts = {
|
|
143419
|
+
format: fmt2,
|
|
143420
|
+
levels: fmt2 === "pdf" ? levels : void 0,
|
|
143421
|
+
scope
|
|
143422
|
+
};
|
|
143423
|
+
try {
|
|
143424
|
+
options.onExport(opts);
|
|
143425
|
+
} catch (err) {
|
|
143426
|
+
(options.logger ?? console).warn("[ExportModal] onExport threw:", err);
|
|
143427
|
+
}
|
|
143428
|
+
close();
|
|
143429
|
+
});
|
|
143430
|
+
setTimeout(() => {
|
|
143431
|
+
const focusable = modal.querySelector('input[name="fmt"]:checked');
|
|
143432
|
+
focusable?.focus();
|
|
143433
|
+
}, 0);
|
|
143434
|
+
return close;
|
|
143435
|
+
}
|
|
143436
|
+
function closeExportModal() {
|
|
143437
|
+
if (_activeBackdrop && _activeBackdrop.parentNode) {
|
|
143438
|
+
_activeBackdrop.parentNode.removeChild(_activeBackdrop);
|
|
143439
|
+
}
|
|
143440
|
+
_activeBackdrop = null;
|
|
143441
|
+
}
|
|
143442
|
+
|
|
143443
|
+
// src/components/header-annotations-panel/ExportCSV.ts
|
|
143444
|
+
var CSV_COLUMNS = [
|
|
143445
|
+
"identifier",
|
|
143446
|
+
"device_name",
|
|
143447
|
+
"device_label",
|
|
143448
|
+
"domain",
|
|
143449
|
+
"annotation_id",
|
|
143450
|
+
"type",
|
|
143451
|
+
"importance",
|
|
143452
|
+
"status",
|
|
143453
|
+
"text",
|
|
143454
|
+
"created_at",
|
|
143455
|
+
"created_by_email",
|
|
143456
|
+
"due_date",
|
|
143457
|
+
"acknowledged"
|
|
143458
|
+
];
|
|
143459
|
+
function csvEscape(value) {
|
|
143460
|
+
if (value == null) return "";
|
|
143461
|
+
const s = String(value);
|
|
143462
|
+
if (s === "") return "";
|
|
143463
|
+
const needsQuoting = /[",\n\r;]/.test(s);
|
|
143464
|
+
if (!needsQuoting) return s;
|
|
143465
|
+
return '"' + s.replace(/"/g, '""') + '"';
|
|
143466
|
+
}
|
|
143467
|
+
function buildRow2(device, ann) {
|
|
143468
|
+
return {
|
|
143469
|
+
identifier: device.identifier ?? "",
|
|
143470
|
+
device_name: device.name ?? "",
|
|
143471
|
+
device_label: device.label ?? "",
|
|
143472
|
+
domain: device.domain ?? "",
|
|
143473
|
+
annotation_id: ann.id ?? "",
|
|
143474
|
+
type: ann.type ?? "",
|
|
143475
|
+
importance: String(ann.importance ?? ""),
|
|
143476
|
+
status: ann.status ?? "",
|
|
143477
|
+
text: ann.text ?? "",
|
|
143478
|
+
created_at: ann.createdAt ?? "",
|
|
143479
|
+
created_by_email: ann.createdBy?.email ?? "",
|
|
143480
|
+
due_date: ann.dueDate ?? "",
|
|
143481
|
+
acknowledged: ann.acknowledged ? "true" : "false"
|
|
143482
|
+
};
|
|
143483
|
+
}
|
|
143484
|
+
function buildAnnotationsCsv(devices, options = {}) {
|
|
143485
|
+
const includeArchived = options.includeArchived ?? false;
|
|
143486
|
+
const lines = [];
|
|
143487
|
+
lines.push(CSV_COLUMNS.join(","));
|
|
143488
|
+
for (const device of devices) {
|
|
143489
|
+
for (const ann of device.annotations) {
|
|
143490
|
+
if (!includeArchived && ann.status === "archived") continue;
|
|
143491
|
+
const row = buildRow2(device, ann);
|
|
143492
|
+
lines.push(CSV_COLUMNS.map((c) => csvEscape(row[c])).join(","));
|
|
143493
|
+
}
|
|
143494
|
+
}
|
|
143495
|
+
return "\uFEFF" + lines.join("\r\n") + "\r\n";
|
|
143496
|
+
}
|
|
143497
|
+
function buildExportFilename(customerName, ext, now = /* @__PURE__ */ new Date()) {
|
|
143498
|
+
const safeCustomer = (customerName || "customer").normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toLowerCase() || "customer";
|
|
143499
|
+
const yyyy = now.getFullYear();
|
|
143500
|
+
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
143501
|
+
const dd = String(now.getDate()).padStart(2, "0");
|
|
143502
|
+
const hh = String(now.getHours()).padStart(2, "0");
|
|
143503
|
+
const mi = String(now.getMinutes()).padStart(2, "0");
|
|
143504
|
+
return `anotacoes_${safeCustomer}_${yyyy}${mm}${dd}_${hh}${mi}.${ext}`;
|
|
143505
|
+
}
|
|
143506
|
+
function downloadTextFile(filename, content, mimeType) {
|
|
143507
|
+
if (typeof document === "undefined" || typeof Blob === "undefined") return;
|
|
143508
|
+
const blob = new Blob([content], { type: mimeType + ";charset=utf-8" });
|
|
143509
|
+
const url = URL.createObjectURL(blob);
|
|
143510
|
+
const a = document.createElement("a");
|
|
143511
|
+
a.href = url;
|
|
143512
|
+
a.download = filename;
|
|
143513
|
+
a.style.display = "none";
|
|
143514
|
+
document.body.appendChild(a);
|
|
143515
|
+
a.click();
|
|
143516
|
+
document.body.removeChild(a);
|
|
143517
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
143518
|
+
}
|
|
143519
|
+
function exportAnnotationsCsv(devices, options = {}) {
|
|
143520
|
+
const filename = buildExportFilename(options.customerName, "csv");
|
|
143521
|
+
const content = buildAnnotationsCsv(devices, { includeArchived: options.includeArchived });
|
|
143522
|
+
downloadTextFile(filename, content, "text/csv");
|
|
143523
|
+
return { filename, content };
|
|
143524
|
+
}
|
|
143525
|
+
|
|
143526
|
+
// src/components/header-annotations-panel/ExportPDF.ts
|
|
143527
|
+
var import_jspdf4 = require("jspdf");
|
|
143528
|
+
var PAGE_W = 210;
|
|
143529
|
+
var PAGE_H = 297;
|
|
143530
|
+
var MARGIN_X = 14;
|
|
143531
|
+
var MARGIN_TOP = 14;
|
|
143532
|
+
var MARGIN_BOTTOM = 18;
|
|
143533
|
+
var LINE_H = 5;
|
|
143534
|
+
function exportAnnotationsPdf(devices, options) {
|
|
143535
|
+
if (!options.levels || options.levels.length === 0) {
|
|
143536
|
+
throw new Error("exportAnnotationsPdf: at least one level required");
|
|
143537
|
+
}
|
|
143538
|
+
const includeArchived = options.includeArchived ?? false;
|
|
143539
|
+
const visibleDevices2 = _filterVisible(devices, includeArchived);
|
|
143540
|
+
const filename = buildExportFilename(options.customerName, "pdf", options.now);
|
|
143541
|
+
const doc = new import_jspdf4.jsPDF({ orientation: "portrait", unit: "mm", format: "a4" });
|
|
143542
|
+
const cursor = { y: MARGIN_TOP };
|
|
143543
|
+
_renderCover(doc, cursor, options, visibleDevices2);
|
|
143544
|
+
if (options.levels.includes("summary")) {
|
|
143545
|
+
_newPageIfNeeded(doc, cursor, 60);
|
|
143546
|
+
_renderSummary(doc, cursor, visibleDevices2);
|
|
143547
|
+
}
|
|
143548
|
+
if (options.levels.includes("consolidated")) {
|
|
143549
|
+
_newPage(doc, cursor);
|
|
143550
|
+
_renderConsolidated(doc, cursor, visibleDevices2);
|
|
143551
|
+
}
|
|
143552
|
+
if (options.levels.includes("detailed")) {
|
|
143553
|
+
_newPage(doc, cursor);
|
|
143554
|
+
_renderDetailed(doc, cursor, visibleDevices2);
|
|
143555
|
+
}
|
|
143556
|
+
_renderFooterAllPages(doc, options.customerName);
|
|
143557
|
+
doc.save(filename);
|
|
143558
|
+
return filename;
|
|
143559
|
+
}
|
|
143560
|
+
function _renderCover(doc, cursor, options, devices) {
|
|
143561
|
+
const total = _countAnnotations(devices);
|
|
143562
|
+
const customer = options.customerName || "customer";
|
|
143563
|
+
doc.setFontSize(20);
|
|
143564
|
+
doc.setTextColor(76, 58, 172);
|
|
143565
|
+
doc.text(options.title || "Anota\xE7\xF5es Operacionais", MARGIN_X, cursor.y + 8);
|
|
143566
|
+
cursor.y += 16;
|
|
143567
|
+
doc.setFontSize(11);
|
|
143568
|
+
doc.setTextColor(80);
|
|
143569
|
+
doc.text(`Cliente: ${customer}`, MARGIN_X, cursor.y);
|
|
143570
|
+
cursor.y += LINE_H;
|
|
143571
|
+
doc.text(
|
|
143572
|
+
`Gerado em: ${(options.now ?? /* @__PURE__ */ new Date()).toLocaleString("pt-BR")}`,
|
|
143573
|
+
MARGIN_X,
|
|
143574
|
+
cursor.y
|
|
143575
|
+
);
|
|
143576
|
+
cursor.y += LINE_H;
|
|
143577
|
+
doc.text(`Total de anota\xE7\xF5es: ${total} (em ${devices.length} devices)`, MARGIN_X, cursor.y);
|
|
143578
|
+
cursor.y += LINE_H + 4;
|
|
143579
|
+
doc.setDrawColor(108, 92, 231);
|
|
143580
|
+
doc.setLineWidth(0.4);
|
|
143581
|
+
doc.line(MARGIN_X, cursor.y, PAGE_W - MARGIN_X, cursor.y);
|
|
143582
|
+
cursor.y += 6;
|
|
143583
|
+
}
|
|
143584
|
+
function _renderSummary(doc, cursor, devices) {
|
|
143585
|
+
_sectionHeader(doc, cursor, "Sum\xE1rio");
|
|
143586
|
+
const byType = _countByType(devices);
|
|
143587
|
+
const byImportance = _countByImportance(devices);
|
|
143588
|
+
const byDomain = _countByDomain(devices);
|
|
143589
|
+
doc.setFontSize(11);
|
|
143590
|
+
doc.setTextColor(40);
|
|
143591
|
+
const lines = [
|
|
143592
|
+
`Por tipo:`,
|
|
143593
|
+
` \u2022 Pend\xEAncia: ${byType.pending}`,
|
|
143594
|
+
` \u2022 Manuten\xE7\xE3o: ${byType.maintenance}`,
|
|
143595
|
+
` \u2022 Observa\xE7\xE3o: ${byType.observation}`,
|
|
143596
|
+
` \u2022 Atividade: ${byType.activity}`,
|
|
143597
|
+
``,
|
|
143598
|
+
`Por import\xE2ncia:`,
|
|
143599
|
+
` \u2022 Cr\xEDtica (5): ${byImportance[5]}`,
|
|
143600
|
+
` \u2022 Alta (4): ${byImportance[4]}`,
|
|
143601
|
+
` \u2022 M\xE9dia (3): ${byImportance[3]}`,
|
|
143602
|
+
` \u2022 Baixa (2): ${byImportance[2]}`,
|
|
143603
|
+
` \u2022 Muito baixa (1): ${byImportance[1]}`,
|
|
143604
|
+
``,
|
|
143605
|
+
`Por dom\xEDnio:`,
|
|
143606
|
+
` \u2022 Energia: ${byDomain.energy}`,
|
|
143607
|
+
` \u2022 \xC1gua: ${byDomain.water}`,
|
|
143608
|
+
` \u2022 Temperatura: ${byDomain.temperature}`,
|
|
143609
|
+
` \u2022 Indeterminado: ${byDomain.unknown}`
|
|
143610
|
+
];
|
|
143611
|
+
for (const ln of lines) {
|
|
143612
|
+
_newPageIfNeeded(doc, cursor, LINE_H);
|
|
143613
|
+
doc.text(ln, MARGIN_X, cursor.y);
|
|
143614
|
+
cursor.y += LINE_H;
|
|
143615
|
+
}
|
|
143616
|
+
}
|
|
143617
|
+
function _renderConsolidated(doc, cursor, devices) {
|
|
143618
|
+
_sectionHeader(doc, cursor, "Consolidado por device");
|
|
143619
|
+
const sorted = devices.slice().filter((d) => d.annotations.length > 0).sort((a, b) => b.annotations.length - a.annotations.length);
|
|
143620
|
+
doc.setFontSize(10);
|
|
143621
|
+
doc.setTextColor(40);
|
|
143622
|
+
for (const d of sorted) {
|
|
143623
|
+
_newPageIfNeeded(doc, cursor, LINE_H * 2);
|
|
143624
|
+
doc.setFont(void 0, "bold");
|
|
143625
|
+
const ident = d.identifier ? `[${d.identifier}] ` : "";
|
|
143626
|
+
doc.text(`${ident}${d.label || d.name} (${d.annotations.length})`, MARGIN_X, cursor.y);
|
|
143627
|
+
cursor.y += LINE_H;
|
|
143628
|
+
doc.setFont(void 0, "normal");
|
|
143629
|
+
const last = d.annotations.reduce(
|
|
143630
|
+
(acc, a) => !acc || a.createdAt > acc.createdAt ? a : acc,
|
|
143631
|
+
null
|
|
143632
|
+
);
|
|
143633
|
+
if (last) {
|
|
143634
|
+
const txt = ` \xDAltima: "${_truncate(last.text, 90)}" \u2014 ${last.createdBy?.name ?? ""} \u2014 ${_formatDate(last.createdAt)}`;
|
|
143635
|
+
const wrapped = doc.splitTextToSize(txt, PAGE_W - MARGIN_X * 2);
|
|
143636
|
+
for (const line of wrapped) {
|
|
143637
|
+
_newPageIfNeeded(doc, cursor, LINE_H);
|
|
143638
|
+
doc.text(line, MARGIN_X, cursor.y);
|
|
143639
|
+
cursor.y += LINE_H;
|
|
143640
|
+
}
|
|
143641
|
+
}
|
|
143642
|
+
cursor.y += 1;
|
|
143643
|
+
}
|
|
143644
|
+
}
|
|
143645
|
+
function _renderDetailed(doc, cursor, devices) {
|
|
143646
|
+
_sectionHeader(doc, cursor, "Detalhado por anota\xE7\xE3o");
|
|
143647
|
+
doc.setFontSize(10);
|
|
143648
|
+
doc.setTextColor(40);
|
|
143649
|
+
for (const d of devices) {
|
|
143650
|
+
if (d.annotations.length === 0) continue;
|
|
143651
|
+
_newPageIfNeeded(doc, cursor, LINE_H * 2);
|
|
143652
|
+
doc.setFont(void 0, "bold");
|
|
143653
|
+
const ident = d.identifier ? `[${d.identifier}] ` : "";
|
|
143654
|
+
doc.text(`${ident}${d.label || d.name}`, MARGIN_X, cursor.y);
|
|
143655
|
+
cursor.y += LINE_H;
|
|
143656
|
+
doc.setFont(void 0, "normal");
|
|
143657
|
+
for (const a of d.annotations) {
|
|
143658
|
+
const header = ` ${a.type.toUpperCase()} \xB7 Import\xE2ncia ${a.importance} \xB7 ${a.status}`;
|
|
143659
|
+
_newPageIfNeeded(doc, cursor, LINE_H * 3);
|
|
143660
|
+
doc.text(header, MARGIN_X, cursor.y);
|
|
143661
|
+
cursor.y += LINE_H;
|
|
143662
|
+
const wrapped = doc.splitTextToSize(` "${a.text}"`, PAGE_W - MARGIN_X * 2);
|
|
143663
|
+
for (const line of wrapped) {
|
|
143664
|
+
_newPageIfNeeded(doc, cursor, LINE_H);
|
|
143665
|
+
doc.text(line, MARGIN_X, cursor.y);
|
|
143666
|
+
cursor.y += LINE_H;
|
|
143667
|
+
}
|
|
143668
|
+
const meta = ` por ${a.createdBy?.name ?? "\u2014"} (${a.createdBy?.email ?? "\u2014"}) em ${_formatDate(a.createdAt)}${a.dueDate ? " \xB7 vence " + _formatDate(a.dueDate) : ""}`;
|
|
143669
|
+
const metaWrapped = doc.splitTextToSize(meta, PAGE_W - MARGIN_X * 2);
|
|
143670
|
+
for (const line of metaWrapped) {
|
|
143671
|
+
_newPageIfNeeded(doc, cursor, LINE_H);
|
|
143672
|
+
doc.text(line, MARGIN_X, cursor.y);
|
|
143673
|
+
cursor.y += LINE_H;
|
|
143674
|
+
}
|
|
143675
|
+
cursor.y += 2;
|
|
143676
|
+
}
|
|
143677
|
+
cursor.y += 2;
|
|
143678
|
+
}
|
|
143679
|
+
}
|
|
143680
|
+
function _renderFooterAllPages(doc, customerName) {
|
|
143681
|
+
const pageCount = doc.internal.getNumberOfPages();
|
|
143682
|
+
for (let p = 1; p <= pageCount; p++) {
|
|
143683
|
+
doc.setPage(p);
|
|
143684
|
+
doc.setFontSize(8);
|
|
143685
|
+
doc.setTextColor(120);
|
|
143686
|
+
doc.text(
|
|
143687
|
+
`MyIO \xB7 ${customerName || ""} \xB7 Documento confidencial \u2014 cont\xE9m dados operacionais.`,
|
|
143688
|
+
MARGIN_X,
|
|
143689
|
+
PAGE_H - 8
|
|
143690
|
+
);
|
|
143691
|
+
doc.text(`P\xE1gina ${p} / ${pageCount}`, PAGE_W - MARGIN_X - 25, PAGE_H - 8);
|
|
143692
|
+
}
|
|
143693
|
+
}
|
|
143694
|
+
function _sectionHeader(doc, cursor, title) {
|
|
143695
|
+
doc.setFontSize(14);
|
|
143696
|
+
doc.setTextColor(76, 58, 172);
|
|
143697
|
+
doc.text(title, MARGIN_X, cursor.y);
|
|
143698
|
+
cursor.y += 7;
|
|
143699
|
+
doc.setDrawColor(200);
|
|
143700
|
+
doc.setLineWidth(0.2);
|
|
143701
|
+
doc.line(MARGIN_X, cursor.y - 3, PAGE_W - MARGIN_X, cursor.y - 3);
|
|
143702
|
+
}
|
|
143703
|
+
function _newPage(doc, cursor) {
|
|
143704
|
+
doc.addPage();
|
|
143705
|
+
cursor.y = MARGIN_TOP;
|
|
143706
|
+
}
|
|
143707
|
+
function _newPageIfNeeded(doc, cursor, required) {
|
|
143708
|
+
if (cursor.y + required > PAGE_H - MARGIN_BOTTOM) {
|
|
143709
|
+
_newPage(doc, cursor);
|
|
143710
|
+
}
|
|
143711
|
+
}
|
|
143712
|
+
function _filterVisible(devices, includeArchived) {
|
|
143713
|
+
if (includeArchived) return devices;
|
|
143714
|
+
return devices.map((d) => ({
|
|
143715
|
+
...d,
|
|
143716
|
+
annotations: d.annotations.filter((a) => a.status !== "archived")
|
|
143717
|
+
})).filter((d) => d.annotations.length > 0);
|
|
143718
|
+
}
|
|
143719
|
+
function _countAnnotations(devices) {
|
|
143720
|
+
return devices.reduce((acc, d) => acc + d.annotations.length, 0);
|
|
143721
|
+
}
|
|
143722
|
+
function _countByType(devices) {
|
|
143723
|
+
const acc = { observation: 0, pending: 0, maintenance: 0, activity: 0 };
|
|
143724
|
+
for (const d of devices) {
|
|
143725
|
+
for (const a of d.annotations) {
|
|
143726
|
+
if (acc[a.type] !== void 0) acc[a.type]++;
|
|
143727
|
+
}
|
|
143728
|
+
}
|
|
143729
|
+
return acc;
|
|
143730
|
+
}
|
|
143731
|
+
function _countByImportance(devices) {
|
|
143732
|
+
const acc = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
|
|
143733
|
+
for (const d of devices) {
|
|
143734
|
+
for (const a of d.annotations) {
|
|
143735
|
+
const lv = a.importance;
|
|
143736
|
+
if (acc[lv] !== void 0) acc[lv]++;
|
|
143737
|
+
}
|
|
143738
|
+
}
|
|
143739
|
+
return acc;
|
|
143740
|
+
}
|
|
143741
|
+
function _countByDomain(devices) {
|
|
143742
|
+
const acc = { energy: 0, water: 0, temperature: 0, unknown: 0 };
|
|
143743
|
+
for (const d of devices) {
|
|
143744
|
+
acc[d.domain] = (acc[d.domain] ?? 0) + d.annotations.length;
|
|
143745
|
+
}
|
|
143746
|
+
return acc;
|
|
143747
|
+
}
|
|
143748
|
+
function _truncate(s, max) {
|
|
143749
|
+
const t = String(s ?? "");
|
|
143750
|
+
return t.length > max ? t.slice(0, max - 1) + "\u2026" : t;
|
|
143751
|
+
}
|
|
143752
|
+
function _formatDate(iso) {
|
|
143753
|
+
if (!iso) return "\u2014";
|
|
143754
|
+
const d = new Date(iso);
|
|
143755
|
+
if (isNaN(d.getTime())) return iso;
|
|
143756
|
+
return d.toLocaleString("pt-BR");
|
|
143757
|
+
}
|
|
143758
|
+
|
|
143759
|
+
// src/components/header-annotations-panel/HeaderAnnotationsPanel.ts
|
|
143760
|
+
var TABS = [
|
|
143761
|
+
{ id: "identifier", label: "Por Identificador" },
|
|
143762
|
+
{ id: "device", label: "Por Device" },
|
|
143763
|
+
{ id: "domain", label: "Por Dom\xEDnio" }
|
|
143764
|
+
];
|
|
143765
|
+
var TAB_STORAGE_KEY = "myio.annotations.activeTab";
|
|
143766
|
+
var SORT_STORAGE_KEY = "myio.annotations.sortBy";
|
|
143767
|
+
var SEARCH_DEBOUNCE_MS = 250;
|
|
143768
|
+
var PANEL_DOM_ID = "myio-annotations-panel";
|
|
143769
|
+
var HeaderAnnotationsPanel = class {
|
|
143770
|
+
root = null;
|
|
143771
|
+
anchorButton = null;
|
|
143772
|
+
activeTab = "identifier";
|
|
143773
|
+
sortBy = DEFAULT_SORT;
|
|
143774
|
+
filter = createDefaultFilter();
|
|
143775
|
+
isOpen = false;
|
|
143776
|
+
// RFC-0203 M6 — Tooltip behaviors state
|
|
143777
|
+
isPinned = false;
|
|
143778
|
+
isMaximized = false;
|
|
143779
|
+
isDragging = false;
|
|
143780
|
+
dragOffset = { x: 0, y: 0 };
|
|
143781
|
+
vlist = null;
|
|
143782
|
+
opts;
|
|
143783
|
+
// Bound listeners stored for removal
|
|
143784
|
+
_onEscKey;
|
|
143785
|
+
_onClickOutside;
|
|
143786
|
+
_onAnnotationsRefreshed;
|
|
143787
|
+
_onDragMove;
|
|
143788
|
+
_onDragEnd;
|
|
143789
|
+
_onFocusTrap;
|
|
143790
|
+
// Debounce timer for search input
|
|
143791
|
+
_searchDebounceTimer = null;
|
|
143792
|
+
constructor(options = {}) {
|
|
143793
|
+
this.opts = {
|
|
143794
|
+
getOrchestrator: options.getOrchestrator ?? (() => (typeof window !== "undefined" ? window.AnnotationServiceOrchestrator : null) ?? null),
|
|
143795
|
+
logger: options.logger ?? console
|
|
143796
|
+
};
|
|
143797
|
+
this.activeTab = this._loadActiveTab();
|
|
143798
|
+
this.sortBy = this._loadSortBy();
|
|
143799
|
+
this._onEscKey = (e) => {
|
|
143800
|
+
if (e.key === "Escape" && this.isOpen) this.hide();
|
|
143801
|
+
};
|
|
143802
|
+
this._onClickOutside = (e) => {
|
|
143803
|
+
if (!this.isOpen || !this.root) return;
|
|
143804
|
+
if (this.isPinned) return;
|
|
143805
|
+
const target = e.target;
|
|
143806
|
+
if (!target) return;
|
|
143807
|
+
if (this.root.contains(target)) return;
|
|
143808
|
+
if (this.anchorButton && this.anchorButton.contains(target)) return;
|
|
143809
|
+
this.hide();
|
|
143810
|
+
};
|
|
143811
|
+
this._onAnnotationsRefreshed = () => {
|
|
143812
|
+
if (this.isOpen) this._render();
|
|
143813
|
+
};
|
|
143814
|
+
this._onDragMove = (e) => {
|
|
143815
|
+
if (!this.isDragging || !this.root) return;
|
|
143816
|
+
e.preventDefault();
|
|
143817
|
+
const left = e.clientX - this.dragOffset.x;
|
|
143818
|
+
const top = e.clientY - this.dragOffset.y;
|
|
143819
|
+
const maxLeft = window.innerWidth - 80;
|
|
143820
|
+
const maxTop = window.innerHeight - 40;
|
|
143821
|
+
this.root.style.left = `${Math.max(0, Math.min(left, maxLeft))}px`;
|
|
143822
|
+
this.root.style.top = `${Math.max(0, Math.min(top, maxTop))}px`;
|
|
143823
|
+
};
|
|
143824
|
+
this._onDragEnd = () => {
|
|
143825
|
+
if (!this.isDragging) return;
|
|
143826
|
+
this.isDragging = false;
|
|
143827
|
+
if (this.root) this.root.classList.remove("is-dragging");
|
|
143828
|
+
window.removeEventListener("mousemove", this._onDragMove);
|
|
143829
|
+
window.removeEventListener("mouseup", this._onDragEnd);
|
|
143830
|
+
};
|
|
143831
|
+
this._onFocusTrap = (e) => {
|
|
143832
|
+
if (!this.isMaximized || !this.root || e.key !== "Tab") return;
|
|
143833
|
+
const focusables = Array.from(
|
|
143834
|
+
this.root.querySelectorAll(
|
|
143835
|
+
'button:not([disabled]), [tabindex="0"], input:not([disabled]), select:not([disabled])'
|
|
143836
|
+
)
|
|
143837
|
+
).filter((el2) => el2.offsetParent !== null);
|
|
143838
|
+
if (focusables.length === 0) return;
|
|
143839
|
+
const first = focusables[0];
|
|
143840
|
+
const last = focusables[focusables.length - 1];
|
|
143841
|
+
const active = document.activeElement;
|
|
143842
|
+
if (e.shiftKey && active === first) {
|
|
143843
|
+
e.preventDefault();
|
|
143844
|
+
last.focus();
|
|
143845
|
+
} else if (!e.shiftKey && active === last) {
|
|
143846
|
+
e.preventDefault();
|
|
143847
|
+
first.focus();
|
|
143848
|
+
}
|
|
143849
|
+
};
|
|
143850
|
+
}
|
|
143851
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────
|
|
143852
|
+
/** Render + position + show. Idempotent. */
|
|
143853
|
+
show(anchorButton) {
|
|
143854
|
+
injectStylesOnce();
|
|
143855
|
+
this.anchorButton = anchorButton;
|
|
143856
|
+
if (!this.root) this.root = this._createRoot();
|
|
143857
|
+
this._render();
|
|
143858
|
+
this._position();
|
|
143859
|
+
this.root.style.display = "";
|
|
143860
|
+
this.root.setAttribute("aria-hidden", "false");
|
|
143861
|
+
this.isOpen = true;
|
|
143862
|
+
this._bindWindowListeners();
|
|
143863
|
+
const firstTab = this.root.querySelector('.myio-annotations-tab[aria-selected="true"]');
|
|
143864
|
+
if (firstTab) firstTab.focus();
|
|
143865
|
+
}
|
|
143866
|
+
/** Hide the panel without destroying it. */
|
|
143867
|
+
hide() {
|
|
143868
|
+
if (!this.root) return;
|
|
143869
|
+
this.root.style.display = "none";
|
|
143870
|
+
this.root.setAttribute("aria-hidden", "true");
|
|
143871
|
+
this.isOpen = false;
|
|
143872
|
+
this._unbindWindowListeners();
|
|
143873
|
+
if (this.anchorButton) {
|
|
143874
|
+
try {
|
|
143875
|
+
this.anchorButton.focus();
|
|
143876
|
+
} catch {
|
|
143877
|
+
}
|
|
143878
|
+
}
|
|
143879
|
+
}
|
|
143880
|
+
/** Toggle convenience. */
|
|
143881
|
+
toggle(anchorButton) {
|
|
143882
|
+
if (this.isOpen) this.hide();
|
|
143883
|
+
else this.show(anchorButton);
|
|
143884
|
+
}
|
|
143885
|
+
/** Remove the panel DOM + listeners. After this, `show()` recreates. */
|
|
143886
|
+
destroy() {
|
|
143887
|
+
this._unbindWindowListeners();
|
|
143888
|
+
if (this.vlist) {
|
|
143889
|
+
this.vlist.destroy();
|
|
143890
|
+
this.vlist = null;
|
|
143891
|
+
}
|
|
143892
|
+
if (this.root && this.root.parentNode) this.root.parentNode.removeChild(this.root);
|
|
143893
|
+
this.root = null;
|
|
143894
|
+
this.anchorButton = null;
|
|
143895
|
+
this.isOpen = false;
|
|
143896
|
+
this.isPinned = false;
|
|
143897
|
+
this.isMaximized = false;
|
|
143898
|
+
this.isDragging = false;
|
|
143899
|
+
}
|
|
143900
|
+
/** Test/inspection helper. */
|
|
143901
|
+
getActiveTab() {
|
|
143902
|
+
return this.activeTab;
|
|
143903
|
+
}
|
|
143904
|
+
/** Test helper — programmatic tab switch (also persists to sessionStorage). */
|
|
143905
|
+
setActiveTab(tab) {
|
|
143906
|
+
if (!TABS.some((t) => t.id === tab)) return;
|
|
143907
|
+
this.activeTab = tab;
|
|
143908
|
+
this._saveActiveTab(tab);
|
|
143909
|
+
if (this.isOpen) this._render();
|
|
143910
|
+
}
|
|
143911
|
+
/** Test/inspection helper. */
|
|
143912
|
+
getSortBy() {
|
|
143913
|
+
return this.sortBy;
|
|
143914
|
+
}
|
|
143915
|
+
/** Test helper — programmatic sort change (persists). */
|
|
143916
|
+
setSortBy(sort) {
|
|
143917
|
+
if (!SORT_OPTIONS.some((o) => o.key === sort)) return;
|
|
143918
|
+
this.sortBy = sort;
|
|
143919
|
+
this._saveSortBy(sort);
|
|
143920
|
+
if (this.isOpen) this._render();
|
|
143921
|
+
}
|
|
143922
|
+
/** Test/inspection helper. */
|
|
143923
|
+
getFilter() {
|
|
143924
|
+
return this.filter;
|
|
143925
|
+
}
|
|
143926
|
+
/** Test helper — programmatic filter merge. */
|
|
143927
|
+
setFilter(patch) {
|
|
143928
|
+
this.filter = { ...this.filter, ...patch };
|
|
143929
|
+
if (this.isOpen) this._render();
|
|
143930
|
+
}
|
|
143931
|
+
// ── Internals ──────────────────────────────────────────────────────────
|
|
143932
|
+
_loadActiveTab() {
|
|
143933
|
+
try {
|
|
143934
|
+
if (typeof sessionStorage === "undefined") return "identifier";
|
|
143935
|
+
const stored = sessionStorage.getItem(TAB_STORAGE_KEY);
|
|
143936
|
+
if (stored && TABS.some((t) => t.id === stored)) return stored;
|
|
143937
|
+
} catch {
|
|
143938
|
+
}
|
|
143939
|
+
return "identifier";
|
|
143940
|
+
}
|
|
143941
|
+
_saveActiveTab(tab) {
|
|
143942
|
+
try {
|
|
143943
|
+
if (typeof sessionStorage !== "undefined") {
|
|
143944
|
+
sessionStorage.setItem(TAB_STORAGE_KEY, tab);
|
|
143945
|
+
}
|
|
143946
|
+
} catch {
|
|
143947
|
+
}
|
|
143948
|
+
}
|
|
143949
|
+
_loadSortBy() {
|
|
143950
|
+
try {
|
|
143951
|
+
if (typeof sessionStorage === "undefined") return DEFAULT_SORT;
|
|
143952
|
+
const stored = sessionStorage.getItem(SORT_STORAGE_KEY);
|
|
143953
|
+
if (stored && SORT_OPTIONS.some((o) => o.key === stored)) return stored;
|
|
143954
|
+
} catch {
|
|
143955
|
+
}
|
|
143956
|
+
return DEFAULT_SORT;
|
|
143957
|
+
}
|
|
143958
|
+
_saveSortBy(sort) {
|
|
143959
|
+
try {
|
|
143960
|
+
if (typeof sessionStorage !== "undefined") {
|
|
143961
|
+
sessionStorage.setItem(SORT_STORAGE_KEY, sort);
|
|
143962
|
+
}
|
|
143963
|
+
} catch {
|
|
143964
|
+
}
|
|
143965
|
+
}
|
|
143966
|
+
_createRoot() {
|
|
143967
|
+
const el2 = document.createElement("div");
|
|
143968
|
+
el2.id = PANEL_DOM_ID;
|
|
143969
|
+
el2.className = "myio-annotations-panel";
|
|
143970
|
+
el2.setAttribute("role", "dialog");
|
|
143971
|
+
el2.setAttribute("aria-modal", "false");
|
|
143972
|
+
el2.setAttribute("aria-labelledby", PANEL_DOM_ID + "-title");
|
|
143973
|
+
el2.setAttribute("aria-hidden", "true");
|
|
143974
|
+
el2.style.display = "none";
|
|
143975
|
+
document.body.appendChild(el2);
|
|
143976
|
+
return el2;
|
|
143977
|
+
}
|
|
143978
|
+
_position() {
|
|
143979
|
+
if (!this.root || !this.anchorButton) return;
|
|
143980
|
+
const rect = this.anchorButton.getBoundingClientRect();
|
|
143981
|
+
const panelWidth = Math.min(720, window.innerWidth * 0.9);
|
|
143982
|
+
let left = rect.left;
|
|
143983
|
+
if (left + panelWidth > window.innerWidth - 8) {
|
|
143984
|
+
left = Math.max(8, window.innerWidth - panelWidth - 8);
|
|
143985
|
+
}
|
|
143986
|
+
const top = rect.bottom + 8;
|
|
143987
|
+
this.root.style.left = `${left}px`;
|
|
143988
|
+
this.root.style.top = `${top}px`;
|
|
143989
|
+
this.root.style.width = `${panelWidth}px`;
|
|
143990
|
+
}
|
|
143991
|
+
_bindWindowListeners() {
|
|
143992
|
+
if (typeof window === "undefined") return;
|
|
143993
|
+
window.addEventListener("keydown", this._onEscKey);
|
|
143994
|
+
setTimeout(() => {
|
|
143995
|
+
window.addEventListener("mousedown", this._onClickOutside, true);
|
|
143996
|
+
}, 0);
|
|
143997
|
+
window.addEventListener("myio:annotations-refreshed", this._onAnnotationsRefreshed);
|
|
143998
|
+
window.addEventListener("keydown", this._onFocusTrap);
|
|
143999
|
+
}
|
|
144000
|
+
_unbindWindowListeners() {
|
|
144001
|
+
if (typeof window === "undefined") return;
|
|
144002
|
+
window.removeEventListener("keydown", this._onEscKey);
|
|
144003
|
+
window.removeEventListener("mousedown", this._onClickOutside, true);
|
|
144004
|
+
window.removeEventListener("myio:annotations-refreshed", this._onAnnotationsRefreshed);
|
|
144005
|
+
window.removeEventListener("keydown", this._onFocusTrap);
|
|
144006
|
+
window.removeEventListener("mousemove", this._onDragMove);
|
|
144007
|
+
window.removeEventListener("mouseup", this._onDragEnd);
|
|
144008
|
+
}
|
|
144009
|
+
_render() {
|
|
144010
|
+
if (!this.root) return;
|
|
144011
|
+
if (this.vlist) {
|
|
144012
|
+
this.vlist.destroy();
|
|
144013
|
+
this.vlist = null;
|
|
144014
|
+
}
|
|
144015
|
+
const orch = this.opts.getOrchestrator();
|
|
144016
|
+
this.root.innerHTML = this._renderHTML(orch);
|
|
144017
|
+
this._bindInteractiveElements();
|
|
144018
|
+
this._maybeActivateVirtualScroll(orch);
|
|
144019
|
+
}
|
|
144020
|
+
/**
|
|
144021
|
+
* AC-28 — Replace the body's flat innerHTML with a VirtualList when total
|
|
144022
|
+
* item count exceeds the threshold (100). Below the threshold, the static
|
|
144023
|
+
* render path stays as-is for simplicity.
|
|
144024
|
+
*/
|
|
144025
|
+
_maybeActivateVirtualScroll(orch) {
|
|
144026
|
+
if (!this.root) return;
|
|
144027
|
+
const body = this.root.querySelector("#myio-anno-body");
|
|
144028
|
+
if (!body) return;
|
|
144029
|
+
if (!orch) return;
|
|
144030
|
+
const rawGroups = orch.getGroups(this.activeTab, this.filter);
|
|
144031
|
+
const groups = sortGroups(rawGroups, this.sortBy);
|
|
144032
|
+
const totalItems = countAnnotationsInGroups(groups);
|
|
144033
|
+
if (!shouldVirtualize(totalItems)) return;
|
|
144034
|
+
const term = this.filter.searchTerm || "";
|
|
144035
|
+
const rows = [];
|
|
144036
|
+
for (const g of groups) {
|
|
144037
|
+
const isNoIdentifier = this.activeTab === "identifier" && g.key === "Sem Identificador";
|
|
144038
|
+
const groupClass = isNoIdentifier ? "myio-annotations-group myio-annotations-group--no-id" : "myio-annotations-group";
|
|
144039
|
+
rows.push({
|
|
144040
|
+
key: `g:${g.key}`,
|
|
144041
|
+
height: 40,
|
|
144042
|
+
// group header
|
|
144043
|
+
render: () => `
|
|
144044
|
+
<header class="myio-annotations-group-header ${groupClass}">
|
|
144045
|
+
${g.icon ? `<span class="myio-annotations-group-icon" aria-hidden="true">${escapeHtml6(g.icon)}</span>` : ""}
|
|
144046
|
+
<span class="myio-annotations-group-label">${escapeHtml6(g.label)}</span>
|
|
144047
|
+
<span class="myio-annotations-group-count">${g.totalAnnotations}</span>
|
|
144048
|
+
</header>`
|
|
144049
|
+
});
|
|
144050
|
+
for (const d of g.devices) {
|
|
144051
|
+
for (const a of d.annotations) {
|
|
144052
|
+
rows.push({
|
|
144053
|
+
key: `i:${d.deviceId}:${a.id}`,
|
|
144054
|
+
height: 78,
|
|
144055
|
+
// item card (text + meta + badges)
|
|
144056
|
+
render: () => renderAnnotationItemCard(d, a, term)
|
|
144057
|
+
});
|
|
144058
|
+
}
|
|
144059
|
+
}
|
|
144060
|
+
}
|
|
144061
|
+
this.vlist = new VirtualList({ container: body, rows });
|
|
144062
|
+
body.addEventListener("click", this._onBodyClickDelegated);
|
|
144063
|
+
}
|
|
144064
|
+
/** Delegated click handler for items rendered by the VirtualList. */
|
|
144065
|
+
_onBodyClickDelegated = (e) => {
|
|
144066
|
+
const target = e.target;
|
|
144067
|
+
if (!target) return;
|
|
144068
|
+
const item = target.closest(".myio-annotations-item");
|
|
144069
|
+
if (item) this._handleItemClick(item);
|
|
144070
|
+
};
|
|
144071
|
+
_renderHTML(orch) {
|
|
144072
|
+
const totalAllUnfiltered = orch?.getTotalCount?.() ?? 0;
|
|
144073
|
+
const pending = orch?.getPendingCount?.() ?? 0;
|
|
144074
|
+
const overdue = orch?.getOverdueCount?.() ?? 0;
|
|
144075
|
+
const tabsHtml = TABS.map(
|
|
144076
|
+
(t) => `<button
|
|
144077
|
+
class="myio-annotations-tab"
|
|
144078
|
+
type="button"
|
|
144079
|
+
role="tab"
|
|
144080
|
+
id="myio-anno-tab-${t.id}"
|
|
144081
|
+
data-tab="${t.id}"
|
|
144082
|
+
aria-selected="${t.id === this.activeTab}"
|
|
144083
|
+
aria-controls="myio-anno-body"
|
|
144084
|
+
tabindex="${t.id === this.activeTab ? 0 : -1}"
|
|
144085
|
+
>${t.label}</button>`
|
|
144086
|
+
).join("");
|
|
144087
|
+
const rawGroups = orch ? orch.getGroups(this.activeTab, this.filter) : [];
|
|
144088
|
+
const groups = sortGroups(rawGroups, this.sortBy);
|
|
144089
|
+
const filteredCount = countAnnotationsInGroups(groups);
|
|
144090
|
+
const bodyHtml = this._renderBody(groups);
|
|
144091
|
+
return `
|
|
144092
|
+
<div class="myio-annotations-panel-header" data-region="header" data-drag-handle>
|
|
144093
|
+
<h2 class="myio-annotations-panel-title" id="${PANEL_DOM_ID}-title">
|
|
144094
|
+
<span class="myio-annotations-icon" aria-hidden="true">\u{1F4CB}</span>Anota\xE7\xF5es
|
|
144095
|
+
</h2>
|
|
144096
|
+
<span class="myio-annotations-panel-meta">${totalAllUnfiltered} ativas \xB7 ${pending} pendentes \xB7 ${overdue} vencidas</span>
|
|
144097
|
+
<div class="myio-annotations-panel-actions">
|
|
144098
|
+
<button
|
|
144099
|
+
class="myio-annotations-panel-action ${this.isMaximized ? "is-active" : ""}"
|
|
144100
|
+
type="button"
|
|
144101
|
+
data-action="maximize"
|
|
144102
|
+
title="${this.isMaximized ? "Restaurar" : "Maximizar"}"
|
|
144103
|
+
aria-label="${this.isMaximized ? "Restaurar tamanho" : "Maximizar painel"}"
|
|
144104
|
+
aria-pressed="${this.isMaximized}"
|
|
144105
|
+
>${this.isMaximized ? "\u{1F5D7}" : "\u2922"}</button>
|
|
144106
|
+
<button
|
|
144107
|
+
class="myio-annotations-panel-action ${this.isPinned ? "is-active" : ""}"
|
|
144108
|
+
type="button"
|
|
144109
|
+
data-action="pin"
|
|
144110
|
+
title="${this.isPinned ? "Desafixar" : "Afixar painel"}"
|
|
144111
|
+
aria-label="${this.isPinned ? "Desafixar painel" : "Afixar painel"}"
|
|
144112
|
+
aria-pressed="${this.isPinned}"
|
|
144113
|
+
>\u{1F4CC}</button>
|
|
144114
|
+
<button class="myio-annotations-panel-action" type="button" data-action="close" title="Fechar" aria-label="Fechar painel">\u2715</button>
|
|
144115
|
+
</div>
|
|
144116
|
+
</div>
|
|
144117
|
+
<div class="myio-annotations-tabs" role="tablist" aria-label="Modo de agrupamento">${tabsHtml}</div>
|
|
144118
|
+
${this._renderToolbar(filteredCount, totalAllUnfiltered)}
|
|
144119
|
+
<div class="myio-annotations-body" id="myio-anno-body" role="tabpanel" aria-labelledby="myio-anno-tab-${this.activeTab}">${bodyHtml}</div>
|
|
144120
|
+
<div class="myio-annotations-panel-footer">
|
|
144121
|
+
<button class="myio-annotations-panel-footer-action" type="button" data-action="export">\u{1F4E5} Exportar\u2026</button>
|
|
144122
|
+
<span class="myio-annotations-panel-footer-meta">RFC-0203 \xB7 ${groups.length} grupos \xB7 ${filteredCount} anota\xE7\xF5es</span>
|
|
144123
|
+
<button class="myio-annotations-panel-footer-action" type="button" data-action="refresh">Atualizar \u21BB</button>
|
|
144124
|
+
</div>
|
|
144125
|
+
`;
|
|
144126
|
+
}
|
|
144127
|
+
_renderToolbar(filteredCount, totalCount) {
|
|
144128
|
+
const sortOptions = SORT_OPTIONS.map(
|
|
144129
|
+
(o) => `<option value="${o.key}" ${o.key === this.sortBy ? "selected" : ""}>${escapeHtml6(o.label)}</option>`
|
|
144130
|
+
).join("");
|
|
144131
|
+
const term = escapeHtml6(this.filter.searchTerm || "");
|
|
144132
|
+
const filtersActiveCount = this._activeFilterCount();
|
|
144133
|
+
const filterIndicator = filtersActiveCount > 0 ? `<span class="myio-annotations-group-count">${filtersActiveCount}</span>` : "";
|
|
144134
|
+
const showingHtml = filteredCount === totalCount ? `${totalCount} anota\xE7\xF5es` : `${filteredCount} de ${totalCount} anota\xE7\xF5es`;
|
|
144135
|
+
return `
|
|
144136
|
+
<div class="myio-annotations-toolbar" data-region="toolbar">
|
|
144137
|
+
<div class="myio-annotations-toolbar-row">
|
|
144138
|
+
<label class="myio-annotations-toolbar-search">
|
|
144139
|
+
<span class="myio-annotations-search-icon" aria-hidden="true">\u{1F50D}</span>
|
|
144140
|
+
<input
|
|
144141
|
+
type="search"
|
|
144142
|
+
data-input="search"
|
|
144143
|
+
placeholder="Buscar identificador, device, texto\u2026"
|
|
144144
|
+
aria-label="Buscar anota\xE7\xF5es"
|
|
144145
|
+
value="${term}"
|
|
144146
|
+
/>
|
|
144147
|
+
</label>
|
|
144148
|
+
<select class="myio-annotations-toolbar-sort" data-input="sort" aria-label="Ordena\xE7\xE3o">
|
|
144149
|
+
${sortOptions}
|
|
144150
|
+
</select>
|
|
144151
|
+
<button
|
|
144152
|
+
class="myio-annotations-toolbar-filter-btn"
|
|
144153
|
+
type="button"
|
|
144154
|
+
data-action="toggle-filters"
|
|
144155
|
+
aria-expanded="false"
|
|
144156
|
+
aria-controls="myio-anno-filters"
|
|
144157
|
+
>\u2699 Filtros ${filterIndicator}</button>
|
|
144158
|
+
</div>
|
|
144159
|
+
<div class="myio-annotations-toolbar-row myio-annotations-toolbar-meta">
|
|
144160
|
+
<span class="myio-annotations-toolbar-count">${showingHtml}</span>
|
|
144161
|
+
</div>
|
|
144162
|
+
<div id="myio-anno-filters" class="myio-annotations-filters" hidden>${this._renderFilters()}</div>
|
|
144163
|
+
</div>`;
|
|
144164
|
+
}
|
|
144165
|
+
_renderFilters() {
|
|
144166
|
+
const typesHtml = FILTER_TYPE_OPTIONS.map(
|
|
144167
|
+
(t) => `<label class="myio-annotations-filter-chip ${this.filter.types.has(t.id) ? "is-on" : ""}">
|
|
144168
|
+
<input type="checkbox" data-filter="type" value="${t.id}" ${this.filter.types.has(t.id) ? "checked" : ""} />
|
|
144169
|
+
<span>${t.icon} ${escapeHtml6(t.label)}</span>
|
|
144170
|
+
</label>`
|
|
144171
|
+
).join("");
|
|
144172
|
+
const statusHtml = FILTER_STATUS_OPTIONS.map(
|
|
144173
|
+
(s) => `<label class="myio-annotations-filter-chip ${this.filter.statuses.has(s.id) ? "is-on" : ""}">
|
|
144174
|
+
<input type="checkbox" data-filter="status" value="${s.id}" ${this.filter.statuses.has(s.id) ? "checked" : ""} />
|
|
144175
|
+
<span>${escapeHtml6(s.label)}</span>
|
|
144176
|
+
</label>`
|
|
144177
|
+
).join("");
|
|
144178
|
+
const impHtml = [1, 2, 3, 4, 5].map(
|
|
144179
|
+
(lv) => `<label class="myio-annotations-filter-chip ${this.filter.importance.has(lv) ? "is-on" : ""}">
|
|
144180
|
+
<input type="checkbox" data-filter="importance" value="${lv}" ${this.filter.importance.has(lv) ? "checked" : ""} />
|
|
144181
|
+
<span>${lv}</span>
|
|
144182
|
+
</label>`
|
|
144183
|
+
).join("");
|
|
144184
|
+
return `
|
|
144185
|
+
<div class="myio-annotations-filter-section">
|
|
144186
|
+
<div class="myio-annotations-filter-section-title">Tipo</div>
|
|
144187
|
+
<div class="myio-annotations-filter-chips">${typesHtml}</div>
|
|
144188
|
+
</div>
|
|
144189
|
+
<div class="myio-annotations-filter-section">
|
|
144190
|
+
<div class="myio-annotations-filter-section-title">Status</div>
|
|
144191
|
+
<div class="myio-annotations-filter-chips">${statusHtml}</div>
|
|
144192
|
+
</div>
|
|
144193
|
+
<div class="myio-annotations-filter-section">
|
|
144194
|
+
<div class="myio-annotations-filter-section-title">Import\xE2ncia</div>
|
|
144195
|
+
<div class="myio-annotations-filter-chips">${impHtml}</div>
|
|
144196
|
+
</div>
|
|
144197
|
+
<div class="myio-annotations-filter-section">
|
|
144198
|
+
<label class="myio-annotations-filter-chip ${this.filter.actionableOnly ? "is-on" : ""}">
|
|
144199
|
+
<input type="checkbox" data-filter="actionable" ${this.filter.actionableOnly ? "checked" : ""} />
|
|
144200
|
+
<span>Acion\xE1veis apenas (pendentes n\xE3o-arquivadas, com vencimento \u2264 7 dias ou sem vencimento)</span>
|
|
144201
|
+
</label>
|
|
144202
|
+
</div>
|
|
144203
|
+
<div class="myio-annotations-filter-actions">
|
|
144204
|
+
<button type="button" class="myio-annotations-panel-footer-action" data-action="clear-filters">Limpar</button>
|
|
144205
|
+
</div>`;
|
|
144206
|
+
}
|
|
144207
|
+
_activeFilterCount() {
|
|
144208
|
+
let n = 0;
|
|
144209
|
+
n += this.filter.types.size;
|
|
144210
|
+
n += this.filter.statuses.size;
|
|
144211
|
+
n += this.filter.importance.size;
|
|
144212
|
+
if (this.filter.actionableOnly) n += 1;
|
|
144213
|
+
return n;
|
|
144214
|
+
}
|
|
144215
|
+
_renderBody(groups) {
|
|
144216
|
+
if (!groups || groups.length === 0) {
|
|
144217
|
+
const hasFilters = this.filter.searchTerm || this._activeFilterCount() > 0;
|
|
144218
|
+
return `
|
|
144219
|
+
<div class="myio-annotations-empty">
|
|
144220
|
+
<div class="myio-annotations-empty-icon" aria-hidden="true">\u{1F4CB}</div>
|
|
144221
|
+
<div>${hasFilters ? "Nada encontrado para o filtro / busca atual." : "Nenhuma anota\xE7\xE3o ativa."}</div>
|
|
144222
|
+
</div>`;
|
|
144223
|
+
}
|
|
144224
|
+
return groups.map((g) => this._renderGroup(g)).join("\n");
|
|
144225
|
+
}
|
|
144226
|
+
_renderGroup(group) {
|
|
144227
|
+
const isNoIdentifier = this.activeTab === "identifier" && group.key === "Sem Identificador";
|
|
144228
|
+
const term = this.filter.searchTerm || "";
|
|
144229
|
+
const items = group.devices.flatMap(
|
|
144230
|
+
(d) => d.annotations.map((a) => renderAnnotationItemCard(d, a, term))
|
|
144231
|
+
);
|
|
144232
|
+
const groupClass = isNoIdentifier ? "myio-annotations-group myio-annotations-group--no-id" : "myio-annotations-group";
|
|
144233
|
+
return `
|
|
144234
|
+
<section class="${groupClass}">
|
|
144235
|
+
<header class="myio-annotations-group-header">
|
|
144236
|
+
${group.icon ? `<span class="myio-annotations-group-icon" aria-hidden="true">${escapeHtml6(group.icon)}</span>` : ""}
|
|
144237
|
+
<span class="myio-annotations-group-label">${escapeHtml6(group.label)}</span>
|
|
144238
|
+
<span class="myio-annotations-group-count">${group.totalAnnotations}</span>
|
|
144239
|
+
</header>
|
|
144240
|
+
${items.join("\n")}
|
|
144241
|
+
</section>`;
|
|
144242
|
+
}
|
|
144243
|
+
_bindInteractiveElements() {
|
|
144244
|
+
if (!this.root) return;
|
|
144245
|
+
const tabBtns = Array.from(this.root.querySelectorAll(".myio-annotations-tab"));
|
|
144246
|
+
tabBtns.forEach((btn) => {
|
|
144247
|
+
btn.addEventListener("click", () => {
|
|
144248
|
+
const tab = btn.getAttribute("data-tab");
|
|
144249
|
+
if (tab) this.setActiveTab(tab);
|
|
144250
|
+
});
|
|
144251
|
+
});
|
|
144252
|
+
const tablist = this.root.querySelector(".myio-annotations-tabs");
|
|
144253
|
+
if (tablist) {
|
|
144254
|
+
tablist.addEventListener("keydown", (e) => this._onTabKeydown(e, tabBtns));
|
|
144255
|
+
}
|
|
144256
|
+
const items = this.root.querySelectorAll(".myio-annotations-item");
|
|
144257
|
+
items.forEach((it) => {
|
|
144258
|
+
it.addEventListener("click", () => this._handleItemClick(it));
|
|
144259
|
+
});
|
|
144260
|
+
const closeBtn = this.root.querySelector('[data-action="close"]');
|
|
144261
|
+
if (closeBtn) closeBtn.addEventListener("click", () => this.hide());
|
|
144262
|
+
const pinBtn = this.root.querySelector('[data-action="pin"]');
|
|
144263
|
+
if (pinBtn) {
|
|
144264
|
+
pinBtn.addEventListener("click", () => {
|
|
144265
|
+
this.isPinned = !this.isPinned;
|
|
144266
|
+
if (this.isOpen) this._render();
|
|
144267
|
+
});
|
|
144268
|
+
}
|
|
144269
|
+
const maxBtn = this.root.querySelector('[data-action="maximize"]');
|
|
144270
|
+
if (maxBtn) {
|
|
144271
|
+
maxBtn.addEventListener("click", () => {
|
|
144272
|
+
this.isMaximized = !this.isMaximized;
|
|
144273
|
+
if (this.root) {
|
|
144274
|
+
if (this.isMaximized) {
|
|
144275
|
+
this.root.classList.add("maximized");
|
|
144276
|
+
this.root.style.width = "";
|
|
144277
|
+
} else {
|
|
144278
|
+
this.root.classList.remove("maximized");
|
|
144279
|
+
this._position();
|
|
144280
|
+
}
|
|
144281
|
+
}
|
|
144282
|
+
if (this.isOpen) this._render();
|
|
144283
|
+
});
|
|
144284
|
+
}
|
|
144285
|
+
const dragHandle = this.root.querySelector("[data-drag-handle]");
|
|
144286
|
+
if (dragHandle) {
|
|
144287
|
+
dragHandle.addEventListener("mousedown", (e) => {
|
|
144288
|
+
const target = e.target;
|
|
144289
|
+
if (target.closest(".myio-annotations-panel-action")) return;
|
|
144290
|
+
if (this.isMaximized) return;
|
|
144291
|
+
if (!this.root) return;
|
|
144292
|
+
const rect = this.root.getBoundingClientRect();
|
|
144293
|
+
this.dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
144294
|
+
this.isDragging = true;
|
|
144295
|
+
this.root.classList.add("is-dragging");
|
|
144296
|
+
window.addEventListener("mousemove", this._onDragMove);
|
|
144297
|
+
window.addEventListener("mouseup", this._onDragEnd);
|
|
144298
|
+
e.preventDefault();
|
|
144299
|
+
});
|
|
144300
|
+
}
|
|
144301
|
+
const refreshBtn = this.root.querySelector('[data-action="refresh"]');
|
|
144302
|
+
if (refreshBtn) {
|
|
144303
|
+
refreshBtn.addEventListener("click", () => {
|
|
144304
|
+
const orch = this.opts.getOrchestrator();
|
|
144305
|
+
if (orch && typeof orch.refresh === "function") {
|
|
144306
|
+
orch.refresh().catch((err) => this.opts.logger.warn("[HeaderAnnotationsPanel] refresh failed:", err));
|
|
144307
|
+
}
|
|
144308
|
+
});
|
|
144309
|
+
}
|
|
144310
|
+
const exportBtn = this.root.querySelector('[data-action="export"]');
|
|
144311
|
+
if (exportBtn) {
|
|
144312
|
+
exportBtn.addEventListener("click", () => this._openExportFlow());
|
|
144313
|
+
}
|
|
144314
|
+
const searchInput = this.root.querySelector('[data-input="search"]');
|
|
144315
|
+
if (searchInput) {
|
|
144316
|
+
searchInput.addEventListener("input", () => {
|
|
144317
|
+
if (this._searchDebounceTimer) clearTimeout(this._searchDebounceTimer);
|
|
144318
|
+
this._searchDebounceTimer = setTimeout(() => {
|
|
144319
|
+
this.filter = withSearchTerm(this.filter, searchInput.value);
|
|
144320
|
+
if (this.isOpen) this._render();
|
|
144321
|
+
requestAnimationFrame(() => {
|
|
144322
|
+
const fresh = this.root?.querySelector('[data-input="search"]');
|
|
144323
|
+
if (fresh) {
|
|
144324
|
+
fresh.focus();
|
|
144325
|
+
try {
|
|
144326
|
+
fresh.setSelectionRange(fresh.value.length, fresh.value.length);
|
|
144327
|
+
} catch {
|
|
144328
|
+
}
|
|
144329
|
+
}
|
|
144330
|
+
});
|
|
144331
|
+
}, SEARCH_DEBOUNCE_MS);
|
|
144332
|
+
});
|
|
144333
|
+
}
|
|
144334
|
+
const sortSelect = this.root.querySelector('[data-input="sort"]');
|
|
144335
|
+
if (sortSelect) {
|
|
144336
|
+
sortSelect.addEventListener("change", () => {
|
|
144337
|
+
this.setSortBy(sortSelect.value);
|
|
144338
|
+
});
|
|
144339
|
+
}
|
|
144340
|
+
const filterToggle = this.root.querySelector(
|
|
144341
|
+
'[data-action="toggle-filters"]'
|
|
144342
|
+
);
|
|
144343
|
+
const filterPanel = this.root.querySelector("#myio-anno-filters");
|
|
144344
|
+
if (filterToggle && filterPanel) {
|
|
144345
|
+
filterToggle.addEventListener("click", () => {
|
|
144346
|
+
const isHidden = filterPanel.hasAttribute("hidden");
|
|
144347
|
+
if (isHidden) {
|
|
144348
|
+
filterPanel.removeAttribute("hidden");
|
|
144349
|
+
filterToggle.setAttribute("aria-expanded", "true");
|
|
144350
|
+
} else {
|
|
144351
|
+
filterPanel.setAttribute("hidden", "");
|
|
144352
|
+
filterToggle.setAttribute("aria-expanded", "false");
|
|
144353
|
+
}
|
|
144354
|
+
});
|
|
144355
|
+
}
|
|
144356
|
+
const filterInputs = this.root.querySelectorAll("[data-filter]");
|
|
144357
|
+
filterInputs.forEach((cb) => {
|
|
144358
|
+
cb.addEventListener("change", () => {
|
|
144359
|
+
const kind = cb.getAttribute("data-filter");
|
|
144360
|
+
if (kind === "type") {
|
|
144361
|
+
const v = cb.value;
|
|
144362
|
+
this.setFilter({ types: toggleInSet(this.filter.types, v) });
|
|
144363
|
+
this._reopenFiltersAfterRender();
|
|
144364
|
+
} else if (kind === "status") {
|
|
144365
|
+
const v = cb.value;
|
|
144366
|
+
this.setFilter({ statuses: toggleInSet(this.filter.statuses, v) });
|
|
144367
|
+
this._reopenFiltersAfterRender();
|
|
144368
|
+
} else if (kind === "importance") {
|
|
144369
|
+
const v = Number(cb.value);
|
|
144370
|
+
this.setFilter({ importance: toggleInSet(this.filter.importance, v) });
|
|
144371
|
+
this._reopenFiltersAfterRender();
|
|
144372
|
+
} else if (kind === "actionable") {
|
|
144373
|
+
this.setFilter({ actionableOnly: cb.checked });
|
|
144374
|
+
this._reopenFiltersAfterRender();
|
|
144375
|
+
}
|
|
144376
|
+
});
|
|
144377
|
+
});
|
|
144378
|
+
const clearBtn = this.root.querySelector('[data-action="clear-filters"]');
|
|
144379
|
+
if (clearBtn) {
|
|
144380
|
+
clearBtn.addEventListener("click", () => {
|
|
144381
|
+
this.filter = createDefaultFilter();
|
|
144382
|
+
if (this.isOpen) this._render();
|
|
144383
|
+
this._reopenFiltersAfterRender();
|
|
144384
|
+
});
|
|
144385
|
+
}
|
|
144386
|
+
}
|
|
144387
|
+
/** After a re-render triggered by a filter checkbox, re-open the filter panel
|
|
144388
|
+
* so the user keeps the context visible. */
|
|
144389
|
+
_reopenFiltersAfterRender() {
|
|
144390
|
+
requestAnimationFrame(() => {
|
|
144391
|
+
const fp = this.root?.querySelector("#myio-anno-filters");
|
|
144392
|
+
const toggle = this.root?.querySelector(
|
|
144393
|
+
'[data-action="toggle-filters"]'
|
|
144394
|
+
);
|
|
144395
|
+
if (fp && toggle) {
|
|
144396
|
+
fp.removeAttribute("hidden");
|
|
144397
|
+
toggle.setAttribute("aria-expanded", "true");
|
|
144398
|
+
}
|
|
144399
|
+
});
|
|
144400
|
+
}
|
|
144401
|
+
_onTabKeydown(e, tabBtns) {
|
|
144402
|
+
const currentIdx = tabBtns.findIndex(
|
|
144403
|
+
(b) => b.getAttribute("aria-selected") === "true"
|
|
144404
|
+
);
|
|
144405
|
+
if (currentIdx < 0) return;
|
|
144406
|
+
let nextIdx = currentIdx;
|
|
144407
|
+
switch (e.key) {
|
|
144408
|
+
case "ArrowRight":
|
|
144409
|
+
nextIdx = (currentIdx + 1) % tabBtns.length;
|
|
144410
|
+
break;
|
|
144411
|
+
case "ArrowLeft":
|
|
144412
|
+
nextIdx = (currentIdx - 1 + tabBtns.length) % tabBtns.length;
|
|
144413
|
+
break;
|
|
144414
|
+
case "Home":
|
|
144415
|
+
nextIdx = 0;
|
|
144416
|
+
break;
|
|
144417
|
+
case "End":
|
|
144418
|
+
nextIdx = tabBtns.length - 1;
|
|
144419
|
+
break;
|
|
144420
|
+
default:
|
|
144421
|
+
return;
|
|
144422
|
+
}
|
|
144423
|
+
e.preventDefault();
|
|
144424
|
+
const tabId = tabBtns[nextIdx].getAttribute("data-tab");
|
|
144425
|
+
if (tabId) {
|
|
144426
|
+
this.setActiveTab(tabId);
|
|
144427
|
+
requestAnimationFrame(() => {
|
|
144428
|
+
const sel = this.root?.querySelector(
|
|
144429
|
+
'.myio-annotations-tab[aria-selected="true"]'
|
|
144430
|
+
);
|
|
144431
|
+
sel?.focus();
|
|
144432
|
+
});
|
|
144433
|
+
}
|
|
144434
|
+
}
|
|
144435
|
+
_handleItemClick(itemEl) {
|
|
144436
|
+
const deviceId = itemEl.getAttribute("data-device-id") || "";
|
|
144437
|
+
const annotationId = itemEl.getAttribute("data-annotation-id") || "";
|
|
144438
|
+
if (typeof window !== "undefined") {
|
|
144439
|
+
window.dispatchEvent(
|
|
144440
|
+
new CustomEvent("myio:annotation-clicked", {
|
|
144441
|
+
detail: { deviceId, annotationId, returnTo: "header-panel" }
|
|
144442
|
+
})
|
|
144443
|
+
);
|
|
144444
|
+
}
|
|
144445
|
+
}
|
|
144446
|
+
/**
|
|
144447
|
+
* RFC-0203 M7 — Opens the export modal and dispatches CSV/PDF generation.
|
|
144448
|
+
*/
|
|
144449
|
+
_openExportFlow() {
|
|
144450
|
+
const orch = this.opts.getOrchestrator();
|
|
144451
|
+
if (!orch) {
|
|
144452
|
+
this.opts.logger.warn("[HeaderAnnotationsPanel] export: no orchestrator");
|
|
144453
|
+
return;
|
|
144454
|
+
}
|
|
144455
|
+
const hasActiveFilter = !!this.filter.searchTerm || this.filter.types.size > 0 || this.filter.statuses.size > 0 || this.filter.importance.size > 0 || this.filter.actionableOnly;
|
|
144456
|
+
const customerName = typeof window !== "undefined" && window.MyIOOrchestrator?.customerName || "";
|
|
144457
|
+
openExportModal({
|
|
144458
|
+
hasActiveFilter,
|
|
144459
|
+
logger: this.opts.logger,
|
|
144460
|
+
onExport: (opts) => {
|
|
144461
|
+
try {
|
|
144462
|
+
const devices = this._devicesForScope(opts.scope, orch);
|
|
144463
|
+
if (opts.format === "csv") {
|
|
144464
|
+
exportAnnotationsCsv(devices, {
|
|
144465
|
+
customerName,
|
|
144466
|
+
includeArchived: this.filter.statuses.has("archived")
|
|
144467
|
+
});
|
|
144468
|
+
this.opts.logger.debug("[HeaderAnnotationsPanel] CSV exported");
|
|
144469
|
+
} else if (opts.format === "pdf") {
|
|
144470
|
+
const fallback = ["summary"];
|
|
144471
|
+
const levels = opts.levels && opts.levels.length > 0 ? opts.levels : fallback;
|
|
144472
|
+
exportAnnotationsPdf(devices, {
|
|
144473
|
+
customerName,
|
|
144474
|
+
levels,
|
|
144475
|
+
includeArchived: this.filter.statuses.has("archived")
|
|
144476
|
+
});
|
|
144477
|
+
this.opts.logger.debug("[HeaderAnnotationsPanel] PDF exported (levels=" + levels.join(",") + ")");
|
|
144478
|
+
}
|
|
144479
|
+
} catch (err) {
|
|
144480
|
+
this.opts.logger.warn("[HeaderAnnotationsPanel] export failed:", err);
|
|
144481
|
+
if (typeof window !== "undefined") {
|
|
144482
|
+
try {
|
|
144483
|
+
alert("Falha ao exportar: " + err?.message);
|
|
144484
|
+
} catch {
|
|
144485
|
+
}
|
|
144486
|
+
}
|
|
144487
|
+
}
|
|
144488
|
+
}
|
|
144489
|
+
});
|
|
144490
|
+
}
|
|
144491
|
+
/**
|
|
144492
|
+
* Resolves the device subset used by the export based on the chosen scope:
|
|
144493
|
+
* - 'current-tab': groups visible in the active tab (respecting filter)
|
|
144494
|
+
* - 'filtered' : the same as current-tab when a filter is active
|
|
144495
|
+
* - 'all' : every device known to the orchestrator (unfiltered)
|
|
144496
|
+
*/
|
|
144497
|
+
_devicesForScope(scope, orch) {
|
|
144498
|
+
if (scope === "all") return orch.getAll();
|
|
144499
|
+
const groups = orch.getGroups(this.activeTab, this.filter);
|
|
144500
|
+
const seen = /* @__PURE__ */ new Set();
|
|
144501
|
+
const out = [];
|
|
144502
|
+
for (const g of groups) {
|
|
144503
|
+
for (const d of g.devices) {
|
|
144504
|
+
if (!seen.has(d.deviceId)) {
|
|
144505
|
+
seen.add(d.deviceId);
|
|
144506
|
+
out.push(d);
|
|
144507
|
+
}
|
|
144508
|
+
}
|
|
144509
|
+
}
|
|
144510
|
+
return out;
|
|
144511
|
+
}
|
|
144512
|
+
};
|
|
144513
|
+
var _singleton = null;
|
|
144514
|
+
function getHeaderAnnotationsPanel() {
|
|
144515
|
+
if (!_singleton) _singleton = new HeaderAnnotationsPanel();
|
|
144516
|
+
return _singleton;
|
|
144517
|
+
}
|
|
144518
|
+
|
|
141946
144519
|
// src/index.ts
|
|
141947
144520
|
var version = package_default.version || "0.0.0";
|
|
141948
144521
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -141954,9 +144527,12 @@ var version = package_default.version || "0.0.0";
|
|
|
141954
144527
|
ALARM_STATE_CONFIG,
|
|
141955
144528
|
AMBIENTE_GROUP_CSS_PREFIX,
|
|
141956
144529
|
AMBIENTE_MODAL_CSS_PREFIX,
|
|
144530
|
+
ANNOTATION_CSV_COLUMNS,
|
|
144531
|
+
ANNOTATION_SORT_OPTIONS,
|
|
141957
144532
|
ANNOTATION_TYPE_COLORS,
|
|
141958
144533
|
ANNOTATION_TYPE_LABELS,
|
|
141959
144534
|
ANNOTATION_TYPE_LABELS_EN,
|
|
144535
|
+
ANNOTATION_VIRTUAL_THRESHOLD,
|
|
141960
144536
|
ActionButtonController,
|
|
141961
144537
|
ActionButtonView,
|
|
141962
144538
|
AlarmService,
|
|
@@ -141979,6 +144555,7 @@ var version = package_default.version || "0.0.0";
|
|
|
141979
144555
|
ContractSummaryTooltip,
|
|
141980
144556
|
CustomerCardV1,
|
|
141981
144557
|
CustomerCardV2,
|
|
144558
|
+
CustomerDeviceService,
|
|
141982
144559
|
DAY_LABELS,
|
|
141983
144560
|
DAY_LABELS_FULL,
|
|
141984
144561
|
DECIMAL_OPTIONS,
|
|
@@ -141986,6 +144563,7 @@ var version = package_default.version || "0.0.0";
|
|
|
141986
144563
|
DEFAULT_ALARM_FILTERS,
|
|
141987
144564
|
DEFAULT_ALARM_FILTER_TABS,
|
|
141988
144565
|
DEFAULT_ALARM_STATS,
|
|
144566
|
+
DEFAULT_ANNOTATION_SORT,
|
|
141989
144567
|
DEFAULT_BAS_SETTINGS,
|
|
141990
144568
|
DEFAULT_CLAMP_RANGE,
|
|
141991
144569
|
DEFAULT_DASHBOARD_KPIS,
|
|
@@ -142083,6 +144661,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142083
144661
|
HEADER_STYLE_DEFAULT,
|
|
142084
144662
|
HEADER_STYLE_PREMIUM_GREEN,
|
|
142085
144663
|
HEADER_STYLE_SLIM,
|
|
144664
|
+
HeaderAnnotationsPanel,
|
|
142086
144665
|
HeaderDevicesGridController,
|
|
142087
144666
|
HeaderDevicesGridView,
|
|
142088
144667
|
HeaderFilterModal,
|
|
@@ -142181,6 +144760,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142181
144760
|
TempRangeTooltip,
|
|
142182
144761
|
TempSensorSummaryTooltip,
|
|
142183
144762
|
UsersSummaryTooltip,
|
|
144763
|
+
VirtualList,
|
|
142184
144764
|
WAITING_STATUSES,
|
|
142185
144765
|
WATER_DEVICE_CATEGORIES,
|
|
142186
144766
|
WATER_SORT_OPTIONS,
|
|
@@ -142197,6 +144777,9 @@ var version = package_default.version || "0.0.0";
|
|
|
142197
144777
|
assignShoppingColors,
|
|
142198
144778
|
averageByDay,
|
|
142199
144779
|
buildAmbienteGroupData,
|
|
144780
|
+
buildAnnotationServiceOrchestrator,
|
|
144781
|
+
buildAnnotationsCsv,
|
|
144782
|
+
buildAnnotationsExportFilename,
|
|
142200
144783
|
buildDeviceGridEntityObject,
|
|
142201
144784
|
buildEquipmentCategoryDataForTooltip,
|
|
142202
144785
|
buildEquipmentCategorySummary,
|
|
@@ -142231,6 +144814,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142231
144814
|
classifyWaterLabels,
|
|
142232
144815
|
clearAllAuthCaches,
|
|
142233
144816
|
clearFreshdeskTicketsOnTB,
|
|
144817
|
+
closeAnnotationsExportModal,
|
|
142234
144818
|
connectionStatusIcons,
|
|
142235
144819
|
createActionButton,
|
|
142236
144820
|
createAlarmCardElement,
|
|
@@ -142247,6 +144831,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142247
144831
|
createCustomerCardV2,
|
|
142248
144832
|
createDateRangePicker,
|
|
142249
144833
|
createDaysGrid,
|
|
144834
|
+
createDefaultAnnotationFilter,
|
|
142250
144835
|
createDeviceGridBusyModal,
|
|
142251
144836
|
createDeviceGridState,
|
|
142252
144837
|
createDeviceGridV6,
|
|
@@ -142295,6 +144880,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142295
144880
|
createToggleSwitch,
|
|
142296
144881
|
createWaterPanelComponent,
|
|
142297
144882
|
createWidgetController,
|
|
144883
|
+
csvEscapeAnnotation,
|
|
142298
144884
|
decodePayload,
|
|
142299
144885
|
decodePayloadBase64Xor,
|
|
142300
144886
|
deleteFreshdeskTicket,
|
|
@@ -142306,6 +144892,10 @@ var version = package_default.version || "0.0.0";
|
|
|
142306
144892
|
determineInterval,
|
|
142307
144893
|
deviceStatusIcons,
|
|
142308
144894
|
doSchedulesOverlap,
|
|
144895
|
+
downloadAnnotationsTextFile,
|
|
144896
|
+
escapeAnnotationHtml,
|
|
144897
|
+
exportAnnotationsCsv,
|
|
144898
|
+
exportAnnotationsPdf,
|
|
142309
144899
|
exportGridCsv,
|
|
142310
144900
|
exportGridPdf,
|
|
142311
144901
|
exportGridXls,
|
|
@@ -142330,6 +144920,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142330
144920
|
formatAlarmRelativeTime,
|
|
142331
144921
|
formatAllInSameUnit,
|
|
142332
144922
|
formatAllInSameWaterUnit,
|
|
144923
|
+
formatAnnotationRelativeTime,
|
|
142333
144924
|
formatDashboardPercentage,
|
|
142334
144925
|
formatDateForInput,
|
|
142335
144926
|
formatDateToYMD,
|
|
@@ -142378,6 +144969,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142378
144969
|
getFirstDayOfMonthFor,
|
|
142379
144970
|
getGroupColor,
|
|
142380
144971
|
getHashColor,
|
|
144972
|
+
getHeaderAnnotationsPanel,
|
|
142381
144973
|
getImageByConsumption,
|
|
142382
144974
|
getLastDayOfMonth,
|
|
142383
144975
|
getModalHeaderStyles,
|
|
@@ -142401,6 +144993,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142401
144993
|
groupByDay,
|
|
142402
144994
|
handleDeviceType,
|
|
142403
144995
|
hasSelectedDays,
|
|
144996
|
+
highlightAnnotationMatches,
|
|
142404
144997
|
initMyIOAuthContext,
|
|
142405
144998
|
initOnOffTimelineTooltips,
|
|
142406
144999
|
injectActionButtonStyles,
|
|
@@ -142414,6 +145007,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142414
145007
|
injectDeviceOperationalCardGridStyles,
|
|
142415
145008
|
injectDeviceOperationalCardStyles,
|
|
142416
145009
|
injectFancoilRemoteStyles,
|
|
145010
|
+
injectHeaderAnnotationsStyles,
|
|
142417
145011
|
injectHeaderDevicesGridStyles,
|
|
142418
145012
|
injectHeaderShoppingStyles,
|
|
142419
145013
|
injectMenuShoppingStyles,
|
|
@@ -142432,6 +145026,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142432
145026
|
injectSwitchControlStyles,
|
|
142433
145027
|
interpolateTemperature,
|
|
142434
145028
|
isAlarmActive,
|
|
145029
|
+
isAnnotationOverdue,
|
|
142435
145030
|
isConnectionStale,
|
|
142436
145031
|
isDeviceOffline,
|
|
142437
145032
|
isEndAfterStart,
|
|
@@ -142454,6 +145049,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142454
145049
|
mapDeviceStatusToCardStatus,
|
|
142455
145050
|
mapDeviceToConnectionStatus,
|
|
142456
145051
|
myioExportData,
|
|
145052
|
+
nfdNormalizeAnnotationSearch,
|
|
142457
145053
|
normalizeConnectionStatus,
|
|
142458
145054
|
normalizeRecipients,
|
|
142459
145055
|
numbers,
|
|
@@ -142462,6 +145058,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142462
145058
|
openAlarmDetailsModal,
|
|
142463
145059
|
openAmbienteDetailModal,
|
|
142464
145060
|
openAmbienteGroupModal,
|
|
145061
|
+
openAnnotationsExportModal,
|
|
142465
145062
|
openContractDevicesModal,
|
|
142466
145063
|
openDashboardPopup,
|
|
142467
145064
|
openDashboardPopupAllReport,
|
|
@@ -142488,6 +145085,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142488
145085
|
openUserManagementModal,
|
|
142489
145086
|
openWelcomeModal,
|
|
142490
145087
|
parseInputDateToDate,
|
|
145088
|
+
parseLogAnnotations,
|
|
142491
145089
|
periodKey,
|
|
142492
145090
|
readFreshdeskTicketsFromTB,
|
|
142493
145091
|
recalculateDeviceStatus,
|
|
@@ -142501,6 +145099,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142501
145099
|
removeOperationalHeaderDevicesGridStyles,
|
|
142502
145100
|
removeSchedulingSharedStyles,
|
|
142503
145101
|
renderAlarmCard,
|
|
145102
|
+
renderAnnotationItemCard,
|
|
142504
145103
|
renderCardAmbienteV6,
|
|
142505
145104
|
renderCardComponent,
|
|
142506
145105
|
renderCardComponentEnhanced,
|
|
@@ -142531,6 +145130,8 @@ var version = package_default.version || "0.0.0";
|
|
|
142531
145130
|
schedShowConfirmModal,
|
|
142532
145131
|
schedShowNotificationModal,
|
|
142533
145132
|
shouldFlashIcon,
|
|
145133
|
+
shouldVirtualizeAnnotationList,
|
|
145134
|
+
sortAnnotationGroups,
|
|
142534
145135
|
sortDeviceGridDevices,
|
|
142535
145136
|
strings,
|
|
142536
145137
|
telemetryInfoFormatEnergy,
|
|
@@ -142542,6 +145143,7 @@ var version = package_default.version || "0.0.0";
|
|
|
142542
145143
|
toCSV,
|
|
142543
145144
|
toFixedSafe,
|
|
142544
145145
|
toFreshdeskTicketSummary,
|
|
145146
|
+
truncateAnnotationText,
|
|
142545
145147
|
updateDeviceGridStats,
|
|
142546
145148
|
updateFreshdeskTicket,
|
|
142547
145149
|
upsertAlarmAnnotation,
|