Добавлена поддержка обмена играми с типом прохождения (playthrough)
- Обновлены схемы SwapCandidate и SwapRequestChallengeInfo для поддержки прохождений - get_swap_candidates теперь возвращает и челленджи, и прохождения - accept_swap_request теперь корректно меняет challenge_id, game_id, is_playthrough и bonus_assignments - Обновлён UI для отображения прохождений в списке кандидатов и запросах обмена Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ from app.models import (
|
|||||||
Event, EventType, Activity, ActivityType, Assignment, AssignmentStatus, Challenge, Game,
|
Event, EventType, Activity, ActivityType, Assignment, AssignmentStatus, Challenge, Game,
|
||||||
SwapRequest as SwapRequestModel, SwapRequestStatus, User,
|
SwapRequest as SwapRequestModel, SwapRequestStatus, User,
|
||||||
)
|
)
|
||||||
|
from app.models.bonus_assignment import BonusAssignment
|
||||||
from fastapi import UploadFile, File, Form
|
from fastapi import UploadFile, File, Form
|
||||||
|
|
||||||
from app.schemas import (
|
from app.schemas import (
|
||||||
@@ -275,6 +276,26 @@ async def stop_event(
|
|||||||
return MessageResponse(message="Event stopped")
|
return MessageResponse(message="Event stopped")
|
||||||
|
|
||||||
|
|
||||||
|
def build_assignment_info(assignment: Assignment) -> SwapRequestChallengeInfo:
|
||||||
|
"""Build SwapRequestChallengeInfo from assignment (challenge or playthrough)"""
|
||||||
|
if assignment.is_playthrough:
|
||||||
|
return SwapRequestChallengeInfo(
|
||||||
|
is_playthrough=True,
|
||||||
|
playthrough_description=assignment.game.playthrough_description,
|
||||||
|
playthrough_points=assignment.game.playthrough_points,
|
||||||
|
game_title=assignment.game.title,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return SwapRequestChallengeInfo(
|
||||||
|
is_playthrough=False,
|
||||||
|
title=assignment.challenge.title,
|
||||||
|
description=assignment.challenge.description,
|
||||||
|
points=assignment.challenge.points,
|
||||||
|
difficulty=assignment.challenge.difficulty,
|
||||||
|
game_title=assignment.challenge.game.title,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_swap_request_response(
|
def build_swap_request_response(
|
||||||
swap_req: SwapRequestModel,
|
swap_req: SwapRequestModel,
|
||||||
) -> SwapRequestResponse:
|
) -> SwapRequestResponse:
|
||||||
@@ -298,20 +319,8 @@ def build_swap_request_response(
|
|||||||
role=swap_req.to_participant.user.role,
|
role=swap_req.to_participant.user.role,
|
||||||
created_at=swap_req.to_participant.user.created_at,
|
created_at=swap_req.to_participant.user.created_at,
|
||||||
),
|
),
|
||||||
from_challenge=SwapRequestChallengeInfo(
|
from_challenge=build_assignment_info(swap_req.from_assignment),
|
||||||
title=swap_req.from_assignment.challenge.title,
|
to_challenge=build_assignment_info(swap_req.to_assignment),
|
||||||
description=swap_req.from_assignment.challenge.description,
|
|
||||||
points=swap_req.from_assignment.challenge.points,
|
|
||||||
difficulty=swap_req.from_assignment.challenge.difficulty,
|
|
||||||
game_title=swap_req.from_assignment.challenge.game.title,
|
|
||||||
),
|
|
||||||
to_challenge=SwapRequestChallengeInfo(
|
|
||||||
title=swap_req.to_assignment.challenge.title,
|
|
||||||
description=swap_req.to_assignment.challenge.description,
|
|
||||||
points=swap_req.to_assignment.challenge.points,
|
|
||||||
difficulty=swap_req.to_assignment.challenge.difficulty,
|
|
||||||
game_title=swap_req.to_assignment.challenge.game.title,
|
|
||||||
),
|
|
||||||
created_at=swap_req.created_at,
|
created_at=swap_req.created_at,
|
||||||
responded_at=swap_req.responded_at,
|
responded_at=swap_req.responded_at,
|
||||||
)
|
)
|
||||||
@@ -349,11 +358,12 @@ async def create_swap_request(
|
|||||||
if target.id == participant.id:
|
if target.id == participant.id:
|
||||||
raise HTTPException(status_code=400, detail="Cannot swap with yourself")
|
raise HTTPException(status_code=400, detail="Cannot swap with yourself")
|
||||||
|
|
||||||
# Get both active assignments
|
# Get both active assignments (with challenge.game or game for playthrough)
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(Assignment)
|
select(Assignment)
|
||||||
.options(
|
.options(
|
||||||
selectinload(Assignment.challenge).selectinload(Challenge.game)
|
selectinload(Assignment.challenge).selectinload(Challenge.game),
|
||||||
|
selectinload(Assignment.game), # For playthrough
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
Assignment.participant_id == participant.id,
|
Assignment.participant_id == participant.id,
|
||||||
@@ -365,7 +375,8 @@ async def create_swap_request(
|
|||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(Assignment)
|
select(Assignment)
|
||||||
.options(
|
.options(
|
||||||
selectinload(Assignment.challenge).selectinload(Challenge.game)
|
selectinload(Assignment.challenge).selectinload(Challenge.game),
|
||||||
|
selectinload(Assignment.game), # For playthrough
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
Assignment.participant_id == target.id,
|
Assignment.participant_id == target.id,
|
||||||
@@ -417,7 +428,7 @@ async def create_swap_request(
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(swap_request)
|
await db.refresh(swap_request)
|
||||||
|
|
||||||
# Load relationships for response
|
# Load relationships for response (including game for playthrough)
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(SwapRequestModel)
|
select(SwapRequestModel)
|
||||||
.options(
|
.options(
|
||||||
@@ -426,9 +437,13 @@ async def create_swap_request(
|
|||||||
selectinload(SwapRequestModel.from_assignment)
|
selectinload(SwapRequestModel.from_assignment)
|
||||||
.selectinload(Assignment.challenge)
|
.selectinload(Assignment.challenge)
|
||||||
.selectinload(Challenge.game),
|
.selectinload(Challenge.game),
|
||||||
|
selectinload(SwapRequestModel.from_assignment)
|
||||||
|
.selectinload(Assignment.game),
|
||||||
selectinload(SwapRequestModel.to_assignment)
|
selectinload(SwapRequestModel.to_assignment)
|
||||||
.selectinload(Assignment.challenge)
|
.selectinload(Assignment.challenge)
|
||||||
.selectinload(Challenge.game),
|
.selectinload(Challenge.game),
|
||||||
|
selectinload(SwapRequestModel.to_assignment)
|
||||||
|
.selectinload(Assignment.game),
|
||||||
)
|
)
|
||||||
.where(SwapRequestModel.id == swap_request.id)
|
.where(SwapRequestModel.id == swap_request.id)
|
||||||
)
|
)
|
||||||
@@ -461,9 +476,13 @@ async def get_my_swap_requests(
|
|||||||
selectinload(SwapRequestModel.from_assignment)
|
selectinload(SwapRequestModel.from_assignment)
|
||||||
.selectinload(Assignment.challenge)
|
.selectinload(Assignment.challenge)
|
||||||
.selectinload(Challenge.game),
|
.selectinload(Challenge.game),
|
||||||
|
selectinload(SwapRequestModel.from_assignment)
|
||||||
|
.selectinload(Assignment.game),
|
||||||
selectinload(SwapRequestModel.to_assignment)
|
selectinload(SwapRequestModel.to_assignment)
|
||||||
.selectinload(Assignment.challenge)
|
.selectinload(Assignment.challenge)
|
||||||
.selectinload(Challenge.game),
|
.selectinload(Challenge.game),
|
||||||
|
selectinload(SwapRequestModel.to_assignment)
|
||||||
|
.selectinload(Assignment.game),
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
SwapRequestModel.event_id == event.id,
|
SwapRequestModel.event_id == event.id,
|
||||||
@@ -553,10 +572,39 @@ async def accept_swap_request(
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
raise HTTPException(status_code=400, detail="One or both assignments are no longer active")
|
raise HTTPException(status_code=400, detail="One or both assignments are no longer active")
|
||||||
|
|
||||||
# Perform the swap
|
# Perform the swap (swap challenge_id, game_id, and is_playthrough)
|
||||||
from_challenge_id = from_assignment.challenge_id
|
from_challenge_id = from_assignment.challenge_id
|
||||||
|
from_game_id = from_assignment.game_id
|
||||||
|
from_is_playthrough = from_assignment.is_playthrough
|
||||||
|
|
||||||
from_assignment.challenge_id = to_assignment.challenge_id
|
from_assignment.challenge_id = to_assignment.challenge_id
|
||||||
|
from_assignment.game_id = to_assignment.game_id
|
||||||
|
from_assignment.is_playthrough = to_assignment.is_playthrough
|
||||||
|
|
||||||
to_assignment.challenge_id = from_challenge_id
|
to_assignment.challenge_id = from_challenge_id
|
||||||
|
to_assignment.game_id = from_game_id
|
||||||
|
to_assignment.is_playthrough = from_is_playthrough
|
||||||
|
|
||||||
|
# Swap bonus assignments between the two assignments
|
||||||
|
from sqlalchemy import update as sql_update
|
||||||
|
|
||||||
|
# Get bonus assignments for both
|
||||||
|
from_bonus_result = await db.execute(
|
||||||
|
select(BonusAssignment).where(BonusAssignment.main_assignment_id == from_assignment.id)
|
||||||
|
)
|
||||||
|
from_bonus_assignments = from_bonus_result.scalars().all()
|
||||||
|
|
||||||
|
to_bonus_result = await db.execute(
|
||||||
|
select(BonusAssignment).where(BonusAssignment.main_assignment_id == to_assignment.id)
|
||||||
|
)
|
||||||
|
to_bonus_assignments = to_bonus_result.scalars().all()
|
||||||
|
|
||||||
|
# Move bonus assignments: from -> to, to -> from
|
||||||
|
for bonus in from_bonus_assignments:
|
||||||
|
bonus.main_assignment_id = to_assignment.id
|
||||||
|
|
||||||
|
for bonus in to_bonus_assignments:
|
||||||
|
bonus.main_assignment_id = from_assignment.id
|
||||||
|
|
||||||
# Update request status
|
# Update request status
|
||||||
swap_request.status = SwapRequestStatus.ACCEPTED.value
|
swap_request.status = SwapRequestStatus.ACCEPTED.value
|
||||||
@@ -865,8 +913,10 @@ async def get_swap_candidates(
|
|||||||
if not event or event.type != EventType.SWAP.value:
|
if not event or event.type != EventType.SWAP.value:
|
||||||
raise HTTPException(status_code=400, detail="No active swap event")
|
raise HTTPException(status_code=400, detail="No active swap event")
|
||||||
|
|
||||||
# Get all participants except current user with active assignments
|
|
||||||
from app.models import Game
|
from app.models import Game
|
||||||
|
candidates = []
|
||||||
|
|
||||||
|
# Get challenge-based assignments
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(Participant, Assignment, Challenge, Game)
|
select(Participant, Assignment, Challenge, Game)
|
||||||
.join(Assignment, Assignment.participant_id == Participant.id)
|
.join(Assignment, Assignment.participant_id == Participant.id)
|
||||||
@@ -877,12 +927,11 @@ async def get_swap_candidates(
|
|||||||
Participant.marathon_id == marathon_id,
|
Participant.marathon_id == marathon_id,
|
||||||
Participant.id != participant.id,
|
Participant.id != participant.id,
|
||||||
Assignment.status == AssignmentStatus.ACTIVE.value,
|
Assignment.status == AssignmentStatus.ACTIVE.value,
|
||||||
|
Assignment.is_playthrough == False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rows = result.all()
|
for p, assignment, challenge, game in result.all():
|
||||||
|
candidates.append(SwapCandidate(
|
||||||
return [
|
|
||||||
SwapCandidate(
|
|
||||||
participant_id=p.id,
|
participant_id=p.id,
|
||||||
user=UserPublic(
|
user=UserPublic(
|
||||||
id=p.user.id,
|
id=p.user.id,
|
||||||
@@ -892,14 +941,45 @@ async def get_swap_candidates(
|
|||||||
role=p.user.role,
|
role=p.user.role,
|
||||||
created_at=p.user.created_at,
|
created_at=p.user.created_at,
|
||||||
),
|
),
|
||||||
|
is_playthrough=False,
|
||||||
challenge_title=challenge.title,
|
challenge_title=challenge.title,
|
||||||
challenge_description=challenge.description,
|
challenge_description=challenge.description,
|
||||||
challenge_points=challenge.points,
|
challenge_points=challenge.points,
|
||||||
challenge_difficulty=challenge.difficulty,
|
challenge_difficulty=challenge.difficulty,
|
||||||
game_title=game.title,
|
game_title=game.title,
|
||||||
|
))
|
||||||
|
|
||||||
|
# Get playthrough-based assignments
|
||||||
|
result = await db.execute(
|
||||||
|
select(Participant, Assignment, Game)
|
||||||
|
.join(Assignment, Assignment.participant_id == Participant.id)
|
||||||
|
.join(Game, Assignment.game_id == Game.id)
|
||||||
|
.options(selectinload(Participant.user))
|
||||||
|
.where(
|
||||||
|
Participant.marathon_id == marathon_id,
|
||||||
|
Participant.id != participant.id,
|
||||||
|
Assignment.status == AssignmentStatus.ACTIVE.value,
|
||||||
|
Assignment.is_playthrough == True,
|
||||||
)
|
)
|
||||||
for p, assignment, challenge, game in rows
|
)
|
||||||
]
|
for p, assignment, game in result.all():
|
||||||
|
candidates.append(SwapCandidate(
|
||||||
|
participant_id=p.id,
|
||||||
|
user=UserPublic(
|
||||||
|
id=p.user.id,
|
||||||
|
login=p.user.login,
|
||||||
|
nickname=p.user.nickname,
|
||||||
|
avatar_url=None,
|
||||||
|
role=p.user.role,
|
||||||
|
created_at=p.user.created_at,
|
||||||
|
),
|
||||||
|
is_playthrough=True,
|
||||||
|
playthrough_description=game.playthrough_description,
|
||||||
|
playthrough_points=game.playthrough_points,
|
||||||
|
game_title=game.title,
|
||||||
|
))
|
||||||
|
|
||||||
|
return candidates
|
||||||
|
|
||||||
|
|
||||||
@router.get("/marathons/{marathon_id}/common-enemy-leaderboard", response_model=list[CommonEnemyLeaderboard])
|
@router.get("/marathons/{marathon_id}/common-enemy-leaderboard", response_model=list[CommonEnemyLeaderboard])
|
||||||
|
|||||||
@@ -128,10 +128,16 @@ class SwapCandidate(BaseModel):
|
|||||||
"""Participant available for assignment swap"""
|
"""Participant available for assignment swap"""
|
||||||
participant_id: int
|
participant_id: int
|
||||||
user: UserPublic
|
user: UserPublic
|
||||||
challenge_title: str
|
is_playthrough: bool = False
|
||||||
challenge_description: str
|
# Challenge fields (used when is_playthrough=False)
|
||||||
challenge_points: int
|
challenge_title: str | None = None
|
||||||
challenge_difficulty: str
|
challenge_description: str | None = None
|
||||||
|
challenge_points: int | None = None
|
||||||
|
challenge_difficulty: str | None = None
|
||||||
|
# Playthrough fields (used when is_playthrough=True)
|
||||||
|
playthrough_description: str | None = None
|
||||||
|
playthrough_points: int | None = None
|
||||||
|
# Common field
|
||||||
game_title: str
|
game_title: str
|
||||||
|
|
||||||
|
|
||||||
@@ -145,11 +151,17 @@ class SwapRequestCreate(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class SwapRequestChallengeInfo(BaseModel):
|
class SwapRequestChallengeInfo(BaseModel):
|
||||||
"""Challenge info for swap request display"""
|
"""Challenge or playthrough info for swap request display"""
|
||||||
title: str
|
is_playthrough: bool = False
|
||||||
description: str
|
# Challenge fields (used when is_playthrough=False)
|
||||||
points: int
|
title: str | None = None
|
||||||
difficulty: str
|
description: str | None = None
|
||||||
|
points: int | None = None
|
||||||
|
difficulty: str | None = None
|
||||||
|
# Playthrough fields (used when is_playthrough=True)
|
||||||
|
playthrough_description: str | None = None
|
||||||
|
playthrough_points: int | None = None
|
||||||
|
# Common field
|
||||||
game_title: str
|
game_title: str
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1054,7 +1054,14 @@ export function PlayPage() {
|
|||||||
<p className="text-center text-gray-500 py-8">Нет доступных заданий для копирования</p>
|
<p className="text-center text-gray-500 py-8">Нет доступных заданий для копирования</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{copycatCandidates.map((candidate) => (
|
{copycatCandidates.map((candidate) => {
|
||||||
|
const displayTitle = candidate.is_playthrough
|
||||||
|
? `Прохождение: ${candidate.game_title}`
|
||||||
|
: candidate.challenge_title || ''
|
||||||
|
const displayDetails = candidate.is_playthrough
|
||||||
|
? `${candidate.playthrough_points || 0} очков`
|
||||||
|
: `${candidate.game_title} • ${candidate.challenge_points} очков • ${candidate.challenge_difficulty}`
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
key={candidate.participant_id}
|
key={candidate.participant_id}
|
||||||
onClick={() => handleUseCopycat(candidate.participant_id)}
|
onClick={() => handleUseCopycat(candidate.participant_id)}
|
||||||
@@ -1062,12 +1069,13 @@ export function PlayPage() {
|
|||||||
className="w-full p-3 bg-dark-700/50 hover:bg-dark-700 rounded-xl text-left transition-colors border border-dark-600 hover:border-cyan-500/30 disabled:opacity-50"
|
className="w-full p-3 bg-dark-700/50 hover:bg-dark-700 rounded-xl text-left transition-colors border border-dark-600 hover:border-cyan-500/30 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<p className="text-white font-medium">{candidate.user.nickname}</p>
|
<p className="text-white font-medium">{candidate.user.nickname}</p>
|
||||||
<p className="text-cyan-400 text-sm">{candidate.challenge_title}</p>
|
<p className="text-cyan-400 text-sm">{displayTitle}</p>
|
||||||
<p className="text-gray-500 text-xs">
|
<p className="text-gray-500 text-xs">
|
||||||
{candidate.game_title} • {candidate.challenge_points} очков • {candidate.challenge_difficulty}
|
{displayDetails}
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</GlassCard>
|
</GlassCard>
|
||||||
@@ -1832,7 +1840,14 @@ export function PlayPage() {
|
|||||||
Входящие запросы ({swapRequests.incoming.length})
|
Входящие запросы ({swapRequests.incoming.length})
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{swapRequests.incoming.map((request) => (
|
{swapRequests.incoming.map((request) => {
|
||||||
|
const challengeTitle = request.from_challenge.is_playthrough
|
||||||
|
? `Прохождение: ${request.from_challenge.game_title}`
|
||||||
|
: request.from_challenge.title || ''
|
||||||
|
const challengeDetails = request.from_challenge.is_playthrough
|
||||||
|
? `${request.from_challenge.playthrough_points || 0} очков`
|
||||||
|
: `${request.from_challenge.game_title} • ${request.from_challenge.points} очков`
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={request.id}
|
key={request.id}
|
||||||
className="p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-xl"
|
className="p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-xl"
|
||||||
@@ -1843,10 +1858,10 @@ export function PlayPage() {
|
|||||||
{request.from_user.nickname} предлагает обмен
|
{request.from_user.nickname} предлагает обмен
|
||||||
</p>
|
</p>
|
||||||
<p className="text-yellow-400 text-sm mt-1">
|
<p className="text-yellow-400 text-sm mt-1">
|
||||||
Вы получите: <span className="font-medium">{request.from_challenge.title}</span>
|
Вы получите: <span className="font-medium">{challengeTitle}</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-400 text-xs">
|
<p className="text-gray-400 text-xs">
|
||||||
{request.from_challenge.game_title} • {request.from_challenge.points} очков
|
{challengeDetails}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@@ -1873,7 +1888,8 @@ export function PlayPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1886,7 +1902,11 @@ export function PlayPage() {
|
|||||||
Отправленные запросы ({swapRequests.outgoing.length})
|
Отправленные запросы ({swapRequests.outgoing.length})
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{swapRequests.outgoing.map((request) => (
|
{swapRequests.outgoing.map((request) => {
|
||||||
|
const challengeTitle = request.to_challenge.is_playthrough
|
||||||
|
? `Прохождение: ${request.to_challenge.game_title}`
|
||||||
|
: request.to_challenge.title || ''
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={request.id}
|
key={request.id}
|
||||||
className="p-4 bg-accent-500/10 border border-accent-500/30 rounded-xl"
|
className="p-4 bg-accent-500/10 border border-accent-500/30 rounded-xl"
|
||||||
@@ -1897,7 +1917,7 @@ export function PlayPage() {
|
|||||||
Запрос к {request.to_user.nickname}
|
Запрос к {request.to_user.nickname}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-accent-400 text-sm mt-1">
|
<p className="text-accent-400 text-sm mt-1">
|
||||||
Вы получите: <span className="font-medium">{request.to_challenge.title}</span>
|
Вы получите: <span className="font-medium">{challengeTitle}</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-500 text-xs mt-1">
|
<p className="text-gray-500 text-xs mt-1">
|
||||||
Ожидание подтверждения...
|
Ожидание подтверждения...
|
||||||
@@ -1915,7 +1935,8 @@ export function PlayPage() {
|
|||||||
</NeonButton>
|
</NeonButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1943,7 +1964,14 @@ export function PlayPage() {
|
|||||||
!swapRequests.outgoing.some(r => r.to_user.id === c.user.id) &&
|
!swapRequests.outgoing.some(r => r.to_user.id === c.user.id) &&
|
||||||
!swapRequests.incoming.some(r => r.from_user.id === c.user.id)
|
!swapRequests.incoming.some(r => r.from_user.id === c.user.id)
|
||||||
)
|
)
|
||||||
.map((candidate) => (
|
.map((candidate) => {
|
||||||
|
const displayTitle = candidate.is_playthrough
|
||||||
|
? `Прохождение: ${candidate.game_title}`
|
||||||
|
: candidate.challenge_title || ''
|
||||||
|
const displayDetails = candidate.is_playthrough
|
||||||
|
? `${candidate.playthrough_points || 0} очков`
|
||||||
|
: `${candidate.game_title} • ${candidate.challenge_points} очков • ${candidate.challenge_difficulty}`
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={candidate.participant_id}
|
key={candidate.participant_id}
|
||||||
className="p-4 bg-dark-700/50 rounded-xl border border-dark-600"
|
className="p-4 bg-dark-700/50 rounded-xl border border-dark-600"
|
||||||
@@ -1954,10 +1982,10 @@ export function PlayPage() {
|
|||||||
{candidate.user.nickname}
|
{candidate.user.nickname}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-neon-400 text-sm font-medium truncate">
|
<p className="text-neon-400 text-sm font-medium truncate">
|
||||||
{candidate.challenge_title}
|
{displayTitle}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-400 text-xs mt-1">
|
<p className="text-gray-400 text-xs mt-1">
|
||||||
{candidate.game_title} • {candidate.challenge_points} очков • {candidate.challenge_difficulty}
|
{displayDetails}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<NeonButton
|
<NeonButton
|
||||||
@@ -1966,7 +1994,7 @@ export function PlayPage() {
|
|||||||
onClick={() => handleSendSwapRequest(
|
onClick={() => handleSendSwapRequest(
|
||||||
candidate.participant_id,
|
candidate.participant_id,
|
||||||
candidate.user.nickname,
|
candidate.user.nickname,
|
||||||
candidate.challenge_title
|
displayTitle
|
||||||
)}
|
)}
|
||||||
isLoading={sendingRequestTo === candidate.participant_id}
|
isLoading={sendingRequestTo === candidate.participant_id}
|
||||||
disabled={sendingRequestTo !== null}
|
disabled={sendingRequestTo !== null}
|
||||||
@@ -1976,7 +2004,8 @@ export function PlayPage() {
|
|||||||
</NeonButton>
|
</NeonButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -321,10 +321,16 @@ export interface DroppedAssignment {
|
|||||||
export interface SwapCandidate {
|
export interface SwapCandidate {
|
||||||
participant_id: number
|
participant_id: number
|
||||||
user: User
|
user: User
|
||||||
challenge_title: string
|
is_playthrough: boolean
|
||||||
challenge_description: string
|
// Challenge fields (used when is_playthrough=false)
|
||||||
challenge_points: number
|
challenge_title: string | null
|
||||||
challenge_difficulty: Difficulty
|
challenge_description: string | null
|
||||||
|
challenge_points: number | null
|
||||||
|
challenge_difficulty: Difficulty | null
|
||||||
|
// Playthrough fields (used when is_playthrough=true)
|
||||||
|
playthrough_description: string | null
|
||||||
|
playthrough_points: number | null
|
||||||
|
// Common field
|
||||||
game_title: string
|
game_title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,10 +338,16 @@ export interface SwapCandidate {
|
|||||||
export type SwapRequestStatus = 'pending' | 'accepted' | 'declined' | 'cancelled'
|
export type SwapRequestStatus = 'pending' | 'accepted' | 'declined' | 'cancelled'
|
||||||
|
|
||||||
export interface SwapRequestChallengeInfo {
|
export interface SwapRequestChallengeInfo {
|
||||||
title: string
|
is_playthrough: boolean
|
||||||
description: string
|
// Challenge fields (used when is_playthrough=false)
|
||||||
points: number
|
title: string | null
|
||||||
difficulty: string
|
description: string | null
|
||||||
|
points: number | null
|
||||||
|
difficulty: string | null
|
||||||
|
// Playthrough fields (used when is_playthrough=true)
|
||||||
|
playthrough_description: string | null
|
||||||
|
playthrough_points: number | null
|
||||||
|
// Common field
|
||||||
game_title: string
|
game_title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user