@visns-studio/visns-components 5.18.1 → 5.20.0
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
CHANGED
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
95
95
|
},
|
|
96
96
|
"name": "@visns-studio/visns-components",
|
|
97
|
-
"version": "5.
|
|
97
|
+
"version": "5.20.0",
|
|
98
98
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
99
99
|
"main": "src/index.js",
|
|
100
100
|
"files": [
|
|
@@ -2800,6 +2800,27 @@ const DataGrid = forwardRef(
|
|
|
2800
2800
|
return { ...acc, ...result };
|
|
2801
2801
|
}, {});
|
|
2802
2802
|
|
|
2803
|
+
// Settings that an 'action' column renders inline render there
|
|
2804
|
+
// instead of the trailing Action column (avoids duplication).
|
|
2805
|
+
const actionColumnSettingIds = new Set(
|
|
2806
|
+
(columns || [])
|
|
2807
|
+
.filter(
|
|
2808
|
+
(c) =>
|
|
2809
|
+
c.type === 'action' &&
|
|
2810
|
+
Array.isArray(c.settings)
|
|
2811
|
+
)
|
|
2812
|
+
.flatMap((c) => c.settings)
|
|
2813
|
+
);
|
|
2814
|
+
|
|
2815
|
+
// Width to fit N action icons: tablet-mode icons have a larger
|
|
2816
|
+
// tap target/margin (~44px) than the default (~34px), plus cell
|
|
2817
|
+
// padding. Used by both action columns and the trailing column.
|
|
2818
|
+
const actionColumnWidthFor = (count) =>
|
|
2819
|
+
Math.max(
|
|
2820
|
+
100,
|
|
2821
|
+
count * (isTabletMode ? 44 : 34) + 16
|
|
2822
|
+
);
|
|
2823
|
+
|
|
2803
2824
|
const renderColumn = (column) => {
|
|
2804
2825
|
let childValue;
|
|
2805
2826
|
let columnId;
|
|
@@ -2951,6 +2972,37 @@ const DataGrid = forwardRef(
|
|
|
2951
2972
|
}
|
|
2952
2973
|
|
|
2953
2974
|
switch (column.type) {
|
|
2975
|
+
case 'action': {
|
|
2976
|
+
const actionSettings = (column.settings || [])
|
|
2977
|
+
.map((id) =>
|
|
2978
|
+
memoizedSettings.find(
|
|
2979
|
+
(s) => s.id === id
|
|
2980
|
+
)
|
|
2981
|
+
)
|
|
2982
|
+
.filter(Boolean);
|
|
2983
|
+
const width = actionColumnWidthFor(
|
|
2984
|
+
actionSettings.length
|
|
2985
|
+
);
|
|
2986
|
+
return {
|
|
2987
|
+
...commonProps,
|
|
2988
|
+
name: Array.isArray(column.id)
|
|
2989
|
+
? column.id.join('_')
|
|
2990
|
+
: column.id,
|
|
2991
|
+
header: column.label ?? '',
|
|
2992
|
+
sortable: false,
|
|
2993
|
+
defaultFlex: undefined,
|
|
2994
|
+
defaultWidth: width,
|
|
2995
|
+
minWidth: width,
|
|
2996
|
+
textAlign: 'center',
|
|
2997
|
+
render: ({ data }) => (
|
|
2998
|
+
<div className={styles.tdactions}>
|
|
2999
|
+
{actionSettings.map((setting) =>
|
|
3000
|
+
renderSetting(setting, data)
|
|
3001
|
+
)}
|
|
3002
|
+
</div>
|
|
3003
|
+
),
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
2954
3006
|
case 'address':
|
|
2955
3007
|
return renderAddressColumn({
|
|
2956
3008
|
column,
|
|
@@ -3283,37 +3335,42 @@ const DataGrid = forwardRef(
|
|
|
3283
3335
|
|
|
3284
3336
|
const newColumns = columns.map(renderColumn);
|
|
3285
3337
|
|
|
3286
|
-
//
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3338
|
+
// Count settings that could render in the trailing Action
|
|
3339
|
+
// column: role-permitted AND not claimed by an 'action' column.
|
|
3340
|
+
// Per-row conditions (active/condition/hide) may hide more at
|
|
3341
|
+
// runtime, but the column must fit the maximum case.
|
|
3342
|
+
const trailingSettings = memoizedSettings.filter(
|
|
3343
|
+
(setting) => {
|
|
3344
|
+
if (actionColumnSettingIds.has(setting.id)) {
|
|
3345
|
+
return false;
|
|
3346
|
+
}
|
|
3293
3347
|
if (
|
|
3294
3348
|
setting.roles &&
|
|
3295
3349
|
Array.isArray(setting.roles) &&
|
|
3296
3350
|
setting.roles.length > 0
|
|
3297
3351
|
) {
|
|
3298
|
-
|
|
3299
|
-
(
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
)
|
|
3352
|
+
return setting.roles.some((requiredRole) =>
|
|
3353
|
+
userProfile?.roles?.some(
|
|
3354
|
+
(userRole) =>
|
|
3355
|
+
userRole.name === requiredRole
|
|
3356
|
+
)
|
|
3304
3357
|
);
|
|
3305
|
-
return hasRequiredRole;
|
|
3306
3358
|
}
|
|
3307
3359
|
// If no roles specified, setting is visible
|
|
3308
3360
|
return true;
|
|
3309
|
-
}
|
|
3310
|
-
|
|
3361
|
+
}
|
|
3362
|
+
);
|
|
3363
|
+
|
|
3364
|
+
if (trailingSettings.length > 0) {
|
|
3365
|
+
const actionColumnWidth = actionColumnWidthFor(
|
|
3366
|
+
trailingSettings.length
|
|
3367
|
+
);
|
|
3311
3368
|
|
|
3312
|
-
if (hasVisibleSettings()) {
|
|
3313
3369
|
newColumns.push({
|
|
3314
3370
|
name: 'setting',
|
|
3315
3371
|
header: 'Action',
|
|
3316
|
-
defaultWidth:
|
|
3372
|
+
defaultWidth: actionColumnWidth,
|
|
3373
|
+
minWidth: actionColumnWidth,
|
|
3317
3374
|
textAlign: 'center',
|
|
3318
3375
|
textVerticalAlign:
|
|
3319
3376
|
style && style.text_vertical_align
|
|
@@ -3322,9 +3379,16 @@ const DataGrid = forwardRef(
|
|
|
3322
3379
|
render: ({ data }) => {
|
|
3323
3380
|
return (
|
|
3324
3381
|
<div className={styles.tdactions}>
|
|
3325
|
-
{memoizedSettings
|
|
3326
|
-
|
|
3327
|
-
|
|
3382
|
+
{memoizedSettings
|
|
3383
|
+
.filter(
|
|
3384
|
+
(setting) =>
|
|
3385
|
+
!actionColumnSettingIds.has(
|
|
3386
|
+
setting.id
|
|
3387
|
+
)
|
|
3388
|
+
)
|
|
3389
|
+
.map((setting) =>
|
|
3390
|
+
renderSetting(setting, data)
|
|
3391
|
+
)}
|
|
3328
3392
|
</div>
|
|
3329
3393
|
);
|
|
3330
3394
|
},
|
|
@@ -3405,7 +3469,7 @@ const DataGrid = forwardRef(
|
|
|
3405
3469
|
.catch((error) => {
|
|
3406
3470
|
console.error('Error in fetching dropdown data: ', error);
|
|
3407
3471
|
});
|
|
3408
|
-
}, [columns, filterDataSource, memoizedSettings]);
|
|
3472
|
+
}, [columns, filterDataSource, memoizedSettings, isTabletMode]);
|
|
3409
3473
|
|
|
3410
3474
|
// Force re-render when filterDataSource updates to ensure dropdown filters get their data
|
|
3411
3475
|
const [renderKey, setRenderKey] = useState(0);
|
package/src/components/Field.jsx
CHANGED
|
@@ -44,6 +44,37 @@ import DatePickerPortal from './utils/DatePickerPortal';
|
|
|
44
44
|
|
|
45
45
|
import 'react-toggle/style.css';
|
|
46
46
|
import 'react-datepicker/dist/react-datepicker.css';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Insert `token` at the textarea's caret (or replacing its selection) and
|
|
50
|
+
* fire a native input event so the controlled React value updates. Restores
|
|
51
|
+
* the caret just after the inserted token. Used by textarea fields that
|
|
52
|
+
* declare a `variables` list (click-to-insert).
|
|
53
|
+
*/
|
|
54
|
+
function insertVariableAtCursor(el, token) {
|
|
55
|
+
if (!el) return;
|
|
56
|
+
const start = el.selectionStart ?? el.value.length;
|
|
57
|
+
const end = el.selectionEnd ?? el.value.length;
|
|
58
|
+
const next = el.value.slice(0, start) + token + el.value.slice(end);
|
|
59
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
60
|
+
window.HTMLTextAreaElement.prototype,
|
|
61
|
+
'value'
|
|
62
|
+
)?.set;
|
|
63
|
+
if (setter) {
|
|
64
|
+
setter.call(el, next);
|
|
65
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
66
|
+
}
|
|
67
|
+
const caret = start + token.length;
|
|
68
|
+
requestAnimationFrame(() => {
|
|
69
|
+
el.focus();
|
|
70
|
+
try {
|
|
71
|
+
el.setSelectionRange(caret, caret);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
/* noop */
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
47
78
|
function Field({
|
|
48
79
|
api,
|
|
49
80
|
autocompleteCallback,
|
|
@@ -99,6 +130,7 @@ function Field({
|
|
|
99
130
|
const [lineWidth, setLineWidth] = useState(2);
|
|
100
131
|
const [lineColor, setLineColor] = useState('rgba(0, 0, 0, 1)');
|
|
101
132
|
const sketchRef = useRef(null);
|
|
133
|
+
const variableTextareaRef = useRef(null);
|
|
102
134
|
|
|
103
135
|
/** Email Functions */
|
|
104
136
|
const validateEmail = (email) => {
|
|
@@ -2566,17 +2598,65 @@ function Field({
|
|
|
2566
2598
|
};
|
|
2567
2599
|
|
|
2568
2600
|
return (
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2601
|
+
<>
|
|
2602
|
+
{Array.isArray(settings.variables) &&
|
|
2603
|
+
settings.variables.length > 0 && (
|
|
2604
|
+
<div
|
|
2605
|
+
style={{
|
|
2606
|
+
display: 'flex',
|
|
2607
|
+
flexWrap: 'wrap',
|
|
2608
|
+
gap: '6px',
|
|
2609
|
+
marginBottom: '8px',
|
|
2610
|
+
}}
|
|
2611
|
+
>
|
|
2612
|
+
{settings.variables.map((v) => {
|
|
2613
|
+
const token = v.token ?? v;
|
|
2614
|
+
return (
|
|
2615
|
+
<button
|
|
2616
|
+
type="button"
|
|
2617
|
+
key={token}
|
|
2618
|
+
title={`Insert ${token}`}
|
|
2619
|
+
onClick={() =>
|
|
2620
|
+
insertVariableAtCursor(
|
|
2621
|
+
variableTextareaRef.current,
|
|
2622
|
+
token
|
|
2623
|
+
)
|
|
2624
|
+
}
|
|
2625
|
+
style={{
|
|
2626
|
+
cursor: 'pointer',
|
|
2627
|
+
fontSize: '12px',
|
|
2628
|
+
fontWeight: 500,
|
|
2629
|
+
padding: '3px 9px',
|
|
2630
|
+
borderRadius: '999px',
|
|
2631
|
+
border:
|
|
2632
|
+
'1px solid rgba(var(--primary-rgb), 0.3)',
|
|
2633
|
+
background:
|
|
2634
|
+
'rgba(var(--primary-rgb), 0.06)',
|
|
2635
|
+
color: 'var(--primary-color)',
|
|
2636
|
+
}}
|
|
2637
|
+
>
|
|
2638
|
+
{v.label ?? token}
|
|
2639
|
+
</button>
|
|
2640
|
+
);
|
|
2641
|
+
})}
|
|
2642
|
+
</div>
|
|
2643
|
+
)}
|
|
2644
|
+
<textarea
|
|
2645
|
+
ref={variableTextareaRef}
|
|
2646
|
+
data-name={settings.id}
|
|
2647
|
+
className={inputClass[settings.id]}
|
|
2648
|
+
onChange={onChange}
|
|
2649
|
+
value={
|
|
2650
|
+
inputValue && inputValue !== 'null'
|
|
2651
|
+
? inputValue
|
|
2652
|
+
: ''
|
|
2653
|
+
}
|
|
2654
|
+
rows={
|
|
2655
|
+
settings.rows ||
|
|
2656
|
+
getTextareaRows(settings.textareaHeight)
|
|
2657
|
+
}
|
|
2658
|
+
></textarea>
|
|
2659
|
+
</>
|
|
2580
2660
|
);
|
|
2581
2661
|
case 'text':
|
|
2582
2662
|
return (
|
|
@@ -1145,17 +1145,58 @@ export const renderOptionColumn = ({
|
|
|
1145
1145
|
let newData;
|
|
1146
1146
|
|
|
1147
1147
|
if (data && column.options && column.options[data[column.id]]) {
|
|
1148
|
+
const rawValue = data[column.id];
|
|
1149
|
+
|
|
1148
1150
|
if (
|
|
1149
1151
|
column.key &&
|
|
1150
1152
|
data[column.key] > 0 &&
|
|
1151
|
-
column.options[
|
|
1153
|
+
column.options[rawValue].includes('#')
|
|
1152
1154
|
) {
|
|
1153
|
-
newData = column.options[
|
|
1155
|
+
newData = column.options[rawValue].replace(
|
|
1154
1156
|
/#/g,
|
|
1155
1157
|
data[column.key]
|
|
1156
1158
|
);
|
|
1157
1159
|
} else {
|
|
1158
|
-
newData = column.options[
|
|
1160
|
+
newData = column.options[rawValue];
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Coloured badge variant — opt in via column.badge. Per-value
|
|
1164
|
+
// colours come from column.badgeColours[rawValue] ({ bg, fg,
|
|
1165
|
+
// border }); unmapped values fall back to a neutral pill.
|
|
1166
|
+
// Without column.badge the original plain-text behaviour is
|
|
1167
|
+
// unchanged (backward compatible).
|
|
1168
|
+
if (column.badge) {
|
|
1169
|
+
const palette =
|
|
1170
|
+
(column.badgeColours &&
|
|
1171
|
+
column.badgeColours[rawValue]) || {
|
|
1172
|
+
bg: '#eceff1',
|
|
1173
|
+
fg: '#37474f',
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
return (
|
|
1177
|
+
<CellWithTooltip value={newData} columnType="option">
|
|
1178
|
+
<span
|
|
1179
|
+
style={{
|
|
1180
|
+
display: 'inline-block',
|
|
1181
|
+
padding: '2px 10px',
|
|
1182
|
+
borderRadius: '999px',
|
|
1183
|
+
fontSize: '11px',
|
|
1184
|
+
fontWeight: 600,
|
|
1185
|
+
lineHeight: 1.6,
|
|
1186
|
+
letterSpacing: '0.02em',
|
|
1187
|
+
textTransform: 'uppercase',
|
|
1188
|
+
whiteSpace: 'nowrap',
|
|
1189
|
+
backgroundColor: palette.bg,
|
|
1190
|
+
color: palette.fg,
|
|
1191
|
+
border: palette.border
|
|
1192
|
+
? `1px solid ${palette.border}`
|
|
1193
|
+
: '1px solid transparent',
|
|
1194
|
+
}}
|
|
1195
|
+
>
|
|
1196
|
+
{newData}
|
|
1197
|
+
</span>
|
|
1198
|
+
</CellWithTooltip>
|
|
1199
|
+
);
|
|
1159
1200
|
}
|
|
1160
1201
|
|
|
1161
1202
|
return (
|
|
@@ -231,8 +231,9 @@ function GenericDetail({
|
|
|
231
231
|
layout = 'full',
|
|
232
232
|
setting,
|
|
233
233
|
setActiveNav = null,
|
|
234
|
-
urlParam,
|
|
234
|
+
urlParam = 'dataId',
|
|
235
235
|
userProfile,
|
|
236
|
+
actions = null,
|
|
236
237
|
}) {
|
|
237
238
|
const gridRef = useRef(null);
|
|
238
239
|
const currentConfigRef = useRef(null);
|
|
@@ -2828,6 +2829,17 @@ function GenericDetail({
|
|
|
2828
2829
|
{!inPlaceEditing ? (
|
|
2829
2830
|
<button
|
|
2830
2831
|
className={styles.btn}
|
|
2832
|
+
title="Edit"
|
|
2833
|
+
aria-label="Edit"
|
|
2834
|
+
style={{
|
|
2835
|
+
display: 'inline-flex',
|
|
2836
|
+
alignItems: 'center',
|
|
2837
|
+
justifyContent:
|
|
2838
|
+
'center',
|
|
2839
|
+
gap: '7px',
|
|
2840
|
+
minWidth: '92px',
|
|
2841
|
+
height: '42px',
|
|
2842
|
+
}}
|
|
2831
2843
|
onClick={() => {
|
|
2832
2844
|
if (
|
|
2833
2845
|
activeTabConfig.form
|
|
@@ -2846,7 +2858,11 @@ function GenericDetail({
|
|
|
2846
2858
|
}
|
|
2847
2859
|
}}
|
|
2848
2860
|
>
|
|
2849
|
-
|
|
2861
|
+
<Pencil
|
|
2862
|
+
size={18}
|
|
2863
|
+
strokeWidth={2.25}
|
|
2864
|
+
/>
|
|
2865
|
+
<span>Edit</span>
|
|
2850
2866
|
</button>
|
|
2851
2867
|
) : (
|
|
2852
2868
|
editingTabId ===
|
|
@@ -3786,11 +3802,22 @@ function GenericDetail({
|
|
|
3786
3802
|
}`}
|
|
3787
3803
|
>
|
|
3788
3804
|
<Breadcrumb data={data} page={page} />
|
|
3789
|
-
{total > 0 && (
|
|
3790
|
-
<div
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3805
|
+
{(actions || total > 0) && (
|
|
3806
|
+
<div
|
|
3807
|
+
style={{
|
|
3808
|
+
display: 'flex',
|
|
3809
|
+
alignItems: 'center',
|
|
3810
|
+
gap: '0.75rem',
|
|
3811
|
+
}}
|
|
3812
|
+
>
|
|
3813
|
+
{total > 0 && (
|
|
3814
|
+
<div className={styles.titleInfo}>
|
|
3815
|
+
<span>
|
|
3816
|
+
[<strong>{total}</strong> Total]
|
|
3817
|
+
</span>
|
|
3818
|
+
</div>
|
|
3819
|
+
)}
|
|
3820
|
+
{actions}
|
|
3794
3821
|
</div>
|
|
3795
3822
|
)}
|
|
3796
3823
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import '../styles/global.css';
|
|
2
2
|
|
|
3
|
-
import React, { useCallback, useEffect, useInsertionEffect, useRef, useState } from 'react';
|
|
4
|
-
import { useParams, Outlet } from 'react-router-dom';
|
|
3
|
+
import React, { useCallback, useEffect, useInsertionEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { useParams, useSearchParams, Outlet } from 'react-router-dom';
|
|
5
5
|
import { toast } from 'react-toastify';
|
|
6
6
|
import { saveAs } from 'file-saver';
|
|
7
7
|
import _ from 'lodash';
|
|
@@ -35,6 +35,61 @@ function useWindowDimensions() {
|
|
|
35
35
|
return { windowHeight, windowWidth };
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Returns a copy of `filters` with the `show` flags adjusted so the subnav
|
|
40
|
+
* item (or child) whose id matches `tabId` is active, enabling deep-links
|
|
41
|
+
* such as /page?tab=tourFormats. If `tabId` is empty or matches nothing,
|
|
42
|
+
* the original filters are returned unchanged (default behaviour preserved).
|
|
43
|
+
*/
|
|
44
|
+
function applyTabParam(filters, tabId) {
|
|
45
|
+
if (!filters || !tabId) {
|
|
46
|
+
return filters;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The left subnav highlight is driven by each item's `class` (and
|
|
50
|
+
// `active`) — not `show` — so we must move all three. Reuse whatever
|
|
51
|
+
// class the currently-active item uses so we stay theme-consistent.
|
|
52
|
+
const activeClass =
|
|
53
|
+
filters.find((f) => f.show && f.class)?.class || 'subactive';
|
|
54
|
+
const activeChildClass =
|
|
55
|
+
filters
|
|
56
|
+
.flatMap((f) => f.children || [])
|
|
57
|
+
.find((c) => c.show && c.class)?.class || 'subactivechildren';
|
|
58
|
+
|
|
59
|
+
let matched = false;
|
|
60
|
+
const next = filters.map((f) => {
|
|
61
|
+
if (f.isParent && Array.isArray(f.children)) {
|
|
62
|
+
const childMatch = f.children.some((c) => c.id === tabId);
|
|
63
|
+
if (childMatch) matched = true;
|
|
64
|
+
return {
|
|
65
|
+
...f,
|
|
66
|
+
show: childMatch,
|
|
67
|
+
active: childMatch,
|
|
68
|
+
class: childMatch ? f.class || activeClass : '',
|
|
69
|
+
children: f.children.map((c) => {
|
|
70
|
+
const isMatch = c.id === tabId;
|
|
71
|
+
return {
|
|
72
|
+
...c,
|
|
73
|
+
show: isMatch,
|
|
74
|
+
active: isMatch,
|
|
75
|
+
class: isMatch ? c.class || activeChildClass : '',
|
|
76
|
+
};
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const isMatch = f.id === tabId;
|
|
81
|
+
if (isMatch) matched = true;
|
|
82
|
+
return {
|
|
83
|
+
...f,
|
|
84
|
+
show: isMatch,
|
|
85
|
+
active: isMatch,
|
|
86
|
+
class: isMatch ? activeClass : '',
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return matched ? next : filters;
|
|
91
|
+
}
|
|
92
|
+
|
|
38
93
|
function useConfig(setting, tabs, filters, setRowsSelected, tableSetting) {
|
|
39
94
|
const [config, setConfig] = useState({});
|
|
40
95
|
const [subnav, setSubnav] = useState(filters || []);
|
|
@@ -258,10 +313,20 @@ function GenericIndex({
|
|
|
258
313
|
};
|
|
259
314
|
}, [setting?.tabletMode]);
|
|
260
315
|
|
|
316
|
+
// Honour a ?tab=<id> deep-link by seeding the active subnav item.
|
|
317
|
+
// Memoised so a stable reference is passed to useConfig (otherwise its
|
|
318
|
+
// reset effect would clobber the user's tab clicks every render).
|
|
319
|
+
const [searchParams] = useSearchParams();
|
|
320
|
+
const tabParam = searchParams.get('tab');
|
|
321
|
+
const effectiveFilters = useMemo(
|
|
322
|
+
() => applyTabParam(setting.filters, tabParam),
|
|
323
|
+
[setting.filters, tabParam]
|
|
324
|
+
);
|
|
325
|
+
|
|
261
326
|
const { config, setConfig, subnav, setSubnav } = useConfig(
|
|
262
327
|
setting,
|
|
263
328
|
setting.tabs,
|
|
264
|
-
|
|
329
|
+
effectiveFilters,
|
|
265
330
|
setRowsSelected,
|
|
266
331
|
setting.tableSetting
|
|
267
332
|
);
|