124 lines
3.7 KiB
TypeScript
124 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { Upload, Loader2 } from "lucide-react";
|
|
import Shell from "@/components/Shell";
|
|
import EditableTable, { Col } from "@/components/EditableTable";
|
|
import Thumb from "@/components/Thumb";
|
|
import { api, getAuth, ASSET_BASE } from "@/lib/api";
|
|
import { toastError, toastSuccess } from "@/lib/toast";
|
|
|
|
type Row = Record<string, unknown>;
|
|
|
|
const COLS: Col[] = [
|
|
{ key: "id", label: "شناسه" },
|
|
{ key: "title", label: "عنوان" },
|
|
{ key: "price_coins", label: "قیمت (سکه)", type: "number" },
|
|
{ key: "vip", label: "VIP رایگان", type: "bool" },
|
|
{ key: "sort", label: "ترتیب", type: "number" },
|
|
];
|
|
|
|
const API =
|
|
process.env.NEXT_PUBLIC_API_URL || "https://hakem.approagency.ir/admin/api";
|
|
|
|
export default function CarpetsPage() {
|
|
const [rows, setRows] = useState<Row[]>([]);
|
|
const [ver, setVer] = useState(1); // برای تازهسازیِ کشِ تصویر پس از آپلود
|
|
|
|
async function load() {
|
|
const r = await api.get<{ carpets: Row[] }>("/carpets");
|
|
setRows(r.carpets || []);
|
|
setVer((v) => v + 1);
|
|
}
|
|
useEffect(() => {
|
|
load();
|
|
}, []);
|
|
|
|
return (
|
|
<Shell title="فرشها">
|
|
<p className="text-sm text-[#8aa] mb-4">
|
|
هر فرش بهجای میزِ بازی استفاده میشود. پس از ساختِ ردیف، تصویرِ jpg آپلود کنید.
|
|
شناسهٔ <code>classic</code> یعنی میزِ پیشفرضِ بدونِ تصویر.
|
|
</p>
|
|
<EditableTable
|
|
cols={COLS}
|
|
rows={rows}
|
|
newTemplate={{ id: "", title: "", price_coins: 0, vip: false, sort: 0 }}
|
|
onSave={async (row) => {
|
|
await api.post("/carpets", row);
|
|
await load();
|
|
}}
|
|
onDelete={async (id) => {
|
|
if (!confirm("حذف فرش؟")) return;
|
|
await api.del(`/carpets?id=${encodeURIComponent(id)}`);
|
|
await load();
|
|
}}
|
|
preview={(row) =>
|
|
String(row.id) === "classic" ? (
|
|
<span className="text-[#8aa] text-xs">پیشفرض</span>
|
|
) : (
|
|
<Thumb src={`${ASSET_BASE}/carpets/${row.id}.jpg?v=${ver}`} size={48} />
|
|
)
|
|
}
|
|
extra={(row) => <UploadBtn id={String(row.id)} onDone={load} />}
|
|
/>
|
|
</Shell>
|
|
);
|
|
}
|
|
|
|
function UploadBtn({ id, onDone }: { id: string; onDone: () => void }) {
|
|
const ref = useRef<HTMLInputElement>(null);
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
async function upload(file: File) {
|
|
setBusy(true);
|
|
try {
|
|
const fd = new FormData();
|
|
fd.append("image", file);
|
|
const auth = getAuth();
|
|
const res = await fetch(`${API}/carpets/${encodeURIComponent(id)}/upload`, {
|
|
method: "POST",
|
|
headers: auth ? { Authorization: `Basic ${auth}` } : {},
|
|
body: fd,
|
|
});
|
|
if (!res.ok) throw new Error("آپلود ناموفق بود");
|
|
toastSuccess("تصویر آپلود شد");
|
|
onDone();
|
|
} catch (e) {
|
|
toastError((e as Error).message);
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<button
|
|
className="btn btn-ghost flex items-center gap-1"
|
|
disabled={busy}
|
|
onClick={() => ref.current?.click()}
|
|
title="آپلود تصویر"
|
|
>
|
|
{busy ? (
|
|
<>
|
|
<Loader2 size={14} className="animate-spin" /> در حال آپلود…
|
|
</>
|
|
) : (
|
|
<Upload size={14} />
|
|
)}
|
|
</button>
|
|
<input
|
|
ref={ref}
|
|
type="file"
|
|
accept="image/*"
|
|
hidden
|
|
onChange={(e) => {
|
|
const f = e.target.files?.[0];
|
|
if (f) upload(f);
|
|
e.target.value = "";
|
|
}}
|
|
/>
|
|
</>
|
|
);
|
|
}
|