shinkan-jinkendo/frontend/src/pages/TrainingModulesListPage.jsx
Lars 4fee5a2b47
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
Implement planning layout and enhance training module functionality
- 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.
2026-05-19 10:02:39 +02:00

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