@centreon/ui 24.5.8 → 24.5.9

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 (78) hide show
  1. package/package.json +4 -8
  2. package/public/mockServiceWorker.js +100 -81
  3. package/src/ActionsList/index.stories.tsx +1 -7
  4. package/src/Dashboard/Dashboard.styles.ts +2 -3
  5. package/src/Dashboard/Item.tsx +5 -39
  6. package/src/Dashboard/Layout.tsx +2 -4
  7. package/src/Dashboard/utils.ts +1 -1
  8. package/src/Graph/BarStack/BarStack.stories.tsx +6 -8
  9. package/src/Graph/BarStack/ResponsiveBarStack.tsx +3 -3
  10. package/src/Graph/HeatMap/HeatMap.stories.tsx +0 -20
  11. package/src/Graph/LineChart/BasicComponents/Lines/RegularLines/index.tsx +8 -12
  12. package/src/Graph/LineChart/BasicComponents/Lines/StackedLines/index.tsx +8 -8
  13. package/src/Graph/LineChart/BasicComponents/Lines/Threshold/BasicThreshold.tsx +2 -1
  14. package/src/Graph/LineChart/BasicComponents/Lines/Threshold/ThresholdWithPatternLines.tsx +2 -1
  15. package/src/Graph/LineChart/BasicComponents/Lines/Threshold/ThresholdWithVariation.tsx +2 -1
  16. package/src/Graph/LineChart/BasicComponents/Lines/Threshold/index.tsx +2 -1
  17. package/src/Graph/LineChart/BasicComponents/Lines/index.tsx +2 -1
  18. package/src/Graph/LineChart/BasicComponents/Lines/models.ts +3 -0
  19. package/src/Graph/LineChart/Legend/LegendHeader.tsx +1 -4
  20. package/src/Graph/LineChart/LineChart.cypress.spec.tsx +0 -53
  21. package/src/Graph/LineChart/LineChart.tsx +2 -1
  22. package/src/Graph/LineChart/common/index.ts +1 -15
  23. package/src/Graph/LineChart/index.stories.tsx +3 -15
  24. package/src/Graph/LineChart/index.tsx +4 -2
  25. package/src/Graph/LineChart/useLineChartData.ts +18 -57
  26. package/src/Graph/PieChart/PieChart.stories.tsx +15 -11
  27. package/src/Graph/PieChart/ResponsivePie.tsx +1 -1
  28. package/src/Graph/Tree/DescendantNodes.tsx +0 -1
  29. package/src/Graph/Tree/Links.tsx +2 -15
  30. package/src/Graph/Tree/Tree.cypress.spec.tsx +0 -24
  31. package/src/Graph/Tree/Tree.stories.tsx +1 -17
  32. package/src/Graph/Tree/models.ts +0 -3
  33. package/src/Graph/common/utils.ts +1 -49
  34. package/src/Listing/Cell/index.tsx +23 -17
  35. package/src/TimePeriods/index.stories.tsx +12 -7
  36. package/src/TopCounterElements/TopCounterLayout.tsx +4 -3
  37. package/src/api/QueryProvider.tsx +1 -1
  38. package/src/api/useFetchQuery/index.test.ts +5 -0
  39. package/src/api/useFetchQuery/index.ts +7 -12
  40. package/src/api/useGraphQuery/index.ts +2 -9
  41. package/src/api/useMutationQuery/index.ts +5 -2
  42. package/src/api/useRequest/index.test.ts +3 -0
  43. package/src/api/useRequest/index.ts +6 -3
  44. package/src/components/Form/AccessRights/AccessRights.cypress.spec.tsx +13 -27
  45. package/src/components/Form/AccessRights/AccessRights.stories.tsx +19 -0
  46. package/src/components/Form/AccessRights/AccessRights.styles.ts +1 -1
  47. package/src/components/Form/AccessRights/AccessRights.tsx +5 -6
  48. package/src/components/Form/AccessRights/Actions/Actions.styles.ts +7 -3
  49. package/src/components/Form/AccessRights/Actions/Actions.tsx +32 -15
  50. package/src/components/Form/AccessRights/Actions/useActions.ts +37 -4
  51. package/src/components/Form/AccessRights/ShareInput/ShareInput.tsx +0 -1
  52. package/src/components/Form/AccessRights/models.ts +3 -0
  53. package/src/components/Form/AccessRights/storiesData.ts +3 -0
  54. package/src/components/Form/Dashboard/DashboardForm.stories.ts +39 -0
  55. package/src/components/Form/Dashboard/translatedLabels.ts +1 -0
  56. package/src/components/List/Item/ListItem.styles.ts +2 -2
  57. package/src/components/Modal/Modal.styles.ts +5 -5
  58. package/src/components/Zoom/Minimap.tsx +2 -4
  59. package/src/components/Zoom/Zoom.cypress.spec.tsx +13 -13
  60. package/src/components/Zoom/Zoom.tsx +1 -4
  61. package/src/components/Zoom/ZoomContent.tsx +2 -5
  62. package/src/components/index.ts +0 -1
  63. package/src/index.ts +1 -1
  64. package/src/utils/index.ts +0 -1
  65. package/src/utils/usePluralizedTranslation.ts +3 -20
  66. package/src/Dashboard/Dashboard.cypress.spec.tsx +0 -68
  67. package/src/Graph/LineChart/mockedData/curvesWithSameColor.json +0 -252
  68. package/src/api/logger.ts +0 -11
  69. package/src/components/Form/AccessRights/useAccessRightsChange.ts +0 -30
  70. package/src/components/Form/AccessRights/utils.ts +0 -18
  71. package/src/components/Tabs/Tab.styles.ts +0 -25
  72. package/src/components/Tabs/TabPanel.tsx +0 -22
  73. package/src/components/Tabs/Tabs.cypress.spec.tsx +0 -70
  74. package/src/components/Tabs/Tabs.stories.tsx +0 -55
  75. package/src/components/Tabs/Tabs.tsx +0 -55
  76. package/src/components/Tabs/index.ts +0 -6
  77. package/src/utils/resourcesStatusURL.ts +0 -166
  78. package/src/utils/usePluralizedTranslation.test.ts +0 -159
@@ -95,6 +95,9 @@ const useStyles = makeStyles<StylesProps>()(
95
95
  caretMore: {
96
96
  transform: 'rotate3d(0,0,1,180deg)'
97
97
  },
98
+ fakeCaret: {
99
+ marginLeft: theme.spacing(3)
100
+ },
98
101
  root: {
99
102
  alignItems: 'center',
100
103
  backgroundColor: getBackgroundColor({
@@ -195,23 +198,26 @@ const Cell = ({
195
198
  props
196
199
  )}
197
200
  >
198
- {displaySubItemsCaret && hasSubItems && (
199
- <IconButton
200
- ariaLabel={`${isSubItemsExpanded ? labelCollapse : labelExpand} ${
201
- props.row.id
202
- }`}
203
- size="small"
204
- onClick={click}
205
- >
206
- <ExpandMoreIcon
207
- className={cx(
208
- classes.caret,
209
- isSubItemsExpanded ? classes.caretMore : classes.caretLess
210
- )}
211
- fontSize="small"
212
- />
213
- </IconButton>
214
- )}
201
+ {displaySubItemsCaret &&
202
+ (hasSubItems ? (
203
+ <IconButton
204
+ ariaLabel={`${isSubItemsExpanded ? labelCollapse : labelExpand} ${
205
+ props.row.id
206
+ }`}
207
+ size="small"
208
+ onClick={click}
209
+ >
210
+ <ExpandMoreIcon
211
+ className={cx(
212
+ classes.caret,
213
+ isSubItemsExpanded ? classes.caretMore : classes.caretLess
214
+ )}
215
+ fontSize="small"
216
+ />
217
+ </IconButton>
218
+ ) : (
219
+ <div className={classes.fakeCaret} />
220
+ ))}
215
221
  {children}
216
222
  </TableCell>
217
223
  );
@@ -10,9 +10,6 @@ import TimePeriod from '.';
10
10
 
11
11
  const meta: Meta<typeof TimePeriod> = {
12
12
  component: TimePeriod,
13
- parameters: {
14
- chromatic: { disableSnapshot: true }
15
- },
16
13
  tags: ['autodocs']
17
14
  };
18
15
 
@@ -91,20 +88,27 @@ const args = {
91
88
  ]
92
89
  };
93
90
 
91
+ const parameters = {
92
+ chromatic: { diffThreshold: 0.1 }
93
+ };
94
+
94
95
  export const BasicTimePeriod: Story = {
95
96
  ...Template,
96
- argTypes
97
+ argTypes,
98
+ parameters
97
99
  };
98
100
 
99
101
  export const WithExtraTimePeriods: Story = {
100
102
  ...Template,
101
103
  argTypes,
102
- args
104
+ args,
105
+ parameters
103
106
  };
104
107
 
105
108
  export const WithExternalComponent: Story = {
106
109
  ...TemplateWithExternalComponent,
107
- argTypes
110
+ argTypes,
111
+ parameters
108
112
  };
109
113
 
110
114
  export const SimpleTimePeriod: StorySimpleTimePeriod = {
@@ -112,5 +116,6 @@ export const SimpleTimePeriod: StorySimpleTimePeriod = {
112
116
  args: {
113
117
  endDate: dayjs(Date.now()).toDate(),
114
118
  startDate: dayjs(Date.now()).subtract(29, 'day').toDate()
115
- }
119
+ },
120
+ parameters
116
121
  };
@@ -1,11 +1,11 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useState, useEffect } from 'react';
2
2
 
3
3
  import { makeStyles } from 'tss-react/mui';
4
4
 
5
- import ExpandLessIcon from '@mui/icons-material/ExpandLess';
6
5
  import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
7
- import type { SvgIcon } from '@mui/material';
6
+ import ExpandLessIcon from '@mui/icons-material/ExpandLess';
8
7
  import { Badge, ClickAwayListener } from '@mui/material';
8
+ import type { SvgIcon } from '@mui/material';
9
9
 
10
10
  import useCloseOnLegacyPage from './useCloseOnLegacyPage';
11
11
 
@@ -115,6 +115,7 @@ const TopCounterLayout = ({
115
115
  }: TopCounterLayoutProps): JSX.Element => {
116
116
  const { classes, cx } = useStyles();
117
117
  const [toggled, setToggled] = useState(false);
118
+
118
119
  const subMenuId = title.replace(/[^A-Za-z]/, '-');
119
120
  useCloseOnLegacyPage({ setToggled });
120
121
 
@@ -4,7 +4,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
4
 
5
5
  const defaultCacheTime = 5 * 1_000;
6
6
 
7
- export const client = new QueryClient({
7
+ const client = new QueryClient({
8
8
  defaultOptions: {
9
9
  queries: {
10
10
  gcTime: defaultCacheTime,
@@ -1,5 +1,6 @@
1
1
  import { renderHook, waitFor, RenderHookResult } from '@testing-library/react';
2
2
  import fetchMock from 'jest-fetch-mock';
3
+ import anyLogger from 'anylogger';
3
4
 
4
5
  import TestQueryProvider from '../TestQueryProvider';
5
6
 
@@ -76,6 +77,8 @@ describe('useFetchQuery', () => {
76
77
  expect(mockedShowErrorMessage).toHaveBeenCalledWith('custom message');
77
78
  });
78
79
 
80
+ expect(anyLogger().error).toHaveBeenCalledWith('custom message');
81
+
79
82
  await waitFor(() => {
80
83
  expect(result.current.error).toStrictEqual({
81
84
  additionalInformation: {
@@ -99,6 +102,8 @@ describe('useFetchQuery', () => {
99
102
  await waitFor(() => {
100
103
  expect(mockedShowErrorMessage).toHaveBeenCalledWith('error');
101
104
  });
105
+
106
+ expect(anyLogger().error).toHaveBeenCalledWith('error');
102
107
  });
103
108
 
104
109
  it('shows a default failure message via the Snackbar as fallback', async () => {
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useMemo, useRef } from 'react';
2
2
 
3
+ import 'ulog';
3
4
  import {
4
5
  QueryKey,
5
6
  QueryObserverBaseResult,
@@ -8,12 +9,12 @@ import {
8
9
  UseQueryOptions
9
10
  } from '@tanstack/react-query';
10
11
  import { JsonDecoder } from 'ts.data.json';
11
- import { equals, has, includes, isNil, not, omit } from 'ramda';
12
+ import anylogger from 'anylogger';
13
+ import { has, includes, isNil, not, omit } from 'ramda';
12
14
 
13
15
  import { CatchErrorProps, customFetch, ResponseError } from '../customFetch';
14
16
  import useSnackbar from '../../Snackbar/useSnackbar';
15
17
  import { useDeepCompare } from '../../utils';
16
- import { errorLog } from '../logger';
17
18
 
18
19
  export interface UseFetchQueryProps<T> {
19
20
  baseEndpoint?: string;
@@ -32,7 +33,6 @@ export interface UseFetchQueryProps<T> {
32
33
  UseQueryOptions<T | ResponseError, Error, T | ResponseError, QueryKey>,
33
34
  'queryKey' | 'queryFn'
34
35
  >;
35
- useLongCache?: boolean;
36
36
  }
37
37
 
38
38
  export type UseFetchQueryState<T> = {
@@ -48,6 +48,8 @@ export interface PrefetchEndpointParams {
48
48
  page: number;
49
49
  }
50
50
 
51
+ const log = anylogger('API Request');
52
+
51
53
  const useFetchQuery = <T extends object>({
52
54
  getEndpoint,
53
55
  getQueryKey,
@@ -59,18 +61,12 @@ const useFetchQuery = <T extends object>({
59
61
  queryOptions,
60
62
  httpCodesBypassErrorSnackbar = [],
61
63
  baseEndpoint,
62
- doNotCancelCallsOnUnmount = false,
63
- useLongCache
64
+ doNotCancelCallsOnUnmount = false
64
65
  }: UseFetchQueryProps<T>): UseFetchQueryState<T> => {
65
66
  const dataRef = useRef<T | undefined>(undefined);
66
67
 
67
68
  const { showErrorMessage } = useSnackbar();
68
69
 
69
- const isCypressTest = equals(window.Cypress?.testingType, 'component');
70
-
71
- const cacheOptions =
72
- !isCypressTest && useLongCache ? { gcTime: 60 * 1000 } : {};
73
-
74
70
  const queryData = useQuery<T | ResponseError, Error>({
75
71
  queryFn: ({ signal }): Promise<T | ResponseError> =>
76
72
  customFetch<T>({
@@ -83,7 +79,6 @@ const useFetchQuery = <T extends object>({
83
79
  signal
84
80
  }),
85
81
  queryKey: getQueryKey(),
86
- ...cacheOptions,
87
82
  ...queryOptions
88
83
  });
89
84
 
@@ -92,7 +87,7 @@ const useFetchQuery = <T extends object>({
92
87
  const manageError = (): void => {
93
88
  const data = queryData.data as ResponseError | undefined;
94
89
  if (data?.isError) {
95
- errorLog(data.message);
90
+ log.error(data.message);
96
91
  const hasACorrespondingHttpCode = includes(
97
92
  data?.statusCode || 0,
98
93
  httpCodesBypassErrorSnackbar
@@ -24,7 +24,6 @@ interface CustomTimePeriod {
24
24
  interface UseMetricsQueryProps {
25
25
  baseEndpoint: string;
26
26
  bypassMetricsExclusion?: boolean;
27
- bypassQueryParams?: boolean;
28
27
  includeAllResources?: boolean;
29
28
  metrics: Array<Metric>;
30
29
  refreshCount?: number;
@@ -91,8 +90,7 @@ const useGraphQuery = ({
91
90
  timePeriodType: 1
92
91
  },
93
92
  refreshInterval = false,
94
- refreshCount,
95
- bypassQueryParams = false
93
+ refreshCount
96
94
  }: UseMetricsQueryProps): UseMetricsQueryState => {
97
95
  const timePeriodToUse = equals(timePeriod?.timePeriodType, -1)
98
96
  ? {
@@ -116,10 +114,6 @@ const useGraphQuery = ({
116
114
  isLoading
117
115
  } = useFetchQuery<PerformanceGraphData>({
118
116
  getEndpoint: () => {
119
- if (bypassQueryParams) {
120
- return baseEndpoint;
121
- }
122
-
123
117
  const endpoint = buildListingEndpoint({
124
118
  baseEndpoint,
125
119
  parameters: {
@@ -149,8 +143,7 @@ const useGraphQuery = ({
149
143
  enabled: areResourcesFullfilled(resources) && !isEmpty(definedMetrics),
150
144
  refetchInterval: refreshInterval,
151
145
  suspense: false
152
- },
153
- useLongCache: true
146
+ }
154
147
  });
155
148
 
156
149
  const data = useRef<PerformanceGraphData | undefined>(undefined);
@@ -1,3 +1,4 @@
1
+ import 'ulog';
1
2
  import { useEffect } from 'react';
2
3
 
3
4
  import {
@@ -6,12 +7,12 @@ import {
6
7
  UseMutationResult
7
8
  } from '@tanstack/react-query';
8
9
  import { JsonDecoder } from 'ts.data.json';
10
+ import anylogger from 'anylogger';
9
11
  import { includes, omit } from 'ramda';
10
12
 
11
13
  import { CatchErrorProps, customFetch, ResponseError } from '../customFetch';
12
14
  import useSnackbar from '../../Snackbar/useSnackbar';
13
15
  import { useDeepCompare } from '../../utils';
14
- import { errorLog } from '../logger';
15
16
 
16
17
  export enum Method {
17
18
  DELETE = 'DELETE',
@@ -51,6 +52,8 @@ export type UseMutationQueryProps<T, TMeta> = {
51
52
  'mutationFn' | 'onError' | 'onMutate' | 'onSuccess' | 'mutateAsync' | 'mutate'
52
53
  >;
53
54
 
55
+ const log = anylogger('API Request');
56
+
54
57
  export type UseMutationQueryState<T, TMeta> = Omit<
55
58
  UseMutationResult<T | ResponseError>,
56
59
  'isError' | 'mutate' | 'mutateAsync'
@@ -124,7 +127,7 @@ const useMutationQuery = <T extends object, TMeta>({
124
127
  const manageError = (): void => {
125
128
  const data = queryData.data as ResponseError | undefined;
126
129
  if (data?.isError) {
127
- errorLog(data.message);
130
+ log.error(data.message);
128
131
  const hasACorrespondingHttpCode = includes(
129
132
  data?.statusCode || 0,
130
133
  httpCodesBypassErrorSnackbar
@@ -1,4 +1,5 @@
1
1
  import axios from 'axios';
2
+ import anyLogger from 'anylogger';
2
3
  import { RenderHookResult, renderHook, act } from '@testing-library/react';
3
4
 
4
5
  import useRequest, { RequestResult, RequestParams } from '.';
@@ -76,6 +77,8 @@ describe(useRequest, () => {
76
77
  });
77
78
  });
78
79
 
80
+ expect(anyLogger().error).toHaveBeenCalledWith(response);
81
+
79
82
  expect(mockedShowErrorMessage).toHaveBeenCalledWith('failure');
80
83
  });
81
84
 
@@ -1,12 +1,15 @@
1
1
  import { useState, useEffect } from 'react';
2
2
 
3
+ import 'ulog';
3
4
  import axios from 'axios';
4
5
  import { pathOr, defaultTo, path, includes, or } from 'ramda';
6
+ import anylogger from 'anylogger';
5
7
  import { JsonDecoder } from 'ts.data.json';
6
8
 
7
9
  import useCancelTokenSource from '../useCancelTokenSource';
8
10
  import useSnackbar from '../../Snackbar/useSnackbar';
9
- import { errorLog, warnLog } from '../logger';
11
+
12
+ const log = anylogger('API Request');
10
13
 
11
14
  export interface RequestParams<TResult> {
12
15
  decoder?: JsonDecoder.Decoder<TResult>;
@@ -38,7 +41,7 @@ const useRequest = <TResult>({
38
41
  }, []);
39
42
 
40
43
  const showRequestErrorMessage = (error): void => {
41
- errorLog(error.message);
44
+ log.error(error);
42
45
 
43
46
  const message = or(
44
47
  pathOr(undefined, ['response', 'data', 'message'], error),
@@ -65,7 +68,7 @@ const useRequest = <TResult>({
65
68
  .catch((error) => {
66
69
  setSending(false);
67
70
  if (axios.isCancel(error)) {
68
- warnLog(error);
71
+ log.warn(error);
69
72
 
70
73
  throw error;
71
74
  }
@@ -15,11 +15,11 @@ import {
15
15
 
16
16
  const initialize = ({
17
17
  initialValues = simpleAccessRights,
18
- loading = false
18
+ loading = false,
19
+ link = 'link'
19
20
  }): unknown => {
20
21
  const cancel = cy.stub();
21
22
  const save = cy.stub();
22
- const change = cy.stub();
23
23
 
24
24
  cy.interceptAPIRequest({
25
25
  alias: 'getContacts',
@@ -47,10 +47,10 @@ const initialize = ({
47
47
  }}
48
48
  initialValues={initialValues}
49
49
  labels={labels}
50
+ link={link}
50
51
  loading={loading}
51
52
  roles={roles}
52
53
  submit={save}
53
- onChange={change}
54
54
  />
55
55
  </Provider>
56
56
  </TestQueryProvider>
@@ -60,7 +60,6 @@ const initialize = ({
60
60
 
61
61
  return {
62
62
  cancel,
63
- change,
64
63
  save
65
64
  };
66
65
  };
@@ -75,12 +74,21 @@ describe('Access rights', () => {
75
74
  cy.findByLabelText('Add a contact').should('be.visible');
76
75
  cy.findByTestId('add_role').should('be.disabled');
77
76
  cy.findByTestId('add').should('be.disabled');
77
+ cy.findByLabelText('Copy link').should('be.visible');
78
78
  cy.findByLabelText('Cancel').should('be.visible');
79
79
  cy.findByLabelText('Save').should('be.visible');
80
80
 
81
81
  cy.makeSnapshot();
82
82
  });
83
83
 
84
+ it('displays the access rights without link', () => {
85
+ initialize({ link: null });
86
+
87
+ cy.findByLabelText('Copy link').should('not.exist');
88
+
89
+ cy.makeSnapshot();
90
+ });
91
+
84
92
  it('displays the access rights with an empty list', () => {
85
93
  initialize({ initialValues: emptyAccessRights });
86
94
 
@@ -89,7 +97,7 @@ describe('Access rights', () => {
89
97
  cy.makeSnapshot();
90
98
  });
91
99
 
92
- it('displays the access rights list', () => {
100
+ it('displays the access rights with an empty list', () => {
93
101
  initialize({});
94
102
 
95
103
  simpleAccessRights.forEach(({ name, email, isContactGroup, role }) => {
@@ -362,26 +370,4 @@ describe('Access rights', () => {
362
370
 
363
371
  cy.makeSnapshot();
364
372
  });
365
-
366
- it('calls the change function when the corresponding prop is set and the form is updated', () => {
367
- const { change } = initialize({});
368
-
369
- cy.contains(labels.add.contact).click();
370
- cy.findByLabelText(labels.add.autocompleteContact).click();
371
-
372
- cy.waitForRequest('@getContacts');
373
-
374
- cy.contains('Entity 10').click();
375
-
376
- cy.findByTestId('add').click();
377
-
378
- cy.contains('Entity 10').should('be.visible');
379
-
380
- cy.findByTestId('role-Entity 10').should('have.value', 'viewer');
381
- cy.contains(labels.list.added)
382
- .should('be.visible')
383
- .then(() => {
384
- expect(change).to.have.callCount(2);
385
- });
386
- });
387
373
  });
@@ -47,6 +47,7 @@ export const Default: Story = {
47
47
  },
48
48
  initialValues: defaultAccessRights,
49
49
  labels,
50
+ link: 'link',
50
51
  roles,
51
52
  submit: () => undefined
52
53
  },
@@ -62,6 +63,7 @@ export const AccessRightsWithStates: Story = {
62
63
  },
63
64
  initialValues: accessRightsWithStates,
64
65
  labels,
66
+ link: 'link',
65
67
  roles,
66
68
  submit: () => undefined
67
69
  },
@@ -77,6 +79,22 @@ export const withEmptyState: Story = {
77
79
  },
78
80
  initialValues: emptyAccessRights,
79
81
  labels,
82
+ link: 'link',
83
+ roles,
84
+ submit: () => undefined
85
+ },
86
+ render: Template
87
+ };
88
+
89
+ export const withoutLink: Story = {
90
+ args: {
91
+ cancel: () => undefined,
92
+ endpoints: {
93
+ contact: '/contact',
94
+ contactGroup: '/contactGroup'
95
+ },
96
+ initialValues: defaultAccessRights,
97
+ labels,
80
98
  roles,
81
99
  submit: () => undefined
82
100
  },
@@ -92,6 +110,7 @@ export const loading: Story = {
92
110
  },
93
111
  initialValues: emptyAccessRights,
94
112
  labels,
113
+ link: 'link',
95
114
  loading: true,
96
115
  roles,
97
116
  submit: () => undefined
@@ -5,6 +5,6 @@ export const useAccessRightsStyles = makeStyles()((theme) => ({
5
5
  display: 'flex',
6
6
  flexDirection: 'column',
7
7
  gap: theme.spacing(3),
8
- width: '100%'
8
+ maxWidth: '520px'
9
9
  }
10
10
  }));
@@ -8,17 +8,16 @@ import Provider from './Provider';
8
8
  import ShareInput from './ShareInput/ShareInput';
9
9
  import Stats from './Stats/Stats';
10
10
  import { AccessRightInitialValues, Endpoints, Labels } from './models';
11
- import { useAccessRightsChange } from './useAccessRightsChange';
12
11
  import { useAccessRightsInitValues } from './useAccessRightsInitValues';
13
12
 
14
13
  interface Props {
15
- cancel?: ({ dirty, values }) => void;
14
+ cancel: ({ dirty, values }) => void;
16
15
  endpoints: Endpoints;
17
16
  initialValues: Array<AccessRightInitialValues>;
18
17
  isSubmitting?: boolean;
19
18
  labels: Labels;
19
+ link?: string;
20
20
  loading?: boolean;
21
- onChange?: (values: Array<AccessRightInitialValues>) => void;
22
21
  roles: Array<SelectEntry>;
23
22
  submit: (values: Array<AccessRightInitialValues>) => Promise<void>;
24
23
  }
@@ -29,14 +28,13 @@ export const AccessRights = ({
29
28
  endpoints,
30
29
  submit,
31
30
  cancel,
31
+ link,
32
32
  loading,
33
33
  labels,
34
- isSubmitting,
35
- onChange
34
+ isSubmitting
36
35
  }: Props): JSX.Element => {
37
36
  const { classes } = useAccessRightsStyles();
38
37
  const clear = useAccessRightsInitValues({ initialValues });
39
- useAccessRightsChange(onChange);
40
38
 
41
39
  return (
42
40
  <div className={classes.container}>
@@ -48,6 +46,7 @@ export const AccessRights = ({
48
46
  clear={clear}
49
47
  isSubmitting={isSubmitting}
50
48
  labels={labels.actions}
49
+ link={link}
51
50
  submit={submit}
52
51
  />
53
52
  </div>
@@ -1,10 +1,14 @@
1
1
  import { makeStyles } from 'tss-react/mui';
2
2
 
3
3
  export const useActionsStyles = makeStyles()((theme) => ({
4
+ actions: {
5
+ backgroundColor: theme.palette.background.paper,
6
+ display: 'flex',
7
+ justifyContent: 'space-between'
8
+ },
4
9
  cancelAndSave: {
5
10
  display: 'flex',
6
- flexDirection: 'row',
7
- gap: theme.spacing(2),
8
- justifyContent: 'flex-end'
11
+ flex: 'row',
12
+ gap: theme.spacing(2)
9
13
  }
10
14
  }));
@@ -1,5 +1,6 @@
1
1
  import { useTranslation } from 'react-i18next';
2
2
 
3
+ import LinkIcon from '@mui/icons-material/Link';
3
4
  import { CircularProgress } from '@mui/material';
4
5
 
5
6
  import { Button } from '../../..';
@@ -9,10 +10,11 @@ import { useActions } from './useActions';
9
10
  import { useActionsStyles } from './Actions.styles';
10
11
 
11
12
  interface Props {
12
- cancel?: ({ dirty, values }) => void;
13
+ cancel: ({ dirty, values }) => void;
13
14
  clear: () => void;
14
15
  isSubmitting?: boolean;
15
16
  labels: Labels['actions'];
17
+ link?: string;
16
18
  submit: (values: Array<AccessRightInitialValues>) => Promise<void>;
17
19
  }
18
20
 
@@ -20,15 +22,17 @@ const Actions = ({
20
22
  labels,
21
23
  cancel,
22
24
  submit,
25
+ link,
23
26
  isSubmitting,
24
27
  clear
25
28
  }: Props): JSX.Element => {
26
29
  const { t } = useTranslation();
27
30
  const { classes } = useActionsStyles();
28
31
 
29
- const { dirty, save, formattedValues } = useActions({
32
+ const { dirty, copyLink, save, formattedValues } = useActions({
30
33
  clear,
31
34
  labels,
35
+ link,
32
36
  submit
33
37
  });
34
38
 
@@ -37,8 +41,21 @@ const Actions = ({
37
41
  };
38
42
 
39
43
  return (
40
- <div className={classes.cancelAndSave}>
41
- {cancel && (
44
+ <div className={classes.actions}>
45
+ {link ? (
46
+ <Button
47
+ aria-label={t(labels.copyLink)}
48
+ icon={<LinkIcon />}
49
+ iconVariant="start"
50
+ variant="ghost"
51
+ onClick={copyLink}
52
+ >
53
+ {t(labels.copyLink)}
54
+ </Button>
55
+ ) : (
56
+ <div />
57
+ )}
58
+ <div className={classes.cancelAndSave}>
42
59
  <Button
43
60
  aria-label={t(labels.cancel)}
44
61
  variant="secondary"
@@ -46,17 +63,17 @@ const Actions = ({
46
63
  >
47
64
  {t(labels.cancel)}
48
65
  </Button>
49
- )}
50
- <Button
51
- aria-label={t(labels.save)}
52
- disabled={isSubmitting || !dirty}
53
- icon={isSubmitting ? <CircularProgress size={24} /> : null}
54
- iconVariant={isSubmitting ? 'start' : 'none'}
55
- variant="primary"
56
- onClick={save}
57
- >
58
- {t(labels.save)}
59
- </Button>
66
+ <Button
67
+ aria-label={t(labels.save)}
68
+ disabled={isSubmitting || !dirty}
69
+ icon={isSubmitting ? <CircularProgress size={24} /> : null}
70
+ iconVariant={isSubmitting ? 'start' : 'none'}
71
+ variant="primary"
72
+ onClick={save}
73
+ >
74
+ {t(labels.save)}
75
+ </Button>
76
+ </div>
60
77
  </div>
61
78
  );
62
79
  };