import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useAuth } from '../../context/AuthContext' import api from '../../utils/api' function bySortThenName(a, b) { const sa = a.sort_order != null ? Number(a.sort_order) : 99999 const sb = b.sort_order != null ? Number(b.sort_order) : 99999 if (sa !== sb) return sa - sb return String(a.name || '').localeCompare(String(b.name || ''), 'de') } async function swapNeighborSort(list, index, delta, updateId) { const sorted = [...list].sort(bySortThenName) const i = sorted.findIndex((x) => x.id === list[index]?.id) const j = i + delta if (i < 0 || j < 0 || j >= sorted.length) return const a = sorted[i] const b = sorted[j] const oa = a.sort_order != null ? Number(a.sort_order) : (i + 1) * 10 const ob = b.sort_order != null ? Number(b.sort_order) : (j + 1) * 10 await updateId(a.id, { sort_order: ob }) await updateId(b.id, { sort_order: oa }) } export default function SkillsCatalogAdmin() { const { user } = useAuth() const isSuperadmin = user?.role === 'superadmin' const [mains, setMains] = useState([]) const [categories, setCategories] = useState([]) const [allCategories, setAllCategories] = useState([]) const [catalog, setCatalog] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [busy, setBusy] = useState(false) const [message, setMessage] = useState('') const [selectedMainId, setSelectedMainId] = useState(null) const [selectedCategoryId, setSelectedCategoryId] = useState(null) const [selectedSkillId, setSelectedSkillId] = useState(null) const [mainForm, setMainForm] = useState({ name: '', slug: '', description: '', sort_order: '' }) const [categoryForm, setCategoryForm] = useState({ name: '', slug: '', description: '', main_category_id: '', sort_order: '' }) const [skillForm, setSkillForm] = useState({ name: '', description: '', category: '', keywords: '', status: 'active', sort_order: '', category_id: '' }) const [newMainName, setNewMainName] = useState('') const [newCategoryName, setNewCategoryName] = useState('') const [newSkillName, setNewSkillName] = useState('') const refreshCategories = useCallback(async (mainId) => { if (mainId == null) { setCategories([]) return } const c = await api.listSkillCategories({ main_category_id: mainId }) setCategories([...c].sort(bySortThenName)) }, []) const bootstrap = useCallback(async () => { setError('') try { const [m, s, ac] = await Promise.all([ api.listSkillMainCategories(), api.listSkillsCatalog({ status: 'all' }), api.listSkillCategories({}) ]) setMains([...m].sort(bySortThenName)) setCatalog(s) setAllCategories([...ac].sort(bySortThenName)) } catch (e) { setError(e.message || String(e)) } }, []) useEffect(() => { let cancelled = false ;(async () => { setLoading(true) await bootstrap() if (!cancelled) setLoading(false) })() return () => { cancelled = true } }, [bootstrap]) useEffect(() => { refreshCategories(selectedMainId) }, [selectedMainId, refreshCategories]) const sortedMains = useMemo(() => [...mains].sort(bySortThenName), [mains]) const sortedCategories = useMemo(() => [...categories].sort(bySortThenName), [categories]) const skillsInCategory = useMemo(() => { if (selectedCategoryId == null) return [] return catalog .filter((s) => s.category_id === selectedCategoryId) .sort(bySortThenName) }, [catalog, selectedCategoryId]) const selectedMain = useMemo( () => mains.find((m) => m.id === selectedMainId) || null, [mains, selectedMainId] ) const selectedCategory = useMemo( () => categories.find((c) => c.id === selectedCategoryId) || null, [categories, selectedCategoryId] ) const selectedSkill = useMemo( () => catalog.find((s) => s.id === selectedSkillId) || null, [catalog, selectedSkillId] ) useEffect(() => { if (!selectedMain) { setMainForm({ name: '', slug: '', description: '', sort_order: '' }) return } setMainForm({ name: selectedMain.name || '', slug: selectedMain.slug || '', description: selectedMain.description || '', sort_order: selectedMain.sort_order ?? '' }) }, [selectedMain]) useEffect(() => { if (!selectedCategory) { setCategoryForm({ name: '', slug: '', description: '', main_category_id: '', sort_order: '' }) return } setCategoryForm({ name: selectedCategory.name || '', slug: selectedCategory.slug || '', description: selectedCategory.description || '', main_category_id: selectedCategory.main_category_id ?? '', sort_order: selectedCategory.sort_order ?? '' }) }, [selectedCategory]) useEffect(() => { if (!selectedSkill) { setSkillForm({ name: '', description: '', category: '', keywords: '', status: 'active', sort_order: '', category_id: '' }) return } setSkillForm({ name: selectedSkill.name || '', description: selectedSkill.description || '', category: selectedSkill.category || '', keywords: selectedSkill.keywords || '', status: selectedSkill.status || 'active', sort_order: selectedSkill.sort_order ?? '', category_id: selectedSkill.category_id ?? '' }) }, [selectedSkill]) async function run(op) { setBusy(true) setMessage('') try { await op() await bootstrap() if (selectedMainId) await refreshCategories(selectedMainId) setMessage('Gespeichert.') } catch (e) { setError(e.message || String(e)) } finally { setBusy(false) } } function selectMain(id) { setSelectedMainId(id) setSelectedCategoryId(null) setSelectedSkillId(null) } function selectCategory(id) { setSelectedCategoryId(id) setSelectedSkillId(null) } async function handleSwapMain(row, delta) { const idx = sortedMains.findIndex((m) => m.id === row.id) await run(async () => { await swapNeighborSort(sortedMains, idx, delta, (id, data) => api.updateSkillMainCategory(id, data) ) }) } async function handleSwapCategory(row, delta) { const idx = sortedCategories.findIndex((c) => c.id === row.id) await run(async () => { await swapNeighborSort(sortedCategories, idx, delta, (id, data) => api.updateSkillCategory(id, data) ) }) } async function handleSwapSkill(row, delta) { const sorted = [...skillsInCategory].sort(bySortThenName) const idx = sorted.findIndex((s) => s.id === row.id) await run(async () => { await swapNeighborSort(sorted, idx, delta, (id, data) => api.updateSkill(id, data)) }) } async function handleSaveMain(e) { e.preventDefault() if (!selectedMainId) return await run(async () => { await api.updateSkillMainCategory(selectedMainId, { name: mainForm.name.trim(), slug: (mainForm.slug || '').trim() || undefined, description: mainForm.description || null, sort_order: mainForm.sort_order === '' || mainForm.sort_order == null ? null : Number(mainForm.sort_order) }) }) } async function handleSaveCategory(e) { e.preventDefault() if (!selectedCategoryId) return const mid = categoryForm.main_category_id === '' || categoryForm.main_category_id == null ? null : Number(categoryForm.main_category_id) await run(async () => { await api.updateSkillCategory(selectedCategoryId, { name: categoryForm.name.trim(), slug: (categoryForm.slug || '').trim() || undefined, description: categoryForm.description || null, main_category_id: mid, sort_order: categoryForm.sort_order === '' || categoryForm.sort_order == null ? null : Number(categoryForm.sort_order) }) }) if (mid != null && mid !== selectedMainId) { setSelectedMainId(mid) setSelectedSkillId(null) } } async function handleSaveSkill(e) { e.preventDefault() if (!selectedSkillId) return let cid = skillForm.category_id === '' || skillForm.category_id == null ? null : Number(skillForm.category_id) if (cid == null && selectedSkill?.category_id) { cid = selectedSkill.category_id } await run(async () => { await api.updateSkill(selectedSkillId, { name: skillForm.name.trim(), description: skillForm.description || null, category: skillForm.category || null, keywords: skillForm.keywords || null, status: skillForm.status, sort_order: skillForm.sort_order === '' || skillForm.sort_order == null ? null : Number(skillForm.sort_order), category_id: cid }) }) if (cid != null && cid !== selectedCategoryId) { const cat = allCategories.find((c) => c.id === cid) if (cat?.main_category_id) { setSelectedMainId(cat.main_category_id) setSelectedCategoryId(cid) } } } async function handleCreateMain(e) { e.preventDefault() const name = newMainName.trim() if (!name) return await run(async () => { const created = await api.createSkillMainCategory({ name }) setNewMainName('') selectMain(created.id) }) } async function handleCreateCategory(e) { e.preventDefault() if (selectedMainId == null) return const name = newCategoryName.trim() if (!name) return await run(async () => { const created = await api.createSkillCategory({ name, main_category_id: selectedMainId }) setNewCategoryName('') setSelectedCategoryId(created.id) setSelectedSkillId(null) }) } async function handleCreateSkill(e) { e.preventDefault() if (selectedCategoryId == null) return const name = newSkillName.trim() if (!name) return await run(async () => { const created = await api.createSkill({ name, category_id: selectedCategoryId, status: 'active' }) setNewSkillName('') setSelectedSkillId(created.id) }) } async function handleDeleteMain() { if (!selectedMainId || !isSuperadmin) return if (!window.confirm('Hauptkategorie wirklich löschen?')) return await run(async () => { await api.deleteSkillMainCategory(selectedMainId) setSelectedMainId(null) setSelectedCategoryId(null) setSelectedSkillId(null) }) } async function handleDeleteCategory() { if (!selectedCategoryId || !isSuperadmin) return if (!window.confirm('Kategorie wirklich löschen?')) return await run(async () => { await api.deleteSkillCategory(selectedCategoryId) setSelectedCategoryId(null) setSelectedSkillId(null) }) } async function handleDeleteSkill() { if (!selectedSkillId || !isSuperadmin) return if (!window.confirm('Fähigkeit wirklich löschen?')) return await run(async () => { await api.deleteSkill(selectedSkillId) setSelectedSkillId(null) }) } const detailMode = selectedSkillId ? 'skill' : selectedCategoryId ? 'category' : selectedMainId ? 'main' : 'none' if (loading) { return (
Lade Katalog…
Struktur: Hauptkategorie → Kategorie →{' '} Fähigkeit. Reihenfolge mit den Pfeiltasten; Zuordnungen und Texte im Bereich „Bearbeiten“.
{error ? ({message}
: null}Zuerst eine Hauptkategorie wählen.
) : ( <>Zuerst eine Kategorie wählen.
) : ( <>Wählen Sie eine Hauptkategorie, Kategorie oder Fähigkeit in den Spalten oben.
) : null} {detailMode === 'main' ? ( ) : null} {detailMode === 'category' ? ( ) : null} {detailMode === 'skill' ? ( ) : null}