"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 { Calculator, Plus } from "lucide-react"; import { accountingApi } from "@/lib/accounting-api"; import { useTenantId } from "@/hooks/useTenantId"; import { formatMoney } from "@/lib/utils"; import { PageHeader, Button, Dialog, Input, DataTable, EmptyState, LoadingState, ErrorState, Badge, Tabs, FormField, Select, MoneyInput, DatePicker, JalaliDateText, Card, CardContent, CardHeader, CardTitle, } from "@/components/ds"; const deptSchema = z.object({ code: z.string().min(1, "کد الزامی است"), name: z.string().min(1, "نام الزامی است"), }); const employeeSchema = z.object({ employee_code: z.string().min(1, "کد پرسنلی الزامی است"), first_name: z.string().min(1, "نام الزامی است"), last_name: z.string().min(1, "نام خانوادگی الزامی است"), base_salary: z.string().optional(), department_id: z.string().optional(), }); const periodSchema = z.object({ name: z.string().min(1, "نام دوره الزامی است"), start_date: z.string().min(1, "تاریخ شروع الزامی است"), end_date: z.string().min(1, "تاریخ پایان الزامی است"), }); const calcSchema = z.object({ payroll_period_id: z.string().min(1, "دوره حقوق را انتخاب کنید"), employee_id: z.string().min(1, "کارمند را انتخاب کنید"), }); type TabId = "departments" | "employees" | "periods" | "operations"; export default function PayrollPage() { const { tenantId } = useTenantId(); const qc = useQueryClient(); const [tab, setTab] = useState("employees"); const [deptOpen, setDeptOpen] = useState(false); const [employeeOpen, setEmployeeOpen] = useState(false); const [periodOpen, setPeriodOpen] = useState(false); const [lastPayroll, setLastPayroll] = useState<{ id: string; gross_salary: string; net_salary: string; status: string; } | null>(null); const departmentsQ = useQuery({ queryKey: ["accounting", tenantId, "payroll-departments"], queryFn: () => accountingApi.payroll.listDepartments(tenantId!), enabled: !!tenantId, }); const employeesQ = useQuery({ queryKey: ["accounting", tenantId, "employees"], queryFn: () => accountingApi.payroll.listEmployees(tenantId!), enabled: !!tenantId, }); const periodsQ = useQuery({ queryKey: ["accounting", tenantId, "payroll-periods"], queryFn: () => accountingApi.payroll.listPeriods(tenantId!), enabled: !!tenantId, }); const deptForm = useForm>({ resolver: zodResolver(deptSchema), defaultValues: { code: "", name: "" }, }); const employeeForm = useForm>({ resolver: zodResolver(employeeSchema), defaultValues: { employee_code: "", first_name: "", last_name: "", base_salary: "0", department_id: "" }, }); const periodForm = useForm>({ resolver: zodResolver(periodSchema), defaultValues: { name: "", start_date: "", end_date: "" }, }); const calcForm = useForm>({ resolver: zodResolver(calcSchema), defaultValues: { payroll_period_id: "", employee_id: "" }, }); const invalidateDepts = () => qc.invalidateQueries({ queryKey: ["accounting", tenantId, "payroll-departments"] }); const invalidateEmployees = () => qc.invalidateQueries({ queryKey: ["accounting", tenantId, "employees"] }); const invalidatePeriods = () => qc.invalidateQueries({ queryKey: ["accounting", tenantId, "payroll-periods"] }); const createDeptM = useMutation({ mutationFn: (d: z.infer) => accountingApi.payroll.createDepartment(tenantId!, d), onSuccess: async () => { toast.success("واحد سازمانی ثبت شد"); setDeptOpen(false); deptForm.reset(); await invalidateDepts(); }, onError: (e: Error) => toast.error(e.message), }); const createEmployeeM = useMutation({ mutationFn: (d: z.infer) => accountingApi.payroll.createEmployee(tenantId!, { employee_code: d.employee_code, first_name: d.first_name, last_name: d.last_name, base_salary: d.base_salary, department_id: d.department_id || undefined, }), onSuccess: async () => { toast.success("کارمند ثبت شد"); setEmployeeOpen(false); employeeForm.reset(); await invalidateEmployees(); }, onError: (e: Error) => toast.error(e.message), }); const createPeriodM = useMutation({ mutationFn: (d: z.infer) => accountingApi.payroll.createPeriod(tenantId!, d), onSuccess: async () => { toast.success("دوره حقوق ایجاد شد"); setPeriodOpen(false); periodForm.reset(); await invalidatePeriods(); }, onError: (e: Error) => toast.error(e.message), }); const calculateM = useMutation({ mutationFn: (d: z.infer) => accountingApi.payroll.calculate(tenantId!, d), onSuccess: (res) => { setLastPayroll(res); toast.success("حقوق محاسبه شد"); }, onError: (e: Error) => toast.error(e.message), }); const postM = useMutation({ mutationFn: (payrollId: string) => accountingApi.payroll.post(tenantId!, payrollId), onSuccess: (res) => { toast.success("حقوق ثبت حسابداری شد"); setLastPayroll((prev) => (prev ? { ...prev, status: res.status } : prev)); }, onError: (e: Error) => toast.error(e.message), }); if (!tenantId) return ; const loading = departmentsQ.isLoading || employeesQ.isLoading || periodsQ.isLoading; if (loading) return ; const err = departmentsQ.error || employeesQ.error || periodsQ.error; if (err) { return ( { void departmentsQ.refetch(); void employeesQ.refetch(); void periodsQ.refetch(); }} /> ); } const tabActions: Record = { departments: ( ), employees: ( ), periods: ( ), operations: null, }; const periodStatusTone = (s: string) => s === "open" ? "success" : s === "closed" ? "danger" : "default"; return (
setTab(id as TabId)} items={[ { id: "departments", label: "واحدها" }, { id: "employees", label: "کارکنان" }, { id: "periods", label: "دوره‌ها" }, { id: "operations", label: "محاسبه و ثبت" }, ]} /> {tab === "departments" && ( []} empty={ setDeptOpen(true)}>ثبت واحد} />} /> )} {tab === "employees" && ( []} empty={ setEmployeeOpen(true)}>ثبت کارمند} />} /> )} {tab === "periods" && ( , }, { key: "end_date", header: "پایان", render: (r) => , }, { key: "status", header: "وضعیت", render: (r) => ( {String(r.status)} ), }, ]} rows={(periodsQ.data ?? []) as unknown as Record[]} empty={ setPeriodOpen(true)}>ایجاد دوره} />} /> )} {tab === "operations" && (
محاسبه حقوق
calculateM.mutate(d))}>
{lastPayroll && ( نتیجه محاسبه
ناخالص {formatMoney(lastPayroll.gross_salary)}
خالص {formatMoney(lastPayroll.net_salary)}
وضعیت {lastPayroll.status}
)}
)} setDeptOpen(false)} title="واحد سازمانی جدید">
createDeptM.mutate(d))}>
setEmployeeOpen(false)} title="کارمند جدید">
createEmployeeM.mutate(d))}>
setPeriodOpen(false)} title="دوره حقوق جدید">
createPeriodM.mutate(d))}>
( )} /> ( )} />
); }