@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.
Files changed (36) hide show
  1. package/package.json +2 -3
  2. package/src/Dashboard/DashboardLayout.stories.tsx +1 -1
  3. package/src/InputField/Text/index.tsx +1 -1
  4. package/src/Listing/index.tsx +9 -2
  5. package/src/Listing/models.ts +8 -0
  6. package/src/MultiSelectEntries/index.tsx +0 -2
  7. package/src/PopoverMenu/index.tsx +9 -2
  8. package/src/SortableItems/index.tsx +1 -0
  9. package/src/ThemeProvider/index.tsx +1 -1
  10. package/src/components/CrudPage/Actions/Actions.styles.ts +16 -0
  11. package/src/components/CrudPage/Actions/Actions.tsx +24 -0
  12. package/src/components/CrudPage/Actions/AddButton.tsx +23 -0
  13. package/src/components/CrudPage/Actions/Filters.tsx +25 -0
  14. package/src/components/CrudPage/Actions/Search.tsx +31 -0
  15. package/src/components/CrudPage/Actions/useSearch.tsx +24 -0
  16. package/src/components/CrudPage/Columns/Actions.tsx +88 -0
  17. package/src/components/CrudPage/CrudPage.cypress.spec.tsx +559 -0
  18. package/src/components/CrudPage/CrudPage.stories.tsx +278 -0
  19. package/src/components/CrudPage/CrudPageRoot.tsx +142 -0
  20. package/src/components/CrudPage/DeleteModal.tsx +77 -0
  21. package/src/components/CrudPage/Form/AddModal.tsx +35 -0
  22. package/src/components/CrudPage/Form/Buttons.tsx +98 -0
  23. package/src/components/CrudPage/Form/UpdateModal.tsx +60 -0
  24. package/src/components/CrudPage/Listing.tsx +63 -0
  25. package/src/components/CrudPage/atoms.ts +30 -0
  26. package/src/components/CrudPage/hooks/useDeleteItem.ts +53 -0
  27. package/src/components/CrudPage/hooks/useGetItem.ts +36 -0
  28. package/src/components/CrudPage/hooks/useGetItems.ts +67 -0
  29. package/src/components/CrudPage/hooks/useListingQueryKey.ts +31 -0
  30. package/src/components/CrudPage/index.tsx +7 -0
  31. package/src/components/CrudPage/models.ts +118 -0
  32. package/src/components/CrudPage/utils.ts +4 -0
  33. package/src/components/DataTable/EmptyState/DataTableEmptyState.styles.ts +3 -0
  34. package/src/components/DataTable/EmptyState/DataTableEmptyState.tsx +6 -0
  35. package/src/components/Layout/AreaIndicator.tsx +1 -1
  36. 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.5",
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(1000)
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;
@@ -148,7 +148,13 @@ const defaultColumnConfiguration = {
148
148
 
149
149
  export const performanceRowsLimit = 60;
150
150
 
151
- const Listing = <TRow extends { id: RowId; internalListingParentId?: RowId }>({
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
  }
@@ -76,3 +76,11 @@ export interface TableStyleAtom {
76
76
  width: number;
77
77
  };
78
78
  }
79
+
80
+ export interface ListingSubItems {
81
+ canCheckSubItems: boolean;
82
+ enable: boolean;
83
+ getRowProperty: (row?) => string;
84
+ labelCollapse: string;
85
+ labelExpand: string;
86
+ }
@@ -134,8 +134,6 @@ const MultiSelectEntries = ({
134
134
  [classes.container]: true
135
135
  } as CxArg)}
136
136
  ref={hoverRef as Ref<HTMLDivElement>}
137
- role="button"
138
- tabIndex={0}
139
137
  onClick={onClick}
140
138
  onKeyDown={onClick}
141
139
  >
@@ -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>{children({ close })}</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: 0,
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;