"use client"; import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; 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"; import { Button } from "@/components/ui/Button"; import { Input } from "@/components/ui/Input"; import { Modal } from "@/components/ui/Modal"; import { formatDate } from "@/lib/utils"; import api from "@/lib/api"; import { toast } from "sonner"; import type { Lead } from "@/types"; const statusVariant: Record = { NEW: "muted", CONTACTED: "default" as any, INTERESTED: "warning", CONVERTED: "success", LOST: "danger", }; 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], queryFn: async () => { const params = new URLSearchParams({ limit: "100" }); if (search) params.set("search", search); const res = await api.get(`/api/v1/leads?${params}`); const d = res.data; return Array.isArray(d) ? d : d.data ?? []; }, }); const addLead = useMutation({ mutationFn: async () => { await api.post("/api/v1/leads", { firstName: form.firstName, lastName: form.lastName, phone: form.phone, email: form.email || undefined, address: form.address || undefined, notes: form.notes || undefined, source: form.source || undefined, }); }, onSuccess: () => { toast.success("Lead added!"); setShowAdd(false); setForm({ firstName: "", lastName: "", phone: "", email: "", address: "", notes: "", source: "" }); qc.invalidateQueries({ queryKey: ["leads"] }); }, onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to add lead"), }); const updateStatus = useMutation({ mutationFn: async ({ id, status }: { id: string; status: string }) => { await api.patch(`/api/v1/leads/${id}`, { status }); }, onSuccess: () => { toast.success("Status updated!"); qc.invalidateQueries({ queryKey: ["leads"] }); if (selected) setSelected(prev => prev ? { ...prev, status: statusUpdate as Lead["status"] } : prev); }, onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to update"), }); const deleteLead = useMutation({ mutationFn: async (id: string) => { await api.delete(`/api/v1/leads/${id}`); }, onSuccess: () => { toast.success("Lead deleted"); setSelected(null); qc.invalidateQueries({ queryKey: ["leads"] }); }, 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 (

Leads

Prospective customers pipeline

{/* Pipeline summary */}
{statusOptions.map(status => (

{counts[status] ?? 0}

{status}
))}

{data.length}

TOTAL

All Leads ({data.length}) setSearch(e.target.value)} />
{isLoading ? ( Array.from({ length: 6 }).map((_, i) => ( )) ) : data.length === 0 ? ( } /> ) : data.map(lead => ( { setSelected(lead); setStatusUpdate(lead.status); }} className="cursor-pointer hover:bg-blue-50 transition-colors"> ))}
NamePhoneEmailAddressStatusSourceAdded
{lead.firstName} {lead.lastName} {lead.phone} {lead.email ?? "—"} {lead.address ?? "—"} {lead.status} {lead.source ?? "—"} {formatDate(lead.createdAt)}
{/* Lead Detail Modal */} setSelected(null)} title={`${selected?.firstName ?? ""} ${selected?.lastName ?? ""}`}> {selected && (
Phone{selected.phone}
{selected.email &&
Email{selected.email}
} {selected.address &&
Address{selected.address}
} {selected.source &&
Source{selected.source}
} {selected.notes &&
Notes{selected.notes}
}
Added{formatDate(selected.createdAt)}
{statusOptions.map(s => ( ))}
{statusUpdate !== selected.status && ( )}
{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">
setForm(f => ({ ...f, firstName: e.target.value }))} /> setForm(f => ({ ...f, lastName: e.target.value }))} />
setForm(f => ({ ...f, phone: e.target.value }))} /> setForm(f => ({ ...f, email: e.target.value }))} /> setForm(f => ({ ...f, address: e.target.value }))} /> setForm(f => ({ ...f, source: e.target.value }))} />