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

232 lines
7.7 KiB
React
Raw Normal View History

import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import api from '../utils/api'
function ExercisesListPage() {
const [exercises, setExercises] = useState([])
const [focusAreas, setFocusAreas] = useState([])
const [loading, setLoading] = useState(true)
const [filters, setFilters] = useState({
focus_area: '',
visibility: '',
status: '',
})
useEffect(() => {
loadData()
}, [filters])
const loadData = async () => {
try {
const [exercisesData, focusAreasData] = await Promise.all([
api.listExercises(filters),
api.listFocusAreas(),
])
setExercises(exercisesData)
setFocusAreas(focusAreasData)
} catch (err) {
console.error('Failed to load data:', err)
alert('Fehler beim Laden: ' + err.message)
} finally {
setLoading(false)
}
}
const handleDelete = async (exercise) => {
if (!confirm(`Übung "${exercise.title}" wirklich löschen?`)) return
try {
await api.deleteExercise(exercise.id)
await loadData()
} catch (err) {
alert('Fehler beim Löschen: ' + err.message)
}
}
if (loading) {
return (
<div style={{ padding: '2rem', textAlign: 'center' }}>
<div className="spinner"></div>
<p>Laden...</p>
</div>
)
}
return (
<div style={{ padding: '2rem' }}>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '1.5rem',
flexWrap: 'wrap',
gap: '0.75rem',
}}
>
<h1>Übungen</h1>
<Link to="/exercises/new" className="btn btn-primary">
+ Neue Übung
</Link>
</div>
<div className="card" style={{ marginBottom: '1.5rem' }}>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
gap: '1rem',
}}
>
<div>
<label className="form-label">Fokusbereich</label>
<select
className="form-input"
value={filters.focus_area}
onChange={(e) => setFilters({ ...filters, focus_area: e.target.value })}
>
<option value="">Alle</option>
{focusAreas.map((fa) => (
<option key={fa.id} value={fa.id}>
{fa.icon} {fa.name}
</option>
))}
</select>
</div>
<div>
<label className="form-label">Sichtbarkeit</label>
<select
className="form-input"
value={filters.visibility}
onChange={(e) => setFilters({ ...filters, visibility: e.target.value })}
>
<option value="">Alle</option>
<option value="private">Privat</option>
<option value="club">Verein</option>
<option value="official">Offiziell</option>
</select>
</div>
<div>
<label className="form-label">Status</label>
<select
className="form-input"
value={filters.status}
onChange={(e) => setFilters({ ...filters, status: e.target.value })}
>
<option value="">Alle</option>
<option value="draft">Entwurf</option>
<option value="in_review">In Prüfung</option>
<option value="approved">Freigegeben</option>
<option value="archived">Archiviert</option>
</select>
</div>
</div>
</div>
{exercises.length === 0 ? (
<div className="card">
<p style={{ color: 'var(--text2)', textAlign: 'center' }}>
Keine Übungen gefunden. Lege jetzt deine erste Übung an!
</p>
</div>
) : (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
gap: '1rem',
}}
>
{exercises.map((exercise) => (
<div key={exercise.id} className="card">
<div style={{ marginBottom: '1rem' }}>
<h3 style={{ marginBottom: '0.5rem' }}>
<Link
to={`/exercises/${exercise.id}`}
style={{ color: 'inherit', textDecoration: 'none' }}
>
{exercise.title}
</Link>
</h3>
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
<span
style={{
fontSize: '0.75rem',
padding: '0.25rem 0.5rem',
borderRadius: '4px',
background: 'var(--surface2)',
color: 'var(--text2)',
}}
>
{exercise.focus_area || 'Ohne Fokus'}
</span>
<span
style={{
fontSize: '0.75rem',
padding: '0.25rem 0.5rem',
borderRadius: '4px',
background:
exercise.visibility === 'official' ? 'var(--accent)' : 'var(--surface2)',
color: exercise.visibility === 'official' ? 'white' : 'var(--text2)',
}}
>
{exercise.visibility}
</span>
<span
style={{
fontSize: '0.75rem',
padding: '0.25rem 0.5rem',
borderRadius: '4px',
background: exercise.status === 'approved' ? '#2ea44f' : 'var(--surface2)',
color: exercise.status === 'approved' ? 'white' : 'var(--text2)',
}}
>
{exercise.status}
</span>
</div>
</div>
{exercise.summary && (
<p style={{ color: 'var(--text2)', fontSize: '0.875rem', marginBottom: '1rem' }}>
{exercise.summary}
</p>
)}
<div style={{ display: 'flex', gap: '0.5rem', marginTop: 'auto', flexWrap: 'wrap' }}>
<Link
to={`/exercises/${exercise.id}`}
className="btn btn-secondary"
style={{ flex: '1 1 100px', textAlign: 'center' }}
>
Ansehen
</Link>
<Link
to={`/exercises/${exercise.id}/edit`}
className="btn btn-secondary"
style={{ flex: '1 1 100px', textAlign: 'center' }}
>
Bearbeiten
</Link>
<button
type="button"
className="btn"
style={{
flex: '1 1 100px',
background: 'var(--danger)',
color: 'white',
border: 'none',
}}
onClick={() => handleDelete(exercise)}
>
Löschen
</button>
</div>
</div>
))}
</div>
)}
</div>
</div>
)
}
export default ExercisesListPage