"use client"; import { useState } from "react"; import { useSearchParams } from "next/navigation"; 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 { Plus } from "lucide-react"; import { accountingApi } from "@/lib/accounting-api"; import { useTenantId } from "@/hooks/useTenantId"; import { formatMoney, parseMoneyInput } from "@/lib/utils"; import { PageHeader, Button, Dialog, Input, DataTable, EmptyState, LoadingState, ErrorState, Badge, Tabs, FormField, Select, MoneyInput, DatePicker, } from "@/components/ds"; const cashBoxSchema = z.object({ code: z.string().min(1, "کد الزامی است"), name: z.string().min(1, "نام الزامی است"), opening_balance: z.string().optional(), }); const bankSchema = z.object({ code: z.string().min(1, "کد الزامی است"), name: z.string().min(1, "نام الزامی است"), }); const bankAccountSchema = z.object({ bank_id: z.string().min(1, "بانک را انتخاب کنید"), account_number: z.string().min(1, "شماره حساب الزامی است"), iban: z.string().optional(), }); const receiptSchema = z.object({ cash_box_id: z.string().min(1, "صندوق را انتخاب کنید"), amount: z.string().min(1, "مبلغ الزامی است"), debit_account_id: z.string().min(1, "حساب بدهکار را انتخاب کنید"), credit_account_id: z.string().min(1, "حساب بستانکار را انتخاب کنید"), description: z.string().optional(), transaction_date: z.string().optional(), }); type TabId = "cash-boxes" | "banks" | "bank-accounts" | "receipt"; function isTabId(v: string | null): v is TabId { return v === "cash-boxes" || v === "banks" || v === "bank-accounts" || v === "receipt"; } export default function TreasuryPage() { const { tenantId } = useTenantId(); const qc = useQueryClient(); const searchParams = useSearchParams(); const initialTab = searchParams.get("tab"); const [tab, setTab] = useState(isTabId(initialTab) ? initialTab : "cash-boxes"); const [cashBoxOpen, setCashBoxOpen] = useState(false); const [bankOpen, setBankOpen] = useState(false); const [bankAccountOpen, setBankAccountOpen] = useState(false); const cashBoxesQ = useQuery({ queryKey: ["accounting", tenantId, "cash-boxes"], queryFn: () => accountingApi.treasury.listCashBoxes(tenantId!), enabled: !!tenantId, }); const banksQ = useQuery({ queryKey: ["accounting", tenantId, "banks"], queryFn: () => accountingApi.treasury.listBanks(tenantId!), enabled: !!tenantId, }); const bankAccountsQ = useQuery({ queryKey: ["accounting", tenantId, "bank-accounts"], queryFn: () => accountingApi.treasury.listBankAccounts(tenantId!), enabled: !!tenantId, }); const accountsQ = useQuery({ queryKey: ["accounting", tenantId, "accounts"], queryFn: () => accountingApi.accounts.list(tenantId!), enabled: !!tenantId, }); const cashBoxForm = useForm>({ resolver: zodResolver(cashBoxSchema), defaultValues: { code: "", name: "", opening_balance: "0" }, }); const bankForm = useForm>({ resolver: zodResolver(bankSchema), defaultValues: { code: "", name: "" }, }); const bankAccountForm = useForm>({ resolver: zodResolver(bankAccountSchema), defaultValues: { bank_id: "", account_number: "", iban: "" }, }); const receiptForm = useForm>({ resolver: zodResolver(receiptSchema), defaultValues: { cash_box_id: "", amount: "", debit_account_id: "", credit_account_id: "", description: "", transaction_date: "", }, }); const postableAccounts = (accountsQ.data ?? []).filter((a) => a.is_postable && a.status === "active"); const invalidateCashBoxes = () => qc.invalidateQueries({ queryKey: ["accounting", tenantId, "cash-boxes"] }); const invalidateBanks = () => qc.invalidateQueries({ queryKey: ["accounting", tenantId, "banks"] }); const invalidateBankAccounts = () => qc.invalidateQueries({ queryKey: ["accounting", tenantId, "bank-accounts"] }); const createCashBoxM = useMutation({ mutationFn: (d: z.infer) => accountingApi.treasury.createCashBox(tenantId!, { code: d.code, name: d.name, opening_balance: parseMoneyInput(d.opening_balance || "0") || "0", }), onSuccess: async () => { toast.success("صندوق ایجاد شد"); setCashBoxOpen(false); cashBoxForm.reset(); await invalidateCashBoxes(); }, onError: (e: Error) => toast.error(e.message), }); const archiveCashBoxM = useMutation({ mutationFn: (id: string) => accountingApi.treasury.updateCashBox(tenantId!, id, { is_active: false }), onSuccess: async () => { toast.success("صندوق بایگانی شد"); await invalidateCashBoxes(); }, onError: (e: Error) => toast.error(e.message), }); const createBankM = useMutation({ mutationFn: (d: z.infer) => accountingApi.treasury.createBank(tenantId!, d), onSuccess: async () => { toast.success("بانک ثبت شد"); setBankOpen(false); bankForm.reset(); await invalidateBanks(); }, onError: (e: Error) => toast.error(e.message), }); const createBankAccountM = useMutation({ mutationFn: (d: z.infer) => accountingApi.treasury.createBankAccount(tenantId!, { bank_id: d.bank_id, account_number: d.account_number, iban: d.iban || undefined, }), onSuccess: async () => { toast.success("حساب بانکی ثبت شد"); setBankAccountOpen(false); bankAccountForm.reset(); await invalidateBankAccounts(); }, onError: (e: Error) => toast.error(e.message), }); const receiptM = useMutation({ mutationFn: (d: z.infer) => accountingApi.treasury.cashReceipt(tenantId!, { cash_box_id: d.cash_box_id, amount: parseMoneyInput(d.amount) || "0", debit_account_id: d.debit_account_id, credit_account_id: d.credit_account_id, description: d.description || undefined, transaction_date: d.transaction_date || undefined, }), onSuccess: () => { toast.success("رسید دریافت نقد ثبت شد"); receiptForm.reset(); void invalidateCashBoxes(); }, onError: (e: Error) => toast.error(e.message), }); if (!tenantId) return ; const loading = cashBoxesQ.isLoading || banksQ.isLoading || bankAccountsQ.isLoading || accountsQ.isLoading; if (loading) return ; const err = cashBoxesQ.error || banksQ.error || bankAccountsQ.error || accountsQ.error; if (err) { return ( { void cashBoxesQ.refetch(); void banksQ.refetch(); void bankAccountsQ.refetch(); void accountsQ.refetch(); }} /> ); } const bankName = (id: string) => banksQ.data?.find((b) => b.id === id)?.name ?? id; const tabActions: Record = { "cash-boxes": ( ), banks: ( ), "bank-accounts": ( ), receipt: null, }; return (
setTab(id as TabId)} items={[ { id: "cash-boxes", label: "صندوق‌ها" }, { id: "banks", label: "بانک‌ها" }, { id: "bank-accounts", label: "حساب‌های بانکی" }, { id: "receipt", label: "دریافت نقد" }, ]} /> {tab === "cash-boxes" && ( formatMoney(String(r.current_balance)), }, { key: "is_active", header: "وضعیت", render: (r) => ( {r.is_active ? "فعال" : "غیرفعال"} ), }, { key: "actions", header: "عملیات", render: (r) => r.is_active ? ( ) : ( "—" ), }, ]} rows={(cashBoxesQ.data ?? []) as unknown as Record[]} empty={ setCashBoxOpen(true)}>ایجاد صندوق} /> } /> )} {tab === "banks" && ( ( {r.is_active ? "فعال" : "غیرفعال"} ), }, ]} rows={(banksQ.data ?? []) as unknown as Record[]} empty={ setBankOpen(true)}>ثبت بانک} />} /> )} {tab === "bank-accounts" && ( bankName(String(r.bank_id)), }, { key: "account_number", header: "شماره حساب" }, { key: "iban", header: "شبا", render: (r) => String(r.iban ?? "—") }, { key: "is_active", header: "وضعیت", render: (r) => ( {r.is_active ? "فعال" : "غیرفعال"} ), }, ]} rows={(bankAccountsQ.data ?? []) as unknown as Record[]} empty={ setBankAccountOpen(true)}>ثبت حساب} /> } /> )} {tab === "receipt" && (
receiptM.mutate(d))} > ( )} />
)} setCashBoxOpen(false)} title="صندوق جدید">
createCashBoxM.mutate(d))}>
setBankOpen(false)} title="بانک جدید">
createBankM.mutate(d))}>
setBankAccountOpen(false)} title="حساب بانکی جدید">
createBankAccountM.mutate(d))} >
); }