"use client"; import { useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { Plus, Pencil, Archive } from "lucide-react"; import { accountingApi, type CostCenter } from "@/lib/accounting-api"; import { useTenantId } from "@/hooks/useTenantId"; import { PageHeader, Button, Dialog, ConfirmDialog, Input, Label, FieldError, DataTable, EmptyState, LoadingState, ErrorState, Badge, Drawer, Select, } from "@/components/ds"; const createSchema = z.object({ code: z.string().min(1, "کد الزامی است"), name: z.string().min(1, "نام الزامی است"), parent_id: z.string().optional(), }); const editSchema = z.object({ name: z.string().min(1, "نام الزامی است"), parent_id: z.string().optional(), }); export default function CostCentersPage() { const { tenantId } = useTenantId(); const qc = useQueryClient(); const [createOpen, setCreateOpen] = useState(false); const [selected, setSelected] = useState(null); const [archiveId, setArchiveId] = useState(null); const createForm = useForm>({ resolver: zodResolver(createSchema), defaultValues: { code: "", name: "", parent_id: "" }, }); const editForm = useForm>({ resolver: zodResolver(editSchema), }); const listQ = useQuery({ queryKey: ["accounting", tenantId, "cost-centers"], queryFn: () => accountingApi.costCenters.list(tenantId!), enabled: !!tenantId, }); const activeCenters = useMemo( () => (listQ.data ?? []).filter((c) => c.is_active), [listQ.data] ); const parentName = (id: string | null | undefined) => { if (!id) return "—"; return listQ.data?.find((c) => c.id === id)?.name ?? id; }; const invalidate = async () => { await qc.invalidateQueries({ queryKey: ["accounting", tenantId, "cost-centers"] }); }; const createM = useMutation({ mutationFn: (d: z.infer) => accountingApi.costCenters.create(tenantId!, { code: d.code, name: d.name, parent_id: d.parent_id || null, }), onSuccess: async () => { toast.success("مرکز هزینه ایجاد شد"); setCreateOpen(false); createForm.reset(); await invalidate(); }, onError: (e: Error) => toast.error(e.message), }); const updateM = useMutation({ mutationFn: (d: z.infer) => accountingApi.costCenters.update(tenantId!, selected!.id, { name: d.name, parent_id: d.parent_id || null, }), onSuccess: async () => { toast.success("مرکز هزینه به‌روز شد"); setSelected(null); await invalidate(); }, onError: (e: Error) => toast.error(e.message), }); const archiveM = useMutation({ mutationFn: (id: string) => accountingApi.costCenters.archive(tenantId!, id), onSuccess: async () => { toast.success("مرکز هزینه آرشیو شد"); setArchiveId(null); setSelected(null); await invalidate(); }, onError: (e: Error) => toast.error(e.message), }); if (!tenantId || listQ.isLoading) return ; if (listQ.error) return listQ.refetch()} />; const parentOptions = (excludeId?: string) => activeCenters.filter((c) => c.id !== excludeId); return (
setCreateOpen(true)}> مرکز جدید } /> parentName(r.parent_id as string | null | undefined), }, { key: "is_active", header: "وضعیت", render: (r) => ( {r.is_active ? "فعال" : "آرشیو"} ), }, { key: "actions", header: "عملیات", render: (r) => { const id = String(r.id); const active = Boolean(r.is_active); return active ? (
) : ( "—" ); }, }, ]} rows={(listQ.data ?? []) as unknown as Record[]} empty={ setCreateOpen(true)}>ایجاد} /> } /> setCreateOpen(false)} title="مرکز هزینه جدید">
createM.mutate(d))}>
{createForm.formState.errors.code?.message}
{createForm.formState.errors.name?.message}
setSelected(null)} title="ویرایش مرکز هزینه" description={selected ? `${selected.code}` : undefined} >
updateM.mutate(d))}>
{editForm.formState.errors.name?.message}
setArchiveId(null)} onConfirm={() => archiveId && archiveM.mutate(archiveId)} title="آرشیو مرکز هزینه؟" description="مرکز آرشیو شده در اسناد جدید قابل انتخاب نیست." confirmLabel="آرشیو" danger loading={archiveM.isPending} />
); }