/* ------------------------------------------------------------------ /app/components/EditMatchPlayersModal.tsx – zeigt ALLE Spieler des gewählten Teams & nutzt DroppableZone-IDs "active" / "inactive" analog zur TeamMemberView. ------------------------------------------------------------------- */ 'use client' import { useEffect, useState } from 'react' import { useSession } from 'next-auth/react' import { DndContext, closestCenter, DragOverlay, } from '@dnd-kit/core' import { SortableContext, verticalListSortingStrategy, } from '@dnd-kit/sortable' import Modal from '@/app/components/Modal' import SortableMiniCard from '@/app/components/SortableMiniCard' import LoadingSpinner from '@/app/components/LoadingSpinner' import { DroppableZone } from '@/app/components/DroppableZone' import type { Player, Team } from '@/app/types/team' /* ───────────────────────── Typen ────────────────────────── */ export type EditSide = 'A' | 'B' interface Props { show : boolean onClose : () => void matchId : string teamA : Team teamB : Team side : EditSide // welches Team wird editiert? initialA: string[] // bereits eingesetzte Spieler-IDs initialB: string[] onSaved?: () => void } /* ───────────────────── Komponente ──────────────────────── */ export default function EditMatchPlayersModal (props: Props) { const { show, onClose, matchId, teamA, teamB, side, initialA, initialB, onSaved, } = props /* ---- Rollen-Check --------------------------------------- */ const { data: session } = useSession() const meSteam = session?.user?.steamId const isAdmin = session?.user?.isAdmin const isLeader = side === 'A' ? meSteam === teamA.leader?.steamId : meSteam === teamB.leader?.steamId const canEdit = isAdmin || isLeader /* ---- States --------------------------------------------- */ const [players, setPlayers] = useState([]) const [selected, setSelected] = useState([]) const [dragItem, setDragItem] = useState(null) const [saving, setSaving] = useState(false) const [saved, setSaved] = useState(false) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) /* ---- Team-Info ------------------------------------------ */ const team = side === 'A' ? teamA : teamB const other = side === 'A' ? teamB : teamA const otherInit = side === 'A' ? initialB : initialA const myInit = side === 'A' ? initialA : initialB /* ---- Komplett-Spielerliste laden ------------------------ */ useEffect(() => { if (!show) return if (!team?.id) { // ❗ Kein verknüpftes Team – zeig einen klaren Hinweis setPlayers([]) setSelected([]) setError('Kein Team mit diesem Match verknüpft (fehlende Team-ID).') setLoading(false) return } setLoading(true) setError(null) ;(async () => { try { const res = await fetch(`/api/team/${encodeURIComponent(team.id)}`, { cache: 'no-store', }) if (!res.ok) { setError(`Team-API: ${res.status}`) setPlayers([]) return } const data = await res.json() // 👉 Hier brauchst du KEIN Normalizer mehr, wenn deine /api/team-Route // (wie zuletzt angepasst) bereits Player-Objekte liefert. const all = [ ...(data.activePlayers ?? []), ...(data.inactivePlayers ?? []), ] .filter((p: Player) => !!p?.steamId) .filter((p: Player, i: number, arr: Player[]) => arr.findIndex(x => x.steamId === p.steamId) === i) .sort((a: Player, b: Player) => (a.name || '').localeCompare(b.name || '')) setPlayers(all) setSelected(myInit) // initiale Auswahl aus Props setSaved(false) } catch (e) { console.error('[EditMatchPlayersModal] load error:', e) setError('Laden fehlgeschlagen') setPlayers([]) } finally { setLoading(false) } })() }, [show, team?.id]) /* ---- Drag’n’Drop-Handler -------------------------------- */ const onDragStart = ({ active }: any) => { setDragItem(players.find(p => p.steamId === active.id) ?? null) } const onDragEnd = ({ active, over }: any) => { setDragItem(null) if (!over) return const id = active.id as string const dropZone = over.id as string // "active" | "inactive" const already = selected.includes(id) const toActive = dropZone === 'active' if ( (toActive && already) || (!toActive && !already) ) return setSelected(sel => toActive ? [...sel, id].slice(0, 5) // max 5 einsatzfähig : sel.filter(x => x !== id), ) } /* ---- Speichern ------------------------------------------ */ const handleSave = async () => { setSaving(true) try { const body = { players: [ /* akt. Auswahl für die bearbeitete Seite */ ...selected.map(steamId => ({ steamId, teamId: team.id })), /* unveränderte Gegenseite unbedingt mitschicken! */ ...otherInit.map(steamId => ({ steamId, teamId: other.id })), ], } const res = await fetch(`/api/matches/${matchId}`, { method : 'PUT', headers: { 'Content-Type': 'application/json' }, body : JSON.stringify(body), }) if (!res.ok) throw new Error() setSaved(true) onSaved?.() } catch (e) { console.error('[EditMatchPlayersModal] save error:', e) } finally { setSaving(false) } } /* ---- Listen trennen ------------------------------------- */ const active = players.filter(p => selected.includes(p.steamId)) const inactive = players.filter(p => !selected.includes(p.steamId)) /* ---- UI -------------------------------------------------- */ if (!show) return null return ( {!canEdit && (

Du darfst dieses Team nicht bearbeiten.

)} {canEdit && ( <> {loading && } {!loading && error && (

Fehler: {error}

)} {!loading && !error && players.length === 0 && (

Keine Spieler gefunden.

)} {!loading && !error && players.length > 0 && ( {/* --- Zone: Aktuell eingestellte Spieler ------------- */} p.steamId)} strategy={verticalListSortingStrategy} > {active.map(p => ( ))} {/* --- Zone: Verfügbar (restliche) ------------------- */} p.steamId)} strategy={verticalListSortingStrategy} > {inactive.map(p => ( ))} {/* Drag-Overlay */} {dragItem && ( )} )} )}
) }