feat: Admin-UI Trainingsstil-Dimension + Umbenennung
Some checks failed
Deploy Development / deploy (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 5s
Test Suite / playwright-tests (push) Failing after 1m54s

Admin-UI Erweiterung:

1. Tab "Trainingsstile" → "Stilrichtungen" umbenannt
   - Überschrift: "Neue Stilrichtung" (statt Trainingsstil)
   - Tab-Label: "Stilrichtungen"

2. Neuer Tab "Trainingsstil" (Breitensport/Leistungssport)
   - CRUD-UI: Create/Update/Delete
   - Felder: Name, Kürzel (abbreviation), Beschreibung
   - State: trainingTypes, editingTT, newTT
   - Funktionen: createTrainingType, updateTrainingType, deleteTrainingType
   - Load-Logik: activeTab === 'training-types'

Tab-Reihenfolge:
- Fokusbereiche → Stilrichtungen → Trainingsstil → Hierarchie → Zielgruppen → Zuordnungen

Pattern: Konsistent mit anderen Katalog-Tabs
Version: AdminCatalogsPage 2.1.0
This commit is contained in:
Lars 2026-04-23 12:17:31 +02:00
parent 72c927e69e
commit a9a4c78a0e
2 changed files with 132 additions and 3 deletions

View File

@ -21,6 +21,11 @@ export default function AdminCatalogsPage() {
const [editingTC, setEditingTC] = useState(null)
const [newTC, setNewTC] = useState({ name: '', description: '' })
// Training Types (Breitensport, Leistungssport, etc.)
const [trainingTypes, setTrainingTypes] = useState([])
const [editingTT, setEditingTT] = useState(null)
const [newTT, setNewTT] = useState({ name: '', abbreviation: '', description: '' })
// Skill Categories
const [skillCategories, setSkillCategories] = useState([])
const [editingSC, setEditingSC] = useState(null)
@ -61,6 +66,9 @@ export default function AdminCatalogsPage() {
} else if (activeTab === 'training-characters') {
const data = await api.listTrainingCharacters()
setTrainingCharacters(data)
} else if (activeTab === 'training-types') {
const data = await api.listTrainingTypes()
setTrainingTypes(data)
} else if (activeTab === 'skill-categories') {
const data = await api.listSkillCategories()
setSkillCategories(data)
@ -189,6 +197,37 @@ export default function AdminCatalogsPage() {
}
}
// Training Types
async function createTrainingType() {
try {
await api.createTrainingType(newTT)
setNewTT({ name: '', abbreviation: '', description: '' })
loadData()
} catch (e) {
setError(e.message)
}
}
async function updateTrainingType(id, data) {
try {
await api.updateTrainingType(id, data)
setEditingTT(null)
loadData()
} catch (e) {
setError(e.message)
}
}
async function deleteTrainingType(id) {
if (!confirm('Trainingsstil wirklich löschen?')) return
try {
await api.deleteTrainingType(id)
loadData()
} catch (e) {
setError(e.message)
}
}
// Skill Categories
async function createSkillCategory() {
try {
@ -280,7 +319,8 @@ export default function AdminCatalogsPage() {
<div style={{ display: 'flex', gap: '8px', borderBottom: '2px solid var(--border)', marginBottom: '24px', overflowX: 'auto' }}>
{[
{ id: 'focus-areas', label: 'Fokusbereiche' },
{ id: 'training-styles', label: 'Trainingsstile' },
{ id: 'training-styles', label: 'Stilrichtungen' },
{ id: 'training-types', label: 'Trainingsstil' },
{ id: 'hierarchy', label: 'Hierarchie' },
{ id: 'target-groups', label: 'Zielgruppen' },
{ id: 'target-groups-matrix', label: 'Zuordnungen' },
@ -437,7 +477,7 @@ export default function AdminCatalogsPage() {
{activeTab === 'training-styles' && (
<div>
<div className="card" style={{ marginBottom: '24px' }}>
<h3>Neuer Trainingsstil</h3>
<h3>Neue Stilrichtung</h3>
<div className="form-row">
<label className="form-label">Name</label>
<input
@ -602,6 +642,95 @@ export default function AdminCatalogsPage() {
</div>
)}
{/* Training Types */}
{activeTab === 'training-types' && (
<div>
<div className="card" style={{ marginBottom: '24px' }}>
<h3>Neuer Trainingsstil</h3>
<div className="form-row">
<label className="form-label">Name</label>
<input
className="form-input"
value={newTT.name}
onChange={e => setNewTT({ ...newTT, name: e.target.value })}
placeholder="z.B. Breitensport"
/>
</div>
<div className="form-row">
<label className="form-label">Kürzel (optional)</label>
<input
className="form-input"
value={newTT.abbreviation || ''}
onChange={e => setNewTT({ ...newTT, abbreviation: e.target.value })}
placeholder="z.B. BS"
/>
</div>
<div className="form-row">
<label className="form-label">Beschreibung</label>
<textarea
className="form-input"
value={newTT.description || ''}
onChange={e => setNewTT({ ...newTT, description: e.target.value })}
rows={3}
/>
</div>
<button className="btn btn-primary" onClick={createTrainingType}>Anlegen</button>
</div>
<div style={{ display: 'grid', gap: '16px' }}>
{trainingTypes.map(tt => (
<div key={tt.id} className="card">
{editingTT?.id === tt.id ? (
<div>
<div className="form-row">
<label className="form-label">Name</label>
<input
className="form-input"
value={editingTT.name}
onChange={e => setEditingTT({ ...editingTT, name: e.target.value })}
/>
</div>
<div className="form-row">
<label className="form-label">Kürzel</label>
<input
className="form-input"
value={editingTT.abbreviation || ''}
onChange={e => setEditingTT({ ...editingTT, abbreviation: e.target.value })}
/>
</div>
<div className="form-row">
<label className="form-label">Beschreibung</label>
<textarea
className="form-input"
value={editingTT.description || ''}
onChange={e => setEditingTT({ ...editingTT, description: e.target.value })}
rows={3}
/>
</div>
<div style={{ display: 'flex', gap: '8px' }}>
<button className="btn btn-primary" onClick={() => updateTrainingType(tt.id, editingTT)}>Speichern</button>
<button className="btn" onClick={() => setEditingTT(null)}>Abbrechen</button>
</div>
</div>
) : (
<div>
<h3>
{tt.name}
{tt.abbreviation && <span style={{ marginLeft: '8px', fontSize: '14px', color: 'var(--text2)' }}>({tt.abbreviation})</span>}
</h3>
{tt.description && <p style={{ margin: '8px 0', color: 'var(--text2)' }}>{tt.description}</p>}
<div style={{ display: 'flex', gap: '8px', marginTop: '12px' }}>
<button className="btn" onClick={() => setEditingTT(tt)}>Bearbeiten</button>
<button className="btn" onClick={() => deleteTrainingType(tt.id)}>Löschen</button>
</div>
</div>
)}
</div>
))}
</div>
</div>
)}
{/* Skill Categories */}
{activeTab === 'skill-categories' && (
<div>

View File

@ -11,5 +11,5 @@ export const PAGE_VERSIONS = {
ClubsPage: "1.0.0",
SkillsPage: "1.0.0",
TrainingPlanningPage: "1.0.0",
AdminCatalogsPage: "2.0.0", // Updated: M:N Refactoring - Hierarchy Tree + Matrix
AdminCatalogsPage: "2.1.0", // Updated: Stilrichtungen + Trainingsstil-Dimension
}