diff --git a/app/(app)/leads/page.tsx b/app/(app)/leads/page.tsx new file mode 100644 index 0000000..c3b244f --- /dev/null +++ b/app/(app)/leads/page.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { UserPlus, RefreshCw } 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 { formatDate } from "@/lib/utils"; +import api from "@/lib/api"; +import type { Lead } from "@/types"; + +const statusVariant: Record = { + NEW: "muted", + CONTACTED: "default", + INTERESTED: "warning", + CONVERTED: "success", + LOST: "danger", +}; + +export default function LeadsPage() { + const [search, setSearch] = useState(""); + + const { data, isLoading, refetch } = useQuery({ + queryKey: ["leads", search], + queryFn: async () => { + const params = new URLSearchParams({ limit: "50" }); + 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 leads = data ?? []; + + return ( +
+
+
+

Leads

+

Prospective customers pipeline

+
+ +
+ + {/* Status summary */} +
+ {Object.entries(statusVariant).map(([status]) => { + const count = leads.filter(l => l.status === status).length; + return count > 0 ? ( +
+

{count}

+

{status}

+
+ ) : null; + })} +
+ + + +
+ All Leads ({leads.length}) + setSearch(e.target.value)} + className="max-w-xs" + /> +
+
+ + + + + + + + + + + + + + + {isLoading ? ( + + ) : leads.length === 0 ? ( + } /> + ) : ( + leads.map((lead) => ( + + + + + + + + + + )) + )} + +
NamePhoneEmailAddressStatusAssigned ToAddedLoading...{lead.firstName} {lead.lastName}{lead.phone}{lead.email ?? "—"}{lead.address ?? "—"} + + {lead.status} + + + {lead.assignedTo + ? `${lead.assignedTo.firstName} ${lead.assignedTo.lastName}` + : "—"} + {formatDate(lead.createdAt)}
+
+
+
+ ); +} diff --git a/app/(app)/tasks/page.tsx b/app/(app)/tasks/page.tsx index 84b80fb..30750fb 100644 --- a/app/(app)/tasks/page.tsx +++ b/app/(app)/tasks/page.tsx @@ -30,7 +30,7 @@ export default function TasksPage() { const { data, isLoading, refetch } = useQuery>({ queryKey: ["tasks", page], queryFn: async () => { - const res = await api.get>(`/api/v1/tasks?page=${page}&limit=20`); + const res = await api.get>(`/api/v1/manual-tasks?page=${page}&limit=20`); return res.data; }, }); @@ -79,7 +79,7 @@ export default function TasksPage() { ) : ( tasks.map((task) => ( - {task.title} + {task.type?.replace(/_/g," ")} {task.type} diff --git a/components/layout/sidebar.tsx b/components/layout/sidebar.tsx index 4dd3f62..0da83a7 100644 --- a/components/layout/sidebar.tsx +++ b/components/layout/sidebar.tsx @@ -4,13 +4,14 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { useAuthStore } from '@/lib/auth-store'; import { - LayoutDashboard, Users, FileText, CreditCard, ArrowLeftRight, + LayoutDashboard, Users, UserPlus, FileText, CreditCard, ArrowLeftRight, Ticket, BarChart3, Settings, Wifi, ClipboardList, ScrollText, } from 'lucide-react'; const navItems = [ { label: 'Dashboard', href: '/dashboard', icon: LayoutDashboard, roles: [] }, { label: 'Clients', href: '/clients', icon: Users, roles: [] }, + { label: 'Leads', href: '/leads', icon: UserPlus, roles: ['admin','staff'] }, { label: 'Subscriptions', href: '/subscriptions', icon: Wifi, roles: [] }, { label: 'Invoices', href: '/invoices', icon: FileText, roles: [] }, { label: 'Payments', href: '/payments', icon: CreditCard, roles: [] }, diff --git a/src/types/index.ts b/src/types/index.ts index 9a24d9d..2fc66d1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -207,3 +207,17 @@ export interface LegacyPaginatedResponse { page: number; limit: number; } + +export interface Lead { + id: string; + firstName: string; + lastName: string; + phone: string; + email?: string; + address?: string; + status: 'NEW' | 'CONTACTED' | 'INTERESTED' | 'CONVERTED' | 'LOST'; + source?: string; + notes?: string; + assignedTo?: { firstName: string; lastName: string }; + createdAt: string; +}