import { useState, useEffect } from 'react' import { useParams, useNavigate, Link } from 'react-router-dom' import { marathonsApi, gamesApi } from '@/api' import type { Marathon, Game, Challenge, ChallengePreview } from '@/types' import { Button, Input, Card, CardContent, CardHeader, CardTitle } from '@/components/ui' import { useAuthStore } from '@/store/auth' import { Plus, Trash2, Sparkles, Play, Loader2, Gamepad2, X, Save, Eye, ChevronDown, ChevronUp, Edit2, Check, CheckCircle, XCircle, Clock, User, ArrowLeft } from 'lucide-react' export function LobbyPage() { const { id } = useParams<{ id: string }>() const navigate = useNavigate() const user = useAuthStore((state) => state.user) const [marathon, setMarathon] = useState(null) const [games, setGames] = useState([]) const [pendingGames, setPendingGames] = useState([]) const [isLoading, setIsLoading] = useState(true) // Add game form const [showAddGame, setShowAddGame] = useState(false) const [gameTitle, setGameTitle] = useState('') const [gameUrl, setGameUrl] = useState('') const [gameGenre, setGameGenre] = useState('') const [isAddingGame, setIsAddingGame] = useState(false) // Moderation const [moderatingGameId, setModeratingGameId] = useState(null) // Generate challenges const [isGenerating, setIsGenerating] = useState(false) const [generateMessage, setGenerateMessage] = useState(null) const [previewChallenges, setPreviewChallenges] = useState(null) const [isSaving, setIsSaving] = useState(false) const [editingIndex, setEditingIndex] = useState(null) // View existing challenges const [expandedGameId, setExpandedGameId] = useState(null) const [gameChallenges, setGameChallenges] = useState>({}) const [loadingChallenges, setLoadingChallenges] = useState(null) // Start marathon const [isStarting, setIsStarting] = useState(false) useEffect(() => { loadData() }, [id]) const loadData = async () => { if (!id) return try { const marathonData = await marathonsApi.get(parseInt(id)) setMarathon(marathonData) // Load games - organizers see all, participants see approved + own const gamesData = await gamesApi.list(parseInt(id)) setGames(gamesData) // If organizer, load pending games separately if (marathonData.my_participation?.role === 'organizer' || user?.role === 'admin') { try { const pending = await gamesApi.listPending(parseInt(id)) setPendingGames(pending) } catch { // If not authorized, just ignore setPendingGames([]) } } } catch (error) { console.error('Failed to load data:', error) navigate('/marathons') } finally { setIsLoading(false) } } const handleAddGame = async () => { if (!id || !gameTitle.trim() || !gameUrl.trim()) return setIsAddingGame(true) try { await gamesApi.create(parseInt(id), { title: gameTitle.trim(), download_url: gameUrl.trim(), genre: gameGenre.trim() || undefined, }) setGameTitle('') setGameUrl('') setGameGenre('') setShowAddGame(false) await loadData() } catch (error) { console.error('Failed to add game:', error) } finally { setIsAddingGame(false) } } const handleDeleteGame = async (gameId: number) => { if (!confirm('Удалить эту игру?')) return try { await gamesApi.delete(gameId) await loadData() } catch (error) { console.error('Failed to delete game:', error) } } const handleApproveGame = async (gameId: number) => { setModeratingGameId(gameId) try { await gamesApi.approve(gameId) await loadData() } catch (error) { console.error('Failed to approve game:', error) } finally { setModeratingGameId(null) } } const handleRejectGame = async (gameId: number) => { if (!confirm('Отклонить эту игру?')) return setModeratingGameId(gameId) try { await gamesApi.reject(gameId) await loadData() } catch (error) { console.error('Failed to reject game:', error) } finally { setModeratingGameId(null) } } const handleToggleGameChallenges = async (gameId: number) => { if (expandedGameId === gameId) { setExpandedGameId(null) return } setExpandedGameId(gameId) if (!gameChallenges[gameId]) { setLoadingChallenges(gameId) try { const challenges = await gamesApi.getChallenges(gameId) setGameChallenges(prev => ({ ...prev, [gameId]: challenges })) } catch (error) { console.error('Failed to load challenges:', error) } finally { setLoadingChallenges(null) } } } const handleDeleteChallenge = async (challengeId: number, gameId: number) => { if (!confirm('Удалить это задание?')) return try { await gamesApi.deleteChallenge(challengeId) // Refresh challenges for this game const challenges = await gamesApi.getChallenges(gameId) setGameChallenges(prev => ({ ...prev, [gameId]: challenges })) await loadData() // Refresh game counts } catch (error) { console.error('Failed to delete challenge:', error) } } const handleGenerateChallenges = async () => { if (!id) return setIsGenerating(true) setGenerateMessage(null) try { const result = await gamesApi.previewChallenges(parseInt(id)) if (result.challenges.length === 0) { setGenerateMessage('Все игры уже имеют задания') } else { setPreviewChallenges(result.challenges) } } catch (error) { console.error('Failed to generate challenges:', error) setGenerateMessage('Не удалось сгенерировать задания') } finally { setIsGenerating(false) } } const handleSaveChallenges = async () => { if (!id || !previewChallenges) return setIsSaving(true) try { const result = await gamesApi.saveChallenges(parseInt(id), previewChallenges) setGenerateMessage(result.message) setPreviewChallenges(null) setGameChallenges({}) // Clear cache to reload await loadData() } catch (error) { console.error('Failed to save challenges:', error) setGenerateMessage('Не удалось сохранить задания') } finally { setIsSaving(false) } } const handleRemovePreviewChallenge = (index: number) => { if (!previewChallenges) return setPreviewChallenges(previewChallenges.filter((_, i) => i !== index)) if (editingIndex === index) setEditingIndex(null) } const handleUpdatePreviewChallenge = (index: number, field: keyof ChallengePreview, value: string | number) => { if (!previewChallenges) return setPreviewChallenges(previewChallenges.map((ch, i) => i === index ? { ...ch, [field]: value } : ch )) } const handleCancelPreview = () => { setPreviewChallenges(null) setEditingIndex(null) } const handleStartMarathon = async () => { if (!id || !confirm('Начать марафон? После этого нельзя будет добавить новые игры.')) return setIsStarting(true) try { await marathonsApi.start(parseInt(id)) navigate(`/marathons/${id}/play`) } catch (err: unknown) { const error = err as { response?: { data?: { detail?: string } } } alert(error.response?.data?.detail || 'Не удалось запустить марафон') } finally { setIsStarting(false) } } if (isLoading || !marathon) { return (
) } const isOrganizer = marathon.my_participation?.role === 'organizer' || user?.role === 'admin' const approvedGames = games.filter(g => g.status === 'approved') const totalChallenges = approvedGames.reduce((sum, g) => sum + g.challenges_count, 0) const getStatusBadge = (status: string) => { switch (status) { case 'approved': return ( Одобрено ) case 'pending': return ( На модерации ) case 'rejected': return ( Отклонено ) default: return null } } const renderGameCard = (game: Game, showModeration = false) => (
{/* Game header */}
0 ? 'cursor-pointer hover:bg-gray-800/50' : '' }`} onClick={() => game.challenges_count > 0 && handleToggleGameChallenges(game.id)} >
{game.challenges_count > 0 && ( {expandedGameId === game.id ? ( ) : ( )} )}

{game.title}

{getStatusBadge(game.status)}
{game.genre && {game.genre}} {game.status === 'approved' && {game.challenges_count} заданий} {game.proposed_by && ( {game.proposed_by.nickname} )}
e.stopPropagation()}> {showModeration && game.status === 'pending' && ( <> )} {(isOrganizer || game.proposed_by?.id === user?.id) && ( )}
{/* Expanded challenges list */} {expandedGameId === game.id && (
{loadingChallenges === game.id ? (
) : gameChallenges[game.id]?.length > 0 ? ( gameChallenges[game.id].map((challenge) => (
{challenge.difficulty === 'easy' ? 'Легко' : challenge.difficulty === 'medium' ? 'Средне' : 'Сложно'} +{challenge.points} {challenge.is_generated && ( ИИ )}
{challenge.title}

{challenge.description}

{isOrganizer && ( )}
)) ) : (

Нет заданий

)}
)}
) return (
{/* Back button */} К марафону

{marathon.title}

{isOrganizer ? 'Настройка - Добавьте игры и сгенерируйте задания' : 'Предложите игры для марафона'}

{isOrganizer && ( )}
{/* Stats - только для организаторов */} {isOrganizer && (
{approvedGames.length}
Игр одобрено
{totalChallenges}
Заданий
)} {/* Pending games for moderation (organizers only) */} {isOrganizer && pendingGames.length > 0 && ( На модерации ({pendingGames.length})
{pendingGames.map((game) => renderGameCard(game, true))}
)} {/* Generate challenges button */} {isOrganizer && approvedGames.length > 0 && !previewChallenges && (

Генерация заданий

Используйте ИИ для генерации заданий для одобренных игр без заданий

{generateMessage && (

{generateMessage}

)}
)} {/* Challenge preview with editing */} {previewChallenges && previewChallenges.length > 0 && (
Предпросмотр заданий ({previewChallenges.length})
{previewChallenges.map((challenge, index) => (
{editingIndex === index ? ( // Edit mode
{challenge.game_title}
handleUpdatePreviewChallenge(index, 'title', e.target.value)} placeholder="Название" className="bg-gray-800" />