From bfaf9469e1f6e7edcef1134044c6ea0d5c687b28 Mon Sep 17 00:00:00 2001 From: Zhi Date: Thu, 12 Mar 2026 10:52:54 +0000 Subject: [PATCH] fix: project form - owner dropdown, sub/related projects multi-select --- src/pages/CreateIssuePage.tsx | 4 +- src/pages/ProjectDetailPage.tsx | 72 ++++++++++++++++++++------------- src/pages/ProjectsPage.tsx | 42 ++++++++++++++++--- src/types/index.ts | 3 ++ 4 files changed, 84 insertions(+), 37 deletions(-) diff --git a/src/pages/CreateIssuePage.tsx b/src/pages/CreateIssuePage.tsx index 283be00..160395f 100644 --- a/src/pages/CreateIssuePage.tsx +++ b/src/pages/CreateIssuePage.tsx @@ -5,8 +5,8 @@ import type { Project } from '@/types' const ISSUE_TYPES = [ { value: 'story', label: 'Story', subtypes: ['feature', 'improvement', 'refactor'] }, - { value: 'issue', label: 'Issue', subtypes: ['infrastructure', 'performance', 'regression', 'security', 'user_experience'] }, - { value: 'task', label: 'Task', subtypes: [] }, + { 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: [] }, diff --git a/src/pages/ProjectDetailPage.tsx b/src/pages/ProjectDetailPage.tsx index 2d56e81..b0522ce 100644 --- a/src/pages/ProjectDetailPage.tsx +++ b/src/pages/ProjectDetailPage.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' import { useParams, useNavigate } from 'react-router-dom' import api from '@/services/api' -import type { Project, ProjectMember, Issue, Milestone, PaginatedResponse } from '@/types' +import type { Project, ProjectMember, Milestone } from '@/types' import dayjs from 'dayjs' export default function ProjectDetailPage() { @@ -9,21 +9,31 @@ export default function ProjectDetailPage() { const navigate = useNavigate() const [project, setProject] = useState(null) const [members, setMembers] = useState([]) - const [issues, setIssues] = useState([]) const [milestones, setMilestones] = useState([]) + const [allProjects, setAllProjects] = useState([]) const [editing, setEditing] = useState(false) - const [editForm, setEditForm] = useState({ name: '', description: '' }) + const [editForm, setEditForm] = useState({ owner: '', description: '', sub_projects: [] as string[], related_projects: [] as string[] }) useEffect(() => { api.get(`/projects/${id}`).then(({ data }) => { setProject(data) - setEditForm({ name: data.name, description: data.description || '' }) + setEditForm({ + owner: data.owner || '', + description: data.description || '', + sub_projects: data.sub_projects || [], + related_projects: data.related_projects || [], + }) }) api.get(`/projects/${id}/members`).then(({ data }) => setMembers(data)) - api.get>(`/issues?project_id=${id}&page_size=10`).then(({ data }) => setIssues(data.items)) api.get(`/milestones?project_id=${id}`).then(({ data }) => setMilestones(data)) + api.get('/projects').then(({ data }) => setAllProjects(data)) }, [id]) + const handleMulti = (e: React.ChangeEvent, field: 'sub_projects' | 'related_projects') => { + const values = Array.from(e.target.selectedOptions).map((o) => o.value) + setEditForm({ ...editForm, [field]: values }) + } + const updateProject = async (e: React.FormEvent) => { e.preventDefault() const { data } = await api.patch(`/projects/${id}`, editForm) @@ -31,8 +41,16 @@ export default function ProjectDetailPage() { setEditing(false) } + const deleteProject = async () => { + if (!confirm('Delete this project?')) return + await api.delete(`/projects/${id}`) + navigate('/projects') + } + if (!project) return
Loading...
+ const selectableProjects = allProjects.filter((p) => p.id !== project.id && p.project_code) + return (
@@ -40,16 +58,34 @@ export default function ProjectDetailPage() {
{editing ? (
- setEditForm({ ...editForm, name: e.target.value })} required /> +
{project.name}
+ {project.project_code && {project.project_code}} + setEditForm({ ...editForm, owner: e.target.value })} placeholder="Owner" required /> setEditForm({ ...editForm, description: e.target.value })} placeholder="Description" /> + +
) : ( <> -

📁 {project.name}

+

📁 {project.name} {project.project_code && {project.project_code}}

{project.description || 'No description'}

+
Owner: {project.owner}
+ )}
@@ -78,28 +114,6 @@ export default function ProjectDetailPage() { ))} {milestones.length === 0 &&

No milestones

}
- -
-
-

Recent Issues

- -
- - - - - - {issues.map((i) => ( - navigate(`/issues/${i.id}`)}> - - - - - - ))} - -
#TitleStatusPriority
{i.id}{i.title}{i.status}{i.priority}
-
) } diff --git a/src/pages/ProjectsPage.tsx b/src/pages/ProjectsPage.tsx index 6139645..20f8e3b 100644 --- a/src/pages/ProjectsPage.tsx +++ b/src/pages/ProjectsPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useMemo } from 'react' import { useNavigate } from 'react-router-dom' import api from '@/services/api' import type { Project } from '@/types' @@ -6,8 +6,9 @@ import dayjs from 'dayjs' export default function ProjectsPage() { const [projects, setProjects] = useState([]) + const [users, setUsers] = useState([]) const [showCreate, setShowCreate] = useState(false) - const [form, setForm] = useState({ name: '', description: '', owner_id: 1 }) + const [form, setForm] = useState({ name: '', description: '', owner_id: 1, sub_projects: [] as string[], related_projects: [] as string[] }) const navigate = useNavigate() const fetchProjects = () => { @@ -15,11 +16,21 @@ export default function ProjectsPage() { } useEffect(() => { fetchProjects() }, []) + useEffect(() => { + api.get('/users').then(({ data }) => setUsers(data)).catch(console.error) + }, []) + + const projectOptions = useMemo(() => projects.filter(p => p.project_code), [projects]) + + const handleMulti = (e: React.ChangeEvent, field: 'sub_projects' | 'related_projects') => { + const values = Array.from(e.target.selectedOptions).map((o) => o.value) + setForm({ ...form, [field]: values }) + } const createProject = async (e: React.FormEvent) => { e.preventDefault() await api.post('/projects', form) - setForm({ name: '', description: '', owner_id: 1 }) + setForm({ name: '', description: '', owner_id: 1, sub_projects: [], related_projects: [] }) setShowCreate(false) fetchProjects() } @@ -29,7 +40,7 @@ export default function ProjectsPage() {

📁 Projects ({projects.length})

@@ -39,10 +50,29 @@ export default function ProjectsPage() { required placeholder="Project name" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} /> + setForm({ ...form, description: e.target.value })} /> + + + + )} @@ -50,7 +80,7 @@ export default function ProjectsPage() {
{projects.map((p) => (
navigate(`/projects/${p.id}`)}> -

{p.name}

{p.project_code && {p.project_code}} +

{p.name}

{p.project_code && {p.project_code}}

{p.description || 'No description'}

Created {dayjs(p.created_at).format('YYYY-MM-DD')} diff --git a/src/types/index.ts b/src/types/index.ts index 9995807..a753af1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -11,9 +11,12 @@ export interface User { export interface Project { id: number name: string + owner: string description: string | null owner_id: number project_code: string | null + sub_projects: string[] | null + related_projects: string[] | null created_at: string }