import type { GetProp, TreeSelectProps } from 'antd';
import { TreeSelect } from 'antd';
import { cloneDeep as _cloneDeep, omit as _omit, set as _set } from 'lodash-es';
import React, { useEffect, useState, useRef } from 'react';
import newRequest from '../request';
import type { FieldProps } from '../typing';
type DefaultOptionType = GetProp<TreeSelectProps, 'treeData'>[number];
const normalizeNodes = (list: any[] = []): Omit<DefaultOptionType, 'label'>[] =>
list?.map((item) => ({
key: item.value,
value: item.value,
title: item.label ?? item.value,
selectable: true,
isLeaf: item?.hasSubDept !== 'Y',
children: [],
...item,
}));
const appendChildren = (
nodes: Omit<DefaultOptionType, 'label'>[],
targetKey: React.Key,
children: Omit<DefaultOptionType, 'label'>[],
): Omit<DefaultOptionType, 'label'>[] =>
nodes.map((node) => {
if (node.key === targetKey) {
return {
...node,
children,
};
}
if (node.children && node.children.length) {
return {
...node,
children: appendChildren(node.children as any, targetKey, children),
};
}
return node;
});
const ComponentTreeSelect: React.FC = (props: FieldProps) => {
const [treeData, setTreeData] = useState<Omit<DefaultOptionType, 'label'>[]>(
[],
);
const [valueMap, setValueMap] = useState<Record<string, string>>({});
const isInitRef = useRef(false);
const fetchDisplayValue = async (value: string) => {
if (!value || valueMap[value]) return;
const newSearchParams = _cloneDeep(props?.searchParams ?? {});
_set(newSearchParams, 'data.value', value);
try {
const res: any = await newRequest({ cache: true, ...newSearchParams });
const nodeData = res?.data?.data?.[0];
if (nodeData) {
setValueMap((prev) => ({
...prev,
[value]: nodeData.label || nodeData.title || value,
}));
}
} catch (error) {
console.warn('Failed to fetch display value:', error);
}
};
useEffect(() => {
const loadRootData = async () => {
try {
const rootRequest = newRequest({
cache: true,
...(props?.searchParams ?? {}),
});
const rootRes: any = await rootRequest;
const finalTreeData = normalizeNodes(rootRes?.data?.data || []);
setTreeData(finalTreeData);
isInitRef.current = true;
if (props.value) {
fetchDisplayValue(props.value);
}
} catch (error) {
console.warn('Failed to load root data:', error);
isInitRef.current = true;
}
};
loadRootData();
}, []);
useEffect(() => {
if (isInitRef.current && props.value) {
fetchDisplayValue(props.value);
}
}, [props.value]);
const onLoadData: TreeSelectProps['loadData'] = ({ key }) =>
new Promise((resolve) => {
if (!key) {
resolve(undefined);
return;
}
const newSearchParams = _cloneDeep(props?.searchParams ?? {});
_set(newSearchParams, 'data.code', key);
newRequest({ cache: true, ...newSearchParams }).then((res: any) => {
const childrenNodes = normalizeNodes(res?.data?.data || []);
setTreeData((prev) => appendChildren(prev, key, childrenNodes));
resolve(undefined);
});
});
const enhancedTreeData = React.useMemo(() => {
const updateNodeTitles = (
nodes: Omit<DefaultOptionType, 'label'>[],
): Omit<DefaultOptionType, 'label'>[] => {
return nodes.map((node) => {
const displayName = valueMap[node.value as string];
return {
...node,
title: displayName || node.title,
children: node.children ? updateNodeTitles(node.children as any) : [],
};
});
};
let result = updateNodeTitles(treeData);
if (props.value && valueMap[props.value]) {
const existsInTree = (nodes: any[], value: string): boolean => {
return nodes.some(
(node) =>
node.value === value ||
(node.children && existsInTree(node.children, value)),
);
};
if (!existsInTree(result, props.value)) {
result = [
{
key: props.value,
value: props.value,
title: valueMap[props.value],
selectable: false,
disabled: true,
isLeaf: true,
children: [],
style: { display: 'none' },
className: 'tree-select-hidden-node',
},
...result,
];
}
}
return result;
}, [treeData, valueMap, props.value]);
return (
<TreeSelect
maxTagCount={20}
treeData={enhancedTreeData}
loadData={onLoadData}
{..._omit(props, ['searchParams'])}
/>
);
};
export default ComponentTreeSelect;