- Adjusted responsive design breakpoints for mobile and desktop views in app.css. - Introduced a new horizontal layout for slots in the framework editor, enhancing usability on wider screens. - Updated versioning for TrainingFrameworkProgramEditPage to 1.2.0 to reflect recent changes. - Improved tab visibility and layout management based on screen size in TrainingFrameworkProgramEditPage.jsx. - Enhanced user guidance in TrainingFrameworkProgramsListPage with additional context on layout behavior.
161 lines
5.6 KiB
JavaScript
161 lines
5.6 KiB
JavaScript
import React, { useCallback, useEffect, useState } from 'react'
|
||
import { Link } from 'react-router-dom'
|
||
import api from '../utils/api'
|
||
|
||
const MODE_LABELS = {
|
||
concrete: 'Konkret (Gruppe / Einheiten)',
|
||
library: 'Bibliothek',
|
||
}
|
||
|
||
export default function TrainingFrameworkProgramsListPage() {
|
||
const [rows, setRows] = useState([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState('')
|
||
|
||
const load = useCallback(async () => {
|
||
setLoading(true)
|
||
setError('')
|
||
try {
|
||
const list = await api.listTrainingFrameworkPrograms()
|
||
setRows(Array.isArray(list) ? list : [])
|
||
} catch (e) {
|
||
setError(e.message || 'Laden fehlgeschlagen')
|
||
setRows([])
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
load()
|
||
}, [load])
|
||
|
||
async function handleDelete(id, title) {
|
||
if (!confirm(`Rahmenprogramm „${title || id}“ wirklich löschen?`)) return
|
||
try {
|
||
await api.deleteTrainingFrameworkProgram(id)
|
||
await load()
|
||
} catch (e) {
|
||
alert(e.message || 'Löschen fehlgeschlagen')
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div style={{ padding: '2rem' }}>
|
||
<div style={{ maxWidth: '900px', margin: '0 auto' }}>
|
||
<div
|
||
style={{
|
||
display: 'flex',
|
||
flexWrap: 'wrap',
|
||
alignItems: 'flex-start',
|
||
justifyContent: 'space-between',
|
||
gap: '1rem',
|
||
marginBottom: '1.25rem',
|
||
}}
|
||
>
|
||
<div>
|
||
<h1 style={{ marginBottom: '0.35rem' }}>Trainingsrahmenprogramme</h1>
|
||
<p style={{ color: 'var(--text2)', fontSize: '0.95rem', maxWidth: '36rem' }}>
|
||
Mehrere Entwicklungsziele und Übungen über Session‑Slots verteilen — als Vorlage in der Bibliothek oder
|
||
im Kontext einer Gruppe.{' '}
|
||
<span style={{ color: 'var(--text3)', fontSize: '0.88rem' }}>
|
||
In der <strong>Bearbeitungsansicht</strong> gibt es auf schmalen Fenstern Registerkarten, auf breiten
|
||
Bildschirmen zwei Spalten (Ziele | Slots).
|
||
</span>
|
||
</p>
|
||
</div>
|
||
<Link
|
||
to="/planning/framework-programs/new"
|
||
className="btn btn-primary"
|
||
style={{ textDecoration: 'none', whiteSpace: 'nowrap' }}
|
||
>
|
||
Rahmenprogramm anlegen
|
||
</Link>
|
||
</div>
|
||
|
||
<p style={{ marginBottom: '1rem' }}>
|
||
<Link to="/planning" style={{ color: 'var(--accent-dark)' }}>
|
||
← Zurück zur Trainingsplanung
|
||
</Link>
|
||
</p>
|
||
|
||
{error && (
|
||
<div className="card" style={{ borderLeft: '4px solid var(--danger)', marginBottom: '1rem' }}>
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
{loading ? (
|
||
<div style={{ textAlign: 'center', padding: '2rem' }}>
|
||
<div className="spinner" />
|
||
<p>Laden…</p>
|
||
</div>
|
||
) : rows.length === 0 ? (
|
||
<div className="card">
|
||
<p style={{ color: 'var(--text2)', marginBottom: '1rem' }}>
|
||
Noch kein Rahmenprogramm gespeichert. Lege ein neues an — mit Titel, mindestens einem Ziel und optional
|
||
Slots samt Übungen.
|
||
</p>
|
||
<Link
|
||
to="/planning/framework-programs/new"
|
||
className="btn btn-primary btn-full"
|
||
style={{ textDecoration: 'none' }}
|
||
>
|
||
Rahmenprogramm anlegen
|
||
</Link>
|
||
</div>
|
||
) : (
|
||
<ul style={{ listStyle: 'none' }}>
|
||
{rows.map((r) => (
|
||
<li key={r.id} className="card" style={{ marginBottom: '12px' }}>
|
||
<div
|
||
style={{
|
||
display: 'flex',
|
||
flexWrap: 'wrap',
|
||
alignItems: 'flex-start',
|
||
justifyContent: 'space-between',
|
||
gap: '0.75rem',
|
||
}}
|
||
>
|
||
<div>
|
||
<Link
|
||
to={`/planning/framework-programs/${r.id}`}
|
||
style={{ fontWeight: 600, fontSize: '1.05rem', color: 'var(--text1)' }}
|
||
>
|
||
{r.title || `Rahmen #${r.id}`}
|
||
</Link>
|
||
<div style={{ fontSize: '0.85rem', color: 'var(--text2)', marginTop: '0.35rem' }}>
|
||
<span>{MODE_LABELS[r.plan_mode] || r.plan_mode}</span>
|
||
{typeof r.goals_count === 'number' || typeof r.slots_count === 'number' ? (
|
||
<span>
|
||
{' '}
|
||
· {r.goals_count ?? '—'} Ziele · {r.slots_count ?? '—'} Slots
|
||
</span>
|
||
) : null}
|
||
</div>
|
||
{r.description ? (
|
||
<p style={{ marginTop: '0.5rem', fontSize: '0.9rem', color: 'var(--text2)' }}>{r.description}</p>
|
||
) : null}
|
||
</div>
|
||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
|
||
<Link
|
||
to={`/planning/framework-programs/${r.id}`}
|
||
className="btn btn-secondary"
|
||
style={{ textDecoration: 'none' }}
|
||
>
|
||
Bearbeiten
|
||
</Link>
|
||
<button type="button" className="btn btn-secondary" onClick={() => handleDelete(r.id, r.title)}>
|
||
Löschen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|