This commit is contained in:
parent
6f5d0a0fd0
commit
1d5dea8ff1
@ -18,8 +18,8 @@ const AdminDashboard = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{/* Jeder eingeloggt Benutzer darf News verwalten */}
|
{/* Jeder Benutzer darf News verwalten */}
|
||||||
<Link to="/admin/news">
|
<Link to="/admin/news">
|
||||||
<Card className="hover:shadow-lg transition-shadow cursor-pointer">
|
<Card className="hover:shadow-lg transition-shadow cursor-pointer">
|
||||||
<CardContent className="p-6 text-center">
|
<CardContent className="p-6 text-center">
|
||||||
@ -49,6 +49,15 @@ const AdminDashboard = () => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
<Link to="/admin/players">
|
||||||
|
<Card className="hover:shadow-lg transition-shadow cursor-pointer">
|
||||||
|
<CardContent className="p-6 text-center">
|
||||||
|
<CardTitle className="text-frog-600">Spieler verwalten</CardTitle>
|
||||||
|
<p className="text-gray-600 mt-2 text-sm">Alle Spieler sehen und bearbeiten</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,14 +2,21 @@ import { useEffect, useState } from "react";
|
|||||||
import { useParams, useNavigate, Link } from "react-router-dom";
|
import { useParams, useNavigate, Link } from "react-router-dom";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useToast } from "@/components/ui//use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
|
||||||
const apiBase = import.meta.env.VITE_API_URL;
|
const apiBase = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
|
type Team = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
const PlayerEdit = () => {
|
const PlayerEdit = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {toast} = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [nickname, setNickname] = useState("");
|
const [nickname, setNickname] = useState("");
|
||||||
@ -21,8 +28,12 @@ const PlayerEdit = () => {
|
|||||||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
|
|
||||||
|
const [teams, setTeams] = useState<Team[]>([]);
|
||||||
|
const [selectedTeamId, setSelectedTeamId] = useState<number | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPlayer();
|
fetchPlayer();
|
||||||
|
fetchTeams();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const fetchPlayer = async () => {
|
const fetchPlayer = async () => {
|
||||||
@ -38,11 +49,24 @@ const PlayerEdit = () => {
|
|||||||
setFavoriteFood(data.favorite_food || "");
|
setFavoriteFood(data.favorite_food || "");
|
||||||
setStatus(data.status || "aktiv");
|
setStatus(data.status || "aktiv");
|
||||||
setImageUrl(data.image_url ? `${apiBase}${data.image_url}` : null);
|
setImageUrl(data.image_url ? `${apiBase}${data.image_url}` : null);
|
||||||
|
if (data.team_ids && data.team_ids.length > 0) {
|
||||||
|
setSelectedTeamId(data.team_ids[0]);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Fehler beim Laden des Spielers:", err);
|
console.error("Fehler beim Laden des Spielers:", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchTeams = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${apiBase}/api/teams`);
|
||||||
|
const data = await res.json();
|
||||||
|
setTeams(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Fehler beim Laden der Teams:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleImageUpload = async (file: File) => {
|
const handleImageUpload = async (file: File) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("image", file);
|
formData.append("image", file);
|
||||||
@ -67,6 +91,7 @@ const PlayerEdit = () => {
|
|||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
|
// Spieler speichern
|
||||||
const res = await fetch(`${apiBase}/api/players/${id}`, {
|
const res = await fetch(`${apiBase}/api/players/${id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@ -78,27 +103,34 @@ const PlayerEdit = () => {
|
|||||||
birthdate,
|
birthdate,
|
||||||
favorite_food: favoriteFood,
|
favorite_food: favoriteFood,
|
||||||
status,
|
status,
|
||||||
image_url: imageUrl?.replace(apiBase, ""), // Nur Pfad speichern
|
image_url: imageUrl?.replace(apiBase, ""),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
if (!res.ok) throw new Error("Fehler beim Speichern");
|
||||||
|
|
||||||
|
// Teamzuordnung aktualisieren
|
||||||
|
if (selectedTeamId) {
|
||||||
|
await fetch(`${apiBase}/api/players/${id}/assign-team`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ team_id: selectedTeamId }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Spieler gespeichert",
|
title: "Spieler gespeichert",
|
||||||
description: "Die Änderungen wurden erfolgreich übernommen.",
|
description: "Die Änderungen wurden erfolgreich übernommen.",
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
}, 500); // Warte kurz, damit der Toast sichtbar bleibt
|
}, 500);
|
||||||
} else {
|
|
||||||
console.error("Fehler beim Speichern");
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Fehler beim Speichern:", err);
|
console.error("Fehler beim Speichern:", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl mx-auto py-12 px-4">
|
<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>
|
<h1 className="text-3xl font-bold text-frog-600 mb-6">Spieler bearbeiten</h1>
|
||||||
@ -130,6 +162,26 @@ const PlayerEdit = () => {
|
|||||||
onChange={(e) => setStatus(e.target.value)}
|
onChange={(e) => setStatus(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Team Auswahl */}
|
||||||
|
<div>
|
||||||
|
<Label>Teamzugehörigkeit</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedTeamId?.toString()}
|
||||||
|
onValueChange={(value) => setSelectedTeamId(Number(value))}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Team auswählen" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{teams.map((team) => (
|
||||||
|
<SelectItem key={team.id} value={team.id.toString()}>
|
||||||
|
{team.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Bild-Upload */}
|
{/* Bild-Upload */}
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
@ -151,7 +203,7 @@ const PlayerEdit = () => {
|
|||||||
{/* Buttons */}
|
{/* Buttons */}
|
||||||
<div className="flex justify-end gap-4 mt-6">
|
<div className="flex justify-end gap-4 mt-6">
|
||||||
<Button variant="outline" asChild>
|
<Button variant="outline" asChild>
|
||||||
<Link to="/admin/teams">Abbrechen</Link>
|
<Link to="/admin/players">Abbrechen</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSave} className="bg-frog-500 hover:bg-frog-600 text-white">
|
<Button onClick={handleSave} className="bg-frog-500 hover:bg-frog-600 text-white">
|
||||||
Speichern
|
Speichern
|
||||||
|
|||||||
101
src/admin/PlayerManagementPage.tsx
Normal file
101
src/admin/PlayerManagementPage.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Card, CardContent, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
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 PlayerManagementPage = () => {
|
||||||
|
const [players, setPlayers] = useState<Player[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
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);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPlayers();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
if (!confirm("Willst du diesen Spieler wirklich löschen?")) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${apiBase}/api/players/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
setPlayers((prev) => prev.filter((p) => p.id !== id));
|
||||||
|
toast.success("Spieler erfolgreich gelöscht!");
|
||||||
|
} else {
|
||||||
|
toast.error("Fehler beim Löschen des Spielers");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Fehler beim Löschen:", err);
|
||||||
|
toast.error("Serverfehler");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) return <p className="text-center py-12">Lade Spieler...</p>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-6xl mx-auto py-12 px-4">
|
||||||
|
<h1 className="text-3xl font-bold text-frog-600 mb-8">Spieler verwalten</h1>
|
||||||
|
|
||||||
|
{players.length === 0 ? (
|
||||||
|
<p className="text-gray-600 text-center">Noch keine Spieler vorhanden.</p>
|
||||||
|
) : (
|
||||||
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{players.map((player) => (
|
||||||
|
<Card key={player.id} className="hover:shadow-md transition-shadow">
|
||||||
|
<CardContent className="flex flex-col items-center p-6">
|
||||||
|
<img
|
||||||
|
src={player.image_url ? `${apiBase}${player.image_url}` : "/images/default-player.png"}
|
||||||
|
alt={player.name}
|
||||||
|
className="w-24 h-24 rounded-full object-cover border-2 border-frog-500 shadow-md mb-4"
|
||||||
|
/>
|
||||||
|
<CardTitle className="text-center">
|
||||||
|
{player.name} {player.nickname && `(${player.nickname})`}
|
||||||
|
</CardTitle>
|
||||||
|
<p className="text-gray-500 text-sm mt-2">{player.position}</p>
|
||||||
|
{player.jersey_number && (
|
||||||
|
<p className="text-gray-500 text-sm"># {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 variant="destructive" onClick={() => handleDelete(player.id)}>
|
||||||
|
Löschen
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlayerManagementPage;
|
||||||
@ -2,11 +2,26 @@ import { useState } from "react";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
||||||
const apiBase = import.meta.env.VITE_API_URL;
|
const apiBase = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
const TeamCreate = () => {
|
const TeamCreate = () => {
|
||||||
const [teamName, setTeamName] = useState("");
|
const [teamName, setTeamName] = useState("");
|
||||||
|
const [liga, setLiga] = useState("");
|
||||||
|
const [suchtSpieler, setSuchtSpieler] = useState(false);
|
||||||
|
const [socialMedia, setSocialMedia] = useState("");
|
||||||
|
const [trainerName, setTrainerName] = useState("");
|
||||||
|
const [carouselImages, setCarouselImages] = useState<string>("");
|
||||||
|
const [trainingszeiten, setTrainingszeiten] = useState("");
|
||||||
|
const [trainingsort, setTrainingsort] = useState("");
|
||||||
|
const [kontaktName, setKontaktName] = useState("");
|
||||||
|
const [kontaktEmail, setKontaktEmail] = useState("");
|
||||||
|
const [teamfarben, setTeamfarben] = useState("");
|
||||||
|
const [beschreibung, setBeschreibung] = useState("");
|
||||||
|
const [tabellenlink, setTabellenlink] = useState("");
|
||||||
|
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [errorMsg, setErrorMsg] = useState("");
|
const [errorMsg, setErrorMsg] = useState("");
|
||||||
|
|
||||||
@ -22,7 +37,21 @@ const TeamCreate = () => {
|
|||||||
const res = await fetch(`${apiBase}/api/teams`, {
|
const res = await fetch(`${apiBase}/api/teams`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ name: teamName }),
|
body: JSON.stringify({
|
||||||
|
name: teamName,
|
||||||
|
liga,
|
||||||
|
sucht_spieler: suchtSpieler,
|
||||||
|
social_media: socialMedia,
|
||||||
|
karussell_bilder: carouselImages.trim() ? JSON.parse(carouselImages) : [],
|
||||||
|
trainer_name: trainerName,
|
||||||
|
trainingszeiten,
|
||||||
|
trainingsort,
|
||||||
|
kontakt_name: kontaktName,
|
||||||
|
kontakt_email: kontaktEmail,
|
||||||
|
teamfarben,
|
||||||
|
beschreibung,
|
||||||
|
tabellenlink,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@ -39,21 +68,49 @@ const TeamCreate = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl mx-auto py-12 px-4">
|
<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>
|
<h1 className="text-3xl font-bold text-frog-600 mb-6">Neues Team anlegen</h1>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Input
|
<Input placeholder="Teamname" value={teamName} onChange={(e) => setTeamName(e.target.value)} />
|
||||||
placeholder="Teamname"
|
<Input placeholder="Liga" value={liga} onChange={(e) => setLiga(e.target.value)} />
|
||||||
value={teamName}
|
<Input placeholder="Trainer/in" value={trainerName} onChange={(e) => setTrainerName(e.target.value)} />
|
||||||
onChange={(e) => setTeamName(e.target.value)}
|
<Input placeholder="Trainingszeiten" value={trainingszeiten} onChange={(e) => setTrainingszeiten(e.target.value)} />
|
||||||
|
<Input placeholder="Trainingsort" value={trainingsort} onChange={(e) => setTrainingsort(e.target.value)} />
|
||||||
|
<Input placeholder="Kontaktname" value={kontaktName} onChange={(e) => setKontaktName(e.target.value)} />
|
||||||
|
<Input placeholder="Kontakt E-Mail" value={kontaktEmail} onChange={(e) => setKontaktEmail(e.target.value)} />
|
||||||
|
<Input placeholder="Teamfarben" value={teamfarben} onChange={(e) => setTeamfarben(e.target.value)} />
|
||||||
|
<Textarea placeholder="Beschreibung" value={beschreibung} onChange={(e) => setBeschreibung(e.target.value)} />
|
||||||
|
<Input placeholder="Link zur Tabelle" value={tabellenlink} onChange={(e) => setTabellenlink(e.target.value)} />
|
||||||
|
<Input placeholder="Social Media" value={socialMedia} onChange={(e) => setSocialMedia(e.target.value)} />
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
id="suchtSpieler"
|
||||||
|
type="checkbox"
|
||||||
|
checked={suchtSpieler}
|
||||||
|
onChange={(e) => setSuchtSpieler(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
|
<Label htmlFor="suchtSpieler">Spieler/in gesucht?</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="carouselImages">Karussell-Bilder (als JSON-Array)</Label>
|
||||||
|
<Textarea
|
||||||
|
id="carouselImages"
|
||||||
|
placeholder='z. B. ["/uploads/x.jpg", "/uploads/y.jpg"]'
|
||||||
|
value={carouselImages}
|
||||||
|
onChange={(e) => setCarouselImages(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{errorMsg && (
|
{errorMsg && (
|
||||||
<p className="text-sm text-red-600 bg-red-50 border border-red-200 px-3 py-2 rounded-md">
|
<p className="text-sm text-red-600 bg-red-50 border border-red-200 px-3 py-2 rounded-md">
|
||||||
{errorMsg}
|
{errorMsg}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate, Link } from "react-router-dom";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Link } from "react-router-dom";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
const apiBase = import.meta.env.VITE_API_URL;
|
const apiBase = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
@ -21,6 +22,18 @@ const TeamDetail = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [teamName, setTeamName] = useState("");
|
const [teamName, setTeamName] = useState("");
|
||||||
|
const [liga, setLiga] = useState("");
|
||||||
|
const [trainerName, setTrainerName] = useState("");
|
||||||
|
const [socialMedia, setSocialMedia] = useState("");
|
||||||
|
const [spielerGesucht, setSpielerGesucht] = useState(false);
|
||||||
|
const [carouselImages, setCarouselImages] = useState("");
|
||||||
|
const [trainingszeiten, setTrainingszeiten] = useState("");
|
||||||
|
const [trainingsort, setTrainingsort] = useState("");
|
||||||
|
const [kontaktName, setKontaktName] = useState("");
|
||||||
|
const [kontaktEmail, setKontaktEmail] = useState("");
|
||||||
|
const [teamfarben, setTeamfarben] = useState("");
|
||||||
|
const [beschreibung, setBeschreibung] = useState("");
|
||||||
|
const [tabellenlink, setTabellenlink] = useState("");
|
||||||
const [players, setPlayers] = useState<Player[]>([]);
|
const [players, setPlayers] = useState<Player[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
@ -28,7 +41,20 @@ const TeamDetail = () => {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`${apiBase}/api/teams/${id}`);
|
const res = await fetch(`${apiBase}/api/teams/${id}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
setTeamName(data.name ?? "");
|
setTeamName(data.name ?? "");
|
||||||
|
setLiga(data.liga ?? "");
|
||||||
|
setTrainerName(data.trainer_name ?? "");
|
||||||
|
setSocialMedia(data.social_media ?? "");
|
||||||
|
setSpielerGesucht(data.sucht_spieler ?? false);
|
||||||
|
setCarouselImages(data.karussell_bilder ? JSON.stringify(data.karussell_bilder) : "");
|
||||||
|
setTrainingszeiten(data.trainingszeiten ?? "");
|
||||||
|
setTrainingsort(data.trainingsort ?? "");
|
||||||
|
setKontaktName(data.kontakt_name ?? "");
|
||||||
|
setKontaktEmail(data.kontakt_email ?? "");
|
||||||
|
setTeamfarben(data.teamfarben ?? "");
|
||||||
|
setBeschreibung(data.beschreibung ?? "");
|
||||||
|
setTabellenlink(data.tabellenlink ?? "");
|
||||||
setPlayers(data.players ?? []);
|
setPlayers(data.players ?? []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Fehler beim Laden des Teams:", err);
|
console.error("Fehler beim Laden des Teams:", err);
|
||||||
@ -41,19 +67,35 @@ const TeamDetail = () => {
|
|||||||
fetchTeam();
|
fetchTeam();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const handleUpdateName = async () => {
|
const handleUpdateTeam = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${apiBase}/api/teams/${id}`, {
|
const res = await fetch(`${apiBase}/api/teams/${id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ name: teamName }),
|
body: JSON.stringify({
|
||||||
|
name: teamName,
|
||||||
|
liga,
|
||||||
|
sucht_spieler: spielerGesucht,
|
||||||
|
social_media: socialMedia,
|
||||||
|
karussell_bilder: carouselImages ? JSON.parse(carouselImages) : [],
|
||||||
|
trainer_name: trainerName,
|
||||||
|
trainingszeiten,
|
||||||
|
trainingsort,
|
||||||
|
kontakt_name: kontaktName,
|
||||||
|
kontakt_email: kontaktEmail,
|
||||||
|
teamfarben,
|
||||||
|
beschreibung,
|
||||||
|
tabellenlink,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) throw new Error("Fehler beim Aktualisieren des Teamnamens");
|
if (!res.ok) throw new Error("Fehler beim Aktualisieren des Teams");
|
||||||
|
|
||||||
alert("Teamname aktualisiert!");
|
toast.success("Team erfolgreich aktualisiert!");
|
||||||
|
fetchTeam(); // Aktuelle Daten neu laden
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
toast.error("Fehler beim Aktualisieren des Teams.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -76,20 +118,40 @@ const TeamDetail = () => {
|
|||||||
if (loading) return <p className="text-center py-12">Lade Team...</p>;
|
if (loading) return <p className="text-center py-12">Lade Team...</p>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto py-12 px-4">
|
<div className="max-w-5xl mx-auto py-12 px-4">
|
||||||
<h1 className="text-3xl font-bold text-frog-600 mb-6">Team bearbeiten</h1>
|
<h1 className="text-3xl font-bold text-frog-600 mb-6">Team bearbeiten</h1>
|
||||||
|
|
||||||
<div className="space-y-4 mb-8">
|
<div className="space-y-4 mb-8">
|
||||||
<Input
|
<Input value={teamName} onChange={(e) => setTeamName(e.target.value)} placeholder="Teamname" />
|
||||||
value={teamName}
|
<Input value={liga} onChange={(e) => setLiga(e.target.value)} placeholder="Liga" />
|
||||||
onChange={(e) => setTeamName(e.target.value)}
|
<Input value={trainerName} onChange={(e) => setTrainerName(e.target.value)} placeholder="Trainer/in" />
|
||||||
placeholder="Teamname"
|
<Input value={trainingszeiten} onChange={(e) => setTrainingszeiten(e.target.value)} placeholder="Trainingszeiten" />
|
||||||
|
<Input value={trainingsort} onChange={(e) => setTrainingsort(e.target.value)} placeholder="Trainingsort" />
|
||||||
|
<Input value={kontaktName} onChange={(e) => setKontaktName(e.target.value)} placeholder="Kontaktname" />
|
||||||
|
<Input value={kontaktEmail} onChange={(e) => setKontaktEmail(e.target.value)} placeholder="Kontakt E-Mail" />
|
||||||
|
<Input value={teamfarben} onChange={(e) => setTeamfarben(e.target.value)} placeholder="Teamfarben" />
|
||||||
|
<Textarea value={beschreibung} onChange={(e) => setBeschreibung(e.target.value)} placeholder="Beschreibung" />
|
||||||
|
<Input value={tabellenlink} onChange={(e) => setTabellenlink(e.target.value)} placeholder="Link zur Tabelle" />
|
||||||
|
<Input value={socialMedia} onChange={(e) => setSocialMedia(e.target.value)} placeholder="Social Media Link" />
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={spielerGesucht}
|
||||||
|
onChange={() => setSpielerGesucht(!spielerGesucht)}
|
||||||
|
className="w-5 h-5 text-frog-500"
|
||||||
/>
|
/>
|
||||||
<Button
|
<label className="text-gray-700">Neue Spieler gesucht?</label>
|
||||||
onClick={handleUpdateName}
|
</div>
|
||||||
className="bg-frog-500 hover:bg-frog-600 text-white"
|
|
||||||
>
|
<Textarea
|
||||||
Teamname speichern
|
value={carouselImages}
|
||||||
|
onChange={(e) => setCarouselImages(e.target.value)}
|
||||||
|
placeholder="Karussell-Bilder (JSON-Array z.B. [\"//uploads/x.jpg\"])"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button onClick={handleUpdateTeam} className="bg-frog-500 hover:bg-frog-600 text-white">
|
||||||
|
Team speichern
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -124,19 +186,10 @@ const TeamDetail = () => {
|
|||||||
<p className="text-gray-600">Nummer: {player.jersey_number}</p>
|
<p className="text-gray-600">Nummer: {player.jersey_number}</p>
|
||||||
)}
|
)}
|
||||||
<div className="flex gap-2 mt-4">
|
<div className="flex gap-2 mt-4">
|
||||||
<Button
|
<Button asChild variant="outline" className="text-frog-600 border-frog-500 hover:bg-frog-50">
|
||||||
asChild
|
<Link to={`/admin/players/${player.id}/edit`}>Bearbeiten</Link>
|
||||||
variant="outline"
|
|
||||||
className="text-frog-600 border-frog-500 hover:bg-frog-50"
|
|
||||||
>
|
|
||||||
<Link to={`/admin/players/${player.id}/edit`}>
|
|
||||||
Bearbeiten
|
|
||||||
</Link>
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button onClick={() => handleRemovePlayer(player.id)} variant="destructive">
|
||||||
onClick={() => handleRemovePlayer(player.id)}
|
|
||||||
variant="destructive"
|
|
||||||
>
|
|
||||||
Entfernen
|
Entfernen
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -147,7 +200,7 @@ const TeamDetail = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center mt-8">
|
||||||
<Button asChild>
|
<Button asChild>
|
||||||
<Link to={`/admin/teams/${id}/add-player`}>
|
<Link to={`/admin/teams/${id}/add-player`}>
|
||||||
+ Spieler hinzufügen
|
+ Spieler hinzufügen
|
||||||
@ -156,8 +209,6 @@ const TeamDetail = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TeamDetail;
|
export default TeamDetail;
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import TeamCreate from "./admin/TeamCreate";
|
|||||||
import TeamDetail from "./admin/TeamDetail";
|
import TeamDetail from "./admin/TeamDetail";
|
||||||
import TeamAddPlayer from "./admin/TeamAddPlayer";
|
import TeamAddPlayer from "./admin/TeamAddPlayer";
|
||||||
import PlayerEdit from "./admin/PlayerEdit";
|
import PlayerEdit from "./admin/PlayerEdit";
|
||||||
|
import PlayerManagementPage from "./admin/PlayerManagementPage";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -69,6 +70,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|||||||
<Route path="/admin/teams/:id" element={<PrivateRoute><Layout><TeamDetail /></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/teams/:id/add-player" element={<PrivateRoute><Layout><TeamAddPlayer /></Layout></PrivateRoute>} />
|
||||||
<Route path="/admin/players/:id/edit" element={<PrivateRoute><Layout><PlayerEdit /></Layout></PrivateRoute>} />
|
<Route path="/admin/players/:id/edit" element={<PrivateRoute><Layout><PlayerEdit /></Layout></PrivateRoute>} />
|
||||||
|
<Route path="/admin/players" element={<PrivateRoute><Layout><PlayerManagementPage /></Layout></PrivateRoute>} />
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user