import React, { useEffect, useMemo, useState } from 'react' import api from '../../utils/api' function downloadJson(obj, filename) { const blob = new Blob([JSON.stringify(obj, null, 2)], { type: 'application/json;charset=utf-8' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = filename a.click() URL.revokeObjectURL(url) } function groupModelSkills(model) { if (!model?.model_skills?.length) return [] const groups = new Map() for (const ms of model.model_skills) { const main = ms.skill_main_category_name || '—' const sub = ms.skill_subcategory_name || '—' const k = `${main}\t${sub}` if (!groups.has(k)) groups.set(k, { main, sub, skills: [] }) groups.get(k).skills.push(ms) } return Array.from(groups.values()) } function cellMapForSkill(model, skillId) { const m = {} for (const sl of model.skill_levels || []) { if (Number(sl.skill_id) === Number(skillId)) { m[sl.level_number] = sl } } return m } export default function MaturityMatrixToolsAdmin() { const [focusAreas, setFocusAreas] = useState([]) const [styles, setStyles] = useState([]) const [trainingTypes, setTrainingTypes] = useState([]) const [models, setModels] = useState([]) const [focusId, setFocusId] = useState('') const [styleId, setStyleId] = useState('') const [typeId, setTypeId] = useState('') const [matrix, setMatrix] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState('') const [message, setMessage] = useState('') const [importMode, setImportMode] = useState('create') const [replaceModelId, setReplaceModelId] = useState('') const [exportModelId, setExportModelId] = useState('') const [importBindings, setImportBindings] = useState(true) useEffect(() => { let cancelled = false ;(async () => { try { const [fa, sd, tt, m] = await Promise.all([ api.listFocusAreas({}), api.listStyleDirections({}), api.listTrainingTypes({}), api.listMaturityModels({}) ]) if (!cancelled) { setFocusAreas(fa) setStyles(sd) setTrainingTypes(tt) setModels(m) } } catch (e) { if (!cancelled) setError(e.message || String(e)) } })() return () => { cancelled = true } }, []) const groups = useMemo(() => (matrix ? groupModelSkills(matrix) : []), [matrix]) const levels = matrix?.levels || [] async function handleResolve() { if (!focusId) { setError('Fokusbereich wählen.') return } setLoading(true) setError('') setMessage('') setMatrix(null) try { const params = { focus_area_id: focusId } if (styleId) params.style_direction_id = styleId if (typeId) params.training_type_id = typeId const m = await api.resolveMaturityModel(params) setMatrix(m) if (!m) setError('Keine Matrix für diesen Kontext.') else setMessage('Matrix geladen.') } catch (e) { setError(e.message || String(e)) } finally { setLoading(false) } } async function handleExportResolved() { if (!focusId) { setError('Fokusbereich wählen.') return } setError('') setMessage('') try { const params = { focus_area_id: focusId } if (styleId) params.style_direction_id = styleId if (typeId) params.training_type_id = typeId const bundle = await api.exportResolvedMaturityBundle(params) downloadJson(bundle, 'faehigkeitsmatrix-aufgeloest.json') setMessage('Export gestartet (Download).') } catch (e) { setError(e.message || String(e)) } } async function handleExportStored() { if (!exportModelId) { setError('Modell für Export auswählen.') return } setError('') setMessage('') try { const bundle = await api.exportMaturityModelBundle(parseInt(exportModelId, 10)) downloadJson(bundle, `reifegradmodell-${exportModelId}.json`) setMessage('Export gestartet (Download).') } catch (e) { setError(e.message || String(e)) } } async function handleImportFile(e) { const file = e.target.files?.[0] if (!file) return setError('') setMessage('') try { const data = JSON.parse(await file.text()) const payload = { ...data, mode: importMode, import_bindings: importBindings } if (importMode === 'replace') { if (!replaceModelId) { setError('Bei „Ersetzen“ die Ziel-Modell-ID angeben.') e.target.value = '' return } payload.replace_model_id = parseInt(replaceModelId, 10) } const res = await api.importMaturityModelBundle(payload) setMessage(`Import erfolgreich. Modell-ID: ${res.id}`) setModels(await api.listMaturityModels({})) } catch (err) { setError(err.message || String(err)) } finally { e.target.value = '' } } return (
Matrix nach Kontext auflösen, hierarchisch nach Hauptkategorie und Kategorie darstellen, sowie JSON exportieren oder importieren (gespeichertes Modell inkl. optional Kontext-Bindings, oder aufgelöste Matrix).
{error ? ({message}
: null}Quelle:{' '} {matrix.resolution.merged ? `zusammengeführt aus Modell-IDs ${(matrix.resolution.source_model_ids || []).join(', ')}` : `Modell-ID ${matrix.id}`}
) : null}| Fähigkeit | {levels.map((lv) => ({lv.level_number} {lv.name} | ))}
|---|---|
| {ms.skill_name} | {levels.map((lv) => { const c = cells[lv.level_number] return (
{c ? (
<>
{c.description || '—'}
{c.observable_criteria ? (
Beobachtung: {c.observable_criteria}
) : null}
>
) : (
—
)}
|
)
})}
Enthält Stufen, Fähigkeiten, Zelltexte und die dem Modell zugeordneten Kontext-Bindings.
Datei shinkan.maturity_model.v1 oder{' '}
shinkan.maturity_matrix_resolved.v1. Aufgelöste
Matrizen legen ein neues Modell an bzw. ersetzen den Inhalt des Zielmodells (ohne Bindings).