@cccsaurora/howler-ui 2.19.0-dev.920 → 2.19.0-dev.921

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.
@@ -30,6 +30,10 @@ const TuiListBaseRoot = styled('div')(({ theme }) => ({
30
30
  display: 'flex',
31
31
  backgroundColor: emphasize(theme.palette.background.default, 0.04)
32
32
  }
33
+ },
34
+ '.elementDisabled': {
35
+ color: theme.palette.text.disabled,
36
+ cursor: 'default'
33
37
  }
34
38
  }));
35
39
  const TuiListBase = ({ keyboard = false, onSelect, children }) => {
@@ -5,11 +5,14 @@ import VSBoxElement from '../layout/vsbox/VSBoxElement';
5
5
  const TuiListElement = ({ position, item, onSelect: onClick, children }) => {
6
6
  const elementEl = useRef();
7
7
  const onItemClick = useCallback(_event => {
8
- if (onClick) {
8
+ if (onClick && !item.disabled) {
9
9
  onClick(item, position);
10
10
  }
11
11
  }, [onClick, item, position]);
12
12
  const classRenderer = useCallback(() => {
13
+ if (item.disabled) {
14
+ return 'elementDisabled';
15
+ }
13
16
  const _classes = ['elementHover'];
14
17
  if (item.cursor) {
15
18
  _classes.push('elementFocus');
@@ -18,7 +21,7 @@ const TuiListElement = ({ position, item, onSelect: onClick, children }) => {
18
21
  _classes.push('elementSelected');
19
22
  }
20
23
  return _classes.join(' ');
21
- }, [item.cursor, item.selected]);
22
- return (_jsx(VSBoxElement, { focus: !!item.cursor, children: _jsx("div", { ref: elementEl, "data-tuilist-index": position, "data-tuilist-id": item.id, "data-tuilist-focus": !!item.cursor, "data-tuilist-selected": !!item.selected, onClick: onItemClick, children: children({ item, position }, classRenderer) }) }));
24
+ }, [item.cursor, item.selected, item.disabled]);
25
+ return (_jsx(VSBoxElement, { focus: !!item.cursor, children: _jsx("div", { ref: elementEl, "data-tuilist-index": position, "data-tuilist-id": item.id, "data-tuilist-focus": !!item.cursor, "data-tuilist-selected": !!item.selected, "aria-disabled": item.disabled, onClick: onItemClick, children: children({ item, position }, classRenderer) }) }));
23
26
  };
24
27
  export default memo(TuiListElement);
@@ -8,6 +8,7 @@ export type TuiListItem<T> = {
8
8
  details?: boolean;
9
9
  cursor?: boolean;
10
10
  selected?: boolean;
11
+ disabled?: boolean;
11
12
  item: T;
12
13
  };
13
14
  export type TuiListItemProps<T> = {
@@ -1,5 +1,9 @@
1
1
  import type { FC } from 'react';
2
2
  declare const ConfirmDeleteModal: FC<{
3
3
  onConfirm: () => void;
4
+ title?: string;
5
+ description?: string;
6
+ preferDelete?: boolean;
7
+ preferCancel?: boolean;
4
8
  }>;
5
9
  export default ConfirmDeleteModal;
@@ -3,13 +3,15 @@ import { Button, Stack, Typography } from '@mui/material';
3
3
  import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
4
4
  import { useCallback, useContext } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
- const ConfirmDeleteModal = ({ onConfirm }) => {
6
+ const ConfirmDeleteModal = ({ onConfirm, title, description, preferDelete, preferCancel }) => {
7
7
  const { t } = useTranslation();
8
8
  const { close } = useContext(ModalContext);
9
9
  const handleConfirm = useCallback(() => {
10
10
  onConfirm();
11
11
  close();
12
12
  }, [close, onConfirm]);
13
- return (_jsxs(Stack, { spacing: 2, p: 2, alignItems: "start", sx: { minWidth: '500px' }, children: [_jsx(Typography, { variant: "h4", children: t('modal.confirm.delete.title') }), _jsx(Typography, { children: t('modal.confirm.delete.description') }), _jsxs(Stack, { direction: "row", spacing: 1, alignSelf: "end", children: [_jsx(Button, { variant: "outlined", onClick: close, children: t('cancel') }), _jsx(Button, { variant: "outlined", onClick: handleConfirm, children: t('confirm') })] })] }));
13
+ const modalTitle = title ?? t('modal.confirm.delete.title');
14
+ const modalDesc = description ?? t('modal.confirm.delete.description');
15
+ return (_jsxs(Stack, { spacing: 2, p: 2, alignItems: "start", sx: { minWidth: '500px' }, children: [_jsx(Typography, { variant: "h4", children: modalTitle }), _jsx(Typography, { children: modalDesc }), _jsxs(Stack, { direction: "row", spacing: 1, alignSelf: "end", children: [_jsx(Button, { variant: preferCancel ? 'contained' : 'outlined', onClick: close, children: t('button.cancel') }), _jsx(Button, { variant: preferDelete ? 'contained' : 'outlined', onClick: handleConfirm, children: t('button.delete') })] })] }));
14
16
  };
15
17
  export default ConfirmDeleteModal;
@@ -1,7 +1,9 @@
1
1
  import type { Template } from '@cccsaurora/howler-ui/models/entities/generated/Template';
2
- import type { FC } from 'react';
2
+ import { type FC } from 'react';
3
3
  declare const TemplateCard: FC<{
4
4
  template: Template;
5
+ onRemove?: (templateId: string) => void;
6
+ error?: boolean;
5
7
  className?: string;
6
8
  }>;
7
9
  export default TemplateCard;
@@ -1,13 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Language, Lock, Person } from '@mui/icons-material';
3
- import { Card, Divider, Stack, Tooltip, Typography } from '@mui/material';
2
+ import { Language, Lock, Person, ReportProblem } from '@mui/icons-material';
3
+ import { Button, Card, Divider, Stack, Tooltip, Typography } from '@mui/material';
4
+ import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
5
+ import ConfirmDeleteModal from '@cccsaurora/howler-ui/components/elements/display/modals/ConfirmDeleteModal';
6
+ import { useContext } from 'react';
4
7
  import { useTranslation } from 'react-i18next';
5
- const TemplateCard = ({ template, className }) => {
8
+ const TemplateCard = ({ template, onRemove, error, className }) => {
6
9
  const { t } = useTranslation();
10
+ const { showModal } = useContext(ModalContext);
7
11
  return (_jsx(Card, { variant: "outlined", sx: { p: 1, mb: 1 }, className: className, children: _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Tooltip, { title: t(`route.templates.manager.${template.type}`), children: {
8
12
  readonly: _jsx(Lock, {}),
9
13
  global: _jsx(Language, {}),
10
14
  personal: _jsx(Person, {})
11
- }[template.type] }), _jsx(Divider, { orientation: "vertical", flexItem: true }), _jsxs(Stack, { children: [_jsxs(Typography, { variant: "body1", children: [t(template.analytic), " - ", t(template.detection ?? 'all')] }), template.keys.map(key => (_jsx(Typography, { variant: "caption", sx: { ml: 1 }, children: _jsx("code", { children: key }) }, template.template_id + key)))] })] }) }, template.template_id));
15
+ }[template.type] }), _jsx(Divider, { orientation: "vertical", flexItem: true }), _jsxs(Stack, { children: [_jsxs(Typography, { variant: "body1", children: [t(template.analytic), " - ", t(template.detection ?? 'all')] }), template.keys.map(key => (_jsx(Typography, { variant: "caption", sx: { ml: 1 }, children: _jsx("code", { children: key }) }, template.template_id + key)))] }), error && (_jsx(Stack, { direction: "row", justifyContent: "end", width: "100%", children: _jsx(Stack, { children: _jsx(Tooltip, { title: t('route.templates.manager.error.action'), children: _jsx(Button, { startIcon: _jsx(ReportProblem, {}), color: "warning", onClick: () => showModal(_jsx(ConfirmDeleteModal, { onConfirm: () => onRemove?.(template.template_id), title: t('route.templates.manager.error.modal.title'), description: t('route.templates.manager.error.modal.description'), preferDelete: true })), children: t('route.templates.manager.error.message') }) }) }) }))] }) }, template.template_id));
12
16
  };
13
17
  export default TemplateCard;
@@ -3,6 +3,7 @@ import { Article } from '@mui/icons-material';
3
3
  import { Stack, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material';
4
4
  import api from '@cccsaurora/howler-ui/api';
5
5
  import { useAppUser } from '@cccsaurora/howler-ui/commons/components/app/hooks';
6
+ import { AnalyticContext } from '@cccsaurora/howler-ui/components/app/providers/AnalyticProvider';
6
7
  import { TuiListProvider } from '@cccsaurora/howler-ui/components/elements/addons/lists';
7
8
  import { TuiListMethodContext } from '@cccsaurora/howler-ui/components/elements/addons/lists/TuiListProvider';
8
9
  import ItemManager from '@cccsaurora/howler-ui/components/elements/display/ItemManager';
@@ -19,8 +20,9 @@ const TemplatesBase = () => {
19
20
  const navigate = useNavigate();
20
21
  const { dispatchApi } = useMyApi();
21
22
  const [searchParams, setSearchParams] = useSearchParams();
22
- const { load } = useContext(TuiListMethodContext);
23
+ const { load, remove } = useContext(TuiListMethodContext);
23
24
  const pageCount = useMyLocalStorageItem(StorageKey.PAGE_COUNT, 25)[0];
25
+ const { analytics } = useContext(AnalyticContext);
24
26
  const [phrase, setPhrase] = useState('');
25
27
  const [offset, setOffset] = useState(parseInt(searchParams.get('offset')) || 0);
26
28
  const [response, setResponse] = useState(null);
@@ -57,17 +59,22 @@ const TemplatesBase = () => {
57
59
  }, [phrase, setSearchParams, searchParams, user.username, types, dispatchApi, pageCount, offset]);
58
60
  // Load the items into list when response changes.
59
61
  // This hook should only trigger when the 'response' changes.
62
+ // or if the analytic list changes to refresh the disabled state
60
63
  useEffect(() => {
61
64
  if (response) {
62
65
  load(response.items.map((item) => ({
63
66
  id: item.template_id,
64
67
  item,
65
68
  selected: false,
66
- cursor: false
69
+ cursor: false,
70
+ disabled: item.detection &&
71
+ !analytics
72
+ .find(v => v.name === item.analytic)
73
+ ?.detections?.map((s) => s.toLowerCase())
74
+ ?.includes(item.detection?.toLowerCase())
67
75
  })));
68
76
  }
69
- // eslint-disable-next-line react-hooks/exhaustive-deps
70
- }, [response, load]);
77
+ }, [response, load, analytics]);
71
78
  const onPageChange = useCallback((_offset) => {
72
79
  if (_offset !== offset) {
73
80
  searchParams.set('offset', _offset.toString());
@@ -96,12 +103,22 @@ const TemplatesBase = () => {
96
103
  }
97
104
  // eslint-disable-next-line react-hooks/exhaustive-deps
98
105
  }, [offset]);
99
- const renderer = useCallback((item, className) => _jsx(TemplateCard, { template: item, className: className }), []);
106
+ const removeTemplate = useCallback(async (templateId) => {
107
+ await dispatchApi(api.template.del(templateId), {
108
+ logError: false,
109
+ showError: true,
110
+ throwError: false
111
+ });
112
+ remove(templateId);
113
+ }, [dispatchApi, remove]);
114
+ const renderer = useCallback((item, error, className) => (_jsx(TemplateCard, { template: item, error: error, onRemove: removeTemplate, className: className })), [removeTemplate]);
100
115
  return (_jsx(ItemManager, { onSearch: onSearch, onPageChange: onPageChange, phrase: phrase, setPhrase: setPhrase, hasError: hasError, searching: searching, searchFilters: _jsx(Stack, { direction: "row", spacing: 1, alignItems: "center", children: _jsxs(ToggleButtonGroup, { sx: { display: 'grid', gridTemplateColumns: '1fr 1fr', alignSelf: 'start' }, size: "small", value: types, onChange: (__, _types) => {
101
116
  if (_types) {
102
117
  setTypes(_types.length < 2 ? _types : []);
103
118
  }
104
- }, children: [_jsx(ToggleButton, { value: "personal", "aria-label": "personal", children: t('route.templates.manager.personal') }), _jsx(ToggleButton, { value: "global", "aria-label": "global", children: t('route.templates.manager.global') })] }) }), aboveSearch: _jsx(Typography, { sx: theme => ({ fontStyle: 'italic', color: theme.palette.text.disabled, mb: 0.5 }), variant: "body2", children: t('route.templates.search.prompt') }), renderer: ({ item }, classRenderer) => renderer(item.item, classRenderer()), response: response, onSelect: (item) => navigate(`/templates/view?type=${item.item.type}&analytic=${item.item.analytic}${item.item.detection ? '&detection=' + item.item.detection : ''}`), onCreate: () => navigate('/templates/view'), createPrompt: "route.templates.create", searchPrompt: "route.templates.manager.search", createIcon: _jsx(Article, { sx: { mr: 1 } }) }));
119
+ }, children: [_jsx(ToggleButton, { value: "personal", "aria-label": "personal", children: t('route.templates.manager.personal') }), _jsx(ToggleButton, { value: "global", "aria-label": "global", children: t('route.templates.manager.global') })] }) }), aboveSearch: _jsx(Typography, { sx: theme => ({ fontStyle: 'italic', color: theme.palette.text.disabled, mb: 0.5 }), variant: "body2", children: t('route.templates.search.prompt') }), renderer: ({ item }, classRenderer) => renderer(item.item, !!item.disabled, classRenderer()), response: response, onSelect: (item) => {
120
+ navigate(`/templates/view?type=${item.item.type}&analytic=${item.item.analytic}${item.item.detection ? '&detection=' + item.item.detection : ''}`);
121
+ }, onCreate: () => navigate('/templates/view'), createPrompt: "route.templates.create", searchPrompt: "route.templates.manager.search", createIcon: _jsx(Article, { sx: { mr: 1 } }) }));
105
122
  };
106
123
  const Templates = () => {
107
124
  return (_jsx(TuiListProvider, { children: _jsx(TemplatesBase, {}) }));
@@ -670,6 +670,10 @@
670
670
  "route.templates.manager.personal": "Personal",
671
671
  "route.templates.manager.readonly": "Built-in",
672
672
  "route.templates.manager.search": "Search Templates",
673
+ "route.templates.manager.error.message": "Invalid Detection",
674
+ "route.templates.manager.error.action": "Click to open quick fix options",
675
+ "route.templates.manager.error.modal.title": "Template detection no longer exists",
676
+ "route.templates.manager.error.modal.description": "The template fields are read only and will not be used. Do you want to remove the template?",
673
677
  "route.templates.personal": "Personal",
674
678
  "route.templates.prompt": "Activate autocomplete using [ctrl + space].",
675
679
  "route.templates.readonly.warning": "This is a built-in template, and cannot be edited. To make changes to it, contact",
@@ -674,6 +674,10 @@
674
674
  "route.templates.manager.personal": "Personnel",
675
675
  "route.templates.manager.readonly": "Intégré",
676
676
  "route.templates.manager.search": "Rechercher les modèles",
677
+ "route.templates.manager.error.message": "Détection non valide",
678
+ "route.templates.manager.error.action": "Cliquez ici pour afficher les options de correction rapide",
679
+ "route.templates.manager.error.modal.title": "La détection du modèle n'existe plus",
680
+ "route.templates.manager.error.modal.description": "Les clés du modèle sont en lecture seule et ne seront pas utilisées. Voulez-vous supprimer le modèle ?",
677
681
  "route.templates.personal": "Personnel",
678
682
  "route.templates.prompt": "Activer l'autocomplétion en utilisant [ctrl + espace].",
679
683
  "route.templates.readonly.warning": "Il s'agit d'un modèle intégré qui ne peut pas être modifié. Pour le modifier, veuillez contacter",
package/package.json CHANGED
@@ -96,7 +96,7 @@
96
96
  "internal-slot": "1.0.7"
97
97
  },
98
98
  "type": "module",
99
- "version": "2.19.0-dev.920",
99
+ "version": "2.19.0-dev.921",
100
100
  "exports": {
101
101
  "./i18n": "./i18n.js",
102
102
  "./index.css": "./index.css",