@centreon/ui 23.10.1-sync-dev-23-10-x-develop-oss.0 → 23.10.1-sync-release-34346.0

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.
Files changed (94) hide show
  1. package/package.json +6 -1
  2. package/public/mockServiceWorker.js +2 -2
  3. package/src/Form/Inputs/ConnectedAutocomplete.tsx +3 -1
  4. package/src/Form/Inputs/models.ts +1 -0
  5. package/src/Graph/Gauge/Gauge.stories.tsx +15 -2
  6. package/src/Graph/HeatMap/HeatMap.stories.tsx +105 -0
  7. package/src/Graph/HeatMap/HeatMap.styles.tsx +29 -0
  8. package/src/Graph/HeatMap/HeatMap.tsx +12 -0
  9. package/src/Graph/HeatMap/HeatMapData.json +137 -0
  10. package/src/Graph/HeatMap/ResponsiveHeatMap.tsx +98 -0
  11. package/src/Graph/HeatMap/index.ts +1 -0
  12. package/src/Graph/HeatMap/model.ts +33 -0
  13. package/src/Graph/LineChart/BasicComponents/Lines/RegularLines/index.tsx +1 -1
  14. package/src/Graph/LineChart/BasicComponents/useFilterLines.ts +2 -4
  15. package/src/Graph/LineChart/Legend/InteractiveValue.tsx +3 -3
  16. package/src/Graph/LineChart/Legend/Legend.styles.ts +78 -32
  17. package/src/Graph/LineChart/Legend/LegendContent.tsx +12 -6
  18. package/src/Graph/LineChart/Legend/LegendHeader.tsx +69 -21
  19. package/src/Graph/LineChart/Legend/index.tsx +51 -23
  20. package/src/Graph/LineChart/Legend/models.ts +5 -0
  21. package/src/Graph/LineChart/Legend/useLegend.ts +9 -10
  22. package/src/Graph/LineChart/LineChart.tsx +17 -3
  23. package/src/Graph/index.ts +1 -0
  24. package/src/InputField/Select/Autocomplete/Connected/Multi/index.test.tsx +1 -0
  25. package/src/InputField/Select/Autocomplete/Connected/index.test.tsx +1 -0
  26. package/src/InputField/Select/Autocomplete/Connected/index.tsx +15 -3
  27. package/src/InputField/Text/index.tsx +6 -1
  28. package/src/Module/LicensedModule/LicenseCheck/index.tsx +24 -9
  29. package/src/Module/LicensedModule/index.tsx +7 -1
  30. package/src/RichTextEditor/RichTextEditor.cypress.spec.tsx +100 -52
  31. package/src/RichTextEditor/RichTextEditor.tsx +9 -3
  32. package/src/RichTextEditor/plugins/ToolbarPlugin/AlignPicker.tsx +21 -20
  33. package/src/RichTextEditor/plugins/ToolbarPlugin/BlockButtons.tsx +35 -77
  34. package/src/RichTextEditor/plugins/ToolbarPlugin/FormatButtons.tsx +29 -83
  35. package/src/RichTextEditor/plugins/ToolbarPlugin/LinkButton.tsx +106 -0
  36. package/src/RichTextEditor/plugins/ToolbarPlugin/ListButton.tsx +155 -0
  37. package/src/RichTextEditor/plugins/ToolbarPlugin/MacrosButton.tsx +23 -39
  38. package/src/RichTextEditor/plugins/ToolbarPlugin/ToolbarPlugin.styles.ts +24 -14
  39. package/src/RichTextEditor/plugins/ToolbarPlugin/UndoRedoButtons.tsx +6 -0
  40. package/src/RichTextEditor/plugins/ToolbarPlugin/index.tsx +5 -10
  41. package/src/RichTextEditor/translatedLabels.ts +3 -0
  42. package/src/ThemeProvider/palettes.ts +4 -4
  43. package/src/Typography/EllipsisTypography.tsx +18 -0
  44. package/src/Typography/Subtitle.tsx +55 -0
  45. package/src/api/buildListingEndpoint/models.ts +2 -1
  46. package/src/api/customFetch.ts +12 -3
  47. package/src/api/useFetchQuery/index.ts +22 -5
  48. package/src/api/useGraphQuery/index.ts +4 -1
  49. package/src/api/useMutationQuery/index.ts +12 -2
  50. package/src/components/Form/AccessRightsV2/AccessRights.cypress.spec.tsx +350 -0
  51. package/src/components/Form/AccessRightsV2/AccessRights.stories.tsx +119 -0
  52. package/src/components/Form/AccessRightsV2/AccessRights.styles.ts +10 -0
  53. package/src/components/Form/AccessRightsV2/AccessRights.tsx +60 -0
  54. package/src/components/Form/AccessRightsV2/Actions/Actions.styles.ts +14 -0
  55. package/src/components/Form/AccessRightsV2/Actions/Actions.tsx +81 -0
  56. package/src/components/Form/AccessRightsV2/Actions/useActions.ts +79 -0
  57. package/src/components/Form/AccessRightsV2/List/Item.tsx +90 -0
  58. package/src/components/Form/AccessRightsV2/List/List.styles.tsx +45 -0
  59. package/src/components/Form/AccessRightsV2/List/List.tsx +52 -0
  60. package/src/components/Form/AccessRightsV2/List/ListItemSkeleton.tsx +18 -0
  61. package/src/components/Form/AccessRightsV2/List/ListSkeleton.tsx +22 -0
  62. package/src/components/Form/AccessRightsV2/List/RemoveAccessRight.tsx +38 -0
  63. package/src/components/Form/AccessRightsV2/List/StateChip.tsx +28 -0
  64. package/src/components/Form/AccessRightsV2/List/useItem.ts +64 -0
  65. package/src/components/Form/AccessRightsV2/Provider.tsx +15 -0
  66. package/src/components/Form/AccessRightsV2/ShareInput/ContactSwitch.tsx +42 -0
  67. package/src/components/Form/AccessRightsV2/ShareInput/ShareInput.styles.ts +9 -0
  68. package/src/components/Form/AccessRightsV2/ShareInput/ShareInput.tsx +77 -0
  69. package/src/components/Form/AccessRightsV2/ShareInput/useShareInput.tsx +109 -0
  70. package/src/components/Form/AccessRightsV2/Stats/Stats.tsx +36 -0
  71. package/src/components/Form/AccessRightsV2/atoms.ts +97 -0
  72. package/src/components/Form/AccessRightsV2/common/RoleSelectField.styles.tsx +7 -0
  73. package/src/components/Form/AccessRightsV2/common/RoleSelectField.tsx +45 -0
  74. package/src/components/Form/AccessRightsV2/models.ts +54 -0
  75. package/src/components/Form/AccessRightsV2/storiesData.ts +221 -0
  76. package/src/components/Form/AccessRightsV2/useAccessRightsInitValues.ts +51 -0
  77. package/src/components/Form/index.ts +1 -0
  78. package/src/components/Header/PageHeader/PageHeader.styles.ts +28 -1
  79. package/src/components/Header/PageHeader/PageHeaderMessage.tsx +32 -0
  80. package/src/components/Header/PageHeader/index.ts +2 -0
  81. package/src/components/ItemComposition/Item.tsx +2 -2
  82. package/src/components/ItemComposition/ItemComposition.styles.ts +3 -0
  83. package/src/components/ItemComposition/ItemComposition.tsx +4 -2
  84. package/src/components/List/Item/ListItem.styles.ts +6 -0
  85. package/src/components/List/Item/ListItem.tsx +3 -1
  86. package/src/components/List/Item/Text.tsx +5 -3
  87. package/src/components/Menu/Button/MenuButton.tsx +4 -2
  88. package/src/components/Menu/MenuItem.tsx +5 -3
  89. package/src/components/Menu/MenuItems.tsx +4 -3
  90. package/src/components/Modal/Modal.styles.ts +4 -2
  91. package/src/index.ts +5 -0
  92. package/src/utils/useInfiniteScrollListing.ts +22 -7
  93. package/src/utils/useLocaleDateTimeFormat/index.ts +3 -0
  94. package/src/Graph/LineChart/Legend/Marker.tsx +0 -43
@@ -0,0 +1,119 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { rest } from 'msw';
3
+
4
+ import { SnackbarProvider } from '../../..';
5
+
6
+ import AccessRightsForm from './AccessRights';
7
+ import {
8
+ accessRightsWithStates,
9
+ buildResult,
10
+ defaultAccessRights,
11
+ emptyAccessRights,
12
+ labels,
13
+ roles
14
+ } from './storiesData';
15
+
16
+ const meta: Meta<typeof AccessRightsForm> = {
17
+ component: AccessRightsForm,
18
+ parameters: {
19
+ msw: {
20
+ handlers: [
21
+ rest.get('api/latest/contact?**', (req, res, ctx) => {
22
+ return res(ctx.json(buildResult(false)));
23
+ }),
24
+ rest.get('api/latest/contactGroup?**', (req, res, ctx) => {
25
+ return res(ctx.json(buildResult(true)));
26
+ })
27
+ ]
28
+ }
29
+ }
30
+ };
31
+
32
+ const Template = (args): JSX.Element => (
33
+ <SnackbarProvider>
34
+ <AccessRightsForm {...args} />
35
+ </SnackbarProvider>
36
+ );
37
+
38
+ export default meta;
39
+ type Story = StoryObj<typeof AccessRightsForm>;
40
+
41
+ export const Default: Story = {
42
+ args: {
43
+ cancel: () => undefined,
44
+ endpoints: {
45
+ contact: '/contact',
46
+ contactGroup: '/contactGroup'
47
+ },
48
+ initialValues: defaultAccessRights,
49
+ labels,
50
+ link: 'link',
51
+ roles,
52
+ submit: () => undefined
53
+ },
54
+ render: Template
55
+ };
56
+
57
+ export const AccessRightsWithStates: Story = {
58
+ args: {
59
+ cancel: () => undefined,
60
+ endpoints: {
61
+ contact: '/contact',
62
+ contactGroup: '/contactGroup'
63
+ },
64
+ initialValues: accessRightsWithStates,
65
+ labels,
66
+ link: 'link',
67
+ roles,
68
+ submit: () => undefined
69
+ },
70
+ render: Template
71
+ };
72
+
73
+ export const withEmptyState: Story = {
74
+ args: {
75
+ cancel: () => undefined,
76
+ endpoints: {
77
+ contact: '/contact',
78
+ contactGroup: '/contactGroup'
79
+ },
80
+ initialValues: emptyAccessRights,
81
+ labels,
82
+ link: 'link',
83
+ roles,
84
+ submit: () => undefined
85
+ },
86
+ render: Template
87
+ };
88
+
89
+ export const withoutLink: Story = {
90
+ args: {
91
+ cancel: () => undefined,
92
+ endpoints: {
93
+ contact: '/contact',
94
+ contactGroup: '/contactGroup'
95
+ },
96
+ initialValues: defaultAccessRights,
97
+ labels,
98
+ roles,
99
+ submit: () => undefined
100
+ },
101
+ render: Template
102
+ };
103
+
104
+ export const loading: Story = {
105
+ args: {
106
+ cancel: () => undefined,
107
+ endpoints: {
108
+ contact: '/contact',
109
+ contactGroup: '/contactGroup'
110
+ },
111
+ initialValues: emptyAccessRights,
112
+ labels,
113
+ link: 'link',
114
+ loading: true,
115
+ roles,
116
+ submit: () => undefined
117
+ },
118
+ render: Template
119
+ };
@@ -0,0 +1,10 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ export const useAccessRightsStyles = makeStyles()((theme) => ({
4
+ container: {
5
+ display: 'flex',
6
+ flexDirection: 'column',
7
+ gap: theme.spacing(3),
8
+ maxWidth: '520px'
9
+ }
10
+ }));
@@ -0,0 +1,60 @@
1
+ import { SelectEntry } from '../../..';
2
+
3
+ import { useAccessRightsStyles } from './AccessRights.styles';
4
+ import Actions from './Actions/Actions';
5
+ import List from './List/List';
6
+ import ListSkeleton from './List/ListSkeleton';
7
+ import Provider from './Provider';
8
+ import ShareInput from './ShareInput/ShareInput';
9
+ import Stats from './Stats/Stats';
10
+ import { AccessRightInitialValues, Endpoints, Labels } from './models';
11
+ import { useAccessRightsInitValues } from './useAccessRightsInitValues';
12
+
13
+ interface Props {
14
+ cancel: ({ dirty, values }) => void;
15
+ endpoints: Endpoints;
16
+ initialValues: Array<AccessRightInitialValues>;
17
+ isSubmitting?: boolean;
18
+ labels: Labels;
19
+ link?: string;
20
+ loading?: boolean;
21
+ roles: Array<SelectEntry>;
22
+ submit: (values: Array<AccessRightInitialValues>) => Promise<void>;
23
+ }
24
+
25
+ export const AccessRights = ({
26
+ initialValues,
27
+ roles,
28
+ endpoints,
29
+ submit,
30
+ cancel,
31
+ link,
32
+ loading,
33
+ labels,
34
+ isSubmitting
35
+ }: Props): JSX.Element => {
36
+ const { classes } = useAccessRightsStyles();
37
+ const clear = useAccessRightsInitValues({ initialValues });
38
+
39
+ return (
40
+ <div className={classes.container}>
41
+ <ShareInput endpoints={endpoints} labels={labels.add} roles={roles} />
42
+ {loading ? <ListSkeleton /> : <List labels={labels.list} roles={roles} />}
43
+ <Stats labels={labels.list} />
44
+ <Actions
45
+ cancel={cancel}
46
+ clear={clear}
47
+ isSubmitting={isSubmitting}
48
+ labels={labels.actions}
49
+ link={link}
50
+ submit={submit}
51
+ />
52
+ </div>
53
+ );
54
+ };
55
+
56
+ export default (props: Props): JSX.Element => (
57
+ <Provider>
58
+ <AccessRights {...props} />
59
+ </Provider>
60
+ );
@@ -0,0 +1,14 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ export const useActionsStyles = makeStyles()((theme) => ({
4
+ actions: {
5
+ backgroundColor: theme.palette.background.paper,
6
+ display: 'flex',
7
+ justifyContent: 'space-between'
8
+ },
9
+ cancelAndSave: {
10
+ display: 'flex',
11
+ flex: 'row',
12
+ gap: theme.spacing(2)
13
+ }
14
+ }));
@@ -0,0 +1,81 @@
1
+ import { useTranslation } from 'react-i18next';
2
+
3
+ import LinkIcon from '@mui/icons-material/Link';
4
+ import { CircularProgress } from '@mui/material';
5
+
6
+ import { Button } from '../../..';
7
+ import { AccessRightInitialValues, Labels } from '../models';
8
+
9
+ import { useActions } from './useActions';
10
+ import { useActionsStyles } from './Actions.styles';
11
+
12
+ interface Props {
13
+ cancel: ({ dirty, values }) => void;
14
+ clear: () => void;
15
+ isSubmitting?: boolean;
16
+ labels: Labels['actions'];
17
+ link?: string;
18
+ submit: (values: Array<AccessRightInitialValues>) => Promise<void>;
19
+ }
20
+
21
+ const Actions = ({
22
+ labels,
23
+ cancel,
24
+ submit,
25
+ link,
26
+ isSubmitting,
27
+ clear
28
+ }: Props): JSX.Element => {
29
+ const { t } = useTranslation();
30
+ const { classes } = useActionsStyles();
31
+
32
+ const { dirty, copyLink, save, formattedValues } = useActions({
33
+ clear,
34
+ labels,
35
+ link,
36
+ submit
37
+ });
38
+
39
+ const onCancel = (): void => {
40
+ cancel({ dirty, values: formattedValues });
41
+ };
42
+
43
+ return (
44
+ <div className={classes.actions}>
45
+ {link ? (
46
+ <Button
47
+ aria-label={t(labels.copyLink)}
48
+ icon={<LinkIcon />}
49
+ iconVariant="start"
50
+ variant="ghost"
51
+ onClick={copyLink}
52
+ >
53
+ {t(labels.copyLink)}
54
+ </Button>
55
+ ) : (
56
+ <div />
57
+ )}
58
+ <div className={classes.cancelAndSave}>
59
+ <Button
60
+ aria-label={t(labels.cancel)}
61
+ variant="secondary"
62
+ onClick={onCancel}
63
+ >
64
+ {t(labels.cancel)}
65
+ </Button>
66
+ <Button
67
+ aria-label={t(labels.save)}
68
+ disabled={isSubmitting || !dirty}
69
+ icon={isSubmitting ? <CircularProgress size={24} /> : null}
70
+ iconVariant={isSubmitting ? 'start' : 'none'}
71
+ variant="primary"
72
+ onClick={save}
73
+ >
74
+ {t(labels.save)}
75
+ </Button>
76
+ </div>
77
+ </div>
78
+ );
79
+ };
80
+
81
+ export default Actions;
@@ -0,0 +1,79 @@
1
+ import { useAtomValue } from 'jotai';
2
+ import { equals, omit } from 'ramda';
3
+
4
+ import { initialValuesAtom, valuesAtom } from '../atoms';
5
+ import { AccessRight, AccessRightInitialValues, Labels } from '../models';
6
+ import { useCopyToClipboard } from '../../../..';
7
+
8
+ const formatValue = (accessRight: AccessRight): AccessRightInitialValues => {
9
+ return omit(['isAdded', 'isUpdated', 'isRemoved'], accessRight);
10
+ };
11
+
12
+ const formatValueForSubmition = (
13
+ accessRight: AccessRight
14
+ ): AccessRightInitialValues => {
15
+ return {
16
+ ...formatValue(accessRight),
17
+ id: Number((accessRight.id as string).split('_')[1])
18
+ };
19
+ };
20
+
21
+ interface Props {
22
+ clear: () => void;
23
+ labels: Labels['actions'];
24
+ link?: string;
25
+ submit: (values: Array<AccessRightInitialValues>) => Promise<void>;
26
+ }
27
+
28
+ interface UseActionsState {
29
+ copyLink: () => void;
30
+ dirty: boolean;
31
+ formattedValues: Array<AccessRightInitialValues>;
32
+ save: () => void;
33
+ }
34
+
35
+ export const useActions = ({
36
+ link,
37
+ labels,
38
+ submit,
39
+ clear
40
+ }: Props): UseActionsState => {
41
+ const values = useAtomValue(valuesAtom);
42
+ const initialValues = useAtomValue(initialValuesAtom);
43
+
44
+ const { copy } = useCopyToClipboard({
45
+ errorMessage: labels.copyError,
46
+ successMessage: labels.copySuccess
47
+ });
48
+
49
+ const formattedValues = values
50
+ .filter(({ isRemoved }) => !isRemoved)
51
+ .map(formatValue);
52
+
53
+ const dirty = !equals(initialValues, formattedValues);
54
+
55
+ const copyLink = (): void => {
56
+ if (!link) {
57
+ return;
58
+ }
59
+ copy(link);
60
+ };
61
+
62
+ const save = (): void => {
63
+ submit(
64
+ values.filter(({ isRemoved }) => !isRemoved).map(formatValueForSubmition)
65
+ )?.then((isError) => {
66
+ if (isError) {
67
+ return;
68
+ }
69
+ clear();
70
+ });
71
+ };
72
+
73
+ return {
74
+ copyLink,
75
+ dirty,
76
+ formattedValues,
77
+ save
78
+ };
79
+ };
@@ -0,0 +1,90 @@
1
+ import { useSetAtom } from 'jotai';
2
+
3
+ import { Chip } from '@mui/material';
4
+
5
+ import { AccessRight, Labels } from '../models';
6
+ import { List } from '../../../List';
7
+ import RoleSelectField from '../common/RoleSelectField';
8
+ import { SelectEntry } from '../../../..';
9
+ import { updateContactRoleDerivedAtom } from '../atoms';
10
+
11
+ import { useItem } from './useItem';
12
+ import { useListStyles } from './List.styles';
13
+ import StateChip from './StateChip';
14
+ import RemoveAccessRight from './RemoveAccessRight';
15
+
16
+ interface Props extends AccessRight {
17
+ index: number;
18
+ labels: Labels['list'];
19
+ roles: Array<SelectEntry>;
20
+ }
21
+
22
+ const Item = ({
23
+ index,
24
+ id,
25
+ isAdded,
26
+ isContactGroup,
27
+ isRemoved,
28
+ isUpdated,
29
+ role,
30
+ name,
31
+ email,
32
+ labels,
33
+ roles
34
+ }: Props): JSX.Element => {
35
+ const { classes } = useListStyles();
36
+ const { initials, groupLabel, getState } = useItem({
37
+ isAdded,
38
+ isContactGroup,
39
+ isRemoved,
40
+ isUpdated,
41
+ list: labels,
42
+ name
43
+ });
44
+ const updateContactRole = useSetAtom(updateContactRoleDerivedAtom);
45
+
46
+ const state = getState();
47
+
48
+ const changeRole = (newRole: string): void => {
49
+ updateContactRole({ id, index, newRole });
50
+ };
51
+
52
+ return (
53
+ <List.Item
54
+ action={
55
+ <>
56
+ {state && <StateChip {...state} />}
57
+ <RoleSelectField
58
+ disabled={isRemoved}
59
+ roles={roles}
60
+ testId={`role-${name}`}
61
+ value={role}
62
+ onChange={changeRole}
63
+ />
64
+ <RemoveAccessRight index={index} isRemoved={isRemoved} name={name} />
65
+ </>
66
+ }
67
+ className={classes.item}
68
+ data-isRemoved={isRemoved}
69
+ >
70
+ <List.Item.Avatar>{initials}</List.Item.Avatar>
71
+ <div className={classes.itemNameAndGroup}>
72
+ <List.Item.Text
73
+ className={classes.name}
74
+ primaryText={name}
75
+ secondaryText={email}
76
+ />
77
+ {groupLabel && (
78
+ <Chip
79
+ data-groupChip={name}
80
+ data-type="group-chip"
81
+ label={groupLabel}
82
+ size="small"
83
+ />
84
+ )}
85
+ </div>
86
+ </List.Item>
87
+ );
88
+ };
89
+
90
+ export default Item;
@@ -0,0 +1,45 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ export const useListStyles = makeStyles()((theme) => ({
4
+ item: {
5
+ '&[data-isRemoved="true"]': {
6
+ '& .MuiAvatar-root,.MuiListItemText-root,[data-type="group-chip"]': {
7
+ filter: 'opacity(0.5)'
8
+ }
9
+ }
10
+ },
11
+ itemNameAndGroup: {
12
+ alignItems: 'center',
13
+ display: 'flex',
14
+ flexDirection: 'row',
15
+ gap: theme.spacing(3),
16
+ width: '100%'
17
+ },
18
+ list: {
19
+ borderBottom: `1px solid ${theme.palette.divider}`,
20
+ borderTop: `1px solid ${theme.palette.divider}`,
21
+ maxHeight: '35vw',
22
+ overflowY: 'auto',
23
+ width: '100%'
24
+ },
25
+ name: {
26
+ flexGrow: 0,
27
+ maxWidth: '200px',
28
+ textOverflow: 'ellipsis',
29
+ whiteSpace: 'nowrap'
30
+ },
31
+ stateChip: {
32
+ '&[data-state="added"]': {
33
+ borderColor: theme.palette.success.main,
34
+ color: theme.palette.success.main
35
+ },
36
+ '&[data-state="removed"]': {
37
+ borderColor: theme.palette.error.main,
38
+ color: theme.palette.error.main
39
+ },
40
+ '&[data-state="updated"]': {
41
+ borderColor: theme.palette.info.main,
42
+ color: theme.palette.info.main
43
+ }
44
+ }
45
+ }));
@@ -0,0 +1,52 @@
1
+ import { useAtomValue } from 'jotai';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { isEmpty } from 'ramda';
4
+
5
+ import { Typography } from '@mui/material';
6
+
7
+ import { valuesAtom } from '../atoms';
8
+ import { List as UIList } from '../../../List';
9
+ import { Labels } from '../models';
10
+ import { SelectEntry, Subtitle } from '../../../..';
11
+
12
+ import { useListStyles } from './List.styles';
13
+ import Item from './Item';
14
+
15
+ interface Props {
16
+ labels: Labels['list'];
17
+ roles: Array<SelectEntry>;
18
+ }
19
+
20
+ const List = ({ labels, roles }: Props): JSX.Element => {
21
+ const { t } = useTranslation();
22
+ const { classes } = useListStyles();
23
+ const values = useAtomValue(valuesAtom);
24
+
25
+ return (
26
+ <div>
27
+ <Subtitle>{t(labels.title)}</Subtitle>
28
+ <div className={classes.list}>
29
+ {isEmpty(values) ? (
30
+ <Typography sx={{ py: 2 }} textAlign="center">
31
+ {t(labels.empty)}
32
+ </Typography>
33
+ ) : (
34
+ <UIList>
35
+ {values.map(({ id, ...rest }, index) => (
36
+ <Item
37
+ id={id}
38
+ key={id}
39
+ labels={labels}
40
+ {...rest}
41
+ index={index}
42
+ roles={roles}
43
+ />
44
+ ))}
45
+ </UIList>
46
+ )}
47
+ </div>
48
+ </div>
49
+ );
50
+ };
51
+
52
+ export default List;
@@ -0,0 +1,18 @@
1
+ import { ReactElement } from 'react';
2
+
3
+ import { List } from '../../../List';
4
+
5
+ import { useListStyles } from './List.styles';
6
+
7
+ const ListItemSkeleton = (): ReactElement => {
8
+ const { classes } = useListStyles();
9
+
10
+ return (
11
+ <List.Item className={classes.item}>
12
+ <List.Item.Avatar.Skeleton />
13
+ <List.Item.Text.Skeleton secondaryText />
14
+ </List.Item>
15
+ );
16
+ };
17
+
18
+ export default ListItemSkeleton;
@@ -0,0 +1,22 @@
1
+ import { ReactElement } from 'react';
2
+
3
+ import { List } from '../../../List';
4
+
5
+ import { useListStyles } from './List.styles';
6
+ import ListItemSkeleton from './ListItemSkeleton';
7
+
8
+ const ListSkeleton = (): ReactElement => {
9
+ const { classes } = useListStyles();
10
+
11
+ return (
12
+ <div className={classes.list}>
13
+ <List>
14
+ {[...Array(3).keys()].map((key) => (
15
+ <ListItemSkeleton key={key} />
16
+ ))}
17
+ </List>
18
+ </div>
19
+ );
20
+ };
21
+
22
+ export default ListSkeleton;
@@ -0,0 +1,38 @@
1
+ import { useSetAtom } from 'jotai';
2
+
3
+ import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
4
+ import RotateLeftIcon from '@mui/icons-material/RotateLeft';
5
+
6
+ import { IconButton } from '../../..';
7
+ import { removeAccessRightDerivedAtom } from '../atoms';
8
+
9
+ interface Props {
10
+ index: number;
11
+ isRemoved: boolean;
12
+ name: string;
13
+ }
14
+
15
+ const RemoveAccessRight = ({ index, isRemoved, name }: Props): JSX.Element => {
16
+ const removeAccessRight = useSetAtom(removeAccessRightDerivedAtom);
17
+
18
+ const remove = (): void => removeAccessRight({ index });
19
+ const recover = (): void => removeAccessRight({ index, recover: true });
20
+
21
+ const icon = isRemoved ? (
22
+ <RotateLeftIcon fontSize="small" />
23
+ ) : (
24
+ <DeleteOutlineIcon color="error" fontSize="small" />
25
+ );
26
+
27
+ return (
28
+ <IconButton
29
+ data-removed={isRemoved}
30
+ data-testid={`remove-${name}`}
31
+ icon={icon}
32
+ size="small"
33
+ onClick={isRemoved ? recover : remove}
34
+ />
35
+ );
36
+ };
37
+
38
+ export default RemoveAccessRight;
@@ -0,0 +1,28 @@
1
+ import { Chip } from '@mui/material';
2
+
3
+ import { ItemState } from '../models';
4
+
5
+ import { useListStyles } from './List.styles';
6
+
7
+ interface Props {
8
+ label: string;
9
+ state: ItemState;
10
+ }
11
+
12
+ const StateChip = ({ label, state }: Props): JSX.Element => {
13
+ const { classes, cx } = useListStyles();
14
+
15
+ return (
16
+ <div className={cx(classes.state)}>
17
+ <Chip
18
+ className={classes.stateChip}
19
+ data-state={state}
20
+ label={label}
21
+ size="small"
22
+ variant="outlined"
23
+ />
24
+ </div>
25
+ );
26
+ };
27
+
28
+ export default StateChip;