"use client"; import { 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, Trophy, XCircle } from "lucide-react"; import { Button, Dialog, Drawer, Input, FormField, Select, EmptyState, ConfirmDialog, } from "@/components/ds"; import { crmApi } from "@/modules/crm/services/crm-api"; import { useTenantId } from "@/hooks/useTenantId"; import { CrmTablePage, CrmStatusChip } from "@/modules/crm/design-system"; import { CrmBreadcrumbs } from "@/modules/crm/components/CrmBreadcrumbs"; import { CrmPageLoader, CrmPageError } from "@/modules/crm/pages/shared"; import type { Opportunity, Pipeline, PipelineStage } from "@/modules/crm/types"; const createSchema = z.object({ title: z.string().min(1, "عنوان الزامی است"), pipeline_id: z.string().uuid(), stage_id: z.string().uuid(), amount: z.string().default("0"), probability: z.number().min(0).max(100).default(0), priority: z.enum(["low", "medium", "high", "urgent"]).default("medium"), expected_close_date: z.string().optional(), description: z.string().optional(), }); export default function DealsPage() { const { tenantId } = useTenantId(); const qc = useQueryClient(); const [createOpen, setCreateOpen] = useState(false); const [editRow, setEditRow] = useState(null); const [winId, setWinId] = useState(null); const [loseId, setLoseId] = useState(null); const [page, setPage] = useState(1); const pipelinesQ = useQuery({ queryKey: ["crm", tenantId, "pipelines"], queryFn: () => crmApi.pipelines.list(tenantId!, { page: 1, page_size: 100 }), enabled: !!tenantId, }); const listQ = useQuery({ queryKey: ["crm", tenantId, "opportunities"], queryFn: () => crmApi.opportunities.list(tenantId!, { page: 1, page_size: 500 }), enabled: !!tenantId, }); const stagesQ = useQuery({ queryKey: ["crm", tenantId, "all-stages"], queryFn: async () => { const pipelines = pipelinesQ.data ?? []; const stages: PipelineStage[] = []; for (const p of pipelines) { const s = await crmApi.pipelines.stages(tenantId!, p.id, { page: 1, page_size: 100 }); stages.push(...s); } return stages; }, enabled: !!tenantId && !!pipelinesQ.data?.length, }); const form = useForm({ resolver: zodResolver(createSchema), defaultValues: { title: "", pipeline_id: "", stage_id: "", amount: "0", probability: 0, priority: "medium" as const, description: "", }, }); const editForm = useForm({ resolver: zodResolver(createSchema.partial()), }); const pipelineId = form.watch("pipeline_id"); const stageOptions = stagesQ.data?.filter((s) => s.pipeline_id === pipelineId) ?? []; const invalidate = () => { qc.invalidateQueries({ queryKey: ["crm", tenantId, "opportunities"] }); }; const createM = useMutation({ mutationFn: (d: Record) => crmApi.opportunities.create(tenantId!, { ...d, name: d.title }), onSuccess: async () => { toast.success("معامله ایجاد شد"); setCreateOpen(false); form.reset(); await invalidate(); }, onError: (e: Error) => toast.error(e.message), }); const updateM = useMutation({ mutationFn: (d: Record) => crmApi.opportunities.update(tenantId!, editRow!.id, d), onSuccess: async () => { toast.success("معامله به‌روز شد"); setEditRow(null); await invalidate(); }, onError: (e: Error) => toast.error(e.message), }); const winM = useMutation({ mutationFn: (id: string) => crmApi.opportunities.win(tenantId!, id), onSuccess: async () => { toast.success("معامله برنده شد"); setWinId(null); await invalidate(); }, onError: (e: Error) => toast.error(e.message), }); const loseM = useMutation({ mutationFn: (id: string) => crmApi.opportunities.lose(tenantId!, id, { reason_id: null }), onSuccess: async () => { toast.success("معامله باخته شد"); setLoseId(null); await invalidate(); }, onError: (e: Error) => toast.error(e.message), }); const stageName = (id: string) => stagesQ.data?.find((s) => s.id === id)?.name ?? id.slice(0, 8); if (!tenantId || listQ.isLoading || pipelinesQ.isLoading) return ; if (listQ.error) return listQ.refetch()} />; const pipelines = pipelinesQ.data ?? []; return (
} title="معاملات" description="فرصت‌های فروش (Opportunities) — قیف، مبلغ، احتمال و بستن معامله." columns={[ { key: "opportunity_number", header: "شماره" }, { key: "title", header: "عنوان" }, { key: "stage_id", header: "مرحله", render: (r) => stageName(String(r.stage_id)), }, { key: "amount", header: "مبلغ" }, { key: "probability", header: "احتمال %" }, { key: "status", header: "وضعیت", render: (r) => ( ), }, { key: "actions", header: "عملیات", searchable: false, render: (r) => (
{r.status === "open" ? ( <> ) : null}
), }, ]} rows={(listQ.data ?? []) as unknown as Record[]} page={page} pageSize={20} onPageChange={setPage} actions={ } empty={ setCreateOpen(true)}>ایجاد معامله ) : undefined } /> } /> setCreateOpen(false)} title="معامله جدید">
createM.mutate(d))}>
setEditRow(null)} title="ویرایش معامله">
updateM.mutate(d))}>
setWinId(null)} onConfirm={() => winId && winM.mutate(winId)} title="برنده شدن معامله" description="وضعیت معامله به برنده تغییر می‌کند." confirmLabel="تأیید برنده" loading={winM.isPending} /> setLoseId(null)} onConfirm={() => loseId && loseM.mutate(loseId)} title="باخت معامله" description="وضعیت معامله به باخته تغییر می‌کند." confirmLabel="تأیید باخت" loading={loseM.isPending} danger />
); }