drf-react-by-schema 0.1.0 → 0.2.1
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/.gitlab-ci.yml +14 -0
- package/README.md +9 -82
- package/dist/index.js +57 -1
- package/package.json +39 -10
- 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/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
|
@@ -1,48 +1,21 @@
|
|
|
1
|
-
import React, { useEffect, useState,
|
|
2
|
-
import moment from 'moment';
|
|
1
|
+
import React, { useEffect, useState, useRef, forwardRef } from 'react';
|
|
2
|
+
// import moment from 'moment';
|
|
3
3
|
// import Mask from 'string-mask';
|
|
4
4
|
import {
|
|
5
|
-
GridApi,
|
|
6
5
|
DataGrid,
|
|
7
6
|
GridEditInputCell,
|
|
8
7
|
GridRowModes,
|
|
9
|
-
GridToolbarContainer,
|
|
10
|
-
GridToolbarColumnsButton,
|
|
11
|
-
GridToolbarFilterButton,
|
|
12
|
-
GridToolbarDensitySelector,
|
|
13
|
-
GridToolbarExport,
|
|
14
|
-
GridToolbarQuickFilter,
|
|
15
|
-
GridFooter,
|
|
16
|
-
GridFooterContainer,
|
|
17
8
|
GridActionsCellItem,
|
|
18
9
|
gridVisibleSortedRowIdsSelector,
|
|
19
|
-
|
|
20
|
-
gridStringOrNumberComparator,
|
|
21
|
-
getGridNumericOperators,
|
|
22
|
-
getGridDateOperators,
|
|
23
|
-
GridFilterOperator
|
|
10
|
+
gridStringOrNumberComparator
|
|
24
11
|
} from '@mui/x-data-grid';
|
|
25
12
|
import * as Yup from 'yup';
|
|
26
|
-
import { Link } from 'react-router-dom';
|
|
27
13
|
import Box from '@mui/material/Box';
|
|
28
14
|
import CircularProgress from '@mui/material/CircularProgress';
|
|
29
|
-
import TextField from '@mui/material/TextField';
|
|
30
|
-
import Button from '@mui/material/Button';
|
|
31
|
-
import Menu from '@mui/material/Menu';
|
|
32
|
-
import MenuItem from '@mui/material/MenuItem';
|
|
33
|
-
import SyncIcon from '@mui/icons-material/Sync';
|
|
34
|
-
import ExpandIcon from '@mui/icons-material/Expand';
|
|
35
|
-
import AddIcon from '@mui/icons-material/Add';
|
|
36
15
|
import EditIcon from '@mui/icons-material/Edit';
|
|
37
16
|
import ClearIcon from '@mui/icons-material/Clear';
|
|
38
17
|
import CheckIcon from '@mui/icons-material/Check';
|
|
39
18
|
import UndoIcon from '@mui/icons-material/Undo';
|
|
40
|
-
import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete';
|
|
41
|
-
import { NumericFormat, PatternFormat } from 'react-number-format';
|
|
42
|
-
import Dialog from '@mui/material/Dialog';
|
|
43
|
-
import DialogActions from '@mui/material/DialogActions';
|
|
44
|
-
import DialogContent from '@mui/material/DialogContent';
|
|
45
|
-
import DialogTitle from '@mui/material/DialogTitle';
|
|
46
19
|
import Snackbar from '@mui/material/Snackbar';
|
|
47
20
|
import Alert from '@mui/material/Alert';
|
|
48
21
|
|
|
@@ -50,700 +23,59 @@ import { Layout } from '../styles';
|
|
|
50
23
|
import { getAutoComplete } from '../api';
|
|
51
24
|
import {
|
|
52
25
|
Item,
|
|
53
|
-
|
|
26
|
+
SchemaType,
|
|
54
27
|
Choice,
|
|
55
28
|
Id,
|
|
56
29
|
GridEnrichedBySchemaColDef,
|
|
57
|
-
reducer,
|
|
58
|
-
getTmpId,
|
|
59
30
|
isTmpId,
|
|
60
31
|
emptyByType,
|
|
61
32
|
buildGenericYupValidationSchema
|
|
62
33
|
} from '../utils';
|
|
63
34
|
import { SxProps } from '@mui/material';
|
|
64
35
|
import { ObjectShape } from 'yup/lib/object';
|
|
36
|
+
import { DRFReactBySchemaContext, DRFReactBySchemaContextType } from '../context/DRFReactBySchemaProvider';
|
|
37
|
+
import { quantityOnlyOperators } from './DataGridBySchemaEditable/utils';
|
|
38
|
+
import { CustomToolbar } from './DataGridBySchemaEditable/CustomToolbar';
|
|
39
|
+
import { SelectEditInputCell } from './DataGridBySchemaEditable/SelectEditInputCell';
|
|
40
|
+
import { GridDecimalInput } from './DataGridBySchemaEditable/GridDecimalInput';
|
|
41
|
+
import { GridPatternInput } from './DataGridBySchemaEditable/GridPatternInput';
|
|
42
|
+
import { FooterToolbar } from './DataGridBySchemaEditable/FooterToolbar';
|
|
43
|
+
import { ConfirmDialog } from './DataGridBySchemaEditable/ConfirmDialog';
|
|
65
44
|
|
|
66
|
-
const filter = createFilterOptions();
|
|
67
45
|
const stringMask = require('string-mask');
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
type ResizeType = 'condense' | 'maxContent' | 'fitScreen';
|
|
101
|
-
|
|
102
|
-
function resizeColumns (
|
|
103
|
-
columns: GridEnrichedBySchemaColDef[],
|
|
104
|
-
resizeType: ResizeType,
|
|
105
|
-
apiRef: React.MutableRefObject<GridApi>
|
|
106
|
-
) {
|
|
107
|
-
const cols = [...columns];
|
|
108
|
-
cols.forEach((col, index: number) => {
|
|
109
|
-
if (resizeType === 'fitScreen') {
|
|
110
|
-
delete col.width;
|
|
111
|
-
col.minWidth = 80;
|
|
112
|
-
if (col.isIndexField) {
|
|
113
|
-
col.flex = 1;
|
|
114
|
-
}
|
|
115
|
-
} else if (resizeType === 'maxContent') {
|
|
116
|
-
const maxColWidth = maxOfCol(index);
|
|
117
|
-
delete col.flex;
|
|
118
|
-
delete col.minWidth;
|
|
119
|
-
col.width = maxColWidth + 22;
|
|
120
|
-
} else {
|
|
121
|
-
col.width = 0;
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
return cols;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
type CustomToolbarProps = {
|
|
128
|
-
preparedColumns: GridEnrichedBySchemaColDef[],
|
|
129
|
-
setPreparedColumns: (p:null | GridEnrichedBySchemaColDef[]) => void
|
|
130
|
-
};
|
|
131
|
-
const CustomToolbar = ({
|
|
132
|
-
preparedColumns,
|
|
133
|
-
setPreparedColumns
|
|
134
|
-
}:CustomToolbarProps) => {
|
|
135
|
-
const apiRef = useGridApiContext();
|
|
136
|
-
const [resizeMenuAnchorEl, setResizeMenuAnchorEl] = useState<any>(null);
|
|
137
|
-
const isResizeMenuOpen = Boolean(resizeMenuAnchorEl);
|
|
138
|
-
const openResizeMenu = (event:React.MouseEvent) => {
|
|
139
|
-
setResizeMenuAnchorEl(event.currentTarget);
|
|
140
|
-
};
|
|
141
|
-
const closeResizeMenu = () => {
|
|
142
|
-
setResizeMenuAnchorEl(null);
|
|
143
|
-
};
|
|
144
|
-
return (
|
|
145
|
-
<GridToolbarContainer sx={{ justifyContent: 'space-between' }}>
|
|
146
|
-
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
|
|
147
|
-
<GridToolbarColumnsButton sx={{ ml: '10px', fontSize: '13px' }} />
|
|
148
|
-
<GridToolbarFilterButton sx={{ ml: '10px', fontSize: '13px' }} />
|
|
149
|
-
<GridToolbarDensitySelector sx={{ ml: '10px', fontSize: '13px' }} />
|
|
150
|
-
<Button onClick={openResizeMenu} sx={{ ml: '0px', fontSize: '13px' }}>
|
|
151
|
-
<ExpandIcon sx={{ transform: 'rotate(90deg)', mr: '6px' }} />
|
|
152
|
-
Ajustar
|
|
153
|
-
</Button>
|
|
154
|
-
<Menu
|
|
155
|
-
anchorEl={resizeMenuAnchorEl}
|
|
156
|
-
open={isResizeMenuOpen}
|
|
157
|
-
onClose={closeResizeMenu}
|
|
158
|
-
>
|
|
159
|
-
<MenuItem
|
|
160
|
-
onClick={() => {
|
|
161
|
-
closeResizeMenu();
|
|
162
|
-
setPreparedColumns(resizeColumns(
|
|
163
|
-
preparedColumns,
|
|
164
|
-
'fitScreen',
|
|
165
|
-
apiRef
|
|
166
|
-
));
|
|
167
|
-
}}
|
|
168
|
-
>
|
|
169
|
-
Ajustar à tela
|
|
170
|
-
</MenuItem>
|
|
171
|
-
<MenuItem
|
|
172
|
-
onClick={() => {
|
|
173
|
-
closeResizeMenu();
|
|
174
|
-
setPreparedColumns(resizeColumns(
|
|
175
|
-
preparedColumns,
|
|
176
|
-
'maxContent',
|
|
177
|
-
apiRef
|
|
178
|
-
));
|
|
179
|
-
}}
|
|
180
|
-
>
|
|
181
|
-
Ajustar ao conteúdo
|
|
182
|
-
</MenuItem>
|
|
183
|
-
</Menu>
|
|
184
|
-
<GridToolbarExport sx={{ ml: '0px', fontSize: '13px' }} />
|
|
185
|
-
</div>
|
|
186
|
-
<div>
|
|
187
|
-
<GridToolbarQuickFilter />
|
|
188
|
-
</div>
|
|
189
|
-
</GridToolbarContainer>
|
|
190
|
-
);
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
type SelectEditInputCellProps = {
|
|
194
|
-
field: string,
|
|
195
|
-
id: number | string,
|
|
196
|
-
value?: any,
|
|
197
|
-
column: GridEnrichedBySchemaColDef,
|
|
198
|
-
type: string,
|
|
199
|
-
optionsAC: { current: Record<string, Item[]> | null },
|
|
200
|
-
isIndexField: boolean,
|
|
201
|
-
multiple?: boolean,
|
|
202
|
-
sx?: SxProps
|
|
203
|
-
};
|
|
204
|
-
function SelectEditInputCell ({
|
|
205
|
-
id,
|
|
206
|
-
value,
|
|
207
|
-
field,
|
|
208
|
-
column,
|
|
209
|
-
type,
|
|
210
|
-
optionsAC,
|
|
211
|
-
isIndexField,
|
|
212
|
-
multiple = false,
|
|
213
|
-
sx = {}
|
|
214
|
-
}: SelectEditInputCellProps) {
|
|
215
|
-
// TODO: allow edit option label, as in formautocomplete!
|
|
216
|
-
const apiRef = useGridApiContext();
|
|
217
|
-
|
|
218
|
-
const handleChange = async (newValue:any) => {
|
|
219
|
-
await apiRef.current.setEditCellValue({ id, field, value: newValue });
|
|
220
|
-
apiRef.current.stopCellEditMode({ id, field });
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const labelKey = (['field', 'nested object'].includes(type) || isIndexField)
|
|
224
|
-
? 'label'
|
|
225
|
-
: 'display_name';
|
|
226
|
-
const valueKey = (['field', 'nested object'].includes(type) || isIndexField)
|
|
227
|
-
? 'id'
|
|
228
|
-
: 'value';
|
|
229
|
-
|
|
230
|
-
let creatableProps = {};
|
|
231
|
-
if (column.creatable || isIndexField) {
|
|
232
|
-
creatableProps = {
|
|
233
|
-
freesolo: 'true',
|
|
234
|
-
filterOptions: (options:Record<string, any>[], params:any) => {
|
|
235
|
-
const filtered = filter(options, params);
|
|
236
|
-
const inputValue = (params.inputValue)
|
|
237
|
-
? params.inputValue
|
|
238
|
-
: '';
|
|
239
|
-
const inputValueLower = inputValue.trim().toLowerCase();
|
|
240
|
-
// Suggest the creation of a new value
|
|
241
|
-
const isExisting = options.some(option => inputValueLower === option[labelKey].trim().toLowerCase());
|
|
242
|
-
if (inputValue !== '' && !isExisting) {
|
|
243
|
-
filtered.push({
|
|
244
|
-
inputValue,
|
|
245
|
-
[labelKey]: `Criar "${inputValue}"`
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
return filtered;
|
|
249
|
-
},
|
|
250
|
-
handleHomeEndKeys: true,
|
|
251
|
-
getOptionLabel: (option:string | Record<string, any>) => {
|
|
252
|
-
// Value selected with enter, right from the input
|
|
253
|
-
if (typeof option === 'string') {
|
|
254
|
-
return option;
|
|
255
|
-
}
|
|
256
|
-
// Criar "xxx" option created dynamically
|
|
257
|
-
if (option.inputValue) {
|
|
258
|
-
return option.inputValue;
|
|
259
|
-
}
|
|
260
|
-
// Regular option
|
|
261
|
-
return option[labelKey];
|
|
262
|
-
},
|
|
263
|
-
renderOption: (props:any, option:Record<string, any>) => {
|
|
264
|
-
return (<li key={option[valueKey]} {...props}>{option[labelKey]}</li>);
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
return (
|
|
270
|
-
<Autocomplete
|
|
271
|
-
key={field}
|
|
272
|
-
id={field}
|
|
273
|
-
value={value}
|
|
274
|
-
options={optionsAC.current && optionsAC.current[field] ? optionsAC.current[field] : []}
|
|
275
|
-
selectOnFocus
|
|
276
|
-
autoHighlight
|
|
277
|
-
multiple={multiple}
|
|
278
|
-
isOptionEqualToValue={(option, value) => {
|
|
279
|
-
return (option[labelKey] === value[labelKey]);
|
|
280
|
-
}}
|
|
281
|
-
getOptionLabel={(option) => {
|
|
282
|
-
return option[labelKey];
|
|
283
|
-
}}
|
|
284
|
-
onChange={(e, value) => {
|
|
285
|
-
if (!column.creatable && !isIndexField) {
|
|
286
|
-
handleChange(value);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
let newValue = value;
|
|
290
|
-
if (typeof newValue === 'string') {
|
|
291
|
-
const tmpId = getTmpId();
|
|
292
|
-
newValue = {
|
|
293
|
-
[valueKey]: tmpId,
|
|
294
|
-
[labelKey]: newValue
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
if (newValue && newValue.inputValue) {
|
|
298
|
-
const tmpId = getTmpId();
|
|
299
|
-
newValue = {
|
|
300
|
-
[valueKey]: tmpId,
|
|
301
|
-
[labelKey]: newValue.inputValue
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
handleChange(newValue);
|
|
305
|
-
}}
|
|
306
|
-
fullWidth
|
|
307
|
-
renderInput={params => (
|
|
308
|
-
<TextField
|
|
309
|
-
{...params}
|
|
310
|
-
sx={sx}
|
|
311
|
-
/>
|
|
312
|
-
)}
|
|
313
|
-
|
|
314
|
-
{...creatableProps}
|
|
315
|
-
/>
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
type GridDecimalInputProps = {
|
|
320
|
-
field: string,
|
|
321
|
-
id: number | string,
|
|
322
|
-
value?: any,
|
|
323
|
-
column: object
|
|
324
|
-
};
|
|
325
|
-
function GridDecimalInput ({
|
|
326
|
-
id,
|
|
327
|
-
value,
|
|
328
|
-
field
|
|
329
|
-
}:GridDecimalInputProps) {
|
|
330
|
-
const apiRef = useGridApiContext();
|
|
331
|
-
const decimalScale = 2;
|
|
332
|
-
const disableCurrency = true;
|
|
333
|
-
const handleChange = async (newValue:any) => {
|
|
334
|
-
await apiRef.current.setEditCellValue({ id, field, value: newValue });
|
|
335
|
-
apiRef.current.stopCellEditMode({ id, field });
|
|
336
|
-
};
|
|
337
|
-
return (
|
|
338
|
-
<NumericFormat
|
|
339
|
-
key={field}
|
|
340
|
-
id={field}
|
|
341
|
-
onValueChange={(values, sourceInfo) => {
|
|
342
|
-
handleChange(values.value);
|
|
343
|
-
}}
|
|
344
|
-
value={value}
|
|
345
|
-
thousandSeparator='.'
|
|
346
|
-
decimalSeparator=','
|
|
347
|
-
decimalScale={decimalScale}
|
|
348
|
-
fixedDecimalScale={true}
|
|
349
|
-
valueIsNumericString
|
|
350
|
-
prefix={disableCurrency ? '' : 'R$ '}
|
|
351
|
-
customInput={TextField}
|
|
352
|
-
/>
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
type GridPatternInputProps = {
|
|
357
|
-
field: string,
|
|
358
|
-
id: number | string,
|
|
359
|
-
value?: any,
|
|
360
|
-
patternFormat?: string
|
|
46
|
+
const moment = require('moment');
|
|
47
|
+
|
|
48
|
+
interface DataGridBySchemaEditableProps {
|
|
49
|
+
schema: SchemaType;
|
|
50
|
+
data: Item[];
|
|
51
|
+
columns: GridEnrichedBySchemaColDef[];
|
|
52
|
+
model: string;
|
|
53
|
+
fieldKey?: string;
|
|
54
|
+
labelKey?: string;
|
|
55
|
+
index?: number;
|
|
56
|
+
name?: string;
|
|
57
|
+
indexField?: string;
|
|
58
|
+
addExistingModel?: string;
|
|
59
|
+
indexFieldMinWidth?: number;
|
|
60
|
+
indexFieldBasePath?: string;
|
|
61
|
+
stateToLink?: object;
|
|
62
|
+
minWidth?: number;
|
|
63
|
+
onEditRelatedModelSave?: (p: any) => Id | { data: Item } | { errors: Item };
|
|
64
|
+
onDeleteRelatedModel?: (p: any) => any;
|
|
65
|
+
customColumnOperations?: (p: any) => GridEnrichedBySchemaColDef;
|
|
66
|
+
customLinkDestination?: (p: any) => string;
|
|
67
|
+
LinkComponent?: any;
|
|
68
|
+
onProcessRow?: (p: any) => void;
|
|
69
|
+
onDataChange?: (p: any) => void;
|
|
70
|
+
isEditable?: boolean;
|
|
71
|
+
sx?: SxProps;
|
|
72
|
+
isAutoHeight?: boolean;
|
|
73
|
+
defaultValues?: Item;
|
|
74
|
+
hideFooterPagination?: boolean;
|
|
75
|
+
setVisibleRows?: (p: any) => void;
|
|
361
76
|
};
|
|
362
|
-
function GridPatternInput ({
|
|
363
|
-
id,
|
|
364
|
-
value,
|
|
365
|
-
field,
|
|
366
|
-
patternFormat = 'cpf'
|
|
367
|
-
}:GridPatternInputProps) {
|
|
368
|
-
const apiRef = useGridApiContext();
|
|
369
|
-
const handleChange = async (newValue:any) => {
|
|
370
|
-
await apiRef.current.setEditCellValue({ id, field, value: newValue });
|
|
371
|
-
apiRef.current.stopCellEditMode({ id, field });
|
|
372
|
-
};
|
|
373
|
-
return (
|
|
374
|
-
<PatternFormat
|
|
375
|
-
key={field}
|
|
376
|
-
id={field}
|
|
377
|
-
onValueChange={(values, sourceInfo) => {
|
|
378
|
-
handleChange(values.value);
|
|
379
|
-
}}
|
|
380
|
-
value={value}
|
|
381
|
-
valueIsNumericString
|
|
382
|
-
format={patternFormat}
|
|
383
|
-
mask="_"
|
|
384
|
-
customInput={TextField}
|
|
385
|
-
/>
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
77
|
|
|
389
|
-
|
|
390
|
-
name: string,
|
|
391
|
-
setRowModesModel: (p:any) => any,
|
|
392
|
-
dataGrid: { data:Record<string, any>[] },
|
|
393
|
-
setDataGrid: (p:any) => any,
|
|
394
|
-
emptyItem: { current:Record<string, any> },
|
|
395
|
-
indexField: string,
|
|
396
|
-
isEditable: boolean
|
|
397
|
-
};
|
|
398
|
-
function FooterToolbar ({
|
|
399
|
-
name,
|
|
400
|
-
setRowModesModel,
|
|
401
|
-
dataGrid,
|
|
402
|
-
setDataGrid,
|
|
403
|
-
emptyItem,
|
|
404
|
-
indexField,
|
|
405
|
-
isEditable
|
|
406
|
-
}:FooterToolbarProps) {
|
|
407
|
-
const handleClick = () => {
|
|
408
|
-
const id = getTmpId();
|
|
409
|
-
emptyItem.current.id = id;
|
|
410
|
-
const newData = [
|
|
411
|
-
{ ...emptyItem.current },
|
|
412
|
-
...dataGrid.data
|
|
413
|
-
];
|
|
414
|
-
setDataGrid({
|
|
415
|
-
data: newData
|
|
416
|
-
});
|
|
417
|
-
setRowModesModel((oldModel:any) => ({
|
|
418
|
-
...oldModel,
|
|
419
|
-
[id]: { mode: GridRowModes.Edit, fieldToFocus: indexField }
|
|
420
|
-
}));
|
|
421
|
-
// Ugly hack to scroll to top, since scroll to cell is only available in Pro
|
|
422
|
-
const el = document.querySelector(`.dataGrid_${name} .MuiDataGrid-virtualScroller`);
|
|
423
|
-
// console.log(el, name);
|
|
424
|
-
if (el) {
|
|
425
|
-
el.scrollTop = 0;
|
|
426
|
-
setTimeout(() => {
|
|
427
|
-
el.scrollTop = 0;
|
|
428
|
-
}, 10);
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
return (
|
|
433
|
-
<GridFooterContainer>
|
|
434
|
-
{isEditable &&
|
|
435
|
-
<Button
|
|
436
|
-
color="primary"
|
|
437
|
-
startIcon={<AddIcon />}
|
|
438
|
-
onClick={handleClick}
|
|
439
|
-
sx={{ ml: 2 }}
|
|
440
|
-
>
|
|
441
|
-
Adicionar
|
|
442
|
-
</Button>
|
|
443
|
-
}
|
|
444
|
-
<GridFooter
|
|
445
|
-
sx={
|
|
446
|
-
(isEditable)
|
|
447
|
-
? { border: 'none' }
|
|
448
|
-
: { width: '100%' }
|
|
449
|
-
}
|
|
450
|
-
/>
|
|
451
|
-
</GridFooterContainer>
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
// FILTROS "ENTRE":
|
|
457
|
-
|
|
458
|
-
const SUBMIT_FILTER_STROKE_TIME = 500;
|
|
459
|
-
type InputIntervalProps = {
|
|
460
|
-
applyValue: (p: any) => void,
|
|
461
|
-
focusElementRef?: null | ((p: any) => void) | {
|
|
462
|
-
current: any
|
|
463
|
-
},
|
|
464
|
-
item: {
|
|
465
|
-
columnField: string,
|
|
466
|
-
id: number | string,
|
|
467
|
-
operatorValue: string,
|
|
468
|
-
value: any
|
|
469
|
-
},
|
|
470
|
-
type: string
|
|
471
|
-
};
|
|
472
|
-
function InputInterval ({
|
|
473
|
-
item,
|
|
474
|
-
applyValue,
|
|
475
|
-
focusElementRef = null,
|
|
476
|
-
type
|
|
477
|
-
}:InputIntervalProps) {
|
|
478
|
-
const filterTimeout = useRef<any>();
|
|
479
|
-
const [filterValueState, setFilterValueState] = React.useState(item.value ?? '');
|
|
480
|
-
|
|
481
|
-
const [applying, setIsApplying] = React.useState(false);
|
|
482
|
-
|
|
483
|
-
React.useEffect(() => {
|
|
484
|
-
return () => {
|
|
485
|
-
clearTimeout(filterTimeout.current);
|
|
486
|
-
};
|
|
487
|
-
}, []);
|
|
488
|
-
|
|
489
|
-
React.useEffect(() => {
|
|
490
|
-
const itemValue = item.value ?? [undefined, undefined];
|
|
491
|
-
setFilterValueState(itemValue);
|
|
492
|
-
}, [item.value]);
|
|
493
|
-
|
|
494
|
-
const updateFilterValue = (lowerBound:any, upperBound:any) => {
|
|
495
|
-
clearTimeout(filterTimeout.current);
|
|
496
|
-
setFilterValueState([lowerBound, upperBound]);
|
|
497
|
-
|
|
498
|
-
setIsApplying(true);
|
|
499
|
-
filterTimeout.current = setTimeout(() => {
|
|
500
|
-
setIsApplying(false);
|
|
501
|
-
applyValue({ ...item, value: [lowerBound, upperBound] });
|
|
502
|
-
}, SUBMIT_FILTER_STROKE_TIME);
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
const handleUpperFilterChange = (event:React.ChangeEvent<HTMLInputElement> | { target: { value: string }}) => {
|
|
506
|
-
const newUpperBound = event.target?.value;
|
|
507
|
-
updateFilterValue(filterValueState[0], newUpperBound);
|
|
508
|
-
};
|
|
509
|
-
const handleLowerFilterChange = (event:React.ChangeEvent<HTMLInputElement> | { target: { value: string }}) => {
|
|
510
|
-
const newLowerBound = event.target?.value;
|
|
511
|
-
updateFilterValue(newLowerBound, filterValueState[1]);
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
return (
|
|
515
|
-
<Box
|
|
516
|
-
sx={{
|
|
517
|
-
display: 'inline-flex',
|
|
518
|
-
flexDirection: 'row',
|
|
519
|
-
alignItems: 'end',
|
|
520
|
-
height: 48,
|
|
521
|
-
pl: '20px'
|
|
522
|
-
}}
|
|
523
|
-
>
|
|
524
|
-
{type === 'number' &&
|
|
525
|
-
<>
|
|
526
|
-
<TextField
|
|
527
|
-
name="lower-bound-input"
|
|
528
|
-
placeholder="De"
|
|
529
|
-
label="De"
|
|
530
|
-
variant="standard"
|
|
531
|
-
value={Number(filterValueState[0])}
|
|
532
|
-
onChange={handleLowerFilterChange}
|
|
533
|
-
type="number"
|
|
534
|
-
inputRef={focusElementRef}
|
|
535
|
-
sx={{ mr: 2, minWidth: 130 }}
|
|
536
|
-
/>
|
|
537
|
-
<TextField
|
|
538
|
-
name="upper-bound-input"
|
|
539
|
-
placeholder="Até"
|
|
540
|
-
label="Até"
|
|
541
|
-
variant="standard"
|
|
542
|
-
value={Number(filterValueState[1])}
|
|
543
|
-
onChange={handleUpperFilterChange}
|
|
544
|
-
type="number"
|
|
545
|
-
sx={{ minWidth: 130 }}
|
|
546
|
-
InputProps={applying ? { endAdornment: <SyncIcon /> } : {}}
|
|
547
|
-
/>
|
|
548
|
-
</>
|
|
549
|
-
}
|
|
550
|
-
{type === 'float' &&
|
|
551
|
-
<>
|
|
552
|
-
<NumericFormat
|
|
553
|
-
name="lower-bound-input"
|
|
554
|
-
placeholder="De"
|
|
555
|
-
label="De"
|
|
556
|
-
variant="standard"
|
|
557
|
-
value={Number(filterValueState[0])}
|
|
558
|
-
onValueChange={(values, sourceInfo) => {
|
|
559
|
-
handleLowerFilterChange({ target: { value: values.value } });
|
|
560
|
-
}}
|
|
561
|
-
thousandSeparator='.'
|
|
562
|
-
decimalSeparator=','
|
|
563
|
-
decimalScale={2}
|
|
564
|
-
fixedDecimalScale={true}
|
|
565
|
-
valueIsNumericString
|
|
566
|
-
inputRef={focusElementRef}
|
|
567
|
-
sx={{ mr: 2, minWidth: 130 }}
|
|
568
|
-
customInput={TextField}
|
|
569
|
-
/>
|
|
570
|
-
<NumericFormat
|
|
571
|
-
name="upper-bound-input"
|
|
572
|
-
placeholder="Até"
|
|
573
|
-
label="Até"
|
|
574
|
-
variant="standard"
|
|
575
|
-
value={Number(filterValueState[1])}
|
|
576
|
-
onValueChange={(values, sourceInfo) => {
|
|
577
|
-
handleUpperFilterChange({ target: { value: values.value } });
|
|
578
|
-
}}
|
|
579
|
-
thousandSeparator='.'
|
|
580
|
-
decimalSeparator=','
|
|
581
|
-
decimalScale={2}
|
|
582
|
-
fixedDecimalScale={true}
|
|
583
|
-
valueIsNumericString
|
|
584
|
-
InputProps={applying ? { endAdornment: <SyncIcon /> } : {}}
|
|
585
|
-
sx={{ minWidth: 130 }}
|
|
586
|
-
customInput={TextField}
|
|
587
|
-
/>
|
|
588
|
-
</>
|
|
589
|
-
}
|
|
590
|
-
{type === 'date' &&
|
|
591
|
-
<>
|
|
592
|
-
<TextField
|
|
593
|
-
name="lower-bound-input"
|
|
594
|
-
label="De"
|
|
595
|
-
variant="standard"
|
|
596
|
-
value={filterValueState[0] || ''}
|
|
597
|
-
onChange={handleLowerFilterChange}
|
|
598
|
-
type="date"
|
|
599
|
-
inputRef={focusElementRef}
|
|
600
|
-
InputLabelProps={{ shrink: true }}
|
|
601
|
-
sx={{ mr: 2, minWidth: 130 }}
|
|
602
|
-
/>
|
|
603
|
-
<TextField
|
|
604
|
-
name="upper-bound-input"
|
|
605
|
-
label="Até"
|
|
606
|
-
variant="standard"
|
|
607
|
-
value={filterValueState[1] || ''}
|
|
608
|
-
onChange={handleUpperFilterChange}
|
|
609
|
-
type="date"
|
|
610
|
-
InputProps={applying ? { endAdornment: <SyncIcon /> } : {}}
|
|
611
|
-
InputLabelProps={{ shrink: true }}
|
|
612
|
-
sx={{ minWidth: 130 }}
|
|
613
|
-
/>
|
|
614
|
-
</>
|
|
615
|
-
}
|
|
616
|
-
</Box>
|
|
617
|
-
);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function InputNumberInterval (props: any) {
|
|
621
|
-
return (
|
|
622
|
-
<InputInterval
|
|
623
|
-
{...props}
|
|
624
|
-
type="number"
|
|
625
|
-
/>
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
function InputDateInterval (props: any) {
|
|
629
|
-
return (
|
|
630
|
-
<InputInterval
|
|
631
|
-
{...props}
|
|
632
|
-
type="date"
|
|
633
|
-
/>
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
function InputFloatInterval (props: any) {
|
|
637
|
-
return (
|
|
638
|
-
<InputInterval
|
|
639
|
-
{...props}
|
|
640
|
-
type="float"
|
|
641
|
-
/>
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
const quantityOnlyOperators = ({ type }:{ type: string }) => {
|
|
646
|
-
const builtInFilters = (type === 'date')
|
|
647
|
-
? getGridDateOperators()
|
|
648
|
-
: getGridNumericOperators();
|
|
649
|
-
let InputComponent = InputNumberInterval;
|
|
650
|
-
if (type === 'date') {
|
|
651
|
-
InputComponent = InputDateInterval;
|
|
652
|
-
}
|
|
653
|
-
if (type === 'float') {
|
|
654
|
-
InputComponent = InputFloatInterval;
|
|
655
|
-
}
|
|
656
|
-
return [
|
|
657
|
-
...builtInFilters,
|
|
658
|
-
{
|
|
659
|
-
label: 'entre',
|
|
660
|
-
value: 'entre',
|
|
661
|
-
getApplyFilterFn: (filterItem: any) => {
|
|
662
|
-
if (!Array.isArray(filterItem.value) || filterItem.value.length !== 2) {
|
|
663
|
-
return null;
|
|
664
|
-
}
|
|
665
|
-
if (filterItem.value[0] === null || filterItem.value[1] === null) {
|
|
666
|
-
return null;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
return ({ value }: { value: any }) => {
|
|
670
|
-
return (
|
|
671
|
-
value !== null &&
|
|
672
|
-
filterItem.value[0] <= value &&
|
|
673
|
-
value <= filterItem.value[1]
|
|
674
|
-
);
|
|
675
|
-
};
|
|
676
|
-
},
|
|
677
|
-
InputComponent
|
|
678
|
-
}
|
|
679
|
-
] as GridFilterOperator[];
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
type FConfirmDialogProps = {
|
|
683
|
-
open: boolean,
|
|
684
|
-
onClose: (p: any) => void,
|
|
685
|
-
onConfirm: (p: any) => void
|
|
686
|
-
};
|
|
687
|
-
const FConfirmDialog = ({
|
|
688
|
-
open,
|
|
689
|
-
onClose,
|
|
690
|
-
onConfirm
|
|
691
|
-
}: FConfirmDialogProps) => {
|
|
692
|
-
return (
|
|
693
|
-
<Dialog open={open} onClose={onClose}>
|
|
694
|
-
<DialogTitle>
|
|
695
|
-
Confirmar
|
|
696
|
-
</DialogTitle>
|
|
697
|
-
<DialogContent>
|
|
698
|
-
Tem certeza de que você quer remover este item?
|
|
699
|
-
</DialogContent>
|
|
700
|
-
<DialogActions>
|
|
701
|
-
<Button
|
|
702
|
-
onClick={onClose}
|
|
703
|
-
>
|
|
704
|
-
Cancelar
|
|
705
|
-
</Button>
|
|
706
|
-
<Button
|
|
707
|
-
onClick={onConfirm}
|
|
708
|
-
>
|
|
709
|
-
Remover
|
|
710
|
-
</Button>
|
|
711
|
-
</DialogActions>
|
|
712
|
-
</Dialog>
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
const ConfirmDialog = React.memo(FConfirmDialog);
|
|
716
|
-
|
|
717
|
-
type DataGridBySchemaEditableProps = {
|
|
718
|
-
schema: Schema,
|
|
719
|
-
data: Item[],
|
|
720
|
-
columns: GridEnrichedBySchemaColDef[],
|
|
721
|
-
model: string,
|
|
722
|
-
fieldKey?: string,
|
|
723
|
-
labelKey?: string,
|
|
724
|
-
index?: number,
|
|
725
|
-
name?: string,
|
|
726
|
-
updateUrl: string,
|
|
727
|
-
indexField?: string,
|
|
728
|
-
addExistingModel?: string,
|
|
729
|
-
indexFieldMinWidth?: number,
|
|
730
|
-
indexFieldBasePath?: string,
|
|
731
|
-
stateToLink?: object,
|
|
732
|
-
minWidth?: number,
|
|
733
|
-
onEditRelatedModelSave?: (p: any) => Id | { data: Item } | { errors: Item },
|
|
734
|
-
onDeleteRelatedModel?: (p: any) => any,
|
|
735
|
-
customColumnOperations?: (p: any) => GridEnrichedBySchemaColDef,
|
|
736
|
-
customLinkDestination?: (p: any) => string,
|
|
737
|
-
onProcessRow?: (p: any) => void,
|
|
738
|
-
onDataChange?: (p: any) => void,
|
|
739
|
-
isEditable?: boolean,
|
|
740
|
-
sx?: SxProps,
|
|
741
|
-
isAutoHeight?: boolean,
|
|
742
|
-
defaultValues?: Item,
|
|
743
|
-
hideFooterPagination?: boolean,
|
|
744
|
-
setVisibleRows?: (p: any) => void
|
|
745
|
-
};
|
|
746
|
-
const DataGridBySchemaEditable = React.forwardRef((
|
|
78
|
+
const DataGridBySchemaEditable = forwardRef((
|
|
747
79
|
{
|
|
748
80
|
schema,
|
|
749
81
|
data,
|
|
@@ -753,7 +85,6 @@ const DataGridBySchemaEditable = React.forwardRef((
|
|
|
753
85
|
labelKey = 'nome',
|
|
754
86
|
index,
|
|
755
87
|
name = Math.floor(Math.random() * 1000000).toString(),
|
|
756
|
-
updateUrl,
|
|
757
88
|
indexField = 'nome',
|
|
758
89
|
addExistingModel,
|
|
759
90
|
indexFieldMinWidth = 350,
|
|
@@ -764,6 +95,7 @@ const DataGridBySchemaEditable = React.forwardRef((
|
|
|
764
95
|
onDeleteRelatedModel,
|
|
765
96
|
customColumnOperations,
|
|
766
97
|
customLinkDestination,
|
|
98
|
+
LinkComponent,
|
|
767
99
|
onProcessRow,
|
|
768
100
|
onDataChange,
|
|
769
101
|
isEditable = false,
|
|
@@ -776,13 +108,16 @@ const DataGridBySchemaEditable = React.forwardRef((
|
|
|
776
108
|
}: DataGridBySchemaEditableProps,
|
|
777
109
|
ref
|
|
778
110
|
) => {
|
|
779
|
-
const
|
|
111
|
+
const { serverEndPoint } = DRFReactBySchemaContext
|
|
112
|
+
? React.useContext(DRFReactBySchemaContext) as DRFReactBySchemaContextType
|
|
113
|
+
: { serverEndPoint: null };
|
|
114
|
+
const initialSnackBar:Record<string, any> = {
|
|
780
115
|
open: false,
|
|
781
116
|
msg: '',
|
|
782
117
|
severity: 'info'
|
|
783
118
|
};
|
|
784
|
-
const [snackBar, setSnackBar] =
|
|
785
|
-
const [dataGrid, setDataGrid] = useState<{ data:
|
|
119
|
+
const [snackBar, setSnackBar] = useState(initialSnackBar);
|
|
120
|
+
const [dataGrid, setDataGrid] = useState<{ data:Item[] }>({ data: [] });
|
|
786
121
|
const [preparedColumns, setPreparedColumns] = useState<null | GridEnrichedBySchemaColDef[]>(null);
|
|
787
122
|
const [dataGridLoading, setDataGridLoading] = useState(false);
|
|
788
123
|
const [rowModesModel, setRowModesModel] = useState<Record<string, any>>({});
|
|
@@ -800,8 +135,12 @@ const DataGridBySchemaEditable = React.forwardRef((
|
|
|
800
135
|
continue;
|
|
801
136
|
}
|
|
802
137
|
if (['field', 'nested object'].includes(schema[col.field].type) && !(col.field in optionsAC.current)) {
|
|
803
|
-
const options = await getAutoComplete(col.field);
|
|
804
|
-
|
|
138
|
+
const options = await getAutoComplete({ model: col.field, serverEndPoint });
|
|
139
|
+
if (options) {
|
|
140
|
+
optionsAC.current[col.field] = options;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
console.log(`Couldn't load autocomplete options from '${col.field}'`);
|
|
805
144
|
continue;
|
|
806
145
|
}
|
|
807
146
|
if (schema[col.field].type === 'choice' && !(col.field in optionsAC.current)) {
|
|
@@ -809,8 +148,14 @@ const DataGridBySchemaEditable = React.forwardRef((
|
|
|
809
148
|
continue;
|
|
810
149
|
}
|
|
811
150
|
if (col.field === indexField && addExistingModel && !optionsAC.current[col.field]) {
|
|
812
|
-
const options = await getAutoComplete(addExistingModel);
|
|
813
|
-
|
|
151
|
+
const options = await getAutoComplete({ model: addExistingModel, serverEndPoint });
|
|
152
|
+
if (options) {
|
|
153
|
+
optionsAC.current[col.field] = options;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!(col.field in optionsAC.current)) {
|
|
157
|
+
delete optionsAC.current[col.field];
|
|
158
|
+
}
|
|
814
159
|
}
|
|
815
160
|
}
|
|
816
161
|
};
|
|
@@ -999,15 +344,16 @@ const DataGridBySchemaEditable = React.forwardRef((
|
|
|
999
344
|
if (col.field === indexField) {
|
|
1000
345
|
column.flex = 1;
|
|
1001
346
|
column.renderCell = params => {
|
|
1002
|
-
if (customLinkDestination) {
|
|
347
|
+
if (LinkComponent && customLinkDestination) {
|
|
1003
348
|
return (
|
|
1004
|
-
<
|
|
349
|
+
<LinkComponent to={`${customLinkDestination(params)}`} state={stateToLink}>
|
|
1005
350
|
{params.formattedValue}
|
|
1006
|
-
</
|
|
351
|
+
</LinkComponent>
|
|
1007
352
|
);
|
|
1008
353
|
}
|
|
1009
354
|
|
|
1010
355
|
if (
|
|
356
|
+
!LinkComponent ||
|
|
1011
357
|
!indexFieldBasePath ||
|
|
1012
358
|
(
|
|
1013
359
|
['field', 'nested object'].includes(schema[col.field].type) &&
|
|
@@ -1022,9 +368,9 @@ const DataGridBySchemaEditable = React.forwardRef((
|
|
|
1022
368
|
: params.row.id.toString();
|
|
1023
369
|
|
|
1024
370
|
return (
|
|
1025
|
-
<
|
|
371
|
+
<LinkComponent to={`${indexFieldBasePath}${targetId}`} state={stateToLink}>
|
|
1026
372
|
{params.formattedValue}
|
|
1027
|
-
</
|
|
373
|
+
</LinkComponent>
|
|
1028
374
|
);
|
|
1029
375
|
};
|
|
1030
376
|
if (
|
|
@@ -1385,7 +731,7 @@ const DataGridBySchemaEditable = React.forwardRef((
|
|
|
1385
731
|
<Snackbar
|
|
1386
732
|
open={snackBar.open}
|
|
1387
733
|
autoHideDuration={5000}
|
|
1388
|
-
onClose={() => { setSnackBar({ open: false }); }}
|
|
734
|
+
onClose={() => { setSnackBar({ ...initialSnackBar, open: false }); }}
|
|
1389
735
|
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
|
1390
736
|
>
|
|
1391
737
|
<Alert severity={snackBar.severity}>
|
|
@@ -1398,4 +744,4 @@ const DataGridBySchemaEditable = React.forwardRef((
|
|
|
1398
744
|
|
|
1399
745
|
DataGridBySchemaEditable.displayName = 'DataGridBySchemaEditable';
|
|
1400
746
|
|
|
1401
|
-
export default DataGridBySchemaEditable;
|
|
747
|
+
export default DataGridBySchemaEditable;
|