Refactor Skill Tree Picker and Catalog Functions for Clarity and Efficiency
Some checks failed
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Has been cancelled

- Simplified the rendering of skill group labels in `SkillTreePickerPanel` for improved readability.
- Updated comments in `skillCatalogTree.js` to clarify the structure of expandable nodes and default expansion behavior.
- Refactored the skill catalog tree building logic to streamline the mapping of categories and skills, enhancing performance and maintainability.
- Adjusted tests in `skillCatalogTree.test.js` to reflect changes in the structure of skill nodes, ensuring accurate validation of functionality.
This commit is contained in:
Lars 2026-05-20 11:18:34 +02:00
parent 39b1fd04f0
commit b2f77ca627
4 changed files with 17 additions and 47 deletions

View File

@ -5320,12 +5320,6 @@ html.modal-scroll-locked .app-main {
line-height: 1.35;
}
.skill-tree__group-label--skill-group {
font-weight: 500;
color: var(--text2);
font-size: 0.84rem;
}
.multi-assoc-block {
border: 1px solid var(--border);
border-radius: 8px;

View File

@ -55,11 +55,7 @@ function SkillTreeNodes({ nodes, depth, expanded, exclude, onToggle, onPickSkill
) : (
<span className="skill-tree__toggle-spacer" aria-hidden />
)}
<span
className={`skill-tree__group-label skill-tree__group-label--${node.type}${
node.type === 'skillGroup' ? ' skill-tree__group-label--skill-group' : ''
}`}
>
<span className={`skill-tree__group-label skill-tree__group-label--${node.type}`}>
{node.label}
</span>
</div>
@ -82,7 +78,7 @@ function SkillTreeNodes({ nodes, depth, expanded, exclude, onToggle, onPickSkill
}
/**
* Ausklappbare Baumliste: Hauptgruppe Kategorie (beide standard offen) Fähigkeiten-Gruppe (standard zu).
* Ausklappbare Baumliste: Hauptgruppe Kategorie Fähigkeit (Hauptgruppe und Kategorie standard offen).
*/
export default function SkillTreePickerPanel({
skills = [],

View File

@ -80,33 +80,18 @@ export function buildSkillCatalogTree(skills) {
if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder
return a.label.localeCompare(b.label, 'de')
})
.map((cat) => {
const skillLeaves = [...cat.skills].sort(bySortThenName).map((skill) => ({
.map((cat) => ({
key: cat.key,
type: cat.type,
label: cat.label,
children: [...cat.skills].sort(bySortThenName).map((skill) => ({
key: `s-${skill.id}`,
type: 'skill',
label: (skill.name || '').trim() || `#${skill.id}`,
skillId: Number(skill.id),
skill,
}))
const n = skillLeaves.length
return {
key: cat.key,
type: cat.type,
label: cat.label,
children:
n > 0
? [
{
key: `${cat.key}-skills`,
type: 'skillGroup',
label: n === 1 ? '1 Fähigkeit' : `${n} Fähigkeiten`,
skillCount: n,
children: skillLeaves,
},
]
: [],
}
}),
})),
})),
}))
}
@ -132,8 +117,7 @@ export function collectSkillLeavesFromTree(tree, excludeIds) {
out.push({ id: n.skillId, label: n.label, pathLabel, skill: n.skill })
}
} else if (n.children?.length) {
const nextPath = n.type === 'skillGroup' ? pathParts : [...pathParts, n.label]
walk(n.children, nextPath)
walk(n.children, [...pathParts, n.label])
}
}
}
@ -153,9 +137,6 @@ export function filterSkillTreeByQuery(tree, query) {
if (n.type === 'skill') {
const hay = `${n.label} ${skillCatalogPathLabel(n.skill)}`.toLowerCase()
if (hay.includes(q)) result.push(n)
} else if (n.type === 'skillGroup') {
const kids = filterNodes(n.children || [])
if (kids.length) result.push({ ...n, children: kids })
} else {
const kids = filterNodes(n.children || [])
if (kids.length) result.push({ ...n, children: kids })
@ -166,7 +147,7 @@ export function filterSkillTreeByQuery(tree, query) {
return filterNodes(tree)
}
/** Alle aufklappbaren Knoten (inkl. Fähigkeiten-Gruppen unter Kategorien). */
/** Alle aufklappbaren Knoten (Hauptgruppe und Kategorie). */
export function allExpandableKeys(tree) {
const keys = []
const walk = (nodes) => {
@ -181,7 +162,7 @@ export function allExpandableKeys(tree) {
return keys
}
/** Standard: Hauptgruppe + Kategorie offen, Fähigkeiten-Liste zugeklappt. */
/** Standard: Hauptgruppe und Kategorie aufgeklappt (Fähigkeiten direkt darunter sichtbar). */
export function defaultExpandedKeysForSkillTree(tree) {
const keys = []
const walk = (nodes) => {

View File

@ -44,9 +44,9 @@ describe('skillCatalogTree', () => {
expect(tree[0].label).toBe('Karate')
expect(tree[0].children).toHaveLength(2)
expect(tree[0].children[0].label).toBe('Kata')
const kataSkills = tree[0].children[0].children[0]
expect(kataSkills.type).toBe('skillGroup')
expect(kataSkills.children[0].skillId).toBe(2)
const kataSkill = tree[0].children[0].children[0]
expect(kataSkill.type).toBe('skill')
expect(kataSkill.skillId).toBe(2)
})
it('defaultExpandedKeysForSkillTree opens main and category only', () => {
@ -54,7 +54,7 @@ describe('skillCatalogTree', () => {
const keys = defaultExpandedKeysForSkillTree(tree)
expect(keys).toContain('m-10')
expect(keys.some((k) => k.includes('c-21'))).toBe(true)
expect(keys.some((k) => k.endsWith('-skills'))).toBe(false)
expect(keys.some((k) => k.startsWith('s-'))).toBe(false)
})
it('skillCatalogPathLabel', () => {
@ -72,8 +72,7 @@ describe('skillCatalogTree', () => {
const tree = buildSkillCatalogTree(sample)
const filtered = filterSkillTreeByQuery(tree, 'dachi')
const kihon = filtered[0].children.find((c) => c.label === 'Kihon')
const group = kihon?.children?.[0]
expect(group?.children?.some((s) => s.skillId === 1)).toBe(true)
expect(kihon?.children?.some((s) => s.skillId === 1)).toBe(true)
})
it('catalogSkillsForSelection lists unassigned buckets', () => {