138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
import { issuesApi } from '../services/api'
|
|
import { Link } from 'react-router-dom'
|
|
|
|
export default function Dashboard() {
|
|
const { data: stats } = useQuery({
|
|
queryKey: ['stats'],
|
|
queryFn: issuesApi.getStats,
|
|
refetchInterval: 10000,
|
|
})
|
|
|
|
const { data: issues } = useQuery({
|
|
queryKey: ['issues'],
|
|
queryFn: () => issuesApi.list(),
|
|
refetchInterval: 10000,
|
|
})
|
|
|
|
const recentIssues = issues?.slice(0, 5) || []
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<h1 className="text-2xl font-bold mb-6">Dashboard</h1>
|
|
|
|
{/* Stats Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
|
<StatCard
|
|
title="Total Issues"
|
|
value={stats?.total || 0}
|
|
icon="📋"
|
|
color="blue"
|
|
/>
|
|
<StatCard
|
|
title="Analyzed"
|
|
value={stats?.analyzed || 0}
|
|
icon="✅"
|
|
color="green"
|
|
/>
|
|
<StatCard
|
|
title="PRs Created"
|
|
value={stats?.prs_created || 0}
|
|
icon="🔀"
|
|
color="purple"
|
|
/>
|
|
<StatCard
|
|
title="Avg Confidence"
|
|
value={`${stats?.avg_confidence || 0}%`}
|
|
icon="🎯"
|
|
color="yellow"
|
|
/>
|
|
</div>
|
|
|
|
{/* Recent Issues */}
|
|
<div className="bg-gray-800 rounded-xl border border-gray-700">
|
|
<div className="p-4 border-b border-gray-700 flex justify-between items-center">
|
|
<h2 className="font-semibold">Recent Issues</h2>
|
|
<Link to="/issues" className="text-sm text-blue-400 hover:text-blue-300">
|
|
View all →
|
|
</Link>
|
|
</div>
|
|
<div className="divide-y divide-gray-700">
|
|
{recentIssues.length === 0 ? (
|
|
<div className="p-8 text-center text-gray-500">No issues yet</div>
|
|
) : (
|
|
recentIssues.map(issue => (
|
|
<Link
|
|
key={issue.id}
|
|
to={`/issues/${issue.id}`}
|
|
className="p-4 block hover:bg-gray-700/50 transition-colors"
|
|
>
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-mono text-blue-400 text-sm">
|
|
{issue.external_key || `#${issue.id}`}
|
|
</span>
|
|
<StatusBadge status={issue.status} />
|
|
</div>
|
|
<h3 className="font-medium mt-1">{issue.title}</h3>
|
|
</div>
|
|
{issue.confidence && (
|
|
<span className="text-sm text-green-400">
|
|
{Math.round(issue.confidence * 100)}%
|
|
</span>
|
|
)}
|
|
</div>
|
|
</Link>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function StatCard({ title, value, icon, color }: {
|
|
title: string
|
|
value: number | string
|
|
icon: string
|
|
color: 'blue' | 'green' | 'purple' | 'yellow'
|
|
}) {
|
|
const colors = {
|
|
blue: 'bg-blue-500/20 text-blue-400',
|
|
green: 'bg-green-500/20 text-green-400',
|
|
purple: 'bg-purple-500/20 text-purple-400',
|
|
yellow: 'bg-yellow-500/20 text-yellow-400',
|
|
}
|
|
|
|
return (
|
|
<div className="bg-gray-800 rounded-xl p-6 border border-gray-700">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-gray-400 text-sm">{title}</p>
|
|
<p className={`text-3xl font-bold mt-1 ${colors[color].split(' ')[1]}`}>
|
|
{value}
|
|
</p>
|
|
</div>
|
|
<div className={`w-12 h-12 rounded-lg flex items-center justify-center ${colors[color].split(' ')[0]}`}>
|
|
<span className="text-2xl">{icon}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function StatusBadge({ status }: { status: string }) {
|
|
const styles = {
|
|
analyzed: 'bg-green-500/20 text-green-400',
|
|
pending: 'bg-yellow-500/20 text-yellow-400',
|
|
error: 'bg-red-500/20 text-red-400',
|
|
}
|
|
|
|
return (
|
|
<span className={`px-2 py-0.5 rounded text-xs ${styles[status as keyof typeof styles] || 'bg-gray-500/20 text-gray-400'}`}>
|
|
{status}
|
|
</span>
|
|
)
|
|
}
|