@centreon/ui 24.4.38 → 24.4.39

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 (108) hide show
  1. package/package.json +19 -14
  2. package/public/mockServiceWorker.js +1 -1
  3. package/src/Button/Icon/index.tsx +1 -1
  4. package/src/Button/Save/StartIcon.tsx +3 -3
  5. package/src/Button/Save/index.tsx +9 -5
  6. package/src/Checkbox/Checkbox.tsx +2 -2
  7. package/src/Checkbox/CheckboxGroup/index.tsx +2 -2
  8. package/src/Dashboard/Item.tsx +1 -1
  9. package/src/Dashboard/Layout.tsx +2 -2
  10. package/src/Dialog/index.tsx +1 -1
  11. package/src/FallbackPage/FallbackPage.tsx +3 -3
  12. package/src/FileDropZone/index.tsx +3 -1
  13. package/src/Form/Form.cypress.spec.tsx +133 -0
  14. package/src/Form/Inputs/CheckboxGroup.tsx +1 -4
  15. package/src/Form/Inputs/List/Content.tsx +62 -0
  16. package/src/Form/Inputs/List/List.styles.ts +29 -0
  17. package/src/Form/Inputs/List/List.tsx +58 -0
  18. package/src/Form/Inputs/List/useList.ts +81 -0
  19. package/src/Form/Inputs/index.tsx +3 -1
  20. package/src/Form/Inputs/models.ts +9 -1
  21. package/src/Graph/LineChart/BasicComponents/Lines/Threshold/Circle.tsx +2 -2
  22. package/src/Graph/LineChart/BasicComponents/Lines/Threshold/index.tsx +5 -4
  23. package/src/Graph/LineChart/BasicComponents/Thresholds.tsx +2 -2
  24. package/src/Graph/LineChart/BasicComponents/useFilterLines.ts +1 -1
  25. package/src/Graph/LineChart/InteractiveComponents/AnchorPoint/GuidingLines.tsx +2 -2
  26. package/src/Graph/LineChart/InteractiveComponents/Annotations/Annotation/index.tsx +2 -3
  27. package/src/Graph/LineChart/InteractiveComponents/Annotations/EventAnnotations.tsx +1 -1
  28. package/src/Graph/LineChart/Legend/index.tsx +7 -8
  29. package/src/Graph/LineChart/Legend/useLegend.ts +3 -3
  30. package/src/Graph/LineChart/LineChart.tsx +4 -1
  31. package/src/Graph/LineChart/helpers/doc.ts +16 -13
  32. package/src/Graph/LineChart/helpers/index.ts +1 -1
  33. package/src/Graph/LineChart/index.stories.tsx +4 -2
  34. package/src/Graph/LineChart/index.tsx +4 -1
  35. package/src/Graph/SingleBar/Thresholds.tsx +2 -2
  36. package/src/Graph/Text/Text.stories.tsx +60 -4
  37. package/src/Graph/common/timeSeries/index.ts +3 -3
  38. package/src/InputField/Select/Autocomplete/Connected/index.tsx +10 -7
  39. package/src/InputField/Select/Autocomplete/Draggable/SortableList.tsx +1 -1
  40. package/src/InputField/Select/Autocomplete/Draggable/SortableListContent.tsx +1 -1
  41. package/src/InputField/Select/Autocomplete/Draggable/index.tsx +1 -1
  42. package/src/InputField/Select/Autocomplete/index.tsx +121 -115
  43. package/src/InputField/Select/IconPopover/index.tsx +2 -2
  44. package/src/InputField/Select/index.tsx +1 -1
  45. package/src/InputField/Text/index.tsx +2 -2
  46. package/src/Listing/Cell/DataCell.tsx +15 -1
  47. package/src/Listing/Header/ListingHeader.tsx +1 -1
  48. package/src/Listing/Listing.styles.ts +2 -3
  49. package/src/Listing/index.stories.tsx +12 -1
  50. package/src/Listing/index.tsx +1 -2
  51. package/src/Module/Module.cypress.spec.tsx +129 -0
  52. package/src/Module/index.tsx +2 -4
  53. package/src/RichTextEditor/RichTextEditor.tsx +12 -1
  54. package/src/SortableItems/index.tsx +2 -7
  55. package/src/ThemeProvider/index.tsx +24 -0
  56. package/src/TimePeriods/CustomTimePeriod/CompactCustomTimePeriod.styles.ts +6 -7
  57. package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/PickersStartEndDate.tsx +8 -3
  58. package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/models.ts +0 -2
  59. package/src/TimePeriods/DateTimePickerInput.tsx +56 -19
  60. package/src/TimePeriods/ResolutionTimePeriod.cypress.spec.tsx +12 -9
  61. package/src/TimePeriods/TimePeriods.cypress.spec.tsx +9 -33
  62. package/src/TimePeriods/helpers/index.ts +1 -1
  63. package/src/TimePeriods/index.stories.tsx +12 -4
  64. package/src/TimePeriods/index.tsx +2 -2
  65. package/src/api/QueryProvider.tsx +1 -1
  66. package/src/api/TestQueryProvider.tsx +1 -1
  67. package/src/api/useFetchQuery/index.ts +27 -23
  68. package/src/api/useGraphQuery/index.ts +26 -5
  69. package/src/api/useGraphQuery/models.ts +5 -0
  70. package/src/api/useMutationQuery/index.ts +45 -21
  71. package/src/components/Button/Icon/IconButton.tsx +6 -2
  72. package/src/components/CollapsibleItem/CollapsibleItem.cypress.spec.tsx +76 -0
  73. package/src/components/CollapsibleItem/CollapsibleItem.stories.tsx +26 -0
  74. package/src/components/CollapsibleItem/CollapsibleItem.tsx +43 -14
  75. package/src/components/CollapsibleItem/useCollapsibleItemStyles.ts +24 -1
  76. package/src/components/DataTable/DataListing.tsx +6 -0
  77. package/src/components/DataTable/DataTable.cypress.spec.tsx +193 -0
  78. package/src/components/DataTable/DataTable.stories.tsx +40 -0
  79. package/src/components/DataTable/DataTable.styles.ts +3 -0
  80. package/src/components/DataTable/DataTable.tsx +3 -3
  81. package/src/components/DataTable/Item/DataTableItem.styles.ts +7 -2
  82. package/src/components/DataTable/Item/DataTableItem.tsx +4 -4
  83. package/src/components/DataTable/index.ts +3 -1
  84. package/src/components/Form/Dashboard/DashboardForm.tsx +15 -12
  85. package/src/components/ItemComposition/Item.tsx +1 -1
  86. package/src/components/ItemComposition/ItemComposition.cypress.spec.tsx +116 -0
  87. package/src/components/ItemComposition/ItemComposition.styles.ts +8 -1
  88. package/src/components/ItemComposition/ItemComposition.tsx +26 -16
  89. package/src/components/Layout/PageLayout/PageLayout.tsx +1 -1
  90. package/src/components/Layout/PageLayout/PageLayoutActions.tsx +1 -0
  91. package/src/components/Layout/PageLayout/PageLayoutBody.tsx +1 -0
  92. package/src/components/Layout/PageLayout/PageLayoutHeader.tsx +5 -1
  93. package/src/components/Layout/PageLayout/PageQuickAccess.tsx +76 -0
  94. package/src/components/Layout/PageLayout/index.ts +3 -1
  95. package/src/components/Layout/PageLayout.cypress.spec.tsx +66 -0
  96. package/src/components/Modal/Modal.styles.ts +1 -1
  97. package/src/components/Tooltip/ConfirmationTooltip/ConfirmationTooltip.stories.tsx +3 -3
  98. package/src/components/Tooltip/ConfirmationTooltip/ConfirmationTooltip.tsx +1 -1
  99. package/src/components/Tooltip/ConfirmationTooltip/models.ts +1 -1
  100. package/src/index.ts +2 -2
  101. package/src/queryParameters/url/index.ts +5 -1
  102. package/src/utils/index.ts +2 -1
  103. package/src/utils/{useLicenseExpirationWarning.cypress.spec.tsx → useLicenseExpirationWarning.test.tsx} +48 -37
  104. package/src/utils/useLicenseExpirationWarning.ts +18 -18
  105. package/src/utils/usePluralizedTranslation.ts +21 -0
  106. package/src/screens/dashboard/DashboardsDetail.stories.tsx +0 -108
  107. package/src/screens/dashboard/DashboardsOverview.stories.tsx +0 -281
  108. package/src/utils/useDateTimePickerAdapter.ts +0 -309
@@ -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,19 +100,18 @@ const useMutationQuery = <T extends object, TMeta>({
78
100
  payload
79
101
  });
80
102
  },
81
- {
82
- onError,
83
- onMutate,
84
- onSuccess: (data, variables, context) => {
85
- if (data?.isError) {
86
- onError?.(data, variables, context);
103
+ onError,
104
+ onMutate,
105
+ onSettled,
106
+ onSuccess: (data, variables, context) => {
107
+ if (data?.isError) {
108
+ onError?.(data, variables, context);
87
109
 
88
- return;
89
- }
90
- onSuccess?.(data, variables, context);
110
+ return;
91
111
  }
112
+ onSuccess?.(data, variables, context);
92
113
  }
93
- );
114
+ });
94
115
 
95
116
  const manageError = (): void => {
96
117
  const data = queryData.data as ResponseError | undefined;
@@ -107,14 +128,17 @@ const useMutationQuery = <T extends object, TMeta>({
107
128
  }
108
129
  };
109
130
 
110
- useEffect(() => {
111
- manageError();
112
- }, useDeepCompare([queryData.data]));
131
+ useEffect(
132
+ () => {
133
+ manageError();
134
+ },
135
+ useDeepCompare([queryData.data])
136
+ );
113
137
 
114
138
  return {
115
- ...queryData,
139
+ ...omit(['isError'], queryData),
116
140
  isError: (queryData.data as ResponseError | undefined)?.isError || false,
117
- isMutating: queryData.isLoading
141
+ isMutating: queryData.isPending
118
142
  };
119
143
  };
120
144
 
@@ -1,6 +1,9 @@
1
1
  import React, { ReactElement, ReactNode } from 'react';
2
2
 
3
- import { IconButton as MuiIconButton } from '@mui/material';
3
+ import {
4
+ IconButton as MuiIconButton,
5
+ IconButtonProps as MuiIconButtonProps
6
+ } from '@mui/material';
4
7
 
5
8
  import { AriaLabelingAttributes } from '../../../@types/aria-attributes';
6
9
  import { DataTestAttributes } from '../../../@types/data-attributes';
@@ -23,7 +26,8 @@ type IconButtonProps = {
23
26
  size?: 'small' | 'medium' | 'large';
24
27
  variant?: 'primary' | 'secondary' | 'ghost';
25
28
  } & AriaLabelingAttributes &
26
- DataTestAttributes;
29
+ DataTestAttributes &
30
+ MuiIconButtonProps;
27
31
 
28
32
  /**
29
33
  * @todo re-factor as `iconVariant: 'icon-only'` Button variant, and remove IconButton component (reason: code duplication)
@@ -0,0 +1,76 @@
1
+ import { CollapsibleItem, Props } from './CollapsibleItem';
2
+
3
+ const title = 'Title';
4
+
5
+ const customizedTitle = <div>Customized title</div>;
6
+
7
+ const initialize = (props: Omit<Props, 'children'>): void => {
8
+ cy.mount({
9
+ Component: <CollapsibleItem {...props}>Content</CollapsibleItem>
10
+ });
11
+ };
12
+
13
+ describe('CollapsibleItem', () => {
14
+ it('displays the component collapsed by default', () => {
15
+ initialize({ title });
16
+
17
+ cy.contains(title).should('be.visible');
18
+ cy.contains('Content').should('not.be.visible');
19
+ cy.get('div[aria-expanded="false"]').should('exist');
20
+
21
+ cy.makeSnapshot();
22
+ });
23
+
24
+ it('displays the component expanded when the corresponding prop is set to true', () => {
25
+ initialize({ defaultExpanded: true, title });
26
+
27
+ cy.contains(title).should('be.visible');
28
+ cy.contains('Content').should('be.visible');
29
+ cy.get('div[aria-expanded="true"]').should('exist');
30
+
31
+ cy.makeSnapshot();
32
+ });
33
+
34
+ it('displays a customized title', () => {
35
+ initialize({ title: customizedTitle });
36
+
37
+ cy.contains('Customized title').should('be.visible');
38
+ cy.get('div[aria-expanded="false"]').should('exist');
39
+
40
+ cy.makeSnapshot();
41
+ });
42
+
43
+ it('displays the component as compact', () => {
44
+ initialize({ compact: true, title });
45
+
46
+ cy.contains(title).should('be.visible');
47
+ cy.get('div[aria-expanded="false"]').should('exist');
48
+ cy.get('div[data-compact="true"]').should('exist');
49
+
50
+ cy.makeSnapshot();
51
+ });
52
+
53
+ it('displays the component as compact and expanded when the icon is clicked', () => {
54
+ initialize({ compact: true, title });
55
+
56
+ cy.contains(title).should('be.visible');
57
+ cy.get('div[aria-expanded="false"]').should('exist');
58
+
59
+ cy.get('div[aria-expanded="false"]').click();
60
+
61
+ cy.get('div[aria-expanded="true"]').should('exist');
62
+ cy.contains('Content').should('be.visible');
63
+
64
+ cy.makeSnapshot();
65
+ });
66
+
67
+ it('displays the component as compact and a customized title', () => {
68
+ initialize({ compact: true, title: customizedTitle });
69
+
70
+ cy.contains('Customized title').should('be.visible');
71
+ cy.get('div[aria-expanded="false"]').should('exist');
72
+ cy.get('div[data-compact="true"]').should('exist');
73
+
74
+ cy.makeSnapshot();
75
+ });
76
+ });
@@ -1,5 +1,7 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
 
3
+ import { Checkbox, Typography } from '@mui/material';
4
+
3
5
  import { CollapsibleItem } from './CollapsibleItem';
4
6
 
5
7
  const meta: Meta<typeof CollapsibleItem> = {
@@ -23,3 +25,27 @@ export const ExpandedByDefault: Story = {
23
25
  title: 'Title'
24
26
  }
25
27
  };
28
+
29
+ export const customizedTitle: Story = {
30
+ args: {
31
+ children: 'Label',
32
+ defaultExpanded: false,
33
+ title: <Typography>Title</Typography>
34
+ }
35
+ };
36
+
37
+ export const customizedTitleAndCompact: Story = {
38
+ args: {
39
+ children: 'Label',
40
+ compact: true,
41
+ defaultExpanded: false,
42
+ title: (
43
+ <div
44
+ style={{ alignItems: 'center', display: 'flex', flexDirection: 'row' }}
45
+ >
46
+ <Checkbox size="small" />
47
+ <Typography>Title compact</Typography>
48
+ </div>
49
+ )
50
+ }
51
+ };
@@ -1,5 +1,7 @@
1
1
  import { ReactNode } from 'react';
2
2
 
3
+ import { equals, type } from 'ramda';
4
+
3
5
  import {
4
6
  AccordionDetails,
5
7
  AccordionSummary,
@@ -10,36 +12,63 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
10
12
 
11
13
  import { useCollapsibleItemStyles } from './useCollapsibleItemStyles';
12
14
 
13
- interface Props {
15
+ export interface Props {
14
16
  children: ReactNode;
17
+ compact?: boolean;
18
+ dataTestId?: string;
15
19
  defaultExpanded?: boolean;
16
- title: string;
20
+ title: string | JSX.Element;
17
21
  }
18
22
 
19
23
  export const CollapsibleItem = ({
20
24
  title,
21
25
  children,
22
- defaultExpanded
26
+ defaultExpanded,
27
+ compact = false,
28
+ dataTestId = ''
23
29
  }: Props): JSX.Element => {
24
- const { classes } = useCollapsibleItemStyles();
30
+ const { classes, cx } = useCollapsibleItemStyles();
31
+
32
+ const isStringTitle = equals(type(title), 'String');
25
33
 
26
34
  return (
27
35
  <Accordion
28
36
  disableGutters
29
37
  className={classes.accordion}
38
+ data-compact={compact}
39
+ data-testid={`${dataTestId}-accordion`}
30
40
  defaultExpanded={defaultExpanded}
31
41
  >
32
- <AccordionSummary
33
- classes={{
34
- content: classes.accordionSummary
35
- }}
36
- expandIcon={<ExpandMoreIcon color="primary" />}
42
+ <div className={classes.summaryContainer}>
43
+ <div className={classes.customTitle}>{!isStringTitle && title}</div>
44
+ <AccordionSummary
45
+ classes={{
46
+ content: cx(
47
+ compact
48
+ ? classes.accordionSummaryCompactContent
49
+ : classes.accordionSummary
50
+ ),
51
+ root: cx(
52
+ compact
53
+ ? classes.accordionSummaryCompactRoot
54
+ : classes.accordionSummaryRoot
55
+ )
56
+ }}
57
+ data-testid={`${dataTestId}-summary`}
58
+ expandIcon={<ExpandMoreIcon color="primary" />}
59
+ >
60
+ {isStringTitle && (
61
+ <Typography color="primary" variant="h6">
62
+ {title}
63
+ </Typography>
64
+ )}
65
+ </AccordionSummary>
66
+ </div>
67
+ <AccordionDetails
68
+ className={cx(
69
+ compact ? classes.accordionDetailsCompact : classes.accordionDetails
70
+ )}
37
71
  >
38
- <Typography color="primary" variant="h6">
39
- {title}
40
- </Typography>
41
- </AccordionSummary>
42
- <AccordionDetails className={classes.accordionDetails}>
43
72
  {children}
44
73
  </AccordionDetails>
45
74
  </Accordion>
@@ -4,12 +4,35 @@ export const useCollapsibleItemStyles = makeStyles()((theme) => ({
4
4
  accordion: {
5
5
  backgroundColor: 'transparent',
6
6
  border: 'none',
7
- borderBottom: `1px solid ${theme.palette.divider}`
7
+ borderBottom: `1px solid ${theme.palette.divider}`,
8
+ width: '100%'
8
9
  },
9
10
  accordionDetails: {
10
11
  padding: theme.spacing(0, 2, 2)
11
12
  },
13
+ accordionDetailsCompact: {
14
+ padding: theme.spacing(0)
15
+ },
12
16
  accordionSummary: {
13
17
  margin: theme.spacing(1.5, 0)
18
+ },
19
+ accordionSummaryCompactContent: {
20
+ margin: theme.spacing(0, 0, 0.5)
21
+ },
22
+ accordionSummaryCompactRoot: {
23
+ minHeight: theme.spacing(1),
24
+ width: '100%'
25
+ },
26
+ accordionSummaryRoot: {
27
+ width: '100%'
28
+ },
29
+ customTitle: {
30
+ whiteSpace: 'nowrap'
31
+ },
32
+ summaryContainer: {
33
+ alignItems: 'center',
34
+ display: 'flex',
35
+ flexDirection: 'row',
36
+ width: '100%'
14
37
  }
15
38
  }));
@@ -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
+ };
@@ -10,6 +10,9 @@ const useStyles = makeStyles()((theme) => ({
10
10
  gridGap: theme.spacing(2.5),
11
11
  gridTemplateColumns: `repeat(auto-fill, ${theme.spacing(45)})`
12
12
  },
13
+ '&[data-variant="listing"]': {
14
+ height: '100%'
15
+ },
13
16
  '&[data-variant][data-is-empty="true"]': {
14
17
  display: 'flex',
15
18
  justifyContent: 'center',
@@ -1,11 +1,11 @@
1
- import React, { ReactElement, ReactNode } from 'react';
1
+ import { ReactElement, ReactNode } from 'react';
2
2
 
3
3
  import { useStyles } from './DataTable.styles';
4
4
 
5
5
  type DataTableProps = {
6
- children: ReactNode | Array<ReactNode>;
6
+ children?: ReactNode | Array<ReactNode>;
7
7
  isEmpty?: boolean;
8
- variant?: 'grid';
8
+ variant: 'grid' | 'listing';
9
9
  };
10
10
 
11
11
  /** *
@@ -1,13 +1,18 @@
1
1
  import { makeStyles } from 'tss-react/mui';
2
2
 
3
3
  const useStyles = makeStyles()((theme) => ({
4
+ actions: {
5
+ display: 'flex',
6
+ flexDirection: 'row',
7
+ justifyContent: 'space-between'
8
+ },
4
9
  dataTableItem: {
5
10
  '& .MuiCardActionArea-root': {
6
11
  alignItems: 'flex-start',
7
12
  display: 'flex',
8
13
  flexDirection: 'column',
9
14
  height: '100%',
10
- justifyContent: 'space-between'
15
+ justifyContent: 'flex-start'
11
16
  },
12
17
  '& .MuiCardActions-root': {
13
18
  '& > span': {
@@ -15,13 +20,13 @@ const useStyles = makeStyles()((theme) => ({
15
20
  gap: theme.spacing(1)
16
21
  },
17
22
  display: 'flex',
18
-
19
23
  justifyContent: 'space-between'
20
24
  },
21
25
  borderRadius: theme.shape.borderRadius,
22
26
  display: 'flex',
23
27
  flexDirection: 'column',
24
28
  height: '186px',
29
+ justifyContent: 'space-between',
25
30
  p: {
26
31
  color: theme.palette.text.secondary,
27
32
  letterSpacing: '0',