ADD: added team management

This commit is contained in:
hwinkel
2025-11-28 14:31:12 +01:00
parent 3a6c3a86e3
commit aac5a3c21d
14 changed files with 587 additions and 166 deletions

View File

@@ -5134,9 +5134,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001718",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
"integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
"version": "1.0.30001757",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
"integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
"funding": [
{
"type": "opencollective",

View File

@@ -1,10 +0,0 @@
const API_URL = 'http://localhost:8080/api';
export async function getTeams(token:string)
{
const res = await fetch(`${API_URL}/team/`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error('Fehler beim Laden des Spielers');
return res.json();
}

View File

@@ -1,12 +1,14 @@
import React, { useEffect, useState } from "react";
import { Team } from "../../components/interfaces/Team";
import { get } from "http";
import { getTeams } from "../../components/requests/TeamData";
import { fetchTeams } from "../api";
import { useNavigate } from "react-router-dom";
const Teams: React.FC = () => {
const [teams, setTeams] = useState<Team[]>([]);
const navigate = useNavigate();
useEffect(() => {
// Replace with API call in production
@@ -16,8 +18,10 @@ const Teams: React.FC = () => {
async function load()
{
try {
const teams : Team[] = await getTeams(localStorage.getItem("token") || "");
setTeams(teams);
const teams : Team[] = await fetchTeams(localStorage.getItem("token") || "");
console.log("Geladene Teams:", teams);
if (teams != null)
setTeams(teams);
} catch (error) {
console.error("Fehler beim Laden der Teams:", error);
}
@@ -28,6 +32,8 @@ const Teams: React.FC = () => {
return (
<div style={{ padding: "2rem" }}>
<h1>Teams Administration</h1>
<button onClick={() => navigate("/teams")
}>Create Team</button>
<table style={{ width: "100%", borderCollapse: "collapse", marginTop: "1rem" }}>
<thead>
<tr>

View File

@@ -51,7 +51,7 @@ export default function UsersPage(): JSX.Element {
params.set("page", String(page));
params.set("limit", String(pageSize));
console.log("Fetching users with params:", params.toString());
// console.log("Fetching users with params:", params.toString());
const token = localStorage.getItem('token');
if (!token) throw new Error("No auth token found");
@@ -62,7 +62,7 @@ export default function UsersPage(): JSX.Element {
user.Email.toLowerCase().includes(query.toLowerCase())
);
setUsers(filteredData);
console.log("Loaded users:", data);
// console.log("Loaded users:", data);
// if (!res.ok) throw new Error(`Failed to load users: ${res.status}`);

View File

@@ -1,140 +1,232 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { getUserFromToken } from '../components/utils/jwt'; // Importiere die Funktion zum Decodieren des Tokens
import { getUserFromToken } from '../components/utils/jwt';
import { fetchTeams } from './api';
interface Team {
id: string;
name: string;
players: string[]; // Emails oder Namen
createdBy: string;
UUID: string;
Name: string;
Players: string[];
OwnerUUID: string;
Description: string
}
const TeamManagement = () => {
const TeamManagement: React.FC = () => {
const [teams, setTeams] = useState<Team[]>([]);
const [name, setName] = useState('');
const [player2, setPlayer2] = useState('');
const [description, setDescription] = useState('');
const [error, setError] = useState('');
const [editingTeam, setEditingTeam] = useState<Team | null>(null);
const [selectedTeam, setSelectedTeam] = useState<Team | null>(null);
const token = localStorage.getItem('token');
const user = token ? getUserFromToken(token) : null;
const isAdmin = user?.role?.includes('admin');
const API_URL = 'http://localhost:8080/api';
const fetchTeams = async () => {
const res = await fetch('/api/teams', {
headers: { Authorization: `Bearer ${token}` }
});
const data = await res.json();
if (res.ok) setTeams(data.data || []);
else setError(data.error || 'Fehler beim Laden der Teams');
};
useEffect(() => {
if (token) fetchTeams();
}, [token]);
load();
}, [token]); // Add token as dependency
async function load() {
if (!token) return;
try {
const data = await fetchTeams(token);
// console.log("Geladene Teams:", data);
if (data) {
setTeams(data);
}
} catch (error) {
// console.error("Fehler beim Laden der Teams:", error);
setError('Fehler beim Laden der Teams');
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!user?.email) {
setError('User email not found. Please log in again.');
return;
}
const body = {
name,
players: [user?.email, player2],
description: description,
owneruuid: user.userId,
};
const method = editingTeam ? 'PUT' : 'POST';
const url = editingTeam ? `/api/teams/${editingTeam.id}` : '/api/teams';
const url = editingTeam ? `/teams/${editingTeam.UUID}` : '/teams';
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(body),
});
try {
const res = await fetch(API_URL+url, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(body),
});
const data = await res.json();
const data = await res.json();
console.log("Response data:", data);
if (res.ok) {
setName('');
setPlayer2('');
setEditingTeam(null);
fetchTeams();
} else {
setError(data.error || 'Fehler beim Speichern');
if (res.ok) {
handleCreateNewClick(); // Reset form state
load(); // Reload teams
} else {
setError(data.error || 'Fehler beim Speichern');
}
} catch (err) {
setError('Ein Netzwerkfehler ist aufgetreten.');
}
};
const handleDelete = async (id: string) => {
const res = await fetch(`/api/teams/${id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
if (!window.confirm("Are you sure you want to delete this team?")) return;
try {
const res = await fetch(`/api/teams/${id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
if (res.ok) fetchTeams();
else setError('Löschen fehlgeschlagen');
if (res.ok) {
handleCreateNewClick(); // Reset form
load(); // Refresh list
} else {
const data = await res.json();
setError(data.error || 'Löschen fehlgeschlagen');
}
} catch (err) {
setError('Ein Netzwerkfehler ist aufgetreten.');
}
};
const canEdit = (team: Team) => isAdmin;
const canDelete = (team: Team) =>
isAdmin || team.createdBy === user?.userId;
const canDelete = (team: Team) => isAdmin || team.OwnerUUID === user?.userId;
const canCreate = () => isAdmin || true;
const canCreate = () => isAdmin || true; // Normale Nutzer dürfen eigene Teams erstellen
const handleSelectTeam = (team: Team) => {
setSelectedTeam(team);
setEditingTeam(team);
setName(team.Name);
setDescription(team.Description);
const currentUserEmail = user?.email;
// description(otherPlayer || '');
};
const handleCreateNewClick = () => {
setSelectedTeam(null);
setEditingTeam(null);
setName('');
setDescription('');
setError('');
};
// const myTeams = teams;
const myTeams = teams.filter(team =>
team.OwnerUUID === user?.userId || (user?.email && team.Players.includes(user.email))
);
// console.log("meine teams ", myTeams);
return (
<div className="max-w-2xl mx-auto p-6 bg-white rounded-xl shadow-md mt-6">
<div className="max-w-4xl mx-auto p-6 bg-white rounded-xl shadow-md mt-6">
<h2 className="text-2xl font-bold mb-4">Team Management</h2>
{error && <p style={{ color: 'red' }}>{error}</p>}
{error && <p className="text-red-500 bg-red-100 p-3 rounded mb-4">{error}</p>}
{canCreate() && (
<form onSubmit={handleSubmit} className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
<h3>{editingTeam ? 'Team bearbeiten' : 'Team erstellen'}</h3>
<input
className="border p-2 rounded"
type="text"
placeholder="Teamname"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<input
className="border p-2 rounded"
type="email"
placeholder="E-Mail von Mitspieler"
value={player2}
onChange={(e) => setPlayer2(e.target.value)}
required={!isAdmin}
/>
<button type="submit"
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
>{editingTeam ? 'Aktualisieren' : 'Erstellen'}</button>
</form>
)}
<hr />
<h3>Alle Teams</h3>
<ul>
{teams.map((team) => (
<li key={team.id}>
<strong>{team.name}</strong> Spieler: {team.players.join(', ')}
<div style={{ marginTop: '4px' }}>
{canEdit(team) && (
<button onClick={() => {
setName(team.name);
setPlayer2(team.players[1] || '');
setEditingTeam(team);
}}>
Bearbeiten
<div className="mb-6">
<h3 className="text-xl font-semibold mb-2">Meine Teams</h3>
<div className="flex flex-wrap gap-2">
{myTeams.map((team) => (
<button
key={team.UUID}
onClick={() => handleSelectTeam(team)}
className={`px-4 py-2 rounded-lg text-sm font-medium
${selectedTeam?.UUID === team.UUID
? 'bg-blue-600 text-white shadow-lg'
: 'bg-gray-200 text-gray-800 hover:bg-gray-300'
}`}
>
{team.Name}
</button>
)}
{canDelete(team) && (
<button onClick={() => handleDelete(team.id)}>Löschen</button>
)}
))}
</div>
</div>
<div className="mb-6">
{canCreate() && (
<button
onClick={handleCreateNewClick}
className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
>
Neues Team erstellen
</button>
)}
</div>
<hr className="my-6"/>
<div>
{!editingTeam && !selectedTeam && (
<h3 className="text-xl font-semibold mb-4">Neues Team erstellen</h3>
)}
{editingTeam && (
<h3 className="text-xl font-semibold mb-4">Team ansehen/bearbeiten: {editingTeam.Name}</h3>
)}
{/* Form for creating or editing a team */}
{(!selectedTeam && canCreate()) || editingTeam ? (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="teamname" className="block text-sm font-medium text-gray-700">Teamname</label>
<input
id="teamname"
className="mt-1 block w-full border p-2 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
type="text"
placeholder="Teamname"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
</li>
))}
</ul>
<div>
<label htmlFor="description" className="block text-sm font-medium text-gray-700">Beschreibung</label>
<input
id="description"
className="mt-1 block w-full border p-2 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
type="text"
placeholder="Beschreibung"
value={description}
onChange={(e) => setDescription(e.target.value)}
// required={!isAdmin} // Admin can create single-player teams maybe?
/>
</div>
<div className="flex items-center gap-4">
<button type="submit"
className="bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{editingTeam ? 'Aktualisieren' : 'Erstellen'}
</button>
{editingTeam && canDelete(editingTeam) && (
<button
type="button"
onClick={() => handleDelete(editingTeam.UUID)}
className="bg-red-600 text-white px-6 py-2 rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
Löschen
</button>
)}
</div>
</form>
) : (
<p className="text-gray-500">Wähle ein Team aus der Liste oben aus, um es zu bearbeiten, oder erstelle ein neues Team.</p>
)}
</div>
</div>
);
};

View File

@@ -111,3 +111,12 @@ export async function registerTeam(id: string, team: { name: string }, token: st
if (!res.ok) throw new Error('Team-Anmeldung fehlgeschlagen');
return res.json();
}
export async function fetchTeams(token:string)
{
const res = await fetch(`${API_URL}/teams`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error('Fehler beim Laden des Spielers');
return res.json();
}