일정 카드 AI 픽을 팀 아래 모델 아이콘으로 표시

기존 "AI 픽 삼성 3" 텍스트 줄 대신 GPT/Claude/Gemini 아이콘을
각자 픽한 팀 쪽 아래에 배치 (무승부 픽은 가운데). 참여수는 우측 유지.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jwkim 2026-08-25 09:07:31 +09:00
parent d6b3ddeee0
commit 8da8b7e7bd

View File

@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
import { timeOnly, dateKey } from "@/lib/format";
import { getLive, type LiveData } from "@/lib/api";
import { type Lang, dict, teamShort, roundLabel as tRound, dayName, monthEn } from "@/lib/i18n";
import type { League, Match, MatchPhase } from "@/lib/types";
import type { League, Match, MatchPhase, ModelName, Outcome } from "@/lib/types";
import TeamFlag from "./TeamFlag";
import StandingsTable from "./StandingsTable";
@ -435,6 +435,29 @@ function RankBadge({ rank, lang = "ko" }: { rank?: number | null; lang?: Lang })
);
}
// AI 픽 아이콘 (일정 카드용 — Arena 와 동일 공식 로고)
const MODEL_ICON: Record<ModelName, string> = {
GPT: "/icons/gpt.png",
Claude: "/icons/claude.jpg",
Gemini: "/icons/gemini.jpeg",
};
function AiPickIcons({ models }: { models: ModelName[] }) {
return (
<>
{models.map((m) => (
<img
key={m}
src={MODEL_ICON[m]}
alt={m}
title={m}
className="h-[18px] w-[18px] shrink-0 rounded-full border border-white/25 object-cover"
/>
))}
</>
);
}
function MatchCard({
match,
lang,
@ -490,6 +513,15 @@ function MatchCard({
const rightSt = flip ? match.extras?.standings?.a : match.extras?.standings?.b;
const flagCls = isClub ? "h-7 w-7 shrink-0" : "h-6 w-9 shrink-0";
// AI 픽: 픽한 팀 아래에 모델 아이콘 배치 (무승부 픽은 가운데)
const picksFor = (o: Outcome) =>
match.predictions.filter((p) => p.outcome === o).map((p) => p.model);
const leftPicks = picksFor(flip ? "TEAM_B_WIN" : "TEAM_A_WIN");
const rightPicks = picksFor(flip ? "TEAM_A_WIN" : "TEAM_B_WIN");
const drawPicks = picksFor("DRAW");
const showAiRow =
match.votable && phase !== "cancelled" && match.predictions.length > 0;
// 비투표(타 조)는 클릭 비활성, 투표 가능 조(A)는 상세로 이동
const inner = (
<>
@ -558,6 +590,24 @@ function MatchCard({
</div>
</div>
{/* AI 픽: 각 모델 아이콘을 픽한 팀 아래에 표시 (무승부 픽은 가운데) */}
{showAiRow && (
<div
className="mt-1.5 grid grid-cols-[1fr_auto_1fr] items-center gap-2"
aria-label={`${t.aiPicks}: ${aiSplit(match, lang).join(", ")}`}
>
<div className="flex items-center gap-1">
<AiPickIcons models={leftPicks} />
</div>
<div className="flex items-center justify-center gap-1">
<AiPickIcons models={drawPicks} />
</div>
<div className="flex items-center justify-end gap-1">
<AiPickIcons models={rightPicks} />
</div>
</div>
)}
{/* 야구: 선발 매치업 (프리뷰 캐시 있을 때만) */}
{isBaseball && !match.result && (match.extras?.starterA || match.extras?.starterB) && (
<div className="mt-2 text-center text-[12px] font-semibold text-white/65">
@ -578,13 +628,9 @@ function MatchCard({
</div>
)}
{/* 투표 가능 조만: AI 픽 갈림 + 참여수 + 이동 화살표 (취소 경기는 숨김) */}
{/* 투표 가능 조만: 참여수 + 이동 화살표 (AI 픽은 위의 아이콘 행이 담당, 취소 경기는 숨김) */}
{match.votable && phase !== "cancelled" && (
<div className="mt-3 flex items-center justify-between text-[12px] font-bold text-[#F4F3FE]">
<span>
<span className="font-semibold text-white/65">{t.aiPicks}</span>{" "}
{aiSplit(match, lang).join(" · ")}
</span>
<div className="mt-3 flex items-center justify-end text-[12px] font-bold text-[#F4F3FE]">
<span className="flex items-center gap-2">
<span>{t.joined((match.crowd?.total ?? 0).toLocaleString())}</span>
<span className="text-[#94FBE0]"></span>