Frontend: - Add Synthesis type with action plan, insights, annotations - ExecutiveSummary: Accept synthesis prop for AI narrative - SentimentPie: Accept insight prop for contextual explanation - IntensityHeatmap: Accept insight + highlightDomain props - TimelineChart: Accept insight + annotations props - All components gracefully degrade when synthesis is null Backend: - Add Stage 4: Synthesize for generating AI narratives - Gathers context from classified spans - Generates executive narrative, section insights, action plan - Produces timeline annotations and marketing angles - Stores synthesis in pipeline.executions table Components show AI insights with purple gradient styling when available, fall back to existing behavior when synthesis is not yet generated. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
64 lines
1.5 KiB
TypeScript
64 lines
1.5 KiB
TypeScript
'use client';
|
|
|
|
import { LucideIcon } from 'lucide-react';
|
|
|
|
interface KPICardProps {
|
|
title: string;
|
|
value: string | number;
|
|
subtitle?: string;
|
|
icon: LucideIcon;
|
|
colorClass: string;
|
|
onClick?: () => void;
|
|
isActive?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Clickable KPI card component for the dashboard.
|
|
*/
|
|
export function KPICard({
|
|
title,
|
|
value,
|
|
subtitle,
|
|
icon: Icon,
|
|
colorClass,
|
|
onClick,
|
|
isActive = false,
|
|
}: KPICardProps) {
|
|
const baseClasses = `
|
|
rounded-xl p-4 shadow-md hover:shadow-lg transition-all cursor-pointer
|
|
border-2 ${colorClass}
|
|
`;
|
|
|
|
const activeClasses = isActive
|
|
? 'ring-2 ring-offset-2 ring-blue-500 scale-[1.02]'
|
|
: '';
|
|
|
|
return (
|
|
<div
|
|
className={`${baseClasses} ${activeClasses}`}
|
|
onClick={onClick}
|
|
role="button"
|
|
tabIndex={0}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
onClick?.();
|
|
}
|
|
}}
|
|
>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center gap-2">
|
|
<Icon className="w-5 h-5" />
|
|
<span className="text-sm font-bold">{title}</span>
|
|
</div>
|
|
{isActive && (
|
|
<span className="px-2 py-0.5 bg-blue-600 text-white text-[10px] font-bold rounded-full">
|
|
ACTIVE
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="text-3xl font-bold">{value}</div>
|
|
{subtitle && <div className="text-xs mt-1 font-medium opacity-80">{subtitle}</div>}
|
|
</div>
|
|
);
|
|
}
|