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

360 lines
13 KiB
TypeScript

import Link from "next/link"
import { notFound } from "next/navigation"
import { fetchQuery } from "convex/nextjs"
import { ArrowLeft, ExternalLink, Phone } from "lucide-react"
import { api } from "@/convex/_generated/api"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
formatPhoneCallDuration,
formatPhoneCallTimestamp,
normalizePhoneFromIdentity,
} from "@/lib/phone-calls"
type PageProps = {
params: Promise<{
id: string
}>
}
export default async function AdminCallDetailPage({ params }: PageProps) {
const { id } = await params
const detail = await fetchQuery(api.voiceSessions.getAdminPhoneCallDetail, {
callId: id,
})
if (!detail) {
notFound()
}
return (
<div className="container mx-auto px-4 py-8">
<div className="space-y-8">
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div className="space-y-2">
<Link
href="/admin/calls"
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="h-4 w-4" />
Back to calls
</Link>
<h1 className="text-4xl font-bold tracking-tight text-balance">
Phone Call Detail
</h1>
<p className="text-muted-foreground">
{detail.call.contactDisplayName ||
normalizePhoneFromIdentity(detail.call.participantIdentity) ||
detail.call.participantIdentity}
</p>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-[1.1fr_0.9fr]">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Phone className="h-5 w-5" />
Call Status
</CardTitle>
<CardDescription>
Operational detail for this direct phone session.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-4 md:grid-cols-2">
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Started
</p>
<p className="font-medium">
{formatPhoneCallTimestamp(detail.call.startedAt)}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Room
</p>
<p className="font-medium break-all">{detail.call.roomName}</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Duration
</p>
<p className="font-medium">
{formatPhoneCallDuration(detail.call.durationMs)}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Participant Identity
</p>
<p className="font-medium break-all">
{detail.call.participantIdentity || "Unknown"}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Caller Phone
</p>
<p className="font-medium">
{detail.call.callerPhone ||
normalizePhoneFromIdentity(detail.call.participantIdentity) ||
"Unknown"}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Company
</p>
<p className="font-medium">{detail.call.contactCompany || "—"}</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Call Status
</p>
<Badge
className="mt-1"
variant={
detail.call.callStatus === "failed"
? "destructive"
: detail.call.callStatus === "started"
? "secondary"
: "default"
}
>
{detail.call.callStatus}
</Badge>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Jessica Answered
</p>
<p className="font-medium">
{detail.call.answered ? "Yes" : "No"}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Lead Outcome
</p>
<p className="font-medium">{detail.call.leadOutcome}</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Email Summary
</p>
<p className="font-medium">{detail.call.notificationStatus}</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Reminder
</p>
<p className="font-medium">
{detail.call.reminderStatus || "none"}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Warm Transfer
</p>
<p className="font-medium">
{detail.call.warmTransferStatus || "none"}
</p>
</div>
<div className="md:col-span-2">
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Summary
</p>
<p className="text-sm whitespace-pre-wrap">
{detail.call.summaryText || "No summary available yet."}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Recording Status
</p>
<p className="font-medium">
{detail.call.recordingStatus || "Unavailable"}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Transcript Turns
</p>
<p className="font-medium">{detail.call.transcriptTurnCount}</p>
</div>
{detail.call.reminderStartAt ? (
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Reminder Time
</p>
<p className="font-medium">
{formatPhoneCallTimestamp(detail.call.reminderStartAt)}
</p>
</div>
) : null}
{detail.call.warmTransferFailureReason ? (
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Transfer Detail
</p>
<p className="font-medium">
{detail.call.warmTransferFailureReason}
</p>
</div>
) : null}
{detail.call.recordingUrl ? (
<div className="md:col-span-2">
<Link
href={detail.call.recordingUrl}
target="_blank"
className="inline-flex items-center gap-2 text-sm text-primary hover:underline"
>
Open recording
<ExternalLink className="h-4 w-4" />
</Link>
</div>
) : null}
{detail.call.notificationError ? (
<div className="md:col-span-2">
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Email Error
</p>
<p className="text-sm text-destructive">
{detail.call.notificationError}
</p>
</div>
) : null}
{detail.call.reminderCalendarHtmlLink ? (
<div className="md:col-span-2">
<Link
href={detail.call.reminderCalendarHtmlLink}
target="_blank"
className="inline-flex items-center gap-2 text-sm text-primary hover:underline"
>
Open reminder
<ExternalLink className="h-4 w-4" />
</Link>
</div>
) : null}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Linked Lead</CardTitle>
<CardDescription>
{detail.linkedLead
? "Lead created from this phone call."
: "No lead was created from this call."}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{detail.linkedLead ? (
<>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Contact
</p>
<p className="font-medium">
{detail.linkedLead.firstName} {detail.linkedLead.lastName}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Lead Type
</p>
<p className="font-medium">{detail.linkedLead.type}</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Email
</p>
<p className="font-medium break-all">
{detail.linkedLead.email}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Phone
</p>
<p className="font-medium">{detail.linkedLead.phone}</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Message
</p>
<p className="text-sm whitespace-pre-wrap">
{detail.linkedLead.message || "—"}
</p>
</div>
</>
) : (
<p className="text-sm text-muted-foreground">
Jessica handled the call, but it did not result in a submitted
lead.
</p>
)}
{detail.contactProfile ? (
<div className="border-t pt-3">
<p className="text-xs uppercase tracking-wide text-muted-foreground">
Contact Profile
</p>
<p className="font-medium">
{detail.contactProfile.displayName ||
[detail.contactProfile.firstName, detail.contactProfile.lastName]
.filter(Boolean)
.join(" ") ||
"Known caller"}
</p>
<p className="text-sm text-muted-foreground">
{detail.contactProfile.company || detail.contactProfile.email || "No company or email yet"}
</p>
</div>
) : null}
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle>Transcript</CardTitle>
<CardDescription>
Complete mirrored transcript for this phone call.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{detail.turns.length === 0 ? (
<p className="text-sm text-muted-foreground">
No transcript turns were captured for this call.
</p>
) : (
detail.turns.map((turn: any) => (
<div key={turn.id} className="rounded-lg border p-3">
<div className="mb-1 flex items-center justify-between gap-3 text-xs text-muted-foreground">
<span className="uppercase tracking-wide">{turn.role}</span>
<span>{formatPhoneCallTimestamp(turn.createdAt)}</span>
</div>
<p className="whitespace-pre-wrap text-sm">{turn.text}</p>
</div>
))
)}
</CardContent>
</Card>
</div>
</div>
)
}
export const metadata = {
title: "Phone Call Detail | Admin",
description:
"Review a mirrored direct phone call transcript and linked lead details",
}