tp-react-elements 2.0.3 → 2.0.5
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/README.md +6 -2
- package/dist/_virtual/index.esm11.js +2 -2
- package/dist/_virtual/index.esm12.js +2 -2
- package/dist/_virtual/index.esm13.js +2 -2
- package/dist/_virtual/index.esm14.js +2 -2
- package/dist/_virtual/index.esm4.js +2 -2
- package/dist/_virtual/index.esm5.js +2 -2
- package/dist/_virtual/index.esm6.js +2 -2
- package/dist/components/DateRangePicker/DateRangePicker.esm.js +49 -72
- package/dist/components/Form/FormRender.esm.js +1 -1
- package/dist/components/FormComponents/DatePicker/DatepickerWrapperV2.esm.js +10 -15
- package/dist/components/FormComponents/DatePicker/DatetimepickerWrapper.esm.js +53 -61
- package/dist/components/FormComponents/FileUpload/FormRenderFileUpload.esm.js +54 -68
- package/dist/components/FormComponents/FileUpload/FormRenderMultiFileUpload.esm.js +61 -76
- package/dist/components/FormComponents/FileUpload/MultiFileWithPreview.esm.js +103 -132
- package/dist/components/FormComponents/FormNumberField/FormNumberField.d.ts +0 -38
- package/dist/components/FormComponents/FormNumberField/FormNumberField.esm.js +32 -97
- package/dist/components/FormComponents/FormNumberField/FormNumberFieldDecimal.esm.js +44 -38
- package/dist/components/FormComponents/FormTextField/FormTextField.esm.js +3 -3
- package/dist/components/FormComponents/FormTextFieldWithSelect/FormTextFieldWithSelect.esm.js +3 -2
- package/dist/components/FormComponents/PasswordField/PasswordField.esm.js +4 -4
- package/dist/components/FormComponents/RichTextEditor/RichTextEditor.esm.js +23 -22
- package/dist/components/FormComponents/Select/SearchableSelect.esm.js +9 -9
- package/dist/components/FormComponents/TimePicker/TimePicker.esm.js +19 -19
- package/dist/components/FormComponents/index.esm.js +2 -2
- package/dist/components/ModalField/ConfirmationDialog.esm.js +3 -3
- package/dist/components/ModalField/ModalField.esm.js +5 -12
- package/dist/components/PaginatedGrid/DynamicFilterBar.d.ts +17 -2
- package/dist/components/PaginatedGrid/DynamicFilterBar.esm.js +161 -61
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.d.ts +21 -3
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +133 -92
- package/dist/components/SelectField/MultiSelectFieldComponent.esm.js +3 -3
- package/dist/components/SessionTimeOut/SessionTimeOut.esm.js +33 -34
- package/dist/node_modules/react-date-range/dist/components/DateInput/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateRange/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateRangePicker/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DayCell/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DefinedRange/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/InputRangeField/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/Month/index.esm.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -202,10 +202,14 @@ npm run storybook
|
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
- **Docs → Paginated Grid** — API reference plus **four copy-paste examples** (embedded panel, wrapped headers, typography, invoice filters)
|
|
205
|
-
- **Components → PaginatedGrid → Sample Grids** — mock grids (no API): header wrap, compact layout, viewport fill
|
|
205
|
+
- **Components → PaginatedGrid → Sample Grids** — mock grids (no API): header wrap, compact layout, viewport fill, multi-select with Apply button, month picker
|
|
206
206
|
- **Components → PaginatedGrid → Example Grid** — invoice grid demo using mock data (no API)
|
|
207
207
|
|
|
208
|
-
**Global Search:** every `GenericPaginatedGrid` includes a built-in search icon in the top-right toolbar
|
|
208
|
+
**Global Search:** every `GenericPaginatedGrid` includes a built-in search icon in the top-right toolbar — **collapsed by default** (set `showGlobalSearchByDefault={true}` to start expanded). Click the icon to expand a free-text search that filters across all visible columns of the current batch. See **Docs → Paginated Grid → Global search**.
|
|
209
|
+
|
|
210
|
+
**Excel export:** the Export button downloads an `.xlsx` file with the header row styled in `headerBgColor` (bold white text) and columns auto-sized to fit content. Requires `xlsx-js-style` as a peer dependency.
|
|
211
|
+
|
|
212
|
+
**Element IDs:** every interactive element (toolbar buttons, filter chips, pagination controls) receives a stable `id` attribute. Pass `gridId="invoice"` (or omit to default to `exportFilename`) and all elements follow the pattern `{gridId}_refresh_icon`, `{gridId}_dateRange_chip`, `{gridId}_next_page_icon`, etc. See **Docs → Paginated Grid — Element IDs** in Storybook for the full reference table.
|
|
209
213
|
|
|
210
214
|
**Example — compact embedded grid with smaller fonts:**
|
|
211
215
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var InputRangeField = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { InputRangeField as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DayCell = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DayCell as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateInput = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateInput as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Month = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { Month as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateRangePicker = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateRangePicker as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DefinedRange = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DefinedRange as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateRange = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateRange as __exports };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
3
3
|
import { Controller } from 'react-hook-form';
|
|
4
4
|
import { d as distExports } from '../../_virtual/index.esm.js';
|
|
5
5
|
import { useRdrStyles } from '../../utils/rdrStyles.esm.js';
|
|
@@ -11,23 +11,20 @@ import { format } from '../../node_modules/date-fns/format.esm.js';
|
|
|
11
11
|
import { parse } from '../../node_modules/date-fns/parse.esm.js';
|
|
12
12
|
import { isValid } from '../../node_modules/date-fns/isValid.esm.js';
|
|
13
13
|
|
|
14
|
+
// Pure utility — no component state dependency, defined at module level
|
|
15
|
+
const getValidDate = (dateString) => {
|
|
16
|
+
if (!dateString)
|
|
17
|
+
return undefined;
|
|
18
|
+
const date = parse(dateString, 'dd/MM/yyyy', new Date());
|
|
19
|
+
return isValid(date) ? date : undefined;
|
|
20
|
+
};
|
|
14
21
|
const DateRangePickerComponent = ({ item, control, watch, setValue, getValues, clearErrors, errors, register, variant, ...rest }) => {
|
|
15
|
-
// Inject react-date-range CSS via JS (not CSS import) to prevent it from being
|
|
16
|
-
// bundled into the library's extracted CSS file and leaking into consumer apps.
|
|
17
22
|
useRdrStyles();
|
|
18
|
-
// Using a generic type for anchorEl to avoid environment-specific linting issues.
|
|
19
|
-
// eslint-disable-next-line react/prop-types
|
|
20
23
|
const [anchorEl, setAnchorEl] = useState(null);
|
|
21
24
|
const fromDateName = item.name;
|
|
22
25
|
const toDateName = `${item.name}_to`;
|
|
23
26
|
const fromDateValue = watch(fromDateName);
|
|
24
27
|
const toDateValue = watch(toDateName);
|
|
25
|
-
const getValidDate = (dateString) => {
|
|
26
|
-
if (!dateString)
|
|
27
|
-
return undefined;
|
|
28
|
-
const date = parse(dateString, 'dd/MM/yyyy', new Date());
|
|
29
|
-
return isValid(date) ? date : undefined;
|
|
30
|
-
};
|
|
31
28
|
const [state, setState] = useState([
|
|
32
29
|
{
|
|
33
30
|
startDate: getValidDate(fromDateValue),
|
|
@@ -35,23 +32,52 @@ const DateRangePickerComponent = ({ item, control, watch, setValue, getValues, c
|
|
|
35
32
|
key: 'selection',
|
|
36
33
|
},
|
|
37
34
|
]);
|
|
35
|
+
// Sync calendar state when watched field values change.
|
|
36
|
+
// Uses fromDateValue/toDateValue directly — no need to call watch() again inside.
|
|
38
37
|
useEffect(() => {
|
|
39
|
-
const fromDate = watch(fromDateName);
|
|
40
|
-
const toDate = watch(toDateName);
|
|
41
38
|
setState([
|
|
42
39
|
{
|
|
43
|
-
startDate: getValidDate(
|
|
44
|
-
endDate: getValidDate(
|
|
40
|
+
startDate: getValidDate(fromDateValue),
|
|
41
|
+
endDate: getValidDate(toDateValue),
|
|
45
42
|
key: 'selection',
|
|
46
43
|
},
|
|
47
44
|
]);
|
|
48
|
-
}, [fromDateValue, toDateValue
|
|
49
|
-
const handleClick = (event) => {
|
|
45
|
+
}, [fromDateValue, toDateValue]);
|
|
46
|
+
const handleClick = useCallback((event) => {
|
|
50
47
|
setAnchorEl(event.currentTarget);
|
|
51
|
-
};
|
|
52
|
-
const handleClose = () => {
|
|
48
|
+
}, []);
|
|
49
|
+
const handleClose = useCallback(() => {
|
|
53
50
|
setAnchorEl(null);
|
|
54
|
-
};
|
|
51
|
+
}, []);
|
|
52
|
+
// Stable range-change handler — only recreated when field names or callbacks change
|
|
53
|
+
const handleRangeChange = useCallback((range) => {
|
|
54
|
+
const selection = range.selection;
|
|
55
|
+
setState([selection]);
|
|
56
|
+
if (selection.startDate && selection.endDate) {
|
|
57
|
+
setValue(fromDateName, format(selection.startDate, 'dd/MM/yyyy'), {
|
|
58
|
+
shouldValidate: true,
|
|
59
|
+
shouldDirty: true,
|
|
60
|
+
});
|
|
61
|
+
setValue(toDateName, format(selection.endDate, 'dd/MM/yyyy'), {
|
|
62
|
+
shouldValidate: true,
|
|
63
|
+
shouldDirty: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (selection.startDate &&
|
|
67
|
+
selection.endDate &&
|
|
68
|
+
selection.startDate.getTime() !== selection.endDate.getTime()) {
|
|
69
|
+
const dateObject = {
|
|
70
|
+
startDate: format(selection.startDate, 'dd/MM/yyyy'),
|
|
71
|
+
endDate: format(selection.endDate, 'dd/MM/yyyy'),
|
|
72
|
+
};
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
74
|
+
item.onChangeFn && item.onChangeFn(dateObject || null);
|
|
75
|
+
setAnchorEl(null);
|
|
76
|
+
}
|
|
77
|
+
}, [fromDateName, toDateName, setValue, item.onChangeFn]);
|
|
78
|
+
// Memoize optional min/max date conversions so they don't re-create each render
|
|
79
|
+
const minDate = useMemo(() => (item?.minDate ? dayjs(item.minDate, item.DateFormat || 'DD/MM/YYYY').toDate() : undefined), [item?.minDate, item?.DateFormat]);
|
|
80
|
+
const maxDate = useMemo(() => (item?.maxDate ? dayjs(item.maxDate, item.DateFormat || 'DD/MM/YYYY').toDate() : undefined), [item?.maxDate, item?.DateFormat]);
|
|
55
81
|
const open = Boolean(anchorEl);
|
|
56
82
|
const id = open ? 'date-range-popover' : undefined;
|
|
57
83
|
const displayValue = state[0].startDate && state[0].endDate
|
|
@@ -61,58 +87,9 @@ const DateRangePickerComponent = ({ item, control, watch, setValue, getValues, c
|
|
|
61
87
|
return (jsxs(Fragment, { children: [jsx(Box, { onClick: handleClick, sx: { width: '100%' }, children: jsx(Controller, { name: fromDateName, control: control, render: ({ fieldState }) => (jsxs(Fragment, { children: [renderLabel(variant, {
|
|
62
88
|
item,
|
|
63
89
|
...rest,
|
|
64
|
-
}), ' ', jsx(TextField, { fullWidth: true, value: displayValue, placeholder: item.placeholder || 'DD/MM/YYYY - DD/MM/YYYY', error: !!fieldState.error, InputProps: {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
vertical: 'bottom',
|
|
68
|
-
horizontal: 'left',
|
|
69
|
-
}, PaperProps: {
|
|
70
|
-
style: {
|
|
71
|
-
transform: 'scale(0.9)',
|
|
72
|
-
transformOrigin: 'top left',
|
|
73
|
-
},
|
|
74
|
-
}, children: jsx(distExports.DateRange, { editableDateInputs: false, onChange: (range) => {
|
|
75
|
-
const selection = range.selection;
|
|
76
|
-
setState([selection]);
|
|
77
|
-
// if (selection.startDate) {
|
|
78
|
-
// setValue(fromDateName, format(selection.startDate, 'dd/MM/yyyy'), {
|
|
79
|
-
// shouldValidate: true,
|
|
80
|
-
// shouldDirty: true,
|
|
81
|
-
// });
|
|
82
|
-
// }
|
|
83
|
-
if (selection.startDate && selection.endDate) {
|
|
84
|
-
setValue(fromDateName, format(selection.startDate, 'dd/MM/yyyy'), {
|
|
85
|
-
shouldValidate: true,
|
|
86
|
-
shouldDirty: true,
|
|
87
|
-
});
|
|
88
|
-
setValue(toDateName, format(selection.endDate, 'dd/MM/yyyy'), {
|
|
89
|
-
shouldValidate: true,
|
|
90
|
-
shouldDirty: true,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
if (selection.startDate &&
|
|
94
|
-
selection.endDate &&
|
|
95
|
-
selection.startDate?.getTime() !== selection.endDate?.getTime()) {
|
|
96
|
-
const dateObject = {
|
|
97
|
-
startDate: format(selection.startDate, 'dd/MM/yyyy'),
|
|
98
|
-
endDate: format(selection.endDate, 'dd/MM/yyyy'),
|
|
99
|
-
};
|
|
100
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
101
|
-
item.onChangeFn && item.onChangeFn(dateObject || null);
|
|
102
|
-
handleClose();
|
|
103
|
-
}
|
|
104
|
-
}, moveRangeOnFirstSelection: false, ranges: state, minDate: item?.minDate
|
|
105
|
-
? dayjs(item?.minDate, item.DateFormat || 'DD/MM/YYYY').toDate()
|
|
106
|
-
: undefined, maxDate: item?.maxDate
|
|
107
|
-
? dayjs(item?.maxDate, item.DateFormat || 'DD/MM/YYYY').toDate()
|
|
108
|
-
: undefined, months: 2, direction: "horizontal", showDateDisplay: false, dateDisplayFormat: "dd/MM/yyyy", rangeColors: ['#4d73b1ff'] }) }), jsx(FormBottomField, { item,
|
|
109
|
-
control,
|
|
110
|
-
errors,
|
|
111
|
-
getValues,
|
|
112
|
-
setValue,
|
|
113
|
-
clearErrors,
|
|
114
|
-
register,
|
|
115
|
-
watch })] }));
|
|
90
|
+
}), ' ', jsx(TextField, { fullWidth: true, value: displayValue, placeholder: item.placeholder || 'DD/MM/YYYY - DD/MM/YYYY', error: !!fieldState.error, InputProps: { readOnly: true }, label: label, size: "small" })] })) }) }), jsx(Popover, { id: id, open: open, anchorEl: anchorEl, onClose: handleClose, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: {
|
|
91
|
+
style: { transform: 'scale(0.9)', transformOrigin: 'top left' },
|
|
92
|
+
}, children: jsx(distExports.DateRange, { editableDateInputs: false, onChange: handleRangeChange, moveRangeOnFirstSelection: false, ranges: state, minDate: minDate, maxDate: maxDate, months: 2, direction: "horizontal", showDateDisplay: false, dateDisplayFormat: "dd/MM/yyyy", rangeColors: ['#4d73b1ff'] }) }), jsx(FormBottomField, { item, control, errors, getValues, setValue, clearErrors, register, watch })] }));
|
|
116
93
|
};
|
|
117
94
|
|
|
118
95
|
export { DateRangePickerComponent as default };
|
|
@@ -6,7 +6,6 @@ import FormNumberField from '../FormComponents/FormNumberField/FormNumberField.e
|
|
|
6
6
|
import FormNumberFieldDecimal from '../FormComponents/FormNumberField/FormNumberFieldDecimal.esm.js';
|
|
7
7
|
import FormRadioGroup from '../FormComponents/FormRadioGroup/FormRadioGroup.esm.js';
|
|
8
8
|
import FormTextAreaField from '../FormComponents/FormTextAreaField/FormTextAreaField.esm.js';
|
|
9
|
-
import FormTextField from '../FormComponents/FormTextField/FormTextField.esm.js';
|
|
10
9
|
import PasswordField from '../FormComponents/PasswordField/PasswordField.esm.js';
|
|
11
10
|
import SingleSelect from '../FormComponents/Select/SingleSelect.esm.js';
|
|
12
11
|
import MultiSelectAutocomplete from '../FormComponents/Select/MultiSelectAutocomplete.esm.js';
|
|
@@ -18,6 +17,7 @@ import DateRangePickerComponent from '../DateRangePicker/DateRangePicker.esm.js'
|
|
|
18
17
|
import RichTextEditorWrapper from '../FormComponents/RichTextEditor/RichTextEditorWrapper.esm.js';
|
|
19
18
|
import SingleSelectSearchApi from '../FormComponents/Select/SingleSelectSearchApi.esm.js';
|
|
20
19
|
import DatetimepickerWrapper from '../FormComponents/DatePicker/DatetimepickerWrapper.esm.js';
|
|
20
|
+
import FormTextField from '../FormComponents/FormTextField/FormTextField.esm.js';
|
|
21
21
|
|
|
22
22
|
// Lazy-loaded heavy components
|
|
23
23
|
// const RichTextEditorWrapper = lazy(() =>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { useRef, useState, useEffect } from 'react';
|
|
2
|
+
import { useRef, useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { getDatePicker } from '../../../utils/Constants/date-pickers.esm.js';
|
|
4
4
|
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
|
5
5
|
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
|
@@ -22,15 +22,14 @@ const DatepickerWrapperV2 = ({ props, variant, }) => {
|
|
|
22
22
|
};
|
|
23
23
|
loadComponents();
|
|
24
24
|
}, []);
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// Stable toggle — does not change between renders
|
|
26
|
+
const handleToggle = useCallback(() => {
|
|
27
|
+
if (!open)
|
|
27
28
|
inputTextRef.current?.blur();
|
|
28
|
-
}
|
|
29
29
|
document.activeElement?.blur();
|
|
30
|
-
setOpen(!
|
|
31
|
-
};
|
|
30
|
+
setOpen((prev) => !prev);
|
|
31
|
+
}, [open]);
|
|
32
32
|
const isError = !!props.errors?.[props.item.name];
|
|
33
|
-
const isShowBorderError = isError;
|
|
34
33
|
if (isLoading) {
|
|
35
34
|
return jsx("div", { style: { minHeight: '36px' }, children: "Loading..." });
|
|
36
35
|
}
|
|
@@ -48,7 +47,7 @@ const DatepickerWrapperV2 = ({ props, variant, }) => {
|
|
|
48
47
|
props?.clearErrors && props?.clearErrors(props.item.name);
|
|
49
48
|
}, slotProps: {
|
|
50
49
|
textField: {
|
|
51
|
-
onClick:
|
|
50
|
+
onClick: handleToggle,
|
|
52
51
|
inputRef: inputTextRef,
|
|
53
52
|
onBlur: (e) => {
|
|
54
53
|
props?.item?.onBlurFn && props?.item?.onBlurFn(e);
|
|
@@ -59,14 +58,10 @@ const DatepickerWrapperV2 = ({ props, variant, }) => {
|
|
|
59
58
|
'aria-describedby': `${props.item.name}-helper ${props.item.name}-error`,
|
|
60
59
|
'aria-required': props.item.required ? true : undefined,
|
|
61
60
|
},
|
|
62
|
-
error:
|
|
61
|
+
error: isError,
|
|
63
62
|
sx: {
|
|
64
|
-
'& .MuiFormLabel-root': {
|
|
65
|
-
|
|
66
|
-
},
|
|
67
|
-
'& .MuiInputLabel-shrink': {
|
|
68
|
-
top: 0,
|
|
69
|
-
},
|
|
63
|
+
'& .MuiFormLabel-root': { top: -10 },
|
|
64
|
+
'& .MuiInputLabel-shrink': { top: 0 },
|
|
70
65
|
},
|
|
71
66
|
},
|
|
72
67
|
} })] })), jsx(FormBottomField, { ...props })] }));
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { useRef, useState, useEffect } from 'react';
|
|
2
|
+
import { useRef, useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { getDateTimePicker } from '../../../utils/Constants/date-pickers.esm.js';
|
|
4
4
|
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
|
5
5
|
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
|
6
6
|
import dayjs from 'dayjs';
|
|
7
|
-
import { Controller } from 'react-hook-form';
|
|
7
|
+
import { useWatch, Controller } from 'react-hook-form';
|
|
8
8
|
import FormBottomField from '../FormBottomField/FormBottomField.esm.js';
|
|
9
9
|
import { renderLabel } from '../../../utils/Constants/FormConstants.esm.js';
|
|
10
10
|
|
|
@@ -17,27 +17,18 @@ const DatetimepickerWrapper = ({ props, variant, }) => {
|
|
|
17
17
|
const timeFormat = isAmpm ? 'hh:mm A' : 'HH:mm';
|
|
18
18
|
const defaultDateFormat = `DD/MM/YYYY ${timeFormat}`;
|
|
19
19
|
const displayFormat = props.item.DateFormat || defaultDateFormat;
|
|
20
|
-
// useEffect(() => {
|
|
21
|
-
// const loadComponents = async () => {
|
|
22
|
-
// setIsLoading(true);
|
|
23
|
-
// const [dateTimePicker] = await Promise.all([getDateTimePicker()]);
|
|
24
|
-
// setDateTimePicker(() => dateTimePicker);
|
|
25
|
-
// setIsLoading(false);
|
|
26
|
-
// };
|
|
27
|
-
// loadComponents();
|
|
28
|
-
// }, []);
|
|
29
20
|
useEffect(() => {
|
|
30
21
|
const loadComponents = async () => {
|
|
31
22
|
try {
|
|
32
23
|
setIsLoading(true);
|
|
33
24
|
const component = await getDateTimePicker();
|
|
34
25
|
if (!component) {
|
|
35
|
-
console.error(
|
|
26
|
+
console.error('DateTimePicker component resolved to undefined.');
|
|
36
27
|
}
|
|
37
28
|
setDateTimePicker(() => component);
|
|
38
29
|
}
|
|
39
30
|
catch (err) {
|
|
40
|
-
console.error(
|
|
31
|
+
console.error('Failed to load component:', err);
|
|
41
32
|
}
|
|
42
33
|
finally {
|
|
43
34
|
setIsLoading(false);
|
|
@@ -45,63 +36,64 @@ const DatetimepickerWrapper = ({ props, variant, }) => {
|
|
|
45
36
|
};
|
|
46
37
|
loadComponents();
|
|
47
38
|
}, []);
|
|
48
|
-
|
|
39
|
+
// Stable toggle handler
|
|
40
|
+
const handleToggle = useCallback(() => {
|
|
49
41
|
if (!open)
|
|
50
42
|
inputTextRef.current?.blur();
|
|
51
43
|
document.activeElement?.blur();
|
|
52
|
-
setOpen(!
|
|
53
|
-
};
|
|
44
|
+
setOpen((prev) => !prev);
|
|
45
|
+
}, [open]);
|
|
46
|
+
// Watch field value at component level so the sync effect below
|
|
47
|
+
// can live here instead of inside the Controller render prop
|
|
48
|
+
// (hooks must not be called conditionally or inside render callbacks).
|
|
49
|
+
const fieldValue = useWatch({ control: props.control, name: props.item.name });
|
|
50
|
+
// Sync derived date/time sub-fields whenever the combined value changes.
|
|
51
|
+
// Previously this useEffect was erroneously placed inside the Controller
|
|
52
|
+
// render prop, violating the rules of hooks.
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (fieldValue && props.setValue) {
|
|
55
|
+
const dateObj = dayjs(fieldValue, displayFormat);
|
|
56
|
+
if (dateObj.isValid()) {
|
|
57
|
+
props.setValue(`${props.item.name}Date`, dateObj.format('DD/MM/YYYY'));
|
|
58
|
+
props.setValue(`${props.item.name}Time`, dateObj.format(timeFormat));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}, [fieldValue, props.item.name, props.setValue, displayFormat, timeFormat]);
|
|
54
62
|
const isError = !!props.errors?.[props.item.name];
|
|
55
63
|
if (isLoading) {
|
|
56
64
|
return jsx("div", { style: { minHeight: '36px' }, children: "Loading..." });
|
|
57
65
|
}
|
|
58
|
-
return (jsx(Controller, { control: props.control, name: props.item.name, render: ({ field }) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
textField: {
|
|
82
|
-
onClick: () => handleToggle(),
|
|
83
|
-
inputRef: inputTextRef,
|
|
84
|
-
onBlur: (e) => props?.item?.onBlurFn && props?.item?.onBlurFn(e),
|
|
85
|
-
inputProps: {
|
|
86
|
-
style: { cursor: 'pointer' },
|
|
87
|
-
'aria-labelledby': `${props.item.name}-label`,
|
|
88
|
-
},
|
|
89
|
-
readOnly: true,
|
|
90
|
-
error: isError,
|
|
91
|
-
sx: {
|
|
92
|
-
'& .MuiFormLabel-root': { top: -10 },
|
|
93
|
-
'& .MuiInputLabel-shrink': { top: 0 },
|
|
94
|
-
},
|
|
66
|
+
return (jsx(Controller, { control: props.control, name: props.item.name, render: ({ field }) => (jsxs(Fragment, { children: [DateTimePicker && (jsxs(LocalizationProvider, { dateAdapter: AdapterDayjs, children: [renderLabel(variant, props), jsx(DateTimePicker, { views: ['year', 'month', 'day', 'hours', 'minutes'], ampm: isAmpm, format: displayFormat, label: variant !== 'standard' ? `${props.item.label}${props.item.required ? ' *' : ''}` : '', value: field.value
|
|
67
|
+
? dayjs(field.value, displayFormat).isValid()
|
|
68
|
+
? dayjs(field.value, displayFormat)
|
|
69
|
+
: dayjs(field.value)
|
|
70
|
+
: null, className: 'read-only', open: open, onOpen: handleToggle, onClose: handleToggle, minDateTime: props?.item?.minDate ? dayjs(props?.item?.minDate, displayFormat) : undefined, maxDateTime: props?.item?.maxDate ? dayjs(props?.item?.maxDate, displayFormat) : undefined, disabled: props.item.disable || false, onChange: (date) => {
|
|
71
|
+
const formattedDateTime = dayjs(date).format(displayFormat);
|
|
72
|
+
field.onChange(formattedDateTime);
|
|
73
|
+
props?.item?.onChangeFn && props?.item?.onChangeFn(formattedDateTime);
|
|
74
|
+
props?.clearErrors && props?.clearErrors(props.item.name);
|
|
75
|
+
}, slotProps: {
|
|
76
|
+
textField: {
|
|
77
|
+
onClick: handleToggle,
|
|
78
|
+
inputRef: inputTextRef,
|
|
79
|
+
onBlur: (e) => props?.item?.onBlurFn && props?.item?.onBlurFn(e),
|
|
80
|
+
inputProps: {
|
|
81
|
+
style: { cursor: 'pointer' },
|
|
82
|
+
'aria-labelledby': `${props.item.name}-label`,
|
|
83
|
+
},
|
|
84
|
+
readOnly: true,
|
|
85
|
+
error: isError,
|
|
86
|
+
sx: {
|
|
87
|
+
'& .MuiFormLabel-root': { top: -10 },
|
|
88
|
+
'& .MuiInputLabel-shrink': { top: 0 },
|
|
95
89
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
}
|
|
90
|
+
},
|
|
91
|
+
desktopPaper: {
|
|
92
|
+
sx: {
|
|
93
|
+
'& .MuiPickersLayout-root': { flexDirection: 'column' },
|
|
102
94
|
},
|
|
103
|
-
}
|
|
104
|
-
|
|
95
|
+
},
|
|
96
|
+
} })] })), jsx(FormBottomField, { ...props })] })) }, props.item.name));
|
|
105
97
|
};
|
|
106
98
|
|
|
107
99
|
export { DatetimepickerWrapper as default };
|
|
@@ -1,96 +1,82 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { Box, TextField } from '@mui/material';
|
|
3
|
-
import { useRef, useEffect } from 'react';
|
|
3
|
+
import { useRef, useEffect, useMemo, useCallback } from 'react';
|
|
4
4
|
import { useWatch } from 'react-hook-form';
|
|
5
5
|
import FormBottomField from '../FormBottomField/FormBottomField.esm.js';
|
|
6
6
|
import 'dayjs';
|
|
7
7
|
import { renderLabel } from '../../../utils/Constants/FormConstants.esm.js';
|
|
8
8
|
|
|
9
|
+
// Static lookup — defined once at module level, never recreated
|
|
10
|
+
const EXTENSION_MAP = {
|
|
11
|
+
excel: ['xls', 'xlsx'],
|
|
12
|
+
pdf: ['pdf'],
|
|
13
|
+
zip: ['zip'],
|
|
14
|
+
image: ['jpg', 'jpeg', 'png'],
|
|
15
|
+
document: ['doc', 'docx', 'pdf'],
|
|
16
|
+
all: ['pdf', 'jpg', 'jpeg', 'png', 'xls', 'xlsx', 'doc', 'docx', 'zip'],
|
|
17
|
+
};
|
|
9
18
|
const FormRenderFileUpload = ({ props, variant, }) => {
|
|
10
19
|
const isError = !!props.errors?.[props.item.name];
|
|
11
|
-
const isShowBorderError = isError;
|
|
12
20
|
const inputRef = useRef(null);
|
|
13
21
|
const watched = useWatch({ control: props.control, name: props.item.name });
|
|
14
22
|
useEffect(() => {
|
|
15
|
-
if (watched === null || watched === undefined) {
|
|
16
|
-
|
|
17
|
-
inputRef.current.value = '';
|
|
23
|
+
if ((watched === null || watched === undefined) && inputRef.current) {
|
|
24
|
+
inputRef.current.value = '';
|
|
18
25
|
}
|
|
19
26
|
}, [watched]);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
pdf: ['pdf'],
|
|
23
|
-
zip: ['zip'],
|
|
24
|
-
image: ['jpg', 'jpeg', 'png'],
|
|
25
|
-
document: ['doc', 'docx', 'pdf'],
|
|
26
|
-
all: ['pdf', 'jpg', 'jpeg', 'png', 'xls', 'xlsx', 'doc', 'docx', 'zip'],
|
|
27
|
-
};
|
|
28
|
-
const getValidExtensions = () => {
|
|
27
|
+
// Derived once per fileType change — not on every render
|
|
28
|
+
const validExtensions = useMemo(() => {
|
|
29
29
|
const fileType = props.item.fileType;
|
|
30
|
-
if (!fileType)
|
|
31
|
-
return
|
|
32
|
-
}
|
|
30
|
+
if (!fileType)
|
|
31
|
+
return EXTENSION_MAP.all;
|
|
33
32
|
if (Array.isArray(fileType)) {
|
|
34
|
-
|
|
35
|
-
const hasPredefinedKeys = fileType.some((item) => extensionMap[item]);
|
|
33
|
+
const hasPredefinedKeys = fileType.some((t) => EXTENSION_MAP[t]);
|
|
36
34
|
if (hasPredefinedKeys) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
else {
|
|
44
|
-
// Treat as direct extension
|
|
45
|
-
extensions.add(item);
|
|
46
|
-
}
|
|
35
|
+
const exts = new Set();
|
|
36
|
+
fileType.forEach((t) => {
|
|
37
|
+
if (EXTENSION_MAP[t])
|
|
38
|
+
EXTENSION_MAP[t].forEach((e) => exts.add(e));
|
|
39
|
+
else
|
|
40
|
+
exts.add(t);
|
|
47
41
|
});
|
|
48
|
-
return Array.from(
|
|
42
|
+
return Array.from(exts);
|
|
49
43
|
}
|
|
50
|
-
// All items are direct extensions
|
|
51
44
|
return fileType;
|
|
52
45
|
}
|
|
53
|
-
return
|
|
54
|
-
};
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
return EXTENSION_MAP[fileType] ?? EXTENSION_MAP.all;
|
|
47
|
+
}, [props.item.fileType]);
|
|
48
|
+
const accept = useMemo(() => validExtensions.map((e) => `.${e}`).join(','), [validExtensions]);
|
|
49
|
+
const handleChange = useCallback((event) => {
|
|
50
|
+
const file = event.target?.files[0];
|
|
51
|
+
const fileName = file ? file.name : null;
|
|
52
|
+
const fileExtension = fileName ? fileName.split('.').pop().toLowerCase() : null;
|
|
53
|
+
if (props.item?.fileType && fileExtension && !validExtensions.includes(fileExtension)) {
|
|
54
|
+
props.item?.handleFileError?.(`Please upload ${validExtensions.join(', ')} files only`);
|
|
55
|
+
event.target.value = '';
|
|
56
|
+
props.setValue(props.item.name, null);
|
|
57
|
+
props.setValue(props.item.name + 'Name', '');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (file && file.size > (props.item.filesize || 20000000)) {
|
|
61
|
+
const maxSizeMB = ((props.item.filesize || 20000000) / (1024 * 1024)).toFixed(2);
|
|
62
|
+
props.item?.handleFileError?.(`File size should be less than ${maxSizeMB}MB`);
|
|
63
|
+
event.target.value = '';
|
|
64
|
+
props.setValue(props.item.name, null);
|
|
65
|
+
props.setValue(props.item.name + 'Name', '');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
console.log(props.item?.name, ':props.item?.name');
|
|
69
|
+
props.setValue(props.item.name, file);
|
|
70
|
+
props.setValue(props.item.name + 'Name', fileName);
|
|
71
|
+
props.clearErrors(props.item.name);
|
|
72
|
+
props.item.onChangeSetFileFn?.(file);
|
|
73
|
+
}, [validExtensions, props.item, props.setValue, props.clearErrors]);
|
|
74
|
+
return (jsxs(Fragment, { children: [jsxs(Box, { paddingLeft: '4px', sx: { ...props.item.sx }, children: [props.item?.label && (jsx(Box, { sx: { fontSize: '10px;' }, children: renderLabel(variant, props, true) })), jsx(TextField, { type: "file", id: props.item.name, error: isError, inputRef: inputRef, inputProps: {
|
|
61
75
|
accept,
|
|
62
76
|
'aria-labelledby': `${props.item.name}-label`,
|
|
63
77
|
'aria-describedby': `${props.item.name}-helper ${props.item.name}-error`,
|
|
64
78
|
'aria-required': props.item.required ? true : undefined,
|
|
65
|
-
}, onChange:
|
|
66
|
-
const file = event.target?.files[0];
|
|
67
|
-
const fileName = file ? file.name : null;
|
|
68
|
-
const fileExtension = fileName ? fileName.split('.').pop().toLowerCase() : null;
|
|
69
|
-
const validExtensions = getValidExtensions();
|
|
70
|
-
if (props.item?.fileType && fileExtension && !validExtensions.includes(fileExtension)) {
|
|
71
|
-
props.item?.handleFileError &&
|
|
72
|
-
props.item?.handleFileError(`Please upload ${validExtensions.join(', ')} files only`);
|
|
73
|
-
event.target.value = ''; // Clear the file input\
|
|
74
|
-
props.setValue(props.item?.name, null);
|
|
75
|
-
props.setValue(props.item?.name + 'Name', '');
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
else if (event.target.files[0].size > (props.item.filesize || 20000000)) {
|
|
79
|
-
const maxSizeMB = ((props.item.filesize || 20000000) / (1024 * 1024)).toFixed(2);
|
|
80
|
-
props.item?.handleFileError &&
|
|
81
|
-
props.item?.handleFileError(`File size should be less than ${maxSizeMB}MB`);
|
|
82
|
-
event.target.value = ''; // Clear the file input
|
|
83
|
-
props.setValue(props.item?.name, null);
|
|
84
|
-
props.setValue(props.item?.name + 'Name', '');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
console.log(props.item?.name, ':props.item?.name');
|
|
88
|
-
props.setValue(props.item?.name, file);
|
|
89
|
-
props.setValue(props.item?.name + 'Name', fileName);
|
|
90
|
-
props.clearErrors(props.item?.name);
|
|
91
|
-
props.item.onChangeSetFileFn &&
|
|
92
|
-
props.item.onChangeSetFileFn(file);
|
|
93
|
-
}, sx: { width: '100%' } })] }), jsx(FormBottomField, { ...props })] }));
|
|
79
|
+
}, onChange: handleChange, sx: { width: '100%' } })] }), jsx(FormBottomField, { ...props })] }));
|
|
94
80
|
};
|
|
95
81
|
|
|
96
82
|
export { FormRenderFileUpload as default };
|