import { useActionQuery } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { IconEye, IconUser, IconPercentage, IconTarget, } from "@tabler/icons-react"; import { LineChart, Line, XAxis, YAxis, ResponsiveContainer, Tooltip as ReTooltip, CartesianGrid, } from "recharts"; import { ViewerAvatar, viewerLabel, } from "@/components/player/recording-views-badge"; import { ViewedByPopover } from "@/components/sharing/viewed-by-popover"; export interface InsightsPanelProps { recordingId: string; durationMs: number; } interface Insights { views: number; uniqueViewers: number; completionRate: number; dropOff: { bucket: number; watching: number }[]; ctaConversionRate: number; topViewers: { viewerEmail: string | null; viewerName: string | null; totalWatchMs: number; completedPct: number; }[]; } export function InsightsPanel({ recordingId, durationMs }: InsightsPanelProps) { const t = useT(); const q = useActionQuery("get-recording-insights", { recordingId }); const vq = useActionQuery<{ viewers: Insights["topViewers"] }>( "list-viewers", { recordingId, limit: 12 }, ); if (q.isLoading) { return (
{t("recordingInsights.loading")}
); } const data = q.data; const viewers = vq.data?.viewers ?? []; if (!data) { return (
{t("recordingInsights.noData")}
); } return (
{data.views > 0 ? ( } label={t("recordingInsights.views")} value={data.views} /> ) : ( } label={t("recordingInsights.views")} value={data.views} /> )} } label={t("recordingInsights.uniqueViewers")} value={data.uniqueViewers} /> } label={t("recordingInsights.completion")} value={`${Math.round(data.completionRate)}%`} /> } label={t("recordingInsights.ctaConversion")} value={`${Math.round(data.ctaConversionRate)}%`} />
{t("recordingInsights.dropOff")}
{ const pct = (b as number) / 100; const ms = pct * durationMs; return msCompact(ms); }} stroke="#6b7280" fontSize={10} /> [ t("recordingInsights.viewersCount", { count: Number(v) }), t("recordingInsights.watching"), ]} labelFormatter={(b) => msCompact(((b as number) / 100) * durationMs) } />
{t("recordingInsights.recentViewers")}
{viewers.length === 0 ? (

{t("recordingInsights.noViewers")}

) : (
{viewers.slice(0, 12).map((v, i) => (
{viewerLabel(v, t("recordingInsights.anonymous"))} {Math.round(v.completedPct)}%
))} {viewers.length > 12 ? ( {t("recordingInsights.moreViewers", { count: viewers.length - 12, })} ) : null}
)}
); } function Stat({ icon, label, value, }: { icon: React.ReactNode; label: string; value: string | number; }) { return (
{icon} {label}
{value}
); } function msCompact(ms: number): string { const s = Math.floor(ms / 1000); const m = Math.floor(s / 60); return `${m}:${String(s % 60).padStart(2, "0")}`; }