first commit

This commit is contained in:
a.alinaghipour
2026-04-05 15:53:20 +03:30
commit aa9ed69dd2
96 changed files with 7721 additions and 0 deletions

View 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>
);
}

View 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>
);
}