@centreon/ui 24.4.8 → 24.4.10

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 (73) hide show
  1. package/package.json +17 -12
  2. package/src/Button/Save/StartIcon.tsx +3 -3
  3. package/src/Dashboard/Item.tsx +1 -1
  4. package/src/Dashboard/Layout.tsx +2 -2
  5. package/src/FileDropZone/index.tsx +3 -1
  6. package/src/Form/Form.cypress.spec.tsx +133 -0
  7. package/src/Form/Inputs/CheckboxGroup.tsx +1 -4
  8. package/src/Form/Inputs/List/Content.tsx +62 -0
  9. package/src/Form/Inputs/List/List.styles.ts +29 -0
  10. package/src/Form/Inputs/List/List.tsx +58 -0
  11. package/src/Form/Inputs/List/useList.ts +81 -0
  12. package/src/Form/Inputs/index.tsx +3 -1
  13. package/src/Form/Inputs/models.ts +9 -1
  14. package/src/Graph/LineChart/BasicComponents/Lines/Threshold/Circle.tsx +2 -2
  15. package/src/Graph/LineChart/BasicComponents/Lines/Threshold/index.tsx +5 -4
  16. package/src/Graph/LineChart/BasicComponents/Thresholds.tsx +2 -2
  17. package/src/Graph/LineChart/BasicComponents/useFilterLines.ts +1 -1
  18. package/src/Graph/LineChart/InteractiveComponents/AnchorPoint/GuidingLines.tsx +2 -2
  19. package/src/Graph/LineChart/InteractiveComponents/Annotations/Annotation/index.tsx +2 -3
  20. package/src/Graph/LineChart/InteractiveComponents/Annotations/EventAnnotations.tsx +1 -1
  21. package/src/Graph/LineChart/Legend/useLegend.ts +3 -3
  22. package/src/Graph/LineChart/helpers/doc.ts +16 -13
  23. package/src/Graph/LineChart/helpers/index.ts +1 -1
  24. package/src/Graph/LineChart/index.stories.tsx +4 -2
  25. package/src/Graph/SingleBar/Thresholds.tsx +2 -2
  26. package/src/Graph/Text/Text.stories.tsx +60 -4
  27. package/src/Graph/common/timeSeries/index.ts +3 -3
  28. package/src/InputField/Select/Autocomplete/Connected/index.tsx +10 -7
  29. package/src/InputField/Select/Autocomplete/Draggable/SortableList.tsx +1 -1
  30. package/src/InputField/Select/Autocomplete/Draggable/SortableListContent.tsx +1 -1
  31. package/src/InputField/Select/Autocomplete/Draggable/index.tsx +1 -1
  32. package/src/InputField/Select/IconPopover/index.tsx +2 -2
  33. package/src/InputField/Select/index.tsx +1 -1
  34. package/src/Listing/Header/ListingHeader.tsx +1 -1
  35. package/src/Listing/Listing.styles.ts +2 -3
  36. package/src/Listing/index.stories.tsx +12 -1
  37. package/src/Listing/index.tsx +1 -2
  38. package/src/RichTextEditor/RichTextEditor.tsx +12 -1
  39. package/src/SortableItems/index.tsx +2 -7
  40. package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/PickersStartEndDate.tsx +8 -3
  41. package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/models.ts +0 -2
  42. package/src/TimePeriods/DateTimePickerInput.tsx +45 -17
  43. package/src/TimePeriods/TimePeriods.cypress.spec.tsx +9 -33
  44. package/src/TimePeriods/helpers/index.ts +1 -1
  45. package/src/TimePeriods/index.stories.tsx +12 -4
  46. package/src/TimePeriods/index.tsx +2 -2
  47. package/src/Typography/Subtitle.tsx +55 -0
  48. package/src/api/QueryProvider.tsx +1 -1
  49. package/src/api/TestQueryProvider.tsx +1 -1
  50. package/src/api/useFetchQuery/index.ts +27 -23
  51. package/src/api/useMutationQuery/index.ts +41 -17
  52. package/src/components/DataTable/DataListing.tsx +6 -0
  53. package/src/components/DataTable/DataTable.cypress.spec.tsx +193 -0
  54. package/src/components/DataTable/DataTable.stories.tsx +40 -0
  55. package/src/components/DataTable/DataTable.styles.ts +3 -0
  56. package/src/components/DataTable/DataTable.tsx +3 -3
  57. package/src/components/DataTable/Item/DataTableItem.styles.ts +7 -2
  58. package/src/components/DataTable/Item/DataTableItem.tsx +2 -2
  59. package/src/components/DataTable/index.ts +3 -1
  60. package/src/components/Form/AccessRights/__fixtures__/contactAccessRight.mock.ts +2 -0
  61. package/src/components/Form/AccessRights/useAccessRightsForm.utils.ts +1 -1
  62. package/src/components/Form/Dashboard/DashboardForm.tsx +15 -12
  63. package/src/components/Modal/Modal.styles.ts +4 -3
  64. package/src/components/Modal/ModalActions.tsx +4 -2
  65. package/src/index.ts +2 -0
  66. package/src/queryParameters/url/index.ts +5 -1
  67. package/src/utils/index.ts +1 -1
  68. package/src/utils/{useLicenseExpirationWarning.cypress.spec.tsx → useLicenseExpirationWarning.test.tsx} +48 -37
  69. package/src/utils/useLicenseExpirationWarning.ts +18 -18
  70. package/src/utils/usePluralizedTranslation.ts +21 -0
  71. package/src/screens/dashboard/DashboardsDetail.stories.tsx +0 -108
  72. package/src/screens/dashboard/DashboardsOverview.stories.tsx +0 -281
  73. package/src/utils/useDateTimePickerAdapter.ts +0 -309
@@ -3,15 +3,10 @@ import localizedFormatPlugin from 'dayjs/plugin/localizedFormat';
3
3
  import timezonePlugin from 'dayjs/plugin/timezone';
4
4
  import utcPlugin from 'dayjs/plugin/utc';
5
5
  import { Provider, createStore } from 'jotai';
6
- import { equals, inc } from 'ramda';
6
+ import { equals } from 'ramda';
7
7
  import { renderHook } from '@testing-library/react';
8
8
 
9
- import { LocalizationProvider } from '@mui/x-date-pickers';
10
-
11
- import {
12
- useLocaleDateTimeFormat,
13
- useDateTimePickerAdapter
14
- } from '@centreon/ui';
9
+ import { useLocaleDateTimeFormat } from '@centreon/ui';
15
10
  import { ListingVariant, userAtom } from '@centreon/ui-context';
16
11
 
17
12
  import { CustomTimePeriodProperty } from './models';
@@ -297,10 +292,6 @@ testData.forEach((item) =>
297
292
  beforeEach(() => {
298
293
  cy.viewport('macbook-13');
299
294
 
300
- const { result } = renderHook(() => useDateTimePickerAdapter());
301
-
302
- const { Adapter } = result.current;
303
-
304
295
  const store = createStore();
305
296
  store.set(userAtom, {
306
297
  ...retrievedUser,
@@ -311,31 +302,16 @@ testData.forEach((item) =>
311
302
  cy.mount({
312
303
  Component: (
313
304
  <Provider store={store}>
314
- <LocalizationProvider adapterLocale="en" dateAdapter={Adapter}>
315
- <DateTimePickerInput
316
- changeDate={cy.stub()}
317
- date={new Date(item.initialDate)}
318
- desktopMediaQuery="@media (min-width: 1024px)"
319
- property={CustomTimePeriodProperty.start}
320
- />
321
- </LocalizationProvider>
305
+ <DateTimePickerInput
306
+ changeDate={cy.stub()}
307
+ date={new Date(item.initialDate)}
308
+ property={CustomTimePeriodProperty.start}
309
+ />
322
310
  </Provider>
323
311
  )
324
312
  });
325
313
  });
326
314
 
327
- it('checks input calendar value contains correct date', () => {
328
- const { result } = renderHook(() => useLocaleDateTimeFormat());
329
- const { format } = result.current;
330
-
331
- const dateInput = format({
332
- date: dayjs(item.initialDate).tz(item.timezone),
333
- formatString: 'L hh:mm A'
334
- });
335
-
336
- cy.get('input').should('have.value', dateInput);
337
- });
338
-
339
315
  it(`displays the correct number of days for the current month when the ${item.button} button is clicked`, () => {
340
316
  cy.get('button').click();
341
317
  item.data.forEach((element) => {
@@ -389,7 +365,7 @@ testData.forEach((item) =>
389
365
 
390
366
  cy.get('[role="rowgroup"]').children().as('listWeeks');
391
367
 
392
- cy.get('@listWeeks').should('have.length', inc(numberWeeks));
368
+ cy.get('@listWeeks').should('have.length', numberWeeks);
393
369
  cy.get('@listWeeks').eq(0).as('firstWeek');
394
370
  cy.get('@firstWeek').children().as('listDaysInFirstWeek');
395
371
 
@@ -416,7 +392,7 @@ testData.forEach((item) =>
416
392
 
417
393
  cy.get('[role="rowgroup"]').children().as('listWeeks');
418
394
 
419
- cy.get('@listWeeks').should('have.length', inc(numberWeeks));
395
+ cy.get('@listWeeks').should('have.length', numberWeeks);
420
396
 
421
397
  cy.get('@listWeeks')
422
398
  .eq(numberWeeks - 1)
@@ -23,4 +23,4 @@ export const getTimePeriodById = ({
23
23
  id,
24
24
  timePeriods
25
25
  }: TimePeriodById): TimePeriod =>
26
- find<TimePeriod>(propEq('id', id))(timePeriods) as TimePeriod;
26
+ find<TimePeriod>(propEq(id, 'id'))(timePeriods) as TimePeriod;
@@ -88,20 +88,27 @@ const args = {
88
88
  ]
89
89
  };
90
90
 
91
+ const parameters = {
92
+ chromatic: { diffThreshold: 0.1 }
93
+ };
94
+
91
95
  export const BasicTimePeriod: Story = {
92
96
  ...Template,
93
- argTypes
97
+ argTypes,
98
+ parameters
94
99
  };
95
100
 
96
101
  export const WithExtraTimePeriods: Story = {
97
102
  ...Template,
98
103
  argTypes,
99
- args
104
+ args,
105
+ parameters
100
106
  };
101
107
 
102
108
  export const WithExternalComponent: Story = {
103
109
  ...TemplateWithExternalComponent,
104
- argTypes
110
+ argTypes,
111
+ parameters
105
112
  };
106
113
 
107
114
  export const SimpleTimePeriod: StorySimpleTimePeriod = {
@@ -109,5 +116,6 @@ export const SimpleTimePeriod: StorySimpleTimePeriod = {
109
116
  args: {
110
117
  endDate: dayjs(Date.now()).toDate(),
111
118
  startDate: dayjs(Date.now()).subtract(29, 'day').toDate()
112
- }
119
+ },
120
+ parameters
113
121
  };
@@ -20,7 +20,7 @@ import { useStyles } from './TimePeriods.styles';
20
20
  import {
21
21
  CustomTimePeriod as CustomTimePeriodModel,
22
22
  EndStartInterval,
23
- TimePeriod
23
+ TimePeriod as TimePeriodModel
24
24
  } from './models';
25
25
  import useTimePeriod from './useTimePeriod';
26
26
 
@@ -34,7 +34,7 @@ interface Parameters extends EndStartInterval {
34
34
  export interface Props {
35
35
  adjustTimePeriodData?: Omit<CustomTimePeriodModel, 'timelineEventsLimit'>;
36
36
  disabled?: boolean;
37
- extraTimePeriods?: Array<Omit<TimePeriod, 'timelineEventsLimit'>>;
37
+ extraTimePeriods?: Array<Omit<TimePeriodModel, 'timelineEventsLimit'>>;
38
38
  getIsError?: (value: boolean) => void;
39
39
  getParameters?: ({ start, end, timelineEventsLimit }: Parameters) => void;
40
40
  renderExternalComponent?: ReactNode;
@@ -0,0 +1,55 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ import { type, equals } from 'ramda';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import { Typography } from '@mui/material';
7
+ import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
8
+
9
+ import { Tooltip } from '../components';
10
+
11
+ interface Props {
12
+ children: ReactNode;
13
+ secondaryLabel?: string | Array<string>;
14
+ }
15
+
16
+ const Subtitle = ({ children, secondaryLabel }: Props): JSX.Element => {
17
+ const { t } = useTranslation();
18
+
19
+ const containsSeveralSecondaryLabels = equals(type(secondaryLabel), 'Array');
20
+
21
+ return (
22
+ <Typography variant="subtitle1">
23
+ <strong>{children}</strong>
24
+ {secondaryLabel && (
25
+ <Tooltip
26
+ aria-label={
27
+ containsSeveralSecondaryLabels
28
+ ? secondaryLabel[0]
29
+ : (secondaryLabel as string)
30
+ }
31
+ followCursor={false}
32
+ label={
33
+ containsSeveralSecondaryLabels ? (
34
+ <>
35
+ {secondaryLabel.map((label) => (
36
+ <p key={label}>{t(label)}</p>
37
+ ))}
38
+ </>
39
+ ) : (
40
+ t(secondaryLabel)
41
+ )
42
+ }
43
+ placement="right"
44
+ >
45
+ <HelpOutlineIcon
46
+ color="primary"
47
+ sx={{ ml: 1, verticalAlign: 'middle' }}
48
+ />
49
+ </Tooltip>
50
+ )}
51
+ </Typography>
52
+ );
53
+ };
54
+
55
+ export default Subtitle;
@@ -7,7 +7,7 @@ const defaultCacheTime = 5 * 1_000;
7
7
  const client = new QueryClient({
8
8
  defaultOptions: {
9
9
  queries: {
10
- cacheTime: defaultCacheTime,
10
+ gcTime: defaultCacheTime,
11
11
  refetchOnWindowFocus: false,
12
12
  staleTime: defaultCacheTime,
13
13
  suspense: true
@@ -5,7 +5,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5
5
  const client = new QueryClient({
6
6
  defaultOptions: {
7
7
  queries: {
8
- cacheTime: 0,
8
+ gcTime: 0,
9
9
  retry: false
10
10
  }
11
11
  }
@@ -26,7 +26,9 @@ export interface UseFetchQueryProps<T> {
26
26
  getQueryKey: () => QueryKey;
27
27
  httpCodesBypassErrorSnackbar?: Array<number>;
28
28
  isPaginated?: boolean;
29
- queryOptions?: Omit<
29
+ queryOptions?: {
30
+ suspense?: boolean;
31
+ } & Omit<
30
32
  UseQueryOptions<T | ResponseError, Error, T | ResponseError, QueryKey>,
31
33
  'queryKey' | 'queryFn'
32
34
  >;
@@ -34,11 +36,12 @@ export interface UseFetchQueryProps<T> {
34
36
 
35
37
  export type UseFetchQueryState<T> = {
36
38
  data?: T;
39
+ error: Omit<ResponseError, 'isError'> | null;
37
40
  fetchQuery: () => Promise<T | ResponseError>;
38
41
  prefetchNextPage: ({ page, getPrefetchQueryKey }) => void;
39
42
  prefetchPreviousPage: ({ page, getPrefetchQueryKey }) => void;
40
43
  prefetchQuery: ({ endpointParams, queryKey }) => void;
41
- } & Omit<QueryObserverBaseResult, 'data'>;
44
+ } & Omit<QueryObserverBaseResult, 'data' | 'error'>;
42
45
 
43
46
  export interface PrefetchEndpointParams {
44
47
  page: number;
@@ -62,9 +65,8 @@ const useFetchQuery = <T extends object>({
62
65
 
63
66
  const { showErrorMessage } = useSnackbar();
64
67
 
65
- const queryData = useQuery<T | ResponseError, Error>(
66
- getQueryKey(),
67
- ({ signal }): Promise<T | ResponseError> =>
68
+ const queryData = useQuery<T | ResponseError, Error>({
69
+ queryFn: ({ signal }): Promise<T | ResponseError> =>
68
70
  customFetch<T>({
69
71
  baseEndpoint,
70
72
  catchError,
@@ -74,10 +76,9 @@ const useFetchQuery = <T extends object>({
74
76
  headers: new Headers(fetchHeaders),
75
77
  signal
76
78
  }),
77
- {
78
- ...queryOptions
79
- }
80
- );
79
+ queryKey: getQueryKey(),
80
+ ...queryOptions
81
+ });
81
82
 
82
83
  const queryClient = useQueryClient();
83
84
 
@@ -97,9 +98,8 @@ const useFetchQuery = <T extends object>({
97
98
  };
98
99
 
99
100
  const prefetchQuery = ({ endpointParams, queryKey }): void => {
100
- queryClient.prefetchQuery(
101
- queryKey,
102
- ({ signal }): Promise<T | ResponseError> =>
101
+ queryClient.prefetchQuery({
102
+ queryFn: ({ signal }): Promise<T | ResponseError> =>
103
103
  customFetch<T>({
104
104
  baseEndpoint,
105
105
  catchError,
@@ -108,8 +108,9 @@ const useFetchQuery = <T extends object>({
108
108
  endpoint: getEndpoint(endpointParams),
109
109
  headers: new Headers(fetchHeaders),
110
110
  signal
111
- })
112
- );
111
+ }),
112
+ queryKey
113
+ });
113
114
  };
114
115
 
115
116
  const prefetchNextPage = ({ page, getPrefetchQueryKey }): void => {
@@ -139,9 +140,8 @@ const useFetchQuery = <T extends object>({
139
140
  };
140
141
 
141
142
  const fetchQuery = (): Promise<T | ResponseError> => {
142
- return queryClient.fetchQuery(
143
- getQueryKey(),
144
- ({ signal }): Promise<T | ResponseError> =>
143
+ return queryClient.fetchQuery({
144
+ queryFn: ({ signal }): Promise<T | ResponseError> =>
145
145
  customFetch<T>({
146
146
  baseEndpoint,
147
147
  catchError,
@@ -150,8 +150,9 @@ const useFetchQuery = <T extends object>({
150
150
  endpoint: getEndpoint(),
151
151
  headers: new Headers(fetchHeaders),
152
152
  signal
153
- })
154
- );
153
+ }),
154
+ queryKey: getQueryKey()
155
+ });
155
156
  };
156
157
 
157
158
  const data = useMemo(
@@ -168,13 +169,16 @@ const useFetchQuery = <T extends object>({
168
169
 
169
170
  useEffect(() => {
170
171
  return (): void => {
171
- queryClient.cancelQueries(getQueryKey());
172
+ queryClient.cancelQueries({ queryKey: getQueryKey() });
172
173
  };
173
174
  }, []);
174
175
 
175
- useEffect(() => {
176
- manageError();
177
- }, useDeepCompare([queryData.data]));
176
+ useEffect(
177
+ () => {
178
+ manageError();
179
+ },
180
+ useDeepCompare([queryData.data])
181
+ );
178
182
 
179
183
  return {
180
184
  ...omit(['data', 'error'], queryData),
@@ -8,7 +8,7 @@ import {
8
8
  } from '@tanstack/react-query';
9
9
  import { JsonDecoder } from 'ts.data.json';
10
10
  import anylogger from 'anylogger';
11
- import { includes } from 'ramda';
11
+ import { includes, omit } from 'ramda';
12
12
 
13
13
  import { CatchErrorProps, customFetch, ResponseError } from '../customFetch';
14
14
  import useSnackbar from '../../Snackbar/useSnackbar';
@@ -31,14 +31,33 @@ export type UseMutationQueryProps<T, TMeta> = {
31
31
  getEndpoint: (_meta: TMeta) => string;
32
32
  httpCodesBypassErrorSnackbar?: Array<number>;
33
33
  method: Method;
34
- } & Omit<UseMutationOptions<T & { _meta?: TMeta }>, 'mutationFn'>;
34
+ onError?: (
35
+ error: ResponseError,
36
+ variables: T & { _meta: TMeta },
37
+ context: unknown
38
+ ) => unknown;
39
+ onMutate?: (variables: T & { _meta: TMeta }) => Promise<unknown> | unknown;
40
+ onSuccess?: (
41
+ data: ResponseError | T,
42
+ variables: T & {
43
+ _meta: TMeta;
44
+ },
45
+ context: unknown
46
+ ) => unknown;
47
+ } & Omit<
48
+ UseMutationOptions<T & { _meta?: TMeta }>,
49
+ 'mutationFn' | 'onError' | 'onMutate' | 'onSuccess'
50
+ >;
35
51
 
36
52
  const log = anylogger('API Request');
37
53
 
38
- export type UseMutationQueryState<T> = {
54
+ export type UseMutationQueryState<T> = Omit<
55
+ UseMutationResult<T | ResponseError>,
56
+ 'isError'
57
+ > & {
39
58
  isError: boolean;
40
59
  isMutating: boolean;
41
- } & UseMutationResult<T | ResponseError>;
60
+ };
42
61
 
43
62
  const useMutationQuery = <T extends object, TMeta>({
44
63
  getEndpoint,
@@ -51,6 +70,7 @@ const useMutationQuery = <T extends object, TMeta>({
51
70
  onMutate,
52
71
  onError,
53
72
  onSuccess,
73
+ onSettled,
54
74
  baseEndpoint
55
75
  }: UseMutationQueryProps<T, TMeta>): UseMutationQueryState<T> => {
56
76
  const { showErrorMessage } = useSnackbar();
@@ -59,8 +79,10 @@ const useMutationQuery = <T extends object, TMeta>({
59
79
  T | ResponseError,
60
80
  ResponseError,
61
81
  T & { _meta: TMeta }
62
- >(
63
- (_payload: T & { _meta: TMeta }): Promise<T | ResponseError> => {
82
+ >({
83
+ mutationFn: (
84
+ _payload: T & { _meta: TMeta }
85
+ ): Promise<T | ResponseError> => {
64
86
  const { _meta, ...payload } = _payload || {};
65
87
 
66
88
  return customFetch<T>({
@@ -78,12 +100,11 @@ const useMutationQuery = <T extends object, TMeta>({
78
100
  payload
79
101
  });
80
102
  },
81
- {
82
- onError,
83
- onMutate,
84
- onSuccess
85
- }
86
- );
103
+ onError,
104
+ onMutate,
105
+ onSettled,
106
+ onSuccess
107
+ });
87
108
 
88
109
  const manageError = (): void => {
89
110
  const data = queryData.data as ResponseError | undefined;
@@ -100,14 +121,17 @@ const useMutationQuery = <T extends object, TMeta>({
100
121
  }
101
122
  };
102
123
 
103
- useEffect(() => {
104
- manageError();
105
- }, useDeepCompare([queryData.data]));
124
+ useEffect(
125
+ () => {
126
+ manageError();
127
+ },
128
+ useDeepCompare([queryData.data])
129
+ );
106
130
 
107
131
  return {
108
- ...queryData,
132
+ ...omit(['isError'], queryData),
109
133
  isError: (queryData.data as ResponseError | undefined)?.isError || false,
110
- isMutating: queryData.isLoading
134
+ isMutating: queryData.isPending
111
135
  };
112
136
  };
113
137
 
@@ -0,0 +1,6 @@
1
+ import { RowId } from '../../Listing/models';
2
+ import { Listing, ListingProps } from '../..';
3
+
4
+ export const DataListing = <TRow extends { id: RowId }>(
5
+ props: ListingProps<TRow>
6
+ ): JSX.Element => <Listing<TRow> {...props} />;
@@ -0,0 +1,193 @@
1
+ import { Box } from '@mui/material';
2
+
3
+ import { ColumnType } from '../../Listing/models';
4
+
5
+ import { DataTable } from '.';
6
+
7
+ const data = Array(5)
8
+ .fill(0)
9
+ .map((_, idx) => ({
10
+ description: `Description ${idx}`,
11
+ id: idx,
12
+ title: `Entity ${idx}`
13
+ }));
14
+
15
+ const initializeDataTableGrid = ({
16
+ hasActions,
17
+ hasCardAction,
18
+ canDelete
19
+ }): void => {
20
+ cy.viewport(1200, 590);
21
+ cy.mount({
22
+ Component: (
23
+ <DataTable variant="grid">
24
+ {data.map(({ title, description }) => (
25
+ <DataTable.Item
26
+ description={description}
27
+ hasActions={hasActions}
28
+ hasCardAction={hasCardAction}
29
+ key={title}
30
+ labelsDelete={{
31
+ cancel: 'Cancel',
32
+ confirm: {
33
+ label: 'Delete'
34
+ }
35
+ }}
36
+ title={title}
37
+ onDelete={canDelete ? cy.stub() : undefined}
38
+ />
39
+ ))}
40
+ </DataTable>
41
+ )
42
+ });
43
+ };
44
+
45
+ const initializeDataTableEmpty = (canCreate = false): void => {
46
+ cy.viewport(1200, 590);
47
+ cy.mount({
48
+ Component: (
49
+ <DataTable isEmpty variant="grid">
50
+ <DataTable.EmptyState
51
+ canCreate={canCreate}
52
+ labels={{
53
+ actions: {
54
+ create: 'Create'
55
+ },
56
+ title: 'Welcome'
57
+ }}
58
+ onCreate={cy.stub()}
59
+ />
60
+ </DataTable>
61
+ )
62
+ });
63
+ };
64
+
65
+ const initializeDataTableListing = (): void => {
66
+ cy.viewport(1200, 590);
67
+ cy.mount({
68
+ Component: (
69
+ <Box sx={{ height: '100vh' }}>
70
+ <DataTable variant="listing">
71
+ <DataTable.Listing
72
+ columns={[
73
+ {
74
+ getFormattedString: (row) => row.title,
75
+ id: 'title',
76
+ label: 'Title',
77
+ type: ColumnType.string
78
+ },
79
+ {
80
+ getFormattedString: (row) => row.description,
81
+ id: 'description',
82
+ label: 'Description',
83
+ type: ColumnType.string
84
+ }
85
+ ]}
86
+ rows={data}
87
+ />
88
+ </DataTable>
89
+ </Box>
90
+ )
91
+ });
92
+ };
93
+
94
+ describe('DataTable: Grid', () => {
95
+ it('displays items with title and description only', () => {
96
+ initializeDataTableGrid({
97
+ canDelete: false,
98
+ hasActions: false,
99
+ hasCardAction: false
100
+ });
101
+
102
+ data.forEach(({ title, description }) => {
103
+ cy.contains(title).should('be.visible');
104
+ cy.contains(description).should('be.visible');
105
+ });
106
+
107
+ cy.makeSnapshot();
108
+ });
109
+
110
+ it('displays items with actions', () => {
111
+ initializeDataTableGrid({
112
+ canDelete: false,
113
+ hasActions: true,
114
+ hasCardAction: false
115
+ });
116
+
117
+ cy.findAllByLabelText('edit access rights').should('have.length', 5);
118
+ cy.findAllByLabelText('edit').should('have.length', 5);
119
+
120
+ cy.makeSnapshot();
121
+ });
122
+
123
+ it('displays items with delete action', () => {
124
+ initializeDataTableGrid({
125
+ canDelete: true,
126
+ hasActions: true,
127
+ hasCardAction: false
128
+ });
129
+
130
+ cy.findAllByLabelText('delete').should('have.length', 5);
131
+
132
+ cy.makeSnapshot();
133
+ });
134
+
135
+ it('displays items with card action only', () => {
136
+ initializeDataTableGrid({
137
+ canDelete: false,
138
+ hasActions: false,
139
+ hasCardAction: true
140
+ });
141
+
142
+ cy.findAllByLabelText('view').should('have.length', 5);
143
+
144
+ cy.makeSnapshot();
145
+ });
146
+
147
+ it('displays items with card action and bottom actions', () => {
148
+ initializeDataTableGrid({
149
+ canDelete: true,
150
+ hasActions: true,
151
+ hasCardAction: true
152
+ });
153
+
154
+ cy.findAllByLabelText('view').should('have.length', 5);
155
+ cy.findAllByLabelText('delete').should('have.length', 5);
156
+ cy.findAllByLabelText('edit access rights').should('have.length', 5);
157
+ cy.findAllByLabelText('edit').should('have.length', 5);
158
+
159
+ cy.makeSnapshot();
160
+ });
161
+ });
162
+
163
+ describe('DataTable: Empty', () => {
164
+ it('displays the title', () => {
165
+ initializeDataTableEmpty();
166
+
167
+ cy.contains('Welcome').should('be.visible');
168
+
169
+ cy.makeSnapshot();
170
+ });
171
+
172
+ it('displays the title and the action button', () => {
173
+ initializeDataTableEmpty(true);
174
+
175
+ cy.contains('Welcome').should('be.visible');
176
+ cy.contains('Create').should('be.visible');
177
+
178
+ cy.makeSnapshot();
179
+ });
180
+ });
181
+
182
+ describe('DataTable: Listing', () => {
183
+ it('displays the listing', () => {
184
+ initializeDataTableListing();
185
+
186
+ data.forEach(({ title, description }) => {
187
+ cy.contains(title).should('be.visible');
188
+ cy.contains(description).should('be.visible');
189
+ });
190
+
191
+ cy.makeSnapshot();
192
+ });
193
+ });
@@ -1,5 +1,9 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
 
3
+ import { Box } from '@mui/material';
4
+
5
+ import { ColumnType } from '../../Listing/models';
6
+
3
7
  import { DataTable } from './index';
4
8
 
5
9
  const meta: Meta<typeof DataTable> = {
@@ -49,3 +53,39 @@ export const withFixedHeightContainer: Story = {
49
53
  </div>
50
54
  )
51
55
  };
56
+
57
+ const ListingTemplate = (args): JSX.Element => (
58
+ <Box sx={{ height: '80vh' }}>
59
+ <DataTable {...args} />
60
+ </Box>
61
+ );
62
+
63
+ export const listing: Story = {
64
+ args: {
65
+ children: (
66
+ <DataTable.Listing
67
+ columns={[
68
+ {
69
+ getFormattedString: (row) => row.title,
70
+ id: 'title',
71
+ label: 'Title',
72
+ type: ColumnType.string
73
+ },
74
+ {
75
+ getFormattedString: (row) => row.description,
76
+ id: 'description',
77
+ label: 'Description',
78
+ type: ColumnType.string
79
+ }
80
+ ]}
81
+ rows={[...Array(5)].map((_, i) => ({
82
+ description: `Item description ${i}`,
83
+ id: i,
84
+ title: `Item ${i}`
85
+ }))}
86
+ />
87
+ ),
88
+ variant: 'listing'
89
+ },
90
+ render: ListingTemplate
91
+ };