@centreon/ui 23.10.39 → 23.10.41

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 (68) hide show
  1. package/package.json +3 -1
  2. package/src/Listing/Cell/DataCell.tsx +16 -4
  3. package/src/Listing/Cell/index.tsx +82 -2
  4. package/src/Listing/Listing.cypress.spec.tsx +120 -0
  5. package/src/Listing/Listing.styles.ts +2 -2
  6. package/src/Listing/ListingDocs.mdx +61 -0
  7. package/src/Listing/Row/Row.tsx +5 -2
  8. package/src/Listing/index.stories.tsx +104 -31
  9. package/src/Listing/index.tsx +103 -33
  10. package/src/Listing/models.ts +1 -0
  11. package/src/Listing/tableAtoms.ts +2 -0
  12. package/src/Panel/index.tsx +1 -1
  13. package/src/ThemeProvider/index.tsx +0 -24
  14. package/src/ThemeProvider/palettes.ts +36 -6
  15. package/src/api/useMutationQuery/index.ts +16 -6
  16. package/src/components/Form/AccessRights/AccessRights.resource.ts +45 -0
  17. package/src/components/Form/AccessRights/AccessRightsForm.stories.tsx +59 -0
  18. package/src/components/Form/AccessRights/AccessRightsForm.styles.ts +21 -0
  19. package/src/components/Form/AccessRights/AccessRightsForm.tsx +67 -0
  20. package/src/components/Form/AccessRights/AccessRightsFormActions.tsx +80 -0
  21. package/src/components/Form/AccessRights/Input/AddAction.tsx +31 -0
  22. package/src/components/Form/AccessRights/Input/ContactAccessRightInput.stories.tsx +54 -0
  23. package/src/components/Form/AccessRights/Input/ContactAccessRightInput.tsx +72 -0
  24. package/src/components/Form/AccessRights/Input/ContactAccessRightsInput.styles.ts +22 -0
  25. package/src/components/Form/AccessRights/Input/ContactInputField.tsx +105 -0
  26. package/src/components/Form/AccessRights/Input/RoleInputField.tsx +29 -0
  27. package/src/components/Form/AccessRights/List/ContactAccessRightsList.stories.tsx +97 -0
  28. package/src/components/Form/AccessRights/List/ContactAccessRightsList.styles.ts +71 -0
  29. package/src/components/Form/AccessRights/List/ContactAccessRightsList.tsx +51 -0
  30. package/src/components/Form/AccessRights/List/ContactAccessRightsListItem.stories.tsx +116 -0
  31. package/src/components/Form/AccessRights/List/ContactAccessRightsListItem.tsx +118 -0
  32. package/src/components/Form/AccessRights/List/ContactAccessRightsListItemSkeleton.tsx +26 -0
  33. package/src/components/Form/AccessRights/List/ContactAccessRightsListSkeleton.tsx +28 -0
  34. package/src/components/Form/AccessRights/Stats/AccessRightsStats.styles.ts +18 -0
  35. package/src/components/Form/AccessRights/Stats/AccessRightsStats.tsx +41 -0
  36. package/src/components/Form/AccessRights/__fixtures__/contactAccessRight.mock.ts +52 -0
  37. package/src/components/Form/AccessRights/common/GroupLabel.styles.ts +18 -0
  38. package/src/components/Form/AccessRights/common/GroupLabel.tsx +15 -0
  39. package/src/components/Form/AccessRights/common/Input.styles.ts +48 -0
  40. package/src/components/Form/AccessRights/common/RoleInputSelect.styles.ts +11 -0
  41. package/src/components/Form/AccessRights/common/RoleInputSelect.tsx +57 -0
  42. package/src/components/Form/AccessRights/index.ts +3 -0
  43. package/src/components/Form/AccessRights/useAccessRightsForm.test.tsx +531 -0
  44. package/src/components/Form/AccessRights/useAccessRightsForm.tsx +282 -0
  45. package/src/components/Form/AccessRights/useAccessRightsForm.utils.ts +41 -0
  46. package/src/components/Form/Dashboard/DashboardForm.styles.ts +1 -7
  47. package/src/components/Form/Dashboard/DashboardForm.tsx +9 -12
  48. package/src/components/Form/Form.resource.ts +1 -0
  49. package/src/components/Form/Form.styles.ts +17 -0
  50. package/src/components/Form/{Dashboard/DashboardFormActions.tsx → FormActions.tsx} +17 -16
  51. package/src/components/Form/index.ts +1 -0
  52. package/src/components/Header/PageHeader/PageHeader.stories.tsx +9 -0
  53. package/src/components/List/Item/Avatar.tsx +21 -0
  54. package/src/components/List/Item/AvatarSkeleton.tsx +17 -0
  55. package/src/components/List/Item/ListItem.stories.tsx +41 -0
  56. package/src/components/List/Item/ListItem.styles.ts +57 -0
  57. package/src/components/List/Item/ListItem.tsx +32 -0
  58. package/src/components/List/Item/Text.tsx +25 -0
  59. package/src/components/List/Item/TextSkeleton.tsx +22 -0
  60. package/src/components/List/Item/index.ts +14 -0
  61. package/src/components/List/List.stories.tsx +70 -0
  62. package/src/components/List/List.styles.ts +8 -0
  63. package/src/components/List/List.tsx +19 -0
  64. package/src/components/List/index.ts +6 -0
  65. package/src/components/Modal/ModalActions.tsx +4 -1
  66. package/src/components/index.ts +1 -0
  67. package/src/utils/index.ts +1 -0
  68. package/src/utils/useInfiniteScrollListing.ts +106 -0
@@ -18,14 +18,17 @@ import {
18
18
  map,
19
19
  not,
20
20
  pick,
21
+ pluck,
21
22
  prop,
22
23
  propEq,
24
+ reduce,
23
25
  reject,
24
26
  slice,
25
27
  subtract,
26
28
  uniqBy
27
29
  } from 'ramda';
28
30
  import { useTranslation } from 'react-i18next';
31
+ import { useAtomValue } from 'jotai';
29
32
 
30
33
  import { Box, LinearProgress, Table, TableBody } from '@mui/material';
31
34
 
@@ -50,10 +53,11 @@ import ListingRow from './Row/Row';
50
53
  import { labelNoResultFound } from './translatedLabels';
51
54
  import useResizeObserver from './useResizeObserver';
52
55
  import useStyleTable from './useStyleTable';
53
- import { loadingIndicatorHeight, useStyles } from './Listing.styles';
56
+ import { loadingIndicatorHeight, useListingStyles } from './Listing.styles';
54
57
  import { EmptyResult } from './EmptyResult/EmptyResult';
55
58
  import { SkeletonLoader } from './Row/SkeletonLoaderRows';
56
59
  import { ListingHeader } from './Header';
60
+ import { subItemsPivotsAtom } from './tableAtoms';
57
61
 
58
62
  const getVisibleColumns = ({
59
63
  columnConfiguration,
@@ -114,6 +118,13 @@ export interface Props<TRow> {
114
118
  selectedRows?: Array<TRow>;
115
119
  sortField?: string;
116
120
  sortOrder?: SortOrder;
121
+ subItems?: {
122
+ canCheckSubItems: boolean;
123
+ enable: boolean;
124
+ labelCollapse: string;
125
+ labelExpand: string;
126
+ rowProperty: string;
127
+ };
117
128
  totalRows?: number;
118
129
  viewMode?: ListingVariant;
119
130
  viewerModeConfiguration?: ViewerModeConfiguration;
@@ -160,7 +171,14 @@ const Listing = <TRow extends { id: RowId }>({
160
171
  viewMode = ListingVariant.compact,
161
172
  widthToMoveTablePagination,
162
173
  getHighlightRowCondition,
163
- viewerModeConfiguration
174
+ viewerModeConfiguration,
175
+ subItems = {
176
+ canCheckSubItems: false,
177
+ enable: false,
178
+ labelCollapse: 'Collapse',
179
+ labelExpand: 'Expand',
180
+ rowProperty: ''
181
+ }
164
182
  }: Props<TRow>): JSX.Element => {
165
183
  const currentVisibleColumns = getVisibleColumns({
166
184
  columnConfiguration,
@@ -172,13 +190,6 @@ const Listing = <TRow extends { id: RowId }>({
172
190
  viewMode
173
191
  });
174
192
 
175
- const { classes, theme } = useStyles({
176
- dataStyle,
177
- getGridTemplateColumn,
178
- limit,
179
- rows,
180
- viewMode
181
- });
182
193
  const { t } = useTranslation();
183
194
 
184
195
  const [tableTopOffset, setTableTopOffset] = React.useState(0);
@@ -192,6 +203,37 @@ const Listing = <TRow extends { id: RowId }>({
192
203
  const containerRef = React.useRef<HTMLDivElement>();
193
204
  const actionBarRef = React.useRef<HTMLDivElement>();
194
205
 
206
+ const subItemsPivots = useAtomValue(subItemsPivotsAtom);
207
+
208
+ const rowsToDisplay = React.useMemo(
209
+ () =>
210
+ subItems?.enable
211
+ ? reduce<TRow, Array<TRow>>(
212
+ (acc, row): Array<TRow> => {
213
+ if (
214
+ row[subItems.rowProperty] &&
215
+ subItemsPivots.includes(row.id)
216
+ ) {
217
+ return [...acc, row, ...row[subItems.rowProperty]];
218
+ }
219
+
220
+ return [...acc, row];
221
+ },
222
+ [],
223
+ rows
224
+ )
225
+ : rows,
226
+ [rows, subItemsPivots, subItems]
227
+ );
228
+
229
+ const { classes, theme } = useListingStyles({
230
+ dataStyle,
231
+ getGridTemplateColumn,
232
+ limit,
233
+ rows: rowsToDisplay,
234
+ viewMode
235
+ });
236
+
195
237
  useResizeObserver({
196
238
  onResize: () => {
197
239
  setTableTopOffset(getCumulativeOffset(containerRef.current));
@@ -422,6 +464,19 @@ const Listing = <TRow extends { id: RowId }>({
422
464
 
423
465
  const areColumnsEditable = not(isNil(onSelectColumns));
424
466
 
467
+ const allSubItemIds = React.useMemo(
468
+ () =>
469
+ reduce<TRow | number, Array<string | number>>(
470
+ (acc, row) => [
471
+ ...acc,
472
+ ...pluck('id', row[subItems?.rowProperty || ''] || [])
473
+ ],
474
+ [],
475
+ rows
476
+ ),
477
+ [rows, subItems]
478
+ );
479
+
425
480
  return (
426
481
  <>
427
482
  {loading && rows.length > 0 && (
@@ -495,14 +550,16 @@ const Listing = <TRow extends { id: RowId }>({
495
550
  component="div"
496
551
  onMouseLeave={clearHoveredRow}
497
552
  >
498
- {rows.map((row, index) => {
553
+ {rowsToDisplay.map((row, index) => {
499
554
  const isRowSelected = isSelected(row);
500
-
501
555
  const isRowHovered = equals(hoveredRowId, getId(row));
556
+ const isSubItem = allSubItemIds.includes(row.id);
502
557
 
503
558
  return (
504
559
  <ListingRow
505
- checkable={checkable}
560
+ checkable={
561
+ checkable && (!isSubItem || subItems.canCheckSubItems)
562
+ }
506
563
  columnConfiguration={columnConfiguration}
507
564
  columnIds={columns.map(prop('id'))}
508
565
  disableRowCondition={disableRowCondition}
@@ -519,6 +576,7 @@ const Listing = <TRow extends { id: RowId }>({
519
576
  row={row}
520
577
  rowColorConditions={rowColorConditions}
521
578
  shiftKeyDownRowPivot={shiftKeyDownRowPivot}
579
+ subItemsPivots={subItemsPivots}
522
580
  tabIndex={-1}
523
581
  viewMode={viewMode}
524
582
  visibleColumns={visibleColumns}
@@ -528,28 +586,37 @@ const Listing = <TRow extends { id: RowId }>({
528
586
  onFocus={(): void => hoverRow(row)}
529
587
  onMouseOver={(): void => hoverRow(row)}
530
588
  >
531
- {checkable && (
532
- <Cell
533
- align="left"
534
- className={classes.checkbox}
535
- disableRowCondition={disableRowCondition}
536
- isRowHovered={isRowHovered}
537
- row={row}
538
- rowColorConditions={rowColorConditions}
539
- onClick={(event): void => selectRow(event, row)}
540
- >
541
- <Checkbox
542
- checked={isRowSelected}
543
- disabled={
544
- disableRowCheckCondition(row) ||
545
- disableRowCondition(row)
546
- }
547
- inputProps={{
548
- 'aria-label': `Select row ${getId(row)}`
549
- }}
589
+ {checkable &&
590
+ (!isSubItem || subItems.canCheckSubItems ? (
591
+ <Cell
592
+ align="left"
593
+ className={classes.checkbox}
594
+ disableRowCondition={disableRowCondition}
595
+ isRowHovered={isRowHovered}
596
+ row={row}
597
+ rowColorConditions={rowColorConditions}
598
+ onClick={(event): void => selectRow(event, row)}
599
+ >
600
+ <Checkbox
601
+ checked={isRowSelected}
602
+ disabled={
603
+ disableRowCheckCondition(row) ||
604
+ disableRowCondition(row)
605
+ }
606
+ inputProps={{
607
+ 'aria-label': `Select row ${getId(row)}`
608
+ }}
609
+ />
610
+ </Cell>
611
+ ) : (
612
+ <Cell
613
+ align="left"
614
+ disableRowCondition={disableRowCondition}
615
+ isRowHovered={isRowHovered}
616
+ row={row}
617
+ rowColorConditions={rowColorConditions}
550
618
  />
551
- </Cell>
552
- )}
619
+ ))}
553
620
 
554
621
  {visibleColumns.map((column) => (
555
622
  <DataCell
@@ -559,8 +626,11 @@ const Listing = <TRow extends { id: RowId }>({
559
626
  isRowHovered={isRowHovered}
560
627
  isRowSelected={isRowSelected}
561
628
  key={`${getId(row)}-${column.id}`}
629
+ labelCollapse={subItems.labelCollapse}
630
+ labelExpand={subItems.labelExpand}
562
631
  row={row}
563
632
  rowColorConditions={rowColorConditions}
633
+ subItemsRowProperty={subItems?.rowProperty}
564
634
  viewMode={viewMode}
565
635
  />
566
636
  ))}
@@ -19,6 +19,7 @@ export interface Column {
19
19
  compact?: boolean;
20
20
  disablePadding?: boolean;
21
21
  disabled?: boolean;
22
+ displaySubItemsCaret?: boolean;
22
23
  getColSpan?: (isSelected) => number | undefined;
23
24
  getFormattedString?: (row) => string | null;
24
25
  getHiddenCondition?: (isSelected) => boolean;
@@ -43,3 +43,5 @@ export const tableStyleDerivedAtom = atom(null, (get, set, { viewMode }) => {
43
43
  header: isExtendedMode ? extendedTableHeader : compactTableHeader
44
44
  });
45
45
  });
46
+
47
+ export const subItemsPivotsAtom = atom<Array<number | string>>([]);
@@ -86,7 +86,7 @@ export interface Tab {
86
86
 
87
87
  export interface Props {
88
88
  className?: string;
89
- header: React.ReactElement;
89
+ header?: React.ReactElement;
90
90
  headerBackgroundColor?: string;
91
91
  labelClose?: string;
92
92
  minWidth?: number;
@@ -148,30 +148,6 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
148
148
  },
149
149
  MuiCssBaseline: {
150
150
  styleOverrides: (theme) => `
151
- ::-webkit-scrollbar {
152
- height: ${theme.spacing(1)};
153
- width: ${theme.spacing(1)};
154
- background-color: ${theme.palette.background.default};
155
- }
156
- ::-webkit-scrollbar-thumb {
157
- background-color: ${
158
- equals(mode, 'dark')
159
- ? theme.palette.divider
160
- : theme.palette.text.disabled
161
- };
162
- border-radius: ${theme.spacing(0.5)};
163
- }
164
- ::-webkit-scrollbar-thumb:hover {
165
- background-color: ${theme.palette.primary.main};
166
- }
167
- * {
168
- scrollbar-color: ${
169
- equals(mode, 'dark')
170
- ? theme.palette.divider
171
- : theme.palette.text.disabled
172
- } ${theme.palette.background.default};
173
- scrollbar-width: thin;
174
- }
175
151
  html {
176
152
  margin: 0;
177
153
  padding: 0;
@@ -18,6 +18,7 @@ declare module '@mui/material/styles/createPalette' {
18
18
  declare module '@mui/material/styles' {
19
19
  interface Palette {
20
20
  background: TypeBackground;
21
+ chip: TypeChip;
21
22
  header: TypeHeader;
22
23
  layout: TypeLayout;
23
24
  menu: TypeMenu;
@@ -36,6 +37,7 @@ declare module '@mui/material/styles' {
36
37
  warning: string;
37
38
  }
38
39
  interface PaletteOptions {
40
+ chip: TypeChip;
39
41
  header: TypeHeader;
40
42
  layout: TypeLayout;
41
43
  menu: TypeMenu;
@@ -113,6 +115,16 @@ declare module '@mui/material/styles' {
113
115
  };
114
116
  };
115
117
  }
118
+
119
+ interface TypeChip {
120
+ color: {
121
+ error: string;
122
+ info: string;
123
+ neutral: string;
124
+ success: string;
125
+ warning: string;
126
+ };
127
+ }
116
128
  }
117
129
 
118
130
  declare module '@mui/material/Button' {
@@ -152,6 +164,15 @@ export const lightPalette: PaletteOptions = {
152
164
  paper: '#FFFFFF',
153
165
  tooltip: '#434E5B'
154
166
  },
167
+ chip: {
168
+ color: {
169
+ error: '#FF6666',
170
+ info: '#1588D1',
171
+ neutral: BaseTokens.colorGrey300,
172
+ success: '#88B922',
173
+ warning: '#FD9B27'
174
+ }
175
+ },
155
176
  divider: '#E3E3E3',
156
177
  error: {
157
178
  contrastText: '#000',
@@ -193,7 +214,7 @@ export const lightPalette: PaletteOptions = {
193
214
  background: {
194
215
  active: BaseTokens.colorBlue50,
195
216
  default: 'transparent',
196
- hover: BaseTokens.colorGrey50
217
+ hover: BaseTokens.colorGrey100
197
218
  },
198
219
  color: {
199
220
  active: BaseTokens.colorBlue400,
@@ -208,7 +229,7 @@ export const lightPalette: PaletteOptions = {
208
229
  background: {
209
230
  active: BaseTokens.colorBlue50,
210
231
  default: 'transparent',
211
- hover: BaseTokens.colorGrey50
232
+ hover: BaseTokens.colorGrey100
212
233
  },
213
234
  color: {
214
235
  active: BaseTokens.colorBlue400,
@@ -282,6 +303,15 @@ export const darkPalette: PaletteOptions = {
282
303
  paper: '#212121',
283
304
  tooltip: '#AAB4C0'
284
305
  },
306
+ chip: {
307
+ color: {
308
+ error: '#D60101',
309
+ info: '#1CA9F4',
310
+ neutral: BaseTokens.colorGrey700,
311
+ success: '#5F8118',
312
+ warning: '#C55400'
313
+ }
314
+ },
285
315
  divider: '#666666',
286
316
  error: {
287
317
  contrastText: '#fff',
@@ -321,9 +351,9 @@ export const darkPalette: PaletteOptions = {
321
351
  background: BaseTokens.colorGrey950,
322
352
  button: {
323
353
  background: {
324
- active: BaseTokens.colorBlue950,
354
+ active: BaseTokens.colorBlue900,
325
355
  default: 'transparent',
326
- hover: BaseTokens.colorGrey950
356
+ hover: BaseTokens.colorGrey900
327
357
  },
328
358
  color: {
329
359
  active: BaseTokens.colorBlue600,
@@ -336,9 +366,9 @@ export const darkPalette: PaletteOptions = {
336
366
  },
337
367
  item: {
338
368
  background: {
339
- active: BaseTokens.colorBlue950,
369
+ active: BaseTokens.colorBlue900,
340
370
  default: 'transparent',
341
- hover: BaseTokens.colorGrey950
371
+ hover: BaseTokens.colorGrey900
342
372
  },
343
373
  color: {
344
374
  active: BaseTokens.colorBlue400,
@@ -26,7 +26,7 @@ export type UseMutationQueryProps<T> = {
26
26
  decoder?: JsonDecoder.Decoder<T>;
27
27
  defaultFailureMessage?: string;
28
28
  fetchHeaders?: HeadersInit;
29
- getEndpoint: (payload) => string;
29
+ getEndpoint: (_meta?: Record<string, unknown> | unknown) => string;
30
30
  httpCodesBypassErrorSnackbar?: Array<number>;
31
31
  method: Method;
32
32
  } & Omit<UseMutationOptions<T>, 'mutationFn'>;
@@ -45,17 +45,22 @@ const useMutationQuery = <T extends object>({
45
45
  defaultFailureMessage,
46
46
  fetchHeaders,
47
47
  httpCodesBypassErrorSnackbar = [],
48
- method
48
+ method,
49
+ onMutate,
50
+ onError
49
51
  }: UseMutationQueryProps<T>): UseMutationQueryState<T> => {
50
52
  const { showErrorMessage } = useSnackbar();
51
53
 
52
54
  const queryData = useMutation<T | ResponseError>(
53
- (payload): Promise<T | ResponseError> =>
54
- customFetch<T>({
55
+ // @ts-expect-error useMutation / useMutationQuery is not typed correctly
56
+ (_payload: Record<string, unknown> | null): Promise<T | ResponseError> => {
57
+ const { _meta, ...payload } = _payload || {};
58
+
59
+ return customFetch<T>({
55
60
  catchError,
56
61
  decoder,
57
62
  defaultFailureMessage,
58
- endpoint: getEndpoint(payload),
63
+ endpoint: getEndpoint(_meta),
59
64
  headers: new Headers({
60
65
  'Content-Type': 'application/x-www-form-urlencoded',
61
66
  ...fetchHeaders
@@ -63,7 +68,12 @@ const useMutationQuery = <T extends object>({
63
68
  isMutation: true,
64
69
  method,
65
70
  payload
66
- })
71
+ });
72
+ },
73
+ {
74
+ onError,
75
+ onMutate
76
+ }
67
77
  );
68
78
 
69
79
  const manageError = (): void => {
@@ -0,0 +1,45 @@
1
+ export type ContactAccessRightState =
2
+ | 'added'
3
+ | 'updated'
4
+ | 'removed'
5
+ | 'unchanged';
6
+
7
+ export type ContactAccessRightStateResource = {
8
+ contactAccessRight: ContactAccessRightResource;
9
+ state: ContactAccessRightState;
10
+ stateHistory: Array<ContactAccessRightState>;
11
+ };
12
+
13
+ export type ContactAccessRightResource = {
14
+ contact: ContactResource | ContactGroupResource | null;
15
+ role: RoleResource['role'];
16
+ };
17
+
18
+ export type ContactResource = {
19
+ email?: string;
20
+ id?: number | string;
21
+ name: string;
22
+ type: 'contact';
23
+ };
24
+
25
+ export const isContactResource = (
26
+ resource: ContactResource | ContactGroupResource | null
27
+ ): resource is ContactResource => {
28
+ return resource?.type === 'contact';
29
+ };
30
+
31
+ export type ContactGroupResource = {
32
+ id?: number | string;
33
+ name: string;
34
+ type: 'contact_group';
35
+ };
36
+
37
+ export const isContactGroupResource = (
38
+ resource: ContactResource | ContactGroupResource | null
39
+ ): resource is ContactResource => {
40
+ return resource?.type === 'contact_group';
41
+ };
42
+
43
+ export type RoleResource = {
44
+ role: 'viewer' | 'editor' | string;
45
+ };
@@ -0,0 +1,59 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { AccessRightsForm } from './AccessRightsForm';
4
+ import { Default as DefaultContactAccessRightsListStory } from './List/ContactAccessRightsList.stories';
5
+ import { Default as DefaultContactAccessRightInputStory } from './Input/ContactAccessRightInput.stories';
6
+ import { ContactAccessRightsListProps } from './List/ContactAccessRightsList';
7
+ import { ContactAccessRightInputProps } from './Input/ContactAccessRightInput';
8
+ import {
9
+ contactAccessRightsMock,
10
+ contactsAndGroupsMock
11
+ } from './__fixtures__/contactAccessRight.mock';
12
+
13
+ const meta: Meta<typeof AccessRightsForm> = {
14
+ component: AccessRightsForm
15
+ };
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof AccessRightsForm>;
19
+
20
+ export const Default: Story = {
21
+ args: {
22
+ initialValues: contactAccessRightsMock(12),
23
+ labels: {
24
+ actions: {
25
+ cancel: 'Cancel',
26
+ copyLink: 'Copy link',
27
+ copyLinkMessages: {
28
+ error: 'Unable to copy link',
29
+ success: 'Link copied'
30
+ },
31
+ submit: 'Update'
32
+ },
33
+ input: {
34
+ ...DefaultContactAccessRightInputStory.args?.labels
35
+ } as ContactAccessRightInputProps['labels'],
36
+ list: {
37
+ ...DefaultContactAccessRightsListStory.args?.labels
38
+ } as ContactAccessRightsListProps['labels'],
39
+ stats: {
40
+ added: 'added',
41
+ removed: 'removed',
42
+ updated: 'updated'
43
+ }
44
+ },
45
+ options: {
46
+ contacts: contactsAndGroupsMock(25),
47
+ roles: [{ role: 'viewer' }, { role: 'editor' }]
48
+ },
49
+ resourceLink: 'https://app.centreon.com/resource/1'
50
+ }
51
+ };
52
+
53
+ export const AsEmptyState: Story = {
54
+ args: {
55
+ labels: Default.args?.labels,
56
+ options: Default.args?.options,
57
+ resourceLink: Default.args?.resourceLink
58
+ }
59
+ };
@@ -0,0 +1,21 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ const useStyles = makeStyles()((theme) => ({
4
+ accessRightsForm: {
5
+ display: 'flex',
6
+ flexDirection: 'column',
7
+ gap: theme.spacing(6),
8
+
9
+ maxWidth: '520px',
10
+ paddingTop: theme.spacing(3),
11
+
12
+ width: '100%'
13
+ },
14
+ accessRightsFormList: {
15
+ display: 'flex',
16
+ flexDirection: 'column',
17
+ gap: theme.spacing(1)
18
+ }
19
+ }));
20
+
21
+ export { useStyles };
@@ -0,0 +1,67 @@
1
+ import { ReactElement } from 'react';
2
+
3
+ import { useStyles } from './AccessRightsForm.styles';
4
+ import {
5
+ AccessRightsFormProvider,
6
+ AccessRightsFormProviderProps
7
+ } from './useAccessRightsForm';
8
+ import {
9
+ ContactAccessRightInput,
10
+ ContactAccessRightInputProps
11
+ } from './Input/ContactAccessRightInput';
12
+ import {
13
+ ContactAccessRightsList,
14
+ ContactAccessRightsListProps
15
+ } from './List/ContactAccessRightsList';
16
+ import {
17
+ AccessRightsFormActions,
18
+ AccessRightsFormActionsProps
19
+ } from './AccessRightsFormActions';
20
+ import {
21
+ AccessRightsStats,
22
+ AccessRightsStatsProps
23
+ } from './Stats/AccessRightsStats';
24
+
25
+ export type AccessRightsFormProps = {
26
+ labels: AccessRightsFormLabels;
27
+ onCancel?: AccessRightsFormActionsProps['onCancel'];
28
+ resourceLink: string;
29
+ } & Pick<
30
+ AccessRightsFormProviderProps,
31
+ 'initialValues' | 'onSubmit' | 'options' | 'loadingStatus'
32
+ >;
33
+
34
+ export type AccessRightsFormLabels = {
35
+ actions: AccessRightsFormActionsProps['labels'];
36
+ input: ContactAccessRightInputProps['labels'];
37
+ list: ContactAccessRightsListProps['labels'];
38
+ stats: AccessRightsStatsProps['labels'];
39
+ };
40
+
41
+ const AccessRightsForm = ({
42
+ labels,
43
+ onCancel,
44
+ resourceLink,
45
+ ...providerProps
46
+ }: AccessRightsFormProps): ReactElement => {
47
+ const { classes } = useStyles();
48
+
49
+ return (
50
+ <AccessRightsFormProvider {...providerProps}>
51
+ <div className={classes.accessRightsForm}>
52
+ <ContactAccessRightInput labels={labels.input} />
53
+ <span className={classes.accessRightsFormList}>
54
+ <ContactAccessRightsList labels={labels.list} />
55
+ <AccessRightsStats labels={labels.stats} />
56
+ </span>
57
+ <AccessRightsFormActions
58
+ labels={labels.actions}
59
+ resourceLink={resourceLink}
60
+ onCancel={onCancel}
61
+ />
62
+ </div>
63
+ </AccessRightsFormProvider>
64
+ );
65
+ };
66
+
67
+ export { AccessRightsForm };