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 { useRef } from "react";
import { useRef, useEffect, useState } from "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 {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 teams = [
{
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 [teams, setTeams] = useState<Team[]>([]);
const [loading, setLoading] = useState(true);
const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({
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 (
<section id="team" className="py-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<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 className="relative">
{/* Navigation Buttons */}
<button
onClick={() => slider.current?.prev()}
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"
>
<ChevronLeft className="text-frog-600" />
</button>
<button
onClick={() => slider.current?.next()}
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>
{loading ? (
<p className="text-center text-gray-500">Lade Teams...</p>
) : (
<div className="relative">
{/* Navigation Buttons */}
<button
onClick={() => slider.current?.prev()}
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"
>
<ChevronLeft className="text-frog-600" />
</button>
<button
onClick={() => slider.current?.next()}
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">
{teams.map((team) => (
<div key={team.id} className="keen-slider__slide">
<Card className="h-full hover:shadow-lg transition-shadow border-t-4 border-t-frog-500">
<CardHeader>
<CardTitle className="flex items-center">
<Users className="h-5 w-5 mr-2 text-frog-500" />
{team.name}
</CardTitle>
<CardDescription>{team.league}</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-gray-600 mb-4">{team.description}</p>
<div className="text-sm font-medium">
<span className="text-frog-600">Training:</span> {team.trainingTimes}
</div>
</CardContent>
<CardFooter>
<Link to={`/teams/${team.id}`} className="w-full">
<Button variant="outline" size="sm" className="w-full border-frog-500 text-frog-600 hover:bg-frog-50">
Team Details
</Button>
</Link>
</CardFooter>
</Card>
</div>
))}
<div ref={sliderRef} className="keen-slider">
{teams.map((team) => (
<div key={team.id} className="keen-slider__slide">
<Card className="h-full hover:shadow-lg transition-shadow border-t-4 border-t-frog-500">
<CardHeader>
<CardTitle className="flex items-center">
<Users className="h-5 w-5 mr-2 text-frog-500" />
{team.name}
</CardTitle>
<CardDescription>{team.liga}</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-gray-600 mb-4">
{team.beschreibung ?? "Keine Beschreibung vorhanden."}
</p>
<div className="text-sm font-medium">
<span className="text-frog-600">Training:</span>{" "}
{team.trainingszeiten ?? "Nicht angegeben"}
</div>
</CardContent>
<CardFooter>
<Link to={`/teams/${team.id}`} className="w-full">
<Button
variant="outline"
size="sm"
className="w-full border-frog-500 text-frog-600 hover:bg-frog-50"
>
Team Details
</Button>
</Link>
</CardFooter>
</Card>
</div>
))}
</div>
</div>
</div>
)}
</div>
</section>
);

View File

@ -1,95 +1,97 @@
import { useParams } from "react-router-dom";
import { useEffect, useState } from "react";
import { Carousel } from "react-responsive-carousel"; // Neue Library!
import "react-responsive-carousel/lib/styles/carousel.min.css"; // Wichtig!
import { Carousel } from "react-responsive-carousel";
import "react-responsive-carousel/lib/styles/carousel.min.css";
const teamData = [
{
id: "1",
name: "Damen I",
league: "Landesliga Nord",
description: "Unsere erste Damenmannschaft spielt auf hohem Niveau und liebt den Volleyballsport.",
trainingTimes: "Dienstag & Donnerstag 19:00 - 21:00 Uhr",
images: [
"/images/damen1-1.jpg",
"/images/damen1-2.jpg",
"/images/damen1-3.jpg",
],
canJoin: false,
players: [
{ name: "Anna", position: "Außenangriff", nickname: "Speedy", image: "/images/anna.jpg" },
{ name: "Lea", position: "Libera", nickname: "Wall-E", image: "/images/lea.jpg" },
{ name: "Sophie", position: "Zuspiel", nickname: "Magic", image: "/images/sophie.jpg" },
],
},
{
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 apiBase = import.meta.env.VITE_API_URL;
type Player = {
id: number;
name: string;
position: string;
nickname?: string;
image_url?: string;
};
type Team = {
id: number;
name: string;
liga?: string;
beschreibung?: string;
trainingszeiten?: string;
sucht_spieler?: boolean;
karussell_bilder?: string[]; // Wichtig: als Array erwartet!
players: Player[];
};
const TeamDetailPage = () => {
const { id } = useParams();
const team = teamData.find((t) => t.id === id);
const { id } = useParams<{ id: string }>();
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 (
<div className="max-w-7xl mx-auto py-8 px-4">
{/* Karussell */}
<div className="mb-8">
<Carousel
showThumbs={false}
showStatus={false}
infiniteLoop
autoPlay
interval={4000}
dynamicHeight={false}
className="rounded-lg overflow-hidden"
>
{team.images.map((src, index) => (
<div key={index} className="h-96">
<img
src={src}
alt={`Team ${team.name} Bild ${index + 1}`}
className="w-full h-full object-cover"
/>
</div>
))}
</Carousel>
</div>
{team.karussell_bilder && team.karussell_bilder.length > 0 && (
<div className="mb-8">
<Carousel
showThumbs={false}
showStatus={false}
infiniteLoop
autoPlay
interval={4000}
className="rounded-lg overflow-hidden"
>
{team.karussell_bilder.map((src, index) => (
<div key={index} className="h-96">
<img
src={`${apiBase}${src}`}
alt={`Bild ${index + 1}`}
className="w-full h-full object-cover"
/>
</div>
))}
</Carousel>
</div>
)}
{/* Team Info */}
<div className="text-center mb-12">
<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-gray-600 mt-4">{team.description}</p>
<p className="text-sm text-frog-700 font-medium mt-2">Training: {team.trainingTimes}</p>
<p className="text-lg text-gray-700 mt-2">{team.liga}</p>
<p className="text-gray-600 mt-4">{team.beschreibung}</p>
<p className="text-sm text-frog-700 font-medium mt-2">
Training: {team.trainingszeiten || "Nicht angegeben"}
</p>
</div>
{/*Join Button */}
{team.canJoin && (
{/* Join Button */}
{team.sucht_spieler && (
<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">
Mitmachen
@ -99,16 +101,25 @@ const TeamDetailPage = () => {
{/* Spielerübersicht */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
{team.players.map((player, idx) => (
<div key={idx} className="bg-white rounded-lg shadow-md p-4 flex flex-col items-center text-center hover:shadow-lg transition">
{team.players.map((player) => (
<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
src={player.image}
src={
player.image_url
? `${apiBase}${player.image_url}`
: "/images/default-player.png"
}
alt={player.name}
className="w-full h-108 object-cover rounded-lg mb-4"
/>
<h3 className="text-xl font-semibold text-frog-700">{player.name}</h3>
<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>