jira-ai-fixer-portal/src/pages/Dashboard.tsx

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>
)
}