Files
football-next/app/(user)/team/TeamBuilder.tsx
2026-05-03 17:01:46 +03:30

757 lines
29 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useMemo, useState } from "react";
import PositionBadge from "@/components/PositionBadge";
import Image from "next/image";
type Player = {
id: string;
name: string;
image: string | null;
position: "GK" | "DEF" | "MID" | "FWD";
price: number;
totalPoints: number;
country: { name: string; code: string; flagUrl?: string | null; isEliminated?: boolean };
};
type TeamPlayer = {
playerId: string;
goldenCardId: string | null;
isCaptain: boolean;
isViceCaptain: boolean;
isBench: boolean;
positionIndex: number;
player: Player;
};
type Team = {
id: string;
name: string;
budget: number;
totalPoints: number;
formation: string;
status: string;
players: TeamPlayer[];
} | null;
type SpecialCard = {
id: string;
status: "SEALED" | "OPENED";
state: "IN_INVENTORY" | "IN_TEAM" | "SOLD";
acquiredDate: string;
openedAt: string | null;
player: Player;
teamPlayer?: { playerId: string; teamId: string } | null;
};
type ReplacementCandidate = {
playerId: string;
name: string;
isBench: boolean;
isSpecial: boolean;
};
const FORMATIONS: Record<string, { label: string; def: number; mid: number; fwd: number }> = {
"4-3-3": { label: "4-3-3", def: 4, mid: 3, fwd: 3 },
"4-4-2": { label: "4-4-2", def: 4, mid: 4, fwd: 2 },
"4-5-1": { label: "4-5-1", def: 4, mid: 5, fwd: 1 },
"3-5-2": { label: "3-5-2", def: 3, mid: 5, fwd: 2 },
"3-4-3": { label: "3-4-3", def: 3, mid: 4, fwd: 3 },
"5-3-2": { label: "5-3-2", def: 5, mid: 3, fwd: 2 },
"5-4-1": { label: "5-4-1", def: 5, mid: 4, fwd: 1 },
};
const POSITION_LABELS: Record<Player["position"], string> = {
GK: "دروازه‌بان",
DEF: "مدافع",
MID: "هافبک",
FWD: "مهاجم",
};
function formatSaleValue(price: number) {
return Math.round(price * 0.7);
}
function isSpecialTeamPlayer(tp: TeamPlayer) {
return Boolean(tp.goldenCardId);
}
export default function TeamBuilder({
team: initialTeam,
allPlayers,
initialSpecialCards,
}: {
team: Team;
allPlayers: Player[];
initialSpecialCards: SpecialCard[];
}) {
const [team, setTeam] = useState<Team>(initialTeam);
const [specialCards, setSpecialCards] = useState<SpecialCard[]>(initialSpecialCards);
const [teamName, setTeamName] = useState("");
const [formation, setFormation] = useState(initialTeam?.formation ?? "4-3-3");
const [filter, setFilter] = useState("");
const [posFilter, setPosFilter] = useState("");
const [loading, setLoading] = useState(false);
const [msg, setMsg] = useState<{ text: string; type: "error" | "success" } | null>(null);
const [submitLoading, setSubmitLoading] = useState(false);
const [replacementDialog, setReplacementDialog] = useState<{
card: SpecialCard;
candidates: ReplacementCandidate[];
} | null>(null);
const specialPlayerIds = useMemo(
() => new Set(specialCards.filter((card) => card.state !== "SOLD").map((card) => card.player.id)),
[specialCards]
);
const spent = team?.players
.filter((tp) => !isSpecialTeamPlayer(tp))
.reduce((sum, tp) => sum + tp.player.price, 0) ?? 0;
const remaining = (team?.budget ?? 100) - spent;
const starters = team?.players.filter((tp) => !tp.isBench) ?? [];
const bench = team?.players.filter((tp) => tp.isBench) ?? [];
const specialSlotsUsed = team?.players.filter(isSpecialTeamPlayer).length ?? 0;
const gkSlots = starters.filter((tp) => tp.player.position === "GK");
const defSlots = starters.filter((tp) => tp.player.position === "DEF");
const midSlots = starters.filter((tp) => tp.player.position === "MID");
const fwdSlots = starters.filter((tp) => tp.player.position === "FWD");
const myPlayerIds = new Set(team?.players.map((tp) => tp.playerId) ?? []);
const filtered = allPlayers.filter(
(p) =>
!myPlayerIds.has(p.id) &&
!specialPlayerIds.has(p.id) &&
(posFilter ? p.position === posFilter : true) &&
(filter ? p.name.includes(filter) || p.country.name.includes(filter) : true)
);
const inventoryCards = specialCards.filter((card) => card.state === "IN_INVENTORY");
const inTeamCards = specialCards.filter((card) => card.state === "IN_TEAM");
const sealedCount = specialCards.filter((card) => card.status === "SEALED").length;
async function createTeam() {
if (!teamName.trim()) return;
setLoading(true);
const res = await fetch("/api/team", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: teamName, formation }),
});
const data = await res.json();
if (res.ok) {
setTeam({ ...data, players: [] });
setMsg(null);
} else {
setMsg({ text: data.error, type: "error" });
}
setLoading(false);
}
async function addPlayer(playerId: string, isBench = false) {
setLoading(true);
const res = await fetch("/api/team/players", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ playerId, isBench }),
});
const data = await res.json();
if (res.ok) {
const player = allPlayers.find((p) => p.id === playerId)!;
setTeam((current) =>
current ? { ...current, players: [...current.players, { ...data, goldenCardId: null, player }] } : current
);
setMsg(null);
} else {
setMsg({ text: data.error, type: "error" });
}
setLoading(false);
}
async function removePlayer(playerId: string) {
setLoading(true);
const teamPlayer = team?.players.find((tp) => tp.playerId === playerId) ?? null;
const res = await fetch("/api/team/players", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ playerId }),
});
const data = await res.json().catch(() => null);
if (res.ok) {
setTeam((current) =>
current ? { ...current, players: current.players.filter((tp) => tp.playerId !== playerId) } : current
);
if (teamPlayer?.goldenCardId) {
setSpecialCards((current) =>
current.map((card) => (card.id === teamPlayer.goldenCardId ? { ...card, state: "IN_INVENTORY" } : card))
);
setMsg({ text: "بازیکن ویژه از تیم خارج شد و به کارت ویژه برگشت", type: "success" });
} else {
setMsg(null);
}
} else if (data?.error) {
setMsg({ text: data.error, type: "error" });
}
setLoading(false);
}
async function sellSpecialCard(cardId: string) {
setLoading(true);
const res = await fetch(`/api/golden-cards/${cardId}/sell`, { method: "POST" });
const data = await res.json();
if (res.ok) {
setSpecialCards((current) => current.map((card) => (card.id === cardId ? { ...card, state: "SOLD" } : card)));
setTeam((current) => {
if (!current) return current;
const soldCard = specialCards.find((card) => card.id === cardId);
return {
...current,
budget: current.budget + data.addedBudget,
players: soldCard ? current.players.filter((tp) => tp.goldenCardId !== cardId) : current.players,
};
});
setMsg({ text: `${data.addedBudget} میلیون به بودجه تیم اضافه شد`, type: "success" });
} else {
setMsg({ text: data.error, type: "error" });
}
setLoading(false);
}
function mergeSpecialPlayer(card: SpecialCard, teamPlayer: { playerId: string; isBench: boolean; goldenCardId: string }) {
setTeam((current) => {
if (!current) return current;
const existing = current.players.find((tp) => tp.playerId === teamPlayer.playerId);
if (existing) {
return {
...current,
players: current.players.map((tp) =>
tp.playerId === teamPlayer.playerId ? { ...tp, goldenCardId: card.id, isBench: teamPlayer.isBench } : tp
),
};
}
return {
...current,
players: [
...current.players,
{
playerId: card.player.id,
goldenCardId: card.id,
isCaptain: false,
isViceCaptain: false,
isBench: teamPlayer.isBench,
positionIndex: 0,
player: card.player,
},
],
};
});
}
async function addSpecialCardToTeam(card: SpecialCard, replacePlayerId?: string) {
setLoading(true);
const res = await fetch(`/api/golden-cards/${card.id}/add-to-team`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(replacePlayerId ? { replacePlayerId } : {}),
});
const data = await res.json();
if (res.ok) {
setReplacementDialog(null);
setSpecialCards((current) => current.map((item) => (item.id === card.id ? { ...item, state: "IN_TEAM" } : item)));
if (data.replacedGoldenCardId) {
setSpecialCards((current) =>
current.map((item) => (item.id === data.replacedGoldenCardId ? { ...item, state: "IN_INVENTORY" } : item))
);
}
if (data.replacedPlayerId) {
setTeam((current) =>
current
? { ...current, players: current.players.filter((tp) => tp.playerId !== data.replacedPlayerId) }
: current
);
}
mergeSpecialPlayer(card, {
playerId: card.player.id,
isBench: data.teamPlayer.isBench,
goldenCardId: card.id,
});
setMsg({ text: data.message ?? `بازیکن ویژه در ${data.placement} قرار گرفت`, type: "success" });
} else if (res.status === 409 && data.needsReplacement) {
setReplacementDialog({ card, candidates: data.candidates });
} else {
setMsg({ text: data.error, type: "error" });
}
setLoading(false);
}
async function setCaptain(playerId: string, type: "captain" | "vice") {
await fetch("/api/team/captain", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ playerId, type }),
});
setTeam((current) => {
if (!current) return current;
return {
...current,
players: current.players.map((tp) => ({
...tp,
isCaptain: type === "captain" ? tp.playerId === playerId : tp.isCaptain,
isViceCaptain: type === "vice" ? tp.playerId === playerId : tp.isViceCaptain,
})),
};
});
}
async function submitTeam() {
setSubmitLoading(true);
const res = await fetch("/api/team/submit", { method: "POST" });
const data = await res.json();
if (res.ok) {
setTeam((current) => (current ? { ...current, status: "ACTIVE" } : current));
setMsg({ text: "تیم ثبت شد و وارد رقابت شد", type: "success" });
} else {
setMsg({ text: data.error, type: "error" });
}
setSubmitLoading(false);
}
const isComplete = starters.length === 11 && bench.length === 4;
const canSubmit = isComplete && team?.status !== "ACTIVE";
if (!team) {
return (
<div className="max-w-md mx-auto py-20 px-6 text-center">
<div className="text-6xl mb-6"></div>
<h1 className="text-2xl font-bold mb-2">تیمت را بساز</h1>
<p className="text-gray-500 mb-8 text-sm">با بودجه 100 میلیون، 15 بازیکن برای تیمت انتخاب کن.</p>
<input
type="text"
placeholder="نام تیم"
value={teamName}
onChange={(e) => setTeamName(e.target.value)}
className="w-full border-2 rounded-xl px-4 py-3 mb-4 focus:outline-none focus:border-green-500 text-center text-lg"
/>
<div className="mb-4">
<label className="block text-sm font-medium mb-2 text-right">ترکیب</label>
<div className="grid grid-cols-4 gap-2">
{Object.entries(FORMATIONS).map(([key]) => (
<button
key={key}
type="button"
onClick={() => setFormation(key)}
className={`py-2 rounded-xl text-sm font-bold border-2 transition ${
formation === key ? "bg-green-700 text-white border-green-700" : "bg-white border-gray-200 hover:border-green-400"
}`}
>
{key}
</button>
))}
</div>
</div>
<button
onClick={createTeam}
disabled={loading || !teamName.trim()}
className="w-full bg-green-700 text-white py-3 rounded-xl font-bold text-lg hover:bg-green-800 transition disabled:opacity-50"
>
ساخت تیم
</button>
</div>
);
}
return (
<div className="max-w-7xl mx-auto py-6 px-4">
{replacementDialog && (
<div className="fixed inset-0 z-50 bg-black/70 p-4 flex items-center justify-center">
<div className="w-full max-w-md rounded-3xl bg-white p-6 shadow-2xl">
<h3 className="text-xl font-bold mb-2">پست {POSITION_LABELS[replacementDialog.card.player.position]} پر است</h3>
<p className="text-sm text-gray-600 mb-4">
برای اضافه کردن {replacementDialog.card.player.name} یکی از بازیکنان این پست را جایگزین کنید.
</p>
<div className="space-y-2">
{replacementDialog.candidates.map((candidate) => (
<button
key={candidate.playerId}
onClick={() => addSpecialCardToTeam(replacementDialog.card, candidate.playerId)}
className="w-full rounded-2xl border border-gray-200 px-4 py-3 text-right hover:border-green-500 hover:bg-green-50 transition"
>
<div className="font-bold">{candidate.name}</div>
<div className="text-xs text-gray-500">
{candidate.isBench ? "ذخیره" : "فیکس"}
{candidate.isSpecial ? " | کارت ویژه" : ""}
</div>
</button>
))}
</div>
<button
onClick={() => setReplacementDialog(null)}
className="w-full mt-4 rounded-2xl bg-gray-100 py-3 text-sm font-bold text-gray-700 hover:bg-gray-200 transition"
>
بستن
</button>
</div>
</div>
)}
<div className="flex flex-wrap items-center justify-between gap-4 mb-6">
<div>
<h1 className="text-2xl font-bold">{team.name}</h1>
<div className="flex items-center gap-3 mt-1">
<span
className={`text-xs px-2 py-1 rounded-full font-medium ${
team.status === "ACTIVE" ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-600"
}`}
>
{team.status === "ACTIVE" ? "فعال" : "در حال تکمیل"}
</span>
<span className="text-sm text-gray-500">ترکیب: {formation}</span>
</div>
</div>
<div className="flex items-center gap-4">
<Metric label="امتیاز" value={team.totalPoints} tone="text-blue-700" />
<Metric label="بودجه" value={`${remaining.toFixed(1)}M`} tone={remaining < 0 ? "text-red-600" : "text-green-700"} />
<Metric label="ویژه" value={`${specialSlotsUsed}/3`} tone="text-amber-600" />
</div>
</div>
{msg && (
<div className={`mb-4 px-4 py-3 rounded-xl text-sm ${msg.type === "error" ? "bg-red-50 text-red-600" : "bg-green-50 text-green-700"}`}>
{msg.text}
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6">
<div className="lg:col-span-3">
<div
className="relative rounded-2xl overflow-hidden shadow-xl"
style={{ background: "linear-gradient(180deg,#1a5c35 0%,#2d8653 20%,#3a9e63 40%,#2d8653 60%,#1a5c35 100%)", minHeight: 500 }}
>
<svg className="absolute inset-0 w-full h-full opacity-20" viewBox="0 0 400 500" preserveAspectRatio="none">
<line x1="200" y1="0" x2="200" y2="500" stroke="white" strokeWidth="1" />
<circle cx="200" cy="250" r="50" stroke="white" strokeWidth="1" fill="none" />
<rect x="120" y="0" width="160" height="60" stroke="white" strokeWidth="1" fill="none" />
<rect x="120" y="440" width="160" height="60" stroke="white" strokeWidth="1" fill="none" />
<rect x="160" y="0" width="80" height="25" stroke="white" strokeWidth="1" fill="none" />
<rect x="160" y="475" width="80" height="25" stroke="white" strokeWidth="1" fill="none" />
</svg>
<div className="relative z-10 p-4 flex flex-col gap-3 h-full">
<PitchRow players={fwdSlots} slots={FORMATIONS[formation]?.fwd ?? 3} onRemove={removePlayer} onCaptain={setCaptain} onSell={sellSpecialCard} />
<PitchRow players={midSlots} slots={FORMATIONS[formation]?.mid ?? 3} onRemove={removePlayer} onCaptain={setCaptain} onSell={sellSpecialCard} />
<PitchRow players={defSlots} slots={FORMATIONS[formation]?.def ?? 4} onRemove={removePlayer} onCaptain={setCaptain} onSell={sellSpecialCard} />
<PitchRow players={gkSlots} slots={1} onRemove={removePlayer} onCaptain={setCaptain} onSell={sellSpecialCard} />
</div>
</div>
<div className="mt-3 bg-gray-800 rounded-2xl p-4">
<p className="text-gray-400 text-xs mb-3 font-medium">نیمکت</p>
<div className="flex gap-3 justify-center flex-wrap">
{bench.map((tp) => (
<PitchCard key={tp.playerId} tp={tp} onRemove={removePlayer} onCaptain={setCaptain} onSell={sellSpecialCard} small />
))}
{Array.from({ length: Math.max(0, 4 - bench.length) }).map((_, index) => (
<EmptySlot key={index} label="ذخیره" />
))}
</div>
</div>
{canSubmit && (
<button
onClick={submitTeam}
disabled={submitLoading}
className="w-full mt-4 bg-green-700 text-white py-3 rounded-xl font-bold text-lg hover:bg-green-800 transition disabled:opacity-50"
>
{submitLoading ? "در حال ثبت..." : "ثبت تیم"}
</button>
)}
</div>
<div className="lg:col-span-2 space-y-6">
<section className="bg-white rounded-2xl shadow p-4">
<div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-bold">کارتهای ویژه</h2>
<span className="text-xs rounded-full bg-amber-100 px-3 py-1 font-bold text-amber-700">
ظرفیت تیم: {specialSlotsUsed}/3
</span>
</div>
{sealedCount > 0 && <p className="text-xs text-gray-500 mb-3">{sealedCount} کارت ویژه هنوز باز نشده است.</p>}
<div className="space-y-3">
{inventoryCards.map((card) => (
<SpecialCardRow
key={card.id}
card={card}
loading={loading}
onAdd={() => addSpecialCardToTeam(card)}
onSell={() => sellSpecialCard(card.id)}
/>
))}
{inTeamCards.map((card) => (
<SpecialCardRow
key={card.id}
card={card}
loading={loading}
onSell={() => sellSpecialCard(card.id)}
/>
))}
{inventoryCards.length === 0 && inTeamCards.length === 0 && (
<div className="rounded-2xl border border-dashed border-gray-200 px-4 py-6 text-center text-sm text-gray-500">
کارت ویژه آماده استفاده ندارید.
</div>
)}
</div>
</section>
<section>
<h2 className="text-lg font-bold mb-3">خرید بازیکن عادی</h2>
<div className="flex gap-2 mb-3 flex-wrap">
{["", "GK", "DEF", "MID", "FWD"].map((pos) => (
<button
key={pos}
onClick={() => setPosFilter(pos)}
className={`px-3 py-1.5 rounded-lg text-xs font-bold transition ${
posFilter === pos ? "bg-green-700 text-white" : "bg-white shadow text-gray-700 hover:bg-gray-50"
}`}
>
{pos === "" ? "همه" : pos}
</button>
))}
</div>
<input
type="text"
placeholder="جست‌وجو"
value={filter}
onChange={(e) => setFilter(e.target.value)}
className="w-full border rounded-xl px-4 py-2 text-sm mb-3 focus:outline-none focus:ring-2 focus:ring-green-400"
/>
<div className="bg-white rounded-2xl shadow p-4 max-h-[520px] overflow-y-auto">
<div className="flex gap-3 flex-wrap">
{filtered.map((p) => (
<div
key={p.id}
className="flex-shrink-0 bg-gray-50 rounded-xl p-2 border-2 border-transparent hover:border-green-500 transition"
style={{ width: "90px" }}
>
<div className="relative w-20 h-20 rounded-lg overflow-hidden bg-gray-200 mb-1 mx-auto">
{p.image ? (
<Image src={`/uploads/players/${p.image}`} alt={p.name} fill className="object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-gray-400 text-3xl">👤</div>
)}
</div>
<div className="text-[10px] font-bold text-gray-800 text-center leading-tight mb-1">
{p.name.split(" ").slice(-1)[0]}
</div>
<div className="flex items-center justify-center gap-1 mb-1">
<PositionBadge position={p.position} />
</div>
<div className="text-[9px] text-center text-gray-600 mb-2">
<span className="text-green-700 font-bold">{p.price}M</span>
<span className="mx-1">|</span>
<span className="text-blue-700 font-bold">{p.totalPoints}pts</span>
</div>
<button
onClick={() => addPlayer(p.id)}
disabled={loading || p.price > remaining + 0.01}
className="w-full bg-green-600 text-white text-xs py-1 rounded-lg hover:bg-green-700 transition disabled:opacity-30 font-bold"
>
+ افزودن
</button>
</div>
))}
{filtered.length === 0 && <div className="w-full text-center text-gray-400 py-8">بازیکنی پیدا نشد</div>}
</div>
</div>
</section>
</div>
</div>
</div>
);
}
function Metric({ label, value, tone }: { label: string; value: string | number; tone: string }) {
return (
<div className="text-center">
<div className={`text-2xl font-bold ${tone}`}>{value}</div>
<div className="text-xs text-gray-500">{label}</div>
</div>
);
}
function PitchRow({
players,
slots,
onRemove,
onCaptain,
onSell,
}: {
players: TeamPlayer[];
slots: number;
onRemove: (id: string) => void;
onCaptain: (id: string, t: "captain" | "vice") => void;
onSell: (cardId: string) => void;
}) {
return (
<div className="flex justify-center gap-2 flex-wrap py-1">
{Array.from({ length: slots }).map((_, index) => {
const tp = players[index];
return tp ? (
<PitchCard key={tp.playerId} tp={tp} onRemove={onRemove} onCaptain={onCaptain} onSell={onSell} />
) : (
<EmptySlot key={index} label="خالی" />
);
})}
</div>
);
}
function PitchCard({
tp,
onRemove,
onCaptain,
onSell,
small,
}: {
tp: TeamPlayer;
onRemove: (id: string) => void;
onCaptain: (id: string, t: "captain" | "vice") => void;
onSell: (cardId: string) => void;
small?: boolean;
}) {
const isEliminated = Boolean(tp.player.country?.isEliminated);
const shortName = tp.player.name.split(" ").slice(-1)[0];
const special = isSpecialTeamPlayer(tp);
return (
<div className="relative group">
<div className={`rounded-xl p-2 shadow-lg ${special ? "bg-amber-50 border border-amber-300" : "bg-white/95"} ${small ? "w-16" : "w-20"}`}>
<div className={`relative ${small ? "w-12 h-12" : "w-16 h-16"} rounded-lg overflow-hidden bg-gray-200 mb-1 mx-auto`}>
{tp.player.image ? (
<Image src={`/uploads/players/${tp.player.image}`} alt={tp.player.name} fill className="object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-gray-400 text-2xl">👤</div>
)}
{isEliminated && (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
<span className="text-white text-xs font-bold">×</span>
</div>
)}
</div>
<div className={`text-[10px] font-bold text-center leading-tight ${isEliminated ? "opacity-50" : "text-gray-800"}`}>{shortName}</div>
{special && <div className="mt-1 text-center text-[8px] font-bold text-amber-700">کارت ویژه</div>}
<div className="flex items-center justify-center gap-1 mt-1">
{tp.isCaptain && <div className="bg-yellow-400 text-yellow-900 rounded-full w-4 h-4 flex items-center justify-center text-[8px] font-bold">C</div>}
{tp.isViceCaptain && <div className="bg-gray-400 text-white rounded-full w-4 h-4 flex items-center justify-center text-[8px] font-bold">V</div>}
</div>
</div>
<div className="absolute -bottom-10 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition flex gap-1 z-20">
<button
onClick={(e) => {
e.stopPropagation();
onCaptain(tp.playerId, tp.isCaptain ? "vice" : "captain");
}}
className="bg-yellow-400 text-yellow-900 text-[8px] px-2 py-0.5 rounded-full font-bold whitespace-nowrap shadow"
>
{tp.isCaptain ? "VC" : "C"}
</button>
{special && tp.goldenCardId ? (
<button
onClick={(e) => {
e.stopPropagation();
onSell(tp.goldenCardId!);
}}
className="bg-amber-500 text-white text-[8px] px-2 py-0.5 rounded-full font-bold shadow"
>
فروش
</button>
) : null}
<button
onClick={(e) => {
e.stopPropagation();
onRemove(tp.playerId);
}}
className="bg-red-500 text-white text-[8px] px-2 py-0.5 rounded-full font-bold shadow"
>
{special ? "برداشتن" : "حذف"}
</button>
</div>
</div>
);
}
function SpecialCardRow({
card,
loading,
onAdd,
onSell,
}: {
card: SpecialCard;
loading: boolean;
onAdd?: () => void;
onSell: () => void;
}) {
const canAdd = card.state === "IN_INVENTORY";
return (
<div className="rounded-2xl border border-amber-200 bg-amber-50/70 p-3">
<div className="flex gap-3">
<div className="relative w-16 h-16 rounded-xl overflow-hidden bg-white shrink-0">
{card.player.image ? (
<Image src={`/uploads/players/${card.player.image}`} alt={card.player.name} fill className="object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-gray-400 text-2xl">👤</div>
)}
</div>
<div className="min-w-0 flex-1">
<div className="font-bold truncate">{card.player.name}</div>
<div className="text-xs text-gray-600 mt-1">
{POSITION_LABELS[card.player.position]} | {card.player.price}M
</div>
<div className="text-xs font-bold text-amber-700 mt-1">
{card.state === "IN_TEAM" ? "در تیم" : `فروش: ${formatSaleValue(card.player.price)}M`}
</div>
</div>
</div>
<div className="flex gap-2 mt-3">
{canAdd && (
<button
onClick={onAdd}
disabled={loading}
className="flex-1 rounded-xl bg-green-700 py-2 text-sm font-bold text-white hover:bg-green-800 disabled:opacity-50 transition"
>
افزودن به تیم
</button>
)}
<button
onClick={onSell}
disabled={loading}
className="flex-1 rounded-xl bg-amber-500 py-2 text-sm font-bold text-white hover:bg-amber-600 disabled:opacity-50 transition"
>
فروش
</button>
</div>
</div>
);
}
function EmptySlot({ label }: { label: string }) {
return (
<div className="w-14 h-14 rounded-full border-2 border-dashed border-white/30 flex items-center justify-center">
<span className="text-white/30 text-[9px]">{label}</span>
</div>
);
}