import React, { useCallback, useEffect, useMemo, useState } from 'react'
import api from '../utils/api'
import NavStateLink from '../components/NavStateLink'
import FrameworkProgramsFilterBlock from '../components/planning/FrameworkProgramsFilterBlock'
import { useAuth } from '../context/AuthContext'
import { getTenantClubDependencyKey } from '../utils/activeClub'
import { buildFrameworkProgramsListReturnContext } from '../utils/navReturnContext'
import {
EMPTY_FRAMEWORK_IMPORT_FILTERS,
filterFrameworkPrograms,
frameworkSessionDurationLabel,
hasActiveFrameworkImportFilters,
} from '../utils/frameworkProgramListHelpers'
function dashIfEmpty(val) {
const s = (val ?? '').toString().trim()
return s.length ? s : '—'
}
function FrameworkSummaryMeta({ r }) {
const trainingTypes =
typeof r.training_type_names_agg === 'string' ? r.training_type_names_agg.trim() : ''
const targetGroups =
typeof r.target_group_names_agg === 'string' ? r.target_group_names_agg.trim() : ''
const styleDir =
typeof r.style_direction_names_agg === 'string'
? r.style_direction_names_agg.trim()
: typeof r.style_direction_name === 'string'
? r.style_direction_name.trim()
: ''
const focus =
typeof r.focus_area_names_agg === 'string'
? r.focus_area_names_agg.trim()
: typeof r.focus_area_name === 'string'
? r.focus_area_name.trim()
: ''
const rowStyle = {
display: 'grid',
gridTemplateColumns: 'minmax(6.5rem, 32%) 1fr',
gap: '0.25rem 0.75rem',
alignItems: 'start',
marginTop: '0.35rem',
lineHeight: 1.45,
}
const durationLabel = frameworkSessionDurationLabel(r)
const goals =
typeof r.goal_titles_agg === 'string' ? r.goal_titles_agg.trim() : ''
return (
Session-Dauer
{durationLabel}
{goals ? (
Entwicklungsziele
{goals}
) : null}
Fokusbereich
{dashIfEmpty(focus)}
Stilrichtungen
{dashIfEmpty(styleDir)}
Trainingsarten
{trainingTypes.length ? trainingTypes : '—'}
Zielgruppen
{targetGroups.length ? targetGroups : '—'}
Kurzbeschreibung
{(r.description && String(r.description).trim()) || '—'}
)
}
export default function TrainingFrameworkProgramsListPage() {
const { user } = useAuth()
const tenantClubDepKey = useMemo(() => getTenantClubDependencyKey(user), [user])
const frameworkListReturn = useMemo(() => buildFrameworkProgramsListReturnContext(), [])
const [rows, setRows] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const [catalogFocusAreas, setCatalogFocusAreas] = useState([])
const [catalogTrainingTypes, setCatalogTrainingTypes] = useState([])
const [catalogTargetGroups, setCatalogTargetGroups] = useState([])
const [filters, setFilters] = useState(() => ({ ...EMPTY_FRAMEWORK_IMPORT_FILTERS }))
const [filterPanelOpen, setFilterPanelOpen] = useState(true)
const filteredRows = useMemo(
() => filterFrameworkPrograms(rows, filters),
[rows, filters]
)
const filterActive = hasActiveFrameworkImportFilters(filters)
const load = useCallback(async () => {
setLoading(true)
setError('')
try {
const [list, fa, tt, tg] = await Promise.all([
api.listTrainingFrameworkPrograms(),
api.listFocusAreas({ status: 'active' }),
api.listTrainingTypes({ status: 'active' }),
api.listTargetGroups({ status: 'active' }),
])
setRows(Array.isArray(list) ? list : [])
setCatalogFocusAreas(Array.isArray(fa) ? fa : [])
setCatalogTrainingTypes(Array.isArray(tt) ? tt : [])
setCatalogTargetGroups(Array.isArray(tg) ? tg : [])
} catch (e) {
setError(e.message || 'Laden fehlgeschlagen')
setRows([])
setCatalogFocusAreas([])
setCatalogTrainingTypes([])
setCatalogTargetGroups([])
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
load()
}, [load, tenantClubDepKey])
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 (
<>
Trainingsrahmenprogramme
Vorlagen für Ziele und Sessions — die Verknüpfung mit Gruppenterminen erfolgt in der
Trainingsplanung (Registerkarte oben).
Mehr zur Übernahme in die Planung
Unter Planung wählst du eine Gruppe und übernimmst Slots aus einem Rahmenprogramm in
echte Termine. So bleibt die Bibliothek wiederverwendbar, ohne dass Einzelgruppen fest verdrahtet sind.
Rahmenprogramm anlegen
{error && (
{error}
)}
{loading ? (
) : rows.length === 0 ? (
Noch kein Rahmenprogramm gespeichert. Lege ein neues an — mit Titel, mindestens einem Ziel und optional
Slots samt Übungen.
Rahmenprogramm anlegen
) : (
<>
{filteredRows.length === 0 ? (
{filterActive
? 'Kein Rahmenprogramm passt zu den gewählten Filtern. Passe die Kriterien an oder setze den Filter zurück.'
: 'Keine Einträge.'}
) : (
{filteredRows.map((r) => (
{r.title || `Rahmen #${r.id}`}
{frameworkSessionDurationLabel(r)}
{(r.goals_count ?? '—') + ' Ziele · '}
{(r.slots_count ?? '—') + ' Slots'}
Bearbeiten
handleDelete(r.id, r.title)}>
Löschen
))}
)}
>
)}
>
)
}