Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Has been cancelled
- Added CSS styles for skill group labels in the skill tree to improve visual hierarchy and readability. - Updated `SkillTreePickerPanel` and `SkillTreeMultiSelect` components to utilize the new default expansion logic, ensuring main and category nodes are open by default while skill groups remain collapsed. - Refactored state management in `SkillTreePickerPanel` to align with the new default expansion behavior. - Enhanced utility functions to support the new default expansion logic for skill trees.
85 lines
2.4 KiB
JavaScript
85 lines
2.4 KiB
JavaScript
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
|
import { skillCatalogPathLabel } from '../utils/skillCatalogTree'
|
|
import SkillTreePickerPanel from './SkillTreePickerPanel'
|
|
|
|
/**
|
|
* Einzelauswahl einer Fähigkeit als hierarchische Treeview.
|
|
*/
|
|
export default function SkillTreeSelect({
|
|
value = '',
|
|
onChange,
|
|
skills = [],
|
|
excludeIds,
|
|
placeholder = 'Fähigkeit wählen…',
|
|
disabled = false,
|
|
className = '',
|
|
searchPlaceholder = 'Fähigkeit suchen…',
|
|
}) {
|
|
const [open, setOpen] = useState(false)
|
|
const [query, setQuery] = useState('')
|
|
const rootRef = useRef(null)
|
|
|
|
const selected = useMemo(() => {
|
|
const id = value ? Number(value) : NaN
|
|
if (!Number.isFinite(id)) return null
|
|
return skills.find((s) => Number(s.id) === id) || null
|
|
}, [value, skills])
|
|
|
|
const displayLabel = selected ? skillCatalogPathLabel(selected) : ''
|
|
|
|
useEffect(() => {
|
|
const onDoc = (e) => {
|
|
if (!rootRef.current?.contains(e.target)) {
|
|
setOpen(false)
|
|
setQuery('')
|
|
}
|
|
}
|
|
document.addEventListener('mousedown', onDoc)
|
|
return () => document.removeEventListener('mousedown', onDoc)
|
|
}, [])
|
|
|
|
const pick = (skillId) => {
|
|
onChange(String(skillId))
|
|
setOpen(false)
|
|
setQuery('')
|
|
}
|
|
|
|
return (
|
|
<div className={`skill-tree-select ${className}`.trim()} ref={rootRef}>
|
|
<button
|
|
type="button"
|
|
className="form-input skill-tree-select__trigger"
|
|
disabled={disabled}
|
|
aria-haspopup="listbox"
|
|
aria-expanded={open}
|
|
onClick={() => !disabled && setOpen((o) => !o)}
|
|
>
|
|
<span className={displayLabel ? '' : 'skill-tree-select__placeholder'}>
|
|
{displayLabel || placeholder}
|
|
</span>
|
|
</button>
|
|
{open ? (
|
|
<div className="skill-tree-select__panel">
|
|
<input
|
|
type="search"
|
|
className="form-input skill-tree-select__search"
|
|
placeholder={searchPlaceholder}
|
|
value={query}
|
|
onChange={(e) => setQuery(e.target.value)}
|
|
autoComplete="off"
|
|
/>
|
|
<div className="skill-tree-select__tree-wrap">
|
|
<SkillTreePickerPanel
|
|
skills={skills}
|
|
excludeIds={excludeIds}
|
|
searchQuery={query}
|
|
onPickSkill={pick}
|
|
pickMode="single"
|
|
/>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|