diff --git a/app/(app)/leads/page.tsx b/app/(app)/leads/page.tsx index 2b75111..1a1f0fd 100644 --- a/app/(app)/leads/page.tsx +++ b/app/(app)/leads/page.tsx @@ -2,7 +2,8 @@ import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { UserPlus, RefreshCw } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { UserPlus, RefreshCw, ArrowRightCircle } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card"; import { Table, TableHead, TableBody, TableRow, Th, Td, EmptyState } from "@/components/ui/Table"; import { Badge } from "@/components/ui/Badge"; @@ -22,11 +23,14 @@ const statusOptions = ["NEW", "CONTACTED", "INTERESTED", "CONVERTED", "LOST"]; export default function LeadsPage() { const qc = useQueryClient(); + const router = useRouter(); const [search, setSearch] = useState(""); const [selected, setSelected] = useState(null); const [showAdd, setShowAdd] = useState(false); + const [showConvert, setShowConvert] = useState(false); const [form, setForm] = useState({ firstName: "", lastName: "", phone: "", email: "", address: "", notes: "", source: "" }); const [statusUpdate, setStatusUpdate] = useState(""); + const [convertForm, setConvertForm] = useState({ firstName: "", lastName: "", phone: "", email: "", address: "", areaId: "", planId: "", billingType: "POSTPAID" }); const { data = [], isLoading, refetch } = useQuery({ queryKey: ["leads", search], @@ -79,6 +83,41 @@ export default function LeadsPage() { onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to delete"), }); + const convertToClient = useMutation({ + mutationFn: async () => { + const res = await api.post("/api/v1/clients", { + firstName: convertForm.firstName, lastName: convertForm.lastName, + phone: convertForm.phone, email: convertForm.email || undefined, + address: convertForm.address || undefined, + areaId: convertForm.areaId || undefined, + planId: convertForm.planId || undefined, + }); + // Mark lead as converted + if (selected) await api.patch(`/api/v1/leads/${selected.id}`, { status: "CONVERTED" }); + return res.data; + }, + onSuccess: (data: any) => { + toast.success("Lead converted to client!"); + setShowConvert(false); + setSelected(null); + qc.invalidateQueries({ queryKey: ["leads"] }); + if (data?.id) router.push(`/clients/${data.id}`); + }, + onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to convert lead"), + }); + + const { data: areas = [] } = useQuery<{ id: string; name: string }[]>({ + queryKey: ["areas"], + queryFn: async () => { const r = await api.get("/api/v1/areas"); return Array.isArray(r.data) ? r.data : (r.data as any).data ?? []; }, + }); + + const { data: activePlans = [] } = useQuery<{ id: string; name: string; monthlyPrice: number; type: string }[]>({ + queryKey: ["plans", "active"], + queryFn: async () => { const r = await api.get("/api/v1/plans?isActive=true"); return Array.isArray(r.data) ? r.data : (r.data as any).data ?? []; }, + }); + + const filteredConvertPlans = activePlans.filter(p => !convertForm.billingType || p.type === convertForm.billingType); + const counts = statusOptions.reduce((acc, s) => ({ ...acc, [s]: data.filter(l => l.status === s).length }), {} as Record); return ( @@ -176,14 +215,72 @@ export default function LeadsPage() { )} -
- +
+
+ + {selected.status !== "CONVERTED" && ( + + )} +
)} + {/* Convert to Client Modal */} + setShowConvert(false)} title="Convert Lead to Client" className="max-w-xl"> +
+

Pre-filled from lead data. Complete missing info to create the client.

+
+ setConvertForm(f => ({ ...f, firstName: e.target.value }))} /> + setConvertForm(f => ({ ...f, lastName: e.target.value }))} /> +
+ setConvertForm(f => ({ ...f, phone: e.target.value }))} /> + setConvertForm(f => ({ ...f, email: e.target.value }))} /> + setConvertForm(f => ({ ...f, address: e.target.value }))} /> +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ {/* Add Lead Modal */} setShowAdd(false)} title="Add New Lead">