@centreon/ui 24.10.5 → 24.10.7
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 +2 -3
- package/src/Dashboard/DashboardLayout.stories.tsx +1 -1
- package/src/InputField/Text/index.tsx +1 -1
- package/src/Listing/index.tsx +9 -2
- package/src/Listing/models.ts +8 -0
- package/src/MultiSelectEntries/index.tsx +0 -2
- package/src/PopoverMenu/index.tsx +9 -2
- package/src/SortableItems/index.tsx +1 -0
- package/src/ThemeProvider/index.tsx +1 -1
- package/src/components/CrudPage/Actions/Actions.styles.ts +16 -0
- package/src/components/CrudPage/Actions/Actions.tsx +24 -0
- package/src/components/CrudPage/Actions/AddButton.tsx +23 -0
- package/src/components/CrudPage/Actions/Filters.tsx +25 -0
- package/src/components/CrudPage/Actions/Search.tsx +31 -0
- package/src/components/CrudPage/Actions/useSearch.tsx +24 -0
- package/src/components/CrudPage/Columns/Actions.tsx +88 -0
- package/src/components/CrudPage/CrudPage.cypress.spec.tsx +559 -0
- package/src/components/CrudPage/CrudPage.stories.tsx +278 -0
- package/src/components/CrudPage/CrudPageRoot.tsx +142 -0
- package/src/components/CrudPage/DeleteModal.tsx +77 -0
- package/src/components/CrudPage/Form/AddModal.tsx +35 -0
- package/src/components/CrudPage/Form/Buttons.tsx +98 -0
- package/src/components/CrudPage/Form/UpdateModal.tsx +60 -0
- package/src/components/CrudPage/Listing.tsx +63 -0
- package/src/components/CrudPage/atoms.ts +30 -0
- package/src/components/CrudPage/hooks/useDeleteItem.ts +53 -0
- package/src/components/CrudPage/hooks/useGetItem.ts +36 -0
- package/src/components/CrudPage/hooks/useGetItems.ts +67 -0
- package/src/components/CrudPage/hooks/useListingQueryKey.ts +31 -0
- package/src/components/CrudPage/index.tsx +7 -0
- package/src/components/CrudPage/models.ts +118 -0
- package/src/components/CrudPage/utils.ts +4 -0
- package/src/components/DataTable/EmptyState/DataTableEmptyState.styles.ts +3 -0
- package/src/components/DataTable/EmptyState/DataTableEmptyState.tsx +6 -0
- package/src/components/Layout/AreaIndicator.tsx +1 -1
- package/src/components/Layout/PageLayout/PageLayout.styles.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centreon/ui",
|
|
3
|
-
"version": "24.10.
|
|
3
|
+
"version": "24.10.7",
|
|
4
4
|
"description": "Centreon UI Components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update:deps": "pnpx npm-check-updates -i --format group",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"cypress:run": "cypress run --component --browser=chrome",
|
|
19
19
|
"tokens:transform": "TS_NODE_PROJECT=tsconfig.node.json ts-node style-dictionary.transform.ts"
|
|
20
20
|
},
|
|
21
|
+
"type": "module",
|
|
21
22
|
"sideEffects": false,
|
|
22
23
|
"repository": {
|
|
23
24
|
"type": "git",
|
|
@@ -53,8 +54,6 @@
|
|
|
53
54
|
"@cypress/webpack-dev-server": "^3.10.1",
|
|
54
55
|
"@faker-js/faker": "^8.4.1",
|
|
55
56
|
"@mdx-js/react": "^3.0.1",
|
|
56
|
-
"@modern-js/prod-server": "^2.58.1",
|
|
57
|
-
"@modern-js/storybook": "^2.58.1",
|
|
58
57
|
"@simonsmith/cypress-image-snapshot": "^9.1.0",
|
|
59
58
|
"@storybook/addon-a11y": "^8.2.9",
|
|
60
59
|
"@storybook/addon-docs": "^8.2.9",
|
|
@@ -95,7 +95,7 @@ export const normal = DashboardTemplate.bind({});
|
|
|
95
95
|
|
|
96
96
|
export const withManyPanels = DashboardTemplate.bind({});
|
|
97
97
|
withManyPanels.args = {
|
|
98
|
-
layout: generateLayout(
|
|
98
|
+
layout: generateLayout(100)
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
export const withItemHeader = DashboardTemplate.bind({});
|
|
@@ -77,7 +77,7 @@ const OptionalLabelInputAdornment = ({
|
|
|
77
77
|
type SizeVariant = 'large' | 'medium' | 'small' | 'compact';
|
|
78
78
|
|
|
79
79
|
export type TextProps = {
|
|
80
|
-
EndAdornment?: React.FC;
|
|
80
|
+
EndAdornment?: React.FC | JSX.Element;
|
|
81
81
|
StartAdornment?: React.FC;
|
|
82
82
|
ariaLabel?: string;
|
|
83
83
|
autoSize?: boolean;
|
package/src/Listing/index.tsx
CHANGED
|
@@ -148,7 +148,13 @@ const defaultColumnConfiguration = {
|
|
|
148
148
|
|
|
149
149
|
export const performanceRowsLimit = 60;
|
|
150
150
|
|
|
151
|
-
const Listing = <
|
|
151
|
+
const Listing = <
|
|
152
|
+
TRow extends {
|
|
153
|
+
id: RowId;
|
|
154
|
+
internalListingParentId?: RowId;
|
|
155
|
+
internalListingParentRow: TRow;
|
|
156
|
+
}
|
|
157
|
+
>({
|
|
152
158
|
customListingComponent,
|
|
153
159
|
displayCustomListing,
|
|
154
160
|
limit = 10,
|
|
@@ -248,7 +254,8 @@ const Listing = <TRow extends { id: RowId; internalListingParentId?: RowId }>({
|
|
|
248
254
|
row,
|
|
249
255
|
...row[subItems.getRowProperty()].map((subRow) => ({
|
|
250
256
|
...subRow,
|
|
251
|
-
internalListingParentId: row.id
|
|
257
|
+
internalListingParentId: row.id,
|
|
258
|
+
internalListingParentRow: row
|
|
252
259
|
}))
|
|
253
260
|
];
|
|
254
261
|
}
|
package/src/Listing/models.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '@mui/material';
|
|
11
11
|
import type { PopperProps } from '@mui/material/Popper';
|
|
12
12
|
|
|
13
|
+
import { equals, type } from 'ramda';
|
|
13
14
|
import { IconButton } from '..';
|
|
14
15
|
|
|
15
16
|
const useStyles = makeStyles()((theme) => ({
|
|
@@ -29,8 +30,9 @@ interface PopoverData {
|
|
|
29
30
|
|
|
30
31
|
interface Props {
|
|
31
32
|
canOpen?: boolean;
|
|
32
|
-
children: (props?) => JSX.Element;
|
|
33
|
+
children: (props?) => JSX.Element | JSX.Element;
|
|
33
34
|
className?: string;
|
|
35
|
+
tooltipClassName?: string;
|
|
34
36
|
dataTestId?: string;
|
|
35
37
|
getPopoverData?: (data: PopoverData) => void;
|
|
36
38
|
icon: JSX.Element;
|
|
@@ -52,6 +54,7 @@ const PopoverMenu = ({
|
|
|
52
54
|
className,
|
|
53
55
|
dataTestId,
|
|
54
56
|
getPopoverData,
|
|
57
|
+
tooltipClassName,
|
|
55
58
|
popperProps
|
|
56
59
|
}: Props): JSX.Element => {
|
|
57
60
|
const { classes, cx } = useStyles();
|
|
@@ -113,7 +116,11 @@ const PopoverMenu = ({
|
|
|
113
116
|
onResizeCapture={(): undefined => undefined}
|
|
114
117
|
{...popperProps}
|
|
115
118
|
>
|
|
116
|
-
<Paper
|
|
119
|
+
<Paper className={tooltipClassName}>
|
|
120
|
+
{equals(type(children), 'Function')
|
|
121
|
+
? children({ close })
|
|
122
|
+
: children}
|
|
123
|
+
</Paper>
|
|
117
124
|
</Popper>
|
|
118
125
|
</ClickAwayListener>
|
|
119
126
|
)}
|
|
@@ -185,6 +185,7 @@ const SortableItems = <T extends { [propertyToFilterItemsOn]: string }>({
|
|
|
185
185
|
>
|
|
186
186
|
<SortableContext items={sortableItemsIds} strategy={sortingStrategy}>
|
|
187
187
|
<RootComponent>
|
|
188
|
+
{/* biome-ignore lint: */}
|
|
188
189
|
<>
|
|
189
190
|
{sortableItemsIds.map((sortableItemId, index) => {
|
|
190
191
|
const item = getItemById(sortableItemId) as
|
|
@@ -261,7 +261,7 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
|
|
|
261
261
|
{
|
|
262
262
|
backgroundColor: theme.palette.background.default,
|
|
263
263
|
border: 'none',
|
|
264
|
-
borderRadius:
|
|
264
|
+
borderRadius: `${theme.shape.borderRadius}px`,
|
|
265
265
|
boxShadow: theme.shadows[3]
|
|
266
266
|
}
|
|
267
267
|
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { makeStyles } from 'tss-react/mui';
|
|
2
|
+
|
|
3
|
+
export const useActionsStyles = makeStyles()((theme) => ({
|
|
4
|
+
search: {
|
|
5
|
+
maxWidth: theme.spacing(50)
|
|
6
|
+
},
|
|
7
|
+
clearButton: {
|
|
8
|
+
alignSelf: 'flex-start'
|
|
9
|
+
},
|
|
10
|
+
tooltipFilters: {
|
|
11
|
+
padding: theme.spacing(2, 3),
|
|
12
|
+
display: 'flex',
|
|
13
|
+
flexDirection: 'column',
|
|
14
|
+
gap: theme.spacing(2)
|
|
15
|
+
}
|
|
16
|
+
}));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Box } from '@mui/material';
|
|
2
|
+
import AddButton from './AddButton';
|
|
3
|
+
import Search from './Search';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
labels: {
|
|
7
|
+
search: string;
|
|
8
|
+
add: string;
|
|
9
|
+
};
|
|
10
|
+
filters: JSX.Element;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Actions = ({ labels, filters }: Props): JSX.Element => {
|
|
14
|
+
return (
|
|
15
|
+
<Box
|
|
16
|
+
sx={{ display: 'grid', gridTemplateColumns: 'min-content auto', gap: 2 }}
|
|
17
|
+
>
|
|
18
|
+
<AddButton label={labels.add} />
|
|
19
|
+
<Search label={labels.search} filters={filters} />
|
|
20
|
+
</Box>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default Actions;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Add } from '@mui/icons-material';
|
|
2
|
+
import { useSetAtom } from 'jotai';
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { Button } from '../../Button';
|
|
5
|
+
import { openFormModalAtom } from '../atoms';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const AddButton = ({ label }: Props): JSX.Element => {
|
|
12
|
+
const setOpenFormModal = useSetAtom(openFormModalAtom);
|
|
13
|
+
|
|
14
|
+
const add = useCallback(() => setOpenFormModal('add'), []);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Button size="small" icon={<Add />} iconVariant="start" onClick={add}>
|
|
18
|
+
{label}
|
|
19
|
+
</Button>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default AddButton;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Tune } from '@mui/icons-material';
|
|
2
|
+
import { isValidElement } from 'react';
|
|
3
|
+
import PopoverMenu from '../../../PopoverMenu';
|
|
4
|
+
import { useActionsStyles } from './Actions.styles';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
label: string;
|
|
8
|
+
filters: JSX.Element;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Filters: React.FC<Props> = ({ label, filters }: Props): JSX.Element => {
|
|
12
|
+
const { classes } = useActionsStyles();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<PopoverMenu
|
|
16
|
+
title={label}
|
|
17
|
+
icon={<Tune />}
|
|
18
|
+
tooltipClassName={classes.tooltipFilters}
|
|
19
|
+
>
|
|
20
|
+
{isValidElement(filters) ? filters : <div />}
|
|
21
|
+
</PopoverMenu>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default Filters;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SearchField } from '@centreon/ui';
|
|
2
|
+
import { useActionsStyles } from './Actions.styles';
|
|
3
|
+
import Filters from './Filters';
|
|
4
|
+
import { useSearch } from './useSearch';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
label: string;
|
|
8
|
+
filters: JSX.Element;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Search = ({ label, filters }: Props): JSX.Element => {
|
|
12
|
+
const { classes } = useActionsStyles();
|
|
13
|
+
|
|
14
|
+
const { change } = useSearch();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<SearchField
|
|
18
|
+
className={classes.search}
|
|
19
|
+
debounced
|
|
20
|
+
fullWidth
|
|
21
|
+
dataTestId={label}
|
|
22
|
+
placeholder={label}
|
|
23
|
+
onChange={change}
|
|
24
|
+
InputProps={{
|
|
25
|
+
endAdornment: <Filters label="filters" filters={filters} />
|
|
26
|
+
}}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default Search;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useAtom } from 'jotai';
|
|
2
|
+
import { ChangeEvent, useCallback, useRef } from 'react';
|
|
3
|
+
import { searchAtom } from '../atoms';
|
|
4
|
+
|
|
5
|
+
interface UseSearchState {
|
|
6
|
+
search: string;
|
|
7
|
+
change: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useSearch = (): UseSearchState => {
|
|
11
|
+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
12
|
+
|
|
13
|
+
const [search, setSearch] = useAtom(searchAtom);
|
|
14
|
+
|
|
15
|
+
const change = useCallback((event: ChangeEvent<HTMLInputElement>): void => {
|
|
16
|
+
if (timeoutRef.current) {
|
|
17
|
+
clearTimeout(timeoutRef.current);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
timeoutRef.current = setTimeout(() => setSearch(event.target.value), 500);
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
return { search, change };
|
|
24
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { EditOutlined } from '@mui/icons-material';
|
|
2
|
+
import DeleteOutline from '@mui/icons-material/DeleteOutline';
|
|
3
|
+
import { Box } from '@mui/material';
|
|
4
|
+
import { useAtomValue, useSetAtom } from 'jotai';
|
|
5
|
+
import { isNil } from 'ramda';
|
|
6
|
+
import { useCallback } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
import { IconButton } from '../../Button';
|
|
9
|
+
import {
|
|
10
|
+
canDeleteSubItemsAtom,
|
|
11
|
+
itemToDeleteAtom,
|
|
12
|
+
openFormModalAtom
|
|
13
|
+
} from '../atoms';
|
|
14
|
+
|
|
15
|
+
interface Props<TData> {
|
|
16
|
+
row: TData & {
|
|
17
|
+
internalListingParentId?: number;
|
|
18
|
+
internalListingParentRow: TData;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const labelDelete = 'Delete';
|
|
23
|
+
const labelUpdate = 'Update';
|
|
24
|
+
|
|
25
|
+
const Actions = <TData extends { id: number; name: string }>({
|
|
26
|
+
row
|
|
27
|
+
}: Props<TData>): JSX.Element => {
|
|
28
|
+
const { t } = useTranslation();
|
|
29
|
+
const canDeleteSubItems = useAtomValue(canDeleteSubItemsAtom);
|
|
30
|
+
const setItemToDelete = useSetAtom(itemToDeleteAtom);
|
|
31
|
+
const setOpenFormModal = useSetAtom(openFormModalAtom);
|
|
32
|
+
|
|
33
|
+
const askBeforeDelete = (): void => {
|
|
34
|
+
setItemToDelete({
|
|
35
|
+
id: row.id,
|
|
36
|
+
name: row.name,
|
|
37
|
+
parent: isNil(row.internalListingParentRow)
|
|
38
|
+
? undefined
|
|
39
|
+
: {
|
|
40
|
+
id: row.internalListingParentRow.id,
|
|
41
|
+
name: row.internalListingParentRow.name
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const updateRow = useCallback(() => setOpenFormModal(row.id), [row.id]);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Box
|
|
50
|
+
sx={{
|
|
51
|
+
display: 'flex',
|
|
52
|
+
flexDirection: 'row',
|
|
53
|
+
gap: 1,
|
|
54
|
+
width: '100%',
|
|
55
|
+
justifyContent: 'flex-end'
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
{isNil(row.internalListingParentRow) && (
|
|
59
|
+
<IconButton
|
|
60
|
+
size="small"
|
|
61
|
+
icon={<EditOutlined fontSize="small" color="primary" />}
|
|
62
|
+
onClick={updateRow}
|
|
63
|
+
title={t(labelUpdate)}
|
|
64
|
+
data-testid={
|
|
65
|
+
row.internalListingParentRow
|
|
66
|
+
? `edit-${row.internalListingParentRow.id}-${row.id}`
|
|
67
|
+
: `edit-${row.id}`
|
|
68
|
+
}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
{(canDeleteSubItems || isNil(row.internalListingParentRow)) && (
|
|
72
|
+
<IconButton
|
|
73
|
+
size="small"
|
|
74
|
+
icon={<DeleteOutline fontSize="small" color="error" />}
|
|
75
|
+
onClick={askBeforeDelete}
|
|
76
|
+
title={t(labelDelete)}
|
|
77
|
+
data-testid={
|
|
78
|
+
row.internalListingParentRow
|
|
79
|
+
? `delete-${row.internalListingParentRow.id}-${row.id}`
|
|
80
|
+
: `delete-${row.id}`
|
|
81
|
+
}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
</Box>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default Actions;
|