modcomms/frontend/components/Analytics.tsx
michael c07c66a583 Connect frontend to PostgreSQL database via API
- Replace all localStorage-based state management with API calls
- Load campaigns, proofs, and audit items from database
- Persist proof analysis results to database via WebSocket
- Add dropdown options CRUD API endpoints (channels, sub-channels, proof types)
- Create DropdownRepository for managing dropdown options
- Update Analytics component to fetch data from API
- Remove demo data and localStorage persistence code

Frontend changes:
- App.tsx: Initialize apiService with MSAL, use API for all CRUD operations
- apiService.ts: Add dropdown options API methods
- Analytics.tsx: Fetch stats from /api/analytics

Backend changes:
- New dropdown_repository.py for dropdown CRUD
- routes.py: Add 7 dropdown endpoints

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 13:50:37 -06:00

141 lines
No EOL
8.3 KiB
TypeScript
Executable file

import React, { useEffect, useState } from 'react';
import { UploadIcon } from './icons/UploadIcon';
import { TrendingUpIcon } from './icons/TrendingUpIcon';
import { BugIcon } from './icons/BugIcon';
import { ClockIcon } from './icons/ClockIcon';
import { LightbulbIcon } from './icons/LightbulbIcon';
import apiService, { AnalyticsResponse } from '../services/apiService';
// Agent performance is still static for now - would need separate API
const agentPerformance = [
{ name: 'Legal Agent', passRate: 85, avgIssues: 1.2, trend: 'up' },
{ name: 'Brand Agent', passRate: 68, avgIssues: 2.5, trend: 'down' },
{ name: 'Tone Agent', passRate: 92, avgIssues: 0.8, trend: 'up' },
{ name: 'Channel Agent', passRate: 71, avgIssues: 1.9, trend: 'stable' },
];
const UpArrow = () => <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={3} stroke="currentColor" className="h-4 w-4"><path strokeLinecap="round" strokeLinejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5" /></svg>;
const DownArrow = () => <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={3} stroke="currentColor" className="h-4 w-4"><path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" /></svg>;
const StableLine = () => <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={3} stroke="currentColor" className="h-4 w-4"><path strokeLinecap="round" strokeLinejoin="round" d="M3.75 12h16.5" /></svg>;
const TrendIndicator: React.FC<{ trend: 'up' | 'down' | 'stable' }> = ({ trend }) => {
if (trend === 'up') {
return <div className="flex items-center gap-1.5 text-green-600"><UpArrow/> Improving</div>;
}
if (trend === 'down') {
return <div className="flex items-center gap-1.5 text-red-600"><DownArrow/> Declining</div>;
}
return <div className="flex items-center gap-1.5 text-gray-500"><StableLine/> Stable</div>;
};
export const Analytics: React.FC = () => {
const [analytics, setAnalytics] = useState<AnalyticsResponse | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const loadAnalytics = async () => {
try {
const data = await apiService.getAnalytics();
setAnalytics(data);
} catch (error) {
console.error('Failed to load analytics:', error);
} finally {
setIsLoading(false);
}
};
loadAnalytics();
}, []);
// Calculate stats from API data
const passRate = analytics && analytics.total_reviews > 0
? Math.round((analytics.passed / analytics.total_reviews) * 100)
: 0;
const stats = [
{ name: 'Proofs Reviewed', value: analytics?.total_reviews?.toString() || '0', icon: UploadIcon },
{ name: 'Pass Rate', value: `${passRate}%`, icon: TrendingUpIcon },
{ name: 'Failed Reviews', value: analytics?.failed?.toString() || '0', icon: BugIcon },
{ name: 'Legal Review Required', value: analytics?.legal_review?.toString() || '0', icon: ClockIcon },
];
return (
<div className="p-4 sm:p-6 lg:p-8 h-full bg-brand-gray">
<header className="mb-8">
<h1 className="text-3xl lg:text-4xl font-bold text-brand-dark-blue">Performance Analytics</h1>
<p className="text-base lg:text-lg text-gray-600 mt-1">Overall usage and performance statistics for the tool.</p>
</header>
{/* Stats Cards */}
<section>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{stats.map((stat) => {
const Icon = stat.icon;
return (
<div key={stat.name} className="bg-white rounded-lg shadow-md p-6 flex items-start border border-gray-200 transition-all hover:shadow-xl hover:border-brand-accent">
<div className="p-3 rounded-full bg-brand-light-blue/20 text-brand-accent mr-4 flex-shrink-0">
<Icon className="h-7 w-7" />
</div>
<div>
<p className="text-sm font-medium text-gray-500">{stat.name}</p>
<p className="text-3xl font-bold text-brand-dark-blue mt-1">{stat.value}</p>
</div>
</div>
);
})}
</div>
</section>
{/* AI Performance Summary */}
<section className="mt-10">
<h2 className="text-2xl font-bold text-brand-dark-blue mb-4">AI Performance Summary</h2>
<div className="bg-blue-50 border-l-4 border-brand-accent text-brand-dark-blue p-6 rounded-r-lg shadow-md flex items-start gap-4">
<div className="flex-shrink-0">
<LightbulbIcon className="h-7 w-7 text-brand-accent" />
</div>
<div>
<p className="font-semibold">Key Insight (Last 7 Days):</p>
<p className="mt-1">
A sharp decline in Best Practice adherence has been noted, primarily driven by proofs from the <strong>Barclays Q4 Social</strong> campaign. The Brand Guardian agent also shows a declining performance trend, suggesting a potential need for updated brand guideline training or proof review.
</p>
</div>
</div>
</section>
{/* Agent Performance Table */}
<section className="mt-10">
<h2 className="text-2xl font-bold text-brand-dark-blue mb-4">Agent Performance (Last 7 Days)</h2>
<div className="bg-white rounded-lg shadow-md overflow-hidden border border-gray-200">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th scope="col" className="px-6 py-3 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Agent Name</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Pass Rate</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Avg. Issues per Proof</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Performance Trend</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{agentPerformance.map((agent) => (
<tr key={agent.name} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-brand-dark-blue">{agent.name}</td>
<td className={`px-6 py-4 whitespace-nowrap text-sm font-semibold`}>
<div className="flex items-center">
<span className={`h-2.5 w-2.5 rounded-full mr-3 ${agent.passRate >= 80 ? 'bg-green-500' : agent.passRate < 70 ? 'bg-red-500' : 'bg-yellow-500'}`}></span>
{agent.passRate}%
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">{agent.avgIssues}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<TrendIndicator trend={agent.trend as 'up' | 'down' | 'stable'} />
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</section>
</div>
);
};