feat(reviewiq): Add AI synthesis support to dashboard components
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>
This commit is contained in:
63
web/components/reviewiq/kpi/KPICard.tsx
Normal file
63
web/components/reviewiq/kpi/KPICard.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user