Spaces:
Sleeping
Sleeping
| "use client" | |
| import { useState, useEffect } from "react" | |
| import { useSession } from "next-auth/react" | |
| import { formatDistanceToNow } from "date-fns" | |
| import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" | |
| import { Input } from "@/components/ui/input" | |
| import { Badge } from "@/components/ui/badge" | |
| import { Button } from "@/components/ui/button" | |
| import { Loader2, Search, Filter } from "lucide-react" | |
| import Link from "next/link" | |
| interface Inquiry { | |
| id: string | |
| name: string | |
| email: string | |
| projectType: string | |
| budget: string | null | |
| status: string | |
| createdAt: string | |
| } | |
| const statusColors: Record<string, string> = { | |
| NEW: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300", | |
| IN_PROGRESS: "bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-300", | |
| QUOTED: "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300", | |
| WON: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300", | |
| LOST: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300", | |
| } | |
| export default function InquiriesPage() { | |
| const { data: session } = useSession() | |
| const [inquiries, setInquiries] = useState<Inquiry[]>([]) | |
| const [loading, setLoading] = useState(true) | |
| const [searchTerm, setSearchTerm] = useState("") | |
| useEffect(() => { | |
| if (session) { | |
| fetchInquiries() | |
| } | |
| }, [session]) | |
| const fetchInquiries = async () => { | |
| try { | |
| const res = await fetch("http://localhost:5000/api/admin/inquiries", { | |
| headers: { | |
| Authorization: `Bearer ${session?.user?.id}`, // using user id as mock token for now | |
| }, | |
| }) | |
| if (res.ok) { | |
| const data = await res.json() | |
| setInquiries(data) | |
| } | |
| } catch (error) { | |
| console.error("Failed to fetch inquiries:", error) | |
| } finally { | |
| setLoading(false) | |
| } | |
| } | |
| const filteredInquiries = inquiries.filter( | |
| (inq) => | |
| inq.name.toLowerCase().includes(searchTerm.toLowerCase()) || | |
| inq.email.toLowerCase().includes(searchTerm.toLowerCase()) || | |
| (inq.projectType && inq.projectType.toLowerCase().includes(searchTerm.toLowerCase())) | |
| ) | |
| if (loading) { | |
| return ( | |
| <div className="flex h-[50vh] items-center justify-center"> | |
| <Loader2 className="h-8 w-8 animate-spin text-primary" /> | |
| </div> | |
| ) | |
| } | |
| return ( | |
| <div className="space-y-6"> | |
| <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"> | |
| <div> | |
| <h1 className="text-3xl font-bold tracking-tight">Inquiry Management</h1> | |
| <p className="text-muted-foreground">Manage and track client quote requests.</p> | |
| </div> | |
| </div> | |
| <Card> | |
| <CardHeader> | |
| <div className="flex items-center space-x-2"> | |
| <Search className="h-4 w-4 text-muted-foreground" /> | |
| <Input | |
| placeholder="Search by name, email, or project type..." | |
| value={searchTerm} | |
| onChange={(e) => setSearchTerm(e.target.value)} | |
| className="max-w-sm" | |
| /> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="rounded-md border"> | |
| <div className="grid grid-cols-12 gap-4 border-b bg-muted/50 p-4 font-medium text-sm text-muted-foreground"> | |
| <div className="col-span-3">Client</div> | |
| <div className="col-span-2 hidden sm:block">Project</div> | |
| <div className="col-span-2 hidden md:block">Budget</div> | |
| <div className="col-span-2">Date</div> | |
| <div className="col-span-2">Status</div> | |
| <div className="col-span-1 text-right">Actions</div> | |
| </div> | |
| {filteredInquiries.length === 0 ? ( | |
| <div className="p-8 text-center text-muted-foreground"> | |
| No inquiries found. | |
| </div> | |
| ) : ( | |
| <div className="divide-y"> | |
| {filteredInquiries.map((inquiry) => ( | |
| <div key={inquiry.id} className="grid grid-cols-12 items-center gap-4 p-4 text-sm hover:bg-muted/50 transition-colors"> | |
| <div className="col-span-3"> | |
| <div className="font-medium truncate">{inquiry.name}</div> | |
| <div className="text-xs text-muted-foreground truncate">{inquiry.email}</div> | |
| </div> | |
| <div className="col-span-2 hidden sm:block truncate capitalize"> | |
| {inquiry.projectType || '—'} | |
| </div> | |
| <div className="col-span-2 hidden md:block text-muted-foreground"> | |
| {inquiry.budget || '—'} | |
| </div> | |
| <div className="col-span-2 text-muted-foreground"> | |
| {formatDistanceToNow(new Date(inquiry.createdAt), { addSuffix: true })} | |
| </div> | |
| <div className="col-span-2"> | |
| <Badge variant="secondary" className={`${statusColors[inquiry.status] || ''} hover:bg-opacity-80`}> | |
| {inquiry.status.replace('_', ' ')} | |
| </Badge> | |
| </div> | |
| <div className="col-span-1 text-right"> | |
| <Button variant="ghost" size="sm" asChild> | |
| <Link href={`/admin/inquiries/${inquiry.id}`}> | |
| View | |
| </Link> | |
| </Button> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| ) | |
| } | |