Teamsection finalized
Some checks are pending
Deploy Volleyball Dev / deploy (push) Waiting to run

This commit is contained in:
Marc Wieland 2025-04-29 14:25:44 +02:00
parent 1d5dea8ff1
commit 19bd2ee41d
2 changed files with 186 additions and 171 deletions

View File

@ -1,55 +1,30 @@
import { useKeenSlider } from "keen-slider/react"; import { useKeenSlider } from "keen-slider/react";
import { useRef } from "react"; import { useRef, useEffect, useState } from "react";
import { ChevronLeft, ChevronRight, Users } from "lucide-react"; import { ChevronLeft, ChevronRight, Users } from "lucide-react";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {Link} from "react-router-dom"; import { Link } from "react-router-dom";
const apiBase = import.meta.env.VITE_API_URL;
type Team = {
id: number;
name: string;
liga?: string;
beschreibung?: string;
trainingszeiten?: string;
};
const TeamSection = () => { const TeamSection = () => {
const teams = [ const [teams, setTeams] = useState<Team[]>([]);
{ const [loading, setLoading] = useState(true);
id: 1,
name: "Damen I",
league: "Landesliga Nord",
description: "Unsere erste Damenmannschaft spielt in der Landesliga Nord und konnte in der letzten Saison den 3. Platz erreichen.",
trainingTimes: "Di & Do 19:00 - 21:00 Uhr"
},
{
id: 2,
name: "Herren I",
league: "Bezirksliga",
description: "Das Herrenteam kämpft in der Bezirksliga und hat das Ziel, in dieser Saison den Aufstieg zu schaffen.",
trainingTimes: "Mo & Mi 20:00 - 22:00 Uhr"
},
{
id: 3,
name: "Jugend U16",
league: "Jugendliga",
description: "Unsere Nachwuchsspielerinnen und -spieler entwickeln sich prächtig und nehmen an der Jugendliga teil.",
trainingTimes: "Fr 17:00 - 19:00 Uhr"
},
{
id: 4,
name: "Freizeitgruppe",
league: "Hobby",
description: "Für alle, die Volleyball zum Spaß spielen möchten. Anfänger und Fortgeschrittene sind willkommen!",
trainingTimes: "Mo 18:00 - 20:00 Uhr"
},
{
id: 5,
name: "U12",
league: "Jugendliga",
description: "Unsere kleinsten Stars sammeln erste Spielerfahrung in der U12-Jugendliga.",
trainingTimes: "Mi 16:00 - 17:30 Uhr"
},
{
id: 6,
name: "Mixed-Team",
league: "Hobbyliga",
description: "Spaß, Gemeinschaft und gemischte Teams unser Mixed-Team steht für Vielfalt!",
trainingTimes: "Fr 19:00 - 21:00 Uhr"
}
];
const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({ const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({
slides: { slides: {
@ -69,58 +44,87 @@ const TeamSection = () => {
}, },
}); });
useEffect(() => {
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);
} finally {
setLoading(false);
}
};
fetchTeams();
}, []);
return ( return (
<section id="team" className="py-16"> <section id="team" className="py-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-3xl font-bold text-gray-900">Unsere Teams</h2> <h2 className="text-3xl font-bold text-gray-900">Unsere Teams</h2>
<p className="mt-4 text-xl text-gray-600">Von Anfängern bis Leistungssport für jeden ist etwas dabei</p> <p className="mt-4 text-xl text-gray-600">
Von Anfängern bis Leistungssport für jeden ist etwas dabei
</p>
</div> </div>
<div className="relative"> {loading ? (
{/* Navigation Buttons */} <p className="text-center text-gray-500">Lade Teams...</p>
<button ) : (
onClick={() => slider.current?.prev()} <div className="relative">
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 bg-white rounded-full p-2 shadow-md hover:bg-frog-100" {/* Navigation Buttons */}
> <button
<ChevronLeft className="text-frog-600" /> onClick={() => slider.current?.prev()}
</button> className="absolute left-0 top-1/2 -translate-y-1/2 z-10 bg-white rounded-full p-2 shadow-md hover:bg-frog-100"
<button >
onClick={() => slider.current?.next()} <ChevronLeft className="text-frog-600" />
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 bg-white rounded-full p-2 shadow-md hover:bg-frog-100" </button>
> <button
<ChevronRight className="text-frog-600" /> onClick={() => slider.current?.next()}
</button> className="absolute right-0 top-1/2 -translate-y-1/2 z-10 bg-white rounded-full p-2 shadow-md hover:bg-frog-100"
>
<ChevronRight className="text-frog-600" />
</button>
<div ref={sliderRef} className="keen-slider"> <div ref={sliderRef} className="keen-slider">
{teams.map((team) => ( {teams.map((team) => (
<div key={team.id} className="keen-slider__slide"> <div key={team.id} className="keen-slider__slide">
<Card className="h-full hover:shadow-lg transition-shadow border-t-4 border-t-frog-500"> <Card className="h-full hover:shadow-lg transition-shadow border-t-4 border-t-frog-500">
<CardHeader> <CardHeader>
<CardTitle className="flex items-center"> <CardTitle className="flex items-center">
<Users className="h-5 w-5 mr-2 text-frog-500" /> <Users className="h-5 w-5 mr-2 text-frog-500" />
{team.name} {team.name}
</CardTitle> </CardTitle>
<CardDescription>{team.league}</CardDescription> <CardDescription>{team.liga}</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-sm text-gray-600 mb-4">{team.description}</p> <p className="text-sm text-gray-600 mb-4">
<div className="text-sm font-medium"> {team.beschreibung ?? "Keine Beschreibung vorhanden."}
<span className="text-frog-600">Training:</span> {team.trainingTimes} </p>
</div> <div className="text-sm font-medium">
</CardContent> <span className="text-frog-600">Training:</span>{" "}
<CardFooter> {team.trainingszeiten ?? "Nicht angegeben"}
<Link to={`/teams/${team.id}`} className="w-full"> </div>
<Button variant="outline" size="sm" className="w-full border-frog-500 text-frog-600 hover:bg-frog-50"> </CardContent>
Team Details <CardFooter>
</Button> <Link to={`/teams/${team.id}`} className="w-full">
</Link> <Button
</CardFooter> variant="outline"
</Card> size="sm"
</div> className="w-full border-frog-500 text-frog-600 hover:bg-frog-50"
))} >
Team Details
</Button>
</Link>
</CardFooter>
</Card>
</div>
))}
</div>
</div> </div>
</div> )}
</div> </div>
</section> </section>
); );

View File

@ -1,95 +1,97 @@
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Carousel } from "react-responsive-carousel"; // Neue Library! import { Carousel } from "react-responsive-carousel";
import "react-responsive-carousel/lib/styles/carousel.min.css"; // Wichtig! import "react-responsive-carousel/lib/styles/carousel.min.css";
const teamData = [ const apiBase = import.meta.env.VITE_API_URL;
{
id: "1", type Player = {
name: "Damen I", id: number;
league: "Landesliga Nord", name: string;
description: "Unsere erste Damenmannschaft spielt auf hohem Niveau und liebt den Volleyballsport.", position: string;
trainingTimes: "Dienstag & Donnerstag 19:00 - 21:00 Uhr", nickname?: string;
images: [ image_url?: string;
"/images/damen1-1.jpg", };
"/images/damen1-2.jpg",
"/images/damen1-3.jpg", type Team = {
], id: number;
canJoin: false, name: string;
players: [ liga?: string;
{ name: "Anna", position: "Außenangriff", nickname: "Speedy", image: "/images/anna.jpg" }, beschreibung?: string;
{ name: "Lea", position: "Libera", nickname: "Wall-E", image: "/images/lea.jpg" }, trainingszeiten?: string;
{ name: "Sophie", position: "Zuspiel", nickname: "Magic", image: "/images/sophie.jpg" }, sucht_spieler?: boolean;
], karussell_bilder?: string[]; // Wichtig: als Array erwartet!
}, players: Player[];
{ };
id: "2",
name: "Herren I",
league: "Landesliga",
description: "Das Team der Herren 1 ist tritt in der Saison 25/26 in der Landesliga beim NVV an.",
trainingTimes: "Montag & Freitag 20:00 - 22:00 Uhr",
images: [
"/images/players/herren1/carousel/carousel2.jpg",
"/images/players/herren1/carousel/carousel2.jpg",
"/images/players/herren1/carousel/carousel2.jpg",
],
canJoin: true,
players: [
{ name: "Samuel", position: "Mittelblock", nickname: "BeSamu", image: "/images/players/herren1/samuel.jpg" },
{ name: "Sten", position: "Außen/Annahme", nickname: "Stenislav", image: "/images/players/herren1/sten.jpg" },
{ name: "David", position: "Mittelblock", nickname: "Blocki", image: "/images/players/herren1/david.jpg" },
{ name: "Erik", position: "Libero", nickname: "Erika", image: "/images/players/herren1/erik.jpg" },
{ name: "Lasse", position: "Außen/Annahme", nickname: "Lass es!", image: "/images/players/herren1/lasse.jpg" },
{ name: "Peter", position: "Zuspieler", nickname: "-", image: "/images/players/herren1/peter.jpg" },
{ name: "Tony", position: "Zuspieler", nickname: "Tony Mahony", image: "/images/players/herren1/tony.jpg" },
{ name: "Marc", position: "Diagonal", nickname: "Marci", image: "/images/players/herren1/marc.jpg" },
{ name: "Phillip", position: "Libero", nickname: "Hifi", image: "/images/players/herren1/phillip.jpg" },
{ name: "Kathrin", position: "Trainerin", nickname: "Mutti", image: "/images/players/herren1/phillip.jpg" },
{ name: "DU?", position: "Alles", },
],
},
];
const TeamDetailPage = () => { const TeamDetailPage = () => {
const { id } = useParams(); const { id } = useParams<{ id: string }>();
const team = teamData.find((t) => t.id === id); const [team, setTeam] = useState<Team | null>(null);
const [loading, setLoading] = useState(true);
if (!team) return <p>Team nicht gefunden 🐸</p>; useEffect(() => {
const fetchTeam = async () => {
try {
const res = await fetch(`${apiBase}/api/teams/${id}`);
const data = await res.json();
// Falls karussell_bilder als String (JSON) kommt → parsen
if (typeof data.karussell_bilder === "string") {
data.karussell_bilder = JSON.parse(data.karussell_bilder);
}
setTeam(data);
} catch (err) {
console.error("Fehler beim Laden des Teams:", err);
} finally {
setLoading(false);
}
};
fetchTeam();
}, [id]);
if (loading) return <p className="text-center py-12">Lade Teamdaten...</p>;
if (!team) return <p className="text-center py-12">Team nicht gefunden 🐸</p>;
return ( return (
<div className="max-w-7xl mx-auto py-8 px-4"> <div className="max-w-7xl mx-auto py-8 px-4">
{/* Karussell */} {/* Karussell */}
<div className="mb-8"> {team.karussell_bilder && team.karussell_bilder.length > 0 && (
<Carousel <div className="mb-8">
showThumbs={false} <Carousel
showStatus={false} showThumbs={false}
infiniteLoop showStatus={false}
autoPlay infiniteLoop
interval={4000} autoPlay
dynamicHeight={false} interval={4000}
className="rounded-lg overflow-hidden" className="rounded-lg overflow-hidden"
> >
{team.images.map((src, index) => ( {team.karussell_bilder.map((src, index) => (
<div key={index} className="h-96"> <div key={index} className="h-96">
<img <img
src={src} src={`${apiBase}${src}`}
alt={`Team ${team.name} Bild ${index + 1}`} alt={`Bild ${index + 1}`}
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
</div> </div>
))} ))}
</Carousel> </Carousel>
</div> </div>
)}
{/* Team Info */} {/* Team Info */}
<div className="text-center mb-12"> <div className="text-center mb-12">
<h1 className="text-4xl font-bold text-frog-600">{team.name}</h1> <h1 className="text-4xl font-bold text-frog-600">{team.name}</h1>
<p className="text-lg text-gray-700 mt-2">{team.league}</p> <p className="text-lg text-gray-700 mt-2">{team.liga}</p>
<p className="text-gray-600 mt-4">{team.description}</p> <p className="text-gray-600 mt-4">{team.beschreibung}</p>
<p className="text-sm text-frog-700 font-medium mt-2">Training: {team.trainingTimes}</p> <p className="text-sm text-frog-700 font-medium mt-2">
Training: {team.trainingszeiten || "Nicht angegeben"}
</p>
</div> </div>
{/*Join Button */}
{team.canJoin && ( {/* Join Button */}
{team.sucht_spieler && (
<div className="mt-6 mb-6 flex justify-center"> <div className="mt-6 mb-6 flex justify-center">
<button className="bg-frog-500 hover:bg-frog-600 text-white font-semibold py-2 px-6 rounded-full shadow-lg transition"> <button className="bg-frog-500 hover:bg-frog-600 text-white font-semibold py-2 px-6 rounded-full shadow-lg transition">
Mitmachen Mitmachen
@ -99,16 +101,25 @@ const TeamDetailPage = () => {
{/* Spielerübersicht */} {/* Spielerübersicht */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
{team.players.map((player, idx) => ( {team.players.map((player) => (
<div key={idx} className="bg-white rounded-lg shadow-md p-4 flex flex-col items-center text-center hover:shadow-lg transition"> <div
key={player.id}
className="bg-white rounded-lg shadow-md p-4 flex flex-col items-center text-center hover:shadow-lg transition"
>
<img <img
src={player.image} src={
player.image_url
? `${apiBase}${player.image_url}`
: "/images/default-player.png"
}
alt={player.name} alt={player.name}
className="w-full h-108 object-cover rounded-lg mb-4" className="w-full h-108 object-cover rounded-lg mb-4"
/> />
<h3 className="text-xl font-semibold text-frog-700">{player.name}</h3> <h3 className="text-xl font-semibold text-frog-700">{player.name}</h3>
<p className="text-gray-500">{player.position}</p> <p className="text-gray-500">{player.position}</p>
<p className="text-sm text-frog-500 mt-1">{player.nickname}</p> {player.nickname && (
<p className="text-sm text-frog-500 mt-1">{player.nickname}</p>
)}
</div> </div>
))} ))}
</div> </div>