All checks were successful
Build and Deploy Log Center / build-and-deploy (push) Successful in 1m42s
152 lines
6.0 KiB
TypeScript
152 lines
6.0 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { getRepairReports, type RepairReport, getProjects } from '../api';
|
|
|
|
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">Repair Reports</h1>
|
|
<p className="page-subtitle">AI-powered bug repair attempts and their results</p>
|
|
</div>
|
|
<div className="filters">
|
|
<select
|
|
className="filter-select"
|
|
value={filters.project_id}
|
|
onChange={(e) => handleFilterChange('project_id', e.target.value)}
|
|
>
|
|
<option value="">All Projects</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">No reports found</div>
|
|
) : (
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Project</th>
|
|
<th>Bug ID</th>
|
|
<th>Modified Files</th>
|
|
<th>Test Result</th>
|
|
<th>Status</th>
|
|
<th>Date</th>
|
|
<th>Actions</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} files</td>
|
|
<td>
|
|
<span className={report.test_passed ? 'test-pass' : 'test-fail'}>
|
|
{report.test_passed ? 'PASS' : 'FAIL'}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span className={`status-badge status-${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">
|
|
View
|
|
</Link>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</div>
|
|
|
|
{totalPages > 1 && (
|
|
<div className="pagination">
|
|
<button
|
|
disabled={filters.page === 1}
|
|
onClick={() => setFilters((prev) => ({ ...prev, page: prev.page - 1 }))}
|
|
>
|
|
Previous
|
|
</button>
|
|
<span className="pagination-info">
|
|
Page {filters.page} of {totalPages}
|
|
</span>
|
|
<button
|
|
disabled={filters.page === totalPages}
|
|
onClick={() => setFilters((prev) => ({ ...prev, page: prev.page + 1 }))}
|
|
>
|
|
Next
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|