shinkan-jinkendo/frontend/src/pages/TrainingFrameworkProgramsListPage.jsx
Lars 88fc9d9ba5
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 41s
feat: update layout and styles for TrainingFrameworkProgramEditPage
- 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.
2026-05-05 12:08:03 +02:00

161 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
)
}