shinkan-jinkendo/frontend/src/pages/SkillsPage.jsx

472 lines
16 KiB
React
Raw Normal View History

import React, { useState, useEffect } from 'react'
import api from '../utils/api'
import { useAuth } from '../context/AuthContext'
import PageSectionNav from '../components/PageSectionNav'
const SKILLS_SECTION_TABS = [
{ id: 'skills', label: 'Fähigkeiten' },
{ id: 'methods', label: 'Trainingsmethoden' },
]
function SkillsPage() {
const { user } = useAuth()
const [activeTab, setActiveTab] = useState('skills')
const [skills, setSkills] = useState([])
const [methods, setMethods] = useState([])
const [loading, setLoading] = useState(true)
const [showModal, setShowModal] = useState(false)
const [editing, setEditing] = useState(null)
const [modalType, setModalType] = useState('skill')
const [formData, setFormData] = useState({})
const isAdmin = user?.role === 'admin' || user?.role === 'superadmin'
useEffect(() => {
loadData()
}, [])
const loadData = async () => {
try {
const [skillsData, methodsData] = await Promise.all([
api.listSkills(),
api.listMethods()
])
setSkills(skillsData)
setMethods(methodsData)
} catch (err) {
console.error('Failed to load data:', err)
alert('Fehler beim Laden: ' + err.message)
} finally {
setLoading(false)
}
}
const handleCreate = (type) => {
setEditing(null)
setModalType(type)
if (type === 'skill') {
setFormData({
name: '',
category: '',
description: '',
importance: 3,
keywords: [],
status: 'active'
})
} else {
setFormData({
name: '',
abbreviation: '',
category: '',
description: '',
typical_duration: '',
typical_group_size: '',
related_skills: [],
keywords: [],
status: 'active'
})
}
setShowModal(true)
}
const handleEdit = (item, type) => {
setEditing(item)
setModalType(type)
setFormData({ ...item })
setShowModal(true)
}
const handleDelete = async (item, type) => {
const confirmMsg = type === 'skill'
? `Fähigkeit "${item.name}" wirklich löschen?`
: `Trainingsmethode "${item.name}" wirklich löschen?`
if (!confirm(confirmMsg)) return
try {
if (type === 'skill') {
await api.deleteSkill(item.id)
} else {
await api.deleteMethod(item.id)
}
await loadData()
} catch (err) {
alert('Fehler beim Löschen: ' + err.message)
}
}
const handleSubmit = async (e) => {
e.preventDefault()
try {
if (modalType === 'skill') {
if (editing) {
await api.updateSkill(editing.id, formData)
} else {
await api.createSkill(formData)
}
} else {
if (editing) {
await api.updateMethod(editing.id, formData)
} else {
await api.createMethod(formData)
}
}
setShowModal(false)
await loadData()
} catch (err) {
alert('Fehler beim Speichern: ' + err.message)
}
}
const updateFormField = (field, value) => {
setFormData(prev => ({ ...prev, [field]: value }))
}
const groupByCategory = (items) => {
const grouped = {}
items.forEach(item => {
const cat = item.category || 'Ohne Kategorie'
if (!grouped[cat]) grouped[cat] = []
grouped[cat].push(item)
})
return grouped
}
if (loading) {
return (
<div className="skills-page__loading">
<div className="spinner"></div>
<p>Laden...</p>
</div>
)
}
const skillsByCategory = groupByCategory(skills)
const methodsByCategory = groupByCategory(methods)
return (
<div className="app-page skills-page">
<h1 className="page-title">Fähigkeiten & Methoden</h1>
<PageSectionNav
ariaLabel="Bereich wählen"
value={activeTab}
onChange={setActiveTab}
items={SKILLS_SECTION_TABS}
className="skills-page__tabs-scroll"
/>
{/* Skills Tab */}
{activeTab === 'skills' && (
<>
<div className="skills-page__intro-row">
<p>
Fähigkeiten sind Kompetenzen, die in Übungen trainiert werden.
</p>
{isAdmin && (
<button className="btn btn-primary" onClick={() => handleCreate('skill')}>
+ Neue Fähigkeit
</button>
)}
</div>
{Object.keys(skillsByCategory).length === 0 ? (
<div className="card">
<p className="skills-page__empty">
Keine Fähigkeiten gefunden
</p>
</div>
) : (
Object.keys(skillsByCategory).sort().map(category => (
<div key={category} className="skills-page__category">
<h2 className="skills-page__category-title">
{category}
</h2>
<div className="skills-page__card-grid">
{skillsByCategory[category].map(skill => (
<div key={skill.id} className="card skills-page-card">
<div className="skills-page-card__head">
<h3 className="skills-page-card__title">{skill.name}</h3>
{skill.importance && (
<span className="skills-page-card__badge">
⭐ {skill.importance}/5
</span>
)}
</div>
{skill.description && (
<p className="skills-page-card__desc">
{skill.description}
</p>
)}
{isAdmin && (
<div className="skills-page-card__actions">
<button
type="button"
className="btn btn-secondary skills-page-card__grow"
onClick={() => handleEdit(skill, 'skill')}
>
Bearbeiten
</button>
<button
type="button"
className="btn btn-danger"
onClick={() => handleDelete(skill, 'skill')}
>
Löschen
</button>
</div>
)}
</div>
))}
</div>
</div>
))
)}
</>
)}
{/* Methods Tab */}
{activeTab === 'methods' && (
<>
<div className="skills-page__intro-row">
<p>
Trainingsmethoden sind didaktische Ansätze für die Trainingsgestaltung.
</p>
{isAdmin && (
<button className="btn btn-primary" onClick={() => handleCreate('method')}>
+ Neue Methode
</button>
)}
</div>
{Object.keys(methodsByCategory).length === 0 ? (
<div className="card">
<p className="skills-page__empty">
Keine Trainingsmethoden gefunden
</p>
</div>
) : (
Object.keys(methodsByCategory).sort().map(category => (
<div key={category} className="skills-page__category">
<h2 className="skills-page__category-title">
{category}
</h2>
<div className="skills-page__card-grid skills-page__card-grid--methods">
{methodsByCategory[category].map(method => (
<div key={method.id} className="card skills-page-card">
<div className="skills-page-card__meta-block">
<h3 className="skills-page-card__title skills-page-card__title--method">
{method.name}
{method.abbreviation && (
<span className="skills-page-card__abbr">
({method.abbreviation})
</span>
)}
</h3>
<div className="skills-page-card__meta-row">
{method.typical_duration && (
<span className="skills-page-card__chip">
⏱️ {method.typical_duration} min
</span>
)}
{method.typical_group_size && (
<span className="skills-page-card__chip">
👥 {method.typical_group_size}
</span>
)}
</div>
</div>
{method.description && (
<p className="skills-page-card__desc">
{method.description}
</p>
)}
{isAdmin && (
<div className="skills-page-card__actions">
<button
type="button"
className="btn btn-secondary skills-page-card__grow"
onClick={() => handleEdit(method, 'method')}
>
Bearbeiten
</button>
<button
type="button"
className="btn btn-danger"
onClick={() => handleDelete(method, 'method')}
>
Löschen
</button>
</div>
)}
</div>
))}
</div>
</div>
))
)}
</>
)}
{/* Modal */}
{showModal && isAdmin && (
<div
className="admin-modal-backdrop"
role="presentation"
onClick={(e) => {
if (e.target === e.currentTarget) setShowModal(false)
}}
>
<div
className="admin-modal-sheet skills-page-modal"
role="dialog"
aria-modal="true"
aria-labelledby="skills-page-modal-title"
onClick={(e) => e.stopPropagation()}
>
<div className="admin-modal-sheet__header">
<h2 id="skills-page-modal-title" className="admin-modal-sheet__title">
{editing
? (modalType === 'skill' ? 'Fähigkeit bearbeiten' : 'Methode bearbeiten')
: (modalType === 'skill' ? 'Neue Fähigkeit' : 'Neue Methode')
}
</h2>
<button
type="button"
className="btn btn-secondary admin-modal-sheet__close"
onClick={() => setShowModal(false)}
>
Schließen
</button>
</div>
<div className="admin-modal-sheet__body">
<form onSubmit={handleSubmit}>
<div className="form-row">
<label className="form-label">Name *</label>
<input
type="text"
className="form-input"
value={formData.name || ''}
onChange={(e) => updateFormField('name', e.target.value)}
required
/>
</div>
{modalType === 'method' && (
<div className="form-row">
<label className="form-label">Kürzel</label>
<input
type="text"
className="form-input"
value={formData.abbreviation || ''}
onChange={(e) => updateFormField('abbreviation', e.target.value)}
maxLength={20}
/>
</div>
)}
<div className="form-row">
<label className="form-label">Kategorie</label>
<input
type="text"
className="form-input"
value={formData.category || ''}
onChange={(e) => updateFormField('category', e.target.value)}
placeholder={modalType === 'skill' ? 'z.B. kihon, kumite, kata' : 'z.B. kondition, didaktik'}
/>
</div>
<div className="form-row">
<label className="form-label">Beschreibung</label>
<textarea
className="form-input"
rows={3}
value={formData.description || ''}
onChange={(e) => updateFormField('description', e.target.value)}
/>
</div>
{modalType === 'skill' && (
<div className="form-row">
<label className="form-label">Wichtigkeit (1-5)</label>
<input
type="number"
className="form-input"
min={1}
max={5}
value={formData.importance || 3}
onChange={(e) => updateFormField('importance', parseInt(e.target.value))}
/>
</div>
)}
{modalType === 'method' && (
<>
<div className="exercise-filters-modal-grid exercise-filters-modal-grid--two">
<div className="form-row">
<label className="form-label">Typische Dauer (min)</label>
<input
type="number"
className="form-input"
value={formData.typical_duration || ''}
onChange={(e) => updateFormField('typical_duration', e.target.value ? parseInt(e.target.value) : '')}
/>
</div>
<div className="form-row">
<label className="form-label">Gruppengröße</label>
<input
type="text"
className="form-input"
value={formData.typical_group_size || ''}
onChange={(e) => updateFormField('typical_group_size', e.target.value)}
placeholder="z.B. 10-20"
/>
</div>
</div>
</>
)}
<div className="form-row">
<label className="form-label">Status</label>
<select
className="form-input"
value={formData.status || 'active'}
onChange={(e) => updateFormField('status', e.target.value)}
>
<option value="active">Aktiv</option>
<option value="inactive">Inaktiv</option>
</select>
</div>
<div className="skills-page-modal__footer">
<button type="submit" className="btn btn-primary skills-page-modal__submit">
{editing ? 'Speichern' : 'Erstellen'}
</button>
<button
type="button"
className="btn btn-secondary"
onClick={() => setShowModal(false)}
>
Abbrechen
</button>
</div>
</form>
</div>
</div>
</div>
)}
</div>
)
}
export default SkillsPage