refactor: rename Issue → Task throughout frontend

- Rename files: IssuesPage → TasksPage, IssueDetailPage → TaskDetailPage,
  CreateIssuePage → CreateTaskPage
- Rename TypeScript interface: Issue → Task (keep backend field names)
- Update routes: /issues → /tasks, /issues/new → /tasks/new, /issues/:id → /tasks/:id
- Update CSS class names: issue-* → task-*, create-issue → create-task
- Update UI text: 'Issues' → 'Tasks', 'Create Issue' → 'Create Task'
- Keep 'issue' as a task subtype value in TASK_TYPES dropdown
- Keep all backend API endpoint paths unchanged (/issues, /comments, etc.)
- Rename local Task interface in MilestoneDetailPage to MilestoneTask
  to avoid conflict with the global Task type
This commit is contained in:
zhi
2026-03-16 07:47:58 +00:00
parent 9880cfc41e
commit 01affdb020
11 changed files with 94 additions and 94 deletions

View File

@@ -0,0 +1,86 @@
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import api from '@/services/api'
import type { Project } from '@/types'
const TASK_TYPES = [
{ value: 'story', label: 'Story', subtypes: ['feature', 'improvement', 'refactor'] },
{ value: 'issue', label: 'Issue', subtypes: ['infrastructure', 'performance', 'regression', 'security', 'user_experience', 'defect'] },
{ value: 'task', label: 'Task', subtypes: ['defect'] },
{ value: 'test', label: 'Test', subtypes: ['regression', 'security', 'smoke', 'stress'] },
{ value: 'maintenance', label: 'Maintenance', subtypes: ['deploy', 'release'] },
{ value: 'research', label: 'Research', subtypes: [] },
{ value: 'review', label: 'Review', subtypes: ['code_review', 'decision_review', 'function_review'] },
{ value: 'support', label: 'Support', subtypes: ['access', 'information'] },
{ value: 'meeting', label: 'Meeting', subtypes: ['conference', 'handover', 'recap'] },
{ value: 'resolution', label: 'Resolution', subtypes: [] },
]
export default function CreateTaskPage() {
const navigate = useNavigate()
const [projects, setProjects] = useState<Project[]>([])
const [form, setForm] = useState({
title: '', description: '', project_id: 0, issue_type: 'task',
issue_subtype: '', 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 currentType = TASK_TYPES.find(t => t.value === form.issue_type) || TASK_TYPES[2]
const subtypes = currentType.subtypes || []
const handleTypeChange = (newType: string) => {
setForm(f => ({ ...f, issue_type: newType, issue_subtype: '' }))
}
const submit = async (e: React.FormEvent) => {
e.preventDefault()
const payload: any = { ...form, tags: form.tags || null }
if (!form.issue_subtype) delete payload.issue_subtype
await api.post('/issues', payload)
navigate('/tasks')
}
return (
<div className="create-task">
<h2>Create Task</h2>
<form onSubmit={submit}>
<label>Title <input required value={form.title} onChange={(e) => setForm({ ...form, title: e.target.value })} /></label>
<label>Description <textarea value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} /></label>
<label>Projects
<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>Type
<select value={form.issue_type} onChange={(e) => handleTypeChange(e.target.value)}>
{TASK_TYPES.map((t) => <option key={t.value} value={t.value}>{t.label}</option>)}
</select>
</label>
{subtypes.length > 0 && (
<label>Subtype
<select value={form.issue_subtype} onChange={(e) => setForm({ ...form, issue_subtype: e.target.value })}>
<option value="">Select subtype</option>
{subtypes.map((s) => <option key={s} value={s}>{s.replace('_', ' ')}</option>)}
</select>
</label>
)}
<label>Priority
<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>Tags <input value={form.tags} onChange={(e) => setForm({ ...form, tags: e.target.value })} placeholder="Comma separated" /></label>
<button type="submit" className="btn-primary">Create</button>
</form>
</div>
)
}