This commit is contained in:
2026-04-07 10:38:28 +03:30
parent aa9ed69dd2
commit 8bcd1c2951
99 changed files with 3357 additions and 178 deletions

View File

@@ -0,0 +1,239 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
type Player = {
id: string;
name: string;
position: string;
};
type Country = {
id: string;
name: string;
code: string;
flagUrl: string | null;
defaultFormation: string;
defaultLineupPlayerIds: string[];
players: Player[];
};
type Lineup = {
id: string;
countryId: string;
formation: string;
playerIds: string[];
};
type Match = {
id: string;
homeTeam: Country;
awayTeam: Country;
lineups: Lineup[];
};
const FORMATIONS: Record<string, { def: number; mid: number; fwd: number }> = {
"4-3-3": { def: 4, mid: 3, fwd: 3 },
"4-4-2": { def: 4, mid: 4, fwd: 2 },
"4-5-1": { def: 4, mid: 5, fwd: 1 },
"3-5-2": { def: 3, mid: 5, fwd: 2 },
"3-4-3": { def: 3, mid: 4, fwd: 3 },
"5-3-2": { def: 5, mid: 3, fwd: 2 },
"5-4-1": { def: 5, mid: 4, fwd: 1 },
};
export default function MatchLineupManager({ match }: { match: Match }) {
const router = useRouter();
const homeLineup = match.lineups.find((l) => l.countryId === match.homeTeam.id);
const awayLineup = match.lineups.find((l) => l.countryId === match.awayTeam.id);
const [homeFormation, setHomeFormation] = useState(homeLineup?.formation ?? match.homeTeam.defaultFormation);
const [homePlayerIds, setHomePlayerIds] = useState<string[]>(homeLineup?.playerIds ?? match.homeTeam.defaultLineupPlayerIds);
const [awayFormation, setAwayFormation] = useState(awayLineup?.formation ?? match.awayTeam.defaultFormation);
const [awayPlayerIds, setAwayPlayerIds] = useState<string[]>(awayLineup?.playerIds ?? match.awayTeam.defaultLineupPlayerIds);
const [loading, setLoading] = useState(false);
const [msg, setMsg] = useState<{ text: string; type: "error" | "success" } | null>(null);
function loadDefaultLineup(team: "home" | "away") {
if (team === "home") {
setHomeFormation(match.homeTeam.defaultFormation);
setHomePlayerIds(match.homeTeam.defaultLineupPlayerIds);
setMsg({ text: "ترکیب پیش‌فرض میزبان بارگذاری شد", type: "success" });
} else {
setAwayFormation(match.awayTeam.defaultFormation);
setAwayPlayerIds(match.awayTeam.defaultLineupPlayerIds);
setMsg({ text: "ترکیب پیش‌فرض میهمان بارگذاری شد", type: "success" });
}
setTimeout(() => setMsg(null), 3000);
}
async function handleSave() {
// چک کنیم هر دو تیم 11 نفر داشته باشن
if (homePlayerIds.length !== 11 || awayPlayerIds.length !== 11) {
setMsg({ text: "هر تیم باید 11 بازیکن داشته باشد", type: "error" });
return;
}
setLoading(true);
const res = await fetch(`/api/admin/matches/${match.id}/lineup`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify([
{ countryId: match.homeTeam.id, formation: homeFormation, playerIds: homePlayerIds },
{ countryId: match.awayTeam.id, formation: awayFormation, playerIds: awayPlayerIds },
]),
});
if (res.ok) {
setMsg({ text: "ترکیب‌ها ذخیره شد", type: "success" });
router.refresh();
} else {
const data = await res.json();
setMsg({ text: data.error || "خطا در ذخیره", type: "error" });
}
setLoading(false);
}
return (
<div className="bg-white rounded-2xl shadow p-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold">ترکیب تیمها</h2>
<button onClick={handleSave} disabled={loading}
className="bg-green-700 text-white px-6 py-2 rounded-xl font-bold hover:bg-green-800 transition disabled:opacity-50">
{loading ? "در حال ذخیره..." : "ذخیره ترکیب‌ها"}
</button>
</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-2 gap-6">
{/* تیم میزبان */}
<TeamLineupEditor
team={match.homeTeam}
formation={homeFormation}
selectedPlayerIds={homePlayerIds}
onFormationChange={setHomeFormation}
onPlayersChange={setHomePlayerIds}
onLoadDefault={() => loadDefaultLineup("home")}
/>
{/* تیم میهمان */}
<TeamLineupEditor
team={match.awayTeam}
formation={awayFormation}
selectedPlayerIds={awayPlayerIds}
onFormationChange={setAwayFormation}
onPlayersChange={setAwayPlayerIds}
onLoadDefault={() => loadDefaultLineup("away")}
/>
</div>
</div>
);
}
function TeamLineupEditor({ team, formation, selectedPlayerIds, onFormationChange, onPlayersChange, onLoadDefault }: {
team: Country;
formation: string;
selectedPlayerIds: string[];
onFormationChange: (f: string) => void;
onPlayersChange: (ids: string[]) => void;
onLoadDefault: () => void;
}) {
const fmt = FORMATIONS[formation] ?? FORMATIONS["4-3-3"];
const gkPlayers = team.players.filter((p) => p.position === "GK");
const defPlayers = team.players.filter((p) => p.position === "DEF");
const midPlayers = team.players.filter((p) => p.position === "MID");
const fwdPlayers = team.players.filter((p) => p.position === "FWD");
const selectedGk = selectedPlayerIds.filter((id) => gkPlayers.find((p) => p.id === id));
const selectedDef = selectedPlayerIds.filter((id) => defPlayers.find((p) => p.id === id));
const selectedMid = selectedPlayerIds.filter((id) => midPlayers.find((p) => p.id === id));
const selectedFwd = selectedPlayerIds.filter((id) => fwdPlayers.find((p) => p.id === id));
function togglePlayer(playerId: string, position: string) {
if (selectedPlayerIds.includes(playerId)) {
onPlayersChange(selectedPlayerIds.filter((id) => id !== playerId));
} else {
const posPlayers = selectedPlayerIds.filter((id) => {
const p = team.players.find((pl) => pl.id === id);
return p?.position === position;
});
let maxCount = 1;
if (position === "DEF") maxCount = fmt.def;
else if (position === "MID") maxCount = fmt.mid;
else if (position === "FWD") maxCount = fmt.fwd;
if (posPlayers.length >= maxCount) return;
onPlayersChange([...selectedPlayerIds, playerId]);
}
}
return (
<div className="border-2 border-gray-200 rounded-xl p-4">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<span className="text-2xl">{team.flagUrl}</span>
<h3 className="font-bold">{team.name}</h3>
</div>
<button onClick={onLoadDefault}
className="text-xs bg-blue-100 text-blue-700 px-3 py-1 rounded-lg hover:bg-blue-200 transition">
بارگذاری پیشفرض
</button>
</div>
{/* انتخاب فرمیشن */}
<div className="mb-4">
<label className="text-xs text-gray-500 mb-1 block">فرمیشن</label>
<select value={formation} onChange={(e) => onFormationChange(e.target.value)}
className="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-green-500">
{Object.keys(FORMATIONS).map((f) => (
<option key={f} value={f}>{f}</option>
))}
</select>
</div>
{/* نمایش تعداد */}
<div className="text-xs text-gray-600 mb-3 bg-gray-50 p-2 rounded-lg">
انتخاب شده: {selectedPlayerIds.length}/11 ·
GK: {selectedGk.length}/1 · DEF: {selectedDef.length}/{fmt.def} ·
MID: {selectedMid.length}/{fmt.mid} · FWD: {selectedFwd.length}/{fmt.fwd}
</div>
{/* لیست بازیکنان */}
<div className="space-y-3 max-h-96 overflow-y-auto">
{["GK", "DEF", "MID", "FWD"].map((pos) => {
const posList = team.players.filter((p) => p.position === pos);
return (
<div key={pos}>
<div className="text-xs font-bold text-gray-500 mb-1">{pos}</div>
<div className="flex flex-col gap-1">
{posList.map((p) => (
<button key={p.id} onClick={() => togglePlayer(p.id, pos)}
className={`text-right px-2 py-1.5 rounded text-xs transition ${
selectedPlayerIds.includes(p.id)
? "bg-green-100 text-green-700 font-bold border border-green-500"
: "bg-gray-50 hover:bg-gray-100"
}`}>
{p.name}
</button>
))}
</div>
</div>
);
})}
</div>
</div>
);
}