292 lines
9.6 KiB
TypeScript
292 lines
9.6 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import Modal from './Modal'
|
|
import MiniCard from './MiniCard'
|
|
import { useSession } from 'next-auth/react'
|
|
import LoadingSpinner from './LoadingSpinner'
|
|
import { Player, Team } from '../types/team'
|
|
import Pagination from './Pagination'
|
|
import { AnimatePresence, motion } from 'framer-motion'
|
|
|
|
type Props = {
|
|
show: boolean
|
|
onClose: () => void
|
|
onSuccess: () => void
|
|
team: Team
|
|
directAdd?: boolean
|
|
}
|
|
|
|
export default function InvitePlayersModal({ show, onClose, onSuccess, team, directAdd = false }: Props) {
|
|
const { data: session } = useSession()
|
|
const steamId = session?.user?.steamId
|
|
|
|
const [allUsers, setAllUsers] = useState<Player[]>([])
|
|
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
|
const [invitedIds, setInvitedIds] = useState<string[]>([])
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [isSuccess, setIsSuccess] = useState(false)
|
|
const [sentCount, setSentCount] = useState(0)
|
|
const [searchTerm, setSearchTerm] = useState('')
|
|
const usersPerPage = 9
|
|
const [currentPage, setCurrentPage] = useState(1)
|
|
|
|
useEffect(() => {
|
|
if (show) {
|
|
fetchUsersNotInTeam()
|
|
setIsSuccess(false)
|
|
setInvitedIds([])
|
|
}
|
|
}, [show])
|
|
|
|
const fetchUsersNotInTeam = async () => {
|
|
try {
|
|
setIsLoading(true)
|
|
const res = await fetch('/api/team/available-users')
|
|
const data = await res.json()
|
|
setAllUsers(data.users || [])
|
|
} catch (err) {
|
|
console.error('Fehler beim Laden der Benutzer:', err)
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleSelect = (steamId: string) => {
|
|
setSelectedIds((prev) =>
|
|
prev.includes(steamId)
|
|
? prev.filter((id) => id !== steamId)
|
|
: [...prev, steamId]
|
|
)
|
|
}
|
|
|
|
const handleInvite = async () => {
|
|
if (selectedIds.length === 0 || !steamId) return
|
|
|
|
try {
|
|
const url = directAdd ? '/api/team/add-players' : '/api/team/invite'
|
|
const body = directAdd
|
|
? { teamId: team.id, steamIds: selectedIds }
|
|
: { teamId: team.id, userIds: selectedIds, invitedBy: steamId }
|
|
|
|
const res = await fetch(url, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
})
|
|
|
|
if (!res.ok) {
|
|
const error = await res.json()
|
|
console.error('Fehler beim Einladen:', error.message)
|
|
} else {
|
|
setSentCount(selectedIds.length)
|
|
setInvitedIds(selectedIds) // 👈 Einladungsliste speichern
|
|
setIsSuccess(true)
|
|
setSelectedIds([]) // ⛔ nicht zu früh löschen!
|
|
onSuccess()
|
|
}
|
|
} catch (err) {
|
|
console.error('Fehler beim Einladen:', err)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (isSuccess) {
|
|
const timeout = setTimeout(() => {
|
|
const modalEl = document.getElementById('invite-members-modal')
|
|
if (modalEl && window.HSOverlay?.close) {
|
|
window.HSOverlay.close(modalEl)
|
|
}
|
|
onClose()
|
|
}, 2000)
|
|
|
|
return () => clearTimeout(timeout)
|
|
}
|
|
}, [isSuccess, onClose])
|
|
|
|
useEffect(() => {
|
|
setCurrentPage(1)
|
|
}, [selectedIds, searchTerm])
|
|
|
|
const filteredUsers = allUsers.filter(user =>
|
|
user.name?.toLowerCase().includes(searchTerm.toLowerCase())
|
|
)
|
|
|
|
const sortedUsers = [...filteredUsers].sort((a, b) => {
|
|
const aSelected = selectedIds.includes(a.steamId) ? -1 : 0
|
|
const bSelected = selectedIds.includes(b.steamId) ? -1 : 0
|
|
return aSelected - bSelected
|
|
})
|
|
|
|
const unselectedUsers = filteredUsers.filter((user) =>
|
|
!selectedIds.includes(user.steamId) &&
|
|
(!isSuccess || !invitedIds.includes(user.steamId))
|
|
)
|
|
|
|
const totalPages = Math.ceil(unselectedUsers.length / usersPerPage)
|
|
const startIdx = (currentPage - 1) * usersPerPage
|
|
const paginatedUsers = unselectedUsers.slice(startIdx, startIdx + usersPerPage)
|
|
|
|
return (
|
|
<Modal
|
|
id="invite-members-modal"
|
|
title={directAdd ? 'Spieler hinzufügen' : 'Spieler einladen'}
|
|
show={show}
|
|
onClose={onClose}
|
|
onSave={handleInvite}
|
|
closeButtonColor={isSuccess ? 'teal' : 'blue'}
|
|
closeButtonTitle={
|
|
isSuccess
|
|
? directAdd ? 'Spieler hinzugefügt' : 'Einladungen versendet'
|
|
: directAdd ? 'Hinzufügen' : 'Einladungen senden'
|
|
}
|
|
>
|
|
<p className="text-sm text-gray-700 dark:text-neutral-300 mb-2">
|
|
{directAdd
|
|
? 'Wähle Spieler aus, die du direkt zum Team hinzufügen möchtest:'
|
|
: 'Wähle Spieler aus, die du in dein Team einladen möchtest:'}
|
|
</p>
|
|
|
|
{/* Ausgewählte Benutzer anzeigen */}
|
|
{selectedIds.length > 0 && (
|
|
<div className="col-span-full">
|
|
<h3 className="text-sm font-semibold text-gray-700 dark:text-neutral-300 mb-2">
|
|
Ausgewählte Spieler:
|
|
</h3>
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3 mb-2">
|
|
<AnimatePresence initial={false}>
|
|
{selectedIds.map((id) => {
|
|
const user = allUsers.find((u) => u.steamId === id)
|
|
if (!user) return null
|
|
return (
|
|
<motion.div
|
|
key={user.steamId}
|
|
layout
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
<MiniCard
|
|
steamId={user.steamId}
|
|
title={user.name}
|
|
avatar={user.avatar}
|
|
location={user.location}
|
|
selected={true}
|
|
onSelect={handleSelect}
|
|
draggable={false}
|
|
currentUserSteamId={steamId!}
|
|
teamLeaderSteamId={team.leader}
|
|
hideActions={true}
|
|
rank={user.premierRank}
|
|
/>
|
|
</motion.div>
|
|
)
|
|
})}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<input
|
|
type="text"
|
|
placeholder="Suchen..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="mt-2 w-full rounded border px-3 py-2 text-sm focus:outline-none focus:ring focus:ring-blue-400 dark:bg-neutral-800 dark:border-neutral-600 dark:text-neutral-100"
|
|
/>
|
|
|
|
{isSuccess && (
|
|
<div className="mt-2 px-4 py-2 text-sm text-green-700 bg-green-100 border border-green-200 rounded-lg">
|
|
{directAdd
|
|
? `${sentCount} Mitglied${sentCount === 1 ? '' : 'er'} hinzugefügt!`
|
|
: `${sentCount} Einladung${sentCount === 1 ? '' : 'en'} versendet!`}
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4 mt-4">
|
|
{isLoading ? (
|
|
<LoadingSpinner />
|
|
) : filteredUsers.length === 0 ? (
|
|
<div className="col-span-full text-center text-gray-500 dark:text-neutral-400">
|
|
{allUsers.length === 0
|
|
? directAdd
|
|
? 'Keine Benutzer verfügbar :('
|
|
: 'Niemand zum Einladen verfügbar :('
|
|
: 'Keine Benutzer gefunden.'}
|
|
</div>
|
|
) : (
|
|
<>
|
|
<AnimatePresence mode="popLayout" initial={false}>
|
|
{!isSuccess && paginatedUsers.map((user) => (
|
|
<motion.div
|
|
key={user.steamId}
|
|
layout
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
<MiniCard
|
|
steamId={user.steamId}
|
|
title={user.name}
|
|
avatar={user.avatar}
|
|
location={user.location}
|
|
selected={false}
|
|
onSelect={handleSelect}
|
|
draggable={false}
|
|
currentUserSteamId={steamId!}
|
|
teamLeaderSteamId={team.leader}
|
|
hideActions={true}
|
|
rank={user.premierRank}
|
|
/>
|
|
</motion.div>
|
|
))}
|
|
{isSuccess &&
|
|
invitedIds.map((id) => {
|
|
const user = allUsers.find((u) => u.steamId === id)
|
|
if (!user) return null
|
|
return (
|
|
<motion.div
|
|
key={`invited-${user.steamId}`}
|
|
layout
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
<MiniCard
|
|
steamId={user.steamId}
|
|
title={user.name}
|
|
avatar={user.avatar}
|
|
location={user.location}
|
|
selected={false}
|
|
draggable={false}
|
|
currentUserSteamId={steamId!}
|
|
teamLeaderSteamId={team.leader}
|
|
hideActions={true}
|
|
rank={user.premierRank}
|
|
message="Eingeladen"
|
|
/>
|
|
</motion.div>
|
|
)
|
|
})}
|
|
</AnimatePresence>
|
|
|
|
{ !isSuccess && (
|
|
<div className="col-span-full flex justify-center mt-2">
|
|
<Pagination
|
|
currentPage={currentPage}
|
|
totalPages={totalPages}
|
|
onPageChange={(page) => setCurrentPage(page)}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
</>
|
|
)}
|
|
</div>
|
|
</Modal>
|
|
)
|
|
}
|