Files
2026-06-03 03:08:57 +03:30

235 lines
6.4 KiB
TypeScript

"use client";
import { useState } from "react";
import { apiFetch, ApiError, toFormData } 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,
Textarea,
Modal,
PageHeader,
Badge,
} from "@/components/ui";
import { EditIcon, PlusIcon, TrashIcon } from "@/components/icons";
import { toFa } from "@/lib/utils";
interface Worry {
id: number;
title?: string;
note?: string;
is_done?: boolean;
resolved?: boolean;
}
export default function WorriesPage() {
const { data, loading, error, reload } = useList<Worry>("/worries");
const toast = useToast();
const [editing, setEditing] = useState<Worry | null>(null);
const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false);
const [title, setTitle] = useState("");
const [note, setNote] = useState("");
const [deleting, setDeleting] = useState<Worry | null>(null);
const [removing, setRemoving] = useState(false);
const [togglingId, setTogglingId] = useState<number | null>(null);
function openCreate() {
setEditing(null);
setTitle("");
setNote("");
setOpen(true);
}
function openEdit(row: Worry) {
setEditing(row);
setTitle(row.title ?? "");
setNote(row.note ?? "");
setOpen(true);
}
async function save(e: React.FormEvent) {
e.preventDefault();
setSaving(true);
try {
if (editing) {
await apiFetch(`/worries/${editing.id}`, {
method: "PUT",
body: { title, note },
});
toast.success("نگرانی ویرایش شد.");
} else {
await apiFetch("/worries", {
method: "POST",
body: toFormData({ title, note }),
});
toast.success("نگرانی افزوده شد.");
}
setOpen(false);
reload();
} catch (err) {
toast.error(err instanceof ApiError ? err.message : "خطا در ذخیره‌سازی");
} finally {
setSaving(false);
}
}
async function toggle(row: Worry) {
setTogglingId(row.id);
try {
await apiFetch(`/worries/${row.id}/toggle`, { method: "PATCH" });
toast.success("وضعیت تغییر کرد.");
reload();
} catch (err) {
toast.error(err instanceof ApiError ? err.message : "خطا در تغییر وضعیت");
} finally {
setTogglingId(null);
}
}
async function confirmDelete() {
if (!deleting) return;
setRemoving(true);
try {
await apiFetch(`/worries/${deleting.id}`, { method: "DELETE" });
toast.success("نگرانی حذف شد.");
setDeleting(null);
reload();
} catch (err) {
toast.error(err instanceof ApiError ? err.message : "خطا در حذف");
} finally {
setRemoving(false);
}
}
function isResolved(row: Worry): boolean | undefined {
if (row.is_done != null) return row.is_done;
if (row.resolved != null) return row.resolved;
return undefined;
}
const columns: Column<Worry>[] = [
{ key: "id", header: "شناسه", render: (r) => toFa(r.id), className: "w-20" },
{ key: "title", header: "عنوان", render: (r) => r.title ?? "—" },
{ key: "note", header: "یادداشت", render: (r) => r.note ?? "—" },
{
key: "status",
header: "وضعیت",
render: (r) => {
const resolved = isResolved(r);
if (resolved == null) return "—";
return (
<Badge tone={resolved ? "success" : "neutral"}>
{resolved ? "انجام‌شده" : "در انتظار"}
</Badge>
);
},
},
];
return (
<div>
<PageHeader
title="نگرانی‌ها"
subtitle="مدیریت نگرانی‌ها و یادداشت‌های کاربران"
action={
<Button icon={<PlusIcon className="h-4 w-4" />} onClick={openCreate}>
نگرانی جدید
</Button>
}
/>
<DataTable
columns={columns}
rows={data}
loading={loading}
error={error}
onRetry={reload}
emptyMessage="هنوز نگرانی‌ای ثبت نشده است."
emptyAction={
<Button icon={<PlusIcon className="h-4 w-4" />} onClick={openCreate}>
نگرانی جدید
</Button>
}
actions={(row) => (
<>
<Button
variant="ghost"
onClick={() => toggle(row)}
loading={togglingId === row.id}
>
تغییر وضعیت
</Button>
<Button
variant="ghost"
onClick={() => openEdit(row)}
aria-label="ویرایش"
>
<EditIcon className="h-4 w-4" />
</Button>
<Button
variant="ghost"
onClick={() => setDeleting(row)}
aria-label="حذف"
className="text-danger"
>
<TrashIcon className="h-4 w-4" />
</Button>
</>
)}
/>
<Modal
open={open}
onClose={() => setOpen(false)}
title={editing ? "ویرایش نگرانی" : "نگرانی جدید"}
footer={
<>
<Button variant="secondary" onClick={() => setOpen(false)}>
انصراف
</Button>
<Button form="worry-form" type="submit" loading={saving}>
ذخیره
</Button>
</>
}
>
<form id="worry-form" onSubmit={save} className="flex flex-col gap-4">
<Field label="عنوان" required>
<Input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="عنوان نگرانی"
required
autoFocus
/>
</Field>
<Field label="یادداشت">
<Textarea
value={note}
onChange={(e) => setNote(e.target.value)}
placeholder="یادداشت (اختیاری)"
/>
</Field>
</form>
</Modal>
<ConfirmDialog
open={!!deleting}
message={`آیا از حذف «${deleting?.title ?? ""}» مطمئن هستید؟`}
loading={removing}
onConfirm={confirmDelete}
onClose={() => setDeleting(null)}
/>
</div>
);
}