@centreon/ui 24.4.1-MON-34407-implement-the-status-chart-widget.29 → 24.4.1-MON-36983-dashboard-duplicate.2
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 +1 -1
- package/src/Form/Inputs/Text.tsx +3 -1
- package/src/Form/Inputs/models.ts +1 -0
- package/src/Graph/BarStack/BarStack.stories.tsx +5 -5
- package/src/Graph/BarStack/BarStack.styles.ts +2 -3
- package/src/Graph/BarStack/ResponsiveBarStack.tsx +27 -41
- package/src/Graph/BarStack/models.ts +0 -1
- package/src/Graph/BarStack/useResponsiveBarStack.ts +5 -3
- package/src/Graph/PieChart/ResponsivePie.tsx +4 -7
- package/src/Graph/PieChart/models.ts +0 -1
- package/src/Graph/PieChart/useResponsivePie.ts +2 -1
- package/src/components/DataTable/DataTable.cypress.spec.tsx +21 -23
- package/src/components/DataTable/Item/DataTableItem.tsx +65 -30
- package/src/components/Form/Dashboard/DashboardDuplicationForm.tsx +85 -0
- package/src/components/Form/Dashboard/index.ts +1 -0
- package/src/components/Form/FormActions.tsx +7 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centreon/ui",
|
|
3
|
-
"version": "24.4.1-MON-
|
|
3
|
+
"version": "24.4.1-MON-36983-dashboard-duplicate.2",
|
|
4
4
|
"description": "Centreon UI Components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
|
package/src/Form/Inputs/Text.tsx
CHANGED
|
@@ -29,7 +29,8 @@ const Text = ({
|
|
|
29
29
|
getRequired,
|
|
30
30
|
change,
|
|
31
31
|
additionalMemoProps,
|
|
32
|
-
text
|
|
32
|
+
text,
|
|
33
|
+
autoFocus
|
|
33
34
|
}: InputPropsWithoutGroup): JSX.Element => {
|
|
34
35
|
const { t } = useTranslation();
|
|
35
36
|
|
|
@@ -109,6 +110,7 @@ const Text = ({
|
|
|
109
110
|
fullWidth
|
|
110
111
|
EndAdornment={EndAdornment}
|
|
111
112
|
ariaLabel={t(label) || ''}
|
|
113
|
+
autoFocus={autoFocus}
|
|
112
114
|
dataTestId={dataTestId || ''}
|
|
113
115
|
disabled={disabled}
|
|
114
116
|
error={error as string | undefined}
|
|
@@ -58,6 +58,11 @@ export const WithoutTitle: Story = {
|
|
|
58
58
|
render: Template
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
export const WithPencentage: Story = {
|
|
62
|
+
args: { data, title: 'hosts', unit: 'percentage' },
|
|
63
|
+
render: Template
|
|
64
|
+
};
|
|
65
|
+
|
|
61
66
|
export const WithoutLegend: Story = {
|
|
62
67
|
args: { data, displayLegend: false, title: 'hosts' },
|
|
63
68
|
|
|
@@ -69,11 +74,6 @@ export const withDisplayedValues: Story = {
|
|
|
69
74
|
render: Template
|
|
70
75
|
};
|
|
71
76
|
|
|
72
|
-
export const WithPencentage: Story = {
|
|
73
|
-
args: { data, displayValues: true, title: 'hosts', unit: 'percentage' },
|
|
74
|
-
render: Template
|
|
75
|
-
};
|
|
76
|
-
|
|
77
77
|
export const WithTooltip: Story = {
|
|
78
78
|
args: { TooltipContent, data, title: 'hosts' },
|
|
79
79
|
render: Template
|
|
@@ -16,7 +16,7 @@ export const useBarStackStyles = makeStyles()((theme) => ({
|
|
|
16
16
|
svgContainer: {
|
|
17
17
|
alignItems: 'center',
|
|
18
18
|
backgroundColor: theme.palette.background.panelGroups,
|
|
19
|
-
borderRadius: theme.
|
|
19
|
+
borderRadius: theme.shape.borderRadius,
|
|
20
20
|
display: 'flex',
|
|
21
21
|
justifyContent: 'center'
|
|
22
22
|
},
|
|
@@ -31,7 +31,6 @@ export const useBarStackStyles = makeStyles()((theme) => ({
|
|
|
31
31
|
fontSize: theme.typography.h6.fontSize,
|
|
32
32
|
fontWeight: theme.typography.fontWeightMedium,
|
|
33
33
|
margin: 0,
|
|
34
|
-
padding: 0
|
|
35
|
-
textAlign: 'center'
|
|
34
|
+
padding: 0
|
|
36
35
|
}
|
|
37
36
|
}));
|
|
@@ -5,7 +5,6 @@ import { Group } from '@visx/group';
|
|
|
5
5
|
import numeral from 'numeral';
|
|
6
6
|
import { Text } from '@visx/text';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
|
-
import { equals } from 'ramda';
|
|
9
8
|
|
|
10
9
|
import { Typography } from '@mui/material';
|
|
11
10
|
|
|
@@ -13,7 +12,7 @@ import { Tooltip } from '../../components';
|
|
|
13
12
|
import { LegendProps } from '../Legend/models';
|
|
14
13
|
import { Legend as LegendComponent } from '../Legend';
|
|
15
14
|
import { getValueByUnit } from '../common/utils';
|
|
16
|
-
import { labelNoDataFound
|
|
15
|
+
import { labelNoDataFound } from '../translatedLabels';
|
|
17
16
|
|
|
18
17
|
import { BarStackProps } from './models';
|
|
19
18
|
import { useBarStackStyles } from './BarStack.styles';
|
|
@@ -36,8 +35,7 @@ const BarStack = ({
|
|
|
36
35
|
unit = 'number',
|
|
37
36
|
displayValues,
|
|
38
37
|
variant = 'vertical',
|
|
39
|
-
legendDirection = 'column'
|
|
40
|
-
labelNoDataFound = defaultlabelNoDataFound
|
|
38
|
+
legendDirection = 'column'
|
|
41
39
|
}: BarStackProps & { height: number; width: number }): JSX.Element => {
|
|
42
40
|
const { t } = useTranslation();
|
|
43
41
|
const { classes } = useBarStackStyles();
|
|
@@ -92,7 +90,7 @@ const BarStack = ({
|
|
|
92
90
|
>
|
|
93
91
|
{title && (
|
|
94
92
|
<div className={classes.title} data-testid="Title" ref={titleRef}>
|
|
95
|
-
{`${numeral(total).format('0a').toUpperCase()} `} {
|
|
93
|
+
{`${numeral(total).format('0a').toUpperCase()} `} {title}
|
|
96
94
|
</div>
|
|
97
95
|
)}
|
|
98
96
|
<div
|
|
@@ -119,20 +117,6 @@ const BarStack = ({
|
|
|
119
117
|
{(barStacks) =>
|
|
120
118
|
barStacks.map((barStack) =>
|
|
121
119
|
barStack.bars.map((bar) => {
|
|
122
|
-
const shouldDisplayValues = (): boolean => {
|
|
123
|
-
if (bar.height < 10) {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
(equals(unit, 'number') && bar.width > 10) ||
|
|
129
|
-
(equals(unit, 'percentage') && bar.width > 25)
|
|
130
|
-
);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const TextX = bar.x + bar.width / 2;
|
|
134
|
-
const TextY = bar.y + bar.height / 2;
|
|
135
|
-
|
|
136
120
|
const onClick = (): void => {
|
|
137
121
|
onSingleBarClick?.(bar);
|
|
138
122
|
};
|
|
@@ -160,38 +144,40 @@ const BarStack = ({
|
|
|
160
144
|
isVerticalBar ? 'right-start' : 'bottom-start'
|
|
161
145
|
}
|
|
162
146
|
>
|
|
163
|
-
<g data-testid={bar.key}
|
|
147
|
+
<g data-testid={bar.key}>
|
|
164
148
|
<rect
|
|
165
|
-
cursor="pointer"
|
|
166
149
|
fill={bar.color}
|
|
167
150
|
height={
|
|
168
151
|
isVerticalBar ? bar.height - 1 : bar.height
|
|
169
152
|
}
|
|
170
153
|
key={`bar-stack-${barStack.index}-${bar.index}`}
|
|
171
|
-
ry={
|
|
154
|
+
ry={5}
|
|
172
155
|
width={isVerticalBar ? bar.width : bar.width - 1}
|
|
173
156
|
x={bar.x}
|
|
174
157
|
y={bar.y}
|
|
158
|
+
onClick={onClick}
|
|
175
159
|
/>
|
|
176
|
-
{displayValues &&
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
160
|
+
{displayValues &&
|
|
161
|
+
bar.height > 10 &&
|
|
162
|
+
bar.width > 10 && (
|
|
163
|
+
<Text
|
|
164
|
+
cursor="pointer"
|
|
165
|
+
data-testid="value"
|
|
166
|
+
fill="#000"
|
|
167
|
+
fontSize={12}
|
|
168
|
+
textAnchor="middle"
|
|
169
|
+
verticalAnchor="middle"
|
|
170
|
+
x={bar.x + bar.width / 2}
|
|
171
|
+
y={bar.y + bar.height / 2}
|
|
172
|
+
>
|
|
173
|
+
{getValueByUnit({
|
|
174
|
+
total,
|
|
175
|
+
unit,
|
|
176
|
+
value:
|
|
177
|
+
barStack.bars[0].bar.data[barStack.key]
|
|
178
|
+
})}
|
|
179
|
+
</Text>
|
|
180
|
+
)}
|
|
195
181
|
</g>
|
|
196
182
|
</Tooltip>
|
|
197
183
|
);
|
|
@@ -55,7 +55,7 @@ const useResponsiveBarStack = ({
|
|
|
55
55
|
const verticalGap = heightOfTitle > 0 ? 8 : 0;
|
|
56
56
|
|
|
57
57
|
const svgWrapperWidth = isVerticalBar
|
|
58
|
-
? size +
|
|
58
|
+
? size + 24
|
|
59
59
|
: width - widthOfLegend - horizontalGap;
|
|
60
60
|
|
|
61
61
|
const svgContainerSize = {
|
|
@@ -72,7 +72,8 @@ const useResponsiveBarStack = ({
|
|
|
72
72
|
|
|
73
73
|
const yScale = isVerticalBar
|
|
74
74
|
? scaleLinear({
|
|
75
|
-
domain: [0, total]
|
|
75
|
+
domain: [0, total],
|
|
76
|
+
nice: true
|
|
76
77
|
})
|
|
77
78
|
: scaleBand({
|
|
78
79
|
domain: [0, 0],
|
|
@@ -85,7 +86,8 @@ const useResponsiveBarStack = ({
|
|
|
85
86
|
padding: 0
|
|
86
87
|
})
|
|
87
88
|
: scaleLinear({
|
|
88
|
-
domain: [0, total]
|
|
89
|
+
domain: [0, total],
|
|
90
|
+
nice: true
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
const keys = pluck('label', data);
|
|
@@ -13,7 +13,7 @@ import { Tooltip } from '../../components';
|
|
|
13
13
|
import { Legend as LegendComponent } from '../Legend';
|
|
14
14
|
import { LegendProps } from '../Legend/models';
|
|
15
15
|
import { getValueByUnit } from '../common/utils';
|
|
16
|
-
import { labelNoDataFound
|
|
16
|
+
import { labelNoDataFound } from '../translatedLabels';
|
|
17
17
|
|
|
18
18
|
import { PieProps } from './models';
|
|
19
19
|
import { usePieStyles } from './PieChart.styles';
|
|
@@ -54,8 +54,7 @@ const ResponsivePie = ({
|
|
|
54
54
|
onArcClick,
|
|
55
55
|
displayValues,
|
|
56
56
|
TooltipContent,
|
|
57
|
-
legendDirection = 'column'
|
|
58
|
-
labelNoDataFound = defaultlabelNoDataFound
|
|
57
|
+
legendDirection = 'column'
|
|
59
58
|
}: PieProps & { height: number; width: number }): JSX.Element => {
|
|
60
59
|
const { t } = useTranslation();
|
|
61
60
|
const theme = useTheme();
|
|
@@ -110,7 +109,7 @@ const ResponsivePie = ({
|
|
|
110
109
|
>
|
|
111
110
|
{equals(variant, 'pie') && title && (
|
|
112
111
|
<div className={classes.title} data-testid="Title" ref={titleRef}>
|
|
113
|
-
{`${numeral(total).format('0a').toUpperCase()} `} {
|
|
112
|
+
{`${numeral(total).format('0a').toUpperCase()} `} {title}
|
|
114
113
|
</div>
|
|
115
114
|
)}
|
|
116
115
|
<div
|
|
@@ -179,7 +178,6 @@ const ResponsivePie = ({
|
|
|
179
178
|
>
|
|
180
179
|
<g data-testid={arc.data.label} onClick={onClick}>
|
|
181
180
|
<path
|
|
182
|
-
cursor="pointer"
|
|
183
181
|
d={pie.path(arc) as string}
|
|
184
182
|
fill={arc.data.color}
|
|
185
183
|
/>
|
|
@@ -191,7 +189,6 @@ const ResponsivePie = ({
|
|
|
191
189
|
dy=".33em"
|
|
192
190
|
fill="#000"
|
|
193
191
|
fontSize={12}
|
|
194
|
-
fontWeight={600}
|
|
195
192
|
pointerEvents="none"
|
|
196
193
|
textAnchor="middle"
|
|
197
194
|
x={x}
|
|
@@ -227,7 +224,7 @@ const ResponsivePie = ({
|
|
|
227
224
|
fill={theme.palette.text.primary}
|
|
228
225
|
textAnchor="middle"
|
|
229
226
|
>
|
|
230
|
-
{
|
|
227
|
+
{title}
|
|
231
228
|
</Text>
|
|
232
229
|
</>
|
|
233
230
|
)}
|
|
@@ -41,11 +41,12 @@ export const useResponsivePie = ({
|
|
|
41
41
|
const horizontalGap = widthOfLegend > 0 ? 16 : 0;
|
|
42
42
|
const verticalGap = heightOfTitle > 0 ? 8 : 0;
|
|
43
43
|
|
|
44
|
+
const svgWrapperWidth = width - widthOfLegend - horizontalGap;
|
|
45
|
+
|
|
44
46
|
const svgContainerSize = Math.min(
|
|
45
47
|
height - heightOfTitle - verticalGap,
|
|
46
48
|
width - widthOfLegend - horizontalGap
|
|
47
49
|
);
|
|
48
|
-
const svgWrapperWidth = svgContainerSize;
|
|
49
50
|
|
|
50
51
|
const outerRadius = Math.min(32, svgContainerSize / 6);
|
|
51
52
|
|
|
@@ -12,11 +12,7 @@ const data = Array(5)
|
|
|
12
12
|
title: `Entity ${idx}`
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
|
-
const initializeDataTableGrid = ({
|
|
16
|
-
hasActions,
|
|
17
|
-
hasCardAction,
|
|
18
|
-
canDelete
|
|
19
|
-
}): void => {
|
|
15
|
+
const initializeDataTableGrid = ({ hasActions, hasCardAction }): void => {
|
|
20
16
|
cy.viewport(1200, 590);
|
|
21
17
|
cy.mount({
|
|
22
18
|
Component: (
|
|
@@ -27,14 +23,18 @@ const initializeDataTableGrid = ({
|
|
|
27
23
|
hasActions={hasActions}
|
|
28
24
|
hasCardAction={hasCardAction}
|
|
29
25
|
key={title}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
labels={{
|
|
27
|
+
labelDelete: 'Delete',
|
|
28
|
+
labelDuplicate: 'Duplicate',
|
|
29
|
+
labelEditProperties: 'Edit Properties',
|
|
30
|
+
labelMoreActions: 'More actions',
|
|
31
|
+
labelShareWithContacts: 'Share with contacts'
|
|
35
32
|
}}
|
|
36
33
|
title={title}
|
|
37
|
-
onDelete={
|
|
34
|
+
onDelete={() => cy.stub()}
|
|
35
|
+
onDuplicate={() => cy.stub()}
|
|
36
|
+
onEdit={() => cy.stub()}
|
|
37
|
+
onEditAccessRights={() => cy.stub()}
|
|
38
38
|
/>
|
|
39
39
|
))}
|
|
40
40
|
</DataTable>
|
|
@@ -94,7 +94,6 @@ const initializeDataTableListing = (): void => {
|
|
|
94
94
|
describe('DataTable: Grid', () => {
|
|
95
95
|
it('displays items with title and description only', () => {
|
|
96
96
|
initializeDataTableGrid({
|
|
97
|
-
canDelete: false,
|
|
98
97
|
hasActions: false,
|
|
99
98
|
hasCardAction: false
|
|
100
99
|
});
|
|
@@ -109,32 +108,33 @@ describe('DataTable: Grid', () => {
|
|
|
109
108
|
|
|
110
109
|
it('displays items with actions', () => {
|
|
111
110
|
initializeDataTableGrid({
|
|
112
|
-
canDelete: false,
|
|
113
111
|
hasActions: true,
|
|
114
112
|
hasCardAction: false
|
|
115
113
|
});
|
|
116
114
|
|
|
117
|
-
cy.findAllByLabelText('
|
|
118
|
-
cy.findAllByLabelText('
|
|
115
|
+
cy.findAllByLabelText('Share with contacts').should('have.length', 5);
|
|
116
|
+
cy.findAllByLabelText('More actions').should('have.length', 5);
|
|
119
117
|
|
|
120
118
|
cy.makeSnapshot();
|
|
121
119
|
});
|
|
122
120
|
|
|
123
|
-
it('displays
|
|
121
|
+
it('displays actions when more actions button was clicked', () => {
|
|
124
122
|
initializeDataTableGrid({
|
|
125
|
-
canDelete: true,
|
|
126
123
|
hasActions: true,
|
|
127
124
|
hasCardAction: false
|
|
128
125
|
});
|
|
129
126
|
|
|
130
|
-
cy.findAllByLabelText('
|
|
127
|
+
cy.findAllByLabelText('More actions').eq(0).click();
|
|
128
|
+
|
|
129
|
+
cy.findByLabelText('Duplicate').should('be.visible');
|
|
130
|
+
cy.findByLabelText('Delete').should('be.visible');
|
|
131
|
+
cy.findByLabelText('Edit Properties').should('be.visible');
|
|
131
132
|
|
|
132
133
|
cy.makeSnapshot();
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
it('displays items with card action only', () => {
|
|
136
137
|
initializeDataTableGrid({
|
|
137
|
-
canDelete: false,
|
|
138
138
|
hasActions: false,
|
|
139
139
|
hasCardAction: true
|
|
140
140
|
});
|
|
@@ -146,15 +146,13 @@ describe('DataTable: Grid', () => {
|
|
|
146
146
|
|
|
147
147
|
it('displays items with card action and bottom actions', () => {
|
|
148
148
|
initializeDataTableGrid({
|
|
149
|
-
canDelete: true,
|
|
150
149
|
hasActions: true,
|
|
151
150
|
hasCardAction: true
|
|
152
151
|
});
|
|
153
152
|
|
|
154
153
|
cy.findAllByLabelText('view').should('have.length', 5);
|
|
155
|
-
cy.findAllByLabelText('
|
|
156
|
-
cy.findAllByLabelText('
|
|
157
|
-
cy.findAllByLabelText('edit').should('have.length', 5);
|
|
154
|
+
cy.findAllByLabelText('Share with contacts').should('have.length', 5);
|
|
155
|
+
cy.findAllByLabelText('More actions').should('have.length', 5);
|
|
158
156
|
|
|
159
157
|
cy.makeSnapshot();
|
|
160
158
|
});
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
ReactElement,
|
|
4
|
+
RefObject,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState
|
|
7
|
+
} from 'react';
|
|
8
|
+
|
|
9
|
+
import { pipe } from 'ramda';
|
|
2
10
|
|
|
3
11
|
import {
|
|
12
|
+
Menu,
|
|
4
13
|
Card as MuiCard,
|
|
5
14
|
CardActionArea as MuiCardActionArea,
|
|
6
15
|
CardActions as MuiCardActions,
|
|
@@ -10,10 +19,12 @@ import {
|
|
|
10
19
|
import {
|
|
11
20
|
Delete as DeleteIcon,
|
|
12
21
|
Settings as SettingsIcon,
|
|
13
|
-
Share as ShareIcon
|
|
22
|
+
Share as ShareIcon,
|
|
23
|
+
ContentCopy as DuplicateIcon,
|
|
24
|
+
MoreHoriz as MoreIcon
|
|
14
25
|
} from '@mui/icons-material';
|
|
15
26
|
|
|
16
|
-
import { IconButton } from '
|
|
27
|
+
import { IconButton, ActionsList, ActionsListActionDivider } from '../../..';
|
|
17
28
|
|
|
18
29
|
import { useStyles } from './DataTableItem.styles';
|
|
19
30
|
|
|
@@ -21,16 +32,19 @@ export interface DataTableItemProps {
|
|
|
21
32
|
description?: string;
|
|
22
33
|
hasActions?: boolean;
|
|
23
34
|
hasCardAction?: boolean;
|
|
35
|
+
labels;
|
|
24
36
|
onClick?: () => void;
|
|
25
|
-
onDelete
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
onDelete: () => void;
|
|
38
|
+
onDuplicate: () => void;
|
|
39
|
+
onEdit: () => void;
|
|
40
|
+
onEditAccessRights: () => void;
|
|
28
41
|
title: string;
|
|
29
42
|
}
|
|
30
43
|
|
|
31
44
|
const DataTableItem = forwardRef(
|
|
32
45
|
(
|
|
33
46
|
{
|
|
47
|
+
labels,
|
|
34
48
|
title,
|
|
35
49
|
description,
|
|
36
50
|
hasCardAction = false,
|
|
@@ -38,11 +52,16 @@ const DataTableItem = forwardRef(
|
|
|
38
52
|
onClick,
|
|
39
53
|
onEdit,
|
|
40
54
|
onDelete,
|
|
55
|
+
onDuplicate,
|
|
41
56
|
onEditAccessRights
|
|
42
57
|
}: DataTableItemProps,
|
|
43
58
|
ref
|
|
44
59
|
): ReactElement => {
|
|
45
60
|
const { classes } = useStyles();
|
|
61
|
+
const [moreActionsOpen, setMoreActionsOpen] = useState(null);
|
|
62
|
+
|
|
63
|
+
const closeMoreActions = (): void => setMoreActionsOpen(null);
|
|
64
|
+
const openMoreActions = (event): void => setMoreActionsOpen(event.target);
|
|
46
65
|
|
|
47
66
|
const ActionArea = useMemo(
|
|
48
67
|
() => (hasCardAction ? MuiCardActionArea : React.Fragment),
|
|
@@ -66,33 +85,49 @@ const DataTableItem = forwardRef(
|
|
|
66
85
|
</ActionArea>
|
|
67
86
|
{hasActions && (
|
|
68
87
|
<MuiCardActions>
|
|
88
|
+
<span />
|
|
69
89
|
<span>
|
|
70
90
|
<IconButton
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/>
|
|
78
|
-
</span>
|
|
79
|
-
<span>
|
|
80
|
-
<IconButton
|
|
81
|
-
aria-label="edit access rights"
|
|
82
|
-
data-testid="edit-access-rights"
|
|
83
|
-
icon={<ShareIcon />}
|
|
84
|
-
size="small"
|
|
85
|
-
variant="primary"
|
|
86
|
-
onClick={() => onEditAccessRights?.()}
|
|
87
|
-
/>
|
|
91
|
+
ariaLabel={labels.labelShareWithContacts}
|
|
92
|
+
title={labels.labelShareWithContacts}
|
|
93
|
+
onClick={pipe(onEditAccessRights, closeMoreActions)}
|
|
94
|
+
>
|
|
95
|
+
<ShareIcon fontSize="small" />
|
|
96
|
+
</IconButton>
|
|
88
97
|
<IconButton
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
ariaLabel={labels.labelMoreActions}
|
|
99
|
+
title={labels.labelMoreActions}
|
|
100
|
+
onClick={openMoreActions}
|
|
101
|
+
>
|
|
102
|
+
<MoreIcon />
|
|
103
|
+
</IconButton>
|
|
104
|
+
<Menu
|
|
105
|
+
anchorEl={moreActionsOpen}
|
|
106
|
+
open={Boolean(moreActionsOpen)}
|
|
107
|
+
onClose={closeMoreActions}
|
|
108
|
+
>
|
|
109
|
+
<ActionsList
|
|
110
|
+
actions={[
|
|
111
|
+
{
|
|
112
|
+
Icon: SettingsIcon,
|
|
113
|
+
label: labels.labelEditProperties,
|
|
114
|
+
onClick: pipe(onEdit, closeMoreActions)
|
|
115
|
+
},
|
|
116
|
+
ActionsListActionDivider.divider,
|
|
117
|
+
{
|
|
118
|
+
Icon: DuplicateIcon,
|
|
119
|
+
label: labels.labelDuplicate,
|
|
120
|
+
onClick: pipe(onDuplicate, closeMoreActions)
|
|
121
|
+
},
|
|
122
|
+
ActionsListActionDivider.divider,
|
|
123
|
+
{
|
|
124
|
+
Icon: DeleteIcon,
|
|
125
|
+
label: labels.labelDelete,
|
|
126
|
+
onClick: pipe(onDelete, closeMoreActions)
|
|
127
|
+
}
|
|
128
|
+
]}
|
|
129
|
+
/>
|
|
130
|
+
</Menu>
|
|
96
131
|
</span>
|
|
97
132
|
</MuiCardActions>
|
|
98
133
|
)}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ReactElement, useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { string, object } from 'yup';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import { InputType } from '../../../Form/Inputs/models';
|
|
7
|
+
import { Form, FormProps } from '../../../Form';
|
|
8
|
+
import { FormVariant } from '../Form.models';
|
|
9
|
+
import { FormActions, FormActionsProps } from '../FormActions';
|
|
10
|
+
|
|
11
|
+
import { useStyles } from './DashboardForm.styles';
|
|
12
|
+
import {
|
|
13
|
+
labelMustBeAtLeast,
|
|
14
|
+
labelMustBeMost,
|
|
15
|
+
labelRequired
|
|
16
|
+
} from './translatedLabels';
|
|
17
|
+
import { DashboardResource } from './Dashboard.resource';
|
|
18
|
+
|
|
19
|
+
type DashboardFormProps = {
|
|
20
|
+
labels: DashboardFormLabels;
|
|
21
|
+
name: string;
|
|
22
|
+
onSubmit?: FormProps<DashboardResource>['submit'];
|
|
23
|
+
variant?: FormVariant;
|
|
24
|
+
} & Pick<FormActionsProps, 'onCancel'>;
|
|
25
|
+
|
|
26
|
+
type DashboardFormLabels = {
|
|
27
|
+
actions: FormActionsProps['labels'];
|
|
28
|
+
entity: Required<DashboardResource>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const DashboardDuplicationForm = ({
|
|
32
|
+
variant = 'create',
|
|
33
|
+
labels,
|
|
34
|
+
onSubmit,
|
|
35
|
+
onCancel,
|
|
36
|
+
name
|
|
37
|
+
}: DashboardFormProps): ReactElement => {
|
|
38
|
+
const { classes } = useStyles();
|
|
39
|
+
const { t } = useTranslation();
|
|
40
|
+
|
|
41
|
+
const formProps = useMemo<FormProps<DashboardResource>>(
|
|
42
|
+
() => ({
|
|
43
|
+
initialValues: { name },
|
|
44
|
+
inputs: [
|
|
45
|
+
{
|
|
46
|
+
autoFocus: true,
|
|
47
|
+
fieldName: 'name',
|
|
48
|
+
group: 'main',
|
|
49
|
+
label: labels?.entity?.name,
|
|
50
|
+
required: true,
|
|
51
|
+
type: InputType.Text
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
submit: (values, bag) => onSubmit?.(values, bag),
|
|
55
|
+
validationSchema: object({
|
|
56
|
+
name: string()
|
|
57
|
+
.label(labels?.entity?.name)
|
|
58
|
+
.min(3, ({ min, label }) => t(labelMustBeAtLeast, { label, min }))
|
|
59
|
+
.max(50, ({ max, label }) => t(labelMustBeMost, { label, max }))
|
|
60
|
+
.required(t(labelRequired) as string)
|
|
61
|
+
})
|
|
62
|
+
}),
|
|
63
|
+
[labels, onSubmit]
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const Actions = useCallback(
|
|
67
|
+
() => (
|
|
68
|
+
<FormActions<DashboardResource>
|
|
69
|
+
enableSubmitWhenNotDirty
|
|
70
|
+
labels={labels?.actions}
|
|
71
|
+
variant={variant}
|
|
72
|
+
onCancel={onCancel}
|
|
73
|
+
/>
|
|
74
|
+
),
|
|
75
|
+
[labels, onCancel, variant]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className={classes.dashboardForm}>
|
|
80
|
+
<Form<DashboardResource> {...formProps} Buttons={Actions} />
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export { DashboardDuplicationForm };
|
|
@@ -8,6 +8,7 @@ import { FormVariant } from './Form.models';
|
|
|
8
8
|
import { useStyles } from './Form.styles';
|
|
9
9
|
|
|
10
10
|
export type FormActionsProps = {
|
|
11
|
+
enableSubmitWhenNotDirty?: boolean;
|
|
11
12
|
labels: FormActionsLabels;
|
|
12
13
|
onCancel: () => void;
|
|
13
14
|
variant: FormVariant;
|
|
@@ -21,12 +22,16 @@ export type FormActionsLabels = {
|
|
|
21
22
|
const FormActions = <TResource extends object>({
|
|
22
23
|
labels,
|
|
23
24
|
onCancel,
|
|
24
|
-
variant
|
|
25
|
+
variant,
|
|
26
|
+
enableSubmitWhenNotDirty
|
|
25
27
|
}: FormActionsProps): ReactElement => {
|
|
26
28
|
const { classes } = useStyles();
|
|
27
29
|
const { isSubmitting, dirty, isValid, submitForm } =
|
|
28
30
|
useFormikContext<TResource>();
|
|
29
31
|
|
|
32
|
+
const isSubmitDisabled =
|
|
33
|
+
isSubmitting || (!dirty && !enableSubmitWhenNotDirty) || !isValid;
|
|
34
|
+
|
|
30
35
|
return (
|
|
31
36
|
<div className={classes.actions}>
|
|
32
37
|
<Button
|
|
@@ -42,7 +47,7 @@ const FormActions = <TResource extends object>({
|
|
|
42
47
|
<Button
|
|
43
48
|
aria-label={labels.submit[variant]}
|
|
44
49
|
data-testid="submit"
|
|
45
|
-
disabled={
|
|
50
|
+
disabled={isSubmitDisabled}
|
|
46
51
|
size="medium"
|
|
47
52
|
type="submit"
|
|
48
53
|
variant="primary"
|