UX - Filter #12
|
|
@ -1411,6 +1411,20 @@ button.capture-shell__nav-item {
|
||||||
.nav-item span {
|
.nav-item span {
|
||||||
font-size: 9.5px;
|
font-size: 9.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-page-subtabs {
|
||||||
|
margin-left: calc(-1 * max(12px, env(safe-area-inset-left, 0px)));
|
||||||
|
margin-right: calc(-1 * max(12px, env(safe-area-inset-right, 0px)));
|
||||||
|
padding-left: max(12px, env(safe-area-inset-left, 0px));
|
||||||
|
padding-right: max(12px, env(safe-area-inset-right, 0px));
|
||||||
|
margin-bottom: 14px;
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-catalog-section {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Trainingsplanung: kompakte Segmente (Gruppe / Verein) */
|
/* Trainingsplanung: kompakte Segmente (Gruppe / Verein) */
|
||||||
|
|
@ -1461,6 +1475,180 @@ button.capture-shell__nav-item {
|
||||||
font-size: 0.92rem;
|
font-size: 0.92rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Admin-Kataloge: Seite „Stammdaten“ — viele Unter-Tabs, Chip-Scroll */
|
||||||
|
.admin-page-subtabs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
scroll-snap-type: x proximity;
|
||||||
|
}
|
||||||
|
.admin-page-subtabs::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.admin-page-subtabs__btn {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 8px 13px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--border2);
|
||||||
|
background: var(--surface2);
|
||||||
|
color: var(--text2);
|
||||||
|
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.admin-page-subtabs__btn:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--text1);
|
||||||
|
}
|
||||||
|
.admin-page-subtabs__btn--active {
|
||||||
|
background: var(--accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.admin-page-subtabs__btn--active:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: color-mix(in srgb, var(--accent) 92%, #000);
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.admin-page-subtabs__btn {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 9px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Admin Hierarchy & Catalog Section (Komponenten) */
|
||||||
|
.admin-hierarchy-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.admin-hierarchy-pane {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: var(--surface);
|
||||||
|
}
|
||||||
|
.admin-hierarchy-pane__title {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.admin-hierarchy-back {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-catalog-stack {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.admin-catalog-stack {
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.admin-catalog-section {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.admin-catalog-section {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.admin-catalog-section__head {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.admin-catalog-section__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
font-weight: 700;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.admin-catalog-section__icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--accent-dark);
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.admin-catalog-section__icon {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.admin-catalog-inline-form {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 14px;
|
||||||
|
background: var(--surface2);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.admin-catalog-inline-form h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.admin-catalog-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.admin-catalog-item {
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--surface2);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.admin-catalog-item__name-row {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.admin-catalog-meta {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: var(--text3);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
.admin-catalog-desc {
|
||||||
|
color: var(--text2);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
.admin-catalog-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.admin-catalog-empty {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text3);
|
||||||
|
padding: 1.25rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ausklappbare Kontext-Hilfe (Filterzeile Planung) */
|
/* Ausklappbare Kontext-Hilfe (Filterzeile Planung) */
|
||||||
.planning-filter-help {
|
.planning-filter-help {
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,23 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
import { Target, Tags, Dumbbell } from 'lucide-react'
|
||||||
import { api } from '../../utils/api'
|
import { api } from '../../utils/api'
|
||||||
|
|
||||||
function CatalogsTab({ targetGroups, skillCategories, trainingCharacters, loading, error, onUpdate }) {
|
function CatalogsTab({ targetGroups, skillCategories, trainingCharacters, loading, error, onUpdate }) {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div style={{ textAlign: 'center', padding: '40px' }}><div className="spinner"></div></div>
|
return (
|
||||||
|
<div className="empty-state" style={{ padding: '2.5rem' }}>
|
||||||
|
<div className="spinner" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: '24px' }}>
|
<div className="admin-catalog-stack">
|
||||||
{error && <div style={{ color: 'var(--danger)', padding: '16px', background: 'var(--surface)', borderRadius: '8px' }}>{error}</div>}
|
{error && <div className="admin-matrix-alert">{error}</div>}
|
||||||
|
|
||||||
<CatalogSection
|
<CatalogSection
|
||||||
title="Zielgruppen"
|
title="Zielgruppen"
|
||||||
icon="🎯"
|
Icon={Target}
|
||||||
items={targetGroups}
|
items={targetGroups}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
createFn={api.createTargetGroup}
|
createFn={api.createTargetGroup}
|
||||||
|
|
@ -28,7 +33,7 @@ function CatalogsTab({ targetGroups, skillCategories, trainingCharacters, loadin
|
||||||
|
|
||||||
<CatalogSection
|
<CatalogSection
|
||||||
title="Fähigkeitskategorien"
|
title="Fähigkeitskategorien"
|
||||||
icon="⚡"
|
Icon={Tags}
|
||||||
items={skillCategories}
|
items={skillCategories}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
createFn={api.createSkillCategory}
|
createFn={api.createSkillCategory}
|
||||||
|
|
@ -42,7 +47,7 @@ function CatalogsTab({ targetGroups, skillCategories, trainingCharacters, loadin
|
||||||
|
|
||||||
<CatalogSection
|
<CatalogSection
|
||||||
title="Trainingscharakter"
|
title="Trainingscharakter"
|
||||||
icon="💪"
|
Icon={Dumbbell}
|
||||||
items={trainingCharacters}
|
items={trainingCharacters}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
createFn={api.createTrainingCharacter}
|
createFn={api.createTrainingCharacter}
|
||||||
|
|
@ -57,27 +62,27 @@ function CatalogsTab({ targetGroups, skillCategories, trainingCharacters, loadin
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function CatalogSection({ title, icon, items, onUpdate, createFn, updateFn, deleteFn, fields }) {
|
function CatalogSection({ title, Icon, items, onUpdate, createFn, updateFn, deleteFn, fields }) {
|
||||||
const [creating, setCreating] = useState(false)
|
const [creating, setCreating] = useState(false)
|
||||||
const [editing, setEditing] = useState(null)
|
const [editing, setEditing] = useState(null)
|
||||||
const [form, setForm] = useState({})
|
const [form, setForm] = useState({})
|
||||||
|
|
||||||
function startCreate() {
|
function startCreate() {
|
||||||
const emptyForm = {}
|
const emptyForm = {}
|
||||||
fields.forEach(f => { emptyForm[f.key] = '' })
|
fields.forEach((f) => { emptyForm[f.key] = '' })
|
||||||
setForm(emptyForm)
|
setForm(emptyForm)
|
||||||
setCreating(true)
|
setCreating(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function startEdit(item) {
|
function startEdit(item) {
|
||||||
const editForm = {}
|
const editForm = {}
|
||||||
fields.forEach(f => { editForm[f.key] = item[f.key] || '' })
|
fields.forEach((f) => { editForm[f.key] = item[f.key] || '' })
|
||||||
setEditing(item.id)
|
setEditing(item.id)
|
||||||
setForm(editForm)
|
setForm(editForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCreate() {
|
async function handleCreate() {
|
||||||
const required = fields.filter(f => f.required)
|
const required = fields.filter((f) => f.required)
|
||||||
for (const field of required) {
|
for (const field of required) {
|
||||||
if (!form[field.key]) {
|
if (!form[field.key]) {
|
||||||
alert(`${field.label} ist erforderlich`)
|
alert(`${field.label} ist erforderlich`)
|
||||||
|
|
@ -116,75 +121,116 @@ function CatalogSection({ title, icon, items, onUpdate, createFn, updateFn, dele
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ background: 'var(--surface)', borderRadius: '12px', padding: '20px' }}>
|
<div className="admin-catalog-section">
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
<div className="admin-catalog-section__head">
|
||||||
<h3 style={{ margin: 0 }}>{icon} {title}</h3>
|
<h3 className="admin-catalog-section__title">
|
||||||
<button className="btn btn-primary" onClick={startCreate}>+ Neu</button>
|
{Icon ? (
|
||||||
|
<Icon className="admin-catalog-section__icon" size={20} strokeWidth={2} aria-hidden />
|
||||||
|
) : null}
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<button type="button" className="btn btn-primary btn-small" onClick={startCreate}>
|
||||||
|
+ Neu
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{creating && (
|
{creating && (
|
||||||
<div style={{ marginBottom: '20px', padding: '16px', background: 'var(--surface2)', borderRadius: '8px' }}>
|
<div className="admin-catalog-inline-form">
|
||||||
<h4 style={{ marginTop: 0 }}>Neu erstellen</h4>
|
<h4>Neu erstellen</h4>
|
||||||
{fields.map(field => (
|
{fields.map((field) => (
|
||||||
<div key={field.key} className="form-row">
|
<div key={field.key} className="form-row">
|
||||||
<label className="form-label">{field.label} {field.required && '*'}</label>
|
<label className="form-label">
|
||||||
|
{field.label} {field.required && '*'}
|
||||||
|
</label>
|
||||||
{field.type === 'textarea' ? (
|
{field.type === 'textarea' ? (
|
||||||
<textarea className="form-input" value={form[field.key] || ''} onChange={e => setForm({ ...form, [field.key]: e.target.value })} rows={3} />
|
<textarea
|
||||||
|
className="form-input"
|
||||||
|
value={form[field.key] || ''}
|
||||||
|
onChange={(e) => setForm({ ...form, [field.key]: e.target.value })}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<input className="form-input" type={field.type} value={form[field.key] || ''} onChange={e => setForm({ ...form, [field.key]: e.target.value })} />
|
<input
|
||||||
|
className="form-input"
|
||||||
|
type={field.type}
|
||||||
|
value={form[field.key] || ''}
|
||||||
|
onChange={(e) => setForm({ ...form, [field.key]: e.target.value })}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '12px' }}>
|
<div className="admin-catalog-actions">
|
||||||
<button className="btn btn-primary" onClick={handleCreate}>Erstellen</button>
|
<button type="button" className="btn btn-primary" onClick={handleCreate}>
|
||||||
<button className="btn" onClick={() => setCreating(false)}>Abbrechen</button>
|
Erstellen
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn btn-secondary" onClick={() => setCreating(false)}>
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ display: 'grid', gap: '12px' }}>
|
<div className="admin-catalog-list">
|
||||||
{items.map(item => (
|
{items.map((item) => (
|
||||||
<div key={item.id} style={{ padding: '12px', background: 'var(--surface2)', borderRadius: '8px' }}>
|
<div key={item.id} className="admin-catalog-item">
|
||||||
{editing === item.id ? (
|
{editing === item.id ? (
|
||||||
<div>
|
<div>
|
||||||
{fields.map(field => (
|
{fields.map((field) => (
|
||||||
<div key={field.key} className="form-row">
|
<div key={field.key} className="form-row">
|
||||||
<label className="form-label">{field.label}</label>
|
<label className="form-label">{field.label}</label>
|
||||||
{field.type === 'textarea' ? (
|
{field.type === 'textarea' ? (
|
||||||
<textarea className="form-input" value={form[field.key] || ''} onChange={e => setForm({ ...form, [field.key]: e.target.value })} rows={3} />
|
<textarea
|
||||||
|
className="form-input"
|
||||||
|
value={form[field.key] || ''}
|
||||||
|
onChange={(e) => setForm({ ...form, [field.key]: e.target.value })}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<input className="form-input" type={field.type} value={form[field.key] || ''} onChange={e => setForm({ ...form, [field.key]: e.target.value })} />
|
<input
|
||||||
|
className="form-input"
|
||||||
|
type={field.type}
|
||||||
|
value={form[field.key] || ''}
|
||||||
|
onChange={(e) => setForm({ ...form, [field.key]: e.target.value })}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '12px' }}>
|
<div className="admin-catalog-actions">
|
||||||
<button className="btn btn-primary" onClick={() => handleUpdate(item.id)}>Speichern</button>
|
<button type="button" className="btn btn-primary" onClick={() => handleUpdate(item.id)}>
|
||||||
<button className="btn" onClick={() => setEditing(null)}>Abbrechen</button>
|
Speichern
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn btn-secondary" onClick={() => setEditing(null)}>
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: '8px' }}>
|
<div className="admin-catalog-item__name-row">
|
||||||
<strong>{item.name}</strong>
|
<strong>{item.name}</strong>
|
||||||
{item.min_age !== null && item.max_age !== null && (
|
{item.min_age != null && item.max_age != null && (
|
||||||
<span style={{ marginLeft: '12px', color: 'var(--text3)', fontSize: '14px' }}>
|
<span className="admin-catalog-meta">
|
||||||
Alter: {item.min_age}-{item.max_age}
|
Alter: {item.min_age}-{item.max_age}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{item.description && <p style={{ color: 'var(--text2)', fontSize: '14px', margin: '8px 0' }}>{item.description}</p>}
|
{item.description ? (
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
|
<p className="admin-catalog-desc">{item.description}</p>
|
||||||
<button className="btn" onClick={() => startEdit(item)}>Bearbeiten</button>
|
) : null}
|
||||||
<button className="btn" onClick={() => handleDelete(item.id, item.name)}>Löschen</button>
|
<div className="admin-catalog-actions">
|
||||||
|
<button type="button" className="btn btn-secondary btn-small" onClick={() => startEdit(item)}>
|
||||||
|
Bearbeiten
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn btn-danger btn-small" onClick={() => handleDelete(item.id, item.name)}>
|
||||||
|
Löschen
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{items.length === 0 && !creating && (
|
{items.length === 0 && !creating && (
|
||||||
<div style={{ textAlign: 'center', color: 'var(--text3)', padding: '20px' }}>
|
<div className="admin-catalog-empty">Noch keine Einträge vorhanden</div>
|
||||||
Noch keine Einträge vorhanden
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,24 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { ArrowLeft } from 'lucide-react'
|
||||||
import FocusAreaNode from './FocusAreaNode'
|
import FocusAreaNode from './FocusAreaNode'
|
||||||
import DetailPanel from './DetailPanel'
|
import DetailPanel from './DetailPanel'
|
||||||
|
|
||||||
function HierarchyTab({ hierarchy, expandedNodes, selectedItem, loading, error, onToggleNode, onSelectItem, onUpdate }) {
|
function HierarchyTab({ hierarchy, expandedNodes, selectedItem, loading, error, onToggleNode, onSelectItem, onUpdate }) {
|
||||||
if (loading && hierarchy.length === 0) {
|
if (loading && hierarchy.length === 0) {
|
||||||
return <div style={{ textAlign: 'center', padding: '40px' }}><div className="spinner"></div></div>
|
return (
|
||||||
|
<div className="empty-state" style={{ padding: '2.5rem' }}>
|
||||||
|
<div className="spinner" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-hierarchy-container">
|
<div className="admin-hierarchy-container admin-hierarchy-layout">
|
||||||
{/* Tree View */}
|
<div className="admin-hierarchy-pane admin-hierarchy-pane--tree" hidden={!!selectedItem}>
|
||||||
<div
|
<h2 className="admin-hierarchy-pane__title">Katalog-Hierarchie</h2>
|
||||||
className="admin-tree-view"
|
{error && <div className="admin-matrix-alert">{error}</div>}
|
||||||
style={{
|
|
||||||
display: selectedItem ? 'none' : 'block',
|
|
||||||
border: '1px solid var(--border)',
|
|
||||||
borderRadius: '12px',
|
|
||||||
padding: '16px',
|
|
||||||
background: 'var(--surface)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h2 style={{ marginTop: 0 }}>Katalog-Hierarchie</h2>
|
|
||||||
{error && <div style={{ color: 'var(--danger)', marginBottom: '16px' }}>{error}</div>}
|
|
||||||
|
|
||||||
{hierarchy.map(fa => (
|
{hierarchy.map((fa) => (
|
||||||
<FocusAreaNode
|
<FocusAreaNode
|
||||||
key={fa.id}
|
key={fa.id}
|
||||||
focusArea={fa}
|
focusArea={fa}
|
||||||
|
|
@ -36,22 +31,15 @@ function HierarchyTab({ hierarchy, expandedNodes, selectedItem, loading, error,
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Detail Panel */}
|
|
||||||
{selectedItem && (
|
{selectedItem && (
|
||||||
<div
|
<div className="admin-hierarchy-pane admin-hierarchy-pane--detail">
|
||||||
style={{
|
|
||||||
border: '1px solid var(--border)',
|
|
||||||
borderRadius: '12px',
|
|
||||||
padding: '20px',
|
|
||||||
background: 'var(--surface)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
className="btn admin-back-button"
|
type="button"
|
||||||
|
className="btn btn-secondary btn-small admin-hierarchy-back"
|
||||||
onClick={() => onSelectItem(null)}
|
onClick={() => onSelectItem(null)}
|
||||||
style={{ marginBottom: '16px' }}
|
|
||||||
>
|
>
|
||||||
← Zurück zur Übersicht
|
<ArrowLeft size={16} strokeWidth={2} aria-hidden />
|
||||||
|
Zurück zur Übersicht
|
||||||
</button>
|
</button>
|
||||||
<DetailPanel item={selectedItem} onUpdate={onUpdate} focusAreas={hierarchy} />
|
<DetailPanel item={selectedItem} onUpdate={onUpdate} focusAreas={hierarchy} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -316,10 +316,9 @@ export default function AdminCatalogsPage() {
|
||||||
<div className="app-page">
|
<div className="app-page">
|
||||||
<AdminPageNav />
|
<AdminPageNav />
|
||||||
|
|
||||||
<h1 style={{ marginBottom: '24px' }}>Stammdaten-Kataloge</h1>
|
<h1 className="page-title">Stammdaten-Kataloge</h1>
|
||||||
|
|
||||||
{/* Tabs */}
|
<div className="admin-page-subtabs" role="tablist" aria-label="Katalogbereiche">
|
||||||
<div style={{ display: 'flex', gap: '8px', borderBottom: '2px solid var(--border)', marginBottom: '24px', overflowX: 'auto' }}>
|
|
||||||
{[
|
{[
|
||||||
{ id: 'focus-areas', label: 'Fokusbereiche' },
|
{ id: 'focus-areas', label: 'Fokusbereiche' },
|
||||||
{ id: 'training-styles', label: 'Stilrichtungen' },
|
{ id: 'training-styles', label: 'Stilrichtungen' },
|
||||||
|
|
@ -330,30 +329,24 @@ export default function AdminCatalogsPage() {
|
||||||
{ id: 'training-characters', label: 'Trainingscharakter' },
|
{ id: 'training-characters', label: 'Trainingscharakter' },
|
||||||
{ id: 'skill-categories', label: 'Fähigkeitskategorien' },
|
{ id: 'skill-categories', label: 'Fähigkeitskategorien' },
|
||||||
{ id: 'trainer-assignments', label: 'Trainer-Zuordnungen' }
|
{ id: 'trainer-assignments', label: 'Trainer-Zuordnungen' }
|
||||||
].map(tab => (
|
].map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={activeTab === tab.id}
|
||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => setActiveTab(tab.id)}
|
||||||
className="btn"
|
className={
|
||||||
style={{
|
'admin-page-subtabs__btn' +
|
||||||
borderBottom: activeTab === tab.id ? '3px solid var(--accent)' : 'none',
|
(activeTab === tab.id ? ' admin-page-subtabs__btn--active' : '')
|
||||||
borderRadius: 0,
|
}
|
||||||
fontWeight: activeTab === tab.id ? 600 : 400,
|
|
||||||
color: activeTab === tab.id ? 'var(--accent)' : 'var(--text2)',
|
|
||||||
padding: '12px 16px',
|
|
||||||
whiteSpace: 'nowrap'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && <div className="admin-matrix-alert">{error}</div>}
|
||||||
<div style={{ padding: '12px', background: 'var(--danger)', color: 'white', borderRadius: '8px', marginBottom: '16px' }}>
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="spinner" />
|
<div className="spinner" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user