first commit
This commit is contained in:
101
app/(admin)/admin/scoring/ScoringRulesEditor.tsx
Normal file
101
app/(admin)/admin/scoring/ScoringRulesEditor.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
const POSITIONS = ["GK", "DEF", "MID", "FWD"] as const;
|
||||
const POS_LABELS: Record<string, string> = { GK: "دروازهبان", DEF: "مدافع", MID: "هافبک", FWD: "مهاجم" };
|
||||
|
||||
const EVENT_LABELS: Record<string, string> = {
|
||||
GOAL: "گل", ASSIST: "پاس گل", YELLOW_CARD: "کارت زرد", RED_CARD: "کارت قرمز",
|
||||
SECOND_YELLOW: "کارت زرد دوم", CLEAN_SHEET: "کلینشیت", PENALTY_SAVED: "پنالتی گرفته",
|
||||
PENALTY_MISSED: "پنالتی از دست داده", OWN_GOAL: "گل به خودی", MOTM: "بازیکن برتر",
|
||||
EXTRA_TIME_BONUS: "وقت اضافه", INJURY_NO_SUB: "مصدومیت بدون تعویض",
|
||||
};
|
||||
|
||||
type Rule = { id: string; position: string; eventType: string; points: number };
|
||||
|
||||
export default function ScoringRulesEditor({ rules, defaultRules }: { rules: Rule[]; defaultRules: any }) {
|
||||
const [values, setValues] = useState<Record<string, number>>(() => {
|
||||
const map: Record<string, number> = {};
|
||||
for (const r of rules) map[`${r.position}_${r.eventType}`] = r.points;
|
||||
return map;
|
||||
});
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
function getValue(pos: string, evt: string): number {
|
||||
const key = `${pos}_${evt}`;
|
||||
if (key in values) return values[key];
|
||||
return defaultRules[pos]?.[evt] ?? 0;
|
||||
}
|
||||
|
||||
function setValue(pos: string, evt: string, val: number) {
|
||||
setValues((v) => ({ ...v, [`${pos}_${evt}`]: val }));
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
setSaving(true);
|
||||
const payload = Object.entries(values).map(([key, points]) => {
|
||||
const [position, eventType] = key.split("_");
|
||||
return { position, eventType, points };
|
||||
});
|
||||
await fetch("/api/admin/scoring", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
setSaved(true);
|
||||
setSaving(false);
|
||||
setTimeout(() => setSaved(false), 3000);
|
||||
}
|
||||
|
||||
const events = Object.keys(EVENT_LABELS);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="overflow-x-auto bg-white rounded-2xl shadow">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-green-800 text-white">
|
||||
<tr>
|
||||
<th className="text-right px-5 py-4 sticky right-0 bg-green-800">رویداد</th>
|
||||
{POSITIONS.map((pos) => (
|
||||
<th key={pos} className="px-5 py-4 text-center">{POS_LABELS[pos]}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{events.map((evt) => (
|
||||
<tr key={evt} className="border-t hover:bg-gray-50">
|
||||
<td className="px-5 py-3 font-medium sticky right-0 bg-white">{EVENT_LABELS[evt]}</td>
|
||||
{POSITIONS.map((pos) => {
|
||||
const val = getValue(pos, evt);
|
||||
return (
|
||||
<td key={pos} className="px-3 py-2 text-center">
|
||||
<input
|
||||
type="number"
|
||||
value={val}
|
||||
onChange={(e) => setValue(pos, evt, parseInt(e.target.value) || 0)}
|
||||
className={`w-16 border rounded-lg px-2 py-1 text-center focus:outline-none focus:ring-2 focus:ring-green-400 font-bold ${
|
||||
val > 0 ? "text-green-700 border-green-200" :
|
||||
val < 0 ? "text-red-600 border-red-200" :
|
||||
"text-gray-400"
|
||||
}`}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 mt-6">
|
||||
<button onClick={handleSave} disabled={saving}
|
||||
className="bg-green-700 text-white px-8 py-3 rounded-xl font-bold hover:bg-green-800 transition disabled:opacity-50">
|
||||
{saving ? "در حال ذخیره..." : "ذخیره همه قوانین"}
|
||||
</button>
|
||||
{saved && <span className="text-green-600 font-medium">✓ ذخیره شد</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
app/(admin)/admin/scoring/page.tsx
Normal file
15
app/(admin)/admin/scoring/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { db } from "@/lib/db";
|
||||
import ScoringRulesEditor from "./ScoringRulesEditor";
|
||||
import { DEFAULT_RULES } from "@/lib/points";
|
||||
|
||||
export default async function AdminScoringPage() {
|
||||
const rules = await db.scoringRule.findMany({ orderBy: [{ position: "asc" }, { eventType: "asc" }] });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-2">مدیریت امتیازدهی</h1>
|
||||
<p className="text-gray-500 text-sm mb-6">امتیاز هر رویداد را به ازای هر پست جداگانه تنظیم کنید</p>
|
||||
<ScoringRulesEditor rules={rules} defaultRules={DEFAULT_RULES} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user