feat: initial frontend - React + TypeScript + Vite

- Login page with JWT auth
- Dashboard with stats and charts
- Issues list with pagination, filtering
- Issue detail with comments, status transitions
- Create issue form
- Dark theme UI
- Docker (nginx) with API proxy to backend
- Sidebar navigation
This commit is contained in:
Zhi
2026-02-27 09:47:19 +00:00
parent 32557f1de2
commit 853594f447
20 changed files with 831 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
import { useState, useEffect } from 'react'
import api from '@/services/api'
import type { DashboardStats } from '@/types'
export default function DashboardPage() {
const [stats, setStats] = useState<DashboardStats | null>(null)
useEffect(() => {
api.get<DashboardStats>('/dashboard/stats').then(({ data }) => setStats(data))
}, [])
if (!stats) return <div className="loading">...</div>
const statusColors: Record<string, string> = {
open: '#3b82f6', in_progress: '#f59e0b', resolved: '#10b981',
closed: '#6b7280', blocked: '#ef4444',
}
const priorityColors: Record<string, string> = {
low: '#6b7280', medium: '#3b82f6', high: '#f59e0b', critical: '#ef4444',
}
return (
<div className="dashboard">
<h2>📊 </h2>
<div className="stats-grid">
<div className="stat-card total">
<span className="stat-number">{stats.total_issues}</span>
<span className="stat-label"> Issues</span>
</div>
{Object.entries(stats.by_status).map(([k, v]) => (
<div className="stat-card" key={k} style={{ borderLeftColor: statusColors[k] || '#ccc' }}>
<span className="stat-number">{v}</span>
<span className="stat-label">{k}</span>
</div>
))}
</div>
<div className="section">
<h3></h3>
<div className="bar-chart">
{Object.entries(stats.by_priority).map(([k, v]) => (
<div className="bar-row" key={k}>
<span className="bar-label">{k}</span>
<div className="bar" style={{
width: `${Math.max((v / stats.total_issues) * 100, 5)}%`,
backgroundColor: priorityColors[k] || '#ccc',
}}>{v}</div>
</div>
))}
</div>
</div>
<div className="section">
<h3> Issues</h3>
<table>
<thead>
<tr><th>ID</th><th></th><th></th><th></th><th></th></tr>
</thead>
<tbody>
{stats.recent_issues.map((i) => (
<tr key={i.id}>
<td>#{i.id}</td>
<td><a href={`/issues/${i.id}`}>{i.title}</a></td>
<td><span className={`badge status-${i.status}`}>{i.status}</span></td>
<td><span className={`badge priority-${i.priority}`}>{i.priority}</span></td>
<td>{i.issue_type}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}