admin
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user