+
{navItems.map((item) => (
))}
+ {activeUser && (
+
+
+ {activeUser.name.charAt(0).toUpperCase()}
+
+
{activeUser.name}
+
+ )}
);
}
diff --git a/frontend/src/components/layout/PageShell.tsx b/frontend/src/components/layout/PageShell.tsx
index 0aac542..4997026 100755
--- a/frontend/src/components/layout/PageShell.tsx
+++ b/frontend/src/components/layout/PageShell.tsx
@@ -1,18 +1,108 @@
+import { useEffect, useState } from 'react';
import { Outlet } from 'react-router-dom';
import { BottomNav, Sidebar } from './BottomNav';
import { ToastContainer } from './Toast';
+import { useUserStore } from '../../stores/userStore';
+
+function UserGate({ children }: { children: React.ReactNode }) {
+ const { users, activeUser, setActiveUser, fetchUsers, createUser } = useUserStore();
+ const [newName, setNewName] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [fetched, setFetched] = useState(false);
+
+ useEffect(() => {
+ fetchUsers().then(() => setFetched(true));
+ }, [fetchUsers]);
+
+ if (!fetched) {
+ return (
+
+ );
+ }
+
+ if (!activeUser) {
+ async function handleCreate(e: React.FormEvent) {
+ e.preventDefault();
+ const name = newName.trim();
+ if (!name) return;
+ setLoading(true);
+ const user = await createUser(name);
+ setLoading(false);
+ if (user) setActiveUser(user);
+ }
+
+ return (
+
+
+
+
Krafttrainer
+
Wer trainiert heute?
+
+
+ {users.length > 0 && (
+
+ {users.map((user) => (
+ -
+
+
+ ))}
+
+ )}
+
+
+
+
+ );
+ }
+
+ return <>{children}>;
+}
export function PageShell() {
return (
-
+
+
+
);
}
diff --git a/frontend/src/pages/HistoryPage.tsx b/frontend/src/pages/HistoryPage.tsx
index 3b0b4a3..29c988f 100755
--- a/frontend/src/pages/HistoryPage.tsx
+++ b/frontend/src/pages/HistoryPage.tsx
@@ -1,15 +1,47 @@
import { useState } from 'react';
import { SessionList } from '../components/history/SessionList';
import { ExerciseChart } from '../components/history/ExerciseChart';
+import { getActiveUserId } from '../stores/userStore';
type Tab = 'history' | 'stats';
+async function downloadExport() {
+ const uid = getActiveUserId();
+ const headers: Record
= {};
+ if (uid) headers['X-User-ID'] = uid;
+
+ const res = await fetch('/api/v1/export', { headers });
+ if (!res.ok) return;
+
+ const blob = await res.blob();
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `training-export-${new Date().toISOString().slice(0, 10)}.csv`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+}
+
export function HistoryPage() {
const [activeTab, setActiveTab] = useState('history');
return (
-
Historie
+
+
Historie
+
+
{/* Tab Toggle */}
diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx
new file mode 100644
index 0000000..c26b499
--- /dev/null
+++ b/frontend/src/pages/SettingsPage.tsx
@@ -0,0 +1,95 @@
+import { useEffect, useState } from 'react';
+import { useUserStore } from '../stores/userStore';
+
+export function SettingsPage() {
+ const { users, activeUser, setActiveUser, fetchUsers, createUser, deleteUser } =
+ useUserStore();
+ const [newName, setNewName] = useState('');
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ fetchUsers();
+ }, [fetchUsers]);
+
+ async function handleCreate(e: React.FormEvent) {
+ e.preventDefault();
+ const name = newName.trim();
+ if (!name) return;
+ setLoading(true);
+ const user = await createUser(name);
+ setLoading(false);
+ if (user) setNewName('');
+ }
+
+ async function handleDelete(id: number) {
+ await deleteUser(id);
+ }
+
+ return (
+
+
Einstellungen
+
+
+ Nutzer
+
+
+ {users.map((user) => (
+ -
+
+
+ {users.length > 1 && (
+
+ )}
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/stores/userStore.ts b/frontend/src/stores/userStore.ts
new file mode 100644
index 0000000..21c6932
--- /dev/null
+++ b/frontend/src/stores/userStore.ts
@@ -0,0 +1,71 @@
+import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
+import type { User } from '../types';
+
+interface UserState {
+ users: User[];
+ activeUser: User | null;
+ setActiveUser: (user: User) => void;
+ fetchUsers: () => Promise
;
+ createUser: (name: string) => Promise;
+ deleteUser: (id: number) => Promise;
+}
+
+export const useUserStore = create()(
+ persist(
+ (set, get) => ({
+ users: [],
+ activeUser: null,
+
+ setActiveUser: (user) => set({ activeUser: user }),
+
+ fetchUsers: async () => {
+ const res = await fetch('/api/v1/users', {
+ headers: { 'Content-Type': 'application/json' },
+ });
+ if (!res.ok) return;
+ const users: User[] = await res.json();
+ set({ users });
+
+ // Aktiven Nutzer aktualisieren falls er sich geändert hat
+ const { activeUser } = get();
+ if (activeUser) {
+ const updated = users.find((u) => u.id === activeUser.id);
+ if (updated) set({ activeUser: updated });
+ }
+ },
+
+ createUser: async (name) => {
+ const res = await fetch('/api/v1/users', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ name }),
+ });
+ if (!res.ok) return null;
+ const user: User = await res.json();
+ set((s) => ({ users: [...s.users, user] }));
+ return user;
+ },
+
+ deleteUser: async (id) => {
+ const res = await fetch(`/api/v1/users/${id}`, { method: 'DELETE' });
+ if (!res.ok) return false;
+ set((s) => ({
+ users: s.users.filter((u) => u.id !== id),
+ activeUser: s.activeUser?.id === id ? null : s.activeUser,
+ }));
+ return true;
+ },
+ }),
+ {
+ name: 'user-store',
+ partialize: (state) => ({ activeUser: state.activeUser }),
+ },
+ ),
+);
+
+// Gibt die aktive User-ID zurück — wird von api/client.ts verwendet.
+export function getActiveUserId(): string | null {
+ const { activeUser } = useUserStore.getState();
+ return activeUser ? String(activeUser.id) : null;
+}
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
index fd424b8..b5073d1 100755
--- a/frontend/src/types/index.ts
+++ b/frontend/src/types/index.ts
@@ -121,6 +121,16 @@ export interface CreateSessionRequest {
set_id: number;
}
+export interface User {
+ id: number;
+ name: string;
+ created_at: string;
+}
+
+export interface CreateUserRequest {
+ name: string;
+}
+
export interface CreateLogRequest {
exercise_id: number;
set_number: number;