import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover'; import { cn } from '@/lib/utils'; import { isEmpty } from 'lodash'; import { ChevronDown, ChevronRight, Loader2 } from 'lucide-react'; import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from './button'; type TreeId = number | string; export type TreeNodeType = { id: TreeId; title: ReactNode; parentId: TreeId; isLeaf?: boolean; }; type AsyncTreeSelectProps = { treeData: TreeNodeType[]; value?: TreeId; onChange?(value: TreeId): void; loadData?(node: TreeNodeType): Promise; }; export function AsyncTreeSelect({ treeData, value, loadData, onChange, }: AsyncTreeSelectProps) { const [open, setOpen] = useState(false); const { t } = useTranslation(); const [expandedKeys, setExpandedKeys] = useState([]); const [loadingId, setLoadingId] = useState(''); const selectedTitle = useMemo(() => { return treeData.find((x) => x.id === value)?.title; }, [treeData, value]); const isExpanded = useCallback( (id: TreeId | undefined) => { if (id === undefined) { return true; } return expandedKeys.indexOf(id) !== -1; }, [expandedKeys], ); const handleNodeClick = useCallback( (id: TreeId) => (e: React.MouseEvent) => { e.stopPropagation(); onChange?.(id); setOpen(false); }, [onChange], ); const handleArrowClick = useCallback( (node: TreeNodeType) => async (e: React.MouseEvent) => { e.stopPropagation(); const { id } = node; if (isExpanded(id)) { setExpandedKeys((keys) => { return keys.filter((x) => x !== id); }); } else { const hasChild = treeData.some((x) => x.parentId === id); setExpandedKeys((keys) => { return [...keys, id]; }); if (!hasChild) { setLoadingId(id); await loadData?.(node); setLoadingId(''); } } }, [isExpanded, loadData, treeData], ); const renderNodes = useCallback( (parentId?: TreeId) => { const currentLevelList = parentId ? treeData.filter((x) => x.parentId === parentId) : treeData.filter((x) => treeData.every((y) => x.parentId !== y.id)); if (currentLevelList.length === 0) return null; return (
    {currentLevelList.map((x) => (
  • {x.title} {x.isLeaf || ( )}
    {renderNodes(x.id)}
  • ))}
); }, [handleArrowClick, handleNodeClick, isExpanded, loadingId, treeData, value], ); useEffect(() => { if (isEmpty(treeData)) { loadData?.({ id: '', parentId: '', title: '' }); } }, [loadData, treeData]); return (
{selectedTitle || ( {t('common.pleaseSelect')} )}
    {renderNodes()}
); }