Files
2026-08-07 09:40:16 +03:30

259 lines
8.2 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { Trash2, Plus, Pencil, X } from "lucide-react";
import Shell from "@/components/Shell";
import Badge, { type BadgeTone } from "@/components/Badge";
import Empty from "@/components/Empty";
import { TableSkeleton } from "@/components/Skeleton";
import { api } from "@/lib/api";
import { toastError } from "@/lib/toast";
type Tournament = {
id: number;
title: string;
description: string;
entry_fee: number;
prizes: number[];
prize_pool: number;
status: string;
players: number;
starts_local: string;
ends_local: string;
};
const STATUS: Record<string, { t: string; tone: BadgeTone }> = {
active: { t: "در حال برگزاری", tone: "green" },
upcoming: { t: "به‌زودی", tone: "gold" },
ended: { t: "پایان‌یافته", tone: "gray" },
};
const EMPTY = {
title: "",
description: "",
entry_fee: 0,
prizes: "",
starts_at: "",
ends_at: "",
};
// "2026-07-07 13:00" → "2026-07-07T13:00" (ورودیِ datetime-local)
const toLocalInput = (s: string) => (s || "").replace(" ", "T");
export default function TournamentsPage() {
const [list, setList] = useState<Tournament[]>([]);
const [form, setForm] = useState({ ...EMPTY });
const [editingId, setEditingId] = useState<number | null>(null);
const [busy, setBusy] = useState(false);
const [loading, setLoading] = useState(true);
async function load() {
setLoading(true);
try {
const r = await api.get<{ tournaments: Tournament[] }>("/tournaments");
setList(r.tournaments || []);
} finally {
setLoading(false);
}
}
useEffect(() => {
load();
}, []);
function startEdit(t: Tournament) {
setEditingId(t.id);
setForm({
title: t.title,
description: t.description || "",
entry_fee: t.entry_fee,
prizes: (t.prizes || []).join(","),
starts_at: toLocalInput(t.starts_local),
ends_at: toLocalInput(t.ends_local),
});
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
}
function cancelEdit() {
setEditingId(null);
setForm({ ...EMPTY });
}
async function submit(e: React.FormEvent) {
e.preventDefault();
setBusy(true);
try {
const payload = {
title: form.title,
description: form.description,
entry_fee: Number(form.entry_fee),
prizes: form.prizes
.split(",")
.map((s) => parseInt(s.trim(), 10))
.filter((n) => !isNaN(n)),
starts_at: form.starts_at,
ends_at: form.ends_at,
};
if (editingId) {
await api.put("/tournaments", { id: editingId, ...payload });
} else {
await api.post("/tournaments", payload);
}
cancelEdit();
await load();
} catch (e) {
toastError((e as Error).message);
} finally {
setBusy(false);
}
}
async function remove(id: number) {
if (!confirm("حذف تورنومنت؟")) return;
await api.del(`/tournaments?id=${id}`);
if (editingId === id) cancelEdit();
await load();
}
return (
<Shell title="تورنومنت‌ها">
{loading ? (
<TableSkeleton rows={5} cols={9} />
) : list.length === 0 ? (
<Empty
title="تورنومنتی وجود ندارد"
hint="با فرمِ پایینِ صفحه، اولین تورنومنت را بسازید."
/>
) : (
<div className="card overflow-x-auto mb-6">
<table>
<thead>
<tr>
<th>#</th>
<th>عنوان</th>
<th>ورودی</th>
<th>جوایز</th>
<th>وضعیت</th>
<th>بازیکنان</th>
<th>شروع (تهران)</th>
<th>پایان (تهران)</th>
<th></th>
</tr>
</thead>
<tbody>
{list.map((t) => {
const s = STATUS[t.status] || STATUS.ended;
return (
<tr key={t.id} className={editingId === t.id ? "bg-[rgba(233,185,73,.08)]" : ""}>
<td className="text-[#8aa]">{t.id}</td>
<td className="font-bold">{t.title}</td>
<td>{t.entry_fee}</td>
<td className="text-[var(--gold)]">{(t.prizes || []).join("، ")}</td>
<td>
<Badge tone={s.tone}>{s.t}</Badge>
</td>
<td>{t.players}</td>
<td dir="ltr" className="text-xs">{t.starts_local}</td>
<td dir="ltr" className="text-xs">{t.ends_local}</td>
<td className="flex gap-1">
<button className="btn btn-ghost" onClick={() => startEdit(t)} title="ویرایش">
<Pencil size={14} />
</button>
<button className="btn btn-danger" onClick={() => remove(t.id)} title="حذف">
<Trash2 size={14} />
</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
<form onSubmit={submit} className="card p-5">
<h2 className="font-bold mb-4 flex items-center gap-2">
{editingId ? (
<>
<Pencil size={18} className="text-[var(--gold)]" /> ویرایش تورنومنت #{editingId}
<button
type="button"
onClick={cancelEdit}
className="btn btn-ghost mr-auto flex items-center gap-1"
>
<X size={14} /> لغو
</button>
</>
) : (
<>
<Plus size={18} className="text-[var(--gold)]" /> افزودن تورنومنت
</>
)}
</h2>
<div className="grid md:grid-cols-2 gap-4">
<label className="text-sm">
عنوان
<input
className="inp mt-1"
value={form.title}
onChange={(e) => setForm({ ...form, title: e.target.value })}
required
/>
</label>
<label className="text-sm">
ورودی (سکه)
<input
className="inp mt-1"
type="number"
value={form.entry_fee}
onChange={(e) => setForm({ ...form, entry_fee: +e.target.value })}
/>
</label>
<label className="text-sm md:col-span-2">
توضیح
<input
className="inp mt-1"
value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })}
/>
</label>
<label className="text-sm md:col-span-2">
جوایز (سکه، با کاما نفر اول، دوم، )
<input
className="inp mt-1"
placeholder="5000,3000,1000"
value={form.prizes}
onChange={(e) => setForm({ ...form, prizes: e.target.value })}
/>
</label>
<label className="text-sm">
شروع (تهران)
<input
className="inp mt-1"
type="datetime-local"
value={form.starts_at}
onChange={(e) => setForm({ ...form, starts_at: e.target.value })}
required
/>
</label>
<label className="text-sm">
پایان (تهران)
<input
className="inp mt-1"
type="datetime-local"
value={form.ends_at}
onChange={(e) => setForm({ ...form, ends_at: e.target.value })}
required
/>
</label>
</div>
<p className="text-xs text-[#8aa] mt-3">
برای فعال‌شدنِ فوری، زمانِ شروع را کمی قبل از الان بگذارید. برد ۱۰۰ و شرکت ۲۵ امتیاز.
</p>
<button className="btn btn-gold mt-4" disabled={busy}>
{busy ? "…" : editingId ? "ذخیره تغییرات" : "ساخت تورنومنت"}
</button>
</form>
</Shell>
);
}