Added scoreboard integration
Some checks failed
Deploy Volleyball CMS / deploy (push) Failing after 13s
Some checks failed
Deploy Volleyball CMS / deploy (push) Failing after 13s
This commit is contained in:
parent
3aff4b89f9
commit
8b516d6f68
@ -1 +1 @@
|
||||
VITE_API_URL=http://localhost:5000
|
||||
VITE_API_URL=http://192.168.50.65:3000
|
||||
|
||||
30
src/App.tsx
30
src/App.tsx
@ -14,20 +14,22 @@ const queryClient = new QueryClient();
|
||||
|
||||
|
||||
const App = () => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TooltipProvider>
|
||||
<Toaster />
|
||||
<Sonner />
|
||||
<BrowserRouter>
|
||||
<ScrollToTop />
|
||||
<Routes>
|
||||
<Route path="/" element={<Index />} />
|
||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</TooltipProvider>
|
||||
</QueryClientProvider>
|
||||
<div className="min-h-screen px-4 py-8 max-w-7xl mx-auto bg-[hsl(var(--background))] text-[hsl(var(--foreground))] transition-colors duration-300">
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TooltipProvider>
|
||||
<Toaster />
|
||||
<Sonner />
|
||||
<BrowserRouter>
|
||||
<ScrollToTop />
|
||||
<Routes>
|
||||
<Route path="/" element={<Index />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</TooltipProvider>
|
||||
</QueryClientProvider>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
export default App;
|
||||
|
||||
@ -38,6 +38,9 @@ const TeamDetail = () => {
|
||||
const [players, setPlayers] = useState<Player[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [spielelink, setSpielelink] = useState("");
|
||||
const [scraper_Identifier, setScraperIdentifier] = useState("");
|
||||
|
||||
const fetchTeam = async () => {
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/api/teams/${id}`);
|
||||
@ -57,6 +60,10 @@ const TeamDetail = () => {
|
||||
setBeschreibung(data.beschreibung ?? "");
|
||||
setTabellenlink(data.tabellenlink ?? "");
|
||||
setPlayers(data.players ?? []);
|
||||
|
||||
setSpielelink(data.spielelink ?? "");
|
||||
setScraperIdentifier(data.scraper_identifier ?? "");
|
||||
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Laden des Teams:", err);
|
||||
} finally {
|
||||
@ -87,6 +94,8 @@ const TeamDetail = () => {
|
||||
teamfarben,
|
||||
beschreibung,
|
||||
tabellenlink,
|
||||
spielelink,
|
||||
scraper_Identifier
|
||||
}),
|
||||
});
|
||||
|
||||
@ -166,6 +175,17 @@ const TeamDetail = () => {
|
||||
<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" />
|
||||
<Input
|
||||
value={spielelink}
|
||||
onChange={(e) => setSpielelink(e.target.value)}
|
||||
placeholder="Link zu den Spielen"
|
||||
/>
|
||||
<Input
|
||||
value={scraper_Identifier}
|
||||
onChange={(e) => setScraperIdentifier(e.target.value)}
|
||||
placeholder="Scraper-Identifier"
|
||||
/>
|
||||
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
|
||||
@ -100,7 +100,7 @@ const TeamSection = () => {
|
||||
<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 bg-white dark:bg-neutral-800 text-gray-800 dark:text-gray-100 border border-gray-200 dark:border-neutral-700 border-t-4 border-t-frog-500 dark:border-t-frog-400">
|
||||
<Card className="flex flex-col h-full hover:shadow-lg transition-shadow bg-white dark:bg-neutral-800 text-gray-800 dark:text-gray-100 border border-gray-200 dark:border-neutral-700 border-t-4 border-t-frog-500 dark:border-t-frog-400">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<Users className="h-5 w-5 mr-2 text-frog-500 dark:text-frog-400" />
|
||||
@ -110,7 +110,7 @@ const TeamSection = () => {
|
||||
{team.liga}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent className="flex-grow">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 mb-4">
|
||||
{team.beschreibung ?? "Keine Beschreibung vorhanden."}
|
||||
</p>
|
||||
@ -119,7 +119,8 @@ const TeamSection = () => {
|
||||
{team.trainingszeiten ?? "Nicht angegeben"}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
|
||||
<CardFooter className="mt-auto">
|
||||
<Link to={`/teams/${team.id}`} className="w-full">
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -130,6 +131,7 @@ const TeamSection = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</CardFooter>
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@ -32,6 +32,12 @@ const TeamDetailPage = () => {
|
||||
const [team, setTeam] = useState<Team | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [liveData, setLiveData] = useState<{
|
||||
scoreboard: any[];
|
||||
spiele: any[];
|
||||
last_updated: string;
|
||||
} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTeam = async () => {
|
||||
try {
|
||||
@ -43,8 +49,13 @@ const TeamDetailPage = () => {
|
||||
}
|
||||
|
||||
setTeam(data);
|
||||
|
||||
// Live-Daten abrufen
|
||||
const liveRes = await fetch(`${apiBase}/api/team-live/${id}`);
|
||||
const liveJson = await liveRes.json();
|
||||
setLiveData(liveJson);
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Laden des Teams:", err);
|
||||
console.error("Fehler beim Laden:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -55,7 +66,7 @@ const TeamDetailPage = () => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto py-8 px-4">
|
||||
<div className="max-w-7xl mx-auto py-8 px-4 bg-white dark:bg-neutral-900 text-gray-900 dark:text-gray-100 transition-colors duration-300">
|
||||
<div className="mb-8">
|
||||
<Skeleton height={400} borderRadius={12} />
|
||||
</div>
|
||||
@ -64,10 +75,10 @@ const TeamDetailPage = () => {
|
||||
<h1 className="text-4xl font-bold text-frog-600">
|
||||
<Skeleton width={220} />
|
||||
</h1>
|
||||
<p className="text-lg text-gray-700 mt-2">
|
||||
<p className="text-lg text-gray-700 dark:text-gray-300 mt-2">
|
||||
<Skeleton width={160} />
|
||||
</p>
|
||||
<p className="mt-4 text-gray-600">
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-400">
|
||||
<Skeleton count={2} />
|
||||
</p>
|
||||
<p className="text-sm text-frog-700 font-medium mt-2">
|
||||
@ -77,7 +88,7 @@ const TeamDetailPage = () => {
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
||||
{[...Array(3)].map((_, i) => (
|
||||
<div key={i} className="bg-white rounded-lg shadow-md p-4">
|
||||
<div key={i} className="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-4">
|
||||
<Skeleton height={200} className="mb-4" />
|
||||
<Skeleton width={`60%`} height={24} />
|
||||
<Skeleton width={`40%`} height={18} />
|
||||
@ -89,7 +100,7 @@ const TeamDetailPage = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (!team) return <p className="text-center py-12">Team nicht gefunden 🐸</p>;
|
||||
if (!team) return <p className="text-center py-12 dark:text-white">Team nicht gefunden 🐸</p>;
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto py-8 px-4">
|
||||
@ -119,14 +130,14 @@ const TeamDetailPage = () => {
|
||||
|
||||
{/* 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.liga}</p>
|
||||
<p className="text-gray-600 mt-4">{team.beschreibung}</p>
|
||||
<p className="text-sm text-frog-700 font-medium mt-2">
|
||||
<h1 className="text-4xl font-bold text-frog-600 dark:text-frog-400">{team.name}</h1>
|
||||
<p className="text-lg text-gray-700 dark:text-gray-300 mt-2">{team.liga}</p>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-4">{team.beschreibung}</p>
|
||||
<p className="text-sm text-frog-700 dark:text-frog-500 font-medium mt-2">
|
||||
Training: {team.trainingszeiten || "Nicht angegeben"}
|
||||
</p>
|
||||
{team.trainingsort && (
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
Ort: {team.trainingsort}
|
||||
</p>
|
||||
)}
|
||||
@ -141,12 +152,85 @@ const TeamDetailPage = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{liveData && (
|
||||
<div className="mt-12">
|
||||
<h2 className="text-2xl font-bold text-frog-600 dark:text-frog-400 mb-4">🏆 Aktuelle Tabelle</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full bg-white dark:bg-neutral-800 rounded shadow">
|
||||
<thead>
|
||||
<tr className="bg-frog-100 dark:bg-frog-700 text-left text-sm font-semibold text-gray-700 dark:text-white">
|
||||
<th className="p-2">Platz</th>
|
||||
<th className="p-2">Team</th>
|
||||
<th className="p-2">Spiele</th>
|
||||
<th className="p-2">Siege</th>
|
||||
<th className="p-2">Sätze</th>
|
||||
<th className="p-2">Punkte</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{liveData.scoreboard.map((row, i) => {
|
||||
const isTop = row.platz === 1;
|
||||
const isBottom = row.platz === liveData.scoreboard.length;
|
||||
|
||||
return (
|
||||
<tr key={i} className="border-t dark:border-neutral-700">
|
||||
<td className="p-2 font-medium flex items-center gap-1">
|
||||
{row.platz}
|
||||
{isTop && <span className="text-green-600">▲</span>}
|
||||
{isBottom && <span className="text-red-600">▼</span>}
|
||||
</td>
|
||||
<td className="p-2">{row.team}</td>
|
||||
<td className="p-2">{row.spiele}</td>
|
||||
<td className="p-2">{row.siege}</td>
|
||||
<td className="p-2">{row.saetze}</td>
|
||||
<td className="p-2">{row.punkte}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold text-frog-600 dark:text-frog-400 mt-10 mb-4">📅 Spiele</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full bg-white dark:bg-neutral-800 rounded shadow">
|
||||
<thead>
|
||||
<tr className="bg-frog-100 dark:bg-frog-700 text-left text-sm font-semibold text-gray-700 dark:text-white">
|
||||
<th className="p-2">Datum</th>
|
||||
<th className="p-2">Team 1</th>
|
||||
<th className="p-2">Team 2</th>
|
||||
<th className="p-2">Ergebnis</th>
|
||||
<th className="p-2">Satzverlauf</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{liveData.spiele.map((spiel, i) => (
|
||||
<tr key={i} className="border-t dark:border-neutral-700">
|
||||
<td className="p-2">{spiel.datum}</td>
|
||||
<td className="p-2">{spiel.team1}</td>
|
||||
<td className="p-2">{spiel.team2}</td>
|
||||
<td className="p-2">{spiel.ergebnis}</td>
|
||||
<td className="p-2">{spiel.satzverlauf}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-4">
|
||||
Zuletzt aktualisiert: {new Date(liveData.last_updated).toLocaleString("de-DE")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Spielerübersicht */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
||||
{team.players.map((player) => (
|
||||
<div
|
||||
key={player.id}
|
||||
className="bg-white rounded-lg shadow-md p-4 flex flex-col items-center text-center transform transition duration-300 hover:-translate-y-1 hover:shadow-xl"
|
||||
className="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-4 flex flex-col items-center text-center transform transition duration-300 hover:-translate-y-1 hover:shadow-xl"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
@ -157,12 +241,12 @@ const TeamDetailPage = () => {
|
||||
alt={player.name}
|
||||
className="w-full h-108 object-cover rounded-lg mb-4"
|
||||
/>
|
||||
<h3 className="text-xl font-semibold text-frog-700">
|
||||
<h3 className="text-xl font-semibold text-frog-700 dark:text-frog-400">
|
||||
{player.name}
|
||||
</h3>
|
||||
<p className="text-gray-500">{player.position}</p>
|
||||
<p className="text-gray-500 dark:text-gray-300">{player.position}</p>
|
||||
{player.nickname && (
|
||||
<p className="text-sm text-frog-500 mt-1">„{player.nickname}“</p>
|
||||
<p className="text-sm text-frog-500 dark:text-frog-300 mt-1">„{player.nickname}“</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user