feat: implement task management module

Backend (FastAPI):
- Database migration for spaces, projects, task_statuses, tasks tables
- SQLAlchemy models with relationships
- Pydantic schemas for CRUD operations
- Spaces API: CRUD with soft delete
- Projects API: CRUD with auto-created default statuses
- Tasks API: CRUD, status change, assign, subtask support
- Permission middleware with Security Level filtering
- Subtask depth limit (max 2 levels)

Frontend (React + Vite):
- Layout component with navigation
- Spaces list page
- Projects list page
- Tasks list page with status management

Fixes:
- auth_client.py: use 'username' field for external API
- config.py: extend JWT expiry to 7 days
- auth/router.py: sync Redis session with JWT expiry

Tests: 36 passed (unit + integration)
E2E: All APIs verified with real authentication

OpenSpec: add-task-management archived

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beabigegg
2025-12-29 00:31:34 +08:00
parent 1fda7da2c2
commit daca7798e3
41 changed files with 3616 additions and 13 deletions

View File

@@ -0,0 +1,131 @@
import { ReactNode } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
interface LayoutProps {
children: ReactNode
}
export default function Layout({ children }: LayoutProps) {
const { user, logout } = useAuth()
const navigate = useNavigate()
const location = useLocation()
const handleLogout = async () => {
await logout()
}
const navItems = [
{ path: '/', label: 'Dashboard' },
{ path: '/spaces', label: 'Spaces' },
]
return (
<div style={styles.container}>
<header style={styles.header}>
<div style={styles.headerLeft}>
<h1 style={styles.logo} onClick={() => navigate('/')}>
Project Control
</h1>
<nav style={styles.nav}>
{navItems.map((item) => (
<button
key={item.path}
onClick={() => navigate(item.path)}
style={{
...styles.navItem,
...(location.pathname === item.path ? styles.navItemActive : {}),
}}
>
{item.label}
</button>
))}
</nav>
</div>
<div style={styles.headerRight}>
<span style={styles.userName}>{user?.name}</span>
{user?.is_system_admin && (
<span style={styles.badge}>Admin</span>
)}
<button onClick={handleLogout} style={styles.logoutButton}>
Logout
</button>
</div>
</header>
<main style={styles.main}>{children}</main>
</div>
)
}
const styles: { [key: string]: React.CSSProperties } = {
container: {
minHeight: '100vh',
backgroundColor: '#f5f5f5',
},
header: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '12px 24px',
backgroundColor: 'white',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
},
headerLeft: {
display: 'flex',
alignItems: 'center',
gap: '24px',
},
logo: {
fontSize: '18px',
fontWeight: 600,
color: '#333',
margin: 0,
cursor: 'pointer',
},
nav: {
display: 'flex',
gap: '4px',
},
navItem: {
padding: '8px 16px',
backgroundColor: 'transparent',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '14px',
color: '#666',
},
navItemActive: {
backgroundColor: '#e3f2fd',
color: '#0066cc',
fontWeight: 500,
},
headerRight: {
display: 'flex',
alignItems: 'center',
gap: '12px',
},
userName: {
color: '#666',
fontSize: '14px',
},
badge: {
backgroundColor: '#0066cc',
color: 'white',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '11px',
fontWeight: 500,
},
logoutButton: {
padding: '8px 16px',
backgroundColor: '#f5f5f5',
border: '1px solid #ddd',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '14px',
},
main: {
minHeight: 'calc(100vh - 60px)',
},
}