@centreon/ui 23.10.39 → 23.10.41
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/package.json +3 -1
- package/src/Listing/Cell/DataCell.tsx +16 -4
- package/src/Listing/Cell/index.tsx +82 -2
- package/src/Listing/Listing.cypress.spec.tsx +120 -0
- package/src/Listing/Listing.styles.ts +2 -2
- package/src/Listing/ListingDocs.mdx +61 -0
- package/src/Listing/Row/Row.tsx +5 -2
- package/src/Listing/index.stories.tsx +104 -31
- package/src/Listing/index.tsx +103 -33
- package/src/Listing/models.ts +1 -0
- package/src/Listing/tableAtoms.ts +2 -0
- package/src/Panel/index.tsx +1 -1
- package/src/ThemeProvider/index.tsx +0 -24
- package/src/ThemeProvider/palettes.ts +36 -6
- package/src/api/useMutationQuery/index.ts +16 -6
- package/src/components/Form/AccessRights/AccessRights.resource.ts +45 -0
- package/src/components/Form/AccessRights/AccessRightsForm.stories.tsx +59 -0
- package/src/components/Form/AccessRights/AccessRightsForm.styles.ts +21 -0
- package/src/components/Form/AccessRights/AccessRightsForm.tsx +67 -0
- package/src/components/Form/AccessRights/AccessRightsFormActions.tsx +80 -0
- package/src/components/Form/AccessRights/Input/AddAction.tsx +31 -0
- package/src/components/Form/AccessRights/Input/ContactAccessRightInput.stories.tsx +54 -0
- package/src/components/Form/AccessRights/Input/ContactAccessRightInput.tsx +72 -0
- package/src/components/Form/AccessRights/Input/ContactAccessRightsInput.styles.ts +22 -0
- package/src/components/Form/AccessRights/Input/ContactInputField.tsx +105 -0
- package/src/components/Form/AccessRights/Input/RoleInputField.tsx +29 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsList.stories.tsx +97 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsList.styles.ts +71 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsList.tsx +51 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsListItem.stories.tsx +116 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsListItem.tsx +118 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsListItemSkeleton.tsx +26 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsListSkeleton.tsx +28 -0
- package/src/components/Form/AccessRights/Stats/AccessRightsStats.styles.ts +18 -0
- package/src/components/Form/AccessRights/Stats/AccessRightsStats.tsx +41 -0
- package/src/components/Form/AccessRights/__fixtures__/contactAccessRight.mock.ts +52 -0
- package/src/components/Form/AccessRights/common/GroupLabel.styles.ts +18 -0
- package/src/components/Form/AccessRights/common/GroupLabel.tsx +15 -0
- package/src/components/Form/AccessRights/common/Input.styles.ts +48 -0
- package/src/components/Form/AccessRights/common/RoleInputSelect.styles.ts +11 -0
- package/src/components/Form/AccessRights/common/RoleInputSelect.tsx +57 -0
- package/src/components/Form/AccessRights/index.ts +3 -0
- package/src/components/Form/AccessRights/useAccessRightsForm.test.tsx +531 -0
- package/src/components/Form/AccessRights/useAccessRightsForm.tsx +282 -0
- package/src/components/Form/AccessRights/useAccessRightsForm.utils.ts +41 -0
- package/src/components/Form/Dashboard/DashboardForm.styles.ts +1 -7
- package/src/components/Form/Dashboard/DashboardForm.tsx +9 -12
- package/src/components/Form/Form.resource.ts +1 -0
- package/src/components/Form/Form.styles.ts +17 -0
- package/src/components/Form/{Dashboard/DashboardFormActions.tsx → FormActions.tsx} +17 -16
- package/src/components/Form/index.ts +1 -0
- package/src/components/Header/PageHeader/PageHeader.stories.tsx +9 -0
- package/src/components/List/Item/Avatar.tsx +21 -0
- package/src/components/List/Item/AvatarSkeleton.tsx +17 -0
- package/src/components/List/Item/ListItem.stories.tsx +41 -0
- package/src/components/List/Item/ListItem.styles.ts +57 -0
- package/src/components/List/Item/ListItem.tsx +32 -0
- package/src/components/List/Item/Text.tsx +25 -0
- package/src/components/List/Item/TextSkeleton.tsx +22 -0
- package/src/components/List/Item/index.ts +14 -0
- package/src/components/List/List.stories.tsx +70 -0
- package/src/components/List/List.styles.ts +8 -0
- package/src/components/List/List.tsx +19 -0
- package/src/components/List/index.ts +6 -0
- package/src/components/Modal/ModalActions.tsx +4 -1
- package/src/components/index.ts +1 -0
- package/src/utils/index.ts +1 -0
- 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,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
|
+
};
|
|
@@ -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"
|
package/src/components/index.ts
CHANGED
package/src/utils/index.ts
CHANGED
|
@@ -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
|
+
};
|