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