first commit
This commit is contained in:
138
app/(admin)/admin/stats/StatsForm.tsx
Normal file
138
app/(admin)/admin/stats/StatsForm.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
type Player = { id: string; name: string; position: string };
|
||||
type StatRow = {
|
||||
playerId: string;
|
||||
goals: number;
|
||||
assists: number;
|
||||
yellowCards: number;
|
||||
redCards: number;
|
||||
minutesPlayed: number;
|
||||
cleanSheet: boolean;
|
||||
};
|
||||
|
||||
export default function StatsForm({ match }: { match: any }) {
|
||||
const allPlayers: Player[] = [
|
||||
...match.homeTeam.players,
|
||||
...match.awayTeam.players,
|
||||
];
|
||||
|
||||
const initStats = (): Record<string, StatRow> => {
|
||||
const map: Record<string, StatRow> = {};
|
||||
for (const p of allPlayers) {
|
||||
const existing = match.playerStats.find((s: any) => s.playerId === p.id);
|
||||
map[p.id] = existing ?? {
|
||||
playerId: p.id,
|
||||
goals: 0,
|
||||
assists: 0,
|
||||
yellowCards: 0,
|
||||
redCards: 0,
|
||||
minutesPlayed: 0,
|
||||
cleanSheet: false,
|
||||
};
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
const [stats, setStats] = useState<Record<string, StatRow>>(initStats);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
function update(playerId: string, field: keyof StatRow, value: any) {
|
||||
setStats((prev) => ({ ...prev, [playerId]: { ...prev[playerId], [field]: value } }));
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
setLoading(true);
|
||||
const payload = Object.values(stats).filter((s) => s.minutesPlayed > 0);
|
||||
await fetch(`/api/matches/${match.id}/stats`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
setSaved(true);
|
||||
setLoading(false);
|
||||
setTimeout(() => setSaved(false), 3000);
|
||||
}
|
||||
|
||||
const renderTeam = (players: Player[], teamName: string) => (
|
||||
<div className="mb-8">
|
||||
<h3 className="font-bold text-lg mb-3 text-green-800">{teamName}</h3>
|
||||
<div className="bg-white rounded-2xl shadow overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-gray-100 text-gray-600">
|
||||
<tr>
|
||||
<th className="text-right px-4 py-3">بازیکن</th>
|
||||
<th className="px-3 py-3">دقیقه</th>
|
||||
<th className="px-3 py-3">گل</th>
|
||||
<th className="px-3 py-3">پاس گل</th>
|
||||
<th className="px-3 py-3">زرد</th>
|
||||
<th className="px-3 py-3">قرمز</th>
|
||||
<th className="px-3 py-3">کلینشیت</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{players.map((p) => {
|
||||
const s = stats[p.id];
|
||||
if (!s) return null;
|
||||
return (
|
||||
<tr key={p.id} className="border-t hover:bg-gray-50">
|
||||
<td className="px-4 py-2">
|
||||
<div className="font-medium">{p.name}</div>
|
||||
<div className="text-xs text-gray-400">{p.position}</div>
|
||||
</td>
|
||||
{(["minutesPlayed", "goals", "assists", "yellowCards", "redCards"] as const).map((field) => (
|
||||
<td key={field} className="px-2 py-2 text-center">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max={field === "minutesPlayed" ? 120 : 10}
|
||||
value={s[field] as number}
|
||||
onChange={(e) => update(p.id, field, parseInt(e.target.value) || 0)}
|
||||
className="w-14 border rounded-lg px-2 py-1 text-center focus:outline-none focus:ring-2 focus:ring-green-400"
|
||||
/>
|
||||
</td>
|
||||
))}
|
||||
<td className="px-2 py-2 text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={s.cleanSheet}
|
||||
onChange={(e) => update(p.id, "cleanSheet", e.target.checked)}
|
||||
className="w-4 h-4 accent-green-600"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-green-50 border border-green-200 rounded-2xl p-4 mb-6 text-center">
|
||||
<span className="font-bold text-lg">{match.homeTeam.name}</span>
|
||||
<span className="mx-4 text-gray-400">vs</span>
|
||||
<span className="font-bold text-lg">{match.awayTeam.name}</span>
|
||||
</div>
|
||||
|
||||
{renderTeam(match.homeTeam.players, match.homeTeam.name)}
|
||||
{renderTeam(match.awayTeam.players, match.awayTeam.name)}
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
className="bg-green-700 text-white px-8 py-3 rounded-xl font-bold hover:bg-green-800 transition disabled:opacity-50"
|
||||
>
|
||||
{loading ? "در حال ذخیره..." : "ذخیره آمار و محاسبه امتیازات"}
|
||||
</button>
|
||||
{saved && <span className="text-green-600 font-medium">✓ ذخیره شد</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
55
app/(admin)/admin/stats/page.tsx
Normal file
55
app/(admin)/admin/stats/page.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { db } from "@/lib/db";
|
||||
import StatsForm from "./StatsForm";
|
||||
|
||||
export default async function AdminStatsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { matchId?: string };
|
||||
}) {
|
||||
const matches = await db.match.findMany({
|
||||
include: { homeTeam: true, awayTeam: true },
|
||||
orderBy: { matchDate: "desc" },
|
||||
});
|
||||
|
||||
const selectedMatch = searchParams.matchId
|
||||
? await db.match.findUnique({
|
||||
where: { id: searchParams.matchId },
|
||||
include: {
|
||||
homeTeam: { include: { players: true } },
|
||||
awayTeam: { include: { players: true } },
|
||||
playerStats: { include: { player: true } },
|
||||
},
|
||||
})
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-6">ثبت آمار بازیکنان</h1>
|
||||
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-medium mb-2">انتخاب بازی</label>
|
||||
<form method="GET">
|
||||
<select
|
||||
name="matchId"
|
||||
defaultValue={searchParams.matchId ?? ""}
|
||||
onChange={(e) => {
|
||||
if (typeof window !== "undefined") {
|
||||
window.location.href = `/admin/stats?matchId=${(e.target as HTMLSelectElement).value}`;
|
||||
}
|
||||
}}
|
||||
className="border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500 w-full max-w-md"
|
||||
>
|
||||
<option value="">انتخاب بازی...</option>
|
||||
{matches.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.homeTeam.name} vs {m.awayTeam.name} - {new Date(m.matchDate).toLocaleDateString("fa-IR")}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{selectedMatch && <StatsForm match={selectedMatch} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user