@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.
- package/package.json +17 -12
- package/src/Button/Save/StartIcon.tsx +3 -3
- package/src/Dashboard/Item.tsx +1 -1
- package/src/Dashboard/Layout.tsx +2 -2
- package/src/FileDropZone/index.tsx +3 -1
- package/src/Form/Form.cypress.spec.tsx +133 -0
- package/src/Form/Inputs/CheckboxGroup.tsx +1 -4
- package/src/Form/Inputs/List/Content.tsx +62 -0
- package/src/Form/Inputs/List/List.styles.ts +29 -0
- package/src/Form/Inputs/List/List.tsx +58 -0
- package/src/Form/Inputs/List/useList.ts +81 -0
- package/src/Form/Inputs/index.tsx +3 -1
- package/src/Form/Inputs/models.ts +9 -1
- package/src/Graph/LineChart/BasicComponents/Lines/Threshold/Circle.tsx +2 -2
- package/src/Graph/LineChart/BasicComponents/Lines/Threshold/index.tsx +5 -4
- package/src/Graph/LineChart/BasicComponents/Thresholds.tsx +2 -2
- package/src/Graph/LineChart/BasicComponents/useFilterLines.ts +1 -1
- package/src/Graph/LineChart/InteractiveComponents/AnchorPoint/GuidingLines.tsx +2 -2
- package/src/Graph/LineChart/InteractiveComponents/Annotations/Annotation/index.tsx +2 -3
- package/src/Graph/LineChart/InteractiveComponents/Annotations/EventAnnotations.tsx +1 -1
- package/src/Graph/LineChart/Legend/useLegend.ts +3 -3
- package/src/Graph/LineChart/helpers/doc.ts +16 -13
- package/src/Graph/LineChart/helpers/index.ts +1 -1
- package/src/Graph/LineChart/index.stories.tsx +4 -2
- package/src/Graph/SingleBar/Thresholds.tsx +2 -2
- package/src/Graph/Text/Text.stories.tsx +60 -4
- package/src/Graph/common/timeSeries/index.ts +3 -3
- package/src/InputField/Select/Autocomplete/Connected/index.tsx +10 -7
- package/src/InputField/Select/Autocomplete/Draggable/SortableList.tsx +1 -1
- package/src/InputField/Select/Autocomplete/Draggable/SortableListContent.tsx +1 -1
- package/src/InputField/Select/Autocomplete/Draggable/index.tsx +1 -1
- package/src/InputField/Select/IconPopover/index.tsx +2 -2
- package/src/InputField/Select/index.tsx +1 -1
- package/src/Listing/Header/ListingHeader.tsx +1 -1
- package/src/Listing/Listing.styles.ts +2 -3
- package/src/Listing/index.stories.tsx +12 -1
- package/src/Listing/index.tsx +1 -2
- package/src/RichTextEditor/RichTextEditor.tsx +12 -1
- package/src/SortableItems/index.tsx +2 -7
- package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/PickersStartEndDate.tsx +8 -3
- package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/models.ts +0 -2
- package/src/TimePeriods/DateTimePickerInput.tsx +45 -17
- package/src/TimePeriods/TimePeriods.cypress.spec.tsx +9 -33
- package/src/TimePeriods/helpers/index.ts +1 -1
- package/src/TimePeriods/index.stories.tsx +12 -4
- package/src/TimePeriods/index.tsx +2 -2
- package/src/Typography/Subtitle.tsx +55 -0
- package/src/api/QueryProvider.tsx +1 -1
- package/src/api/TestQueryProvider.tsx +1 -1
- package/src/api/useFetchQuery/index.ts +27 -23
- package/src/api/useMutationQuery/index.ts +41 -17
- package/src/components/DataTable/DataListing.tsx +6 -0
- package/src/components/DataTable/DataTable.cypress.spec.tsx +193 -0
- package/src/components/DataTable/DataTable.stories.tsx +40 -0
- package/src/components/DataTable/DataTable.styles.ts +3 -0
- package/src/components/DataTable/DataTable.tsx +3 -3
- package/src/components/DataTable/Item/DataTableItem.styles.ts +7 -2
- package/src/components/DataTable/Item/DataTableItem.tsx +2 -2
- package/src/components/DataTable/index.ts +3 -1
- package/src/components/Form/AccessRights/__fixtures__/contactAccessRight.mock.ts +2 -0
- package/src/components/Form/AccessRights/useAccessRightsForm.utils.ts +1 -1
- package/src/components/Form/Dashboard/DashboardForm.tsx +15 -12
- package/src/components/Modal/Modal.styles.ts +4 -3
- package/src/components/Modal/ModalActions.tsx +4 -2
- package/src/index.ts +2 -0
- package/src/queryParameters/url/index.ts +5 -1
- package/src/utils/index.ts +1 -1
- package/src/utils/{useLicenseExpirationWarning.cypress.spec.tsx → useLicenseExpirationWarning.test.tsx} +48 -37
- package/src/utils/useLicenseExpirationWarning.ts +18 -18
- package/src/utils/usePluralizedTranslation.ts +21 -0
- package/src/screens/dashboard/DashboardsDetail.stories.tsx +0 -108
- package/src/screens/dashboard/DashboardsOverview.stories.tsx +0 -281
- 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
|
|
6
|
+
import { equals } from 'ramda';
|
|
7
7
|
import { renderHook } from '@testing-library/react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
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
|
-
<
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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',
|
|
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',
|
|
395
|
+
cy.get('@listWeeks').should('have.length', numberWeeks);
|
|
420
396
|
|
|
421
397
|
cy.get('@listWeeks')
|
|
422
398
|
.eq(numberWeeks - 1)
|
|
@@ -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<
|
|
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;
|
|
@@ -26,7 +26,9 @@ export interface UseFetchQueryProps<T> {
|
|
|
26
26
|
getQueryKey: () => QueryKey;
|
|
27
27
|
httpCodesBypassErrorSnackbar?: Array<number>;
|
|
28
28
|
isPaginated?: boolean;
|
|
29
|
-
queryOptions?:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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.
|
|
134
|
+
isMutating: queryData.isPending
|
|
111
135
|
};
|
|
112
136
|
};
|
|
113
137
|
|
|
@@ -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
|
+
};
|