Some checks are pending
Deploy Volleyball Dev / deploy (push) Waiting to run
139 lines
4.4 KiB
TypeScript
139 lines
4.4 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import DOMPurify from "dompurify";
|
|
|
|
const apiBase = import.meta.env.VITE_API_URL;
|
|
|
|
type NewsItem = {
|
|
id: number;
|
|
title: string;
|
|
description: string;
|
|
image_url: string;
|
|
team: string;
|
|
created_at: string;
|
|
};
|
|
|
|
const AlleNeuigkeitenPage = () => {
|
|
const [news, setNews] = useState<NewsItem[]>([]);
|
|
const [expandedIds, setExpandedIds] = useState<number[]>([]);
|
|
const [activeCardId, setActiveCardId] = useState<number | null>(null);
|
|
const [selectedTeam, setSelectedTeam] = useState<string>("Alle Teams");
|
|
|
|
const defaultImage = "/images/tgl-ball.png";
|
|
|
|
//Filtern nach Teams
|
|
const teams = Array.from(new Set(news.map((n) => n.team).filter(Boolean)));
|
|
|
|
const filteredNews = selectedTeam === "Alle Teams" ? news : news.filter((n) => n.team === selectedTeam);
|
|
|
|
const groupedNews = filteredNews.reduce((acc: Record<string, NewsItem[]>, item) => {
|
|
const year = new Date(item.created_at).getFullYear().toString();
|
|
if (!acc[year]) acc[year] = [];
|
|
acc[year].push(item);
|
|
return acc;
|
|
}, {});
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
fetch(`${apiBase}/api/news`)
|
|
.then((res) => res.json())
|
|
.then((data) => setNews(data))
|
|
.catch((err) => console.error("Fehler beim Laden der News:", err));
|
|
}, []);
|
|
|
|
|
|
const toggleCard = (id: number) => {
|
|
setActiveCardId((prev) => (prev === id ? null : id));
|
|
};
|
|
|
|
|
|
|
|
//Toggle der Cards
|
|
const toggleExpand = (id: number) => {
|
|
setExpandedIds((prev) =>
|
|
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="max-w-5xl mx-auto px-4 py-12">
|
|
<h1 className="text-3xl font-bold text-center mb-8 text-frog-600">Alle Neuigkeiten</h1>
|
|
|
|
<div className="mb-8 flex justify-center">
|
|
<select
|
|
value={selectedTeam}
|
|
onChange={(e) => setSelectedTeam(e.target.value)}
|
|
className="border border-gray-300 rounded-md px-4 py-2 text-gray-700"
|
|
>
|
|
<option value="Alle Teams">Alle Teams</option>
|
|
{teams.map((team) => (
|
|
<option key={team} value={team}>
|
|
{team}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
|
|
{/* News nach Jahren gruppiert */}
|
|
{Object.entries(groupedNews).map(([year, items]) => (
|
|
<div key={year} className="mb-12">
|
|
<h2 className="text-2xl font-bold text-frog-500 mb-4">{year}</h2>
|
|
<div className="grid md:grid-cols-2 gap-6">
|
|
{items.map((item) => (
|
|
<Card
|
|
key={item.id}
|
|
className={`overflow-hidden transition-all duration-500 ease-in-out ${
|
|
activeCardId === item.id
|
|
? "col-span-2 lg:col-span-2 xl:col-span-2 scale-[1.02] shadow-xl"
|
|
: ""
|
|
}`}
|
|
>
|
|
<div className="h-48 overflow-hidden">
|
|
<img
|
|
src={
|
|
item.image_url
|
|
? `${apiBase}${item.image_url}`
|
|
: defaultImage
|
|
}
|
|
alt={item.title}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
<CardHeader>
|
|
<CardTitle>{item.title}</CardTitle>
|
|
<CardDescription>{new Date(item.created_at).toLocaleDateString("de-DE")}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div
|
|
className={`prose max-w-none text-gray-700 transition-all duration-300 prose-p:my-1 prose-a:text-frog-600 ${
|
|
activeCardId === item.id ? "" : "line-clamp-3 overflow-hidden"
|
|
}`}
|
|
>
|
|
<div
|
|
dangerouslySetInnerHTML={{
|
|
__html: DOMPurify.sanitize(item.description),
|
|
}}
|
|
/>
|
|
</div>
|
|
<button
|
|
onClick={() => toggleCard(item.id)}
|
|
className="mt-2 text-frog-600 text-sm font-medium hover:underline"
|
|
>
|
|
{activeCardId === item.id ? "Weniger anzeigen" : "Weiterlesen"}
|
|
</button>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AlleNeuigkeitenPage;
|