Files
2026-06-03 03:19:46 +03:30

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>
);
}