+ {/* Header */}
+
+
+
Детали выполнения
+
+
+ {/* Challenge info */}
+
+
+
+
+
{assignment.challenge.game.title}
+
{assignment.challenge.title}
+
+ {getStatusBadge(assignment.status)}
+
+
+ {assignment.challenge.description}
+
+
+
+ +{assignment.challenge.points} очков
+
+
+ {assignment.challenge.difficulty}
+
+ {assignment.challenge.estimated_time && (
+
+ ~{assignment.challenge.estimated_time} мин
+
+ )}
+
+
+
+
+ Выполнил: {assignment.participant.nickname}
+
+ {assignment.completed_at && (
+
+ Дата: {formatDate(assignment.completed_at)}
+
+ )}
+ {assignment.points_earned > 0 && (
+
+ Получено очков: {assignment.points_earned}
+
+ )}
+
+
+
+
+ {/* Proof section */}
+
+
+
+
+ Доказательство
+
+
+ {/* Proof image */}
+ {assignment.proof_image_url && (
+
+

+
+ )}
+
+ {/* Proof URL */}
+ {assignment.proof_url && (
+
+ )}
+
+ {/* Proof comment */}
+ {assignment.proof_comment && (
+
+
Комментарий:
+
{assignment.proof_comment}
+
+ )}
+
+ {!assignment.proof_image_url && !assignment.proof_url && (
+ Пруф не предоставлен
+ )}
+
+
+
+ {/* Dispute button */}
+ {assignment.can_dispute && !dispute && !showDisputeForm && (
+
+ )}
+
+ {/* Dispute creation form */}
+ {showDisputeForm && !dispute && (
+
+
+
+
+ Оспорить выполнение
+
+
+
+ Опишите причину оспаривания. После создания у участников будет 24 часа для голосования.
+
+
+
+
+ )}
+
+ {/* Dispute section */}
+ {dispute && (
+
+
+
+
+
+ Оспаривание
+
+
+ {dispute.status === 'open' ? (
+
+
+ {getTimeRemaining(dispute.expires_at)}
+
+ ) : dispute.status === 'valid' ? (
+
+
+ Пруф валиден
+
+ ) : (
+
+
+ Пруф невалиден
+
+ )}
+
+
+
+
+ Открыл: {dispute.raised_by.nickname}
+
+
+ Дата: {formatDate(dispute.created_at)}
+
+
+
+
+
Причина:
+
{dispute.reason}
+
+
+ {/* Voting section */}
+ {dispute.status === 'open' && (
+
+
Голосование
+
+
+
+
+ {dispute.votes_valid}
+ валидно
+
+
+
+ {dispute.votes_invalid}
+ невалидно
+
+
+
+
+
+
+
+
+ {dispute.my_vote !== null && (
+
+ Вы проголосовали: {dispute.my_vote ? 'валидно' : 'невалидно'}
+
+ )}
+
+ )}
+
+ {/* Comments section */}
+
+
+
+ Обсуждение ({dispute.comments.length})
+
+
+ {dispute.comments.length > 0 && (
+
+ {dispute.comments.map((comment) => (
+
+
+
+ {comment.user.nickname}
+ {comment.user.id === user?.id && ' (Вы)'}
+
+
+ {formatDate(comment.created_at)}
+
+
+
{comment.text}
+
+ ))}
+
+ )}
+
+ {/* Add comment form */}
+ {dispute.status === 'open' && (
+
+ setCommentText(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault()
+ handleAddComment()
+ }
+ }}
+ />
+
+
+ )}
+
+
+
+ )}
+
+ )
+}
diff --git a/frontend/src/pages/PlayPage.tsx b/frontend/src/pages/PlayPage.tsx
index c73edee..280bc48 100644
--- a/frontend/src/pages/PlayPage.tsx
+++ b/frontend/src/pages/PlayPage.tsx
@@ -1,11 +1,11 @@
import { useState, useEffect, useRef } from 'react'
import { useParams, Link } from 'react-router-dom'
-import { marathonsApi, wheelApi, gamesApi, eventsApi } from '@/api'
-import type { Marathon, Assignment, SpinResult, Game, ActiveEvent, SwapCandidate, MySwapRequests, CommonEnemyLeaderboardEntry, EventAssignment, GameChoiceChallenges } from '@/types'
+import { marathonsApi, wheelApi, gamesApi, eventsApi, assignmentsApi } from '@/api'
+import type { Marathon, Assignment, SpinResult, Game, ActiveEvent, SwapCandidate, MySwapRequests, CommonEnemyLeaderboardEntry, EventAssignment, GameChoiceChallenges, ReturnedAssignment } from '@/types'
import { Button, Card, CardContent } from '@/components/ui'
import { SpinWheel } from '@/components/SpinWheel'
import { EventBanner } from '@/components/EventBanner'
-import { Loader2, Upload, X, Gamepad2, ArrowLeftRight, Check, XCircle, Clock, Send, Trophy, Users, ArrowLeft } from 'lucide-react'
+import { Loader2, Upload, X, Gamepad2, ArrowLeftRight, Check, XCircle, Clock, Send, Trophy, Users, ArrowLeft, AlertTriangle } from 'lucide-react'
export function PlayPage() {
const { id } = useParams<{ id: string }>()
@@ -53,6 +53,9 @@ export function PlayPage() {
const [eventComment, setEventComment] = useState('')
const [isEventCompleting, setIsEventCompleting] = useState(false)
+ // Returned assignments state
+ const [returnedAssignments, setReturnedAssignments] = useState