tp-react-elements 1.3.2 → 1.3.4
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/dist/_virtual/index.esm10.js +2 -2
- package/dist/_virtual/index.esm4.js +2 -2
- package/dist/_virtual/index.esm5.js +2 -2
- package/dist/_virtual/index.esm7.js +2 -2
- package/dist/_virtual/index.esm9.js +2 -2
- package/dist/components/DateRangePicker/DateRangePicker.d.ts +0 -3
- package/dist/components/DateRangePicker/DateRangePicker.esm.js +4 -0
- package/dist/components/Form/FormRender.esm.js +12 -8
- package/dist/components/FormComponents/Select/SearchableSelect.d.ts +6 -0
- package/dist/components/FormComponents/Select/SearchableSelect.esm.js +78 -0
- package/dist/components/FormComponents/index.d.ts +1 -0
- package/dist/components/FormComponents/index.esm.js +13 -26
- package/dist/components/PaginatedGrid/DynamicFilterBar.d.ts +33 -7
- package/dist/components/PaginatedGrid/DynamicFilterBar.esm.js +101 -40
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.d.ts +10 -3
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +49 -5
- package/dist/components/PaginatedGrid/columns.d.ts +2 -2
- package/dist/components/PaginatedGrid/types.d.ts +61 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +1 -0
- package/dist/node_modules/prop-types/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/accessibility/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/Calendar/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/DefinedRange/index.esm.js +1 -1
- package/dist/utils/Interface/FormInterface.d.ts +1 -1
- package/dist/utils/rdrStyles.d.ts +12 -0
- package/dist/utils/rdrStyles.esm.js +180 -0
- package/package.json +5 -1
- package/dist/node_modules/@tanstack/query-core/build/modern/environmentManager.esm.js +0 -22
- package/dist/node_modules/@tanstack/query-core/build/modern/focusManager.esm.js +0 -65
- package/dist/node_modules/@tanstack/query-core/build/modern/notifyManager.esm.js +0 -83
- package/dist/node_modules/@tanstack/query-core/build/modern/onlineManager.esm.js +0 -55
- package/dist/node_modules/@tanstack/query-core/build/modern/query.esm.js +0 -16
- package/dist/node_modules/@tanstack/query-core/build/modern/queryObserver.esm.js +0 -463
- package/dist/node_modules/@tanstack/query-core/build/modern/retryer.esm.js +0 -8
- package/dist/node_modules/@tanstack/query-core/build/modern/subscribable.esm.js +0 -24
- package/dist/node_modules/@tanstack/query-core/build/modern/thenable.esm.js +0 -34
- package/dist/node_modules/@tanstack/query-core/build/modern/timeoutManager.esm.js +0 -66
- package/dist/node_modules/@tanstack/query-core/build/modern/utils.esm.js +0 -115
- package/dist/node_modules/@tanstack/react-query/build/modern/IsRestoringProvider.esm.js +0 -7
- package/dist/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.esm.js +0 -15
- package/dist/node_modules/@tanstack/react-query/build/modern/QueryErrorResetBoundary.esm.js +0 -21
- package/dist/node_modules/@tanstack/react-query/build/modern/errorBoundaryUtils.esm.js +0 -27
- package/dist/node_modules/@tanstack/react-query/build/modern/suspense.esm.js +0 -22
- package/dist/node_modules/@tanstack/react-query/build/modern/useBaseQuery.esm.js +0 -94
- package/dist/node_modules/@tanstack/react-query/build/modern/useQuery.esm.js +0 -8
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var accessibility = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { accessibility 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,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Calendar = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { Calendar as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var propTypes = {exports: {}};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { propTypes as __module };
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import 'react-date-range/dist/styles.css';
|
|
2
|
-
import 'react-date-range/dist/theme/default.css';
|
|
3
1
|
import { FormRenderProps, VariantProps } from '../../utils';
|
|
4
|
-
import './DateRangePicker.scss';
|
|
5
2
|
declare const DateRangePickerComponent: ({ item, control, watch, setValue, getValues, clearErrors, errors, register, variant, ...rest }: FormRenderProps & {
|
|
6
3
|
variant: VariantProps;
|
|
7
4
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,6 +2,7 @@ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import { Controller } from 'react-hook-form';
|
|
4
4
|
import { d as distExports } from '../../_virtual/index.esm.js';
|
|
5
|
+
import { useRdrStyles } from '../../utils/rdrStyles.esm.js';
|
|
5
6
|
import dayjs from 'dayjs';
|
|
6
7
|
import { renderLabel } from '../../utils/Constants/FormConstants.esm.js';
|
|
7
8
|
import FormBottomField from '../FormComponents/FormBottomField/FormBottomField.esm.js';
|
|
@@ -11,6 +12,9 @@ import { parse } from '../../node_modules/date-fns/parse.esm.js';
|
|
|
11
12
|
import { isValid } from '../../node_modules/date-fns/isValid.esm.js';
|
|
12
13
|
|
|
13
14
|
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
|
+
useRdrStyles();
|
|
14
18
|
// Using a generic type for anchorEl to avoid environment-specific linting issues.
|
|
15
19
|
// eslint-disable-next-line react/prop-types
|
|
16
20
|
const [anchorEl, setAnchorEl] = useState(null);
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import React__default, { lazy, Suspense } from 'react';
|
|
3
|
-
import FormCheckBox from '../FormComponents/FormCheckBox/FormCheckBox.esm.js';
|
|
4
|
-
import FormCheckBoxGroup from '../FormComponents/FormCheckBoxGroup/FormCheckBoxGroup.esm.js';
|
|
5
|
-
import FormNumberField from '../FormComponents/FormNumberField/FormNumberField.esm.js';
|
|
6
|
-
import FormNumberFieldDecimal from '../FormComponents/FormNumberField/FormNumberFieldDecimal.esm.js';
|
|
7
|
-
import FormRadioGroup from '../FormComponents/FormRadioGroup/FormRadioGroup.esm.js';
|
|
8
|
-
import FormTextAreaField from '../FormComponents/FormTextAreaField/FormTextAreaField.esm.js';
|
|
9
|
-
import FormTextField from '../FormComponents/FormTextField/FormTextField.esm.js';
|
|
10
|
-
import PasswordField from '../FormComponents/PasswordField/PasswordField.esm.js';
|
|
11
3
|
import SingleSelect from '../FormComponents/Select/SingleSelect.esm.js';
|
|
12
4
|
import MultiSelectAutocomplete from '../FormComponents/Select/MultiSelectAutocomplete.esm.js';
|
|
13
5
|
import SingleSelectNonAutoComplete from '../FormComponents/Select/SingleSelectNonAutoComplete.esm.js';
|
|
6
|
+
import SearchableSelect from '../FormComponents/Select/SearchableSelect.esm.js';
|
|
14
7
|
import FormActiveSwitch from './FormActiveSwitch.esm.js';
|
|
15
8
|
import FormTextFieldWithSelect from '../FormComponents/FormTextFieldWithSelect/FormTextFieldWithSelect.esm.js';
|
|
16
9
|
import DateRangePickerComponent from '../DateRangePicker/DateRangePicker.esm.js';
|
|
17
10
|
import RichTextEditorWrapper from '../FormComponents/RichTextEditor/RichTextEditorWrapper.esm.js';
|
|
18
11
|
import SingleSelectSearchApi from '../FormComponents/Select/SingleSelectSearchApi.esm.js';
|
|
19
12
|
import DatetimepickerWrapper from '../FormComponents/DatePicker/DatetimepickerWrapper.esm.js';
|
|
13
|
+
import FormTextAreaField from '../FormComponents/FormTextAreaField/FormTextAreaField.esm.js';
|
|
14
|
+
import FormCheckBox from '../FormComponents/FormCheckBox/FormCheckBox.esm.js';
|
|
15
|
+
import FormRadioGroup from '../FormComponents/FormRadioGroup/FormRadioGroup.esm.js';
|
|
16
|
+
import FormCheckBoxGroup from '../FormComponents/FormCheckBoxGroup/FormCheckBoxGroup.esm.js';
|
|
17
|
+
import FormNumberFieldDecimal from '../FormComponents/FormNumberField/FormNumberFieldDecimal.esm.js';
|
|
18
|
+
import FormNumberField from '../FormComponents/FormNumberField/FormNumberField.esm.js';
|
|
19
|
+
import PasswordField from '../FormComponents/PasswordField/PasswordField.esm.js';
|
|
20
|
+
import FormTextField from '../FormComponents/FormTextField/FormTextField.esm.js';
|
|
20
21
|
|
|
21
22
|
// Lazy-loaded heavy components
|
|
22
23
|
// const RichTextEditorWrapper = lazy(() =>
|
|
@@ -92,6 +93,9 @@ const RenderForm = (props) => {
|
|
|
92
93
|
case 'select-v2':
|
|
93
94
|
case 'basic-select': // new alias
|
|
94
95
|
return jsx(SingleSelectNonAutoComplete, { props: props, variant: variant });
|
|
96
|
+
// Searchable select — read-only TextField trigger + search-filtered Popover
|
|
97
|
+
case 'searchable-select':
|
|
98
|
+
return jsx(SearchableSelect, { props: props, variant: variant });
|
|
95
99
|
case 'input-select':
|
|
96
100
|
return jsx(FormTextFieldWithSelect, { props: props, variant: variant });
|
|
97
101
|
// Numeric inputs (integer, pincode, phone)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { FormControl, Box, TextField, InputAdornment, Popover, List, Typography, ListItemButton, ListItemText } from '@mui/material';
|
|
4
|
+
import SearchIcon from '@mui/icons-material/Search';
|
|
5
|
+
import CheckIcon from '@mui/icons-material/Check';
|
|
6
|
+
import { useWatch } from 'react-hook-form';
|
|
7
|
+
import FormBottomField from '../FormBottomField/FormBottomField.esm.js';
|
|
8
|
+
import 'dayjs';
|
|
9
|
+
import { renderLabel } from '../../../utils/Constants/FormConstants.esm.js';
|
|
10
|
+
|
|
11
|
+
const SearchableSelect = ({ props, variant, }) => {
|
|
12
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
13
|
+
const [search, setSearch] = useState('');
|
|
14
|
+
const value = useWatch({ control: props.control, name: props.item.name });
|
|
15
|
+
const isError = !!props.errors?.[props.item.name];
|
|
16
|
+
const options = props.item.options ?? [];
|
|
17
|
+
// Find the label that corresponds to the currently selected value
|
|
18
|
+
const selectedOption = options.find((o) => String(o.value) === String(value ?? ''));
|
|
19
|
+
const displayValue = selectedOption ? String(selectedOption.label) : '';
|
|
20
|
+
const handleOpen = (e) => {
|
|
21
|
+
if (props.item.disable)
|
|
22
|
+
return;
|
|
23
|
+
setSearch('');
|
|
24
|
+
setAnchorEl(e.currentTarget);
|
|
25
|
+
};
|
|
26
|
+
const handleClose = () => setAnchorEl(null);
|
|
27
|
+
const handleSelect = (val) => {
|
|
28
|
+
props.setValue(props.item.name, val);
|
|
29
|
+
props.item.onChangeFn && props.item.onChangeFn(val);
|
|
30
|
+
props.clearErrors && props.clearErrors(props.item.name);
|
|
31
|
+
handleClose();
|
|
32
|
+
};
|
|
33
|
+
// Case-insensitive substring filter across option labels
|
|
34
|
+
const filtered = search
|
|
35
|
+
? options.filter((o) => String(o.label).toLowerCase().includes(search.toLowerCase()))
|
|
36
|
+
: options;
|
|
37
|
+
// Label shown inside the MUI TextField (suppressed in 'standard' variant)
|
|
38
|
+
const muiLabel = variant !== 'standard'
|
|
39
|
+
? `${props.item.label}${props.item.required ? ' *' : ''}`
|
|
40
|
+
: '';
|
|
41
|
+
return (jsxs(FormControl, { fullWidth: true, sx: { position: 'relative' }, disabled: props.item.disable, children: [renderLabel(variant, props), jsx(Box, { onClick: handleOpen, sx: { cursor: props.item.disable ? 'default' : 'pointer' }, children: jsx(TextField, { fullWidth: true, value: displayValue, label: muiLabel, placeholder: props.item.placeholder || 'Select…', error: isError, size: "small", InputProps: {
|
|
42
|
+
readOnly: true,
|
|
43
|
+
endAdornment: (jsx(InputAdornment, { position: "end", children: jsx(SearchIcon, { sx: { fontSize: 16, color: 'text.disabled' } }) })),
|
|
44
|
+
sx: { cursor: 'inherit' },
|
|
45
|
+
}, inputProps: {
|
|
46
|
+
'aria-labelledby': `${props.item.name}-label`,
|
|
47
|
+
'aria-describedby': `${props.item.name}-helper ${props.item.name}-error`,
|
|
48
|
+
'aria-required': props.item.required ? true : undefined,
|
|
49
|
+
style: { cursor: 'inherit' },
|
|
50
|
+
}, sx: {
|
|
51
|
+
'& .MuiFormLabel-root': { top: -10 },
|
|
52
|
+
'& .MuiInputLabel-shrink': { top: 0 },
|
|
53
|
+
} }) }), jsxs(Popover, { open: Boolean(anchorEl), anchorEl: anchorEl, onClose: handleClose, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: {
|
|
54
|
+
sx: {
|
|
55
|
+
mt: 0.5,
|
|
56
|
+
borderRadius: 2,
|
|
57
|
+
boxShadow: 6,
|
|
58
|
+
// Match the width of the trigger field
|
|
59
|
+
width: anchorEl?.offsetWidth ?? 280,
|
|
60
|
+
display: 'flex',
|
|
61
|
+
flexDirection: 'column',
|
|
62
|
+
},
|
|
63
|
+
}, children: [jsx(Box, { sx: { px: 1.5, pt: 1.5, pb: 0.75 }, children: jsx(TextField, { size: "small", fullWidth: true, autoFocus: true, placeholder: "Search\u2026", value: search, onChange: (e) => setSearch(e.target.value), onKeyDown: (e) => {
|
|
64
|
+
if (e.key === 'Escape')
|
|
65
|
+
handleClose();
|
|
66
|
+
}, InputProps: {
|
|
67
|
+
startAdornment: (jsx(InputAdornment, { position: "start", children: jsx(SearchIcon, { sx: { fontSize: 16, color: 'text.disabled' } }) })),
|
|
68
|
+
}, sx: { '& .MuiOutlinedInput-root': { borderRadius: 2, fontSize: 13 } } }) }), jsx(List, { dense: true, disablePadding: true, sx: { maxHeight: 260, overflowY: 'auto', pb: 0.75 }, children: filtered.length === 0 ? (jsx(Box, { sx: { px: 2, py: 1.5 }, children: jsxs(Typography, { variant: "caption", color: "text.disabled", children: ["No options match \"", search, "\""] }) })) : (filtered.map((opt) => {
|
|
69
|
+
const isSelected = String(value ?? '') === String(opt.value);
|
|
70
|
+
return (jsxs(ListItemButton, { onClick: () => handleSelect(opt.value), selected: isSelected, disabled: opt.disable, sx: { px: 2, py: 0.5 }, children: [jsx(ListItemText, { primary: String(opt.label), primaryTypographyProps: {
|
|
71
|
+
fontSize: 13,
|
|
72
|
+
color: isSelected ? 'primary.main' : 'text.primary',
|
|
73
|
+
fontWeight: isSelected ? 600 : 400,
|
|
74
|
+
} }), isSelected && (jsx(CheckIcon, { sx: { fontSize: 15, color: 'primary.main', flexShrink: 0 } }))] }, String(opt.value)));
|
|
75
|
+
})) })] }), jsx(FormBottomField, { ...props })] }, props.item.name));
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export { SearchableSelect as default };
|
|
@@ -8,6 +8,7 @@ export { default as SingleSelectNonAutoComplete } from './Select/SingleSelectNon
|
|
|
8
8
|
export { default as SingleSelectSearchApi } from './Select/SingleSelectSearchApi';
|
|
9
9
|
export { default as MultiSelectV1 } from './Select/MultiSelectv1';
|
|
10
10
|
export { default as MultiSelectTable } from './Select/MultiSelectTable';
|
|
11
|
+
export { default as SearchableSelect } from './Select/SearchableSelect';
|
|
11
12
|
export { FormCheckBoxGroup } from '../CheckBoxGroup';
|
|
12
13
|
export { FormRadioGroup } from '../RadioGroup';
|
|
13
14
|
export { DatepickerWrapperV2, Monthpickerrender, YearPickerField, } from '../DatePicker';
|
|
@@ -1,38 +1,25 @@
|
|
|
1
|
-
export { default as FormNumberField } from './FormNumberField/FormNumberField.esm.js';
|
|
2
|
-
export { default as FormNumberFieldDecimal } from './FormNumberField/FormNumberFieldDecimal.esm.js';
|
|
3
|
-
export { default as FormTextAreaField } from './FormTextAreaField/FormTextAreaField.esm.js';
|
|
4
|
-
export { default as FormTextField } from './FormTextField/FormTextField.esm.js';
|
|
5
|
-
export { default as PasswordField } from './PasswordField/PasswordField.esm.js';
|
|
6
|
-
export { default as FormCheckBox } from './FormCheckBox/FormCheckBox.esm.js';
|
|
7
1
|
export { default as SingleSelect } from './Select/SingleSelect.esm.js';
|
|
8
2
|
export { default as SingleSelectNonAutoComplete } from './Select/SingleSelectNonAutoComplete.esm.js';
|
|
9
3
|
export { default as SingleSelectSearchApi } from './Select/SingleSelectSearchApi.esm.js';
|
|
10
4
|
export { default as MultiSelectV1 } from './Select/MultiSelectv1.esm.js';
|
|
11
5
|
export { default as MultiSelectTable } from './Select/MultiSelectTable.esm.js';
|
|
6
|
+
export { default as SearchableSelect } from './Select/SearchableSelect.esm.js';
|
|
7
|
+
export { default as DatepickerWrapperV2 } from './DatePicker/DatepickerWrapperV2.esm.js';
|
|
8
|
+
export { default as FormBottomField } from './FormBottomField/FormBottomField.esm.js';
|
|
9
|
+
export { default as FormCheckBox } from './FormCheckBox/FormCheckBox.esm.js';
|
|
12
10
|
export { default as FormCheckBoxGroup } from './FormCheckBoxGroup/FormCheckBoxGroup.esm.js';
|
|
11
|
+
export { default as FormErrorField } from './FormErrorField/FormErrorField.esm.js';
|
|
12
|
+
export { default as FormNumberField } from './FormNumberField/FormNumberField.esm.js';
|
|
13
|
+
export { default as FormNumberFieldDecimal } from './FormNumberField/FormNumberFieldDecimal.esm.js';
|
|
13
14
|
export { default as FormRadioGroup } from './FormRadioGroup/FormRadioGroup.esm.js';
|
|
14
|
-
export { default as DatepickerWrapperV2 } from './DatePicker/DatepickerWrapperV2.esm.js';
|
|
15
|
-
export { default as Monthpickerrender } from './DatePicker/Monthpickerrender.esm.js';
|
|
16
|
-
export { default as YearPickerField } from './YearPickerField/YearPickerField.esm.js';
|
|
17
|
-
export { default as TimePicker } from './TimePicker/TimePicker.esm.js';
|
|
18
15
|
export { default as FormRenderFileUpload } from './FileUpload/FormRenderFileUpload.esm.js';
|
|
19
16
|
export { default as FormRenderMultiFileUpload } from './FileUpload/FormRenderMultiFileUpload.esm.js';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import 'react';
|
|
23
|
-
import 'react-hook-form';
|
|
24
|
-
import 'dayjs';
|
|
25
|
-
export { default as FormBottomField } from './FormBottomField/FormBottomField.esm.js';
|
|
26
|
-
import '@mui/icons-material/Visibility';
|
|
27
|
-
import '@mui/icons-material/Close';
|
|
28
|
-
import '@mui/icons-material/NavigateBefore';
|
|
29
|
-
import '@mui/icons-material/NavigateNext';
|
|
30
|
-
import '@mui/icons-material/Delete';
|
|
31
|
-
import '@mui/icons-material/UploadFile';
|
|
32
|
-
import '@mui/icons-material/PictureAsPdf';
|
|
33
|
-
import '@mui/icons-material/TableChart';
|
|
34
|
-
import '@mui/icons-material/Description';
|
|
35
|
-
export { default as FormErrorField } from './FormErrorField/FormErrorField.esm.js';
|
|
17
|
+
export { default as FormTextAreaField } from './FormTextAreaField/FormTextAreaField.esm.js';
|
|
18
|
+
export { default as FormTextField } from './FormTextField/FormTextField.esm.js';
|
|
36
19
|
export { default as HelperText } from './HelperText/HelperText.esm.js';
|
|
20
|
+
export { default as Monthpickerrender } from './DatePicker/Monthpickerrender.esm.js';
|
|
21
|
+
export { default as PasswordField } from './PasswordField/PasswordField.esm.js';
|
|
37
22
|
export { default as RichTextEditor } from './RichTextEditor/RichTextEditor.esm.js';
|
|
38
23
|
export { default as RichTextEditorWrapper } from './RichTextEditor/RichTextEditorWrapper.esm.js';
|
|
24
|
+
export { default as TimePicker } from './TimePicker/TimePicker.esm.js';
|
|
25
|
+
export { default as YearPickerField } from './YearPickerField/YearPickerField.esm.js';
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import 'react-date-range/dist/styles.css';
|
|
2
|
-
import 'react-date-range/dist/theme/default.css';
|
|
3
1
|
import type { MRT_ColumnFiltersState } from 'material-react-table';
|
|
4
2
|
/**
|
|
5
3
|
* Describes one server-side filter chip.
|
|
6
4
|
*
|
|
7
|
-
* - `dateRange`
|
|
8
|
-
* - `text`
|
|
9
|
-
* - `dropdown`
|
|
10
|
-
*
|
|
11
|
-
* - `
|
|
5
|
+
* - `dateRange` — two-date popover. `fromKey` / `toKey` map to `filterValues`.
|
|
6
|
+
* - `text` — single text-input popover. `filterKey` maps to `filterValues`.
|
|
7
|
+
* - `dropdown` — single-select popover with a fixed list of `options`.
|
|
8
|
+
* `filterKey` maps to `filterValues`.
|
|
9
|
+
* - `searchableDropdown` — like `dropdown` but the popover has a search box so users can
|
|
10
|
+
* quickly filter long option lists. Same `filterKey` / `options`
|
|
11
|
+
* shape as `dropdown`.
|
|
12
|
+
* - `date` — single date-picker chip. Applied client-side via MRT `columnFilters`.
|
|
12
13
|
*/
|
|
13
14
|
export type ServerFilterDef = {
|
|
14
15
|
type: 'dateRange';
|
|
@@ -31,6 +32,20 @@ export type ServerFilterDef = {
|
|
|
31
32
|
label: string;
|
|
32
33
|
value: string;
|
|
33
34
|
}[];
|
|
35
|
+
} | {
|
|
36
|
+
/**
|
|
37
|
+
* Like `dropdown` but adds a search input at the top of the popover so users
|
|
38
|
+
* can filter through long option lists by typing.
|
|
39
|
+
*/
|
|
40
|
+
type: 'searchableDropdown';
|
|
41
|
+
id: string;
|
|
42
|
+
label: string;
|
|
43
|
+
filterKey: string;
|
|
44
|
+
options: {
|
|
45
|
+
label: string;
|
|
46
|
+
value: string;
|
|
47
|
+
}[];
|
|
48
|
+
placeholder?: string;
|
|
34
49
|
} | {
|
|
35
50
|
/**
|
|
36
51
|
* Single-date picker chip — applied client-side against the MRT column
|
|
@@ -44,6 +59,17 @@ export type ServerFilterDef = {
|
|
|
44
59
|
interface AvailableColumn {
|
|
45
60
|
id: string;
|
|
46
61
|
label: string;
|
|
62
|
+
/**
|
|
63
|
+
* Filter chip variant used in the "Column Filters" section of Manage Filters.
|
|
64
|
+
* Sourced from the `filterType` prop on the column's `TPColumnDef`.
|
|
65
|
+
* Defaults to `'text'` when omitted.
|
|
66
|
+
*/
|
|
67
|
+
filterType?: 'text' | 'date' | 'select' | 'searchable-select' | 'number';
|
|
68
|
+
/** Options for `'select'` / `'searchable-select'` chip variants. */
|
|
69
|
+
filterOptions?: {
|
|
70
|
+
label: string;
|
|
71
|
+
value: string;
|
|
72
|
+
}[];
|
|
47
73
|
}
|
|
48
74
|
export interface DynamicFilterBarProps {
|
|
49
75
|
serverFilterDefs: ServerFilterDef[];
|
|
@@ -4,25 +4,39 @@ import { useForm } from 'react-hook-form';
|
|
|
4
4
|
import dayjs from 'dayjs';
|
|
5
5
|
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
6
6
|
import { d as distExports } from '../../_virtual/index.esm.js';
|
|
7
|
-
import
|
|
7
|
+
import { useRdrStyles } from '../../utils/rdrStyles.esm.js';
|
|
8
|
+
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
|
9
|
+
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
|
10
|
+
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
|
8
11
|
import SingleSelectNonAutoComplete from '../FormComponents/Select/SingleSelectNonAutoComplete.esm.js';
|
|
9
|
-
import { Box, Typography, Button, Popover, TextField, Stack,
|
|
12
|
+
import { Box, Typography, Button, Popover, TextField, Stack, InputAdornment, List, ListItemButton, ListItemText, Tooltip, Divider } from '@mui/material';
|
|
10
13
|
import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
|
|
11
14
|
import TuneIcon from '@mui/icons-material/Tune';
|
|
12
15
|
import CloseIcon from '@mui/icons-material/Close';
|
|
13
16
|
import FilterListIcon from '@mui/icons-material/FilterList';
|
|
14
17
|
import CheckIcon from '@mui/icons-material/Check';
|
|
15
18
|
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
|
19
|
+
import SearchIcon from '@mui/icons-material/Search';
|
|
16
20
|
|
|
17
21
|
// Enable strict-format parsing so dayjs can handle the DD/MM/YYYY strings
|
|
18
22
|
// that DatepickerWrapperV2 passes to onChangeFn
|
|
19
23
|
dayjs.extend(customParseFormat);
|
|
20
24
|
// ─── Type pill metadata ───────────────────────────────────────────────────────
|
|
25
|
+
// Server-side filter pills (used in SERVER FILTERS section of Manage Filters)
|
|
21
26
|
const TYPE_PILL = {
|
|
22
27
|
dateRange: { label: 'Range', color: '#1565c0', bg: '#e3f2fd' },
|
|
23
28
|
date: { label: 'Date', color: '#00695c', bg: '#e0f2f1' },
|
|
24
29
|
text: { label: 'Text', color: '#2e7d32', bg: '#e8f5e9' },
|
|
25
30
|
dropdown: { label: 'Select', color: '#6a1b9a', bg: '#f3e5f5' },
|
|
31
|
+
searchableDropdown: { label: 'Search', color: '#bf360c', bg: '#fbe9e7' },
|
|
32
|
+
};
|
|
33
|
+
// Column filter type pills (used in COLUMN FILTERS section of Manage Filters)
|
|
34
|
+
const COLUMN_TYPE_PILL = {
|
|
35
|
+
text: { label: 'Text', color: '#2e7d32', bg: '#e8f5e9' },
|
|
36
|
+
date: { label: 'Date', color: '#00695c', bg: '#e0f2f1' },
|
|
37
|
+
select: { label: 'Select', color: '#bf360c', bg: '#fbe9e7' },
|
|
38
|
+
'searchable-select': { label: 'Search', color: '#bf360c', bg: '#fbe9e7' },
|
|
39
|
+
number: { label: 'Number', color: '#e65100', bg: '#fff3e0' },
|
|
26
40
|
};
|
|
27
41
|
// ─── Chip shared style ────────────────────────────────────────────────────────
|
|
28
42
|
const chipSx = (active) => ({
|
|
@@ -49,6 +63,9 @@ const fmtDate = (iso) => {
|
|
|
49
63
|
return `${Number(d)} ${months[Number(m) - 1]}${yearSuffix}`;
|
|
50
64
|
};
|
|
51
65
|
const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove }) => {
|
|
66
|
+
// Inject react-date-range CSS via JS (not CSS import) to prevent it from being
|
|
67
|
+
// bundled into the library's extracted CSS file and leaking into consumer apps.
|
|
68
|
+
useRdrStyles();
|
|
52
69
|
const [anchor, setAnchor] = useState(null);
|
|
53
70
|
const active = Boolean(fromDate || toDate);
|
|
54
71
|
const chipLabel = active ? `${fmtDate(fromDate)} – ${fmtDate(toDate)}` : label;
|
|
@@ -103,41 +120,35 @@ const TextFilterChip = ({ label, value, onApply, onRemove, placeholder }) => {
|
|
|
103
120
|
} }), jsxs(Stack, { direction: "row", spacing: 1, justifyContent: "flex-end", mt: 1.5, children: [active && (jsx(Button, { size: "small", color: "inherit", onClick: () => { onApply(''); setAnchor(null); }, children: "Clear" })), jsx(Button, { size: "small", variant: "contained", disableElevation: true, onClick: () => { onApply(draft); setAnchor(null); }, children: "Apply" })] })] }) })] }));
|
|
104
121
|
};
|
|
105
122
|
const DatePickerChip = ({ label, value, onApply, onRemove }) => {
|
|
106
|
-
const [
|
|
123
|
+
const [open, setOpen] = useState(false);
|
|
124
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
107
125
|
const active = Boolean(value);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// • a "DD/MM/YYYY" formatted string — parse with strict mode
|
|
123
|
-
// • null / undefined — clear the filter
|
|
124
|
-
let iso = '';
|
|
125
|
-
if (!val) {
|
|
126
|
-
iso = '';
|
|
127
|
-
}
|
|
128
|
-
else if (typeof val.format === 'function') {
|
|
129
|
-
iso = val.format('YYYY-MM-DD');
|
|
130
|
-
}
|
|
131
|
-
else if (typeof val === 'string' && val.trim()) {
|
|
132
|
-
const parsed = dayjs(val.trim(), 'DD/MM/YYYY', true);
|
|
133
|
-
iso = parsed.isValid() ? parsed.format('YYYY-MM-DD') : '';
|
|
134
|
-
}
|
|
135
|
-
onApply(iso);
|
|
136
|
-
// Auto-close the popover as soon as a date is picked — no need to click "Done"
|
|
137
|
-
setAnchor(null);
|
|
138
|
-
},
|
|
126
|
+
// dayjs value expected by MUI X DatePicker (null = no selection)
|
|
127
|
+
const dayjsValue = value ? dayjs(value, 'YYYY-MM-DD') : null;
|
|
128
|
+
const handleChipClick = (e) => {
|
|
129
|
+
setAnchorEl(e.currentTarget);
|
|
130
|
+
setOpen(true);
|
|
131
|
+
};
|
|
132
|
+
const handleChange = (newVal) => {
|
|
133
|
+
if (newVal && dayjs.isDayjs(newVal) && newVal.isValid()) {
|
|
134
|
+
onApply(newVal.format('YYYY-MM-DD'));
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
onApply('');
|
|
138
|
+
}
|
|
139
|
+
setOpen(false);
|
|
139
140
|
};
|
|
140
|
-
return (
|
|
141
|
+
return (
|
|
142
|
+
// LocalizationProvider must wrap DatePicker — scoped here so it only
|
|
143
|
+
// loads when the chip is actually used (DatePickerChip is tree-shaken
|
|
144
|
+
// if the 'date' filter type is never included in serverFilterDefs).
|
|
145
|
+
jsxs(LocalizationProvider, { dateAdapter: AdapterDayjs, children: [jsx(Button, { variant: "outlined", size: "small", onClick: handleChipClick, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active), children: active ? `${label}: ${fmtDate(value)}` : label }), jsx(DatePicker, { value: dayjsValue, open: open, onClose: () => setOpen(false), onChange: handleChange, slotProps: {
|
|
146
|
+
textField: { sx: { display: 'none' }, size: 'small' },
|
|
147
|
+
popper: {
|
|
148
|
+
anchorEl,
|
|
149
|
+
placement: 'bottom-start',
|
|
150
|
+
},
|
|
151
|
+
} })] }));
|
|
141
152
|
};
|
|
142
153
|
const DropdownFilterChip = ({ label, value, options, onApply, onRemove }) => {
|
|
143
154
|
const [anchor, setAnchor] = useState(null);
|
|
@@ -180,6 +191,41 @@ const DropdownFilterChip = ({ label, value, options, onApply, onRemove }) => {
|
|
|
180
191
|
watch,
|
|
181
192
|
}, variant: "outlined" }) })] }));
|
|
182
193
|
};
|
|
194
|
+
const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeholder = 'Search…', }) => {
|
|
195
|
+
const [anchor, setAnchor] = useState(null);
|
|
196
|
+
const [search, setSearch] = useState('');
|
|
197
|
+
const active = Boolean(value);
|
|
198
|
+
const selectedLabel = options.find((o) => o.value === value)?.label ?? '';
|
|
199
|
+
const open = (e) => {
|
|
200
|
+
setSearch(''); // always start with all options visible
|
|
201
|
+
setAnchor(e.currentTarget);
|
|
202
|
+
};
|
|
203
|
+
const select = (val) => {
|
|
204
|
+
onApply(val);
|
|
205
|
+
setAnchor(null);
|
|
206
|
+
};
|
|
207
|
+
// Case-insensitive filter; always show "All" row on top
|
|
208
|
+
const filtered = search
|
|
209
|
+
? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()))
|
|
210
|
+
: options;
|
|
211
|
+
return (jsxs(Fragment, { children: [jsx(Button, { variant: "outlined", size: "small", onClick: open, startIcon: jsx(SearchIcon, { sx: { fontSize: '14px !important', mr: -0.25 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active), children: active ? `${label}: ${selectedLabel}` : label }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: {
|
|
212
|
+
sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, width: 280, display: 'flex', flexDirection: 'column' },
|
|
213
|
+
}, children: [jsx(Box, { sx: { px: 1.5, pt: 1.5, pb: 0.75 }, children: jsx(TextField, { size: "small", fullWidth: true, autoFocus: true, placeholder: placeholder, value: search, onChange: (e) => setSearch(e.target.value), onKeyDown: (e) => { if (e.key === 'Escape')
|
|
214
|
+
setAnchor(null); }, InputProps: {
|
|
215
|
+
startAdornment: (jsx(InputAdornment, { position: "start", children: jsx(SearchIcon, { sx: { fontSize: 16, color: 'text.disabled' } }) })),
|
|
216
|
+
}, sx: { '& .MuiOutlinedInput-root': { borderRadius: 2, fontSize: 13 } } }) }), jsxs(List, { dense: true, disablePadding: true, sx: { maxHeight: 260, overflowY: 'auto', pb: 0.75 }, children: [!search && (jsxs(ListItemButton, { onClick: () => select(''), selected: !active, sx: { px: 2, py: 0.5 }, children: [jsx(ListItemText, { primary: "All", primaryTypographyProps: {
|
|
217
|
+
fontSize: 13,
|
|
218
|
+
color: !active ? 'primary.main' : 'text.secondary',
|
|
219
|
+
fontStyle: 'italic',
|
|
220
|
+
} }), !active && jsx(CheckIcon, { sx: { fontSize: 15, color: 'primary.main', flexShrink: 0 } })] })), filtered.length === 0 ? (jsx(Box, { sx: { px: 2, py: 1.5 }, children: jsxs(Typography, { variant: "caption", color: "text.disabled", children: ["No options match \"", search, "\""] }) })) : (filtered.map((opt) => {
|
|
221
|
+
const isSelected = value === opt.value;
|
|
222
|
+
return (jsxs(ListItemButton, { onClick: () => select(opt.value), selected: isSelected, sx: { px: 2, py: 0.5 }, children: [jsx(ListItemText, { primary: opt.label, primaryTypographyProps: {
|
|
223
|
+
fontSize: 13,
|
|
224
|
+
color: isSelected ? 'primary.main' : 'text.primary',
|
|
225
|
+
fontWeight: isSelected ? 600 : 400,
|
|
226
|
+
} }), isSelected && (jsx(CheckIcon, { sx: { fontSize: 15, color: 'primary.main', flexShrink: 0 } }))] }, opt.value));
|
|
227
|
+
}))] })] })] }));
|
|
228
|
+
};
|
|
183
229
|
const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, }) => {
|
|
184
230
|
const [anchor, setAnchor] = useState(null);
|
|
185
231
|
return (jsxs(Fragment, { children: [jsx(Tooltip, { title: "Add or remove filter chips", children: jsx(Button, { size: "small", variant: "outlined", startIcon: jsx(TuneIcon, { sx: { fontSize: 14 } }), onClick: (e) => setAnchor(e.currentTarget), sx: {
|
|
@@ -206,17 +252,20 @@ const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, o
|
|
|
206
252
|
} }), selected && jsx(CheckIcon, { sx: { fontSize: 16, color: 'primary.main', flexShrink: 0 } })] }, def.id));
|
|
207
253
|
}) })] })), availableColumns.length > 0 && (jsxs(Fragment, { children: [jsx(Divider, { sx: { my: 1 } }), jsx(Typography, { variant: "caption", fontWeight: 700, color: "text.disabled", sx: { px: 2, pb: 0.5, display: 'block', textTransform: 'uppercase', letterSpacing: 0.5 }, children: "Column Filters" }), jsx(List, { dense: true, disablePadding: true, sx: { maxHeight: 260, overflowY: 'auto' }, children: availableColumns.map((col) => {
|
|
208
254
|
const selected = visibleIds.includes(col.id);
|
|
255
|
+
// Pick the type pill based on the column's declared filter type
|
|
256
|
+
const pill = COLUMN_TYPE_PILL[col.filterType ?? 'text'] ??
|
|
257
|
+
COLUMN_TYPE_PILL.text;
|
|
209
258
|
return (jsxs(ListItemButton, { onClick: () => onToggle(col.id), sx: { px: 2, py: 0.4 }, children: [jsx(Box, { component: "span", sx: {
|
|
210
259
|
mr: 1.25, flexShrink: 0,
|
|
211
260
|
fontSize: 10, fontWeight: 700,
|
|
212
261
|
px: 0.75, py: '2px',
|
|
213
262
|
borderRadius: '4px',
|
|
214
|
-
bgcolor:
|
|
215
|
-
color:
|
|
263
|
+
bgcolor: pill.bg,
|
|
264
|
+
color: pill.color,
|
|
216
265
|
textTransform: 'uppercase',
|
|
217
266
|
letterSpacing: 0.4,
|
|
218
267
|
lineHeight: 1.6,
|
|
219
|
-
}, children:
|
|
268
|
+
}, children: pill.label }), jsx(ListItemText, { primary: col.label, primaryTypographyProps: {
|
|
220
269
|
fontSize: 13,
|
|
221
270
|
color: selected ? 'primary.main' : 'text.primary',
|
|
222
271
|
} }), selected && jsx(CheckIcon, { sx: { fontSize: 16, color: 'primary.main', flexShrink: 0 } })] }, col.id));
|
|
@@ -233,7 +282,9 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
|
|
|
233
282
|
if (def?.type === 'dateRange') {
|
|
234
283
|
onFilterValuesChange({ ...filterValues, [def.fromKey]: '', [def.toKey]: '' });
|
|
235
284
|
}
|
|
236
|
-
else if (def?.type === 'text' ||
|
|
285
|
+
else if (def?.type === 'text' ||
|
|
286
|
+
def?.type === 'dropdown' ||
|
|
287
|
+
def?.type === 'searchableDropdown') {
|
|
237
288
|
onFilterValuesChange({ ...filterValues, [def.filterKey]: '' });
|
|
238
289
|
}
|
|
239
290
|
else if (def?.type === 'date') {
|
|
@@ -255,13 +306,23 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
|
|
|
255
306
|
if (def?.type === 'dropdown') {
|
|
256
307
|
return (jsx(DropdownFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id) }, id));
|
|
257
308
|
}
|
|
309
|
+
if (def?.type === 'searchableDropdown') {
|
|
310
|
+
return (jsx(SearchableSelectChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder ?? 'Search…' }, id));
|
|
311
|
+
}
|
|
258
312
|
if (def?.type === 'date') {
|
|
259
313
|
return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id) }, id));
|
|
260
314
|
}
|
|
261
|
-
// Column filter chip
|
|
315
|
+
// Column filter chip — render the variant that matches the column's filterType
|
|
262
316
|
const col = availableColumns.find((c) => c.id === id);
|
|
263
317
|
if (!col)
|
|
264
318
|
return null;
|
|
319
|
+
if (col.filterType === 'date') {
|
|
320
|
+
return (jsx(DatePickerChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id) }, id));
|
|
321
|
+
}
|
|
322
|
+
if (col.filterType === 'select' || col.filterType === 'searchable-select') {
|
|
323
|
+
return (jsx(SearchableSelectChip, { label: col.label, value: getColValue(id), options: col.filterOptions ?? [], onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id) }, id));
|
|
324
|
+
}
|
|
325
|
+
// Default: free-text input
|
|
265
326
|
return (jsx(TextFilterChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), placeholder: `Filter by ${col.label}` }, id));
|
|
266
327
|
}), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter })] }));
|
|
267
328
|
};
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TPColumnDef } from './types';
|
|
2
2
|
import { type ServerFilterDef } from './DynamicFilterBar';
|
|
3
3
|
export type { ServerFilterDef };
|
|
4
|
+
export type { TPColumnDef } from './types';
|
|
4
5
|
/** Shape that fetchPage must return. */
|
|
5
6
|
export interface PagedResult<TData> {
|
|
6
7
|
data: TData[];
|
|
7
8
|
totalRecords: number;
|
|
8
9
|
}
|
|
9
10
|
export interface GenericPaginatedGridProps<TData extends object> {
|
|
10
|
-
/**
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Column definitions. Use `TPColumnDef<TData>` (a superset of `MRT_ColumnDef`)
|
|
13
|
+
* to get top-level `filterType` and `options` support with no `meta` boilerplate.
|
|
14
|
+
*
|
|
15
|
+
* The grid automatically injects the right `filterFn` for each `filterType`,
|
|
16
|
+
* and wires up the correct chip in Manage Filters → Column Filters.
|
|
17
|
+
*/
|
|
18
|
+
columns: TPColumnDef<TData>[];
|
|
12
19
|
/**
|
|
13
20
|
* Async function called on every page/filter change.
|
|
14
21
|
* Receives the current flat filter map and the 0-based page index.
|