"use client"; import { useState } from "react"; import { Controller, 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 { Eye, Pencil, Plus } from "lucide-react"; import { accountingApi } from "@/lib/accounting-api"; import { useTenantId } from "@/hooks/useTenantId"; import { formatMoney } from "@/lib/utils"; import { PageHeader, Button, Dialog, Drawer, Input, DataTable, EmptyState, LoadingState, ErrorState, Badge, FormField, MoneyInput, Checkbox, DatePicker, JalaliDateText, StatCard, } from "@/components/ds"; const createSchema = z.object({ code: z.string().optional(), name: z.string().min(1, "نام الزامی است"), credit_limit: z.string().optional(), }); const editSchema = z.object({ name: z.string().min(1, "نام الزامی است"), credit_limit: z.string().optional(), is_active: z.boolean(), }); const invoiceSchema = z.object({ invoice_number: z.string().optional(), invoice_date: z.string().min(1, "تاریخ الزامی است"), due_date: z.string().optional(), total_amount: z.string().min(1, "مبلغ الزامی است"), }); type CustomerRow = { id: string; code: string; name: string; outstanding_balance: string; is_active: boolean; }; export default function CustomersPage() { const { tenantId } = useTenantId(); const qc = useQueryClient(); const [createOpen, setCreateOpen] = useState(false); const [editCustomer, setEditCustomer] = useState(null); const [detailCustomer, setDetailCustomer] = useState(null); const [invoiceOpen, setInvoiceOpen] = useState(false); const listQ = useQuery({ queryKey: ["accounting", tenantId, "customers"], queryFn: () => accountingApi.customers.list(tenantId!), enabled: !!tenantId, }); const agingQ = useQuery({ queryKey: ["accounting", tenantId, "customer-aging", detailCustomer?.id], queryFn: () => accountingApi.customers.aging(tenantId!, detailCustomer!.id), enabled: !!tenantId && !!detailCustomer, }); const invoicesQ = useQuery({ queryKey: ["accounting", tenantId, "customer-invoices", detailCustomer?.id], queryFn: () => accountingApi.customers.listInvoices(tenantId!, detailCustomer!.id), enabled: !!tenantId && !!detailCustomer, }); const createForm = useForm>({ resolver: zodResolver(createSchema), defaultValues: { code: "", name: "", credit_limit: "" }, }); const editForm = useForm>({ resolver: zodResolver(editSchema), defaultValues: { name: "", credit_limit: "", is_active: true }, }); const invoiceForm = useForm>({ resolver: zodResolver(invoiceSchema), defaultValues: { invoice_number: "", invoice_date: "", due_date: "", total_amount: "" }, }); const invalidateCustomers = () => qc.invalidateQueries({ queryKey: ["accounting", tenantId, "customers"] }); const invalidateInvoices = () => qc.invalidateQueries({ queryKey: ["accounting", tenantId, "customer-invoices"] }); const createM = useMutation({ mutationFn: (d: z.infer) => accountingApi.customers.create(tenantId!, { code: d.code?.trim() || undefined, name: d.name, credit_limit: d.credit_limit || undefined, }), onSuccess: async () => { toast.success("مشتری ایجاد شد"); setCreateOpen(false); createForm.reset(); await invalidateCustomers(); }, onError: (e: Error) => toast.error(e.message), }); const updateM = useMutation({ mutationFn: (d: z.infer) => accountingApi.customers.update(tenantId!, editCustomer!.id, { name: d.name, credit_limit: d.credit_limit || null, is_active: d.is_active, }), onSuccess: async () => { toast.success("مشتری به‌روز شد"); setEditCustomer(null); await invalidateCustomers(); }, onError: (e: Error) => toast.error(e.message), }); const createInvoiceM = useMutation({ mutationFn: (d: z.infer) => accountingApi.customers.createInvoice(tenantId!, { customer_id: detailCustomer!.id, invoice_number: d.invoice_number?.trim() || undefined, invoice_date: d.invoice_date, due_date: d.due_date || undefined, total_amount: d.total_amount, }), onSuccess: async () => { toast.success("فاکتور ثبت شد"); setInvoiceOpen(false); invoiceForm.reset(); await invalidateInvoices(); await invalidateCustomers(); if (detailCustomer) { await qc.invalidateQueries({ queryKey: ["accounting", tenantId, "customer-aging", detailCustomer.id], }); } }, onError: (e: Error) => toast.error(e.message), }); const openEdit = (row: CustomerRow) => { setEditCustomer(row); editForm.reset({ name: row.name, credit_limit: "", is_active: row.is_active, }); }; if (!tenantId || listQ.isLoading) return ; if (listQ.error) { return listQ.refetch()} />; } const aging = agingQ.data; return (
setCreateOpen(true)}> مشتری جدید } /> formatMoney(String(r.outstanding_balance)), }, { key: "is_active", header: "وضعیت", render: (r) => ( {r.is_active ? "فعال" : "غیرفعال"} ), }, { key: "actions", header: "عملیات", render: (r) => (
), }, ]} rows={(listQ.data ?? []) as unknown as Record[]} empty={ setCreateOpen(true)}>ایجاد مشتری} />} /> setCreateOpen(false)} title="مشتری جدید">
createM.mutate(d))}>
setEditCustomer(null)} title="ویرایش مشتری" description={editCustomer ? `${editCustomer.code} — ${editCustomer.name}` : undefined} >
updateM.mutate(d))}>
{ setDetailCustomer(null); setInvoiceOpen(false); }} title={detailCustomer?.name ?? "جزئیات مشتری"} description={detailCustomer ? `کد: ${detailCustomer.code}` : undefined} width="lg" > {detailCustomer && (
{agingQ.isLoading ? (

در حال بارگذاری سررسید…

) : agingQ.error ? (

{agingQ.error.message}

) : aging ? ( <> ) : null}

فاکتورهای دریافتنی

{invoicesQ.isLoading ? ( ) : invoicesQ.error ? ( invoicesQ.refetch()} /> ) : ( , }, { key: "total_amount", header: "مبلغ", render: (r) => formatMoney(String(r.total_amount)), }, { key: "remaining_amount", header: "مانده", render: (r) => formatMoney(String(r.remaining_amount)), }, { key: "status", header: "وضعیت", render: (r) => {String(r.status)}, }, ]} rows={(invoicesQ.data ?? []) as unknown as Record[]} empty={} /> )}
)}
setInvoiceOpen(false)} title="فاکتور دریافتنی جدید">
createInvoiceM.mutate(d))}> ( )} /> ( )} />
); }