tp-react-elements 1.3.0 → 1.3.2

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 (51) hide show
  1. package/dist/_virtual/index.esm12.js +2 -2
  2. package/dist/_virtual/index.esm13.js +2 -2
  3. package/dist/_virtual/index.esm14.js +2 -2
  4. package/dist/_virtual/index.esm5.js +2 -2
  5. package/dist/_virtual/index.esm7.js +2 -2
  6. package/dist/components/DatePicker/index.d.ts +0 -1
  7. package/dist/components/DatePicker/index.esm.js +0 -1
  8. package/dist/components/FormComponents/DatePicker/Monthpickerrender.esm.js +12 -2
  9. package/dist/components/FormComponents/FormTextAreaField/FormTextAreaField.esm.js +2 -2
  10. package/dist/components/FormComponents/PasswordField/PasswordField.esm.js +40 -40
  11. package/dist/components/FormComponents/index.d.ts +1 -1
  12. package/dist/components/FormComponents/index.esm.js +0 -1
  13. package/dist/components/PaginatedGrid/DynamicFilterBar.d.ts +59 -0
  14. package/dist/components/PaginatedGrid/DynamicFilterBar.esm.js +269 -0
  15. package/dist/components/PaginatedGrid/GenericPaginatedGrid.d.ts +40 -0
  16. package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +222 -0
  17. package/dist/components/PaginatedGrid/InvoiceGrid.d.ts +2 -0
  18. package/dist/components/PaginatedGrid/columns.d.ts +3 -0
  19. package/dist/components/PaginatedGrid/helpers.d.ts +3 -0
  20. package/dist/components/PaginatedGrid/index.d.ts +4 -0
  21. package/dist/components/PaginatedGrid/index.esm.js +2 -0
  22. package/dist/components/PaginatedGrid/invoiceApi.d.ts +2 -0
  23. package/dist/components/PaginatedGrid/types.d.ts +48 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.esm.js +1 -0
  26. package/dist/node_modules/@tanstack/query-core/build/modern/environmentManager.esm.js +22 -0
  27. package/dist/node_modules/@tanstack/query-core/build/modern/focusManager.esm.js +65 -0
  28. package/dist/node_modules/@tanstack/query-core/build/modern/notifyManager.esm.js +83 -0
  29. package/dist/node_modules/@tanstack/query-core/build/modern/onlineManager.esm.js +55 -0
  30. package/dist/node_modules/@tanstack/query-core/build/modern/query.esm.js +16 -0
  31. package/dist/node_modules/@tanstack/query-core/build/modern/queryObserver.esm.js +463 -0
  32. package/dist/node_modules/@tanstack/query-core/build/modern/retryer.esm.js +8 -0
  33. package/dist/node_modules/@tanstack/query-core/build/modern/subscribable.esm.js +24 -0
  34. package/dist/node_modules/@tanstack/query-core/build/modern/thenable.esm.js +34 -0
  35. package/dist/node_modules/@tanstack/query-core/build/modern/timeoutManager.esm.js +66 -0
  36. package/dist/node_modules/@tanstack/query-core/build/modern/utils.esm.js +115 -0
  37. package/dist/node_modules/@tanstack/react-query/build/modern/IsRestoringProvider.esm.js +7 -0
  38. package/dist/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.esm.js +15 -0
  39. package/dist/node_modules/@tanstack/react-query/build/modern/QueryErrorResetBoundary.esm.js +21 -0
  40. package/dist/node_modules/@tanstack/react-query/build/modern/errorBoundaryUtils.esm.js +27 -0
  41. package/dist/node_modules/@tanstack/react-query/build/modern/suspense.esm.js +22 -0
  42. package/dist/node_modules/@tanstack/react-query/build/modern/useBaseQuery.esm.js +94 -0
  43. package/dist/node_modules/@tanstack/react-query/build/modern/useQuery.esm.js +8 -0
  44. package/dist/node_modules/react-date-range/dist/components/Calendar/index.esm.js +1 -1
  45. package/dist/node_modules/react-date-range/dist/components/DateInput/index.esm.js +1 -1
  46. package/dist/node_modules/react-date-range/dist/components/DefinedRange/index.esm.js +1 -1
  47. package/dist/node_modules/react-date-range/dist/components/InputRangeField/index.esm.js +1 -1
  48. package/dist/node_modules/react-date-range/dist/components/Month/index.esm.js +1 -1
  49. package/package.json +2 -1
  50. package/dist/components/FormComponents/DatePicker/MonthPicker.d.ts +0 -2
  51. package/dist/components/FormComponents/DatePicker/MonthPicker.esm.js +0 -59
@@ -1,3 +1,3 @@
1
- var InputRangeField = {};
1
+ var Month = {};
2
2
 
3
- export { InputRangeField as __exports };
3
+ export { Month 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 InputRangeField = {};
2
2
 
3
- export { DateInput as __exports };
3
+ export { InputRangeField as __exports };
@@ -1,3 +1,3 @@
1
- var DefinedRange = {};
1
+ var Calendar = {};
2
2
 
3
- export { DefinedRange as __exports };
3
+ export { Calendar as __exports };
@@ -1,3 +1,3 @@
1
- var Calendar = {};
1
+ var DefinedRange = {};
2
2
 
3
- export { Calendar as __exports };
3
+ export { DefinedRange as __exports };
@@ -1,4 +1,3 @@
1
1
  export { default as DatepickerWrapperV2 } from '../FormComponents/DatePicker/DatepickerWrapperV2';
2
- export { default as MonthPicker } from '../FormComponents/DatePicker/MonthPicker';
3
2
  export { default as Monthpickerrender } from '../FormComponents/DatePicker/Monthpickerrender';
4
3
  export { default as YearPickerField } from '../FormComponents/YearPickerField/YearPickerField';
@@ -1,5 +1,4 @@
1
1
  import DatepickerWrapperV2 from '../FormComponents/DatePicker/DatepickerWrapperV2.esm.js';
2
- export { default as MonthPicker } from '../FormComponents/DatePicker/MonthPicker.esm.js';
3
2
  import Monthpickerrender from '../FormComponents/DatePicker/Monthpickerrender.esm.js';
4
3
  export { default as YearPickerField } from '../FormComponents/YearPickerField/YearPickerField.esm.js';
5
4
 
@@ -13,6 +13,7 @@ const Monthpickerrender = ({ props, variant, }) => {
13
13
  const [open, setOpen] = useState(false);
14
14
  const [DatePicker, setDatePicker] = useState(null);
15
15
  const [isLoading, setIsLoading] = useState(true);
16
+ const [view, setView] = useState('month');
16
17
  useEffect(() => {
17
18
  const loadComponents = async () => {
18
19
  setIsLoading(true);
@@ -35,7 +36,7 @@ const Monthpickerrender = ({ props, variant, }) => {
35
36
  return jsx("div", { style: { minHeight: '36px' }, children: "Loading..." });
36
37
  }
37
38
  return (jsx(Fragment, { children: DatePicker && (jsx(Controller, { control: props.control, name: props.item.name, render: ({ field }) => (jsxs(Fragment, { children: [jsxs(LocalizationProvider, { dateAdapter: AdapterDayjs, children: [renderLabel(variant, props), jsx(DatePicker, { ref: ref, label: variant !== 'standard' &&
38
- `${props.item.label}${props.item.required ? ' *' : ''}`, views: ['month', 'year'], disabled: props.item.disable, value: field.value ? dayjs(formatDateMonthAndYear(field.value || null)) : null, slotProps: {
39
+ `${props.item.label}${props.item.required ? ' *' : ''}`, views: ['year', 'month'], openTo: "month", view: view, onViewChange: (newView) => setView(newView), disabled: props.item.disable, value: field.value ? dayjs(formatDateMonthAndYear(field.value || null)) : null, slotProps: {
39
40
  textField: {
40
41
  onClick: () => handleToggle(),
41
42
  inputRef: ref,
@@ -45,10 +46,19 @@ const Monthpickerrender = ({ props, variant, }) => {
45
46
  style: { cursor: 'pointer' },
46
47
  },
47
48
  },
48
- }, disableOpenPicker: props.item.disable, open: open, onOpen: handleToggle, onClose: handleToggle, defaultValue: field.value, onChange: (date) => {
49
+ }, disableOpenPicker: props.item.disable, open: open, onOpen: () => {
50
+ setOpen(true);
51
+ setView('month');
52
+ }, onClose: () => setOpen(false), defaultValue: field.value, onChange: (date) => {
49
53
  const formatted = dayjs(date).format('MM/YYYY');
50
54
  field.onChange(formatted);
51
55
  props.setValue(props.item.name, formatted);
56
+ if (view === 'month') {
57
+ setOpen(false);
58
+ }
59
+ else if (view === 'year') {
60
+ setView('month');
61
+ }
52
62
  }, sx: {
53
63
  '& .css-1holvmy,.css-kichxs-MuiFormLabel-root-MuiInputLabel-root': {
54
64
  top: '-10px',
@@ -25,12 +25,12 @@ const FormTextAreaField = ({ props, variant, }) => {
25
25
  /**
26
26
  * Renders the textarea component with validation support
27
27
  */
28
- return (jsx(Fragment, { children: jsx(Controller, { control: props.control, name: props.item.name, render: ({ field, fieldState }) => (jsxs(Fragment, { children: [renderLabel(variant, props), jsx(Tooltip, { title: props.item.isTooltipEnable ? field.value || '' : '', placement: "bottom-start", children: jsxs(Box, { sx: { width: '100%', position: 'relative' }, children: [jsx(TextAreaField, { multiline: true, size: "small", InputProps: {
28
+ return (jsx(Fragment, { children: jsx(Controller, { control: props.control, name: props.item.name, render: ({ field, fieldState }) => (jsxs(Fragment, { children: [renderLabel(variant, props), jsx(Tooltip, { title: props.item.isTooltipEnable ? field.value || '' : '', placement: "bottom-start", children: jsxs(Box, { sx: { width: '100%', position: 'relative' }, children: [jsx(TextAreaField, { multiline: true, fullWidth: true, size: "small", InputProps: {
29
29
  style: {
30
30
  border: 'none',
31
31
  },
32
32
  }, sx: {
33
- ...props.item.sx,
33
+ ...props?.item?.sx,
34
34
  }, minRows: props.item.minRows || 1, maxRows: props.item.maxRows || 100, placeholder: props.item.placeholder || 'Type Something...', ...field, onChange: (e) => {
35
35
  field.onChange(e);
36
36
  props?.clearErrors && props?.clearErrors(props.item.name);
@@ -46,46 +46,46 @@ const PasswordField = ({ props, variant }) => {
46
46
  /**
47
47
  * Renders the password field component with visibility toggle
48
48
  */
49
- return (jsx(Box, { position: 'relative', children: jsx(FormControl, { fullWidth: true, children: jsx(Controller, { control: props.control, name: props.item.name, render: ({ field }) => (jsxs(Fragment, { children: [renderLabel(variant, props), jsx(TextField, { size: "small", error: isShowBorderError, type: showPassword ? 'text' : 'password', autoComplete: props.item?.InputProps?.autoComplete || 'off', placeholder: props.item.placeholder || '', ...field, label: label, sx: {
50
- width: '100%',
51
- '& .css-kichxs-MuiFormLabel-root-MuiInputLabel-root,.css-1holvmy': {
52
- top: '-8px',
53
- },
54
- }, inputProps: {
55
- maxLength: props.item.maxLength || 100,
56
- /**
57
- * Handles input events
58
- * Calls custom onInputProps handler if provided
59
- *
60
- * @param {any} e - The input event object
61
- */
62
- onInput: (e) => {
63
- props.item.onInputProps && props.item.onInputProps(e);
64
- },
65
- /**
66
- * Handles paste events
67
- * Prevents paste action if configured
68
- *
69
- * @param {React.ClipboardEvent} e - The paste event object
70
- */
71
- onPaste: (e) => {
72
- if (props.item?.doNotAllowPaste) {
73
- e.preventDefault();
74
- }
75
- },
76
- }, value: field.value || null, disabled: props.item.disable }), jsx(IconButton, { sx: {
77
- position: 'absolute',
78
- right: '14px',
79
- top: variant === 'standard' ? '65%' : '45%',
80
- transform: 'translateY(-50%)',
81
- width: '25px',
82
- }, "aria-label": showPassword ? 'Hide password' : 'Show password', "aria-pressed": showPassword, onClick: handleTogglePasswordVisibility, edge: "end", children: showPassword ? (jsx(Visibility, { sx: {
83
- fontSize: '12px',
84
- position: 'absolute',
85
- } })) : (jsx(VisibilityOff, { sx: {
86
- fontSize: '12px',
87
- position: 'absolute',
88
- } })) }), jsx(FormBottomField, { ...props })] })) }) }) }, props.item.name));
49
+ return (jsx(Box, { children: jsx(FormControl, { fullWidth: true, children: jsx(Controller, { control: props.control, name: props.item.name, render: ({ field }) => (jsxs(Fragment, { children: [jsxs(Box, { sx: { position: 'relative' }, children: [renderLabel(variant, props), jsx(TextField, { size: "small", error: isShowBorderError, type: showPassword ? 'text' : 'password', autoComplete: props.item?.InputProps?.autoComplete || 'off', placeholder: props.item.placeholder || '', ...field, label: label, sx: {
50
+ width: '100%',
51
+ '& .css-kichxs-MuiFormLabel-root-MuiInputLabel-root,.css-1holvmy': {
52
+ top: '-8px',
53
+ },
54
+ }, inputProps: {
55
+ maxLength: props.item.maxLength || 100,
56
+ /**
57
+ * Handles input events
58
+ * Calls custom onInputProps handler if provided
59
+ *
60
+ * @param {any} e - The input event object
61
+ */
62
+ onInput: (e) => {
63
+ props.item.onInputProps && props.item.onInputProps(e);
64
+ },
65
+ /**
66
+ * Handles paste events
67
+ * Prevents paste action if configured
68
+ *
69
+ * @param {React.ClipboardEvent} e - The paste event object
70
+ */
71
+ onPaste: (e) => {
72
+ if (props.item?.doNotAllowPaste) {
73
+ e.preventDefault();
74
+ }
75
+ },
76
+ }, value: field.value || null, disabled: props.item.disable }), jsx(IconButton, { sx: {
77
+ position: 'absolute',
78
+ right: '14px',
79
+ top: variant === 'standard' ? '70%' : '45%',
80
+ transform: 'translateY(-50%)',
81
+ width: '25px',
82
+ }, "aria-label": showPassword ? 'Hide password' : 'Show password', "aria-pressed": showPassword, onClick: handleTogglePasswordVisibility, edge: "end", children: showPassword ? (jsx(Visibility, { sx: {
83
+ fontSize: '12px',
84
+ position: 'absolute',
85
+ } })) : (jsx(VisibilityOff, { sx: {
86
+ fontSize: '12px',
87
+ position: 'absolute',
88
+ } })) })] }), jsx(FormBottomField, { ...props })] })) }) }) }, props.item.name));
89
89
  };
90
90
 
91
91
  export { PasswordField as default };
@@ -10,7 +10,7 @@ export { default as MultiSelectV1 } from './Select/MultiSelectv1';
10
10
  export { default as MultiSelectTable } from './Select/MultiSelectTable';
11
11
  export { FormCheckBoxGroup } from '../CheckBoxGroup';
12
12
  export { FormRadioGroup } from '../RadioGroup';
13
- export { DatepickerWrapperV2, MonthPicker, Monthpickerrender, YearPickerField, } from '../DatePicker';
13
+ export { DatepickerWrapperV2, Monthpickerrender, YearPickerField, } from '../DatePicker';
14
14
  export { TimePicker } from '../TimePicker';
15
15
  export { FormRenderFileUpload, FormRenderMultiFileUpload } from '../FileUpload';
16
16
  export { RichTextEditor, RichTextEditorWrapper } from '../RichTextEditor';
@@ -12,7 +12,6 @@ export { default as MultiSelectTable } from './Select/MultiSelectTable.esm.js';
12
12
  export { default as FormCheckBoxGroup } from './FormCheckBoxGroup/FormCheckBoxGroup.esm.js';
13
13
  export { default as FormRadioGroup } from './FormRadioGroup/FormRadioGroup.esm.js';
14
14
  export { default as DatepickerWrapperV2 } from './DatePicker/DatepickerWrapperV2.esm.js';
15
- export { default as MonthPicker } from './DatePicker/MonthPicker.esm.js';
16
15
  export { default as Monthpickerrender } from './DatePicker/Monthpickerrender.esm.js';
17
16
  export { default as YearPickerField } from './YearPickerField/YearPickerField.esm.js';
18
17
  export { default as TimePicker } from './TimePicker/TimePicker.esm.js';
@@ -0,0 +1,59 @@
1
+ import 'react-date-range/dist/styles.css';
2
+ import 'react-date-range/dist/theme/default.css';
3
+ import type { MRT_ColumnFiltersState } from 'material-react-table';
4
+ /**
5
+ * Describes one server-side filter chip.
6
+ *
7
+ * - `dateRange` — two-date popover. `fromKey` / `toKey` map to `filterValues`.
8
+ * - `text` — single text-input popover. `filterKey` maps to `filterValues`.
9
+ * - `dropdown` — single-select popover with a fixed list of `options`.
10
+ * `filterKey` maps to `filterValues`.
11
+ * - `date` — single date-picker chip. Applied client-side via MRT `columnFilters`.
12
+ */
13
+ export type ServerFilterDef = {
14
+ type: 'dateRange';
15
+ id: string;
16
+ label: string;
17
+ fromKey: string;
18
+ toKey: string;
19
+ } | {
20
+ type: 'text';
21
+ id: string;
22
+ label: string;
23
+ filterKey: string;
24
+ placeholder?: string;
25
+ } | {
26
+ type: 'dropdown';
27
+ id: string;
28
+ label: string;
29
+ filterKey: string;
30
+ options: {
31
+ label: string;
32
+ value: string;
33
+ }[];
34
+ } | {
35
+ /**
36
+ * Single-date picker chip — applied client-side against the MRT column
37
+ * whose `accessorKey` matches `columnKey`.
38
+ */
39
+ type: 'date';
40
+ id: string;
41
+ label: string;
42
+ columnKey: string;
43
+ };
44
+ interface AvailableColumn {
45
+ id: string;
46
+ label: string;
47
+ }
48
+ export interface DynamicFilterBarProps {
49
+ serverFilterDefs: ServerFilterDef[];
50
+ visibleFilterIds: string[];
51
+ onToggleFilter: (id: string) => void;
52
+ filterValues: Record<string, string>;
53
+ onFilterValuesChange: (values: Record<string, string>) => void;
54
+ columnFilters: MRT_ColumnFiltersState;
55
+ onColumnFilterChange: (f: MRT_ColumnFiltersState) => void;
56
+ availableColumns: AvailableColumn[];
57
+ }
58
+ declare const DynamicFilterBar: ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, }: DynamicFilterBarProps) => import("react/jsx-runtime").JSX.Element;
59
+ export default DynamicFilterBar;
@@ -0,0 +1,269 @@
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
+ import { useState, useEffect } from 'react';
3
+ import { useForm } from 'react-hook-form';
4
+ import dayjs from 'dayjs';
5
+ import customParseFormat from 'dayjs/plugin/customParseFormat';
6
+ import { d as distExports } from '../../_virtual/index.esm.js';
7
+ import DatepickerWrapperV2 from '../FormComponents/DatePicker/DatepickerWrapperV2.esm.js';
8
+ import SingleSelectNonAutoComplete from '../FormComponents/Select/SingleSelectNonAutoComplete.esm.js';
9
+ import { Box, Typography, Button, Popover, TextField, Stack, Tooltip, List, ListItemButton, ListItemText, Divider } from '@mui/material';
10
+ import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
11
+ import TuneIcon from '@mui/icons-material/Tune';
12
+ import CloseIcon from '@mui/icons-material/Close';
13
+ import FilterListIcon from '@mui/icons-material/FilterList';
14
+ import CheckIcon from '@mui/icons-material/Check';
15
+ import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
16
+
17
+ // Enable strict-format parsing so dayjs can handle the DD/MM/YYYY strings
18
+ // that DatepickerWrapperV2 passes to onChangeFn
19
+ dayjs.extend(customParseFormat);
20
+ // ─── Type pill metadata ───────────────────────────────────────────────────────
21
+ const TYPE_PILL = {
22
+ dateRange: { label: 'Range', color: '#1565c0', bg: '#e3f2fd' },
23
+ date: { label: 'Date', color: '#00695c', bg: '#e0f2f1' },
24
+ text: { label: 'Text', color: '#2e7d32', bg: '#e8f5e9' },
25
+ dropdown: { label: 'Select', color: '#6a1b9a', bg: '#f3e5f5' },
26
+ };
27
+ // ─── Chip shared style ────────────────────────────────────────────────────────
28
+ const chipSx = (active) => ({
29
+ borderRadius: 5,
30
+ textTransform: 'none',
31
+ fontSize: 12,
32
+ fontWeight: active ? 600 : 400,
33
+ borderColor: active ? 'primary.main' : 'divider',
34
+ color: active ? 'primary.main' : 'text.secondary',
35
+ bgcolor: active ? 'rgba(25,118,210,0.08)' : 'transparent',
36
+ whiteSpace: 'nowrap',
37
+ px: 1.5,
38
+ minHeight: 30,
39
+ '&:hover': { borderColor: 'primary.main', bgcolor: 'rgba(25,118,210,0.08)' },
40
+ });
41
+ // ─── Date helper ──────────────────────────────────────────────────────────────
42
+ const fmtDate = (iso) => {
43
+ if (!iso)
44
+ return '';
45
+ const [y, m, d] = iso.split('-');
46
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
47
+ const thisYear = new Date().getFullYear().toString();
48
+ const yearSuffix = y !== thisYear ? ` '${y.slice(2)}` : '';
49
+ return `${Number(d)} ${months[Number(m) - 1]}${yearSuffix}`;
50
+ };
51
+ const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove }) => {
52
+ const [anchor, setAnchor] = useState(null);
53
+ const active = Boolean(fromDate || toDate);
54
+ const chipLabel = active ? `${fmtDate(fromDate)} – ${fmtDate(toDate)}` : label;
55
+ // Convert ISO "YYYY-MM-DD" → Date, using noon to avoid timezone day-shift
56
+ const isoToDate = (iso) => iso ? new Date(iso + 'T12:00:00') : new Date();
57
+ // Build the Range array expected by react-date-range
58
+ const buildRange = () => [{
59
+ startDate: fromDate ? isoToDate(fromDate) : undefined,
60
+ endDate: toDate ? isoToDate(toDate) : undefined,
61
+ key: 'selection',
62
+ }];
63
+ const [selRange, setSelRange] = useState(buildRange);
64
+ // Sync when the parent resets values (e.g. Clear All)
65
+ useEffect(() => {
66
+ setSelRange(buildRange());
67
+ }, [fromDate, toDate]); // eslint-disable-line react-hooks/exhaustive-deps
68
+ const openPicker = (e) => {
69
+ setSelRange(buildRange());
70
+ setAnchor(e.currentTarget);
71
+ };
72
+ // Convert a Date to ISO "YYYY-MM-DD" without relying on toISOString (avoids UTC shift)
73
+ const toISO = (d) => {
74
+ const y = d.getFullYear();
75
+ const m = String(d.getMonth() + 1).padStart(2, '0');
76
+ const dd = String(d.getDate()).padStart(2, '0');
77
+ return `${y}-${m}-${dd}`;
78
+ };
79
+ const handleRangeChange = (ranges) => {
80
+ const sel = ranges.selection;
81
+ setSelRange([sel]);
82
+ // Auto-apply & close as soon as a complete range is chosen (start ≠ end day)
83
+ if (sel.startDate &&
84
+ sel.endDate &&
85
+ sel.startDate.getTime() !== sel.endDate.getTime()) {
86
+ onApply(toISO(sel.startDate), toISO(sel.endDate));
87
+ setAnchor(null);
88
+ }
89
+ };
90
+ return (jsxs(Fragment, { children: [jsx(Button, { variant: "outlined", size: "small", onClick: openPicker, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active), children: chipLabel }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, overflow: 'hidden' } }, children: [jsx(distExports.DateRange, { editableDateInputs: false, onChange: handleRangeChange, moveRangeOnFirstSelection: false, ranges: selRange, months: 2, direction: "horizontal", showDateDisplay: false, dateDisplayFormat: "dd/MM/yyyy", rangeColors: ['#1976d2'] }), active && (jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end', px: 2, pb: 1.5 }, children: jsx(Button, { size: "small", color: "inherit", onClick: () => { onApply('', ''); setAnchor(null); }, children: "Clear" }) }))] })] }));
91
+ };
92
+ const TextFilterChip = ({ label, value, onApply, onRemove, placeholder }) => {
93
+ const [anchor, setAnchor] = useState(null);
94
+ const [draft, setDraft] = useState('');
95
+ const active = Boolean(value);
96
+ return (jsxs(Fragment, { children: [jsx(Button, { variant: "outlined", size: "small", onClick: (e) => { setDraft(value); setAnchor(e.currentTarget); }, endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active), children: active ? `${label}: ${value}` : label }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, minWidth: 240 } }, children: jsxs(Box, { sx: { p: 2 }, children: [jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", display: "block", mb: 1.5, children: label }), jsx(TextField, { value: draft, onChange: (e) => setDraft(e.target.value), placeholder: placeholder, size: "small", fullWidth: true, autoFocus: true, onKeyDown: (e) => {
97
+ if (e.key === 'Enter') {
98
+ onApply(draft);
99
+ setAnchor(null);
100
+ }
101
+ if (e.key === 'Escape')
102
+ setAnchor(null);
103
+ } }), 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
+ };
105
+ const DatePickerChip = ({ label, value, onApply, onRemove }) => {
106
+ const [anchor, setAnchor] = useState(null);
107
+ const active = Boolean(value);
108
+ const { register, control, getValues, clearErrors, setValue, watch } = useForm({
109
+ defaultValues: { date: value || null },
110
+ });
111
+ // Sync form value whenever the external value prop changes (e.g. on Clear All)
112
+ useEffect(() => {
113
+ setValue('date', value || null);
114
+ }, [value, setValue]);
115
+ const item = {
116
+ name: 'date',
117
+ label,
118
+ inputType: 'datepicker',
119
+ onChangeFn: (val) => {
120
+ // DatepickerWrapperV2 calls onChangeFn with one of:
121
+ // • a dayjs-like object (has .format()) — use it directly
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
+ },
139
+ };
140
+ return (jsxs(Fragment, { children: [jsx(Button, { variant: "outlined", size: "small", onClick: (e) => setAnchor(e.currentTarget), 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 }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, p: 2, minWidth: 260 } }, children: [jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", display: "block", mb: 1.5, children: label }), jsx(DatepickerWrapperV2, { props: { item, register, control, errors: {}, getValues, clearErrors, setValue, watch }, variant: "outlined" }), jsxs(Stack, { direction: "row", spacing: 1, justifyContent: "flex-end", mt: 1.5, children: [active && (jsx(Button, { size: "small", color: "inherit", onClick: () => { onApply(''); setValue('date', null); setAnchor(null); }, children: "Clear" })), jsx(Button, { size: "small", variant: "contained", disableElevation: true, onClick: () => setAnchor(null), children: "Done" })] })] })] }));
141
+ };
142
+ const DropdownFilterChip = ({ label, value, options, onApply, onRemove }) => {
143
+ const [anchor, setAnchor] = useState(null);
144
+ const active = Boolean(value);
145
+ const selectedLabel = options.find((o) => o.value === value)?.label ?? '';
146
+ const rhf = useForm({ defaultValues: { select: value || '' } });
147
+ const { register, control, getValues, clearErrors, watch } = rhf;
148
+ // Sync form value when external value changes (e.g. Clear All)
149
+ useEffect(() => {
150
+ rhf.setValue('select', value || '');
151
+ }, [value]); // eslint-disable-line react-hooks/exhaustive-deps
152
+ // Intercept setValue: SingleSelectNonAutoComplete calls props.setValue(name, val)
153
+ // on every selection. We forward to react-hook-form AND close the popover with
154
+ // the chosen value — but only if the popover is currently open (anchor !== null),
155
+ // to avoid reacting to external syncs from the parent.
156
+ const interceptedSetValue = (name, val, opts) => {
157
+ rhf.setValue(name, val, opts);
158
+ if (anchor !== null) {
159
+ onApply(val ?? '');
160
+ setAnchor(null);
161
+ }
162
+ };
163
+ const item = {
164
+ name: 'select',
165
+ label,
166
+ inputType: 'basic-select',
167
+ options: [
168
+ { label: 'All', value: '' },
169
+ ...options.map((o) => ({ label: o.label, value: o.value })),
170
+ ],
171
+ };
172
+ return (jsxs(Fragment, { children: [jsx(Button, { variant: "outlined", size: "small", onClick: (e) => setAnchor(e.currentTarget), startIcon: jsx(ArrowDropDownIcon, { sx: { fontSize: '15px !important', mr: -0.5 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active), children: active ? `${label}: ${selectedLabel}` : label }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, p: 1.5, minWidth: 240 } }, children: jsx(SingleSelectNonAutoComplete, { props: {
173
+ item,
174
+ register,
175
+ control,
176
+ errors: {},
177
+ getValues,
178
+ clearErrors,
179
+ setValue: interceptedSetValue,
180
+ watch,
181
+ }, variant: "outlined" }) })] }));
182
+ };
183
+ const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, }) => {
184
+ const [anchor, setAnchor] = useState(null);
185
+ 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: {
186
+ borderRadius: 5, textTransform: 'none', fontSize: 12,
187
+ borderColor: 'divider', color: 'text.secondary',
188
+ px: 1.5, minHeight: 30,
189
+ '&:hover': { borderColor: 'primary.main', color: 'primary.main' },
190
+ }, children: "Manage Filters" }) }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, width: 280 } }, children: jsxs(Box, { sx: { py: 1 }, children: [serverFilterDefs.length > 0 && (jsxs(Fragment, { children: [jsx(Typography, { variant: "caption", fontWeight: 700, color: "text.disabled", sx: { px: 2, pb: 0.5, display: 'block', textTransform: 'uppercase', letterSpacing: 0.5 }, children: "Server Filters" }), jsx(List, { dense: true, disablePadding: true, children: serverFilterDefs.map((def) => {
191
+ const selected = visibleIds.includes(def.id);
192
+ const pill = TYPE_PILL[def.type];
193
+ return (jsxs(ListItemButton, { onClick: () => onToggle(def.id), sx: { px: 2, py: 0.5 }, children: [jsx(Box, { component: "span", sx: {
194
+ mr: 1.25, flexShrink: 0,
195
+ fontSize: 10, fontWeight: 700,
196
+ px: 0.75, py: '2px',
197
+ borderRadius: '4px',
198
+ bgcolor: pill.bg,
199
+ color: pill.color,
200
+ textTransform: 'uppercase',
201
+ letterSpacing: 0.4,
202
+ lineHeight: 1.6,
203
+ }, children: pill.label }), jsx(ListItemText, { primary: def.label, primaryTypographyProps: {
204
+ fontSize: 13,
205
+ color: selected ? 'primary.main' : 'text.primary',
206
+ } }), selected && jsx(CheckIcon, { sx: { fontSize: 16, color: 'primary.main', flexShrink: 0 } })] }, def.id));
207
+ }) })] })), 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
+ const selected = visibleIds.includes(col.id);
209
+ return (jsxs(ListItemButton, { onClick: () => onToggle(col.id), sx: { px: 2, py: 0.4 }, children: [jsx(Box, { component: "span", sx: {
210
+ mr: 1.25, flexShrink: 0,
211
+ fontSize: 10, fontWeight: 700,
212
+ px: 0.75, py: '2px',
213
+ borderRadius: '4px',
214
+ bgcolor: TYPE_PILL.text.bg,
215
+ color: TYPE_PILL.text.color,
216
+ textTransform: 'uppercase',
217
+ letterSpacing: 0.4,
218
+ lineHeight: 1.6,
219
+ }, children: "Text" }), jsx(ListItemText, { primary: col.label, primaryTypographyProps: {
220
+ fontSize: 13,
221
+ color: selected ? 'primary.main' : 'text.primary',
222
+ } }), selected && jsx(CheckIcon, { sx: { fontSize: 16, color: 'primary.main', flexShrink: 0 } })] }, col.id));
223
+ }) })] }))] }) })] }));
224
+ };
225
+ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, }) => {
226
+ const getColValue = (id) => columnFilters.find((f) => f.id === id)?.value ?? '';
227
+ const applyColFilter = (id, value) => {
228
+ const rest = columnFilters.filter((f) => f.id !== id);
229
+ onColumnFilterChange(value ? [...rest, { id, value }] : rest);
230
+ };
231
+ const removeFilter = (id) => {
232
+ const def = serverFilterDefs.find((d) => d.id === id);
233
+ if (def?.type === 'dateRange') {
234
+ onFilterValuesChange({ ...filterValues, [def.fromKey]: '', [def.toKey]: '' });
235
+ }
236
+ else if (def?.type === 'text' || def?.type === 'dropdown') {
237
+ onFilterValuesChange({ ...filterValues, [def.filterKey]: '' });
238
+ }
239
+ else if (def?.type === 'date') {
240
+ applyColFilter(def.columnKey, '');
241
+ }
242
+ else {
243
+ applyColFilter(id, '');
244
+ }
245
+ onToggleFilter(id);
246
+ };
247
+ return (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 0.75 }, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.4, mr: 0.25 }, children: [jsx(FilterListIcon, { sx: { fontSize: 15, color: 'text.disabled' } }), jsx(Typography, { variant: "body2", fontSize: 12, color: "text.secondary", fontWeight: 500, noWrap: true, children: "Filter by:" })] }), visibleFilterIds.map((id) => {
248
+ const def = serverFilterDefs.find((d) => d.id === id);
249
+ if (def?.type === 'dateRange') {
250
+ return (jsx(DateRangeChip, { label: def.label, fromDate: filterValues[def.fromKey] ?? '', toDate: filterValues[def.toKey] ?? '', onApply: (from, to) => onFilterValuesChange({ ...filterValues, [def.fromKey]: from, [def.toKey]: to }), onRemove: () => removeFilter(id) }, id));
251
+ }
252
+ if (def?.type === 'text') {
253
+ return (jsx(TextFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder }, id));
254
+ }
255
+ if (def?.type === 'dropdown') {
256
+ 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
+ }
258
+ if (def?.type === 'date') {
259
+ return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id) }, id));
260
+ }
261
+ // Column filter chip
262
+ const col = availableColumns.find((c) => c.id === id);
263
+ if (!col)
264
+ return null;
265
+ return (jsx(TextFilterChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), placeholder: `Filter by ${col.label}` }, id));
266
+ }), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter })] }));
267
+ };
268
+
269
+ export { DynamicFilterBar as default };
@@ -0,0 +1,40 @@
1
+ import { type MRT_ColumnDef } from 'material-react-table';
2
+ import { type ServerFilterDef } from './DynamicFilterBar';
3
+ export type { ServerFilterDef };
4
+ /** Shape that fetchPage must return. */
5
+ export interface PagedResult<TData> {
6
+ data: TData[];
7
+ totalRecords: number;
8
+ }
9
+ export interface GenericPaginatedGridProps<TData extends object> {
10
+ /** MRT column definitions typed to your data shape. */
11
+ columns: MRT_ColumnDef<TData>[];
12
+ /**
13
+ * Async function called on every page/filter change.
14
+ * Receives the current flat filter map and the 0-based page index.
15
+ */
16
+ fetchPage: (filters: Record<string, string>, pageIndex: number) => Promise<PagedResult<TData>>;
17
+ /** Base react-query cache key. */
18
+ queryKey: unknown[];
19
+ /** Server-side filter chip definitions. */
20
+ serverFilterDefs: ServerFilterDef[];
21
+ /** Initial key → value map for every server filter. */
22
+ defaultFilters: Record<string, string>;
23
+ /** Which chip IDs are visible on first render. Defaults to first entry. */
24
+ defaultVisibleFilterIds?: string[];
25
+ /** Number of rows fetched per API call. Default: 10 000. */
26
+ batchSize?: number;
27
+ /** CSV file name prefix. Default: "export". */
28
+ exportFilename?: string;
29
+ /** max-height of the scrollable table body. Default: "60vh". */
30
+ tableHeight?: string;
31
+ /** Choices shown in the "Rows per page" footer dropdown. Default: [1 000, 5 000, 10 000]. */
32
+ pageSizeOptions?: number[];
33
+ /**
34
+ * Background colour applied to every header cell.
35
+ * Accepts any valid CSS colour string.
36
+ */
37
+ headerBgColor?: string;
38
+ }
39
+ declare function GenericPaginatedGrid<TData extends object>({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize, exportFilename, tableHeight, pageSizeOptions, headerBgColor, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
40
+ export default GenericPaginatedGrid;