All checks were successful
Build and Deploy Log Center / build-and-deploy (push) Successful in 1m20s
164 lines
6.3 KiB
TypeScript
164 lines
6.3 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { Link } from 'react-router-dom';
|
||
import { getRepairReports, type RepairReport, getProjects } from '../api';
|
||
|
||
const STATUS_LABELS: Record<string, string> = {
|
||
NEW: '新发现',
|
||
VERIFYING: '验证中',
|
||
CANNOT_REPRODUCE: '无法复现',
|
||
PENDING_FIX: '待修复',
|
||
FIXING: '修复中',
|
||
FIXED: '已修复',
|
||
VERIFIED: '已验证',
|
||
DEPLOYED: '已部署',
|
||
FIX_FAILED: '修复失败',
|
||
};
|
||
|
||
export default function RepairList() {
|
||
const [reports, setReports] = useState<RepairReport[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [projects, setProjects] = useState<string[]>([]);
|
||
const [filters, setFilters] = useState({
|
||
project_id: '',
|
||
page: 1,
|
||
});
|
||
const [totalPages, setTotalPages] = useState(1);
|
||
|
||
useEffect(() => {
|
||
const fetchProjects = async () => {
|
||
try {
|
||
const res = await getProjects();
|
||
setProjects(res.data.projects);
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
};
|
||
fetchProjects();
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
const fetchReports = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await getRepairReports({
|
||
page: filters.page,
|
||
project_id: filters.project_id || undefined,
|
||
});
|
||
setReports(res.data.items);
|
||
setTotalPages(res.data.total_pages);
|
||
} catch (err) {
|
||
console.error(err);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
fetchReports();
|
||
}, [filters]);
|
||
|
||
const handleFilterChange = (key: string, value: string) => {
|
||
setFilters((prev) => ({ ...prev, [key]: value, page: 1 }));
|
||
};
|
||
|
||
return (
|
||
<div>
|
||
<div className="page-header">
|
||
<div className="title-row">
|
||
<div>
|
||
<h1 className="page-title">修复报告</h1>
|
||
<p className="page-subtitle">AI 自动修复记录及结果</p>
|
||
</div>
|
||
<div className="filters">
|
||
<select
|
||
className="filter-select"
|
||
value={filters.project_id}
|
||
onChange={(e) => handleFilterChange('project_id', e.target.value)}
|
||
>
|
||
<option value="">全部项目</option>
|
||
{projects.map((p) => (
|
||
<option key={p} value={p}>{p}</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="table-container">
|
||
{loading ? (
|
||
<div className="loading">
|
||
<div className="spinner"></div>
|
||
</div>
|
||
) : reports.length === 0 ? (
|
||
<div className="empty-state">暂无修复报告</div>
|
||
) : (
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>编号</th>
|
||
<th>项目</th>
|
||
<th>缺陷编号</th>
|
||
<th>修改文件数</th>
|
||
<th>测试结果</th>
|
||
<th>状态</th>
|
||
<th>日期</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{reports.map((report) => (
|
||
<tr key={report.id}>
|
||
<td>#{report.id}</td>
|
||
<td>{report.project_id}</td>
|
||
<td>
|
||
<Link to={`/bugs/${report.error_log_id}`}>
|
||
#{report.error_log_id}
|
||
</Link>
|
||
</td>
|
||
<td>{report.modified_files.length} 个文件</td>
|
||
<td>
|
||
<span className={report.test_passed ? 'test-pass' : 'test-fail'}>
|
||
{report.test_passed ? '通过' : '失败'}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<span className={`status-badge status-${report.status}`}>
|
||
{STATUS_LABELS[report.status] || report.status}
|
||
</span>
|
||
</td>
|
||
<td className="cell-secondary">
|
||
{new Date(report.created_at).toLocaleString()}
|
||
</td>
|
||
<td>
|
||
<Link to={`/repairs/${report.id}`} className="btn-link">
|
||
查看
|
||
</Link>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
)}
|
||
</div>
|
||
|
||
{totalPages > 1 && (
|
||
<div className="pagination">
|
||
<button
|
||
disabled={filters.page === 1}
|
||
onClick={() => setFilters((prev) => ({ ...prev, page: prev.page - 1 }))}
|
||
>
|
||
上一页
|
||
</button>
|
||
<span className="pagination-info">
|
||
第 {filters.page} 页,共 {totalPages} 页
|
||
</span>
|
||
<button
|
||
disabled={filters.page === totalPages}
|
||
onClick={() => setFilters((prev) => ({ ...prev, page: prev.page + 1 }))}
|
||
>
|
||
下一页
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|