@evergis/react 4.0.76 → 4.0.78
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/components/Dashboard/containers/FiltersContainer/hooks/useTreeFilter.d.ts +2 -0
- package/dist/components/Dashboard/containers/FiltersContainer/hooks/useTreeFilterData.d.ts +2 -1
- package/dist/components/Dashboard/containers/FiltersContainer/hooks/useTreeFilterSearch.d.ts +2 -2
- package/dist/components/Dashboard/containers/FiltersContainer/hooks/useTreeFilterSelection.d.ts +2 -0
- package/dist/components/Dashboard/containers/FiltersContainer/hooks/useTreeFilterTree.d.ts +3 -2
- package/dist/components/Dashboard/containers/FiltersContainer/types.d.ts +7 -2
- package/dist/components/Dashboard/containers/FiltersContainer/utils/treeFilterUtils.d.ts +12 -7
- package/dist/components/Dashboard/types.d.ts +9 -2
- package/dist/index.js +87 -53
- package/dist/index.js.map +1 -1
- package/dist/react.esm.js +87 -53
- package/dist/react.esm.js.map +1 -1
- package/package.json +3 -3
|
@@ -7,5 +7,6 @@ export declare const useTreeFilterData: (type: WidgetType, filterName: string) =
|
|
|
7
7
|
loadLevelOne: () => Promise<TreeItemProps[]>;
|
|
8
8
|
loadChildren: (item: TreeItemProps) => Promise<PartialLoadData<TreeNodeData>>;
|
|
9
9
|
searchNodes: (query: string) => Promise<TreeItemProps[]>;
|
|
10
|
-
|
|
10
|
+
loadByNames: (names: TreeId[]) => Promise<TreeItemProps[]>;
|
|
11
|
+
loadByValues: (values: TreeId[]) => Promise<TreeItemProps[]>;
|
|
11
12
|
};
|
package/dist/components/Dashboard/containers/FiltersContainer/hooks/useTreeFilterSearch.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { TreeId, TreeItemProps } from '@evergis/uilib-gl';
|
|
2
2
|
type UseTreeFilterSearchParams = {
|
|
3
3
|
searchNodes: (query: string) => Promise<TreeItemProps[]>;
|
|
4
|
-
|
|
4
|
+
loadByNames: (names: TreeId[]) => Promise<TreeItemProps[]>;
|
|
5
5
|
};
|
|
6
|
-
export declare const useTreeFilterSearch: ({ searchNodes,
|
|
6
|
+
export declare const useTreeFilterSearch: ({ searchNodes, loadByNames }: UseTreeFilterSearchParams) => {
|
|
7
7
|
searchValue: string;
|
|
8
8
|
isSearchActive: boolean;
|
|
9
9
|
searching: boolean;
|
package/dist/components/Dashboard/containers/FiltersContainer/hooks/useTreeFilterSelection.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export declare const useTreeFilterSelection: (type: WidgetType, filterName: stri
|
|
|
4
4
|
selected: Map<TreeId, number>;
|
|
5
5
|
selectedIds: Set<TreeId>;
|
|
6
6
|
selectedLabel: string;
|
|
7
|
+
dirty: boolean;
|
|
7
8
|
onToggleSelect: (item: TreeItemProps, multiSelect?: boolean) => void;
|
|
8
9
|
onReset: () => void;
|
|
10
|
+
onSave: () => void;
|
|
9
11
|
};
|
|
@@ -2,9 +2,10 @@ import { TreeId, TreeItemProps } from '@evergis/uilib-gl';
|
|
|
2
2
|
type UseTreeFilterTreeParams = {
|
|
3
3
|
selected: Map<TreeId, number>;
|
|
4
4
|
loadLevelOne: () => Promise<TreeItemProps[]>;
|
|
5
|
-
|
|
5
|
+
loadByValues: (values: TreeId[]) => Promise<TreeItemProps[]>;
|
|
6
|
+
loadByNames: (names: TreeId[]) => Promise<TreeItemProps[]>;
|
|
6
7
|
};
|
|
7
|
-
export declare const useTreeFilterTree: ({ selected, loadLevelOne,
|
|
8
|
+
export declare const useTreeFilterTree: ({ selected, loadLevelOne, loadByValues, loadByNames }: UseTreeFilterTreeParams) => {
|
|
8
9
|
treeData: TreeItemProps[];
|
|
9
10
|
setTreeData: import('react').Dispatch<import('react').SetStateAction<TreeItemProps[]>>;
|
|
10
11
|
treeExpanded: TreeId[];
|
|
@@ -6,9 +6,14 @@ export type FilterOption = {
|
|
|
6
6
|
min: string | number;
|
|
7
7
|
max: string | number;
|
|
8
8
|
};
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Доменные данные узла иерархического фильтра "tree", хранятся в `TreeItemProps.data`.
|
|
11
|
+
* `name`/`parentName` — служебная связь дерева (по `attributeName`/`attributeParentName`);
|
|
12
|
+
* идентичность/значение узла лежит в `TreeItemProps.id` (по `attributeValue`).
|
|
13
|
+
*/
|
|
10
14
|
export type TreeNodeData = {
|
|
11
|
-
|
|
15
|
+
name: TreeId;
|
|
16
|
+
parentName: TreeId | null;
|
|
12
17
|
level: number;
|
|
13
18
|
};
|
|
14
19
|
export type WidgetFilterProps = {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { TreeId, TreeItemProps } from '@evergis/uilib-gl';
|
|
2
2
|
import { TreeFilterValue } from '../../../types';
|
|
3
3
|
export declare const buildLevelOneCondition: (attributeLevel: string) => string;
|
|
4
|
-
export declare const buildChildrenCondition: (
|
|
4
|
+
export declare const buildChildrenCondition: (attributeParentName: string, parentName: TreeId) => string;
|
|
5
5
|
export declare const buildSearchCondition: (attributeAlias: string, query: string) => string;
|
|
6
|
-
|
|
6
|
+
/** Запрос пачки записей по их именам (`attributeName`) — для достройки предков. */
|
|
7
|
+
export declare const buildNamesCondition: (attributeName: string, names: Array<string | number>) => string;
|
|
8
|
+
/** Запрос пачки записей по их значениям (`attributeValue`) — для восстановления `defaultValue`. */
|
|
9
|
+
export declare const buildValuesCondition: (attributeValue: string, values: Array<string | number>) => string;
|
|
7
10
|
/** Группирует выбранные id по уровню в объектное значение фильтра `{ l{N}: [ids] }`. */
|
|
8
11
|
export declare const buildFilterValue: (selected: Map<TreeId, number>) => TreeFilterValue;
|
|
9
12
|
/** Разбирает объектное значение фильтра в карту `id → level`. */
|
|
@@ -11,13 +14,15 @@ export declare const parseFilterValue: (value?: TreeFilterValue) => Map<TreeId,
|
|
|
11
14
|
/** Стабильная сериализация выбора для сравнения (порядок не важен). */
|
|
12
15
|
export declare const serializeSelection: (selected: Map<TreeId, number>) => string;
|
|
13
16
|
/**
|
|
14
|
-
* Дозапрашивает цепочку предков вверх по `
|
|
15
|
-
* пока не достигнет уровня 1.
|
|
17
|
+
* Дозапрашивает цепочку предков вверх по `parentName` уровень за уровнем (батч `attributeName in
|
|
18
|
+
* (...)`), пока не достигнет уровня 1. Связь идёт по именам (`name`/`parentName`), т.к. `node.id`
|
|
19
|
+
* хранит значение (`attributeValue`). Возвращает исходные узлы вместе с предками.
|
|
16
20
|
*/
|
|
17
|
-
export declare const collectAncestors: (nodes: TreeItemProps[],
|
|
21
|
+
export declare const collectAncestors: (nodes: TreeItemProps[], loadByNames: (names: TreeId[]) => Promise<TreeItemProps[]>) => Promise<TreeItemProps[]>;
|
|
18
22
|
/**
|
|
19
|
-
* Собирает плоский набор узлов в дерево по `
|
|
20
|
-
*
|
|
23
|
+
* Собирает плоский набор узлов в дерево по связи `parentName` ↔ `name`. Узлы с подгруженными
|
|
24
|
+
* потомками раскрываются (`expanded` хранит `node.id` = значение); листья-папки без потомков
|
|
25
|
+
* получают `children = null` (ленивая подгрузка).
|
|
21
26
|
*/
|
|
22
27
|
export declare const assembleTree: (nodes: TreeItemProps[]) => {
|
|
23
28
|
roots: TreeItemProps[];
|
|
@@ -326,11 +326,18 @@ export interface ConfigFilter {
|
|
|
326
326
|
relatedDataSource?: string;
|
|
327
327
|
defaultValue?: SelectedFilter["value"];
|
|
328
328
|
attributeAlias?: string;
|
|
329
|
+
/**
|
|
330
|
+
* Атрибут хранимого значения фильтра — то, что фильтр отдаёт потребителям (плейсхолдеры
|
|
331
|
+
* `%name`, для "tree" — `%name.lN`). Для "tree" служит ещё и идентичностью узла дерева
|
|
332
|
+
* (`TreeItemProps.id`). Если не задан — фолбэк на {@link ConfigFilter.attributeName}.
|
|
333
|
+
*/
|
|
329
334
|
attributeValue?: string;
|
|
330
335
|
attributeMin?: string;
|
|
331
336
|
attributeMax?: string;
|
|
332
|
-
/**
|
|
333
|
-
|
|
337
|
+
/** Атрибут-имя записи — служебная связь дерева (`child.attributeParentName == parent.attributeName`); для "tree". */
|
|
338
|
+
attributeName?: string;
|
|
339
|
+
/** Атрибут-имя родителя — служебная связь дерева; для "tree". */
|
|
340
|
+
attributeParentName?: string;
|
|
334
341
|
/** Атрибут числового уровня (корень = 1) — для "tree". */
|
|
335
342
|
attributeLevel?: string;
|
|
336
343
|
/** Атрибут-флаг наличия детей (показ шеврона) — для "tree". */
|
package/dist/index.js
CHANGED
|
@@ -11446,9 +11446,12 @@ function getIconFromAttribute(option, iconAttribute) {
|
|
|
11446
11446
|
|
|
11447
11447
|
const MAX_ANCESTOR_DEPTH = 50;
|
|
11448
11448
|
const buildLevelOneCondition = (attributeLevel) => `${attributeLevel} = 1`;
|
|
11449
|
-
const buildChildrenCondition = (
|
|
11449
|
+
const buildChildrenCondition = (attributeParentName, parentName) => `${attributeParentName} = ${formatConditionValue({ value: parentName })}`;
|
|
11450
11450
|
const buildSearchCondition = (attributeAlias, query) => `${attributeAlias} LIKE '%${query.replace(/'/g, "''")}%'`;
|
|
11451
|
-
|
|
11451
|
+
/** Запрос пачки записей по их именам (`attributeName`) — для достройки предков. */
|
|
11452
|
+
const buildNamesCondition = (attributeName, names) => `${attributeName} in ${formatConditionValue({ value: names })}`;
|
|
11453
|
+
/** Запрос пачки записей по их значениям (`attributeValue`) — для восстановления `defaultValue`. */
|
|
11454
|
+
const buildValuesCondition = (attributeValue, values) => `${attributeValue} in ${formatConditionValue({ value: values })}`;
|
|
11452
11455
|
/** Группирует выбранные id по уровню в объектное значение фильтра `{ l{N}: [ids] }`. */
|
|
11453
11456
|
const buildFilterValue = (selected) => {
|
|
11454
11457
|
const result = {};
|
|
@@ -11473,54 +11476,62 @@ const parseFilterValue = (value) => {
|
|
|
11473
11476
|
});
|
|
11474
11477
|
return result;
|
|
11475
11478
|
};
|
|
11479
|
+
/** Стабильная сериализация выбора для сравнения (порядок не важен). */
|
|
11480
|
+
const serializeSelection = (selected) => [...selected.entries()]
|
|
11481
|
+
.map(([id, level]) => `${id}:${level}`)
|
|
11482
|
+
.sort()
|
|
11483
|
+
.join(",");
|
|
11476
11484
|
/**
|
|
11477
|
-
* Дозапрашивает цепочку предков вверх по `
|
|
11478
|
-
* пока не достигнет уровня 1.
|
|
11485
|
+
* Дозапрашивает цепочку предков вверх по `parentName` уровень за уровнем (батч `attributeName in
|
|
11486
|
+
* (...)`), пока не достигнет уровня 1. Связь идёт по именам (`name`/`parentName`), т.к. `node.id`
|
|
11487
|
+
* хранит значение (`attributeValue`). Возвращает исходные узлы вместе с предками.
|
|
11479
11488
|
*/
|
|
11480
|
-
const collectAncestors = async (nodes,
|
|
11481
|
-
const
|
|
11482
|
-
nodes.forEach(node =>
|
|
11489
|
+
const collectAncestors = async (nodes, loadByNames) => {
|
|
11490
|
+
const byName = new Map();
|
|
11491
|
+
nodes.forEach(node => byName.set(node.data.name, node));
|
|
11483
11492
|
let frontier = nodes;
|
|
11484
11493
|
for (let depth = 0; depth < MAX_ANCESTOR_DEPTH; depth++) {
|
|
11485
11494
|
const missingParents = new Set();
|
|
11486
11495
|
frontier.forEach(node => {
|
|
11487
|
-
const {
|
|
11488
|
-
if (
|
|
11489
|
-
missingParents.add(
|
|
11496
|
+
const { parentName, level } = node.data ?? {};
|
|
11497
|
+
if (parentName != null && level > 1 && !byName.has(parentName)) {
|
|
11498
|
+
missingParents.add(parentName);
|
|
11490
11499
|
}
|
|
11491
11500
|
});
|
|
11492
11501
|
if (!missingParents.size) {
|
|
11493
11502
|
break;
|
|
11494
11503
|
}
|
|
11495
|
-
const parents = await
|
|
11504
|
+
const parents = await loadByNames([...missingParents]);
|
|
11496
11505
|
if (!parents.length) {
|
|
11497
11506
|
break;
|
|
11498
11507
|
}
|
|
11499
|
-
parents.forEach(parent =>
|
|
11508
|
+
parents.forEach(parent => byName.set(parent.data.name, parent));
|
|
11500
11509
|
frontier = parents;
|
|
11501
11510
|
}
|
|
11502
|
-
return [...
|
|
11511
|
+
return [...byName.values()];
|
|
11503
11512
|
};
|
|
11504
11513
|
/**
|
|
11505
|
-
* Собирает плоский набор узлов в дерево по `
|
|
11506
|
-
*
|
|
11514
|
+
* Собирает плоский набор узлов в дерево по связи `parentName` ↔ `name`. Узлы с подгруженными
|
|
11515
|
+
* потомками раскрываются (`expanded` хранит `node.id` = значение); листья-папки без потомков
|
|
11516
|
+
* получают `children = null` (ленивая подгрузка).
|
|
11507
11517
|
*/
|
|
11508
11518
|
const assembleTree = (nodes) => {
|
|
11509
|
-
const
|
|
11510
|
-
nodes.forEach(node =>
|
|
11519
|
+
const nodesByName = new Map();
|
|
11520
|
+
nodes.forEach(node => nodesByName.set(node.data.name, node));
|
|
11511
11521
|
const childrenMap = new Map();
|
|
11512
|
-
|
|
11513
|
-
const
|
|
11514
|
-
if (
|
|
11515
|
-
const siblings = childrenMap.get(
|
|
11522
|
+
nodesByName.forEach(node => {
|
|
11523
|
+
const parentName = node.data?.parentName;
|
|
11524
|
+
if (parentName != null && nodesByName.has(parentName)) {
|
|
11525
|
+
const siblings = childrenMap.get(parentName) ?? [];
|
|
11516
11526
|
siblings.push(node);
|
|
11517
|
-
childrenMap.set(
|
|
11527
|
+
childrenMap.set(parentName, siblings);
|
|
11518
11528
|
}
|
|
11519
11529
|
});
|
|
11520
11530
|
const roots = [];
|
|
11521
11531
|
const expanded = [];
|
|
11522
|
-
|
|
11523
|
-
const
|
|
11532
|
+
nodesByName.forEach(node => {
|
|
11533
|
+
const { name, parentName } = node.data;
|
|
11534
|
+
const kids = childrenMap.get(name);
|
|
11524
11535
|
if (kids?.length) {
|
|
11525
11536
|
node.children = kids;
|
|
11526
11537
|
expanded.push(node.id);
|
|
@@ -11528,8 +11539,7 @@ const assembleTree = (nodes) => {
|
|
|
11528
11539
|
else if (node.isLeaf) {
|
|
11529
11540
|
node.children = null;
|
|
11530
11541
|
}
|
|
11531
|
-
|
|
11532
|
-
if (parentId == null || !nodesById.has(parentId)) {
|
|
11542
|
+
if (parentName == null || !nodesByName.has(parentName)) {
|
|
11533
11543
|
roots.push(node);
|
|
11534
11544
|
}
|
|
11535
11545
|
});
|
|
@@ -11540,22 +11550,24 @@ const useTreeFilterData = (type, filterName) => {
|
|
|
11540
11550
|
const { api } = useGlobalContext();
|
|
11541
11551
|
const { currentPage } = useWidgetPage(type);
|
|
11542
11552
|
const configFilter = React.useMemo(() => getConfigFilter(filterName, currentPage?.filters), [filterName, currentPage?.filters]);
|
|
11543
|
-
const { relatedDataSource,
|
|
11553
|
+
const { relatedDataSource, attributeName = DEFAULT_ATTRIBUTE_NAME, attributeValue, attributeAlias = DEFAULT_ATTRIBUTE_NAME, attributeParentName, attributeLevel, attributeHasChildren, limit = DEFAULT_DATA_SOURCE_LIMIT, } = configFilter ?? {};
|
|
11554
|
+
const valueAttribute = attributeValue ?? attributeName;
|
|
11544
11555
|
const layerName = React.useMemo(() => currentPage?.dataSources?.find(({ name }) => name === relatedDataSource)?.layerName, [currentPage?.dataSources, relatedDataSource]);
|
|
11545
11556
|
const mapNode = React.useCallback(({ properties }) => {
|
|
11546
11557
|
const hasChildren = attributeHasChildren ? !!properties[attributeHasChildren] : false;
|
|
11547
11558
|
const data = {
|
|
11548
|
-
|
|
11559
|
+
name: properties[attributeName],
|
|
11560
|
+
parentName: attributeParentName ? properties[attributeParentName] ?? null : null,
|
|
11549
11561
|
level: attributeLevel ? Number(properties[attributeLevel]) : 1,
|
|
11550
11562
|
};
|
|
11551
11563
|
return {
|
|
11552
|
-
id: properties[
|
|
11564
|
+
id: properties[valueAttribute],
|
|
11553
11565
|
text: String(properties[attributeAlias] ?? ""),
|
|
11554
11566
|
isLeaf: hasChildren,
|
|
11555
11567
|
children: hasChildren ? null : undefined,
|
|
11556
11568
|
data,
|
|
11557
11569
|
};
|
|
11558
|
-
}, [
|
|
11570
|
+
}, [valueAttribute, attributeName, attributeAlias, attributeParentName, attributeLevel, attributeHasChildren]);
|
|
11559
11571
|
const runQuery = React.useCallback(async (condition) => {
|
|
11560
11572
|
if (!layerName) {
|
|
11561
11573
|
return [];
|
|
@@ -11571,29 +11583,36 @@ const useTreeFilterData = (type, filterName) => {
|
|
|
11571
11583
|
return features.map(mapNode);
|
|
11572
11584
|
}, [attributeLevel, runQuery, mapNode]);
|
|
11573
11585
|
const loadChildren = React.useCallback(async (item) => {
|
|
11574
|
-
if (!
|
|
11586
|
+
if (!attributeParentName) {
|
|
11575
11587
|
return { children: [] };
|
|
11576
11588
|
}
|
|
11577
|
-
const features = await runQuery(buildChildrenCondition(
|
|
11589
|
+
const features = await runQuery(buildChildrenCondition(attributeParentName, item.data.name));
|
|
11578
11590
|
return { children: features.map(mapNode) };
|
|
11579
|
-
}, [
|
|
11591
|
+
}, [attributeParentName, runQuery, mapNode]);
|
|
11580
11592
|
const searchNodes = React.useCallback(async (query) => {
|
|
11581
11593
|
const features = await runQuery(buildSearchCondition(attributeAlias, query));
|
|
11582
11594
|
return features.map(mapNode);
|
|
11583
11595
|
}, [attributeAlias, runQuery, mapNode]);
|
|
11584
|
-
const
|
|
11585
|
-
if (!
|
|
11596
|
+
const loadByNames = React.useCallback(async (names) => {
|
|
11597
|
+
if (!names.length) {
|
|
11586
11598
|
return [];
|
|
11587
11599
|
}
|
|
11588
|
-
const features = await runQuery(
|
|
11600
|
+
const features = await runQuery(buildNamesCondition(attributeName, names));
|
|
11589
11601
|
return features.map(mapNode);
|
|
11590
|
-
}, [
|
|
11591
|
-
|
|
11602
|
+
}, [attributeName, runQuery, mapNode]);
|
|
11603
|
+
const loadByValues = React.useCallback(async (values) => {
|
|
11604
|
+
if (!values.length) {
|
|
11605
|
+
return [];
|
|
11606
|
+
}
|
|
11607
|
+
const features = await runQuery(buildValuesCondition(valueAttribute, values));
|
|
11608
|
+
return features.map(mapNode);
|
|
11609
|
+
}, [valueAttribute, runQuery, mapNode]);
|
|
11610
|
+
return { configFilter, layerName, loadLevelOne, loadChildren, searchNodes, loadByNames, loadByValues };
|
|
11592
11611
|
};
|
|
11593
11612
|
|
|
11594
11613
|
const SEARCH_MIN_LENGTH = 3;
|
|
11595
11614
|
const SEARCH_DEBOUNCE = 300;
|
|
11596
|
-
const useTreeFilterSearch = ({ searchNodes,
|
|
11615
|
+
const useTreeFilterSearch = ({ searchNodes, loadByNames }) => {
|
|
11597
11616
|
const [searchValue, setSearchValue] = React.useState("");
|
|
11598
11617
|
const [searchData, setSearchData] = React.useState([]);
|
|
11599
11618
|
const [searchExpanded, setSearchExpanded] = React.useState([]);
|
|
@@ -11606,7 +11625,7 @@ const useTreeFilterSearch = ({ searchNodes, loadByIds }) => {
|
|
|
11606
11625
|
setSearching(true);
|
|
11607
11626
|
try {
|
|
11608
11627
|
const results = await searchNodes(query);
|
|
11609
|
-
const withAncestors = await collectAncestors(results,
|
|
11628
|
+
const withAncestors = await collectAncestors(results, loadByNames);
|
|
11610
11629
|
const { roots, expanded } = assembleTree(withAncestors);
|
|
11611
11630
|
if (id !== requestId.current) {
|
|
11612
11631
|
return;
|
|
@@ -11619,7 +11638,7 @@ const useTreeFilterSearch = ({ searchNodes, loadByIds }) => {
|
|
|
11619
11638
|
setSearching(false);
|
|
11620
11639
|
}
|
|
11621
11640
|
}
|
|
11622
|
-
}, [searchNodes,
|
|
11641
|
+
}, [searchNodes, loadByNames]);
|
|
11623
11642
|
const clearSearchState = React.useCallback(() => {
|
|
11624
11643
|
requestId.current++;
|
|
11625
11644
|
setSearchData([]);
|
|
@@ -11663,6 +11682,9 @@ const useTreeFilterSelection = (type, filterName, configFilter) => {
|
|
|
11663
11682
|
const [selected, setSelected] = React.useState(() => parseFilterValue(configFilter?.defaultValue));
|
|
11664
11683
|
const [selectedLabel, setSelectedLabel] = React.useState();
|
|
11665
11684
|
const selectedIds = React.useMemo(() => new Set(selected.keys()), [selected]);
|
|
11685
|
+
/** Уже применённый (в redux) выбор — база для `dirty`. */
|
|
11686
|
+
const committed = React.useMemo(() => parseFilterValue((filters[filterName]?.value ?? configFilter?.defaultValue)), [filters, filterName, configFilter?.defaultValue]);
|
|
11687
|
+
const dirty = React.useMemo(() => serializeSelection(selected) !== serializeSelection(committed), [selected, committed]);
|
|
11666
11688
|
const commit = React.useCallback((next) => {
|
|
11667
11689
|
setSelected(next);
|
|
11668
11690
|
changeFilters({ [filterName]: { value: buildFilterValue(next) } }, configFilter?.resetFilters);
|
|
@@ -11683,8 +11705,17 @@ const useTreeFilterSelection = (type, filterName, configFilter) => {
|
|
|
11683
11705
|
}
|
|
11684
11706
|
next.set(item.id, level);
|
|
11685
11707
|
}
|
|
11686
|
-
|
|
11708
|
+
// Мультивыбор не коммитит в redux — сохранение по кнопке (onSave). Single — сразу.
|
|
11709
|
+
if (multiSelect) {
|
|
11710
|
+
setSelected(next);
|
|
11711
|
+
}
|
|
11712
|
+
else {
|
|
11713
|
+
commit(next);
|
|
11714
|
+
}
|
|
11687
11715
|
}, [selected, commit]);
|
|
11716
|
+
const onSave = React.useCallback(() => {
|
|
11717
|
+
changeFilters({ [filterName]: { value: buildFilterValue(selected) } }, configFilter?.resetFilters);
|
|
11718
|
+
}, [changeFilters, filterName, selected, configFilter?.resetFilters]);
|
|
11688
11719
|
const onReset = React.useCallback(() => {
|
|
11689
11720
|
setSelectedLabel(undefined);
|
|
11690
11721
|
commit(new Map());
|
|
@@ -11699,10 +11730,10 @@ const useTreeFilterSelection = (type, filterName, configFilter) => {
|
|
|
11699
11730
|
changeFilters({ [filterName]: { value: buildFilterValue(selected) } });
|
|
11700
11731
|
}
|
|
11701
11732
|
}, [filters, filterName, changeFilters, selected]);
|
|
11702
|
-
return { selected, selectedIds, selectedLabel, onToggleSelect, onReset };
|
|
11733
|
+
return { selected, selectedIds, selectedLabel, dirty, onToggleSelect, onReset, onSave };
|
|
11703
11734
|
};
|
|
11704
11735
|
|
|
11705
|
-
const useTreeFilterTree = ({ selected, loadLevelOne,
|
|
11736
|
+
const useTreeFilterTree = ({ selected, loadLevelOne, loadByValues, loadByNames }) => {
|
|
11706
11737
|
const [treeData, setTreeData] = React.useState([]);
|
|
11707
11738
|
const [treeExpanded, setTreeExpanded] = React.useState([]);
|
|
11708
11739
|
const [loading, setLoading] = React.useState(false);
|
|
@@ -11722,8 +11753,8 @@ const useTreeFilterTree = ({ selected, loadLevelOne, loadByIds }) => {
|
|
|
11722
11753
|
setTreeData(roots);
|
|
11723
11754
|
return;
|
|
11724
11755
|
}
|
|
11725
|
-
const selectedNodes = await
|
|
11726
|
-
const withAncestors = await collectAncestors([...roots, ...selectedNodes],
|
|
11756
|
+
const selectedNodes = await loadByValues(preselected);
|
|
11757
|
+
const withAncestors = await collectAncestors([...roots, ...selectedNodes], loadByNames);
|
|
11727
11758
|
const { roots: assembled, expanded } = assembleTree(withAncestors);
|
|
11728
11759
|
setTreeData(assembled);
|
|
11729
11760
|
setTreeExpanded(expanded);
|
|
@@ -11731,20 +11762,21 @@ const useTreeFilterTree = ({ selected, loadLevelOne, loadByIds }) => {
|
|
|
11731
11762
|
finally {
|
|
11732
11763
|
setLoading(false);
|
|
11733
11764
|
}
|
|
11734
|
-
}, [loadLevelOne,
|
|
11765
|
+
}, [loadLevelOne, loadByValues, loadByNames]);
|
|
11735
11766
|
return { treeData, setTreeData, treeExpanded, setTreeExpanded, loading, onOpen };
|
|
11736
11767
|
};
|
|
11737
11768
|
|
|
11738
11769
|
const useTreeFilter = (type, filter) => {
|
|
11739
11770
|
const { filterName, multiSelect, placeholder, width } = filter.options ?? {};
|
|
11740
|
-
const { configFilter, loadLevelOne, loadChildren, searchNodes,
|
|
11741
|
-
const { selected, selectedIds, selectedLabel, onToggleSelect: toggleSelect, onReset } = useTreeFilterSelection(type, filterName ?? "", configFilter);
|
|
11771
|
+
const { configFilter, loadLevelOne, loadChildren, searchNodes, loadByNames, loadByValues } = useTreeFilterData(type, filterName ?? "");
|
|
11772
|
+
const { selected, selectedIds, selectedLabel, dirty, onToggleSelect: toggleSelect, onReset, onSave, } = useTreeFilterSelection(type, filterName ?? "", configFilter);
|
|
11742
11773
|
const { treeData, setTreeData, treeExpanded, setTreeExpanded, loading, onOpen } = useTreeFilterTree({
|
|
11743
11774
|
selected,
|
|
11744
11775
|
loadLevelOne,
|
|
11745
|
-
|
|
11776
|
+
loadByValues,
|
|
11777
|
+
loadByNames,
|
|
11746
11778
|
});
|
|
11747
|
-
const search = useTreeFilterSearch({ searchNodes,
|
|
11779
|
+
const search = useTreeFilterSearch({ searchNodes, loadByNames });
|
|
11748
11780
|
const onToggleSelect = React.useCallback(item => toggleSelect(item, multiSelect), [toggleSelect, multiSelect]);
|
|
11749
11781
|
const onClose = React.useCallback(() => search.resetSearch(), [search]);
|
|
11750
11782
|
const view = React.useMemo(() => search.isSearchActive
|
|
@@ -11769,12 +11801,14 @@ const useTreeFilter = (type, filter) => {
|
|
|
11769
11801
|
onClose,
|
|
11770
11802
|
onToggleSelect,
|
|
11771
11803
|
onReset,
|
|
11804
|
+
onSave,
|
|
11805
|
+
dirty,
|
|
11772
11806
|
};
|
|
11773
11807
|
};
|
|
11774
11808
|
|
|
11775
11809
|
const TreeFilter = ({ type, filter }) => {
|
|
11776
|
-
const { width, placeholder, multiSelect, loading, data, setData, expanded, setExpanded, onLoadChildren, selectedIds, selectedCount, selectedLabel, searchValue, onSearchChange, onOpen, onClose, onToggleSelect, onReset, } = useTreeFilter(type, filter);
|
|
11777
|
-
return (jsxRuntime.jsx(uilibGl.TreeDropdown, { width: width, placeholder: placeholder, multiSelect: multiSelect, loading: loading, data: data, setData: setData, expanded: expanded, setExpanded: setExpanded, onLoadChildren: onLoadChildren, selectedIds: selectedIds, selectedCount: selectedCount, selectedLabel: selectedLabel, searchValue: searchValue, onSearchChange: onSearchChange, onOpen: onOpen, onClose: onClose, onToggleSelect: onToggleSelect, onReset: onReset }));
|
|
11810
|
+
const { width, placeholder, multiSelect, loading, data, setData, expanded, setExpanded, onLoadChildren, selectedIds, selectedCount, selectedLabel, searchValue, onSearchChange, onOpen, onClose, onToggleSelect, onReset, onSave, dirty, } = useTreeFilter(type, filter);
|
|
11811
|
+
return (jsxRuntime.jsx(uilibGl.TreeDropdown, { zIndex: 100, width: width, placeholder: placeholder, multiSelect: multiSelect, loading: loading, data: data, setData: setData, expanded: expanded, setExpanded: setExpanded, onLoadChildren: onLoadChildren, selectedIds: selectedIds, selectedCount: selectedCount, selectedLabel: selectedLabel, searchValue: searchValue, onSearchChange: onSearchChange, onOpen: onOpen, onClose: onClose, onToggleSelect: onToggleSelect, onReset: onReset, onSave: onSave, dirty: dirty }));
|
|
11778
11812
|
};
|
|
11779
11813
|
|
|
11780
11814
|
const getFilterComponent = (filterType) => {
|