Files
HarborForge.Frontend/src/pages/ProjectsPage.tsx
zhi 54d4c4379a feat: remove nginx, add projects/milestones/notifications pages
- Dockerfile: replace nginx with serve for static files
- Fix auth endpoint: /auth/login → /auth/token
- Add ProjectsPage, ProjectDetailPage
- Add MilestonesPage, MilestoneDetailPage with progress bar
- Add NotificationsPage with unread count
- Sidebar: add milestones/notifications nav, live unread badge
- API: configurable VITE_API_BASE for host nginx proxy
- Types: add Milestone, MilestoneProgress, Notification, ProjectMember
2026-03-06 13:05:19 +00:00

65 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import api from '@/services/api'
import type { Project } from '@/types'
import dayjs from 'dayjs'
export default function ProjectsPage() {
const [projects, setProjects] = useState<Project[]>([])
const [showCreate, setShowCreate] = useState(false)
const [form, setForm] = useState({ name: '', description: '', owner_id: 1 })
const navigate = useNavigate()
const fetchProjects = () => {
api.get<Project[]>('/projects').then(({ data }) => setProjects(data))
}
useEffect(() => { fetchProjects() }, [])
const createProject = async (e: React.FormEvent) => {
e.preventDefault()
await api.post('/projects', form)
setForm({ name: '', description: '', owner_id: 1 })
setShowCreate(false)
fetchProjects()
}
return (
<div className="projects-page">
<div className="page-header">
<h2>📁 ({projects.length})</h2>
<button className="btn-primary" onClick={() => setShowCreate(!showCreate)}>
{showCreate ? '取消' : '+ 新建项目'}
</button>
</div>
{showCreate && (
<form className="inline-form" onSubmit={createProject}>
<input
required placeholder="项目名称" value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
/>
<input
placeholder="项目描述(可选)" value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })}
/>
<button type="submit" className="btn-primary"></button>
</form>
)}
<div className="project-grid">
{projects.map((p) => (
<div key={p.id} className="project-card" onClick={() => navigate(`/projects/${p.id}`)}>
<h3>{p.name}</h3>
<p className="project-desc">{p.description || '暂无描述'}</p>
<div className="project-meta">
<span> {dayjs(p.created_at).format('YYYY-MM-DD')}</span>
</div>
</div>
))}
{projects.length === 0 && <p className="empty"></p>}
</div>
</div>
)
}