@centreon/ui 23.10.38 → 23.10.40

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 (59) hide show
  1. package/package.json +2 -1
  2. package/src/ThemeProvider/index.tsx +0 -24
  3. package/src/ThemeProvider/palettes.ts +36 -6
  4. package/src/Wizard/index.tsx +13 -3
  5. package/src/Wizard/models.ts +1 -0
  6. package/src/api/useMutationQuery/index.ts +16 -6
  7. package/src/components/Form/AccessRights/AccessRights.resource.ts +45 -0
  8. package/src/components/Form/AccessRights/AccessRightsForm.stories.tsx +59 -0
  9. package/src/components/Form/AccessRights/AccessRightsForm.styles.ts +21 -0
  10. package/src/components/Form/AccessRights/AccessRightsForm.tsx +67 -0
  11. package/src/components/Form/AccessRights/AccessRightsFormActions.tsx +80 -0
  12. package/src/components/Form/AccessRights/Input/AddAction.tsx +31 -0
  13. package/src/components/Form/AccessRights/Input/ContactAccessRightInput.stories.tsx +54 -0
  14. package/src/components/Form/AccessRights/Input/ContactAccessRightInput.tsx +72 -0
  15. package/src/components/Form/AccessRights/Input/ContactAccessRightsInput.styles.ts +22 -0
  16. package/src/components/Form/AccessRights/Input/ContactInputField.tsx +105 -0
  17. package/src/components/Form/AccessRights/Input/RoleInputField.tsx +29 -0
  18. package/src/components/Form/AccessRights/List/ContactAccessRightsList.stories.tsx +97 -0
  19. package/src/components/Form/AccessRights/List/ContactAccessRightsList.styles.ts +71 -0
  20. package/src/components/Form/AccessRights/List/ContactAccessRightsList.tsx +51 -0
  21. package/src/components/Form/AccessRights/List/ContactAccessRightsListItem.stories.tsx +116 -0
  22. package/src/components/Form/AccessRights/List/ContactAccessRightsListItem.tsx +118 -0
  23. package/src/components/Form/AccessRights/List/ContactAccessRightsListItemSkeleton.tsx +26 -0
  24. package/src/components/Form/AccessRights/List/ContactAccessRightsListSkeleton.tsx +28 -0
  25. package/src/components/Form/AccessRights/Stats/AccessRightsStats.styles.ts +18 -0
  26. package/src/components/Form/AccessRights/Stats/AccessRightsStats.tsx +41 -0
  27. package/src/components/Form/AccessRights/__fixtures__/contactAccessRight.mock.ts +52 -0
  28. package/src/components/Form/AccessRights/common/GroupLabel.styles.ts +18 -0
  29. package/src/components/Form/AccessRights/common/GroupLabel.tsx +15 -0
  30. package/src/components/Form/AccessRights/common/Input.styles.ts +48 -0
  31. package/src/components/Form/AccessRights/common/RoleInputSelect.styles.ts +11 -0
  32. package/src/components/Form/AccessRights/common/RoleInputSelect.tsx +57 -0
  33. package/src/components/Form/AccessRights/index.ts +3 -0
  34. package/src/components/Form/AccessRights/useAccessRightsForm.test.tsx +531 -0
  35. package/src/components/Form/AccessRights/useAccessRightsForm.tsx +282 -0
  36. package/src/components/Form/AccessRights/useAccessRightsForm.utils.ts +41 -0
  37. package/src/components/Form/Dashboard/DashboardForm.styles.ts +1 -7
  38. package/src/components/Form/Dashboard/DashboardForm.tsx +9 -12
  39. package/src/components/Form/Form.resource.ts +1 -0
  40. package/src/components/Form/Form.styles.ts +17 -0
  41. package/src/components/Form/{Dashboard/DashboardFormActions.tsx → FormActions.tsx} +17 -16
  42. package/src/components/Form/index.ts +1 -0
  43. package/src/components/Header/PageHeader/PageHeader.stories.tsx +9 -0
  44. package/src/components/List/Item/Avatar.tsx +21 -0
  45. package/src/components/List/Item/AvatarSkeleton.tsx +17 -0
  46. package/src/components/List/Item/ListItem.stories.tsx +41 -0
  47. package/src/components/List/Item/ListItem.styles.ts +57 -0
  48. package/src/components/List/Item/ListItem.tsx +32 -0
  49. package/src/components/List/Item/Text.tsx +25 -0
  50. package/src/components/List/Item/TextSkeleton.tsx +22 -0
  51. package/src/components/List/Item/index.ts +14 -0
  52. package/src/components/List/List.stories.tsx +70 -0
  53. package/src/components/List/List.styles.ts +8 -0
  54. package/src/components/List/List.tsx +19 -0
  55. package/src/components/List/index.ts +6 -0
  56. package/src/components/Modal/ModalActions.tsx +4 -1
  57. package/src/components/index.ts +1 -0
  58. package/src/utils/index.ts +1 -0
  59. package/src/utils/useInfiniteScrollListing.ts +106 -0
@@ -0,0 +1,25 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+
3
+ import { ListItemText as MuiListItemText } from '@mui/material';
4
+
5
+ import { useStyles } from './ListItem.styles';
6
+
7
+ type TextProps = {
8
+ primaryText: ReactNode;
9
+ secondaryText?: ReactNode;
10
+ };
11
+
12
+ export const Text = ({
13
+ primaryText,
14
+ secondaryText
15
+ }: TextProps): ReactElement => {
16
+ const { classes } = useStyles();
17
+
18
+ return (
19
+ <MuiListItemText
20
+ className={classes.text}
21
+ primary={primaryText}
22
+ secondary={secondaryText}
23
+ />
24
+ );
25
+ };
@@ -0,0 +1,22 @@
1
+ import { ReactElement } from 'react';
2
+
3
+ import { Skeleton as MuiSkeleton } from '@mui/material';
4
+
5
+ import { useStyles } from './ListItem.styles';
6
+
7
+ type TextSkeletonProps = {
8
+ secondaryText?: boolean;
9
+ };
10
+
11
+ export const TextSkeleton = ({
12
+ secondaryText
13
+ }: TextSkeletonProps): ReactElement => {
14
+ const { classes } = useStyles();
15
+
16
+ return (
17
+ <span className={classes.textSkeleton}>
18
+ <MuiSkeleton animation="wave" variant="text" />
19
+ {!!secondaryText && <MuiSkeleton animation="wave" variant="text" />}
20
+ </span>
21
+ );
22
+ };
@@ -0,0 +1,14 @@
1
+ import { ListItem as ListItemRoot } from './ListItem';
2
+ import { Text as TextRoot } from './Text';
3
+ import { TextSkeleton } from './TextSkeleton';
4
+ import { Avatar as AvatarRoot } from './Avatar';
5
+ import { AvatarSkeleton } from './AvatarSkeleton';
6
+
7
+ export const ListItem = Object.assign(ListItemRoot, {
8
+ Avatar: Object.assign(AvatarRoot, {
9
+ Skeleton: AvatarSkeleton
10
+ }),
11
+ Text: Object.assign(TextRoot, {
12
+ Skeleton: TextSkeleton
13
+ })
14
+ });
@@ -0,0 +1,70 @@
1
+ /* eslint-disable react/no-array-index-key */
2
+ import { Meta, StoryObj } from '@storybook/react';
3
+
4
+ import DeleteIcon from '@mui/icons-material/Delete';
5
+ import { Box } from '@mui/material';
6
+
7
+ import { List } from '.';
8
+
9
+ const meta: Meta<typeof List> = {
10
+ component: List
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof List>;
15
+
16
+ const sampleData = Array(50).fill({
17
+ icon: <DeleteIcon />,
18
+ primary: 'Primary text',
19
+ secondary: 'Secondary text'
20
+ });
21
+
22
+ export const Default: Story = {
23
+ render: () => (
24
+ <List>
25
+ {sampleData.map(({ primary }, index) => (
26
+ <List.Item key={`${primary}_${index}`}>
27
+ <List.Item.Text primaryText={primary} />
28
+ </List.Item>
29
+ ))}
30
+ </List>
31
+ )
32
+ };
33
+
34
+ export const withAction: Story = {
35
+ render: () => (
36
+ <List>
37
+ {sampleData.map(({ icon, primary }, index) => (
38
+ <List.Item action={icon} key={`${primary}_${index}`}>
39
+ <List.Item.Text primaryText={primary} />
40
+ </List.Item>
41
+ ))}
42
+ </List>
43
+ )
44
+ };
45
+
46
+ export const withSecondaryText: Story = {
47
+ render: () => (
48
+ <List>
49
+ {sampleData.map(({ primary, secondary }, index) => (
50
+ <List.Item key={`${primary}_${index}`}>
51
+ <List.Item.Text primaryText={primary} secondaryText={secondary} />
52
+ </List.Item>
53
+ ))}
54
+ </List>
55
+ )
56
+ };
57
+
58
+ export const withSmallContainer: Story = {
59
+ render: () => (
60
+ <Box sx={{ height: 400, width: 200 }}>
61
+ <List>
62
+ {sampleData.map(({ icon, primary, secondary }, index) => (
63
+ <List.Item action={icon} key={`${primary}_${index}`}>
64
+ <List.Item.Text primaryText={primary} secondaryText={secondary} />
65
+ </List.Item>
66
+ ))}
67
+ </List>
68
+ </Box>
69
+ )
70
+ };
@@ -0,0 +1,8 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ export const useStyles = makeStyles()({
4
+ list: {
5
+ height: '100%',
6
+ overflowY: 'auto'
7
+ }
8
+ });
@@ -0,0 +1,19 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+
3
+ import { List as MuiList } from '@mui/material';
4
+
5
+ import { useStyles } from './List.styles';
6
+
7
+ type Props = {
8
+ children: ReactNode;
9
+ };
10
+
11
+ export const List = ({ children }: Props): ReactElement => {
12
+ const { classes } = useStyles();
13
+
14
+ return (
15
+ <MuiList dense className={classes.list}>
16
+ {children}
17
+ </MuiList>
18
+ );
19
+ };
@@ -0,0 +1,6 @@
1
+ import { List as ListRoot } from './List';
2
+ import { ListItem } from './Item';
3
+
4
+ export const List = Object.assign(ListRoot, {
5
+ Item: ListItem
6
+ });
@@ -6,6 +6,7 @@ import { useStyles } from './Modal.styles';
6
6
 
7
7
  export type ModalActionsProps = {
8
8
  children?: React.ReactNode;
9
+ disabled?: boolean;
9
10
  isDanger?: boolean;
10
11
  labels?: ModalActionsLabels;
11
12
  onCancel?: () => void;
@@ -22,7 +23,8 @@ const ModalActions = ({
22
23
  labels,
23
24
  onCancel,
24
25
  onConfirm,
25
- isDanger = false
26
+ isDanger = false,
27
+ disabled
26
28
  }: ModalActionsProps): ReactElement => {
27
29
  const { classes } = useStyles();
28
30
 
@@ -42,6 +44,7 @@ const ModalActions = ({
42
44
  <Button
43
45
  aria-label={labels?.confirm}
44
46
  data-testid="confirm"
47
+ disabled={disabled}
45
48
  isDanger={isDanger}
46
49
  size="small"
47
50
  type="submit"
@@ -6,3 +6,4 @@ export * from './DataTable';
6
6
  export * from './Layout';
7
7
  export * from './Tooltip';
8
8
  export * from './Menu';
9
+ export * from './List';
@@ -16,3 +16,4 @@ export * from './useViewportIntersection';
16
16
  export * from './panelSize';
17
17
  export * from './sanitizedHTML';
18
18
  export * from './docsURL';
19
+ export * from './useInfiniteScrollListing';
@@ -0,0 +1,106 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+
3
+ import { equals, gte, isNil, reduce } from 'ramda';
4
+ import { PrimitiveAtom, useAtom } from 'jotai';
5
+ import { JsonDecoder } from 'ts.data.json';
6
+
7
+ import {
8
+ buildListingEndpoint,
9
+ useFetchQuery,
10
+ useIntersectionObserver
11
+ } from '@centreon/ui';
12
+
13
+ import type { Listing } from '../api/models';
14
+
15
+ const limit = 100;
16
+
17
+ interface UseInfiniteScrollListing<T> {
18
+ elementRef: (node) => void;
19
+ elements: Array<T>;
20
+ isLoading: boolean;
21
+ }
22
+
23
+ interface UseInfiniteScrollListingProps<T> {
24
+ decoder: JsonDecoder.Decoder<Listing<T>>;
25
+ endpoint: string;
26
+ pageAtom: PrimitiveAtom<number>;
27
+ queryKeyName: string;
28
+ }
29
+
30
+ export const useInfiniteScrollListing = <T>({
31
+ queryKeyName,
32
+ endpoint,
33
+ decoder,
34
+ pageAtom
35
+ }: UseInfiniteScrollListingProps<T>): UseInfiniteScrollListing<T> => {
36
+ const [maxPage, setMaxPage] = useState(1);
37
+
38
+ const elements = useRef<Array<T> | undefined>(undefined);
39
+
40
+ const [page, setPage] = useAtom(pageAtom);
41
+
42
+ const { data, isLoading, prefetchNextPage, fetchStatus } = useFetchQuery<
43
+ Listing<T>
44
+ >({
45
+ decoder,
46
+ getEndpoint: (params) =>
47
+ buildListingEndpoint({
48
+ baseEndpoint: endpoint,
49
+ parameters: { limit, page: params?.page || page }
50
+ }),
51
+ getQueryKey: () => [queryKeyName, page],
52
+ isPaginated: true,
53
+ queryOptions: {
54
+ refetchOnMount: false,
55
+ refetchOnWindowFocus: false,
56
+ suspense: equals(page, 1)
57
+ }
58
+ });
59
+
60
+ const elementRef = useIntersectionObserver({
61
+ action: () => {
62
+ setPage((currentPage) => currentPage + 1);
63
+ },
64
+ loading: !equals(fetchStatus, 'idle'),
65
+ maxPage,
66
+ page
67
+ });
68
+
69
+ elements.current = useMemo(() => {
70
+ if (isNil(data) || !equals(fetchStatus, 'idle')) {
71
+ return elements.current;
72
+ }
73
+
74
+ return reduce<T, Array<T>>(
75
+ (acc, element) => [...acc, element],
76
+ equals(page, 1) ? [] : elements.current || [],
77
+ data.result
78
+ );
79
+ }, [page, data, fetchStatus]);
80
+
81
+ useEffect(() => {
82
+ if (isNil(data)) {
83
+ return;
84
+ }
85
+
86
+ const total = data.meta.total || 1;
87
+ const newMaxPage = Math.ceil(total / limit);
88
+
89
+ setMaxPage(newMaxPage);
90
+
91
+ if (gte(page, newMaxPage)) {
92
+ return;
93
+ }
94
+
95
+ prefetchNextPage({
96
+ getPrefetchQueryKey: (newPage) => [`dashboards`, newPage],
97
+ page
98
+ });
99
+ }, [data]);
100
+
101
+ return {
102
+ elementRef,
103
+ elements: elements.current || [],
104
+ isLoading
105
+ };
106
+ };