feat: unify task creation with shared modal

This commit is contained in:
zhi
2026-03-16 16:32:09 +00:00
parent 0c5c78a45d
commit ef42231697
7 changed files with 300 additions and 42 deletions

View File

@@ -3,6 +3,7 @@ import { useParams, useNavigate } from 'react-router-dom'
import api from '@/services/api'
import type { Milestone, MilestoneProgress, Task } from '@/types'
import dayjs from 'dayjs'
import CreateTaskModal from '@/components/CreateTaskModal'
interface MilestoneTask {
id: number
@@ -33,8 +34,6 @@ export default function MilestoneDetailPage() {
const [showCreateMeeting, setShowCreateMeeting] = useState(false)
const [newTitle, setNewTitle] = useState('')
const [newDesc, setNewDesc] = useState('')
const [newEffort, setNewEffort] = useState(5)
const [newTime, setNewTime] = useState('09:00')
const [projectCode, setProjectCode] = useState('')
useEffect(() => {
@@ -50,33 +49,29 @@ export default function MilestoneDetailPage() {
api.get<MilestoneProgress>(`/milestones/${id}/progress`).then(({ data }) => setProgress(data)).catch(() => {})
}, [id])
useEffect(() => {
const refreshMilestoneItems = () => {
if (!projectCode || !id) return
api.get<MilestoneTask[]>(`/tasks/${projectCode}/${id}`).then(({ data }) => setTasks(data)).catch(() => {})
api.get<Task[]>(`/supports/${projectCode}/${id}`).then(({ data }) => setSupports(data)).catch(() => {})
api.get<Task[]>(`/meetings/${projectCode}/${id}`).then(({ data }) => setMeetings(data)).catch(() => {})
}
useEffect(() => {
refreshMilestoneItems()
}, [projectCode, id])
const createItem = async (type: 'tasks' | 'supports' | 'meetings') => {
const createItem = async (type: 'supports' | 'meetings') => {
if (!newTitle.trim() || !projectCode) return
const payload: any = {
const payload = {
title: newTitle,
description: newDesc || null,
}
if (type === 'tasks') {
payload.estimated_effort = newEffort
payload.estimated_working_time = newTime
}
await api.post(`/${type}/${projectCode}/${id}`, payload)
setNewTitle('')
setNewDesc('')
setShowCreateTask(false)
setShowCreateSupport(false)
setShowCreateMeeting(false)
// Refresh
api.get<MilestoneTask[]>(`/tasks/${projectCode}/${id}`).then(({ data }) => setTasks(data))
api.get<Task[]>(`/supports/${projectCode}/${id}`).then(({ data }) => setSupports(data))
api.get<Task[]>(`/meetings/${projectCode}/${id}`).then(({ data }) => setMeetings(data))
refreshMilestoneItems()
}
const isProgressing = milestone?.status === 'progressing'
@@ -84,7 +79,7 @@ export default function MilestoneDetailPage() {
if (!milestone) return <div className="loading">Loading...</div>
const renderTaskRow = (t: MilestoneTask) => (
<tr key={t.id} className="clickable" onClick={() => navigate(`/milestones/${id}`)}>
<tr key={t.id} className="clickable" onClick={() => navigate(`/tasks/${t.id}`)}>
<td>{t.task_code || t.id}</td>
<td className="task-title">{t.title}</td>
<td><span className={`badge status-${t.task_status || t.status}`}>{t.task_status || t.status}</span></td>
@@ -138,15 +133,28 @@ export default function MilestoneDetailPage() {
<div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
{!isProgressing && (
<>
<button className="btn-primary" onClick={() => { setActiveTab("tasks"); setShowCreateTask(true) }}>+ Create Task</button>
<button className="btn-primary" onClick={() => { setActiveTab("supports"); setShowCreateSupport(true) }}>+ Create Support</button>
<button className="btn-primary" onClick={() => { setActiveTab("meetings"); setShowCreateMeeting(true) }}>+ Schedule Meeting</button>
<button className="btn-primary" onClick={() => { setActiveTab('tasks'); setShowCreateTask(true) }}>+ Create Task</button>
<button className="btn-primary" onClick={() => { setActiveTab('supports'); setShowCreateSupport(true) }}>+ Create Support</button>
<button className="btn-primary" onClick={() => { setActiveTab('meetings'); setShowCreateMeeting(true) }}>+ Schedule Meeting</button>
</>
)}
{isProgressing && <span className="text-dim">Milestone is in progress - cannot add new items</span>}
</div>
{(showCreateTask || showCreateSupport || showCreateMeeting) && (
<CreateTaskModal
isOpen={showCreateTask}
onClose={() => setShowCreateTask(false)}
initialProjectId={milestone.project_id}
initialMilestoneId={milestone.id}
lockProject
lockMilestone
onCreated={() => {
setActiveTab('tasks')
refreshMilestoneItems()
}}
/>
{(showCreateSupport || showCreateMeeting) && (
<div className="card" style={{ marginBottom: 16 }}>
<input
placeholder="Title"
@@ -160,19 +168,9 @@ export default function MilestoneDetailPage() {
onChange={(e) => setNewDesc(e.target.value)}
style={{ marginBottom: 8, width: '100%' }}
/>
{showCreateTask && (
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<label>Effort (1-10):
<input type="number" min="1" max="10" value={newEffort} onChange={(e) => setNewEffort(Number(e.target.value))} style={{ width: 60 }} />
</label>
<label>Est. Time:
<input type="time" value={newTime} onChange={(e) => setNewTime(e.target.value)} />
</label>
</div>
)}
<div style={{ display: 'flex', gap: 8 }}>
<button className="btn-primary" onClick={() => createItem(activeTab)}>Create</button>
<button className="btn-back" onClick={() => { setShowCreateTask(false); setShowCreateSupport(false); setShowCreateMeeting(false) }}>Cancel</button>
<button className="btn-primary" onClick={() => createItem(activeTab as 'supports' | 'meetings')}>Create</button>
<button className="btn-back" onClick={() => { setShowCreateSupport(false); setShowCreateMeeting(false) }}>Cancel</button>
</div>
</div>
)}