From 2d03f01eccf43ebd535514c4446b757cf32e96f2 Mon Sep 17 00:00:00 2001 From: zyc <1439655764@qq.com> Date: Tue, 10 Feb 2026 13:20:07 +0800 Subject: [PATCH] change tabs --- web/src/index.css | 142 ++++++++++++++++++++++++++++++++ web/src/pages/BugDetail.tsx | 8 +- web/src/pages/BugList.tsx | 159 ++++++++++++++++++++++++------------ 3 files changed, 255 insertions(+), 54 deletions(-) diff --git a/web/src/index.css b/web/src/index.css index 8222193..036b727 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -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); diff --git a/web/src/pages/BugDetail.tsx b/web/src/pages/BugDetail.tsx index d54dee4..b8954fa 100644 --- a/web/src/pages/BugDetail.tsx +++ b/web/src/pages/BugDetail.tsx @@ -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(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 (
- + ← Back to Bug List diff --git a/web/src/pages/BugList.tsx b/web/src/pages/BugList.tsx index 36a72c0..04c16b2 100644 --- a/web/src/pages/BugList.tsx +++ b/web/src/pages/BugList.tsx @@ -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([]); 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([]); + // 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) => { + 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 = { 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 (
@@ -55,28 +77,42 @@ export default function BugList() {

All reported errors and their current status

-
- + All Projects + + {projects.map(p => ( + + ))} +
- + All Status + + {STATUSES.map(s => ( + + ))}
@@ -84,6 +120,12 @@ export default function BugList() {
+ ) : bugs.length === 0 ? ( +
+ No bugs found + {currentProject && in {currentProject}} + {currentStatus && with status {currentStatus}} +
) : ( <> @@ -101,11 +143,22 @@ export default function BugList() { {bugs.map(bug => ( - +
- + #{bug.id} {bug.project_id} + + {bug.error_type} {bug.file_path}:{bug.line_number} @@ -123,23 +176,25 @@ export default function BugList() {
-
- - - Page {page} of {totalPages} - - -
+ {totalPages > 1 && ( +
+ + + Page {currentPage} of {totalPages} + + +
+ )} )}