change tabs
All checks were successful
Build and Deploy Log Center / build-and-deploy (push) Successful in 1m23s
All checks were successful
Build and Deploy Log Center / build-and-deploy (push) Successful in 1m23s
This commit is contained in:
parent
d857748314
commit
2d03f01ecc
@ -273,6 +273,148 @@ tr:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Project Tabs (breadcrumb navigation) */
|
||||
.project-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-bottom: 16px;
|
||||
padding: 6px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.project-tab {
|
||||
position: relative;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.project-tab:hover {
|
||||
color: var(--text-primary);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.project-tab.active {
|
||||
background: var(--accent);
|
||||
color: var(--bg-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Status Tabs */
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 20px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.status-tab {
|
||||
background: none;
|
||||
border: 1px solid var(--border);
|
||||
padding: 6px 14px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border-radius: 20px;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.status-tab:hover {
|
||||
border-color: var(--text-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.status-tab.active {
|
||||
border-color: var(--accent);
|
||||
background: rgba(0, 212, 255, 0.15);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.status-tab.active.status-color-NEW {
|
||||
border-color: var(--accent);
|
||||
background: rgba(0, 212, 255, 0.15);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.status-tab.active.status-color-FIXED,
|
||||
.status-tab.active.status-color-VERIFIED,
|
||||
.status-tab.active.status-color-DEPLOYED {
|
||||
border-color: var(--success);
|
||||
background: rgba(0, 230, 118, 0.15);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.status-tab.active.status-color-PENDING_FIX {
|
||||
border-color: var(--warning);
|
||||
background: rgba(255, 171, 0, 0.15);
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.status-tab.active.status-color-FIX_FAILED {
|
||||
border-color: var(--error);
|
||||
background: rgba(255, 82, 82, 0.15);
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.status-tab.active.status-color-FIXING,
|
||||
.status-tab.active.status-color-VERIFYING {
|
||||
border-color: var(--accent-secondary);
|
||||
background: rgba(123, 44, 191, 0.15);
|
||||
color: #b388ff;
|
||||
}
|
||||
|
||||
.status-tab.active.status-color-CANNOT_REPRODUCE {
|
||||
border-color: var(--text-secondary);
|
||||
background: rgba(160, 160, 176, 0.15);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Project link in table */
|
||||
.project-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.project-link:hover {
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 15px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* Bug Detail */
|
||||
.detail-card {
|
||||
background: var(--bg-card);
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useParams, Link, useLocation } from 'react-router-dom';
|
||||
import { getBugDetail, type ErrorLog } from '../api';
|
||||
|
||||
export default function BugDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const location = useLocation();
|
||||
const [bug, setBug] = useState<ErrorLog | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Preserve search params from the list page for back navigation
|
||||
const backSearch = location.state?.fromSearch || '';
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBug = async () => {
|
||||
if (!id) return;
|
||||
@ -36,7 +40,7 @@ export default function BugDetail() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Link to="/bugs" className="back-link">
|
||||
<Link to={`/bugs${backSearch ? `?${backSearch}` : ''}`} className="back-link">
|
||||
← Back to Bug List
|
||||
</Link>
|
||||
|
||||
|
||||
@ -1,16 +1,43 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import { getBugs, getProjects, type ErrorLog } from '../api';
|
||||
|
||||
const STATUSES = [
|
||||
'NEW', 'VERIFYING', 'CANNOT_REPRODUCE', 'PENDING_FIX',
|
||||
'FIXING', 'FIXED', 'VERIFIED', 'DEPLOYED', 'FIX_FAILED'
|
||||
];
|
||||
|
||||
export default function BugList() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [bugs, setBugs] = useState<ErrorLog[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
const [projectFilter, setProjectFilter] = useState('');
|
||||
const [projects, setProjects] = useState<string[]>([]);
|
||||
|
||||
// Read filters from URL params, default status to NEW
|
||||
const currentProject = searchParams.get('project') || '';
|
||||
const currentStatus = searchParams.get('status') ?? 'NEW';
|
||||
const currentPage = parseInt(searchParams.get('page') || '1', 10);
|
||||
|
||||
const updateParams = useCallback((updates: Record<string, string>) => {
|
||||
setSearchParams(prev => {
|
||||
const next = new URLSearchParams(prev);
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
if (value) {
|
||||
next.set(key, value);
|
||||
} else {
|
||||
next.delete(key);
|
||||
}
|
||||
}
|
||||
// Reset page when changing filters
|
||||
if ('project' in updates || 'status' in updates) {
|
||||
next.delete('page');
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, [setSearchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProjects = async () => {
|
||||
try {
|
||||
@ -27,9 +54,9 @@ export default function BugList() {
|
||||
const fetchBugs = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const params: any = { page, page_size: 20 };
|
||||
if (statusFilter) params.status = statusFilter;
|
||||
if (projectFilter) params.project_id = projectFilter;
|
||||
const params: Record<string, string | number> = { page: currentPage, page_size: 20 };
|
||||
if (currentStatus) params.status = currentStatus;
|
||||
if (currentProject) params.project_id = currentProject;
|
||||
|
||||
const response = await getBugs(params);
|
||||
setBugs(response.data.items);
|
||||
@ -41,12 +68,7 @@ export default function BugList() {
|
||||
}
|
||||
};
|
||||
fetchBugs();
|
||||
}, [page, statusFilter, projectFilter]);
|
||||
|
||||
const statuses = [
|
||||
'NEW', 'VERIFYING', 'CANNOT_REPRODUCE', 'PENDING_FIX',
|
||||
'FIXING', 'FIXED', 'VERIFIED', 'DEPLOYED', 'FIX_FAILED'
|
||||
];
|
||||
}, [currentPage, currentStatus, currentProject]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -55,28 +77,42 @@ export default function BugList() {
|
||||
<p className="page-subtitle">All reported errors and their current status</p>
|
||||
</div>
|
||||
|
||||
<div className="filters">
|
||||
<select
|
||||
className="filter-select"
|
||||
value={statusFilter}
|
||||
onChange={(e) => { setStatusFilter(e.target.value); setPage(1); }}
|
||||
{/* Project breadcrumb navigation */}
|
||||
<div className="project-tabs">
|
||||
<button
|
||||
className={`project-tab ${currentProject === '' ? 'active' : ''}`}
|
||||
onClick={() => updateParams({ project: '' })}
|
||||
>
|
||||
<option value="">All Status</option>
|
||||
{statuses.map(s => (
|
||||
<option key={s} value={s}>{s}</option>
|
||||
))}
|
||||
</select>
|
||||
All Projects
|
||||
</button>
|
||||
{projects.map(p => (
|
||||
<button
|
||||
key={p}
|
||||
className={`project-tab ${currentProject === p ? 'active' : ''}`}
|
||||
onClick={() => updateParams({ project: p })}
|
||||
>
|
||||
{p}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<select
|
||||
className="filter-select"
|
||||
value={projectFilter}
|
||||
onChange={(e) => { setProjectFilter(e.target.value); setPage(1); }}
|
||||
{/* Status filter tabs */}
|
||||
<div className="status-tabs">
|
||||
<button
|
||||
className={`status-tab ${currentStatus === '' ? 'active' : ''}`}
|
||||
onClick={() => updateParams({ status: '' })}
|
||||
>
|
||||
<option value="">All Projects</option>
|
||||
{projects.map(p => (
|
||||
<option key={p} value={p}>{p}</option>
|
||||
))}
|
||||
</select>
|
||||
All Status
|
||||
</button>
|
||||
{STATUSES.map(s => (
|
||||
<button
|
||||
key={s}
|
||||
className={`status-tab ${currentStatus === s ? 'active' : ''} status-color-${s}`}
|
||||
onClick={() => updateParams({ status: s })}
|
||||
>
|
||||
{s.replace('_', ' ')}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="table-container">
|
||||
@ -84,6 +120,12 @@ export default function BugList() {
|
||||
<div className="loading">
|
||||
<div className="spinner"></div>
|
||||
</div>
|
||||
) : bugs.length === 0 ? (
|
||||
<div className="empty-state">
|
||||
No bugs found
|
||||
{currentProject && <span> in <strong>{currentProject}</strong></span>}
|
||||
{currentStatus && <span> with status <strong>{currentStatus}</strong></span>}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<table>
|
||||
@ -101,11 +143,22 @@ export default function BugList() {
|
||||
{bugs.map(bug => (
|
||||
<tr key={bug.id}>
|
||||
<td>
|
||||
<Link to={`/bugs/${bug.id}`} style={{ color: 'var(--accent)' }}>
|
||||
<Link
|
||||
to={`/bugs/${bug.id}`}
|
||||
state={{ fromSearch: searchParams.toString() }}
|
||||
style={{ color: 'var(--accent)' }}
|
||||
>
|
||||
#{bug.id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>{bug.project_id}</td>
|
||||
<td>
|
||||
<button
|
||||
className="project-link"
|
||||
onClick={() => updateParams({ project: bug.project_id })}
|
||||
>
|
||||
{bug.project_id}
|
||||
</button>
|
||||
</td>
|
||||
<td style={{ color: 'var(--error)' }}>{bug.error_type}</td>
|
||||
<td style={{ fontFamily: 'monospace', fontSize: '13px' }}>
|
||||
{bug.file_path}:{bug.line_number}
|
||||
@ -123,23 +176,25 @@ export default function BugList() {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="pagination">
|
||||
<button
|
||||
onClick={() => setPage(p => Math.max(1, p - 1))}
|
||||
disabled={page === 1}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span style={{ padding: '8px 16px', color: 'var(--text-secondary)' }}>
|
||||
Page {page} of {totalPages}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setPage(p => Math.min(totalPages, p + 1))}
|
||||
disabled={page === totalPages}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
{totalPages > 1 && (
|
||||
<div className="pagination">
|
||||
<button
|
||||
onClick={() => updateParams({ page: String(Math.max(1, currentPage - 1)) })}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span style={{ padding: '8px 16px', color: 'var(--text-secondary)' }}>
|
||||
Page {currentPage} of {totalPages}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => updateParams({ page: String(Math.min(totalPages, currentPage + 1)) })}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user