@centreon/ui 24.11.2 → 24.11.4
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 +2 -3
- package/src/Dashboard/Dashboard.styles.ts +4 -3
- package/src/Dashboard/DashboardLayout.stories.tsx +1 -1
- package/src/Dashboard/Grid.tsx +17 -11
- package/src/Dashboard/Layout.tsx +56 -27
- package/src/FileDropZone/index.tsx +21 -23
- package/src/Form/CollapsibleGroup.tsx +3 -2
- package/src/Form/Form.cypress.spec.tsx +39 -0
- package/src/Form/Form.tsx +1 -0
- package/src/Form/Inputs/Autocomplete.tsx +27 -4
- package/src/Form/Inputs/ConnectedAutocomplete.tsx +20 -10
- package/src/Form/Inputs/File.tsx +69 -0
- package/src/Form/Inputs/Grid.tsx +30 -2
- package/src/Form/Inputs/Radio.tsx +12 -4
- package/src/Form/Inputs/Switch.tsx +10 -2
- package/src/Form/Inputs/Text.tsx +13 -4
- package/src/Form/Inputs/index.tsx +5 -2
- package/src/Form/Inputs/models.ts +18 -2
- package/src/Form/storiesData.tsx +15 -3
- package/src/Form/translatedLabels.ts +1 -0
- package/src/Graph/BarChart/BarChart.tsx +4 -1
- package/src/Graph/BarChart/ResponsiveBarChart.tsx +3 -2
- package/src/Graph/Chart/Chart.tsx +9 -2
- package/src/Graph/Chart/InteractiveComponents/AnchorPoint/useTickGraph.ts +2 -2
- package/src/Graph/Chart/InteractiveComponents/index.tsx +10 -2
- package/src/Graph/Chart/helpers/index.ts +5 -5
- package/src/Graph/Chart/index.tsx +7 -0
- package/src/Graph/Chart/models.ts +1 -0
- package/src/Graph/common/timeSeries/index.ts +15 -8
- package/src/InputField/Text/index.tsx +1 -1
- package/src/Listing/index.tsx +39 -27
- package/src/Listing/models.ts +8 -0
- package/src/MultiSelectEntries/index.tsx +0 -2
- package/src/PopoverMenu/index.tsx +9 -2
- package/src/SortableItems/index.tsx +1 -0
- package/src/ThemeProvider/index.tsx +1 -1
- package/src/ThemeProvider/palettes.ts +4 -4
- package/src/api/customFetch.ts +4 -1
- package/src/components/CrudPage/Actions/Actions.styles.ts +16 -0
- package/src/components/CrudPage/Actions/Actions.tsx +24 -0
- package/src/components/CrudPage/Actions/AddButton.tsx +23 -0
- package/src/components/CrudPage/Actions/Filters.tsx +25 -0
- package/src/components/CrudPage/Actions/Search.tsx +31 -0
- package/src/components/CrudPage/Actions/useSearch.tsx +24 -0
- package/src/components/CrudPage/Columns/Actions.tsx +88 -0
- package/src/components/CrudPage/CrudPage.cypress.spec.tsx +559 -0
- package/src/components/CrudPage/CrudPage.stories.tsx +278 -0
- package/src/components/CrudPage/CrudPageRoot.tsx +142 -0
- package/src/components/CrudPage/DeleteModal.tsx +77 -0
- package/src/components/CrudPage/Form/AddModal.tsx +35 -0
- package/src/components/CrudPage/Form/Buttons.tsx +98 -0
- package/src/components/CrudPage/Form/UpdateModal.tsx +60 -0
- package/src/components/CrudPage/Listing.tsx +63 -0
- package/src/components/CrudPage/atoms.ts +30 -0
- package/src/components/CrudPage/hooks/useDeleteItem.ts +53 -0
- package/src/components/CrudPage/hooks/useGetItem.ts +36 -0
- package/src/components/CrudPage/hooks/useGetItems.ts +67 -0
- package/src/components/CrudPage/hooks/useListingQueryKey.ts +31 -0
- package/src/components/CrudPage/index.tsx +7 -0
- package/src/components/CrudPage/models.ts +118 -0
- package/src/components/CrudPage/utils.ts +4 -0
- package/src/components/DataTable/DataTable.cypress.spec.tsx +2 -1
- package/src/components/DataTable/DataTable.stories.tsx +17 -0
- package/src/components/DataTable/DataTable.styles.ts +1 -1
- package/src/components/DataTable/EmptyState/DataTableEmptyState.styles.ts +3 -1
- package/src/components/DataTable/EmptyState/DataTableEmptyState.tsx +6 -0
- package/src/components/DataTable/Item/DataTableItem.styles.ts +28 -2
- package/src/components/DataTable/Item/DataTableItem.tsx +19 -4
- package/src/components/Layout/AreaIndicator.tsx +1 -1
- package/src/components/Layout/PageLayout/PageLayout.styles.ts +7 -2
- package/src/components/Layout/PageLayout/PageLayoutBody.tsx +1 -0
- package/src/components/Modal/Modal.styles.ts +1 -1
- package/src/components/Zoom/Zoom.tsx +2 -2
- package/src/components/Zoom/ZoomContent.tsx +2 -2
- package/src/components/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centreon/ui",
|
|
3
|
-
"version": "24.11.
|
|
3
|
+
"version": "24.11.4",
|
|
4
4
|
"description": "Centreon UI Components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update:deps": "pnpx npm-check-updates -i --format group",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"cypress:run": "cypress run --component --browser=chrome",
|
|
19
19
|
"tokens:transform": "TS_NODE_PROJECT=tsconfig.node.json ts-node style-dictionary.transform.ts"
|
|
20
20
|
},
|
|
21
|
+
"type": "module",
|
|
21
22
|
"sideEffects": false,
|
|
22
23
|
"repository": {
|
|
23
24
|
"type": "git",
|
|
@@ -53,8 +54,6 @@
|
|
|
53
54
|
"@cypress/webpack-dev-server": "^3.10.1",
|
|
54
55
|
"@faker-js/faker": "^8.4.1",
|
|
55
56
|
"@mdx-js/react": "^3.0.1",
|
|
56
|
-
"@modern-js/prod-server": "^2.58.1",
|
|
57
|
-
"@modern-js/storybook": "^2.58.1",
|
|
58
57
|
"@simonsmith/cypress-image-snapshot": "^9.1.0",
|
|
59
58
|
"@storybook/addon-a11y": "^8.2.9",
|
|
60
59
|
"@storybook/addon-docs": "^8.2.9",
|
|
@@ -40,7 +40,7 @@ export const useDashboardLayoutStyles = makeStyles<boolean>()(
|
|
|
40
40
|
width: theme.spacing(1)
|
|
41
41
|
},
|
|
42
42
|
'& .react-resizable-handle.react-resizable-handle-s': {
|
|
43
|
-
bottom:
|
|
43
|
+
bottom: 4,
|
|
44
44
|
cursor: 'ns-resize',
|
|
45
45
|
height: theme.spacing(1),
|
|
46
46
|
left: 0,
|
|
@@ -49,7 +49,7 @@ export const useDashboardLayoutStyles = makeStyles<boolean>()(
|
|
|
49
49
|
width: `calc(100% - ${theme.spacing(3)})`
|
|
50
50
|
},
|
|
51
51
|
'& .react-resizable-handle.react-resizable-handle-se': {
|
|
52
|
-
bottom:
|
|
52
|
+
bottom: 4,
|
|
53
53
|
cursor: 'nwse-resize',
|
|
54
54
|
height: theme.spacing(2),
|
|
55
55
|
right: 0,
|
|
@@ -62,7 +62,8 @@ export const useDashboardLayoutStyles = makeStyles<boolean>()(
|
|
|
62
62
|
'& .react-resizable-handle:hover': {
|
|
63
63
|
opacity: 1
|
|
64
64
|
},
|
|
65
|
-
position: 'relative'
|
|
65
|
+
position: 'relative',
|
|
66
|
+
height: '100%',
|
|
66
67
|
}
|
|
67
68
|
})
|
|
68
69
|
);
|
|
@@ -95,7 +95,7 @@ export const normal = DashboardTemplate.bind({});
|
|
|
95
95
|
|
|
96
96
|
export const withManyPanels = DashboardTemplate.bind({});
|
|
97
97
|
withManyPanels.args = {
|
|
98
|
-
layout: generateLayout(
|
|
98
|
+
layout: generateLayout(100)
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
export const withItemHeader = DashboardTemplate.bind({});
|
package/src/Dashboard/Grid.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactElement, useMemo } from 'react';
|
|
1
|
+
import { MutableRefObject, ReactElement, useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import { scaleLinear } from '@visx/scale';
|
|
4
4
|
import { Grid as VisxGrid } from '@visx/visx';
|
|
@@ -13,9 +13,15 @@ interface Props {
|
|
|
13
13
|
columns: number;
|
|
14
14
|
height: number;
|
|
15
15
|
width: number;
|
|
16
|
+
containerRef: MutableRefObject<HTMLDivElement | null>;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const Grid = ({
|
|
19
|
+
const Grid = ({
|
|
20
|
+
width,
|
|
21
|
+
height,
|
|
22
|
+
columns,
|
|
23
|
+
containerRef
|
|
24
|
+
}: Props): ReactElement => {
|
|
19
25
|
const theme = useTheme();
|
|
20
26
|
|
|
21
27
|
const xScale = useMemo(
|
|
@@ -44,19 +50,19 @@ const Grid = ({ width, height, columns }: Props): ReactElement => {
|
|
|
44
50
|
.fill(0)
|
|
45
51
|
.map((_, index) => index * tick);
|
|
46
52
|
|
|
53
|
+
const yTickValues = Array(numberOfRows)
|
|
54
|
+
.fill(0)
|
|
55
|
+
.map((_, index) => index);
|
|
56
|
+
|
|
47
57
|
return useMemoComponent({
|
|
48
58
|
Component: (
|
|
49
59
|
<svg style={{ height, position: 'absolute', width }}>
|
|
50
|
-
<VisxGrid.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
stroke={theme.palette.divider}
|
|
54
|
-
tickValues={xTickValues}
|
|
55
|
-
width={width}
|
|
56
|
-
/>
|
|
57
|
-
<VisxGrid.GridRows
|
|
60
|
+
<VisxGrid.Grid
|
|
61
|
+
columnTickValues={xTickValues}
|
|
62
|
+
rowTickValues={yTickValues}
|
|
58
63
|
height={height}
|
|
59
|
-
|
|
64
|
+
yScale={yScale}
|
|
65
|
+
xScale={xScale}
|
|
60
66
|
stroke={theme.palette.divider}
|
|
61
67
|
top={-10}
|
|
62
68
|
width={width}
|
package/src/Dashboard/Layout.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useSetAtom } from 'jotai';
|
|
4
4
|
import GridLayout, { Layout, WidthProvider } from 'react-grid-layout';
|
|
@@ -6,10 +6,10 @@ import 'react-grid-layout/css/styles.css';
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
ParentSize,
|
|
9
|
-
Responsive as ResponsiveHeight,
|
|
10
9
|
useMemoComponent
|
|
11
10
|
} from '..';
|
|
12
11
|
|
|
12
|
+
import { Box } from '@mui/material';
|
|
13
13
|
import { useDashboardLayoutStyles } from './Dashboard.styles';
|
|
14
14
|
import Grid from './Grid';
|
|
15
15
|
import { isResizingItemAtom } from './atoms';
|
|
@@ -26,6 +26,18 @@ interface DashboardLayoutProps<T> {
|
|
|
26
26
|
layout: Array<T>;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
const bottom = (layout: Array<Layout>): number => {
|
|
30
|
+
let max = 0;
|
|
31
|
+
let bottomY = 0;
|
|
32
|
+
|
|
33
|
+
layout.forEach((panel) => {
|
|
34
|
+
bottomY = panel.y + panel.h;
|
|
35
|
+
if (bottomY > max) max = bottomY;
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return max;
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
const DashboardLayout = <T extends Layout>({
|
|
30
42
|
children,
|
|
31
43
|
changeLayout,
|
|
@@ -34,6 +46,8 @@ const DashboardLayout = <T extends Layout>({
|
|
|
34
46
|
isStatic = false,
|
|
35
47
|
additionalMemoProps = []
|
|
36
48
|
}: DashboardLayoutProps<T>): JSX.Element => {
|
|
49
|
+
const dashboardContainerRef = useRef<HTMLDivElement | null>(null);
|
|
50
|
+
|
|
37
51
|
const { classes } = useDashboardLayoutStyles(isStatic);
|
|
38
52
|
|
|
39
53
|
const [columns, setColumns] = useState(getColumnsFromScreenSize());
|
|
@@ -52,6 +66,16 @@ const DashboardLayout = <T extends Layout>({
|
|
|
52
66
|
setIsResizingItem(null);
|
|
53
67
|
}, []);
|
|
54
68
|
|
|
69
|
+
const containerHeight = useMemo((): number | undefined => {
|
|
70
|
+
const nbRow = bottom(getLayout(layout));
|
|
71
|
+
const containerPaddingY = 4
|
|
72
|
+
return (
|
|
73
|
+
nbRow * rowHeight +
|
|
74
|
+
(nbRow - 1) * 20 +
|
|
75
|
+
containerPaddingY * 2
|
|
76
|
+
);
|
|
77
|
+
}, [layout, rowHeight])
|
|
78
|
+
|
|
55
79
|
useEffect(() => {
|
|
56
80
|
window.addEventListener('resize', resize);
|
|
57
81
|
|
|
@@ -62,31 +86,36 @@ const DashboardLayout = <T extends Layout>({
|
|
|
62
86
|
|
|
63
87
|
return useMemoComponent({
|
|
64
88
|
Component: (
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
<Box ref={dashboardContainerRef} sx={{ overflowY: 'auto', overflowX: 'hidden' }}>
|
|
90
|
+
<ParentSize>
|
|
91
|
+
{({ width, height }): JSX.Element => (
|
|
92
|
+
<Box className={classes.container}>
|
|
93
|
+
{displayGrid && (
|
|
94
|
+
<Grid
|
|
95
|
+
columns={columns}
|
|
96
|
+
height={(containerHeight || 0) > height ? containerHeight : height}
|
|
97
|
+
width={width}
|
|
98
|
+
containerRef={dashboardContainerRef}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
<ReactGridLayout
|
|
102
|
+
cols={columns}
|
|
103
|
+
containerPadding={[4, 0]}
|
|
104
|
+
layout={getLayout(layout)}
|
|
105
|
+
margin={[20, 20]}
|
|
106
|
+
resizeHandles={['s', 'e', 'se']}
|
|
107
|
+
rowHeight={rowHeight}
|
|
108
|
+
width={width}
|
|
109
|
+
onLayoutChange={changeLayout}
|
|
110
|
+
onResizeStart={startResize}
|
|
111
|
+
onResizeStop={stopResize}
|
|
112
|
+
>
|
|
113
|
+
{children}
|
|
114
|
+
</ReactGridLayout>
|
|
115
|
+
</Box>
|
|
116
|
+
)}
|
|
117
|
+
</ParentSize>
|
|
118
|
+
</Box>
|
|
90
119
|
),
|
|
91
120
|
memoProps: [columns, layout, displayGrid, isStatic, ...additionalMemoProps]
|
|
92
121
|
});
|
|
@@ -30,23 +30,15 @@ interface StylesProps {
|
|
|
30
30
|
const useStyles = makeStyles<StylesProps>()(
|
|
31
31
|
(theme, { hasCustomDropZoneContent, isDraggingOver }) => ({
|
|
32
32
|
dropzone: {
|
|
33
|
-
'&:hover':
|
|
34
|
-
|
|
35
|
-
:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}`,
|
|
40
|
-
boxShadow: theme.shadows[3],
|
|
41
|
-
cursor: 'pointer'
|
|
42
|
-
},
|
|
43
|
-
border: `${theme.spacing(0.3)} dashed ${
|
|
44
|
-
hasCustomDropZoneContent && !isDraggingOver
|
|
45
|
-
? 'transparent'
|
|
46
|
-
: theme.palette.primary.main
|
|
47
|
-
}`,
|
|
33
|
+
'&:hover': {
|
|
34
|
+
backgroundColor: alpha(theme.palette.primary.main, 0.1),
|
|
35
|
+
boxShadow: theme.shadows[3],
|
|
36
|
+
cursor: 'pointer'
|
|
37
|
+
},
|
|
38
|
+
border: `${theme.spacing(0.3)} dashed ${theme.palette.primary.main}`,
|
|
48
39
|
boxShadow: isDraggingOver ? theme.shadows[3] : theme.shadows[0],
|
|
49
|
-
|
|
40
|
+
borderRadius: `${theme.shape.borderRadius}px`,
|
|
41
|
+
padding: theme.spacing(0.5, 1),
|
|
50
42
|
width: hasCustomDropZoneContent ? '100%' : theme.spacing(50)
|
|
51
43
|
},
|
|
52
44
|
dropzoneInfo: {
|
|
@@ -66,7 +58,7 @@ const useStyles = makeStyles<StylesProps>()(
|
|
|
66
58
|
export type CustomDropZoneContentProps = Pick<
|
|
67
59
|
UseDropzoneState,
|
|
68
60
|
'openFileExplorer'
|
|
69
|
-
|
|
61
|
+
> & { files: FileList | null; label?: string };
|
|
70
62
|
|
|
71
63
|
interface Props {
|
|
72
64
|
CustomDropZoneContent?: ({
|
|
@@ -79,6 +71,7 @@ interface Props {
|
|
|
79
71
|
maxFileSize?: number;
|
|
80
72
|
multiple?: boolean;
|
|
81
73
|
resetFilesStatusAndUploadData: () => void;
|
|
74
|
+
label?: string;
|
|
82
75
|
}
|
|
83
76
|
|
|
84
77
|
const getExtensions = cond([
|
|
@@ -115,7 +108,8 @@ const Dropzone = ({
|
|
|
115
108
|
accept,
|
|
116
109
|
CustomDropZoneContent,
|
|
117
110
|
maxFileSize,
|
|
118
|
-
className
|
|
111
|
+
className,
|
|
112
|
+
label
|
|
119
113
|
}: Props): JSX.Element => {
|
|
120
114
|
const hasCustomDropZoneContent = !isNil(CustomDropZoneContent);
|
|
121
115
|
const {
|
|
@@ -145,25 +139,29 @@ const Dropzone = ({
|
|
|
145
139
|
<div>
|
|
146
140
|
<Box
|
|
147
141
|
className={cx(classes.dropzone, className)}
|
|
148
|
-
onClick={
|
|
142
|
+
onClick={openFileExplorer}
|
|
149
143
|
onDragLeave={dragOver(false)}
|
|
150
144
|
onDragOver={dragOver(true)}
|
|
151
145
|
onDrop={dropFiles}
|
|
152
146
|
>
|
|
153
147
|
<div className={classes.dropzoneInfo}>
|
|
154
148
|
{hasCustomDropZoneContent ? (
|
|
155
|
-
<CustomDropZoneContent
|
|
149
|
+
<CustomDropZoneContent
|
|
150
|
+
openFileExplorer={openFileExplorer}
|
|
151
|
+
files={files}
|
|
152
|
+
label={label}
|
|
153
|
+
/>
|
|
156
154
|
) : (
|
|
157
|
-
|
|
155
|
+
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
|
158
156
|
<PostAddIcon color="primary" fontSize="large" />
|
|
159
157
|
<Typography>
|
|
160
158
|
{t(labelDropOr)} {t(labelSelectAFile)}
|
|
161
159
|
</Typography>
|
|
162
|
-
|
|
160
|
+
</Box>
|
|
163
161
|
)}
|
|
164
162
|
<input
|
|
165
163
|
accept={accept}
|
|
166
|
-
aria-label={t(labelSelectAFile)
|
|
164
|
+
aria-label={t(labelSelectAFile)}
|
|
167
165
|
className={classes.input}
|
|
168
166
|
multiple={multiple}
|
|
169
167
|
ref={fileInputRef}
|
|
@@ -43,7 +43,8 @@ const useStyles = makeStyles()((theme) => ({
|
|
|
43
43
|
},
|
|
44
44
|
tooltip: {
|
|
45
45
|
maxWidth: theme.spacing(60)
|
|
46
|
-
}
|
|
46
|
+
},
|
|
47
|
+
title: {}
|
|
47
48
|
}));
|
|
48
49
|
|
|
49
50
|
const CollapsibleGroup = ({
|
|
@@ -97,7 +98,7 @@ const CollapsibleGroup = ({
|
|
|
97
98
|
<div className={classes.groupTitleIcon}>
|
|
98
99
|
<Typography
|
|
99
100
|
className="groupText"
|
|
100
|
-
variant="
|
|
101
|
+
variant="h6"
|
|
101
102
|
{...group?.titleAttributes}
|
|
102
103
|
>
|
|
103
104
|
{t(group?.name as string)}
|
|
@@ -131,3 +131,42 @@ describe('Form list', () => {
|
|
|
131
131
|
cy.makeSnapshot();
|
|
132
132
|
});
|
|
133
133
|
});
|
|
134
|
+
|
|
135
|
+
const initializeFile = (): void => {
|
|
136
|
+
cy.mount({
|
|
137
|
+
Component: (
|
|
138
|
+
<Form
|
|
139
|
+
initialValues={{
|
|
140
|
+
list: []
|
|
141
|
+
}}
|
|
142
|
+
inputs={[
|
|
143
|
+
{
|
|
144
|
+
fieldName: 'file',
|
|
145
|
+
group: '',
|
|
146
|
+
label: 'json',
|
|
147
|
+
type: InputType.File,
|
|
148
|
+
file: {
|
|
149
|
+
accept: '.json'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
]}
|
|
153
|
+
submit={cy.stub()}
|
|
154
|
+
validationSchema={object()}
|
|
155
|
+
/>
|
|
156
|
+
)
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
describe('File', () => {
|
|
161
|
+
it('uploads a file when a file is selected', () => {
|
|
162
|
+
initializeFile();
|
|
163
|
+
|
|
164
|
+
cy.contains('Drop or select a file').should('be.visible');
|
|
165
|
+
cy.findByLabelText('select a file').selectFile('package.json', {
|
|
166
|
+
force: true
|
|
167
|
+
});
|
|
168
|
+
cy.contains('package.json').should('be.visible');
|
|
169
|
+
|
|
170
|
+
cy.makeSnapshot();
|
|
171
|
+
});
|
|
172
|
+
});
|
package/src/Form/Form.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useMemo, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { FormikValues, useFormikContext } from 'formik';
|
|
4
|
-
import {
|
|
4
|
+
import { equals, isNil, map, not, path, prop, type } from 'ramda';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
7
|
import { FormHelperText, Stack } from '@mui/material';
|
|
@@ -53,7 +53,15 @@ const Autocomplete = ({
|
|
|
53
53
|
|
|
54
54
|
const [inputText, setInputText] = useState('');
|
|
55
55
|
|
|
56
|
-
const {
|
|
56
|
+
const {
|
|
57
|
+
values,
|
|
58
|
+
setFieldValue,
|
|
59
|
+
setFieldTouched,
|
|
60
|
+
errors,
|
|
61
|
+
touched,
|
|
62
|
+
setValues,
|
|
63
|
+
setTouched
|
|
64
|
+
} = useFormikContext<FormikValues>();
|
|
57
65
|
|
|
58
66
|
const isMultiple = equals(inputType, InputType.MultiAutocomplete);
|
|
59
67
|
|
|
@@ -67,11 +75,20 @@ const Autocomplete = ({
|
|
|
67
75
|
setInputText('');
|
|
68
76
|
|
|
69
77
|
if (change) {
|
|
70
|
-
|
|
78
|
+
setFieldTouched(fieldName, true, false);
|
|
79
|
+
change({
|
|
80
|
+
setFieldValue,
|
|
81
|
+
value: normalizedNewValues,
|
|
82
|
+
setFieldTouched,
|
|
83
|
+
setValues,
|
|
84
|
+
values,
|
|
85
|
+
setTouched
|
|
86
|
+
});
|
|
71
87
|
|
|
72
88
|
return;
|
|
73
89
|
}
|
|
74
90
|
|
|
91
|
+
setFieldTouched(fieldName, true, false);
|
|
75
92
|
setFieldValue(fieldName, normalizedNewValues);
|
|
76
93
|
};
|
|
77
94
|
|
|
@@ -83,6 +100,10 @@ const Autocomplete = ({
|
|
|
83
100
|
);
|
|
84
101
|
|
|
85
102
|
const getError = useCallback((): Array<string> | undefined => {
|
|
103
|
+
if (!path([...fieldName.split('.')], touched)) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
86
107
|
const error = path([...fieldName.split('.')], errors) as
|
|
87
108
|
| Array<string>
|
|
88
109
|
| string
|
|
@@ -111,7 +132,7 @@ const Autocomplete = ({
|
|
|
111
132
|
const filteredError = formattedError?.filter(Boolean);
|
|
112
133
|
|
|
113
134
|
return (filteredError as Array<string>) || undefined;
|
|
114
|
-
}, [errors, fieldName, isMultiple, selectedValues]);
|
|
135
|
+
}, [errors, fieldName, isMultiple, selectedValues, touched]);
|
|
115
136
|
|
|
116
137
|
const textChange = useCallback(
|
|
117
138
|
(event): void => setInputText(event.target.value),
|
|
@@ -167,6 +188,7 @@ const Autocomplete = ({
|
|
|
167
188
|
value={getValues() ?? null}
|
|
168
189
|
onChange={changeValues}
|
|
169
190
|
onTextChange={textChange}
|
|
191
|
+
style={{ width: autocomplete?.fullWidth ?? true ? 'auto' : '180px' }}
|
|
170
192
|
/>
|
|
171
193
|
{inputErrors && (
|
|
172
194
|
<Stack>
|
|
@@ -180,6 +202,7 @@ const Autocomplete = ({
|
|
|
180
202
|
</div>
|
|
181
203
|
),
|
|
182
204
|
memoProps: [
|
|
205
|
+
values,
|
|
183
206
|
getValues(),
|
|
184
207
|
inputErrors,
|
|
185
208
|
additionalLabel,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import { FormikValues, useFormikContext } from 'formik';
|
|
4
|
-
import {
|
|
4
|
+
import { equals, isEmpty, path, propEq, reject, split } from 'ramda';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -30,8 +30,15 @@ const ConnectedAutocomplete = ({
|
|
|
30
30
|
}: InputPropsWithoutGroup): JSX.Element => {
|
|
31
31
|
const { t } = useTranslation();
|
|
32
32
|
|
|
33
|
-
const {
|
|
34
|
-
|
|
33
|
+
const {
|
|
34
|
+
values,
|
|
35
|
+
touched,
|
|
36
|
+
errors,
|
|
37
|
+
setFieldValue,
|
|
38
|
+
setFieldTouched,
|
|
39
|
+
setValues,
|
|
40
|
+
setTouched
|
|
41
|
+
} = useFormikContext<FormikValues>();
|
|
35
42
|
|
|
36
43
|
const filterKey = connectedAutocomplete?.filterKey || defaultFilterKey;
|
|
37
44
|
|
|
@@ -58,18 +65,20 @@ const ConnectedAutocomplete = ({
|
|
|
58
65
|
const changeAutocomplete = useCallback(
|
|
59
66
|
(_, value): void => {
|
|
60
67
|
if (change) {
|
|
61
|
-
change({
|
|
68
|
+
change({
|
|
69
|
+
setFieldValue,
|
|
70
|
+
value,
|
|
71
|
+
setFieldTouched,
|
|
72
|
+
setValues,
|
|
73
|
+
values,
|
|
74
|
+
setTouched
|
|
75
|
+
});
|
|
62
76
|
|
|
63
77
|
return;
|
|
64
78
|
}
|
|
65
79
|
|
|
80
|
+
setFieldTouched(fieldName, true, false);
|
|
66
81
|
setFieldValue(fieldName, value);
|
|
67
|
-
|
|
68
|
-
if (path(fieldNamePath, touched)) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
setFieldTouched(fieldName, true);
|
|
73
82
|
},
|
|
74
83
|
[fieldName, touched, additionalMemoProps]
|
|
75
84
|
);
|
|
@@ -105,6 +114,7 @@ const ConnectedAutocomplete = ({
|
|
|
105
114
|
const deleteItem = (_, option): void => {
|
|
106
115
|
const newValue = reject(propEq(option.id, 'id'), value);
|
|
107
116
|
|
|
117
|
+
setFieldTouched(fieldName, true, false);
|
|
108
118
|
setFieldValue(fieldName, newValue);
|
|
109
119
|
};
|
|
110
120
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
|
|
2
|
+
import { Box, Typography } from '@mui/material';
|
|
3
|
+
import { FormikValues, useFormikContext } from 'formik';
|
|
4
|
+
import { path, split } from 'ramda';
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import FileDropZone, { transformFileListToArray } from '../../FileDropZone';
|
|
8
|
+
import { InputPropsWithoutGroup } from './models';
|
|
9
|
+
|
|
10
|
+
const File = ({
|
|
11
|
+
fieldName,
|
|
12
|
+
file,
|
|
13
|
+
change,
|
|
14
|
+
dataTestId,
|
|
15
|
+
label
|
|
16
|
+
}: InputPropsWithoutGroup): JSX.Element => {
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
|
|
19
|
+
const { values, setFieldValue, setFieldTouched } =
|
|
20
|
+
useFormikContext<FormikValues>();
|
|
21
|
+
|
|
22
|
+
const fieldNamePath = split('.', fieldName);
|
|
23
|
+
|
|
24
|
+
const files = useMemo(
|
|
25
|
+
() => path(fieldNamePath, values),
|
|
26
|
+
[values]
|
|
27
|
+
) as FileList;
|
|
28
|
+
|
|
29
|
+
const filesArray = transformFileListToArray(files);
|
|
30
|
+
|
|
31
|
+
const changeFiles = (newFiles: FileList | null): void => {
|
|
32
|
+
if (change) {
|
|
33
|
+
change({ setFieldValue, setFieldTouched, value: newFiles });
|
|
34
|
+
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setFieldValue(fieldName, newFiles);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Box data-testid={dataTestId} aria-label={t(label)}>
|
|
43
|
+
<Typography variant="h6">{t(label)}</Typography>
|
|
44
|
+
<Box sx={{ display: 'flex', gap: 1, flexDirection: 'column' }}>
|
|
45
|
+
<FileDropZone
|
|
46
|
+
{...file}
|
|
47
|
+
accept={file?.accept || '*'}
|
|
48
|
+
files={files || null}
|
|
49
|
+
changeFiles={changeFiles}
|
|
50
|
+
resetFilesStatusAndUploadData={() => undefined}
|
|
51
|
+
label={label}
|
|
52
|
+
/>
|
|
53
|
+
<Box sx={{ display: 'flex', gap: 1, flexDirection: 'column' }}>
|
|
54
|
+
{filesArray.map((file) => (
|
|
55
|
+
<Box
|
|
56
|
+
key={file.name}
|
|
57
|
+
sx={{ display: 'flex', gap: 1, flexDirection: 'row' }}
|
|
58
|
+
>
|
|
59
|
+
<DescriptionOutlinedIcon color="success" fontSize="small" />
|
|
60
|
+
<Typography>{file.name}</Typography>
|
|
61
|
+
</Box>
|
|
62
|
+
))}
|
|
63
|
+
</Box>
|
|
64
|
+
</Box>
|
|
65
|
+
</Box>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default File;
|
package/src/Form/Inputs/Grid.tsx
CHANGED
|
@@ -2,6 +2,8 @@ import { makeStyles } from 'tss-react/mui';
|
|
|
2
2
|
|
|
3
3
|
import { InputPropsWithoutGroup } from './models';
|
|
4
4
|
|
|
5
|
+
import { Box, Typography } from '@mui/material';
|
|
6
|
+
import { FormikValues, useFormikContext } from 'formik';
|
|
5
7
|
import { getInput } from '.';
|
|
6
8
|
|
|
7
9
|
interface StylesProps {
|
|
@@ -22,13 +24,22 @@ const useStyles = makeStyles<StylesProps>()(
|
|
|
22
24
|
})
|
|
23
25
|
);
|
|
24
26
|
|
|
25
|
-
const Grid = ({
|
|
27
|
+
const Grid = ({
|
|
28
|
+
grid,
|
|
29
|
+
hideInput
|
|
30
|
+
}: InputPropsWithoutGroup): JSX.Element | null => {
|
|
26
31
|
const { classes, cx } = useStyles({
|
|
27
32
|
alignItems: grid?.alignItems,
|
|
28
33
|
columns: grid?.columns.length,
|
|
29
34
|
gridTemplateColumns: grid?.gridTemplateColumns
|
|
30
35
|
});
|
|
31
36
|
|
|
37
|
+
const { values } = useFormikContext<FormikValues>();
|
|
38
|
+
|
|
39
|
+
if (hideInput?.(values) ?? false) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
const className = grid?.className || '';
|
|
33
44
|
|
|
34
45
|
return (
|
|
@@ -36,7 +47,24 @@ const Grid = ({ grid }: InputPropsWithoutGroup): JSX.Element => {
|
|
|
36
47
|
{grid?.columns.map((field) => {
|
|
37
48
|
const Input = getInput(field.type);
|
|
38
49
|
|
|
39
|
-
|
|
50
|
+
if (field.hideInput?.(values) ?? false) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Box sx={{ width: '100%' }} key={field.fieldName}>
|
|
56
|
+
{field.additionalLabel && (
|
|
57
|
+
<Typography
|
|
58
|
+
sx={{ marginBottom: 0.5, color: 'primary.main' }}
|
|
59
|
+
className={cx(field?.additionalLabelClassName)}
|
|
60
|
+
variant="h6"
|
|
61
|
+
>
|
|
62
|
+
{field.additionalLabel}
|
|
63
|
+
</Typography>
|
|
64
|
+
)}
|
|
65
|
+
<Input {...field} />
|
|
66
|
+
</Box>
|
|
67
|
+
);
|
|
40
68
|
})}
|
|
41
69
|
</div>
|
|
42
70
|
);
|