@vention/machine-apps-components 0.2.8

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/index.esm.js ADDED
@@ -0,0 +1,1711 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { useLocation, useNavigate } from 'react-router-dom';
3
+ import { useTheme, Typography, Box, Button, InputAdornment, IconButton, Divider, TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody } from '@mui/material';
4
+ import { tss } from 'tss-react/mui';
5
+ import { COLORS, VentionModalBase, VentionIcon, VentionTextInput, VentionButton, VentionStatusIndicator, VentionSpinner, VentionAlert, VentionSelect } from '@ventionco/machine-ui';
6
+ import React, { useState, useEffect, memo, useRef, forwardRef, useCallback, useImperativeHandle } from 'react';
7
+ import dayjs from 'dayjs';
8
+
9
+ function TimeLabel({
10
+ className,
11
+ color
12
+ }) {
13
+ const theme = useTheme();
14
+ const [timeLabel, setTimeLabel] = useState(() => new Date().toLocaleTimeString([], {
15
+ hour: "numeric",
16
+ minute: "2-digit"
17
+ }).toLowerCase().replace(" ", ""));
18
+ useEffect(function updateTimeLabel() {
19
+ const intervalId = setInterval(() => {
20
+ setTimeLabel(new Date().toLocaleTimeString([], {
21
+ hour: "numeric",
22
+ minute: "2-digit"
23
+ }).toLowerCase().replace(" ", ""));
24
+ }, 1000);
25
+ return () => clearInterval(intervalId);
26
+ }, []);
27
+ return jsx(Typography, {
28
+ className: className,
29
+ color: color !== null && color !== void 0 ? color : theme.palette.common.white,
30
+ children: timeLabel
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Detects if the current device has touch screen capabilities
36
+ * @returns true if the device supports touch events, false otherwise
37
+ */
38
+ const isTouchScreenDevice = () => {
39
+ return "ontouchstart" in window || navigator.maxTouchPoints > 0;
40
+ };
41
+ /**
42
+ * Closes the custom UI by sending a message to the parent window
43
+ */
44
+ const closeCustomUi = () => {
45
+ window.parent.postMessage("closeCustomUi", "*");
46
+ };
47
+ /**
48
+ * Navigates to a specific route in the Control Center
49
+ * @param route - The route to navigate to (remoteSupport or controlCenterHomepage)
50
+ */
51
+ const navigateControlCenter = route => {
52
+ const capitalizedRoute = route.charAt(0).toUpperCase() + route.slice(1);
53
+ window.parent.postMessage(`navigateTo${capitalizedRoute}`, "*");
54
+ };
55
+
56
+ const NavigationConfirmationModal = props => {
57
+ const {
58
+ isOpen,
59
+ destination,
60
+ onClose
61
+ } = props;
62
+ const {
63
+ classes
64
+ } = useStyles$a();
65
+ const handleConfirm = () => {
66
+ if (!destination) return;
67
+ onClose();
68
+ if (destination === "controlCenter") {
69
+ closeCustomUi();
70
+ } else {
71
+ navigateControlCenter(destination);
72
+ }
73
+ };
74
+ const getButtonText = () => {
75
+ if (destination === "controlCenter") {
76
+ return "Go to Control Center";
77
+ }
78
+ return "Go to Remote Support";
79
+ };
80
+ return jsx(VentionModalBase, {
81
+ isOpen: isOpen,
82
+ onClose: onClose,
83
+ modalSize: "xx-large",
84
+ isTouchDevice: isTouchScreenDevice(),
85
+ width: "1312px",
86
+ children: jsxs(Box, {
87
+ className: classes.modalContent,
88
+ children: [jsx(Box, {
89
+ className: classes.iconContainer,
90
+ children: jsx(VentionIcon, {
91
+ type: "alert-triangle-filled",
92
+ size: 56,
93
+ color: "#F59E0B"
94
+ })
95
+ }), jsx(Typography, {
96
+ className: classes.title,
97
+ children: "You are about to exit the application"
98
+ }), jsx(Typography, {
99
+ className: classes.body,
100
+ children: "Please make sure your changes are saved before you leave the app."
101
+ }), jsx(Button, {
102
+ onClick: handleConfirm,
103
+ className: classes.button,
104
+ disableRipple: true,
105
+ children: jsx(Box, {
106
+ className: classes.buttonLabel,
107
+ children: jsx(Typography, {
108
+ className: classes.buttonText,
109
+ children: getButtonText()
110
+ })
111
+ })
112
+ })]
113
+ })
114
+ });
115
+ };
116
+ const useStyles$a = tss.create(({
117
+ theme
118
+ }) => ({
119
+ modalContent: {
120
+ display: "flex",
121
+ flexDirection: "column",
122
+ alignItems: "center",
123
+ padding: "0 96px 24px 96px",
124
+ height: "360px",
125
+ boxSizing: "border-box"
126
+ },
127
+ iconContainer: {
128
+ marginBottom: theme.spacing(7)
129
+ },
130
+ title: Object.assign(Object.assign({}, theme.typography.hmiText28SemiBold), {
131
+ textAlign: "center",
132
+ marginBottom: theme.spacing(6)
133
+ }),
134
+ body: Object.assign(Object.assign({}, theme.typography.hmiText22Regular), {
135
+ textAlign: "center"
136
+ }),
137
+ button: {
138
+ width: "320px",
139
+ height: "80px",
140
+ backgroundColor: COLORS.slate[800],
141
+ borderRadius: "4px",
142
+ padding: "8px",
143
+ boxSizing: "border-box",
144
+ marginTop: "auto"
145
+ },
146
+ buttonLabel: {
147
+ display: "flex",
148
+ alignItems: "center",
149
+ justifyContent: "center",
150
+ padding: "0 8px",
151
+ boxSizing: "border-box"
152
+ },
153
+ buttonText: {
154
+ fontFamily: "Inter",
155
+ fontSize: "24px",
156
+ fontWeight: 600,
157
+ lineHeight: "32px",
158
+ color: theme.palette.common.white,
159
+ textAlign: "center",
160
+ whiteSpace: "nowrap"
161
+ }
162
+ }));
163
+
164
+ const PasswordProtectionModal = props => {
165
+ const {
166
+ isOpen,
167
+ onClose,
168
+ onSuccess,
169
+ correctPassword
170
+ } = props;
171
+ const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
172
+ const {
173
+ classes
174
+ } = useStyles$9();
175
+ const [password, setPassword] = useState("");
176
+ const [showPassword, setShowPassword] = useState(false);
177
+ const [error, setError] = useState(false);
178
+ const handleConfirm = () => {
179
+ if (password === correctPassword) {
180
+ setPassword("");
181
+ setError(false);
182
+ onSuccess();
183
+ onClose();
184
+ } else {
185
+ setError(true);
186
+ }
187
+ };
188
+ const handleClose = () => {
189
+ setPassword("");
190
+ setError(false);
191
+ onClose();
192
+ };
193
+ const handleKeyPress = event => {
194
+ if (event.key === "Enter") {
195
+ handleConfirm();
196
+ }
197
+ };
198
+ useEffect(() => {
199
+ if (!isOpen || !isTouchScreenDevice()) {
200
+ setIsKeyboardVisible(false);
201
+ return;
202
+ }
203
+ const checkKeyboard = () => {
204
+ const keyboard = document.getElementById("virtual_keyboard");
205
+ setIsKeyboardVisible(keyboard !== null);
206
+ };
207
+ checkKeyboard();
208
+ const observer = new MutationObserver(checkKeyboard);
209
+ observer.observe(document.body, {
210
+ childList: true,
211
+ subtree: true
212
+ });
213
+ return () => {
214
+ observer.disconnect();
215
+ setIsKeyboardVisible(false);
216
+ };
217
+ }, [isOpen]);
218
+ return jsxs(Fragment, {
219
+ children: [jsx("style", {
220
+ children: isKeyboardVisible && `
221
+ [data-testid="vention-modal-container"] {
222
+ transform: translateY(-22.5vh) !important;
223
+ transition: transform 0.3s ease-out;
224
+ }
225
+ `
226
+ }), jsx(VentionModalBase, {
227
+ isOpen: isOpen,
228
+ onClose: handleClose,
229
+ modalSize: "x-large",
230
+ isTouchDevice: isTouchScreenDevice(),
231
+ width: "1120px",
232
+ backDropClickClosable: false,
233
+ children: jsxs(Box, {
234
+ className: classes.modalContent,
235
+ children: [jsx(Typography, {
236
+ className: classes.title,
237
+ children: "Administrator login required"
238
+ }), jsx(Box, {
239
+ className: classes.inputContainer,
240
+ children: jsx(VentionTextInput, {
241
+ label: "Password",
242
+ type: showPassword ? "text" : "password",
243
+ value: password,
244
+ onChange: event => {
245
+ setPassword(event.target.value);
246
+ setError(false);
247
+ },
248
+ onKeyPress: handleKeyPress,
249
+ state: error ? "error" : "default",
250
+ helperText: error ? "Incorrect password" : "",
251
+ fullWidth: true,
252
+ size: "xx-large",
253
+ InputProps: {
254
+ endAdornment: jsx(InputAdornment, {
255
+ position: "end",
256
+ children: jsx(IconButton, {
257
+ onClick: () => setShowPassword(!showPassword),
258
+ edge: "end",
259
+ disableRipple: true,
260
+ children: jsx(VentionIcon, {
261
+ type: showPassword ? "eye-closed" : "eye",
262
+ size: 28,
263
+ color: COLORS.slate[900]
264
+ })
265
+ })
266
+ })
267
+ }
268
+ })
269
+ }), jsx(Button, {
270
+ onClick: handleConfirm,
271
+ className: classes.button,
272
+ disableRipple: true,
273
+ children: jsx(Box, {
274
+ className: classes.buttonLabel,
275
+ children: jsx(Typography, {
276
+ className: classes.buttonText,
277
+ children: "Confirm"
278
+ })
279
+ })
280
+ })]
281
+ })
282
+ })]
283
+ });
284
+ };
285
+ const useStyles$9 = tss.create(({
286
+ theme
287
+ }) => ({
288
+ modalContent: {
289
+ display: "flex",
290
+ flexDirection: "column",
291
+ alignItems: "center",
292
+ padding: "64px 96px 64px 96px",
293
+ gap: theme.spacing(7),
294
+ boxSizing: "border-box",
295
+ height: "324px"
296
+ },
297
+ title: {
298
+ fontFamily: "Inter",
299
+ fontSize: "36px",
300
+ fontWeight: 700,
301
+ lineHeight: "48px",
302
+ textAlign: "center",
303
+ color: COLORS.slate[900]
304
+ },
305
+ inputContainer: {
306
+ width: "499px",
307
+ display: "flex",
308
+ flexDirection: "column",
309
+ gap: theme.spacing(1)
310
+ },
311
+ button: {
312
+ width: "250px",
313
+ height: "64px",
314
+ backgroundColor: COLORS.slate[800],
315
+ borderRadius: "4px",
316
+ padding: "8px",
317
+ boxSizing: "border-box",
318
+ "&:hover": {
319
+ backgroundColor: COLORS.slate[700]
320
+ }
321
+ },
322
+ buttonLabel: {
323
+ display: "flex",
324
+ alignItems: "center",
325
+ justifyContent: "center",
326
+ padding: "0 8px",
327
+ boxSizing: "border-box"
328
+ },
329
+ buttonText: {
330
+ fontFamily: "Inter",
331
+ fontSize: "24px",
332
+ fontWeight: 600,
333
+ lineHeight: "32px",
334
+ color: theme.palette.common.white,
335
+ textAlign: "center",
336
+ whiteSpace: "nowrap"
337
+ }
338
+ }));
339
+
340
+ const NavigationBarItem = props => {
341
+ const {
342
+ icon,
343
+ iconDisabled,
344
+ label,
345
+ path,
346
+ onClick,
347
+ password,
348
+ isDisabled = false,
349
+ location,
350
+ handleNavigate,
351
+ currentPassword,
352
+ setPasswordProtectedItem
353
+ } = props;
354
+ const {
355
+ classes,
356
+ cx
357
+ } = useStyles$8();
358
+ const isActive = (location === null || location === void 0 ? void 0 : location.pathname) === path;
359
+ const currentIcon = isDisabled && iconDisabled ? iconDisabled : icon;
360
+ const handleItemClick = () => {
361
+ if (isDisabled || !handleNavigate || !setPasswordProtectedItem) return;
362
+ // If destination has a password and it's different from current password, show modal
363
+ if (password && password !== currentPassword) {
364
+ setPasswordProtectedItem(props);
365
+ return;
366
+ }
367
+ // Otherwise navigate directly
368
+ onClick ? onClick() : handleNavigate(path);
369
+ };
370
+ return jsxs(Button, {
371
+ disableRipple: true,
372
+ disabled: isDisabled,
373
+ onClick: handleItemClick,
374
+ className: cx(classes.tabButton, {
375
+ [classes.active]: isActive,
376
+ [classes.disabled]: isDisabled
377
+ }),
378
+ children: [jsx(Box, {
379
+ className: classes.navIcon,
380
+ children: currentIcon
381
+ }), jsx(Typography, {
382
+ className: classes.navLabel,
383
+ children: label
384
+ })]
385
+ });
386
+ };
387
+ const useStyles$8 = tss.create(({
388
+ theme
389
+ }) => ({
390
+ tabButton: {
391
+ display: "flex",
392
+ alignItems: "center",
393
+ justifyContent: "center",
394
+ flexDirection: "column",
395
+ height: "96px",
396
+ width: 184,
397
+ gap: theme.spacing(2),
398
+ borderRadius: 8,
399
+ backgroundColor: "transparent",
400
+ transition: "background-color 0.2s",
401
+ "&:hover": {
402
+ backgroundColor: COLORS.slate[700]
403
+ },
404
+ color: theme.palette.common.white,
405
+ "&.Mui-disabled": {
406
+ color: theme.palette.common.white
407
+ }
408
+ },
409
+ active: {
410
+ backgroundColor: COLORS.slate[600]
411
+ },
412
+ disabled: {
413
+ opacity: 0.5,
414
+ cursor: "not-allowed",
415
+ "&:hover": {
416
+ backgroundColor: "transparent"
417
+ }
418
+ },
419
+ navIcon: {
420
+ width: 32,
421
+ height: 32,
422
+ display: "flex",
423
+ alignItems: "center",
424
+ justifyContent: "center"
425
+ },
426
+ navLabel: Object.assign({}, theme.typography.hmiText22Regular)
427
+ }));
428
+
429
+ const iconSize = 32;
430
+ const NavigationBarRoot = props => {
431
+ var _a;
432
+ const {
433
+ children,
434
+ showTimer = true,
435
+ onControlCenterClick,
436
+ onSupportClick,
437
+ disabledNavigationItems = [],
438
+ isControlCenterDisabled = false,
439
+ isSupportDisabled = false,
440
+ showControlCenterButton = true,
441
+ showSupportButton = true
442
+ } = props;
443
+ const location = useLocation();
444
+ const navigate = useNavigate();
445
+ const theme = useTheme();
446
+ const {
447
+ classes
448
+ } = useStyles$7();
449
+ const [modalDestination, setModalDestination] = useState(null);
450
+ const [passwordProtectedItem, setPasswordProtectedItem] = useState(null);
451
+ const [currentPassword, setCurrentPassword] = useState(null);
452
+ const disabledSet = new Set(disabledNavigationItems);
453
+ const getFadedColor = (color, opacity = 1) => {
454
+ return `${color}${Math.round(opacity * 255).toString(16).padStart(2, "0")}`;
455
+ };
456
+ const handleNavigate = path => {
457
+ if (location.pathname !== path) navigate(path);
458
+ };
459
+ useEffect(function updateCurrentPassword() {
460
+ const currentPage = React.Children.toArray(children).find(child => React.isValidElement(child) && child.type === NavigationBarItem && child.props.path === location.pathname);
461
+ const currentPagePassword = React.isValidElement(currentPage) ? currentPage.props.password : undefined;
462
+ setCurrentPassword(currentPagePassword !== null && currentPagePassword !== void 0 ? currentPagePassword : null);
463
+ }, [location.pathname, children]);
464
+ const handleControlCenterClick = () => {
465
+ if (isControlCenterDisabled) return;
466
+ if (onControlCenterClick) {
467
+ onControlCenterClick();
468
+ } else {
469
+ setModalDestination("controlCenter");
470
+ }
471
+ };
472
+ const handleSupportClick = () => {
473
+ if (isSupportDisabled) return;
474
+ if (onSupportClick) {
475
+ onSupportClick();
476
+ } else {
477
+ setModalDestination("remoteSupport");
478
+ }
479
+ };
480
+ const handlePasswordSuccess = () => {
481
+ if (passwordProtectedItem) {
482
+ if (passwordProtectedItem.onClick) {
483
+ passwordProtectedItem.onClick();
484
+ } else {
485
+ handleNavigate(passwordProtectedItem.path);
486
+ }
487
+ }
488
+ };
489
+ const processedChildren = React.Children.map(children, child => {
490
+ if (!React.isValidElement(child)) return child;
491
+ if (child.type !== NavigationBarItem) return child;
492
+ const itemId = child.props.id;
493
+ const isDisabled = disabledSet.has(itemId);
494
+ return React.cloneElement(child, Object.assign(Object.assign({}, child.props), {
495
+ isDisabled: isDisabled,
496
+ location: location,
497
+ handleNavigate: handleNavigate,
498
+ currentPassword: currentPassword,
499
+ setPasswordProtectedItem: setPasswordProtectedItem
500
+ }));
501
+ });
502
+ return jsxs(Fragment, {
503
+ children: [jsx(NavigationConfirmationModal, {
504
+ isOpen: modalDestination !== null,
505
+ destination: modalDestination,
506
+ onClose: () => setModalDestination(null)
507
+ }), jsx(PasswordProtectionModal, {
508
+ isOpen: passwordProtectedItem !== null,
509
+ onClose: () => setPasswordProtectedItem(null),
510
+ onSuccess: handlePasswordSuccess,
511
+ correctPassword: (_a = passwordProtectedItem === null || passwordProtectedItem === void 0 ? void 0 : passwordProtectedItem.password) !== null && _a !== void 0 ? _a : ""
512
+ }), jsx("footer", {
513
+ className: classes.root,
514
+ children: jsxs(Box, {
515
+ className: classes.container,
516
+ children: [jsx(Box, {
517
+ className: classes.sideClusterLeft,
518
+ children: showControlCenterButton && jsx(VentionButton, {
519
+ disableRipple: true,
520
+ onClick: handleControlCenterClick,
521
+ disabled: isControlCenterDisabled,
522
+ className: classes.sideButton,
523
+ style: {
524
+ backgroundColor: isControlCenterDisabled ? getFadedColor(COLORS.slate[800]) : COLORS.slate[800],
525
+ borderColor: isControlCenterDisabled ? getFadedColor(COLORS.slate[600]) : COLORS.slate[600],
526
+ opacity: isControlCenterDisabled ? 0.5 : 1,
527
+ cursor: isControlCenterDisabled ? "not-allowed" : "pointer"
528
+ },
529
+ children: jsx(Typography, {
530
+ className: classes.sideButtonLabel,
531
+ style: {
532
+ color: isControlCenterDisabled ? getFadedColor(theme.palette.common.white) : theme.palette.common.white
533
+ },
534
+ children: "Control Center"
535
+ })
536
+ })
537
+ }), jsx("nav", {
538
+ className: classes.tabBarButtonContainer,
539
+ children: processedChildren
540
+ }), jsxs(Box, {
541
+ className: classes.sideClusterRight,
542
+ children: [showSupportButton && jsx(VentionButton, {
543
+ disableRipple: true,
544
+ onClick: handleSupportClick,
545
+ disabled: isSupportDisabled,
546
+ className: classes.sideButton,
547
+ style: {
548
+ backgroundColor: isSupportDisabled ? getFadedColor(COLORS.slate[800]) : COLORS.slate[800],
549
+ borderColor: isSupportDisabled ? getFadedColor(COLORS.slate[600]) : COLORS.slate[600],
550
+ opacity: isSupportDisabled ? 0.5 : 1,
551
+ cursor: isSupportDisabled ? "not-allowed" : "pointer"
552
+ },
553
+ iconLeft: jsx(VentionIcon, {
554
+ size: iconSize,
555
+ color: isSupportDisabled ? getFadedColor(theme.palette.common.white) : theme.palette.common.white,
556
+ type: "headset-filled"
557
+ }),
558
+ children: jsx(Typography, {
559
+ className: classes.sideButtonLabel,
560
+ style: {
561
+ color: isSupportDisabled ? getFadedColor(theme.palette.common.white) : theme.palette.common.white
562
+ },
563
+ children: "Support"
564
+ })
565
+ }), showTimer && jsx(TimeLabel, {
566
+ className: classes.timeLabel,
567
+ color: theme.palette.common.white
568
+ })]
569
+ })]
570
+ })
571
+ })]
572
+ });
573
+ };
574
+ const useStyles$7 = tss.create(({
575
+ theme
576
+ }) => ({
577
+ root: {
578
+ boxSizing: "border-box",
579
+ display: "flex",
580
+ justifyContent: "center",
581
+ alignItems: "center",
582
+ height: "120px",
583
+ padding: theme.spacing(2, 6),
584
+ backgroundColor: COLORS.slate[900],
585
+ position: "fixed",
586
+ left: 0,
587
+ right: 0,
588
+ bottom: 0,
589
+ borderTop: `1px solid ${COLORS.slate[800]}`
590
+ },
591
+ container: {
592
+ display: "flex",
593
+ alignItems: "center",
594
+ justifyContent: "space-between",
595
+ position: "relative",
596
+ width: "100%",
597
+ gap: "20px",
598
+ height: theme.spacing(13)
599
+ },
600
+ sideClusterLeft: {
601
+ display: "flex",
602
+ alignItems: "center",
603
+ gap: theme.spacing(8)
604
+ },
605
+ sideClusterRight: {
606
+ display: "flex",
607
+ alignItems: "center",
608
+ gap: theme.spacing(8)
609
+ },
610
+ sideButton: {
611
+ display: "flex",
612
+ flexDirection: "row",
613
+ alignItems: "center",
614
+ justifyContent: "center",
615
+ height: theme.spacing(11),
616
+ padding: theme.spacing(2, 4),
617
+ backgroundColor: COLORS.slate[800],
618
+ borderRadius: 4,
619
+ border: `2px solid ${COLORS.slate[600]}`,
620
+ color: theme.palette.common.white,
621
+ textTransform: "none",
622
+ minWidth: 0,
623
+ "&:hover": {
624
+ backgroundColor: COLORS.slate[700]
625
+ },
626
+ "&.Mui-disabled": {
627
+ color: theme.palette.common.white
628
+ }
629
+ },
630
+ sideButtonLabel: {
631
+ display: "flex",
632
+ justifyContent: "center",
633
+ alignItems: "center",
634
+ fontSize: 24,
635
+ fontFamily: "Inter",
636
+ fontStyle: "normal",
637
+ fontWeight: 600,
638
+ lineHeight: "32px"
639
+ },
640
+ timeLabel: Object.assign(Object.assign({}, theme.typography.hmiText22Regular), {
641
+ fontWeight: 600
642
+ }),
643
+ tabBarButtonContainer: {
644
+ display: "flex",
645
+ alignItems: "center",
646
+ justifyContent: "center",
647
+ position: "absolute",
648
+ left: "50%",
649
+ transform: "translateX(-50%)",
650
+ gap: theme.spacing(5),
651
+ height: "104px"
652
+ }
653
+ }));
654
+ const NavigationBar = Object.assign(NavigationBarRoot, {
655
+ Item: NavigationBarItem
656
+ });
657
+
658
+ const StatusTopBarButton = props => {
659
+ const {
660
+ label,
661
+ onClick,
662
+ backgroundColor,
663
+ backgroundColorHover,
664
+ borderColor,
665
+ textColor,
666
+ width = 208,
667
+ height = 80,
668
+ icon,
669
+ iconDisabled,
670
+ visible = true,
671
+ isDisabled = false
672
+ } = props;
673
+ const {
674
+ classes
675
+ } = useStyles$6();
676
+ if (!visible) return null;
677
+ const getFadedColor = (color, opacity = 1) => {
678
+ return `${color}${Math.round(opacity * 255).toString(16).padStart(2, "0")}`;
679
+ };
680
+ const hasBackgroundColor = !!backgroundColor;
681
+ const variant = hasBackgroundColor ? "filled-brand" : "outline";
682
+ const finalBackgroundColor = isDisabled && backgroundColor ? getFadedColor(backgroundColor) : backgroundColor;
683
+ const finalBackgroundColorHover = isDisabled && backgroundColorHover ? getFadedColor(backgroundColorHover) : backgroundColorHover;
684
+ const finalBorderColor = isDisabled && borderColor ? getFadedColor(borderColor) : borderColor;
685
+ const finalTextColor = isDisabled && textColor ? getFadedColor(textColor) : textColor;
686
+ const finalIcon = isDisabled && iconDisabled ? iconDisabled : icon;
687
+ const handleClick = () => {
688
+ if (!isDisabled && onClick) {
689
+ onClick();
690
+ }
691
+ };
692
+ return jsx(VentionButton, {
693
+ variant: variant,
694
+ onClick: handleClick,
695
+ disabled: isDisabled || !onClick,
696
+ className: classes.actionButton,
697
+ style: {
698
+ width,
699
+ height,
700
+ backgroundColor: finalBackgroundColor || "transparent",
701
+ border: finalBorderColor ? `1px solid ${finalBorderColor}` : undefined
702
+ },
703
+ sx: {
704
+ "&:hover": {
705
+ backgroundColor: finalBackgroundColorHover || finalBackgroundColor
706
+ }
707
+ },
708
+ iconLeft: finalIcon ? jsx(Box, {
709
+ className: classes.iconWrapper,
710
+ children: finalIcon
711
+ }) : undefined,
712
+ children: jsx(Typography, {
713
+ className: classes.actionLabel,
714
+ style: {
715
+ color: finalTextColor
716
+ },
717
+ children: label
718
+ })
719
+ });
720
+ };
721
+ const useStyles$6 = tss.create(({
722
+ theme
723
+ }) => ({
724
+ actionButton: {
725
+ display: "flex",
726
+ alignItems: "center",
727
+ justifyContent: "center",
728
+ gap: theme.spacing(1),
729
+ padding: theme.spacing(1, 2),
730
+ borderRadius: 4,
731
+ minHeight: 0,
732
+ minWidth: 0
733
+ },
734
+ iconWrapper: {
735
+ padding: "5px",
736
+ display: "flex",
737
+ alignItems: "center",
738
+ justifyContent: "center"
739
+ },
740
+ actionLabel: {
741
+ fontWeight: 600,
742
+ fontSize: 24,
743
+ lineHeight: "32px",
744
+ textTransform: "none",
745
+ padding: theme.spacing(1)
746
+ }
747
+ }));
748
+
749
+ const StatusTopBarRoot = props => {
750
+ const {
751
+ children,
752
+ statusLabel,
753
+ dotColor,
754
+ visibleButtons,
755
+ disabledButtons = []
756
+ } = props;
757
+ const {
758
+ classes
759
+ } = useStyles$5();
760
+ const visibleSet = visibleButtons ? new Set(visibleButtons) : null;
761
+ const disabledSet = new Set(disabledButtons);
762
+ const processedChildren = React.Children.map(children, child => {
763
+ if (!React.isValidElement(child)) return child;
764
+ if (child.type !== StatusTopBarButton) return child;
765
+ const buttonId = child.props.id;
766
+ const visible = visibleSet === null || visibleSet.has(buttonId);
767
+ const isDisabled = disabledSet.has(buttonId);
768
+ return React.cloneElement(child, Object.assign(Object.assign({}, child.props), {
769
+ visible: visible,
770
+ isDisabled: isDisabled
771
+ }));
772
+ });
773
+ const hasVisibleButtons = React.Children.toArray(children).some(child => {
774
+ if (!React.isValidElement(child) || child.type !== StatusTopBarButton) return false;
775
+ const buttonId = child.props.id;
776
+ const visible = visibleSet === null || visibleSet.has(buttonId);
777
+ return visible;
778
+ });
779
+ return jsxs("header", {
780
+ className: classes.root,
781
+ children: [jsxs("div", {
782
+ className: classes.content,
783
+ children: [jsx("div", {
784
+ className: classes.leftArea,
785
+ children: jsx(VentionStatusIndicator, {
786
+ size: "xx-large",
787
+ dotColor: dotColor,
788
+ label: statusLabel
789
+ })
790
+ }), hasVisibleButtons && jsx("div", {
791
+ className: classes.rightArea,
792
+ children: processedChildren
793
+ }), !hasVisibleButtons && jsx("div", {
794
+ style: {
795
+ display: "none"
796
+ },
797
+ children: processedChildren
798
+ })]
799
+ }), jsx("div", {
800
+ className: classes.bottomBorder
801
+ })]
802
+ });
803
+ };
804
+ const useStyles$5 = tss.create(({
805
+ theme
806
+ }) => ({
807
+ root: {
808
+ boxSizing: "border-box",
809
+ position: "fixed",
810
+ top: 0,
811
+ right: 0,
812
+ left: 0,
813
+ backgroundColor: theme.palette.common.white,
814
+ height: "128px"
815
+ },
816
+ content: {
817
+ display: "flex",
818
+ alignItems: "center",
819
+ justifyContent: "space-between",
820
+ padding: theme.spacing(5, 4)
821
+ },
822
+ leftArea: {
823
+ display: "flex",
824
+ alignItems: "center"
825
+ },
826
+ rightArea: {
827
+ display: "flex",
828
+ alignItems: "center",
829
+ gap: theme.spacing(2),
830
+ height: theme.spacing(10)
831
+ },
832
+ bottomBorder: {
833
+ position: "absolute",
834
+ left: 0,
835
+ right: 0,
836
+ bottom: 0,
837
+ height: 1,
838
+ backgroundColor: theme.palette.divider
839
+ }
840
+ }));
841
+ const StatusTopBar = Object.assign(StatusTopBarRoot, {
842
+ Button: StatusTopBarButton
843
+ });
844
+
845
+ const Sidebar = props => {
846
+ const {
847
+ classes
848
+ } = useStyles$4();
849
+ return jsx(Box, {
850
+ className: classes.container,
851
+ children: props.items.map((item, index) => jsxs(React.Fragment, {
852
+ children: [jsx(SidebarItemComponent, {
853
+ item: item
854
+ }), index < props.items.length - 1 && jsx(Divider, {
855
+ className: classes.divider
856
+ })]
857
+ }, item.id))
858
+ });
859
+ };
860
+ const ICON_SIZE = 32;
861
+ const SidebarItemComponent = props => {
862
+ const {
863
+ item
864
+ } = props;
865
+ const {
866
+ classes
867
+ } = useSidebarItemStyles({
868
+ state: item.state
869
+ });
870
+ const isDisabled = item.state === "disabled";
871
+ return jsx(Button, {
872
+ className: classes.button,
873
+ onClick: item.onClick,
874
+ disabled: isDisabled,
875
+ disableRipple: true,
876
+ disableFocusRipple: true,
877
+ disableTouchRipple: true,
878
+ "data-testid": `sidebar-item-${item.id}`,
879
+ children: jsxs(Box, {
880
+ className: classes.content,
881
+ children: [jsxs(Box, {
882
+ className: classes.leftSection,
883
+ children: [item.icon ? item.icon : jsx(Box, {
884
+ className: classes.iconPlaceholder
885
+ }), jsxs(Box, {
886
+ className: classes.textContainer,
887
+ children: [jsx(Typography, {
888
+ className: classes.title,
889
+ children: item.title
890
+ }), item.subtitle && jsx(Typography, {
891
+ className: classes.subtitle,
892
+ children: item.subtitle
893
+ })]
894
+ })]
895
+ }), item.iconType && jsx(Box, {
896
+ className: classes.rightSection,
897
+ children: jsx(Box, {
898
+ className: classes.rightIcon,
899
+ children: jsx(VentionIcon, {
900
+ type: item.iconType,
901
+ size: ICON_SIZE
902
+ })
903
+ })
904
+ })]
905
+ })
906
+ });
907
+ };
908
+ const useStyles$4 = tss.create(({
909
+ theme
910
+ }) => ({
911
+ container: {
912
+ display: "flex",
913
+ flexDirection: "column",
914
+ width: "100%"
915
+ },
916
+ divider: {
917
+ borderColor: theme.palette.border.main
918
+ }
919
+ }));
920
+ const useSidebarItemStyles = tss.withParams().create(({
921
+ theme,
922
+ state
923
+ }) => {
924
+ const isActive = state === "active";
925
+ const isDisabled = state === "disabled";
926
+ return {
927
+ button: {
928
+ width: "100%",
929
+ height: theme.spacing(14),
930
+ padding: theme.spacing(7, 5),
931
+ textAlign: "left",
932
+ textTransform: "none",
933
+ borderRadius: 0,
934
+ backgroundColor: isActive ? theme.palette.background.surface1active : "transparent",
935
+ border: isActive ? `1px solid ${theme.palette.border.main}` : "1px solid transparent",
936
+ display: "flex",
937
+ alignItems: "center",
938
+ justifyContent: "flex-start",
939
+ transition: "background-color 0.2s ease",
940
+ "&:hover": {
941
+ backgroundColor: isDisabled ? "transparent" : isActive ? theme.palette.background.surface1active : theme.palette.action.hover
942
+ }
943
+ },
944
+ content: {
945
+ display: "flex",
946
+ alignItems: "center",
947
+ justifyContent: "space-between",
948
+ width: "100%",
949
+ height: "100%"
950
+ },
951
+ leftSection: {
952
+ display: "flex",
953
+ alignItems: "center",
954
+ gap: theme.spacing(3),
955
+ flex: 1
956
+ },
957
+ iconPlaceholder: {
958
+ width: theme.spacing(4)
959
+ },
960
+ textContainer: {
961
+ display: "flex",
962
+ flexDirection: "column",
963
+ gap: theme.spacing(1)
964
+ },
965
+ title: Object.assign(Object.assign({}, theme.typography.heading24SemiBold), {
966
+ color: isDisabled ? theme.palette.text.disabled : theme.palette.text.primary
967
+ }),
968
+ subtitle: Object.assign(Object.assign({}, theme.typography.hmiText20Regular), {
969
+ color: isDisabled ? theme.palette.text.disabled : theme.palette.text.primary
970
+ }),
971
+ rightSection: {
972
+ display: "flex",
973
+ alignItems: "center",
974
+ marginLeft: theme.spacing(2)
975
+ },
976
+ rightIcon: {
977
+ display: "flex",
978
+ alignItems: "center",
979
+ color: isActive ? theme.palette.icon.primary : theme.palette.icon.tertiary,
980
+ "& svg": {
981
+ color: isActive ? theme.palette.icon.primary : theme.palette.icon.tertiary
982
+ }
983
+ }
984
+ };
985
+ });
986
+
987
+ const LogsTable = memo(({
988
+ logs = [],
989
+ isLoading = false,
990
+ error = null,
991
+ onLoadMoreLogs,
992
+ hasMoreLogs,
993
+ onLogClick,
994
+ tableHeight,
995
+ emptyStateMessage = "You have no logs",
996
+ emptyStateIcon
997
+ }) => {
998
+ const {
999
+ classes,
1000
+ cx
1001
+ } = useStyles$3({
1002
+ tableHeight
1003
+ });
1004
+ const theme = useTheme();
1005
+ const loadMoreRef = useRef(null);
1006
+ useEffect(function setupInfiniteScroll() {
1007
+ if (!onLoadMoreLogs || !hasMoreLogs || !loadMoreRef.current) return;
1008
+ const observer = new IntersectionObserver(entries => {
1009
+ if (entries[0].isIntersecting && hasMoreLogs) {
1010
+ onLoadMoreLogs();
1011
+ }
1012
+ }, {
1013
+ threshold: 0.1
1014
+ });
1015
+ observer.observe(loadMoreRef.current);
1016
+ return function cleanupInfiniteScroll() {
1017
+ observer.disconnect();
1018
+ };
1019
+ }, [onLoadMoreLogs, hasMoreLogs]);
1020
+ if (isLoading) {
1021
+ return jsx(Box, {
1022
+ className: classes.tableSection,
1023
+ children: jsxs(Box, {
1024
+ className: classes.centerState,
1025
+ children: [jsx(VentionSpinner, {
1026
+ size: "large"
1027
+ }), jsx(Typography, {
1028
+ variant: "heading24Medium",
1029
+ sx: {
1030
+ marginTop: 2
1031
+ },
1032
+ children: "Loading logs..."
1033
+ })]
1034
+ })
1035
+ });
1036
+ }
1037
+ if (error) {
1038
+ return jsx(Box, {
1039
+ className: classes.tableSection,
1040
+ children: jsx(Box, {
1041
+ className: classes.errorState,
1042
+ children: jsx(VentionAlert, {
1043
+ severity: "error",
1044
+ title: "Error loading logs",
1045
+ descriptionText: error,
1046
+ size: "large"
1047
+ })
1048
+ })
1049
+ });
1050
+ }
1051
+ if (!logs || logs.length === 0) {
1052
+ return jsx(Box, {
1053
+ className: classes.tableSection,
1054
+ children: jsxs(Box, {
1055
+ className: classes.centerState,
1056
+ children: [emptyStateIcon && jsx(Box, {
1057
+ className: classes.emptyStateIcon,
1058
+ children: emptyStateIcon
1059
+ }), jsx(Typography, {
1060
+ variant: "heading24Medium",
1061
+ children: emptyStateMessage
1062
+ })]
1063
+ })
1064
+ });
1065
+ }
1066
+ const getTypeClassName = type => {
1067
+ switch (type) {
1068
+ case "error":
1069
+ return classes.errorType;
1070
+ case "warning":
1071
+ return classes.warningType;
1072
+ case "info":
1073
+ return classes.infoType;
1074
+ default:
1075
+ return "";
1076
+ }
1077
+ };
1078
+ const renderTypeIcon = type => {
1079
+ if (type === "error") {
1080
+ return jsx(VentionIcon, {
1081
+ size: 20,
1082
+ type: "alert-circle-filled",
1083
+ color: theme.palette.error.main
1084
+ });
1085
+ }
1086
+ if (type === "warning") {
1087
+ return jsx(VentionIcon, {
1088
+ size: 20,
1089
+ type: "alert-triangle-filled",
1090
+ color: theme.palette.warning.main
1091
+ });
1092
+ }
1093
+ return jsx(VentionIcon, {
1094
+ size: 20,
1095
+ type: "info-circle-filled",
1096
+ color: theme.palette.info.main
1097
+ });
1098
+ };
1099
+ const formatDate = dateStr => {
1100
+ return dayjs(dateStr).format("YYYY-MM-DD h:mm:ssa");
1101
+ };
1102
+ return jsxs(Box, {
1103
+ className: classes.tableSection,
1104
+ children: [jsx(TableContainer, {
1105
+ component: Paper,
1106
+ className: classes.tableContainer,
1107
+ children: jsxs(Table, {
1108
+ stickyHeader: true,
1109
+ children: [jsx(TableHead, {
1110
+ className: classes.tableHead,
1111
+ children: jsxs(TableRow, {
1112
+ children: [jsx(TableCell, {
1113
+ className: classes.iconHeaderCell
1114
+ }), jsx(TableCell, {
1115
+ className: classes.tableHeaderCell,
1116
+ children: jsx(Typography, {
1117
+ variant: "heading24Medium",
1118
+ className: classes.tableText,
1119
+ children: "Date"
1120
+ })
1121
+ }), jsx(TableCell, {
1122
+ className: classes.tableHeaderCell,
1123
+ children: jsx(Typography, {
1124
+ variant: "heading24Medium",
1125
+ className: classes.tableText,
1126
+ children: "Type"
1127
+ })
1128
+ }), jsx(TableCell, {
1129
+ className: classes.tableHeaderCell,
1130
+ children: jsx(Typography, {
1131
+ variant: "heading24Medium",
1132
+ className: classes.tableText,
1133
+ children: "Code"
1134
+ })
1135
+ }), jsx(TableCell, {
1136
+ className: classes.tableHeaderCell,
1137
+ children: jsx(Typography, {
1138
+ variant: "heading24Medium",
1139
+ className: classes.tableText,
1140
+ children: "Message"
1141
+ })
1142
+ }), jsx(TableCell, {
1143
+ className: classes.tableHeaderCell,
1144
+ children: jsx(Typography, {
1145
+ variant: "heading24Medium",
1146
+ className: classes.tableText,
1147
+ children: "Description"
1148
+ })
1149
+ })]
1150
+ })
1151
+ }), jsx(TableBody, {
1152
+ children: logs.map(log => jsxs(TableRow, {
1153
+ hover: true,
1154
+ onClick: onLogClick ? () => onLogClick(log) : undefined,
1155
+ className: onLogClick ? classes.clickableRow : undefined,
1156
+ children: [jsx(TableCell, {
1157
+ className: classes.iconCell,
1158
+ children: jsx(Box, {
1159
+ className: classes.iconWrapper,
1160
+ children: renderTypeIcon(log.type)
1161
+ })
1162
+ }), jsx(TableCell, {
1163
+ children: jsx(Typography, {
1164
+ variant: "heading24Medium",
1165
+ className: classes.tableText,
1166
+ children: formatDate(log.date)
1167
+ })
1168
+ }), jsx(TableCell, {
1169
+ className: cx(classes.typeCell, getTypeClassName(log.type)),
1170
+ children: jsx(Typography, {
1171
+ variant: "heading24Medium",
1172
+ className: classes.tableText,
1173
+ children: log.type
1174
+ })
1175
+ }), jsx(TableCell, {
1176
+ children: jsx(Typography, {
1177
+ variant: "heading24Medium",
1178
+ className: classes.tableText,
1179
+ children: log.code
1180
+ })
1181
+ }), jsx(TableCell, {
1182
+ children: jsx(Typography, {
1183
+ variant: "heading24Medium",
1184
+ className: classes.tableText,
1185
+ children: log.message
1186
+ })
1187
+ }), jsx(TableCell, {
1188
+ children: jsx(Typography, {
1189
+ variant: "heading24Medium",
1190
+ className: classes.tableText,
1191
+ children: log.description
1192
+ })
1193
+ })]
1194
+ }, log.id))
1195
+ })]
1196
+ })
1197
+ }), onLoadMoreLogs && hasMoreLogs && jsxs(Box, {
1198
+ ref: loadMoreRef,
1199
+ className: classes.loadMoreTrigger,
1200
+ children: [jsx(VentionSpinner, {
1201
+ size: "medium"
1202
+ }), jsx(Typography, {
1203
+ variant: "uiText14Regular",
1204
+ className: classes.loadMoreText,
1205
+ children: "Loading more logs..."
1206
+ })]
1207
+ }), onLoadMoreLogs && !hasMoreLogs && logs.length > 0 && jsx(Box, {
1208
+ className: classes.endMessage,
1209
+ children: jsx(Typography, {
1210
+ variant: "uiText14Regular",
1211
+ children: "No more logs to load"
1212
+ })
1213
+ })]
1214
+ });
1215
+ });
1216
+ LogsTable.displayName = "LogsTable";
1217
+ const useStyles$3 = tss.withParams().create(({
1218
+ theme,
1219
+ tableHeight
1220
+ }) => ({
1221
+ tableSection: {
1222
+ display: "flex",
1223
+ flexDirection: "column",
1224
+ alignItems: "center",
1225
+ gap: theme.spacing(4),
1226
+ alignSelf: "stretch",
1227
+ flex: 1,
1228
+ height: "auto"
1229
+ },
1230
+ centerState: {
1231
+ display: "flex",
1232
+ flexDirection: "column",
1233
+ alignItems: "center",
1234
+ justifyContent: "center",
1235
+ flex: 1,
1236
+ color: theme.palette.text.secondary
1237
+ },
1238
+ errorState: {
1239
+ display: "flex",
1240
+ alignItems: "center",
1241
+ justifyContent: "center",
1242
+ flex: 1,
1243
+ padding: theme.spacing(4)
1244
+ },
1245
+ tableContainer: Object.assign({
1246
+ width: "100%",
1247
+ flex: 1,
1248
+ overflow: "auto",
1249
+ minHeight: "560px"
1250
+ }, tableHeight && {
1251
+ maxHeight: tableHeight
1252
+ }),
1253
+ tableHead: {
1254
+ backgroundColor: theme.palette.background.default,
1255
+ borderBottom: `1px solid ${theme.palette.background.mutedSlate}`
1256
+ },
1257
+ iconHeaderCell: {
1258
+ width: 40,
1259
+ backgroundColor: theme.palette.background.default
1260
+ },
1261
+ tableHeaderCell: {
1262
+ fontWeight: 200,
1263
+ backgroundColor: theme.palette.background.default,
1264
+ color: theme.palette.text.tertiary
1265
+ },
1266
+ iconCell: {
1267
+ width: 40
1268
+ },
1269
+ iconWrapper: {
1270
+ display: "flex",
1271
+ alignItems: "center",
1272
+ justifyContent: "center"
1273
+ },
1274
+ typeCell: {
1275
+ textTransform: "capitalize",
1276
+ fontWeight: 500
1277
+ },
1278
+ errorType: {
1279
+ color: theme.palette.error.main
1280
+ },
1281
+ warningType: {
1282
+ color: theme.palette.warning.main
1283
+ },
1284
+ infoType: {
1285
+ color: theme.palette.info.main
1286
+ },
1287
+ loadMoreTrigger: {
1288
+ display: "flex",
1289
+ flexDirection: "column",
1290
+ alignItems: "center",
1291
+ justifyContent: "center",
1292
+ padding: theme.spacing(4),
1293
+ color: theme.palette.text.secondary
1294
+ },
1295
+ loadMoreText: {
1296
+ marginTop: theme.spacing(1)
1297
+ },
1298
+ clickableRow: {
1299
+ cursor: "pointer",
1300
+ "&:hover": {
1301
+ backgroundColor: theme.palette.action.hover
1302
+ }
1303
+ },
1304
+ emptyStateIcon: {
1305
+ marginBottom: theme.spacing(2),
1306
+ display: "flex",
1307
+ alignItems: "center",
1308
+ justifyContent: "center"
1309
+ },
1310
+ endMessage: {
1311
+ display: "flex",
1312
+ alignItems: "center",
1313
+ justifyContent: "center",
1314
+ padding: theme.spacing(4),
1315
+ color: theme.palette.text.secondary
1316
+ },
1317
+ tableText: {
1318
+ fontSize: "20px",
1319
+ lineHeight: "24px"
1320
+ }
1321
+ }));
1322
+
1323
+ const LogFilterForm = memo(({
1324
+ onFilterChange,
1325
+ onReset,
1326
+ initialFilters
1327
+ }) => {
1328
+ var _a, _b, _c, _d;
1329
+ const {
1330
+ classes
1331
+ } = useStyles$2();
1332
+ const [fromDate, setFromDate] = useState((_a = initialFilters === null || initialFilters === void 0 ? void 0 : initialFilters.fromDate) !== null && _a !== void 0 ? _a : "");
1333
+ const [toDate, setToDate] = useState((_b = initialFilters === null || initialFilters === void 0 ? void 0 : initialFilters.toDate) !== null && _b !== void 0 ? _b : "");
1334
+ const [logType, setLogType] = useState((_c = initialFilters === null || initialFilters === void 0 ? void 0 : initialFilters.logType) !== null && _c !== void 0 ? _c : null);
1335
+ const [sortOrder, setSortOrder] = useState((_d = initialFilters === null || initialFilters === void 0 ? void 0 : initialFilters.sortOrder) !== null && _d !== void 0 ? _d : "latest");
1336
+ const typeOptions = [{
1337
+ value: "",
1338
+ displayText: "All"
1339
+ }, {
1340
+ value: "error",
1341
+ displayText: "Error"
1342
+ }, {
1343
+ value: "warning",
1344
+ displayText: "Warning"
1345
+ }, {
1346
+ value: "info",
1347
+ displayText: "Info"
1348
+ }];
1349
+ const sortOptions = [{
1350
+ value: "latest",
1351
+ displayText: "From latest"
1352
+ }, {
1353
+ value: "oldest",
1354
+ displayText: "From oldest"
1355
+ }];
1356
+ const handleReset = () => {
1357
+ setFromDate("");
1358
+ setToDate("");
1359
+ setLogType(null);
1360
+ setSortOrder("latest");
1361
+ onReset === null || onReset === void 0 ? void 0 : onReset();
1362
+ };
1363
+ useEffect(function notifyFilterChange() {
1364
+ onFilterChange === null || onFilterChange === void 0 ? void 0 : onFilterChange({
1365
+ fromDate,
1366
+ toDate,
1367
+ logType,
1368
+ sortOrder
1369
+ });
1370
+ }, [fromDate, toDate, logType, sortOrder, onFilterChange]);
1371
+ return jsxs(Box, {
1372
+ className: classes.filterSection,
1373
+ children: [jsx(Box, {
1374
+ className: classes.filterInput,
1375
+ children: jsx(VentionTextInput, {
1376
+ size: "xx-large",
1377
+ label: "From",
1378
+ value: fromDate,
1379
+ onChange: event => setFromDate(event.target.value),
1380
+ type: "date"
1381
+ })
1382
+ }), jsx(Box, {
1383
+ className: classes.filterInput,
1384
+ children: jsx(VentionTextInput, {
1385
+ size: "xx-large",
1386
+ label: "To",
1387
+ value: toDate,
1388
+ onChange: event => setToDate(event.target.value),
1389
+ type: "date"
1390
+ })
1391
+ }), jsx(Box, {
1392
+ className: classes.filterInput,
1393
+ children: jsx(VentionSelect, {
1394
+ size: "xx-large",
1395
+ variant: "outlined",
1396
+ labelText: "Type",
1397
+ value: logType,
1398
+ onChange: event => setLogType(event.target.value),
1399
+ placeholder: "Choose type",
1400
+ menuItems: typeOptions
1401
+ })
1402
+ }), jsx(Box, {
1403
+ className: classes.filterInput,
1404
+ children: jsx(VentionSelect, {
1405
+ size: "xx-large",
1406
+ variant: "outlined",
1407
+ labelText: "Sort",
1408
+ value: sortOrder,
1409
+ onChange: event => setSortOrder(event.target.value),
1410
+ menuItems: sortOptions
1411
+ })
1412
+ }), jsx(VentionButton, {
1413
+ size: "x-large",
1414
+ onClick: handleReset,
1415
+ className: classes.resetButton,
1416
+ children: "Reset"
1417
+ })]
1418
+ });
1419
+ });
1420
+ LogFilterForm.displayName = "LogFilterForm";
1421
+ const useStyles$2 = tss.create(({
1422
+ theme
1423
+ }) => ({
1424
+ filterSection: {
1425
+ display: "flex",
1426
+ alignItems: "flex-end",
1427
+ gap: theme.spacing(4),
1428
+ alignSelf: "stretch",
1429
+ flexWrap: "wrap"
1430
+ },
1431
+ filterInput: {
1432
+ flex: "1 1 200px",
1433
+ minWidth: "200px"
1434
+ },
1435
+ resetButton: {
1436
+ width: "103px",
1437
+ height: "64px",
1438
+ backgroundColor: theme.palette.background.default,
1439
+ color: theme.palette.text.primary,
1440
+ "&:hover": {
1441
+ backgroundColor: theme.palette.background.default
1442
+ }
1443
+ }
1444
+ }));
1445
+
1446
+ /******************************************************************************
1447
+ Copyright (c) Microsoft Corporation.
1448
+
1449
+ Permission to use, copy, modify, and/or distribute this software for any
1450
+ purpose with or without fee is hereby granted.
1451
+
1452
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
1453
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
1454
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
1455
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1456
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1457
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1458
+ PERFORMANCE OF THIS SOFTWARE.
1459
+ ***************************************************************************** */
1460
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
1461
+
1462
+
1463
+ function __awaiter(thisArg, _arguments, P, generator) {
1464
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1465
+ return new (P || (P = Promise))(function (resolve, reject) {
1466
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1467
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1468
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1469
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
1470
+ });
1471
+ }
1472
+
1473
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
1474
+ var e = new Error(message);
1475
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
1476
+ };
1477
+
1478
+ const LogsPagination = memo(({
1479
+ currentPage,
1480
+ totalPages,
1481
+ onPageChange
1482
+ }) => {
1483
+ const {
1484
+ classes
1485
+ } = useStyles$1();
1486
+ const handlePrevious = () => {
1487
+ if (currentPage > 1) {
1488
+ onPageChange(currentPage - 1);
1489
+ }
1490
+ };
1491
+ const handleNext = () => {
1492
+ if (currentPage < totalPages) {
1493
+ onPageChange(currentPage + 1);
1494
+ }
1495
+ };
1496
+ return jsxs(Box, {
1497
+ className: classes.paginationSection,
1498
+ children: [jsx(VentionButton, {
1499
+ size: "x-large",
1500
+ className: classes.paginationButton,
1501
+ onClick: handlePrevious,
1502
+ disabled: currentPage === 1,
1503
+ children: "\u2190"
1504
+ }), jsxs(Typography, {
1505
+ variant: "heading18SemiBold",
1506
+ children: ["Page ", currentPage, " / ", totalPages]
1507
+ }), jsx(VentionButton, {
1508
+ size: "x-large",
1509
+ className: classes.paginationButton,
1510
+ onClick: handleNext,
1511
+ disabled: currentPage === totalPages,
1512
+ children: "\u2192"
1513
+ })]
1514
+ });
1515
+ });
1516
+ LogsPagination.displayName = "LogsPagination";
1517
+ const useStyles$1 = tss.create(({
1518
+ theme
1519
+ }) => ({
1520
+ paginationSection: {
1521
+ display: "flex",
1522
+ justifyContent: "center",
1523
+ alignItems: "center",
1524
+ alignSelf: "stretch",
1525
+ gap: theme.spacing(4)
1526
+ },
1527
+ paginationButton: {
1528
+ width: "64px",
1529
+ height: "64px",
1530
+ backgroundColor: theme.palette.background.default,
1531
+ color: theme.palette.text.primary,
1532
+ "&:hover": {
1533
+ backgroundColor: theme.palette.grey[300]
1534
+ },
1535
+ "&:disabled": {
1536
+ opacity: 0.5,
1537
+ cursor: "not-allowed",
1538
+ backgroundColor: theme.palette.background.slate
1539
+ }
1540
+ }
1541
+ }));
1542
+
1543
+ const formatDateToInput = date => {
1544
+ return dayjs(date).format("YYYY-MM-DD");
1545
+ };
1546
+ const parseLogDate = dateStr => {
1547
+ const parsed = dayjs(dateStr);
1548
+ return parsed.isValid() ? parsed.valueOf() : 0;
1549
+ };
1550
+ const LogsPanel = forwardRef((props, ref) => {
1551
+ var _a, _b, _c, _d, _e, _f, _g;
1552
+ const {
1553
+ dataFetcher,
1554
+ initialFilters,
1555
+ onError,
1556
+ pagination,
1557
+ onFilterChange,
1558
+ onLogClick,
1559
+ className,
1560
+ tableHeight,
1561
+ emptyStateMessage,
1562
+ emptyStateIcon
1563
+ } = props;
1564
+ const {
1565
+ classes,
1566
+ cx
1567
+ } = useStyles();
1568
+ const [logs, setLogs] = useState([]);
1569
+ const [isLoading, setIsLoading] = useState(true);
1570
+ const [error, setError] = useState(null);
1571
+ const [filters, setFilters] = useState({
1572
+ fromDate: (_a = initialFilters === null || initialFilters === void 0 ? void 0 : initialFilters.fromDate) !== null && _a !== void 0 ? _a : null,
1573
+ toDate: (_b = initialFilters === null || initialFilters === void 0 ? void 0 : initialFilters.toDate) !== null && _b !== void 0 ? _b : null,
1574
+ logType: (_c = initialFilters === null || initialFilters === void 0 ? void 0 : initialFilters.logType) !== null && _c !== void 0 ? _c : null,
1575
+ sortOrder: (_d = initialFilters === null || initialFilters === void 0 ? void 0 : initialFilters.sortOrder) !== null && _d !== void 0 ? _d : "latest"
1576
+ });
1577
+ const paginationMode = (_e = pagination === null || pagination === void 0 ? void 0 : pagination.mode) !== null && _e !== void 0 ? _e : "none";
1578
+ const pageSize = (_f = pagination === null || pagination === void 0 ? void 0 : pagination.pageSize) !== null && _f !== void 0 ? _f : 10;
1579
+ const [currentPage, setCurrentPage] = useState((_g = pagination === null || pagination === void 0 ? void 0 : pagination.initialPage) !== null && _g !== void 0 ? _g : 1);
1580
+ const [totalPages, setTotalPages] = useState(1);
1581
+ const [hasMorePages, setHasMorePages] = useState(false);
1582
+ const fetchData = useCallback(page => __awaiter(void 0, void 0, void 0, function* () {
1583
+ let isCancelled = false;
1584
+ setIsLoading(true);
1585
+ setError(null);
1586
+ try {
1587
+ const result = yield dataFetcher({
1588
+ filters,
1589
+ page,
1590
+ pageSize
1591
+ });
1592
+ if (!isCancelled) {
1593
+ if (paginationMode === "infinite-scroll" && page > 1) {
1594
+ setLogs(prev => [...prev, ...result.logs]);
1595
+ } else {
1596
+ setLogs(result.logs);
1597
+ }
1598
+ setTotalPages(result.totalPages);
1599
+ setHasMorePages(result.hasMorePages);
1600
+ setCurrentPage(result.currentPage);
1601
+ setIsLoading(false);
1602
+ }
1603
+ } catch (error) {
1604
+ if (!isCancelled) {
1605
+ const errorMessage = error instanceof Error ? error.message : "Failed to load logs";
1606
+ setError(errorMessage);
1607
+ setIsLoading(false);
1608
+ if (onError) {
1609
+ onError(error);
1610
+ }
1611
+ }
1612
+ }
1613
+ return function cleanup() {
1614
+ isCancelled = true;
1615
+ };
1616
+ }), [dataFetcher, filters, pageSize, paginationMode, onError]);
1617
+ useEffect(function fetchDataOnChange() {
1618
+ fetchData(currentPage);
1619
+ }, [fetchData, currentPage]);
1620
+ const handlePageChange = useCallback(page => {
1621
+ setCurrentPage(page);
1622
+ }, []);
1623
+ const handleLoadMorePages = useCallback(() => {
1624
+ if (hasMorePages) {
1625
+ setCurrentPage(prev => prev + 1);
1626
+ }
1627
+ }, [hasMorePages]);
1628
+ const handleFilterChange = useCallback(next => {
1629
+ setFilters(next);
1630
+ setCurrentPage(1);
1631
+ if (paginationMode === "infinite-scroll") {
1632
+ setLogs([]);
1633
+ }
1634
+ if (onFilterChange) {
1635
+ onFilterChange(next);
1636
+ }
1637
+ }, [onFilterChange, paginationMode]);
1638
+ const handleReset = useCallback(() => {
1639
+ const resetFilters = {
1640
+ fromDate: null,
1641
+ toDate: null,
1642
+ logType: null,
1643
+ sortOrder: "latest"
1644
+ };
1645
+ setFilters(resetFilters);
1646
+ setCurrentPage(1);
1647
+ if (paginationMode === "infinite-scroll") {
1648
+ setLogs([]);
1649
+ }
1650
+ if (onFilterChange) {
1651
+ onFilterChange(resetFilters);
1652
+ }
1653
+ }, [onFilterChange, paginationMode]);
1654
+ const refresh = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
1655
+ yield fetchData(currentPage);
1656
+ }), [fetchData, currentPage]);
1657
+ const resetFilters = useCallback(() => {
1658
+ handleReset();
1659
+ }, [handleReset]);
1660
+ const applyFilters = useCallback(newFilters => {
1661
+ const updatedFilters = Object.assign(Object.assign({}, filters), newFilters);
1662
+ setFilters(updatedFilters);
1663
+ if (onFilterChange) {
1664
+ onFilterChange(updatedFilters);
1665
+ }
1666
+ }, [filters, onFilterChange]);
1667
+ const getCurrentFilters = useCallback(() => filters, [filters]);
1668
+ const exportLogs = useCallback(() => logs, [logs]);
1669
+ useImperativeHandle(ref, () => ({
1670
+ refresh,
1671
+ resetFilters,
1672
+ applyFilters,
1673
+ getCurrentFilters,
1674
+ exportLogs
1675
+ }), [refresh, resetFilters, applyFilters, getCurrentFilters, exportLogs]);
1676
+ return jsxs(Box, {
1677
+ className: cx(classes.root, className),
1678
+ children: [jsx(LogFilterForm, {
1679
+ onFilterChange: handleFilterChange,
1680
+ onReset: handleReset,
1681
+ initialFilters: filters
1682
+ }), jsx(LogsTable, {
1683
+ logs: logs,
1684
+ isLoading: isLoading,
1685
+ error: error,
1686
+ onLoadMoreLogs: paginationMode === "infinite-scroll" ? handleLoadMorePages : undefined,
1687
+ hasMoreLogs: paginationMode === "infinite-scroll" ? hasMorePages : undefined,
1688
+ onLogClick: onLogClick,
1689
+ tableHeight: tableHeight,
1690
+ emptyStateMessage: emptyStateMessage,
1691
+ emptyStateIcon: emptyStateIcon
1692
+ }), paginationMode === "pagination" && !isLoading && !error && logs.length > 0 && jsx(LogsPagination, {
1693
+ currentPage: currentPage,
1694
+ totalPages: totalPages,
1695
+ onPageChange: handlePageChange
1696
+ })]
1697
+ });
1698
+ });
1699
+ LogsPanel.displayName = "LogsPanel";
1700
+ const useStyles = tss.create(({
1701
+ theme
1702
+ }) => ({
1703
+ root: {
1704
+ display: "flex",
1705
+ flexDirection: "column",
1706
+ gap: theme.spacing(4),
1707
+ alignSelf: "stretch"
1708
+ }
1709
+ }));
1710
+
1711
+ export { LogFilterForm, LogsPagination, LogsPanel, LogsTable, NavigationBar, NavigationConfirmationModal, PasswordProtectionModal, Sidebar, StatusTopBar, TimeLabel, formatDateToInput, parseLogDate };