diff --git a/backend/routers/auth.py b/backend/routers/auth.py index 0d9e2f3..166d48d 100644 --- a/backend/routers/auth.py +++ b/backend/routers/auth.py @@ -245,7 +245,7 @@ async def register(req: RegisterRequest, request: Request): id, name, email, pin_hash, auth_type, role, tier, email_verified, verification_token, verification_expires, trial_ends_at, created_at - ) VALUES (%s, %s, %s, %s, 'email', 'user', 'free', FALSE, %s, %s, %s, CURRENT_TIMESTAMP) + ) VALUES (%s, %s, %s, %s, 'email', 'trainer', 'free', FALSE, %s, %s, %s, CURRENT_TIMESTAMP) """, (profile_id, name, email, pin_hash, verification_token, verification_expires, trial_ends)) # Send verification email diff --git a/backend/routers/clubs.py b/backend/routers/clubs.py index 7e20293..9a630a0 100644 --- a/backend/routers/clubs.py +++ b/backend/routers/clubs.py @@ -82,10 +82,10 @@ def get_club(club_id: int, session=Depends(require_auth)): # ── Create Club ─────────────────────────────────────────────────────── @router.post("/clubs") def create_club(data: dict, session=Depends(require_auth)): - """Create new club (admin only).""" + """Create new club (Admin oder Trainer — MVP ohne separates Vereins-Onboarding).""" role = session.get('role') - if role not in ['admin', 'superadmin']: - raise HTTPException(403, "Nur Admins dürfen Vereine erstellen") + if role not in ['admin', 'superadmin', 'trainer', 'user']: + raise HTTPException(403, "Keine Berechtigung, Vereine anzulegen") name = data.get('name') if not name: @@ -395,10 +395,11 @@ def get_training_group(group_id: int, session=Depends(require_auth)): # ── Create Training Group ───────────────────────────────────────────── @router.post("/groups") def create_training_group(data: dict, session=Depends(require_auth)): - """Create new training group (admin or trainer).""" + """Create new training group (admin, trainer oder normaler Nutzer mit Vereinsbezug).""" + profile_id = session["profile_id"] role = session.get('role') - if role not in ['admin', 'superadmin', 'trainer']: - raise HTTPException(403, "Nur Admins und Trainer dürfen Gruppen erstellen") + if role not in ['admin', 'superadmin', 'trainer', 'user']: + raise HTTPException(403, "Keine Berechtigung, Trainingsgruppen anzulegen") club_id = data.get('club_id') name = data.get('name') @@ -406,6 +407,10 @@ def create_training_group(data: dict, session=Depends(require_auth)): if not club_id or not name: raise HTTPException(400, "club_id und name sind Pflichtfelder") + trainer_id = data.get('trainer_id') + if trainer_id in (None, "", 0) and role in ("trainer", "user"): + trainer_id = profile_id + with get_db() as conn: cur = get_cursor(conn) @@ -436,7 +441,7 @@ def create_training_group(data: dict, session=Depends(require_auth)): data.get('time_start'), data.get('time_end'), data.get('location'), - data.get('trainer_id'), + trainer_id, data.get('co_trainer_ids'), data.get('status', 'active') )) diff --git a/backend/routers/training_planning.py b/backend/routers/training_planning.py index c54b3b6..48403a8 100644 --- a/backend/routers/training_planning.py +++ b/backend/routers/training_planning.py @@ -14,6 +14,11 @@ from auth import require_auth router = APIRouter(prefix="/api", tags=["training_planning"]) +def _has_planning_role(role: Optional[str]) -> bool: + """Kann Trainingseinheiten/Vorlagen anlegen (bis Governance: auch einfacher Account).""" + return role in ("admin", "superadmin", "trainer", "user") + + def _optional_positive_int(val, field_name: str) -> Optional[int]: if val is None or val == "": return None @@ -50,7 +55,7 @@ def _can_access_group_for_create(cur, group_id: int, profile_id: int, role: str) if not group: raise HTTPException(status_code=404, detail="Trainingsgruppe nicht gefunden") co_trainers = group["co_trainer_ids"] or [] - if role not in ["admin", "superadmin", "trainer"]: + if not _has_planning_role(role): raise HTTPException(status_code=403, detail="Nur Trainer dürfen Trainingseinheiten erstellen") if role not in ["admin", "superadmin"]: if group["trainer_id"] != profile_id and profile_id not in co_trainers: @@ -381,7 +386,7 @@ def get_training_plan_template(template_id: int, session=Depends(require_auth)): def create_training_plan_template(data: dict, session=Depends(require_auth)): profile_id = session["profile_id"] role = session.get("role") - if role not in ["admin", "superadmin", "trainer"]: + if not _has_planning_role(role): raise HTTPException(status_code=403, detail="Nur Trainer dürfen Vorlagen anlegen") name = (data.get("name") or "").strip() if not name: @@ -787,7 +792,7 @@ def quick_create_training_unit(data: dict, session=Depends(require_auth)): role = session.get("role") co_trainers = group["co_trainer_ids"] or [] - if role not in ["admin", "superadmin", "trainer"]: + if not _has_planning_role(role): raise HTTPException(status_code=403, detail="Nur Trainer dürfen Trainingseinheiten erstellen") if role not in ["admin", "superadmin"]: diff --git a/frontend/src/pages/ClubsPage.jsx b/frontend/src/pages/ClubsPage.jsx index c51bd9e..470fae2 100644 --- a/frontend/src/pages/ClubsPage.jsx +++ b/frontend/src/pages/ClubsPage.jsx @@ -18,6 +18,7 @@ function ClubsPage() { const isAdmin = user?.role === 'admin' || user?.role === 'superadmin' const isTrainer = user?.role === 'trainer' || isAdmin + const canCreateClub = ['admin', 'superadmin', 'trainer', 'user'].includes(user?.role) useEffect(() => { loadData() @@ -61,7 +62,7 @@ function ClubsPage() { time_start: '', time_end: '', location: '', - trainer_id: '', + trainer_id: user?.id ?? '', co_trainer_ids: [], status: 'active' }) @@ -144,7 +145,11 @@ function ClubsPage() { return (
-

Vereinsverwaltung

+

Vereinsverwaltung

+

+ Für die Trainingsplanung wird mindestens ein Verein und eine Trainingsgruppe gebraucht. + Sparten sind optional — typische Eckdaten einer Gruppe (Wochentag, Zeit, Ort) kannst du schrittweise eintragen. +

{/* Tabs */}

Vereine

- {isAdmin && ( + {canCreateClub && ( @@ -188,9 +193,17 @@ function ClubsPage() { {clubs.length === 0 ? (
-

- Keine Vereine gefunden +

+ Noch kein Verein angelegt. + {canCreateClub + ? ' Nutze „+ Neuer Verein“ — ein Name reicht zum Start.' + : ' Bitte einen Administrator oder Support um Anlage.'}

+ {canCreateClub && ( +

+ Danach im Tab Trainingsgruppen eine Gruppe diesem Verein zuordnen; Details sind optional. +

+ )}
) : (
@@ -328,7 +341,7 @@ function ClubsPage() { <>

Trainingsgruppen

- {isTrainer && ( + {canCreateClub && ( diff --git a/frontend/src/pages/TrainingPlanningPage.jsx b/frontend/src/pages/TrainingPlanningPage.jsx index 4edeee3..7b968c7 100644 --- a/frontend/src/pages/TrainingPlanningPage.jsx +++ b/frontend/src/pages/TrainingPlanningPage.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback } from 'react' +import { Link } from 'react-router-dom' import api from '../utils/api' import { useAuth } from '../context/AuthContext' @@ -481,7 +482,32 @@ function TrainingPlanningPage() { return (
-

Trainingsplanung

+

Trainingsplanung

+

+ Wähle eine Trainingsgruppe, lege dann Termine mit Inhalt (Abschnitte und Übungen) an — ein Plan entsteht aus einer oder mehreren{' '} + Trainingseinheiten im gewählten Zeitraum. +

+ + {!loading && groups.length === 0 && ( +
+

Erst Verein & Gruppe anlegen

+

+ Ohne Trainingsgruppe kann hier nichts gebucht werden. Unter Vereine legst du einen Verein an + (kurzer Name genügt), optional eine Sparte, dann eine Trainingsgruppe. Wochentage, feste Zeiten oder + Eigenschaften sind optional und kannst du später ergänzen. +

+ + Zu Vereinen & Trainingsgruppen + +
+ )}
)} -
- {selectedGroupId && (
- -
- - - +
+ + + +
- )} +
{!selectedGroupId ? (

- Bitte wähle eine Trainingsgruppe aus + Wähle oben eine Trainingsgruppe — danach kannst du mit „Neue Trainingseinheit planen“ starten.

) : units.length === 0 ? (

- Keine Trainingseinheiten im gewählten Zeitraum + Keine Trainingseinheiten in diesem Zeitraum. Nutze oben „Neue Trainingseinheit planen“ oder{' '} + „Schnell erstellen“, um den ersten Termin anzulegen.

) : (