From ad4fe7c29a74194f5cb5789d2392380a51e0b1f9 Mon Sep 17 00:00:00 2001 From: Linrador Date: Mon, 4 Aug 2025 23:40:53 +0200 Subject: [PATCH] update --- src/app/admin/[tab]/page.tsx | 2 +- src/app/api/matches/route.ts | 70 +++---- src/app/api/team/kick/route.ts | 99 ++++----- src/app/api/team/leave/route.ts | 131 ++++++------ src/app/api/user/[steamId]/matches/route.ts | 112 +++++----- src/app/components/CommunityMatchList.tsx | 197 ++++++++++++++++++ src/app/components/DroppableZone.tsx | 28 ++- src/app/components/MatchDetails.tsx | 11 +- src/app/components/MatchList.tsx | 121 ----------- src/app/components/Switch.tsx | 2 +- src/app/components/TeamCard.tsx | 5 +- src/app/components/TeamMemberView.tsx | 7 +- src/app/components/UserMatchesTable.tsx | 144 ------------- .../components/admin/MatchesAdminManager.tsx | 156 +------------- .../components/admin/teams/AdminTeamsView.tsx | 3 +- .../[steamId]/matches/UserMatchesList.tsx | 137 ++++++++++++ .../[steamId]/stats}/UserProfile.tsx | 4 +- src/app/lib/removePlayerFromMatches.ts | 38 ++++ src/app/matches/page.tsx | 4 +- src/app/profile/[steamId]/matches/page.tsx | 4 +- src/app/profile/[steamId]/stats/page.tsx | 2 +- src/app/schedule/page.tsx | 87 +------- src/app/types/match.ts | 108 +++++----- src/app/types/team.ts | 1 + 24 files changed, 692 insertions(+), 781 deletions(-) create mode 100644 src/app/components/CommunityMatchList.tsx delete mode 100644 src/app/components/MatchList.tsx delete mode 100644 src/app/components/UserMatchesTable.tsx create mode 100644 src/app/components/profile/[steamId]/matches/UserMatchesList.tsx rename src/app/components/{ => profile/[steamId]/stats}/UserProfile.tsx (98%) create mode 100644 src/app/lib/removePlayerFromMatches.ts diff --git a/src/app/admin/[tab]/page.tsx b/src/app/admin/[tab]/page.tsx index aba9a1c..db6a905 100644 --- a/src/app/admin/[tab]/page.tsx +++ b/src/app/admin/[tab]/page.tsx @@ -17,7 +17,7 @@ export default function AdminPage() { switch (activeTab) { case 'matches': return ( - + ) diff --git a/src/app/api/matches/route.ts b/src/app/api/matches/route.ts index 1ee5db5..1cdffa3 100644 --- a/src/app/api/matches/route.ts +++ b/src/app/api/matches/route.ts @@ -1,53 +1,53 @@ -// /app/api/matches/route.ts import { NextResponse } from 'next/server' import { prisma } from '@/app/lib/prisma' -export async function GET() { +export async function GET(req: Request) { try { + /* optionalen Query-Parameter lesen */ + const { searchParams } = new URL(req.url) + const matchType = searchParams.get('type') // z. B. "community" + + /* falls übergeben ⇒ danach filtern */ const matches = await prisma.match.findMany({ + where : matchType ? { matchType } : undefined, orderBy: { demoDate: 'desc' }, include: { - teamA: true, - teamB: true, - players: { - include: { - user: true, - stats: true, - team: true, - }, - }, + teamA : true, + teamB : true, + players: { include: { user: true, stats: true, team: true } }, }, }) - - const formatted = matches.map(match => ({ - id: match.id, - map: match.map, - demoDate: match.demoDate, - matchType: match.matchType, - scoreA: match.scoreA, - scoreB: match.scoreB, - winnerTeam: match.winnerTeam ?? null, + + /* … rest bleibt unverändert … */ + const formatted = matches.map(m => ({ + id : m.id, + map : m.map, + demoDate: m.demoDate, + matchType: m.matchType, + scoreA : m.scoreA, + scoreB : m.scoreB, + winnerTeam: m.winnerTeam ?? null, teamA: { - id: match.teamA?.id ?? null, - name: match.teamA?.name ?? 'CT', - logo: match.teamA?.logo ?? null, - score: match.scoreA, + id : m.teamA?.id ?? null, + name: m.teamA?.name ?? 'CT', + logo: m.teamA?.logo ?? null, + score: m.scoreA, }, teamB: { - id: match.teamB?.id ?? null, - name: match.teamB?.name ?? 'T', - logo: match.teamB?.logo ?? null, - score: match.scoreB, + id : m.teamB?.id ?? null, + name: m.teamB?.name ?? 'T', + logo: m.teamB?.logo ?? null, + score: m.scoreB, }, - players: match.players.map(p => ({ - steamId: p.steamId, - name: p.user?.name, - avatar: p.user?.avatar, - stats: p.stats, - teamId: p.teamId, + players: m.players.map(p => ({ + steamId : p.steamId, + name : p.user?.name, + avatar : p.user?.avatar, + stats : p.stats, + teamId : p.teamId, teamName: p.team?.name ?? null, })), - })); + })) return NextResponse.json(formatted) } catch (err) { diff --git a/src/app/api/team/kick/route.ts b/src/app/api/team/kick/route.ts index 7354c6c..2159328 100644 --- a/src/app/api/team/kick/route.ts +++ b/src/app/api/team/kick/route.ts @@ -1,40 +1,30 @@ -// src/app/api/team/kick/route.ts -import { NextResponse, type NextRequest } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/app/lib/prisma' import { sendServerSSEMessage } from '@/app/lib/sse-server-client' +import { removePlayerFromMatches } from '@/app/lib/removePlayerFromMatches' export const dynamic = 'force-dynamic' export async function POST(req: NextRequest) { try { - /* ------------------------------------------------------------------ * - * 1) Payload-Validierung * - * ------------------------------------------------------------------ */ + /* ───────── 1) Payload prüfen ───────── */ const { teamId, steamId } = await req.json() - if (!teamId || !steamId) { return NextResponse.json({ message: 'Fehlende Daten' }, { status: 400 }) } - /* ------------------------------------------------------------------ * - * 2) Team & User laden * - * ------------------------------------------------------------------ */ + /* ───────── 2) Team + User laden ─────── */ const team = await prisma.team.findUnique({ where: { id: teamId } }) - if (!team) { - return NextResponse.json({ message: 'Team nicht gefunden' }, { status: 404 }) - } + if (!team) return NextResponse.json({ message: 'Team nicht gefunden' }, { status: 404 }) const user = await prisma.user.findUnique({ - where: { steamId }, + where : { steamId }, select: { name: true }, }) - const userName = user?.name ?? 'Ein Mitglied' const teamName = team.name ?? 'Unbekanntes Team' - /* ------------------------------------------------------------------ * - * 3) Spielerlisten aktualisieren * - * ------------------------------------------------------------------ */ + /* ───────── 3) Spieler aus Team-Arrays entfernen ───────── */ const active = team.activePlayers.filter(id => id !== steamId) const inactive = team.inactivePlayers.filter(id => id !== steamId) @@ -46,70 +36,63 @@ export async function POST(req: NextRequest) { }, }) - /* der gekickte User gehört zu keinem Team mehr */ - await prisma.user.update({ - where: { steamId }, - data : { teamId: null }, - }) + /* ───────── 4) User vom Team lösen ───────── */ + await prisma.user.update({ where: { steamId }, data: { teamId: null } }) - /* ------------------------------------------------------------------ * - * 4) Notifikation für den gekickten User * - * ------------------------------------------------------------------ */ - const kickedNotification = await prisma.notification.create({ + /* ───────── 5) Spieler aus offenen Matches werfen ───────── */ + await removePlayerFromMatches(teamId, steamId) + + /* ───────── 6) Notifications & SSE ───────── */ + + /* an gekickten User */ + const kickedN = await prisma.notification.create({ data: { + user : { connect: { steamId } }, title : 'Team verlassen', message : `Du wurdest aus dem Team „${teamName}“ geworfen.`, actionType : 'team-kick', - actionData : null, - user : { connect: { steamId } }, // <-- Relation herstellen }, }) - await sendServerSSEMessage({ - type : kickedNotification.actionType ?? 'notification', + type : kickedN.actionType ?? 'notification', targetUserIds: [steamId], - message : kickedNotification.message, - id : kickedNotification.id, - actionType : kickedNotification.actionType ?? undefined, - actionData : kickedNotification.actionData ?? undefined, - createdAt : kickedNotification.createdAt.toISOString(), + id : kickedN.id, + message : kickedN.message, + createdAt : kickedN.createdAt.toISOString(), }) - /* ------------------------------------------------------------------ * - * 5) Notifikation für verbleibende Mitglieder * - * ------------------------------------------------------------------ */ - const remainingUserIds = [...active, ...inactive] - + /* an verbleibende Mitglieder */ + const remaining = [...active, ...inactive] await Promise.all( - remainingUserIds.map(async memberSteamId => { + remaining.map(async uid => { const n = await prisma.notification.create({ data: { + user : { connect: { steamId: uid } }, title : 'Team-Update', - message : `${userName} wurde aus dem Team „${teamName}“ geworfen.`, + message : `${userName} wurde aus dem Team „${teamName}“ gekickt.`, actionType : 'team-kick-other', - actionData : null, - user : { connect: { steamId: memberSteamId } }, // <-- Relation }, }) - await sendServerSSEMessage({ - type : n.actionType ?? 'notification', - targetUserIds: [memberSteamId], - message : n.message, - id : n.id, - actionType : n.actionType ?? undefined, - actionData : n.actionData ?? undefined, - createdAt : n.createdAt.toISOString(), + type : n.actionType ?? 'notification', + targetUserIds: [uid], + id : n.id, + message : n.message, + createdAt : n.createdAt.toISOString(), }) - }) + }), ) - /* ------------------------------------------------------------------ * - * 6) Erfolg * - * ------------------------------------------------------------------ */ + /* ► UI neu laden lassen */ + await sendServerSSEMessage({ + type : 'team-updated', + teamId, + targetUserIds : remaining, + }) + return NextResponse.json({ message: 'Mitglied entfernt' }) - } catch (error) { - console.error('[KICK] Fehler:', error) + } catch (err) { + console.error('[KICK] Fehler:', err) return NextResponse.json({ message: 'Serverfehler' }, { status: 500 }) } } diff --git a/src/app/api/team/leave/route.ts b/src/app/api/team/leave/route.ts index e74e893..33a11ad 100644 --- a/src/app/api/team/leave/route.ts +++ b/src/app/api/team/leave/route.ts @@ -1,123 +1,110 @@ -import { NextResponse, type NextRequest } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/app/lib/prisma' import { removePlayerFromTeam } from '@/app/lib/removePlayerFromTeam' +import { removePlayerFromMatches } from '@/app/lib/removePlayerFromMatches' import { sendServerSSEMessage } from '@/app/lib/sse-server-client' +export const dynamic = 'force-dynamic' + export async function POST(req: NextRequest) { try { const { steamId } = await req.json() - if (!steamId) { - return NextResponse.json({ message: 'Steam ID fehlt' }, { status: 400 }) + return NextResponse.json({ message: 'Steam-ID fehlt' }, { status: 400 }) } + /* ───────── 1) Team ermitteln ───────── */ const team = await prisma.team.findFirst({ where: { OR: [ - { activePlayers: { has: steamId } }, + { activePlayers : { has: steamId } }, { inactivePlayers: { has: steamId } }, ], }, }) - - if (!team) { - return NextResponse.json({ message: 'Kein Team gefunden.' }, { status: 404 }) - } + if (!team) return NextResponse.json({ message: 'Kein Team gefunden' }, { status: 404 }) const { activePlayers, inactivePlayers, leader } = removePlayerFromTeam( - { - activePlayers: team.activePlayers, - inactivePlayers: team.inactivePlayers, - leader: team.leaderId, - }, steamId) + { activePlayers: team.activePlayers, inactivePlayers: team.inactivePlayers, leader: team.leaderId }, + steamId, + ) + /* ───────── 2) Team anpassen / löschen ───────── */ if (!leader) { await prisma.team.delete({ where: { id: team.id } }) } else { await prisma.team.update({ where: { id: team.id }, - data: { - leader: { - connect: { steamId: leader }, - }, + data : { + leader: { connect: { steamId: leader } }, activePlayers, inactivePlayers, }, }) } - await prisma.user.update({ - where: { steamId }, - data: { teamId: null }, - }) + /* ───────── 3) User lösen ───────── */ + await prisma.user.update({ where: { steamId }, data: { teamId: null } }) - const user = await prisma.user.findUnique({ - where: { steamId }, - select: { name: true }, - }) + /* ───────── 4) Spieler aus Matches entfernen ───────── */ + await removePlayerFromMatches(team.id, steamId) - const notification = await prisma.notification.create({ + /* ───────── 5) Notifications ───────── */ + const user = await prisma.user.findUnique({ where: { steamId }, select: { name: true } }) + const userName = user?.name ?? 'Ein Spieler' + const teamName = team.name ?? 'Dein Team' + const remaining = [...activePlayers, ...inactivePlayers].filter(id => id !== steamId) + + /* an leavenden User */ + const leaveN = await prisma.notification.create({ data: { - user: { - connect: { steamId }, - }, - title: 'Teamupdate', - message: `Du hast das Team "${team.name}" verlassen.`, - actionType: 'team-left', - actionData: null, + user : { connect: { steamId } }, + title : 'Teamupdate', + message : `Du hast das Team „${teamName}“ verlassen.`, + actionType : 'team-left', }, }) - await sendServerSSEMessage({ - type: notification.actionType ?? 'notification', + type : leaveN.actionType ?? 'notification', targetUserIds: [steamId], - message: notification.message, - id: notification.id, - actionType: notification.actionType ?? undefined, - actionData: notification.actionData ?? undefined, - createdAt: notification.createdAt.toISOString(), + id : leaveN.id, + message : leaveN.message, + createdAt : leaveN.createdAt.toISOString(), }) - const allRemainingPlayers = Array.from(new Set([ - ...activePlayers, - ...inactivePlayers, - ])).filter(id => id !== steamId) - + /* an verbleibende Mitglieder */ await Promise.all( - allRemainingPlayers.map(async (userId) => { - const notification = await prisma.notification.create({ + remaining.map(async uid => { + const n = await prisma.notification.create({ data: { - user: { - connect: { steamId: userId }, - }, - title: 'Teamupdate', - message: `${user?.name ?? 'Ein Spieler'} hat das Team verlassen.`, - actionType: 'team-member-left', - actionData: null, + user : { connect: { steamId: uid } }, + title : 'Teamupdate', + message : `${userName} hat das Team verlassen.`, + actionType : 'team-member-left', }, }) - await sendServerSSEMessage({ - type: notification.actionType ?? 'notification', - targetUserIds: [userId], - message: notification.message, - id: notification.id, - actionType: notification.actionType ?? undefined, - actionData: notification.actionData ?? undefined, - createdAt: notification.createdAt.toISOString(), + type : n.actionType ?? 'notification', + targetUserIds: [uid], + id : n.id, + message : n.message, + createdAt : n.createdAt.toISOString(), }) - - await sendServerSSEMessage({ - type: 'team-updated', - teamId: team.id, - targetUserIds: allRemainingPlayers, - }) - }) + }), ) + /* ► UI neu laden lassen */ + if (remaining.length) { + await sendServerSSEMessage({ + type : 'team-updated', + teamId : team.id, + targetUserIds : remaining, + }) + } + return NextResponse.json({ message: 'Erfolgreich aus dem Team entfernt' }) - } catch (error) { - console.error('Fehler beim Verlassen des Teams:', error) - return NextResponse.json({ message: 'Fehler beim Verlassen des Teams' }, { status: 500 }) + } catch (err) { + console.error('[LEAVE] Fehler:', err) + return NextResponse.json({ message: 'Serverfehler' }, { status: 500 }) } } diff --git a/src/app/api/user/[steamId]/matches/route.ts b/src/app/api/user/[steamId]/matches/route.ts index a2e5718..4c21069 100644 --- a/src/app/api/user/[steamId]/matches/route.ts +++ b/src/app/api/user/[steamId]/matches/route.ts @@ -1,91 +1,107 @@ // /app/api/user/[steamId]/matches/route.ts -import { NextResponse } from 'next/server' -import { prisma } from '@/app/lib/prisma' +import { NextResponse, type NextRequest } from 'next/server' +import { prisma } from '@/app/lib/prisma' export async function GET( - _req: Request, - { params }: { params: { steamId: string } } + req : NextRequest, // ← Request wird gebraucht! + { params }: { params: { steamId: string } }, ) { const steamId = params.steamId - if (!steamId) { return NextResponse.json({ error: 'Steam-ID fehlt' }, { status: 400 }) } + /* ───────── Query-Parameter „types“ auslesen ───────── */ + const { searchParams } = new URL(req.url) + // ?types=premier,competitive + const typesParam = searchParams.get('types') // string | null + const types = typesParam + ? typesParam.split(',').map(t => t.trim()).filter(Boolean) + : [] // leer ⇒ kein Filter + + /* ───────── Daten holen ───────── */ try { const matchPlayers = await prisma.matchPlayer.findMany({ - where: { steamId }, + where: { + steamId, + + /* nur wenn Filter gesetzt ist */ + ...(types.length && { + match: { matchType: { in: types } }, + }), + }, + select: { teamId: true, - team: true, - match: { + team : true, + match : { select: { - id: true, - demoDate: true, - map: true, - roundCount: true, - scoreA: true, - scoreB: true, - matchType: true, - teamAId: true, - teamBId: true, - teamAUsers: true, - teamBUsers: true, - winnerTeam: true, + id : true, + demoDate : true, + map : true, + roundCount : true, + scoreA : true, + scoreB : true, + matchType : true, + teamAId : true, + teamBId : true, + teamAUsers : { select: { steamId: true } }, + teamBUsers : { select: { steamId: true } }, + winnerTeam : true, }, }, stats: true, }, - orderBy: { - match: { - demoDate: 'desc', - }, - }, + + orderBy: { match: { demoDate: 'desc' } }, }) - const data = matchPlayers.map((mp) => { - const match = mp.match + /* ───────── Aufbereiten fürs Frontend ───────── */ + const data = matchPlayers.map(mp => { + const m = mp.match const stats = mp.stats - const kills = stats?.kills ?? 0 + + const kills = stats?.kills ?? 0 const deaths = stats?.deaths ?? 0 - const kdr = deaths > 0 ? (kills / deaths).toFixed(2) : '∞' - const roundCount = match.roundCount + const kdr = deaths ? (kills / deaths).toFixed(2) : '∞' + const rankOld = stats?.rankOld ?? null const rankNew = stats?.rankNew ?? null + const rankChange = - typeof rankNew === 'number' && typeof rankOld === 'number' - ? rankNew - rankOld - : null - const matchType = match.matchType ?? 'community' + rankNew != null && rankOld != null ? rankNew - rankOld : null - const isInTeamA = match.teamAUsers.some((user) => user.steamId === steamId) - const playerTeam = isInTeamA ? 'CT' : 'T' + /* Team des Spielers ermitteln */ + const playerTeam = + m.teamAUsers.some(u => u.steamId === steamId) ? 'CT' : 'T' - const scoreCT = match.scoreA ?? 0 - const scoreT = match.scoreB ?? 0 - const score = `${scoreCT} : ${scoreT}` + const score = `${m.scoreA ?? 0} : ${m.scoreB ?? 0}` return { - id: match.id, - map: match.map ?? 'Unknown', - date: match.demoDate, - matchType, + id : m.id, + map : m.map ?? 'Unknown', + date : m.demoDate?.toISOString() ?? '', + matchType : m.matchType ?? 'community', + score, - roundCount, + roundCount: m.roundCount, + rankOld, rankNew, rankChange, + kills, deaths, kdr, - winnerTeam: match.winnerTeam ?? null, - team: playerTeam, + + winnerTeam: m.winnerTeam ?? null, + team : playerTeam, // „CT“ oder „T“ } }) return NextResponse.json(data) - } catch (error) { - console.error('[API] Fehler beim Laden der Matches:', error) + } catch (err) { + console.error('[API] Fehler beim Laden der Matches:', err) return NextResponse.json({ error: 'Serverfehler' }, { status: 500 }) } } diff --git a/src/app/components/CommunityMatchList.tsx b/src/app/components/CommunityMatchList.tsx new file mode 100644 index 0000000..0e2822d --- /dev/null +++ b/src/app/components/CommunityMatchList.tsx @@ -0,0 +1,197 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useSession } from 'next-auth/react' +import { useRouter } from 'next/navigation' +import Link from 'next/link' +import Image from 'next/image' +import { format } from 'date-fns' +import { de } from 'date-fns/locale' +import Switch from '@/app/components/Switch' +import Button from './Button' +import { Match } from '../types/match' +import { differenceInMinutes } from 'date-fns' + +type Props = { matchType?: string } + +/* ------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------ */ +const getTeamLogo = (logo?: string | null) => + logo ? `/assets/img/logos/${logo}` : '/assets/img/logos/cs2.webp' + +const toDateKey = (d: Date) => d.toISOString().slice(0, 10) +const weekdayDE = new Intl.DateTimeFormat('de-DE', { weekday: 'long' }) + +/* ------------------------------------------------------------ */ +/* Component */ +/* ------------------------------------------------------------ */ +export default function CommunityMatchList({ matchType }: Props) { + const { data: session } = useSession() + const router = useRouter() + + const [matches, setMatches] = useState([]) + const [onlyOwn, setOnlyOwn] = useState(false) + + /* Daten laden */ + useEffect(() => { + const url = `/api/matches${matchType ? `?type=${encodeURIComponent(matchType)}` : ''}` + + fetch(url) + .then(r => (r.ok ? r.json() : [])) + .then(setMatches) + .catch(err => console.error('[MatchList] Laden fehlgeschlagen:', err)) + }, [matchType]) + + /* Sortieren + Gruppieren (ohne vorher zu filtern!) */ + const grouped = (() => { + const sorted = [...matches].sort( + (a, b) => new Date(a.demoDate).getTime() - new Date(b.demoDate).getTime(), + ) + + const map = new Map() + for (const m of sorted) { + const key = toDateKey(new Date(m.demoDate)) + map.set(key, [...(map.get(key) ?? []), m]) + } + return Array.from(map.entries()) // [ [ '2025-08-28', [ … ] ], … ] + })() + + /* Render */ + return ( +
+ {/* Kopfzeile ----------------------------------------------------- */} +
+

+ Geplante Matches +

+
+ + + {session?.user?.isAdmin && ( + + + + )} +
+
+ + {/* Inhalt ------------------------------------------------------- */} + {grouped.length === 0 ? ( +

Keine Matches geplant.

+ ) : ( +
+ {grouped.map(([dateKey, dayMatches], dayIdx) => { + const dateObj = new Date(dateKey + 'T00:00:00') + const dayLabel = `Tag #${dayIdx + 1} – ${weekdayDE.format(dateObj)}` + + return ( +
+ {/* Tages-Header */} +
+ {dayLabel}
+ {dateKey} +
+ + {/* Matches des Tages */} + {dayMatches.map(m => { + /* 1️⃣ Regeln --------------------------------------------- */ + const demoDate = new Date(m.demoDate) + const started = demoDate <= Date.now() + const unfinished = !m.winnerTeam && m.scoreA == null && m.scoreB == null + const isLive = started && unfinished // ← live-Flag + + const isOwnTeam = + session?.user?.team && + (m.teamA.id === session.user.team || m.teamB.id === session.user.team) + + /* Wenn nur-Own aktiv & nicht eigenes Match → abdunkeln */ + const dimmed = onlyOwn && !isOwnTeam + + return ( + + + {/** ⏱ kleine Live-Marke, falls gewünscht */} + {isLive && ( + + LIVE + + )} + + {/* Teams -------------------------------------------------- */} +
+ {/* Team A */} +
+ {m.teamA.name} + {m.teamA.name} +
+ + {/* vs */} + vs + + {/* Team B */} +
+ {m.teamB.name} + {m.teamB.name} +
+
+ + + {/* Datum + Uhrzeit --------------------------------------- */} +
+ {/* Datum */} + + {format(new Date(m.demoDate), 'dd.MM.yyyy', { locale: de })} + + + {/* Zeit */} + + + + + {format(new Date(m.demoDate), 'HH:mm', { locale: de })} Uhr + +
+ + ) + })} +
+ ) + })} +
+ )} +
+ ) +} diff --git a/src/app/components/DroppableZone.tsx b/src/app/components/DroppableZone.tsx index 47fc1bb..1be816d 100644 --- a/src/app/components/DroppableZone.tsx +++ b/src/app/components/DroppableZone.tsx @@ -9,12 +9,14 @@ type DroppableZoneProps = { label: string children: React.ReactNode activeDragItem: Player | null + saveSuccess?: boolean } export function DroppableZone({ id, label, children, + saveSuccess = false, }: DroppableZoneProps) { const { isOver, setNodeRef } = useDroppable({ id }) @@ -31,9 +33,29 @@ export function DroppableZone({ return (
-

- {label} -

+
+

+ {label} +

+ + {saveSuccess && ( +
+ + + + Änderungen gespeichert! +
+ )} +
{/* Hier sitzt der Droppable-Ref */}
diff --git a/src/app/components/MatchDetails.tsx b/src/app/components/MatchDetails.tsx index 851cf09..abae46f 100644 --- a/src/app/components/MatchDetails.tsx +++ b/src/app/components/MatchDetails.tsx @@ -18,6 +18,7 @@ import EditMatchPlayersModal from './EditMatchPlayersModal' import type { EditSide } from './EditMatchPlayersModal' // 'A' | 'B' import type { Match, MatchPlayer } from '../types/match' +import Button from './Button' /* ─────────────────── Hilfsfunktionen ────────────────────────── */ const kdr = (k?: number, d?: number) => @@ -165,13 +166,14 @@ export function MatchDetails ({ match }: { match: Match }) { {canEditA && isFutureMatch && ( - + )}
@@ -186,13 +188,14 @@ export function MatchDetails ({ match }: { match: Match }) { {canEditB && isFutureMatch && ( - + )}
diff --git a/src/app/components/MatchList.tsx b/src/app/components/MatchList.tsx deleted file mode 100644 index 371e0a8..0000000 --- a/src/app/components/MatchList.tsx +++ /dev/null @@ -1,121 +0,0 @@ -'use client' - -import Link from 'next/link' -import Image from 'next/image' -import { useEffect, useState } from 'react' -import { useSession } from 'next-auth/react' -import Switch from '@/app/components/Switch' - -type Match = { - id: string - title: string - description?: string - matchDate: string - teamA: { id: string; teamname: string; logo?: string | null } - teamB: { id: string; teamname: string; logo?: string | null } -} - -function getTeamLogo(logo?: string | null) { - return logo ? `/assets/img/logos/${logo}` : '/default-logo.png' -} - -export default function MatchList() { - const { data: session } = useSession() - const [matches, setMatches] = useState([]) - const [onlyOwnTeam, setOnlyOwnTeam] = useState(false) - - useEffect(() => { - fetch('/api/matches') - .then((res) => res.ok ? res.json() : []) - .then(setMatches) - .catch((err) => console.error('Fehler beim Laden der Matches:', err)) - }, []) - - const filteredMatches = onlyOwnTeam && session?.user?.team - ? matches.filter(m => - m.teamA.id === session.user.team || m.teamB.id === session.user.team - ) - : matches - - return ( -
-
-

Geplante Matches

- {session?.user?.team && ( - - )} -
- - {filteredMatches.length === 0 ? ( -

Keine Matches geplant.

- ) : ( -
    - {filteredMatches.map((match) => ( -
  • - -
    - {/* Team A */} -
    - {match.teamA.teamname} - - {match.teamA.teamname} - -
    - - {/* Datum / Zeit */} -
    -
    {new Date(match.matchDate).toLocaleDateString('de-DE')}
    -
    {new Date(match.matchDate).toLocaleTimeString('de-DE', { - hour: '2-digit', - minute: '2-digit' - })} Uhr
    -
    - - {/* Team B */} -
    - {match.teamB.teamname} - - {match.teamB.teamname} - -
    -
    - - {/* Match-Titel */} -
    - {match.title} -
    - - {/* Match-Beschreibung (optional) */} - {match.description && ( -
    - {match.description} -
    - )} - -
  • - ))} -
- )} -
- ) -} diff --git a/src/app/components/Switch.tsx b/src/app/components/Switch.tsx index f1a148d..0532cb0 100644 --- a/src/app/components/Switch.tsx +++ b/src/app/components/Switch.tsx @@ -41,7 +41,7 @@ export default function Switch({ {labelRight && ( -