Files
SPARC/frontend/src/pages/Analysis.tsx
T
agent-company 1bd9dccdb8 feat: add CSV export for company analysis results
Add GET /export/{company_name} backend endpoint that returns analysis
records as a downloadable CSV file. Add Export CSV button to the
Analysis page that triggers the download via the API.

Closes leeworks-agents/SPARC#20

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:20:51 +00:00

145 lines
5.6 KiB
TypeScript

import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';
import { analysisApi, exportApi } from '../api/client';
import { Search, CheckCircle, AlertCircle, Clock, FileText, Download } from 'lucide-react';
import type { CompanyAnalysis } from '../types';
export function Analysis() {
const [companyName, setCompanyName] = useState('');
const [result, setResult] = useState<CompanyAnalysis | null>(null);
const mutation = useMutation({
mutationFn: (name: string) => analysisApi.analyzeCompany(name),
onSuccess: (data) => setResult(data),
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (companyName.trim()) {
mutation.mutate(companyName.trim());
}
};
return (
<div className="space-y-6">
{/* Header */}
<div>
<h2 className="text-xl font-semibold text-text-primary border-b-2 border-primary/30 pb-2 mb-2">
Single Company Analysis
</h2>
<p className="text-text-secondary">
Analyze a company's patent portfolio using AI-powered insights.
</p>
</div>
{/* Search Form */}
<form onSubmit={handleSubmit} className="flex gap-4">
<div className="flex-1 relative">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 text-text-secondary" size={18} />
<input
type="text"
value={companyName}
onChange={(e) => setCompanyName(e.target.value)}
placeholder="Enter company name (e.g., nvidia, intel, amd)"
className="w-full bg-bg-card/80 border border-primary/30 rounded-xl pl-12 pr-4 py-3 text-text-primary placeholder-text-secondary/50 focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all"
/>
</div>
<button
type="submit"
disabled={mutation.isPending || !companyName.trim()}
className="bg-gradient-to-r from-primary to-primary-dark text-white font-semibold py-3 px-6 rounded-xl hover:shadow-lg hover:shadow-primary/30 transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
{mutation.isPending ? (
<div className="animate-spin rounded-full h-5 w-5 border-t-2 border-b-2 border-white"></div>
) : (
<>
<Search size={18} />
Analyze
</>
)}
</button>
</form>
{/* Error */}
{mutation.isError && (
<div className="flex items-center gap-2 bg-error/10 border border-error/20 text-error rounded-xl px-4 py-3">
<AlertCircle size={18} />
<span>Analysis failed. Please try again.</span>
</div>
)}
{/* Results */}
{result && (
<div className="space-y-6">
{/* Success/Failure Status */}
{result.success ? (
<div className="flex items-center gap-2 bg-success/10 border border-success/20 text-success rounded-xl px-4 py-3">
<CheckCircle size={18} />
<span>Analysis complete for {result.company_name.toUpperCase()}</span>
</div>
) : (
<div className="flex items-center gap-2 bg-error/10 border border-error/20 text-error rounded-xl px-4 py-3">
<AlertCircle size={18} />
<span>Analysis failed: {result.error}</span>
</div>
)}
{/* Metrics */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<MetricCard
icon={FileText}
label="Patents Found"
value={result.patent_count.toString()}
/>
<MetricCard
icon={CheckCircle}
label="Analysis Status"
value={result.success ? 'Complete' : 'Failed'}
/>
<MetricCard
icon={Clock}
label="Timestamp"
value={new Date(result.timestamp).toLocaleTimeString()}
/>
</div>
{/* Analysis Content */}
{result.success && result.analysis && (
<div className="bg-bg-card/60 backdrop-blur-lg border border-primary/15 rounded-2xl p-6">
<div className="flex items-center justify-between border-b-2 border-primary/30 pb-2 mb-4">
<h3 className="text-lg font-semibold text-text-primary">
AI Analysis Results
</h3>
<button
onClick={() => exportApi.exportCsv(result.company_name)}
className="flex items-center gap-2 text-sm bg-primary/20 hover:bg-primary/30 text-primary font-medium px-3 py-1.5 rounded-lg transition-colors"
>
<Download size={14} />
Export CSV
</button>
</div>
<div className="prose prose-invert max-w-none">
<div className="text-text-primary whitespace-pre-wrap leading-relaxed">
{result.analysis}
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
function MetricCard({ icon: Icon, label, value }: { icon: typeof FileText; label: string; value: string }) {
return (
<div className="bg-gradient-to-br from-primary/10 to-secondary/10 border border-primary/20 rounded-xl p-5 text-center">
<Icon className="mx-auto mb-2 text-primary" size={24} />
<div className="text-2xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
{value}
</div>
<div className="text-sm text-text-secondary uppercase tracking-wide mt-1">{label}</div>
</div>
);
}