@visns-studio/visns-components 5.10.11 → 5.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/package.json +6 -6
- package/src/components/crm/generic/ContactSelectorModal.jsx +380 -0
- package/src/components/crm/generic/DatePickerDialog.jsx +302 -0
- package/src/components/crm/generic/GenericGrid.jsx +1534 -0
- package/src/components/crm/generic/GenericIndex.jsx +5 -1255
- package/src/components/crm/generic/GenericReportForm.jsx +194 -0
- package/src/components/crm/generic/shared/formatters.js +125 -0
- package/src/components/crm/generic/shared/gridUtils.js +194 -0
- package/src/components/crm/generic/styles/ContactSelectorModal.css +367 -0
- package/src/components/crm/generic/styles/DatePickerDialog.css +84 -0
- package/src/components/crm/generic/styles/GenericIndex.module.scss +176 -0
- package/src/index.js +4 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { toast } from 'react-toastify';
|
|
3
|
+
import { saveAs } from 'file-saver';
|
|
4
|
+
import { CloudDownload } from 'akar-icons';
|
|
5
|
+
|
|
6
|
+
import Download from '../Download';
|
|
7
|
+
import Field from '../Field';
|
|
8
|
+
|
|
9
|
+
import reportStyles from './styles/ReportForm.module.scss';
|
|
10
|
+
|
|
11
|
+
const GenericReportForm = ({ config, userProfile, onExport, onFormChange }) => {
|
|
12
|
+
const [formData, setFormData] = useState({});
|
|
13
|
+
|
|
14
|
+
const handleChange = (e) => {
|
|
15
|
+
const {
|
|
16
|
+
value,
|
|
17
|
+
dataset: { name },
|
|
18
|
+
} = e.target;
|
|
19
|
+
|
|
20
|
+
const newFormData = { ...formData, [name]: value };
|
|
21
|
+
setFormData(newFormData);
|
|
22
|
+
|
|
23
|
+
// Notify parent component of form change
|
|
24
|
+
if (onFormChange) {
|
|
25
|
+
onFormChange(newFormData);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleChangeDate = (date, id) => {
|
|
30
|
+
const newFormData = { ...formData, [id]: date };
|
|
31
|
+
setFormData(newFormData);
|
|
32
|
+
|
|
33
|
+
// Notify parent component of form change
|
|
34
|
+
if (onFormChange) {
|
|
35
|
+
onFormChange(newFormData);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handleChangeSelect = (inputValue, _, id) => {
|
|
40
|
+
const newFormData = { ...formData, [id]: inputValue };
|
|
41
|
+
setFormData(newFormData);
|
|
42
|
+
|
|
43
|
+
// Notify parent component of form change
|
|
44
|
+
if (onFormChange) {
|
|
45
|
+
onFormChange(newFormData);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleExport = async (e) => {
|
|
50
|
+
try {
|
|
51
|
+
if (e) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check if there's a custom export handler
|
|
56
|
+
if (onExport) {
|
|
57
|
+
return onExport(formData);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Default export logic
|
|
61
|
+
const missingFields = config.form.filters
|
|
62
|
+
.filter(
|
|
63
|
+
(item) => item.required === true && formData[item.id] === ''
|
|
64
|
+
)
|
|
65
|
+
.map((item) => item.label);
|
|
66
|
+
|
|
67
|
+
if (missingFields.length > 0) {
|
|
68
|
+
const fieldNames = missingFields.join(', ');
|
|
69
|
+
const errorMessage = `Please fill in a value for the following field(s): ${fieldNames}.`;
|
|
70
|
+
toast.error(errorMessage);
|
|
71
|
+
} else {
|
|
72
|
+
toast.info('Preparing your export...');
|
|
73
|
+
|
|
74
|
+
const res = await Download(
|
|
75
|
+
config.form.url,
|
|
76
|
+
config.form.method,
|
|
77
|
+
formData
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (res.data.error) {
|
|
81
|
+
toast.error(res.data.error);
|
|
82
|
+
} else {
|
|
83
|
+
// Assuming the response contains a Blob or similar data
|
|
84
|
+
if (res.data && res.data instanceof Blob) {
|
|
85
|
+
const fileName = `${config.form.filename}`;
|
|
86
|
+
saveAs(res.data, fileName);
|
|
87
|
+
toast.success('Export completed successfully');
|
|
88
|
+
} else {
|
|
89
|
+
// Handle the case where the response is not a Blob
|
|
90
|
+
toast.error('Invalid file format received.');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.error(err);
|
|
96
|
+
toast.error('An error occurred during export');
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (config.form?.filters) {
|
|
102
|
+
const tmpForm = config.form.filters.reduce((acc, item) => {
|
|
103
|
+
acc[item.id] = '';
|
|
104
|
+
return acc;
|
|
105
|
+
}, {});
|
|
106
|
+
setFormData(tmpForm);
|
|
107
|
+
|
|
108
|
+
// Notify parent component of initial form data
|
|
109
|
+
if (onFormChange) {
|
|
110
|
+
onFormChange(tmpForm);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}, [config.form?.filters, onFormChange]);
|
|
114
|
+
|
|
115
|
+
// Determine if the form has a title
|
|
116
|
+
const formTitle = config.form?.title || 'Export Report';
|
|
117
|
+
const formDescription =
|
|
118
|
+
config.form?.description || 'Select your export criteria below.';
|
|
119
|
+
|
|
120
|
+
// Check if form configuration is valid
|
|
121
|
+
if (!config.form?.filters) {
|
|
122
|
+
return (
|
|
123
|
+
<div className={reportStyles.reportFormContainer}>
|
|
124
|
+
<div className={reportStyles.reportForm}>
|
|
125
|
+
<h2 className={reportStyles.formTitle}>No Report Configuration</h2>
|
|
126
|
+
<p className={reportStyles.formDescription}>
|
|
127
|
+
Report form configuration is missing.
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className={reportStyles.reportFormContainer}>
|
|
136
|
+
<form className={reportStyles.reportForm}>
|
|
137
|
+
<h2 className={reportStyles.formTitle}>{formTitle}</h2>
|
|
138
|
+
<p className={reportStyles.formDescription}>
|
|
139
|
+
{formDescription}
|
|
140
|
+
</p>
|
|
141
|
+
|
|
142
|
+
{config.form.filters.map((item, key) => (
|
|
143
|
+
<div
|
|
144
|
+
key={`filter-container-${key}`}
|
|
145
|
+
className={`${reportStyles.formField} ${
|
|
146
|
+
item.fullWidth ? reportStyles.fullWidthField : ''
|
|
147
|
+
}`}
|
|
148
|
+
>
|
|
149
|
+
<Field
|
|
150
|
+
api={userProfile?.settings?.api}
|
|
151
|
+
autocompleteCallback={() => {}}
|
|
152
|
+
childDropdownCallback={() => {}}
|
|
153
|
+
fetchData={() => {}}
|
|
154
|
+
formData={formData}
|
|
155
|
+
inputClass={''}
|
|
156
|
+
inputValue={formData[item.id]}
|
|
157
|
+
key={`filter-fields-${key}`}
|
|
158
|
+
mapbox={{}}
|
|
159
|
+
onChange={handleChange}
|
|
160
|
+
onChangeCheckbox={() => {}}
|
|
161
|
+
onChangeDate={handleChangeDate}
|
|
162
|
+
onChangeNumberFormat={() => {}}
|
|
163
|
+
onChangeRicheditor={() => {}}
|
|
164
|
+
onChangeSelect={handleChangeSelect}
|
|
165
|
+
onChangeToggle={() => {}}
|
|
166
|
+
settings={item}
|
|
167
|
+
setFormData={setFormData}
|
|
168
|
+
style={userProfile?.settings?.style}
|
|
169
|
+
uploadProgress={0}
|
|
170
|
+
userProfile={userProfile}
|
|
171
|
+
/>
|
|
172
|
+
</div>
|
|
173
|
+
))}
|
|
174
|
+
|
|
175
|
+
<div className={reportStyles.formActions}>
|
|
176
|
+
<button
|
|
177
|
+
className={reportStyles.exportButton}
|
|
178
|
+
onClick={handleExport}
|
|
179
|
+
type="button"
|
|
180
|
+
>
|
|
181
|
+
<CloudDownload
|
|
182
|
+
strokeWidth={2}
|
|
183
|
+
size={18}
|
|
184
|
+
style={{ marginRight: '8px' }}
|
|
185
|
+
/>{' '}
|
|
186
|
+
Export
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
</form>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export default GenericReportForm;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import parse from 'html-react-parser';
|
|
2
|
+
import moment from 'moment';
|
|
3
|
+
import numeral from 'numeral';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format cell content based on column type or value type
|
|
7
|
+
* @param {*} value - The value to format
|
|
8
|
+
* @param {Object} column - Column configuration object
|
|
9
|
+
* @returns {*} Formatted value
|
|
10
|
+
*/
|
|
11
|
+
export const formatCellContent = (value, column) => {
|
|
12
|
+
if (value === null || value === undefined) return '';
|
|
13
|
+
if (value === 0) return '0'; // Explicitly handle zero values
|
|
14
|
+
if (value === '') return '';
|
|
15
|
+
|
|
16
|
+
// Try to infer the type if not provided
|
|
17
|
+
const valueType = column.type || typeof value;
|
|
18
|
+
|
|
19
|
+
switch (valueType) {
|
|
20
|
+
case 'date':
|
|
21
|
+
return moment(value).isValid()
|
|
22
|
+
? moment(value).format(column.format || 'DD/MM/YYYY')
|
|
23
|
+
: value;
|
|
24
|
+
case 'datetime':
|
|
25
|
+
return moment(value).isValid()
|
|
26
|
+
? moment(value).format(column.format || 'DD/MM/YYYY HH:mm')
|
|
27
|
+
: value;
|
|
28
|
+
case 'currency':
|
|
29
|
+
return numeral(value).format(column.format || '$0,0.00');
|
|
30
|
+
case 'number':
|
|
31
|
+
// Check if it's actually a number before formatting
|
|
32
|
+
return !isNaN(parseFloat(value)) && isFinite(value)
|
|
33
|
+
? numeral(value).format(column.format || '0,0')
|
|
34
|
+
: value;
|
|
35
|
+
case 'boolean':
|
|
36
|
+
if (
|
|
37
|
+
value === 1 ||
|
|
38
|
+
value === true ||
|
|
39
|
+
value === 'true' ||
|
|
40
|
+
value === 'yes' ||
|
|
41
|
+
value === 'Yes'
|
|
42
|
+
) {
|
|
43
|
+
return 'Yes';
|
|
44
|
+
}
|
|
45
|
+
if (
|
|
46
|
+
value === 0 ||
|
|
47
|
+
value === false ||
|
|
48
|
+
value === 'false' ||
|
|
49
|
+
value === 'no' ||
|
|
50
|
+
value === 'No'
|
|
51
|
+
) {
|
|
52
|
+
return 'No';
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
case 'year':
|
|
56
|
+
// Special handling for year values
|
|
57
|
+
return value.toString();
|
|
58
|
+
case 'quarter':
|
|
59
|
+
// Special handling for quarter values (Q1, Q2, etc.)
|
|
60
|
+
if (value && value.toString().startsWith('Q')) {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
case 'html':
|
|
65
|
+
// Explicitly parse HTML content
|
|
66
|
+
return parse(String(value));
|
|
67
|
+
case 'richtext':
|
|
68
|
+
// Parse rich text content
|
|
69
|
+
return parse(String(value));
|
|
70
|
+
default:
|
|
71
|
+
// For text values, check if it contains HTML tags
|
|
72
|
+
if (
|
|
73
|
+
typeof value === 'string' &&
|
|
74
|
+
(value.includes('<br') ||
|
|
75
|
+
value.includes('<p>') ||
|
|
76
|
+
value.includes('<div') ||
|
|
77
|
+
value.includes('<span'))
|
|
78
|
+
) {
|
|
79
|
+
// Ensure <br /> tags are properly handled
|
|
80
|
+
const processedValue = String(value)
|
|
81
|
+
.replace(/<br \/>/g, '<br>')
|
|
82
|
+
.replace(/<br\/>/g, '<br>');
|
|
83
|
+
return parse(processedValue);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Generate year options for dropdown
|
|
91
|
+
* @param {number} startYear - Starting year
|
|
92
|
+
* @returns {Array} Array of year options
|
|
93
|
+
*/
|
|
94
|
+
export const generateYearOptions = (startYear) => {
|
|
95
|
+
const currentYear = new Date().getFullYear();
|
|
96
|
+
const years = [];
|
|
97
|
+
|
|
98
|
+
// Start from the specified year and go up to current year
|
|
99
|
+
for (let year = startYear; year <= currentYear + 1; year++) {
|
|
100
|
+
years.push({ value: year.toString(), label: year.toString() });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return years;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Apply custom styling from userProfile
|
|
108
|
+
* @param {Object} userProfile - User profile object
|
|
109
|
+
* @returns {Object} Custom styles object
|
|
110
|
+
*/
|
|
111
|
+
export const getCustomStyles = (userProfile) => {
|
|
112
|
+
const customStyles = {};
|
|
113
|
+
|
|
114
|
+
if (userProfile?.settings?.style) {
|
|
115
|
+
const { style } = userProfile.settings;
|
|
116
|
+
|
|
117
|
+
if (style.hoverColor) {
|
|
118
|
+
customStyles.hoverColor = {
|
|
119
|
+
'--hover-color': style.hoverColor,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return customStyles;
|
|
125
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sort data based on column configuration
|
|
3
|
+
* @param {Array} data - Data array to sort
|
|
4
|
+
* @param {string} key - Column key to sort by
|
|
5
|
+
* @param {string} direction - Sort direction ('asc' or 'desc')
|
|
6
|
+
* @param {Array} gridHeaders - Grid headers configuration
|
|
7
|
+
* @returns {Array} Sorted data array
|
|
8
|
+
*/
|
|
9
|
+
export const sortData = (data, key, direction, gridHeaders) => {
|
|
10
|
+
if (!key) return [...data];
|
|
11
|
+
|
|
12
|
+
return [...data].sort((a, b) => {
|
|
13
|
+
// Find the header config for this key to determine type
|
|
14
|
+
const headerConfig = gridHeaders.find((h) => h.key === key);
|
|
15
|
+
const type = headerConfig?.type || 'text';
|
|
16
|
+
|
|
17
|
+
// Get values, handling null/undefined
|
|
18
|
+
let aValue = a[key] === null || a[key] === undefined ? '' : a[key];
|
|
19
|
+
let bValue = b[key] === null || b[key] === undefined ? '' : b[key];
|
|
20
|
+
|
|
21
|
+
// Sort based on type
|
|
22
|
+
switch (type) {
|
|
23
|
+
case 'number':
|
|
24
|
+
case 'year':
|
|
25
|
+
aValue = parseFloat(aValue) || 0;
|
|
26
|
+
bValue = parseFloat(bValue) || 0;
|
|
27
|
+
break;
|
|
28
|
+
case 'date':
|
|
29
|
+
// Convert to timestamp for comparison
|
|
30
|
+
aValue = aValue ? new Date(aValue).getTime() : 0;
|
|
31
|
+
bValue = bValue ? new Date(bValue).getTime() : 0;
|
|
32
|
+
break;
|
|
33
|
+
default:
|
|
34
|
+
// For text, convert to lowercase strings
|
|
35
|
+
aValue = String(aValue).toLowerCase();
|
|
36
|
+
bValue = String(bValue).toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Compare values
|
|
40
|
+
if (aValue < bValue) {
|
|
41
|
+
return direction === 'asc' ? -1 : 1;
|
|
42
|
+
}
|
|
43
|
+
if (aValue > bValue) {
|
|
44
|
+
return direction === 'asc' ? 1 : -1;
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Filter data based on filter values and configuration
|
|
52
|
+
* @param {Array} data - Data array to filter
|
|
53
|
+
* @param {Object} filterValues - Filter values object
|
|
54
|
+
* @param {Array} gridHeaders - Grid headers configuration
|
|
55
|
+
* @returns {Array} Filtered data array
|
|
56
|
+
*/
|
|
57
|
+
export const filterData = (data, filterValues, gridHeaders) => {
|
|
58
|
+
// If no filter values are set, return the original data
|
|
59
|
+
if (Object.keys(filterValues).length === 0) {
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Filter the data based on the filter values
|
|
64
|
+
return data.filter((row) => {
|
|
65
|
+
// Check each filter
|
|
66
|
+
return Object.entries(filterValues).every(([key, value]) => {
|
|
67
|
+
// Skip empty filter values
|
|
68
|
+
if (!value) return true;
|
|
69
|
+
|
|
70
|
+
// Get the row value
|
|
71
|
+
const rowValue = row[key];
|
|
72
|
+
if (rowValue === null || rowValue === undefined) return false;
|
|
73
|
+
|
|
74
|
+
// Find the header config for this key
|
|
75
|
+
const headerConfig = gridHeaders.find((h) => h.key === key);
|
|
76
|
+
if (!headerConfig || !headerConfig.filter) return true;
|
|
77
|
+
|
|
78
|
+
const { type, operator } = headerConfig.filter;
|
|
79
|
+
|
|
80
|
+
// Apply filter based on type and operator
|
|
81
|
+
switch (type) {
|
|
82
|
+
case 'string':
|
|
83
|
+
const rowStr = String(rowValue).toLowerCase();
|
|
84
|
+
const filterStr = String(value).toLowerCase();
|
|
85
|
+
|
|
86
|
+
switch (operator) {
|
|
87
|
+
case 'equals':
|
|
88
|
+
return rowStr === filterStr;
|
|
89
|
+
case 'contains':
|
|
90
|
+
return rowStr.includes(filterStr);
|
|
91
|
+
case 'startsWith':
|
|
92
|
+
return rowStr.startsWith(filterStr);
|
|
93
|
+
case 'endsWith':
|
|
94
|
+
return rowStr.endsWith(filterStr);
|
|
95
|
+
default:
|
|
96
|
+
return rowStr.includes(filterStr);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case 'number':
|
|
100
|
+
const rowNum = parseFloat(rowValue);
|
|
101
|
+
const filterNum = parseFloat(value);
|
|
102
|
+
|
|
103
|
+
if (isNaN(rowNum) || isNaN(filterNum)) return false;
|
|
104
|
+
|
|
105
|
+
switch (operator) {
|
|
106
|
+
case 'equals':
|
|
107
|
+
return rowNum === filterNum;
|
|
108
|
+
case 'greaterThan':
|
|
109
|
+
return rowNum > filterNum;
|
|
110
|
+
case 'lessThan':
|
|
111
|
+
return rowNum < filterNum;
|
|
112
|
+
case 'greaterThanOrEqual':
|
|
113
|
+
return rowNum >= filterNum;
|
|
114
|
+
case 'lessThanOrEqual':
|
|
115
|
+
return rowNum <= filterNum;
|
|
116
|
+
default:
|
|
117
|
+
return rowNum === filterNum;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
case 'date':
|
|
121
|
+
const rowDate = new Date(rowValue);
|
|
122
|
+
const filterDate = new Date(value);
|
|
123
|
+
|
|
124
|
+
if (isNaN(rowDate.getTime()) || isNaN(filterDate.getTime()))
|
|
125
|
+
return false;
|
|
126
|
+
|
|
127
|
+
switch (operator) {
|
|
128
|
+
case 'equals':
|
|
129
|
+
return (
|
|
130
|
+
rowDate.toDateString() ===
|
|
131
|
+
filterDate.toDateString()
|
|
132
|
+
);
|
|
133
|
+
case 'after':
|
|
134
|
+
return rowDate > filterDate;
|
|
135
|
+
case 'before':
|
|
136
|
+
return rowDate < filterDate;
|
|
137
|
+
default:
|
|
138
|
+
return (
|
|
139
|
+
rowDate.toDateString() ===
|
|
140
|
+
filterDate.toDateString()
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
default:
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Transform headers from API response to grid headers format
|
|
153
|
+
* @param {Object} headerData - Header data from API response
|
|
154
|
+
* @returns {Array} Formatted grid headers array
|
|
155
|
+
*/
|
|
156
|
+
export const transformApiHeaders = (headerData) => {
|
|
157
|
+
const headerEntries = Object.entries(headerData);
|
|
158
|
+
return headerEntries.map(([key, config]) => ({
|
|
159
|
+
key,
|
|
160
|
+
label: config.label || key,
|
|
161
|
+
sort: config.sort || key,
|
|
162
|
+
type: config.type || 'text',
|
|
163
|
+
filter: config.filter || null,
|
|
164
|
+
onClick: config.onClick || null,
|
|
165
|
+
}));
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Initialize filter values from grid headers
|
|
170
|
+
* @param {Array} gridHeaders - Grid headers array
|
|
171
|
+
* @returns {Object} Initial filter values object
|
|
172
|
+
*/
|
|
173
|
+
export const initializeFilterValues = (gridHeaders) => {
|
|
174
|
+
const initialFilterValues = {};
|
|
175
|
+
gridHeaders.forEach((header) => {
|
|
176
|
+
if (header.filter) {
|
|
177
|
+
initialFilterValues[header.key] = '';
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return initialFilterValues;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get sort indicator style based on current sort configuration
|
|
185
|
+
* @param {string} key - Column key
|
|
186
|
+
* @param {Object} sortConfig - Current sort configuration
|
|
187
|
+
* @returns {Object} Style object for sort indicator
|
|
188
|
+
*/
|
|
189
|
+
export const getSortIndicatorStyle = (key, sortConfig) => {
|
|
190
|
+
if (sortConfig.key !== key) {
|
|
191
|
+
return { opacity: 0.3 }; // Dim the icon when not sorting by this column
|
|
192
|
+
}
|
|
193
|
+
return {}; // Full opacity when sorting by this column
|
|
194
|
+
};
|