import React, { useEffect, useState } from 'react' import { Navigate } from 'react-router-dom' import { useAuth } from '../context/AuthContext' import api from '../utils/api' import AdminPageNav from '../components/AdminPageNav' function MultiIdSelect({ label, options, valueIds, onChange, hint }) { return (
{hint ? (
{hint}
) : null}
) } export default function AdminMaturityModelsPage() { const { user } = useAuth() const isAdmin = user?.role === 'admin' || user?.role === 'superadmin' const isSuperadmin = user?.role === 'superadmin' const [models, setModels] = useState([]) const [selectedId, setSelectedId] = useState(null) const [detail, setDetail] = useState(null) const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [error, setError] = useState('') const [focusAreas, setFocusAreas] = useState([]) const [styles, setStyles] = useState([]) const [targetGroups, setTargetGroups] = useState([]) const [allSkills, setAllSkills] = useState([]) const [newModel, setNewModel] = useState({ name: '', level_count: 5, focus_area_ids: [], style_direction_ids: [], target_group_ids: [], status: 'draft' }) const [meta, setMeta] = useState(null) const [levelCount, setLevelCount] = useState(5) const [levelsForm, setLevelsForm] = useState([]) const [cellDraft, setCellDraft] = useState({}) const [skillToAdd, setSkillToAdd] = useState('') useEffect(() => { if (!isAdmin) return let cancelled = false ;(async () => { try { const [m, fa, sd, tg, sk] = await Promise.all([ api.listMaturityModels({}), api.listFocusAreas({}), api.listStyleDirections({}), api.listTargetGroups({}), api.listSkills({ status: 'active' }) ]) if (!cancelled) { setModels(m) setFocusAreas(fa) setStyles(sd) setTargetGroups(tg) setAllSkills(sk) } } catch (e) { if (!cancelled) setError(e.message) } })() return () => { cancelled = true } }, [isAdmin]) async function refreshModels() { const m = await api.listMaturityModels({}) setModels(m) } async function selectModel(id) { setSelectedId(id) setLoading(true) setError('') try { const d = await api.getMaturityModel(id) setDetail(d) setMeta({ name: d.name, description: d.description || '', focus_area_ids: (d.focus_areas || []).map((x) => x.id), style_direction_ids: (d.style_directions || []).map((x) => x.id), target_group_ids: (d.target_groups || []).map((x) => x.id), status: d.status, version: d.version || '1.0' }) const lc = parseInt(String(d.level_count), 10) setLevelCount(lc) setLevelsForm( (d.levels || []).map((l) => ({ level_number: l.level_number, name: l.name, description: l.description || '', sort_order: l.sort_order })) ) const draft = {} for (const r of d.skill_levels || []) { draft[`${r.skill_id}-${r.level_number}`] = { description: r.description || '', observable_criteria: r.observable_criteria || '' } } setCellDraft(draft) } catch (e) { setError(e.message) setDetail(null) setMeta(null) } finally { setLoading(false) } } async function handleCreate(e) { e.preventDefault() setError('') setSaving(true) try { const payload = { name: newModel.name.trim(), level_count: parseInt(String(newModel.level_count), 10), status: newModel.status, focus_area_ids: newModel.focus_area_ids, style_direction_ids: newModel.style_direction_ids, target_group_ids: newModel.target_group_ids } const created = await api.createMaturityModel(payload) await refreshModels() await selectModel(created.id) setNewModel({ name: '', level_count: 5, focus_area_ids: [], style_direction_ids: [], target_group_ids: [], status: 'draft' }) } catch (e) { setError(e.message) } finally { setSaving(false) } } async function handleSaveMeta() { if (!selectedId || !meta) return setError('') setSaving(true) try { await api.updateMaturityModel(selectedId, { name: meta.name, description: meta.description || null, focus_area_ids: meta.focus_area_ids, style_direction_ids: meta.style_direction_ids, target_group_ids: meta.target_group_ids, status: meta.status, version: meta.version }) await refreshModels() await selectModel(selectedId) } catch (e) { setError(e.message) } finally { setSaving(false) } } async function handleSaveLevels() { if (!selectedId || !meta) return setError('') setSaving(true) try { await api.updateMaturityModel(selectedId, { level_count: parseInt(String(levelCount), 10), levels: levelsForm.map((l) => ({ level_number: parseInt(String(l.level_number), 10), name: l.name, description: l.description || null, sort_order: parseInt(String(l.sort_order), 10) })) }) await refreshModels() await selectModel(selectedId) } catch (e) { setError(e.message) } finally { setSaving(false) } } async function handleSaveMatrix() { if (!selectedId || !detail) return if (!detail.model_skills?.length) { setError('Matrix: Zuerst Fähigkeiten hinzufügen.') return } setError('') setSaving(true) try { const entries = [] for (const ms of detail.model_skills) { for (const lev of detail.levels || []) { const key = `${ms.skill_id}-${lev.level_number}` const d = cellDraft[key] || { description: '', observable_criteria: '' } entries.push({ skill_id: ms.skill_id, level_number: lev.level_number, description: (d.description || '').trim(), observable_criteria: (d.observable_criteria || '').trim() || null }) } } await api.upsertMaturityModelSkillLevels(selectedId, { entries }) await selectModel(selectedId) } catch (e) { setError(e.message) } finally { setSaving(false) } } async function handleAddSkill() { if (!selectedId || !skillToAdd) return setError('') setSaving(true) try { await api.addMaturityModelSkill(selectedId, { skill_id: parseInt(String(skillToAdd), 10) }) setSkillToAdd('') await selectModel(selectedId) } catch (e) { setError(e.message) } finally { setSaving(false) } } async function handleRemoveSkill(skillId) { if (!selectedId) return if (!confirm('Fähigkeit aus dem Modell entfernen? Zelltexte werden gelöscht.')) return setError('') setSaving(true) try { await api.removeMaturityModelSkill(selectedId, skillId) await selectModel(selectedId) } catch (e) { setError(e.message) } finally { setSaving(false) } } async function handleDeleteModel() { if (!selectedId || !isSuperadmin) return if (!confirm('Reifegradmodell dauerhaft löschen?')) return setError('') setSaving(true) try { await api.deleteMaturityModel(selectedId) setSelectedId(null) setDetail(null) setMeta(null) await refreshModels() } catch (e) { setError(e.message) } finally { setSaving(false) } } function onLevelCountChange(raw) { const next = Math.max(3, Math.min(10, parseInt(String(raw), 10) || 3)) setLevelCount(next) setLevelsForm((prev) => { const byNum = Object.fromEntries(prev.map((r) => [r.level_number, { ...r }])) const rows = [] for (let i = 1; i <= next; i++) { rows.push( byNum[i] || { level_number: i, name: `Stufe ${i}`, description: '', sort_order: i } ) } return rows }) } function setCell(skillId, levelNumber, field, value) { const key = `${skillId}-${levelNumber}` setCellDraft((prev) => ({ ...prev, [key]: { description: field === 'description' ? value : (prev[key]?.description ?? ''), observable_criteria: field === 'observable_criteria' ? value : (prev[key]?.observable_criteria ?? '') } })) } if (!isAdmin) { return } return (

Admin: Fähigkeitsmatrix

Reifegradmodelle mit Fokusbereich, Stilrichtung und Zielgruppe. Pro Modell: Stufen definieren, Fähigkeiten zuordnen, Zelltexte pflegen.

{error ? (
{error}
) : null}

Modelle

    {models.map((m) => (
  • ))}

Neues Modell

setNewModel((s) => ({ ...s, name: e.target.value }))} required /> setNewModel((s) => ({ ...s, level_count: e.target.value }))} /> setNewModel((s) => ({ ...s, focus_area_ids: ids }))} hint="Strg/Cmd gedrückt halten für Mehrfachauswahl." /> setNewModel((s) => ({ ...s, style_direction_ids: ids }))} hint="Strg/Cmd gedrückt halten für Mehrfachauswahl." /> setNewModel((s) => ({ ...s, target_group_ids: ids }))} hint="Strg/Cmd gedrückt halten für Mehrfachauswahl." />
{loading ?
: null} {!loading && !detail && (
Modell links wählen oder neu anlegen.
)} {!loading && detail && meta && ( <>

Kontext & Metadaten

setMeta((m) => ({ ...m, name: e.target.value }))} />