176 lines
4.7 KiB
TypeScript
176 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { apiFetch, ApiError } from "@/lib/api";
|
|
import { useList } from "@/lib/useResource";
|
|
import { useToast } from "@/components/toast";
|
|
import { DataTable, type Column } from "@/components/DataTable";
|
|
import {
|
|
Button,
|
|
ConfirmDialog,
|
|
Field,
|
|
Input,
|
|
Select,
|
|
PageHeader,
|
|
Card,
|
|
} from "@/components/ui";
|
|
import { TrashIcon } from "@/components/icons";
|
|
import { toFa } from "@/lib/utils";
|
|
|
|
interface Comment {
|
|
id?: number;
|
|
content?: string;
|
|
text?: string;
|
|
body?: string;
|
|
created_at?: string;
|
|
user?: {
|
|
name?: string;
|
|
};
|
|
name?: string;
|
|
}
|
|
|
|
export default function CommentsPage() {
|
|
const toast = useToast();
|
|
|
|
// Form (uncommitted) state
|
|
const [type, setType] = useState("media");
|
|
const [id, setId] = useState("");
|
|
|
|
// Committed query state used to build the request path
|
|
const [query, setQuery] = useState<{ type: string; id: string } | null>(null);
|
|
|
|
const path =
|
|
query && query.type && query.id
|
|
? `/comments/${query.type}/${query.id}`
|
|
: null;
|
|
|
|
const { data, loading, error, reload } = useList<Comment>(path);
|
|
|
|
const [deleting, setDeleting] = useState<Comment | null>(null);
|
|
const [removing, setRemoving] = useState(false);
|
|
|
|
function submit(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
if (!type || !id.trim()) {
|
|
toast.error("نوع و شناسه را وارد کنید.");
|
|
return;
|
|
}
|
|
setQuery({ type, id: id.trim() });
|
|
}
|
|
|
|
async function confirmDelete() {
|
|
if (!deleting || !query || deleting.id == null) return;
|
|
setRemoving(true);
|
|
try {
|
|
// Delete targets the comment's own id: /comments/{type}/{id}/{commentId}
|
|
await apiFetch(`/comments/${query.type}/${query.id}/${deleting.id}`, {
|
|
method: "DELETE",
|
|
});
|
|
toast.success("نظر حذف شد.");
|
|
setDeleting(null);
|
|
reload();
|
|
} catch (err) {
|
|
toast.error(err instanceof ApiError ? err.message : "خطا در حذف");
|
|
} finally {
|
|
setRemoving(false);
|
|
}
|
|
}
|
|
|
|
const columns: Column<Comment>[] = [
|
|
{
|
|
key: "user",
|
|
header: "کاربر",
|
|
render: (r) => r.user?.name ?? r.name ?? "—",
|
|
},
|
|
{
|
|
key: "content",
|
|
header: "متن",
|
|
render: (r) => r.content ?? r.text ?? r.body ?? "—",
|
|
},
|
|
{
|
|
key: "created_at",
|
|
header: "تاریخ",
|
|
render: (r) =>
|
|
r.created_at
|
|
? toFa(new Date(r.created_at).toLocaleDateString("fa-IR"))
|
|
: "—",
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<PageHeader
|
|
title="نظرات"
|
|
subtitle="مشاهده و مدیریت نظرات بر اساس نوع و شناسه محتوا"
|
|
/>
|
|
|
|
<Card className="mb-4">
|
|
<form
|
|
onSubmit={submit}
|
|
className="flex flex-wrap items-end gap-4"
|
|
>
|
|
<div className="min-w-40">
|
|
<Field label="نوع">
|
|
<Select value={type} onChange={(e) => setType(e.target.value)}>
|
|
<option value="media">رسانه</option>
|
|
<option value="music">موسیقی</option>
|
|
<option value="playlist">پلیلیست</option>
|
|
</Select>
|
|
</Field>
|
|
</div>
|
|
<div className="min-w-40">
|
|
<Field label="شناسه">
|
|
<Input
|
|
value={id}
|
|
onChange={(e) => setId(e.target.value)}
|
|
placeholder="شناسه محتوا"
|
|
dir="ltr"
|
|
/>
|
|
</Field>
|
|
</div>
|
|
<Button type="submit">نمایش</Button>
|
|
</form>
|
|
</Card>
|
|
|
|
{path ? (
|
|
<DataTable
|
|
columns={columns}
|
|
rows={data}
|
|
loading={loading}
|
|
error={error}
|
|
onRetry={reload}
|
|
emptyMessage={
|
|
path
|
|
? "نظری برای این مورد ثبت نشده است."
|
|
: "برای مشاهده نظرات، نوع و شناسه را انتخاب و روی «نمایش» بزنید."
|
|
}
|
|
actions={(row) => (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => setDeleting(row)}
|
|
aria-label="حذف"
|
|
className="text-danger"
|
|
>
|
|
<TrashIcon className="h-4 w-4" />
|
|
</Button>
|
|
)}
|
|
/>
|
|
) : (
|
|
<Card>
|
|
<p className="text-sm text-muted">
|
|
برای مشاهده نظرات، نوع و شناسه را انتخاب و دکمه نمایش را بزنید.
|
|
</p>
|
|
</Card>
|
|
)}
|
|
|
|
<ConfirmDialog
|
|
open={!!deleting}
|
|
message="آیا از حذف این نظر مطمئن هستید؟"
|
|
loading={removing}
|
|
onConfirm={confirmDelete}
|
|
onClose={() => setDeleting(null)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|