Files
HarborForge.Frontend/src/pages/IssuesPage.tsx
Zhi 853594f447 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
2026-02-27 09:47:19 +00:00

79 lines
3.0 KiB
TypeScript

import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import api from '@/services/api'
import type { Issue, PaginatedResponse } from '@/types'
export default function IssuesPage() {
const [issues, setIssues] = useState<Issue[]>([])
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [totalPages, setTotalPages] = useState(1)
const [statusFilter, setStatusFilter] = useState('')
const [priorityFilter, setPriorityFilter] = useState('')
const navigate = useNavigate()
const fetchIssues = () => {
const params = new URLSearchParams({ page: String(page), page_size: '20' })
if (statusFilter) params.set('issue_status', statusFilter)
api.get<PaginatedResponse<Issue>>(`/issues?${params}`).then(({ data }) => {
setIssues(data.items)
setTotal(data.total)
setTotalPages(data.total_pages)
})
}
useEffect(() => { fetchIssues() }, [page, statusFilter, priorityFilter])
const statusColors: Record<string, string> = {
open: '#3b82f6', in_progress: '#f59e0b', resolved: '#10b981',
closed: '#6b7280', blocked: '#ef4444',
}
return (
<div className="issues-page">
<div className="page-header">
<h2>📋 Issues ({total})</h2>
<button className="btn-primary" onClick={() => navigate('/issues/new')}>+ Issue</button>
</div>
<div className="filters">
<select value={statusFilter} onChange={(e) => { setStatusFilter(e.target.value); setPage(1) }}>
<option value=""></option>
<option value="open">Open</option>
<option value="in_progress">In Progress</option>
<option value="resolved">Resolved</option>
<option value="closed">Closed</option>
<option value="blocked">Blocked</option>
</select>
</div>
<table className="issues-table">
<thead>
<tr><th>#</th><th></th><th></th><th></th><th></th><th></th><th></th></tr>
</thead>
<tbody>
{issues.map((i) => (
<tr key={i.id} onClick={() => navigate(`/issues/${i.id}`)} className="clickable">
<td>{i.id}</td>
<td className="issue-title">{i.title}</td>
<td><span className="badge" style={{ backgroundColor: statusColors[i.status] || '#ccc' }}>{i.status}</span></td>
<td><span className={`badge priority-${i.priority}`}>{i.priority}</span></td>
<td>{i.issue_type}</td>
<td>{i.tags || '-'}</td>
<td>{new Date(i.created_at).toLocaleDateString()}</td>
</tr>
))}
</tbody>
</table>
{totalPages > 1 && (
<div className="pagination">
<button disabled={page <= 1} onClick={() => setPage(page - 1)}></button>
<span>{page} / {totalPages}</span>
<button disabled={page >= totalPages} onClick={() => setPage(page + 1)}></button>
</div>
)}
</div>
)
}