152 lines
4.2 KiB
TypeScript
152 lines
4.2 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import Shell from "@/components/Shell";
|
||
import { TableSkeleton } from "@/components/Skeleton";
|
||
import { api } from "@/lib/api";
|
||
import { toastError } from "@/lib/toast";
|
||
|
||
type Tier = {
|
||
id: string;
|
||
label: string;
|
||
c1: string;
|
||
c2: string;
|
||
sort: number;
|
||
};
|
||
|
||
const hex = (s: string) => {
|
||
const v = String(s || "").replace("#", "").trim();
|
||
return /^[0-9a-fA-F]{6}$/.test(v) ? `#${v}` : null;
|
||
};
|
||
|
||
function Swatch({ c1, c2 }: { c1: string; c2: string }) {
|
||
const a = hex(c1);
|
||
const b = hex(c2);
|
||
return (
|
||
<div
|
||
style={{
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: "50%",
|
||
background:
|
||
a && b ? `linear-gradient(160deg, ${a}, ${b})` : "#16305a",
|
||
border: "1px solid #1e3a63",
|
||
}}
|
||
/>
|
||
);
|
||
}
|
||
|
||
export default function RankTiersPage() {
|
||
const [tiers, setTiers] = useState<Tier[]>([]);
|
||
const [busy, setBusy] = useState<string | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
async function load() {
|
||
setLoading(true);
|
||
try {
|
||
const r = await api.get<{ rank_tiers: Tier[] }>("/rank-tiers");
|
||
setTiers(r.rank_tiers || []);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
useEffect(() => {
|
||
load();
|
||
}, []);
|
||
|
||
const patch = (id: string, key: keyof Tier, val: string | number) =>
|
||
setTiers((ts) => ts.map((t) => (t.id === id ? { ...t, [key]: val } : t)));
|
||
|
||
async function save(t: Tier) {
|
||
setBusy(t.id);
|
||
try {
|
||
await api.post("/rank-tiers", t);
|
||
} catch (e) {
|
||
toastError((e as Error).message);
|
||
} finally {
|
||
setBusy(null);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<Shell title="رتبهها">
|
||
<p className="text-sm text-[#8aa] mb-4">
|
||
رنگ و برچسبِ رتبهها (برنز تا پادشاه). اینها در حلقهی آواتار و نشانِ
|
||
رتبهی داخلِ بازی دیده میشوند و بدونِ بهروزرسانیِ اپ اعمال میشوند.
|
||
رنگ را با کدِ hex (مثلِ <code>FFD54F</code>) وارد کنید.
|
||
</p>
|
||
{loading ? (
|
||
<TableSkeleton rows={5} cols={7} />
|
||
) : (
|
||
<div className="card overflow-x-auto">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>پیشنمایش</th>
|
||
<th>شناسه</th>
|
||
<th>برچسب</th>
|
||
<th>رنگ ۱ (hex)</th>
|
||
<th>رنگ ۲ (hex)</th>
|
||
<th>ترتیب</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{tiers.map((t) => (
|
||
<tr key={t.id}>
|
||
<td>
|
||
<Swatch c1={t.c1} c2={t.c2} />
|
||
</td>
|
||
<td className="text-[#8aa]">{t.id}</td>
|
||
<td>
|
||
<input
|
||
className="inp"
|
||
style={{ minWidth: 90 }}
|
||
value={t.label}
|
||
onChange={(e) => patch(t.id, "label", e.target.value)}
|
||
/>
|
||
</td>
|
||
<td>
|
||
<input
|
||
className="inp"
|
||
style={{ minWidth: 90 }}
|
||
value={t.c1}
|
||
onChange={(e) => patch(t.id, "c1", e.target.value)}
|
||
/>
|
||
</td>
|
||
<td>
|
||
<input
|
||
className="inp"
|
||
style={{ minWidth: 90 }}
|
||
value={t.c2}
|
||
onChange={(e) => patch(t.id, "c2", e.target.value)}
|
||
/>
|
||
</td>
|
||
<td>
|
||
<input
|
||
className="inp"
|
||
style={{ minWidth: 60 }}
|
||
type="number"
|
||
value={t.sort}
|
||
onChange={(e) => patch(t.id, "sort", +e.target.value)}
|
||
/>
|
||
</td>
|
||
<td>
|
||
<button
|
||
className="btn btn-gold"
|
||
disabled={busy === t.id}
|
||
onClick={() => save(t)}
|
||
>
|
||
ذخیره
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</Shell>
|
||
);
|
||
}
|