This commit is contained in:
parent
8960806f9b
commit
6f5d0a0fd0
165
src/admin/PlayerEdit.tsx
Normal file
165
src/admin/PlayerEdit.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams, useNavigate, Link } from "react-router-dom";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/components/ui//use-toast";
|
||||
|
||||
const apiBase = import.meta.env.VITE_API_URL;
|
||||
|
||||
const PlayerEdit = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const {toast} = useToast();
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [nickname, setNickname] = useState("");
|
||||
const [position, setPosition] = useState("");
|
||||
const [jerseyNumber, setJerseyNumber] = useState<number | undefined>();
|
||||
const [birthdate, setBirthdate] = useState("");
|
||||
const [favoriteFood, setFavoriteFood] = useState("");
|
||||
const [status, setStatus] = useState("aktiv");
|
||||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPlayer();
|
||||
}, [id]);
|
||||
|
||||
const fetchPlayer = async () => {
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/players/${id}`);
|
||||
const data = await res.json();
|
||||
|
||||
setName(data.name || "");
|
||||
setNickname(data.nickname || "");
|
||||
setPosition(data.position || "");
|
||||
setJerseyNumber(data.jersey_number || undefined);
|
||||
setBirthdate(data.birthdate ? data.birthdate.substring(0, 10) : "");
|
||||
setFavoriteFood(data.favorite_food || "");
|
||||
setStatus(data.status || "aktiv");
|
||||
setImageUrl(data.image_url ? `${apiBase}${data.image_url}` : null);
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Laden des Spielers:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleImageUpload = async (file: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append("image", file);
|
||||
setUploading(true);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/upload-player-image`, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error("Fehler beim Hochladen des Bildes");
|
||||
|
||||
const data = await res.json();
|
||||
setImageUrl(`${apiBase}${data.imageUrl}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/players/${id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
nickname,
|
||||
position,
|
||||
jersey_number: jerseyNumber,
|
||||
birthdate,
|
||||
favorite_food: favoriteFood,
|
||||
status,
|
||||
image_url: imageUrl?.replace(apiBase, ""), // Nur Pfad speichern
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
toast({
|
||||
title: "Spieler gespeichert",
|
||||
description: "Die Änderungen wurden erfolgreich übernommen.",
|
||||
});
|
||||
setTimeout(() => {
|
||||
navigate(-1);
|
||||
}, 500); // Warte kurz, damit der Toast sichtbar bleibt
|
||||
} else {
|
||||
console.error("Fehler beim Speichern");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Speichern:", err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto py-12 px-4">
|
||||
<h1 className="text-3xl font-bold text-frog-600 mb-6">Spieler bearbeiten</h1>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Input placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
|
||||
<Input placeholder="Spitzname" value={nickname} onChange={(e) => setNickname(e.target.value)} />
|
||||
<Input placeholder="Position" value={position} onChange={(e) => setPosition(e.target.value)} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Trikotnummer"
|
||||
value={jerseyNumber || ""}
|
||||
onChange={(e) => setJerseyNumber(Number(e.target.value))}
|
||||
/>
|
||||
<Input
|
||||
type="date"
|
||||
placeholder="Geburtstag"
|
||||
value={birthdate}
|
||||
onChange={(e) => setBirthdate(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Lieblingsessen"
|
||||
value={favoriteFood}
|
||||
onChange={(e) => setFavoriteFood(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Status (aktiv, verletzt, pausiert)"
|
||||
value={status}
|
||||
onChange={(e) => setStatus(e.target.value)}
|
||||
/>
|
||||
|
||||
{/* Bild-Upload */}
|
||||
<Input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) handleImageUpload(file);
|
||||
}}
|
||||
/>
|
||||
{uploading && <p className="text-sm text-gray-400">Bild wird hochgeladen...</p>}
|
||||
{imageUrl && (
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt="Spielerbild"
|
||||
className="mt-2 max-h-40 rounded-md object-cover shadow-md"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Buttons */}
|
||||
<div className="flex justify-end gap-4 mt-6">
|
||||
<Button variant="outline" asChild>
|
||||
<Link to="/admin/teams">Abbrechen</Link>
|
||||
</Button>
|
||||
<Button onClick={handleSave} className="bg-frog-500 hover:bg-frog-600 text-white">
|
||||
Speichern
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlayerEdit;
|
||||
217
src/admin/TeamAddPlayer.tsx
Normal file
217
src/admin/TeamAddPlayer.tsx
Normal file
@ -0,0 +1,217 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
const apiBase = import.meta.env.VITE_API_URL;
|
||||
|
||||
type Player = {
|
||||
id: number;
|
||||
name: string;
|
||||
nickname?: string;
|
||||
position: string;
|
||||
jersey_number?: number;
|
||||
age?: number;
|
||||
favorite_food?: string;
|
||||
image_url?: string;
|
||||
};
|
||||
|
||||
const TeamAddPlayer = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [players, setPlayers] = useState<Player[]>([]);
|
||||
const [newPlayer, setNewPlayer] = useState({
|
||||
name: "",
|
||||
nickname: "",
|
||||
position: "",
|
||||
jersey_number: "",
|
||||
age: "",
|
||||
favorite_food: "",
|
||||
image_url: "",
|
||||
});
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
const fetchPlayers = async () => {
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/players`);
|
||||
const data = await res.json();
|
||||
setPlayers(data);
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Laden der Spieler:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddExistingPlayer = async (playerId: number) => {
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/teams/${id}/players`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ playerId }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
navigate(`/admin/teams/${id}`);
|
||||
} else {
|
||||
console.error("Fehler beim Hinzufügen");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateNewPlayer = async () => {
|
||||
if (!newPlayer.name.trim() || !newPlayer.position.trim()) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/players`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
...newPlayer,
|
||||
jersey_number: newPlayer.jersey_number ? Number(newPlayer.jersey_number) : null,
|
||||
age: newPlayer.age ? Number(newPlayer.age) : null,
|
||||
team_ids: [Number(id)],
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
navigate(`/admin/teams/${id}`);
|
||||
} else {
|
||||
console.error("Fehler beim Erstellen des Spielers");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleImageUpload = async (file: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append("image", file);
|
||||
setUploading(true);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/upload-player-image`, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error("Fehler beim Bild-Upload");
|
||||
|
||||
const data = await res.json();
|
||||
setNewPlayer((prev) => ({ ...prev, image_url: data.imageUrl }));
|
||||
} catch (err) {
|
||||
console.error("Bild-Upload fehlgeschlagen:", err);
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchPlayers();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto py-12 px-4">
|
||||
<h1 className="text-3xl font-bold text-frog-600 mb-6">Spieler zum Team hinzufügen</h1>
|
||||
|
||||
{/* Bestehende Spieler */}
|
||||
<div className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-frog-500 mb-4">Bestehende Spieler auswählen</h2>
|
||||
|
||||
{players.length === 0 ? (
|
||||
<p className="text-gray-600">Keine Spieler gefunden.</p>
|
||||
) : (
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{players.map((player) => (
|
||||
<Card key={player.id} className="hover:shadow-md transition-shadow">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
{player.name} {player.nickname && `(${player.nickname})`}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600">Position: {player.position}</p>
|
||||
{player.jersey_number && <p className="text-gray-600">Nr: {player.jersey_number}</p>}
|
||||
<Button
|
||||
className="mt-2 bg-frog-500 hover:bg-frog-600 text-white w-full"
|
||||
onClick={() => handleAddExistingPlayer(player.id)}
|
||||
>
|
||||
Hinzufügen
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Neuen Spieler anlegen */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-frog-500 mb-4">Neuen Spieler erstellen</h2>
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
placeholder="Name *"
|
||||
value={newPlayer.name}
|
||||
onChange={(e) => setNewPlayer((prev) => ({ ...prev, name: e.target.value }))}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Spitzname"
|
||||
value={newPlayer.nickname}
|
||||
onChange={(e) => setNewPlayer((prev) => ({ ...prev, nickname: e.target.value }))}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Position *"
|
||||
value={newPlayer.position}
|
||||
onChange={(e) => setNewPlayer((prev) => ({ ...prev, position: e.target.value }))}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Trikotnummer"
|
||||
type="number"
|
||||
value={newPlayer.jersey_number}
|
||||
onChange={(e) => setNewPlayer((prev) => ({ ...prev, jersey_number: e.target.value }))}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Alter"
|
||||
type="number"
|
||||
value={newPlayer.age}
|
||||
onChange={(e) => setNewPlayer((prev) => ({ ...prev, age: e.target.value }))}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Lieblingsessen"
|
||||
value={newPlayer.favorite_food}
|
||||
onChange={(e) => setNewPlayer((prev) => ({ ...prev, favorite_food: e.target.value }))}
|
||||
/>
|
||||
|
||||
{/* Bild Upload */}
|
||||
<Input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) handleImageUpload(file);
|
||||
}}
|
||||
/>
|
||||
{uploading && <p className="text-sm text-gray-400">Bild wird hochgeladen...</p>}
|
||||
{newPlayer.image_url && (
|
||||
<img
|
||||
src={`${apiBase}${newPlayer.image_url}`}
|
||||
alt="Spielerbild"
|
||||
className="mt-2 max-h-40 rounded-md shadow-md"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleCreateNewPlayer}
|
||||
className="bg-frog-500 hover:bg-frog-600 text-white w-full"
|
||||
>
|
||||
Spieler erstellen & hinzufügen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamAddPlayer;
|
||||
69
src/admin/TeamCreate.tsx
Normal file
69
src/admin/TeamCreate.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const apiBase = import.meta.env.VITE_API_URL;
|
||||
|
||||
const TeamCreate = () => {
|
||||
const [teamName, setTeamName] = useState("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [errorMsg, setErrorMsg] = useState("");
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!teamName.trim()) return;
|
||||
|
||||
setSaving(true);
|
||||
setErrorMsg("");
|
||||
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/teams`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: teamName }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const msg = await res.text();
|
||||
setErrorMsg(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
navigate("/admin/teams");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto py-12 px-4">
|
||||
<h1 className="text-3xl font-bold text-frog-600 mb-6">Neues Team anlegen</h1>
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
placeholder="Teamname"
|
||||
value={teamName}
|
||||
onChange={(e) => setTeamName(e.target.value)}
|
||||
/>
|
||||
{errorMsg && (
|
||||
<p className="text-sm text-red-600 bg-red-50 border border-red-200 px-3 py-2 rounded-md">
|
||||
{errorMsg}
|
||||
</p>
|
||||
)}
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="bg-frog-500 hover:bg-frog-600 text-white"
|
||||
>
|
||||
{saving ? "Speichern..." : "Speichern"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamCreate;
|
||||
163
src/admin/TeamDetail.tsx
Normal file
163
src/admin/TeamDetail.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const apiBase = import.meta.env.VITE_API_URL;
|
||||
|
||||
type Player = {
|
||||
id: number;
|
||||
name: string;
|
||||
nickname?: string;
|
||||
position: string;
|
||||
jersey_number?: number;
|
||||
image_url?: string;
|
||||
};
|
||||
|
||||
const TeamDetail = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [teamName, setTeamName] = useState("");
|
||||
const [players, setPlayers] = useState<Player[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const fetchTeam = async () => {
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/teams/${id}`);
|
||||
const data = await res.json();
|
||||
setTeamName(data.name ?? "");
|
||||
setPlayers(data.players ?? []);
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Laden des Teams:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTeam();
|
||||
}, [id]);
|
||||
|
||||
const handleUpdateName = async () => {
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/teams/${id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: teamName }),
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error("Fehler beim Aktualisieren des Teamnamens");
|
||||
|
||||
alert("Teamname aktualisiert!");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemovePlayer = async (playerId: number) => {
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/teams/${id}/players/${playerId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
setPlayers((prev) => prev.filter((p) => p.id !== playerId));
|
||||
} else {
|
||||
console.error("Fehler beim Entfernen des Spielers");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <p className="text-center py-12">Lade Team...</p>;
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto py-12 px-4">
|
||||
<h1 className="text-3xl font-bold text-frog-600 mb-6">Team bearbeiten</h1>
|
||||
|
||||
<div className="space-y-4 mb-8">
|
||||
<Input
|
||||
value={teamName}
|
||||
onChange={(e) => setTeamName(e.target.value)}
|
||||
placeholder="Teamname"
|
||||
/>
|
||||
<Button
|
||||
onClick={handleUpdateName}
|
||||
className="bg-frog-500 hover:bg-frog-600 text-white"
|
||||
>
|
||||
Teamname speichern
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-frog-500 mb-4">Spieler im Team</h2>
|
||||
|
||||
{players.length === 0 ? (
|
||||
<p className="text-gray-600">Noch keine Spieler zugeordnet.</p>
|
||||
) : (
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{players.map((player) => (
|
||||
<Card key={player.id} className="hover:shadow-md transition-shadow">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>
|
||||
{player.name} {player.nickname && `(${player.nickname})`}
|
||||
</CardTitle>
|
||||
</div>
|
||||
<div>
|
||||
<img
|
||||
src={player.image_url ? `${apiBase}${player.image_url}` : "/images/default-player.png"}
|
||||
alt={player.name}
|
||||
className="w-16 h-16 object-cover rounded-full border-2 border-frog-500 shadow-sm hover:scale-105 transition-transform"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600">Position: {player.position}</p>
|
||||
{player.jersey_number && (
|
||||
<p className="text-gray-600">Nummer: {player.jersey_number}</p>
|
||||
)}
|
||||
<div className="flex gap-2 mt-4">
|
||||
<Button
|
||||
asChild
|
||||
variant="outline"
|
||||
className="text-frog-600 border-frog-500 hover:bg-frog-50"
|
||||
>
|
||||
<Link to={`/admin/players/${player.id}/edit`}>
|
||||
Bearbeiten
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleRemovePlayer(player.id)}
|
||||
variant="destructive"
|
||||
>
|
||||
Entfernen
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<Button asChild>
|
||||
<Link to={`/admin/teams/${id}/add-player`}>
|
||||
+ Spieler hinzufügen
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
};
|
||||
|
||||
export default TeamDetail;
|
||||
@ -13,6 +13,10 @@ type Team = {
|
||||
|
||||
const TeamList = () => {
|
||||
const [teams, setTeams] = useState<Team[]>([]);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [teamToDelete, setTeamToDelete] = useState<Team | null>(null);
|
||||
const [confirmInput, setConfirmInput] = useState("");
|
||||
|
||||
|
||||
const fetchTeams = async () => {
|
||||
try {
|
||||
@ -24,6 +28,25 @@ const TeamList = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteTeam = async () => {
|
||||
if (!teamToDelete) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/teams/${teamToDelete.id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
setTeams((prev) => prev.filter((t) => t.id !== teamToDelete.id));
|
||||
setShowDeleteModal(false);
|
||||
} else {
|
||||
console.error("Fehler beim Löschen");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Fehler:", err);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTeams();
|
||||
}, []);
|
||||
@ -51,12 +74,61 @@ const TeamList = () => {
|
||||
>
|
||||
<Link to={`/admin/teams/${team.id}`}>Bearbeiten</Link>
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="mt-2"
|
||||
onClick={() => {
|
||||
setTeamToDelete(team);
|
||||
setShowDeleteModal(true);
|
||||
setConfirmInput("");
|
||||
}}
|
||||
>
|
||||
Löschen
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 🔥 MODAL INS RETURN */}
|
||||
{showDeleteModal && teamToDelete && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white p-6 rounded-lg shadow-lg max-w-md w-full">
|
||||
<h2 className="text-xl font-bold text-red-600 mb-4 text-center">
|
||||
Team wirklich löschen?
|
||||
</h2>
|
||||
<p className="mb-2 text-sm text-gray-700 text-center">
|
||||
Gib den Teamnamen <strong>"{teamToDelete.name}"</strong> ein, um zu bestätigen:
|
||||
</p>
|
||||
<input
|
||||
type="text"
|
||||
value={confirmInput}
|
||||
onChange={(e) => setConfirmInput(e.target.value)}
|
||||
className="w-full border border-gray-300 rounded-md px-3 py-2 mb-4"
|
||||
placeholder="Teamname zur Bestätigung"
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={() => setShowDeleteModal(false)}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={deleteTeam}
|
||||
disabled={confirmInput !== teamToDelete.name}
|
||||
>
|
||||
Löschen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default TeamList;
|
||||
|
||||
@ -20,6 +20,10 @@ import UserManagementPage from "./admin/UserManagementPage";
|
||||
import UserCreatePage from "./admin/UserCreatePage";
|
||||
import UserEditPage from "./admin/UserEditPage";
|
||||
import TeamList from "./admin/TeamList";
|
||||
import TeamCreate from "./admin/TeamCreate";
|
||||
import TeamDetail from "./admin/TeamDetail";
|
||||
import TeamAddPlayer from "./admin/TeamAddPlayer";
|
||||
import PlayerEdit from "./admin/PlayerEdit";
|
||||
|
||||
|
||||
|
||||
@ -61,6 +65,10 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<Route path="/admin/users/new" element={<PrivateRoute><Layout><UserCreatePage /></Layout></PrivateRoute>} />
|
||||
<Route path="/admin/users/edit/:id" element={<PrivateRoute><Layout><UserEditPage /></Layout></PrivateRoute>} />
|
||||
<Route path="/admin/teams" element={<PrivateRoute><Layout><TeamList /></Layout></PrivateRoute>} />
|
||||
<Route path="/admin/teams/new" element={<PrivateRoute><Layout><TeamCreate /></Layout></PrivateRoute>} />
|
||||
<Route path="/admin/teams/:id" element={<PrivateRoute><Layout><TeamDetail /></Layout></PrivateRoute>} />
|
||||
<Route path="/admin/teams/:id/add-player" element={<PrivateRoute><Layout><TeamAddPlayer /></Layout></PrivateRoute>} />
|
||||
<Route path="/admin/players/:id/edit" element={<PrivateRoute><Layout><PlayerEdit /></Layout></PrivateRoute>} />
|
||||
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user