Files
football-next/app/(admin)/admin/rounds/[id]/match/[matchId]/MatchLineupManager.tsx
2026-04-07 10:38:28 +03:30

240 lines
8.7 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 { 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>
);
}