io
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import PositionBadge from "@/components/PositionBadge";
|
||||
import Image from "next/image";
|
||||
|
||||
@@ -8,14 +8,15 @@ type Player = {
|
||||
id: string;
|
||||
name: string;
|
||||
image: string | null;
|
||||
position: string;
|
||||
position: "GK" | "DEF" | "MID" | "FWD";
|
||||
price: number;
|
||||
totalPoints: number;
|
||||
country: { name: string; code: string; flagUrl?: string | null };
|
||||
country: { name: string; code: string; flagUrl?: string | null; isEliminated?: boolean };
|
||||
};
|
||||
|
||||
type TeamPlayer = {
|
||||
playerId: string;
|
||||
goldenCardId: string | null;
|
||||
isCaptain: boolean;
|
||||
isViceCaptain: boolean;
|
||||
isBench: boolean;
|
||||
@@ -33,53 +34,103 @@ type Team = {
|
||||
players: TeamPlayer[];
|
||||
} | null;
|
||||
|
||||
const FORMATIONS: Record<string, { label: string; def: number; mid: number; fwd: number }> = {
|
||||
"4-3-3": { label: "۴-۳-۳", def: 4, mid: 3, fwd: 3 },
|
||||
"4-4-2": { label: "۴-۴-۲", def: 4, mid: 4, fwd: 2 },
|
||||
"4-5-1": { label: "۴-۵-۱", def: 4, mid: 5, fwd: 1 },
|
||||
"3-5-2": { label: "۳-۵-۲", def: 3, mid: 5, fwd: 2 },
|
||||
"3-4-3": { label: "۳-۴-۳", def: 3, mid: 4, fwd: 3 },
|
||||
"5-3-2": { label: "۵-۳-۲", def: 5, mid: 3, fwd: 2 },
|
||||
"5-4-1": { label: "۵-۴-۱", def: 5, mid: 4, fwd: 1 },
|
||||
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;
|
||||
};
|
||||
|
||||
const POS_COLORS: Record<string, string> = {
|
||||
GK: "bg-yellow-400 text-yellow-900 border-yellow-500",
|
||||
DEF: "bg-blue-500 text-white border-blue-600",
|
||||
MID: "bg-green-500 text-white border-green-600",
|
||||
FWD: "bg-red-500 text-white border-red-600",
|
||||
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 [draggedId, setDraggedId] = useState<string | null>(null);
|
||||
const [submitLoading, setSubmitLoading] = useState(false);
|
||||
const [replacementDialog, setReplacementDialog] = useState<{
|
||||
card: SpecialCard;
|
||||
candidates: ReplacementCandidate[];
|
||||
} | null>(null);
|
||||
|
||||
const spent = team?.players.filter((tp) => !tp.isBench).reduce((s, tp) => s + tp.player.price, 0) ?? 0;
|
||||
const benchSpent = team?.players.filter((tp) => tp.isBench).reduce((s, tp) => s + tp.player.price, 0) ?? 0;
|
||||
const remaining = (team?.budget ?? 100) - spent - benchSpent;
|
||||
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 fmt = FORMATIONS[formation] ?? FORMATIONS["4-3-3"];
|
||||
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);
|
||||
@@ -89,8 +140,12 @@ export default function TeamBuilder({
|
||||
body: JSON.stringify({ name: teamName, formation }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) setTeam({ ...data, players: [] });
|
||||
else setMsg({ text: data.error, type: "error" });
|
||||
if (res.ok) {
|
||||
setTeam({ ...data, players: [] });
|
||||
setMsg(null);
|
||||
} else {
|
||||
setMsg({ text: data.error, type: "error" });
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -104,7 +159,9 @@ export default function TeamBuilder({
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
const player = allPlayers.find((p) => p.id === playerId)!;
|
||||
setTeam((t) => t ? { ...t, players: [...t.players, { ...data, player }] } : t);
|
||||
setTeam((current) =>
|
||||
current ? { ...current, players: [...current.players, { ...data, goldenCardId: null, player }] } : current
|
||||
);
|
||||
setMsg(null);
|
||||
} else {
|
||||
setMsg({ text: data.error, type: "error" });
|
||||
@@ -114,12 +171,125 @@ export default function TeamBuilder({
|
||||
|
||||
async function removePlayer(playerId: string) {
|
||||
setLoading(true);
|
||||
await fetch("/api/team/players", {
|
||||
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 }),
|
||||
});
|
||||
setTeam((t) => t ? { ...t, players: t.players.filter((tp) => tp.playerId !== playerId) } : t);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -129,11 +299,11 @@ export default function TeamBuilder({
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ playerId, type }),
|
||||
});
|
||||
setTeam((t) => {
|
||||
if (!t) return t;
|
||||
setTeam((current) => {
|
||||
if (!current) return current;
|
||||
return {
|
||||
...t,
|
||||
players: t.players.map((tp) => ({
|
||||
...current,
|
||||
players: current.players.map((tp) => ({
|
||||
...tp,
|
||||
isCaptain: type === "captain" ? tp.playerId === playerId : tp.isCaptain,
|
||||
isViceCaptain: type === "vice" ? tp.playerId === playerId : tp.isViceCaptain,
|
||||
@@ -147,69 +317,52 @@ export default function TeamBuilder({
|
||||
const res = await fetch("/api/team/submit", { method: "POST" });
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
setTeam((t) => t ? { ...t, status: "PENDING" } : t);
|
||||
setMsg({ text: "تیم برای تایید ارسال شد", type: "success" });
|
||||
setTeam((current) => (current ? { ...current, status: "ACTIVE" } : current));
|
||||
setMsg({ text: "تیم ثبت شد و وارد رقابت شد", type: "success" });
|
||||
} else {
|
||||
setMsg({ text: data.error, type: "error" });
|
||||
}
|
||||
setSubmitLoading(false);
|
||||
}
|
||||
|
||||
// drag & drop swap
|
||||
function onDragStart(playerId: string) { setDraggedId(playerId); }
|
||||
function onDrop(targetId: string) {
|
||||
if (!draggedId || draggedId === targetId) return;
|
||||
setTeam((t) => {
|
||||
if (!t) return t;
|
||||
const a = t.players.find((p) => p.playerId === draggedId);
|
||||
const b = t.players.find((p) => p.playerId === targetId);
|
||||
if (!a || !b) return t;
|
||||
// swap bench status
|
||||
return {
|
||||
...t,
|
||||
players: t.players.map((p) => {
|
||||
if (p.playerId === draggedId) return { ...p, isBench: b.isBench };
|
||||
if (p.playerId === targetId) return { ...p, isBench: a.isBench };
|
||||
return p;
|
||||
}),
|
||||
};
|
||||
});
|
||||
setDraggedId(null);
|
||||
}
|
||||
|
||||
const myPlayerIds = new Set(team?.players.map((tp) => tp.playerId) ?? []);
|
||||
const filtered = allPlayers.filter(
|
||||
(p) =>
|
||||
!myPlayerIds.has(p.id) &&
|
||||
(posFilter ? p.position === posFilter : true) &&
|
||||
(filter ? p.name.includes(filter) || p.country.name.includes(filter) : true)
|
||||
);
|
||||
|
||||
const isComplete = starters.length === 11 && bench.length >= 4;
|
||||
const canSubmit = isComplete && team?.status === "INACTIVE";
|
||||
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">با بودجه ۱۰۰ میلیون، ۱۵ بازیکن انتخاب کن</p>
|
||||
<input type="text" placeholder="نام تیم" value={teamName}
|
||||
<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" />
|
||||
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, val]) => (
|
||||
<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"}`}>
|
||||
{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
|
||||
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>
|
||||
@@ -218,35 +371,56 @@ export default function TeamBuilder({
|
||||
|
||||
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
|
||||
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">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-blue-700">{team.totalPoints}</div>
|
||||
<div className="text-xs text-gray-500">امتیاز</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className={`text-2xl font-bold ${remaining < 0 ? "text-red-600" : "text-green-700"}`}>
|
||||
{remaining.toFixed(1)}M
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">بودجه</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-gray-700">{starters.length}/11</div>
|
||||
<div className="text-xs text-gray-500">بازیکن</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
@@ -257,24 +431,11 @@ export default function TeamBuilder({
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6">
|
||||
{/* زمین - ۳ ستون */}
|
||||
<div className="lg:col-span-3">
|
||||
{/* انتخاب ترکیب */}
|
||||
{team.status === "DRAFT" && (
|
||||
<div className="flex gap-2 mb-3 flex-wrap">
|
||||
{Object.keys(FORMATIONS).map((f) => (
|
||||
<button key={f} onClick={() => setFormation(f)}
|
||||
className={`px-3 py-1 rounded-lg text-xs font-bold border transition ${formation === f ? "bg-green-700 text-white border-green-700" : "bg-white border-gray-200 hover:border-green-400"}`}>
|
||||
{f}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* زمین فوتبال */}
|
||||
<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 }}>
|
||||
{/* خطوط */}
|
||||
<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" />
|
||||
@@ -285,209 +446,218 @@ export default function TeamBuilder({
|
||||
</svg>
|
||||
|
||||
<div className="relative z-10 p-4 flex flex-col gap-3 h-full">
|
||||
{/* مهاجمان */}
|
||||
<PitchRow players={fwdSlots} slots={fmt.fwd} position="FWD"
|
||||
onRemove={removePlayer} onDragStart={onDragStart} onDrop={onDrop}
|
||||
onCaptain={setCaptain} draggedId={draggedId} />
|
||||
{/* هافبکها */}
|
||||
<PitchRow players={midSlots} slots={fmt.mid} position="MID"
|
||||
onRemove={removePlayer} onDragStart={onDragStart} onDrop={onDrop}
|
||||
onCaptain={setCaptain} draggedId={draggedId} />
|
||||
{/* مدافعان */}
|
||||
<PitchRow players={defSlots} slots={fmt.def} position="DEF"
|
||||
onRemove={removePlayer} onDragStart={onDragStart} onDrop={onDrop}
|
||||
onCaptain={setCaptain} draggedId={draggedId} />
|
||||
{/* دروازهبان */}
|
||||
<PitchRow players={gkSlots} slots={1} position="GK"
|
||||
onRemove={removePlayer} onDragStart={onDragStart} onDrop={onDrop}
|
||||
onCaptain={setCaptain} draggedId={draggedId} />
|
||||
<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>
|
||||
<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}
|
||||
onDragStart={onDragStart} onDrop={onDrop} onCaptain={setCaptain}
|
||||
draggedId={draggedId} small />
|
||||
<PitchCard key={tp.playerId} tp={tp} onRemove={removePlayer} onCaptain={setCaptain} onSell={sellSpecialCard} small />
|
||||
))}
|
||||
{Array.from({ length: Math.max(0, 4 - bench.length) }).map((_, i) => (
|
||||
<EmptySlot key={i} label="ذخیره" />
|
||||
{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
|
||||
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>
|
||||
)}
|
||||
{!isComplete && team.status === "DRAFT" && (
|
||||
<p className="text-center text-sm text-gray-400 mt-3">
|
||||
برای ورود به رقابت باید ۱۱ بازیکن اصلی + ۴ ذخیره (هر پست ۱ ذخیره) داشته باشی
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* لیست بازیکنان - ۲ ستون */}
|
||||
<div className="lg:col-span-2">
|
||||
<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" style={{ maxHeight: 520, overflowY: "auto" }}>
|
||||
<div className="flex gap-3 flex-wrap">
|
||||
{filtered.map((p) => (
|
||||
<div
|
||||
key={p.id}
|
||||
draggable
|
||||
onDragStart={() => setDraggedId(p.id)}
|
||||
className="flex-shrink-0 bg-gray-50 rounded-xl p-2 cursor-move hover:bg-gray-100 transition border-2 border-transparent hover:border-green-500"
|
||||
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">
|
||||
<span className="text-xs">{p.country.flagUrl}</span>
|
||||
<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)}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
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>
|
||||
<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)}
|
||||
/>
|
||||
))}
|
||||
{filtered.length === 0 && (
|
||||
<div className="w-full text-center text-gray-400 py-8">بازیکنی پیدا نشد</div>
|
||||
{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>
|
||||
</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 PitchRow({ players, slots, position, onRemove, onDragStart, onDrop, onCaptain, draggedId, }: {
|
||||
players: TeamPlayer[]; slots: number; position: string;
|
||||
onRemove: (id: string) => void; onDragStart: (id: string) => void;
|
||||
onDrop: (id: string) => void; onCaptain: (id: string, t: "captain" | "vice") => void;
|
||||
draggedId: string | null;
|
||||
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((_, i) => {
|
||||
const tp = players[i];
|
||||
{Array.from({ length: slots }).map((_, index) => {
|
||||
const tp = players[index];
|
||||
return tp ? (
|
||||
<PitchCard key={tp.playerId} tp={tp} onRemove={onRemove}
|
||||
onDragStart={onDragStart} onDrop={onDrop} onCaptain={onCaptain} draggedId={draggedId} />
|
||||
<PitchCard key={tp.playerId} tp={tp} onRemove={onRemove} onCaptain={onCaptain} onSell={onSell} />
|
||||
) : (
|
||||
<EmptySlot key={i} label={position} />
|
||||
<EmptySlot key={index} label="خالی" />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PitchCard({ tp, onRemove, onDragStart, onDrop, onCaptain, draggedId, small }: {
|
||||
tp: TeamPlayer; onRemove: (id: string) => void; onDragStart: (id: string) => void;
|
||||
onDrop: (id: string) => void; onCaptain: (id: string, t: "captain" | "vice") => void;
|
||||
draggedId: string | null; small?: boolean;
|
||||
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 [showMenu, setShowMenu] = useState(false);
|
||||
const isDragging = draggedId === tp.playerId;
|
||||
const isEliminated = (tp.player as any).country?.isEliminated;
|
||||
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 ${isDragging ? "opacity-50" : ""}`}
|
||||
draggable
|
||||
onDragStart={() => onDragStart(tp.playerId)}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={() => onDrop(tp.playerId)}
|
||||
>
|
||||
<div className={`bg-white/95 rounded-xl p-2 cursor-move hover:bg-white transition shadow-lg ${small ? "w-16" : "w-20"}`}>
|
||||
<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"
|
||||
/>
|
||||
<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>
|
||||
<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>
|
||||
<span className="text-white text-xs font-bold">×</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={`text-[10px] font-bold text-gray-800 text-center leading-tight ${isEliminated ? "opacity-50" : ""}`}>
|
||||
{shortName}
|
||||
</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 className="text-[8px] text-center text-gray-600 mt-1">
|
||||
{tp.player.totalPoints}pts
|
||||
{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-8 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition flex gap-1 z-20">
|
||||
|
||||
<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();
|
||||
@@ -497,6 +667,17 @@ function PitchCard({ tp, onRemove, onDragStart, onDrop, onCaptain, draggedId, sm
|
||||
>
|
||||
{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();
|
||||
@@ -504,7 +685,62 @@ function PitchCard({ tp, onRemove, onDragStart, onDrop, onCaptain, draggedId, sm
|
||||
}}
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user