import Link from "next/link" import { notFound } from "next/navigation" import { fetchQuery } from "convex/nextjs" import { ArrowLeft, ExternalLink, 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", }) } function formatDuration(value?: number) { if (!value) { return "—" } const totalSeconds = Math.round(value / 1000) const minutes = Math.floor(totalSeconds / 60) const seconds = totalSeconds % 60 return `${minutes}:${String(seconds).padStart(2, "0")}` } export default async function AdminConversationDetailPage({ params, }: PageProps) { const { id } = await params const detail = await fetchQuery(api.crm.getAdminConversationDetail, { conversationId: id, }) if (!detail) { notFound() } return (
Back to conversations

{detail.conversation.title || "Conversation Detail"}

Unified thread for Rocky-owned conversation management.

Conversation Status Channel, ownership, and sync metadata.

Channel

{detail.conversation.channel}

Status

{detail.conversation.status}

Contact

{detail.contact?.name || "Unlinked"}

Started

{formatTimestamp(detail.conversation.startedAt)}

Last Activity

{formatTimestamp(detail.conversation.lastMessageAt)}

GHL Conversation ID

{detail.conversation.ghlConversationId || "—"}

{detail.conversation.summaryText ? (

Summary

{detail.conversation.summaryText}

) : null}
Recordings & Leads Call artifacts and related lead outcomes for this thread. {detail.recordings.map((recording: any) => (
{recording.recordingStatus || "recording"} {formatDuration(recording.durationMs)}
{recording.recordingUrl ? ( Open recording ) : null} {recording.transcriptionText ? (

{recording.transcriptionText}

) : null}
))} {detail.leads.map((lead: any) => (

{lead.type}

{lead.status}

{lead.message || lead.intent || "—"}

))} {detail.recordings.length === 0 && detail.leads.length === 0 ? (

No recordings or linked leads for this conversation yet.

) : null}
Messages Full backend-owned thread history for this conversation. {detail.messages.length === 0 ? (

No messages have been mirrored into this conversation yet.

) : ( detail.messages.map((message: any) => (
{message.channel} • {message.direction} {formatTimestamp(message.sentAt)}

{message.body}

)) )}
) } export const metadata = { title: "Conversation Detail | Admin", description: "Review a Rocky conversation thread, recordings, and leads", }