- Incremented application version to 0.8.0 and updated database schema version to 20260428031. - Introduced support for training plan templates, allowing users to create and manage reusable training structures. - Enhanced the Training Planning UI to include sections and exercises, improving the organization of training units. - Updated API endpoints for training plan templates, enabling CRUD operations for better integration with the frontend. - Improved validation and permission checks for creating training units, ensuring proper access control.
984 lines
27 KiB
JavaScript
984 lines
27 KiB
JavaScript
/**
|
|
* Shinkan Jinkendo API Client
|
|
*
|
|
* Zentrale API-Kommunikation mit automatischer Token-Injektion
|
|
*/
|
|
|
|
import { stripHtmlToText } from './htmlUtils'
|
|
|
|
const API_URL = import.meta.env.VITE_API_URL || ''
|
|
|
|
/**
|
|
* Generic API request with automatic token injection
|
|
*/
|
|
async function request(endpoint, options = {}) {
|
|
const token = localStorage.getItem('authToken')
|
|
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
...options.headers
|
|
}
|
|
|
|
if (token) {
|
|
headers['X-Auth-Token'] = token
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}${endpoint}`, {
|
|
...options,
|
|
headers
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ detail: 'Unknown error' }))
|
|
throw new Error(error.detail || `HTTP ${response.status}`)
|
|
}
|
|
|
|
return response.json()
|
|
}
|
|
|
|
// ============================================================================
|
|
// Auth
|
|
// ============================================================================
|
|
|
|
export async function login(email, password) {
|
|
return request('/api/auth/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ email, password })
|
|
})
|
|
}
|
|
|
|
export async function register(email, password, name) {
|
|
return request('/api/auth/register', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ email, password, name })
|
|
})
|
|
}
|
|
|
|
export async function logout() {
|
|
return request('/api/auth/logout', { method: 'POST' })
|
|
}
|
|
|
|
export async function getCurrentProfile() {
|
|
return request('/api/profiles/me')
|
|
}
|
|
|
|
// ============================================================================
|
|
// Clubs & Groups
|
|
// ============================================================================
|
|
|
|
export async function listClubs() {
|
|
return request('/api/clubs')
|
|
}
|
|
|
|
export async function getClub(id) {
|
|
return request(`/api/clubs/${id}`)
|
|
}
|
|
|
|
export async function createClub(data) {
|
|
return request('/api/clubs', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateClub(id, data) {
|
|
return request(`/api/clubs/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteClub(id) {
|
|
return request(`/api/clubs/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function listDivisions(clubId) {
|
|
const query = clubId ? `?club_id=${clubId}` : ''
|
|
return request(`/api/divisions${query}`)
|
|
}
|
|
|
|
export async function createDivision(data) {
|
|
return request('/api/divisions', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateDivision(id, data) {
|
|
return request(`/api/divisions/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteDivision(id) {
|
|
return request(`/api/divisions/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function listTrainingGroups(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/groups${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function getTrainingGroup(id) {
|
|
return request(`/api/groups/${id}`)
|
|
}
|
|
|
|
export async function createTrainingGroup(data) {
|
|
return request('/api/groups', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateTrainingGroup(id, data) {
|
|
return request(`/api/groups/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteTrainingGroup(id) {
|
|
return request(`/api/groups/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// ============================================================================
|
|
// Skills & Methods
|
|
// ============================================================================
|
|
|
|
export async function listSkills(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/skills${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
/** Admin: Fähigkeiten hierarchisch sortiert (Hauptkategorie → Kategorie → Sortierung) */
|
|
export async function listSkillsCatalog(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/skills/catalog${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function getSkill(id) {
|
|
return request(`/api/skills/${id}`)
|
|
}
|
|
|
|
export async function createSkill(data) {
|
|
return request('/api/skills', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateSkill(id, data) {
|
|
return request(`/api/skills/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteSkill(id) {
|
|
return request(`/api/skills/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function listMethods(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/methods${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function getMethod(id) {
|
|
return request(`/api/methods/${id}`)
|
|
}
|
|
|
|
export async function createMethod(data) {
|
|
return request('/api/methods', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateMethod(id, data) {
|
|
return request(`/api/methods/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteMethod(id) {
|
|
return request(`/api/methods/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// ============================================================================
|
|
// Exercises
|
|
// ============================================================================
|
|
|
|
export async function listExercises(filters = {}) {
|
|
const q = new URLSearchParams()
|
|
Object.entries(filters).forEach(([k, v]) => {
|
|
if (v === undefined || v === null) return
|
|
if (typeof v === 'boolean') {
|
|
if (v) q.set(k, 'true')
|
|
return
|
|
}
|
|
if (Array.isArray(v)) {
|
|
if (v.length === 0) return
|
|
v.forEach((item) => {
|
|
if (item !== '' && item !== undefined && item !== null && String(item).trim() !== '') {
|
|
q.append(k, String(item))
|
|
}
|
|
})
|
|
return
|
|
}
|
|
if (String(v).trim() !== '') q.set(k, String(v))
|
|
})
|
|
const query = q.toString()
|
|
return request(`/api/exercises${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
/** Formular → API-Body (M:N gemäß EXERCISES_API_SPEC + training_types) */
|
|
export function buildExerciseApiPayload(formData) {
|
|
const num = (v) => (v === '' || v == null ? null : Number(v))
|
|
|
|
const goalHtml = formData.goal || ''
|
|
const execHtml = formData.execution || ''
|
|
const goalText = stripHtmlToText(goalHtml)
|
|
const execText = stripHtmlToText(execHtml)
|
|
|
|
if (!goalText && !execText) {
|
|
throw new Error('Ziel oder Durchführung ausfüllen (mindestens eines, auch mit Formatierung).')
|
|
}
|
|
|
|
const mapFocus = (formData.focus_areas_multi || [])
|
|
.filter((x) => x && x.focus_area_id)
|
|
.map((x) => ({ focus_area_id: Number(x.focus_area_id), is_primary: !!x.is_primary }))
|
|
const mapStyles = (formData.training_styles_multi || [])
|
|
.filter((x) => x && x.training_style_id)
|
|
.map((x) => ({ training_style_id: Number(x.training_style_id), is_primary: !!x.is_primary }))
|
|
const mapTTypes = (formData.training_types_multi || [])
|
|
.filter((x) => x && x.training_type_id)
|
|
.map((x) => ({ training_type_id: Number(x.training_type_id), is_primary: !!x.is_primary }))
|
|
const mapTg = (formData.target_groups_multi || [])
|
|
.filter((x) => x && x.target_group_id)
|
|
.map((x) => ({ target_group_id: Number(x.target_group_id), is_primary: !!x.is_primary }))
|
|
|
|
return {
|
|
title: (formData.title || '').trim(),
|
|
summary: formData.summary || null,
|
|
goal: goalHtml.trim() ? goalHtml : null,
|
|
execution: execHtml.trim() ? execHtml : null,
|
|
preparation: formData.preparation || null,
|
|
trainer_notes: formData.trainer_notes || null,
|
|
duration_min: num(formData.duration_min),
|
|
duration_max: num(formData.duration_max),
|
|
group_size_min: num(formData.group_size_min),
|
|
group_size_max: num(formData.group_size_max),
|
|
equipment: Array.isArray(formData.equipment) ? formData.equipment : [],
|
|
focus_areas_multi: mapFocus,
|
|
training_styles_multi: mapStyles,
|
|
training_types_multi: mapTTypes,
|
|
target_groups_multi: mapTg,
|
|
age_groups: [],
|
|
skills: (formData.skills || []).map((s) => ({
|
|
skill_id: s.skill_id,
|
|
is_primary: !!s.is_primary,
|
|
intensity: s.intensity || null,
|
|
required_level: s.required_level || null,
|
|
target_level: s.target_level || null,
|
|
})),
|
|
visibility: formData.visibility || 'private',
|
|
status: formData.status || 'draft',
|
|
club_id: formData.club_id ?? null,
|
|
}
|
|
}
|
|
|
|
export async function uploadExerciseMedia(exerciseId, formData) {
|
|
const token = localStorage.getItem('authToken')
|
|
const headers = {}
|
|
if (token) headers['X-Auth-Token'] = token
|
|
const response = await fetch(`${API_URL}/api/exercises/${exerciseId}/media`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: formData,
|
|
})
|
|
if (!response.ok) {
|
|
const err = await response.json().catch(() => ({ detail: 'Unknown error' }))
|
|
const d = err.detail
|
|
const msg =
|
|
typeof d === 'string'
|
|
? d
|
|
: d != null
|
|
? JSON.stringify(d)
|
|
: `HTTP ${response.status}`
|
|
throw new Error(msg)
|
|
}
|
|
return response.json()
|
|
}
|
|
|
|
export async function updateExerciseMedia(exerciseId, mediaId, data) {
|
|
return request(`/api/exercises/${exerciseId}/media/${mediaId}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data),
|
|
})
|
|
}
|
|
|
|
export async function deleteExerciseMedia(exerciseId, mediaId) {
|
|
return request(`/api/exercises/${exerciseId}/media/${mediaId}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function reorderExerciseMedia(exerciseId, mediaIds) {
|
|
return request(`/api/exercises/${exerciseId}/media/reorder`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify({ media_ids: mediaIds }),
|
|
})
|
|
}
|
|
|
|
export async function getExercise(id) {
|
|
return request(`/api/exercises/${id}`)
|
|
}
|
|
|
|
export async function createExercise(data) {
|
|
return request('/api/exercises', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateExercise(id, data) {
|
|
return request(`/api/exercises/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteExercise(id) {
|
|
return request(`/api/exercises/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function createExerciseVariant(exerciseId, data) {
|
|
return request(`/api/exercises/${exerciseId}/variants`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
})
|
|
}
|
|
|
|
export async function updateExerciseVariant(exerciseId, variantId, data) {
|
|
return request(`/api/exercises/${exerciseId}/variants/${variantId}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data),
|
|
})
|
|
}
|
|
|
|
export async function deleteExerciseVariant(exerciseId, variantId) {
|
|
return request(`/api/exercises/${exerciseId}/variants/${variantId}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function reorderExerciseVariants(exerciseId, variantIds) {
|
|
return request(`/api/exercises/${exerciseId}/variants/reorder`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify({ variant_ids: variantIds }),
|
|
})
|
|
}
|
|
|
|
/** KI-Ausbaustufe (EXERCISES_API_SPEC): benötigt Backend + z. B. OPENROUTER_API_KEY. */
|
|
export async function suggestExerciseAi(payload) {
|
|
return request('/api/exercises/ai/suggest', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
export async function regenerateExerciseAi(exerciseId, payload) {
|
|
return request(`/api/exercises/${exerciseId}/ai/regenerate`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Catalogs (Admin-verwaltbare Stammdaten)
|
|
// ============================================================================
|
|
|
|
// Focus Areas
|
|
export async function listFocusAreas(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/focus-areas${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function createFocusArea(data) {
|
|
return request('/api/focus-areas', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateFocusArea(id, data) {
|
|
return request(`/api/focus-areas/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteFocusArea(id) {
|
|
return request(`/api/focus-areas/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// Admin Hierarchy (Tree View)
|
|
export async function getAdminHierarchy() {
|
|
return request('/api/admin/hierarchy')
|
|
}
|
|
|
|
// ============================================================================
|
|
// Reifegradmodelle / Fähigkeitsmatrix
|
|
// ============================================================================
|
|
|
|
export async function listMaturityModels(filters = {}) {
|
|
const query = new URLSearchParams(
|
|
Object.entries(filters).filter(([, v]) => v !== undefined && v !== null && v !== '')
|
|
).toString()
|
|
return request(`/api/maturity-models${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function resolveMaturityModel(filters = {}) {
|
|
const query = new URLSearchParams(
|
|
Object.entries(filters).filter(([, v]) => v !== undefined && v !== null && v !== '')
|
|
).toString()
|
|
return request(`/api/maturity-models/resolve${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function getMaturityModel(id) {
|
|
return request(`/api/maturity-models/${id}`)
|
|
}
|
|
|
|
export async function createMaturityModel(data) {
|
|
return request('/api/maturity-models', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateMaturityModel(id, data) {
|
|
return request(`/api/maturity-models/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteMaturityModel(id) {
|
|
return request(`/api/maturity-models/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function addMaturityModelSkill(modelId, data) {
|
|
return request(`/api/maturity-models/${modelId}/skills`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function removeMaturityModelSkill(modelId, skillId) {
|
|
return request(`/api/maturity-models/${modelId}/skills/${skillId}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function upsertMaturityModelSkillLevels(modelId, data) {
|
|
return request(`/api/maturity-models/${modelId}/skill-levels`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
/** Hierarchische Zuordnung Modell → Fokus / Stilrichtung / Trainingsstil (training_types) */
|
|
export async function listMaturityModelContextBindings() {
|
|
return request('/api/maturity-model-context-bindings')
|
|
}
|
|
|
|
export async function upsertMaturityModelContextBinding(data) {
|
|
return request('/api/maturity-model-context-bindings', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteMaturityModelContextBinding(id) {
|
|
return request(`/api/maturity-model-context-bindings/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function importMaturityModelBundle(payload) {
|
|
return request('/api/maturity-models/import', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload)
|
|
})
|
|
}
|
|
|
|
export async function exportMaturityModelBundle(modelId) {
|
|
return request(`/api/maturity-models/${modelId}/export`)
|
|
}
|
|
|
|
export async function exportResolvedMaturityBundle(filters = {}) {
|
|
const query = new URLSearchParams(
|
|
Object.entries(filters).filter(([, v]) => v !== undefined && v !== null && v !== '')
|
|
).toString()
|
|
return request(`/api/maturity-models/export-resolved${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
/** Komplett: Fähigkeitskatalog + Reifegradmodelle + Kontext-Bindings (slug-/namensbasiert auf Ziel-DB) */
|
|
export async function exportMatrixStackBundle() {
|
|
return request('/api/admin/matrix-stack/export')
|
|
}
|
|
|
|
export async function importMatrixStackBundle(payload) {
|
|
return request('/api/admin/matrix-stack/import', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload)
|
|
})
|
|
}
|
|
|
|
// Style Directions (formerly Training Styles)
|
|
export async function listStyleDirections(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/training-styles${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
// Backward-compatibility alias
|
|
export const listTrainingStyles = listStyleDirections
|
|
|
|
export async function createStyleDirection(data) {
|
|
return request('/api/training-styles', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateStyleDirection(id, data) {
|
|
return request(`/api/training-styles/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteStyleDirection(id) {
|
|
return request(`/api/training-styles/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// Training Characters
|
|
export async function listTrainingCharacters(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/training-characters${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function createTrainingCharacter(data) {
|
|
return request('/api/training-characters', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateTrainingCharacter(id, data) {
|
|
return request(`/api/training-characters/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteTrainingCharacter(id) {
|
|
return request(`/api/training-characters/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// Training Types (Breitensport, Leistungssport, etc.)
|
|
export async function listTrainingTypes(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/training-types${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function createTrainingType(data) {
|
|
return request('/api/training-types', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateTrainingType(id, data) {
|
|
return request(`/api/training-types/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteTrainingType(id) {
|
|
return request(`/api/training-types/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// Skill main categories (Hauptgruppen im Fähigkeitskatalog)
|
|
export async function listSkillMainCategories() {
|
|
return request('/api/skill-main-categories')
|
|
}
|
|
|
|
export async function createSkillMainCategory(data) {
|
|
return request('/api/skill-main-categories', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateSkillMainCategory(id, data) {
|
|
return request(`/api/skill-main-categories/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteSkillMainCategory(id) {
|
|
return request(`/api/skill-main-categories/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// Skill Categories
|
|
export async function listSkillCategories(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/skill-categories${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function createSkillCategory(data) {
|
|
return request('/api/skill-categories', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateSkillCategory(id, data) {
|
|
return request(`/api/skill-categories/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteSkillCategory(id) {
|
|
return request(`/api/skill-categories/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// Trainer Focus Areas
|
|
export async function listTrainerFocusAreas(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/trainer-focus-areas${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function assignTrainerFocusArea(data) {
|
|
return request('/api/trainer-focus-areas', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteTrainerFocusArea(id) {
|
|
return request(`/api/trainer-focus-areas/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// Target Groups (Zielgruppen)
|
|
export async function listTargetGroups(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/target-groups${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function createTargetGroup(data) {
|
|
return request('/api/target-groups', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateTargetGroup(id, data) {
|
|
return request(`/api/target-groups/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteTargetGroup(id) {
|
|
return request(`/api/target-groups/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// Style Direction → Target Groups (M:N Assignments)
|
|
export async function listStyleDirectionTargetGroups(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/training-style-target-groups${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
export async function createStyleDirectionTargetGroup(data) {
|
|
return request('/api/training-style-target-groups', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateStyleDirectionTargetGroup(id, data) {
|
|
return request(`/api/training-style-target-groups/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteStyleDirectionTargetGroup(id) {
|
|
return request(`/api/training-style-target-groups/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function getStyleDirectionsHierarchy(filters = {}) {
|
|
const query = new URLSearchParams(filters).toString()
|
|
return request(`/api/training-styles/hierarchy${query ? '?' + query : ''}`)
|
|
}
|
|
|
|
// Trainer Contexts (Fokussierte Ansichten)
|
|
export async function listTrainerContexts() {
|
|
return request('/api/trainer-contexts')
|
|
}
|
|
|
|
export async function createTrainerContext(data) {
|
|
return request('/api/trainer-contexts', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateTrainerContext(id, data) {
|
|
return request(`/api/trainer-contexts/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteTrainerContext(id) {
|
|
return request(`/api/trainer-contexts/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// ============================================================================
|
|
// Training Planning
|
|
// ============================================================================
|
|
|
|
/** Query-Parameter wie GET /api/training-units (group_id, start_date, end_date, status). */
|
|
export async function listTrainingUnits(filters = {}) {
|
|
const q = new URLSearchParams()
|
|
if (filters.group_id != null && filters.group_id !== '') {
|
|
q.set('group_id', String(filters.group_id))
|
|
}
|
|
if (filters.start_date) q.set('start_date', filters.start_date)
|
|
if (filters.end_date) q.set('end_date', filters.end_date)
|
|
if (filters.status) q.set('status', filters.status)
|
|
const qs = q.toString()
|
|
return request(`/api/training-units${qs ? `?${qs}` : ''}`)
|
|
}
|
|
|
|
export async function getTrainingUnit(id) {
|
|
return request(`/api/training-units/${id}`)
|
|
}
|
|
|
|
export async function createTrainingUnit(data) {
|
|
return request('/api/training-units', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateTrainingUnit(id, data) {
|
|
return request(`/api/training-units/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteTrainingUnit(id) {
|
|
return request(`/api/training-units/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export async function quickCreateTrainingUnit(data) {
|
|
return request('/api/training-units/quick-create', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function listTrainingPlanTemplates() {
|
|
return request('/api/training-plan-templates')
|
|
}
|
|
|
|
export async function getTrainingPlanTemplate(id) {
|
|
return request(`/api/training-plan-templates/${id}`)
|
|
}
|
|
|
|
export async function createTrainingPlanTemplate(data) {
|
|
return request('/api/training-plan-templates', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function updateTrainingPlanTemplate(id, data) {
|
|
return request(`/api/training-plan-templates/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
|
|
export async function deleteTrainingPlanTemplate(id) {
|
|
return request(`/api/training-plan-templates/${id}`, { method: 'DELETE' })
|
|
}
|
|
|
|
// ============================================================================
|
|
// Version & Health
|
|
// ============================================================================
|
|
|
|
export async function getVersion() {
|
|
return request('/api/version')
|
|
}
|
|
|
|
export async function healthCheck() {
|
|
return request('/health')
|
|
}
|
|
|
|
export const api = {
|
|
// Auth
|
|
login,
|
|
register,
|
|
logout,
|
|
getCurrentProfile,
|
|
|
|
// Clubs & Groups
|
|
listClubs,
|
|
getClub,
|
|
createClub,
|
|
updateClub,
|
|
deleteClub,
|
|
listDivisions,
|
|
createDivision,
|
|
updateDivision,
|
|
deleteDivision,
|
|
listTrainingGroups,
|
|
getTrainingGroup,
|
|
createTrainingGroup,
|
|
updateTrainingGroup,
|
|
deleteTrainingGroup,
|
|
|
|
// Skills & Methods
|
|
listSkills,
|
|
listSkillsCatalog,
|
|
getSkill,
|
|
createSkill,
|
|
updateSkill,
|
|
deleteSkill,
|
|
listMethods,
|
|
getMethod,
|
|
createMethod,
|
|
updateMethod,
|
|
deleteMethod,
|
|
|
|
// Exercises
|
|
listExercises,
|
|
getExercise,
|
|
createExercise,
|
|
updateExercise,
|
|
deleteExercise,
|
|
createExerciseVariant,
|
|
updateExerciseVariant,
|
|
deleteExerciseVariant,
|
|
reorderExerciseVariants,
|
|
buildExerciseApiPayload,
|
|
suggestExerciseAi,
|
|
regenerateExerciseAi,
|
|
uploadExerciseMedia,
|
|
updateExerciseMedia,
|
|
deleteExerciseMedia,
|
|
reorderExerciseMedia,
|
|
|
|
// Training Planning
|
|
listTrainingUnits,
|
|
getTrainingUnit,
|
|
createTrainingUnit,
|
|
updateTrainingUnit,
|
|
deleteTrainingUnit,
|
|
quickCreateTrainingUnit,
|
|
listTrainingPlanTemplates,
|
|
getTrainingPlanTemplate,
|
|
createTrainingPlanTemplate,
|
|
updateTrainingPlanTemplate,
|
|
deleteTrainingPlanTemplate,
|
|
|
|
// Catalogs
|
|
listFocusAreas,
|
|
createFocusArea,
|
|
updateFocusArea,
|
|
deleteFocusArea,
|
|
getAdminHierarchy,
|
|
listStyleDirections,
|
|
listTrainingStyles,
|
|
createStyleDirection,
|
|
updateStyleDirection,
|
|
deleteStyleDirection,
|
|
listTrainingCharacters,
|
|
createTrainingCharacter,
|
|
updateTrainingCharacter,
|
|
deleteTrainingCharacter,
|
|
listTrainingTypes,
|
|
createTrainingType,
|
|
updateTrainingType,
|
|
deleteTrainingType,
|
|
listSkillMainCategories,
|
|
createSkillMainCategory,
|
|
updateSkillMainCategory,
|
|
deleteSkillMainCategory,
|
|
listSkillCategories,
|
|
createSkillCategory,
|
|
updateSkillCategory,
|
|
deleteSkillCategory,
|
|
listTrainerFocusAreas,
|
|
assignTrainerFocusArea,
|
|
deleteTrainerFocusArea,
|
|
listTargetGroups,
|
|
createTargetGroup,
|
|
updateTargetGroup,
|
|
deleteTargetGroup,
|
|
listStyleDirectionTargetGroups,
|
|
createStyleDirectionTargetGroup,
|
|
updateStyleDirectionTargetGroup,
|
|
deleteStyleDirectionTargetGroup,
|
|
listMaturityModels,
|
|
listMaturityModelContextBindings,
|
|
upsertMaturityModelContextBinding,
|
|
deleteMaturityModelContextBinding,
|
|
importMaturityModelBundle,
|
|
exportMaturityModelBundle,
|
|
exportResolvedMaturityBundle,
|
|
exportMatrixStackBundle,
|
|
importMatrixStackBundle,
|
|
resolveMaturityModel,
|
|
getMaturityModel,
|
|
createMaturityModel,
|
|
updateMaturityModel,
|
|
deleteMaturityModel,
|
|
addMaturityModelSkill,
|
|
removeMaturityModelSkill,
|
|
upsertMaturityModelSkillLevels,
|
|
getStyleDirectionsHierarchy,
|
|
listTrainerContexts,
|
|
createTrainerContext,
|
|
updateTrainerContext,
|
|
deleteTrainerContext,
|
|
|
|
// System
|
|
getVersion,
|
|
healthCheck,
|
|
|
|
// MediaWiki Import
|
|
previewMediaWikiImport: (category, importType = 'exercise', limit = 10) =>
|
|
request(`/api/import/mediawiki/preview?category=${encodeURIComponent(category)}&import_type=${importType}&limit=${limit}`),
|
|
|
|
executeMediaWikiImport: (data) =>
|
|
request('/api/import/mediawiki/execute', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
}),
|
|
|
|
getMediaWikiImportStatus: (logId) =>
|
|
request(`/api/import/mediawiki/status/${logId}`),
|
|
|
|
listMediaWikiImportLogs: () =>
|
|
request('/api/import/mediawiki/logs'),
|
|
|
|
deleteMediaWikiImportReference: (refId) =>
|
|
request(`/api/import/mediawiki/references/${refId}`, { method: 'DELETE' })
|
|
}
|
|
|
|
export default api
|