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'
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_id: '',
style_direction_id: '',
target_group_id: '',
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_id: d.focus_area_id ?? '',
style_direction_id: d.style_direction_id ?? '',
target_group_id: d.target_group_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_id: newModel.focus_area_id ? parseInt(String(newModel.focus_area_id), 10) : null,
style_direction_id: newModel.style_direction_id
? parseInt(String(newModel.style_direction_id), 10)
: null,
target_group_id: newModel.target_group_id
? parseInt(String(newModel.target_group_id), 10)
: null
}
const created = await api.createMaturityModel(payload)
await refreshModels()
await selectModel(created.id)
setNewModel({
name: '',
level_count: 5,
focus_area_id: '',
style_direction_id: '',
target_group_id: '',
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_id: meta.focus_area_id === '' ? null : parseInt(String(meta.focus_area_id), 10),
style_direction_id:
meta.style_direction_id === '' ? null : parseInt(String(meta.style_direction_id), 10),
target_group_id:
meta.target_group_id === '' ? null : parseInt(String(meta.target_group_id), 10),
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
Reifegradmodelle mit Fokusbereich, Stilrichtung und Zielgruppe. Pro Modell: Stufen definieren, Fähigkeiten zuordnen, Zelltexte pflegen.
{error ? (Reihenfolge muss lückenlos 1…N sein. Stufenanzahl ändern passt die Tabelle an; danach speichern.
| Nr. | Name | Beschreibung | Sort |
|---|---|---|---|
| {row.level_number} | { const next = [...levelsForm] next[idx] = { ...row, name: e.target.value } setLevelsForm(next) }} /> | { const next = [...levelsForm] next[idx] = { ...row, description: e.target.value } setLevelsForm(next) }} /> | { const next = [...levelsForm] next[idx] = { ...row, sort_order: e.target.value } setLevelsForm(next) }} /> |
Leere Zellen werden beim Speichern aus der Datenbank entfernt. Beobachtungskriterien optional in zweiter Zeile (nach Speichern mit Beschreibung).
| Fähigkeit | {(detail.levels || []).map((l) => ({l.level_number}. {l.name} | ))}
|---|---|
| {ms.skill_name} | {(detail.levels || []).map((l) => { const key = `${ms.skill_id}-${l.level_number}` const d = cellDraft[key] || { description: '', observable_criteria: '' } return () })} |