adbcc4b9af
feat: implement conversations API with message retrieval and posting feat: add avatar URL handling for users and update user role definitions feat: create chat system tables in the database with initial seed data fix: update user roles from "sales_user" to "sales" for consistency chore: add middleware for system API route access fixed fuckups on john's side, added a benchmark Made Graphs actualy load from database and realtime data, graphs will update and percentages will be mathematically made, and made realtime added chatcart edits, made graphs glow. added in some little small home feeling fuctions added in a slide show, in login with qoutes from creators because i want to leave a print on it saying we made this shit, we built it brick for brick login page added qoutes some random, and made them shuffle from left to right one dissapears other come in adding shape to the textbox for the qoutes Fixed my chat fuckups when it comes to chats
191 lines
6.3 KiB
TypeScript
191 lines
6.3 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import Link from "next/link"
|
|
import { motion } from "framer-motion"
|
|
import { use } from "react"
|
|
import { PageHeader } from "@/components/shared/page-header"
|
|
import { LeadDetailsCard } from "@/components/leads/lead-details-card"
|
|
import { LeadStatusBadge } from "@/components/leads/lead-status-badge"
|
|
import { NoteTimeline } from "@/components/notes/note-timeline"
|
|
import { NoteForm } from "@/components/notes/note-form"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select"
|
|
import { ArrowLeft, Edit, ExternalLink } from "lucide-react"
|
|
import { Lead, Note } from "@/types"
|
|
|
|
export default function LeadDetailsPage({ params }: { params: Promise<{ id: string }> }) {
|
|
const { id } = use(params)
|
|
const [lead, setLead] = useState<Lead | null>(null)
|
|
const [leadNotes, setLeadNotes] = useState<Note[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
async function fetchData() {
|
|
try {
|
|
const [leadRes, notesRes] = await Promise.all([
|
|
fetch(`/api/leads/${id}`),
|
|
fetch(`/api/leads/${id}/notes`),
|
|
])
|
|
if (leadRes.ok) setLead(await leadRes.json())
|
|
if (notesRes.ok) setLeadNotes(await notesRes.json())
|
|
} catch {
|
|
// ignore
|
|
}
|
|
setLoading(false)
|
|
}
|
|
fetchData()
|
|
}, [id])
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center h-[60vh]">
|
|
<p className="text-muted-foreground">Loading...</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!lead) {
|
|
return (
|
|
<div className="flex items-center justify-center h-[60vh]">
|
|
<div className="text-center">
|
|
<h2 className="text-2xl font-bold">Lead not found</h2>
|
|
<p className="mt-2 text-muted-foreground">The lead you are looking for does not exist.</p>
|
|
<Link href="/leads" className="mt-4 inline-block">
|
|
<Button variant="outline">
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
Back to Leads
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<Link
|
|
href="/leads"
|
|
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
Back to leads
|
|
</Link>
|
|
|
|
<PageHeader
|
|
title={
|
|
<div className="flex items-center gap-3">
|
|
<span>{lead.companyName}</span>
|
|
<LeadStatusBadge status={lead.status} />
|
|
</div>
|
|
}
|
|
description={lead.contactName}
|
|
>
|
|
<Select
|
|
value={lead.status}
|
|
onValueChange={(v) => {
|
|
setLead((prev) => prev ? { ...prev, status: v as Lead["status"] } : prev)
|
|
}}
|
|
>
|
|
<SelectTrigger className="h-9 w-[140px]">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="open">Open</SelectItem>
|
|
<SelectItem value="contacted">Contacted</SelectItem>
|
|
<SelectItem value="pending">Pending</SelectItem>
|
|
<SelectItem value="closed">Closed</SelectItem>
|
|
<SelectItem value="ignored">Ignored</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<Button variant="outline" size="sm">
|
|
<Edit className="mr-2 h-4 w-4" />
|
|
Edit
|
|
</Button>
|
|
</PageHeader>
|
|
|
|
<div className="grid gap-6 lg:grid-cols-3">
|
|
<div className="lg:col-span-2 space-y-6">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
<LeadDetailsCard lead={lead} />
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3, delay: 0.1 }}
|
|
>
|
|
<NoteForm leadId={lead.id} />
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3, delay: 0.2 }}
|
|
>
|
|
<NoteTimeline notes={leadNotes} />
|
|
</motion.div>
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
<motion.div
|
|
initial={{ opacity: 0, x: 20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.3, delay: 0.15 }}
|
|
className="rounded-lg border bg-card p-6"
|
|
>
|
|
<h3 className="font-semibold mb-4">Activity</h3>
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-3 text-sm">
|
|
<div className="h-2 w-2 rounded-full bg-blue-500" />
|
|
<div>
|
|
<p className="font-medium">Lead Created</p>
|
|
<p className="text-xs text-muted-foreground">{new Date(lead.createdAt).toLocaleDateString()}</p>
|
|
</div>
|
|
</div>
|
|
{leadNotes.slice(0, 3).map((note) => (
|
|
<div key={note.id} className="flex items-start gap-3 text-sm">
|
|
<div className="h-2 w-2 rounded-full bg-muted-foreground mt-1.5" />
|
|
<div>
|
|
<p className="font-medium">{note.authorName}</p>
|
|
<p className="text-xs text-muted-foreground">{note.note.slice(0, 60)}...</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0, x: 20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.3, delay: 0.25 }}
|
|
className="rounded-lg border bg-card p-6"
|
|
>
|
|
<h3 className="font-semibold mb-4">Quick Actions</h3>
|
|
<div className="space-y-2">
|
|
<Button variant="outline" className="w-full justify-start" size="sm">
|
|
<ExternalLink className="mr-2 h-4 w-4" />
|
|
Send Email
|
|
</Button>
|
|
<Button variant="outline" className="w-full justify-start" size="sm">
|
|
<ExternalLink className="mr-2 h-4 w-4" />
|
|
Call Lead
|
|
</Button>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|