Some checks failed
Deploy Development / deploy (push) Failing after 23s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Failing after 6s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Has been cancelled
- Added a new PlanningLayout component to manage the training planning interface, allowing for better organization of related pages. - Introduced a FormActionBar component across various modals and forms to standardize action buttons for saving and canceling. - Updated the TrainingPlanningPageRoot and TrainingPlanningUnitFormModal to utilize the new FormActionBar for improved user experience. - Enhanced the TrainingModuleEditPage and TrainingFrameworkProgramEditPage with save and close functionality, streamlining the editing process. - Refactored existing modals to incorporate the new layout and action bar, ensuring consistency across the application.
133 lines
4.6 KiB
JavaScript
133 lines
4.6 KiB
JavaScript
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
import { Link } from 'react-router-dom'
|
|
import api from '../utils/api'
|
|
import { useAuth } from '../context/AuthContext'
|
|
import { getTenantClubDependencyKey } from '../utils/activeClub'
|
|
|
|
export default function TrainingModulesListPage() {
|
|
const { user } = useAuth()
|
|
const tenantClubDepKey = useMemo(() => getTenantClubDependencyKey(user), [user])
|
|
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.listTrainingModules()
|
|
setRows(Array.isArray(list) ? list : [])
|
|
} catch (e) {
|
|
setError(e.message || 'Laden fehlgeschlagen')
|
|
setRows([])
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
load()
|
|
}, [load, tenantClubDepKey])
|
|
|
|
async function handleDelete(id, title) {
|
|
if (!confirm(`Trainingsmodul „${title || id}“ wirklich löschen?`)) return
|
|
try {
|
|
await api.deleteTrainingModule(id)
|
|
await load()
|
|
} catch (e) {
|
|
alert(e.message || 'Löschen fehlgeschlagen')
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
flexWrap: 'wrap',
|
|
alignItems: 'flex-start',
|
|
justifyContent: 'space-between',
|
|
gap: '1rem',
|
|
marginBottom: '1.25rem',
|
|
}}
|
|
>
|
|
<div>
|
|
<h1 className="page-title" style={{ marginBottom: '0.35rem' }}>
|
|
Trainingsmodule
|
|
</h1>
|
|
<p style={{ color: 'var(--text2)', fontSize: '0.95rem', maxWidth: '38rem', margin: 0 }}>
|
|
Wiederverwendbare Übungsfolgen für die Trainingsplanung. Übernahme in eine Einheit erfolgt dort als
|
|
lokale Kopie (mit Herkunftsmarkierung).
|
|
</p>
|
|
</div>
|
|
<Link to="/planning/training-modules/new" className="btn btn-primary" style={{ textDecoration: 'none' }}>
|
|
Neues Modul
|
|
</Link>
|
|
</div>
|
|
|
|
{error ? (
|
|
<p style={{ color: 'var(--danger)', marginBottom: '1rem' }}>{error}</p>
|
|
) : null}
|
|
{loading ? (
|
|
<p style={{ color: 'var(--text2)' }}>Laden …</p>
|
|
) : rows.length === 0 ? (
|
|
<div className="card" style={{ padding: '1.25rem' }}>
|
|
<p style={{ margin: 0, color: 'var(--text2)' }}>Noch keine Module angelegt.</p>
|
|
</div>
|
|
) : (
|
|
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
|
{rows.map((r) => (
|
|
<li key={r.id} className="card" style={{ padding: '1rem 1.15rem' }}>
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
flexWrap: 'wrap',
|
|
justifyContent: 'space-between',
|
|
gap: '10px',
|
|
alignItems: 'flex-start',
|
|
}}
|
|
>
|
|
<div style={{ flex: '1 1 220px', minWidth: 0 }}>
|
|
<Link
|
|
to={`/planning/training-modules/${r.id}`}
|
|
style={{
|
|
fontWeight: 700,
|
|
fontSize: '1.05rem',
|
|
color: 'var(--accent-dark)',
|
|
textDecoration: 'none',
|
|
wordBreak: 'break-word',
|
|
}}
|
|
>
|
|
{(r.title || '').trim() || `Modul #${r.id}`}
|
|
</Link>
|
|
<p style={{ margin: '0.35rem 0 0', fontSize: '0.88rem', color: 'var(--text2)', lineHeight: 1.45 }}>
|
|
{(r.summary || '').trim() || '—'}{' '}
|
|
<span style={{ color: 'var(--text3)' }}>
|
|
({Number(r.items_count) || 0} Position{r.items_count === 1 ? '' : 'en'})
|
|
</span>
|
|
</p>
|
|
<p style={{ margin: '0.35rem 0 0', fontSize: '0.8rem', color: 'var(--text3)' }}>
|
|
Sichtbarkeit: <strong>{r.visibility || '—'}</strong>
|
|
</p>
|
|
</div>
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
|
<Link
|
|
className="btn btn-secondary"
|
|
style={{ textDecoration: 'none' }}
|
|
to={`/planning/training-modules/${r.id}`}
|
|
>
|
|
Bearbeiten
|
|
</Link>
|
|
<button type="button" className="btn btn-secondary" onClick={() => handleDelete(r.id, r.title)}>
|
|
Löschen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</>
|
|
)
|
|
}
|