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.
@@ -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
+ }