import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { collectSkillLeavesFromTree, buildSkillCatalogTree } from '../utils/skillCatalogTree' import SkillTreePickerPanel from './SkillTreePickerPanel' function normId(id) { return String(id) } /** * Mehrfachauswahl Fähigkeiten mit hierarchischer Treeview („Alle“) und Pfad-Suche. */ export default function SkillTreeMultiSelect({ value = [], onChange, skills = [], placeholder = 'Fähigkeit suchen …', browseLabel = '▼ Katalog', emptyHint = 'Keine Treffer', className = '', }) { const [query, setQuery] = useState('') const [open, setOpen] = useState(false) const [browseTree, setBrowseTree] = useState(false) const rootRef = useRef(null) const tree = useMemo(() => buildSkillCatalogTree(skills), [skills]) const selectedSet = useMemo(() => new Set(value.map(normId)), [value]) const leaves = useMemo(() => collectSkillLeavesFromTree(tree, value), [tree, value]) const selectedLabels = useMemo(() => { return value.map((id) => { const leaf = leaves.find((l) => normId(l.id) === normId(id)) || leaves.find(() => false) const fromSkills = skills.find((s) => normId(s.id) === normId(id)) return leaf?.pathLabel || fromSkills?.name || `#${id}` }) }, [value, leaves, skills]) const addId = useCallback( (id) => { const sid = normId(id) if (selectedSet.has(sid)) return onChange([...value, id]) setQuery('') setBrowseTree(false) }, [value, onChange, selectedSet] ) const removeAt = useCallback( (idx) => { onChange(value.filter((_, i) => i !== idx)) }, [value, onChange] ) useEffect(() => { const onDoc = (e) => { if (!rootRef.current?.contains(e.target)) { setOpen(false) setBrowseTree(false) } } document.addEventListener('mousedown', onDoc) return () => document.removeEventListener('mousedown', onDoc) }, []) const showTree = browseTree || !query.trim() return (
{value.map((id, idx) => ( ))}
{ setQuery(e.target.value) setOpen(true) setBrowseTree(false) }} onFocus={() => setOpen(true)} autoComplete="off" aria-expanded={open} />
{open ? (
{showTree ? ( addId(id)} pickMode="multi" /> ) : (
    {leaves.filter((l) => l.pathLabel.toLowerCase().includes(query.trim().toLowerCase())).length === 0 ? (
  • {emptyHint}
  • ) : ( leaves .filter((l) => l.pathLabel.toLowerCase().includes(query.trim().toLowerCase())) .map((l) => (
  • )) )}
)}
) : null}
) }