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.
Files changed (41) hide show
  1. package/README.md +6 -2
  2. package/dist/_virtual/index.esm11.js +2 -2
  3. package/dist/_virtual/index.esm12.js +2 -2
  4. package/dist/_virtual/index.esm13.js +2 -2
  5. package/dist/_virtual/index.esm14.js +2 -2
  6. package/dist/_virtual/index.esm4.js +2 -2
  7. package/dist/_virtual/index.esm5.js +2 -2
  8. package/dist/_virtual/index.esm6.js +2 -2
  9. package/dist/components/DateRangePicker/DateRangePicker.esm.js +49 -72
  10. package/dist/components/Form/FormRender.esm.js +1 -1
  11. package/dist/components/FormComponents/DatePicker/DatepickerWrapperV2.esm.js +10 -15
  12. package/dist/components/FormComponents/DatePicker/DatetimepickerWrapper.esm.js +53 -61
  13. package/dist/components/FormComponents/FileUpload/FormRenderFileUpload.esm.js +54 -68
  14. package/dist/components/FormComponents/FileUpload/FormRenderMultiFileUpload.esm.js +61 -76
  15. package/dist/components/FormComponents/FileUpload/MultiFileWithPreview.esm.js +103 -132
  16. package/dist/components/FormComponents/FormNumberField/FormNumberField.d.ts +0 -38
  17. package/dist/components/FormComponents/FormNumberField/FormNumberField.esm.js +32 -97
  18. package/dist/components/FormComponents/FormNumberField/FormNumberFieldDecimal.esm.js +44 -38
  19. package/dist/components/FormComponents/FormTextField/FormTextField.esm.js +3 -3
  20. package/dist/components/FormComponents/FormTextFieldWithSelect/FormTextFieldWithSelect.esm.js +3 -2
  21. package/dist/components/FormComponents/PasswordField/PasswordField.esm.js +4 -4
  22. package/dist/components/FormComponents/RichTextEditor/RichTextEditor.esm.js +23 -22
  23. package/dist/components/FormComponents/Select/SearchableSelect.esm.js +9 -9
  24. package/dist/components/FormComponents/TimePicker/TimePicker.esm.js +19 -19
  25. package/dist/components/FormComponents/index.esm.js +2 -2
  26. package/dist/components/ModalField/ConfirmationDialog.esm.js +3 -3
  27. package/dist/components/ModalField/ModalField.esm.js +5 -12
  28. package/dist/components/PaginatedGrid/DynamicFilterBar.d.ts +17 -2
  29. package/dist/components/PaginatedGrid/DynamicFilterBar.esm.js +161 -61
  30. package/dist/components/PaginatedGrid/GenericPaginatedGrid.d.ts +21 -3
  31. package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +133 -92
  32. package/dist/components/SelectField/MultiSelectFieldComponent.esm.js +3 -3
  33. package/dist/components/SessionTimeOut/SessionTimeOut.esm.js +33 -34
  34. package/dist/node_modules/react-date-range/dist/components/DateInput/index.esm.js +1 -1
  35. package/dist/node_modules/react-date-range/dist/components/DateRange/index.esm.js +1 -1
  36. package/dist/node_modules/react-date-range/dist/components/DateRangePicker/index.esm.js +1 -1
  37. package/dist/node_modules/react-date-range/dist/components/DayCell/index.esm.js +1 -1
  38. package/dist/node_modules/react-date-range/dist/components/DefinedRange/index.esm.js +1 -1
  39. package/dist/node_modules/react-date-range/dist/components/InputRangeField/index.esm.js +1 -1
  40. package/dist/node_modules/react-date-range/dist/components/Month/index.esm.js +1 -1
  41. 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 (next to Show/Hide Columns, Density, and Fullscreen) — no extra props needed. Click it to expand a free-text search that filters across all visible columns of the current batch. See **Docs → Paginated Grid → Global search**.
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 DayCell = {};
1
+ var InputRangeField = {};
2
2
 
3
- export { DayCell as __exports };
3
+ export { InputRangeField as __exports };
@@ -1,3 +1,3 @@
1
- var InputRangeField = {};
1
+ var DayCell = {};
2
2
 
3
- export { InputRangeField as __exports };
3
+ export { DayCell as __exports };
@@ -1,3 +1,3 @@
1
- var Month = {};
1
+ var DateInput = {};
2
2
 
3
- export { Month as __exports };
3
+ export { DateInput as __exports };
@@ -1,3 +1,3 @@
1
- var DateInput = {};
1
+ var Month = {};
2
2
 
3
- export { DateInput as __exports };
3
+ export { Month as __exports };
@@ -1,3 +1,3 @@
1
- var DateRange = {};
1
+ var DateRangePicker = {};
2
2
 
3
- export { DateRange as __exports };
3
+ export { DateRangePicker as __exports };
@@ -1,3 +1,3 @@
1
- var DateRangePicker = {};
1
+ var DefinedRange = {};
2
2
 
3
- export { DateRangePicker as __exports };
3
+ export { DefinedRange as __exports };
@@ -1,3 +1,3 @@
1
- var DefinedRange = {};
1
+ var DateRange = {};
2
2
 
3
- export { DefinedRange as __exports };
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(fromDate),
44
- endDate: getValidDate(toDate),
40
+ startDate: getValidDate(fromDateValue),
41
+ endDate: getValidDate(toDateValue),
45
42
  key: 'selection',
46
43
  },
47
44
  ]);
48
- }, [fromDateValue, toDateValue, watch, fromDateName, toDateName]);
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
- readOnly: true,
66
- }, label: label, size: "small" })] })) }) }), jsx(Popover, { id: id, open: open, anchorEl: anchorEl, onClose: handleClose, anchorOrigin: {
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
- const handleToggle = () => {
26
- if (!open) {
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(!open);
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: () => handleToggle(),
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: isShowBorderError,
61
+ error: isError,
63
62
  sx: {
64
- '& .MuiFormLabel-root': {
65
- top: -10,
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("DateTimePicker component resolved to undefined. Check your utility export.");
26
+ console.error('DateTimePicker component resolved to undefined.');
36
27
  }
37
28
  setDateTimePicker(() => component);
38
29
  }
39
30
  catch (err) {
40
- console.error("Failed to load component:", err);
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
- const handleToggle = () => {
39
+ // Stable toggle handler
40
+ const handleToggle = useCallback(() => {
49
41
  if (!open)
50
42
  inputTextRef.current?.blur();
51
43
  document.activeElement?.blur();
52
- setOpen(!open);
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
- // --- SYNC LOGIC FOR RESET / INITIAL VALUES ---
60
- // This effect runs whenever the value in the form state changes
61
- // eslint-disable-next-line react-hooks/rules-of-hooks
62
- useEffect(() => {
63
- if (field.value && props.setValue) {
64
- const dateObj = dayjs(field.value, displayFormat);
65
- if (dateObj.isValid()) {
66
- props.setValue(`${props.item.name}Date`, dateObj.format('DD/MM/YYYY'));
67
- props.setValue(`${props.item.name}Time`, dateObj.format(timeFormat));
68
- }
69
- }
70
- }, [field.value, props.item.name, props.setValue, displayFormat, timeFormat]);
71
- return (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
72
- ? dayjs(field.value, displayFormat).isValid()
73
- ? dayjs(field.value, displayFormat)
74
- : dayjs(field.value)
75
- : 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) => {
76
- const formattedDateTime = dayjs(date).format(displayFormat);
77
- field.onChange(formattedDateTime);
78
- props?.item?.onChangeFn && props?.item?.onChangeFn(formattedDateTime);
79
- props?.clearErrors && props?.clearErrors(props.item.name);
80
- }, slotProps: {
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
- desktopPaper: {
97
- sx: {
98
- '& .MuiPickersLayout-root': {
99
- flexDirection: 'column',
100
- }
101
- }
90
+ },
91
+ desktopPaper: {
92
+ sx: {
93
+ '& .MuiPickersLayout-root': { flexDirection: 'column' },
102
94
  },
103
- } })] })), jsx(FormBottomField, { ...props })] }));
104
- } }, props.item.name));
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
- if (inputRef.current)
17
- inputRef.current.value = '';
23
+ if ((watched === null || watched === undefined) && inputRef.current) {
24
+ inputRef.current.value = '';
18
25
  }
19
26
  }, [watched]);
20
- const extensionMap = {
21
- excel: ['xls', 'xlsx'],
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 extensionMap.all;
32
- }
30
+ if (!fileType)
31
+ return EXTENSION_MAP.all;
33
32
  if (Array.isArray(fileType)) {
34
- // Check if array contains predefined keys (like 'image', 'pdf') or actual extensions
35
- const hasPredefinedKeys = fileType.some((item) => extensionMap[item]);
33
+ const hasPredefinedKeys = fileType.some((t) => EXTENSION_MAP[t]);
36
34
  if (hasPredefinedKeys) {
37
- // Merge extensions from all predefined keys
38
- const extensions = new Set();
39
- fileType.forEach((item) => {
40
- if (extensionMap[item]) {
41
- extensionMap[item].forEach((ext) => extensions.add(ext));
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(extensions);
42
+ return Array.from(exts);
49
43
  }
50
- // All items are direct extensions
51
44
  return fileType;
52
45
  }
53
- return extensionMap[fileType] || extensionMap.all;
54
- };
55
- const getAcceptString = () => {
56
- const extensions = getValidExtensions();
57
- return extensions.map((ext) => `.${ext}`).join(',');
58
- };
59
- const accept = getAcceptString();
60
- 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: isShowBorderError, inputRef: inputRef, inputProps: {
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: (event) => {
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 };