log-center/web/src/App.tsx
zyc 625e53dc44
All checks were successful
Build and Deploy Log Center / build-and-deploy (push) Successful in 2m16s
feat(project-mgmt): 项目管理 + 失败原因追踪 + 前端展示
- 新增 Project 模型(repo_url, local_path, name, description)
- 项目 CRUD API(GET/PUT /api/v1/projects)
- 日志上报自动 upsert Project 记录
- ErrorLog 增加 failure_reason 字段
- update_task_status / create_repair_report 写入失败原因
- Repair Agent 优先从 API 获取项目配置,回退 .env
- 新增 Web 端「项目管理」页面(表格 + 行内编辑)
- BugList/BugDetail/RepairList 展示失败原因
- 更新接入指南文档

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 11:18:27 +08:00

116 lines
3.5 KiB
TypeScript

import { useState, useEffect } from 'react';
import { BrowserRouter, Routes, Route, NavLink, useLocation } from 'react-router-dom';
import { LayoutDashboard, Bug, Wrench, FolderGit2, Shield, Menu, X } from 'lucide-react';
import Dashboard from './pages/Dashboard';
import BugList from './pages/BugList';
import BugDetail from './pages/BugDetail';
import RepairList from './pages/RepairList';
import RepairDetail from './pages/RepairDetail';
import ProjectList from './pages/ProjectList';
import './index.css';
function AppLayout() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const location = useLocation();
// Close sidebar on route change (mobile)
useEffect(() => {
setSidebarOpen(false);
}, [location.pathname]);
return (
<div className="app">
{/* Mobile header */}
<header className="mobile-header">
<button className="mobile-menu-btn" onClick={() => setSidebarOpen(true)}>
<Menu size={20} />
</button>
<div className="mobile-logo">
<div className="logo-icon"><Shield size={14} /></div>
</div>
<div style={{ width: 36 }} />
</header>
{/* Overlay */}
{sidebarOpen && (
<div className="sidebar-overlay" onClick={() => setSidebarOpen(false)} />
)}
<aside className={`sidebar ${sidebarOpen ? 'sidebar-open' : ''}`}>
<div className="sidebar-top">
<div className="logo">
<div className="logo-icon"><Shield size={16} /></div>
</div>
<button className="sidebar-close-btn" onClick={() => setSidebarOpen(false)}>
<X size={18} />
</button>
</div>
<nav>
<ul className="nav-menu">
<li className="nav-item">
<NavLink
to="/"
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
end
>
<LayoutDashboard size={16} />
</NavLink>
</li>
<li className="nav-item">
<NavLink
to="/bugs"
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
>
<Bug size={16} />
</NavLink>
</li>
<li className="nav-item">
<NavLink
to="/repairs"
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
>
<Wrench size={16} />
</NavLink>
</li>
<li className="nav-item">
<NavLink
to="/projects"
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
>
<FolderGit2 size={16} />
</NavLink>
</li>
</ul>
</nav>
</aside>
<main className="main-content">
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/bugs" element={<BugList />} />
<Route path="/bugs/:id" element={<BugDetail />} />
<Route path="/repairs" element={<RepairList />} />
<Route path="/repairs/:id" element={<RepairDetail />} />
<Route path="/projects" element={<ProjectList />} />
</Routes>
</main>
</div>
);
}
function App() {
return (
<BrowserRouter>
<AppLayout />
</BrowserRouter>
);
}
export default App;