diff --git a/frontend/src/app.css b/frontend/src/app.css index 475b3fd..b7d27f3 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -1248,6 +1248,426 @@ a.analysis-split__nav-item { min-width: 2rem; } +.btn-ghost { + background: transparent; + border: none; + color: var(--text2); + box-shadow: none; +} +.btn-ghost:hover { + background: var(--surface2); + color: var(--text1); +} + +/* Touch-Ziel mind. ca. 44×44 (Apple HIG) */ +.btn-icon-touch { + min-width: 44px; + min-height: 44px; + padding: 0 10px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 1.15rem; + line-height: 1; + border-radius: 10px; +} + +.admin-modal-backdrop { + position: fixed; + inset: 0; + z-index: 1000; + background: rgba(0, 0, 0, 0.45); + display: flex; + align-items: flex-end; + justify-content: center; + padding: 0; + padding-bottom: env(safe-area-inset-bottom, 0px); +} +@media (min-width: 640px) { + .admin-modal-backdrop { + align-items: center; + padding: 24px; + padding-bottom: max(24px, env(safe-area-inset-bottom, 0px)); + } +} + +.admin-modal-sheet { + width: 100%; + max-width: 520px; + max-height: min(92vh, 100dvh); + background: var(--surface); + border-radius: 16px 16px 0 0; + border: 1px solid var(--border); + border-bottom: none; + box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.12); + display: flex; + flex-direction: column; + overflow: hidden; +} +@media (min-width: 640px) { + .admin-modal-sheet { + border-radius: 16px; + border-bottom: 1px solid var(--border); + max-height: min(88vh, 900px); + } +} + +.admin-modal-sheet__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 16px 16px 12px; + padding-top: max(16px, env(safe-area-inset-top, 0px)); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.admin-modal-sheet__title { + margin: 0; + font-size: 1.1rem; + font-weight: 700; + line-height: 1.25; +} + +.admin-modal-sheet__close { + flex-shrink: 0; + min-width: 44px; + min-height: 44px; + padding: 0 12px; +} + +.admin-modal-sheet__body { + padding: 16px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; +} + +/* Reifegradmodell-Admin: klare Schritte, responsives Raster */ +.admin-matrix-alert { + border: 1px solid var(--danger); + padding: 14px 16px; + margin-bottom: 16px; + border-radius: 12px; + color: var(--danger); +} + +.admin-matrix-layout { + display: grid; + grid-template-columns: minmax(260px, 300px) 1fr; + gap: 20px; + align-items: start; +} +@media (max-width: 900px) { + .admin-matrix-layout { + grid-template-columns: 1fr; + } +} + +.admin-matrix-sidebar { + position: sticky; + top: 12px; +} +@media (max-width: 900px) { + .admin-matrix-sidebar { + position: static; + } +} + +.admin-matrix-sidebar__title { + margin: 0 0 12px; + font-size: 1.05rem; + font-weight: 700; +} +.admin-matrix-sidebar__subtitle { + margin: 0 0 10px; + font-size: 0.95rem; + font-weight: 600; +} + +.admin-matrix-divider { + border: none; + border-top: 1px solid var(--border); + margin: 16px 0; +} + +.admin-matrix-model-list { + list-style: none; + margin: 0; + padding: 0; + max-height: min(40vh, 320px); + overflow-y: auto; +} +.admin-matrix-model-list li + li { + margin-top: 8px; +} + +.admin-matrix-model-btn { + width: 100%; + text-align: left; + padding: 12px 14px; + border-radius: 10px; + border: 1px solid var(--border2); + background: var(--surface2); + color: var(--text1); + font: inherit; + cursor: pointer; + display: flex; + flex-direction: column; + gap: 4px; + min-height: 48px; +} +.admin-matrix-model-btn:hover { + background: var(--surface); +} +.admin-matrix-model-btn--active { + border-color: var(--accent); + background: var(--accent-light); + color: var(--accent-dark); +} +@media (prefers-color-scheme: dark) { + .admin-matrix-model-btn--active { + color: var(--accent); + } +} +.admin-matrix-model-btn__name { + font-weight: 600; +} +.admin-matrix-model-btn__meta { + font-size: 12px; +} + +.admin-matrix-new-form { + display: flex; + flex-direction: column; + gap: 10px; +} + +.admin-matrix-main { + min-width: 0; + display: flex; + flex-direction: column; + gap: 16px; +} + +.admin-matrix-loading { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 0; +} + +.admin-matrix-empty { + padding: 24px; + color: var(--text2); +} + +.admin-matrix-section__head { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 12px; +} +.admin-matrix-section__title { + margin: 0; + font-size: 1.1rem; + font-weight: 700; +} +.admin-matrix-step { + flex-shrink: 0; + width: 32px; + height: 32px; + border-radius: 50%; + background: var(--accent); + color: #fff; + font-size: 15px; + font-weight: 700; + display: inline-flex; + align-items: center; + justify-content: center; +} +.admin-matrix-hint { + font-size: 14px; + margin: 0 0 12px; + line-height: 1.45; +} + +.admin-matrix-meta-grid { + display: grid; + gap: 12px; + margin-bottom: 12px; +} +.admin-matrix-meta-grid__row2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} +@media (max-width: 600px) { + .admin-matrix-meta-grid__row2 { + grid-template-columns: 1fr; + } +} + +.admin-matrix-context-readonly { + font-size: 13px; + line-height: 1.5; + padding: 12px 14px; + border-radius: 10px; + background: var(--surface2); + border: 1px dashed var(--border2); + margin-bottom: 12px; +} +.admin-matrix-context-readonly strong { + color: var(--text2); +} + +.admin-matrix-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 4px; +} +.admin-matrix-actions--mt { + margin-top: 12px; +} + +.admin-matrix-level-count { + max-width: 220px; + margin-bottom: 12px; +} + +.admin-matrix-table-wrap { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + margin-bottom: 8px; +} +.admin-matrix-table { + width: 100%; + border-collapse: collapse; + font-size: 14px; +} +.admin-matrix-table th, +.admin-matrix-table td { + text-align: left; + padding: 8px; + border-top: 1px solid var(--border); + vertical-align: middle; +} +.admin-matrix-table thead th { + border-top: none; + font-weight: 600; + color: var(--text2); + font-size: 13px; +} +.admin-matrix-table__narrow { + width: 88px; +} + +.admin-matrix-skill-add { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: flex-end; +} +.admin-matrix-skill-add__select { + flex: 1 1 220px; + min-width: 0; +} +.admin-matrix-skill-add__btn { + flex-shrink: 0; + min-height: 44px; +} + +.admin-matrix-skill-list { + margin: 12px 0 0; + padding: 0; + list-style: none; +} +.admin-matrix-skill-list__item { + padding: 12px 0; + border-top: 1px solid var(--border); +} +.admin-matrix-skill-list__item:first-child { + border-top: none; +} +.admin-matrix-skill-list__row { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.admin-matrix-skill-list__name { + font-size: 15px; +} +.admin-matrix-skill-list__path { + font-size: 12px; + margin-top: 4px; +} + +.admin-matrix-matrix-scroll { + overflow: auto; + max-height: min(70vh, 720px); + -webkit-overflow-scrolling: touch; + border: 1px solid var(--border); + border-radius: 10px; +} +.admin-matrix-table--matrix th, +.admin-matrix-table--matrix td { + border: 1px solid var(--border); +} +.admin-matrix-matrix__corner { + position: sticky; + left: 0; + z-index: 2; + background: var(--surface); + min-width: 140px; + padding: 8px; +} +.admin-matrix-matrix__level-head { + padding: 8px; + min-width: 160px; + background: var(--surface2); + font-size: 13px; +} +.admin-matrix-matrix__skill-cell { + position: sticky; + left: 0; + z-index: 1; + background: var(--surface); + font-weight: 600; + max-width: 220px; + vertical-align: top; + padding: 8px; +} +.admin-matrix-matrix__skill-path { + font-size: 11px; + font-weight: 400; + margin-top: 6px; + line-height: 1.35; +} +.admin-matrix-matrix__cell { + vertical-align: top; + padding: 6px; + min-width: 148px; +} +.admin-matrix-matrix__ta { + font-size: 12px; + width: 100%; + min-width: 120px; +} +.admin-matrix-matrix__ta--criteria { + font-size: 11px; + margin-top: 6px; +} + +.btn-small { + padding: 8px 12px; + font-size: 13px; + min-height: 40px; +} + .muted { color: var(--text3); font-size: 13px; } .empty-state { text-align: center; padding: 48px 16px; color: var(--text3); } .empty-state h3 { font-size: 16px; color: var(--text2); margin-bottom: 6px; } diff --git a/frontend/src/components/admin/MaturityModelsAdminPanel.jsx b/frontend/src/components/admin/MaturityModelsAdminPanel.jsx index 3fc2863..e9e2de4 100644 --- a/frontend/src/components/admin/MaturityModelsAdminPanel.jsx +++ b/frontend/src/components/admin/MaturityModelsAdminPanel.jsx @@ -2,31 +2,6 @@ import React, { useEffect, useState } from 'react' import { useAuth } from '../../context/AuthContext' import api from '../../utils/api' -function MultiIdSelect({ label, options, valueIds, onChange, hint }) { - return ( -
- - - {hint ? ( -
{hint}
- ) : null} -
- ) -} - export default function MaturityModelsAdminPanel() { const { user } = useAuth() const isSuperadmin = user?.role === 'superadmin' @@ -37,17 +12,11 @@ export default function MaturityModelsAdminPanel() { 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' }) @@ -61,18 +30,12 @@ export default function MaturityModelsAdminPanel() { let cancelled = false ;(async () => { try { - const [m, fa, sd, tg, sk] = await Promise.all([ + const [m, 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) { @@ -97,9 +60,6 @@ export default function MaturityModelsAdminPanel() { 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' }) @@ -138,10 +98,7 @@ export default function MaturityModelsAdminPanel() { 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 + status: newModel.status } const created = await api.createMaturityModel(payload) await refreshModels() @@ -149,9 +106,6 @@ export default function MaturityModelsAdminPanel() { setNewModel({ name: '', level_count: 5, - focus_area_ids: [], - style_direction_ids: [], - target_group_ids: [], status: 'draft' }) } catch (e) { @@ -169,9 +123,6 @@ export default function MaturityModelsAdminPanel() { 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 }) @@ -321,58 +272,45 @@ export default function MaturityModelsAdminPanel() { return (

- Reifegradmodelle: Kontext (Fokus / Stil / Zielgruppe), Stufen, Matrix-Zelltexte. Die Fähigkeiten selbst pflegen Sie im Tab „Katalog und Hierarchie“. + Ablauf: Modell wählen → Stammdaten → Stufen definieren → Fähigkeiten zuordnen → Matrix-Zelltexte pflegen. + Fähigkeiten legen Sie im Tab „Katalog und Hierarchie“ an.{' '} + Zuordnung zu Fokusbereich / Stil / Zielgruppe ist hier vorerst deaktiviert; bestehende + Einträge in der Datenbank bleiben unverändert, bis wir das Konzept (Überschneidungen) geklärt haben.

{error ? ( -
+
{error}
) : null} -
-
-

Modelle

-
    +
    +
    + -
    - {loading ?
    : null} +
    + {loading ? ( +
    +
    + Lade Modell… +
    + ) : null} {!loading && !detail && ( -
    - Modell links wählen oder neu anlegen. +
    + Modell in der linken Spalte wählen oder neu anlegen.
    )} {!loading && detail && meta && ( <> -
    -

    Kontext & Metadaten

    -
    +
    +
    + 1 +

    Stammdaten

    +
    +
    setMeta((m) => ({ ...m, description: e.target.value }))} />
    - setMeta((m) => ({ ...m, focus_area_ids: ids }))} - hint="Strg/Cmd + Klick für mehrere. Alle abwählen = gilt in jedem Fokusbereich." - /> - setMeta((m) => ({ ...m, style_direction_ids: ids }))} - hint="Strg/Cmd + Klick für mehrere." - /> - setMeta((m) => ({ ...m, target_group_ids: ids }))} - hint="Strg/Cmd + Klick für mehrere." - /> -
    +
    onLevelCountChange(e.target.value)} />
    -
    - +
    +
    - - - - + + + + {levelsForm.map((row, idx) => ( - - - + + - -
    Nr.NameBeschreibungSortNr.NameBeschreibungSort
    {row.level_number} +
    {row.level_number} + + { const next = [...levelsForm] @@ -584,19 +511,21 @@ export default function MaturityModelsAdminPanel() { - + -
    -

    Fähigkeiten im Modell

    -
    -
    +
    +
    + 3 +

    Fähigkeiten im Modell

    +
    +
    +
    +
    +
    - + {(detail.levels || []).map((l) => ( - ))} @@ -665,21 +594,10 @@ export default function MaturityModelsAdminPanel() { {(detail.model_skills || []).map((ms) => ( -
    - Fähigkeit - Fähigkeit + {l.level_number}. {l.name}
    +
    {ms.skill_name}
    {(ms.skill_main_category_name || ms.skill_subcategory_name) ? ( -
    +
    {ms.skill_main_category_name || '—'} {ms.skill_subcategory_name ? ` › ${ms.skill_subcategory_name}` : ''}
    @@ -689,22 +607,20 @@ export default function MaturityModelsAdminPanel() { const key = `${ms.skill_id}-${l.level_number}` const d = cellDraft[key] || { description: '', observable_criteria: '' } return ( -
    +