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

189 lines
8.5 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">
{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">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 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.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}
</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>
)}
</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",
};