drf-react-by-schema 0.1.0 → 0.2.0
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/.eslintrc.js +5 -2
- package/dist/index.js +57 -1
- package/package.json +32 -9
- package/src/api.ts +380 -173
- package/src/components/DataGridBySchemaEditable/ConfirmDialog.tsx +41 -0
- package/src/components/DataGridBySchemaEditable/CustomToolbar.tsx +93 -0
- package/src/components/DataGridBySchemaEditable/FooterToolbar.tsx +77 -0
- package/src/components/DataGridBySchemaEditable/GridDecimalInput.tsx +41 -0
- package/src/components/DataGridBySchemaEditable/GridPatternInput.tsx +37 -0
- package/src/components/DataGridBySchemaEditable/InputInterval.tsx +194 -0
- package/src/components/DataGridBySchemaEditable/SelectEditInputCell.tsx +153 -0
- package/src/components/DataGridBySchemaEditable/utils.ts +118 -0
- package/src/components/DataGridBySchemaEditable.md +50 -0
- package/src/components/DataGridBySchemaEditable.tsx +72 -726
- package/src/components/DataTotals.tsx +56 -0
- package/src/components/GenericModelList.tsx +155 -0
- package/src/components/TextFieldBySchema.tsx +62 -0
- package/src/context/DRFReactBySchemaProvider.md +50 -0
- package/src/context/DRFReactBySchemaProvider.tsx +78 -0
- package/src/index.ts +62 -1
- package/src/utils.ts +5 -5
- package/styleguide.config.js +18 -0
- package/webpack.config.js +24 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Button from '@mui/material/Button';
|
|
3
|
+
import Dialog from '@mui/material/Dialog';
|
|
4
|
+
import DialogActions from '@mui/material/DialogActions';
|
|
5
|
+
import DialogContent from '@mui/material/DialogContent';
|
|
6
|
+
import DialogTitle from '@mui/material/DialogTitle';
|
|
7
|
+
|
|
8
|
+
type FConfirmDialogProps = {
|
|
9
|
+
open: boolean,
|
|
10
|
+
onClose: (p: any) => void,
|
|
11
|
+
onConfirm: (p: any) => void
|
|
12
|
+
};
|
|
13
|
+
const FConfirmDialog = ({
|
|
14
|
+
open,
|
|
15
|
+
onClose,
|
|
16
|
+
onConfirm
|
|
17
|
+
}: FConfirmDialogProps) => {
|
|
18
|
+
return (
|
|
19
|
+
<Dialog open={open} onClose={onClose}>
|
|
20
|
+
<DialogTitle>
|
|
21
|
+
Confirmar
|
|
22
|
+
</DialogTitle>
|
|
23
|
+
<DialogContent>
|
|
24
|
+
Tem certeza de que você quer remover este item?
|
|
25
|
+
</DialogContent>
|
|
26
|
+
<DialogActions>
|
|
27
|
+
<Button
|
|
28
|
+
onClick={onClose}
|
|
29
|
+
>
|
|
30
|
+
Cancelar
|
|
31
|
+
</Button>
|
|
32
|
+
<Button
|
|
33
|
+
onClick={onConfirm}
|
|
34
|
+
>
|
|
35
|
+
Remover
|
|
36
|
+
</Button>
|
|
37
|
+
</DialogActions>
|
|
38
|
+
</Dialog>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
export const ConfirmDialog = React.memo(FConfirmDialog);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
useGridApiContext,
|
|
4
|
+
GridToolbarContainer,
|
|
5
|
+
GridToolbarColumnsButton,
|
|
6
|
+
GridToolbarFilterButton,
|
|
7
|
+
GridToolbarDensitySelector,
|
|
8
|
+
GridToolbarExport,
|
|
9
|
+
GridToolbarQuickFilter
|
|
10
|
+
} from '@mui/x-data-grid';
|
|
11
|
+
import Button from '@mui/material/Button';
|
|
12
|
+
import ExpandIcon from '@mui/icons-material/Expand';
|
|
13
|
+
import Menu from '@mui/material/Menu';
|
|
14
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
15
|
+
|
|
16
|
+
import { GridEnrichedBySchemaColDef } from '../../utils';
|
|
17
|
+
import { resizeColumns } from './utils';
|
|
18
|
+
|
|
19
|
+
type CustomToolbarProps = {
|
|
20
|
+
preparedColumns: GridEnrichedBySchemaColDef[],
|
|
21
|
+
setPreparedColumns: (p:null | GridEnrichedBySchemaColDef[]) => void
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
*
|
|
27
|
+
* @param {CustomToolbarProps} {
|
|
28
|
+
* preparedColumns,
|
|
29
|
+
* setPreparedColumns
|
|
30
|
+
* }
|
|
31
|
+
* @returns Custom Toolbar for the grid
|
|
32
|
+
*/
|
|
33
|
+
export const CustomToolbar = ({
|
|
34
|
+
preparedColumns,
|
|
35
|
+
setPreparedColumns
|
|
36
|
+
}:CustomToolbarProps) => {
|
|
37
|
+
const apiRef = useGridApiContext();
|
|
38
|
+
const [resizeMenuAnchorEl, setResizeMenuAnchorEl] = useState<any>(null);
|
|
39
|
+
const isResizeMenuOpen = Boolean(resizeMenuAnchorEl);
|
|
40
|
+
const openResizeMenu = (event:React.MouseEvent) => {
|
|
41
|
+
setResizeMenuAnchorEl(event.currentTarget);
|
|
42
|
+
};
|
|
43
|
+
const closeResizeMenu = () => {
|
|
44
|
+
setResizeMenuAnchorEl(null);
|
|
45
|
+
};
|
|
46
|
+
return (
|
|
47
|
+
<GridToolbarContainer sx={{ justifyContent: 'space-between' }}>
|
|
48
|
+
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
|
|
49
|
+
<GridToolbarColumnsButton sx={{ ml: '10px', fontSize: '13px' }} />
|
|
50
|
+
<GridToolbarFilterButton sx={{ ml: '10px', fontSize: '13px' }} />
|
|
51
|
+
<GridToolbarDensitySelector sx={{ ml: '10px', fontSize: '13px' }} />
|
|
52
|
+
<Button onClick={openResizeMenu} sx={{ ml: '0px', fontSize: '13px' }}>
|
|
53
|
+
<ExpandIcon sx={{ transform: 'rotate(90deg)', mr: '6px' }} />
|
|
54
|
+
Ajustar
|
|
55
|
+
</Button>
|
|
56
|
+
<Menu
|
|
57
|
+
anchorEl={resizeMenuAnchorEl}
|
|
58
|
+
open={isResizeMenuOpen}
|
|
59
|
+
onClose={closeResizeMenu}
|
|
60
|
+
>
|
|
61
|
+
<MenuItem
|
|
62
|
+
onClick={() => {
|
|
63
|
+
closeResizeMenu();
|
|
64
|
+
setPreparedColumns(resizeColumns(
|
|
65
|
+
preparedColumns,
|
|
66
|
+
'fitScreen',
|
|
67
|
+
apiRef
|
|
68
|
+
));
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
Ajustar à tela
|
|
72
|
+
</MenuItem>
|
|
73
|
+
<MenuItem
|
|
74
|
+
onClick={() => {
|
|
75
|
+
closeResizeMenu();
|
|
76
|
+
setPreparedColumns(resizeColumns(
|
|
77
|
+
preparedColumns,
|
|
78
|
+
'maxContent',
|
|
79
|
+
apiRef
|
|
80
|
+
));
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
Ajustar ao conteúdo
|
|
84
|
+
</MenuItem>
|
|
85
|
+
</Menu>
|
|
86
|
+
<GridToolbarExport sx={{ ml: '0px', fontSize: '13px' }} />
|
|
87
|
+
</div>
|
|
88
|
+
<div>
|
|
89
|
+
<GridToolbarQuickFilter />
|
|
90
|
+
</div>
|
|
91
|
+
</GridToolbarContainer>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
GridRowModes,
|
|
4
|
+
GridFooterContainer,
|
|
5
|
+
GridFooter
|
|
6
|
+
} from '@mui/x-data-grid';
|
|
7
|
+
import Button from '@mui/material/Button';
|
|
8
|
+
import AddIcon from '@mui/icons-material/Add';
|
|
9
|
+
|
|
10
|
+
import { getTmpId, Item } from '../../utils';
|
|
11
|
+
|
|
12
|
+
type FooterToolbarProps = {
|
|
13
|
+
name: string,
|
|
14
|
+
setRowModesModel: (p:any) => any,
|
|
15
|
+
dataGrid: { data:Item[] },
|
|
16
|
+
setDataGrid: (p:any) => any,
|
|
17
|
+
emptyItem: { current:Record<string, any> },
|
|
18
|
+
indexField: string,
|
|
19
|
+
isEditable: boolean
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const FooterToolbar = ({
|
|
23
|
+
name,
|
|
24
|
+
setRowModesModel,
|
|
25
|
+
dataGrid,
|
|
26
|
+
setDataGrid,
|
|
27
|
+
emptyItem,
|
|
28
|
+
indexField,
|
|
29
|
+
isEditable
|
|
30
|
+
}:FooterToolbarProps) => {
|
|
31
|
+
const handleClick = () => {
|
|
32
|
+
const id = getTmpId();
|
|
33
|
+
emptyItem.current.id = id;
|
|
34
|
+
const newData = [
|
|
35
|
+
{ ...emptyItem.current },
|
|
36
|
+
...dataGrid.data
|
|
37
|
+
];
|
|
38
|
+
setDataGrid({
|
|
39
|
+
data: newData
|
|
40
|
+
});
|
|
41
|
+
setRowModesModel((oldModel:any) => ({
|
|
42
|
+
...oldModel,
|
|
43
|
+
[id]: { mode: GridRowModes.Edit, fieldToFocus: indexField }
|
|
44
|
+
}));
|
|
45
|
+
// Ugly hack to scroll to top, since scroll to cell is only available in Pro
|
|
46
|
+
const el = document.querySelector(`.dataGrid_${name} .MuiDataGrid-virtualScroller`);
|
|
47
|
+
// console.log(el, name);
|
|
48
|
+
if (el) {
|
|
49
|
+
el.scrollTop = 0;
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
el.scrollTop = 0;
|
|
52
|
+
}, 10);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<GridFooterContainer>
|
|
58
|
+
{isEditable &&
|
|
59
|
+
<Button
|
|
60
|
+
color="primary"
|
|
61
|
+
startIcon={<AddIcon />}
|
|
62
|
+
onClick={handleClick}
|
|
63
|
+
sx={{ ml: 2 }}
|
|
64
|
+
>
|
|
65
|
+
Adicionar
|
|
66
|
+
</Button>
|
|
67
|
+
}
|
|
68
|
+
<GridFooter
|
|
69
|
+
sx={
|
|
70
|
+
(isEditable)
|
|
71
|
+
? { border: 'none' }
|
|
72
|
+
: { width: '100%' }
|
|
73
|
+
}
|
|
74
|
+
/>
|
|
75
|
+
</GridFooterContainer>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useGridApiContext } from '@mui/x-data-grid';
|
|
3
|
+
import { NumericFormat } from 'react-number-format';
|
|
4
|
+
import TextField from '@mui/material/TextField';
|
|
5
|
+
|
|
6
|
+
type GridDecimalInputProps = {
|
|
7
|
+
field: string,
|
|
8
|
+
id: number | string,
|
|
9
|
+
value?: any,
|
|
10
|
+
column: object
|
|
11
|
+
};
|
|
12
|
+
export const GridDecimalInput = ({
|
|
13
|
+
id,
|
|
14
|
+
value,
|
|
15
|
+
field
|
|
16
|
+
}:GridDecimalInputProps) => {
|
|
17
|
+
const apiRef = useGridApiContext();
|
|
18
|
+
const decimalScale = 2;
|
|
19
|
+
const disableCurrency = true;
|
|
20
|
+
const handleChange = async (newValue:any) => {
|
|
21
|
+
await apiRef.current.setEditCellValue({ id, field, value: newValue });
|
|
22
|
+
apiRef.current.stopCellEditMode({ id, field });
|
|
23
|
+
};
|
|
24
|
+
return (
|
|
25
|
+
<NumericFormat
|
|
26
|
+
key={field}
|
|
27
|
+
id={field}
|
|
28
|
+
onValueChange={(values, sourceInfo) => {
|
|
29
|
+
handleChange(values.value);
|
|
30
|
+
}}
|
|
31
|
+
value={value}
|
|
32
|
+
thousandSeparator='.'
|
|
33
|
+
decimalSeparator=','
|
|
34
|
+
decimalScale={decimalScale}
|
|
35
|
+
fixedDecimalScale={true}
|
|
36
|
+
valueIsNumericString
|
|
37
|
+
prefix={disableCurrency ? '' : 'R$ '}
|
|
38
|
+
customInput={TextField}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useGridApiContext } from '@mui/x-data-grid';
|
|
3
|
+
import { PatternFormat } from 'react-number-format';
|
|
4
|
+
import TextField from '@mui/material/TextField';
|
|
5
|
+
|
|
6
|
+
type GridPatternInputProps = {
|
|
7
|
+
field: string,
|
|
8
|
+
id: number | string,
|
|
9
|
+
value?: any,
|
|
10
|
+
patternFormat?: string
|
|
11
|
+
};
|
|
12
|
+
export const GridPatternInput = ({
|
|
13
|
+
id,
|
|
14
|
+
value,
|
|
15
|
+
field,
|
|
16
|
+
patternFormat = 'cpf'
|
|
17
|
+
}:GridPatternInputProps) => {
|
|
18
|
+
const apiRef = useGridApiContext();
|
|
19
|
+
const handleChange = async (newValue:any) => {
|
|
20
|
+
await apiRef.current.setEditCellValue({ id, field, value: newValue });
|
|
21
|
+
apiRef.current.stopCellEditMode({ id, field });
|
|
22
|
+
};
|
|
23
|
+
return (
|
|
24
|
+
<PatternFormat
|
|
25
|
+
key={field}
|
|
26
|
+
id={field}
|
|
27
|
+
onValueChange={(values, sourceInfo) => {
|
|
28
|
+
handleChange(values.value);
|
|
29
|
+
}}
|
|
30
|
+
value={value}
|
|
31
|
+
valueIsNumericString
|
|
32
|
+
format={patternFormat}
|
|
33
|
+
mask="_"
|
|
34
|
+
customInput={TextField}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import Box from '@mui/system/Box';
|
|
3
|
+
import TextField from '@mui/material/TextField';
|
|
4
|
+
import { NumericFormat } from 'react-number-format';
|
|
5
|
+
import SyncIcon from '@mui/icons-material/Sync';
|
|
6
|
+
|
|
7
|
+
const SUBMIT_FILTER_STROKE_TIME = 500;
|
|
8
|
+
type InputIntervalProps = {
|
|
9
|
+
applyValue: (p: any) => void,
|
|
10
|
+
focusElementRef?: null | ((p: any) => void) | {
|
|
11
|
+
current: any
|
|
12
|
+
},
|
|
13
|
+
item: {
|
|
14
|
+
columnField: string,
|
|
15
|
+
id: number | string,
|
|
16
|
+
operatorValue: string,
|
|
17
|
+
value: any
|
|
18
|
+
},
|
|
19
|
+
type: string
|
|
20
|
+
};
|
|
21
|
+
const InputInterval = ({
|
|
22
|
+
item,
|
|
23
|
+
applyValue,
|
|
24
|
+
focusElementRef = null,
|
|
25
|
+
type
|
|
26
|
+
}:InputIntervalProps) => {
|
|
27
|
+
const filterTimeout = useRef<any>();
|
|
28
|
+
const [filterValueState, setFilterValueState] = React.useState(item.value ?? '');
|
|
29
|
+
|
|
30
|
+
const [applying, setIsApplying] = React.useState(false);
|
|
31
|
+
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
return () => {
|
|
34
|
+
clearTimeout(filterTimeout.current);
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
const itemValue = item.value ?? [undefined, undefined];
|
|
40
|
+
setFilterValueState(itemValue);
|
|
41
|
+
}, [item.value]);
|
|
42
|
+
|
|
43
|
+
const updateFilterValue = (lowerBound:any, upperBound:any) => {
|
|
44
|
+
clearTimeout(filterTimeout.current);
|
|
45
|
+
setFilterValueState([lowerBound, upperBound]);
|
|
46
|
+
|
|
47
|
+
setIsApplying(true);
|
|
48
|
+
filterTimeout.current = setTimeout(() => {
|
|
49
|
+
setIsApplying(false);
|
|
50
|
+
applyValue({ ...item, value: [lowerBound, upperBound] });
|
|
51
|
+
}, SUBMIT_FILTER_STROKE_TIME);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleUpperFilterChange = (event:React.ChangeEvent<HTMLInputElement> | { target: { value: string }}) => {
|
|
55
|
+
const newUpperBound = event.target?.value;
|
|
56
|
+
updateFilterValue(filterValueState[0], newUpperBound);
|
|
57
|
+
};
|
|
58
|
+
const handleLowerFilterChange = (event:React.ChangeEvent<HTMLInputElement> | { target: { value: string }}) => {
|
|
59
|
+
const newLowerBound = event.target?.value;
|
|
60
|
+
updateFilterValue(newLowerBound, filterValueState[1]);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Box
|
|
65
|
+
sx={{
|
|
66
|
+
display: 'inline-flex',
|
|
67
|
+
flexDirection: 'row',
|
|
68
|
+
alignItems: 'end',
|
|
69
|
+
height: 48,
|
|
70
|
+
pl: '20px'
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
{type === 'number' &&
|
|
74
|
+
<>
|
|
75
|
+
<TextField
|
|
76
|
+
name="lower-bound-input"
|
|
77
|
+
placeholder="De"
|
|
78
|
+
label="De"
|
|
79
|
+
variant="standard"
|
|
80
|
+
value={Number(filterValueState[0])}
|
|
81
|
+
onChange={handleLowerFilterChange}
|
|
82
|
+
type="number"
|
|
83
|
+
inputRef={focusElementRef}
|
|
84
|
+
sx={{ mr: 2, minWidth: 130 }}
|
|
85
|
+
/>
|
|
86
|
+
<TextField
|
|
87
|
+
name="upper-bound-input"
|
|
88
|
+
placeholder="Até"
|
|
89
|
+
label="Até"
|
|
90
|
+
variant="standard"
|
|
91
|
+
value={Number(filterValueState[1])}
|
|
92
|
+
onChange={handleUpperFilterChange}
|
|
93
|
+
type="number"
|
|
94
|
+
sx={{ minWidth: 130 }}
|
|
95
|
+
InputProps={applying ? { endAdornment: <SyncIcon /> } : {}}
|
|
96
|
+
/>
|
|
97
|
+
</>
|
|
98
|
+
}
|
|
99
|
+
{type === 'float' &&
|
|
100
|
+
<>
|
|
101
|
+
<NumericFormat
|
|
102
|
+
name="lower-bound-input"
|
|
103
|
+
placeholder="De"
|
|
104
|
+
label="De"
|
|
105
|
+
variant="standard"
|
|
106
|
+
value={Number(filterValueState[0])}
|
|
107
|
+
onValueChange={(values, sourceInfo) => {
|
|
108
|
+
handleLowerFilterChange({ target: { value: values.value } });
|
|
109
|
+
}}
|
|
110
|
+
thousandSeparator='.'
|
|
111
|
+
decimalSeparator=','
|
|
112
|
+
decimalScale={2}
|
|
113
|
+
fixedDecimalScale={true}
|
|
114
|
+
valueIsNumericString
|
|
115
|
+
inputRef={focusElementRef}
|
|
116
|
+
sx={{ mr: 2, minWidth: 130 }}
|
|
117
|
+
customInput={TextField}
|
|
118
|
+
/>
|
|
119
|
+
<NumericFormat
|
|
120
|
+
name="upper-bound-input"
|
|
121
|
+
placeholder="Até"
|
|
122
|
+
label="Até"
|
|
123
|
+
variant="standard"
|
|
124
|
+
value={Number(filterValueState[1])}
|
|
125
|
+
onValueChange={(values, sourceInfo) => {
|
|
126
|
+
handleUpperFilterChange({ target: { value: values.value } });
|
|
127
|
+
}}
|
|
128
|
+
thousandSeparator='.'
|
|
129
|
+
decimalSeparator=','
|
|
130
|
+
decimalScale={2}
|
|
131
|
+
fixedDecimalScale={true}
|
|
132
|
+
valueIsNumericString
|
|
133
|
+
InputProps={applying ? { endAdornment: <SyncIcon /> } : {}}
|
|
134
|
+
sx={{ minWidth: 130 }}
|
|
135
|
+
customInput={TextField}
|
|
136
|
+
/>
|
|
137
|
+
</>
|
|
138
|
+
}
|
|
139
|
+
{type === 'date' &&
|
|
140
|
+
<>
|
|
141
|
+
<TextField
|
|
142
|
+
name="lower-bound-input"
|
|
143
|
+
label="De"
|
|
144
|
+
variant="standard"
|
|
145
|
+
value={filterValueState[0] || ''}
|
|
146
|
+
onChange={handleLowerFilterChange}
|
|
147
|
+
type="date"
|
|
148
|
+
inputRef={focusElementRef}
|
|
149
|
+
InputLabelProps={{ shrink: true }}
|
|
150
|
+
sx={{ mr: 2, minWidth: 130 }}
|
|
151
|
+
/>
|
|
152
|
+
<TextField
|
|
153
|
+
name="upper-bound-input"
|
|
154
|
+
label="Até"
|
|
155
|
+
variant="standard"
|
|
156
|
+
value={filterValueState[1] || ''}
|
|
157
|
+
onChange={handleUpperFilterChange}
|
|
158
|
+
type="date"
|
|
159
|
+
InputProps={applying ? { endAdornment: <SyncIcon /> } : {}}
|
|
160
|
+
InputLabelProps={{ shrink: true }}
|
|
161
|
+
sx={{ minWidth: 130 }}
|
|
162
|
+
/>
|
|
163
|
+
</>
|
|
164
|
+
}
|
|
165
|
+
</Box>
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const InputNumberInterval = (props: any) => {
|
|
170
|
+
return (
|
|
171
|
+
<InputInterval
|
|
172
|
+
{...props}
|
|
173
|
+
type="number"
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const InputDateInterval = (props: any) => {
|
|
179
|
+
return (
|
|
180
|
+
<InputInterval
|
|
181
|
+
{...props}
|
|
182
|
+
type="date"
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const InputFloatInterval = (props: any) => {
|
|
188
|
+
return (
|
|
189
|
+
<InputInterval
|
|
190
|
+
{...props}
|
|
191
|
+
type="float"
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useGridApiContext } from '@mui/x-data-grid';
|
|
3
|
+
import { SxProps } from '@mui/material';
|
|
4
|
+
import TextField from '@mui/material/TextField';
|
|
5
|
+
import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete';
|
|
6
|
+
|
|
7
|
+
import { getTmpId } from '../../utils';
|
|
8
|
+
import { GridEnrichedBySchemaColDef, Item } from '../../utils';
|
|
9
|
+
|
|
10
|
+
const filter = createFilterOptions();
|
|
11
|
+
|
|
12
|
+
type SelectEditInputCellProps = {
|
|
13
|
+
field: string,
|
|
14
|
+
id: number | string,
|
|
15
|
+
value?: any,
|
|
16
|
+
column: GridEnrichedBySchemaColDef,
|
|
17
|
+
type: string,
|
|
18
|
+
optionsAC: { current: Record<string, Item[]> | null },
|
|
19
|
+
isIndexField: boolean,
|
|
20
|
+
multiple?: boolean,
|
|
21
|
+
sx?: SxProps
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
*
|
|
27
|
+
* @param {SelectEditInputCellProps} {
|
|
28
|
+
* field,
|
|
29
|
+
* id,
|
|
30
|
+
* value,
|
|
31
|
+
* column,
|
|
32
|
+
* type,
|
|
33
|
+
* optionsAC,
|
|
34
|
+
* isIndexField,
|
|
35
|
+
* multiple = false,
|
|
36
|
+
* sx = {}
|
|
37
|
+
* }
|
|
38
|
+
* @returns {*} {JSX.Element}
|
|
39
|
+
*/
|
|
40
|
+
export function SelectEditInputCell ({
|
|
41
|
+
field,
|
|
42
|
+
id,
|
|
43
|
+
value,
|
|
44
|
+
column,
|
|
45
|
+
type,
|
|
46
|
+
optionsAC,
|
|
47
|
+
isIndexField,
|
|
48
|
+
multiple = false,
|
|
49
|
+
sx = {}
|
|
50
|
+
}: SelectEditInputCellProps): JSX.Element {
|
|
51
|
+
// TODO: allow edit option label, as in formautocomplete!
|
|
52
|
+
const apiRef = useGridApiContext();
|
|
53
|
+
|
|
54
|
+
const handleChange = async (newValue:any) => {
|
|
55
|
+
await apiRef.current.setEditCellValue({ id, field, value: newValue });
|
|
56
|
+
apiRef.current.stopCellEditMode({ id, field });
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const labelKey = (['field', 'nested object'].includes(type) || isIndexField)
|
|
60
|
+
? 'label'
|
|
61
|
+
: 'display_name';
|
|
62
|
+
const valueKey = (['field', 'nested object'].includes(type) || isIndexField)
|
|
63
|
+
? 'id'
|
|
64
|
+
: 'value';
|
|
65
|
+
|
|
66
|
+
let creatableProps = {};
|
|
67
|
+
if (column.creatable || isIndexField) {
|
|
68
|
+
creatableProps = {
|
|
69
|
+
freesolo: 'true',
|
|
70
|
+
filterOptions: (options:Record<string, any>[], params:any) => {
|
|
71
|
+
const filtered = filter(options, params);
|
|
72
|
+
const inputValue = (params.inputValue)
|
|
73
|
+
? params.inputValue
|
|
74
|
+
: '';
|
|
75
|
+
const inputValueLower = inputValue.trim().toLowerCase();
|
|
76
|
+
// Suggest the creation of a new value
|
|
77
|
+
const isExisting = options.some(option => inputValueLower === option[labelKey].trim().toLowerCase());
|
|
78
|
+
if (inputValue !== '' && !isExisting) {
|
|
79
|
+
filtered.push({
|
|
80
|
+
inputValue,
|
|
81
|
+
[labelKey]: `Criar "${inputValue}"`
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return filtered;
|
|
85
|
+
},
|
|
86
|
+
handleHomeEndKeys: true,
|
|
87
|
+
getOptionLabel: (option:string | Record<string, any>) => {
|
|
88
|
+
// Value selected with enter, right from the input
|
|
89
|
+
if (typeof option === 'string') {
|
|
90
|
+
return option;
|
|
91
|
+
}
|
|
92
|
+
// Criar "xxx" option created dynamically
|
|
93
|
+
if (option.inputValue) {
|
|
94
|
+
return option.inputValue;
|
|
95
|
+
}
|
|
96
|
+
// Regular option
|
|
97
|
+
return option[labelKey];
|
|
98
|
+
},
|
|
99
|
+
renderOption: (props:any, option:Record<string, any>) => {
|
|
100
|
+
return (<li key={option[valueKey]} {...props}>{option[labelKey]}</li>);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Autocomplete
|
|
107
|
+
key={field}
|
|
108
|
+
id={field}
|
|
109
|
+
value={value}
|
|
110
|
+
options={optionsAC.current && optionsAC.current[field] ? optionsAC.current[field] : []}
|
|
111
|
+
selectOnFocus
|
|
112
|
+
autoHighlight
|
|
113
|
+
multiple={multiple}
|
|
114
|
+
isOptionEqualToValue={(option, value) => {
|
|
115
|
+
return (option[labelKey] === value[labelKey]);
|
|
116
|
+
}}
|
|
117
|
+
getOptionLabel={(option) => {
|
|
118
|
+
return option[labelKey];
|
|
119
|
+
}}
|
|
120
|
+
onChange={(e, value) => {
|
|
121
|
+
if (!column.creatable && !isIndexField) {
|
|
122
|
+
handleChange(value);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
let newValue = value;
|
|
126
|
+
if (typeof newValue === 'string') {
|
|
127
|
+
const tmpId = getTmpId();
|
|
128
|
+
newValue = {
|
|
129
|
+
[valueKey]: tmpId,
|
|
130
|
+
[labelKey]: newValue
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (newValue && newValue.inputValue) {
|
|
134
|
+
const tmpId = getTmpId();
|
|
135
|
+
newValue = {
|
|
136
|
+
[valueKey]: tmpId,
|
|
137
|
+
[labelKey]: newValue.inputValue
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
handleChange(newValue);
|
|
141
|
+
}}
|
|
142
|
+
fullWidth
|
|
143
|
+
renderInput={params => (
|
|
144
|
+
<TextField
|
|
145
|
+
{...params}
|
|
146
|
+
sx={sx}
|
|
147
|
+
/>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{...creatableProps}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
}
|