@visns-studio/visns-components 5.6.8 → 5.6.9
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/package.json +1 -1
- package/src/components/crm/TableFilter.jsx +287 -102
- package/src/components/crm/auth/TwoFactorAuth.jsx +7 -9
- package/src/components/crm/auth/styles/TwoFactorAuth.module.scss +154 -25
- package/src/components/crm/examples/FilterExample.jsx +207 -0
- package/src/components/crm/examples/README.md +113 -0
- package/src/components/crm/generic/GenericIndex.jsx +244 -87
- package/src/components/crm/generic/styles/ReportForm.module.scss +150 -0
- package/src/components/crm/styles/TableFilter.module.scss +121 -23
package/package.json
CHANGED
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
85
85
|
},
|
|
86
86
|
"name": "@visns-studio/visns-components",
|
|
87
|
-
"version": "5.6.
|
|
87
|
+
"version": "5.6.9",
|
|
88
88
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
89
89
|
"main": "src/index.js",
|
|
90
90
|
"files": [
|
|
@@ -2,31 +2,86 @@ import React, { useState, useEffect, useRef } from 'react';
|
|
|
2
2
|
import styles from './styles/TableFilter.module.scss';
|
|
3
3
|
|
|
4
4
|
function TableFilter({
|
|
5
|
-
collapsible = false,
|
|
6
5
|
filters,
|
|
7
6
|
setFilters,
|
|
8
7
|
setSettings,
|
|
9
8
|
type,
|
|
9
|
+
onFilterChange,
|
|
10
10
|
}) {
|
|
11
|
-
const [visibleChildren, setVisibleChildren] = useState(
|
|
11
|
+
const [visibleChildren, setVisibleChildren] = useState({});
|
|
12
12
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
13
13
|
const isFirstRender = useRef(true);
|
|
14
14
|
|
|
15
|
+
// Initialize visible children for all parent categories
|
|
15
16
|
useEffect(() => {
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
if (isFirstRender.current && filters.length > 0) {
|
|
18
|
+
const initialVisibleState = {};
|
|
19
|
+
|
|
20
|
+
// Find the first parent with isParent=true and set only it to visible
|
|
21
|
+
let foundFirstParent = false;
|
|
22
|
+
|
|
23
|
+
for (const filter of filters) {
|
|
24
|
+
if (
|
|
25
|
+
filter.isParent &&
|
|
26
|
+
filter.children &&
|
|
27
|
+
filter.children.length > 0
|
|
28
|
+
) {
|
|
29
|
+
// Only show the first parent's children by default
|
|
30
|
+
// and ensure all others are explicitly set to false
|
|
31
|
+
initialVisibleState[filter.id] = !foundFirstParent;
|
|
32
|
+
|
|
33
|
+
if (!foundFirstParent) {
|
|
34
|
+
foundFirstParent = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setVisibleChildren(initialVisibleState);
|
|
23
40
|
isFirstRender.current = false;
|
|
24
41
|
}
|
|
25
42
|
}, [filters]);
|
|
26
43
|
|
|
44
|
+
// Toggle visibility of children when clicking on a parent
|
|
45
|
+
// Only one parent can be open at a time
|
|
46
|
+
const toggleChildren = (parentId) => {
|
|
47
|
+
setVisibleChildren((prev) => {
|
|
48
|
+
// If this parent is already open, close it
|
|
49
|
+
if (prev[parentId]) {
|
|
50
|
+
return {
|
|
51
|
+
...prev,
|
|
52
|
+
[parentId]: false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Otherwise, close all parents and open this one
|
|
57
|
+
const newState = {};
|
|
58
|
+
Object.keys(prev).forEach((key) => {
|
|
59
|
+
newState[key] = false;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...newState,
|
|
64
|
+
[parentId]: true,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
27
69
|
const filterTable = (e) => {
|
|
70
|
+
e.preventDefault();
|
|
28
71
|
const { id, parent, filtertype, url, value } = e.target.dataset;
|
|
29
72
|
|
|
73
|
+
// If clicking on a parent category, toggle its children visibility
|
|
74
|
+
if (filtertype === 'parent') {
|
|
75
|
+
const clickedFilter = filters.find((f) => f.id === id);
|
|
76
|
+
if (clickedFilter && clickedFilter.isParent) {
|
|
77
|
+
toggleChildren(id);
|
|
78
|
+
|
|
79
|
+
// Don't activate the parent as a filter, but keep processing to update settings
|
|
80
|
+
// This allows the parent to expand/collapse while still triggering content updates
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Handle filter selection (both parent and child)
|
|
30
85
|
const updateFilter = (filter) => {
|
|
31
86
|
let isActive = false;
|
|
32
87
|
|
|
@@ -39,83 +94,171 @@ function TableFilter({
|
|
|
39
94
|
const isParent =
|
|
40
95
|
parent && filtertype === 'children' && filter.id === parent;
|
|
41
96
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
: [];
|
|
97
|
+
// For parent filters
|
|
98
|
+
if (filter.isParent) {
|
|
99
|
+
// If this is the clicked parent, expand/collapse it
|
|
100
|
+
if (filter.id === id && filtertype === 'parent') {
|
|
101
|
+
// Update children - don't change their active state
|
|
102
|
+
const updatedChildren = filter.children
|
|
103
|
+
? filter.children.map((child) => ({
|
|
104
|
+
...child,
|
|
105
|
+
// Preserve existing active state
|
|
106
|
+
class: child.class,
|
|
107
|
+
show: child.show,
|
|
108
|
+
}))
|
|
109
|
+
: [];
|
|
56
110
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
111
|
+
return {
|
|
112
|
+
...filter,
|
|
113
|
+
// Don't change parent's active state when just expanding/collapsing
|
|
114
|
+
class: filter.class,
|
|
115
|
+
show: filter.show,
|
|
116
|
+
children: updatedChildren,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// If this is a parent of a clicked child
|
|
120
|
+
else if (isParent) {
|
|
121
|
+
// Update children - set the clicked child as active
|
|
122
|
+
const updatedChildren = filter.children
|
|
123
|
+
? filter.children.map((child) => {
|
|
124
|
+
const isActiveChild = child.id === id;
|
|
125
|
+
return {
|
|
126
|
+
...child,
|
|
127
|
+
class: isActiveChild
|
|
128
|
+
? 'subactivechildren'
|
|
129
|
+
: '',
|
|
130
|
+
show: isActiveChild,
|
|
131
|
+
// Make sure the child has the active state set
|
|
132
|
+
active: isActiveChild,
|
|
133
|
+
};
|
|
134
|
+
})
|
|
135
|
+
: [];
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
...filter,
|
|
139
|
+
// Parent becomes active when its child is selected
|
|
140
|
+
class: 'subactive',
|
|
141
|
+
show: true,
|
|
142
|
+
active: true,
|
|
143
|
+
children: updatedChildren,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Other parents - deactivate them and their children
|
|
147
|
+
else {
|
|
148
|
+
const updatedChildren = filter.children
|
|
149
|
+
? filter.children.map((child) => ({
|
|
150
|
+
...child,
|
|
151
|
+
class: '',
|
|
152
|
+
show: false,
|
|
153
|
+
active: false,
|
|
154
|
+
}))
|
|
155
|
+
: [];
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
...filter,
|
|
159
|
+
class: '',
|
|
160
|
+
show: false,
|
|
161
|
+
active: false,
|
|
162
|
+
children: updatedChildren,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// For non-parent filters (backward compatibility)
|
|
167
|
+
else {
|
|
168
|
+
return {
|
|
169
|
+
...filter,
|
|
170
|
+
class: isActive
|
|
61
171
|
? type === 'simple'
|
|
62
172
|
? 'activetab'
|
|
63
173
|
: 'subactive'
|
|
64
174
|
: '',
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
175
|
+
show: isActive,
|
|
176
|
+
active: isActive,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
68
179
|
};
|
|
69
180
|
|
|
70
|
-
|
|
181
|
+
// Calculate the updated filters
|
|
182
|
+
const updatedFilters = filters.map(updateFilter);
|
|
71
183
|
|
|
72
|
-
|
|
184
|
+
// Only navigate to URL if it's provided and not a parent toggle action
|
|
185
|
+
if (
|
|
186
|
+
url &&
|
|
187
|
+
url !== '' &&
|
|
188
|
+
!(
|
|
189
|
+
filtertype === 'parent' &&
|
|
190
|
+
filters.find(
|
|
191
|
+
(f) => f.id === id && f.isParent && f.children?.length > 0
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
) {
|
|
73
195
|
window.open(url);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Calculate the updated settings
|
|
199
|
+
let updatedSettings = null;
|
|
200
|
+
|
|
201
|
+
if (setSettings) {
|
|
202
|
+
// For child items or non-parent items, we need to update the filter value
|
|
203
|
+
if (
|
|
204
|
+
filtertype === 'children' ||
|
|
205
|
+
(filtertype === 'parent' &&
|
|
206
|
+
!filters.find((f) => f.id === id && f.isParent))
|
|
207
|
+
) {
|
|
208
|
+
// For child filters, we need to update the filter with the child's ID
|
|
209
|
+
const filterId = id;
|
|
210
|
+
const filterValue = value || '';
|
|
211
|
+
|
|
212
|
+
// Start with the current settings
|
|
213
|
+
updatedSettings = { ...setSettings._currentValue };
|
|
214
|
+
|
|
215
|
+
if (updatedSettings.ajaxSetting) {
|
|
216
|
+
// First, remove any existing filters that might conflict
|
|
217
|
+
let _where = [...updatedSettings.ajaxSetting.where].filter(
|
|
218
|
+
(item) => {
|
|
219
|
+
// Keep items that don't match this filter ID
|
|
220
|
+
return item.id !== filterId;
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Add the new filter
|
|
225
|
+
_where.push({ id: filterId, value: filterValue });
|
|
226
|
+
|
|
227
|
+
updatedSettings = {
|
|
228
|
+
...updatedSettings,
|
|
229
|
+
ajaxSetting: {
|
|
230
|
+
...updatedSettings.ajaxSetting,
|
|
231
|
+
where: _where,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
} else if (updatedSettings.where) {
|
|
235
|
+
// Handle the case where we're using 'where' directly instead of ajaxSetting.where
|
|
236
|
+
let w = updatedSettings.where
|
|
237
|
+
? [...updatedSettings.where]
|
|
238
|
+
: [];
|
|
239
|
+
|
|
240
|
+
// Remove any existing filters that might conflict
|
|
241
|
+
w = w.filter((item) => item.id !== filterId);
|
|
242
|
+
|
|
243
|
+
// Add the new filter
|
|
244
|
+
w.push({ id: filterId, value: filterValue });
|
|
245
|
+
|
|
246
|
+
updatedSettings = { ...updatedSettings, where: w };
|
|
247
|
+
}
|
|
113
248
|
}
|
|
114
249
|
}
|
|
115
250
|
|
|
116
|
-
//
|
|
117
|
-
if (
|
|
118
|
-
|
|
251
|
+
// Use the onFilterChange prop if available, otherwise use the individual setters
|
|
252
|
+
if (onFilterChange) {
|
|
253
|
+
onFilterChange(updatedFilters, updatedSettings);
|
|
254
|
+
} else {
|
|
255
|
+
// Update the filters
|
|
256
|
+
setFilters(updatedFilters);
|
|
257
|
+
|
|
258
|
+
// Update the settings if needed
|
|
259
|
+
if (updatedSettings && setSettings) {
|
|
260
|
+
setSettings(updatedSettings);
|
|
261
|
+
}
|
|
119
262
|
}
|
|
120
263
|
|
|
121
264
|
// Close mobile menu after selection on small screens
|
|
@@ -123,9 +266,15 @@ function TableFilter({
|
|
|
123
266
|
};
|
|
124
267
|
|
|
125
268
|
const renderContent = (d) => {
|
|
126
|
-
|
|
269
|
+
// Determine if this is a parent category
|
|
270
|
+
const isParent = d.isParent === true;
|
|
271
|
+
|
|
272
|
+
// Set appropriate classes
|
|
273
|
+
const parentClass = `${isParent ? styles.parentLink : styles.link} ${
|
|
274
|
+
styles[d.class] || ''
|
|
275
|
+
}`;
|
|
127
276
|
const childClass = (child) =>
|
|
128
|
-
`${styles.
|
|
277
|
+
`${styles.childLink} ${styles[child.class] || ''}`;
|
|
129
278
|
|
|
130
279
|
if (type === 'simple') {
|
|
131
280
|
return (
|
|
@@ -143,8 +292,9 @@ function TableFilter({
|
|
|
143
292
|
);
|
|
144
293
|
} else {
|
|
145
294
|
return (
|
|
146
|
-
<li>
|
|
295
|
+
<li className={isParent ? styles.parentItem : ''}>
|
|
147
296
|
<a
|
|
297
|
+
href="#"
|
|
148
298
|
data-id={d.id}
|
|
149
299
|
data-filtertype="parent"
|
|
150
300
|
data-url={d.url || ''}
|
|
@@ -153,35 +303,62 @@ function TableFilter({
|
|
|
153
303
|
className={parentClass}
|
|
154
304
|
>
|
|
155
305
|
{d.label}
|
|
306
|
+
{isParent && d.children && d.children.length > 0 && (
|
|
307
|
+
<span
|
|
308
|
+
className={`${styles.arrow} ${
|
|
309
|
+
visibleChildren[d.id]
|
|
310
|
+
? styles.arrowDown
|
|
311
|
+
: ''
|
|
312
|
+
}`}
|
|
313
|
+
>
|
|
314
|
+
▶
|
|
315
|
+
</span>
|
|
316
|
+
)}
|
|
156
317
|
</a>
|
|
157
|
-
{d.children && d.children.length > 0 && (
|
|
158
|
-
<
|
|
318
|
+
{isParent && d.children && d.children.length > 0 && (
|
|
319
|
+
<div
|
|
159
320
|
className={`${styles.collapsibleMenu} ${
|
|
160
|
-
visibleChildren
|
|
161
|
-
? styles.visibleMenu
|
|
162
|
-
: ''
|
|
321
|
+
visibleChildren[d.id] ? styles.visibleMenu : ''
|
|
163
322
|
}`}
|
|
164
323
|
>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
data-filtertype="children"
|
|
177
|
-
data-parent={d.id}
|
|
178
|
-
onClick={filterTable}
|
|
324
|
+
<ul
|
|
325
|
+
style={{
|
|
326
|
+
listStyle: 'none',
|
|
327
|
+
padding: 0,
|
|
328
|
+
margin: 0,
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
331
|
+
{d.children.map((child, key) => (
|
|
332
|
+
<li
|
|
333
|
+
key={`table-filter-child-${d.id}-${key}`}
|
|
334
|
+
className={styles.childItem}
|
|
179
335
|
>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
336
|
+
<a
|
|
337
|
+
href="#"
|
|
338
|
+
className={childClass(child)}
|
|
339
|
+
data-id={child.id}
|
|
340
|
+
data-value={
|
|
341
|
+
child.hasOwnProperty('value')
|
|
342
|
+
? child.value
|
|
343
|
+
: ''
|
|
344
|
+
}
|
|
345
|
+
data-url={child.url || ''}
|
|
346
|
+
data-filtertype="children"
|
|
347
|
+
data-parent={d.id}
|
|
348
|
+
onClick={(e) => {
|
|
349
|
+
// Make sure the parent is expanded when a child is clicked
|
|
350
|
+
if (!visibleChildren[d.id]) {
|
|
351
|
+
toggleChildren(d.id);
|
|
352
|
+
}
|
|
353
|
+
filterTable(e);
|
|
354
|
+
}}
|
|
355
|
+
>
|
|
356
|
+
{child.label}
|
|
357
|
+
</a>
|
|
358
|
+
</li>
|
|
359
|
+
))}
|
|
360
|
+
</ul>
|
|
361
|
+
</div>
|
|
185
362
|
)}
|
|
186
363
|
</li>
|
|
187
364
|
);
|
|
@@ -193,7 +370,15 @@ function TableFilter({
|
|
|
193
370
|
};
|
|
194
371
|
|
|
195
372
|
// Find active filter for mobile display
|
|
196
|
-
const activeFilter = filters.find((filter) =>
|
|
373
|
+
const activeFilter = filters.find((filter) => {
|
|
374
|
+
if (filter.isParent) {
|
|
375
|
+
return (
|
|
376
|
+
filter.children && filter.children.some((child) => child.show)
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return filter.show === true;
|
|
380
|
+
});
|
|
381
|
+
|
|
197
382
|
const activeLabel = activeFilter ? activeFilter.label : 'Select Option';
|
|
198
383
|
|
|
199
384
|
return (
|
|
@@ -265,15 +265,11 @@ const TwoFactorAuth = ({ logo, setSystemAuth, setUserProfile }) => {
|
|
|
265
265
|
alt="CRM"
|
|
266
266
|
className={styles.loginlogo}
|
|
267
267
|
/>
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
Please enter the 6-digit code from your
|
|
274
|
-
authenticator app.
|
|
275
|
-
</p>
|
|
276
|
-
</div>
|
|
268
|
+
<h2>Two-Factor Authentication</h2>
|
|
269
|
+
<p className={styles.instructions}>
|
|
270
|
+
Please enter the 6-digit code from your
|
|
271
|
+
authenticator app.
|
|
272
|
+
</p>
|
|
277
273
|
<div
|
|
278
274
|
className={`${styles.formItem} ${styles.fwItem}`}
|
|
279
275
|
>
|
|
@@ -308,6 +304,7 @@ const TwoFactorAuth = ({ logo, setSystemAuth, setUserProfile }) => {
|
|
|
308
304
|
className={styles.btn}
|
|
309
305
|
type="submit"
|
|
310
306
|
disabled={isLoading}
|
|
307
|
+
tabIndex="2"
|
|
311
308
|
>
|
|
312
309
|
{isLoading ? 'Verifying...' : 'Verify'}
|
|
313
310
|
</button>
|
|
@@ -330,6 +327,7 @@ const TwoFactorAuth = ({ logo, setSystemAuth, setUserProfile }) => {
|
|
|
330
327
|
e.preventDefault();
|
|
331
328
|
navigate('/login');
|
|
332
329
|
}}
|
|
330
|
+
tabIndex="3"
|
|
333
331
|
>
|
|
334
332
|
Back to Login
|
|
335
333
|
</a>
|