/** 방에서 사람 이름을 누르면 뜨는 기록 카드 (docs/06 §5): 이 게임의 판·승·패·무와 전체 합계. */ import { useEffect, useState, type ReactNode } from 'react'; import { createPortal } from 'react-dom'; import { isBotId, type MyGameSummary, type PublicUser } from '@bg/shared/lite'; import { api, type GameStat } from '../net/api'; import { Avatar, Sheet } from './ui'; import { gameName } from './GameNotice'; /** 패 = 끝까지 한 판 - 승 - 무 (1등이 아니면 패로 센다). */ export function record(s: Pick) { const losses = Math.max(0, s.played - s.wins - s.draws); const rate = s.played ? Math.round((s.wins / s.played) * 100) : null; return { played: s.played, wins: s.wins, draws: s.draws, losses, rate }; } export function sumStats(stats: GameStat[]) { return stats.reduce((t, s) => ({ played: t.played + s.played, wins: t.wins + s.wins, draws: t.draws + s.draws }), { played: 0, wins: 0, draws: 0 }); } export function recordText(s: Pick): string { const r = record(s); return `${r.played}판 ${r.wins}승 ${r.losses}패 ${r.draws}무${r.rate !== null ? ` (승률 ${r.rate}%)` : ''}`; } /** One finished game from the player's side (내 게임 기록, 관리자 사용자 기록). */ export function resultLabel(g: MyGameSummary): string { if (g.endReason === 'void') return '무효'; if (g.myRank === null) return '끝남'; if (g.players.length === 1) return '혼자 했어요'; if (g.players.every((p) => p.rank === 1)) return '무승부'; if (g.players.length === 2) return g.myRank === 1 ? '이겼어요' : '졌어요'; return `${g.myRank}등`; } /** A player's name that opens their record card. Computer players are plain text. */ export function PlayerName({ user, gameId, session, children }: { user: PublicUser; gameId: string; session?: { played: number; wins: number }; children?: ReactNode }) { const [open, setOpen] = useState(false); if (isBotId(user.id)) return <>{children ?? user.nickname}; return ( <> {/* Portal: the name sits in one-line, bold text that the card must not inherit. */} {open && createPortal( setOpen(false)} />, document.body)} ); } function PlayerCard({ user, gameId, session, onClose }: { user: PublicUser; gameId: string; session?: { played: number; wins: number }; onClose: () => void }) { const [stats, setStats] = useState(null); const [error, setError] = useState(null); useEffect(() => { api.userStats(user.id).then( (r) => setStats(r.stats), (e) => setError((e as Error).message), ); }, [user.id]); const name = gameName(gameId); const here = stats ? record(stats.find((s) => s.gameId === gameId) ?? { played: 0, wins: 0, draws: 0 }) : null; const total = stats ? sumStats(stats) : null; return (

{user.nickname}

{user.kind === 'member' ? '디스코드 회원' : '게스트'}
{error ? (

{error}

) : !here || !total ? (

기록을 불러오는 중…

) : ( <>
{name} 기록 {here.played === 0 ? (

아직 이 게임을 끝까지 한 적이 없어요.

) : (
)}
{session && session.played > 0 && (

이 방에서 {session.played}판 {session.wins}승

)}

전체 게임 {total.played ? recordText(total) : '기록 없음'}

{user.kind === 'guest' &&

게스트 기록은 사이트에 들어오지 않은 채 하루가 지나면 계정과 함께 지워져요.

} )}
); } export function RecordCell({ label, value }: { label: string; value: number | string }) { return (
{label} {value}
); }