first commit
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user