Rocky_Mountain_Vending/app/admin/contacts/[id]/page.tsx

201 lines
7 KiB
TypeScript

import Link from "next/link"
import { notFound } from "next/navigation"
import { fetchQuery } from "convex/nextjs"
import { ArrowLeft, ContactRound, MessageSquare } from "lucide-react"
import { api } from "@/convex/_generated/api"
import { Badge } from "@/components/ui/badge"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
type PageProps = {
params: Promise<{
id: string
}>
}
function formatTimestamp(value?: number) {
if (!value) {
return "—"
}
return new Date(value).toLocaleString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
})
}
export default async function AdminContactDetailPage({ params }: PageProps) {
const { id } = await params
const detail = await fetchQuery(api.crm.getAdminContactDetail, {
contactId: id,
})
if (!detail) {
notFound()
}
return (
<div className="container mx-auto px-4 py-8">
<div className="space-y-8">
<div className="space-y-2">
<Link
href="/admin/contacts"
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="h-4 w-4" />
Back to contacts
</Link>
<h1 className="text-4xl font-bold tracking-tight text-balance">
{detail.contact.firstName} {detail.contact.lastName}
</h1>
<p className="text-muted-foreground">
Unified CRM record across forms, calls, SMS, and sync imports.
</p>
</div>
<div className="grid gap-6 lg:grid-cols-[0.95fr_1.05fr]">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<ContactRound className="h-5 w-5" />
Contact Profile
</CardTitle>
<CardDescription>
Backend-owned identity and sync metadata.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-4 md:grid-cols-2">
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Email
</p>
<p className="font-medium break-all">
{detail.contact.email || "—"}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Phone
</p>
<p className="font-medium">{detail.contact.phone || "—"}</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Company
</p>
<p className="font-medium">{detail.contact.company || "—"}</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Status
</p>
<Badge className="mt-1" variant="secondary">
{detail.contact.status}
</Badge>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
GHL Contact ID
</p>
<p className="font-medium break-all">
{detail.contact.ghlContactId || "—"}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Last Activity
</p>
<p className="font-medium">
{formatTimestamp(detail.contact.lastActivityAt)}
</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<MessageSquare className="h-5 w-5" />
Conversations
</CardTitle>
<CardDescription>
Every mirrored conversation associated to this contact.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{detail.conversations.length === 0 ? (
<p className="text-sm text-muted-foreground">
No conversations are linked to this contact yet.
</p>
) : (
detail.conversations.map((conversation: any) => (
<div key={conversation.id} className="rounded-lg border p-3">
<div className="flex items-center justify-between gap-3">
<div>
<p className="font-medium">
{conversation.title || "Untitled conversation"}
</p>
<p className="text-xs text-muted-foreground">
{conversation.channel} {" "}
{formatTimestamp(conversation.lastMessageAt)}
</p>
</div>
<Link href={`/admin/conversations/${conversation.id}`}>
<Badge variant="outline">{conversation.status}</Badge>
</Link>
</div>
<p className="mt-2 text-sm text-muted-foreground">
{conversation.lastMessagePreview || "No preview yet"}
</p>
</div>
))
)}
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle>Timeline</CardTitle>
<CardDescription>
Calls, messages, recordings, and lead events in one stream.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{detail.timeline.length === 0 ? (
<p className="text-sm text-muted-foreground">
No timeline activity for this contact yet.
</p>
) : (
detail.timeline.map((item: any) => (
<div key={`${item.type}-${item.id}`} className="rounded-lg border p-3">
<div className="flex items-center justify-between gap-3 text-xs text-muted-foreground">
<span className="uppercase tracking-wide">{item.type}</span>
<span>{formatTimestamp(item.timestamp)}</span>
</div>
<p className="mt-1 font-medium">{item.title || "Untitled"}</p>
<p className="mt-1 text-sm text-muted-foreground whitespace-pre-wrap">
{item.body || "—"}
</p>
</div>
))
)}
</CardContent>
</Card>
</div>
</div>
)
}
export const metadata = {
title: "Contact Detail | Admin",
description: "Review a Rocky CRM contact and full interaction timeline",
}