feat: initial frontend - React + TypeScript + Vite
- Login page with JWT auth - Dashboard with stats and charts - Issues list with pagination, filtering - Issue detail with comments, status transitions - Create issue form - Dark theme UI - Docker (nginx) with API proxy to backend - Sidebar navigation
This commit is contained in:
60
src/pages/CreateIssuePage.tsx
Normal file
60
src/pages/CreateIssuePage.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import api from '@/services/api'
|
||||
import type { Project } from '@/types'
|
||||
|
||||
export default function CreateIssuePage() {
|
||||
const navigate = useNavigate()
|
||||
const [projects, setProjects] = useState<Project[]>([])
|
||||
const [form, setForm] = useState({
|
||||
title: '', description: '', project_id: 0, issue_type: 'task',
|
||||
priority: 'medium', tags: '', reporter_id: 1,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
api.get<Project[]>('/projects').then(({ data }) => {
|
||||
setProjects(data)
|
||||
if (data.length) setForm((f) => ({ ...f, project_id: data[0].id }))
|
||||
})
|
||||
}, [])
|
||||
|
||||
const submit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
const payload = { ...form, tags: form.tags || null }
|
||||
await api.post('/issues', payload)
|
||||
navigate('/issues')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="create-issue">
|
||||
<h2>新建 Issue</h2>
|
||||
<form onSubmit={submit}>
|
||||
<label>标题 <input required value={form.title} onChange={(e) => setForm({ ...form, title: e.target.value })} /></label>
|
||||
<label>描述 <textarea value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} /></label>
|
||||
<label>项目
|
||||
<select value={form.project_id} onChange={(e) => setForm({ ...form, project_id: Number(e.target.value) })}>
|
||||
{projects.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label>类型
|
||||
<select value={form.issue_type} onChange={(e) => setForm({ ...form, issue_type: e.target.value })}>
|
||||
<option value="task">Task</option>
|
||||
<option value="bug">Bug</option>
|
||||
<option value="feature">Feature</option>
|
||||
<option value="resolution">Resolution</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>优先级
|
||||
<select value={form.priority} onChange={(e) => setForm({ ...form, priority: e.target.value })}>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
<option value="critical">Critical</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>标签 <input value={form.tags} onChange={(e) => setForm({ ...form, tags: e.target.value })} placeholder="逗号分隔" /></label>
|
||||
<button type="submit" className="btn-primary">创建</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user