diff --git a/src/components/CreateTaskModal.tsx b/src/components/CreateTaskModal.tsx index 2af39ac..4a59963 100644 --- a/src/components/CreateTaskModal.tsx +++ b/src/components/CreateTaskModal.tsx @@ -17,13 +17,27 @@ type Props = { isOpen: boolean onClose: () => void onCreated?: (task: Task) => void | Promise + onSaved?: (task: Task) => void | Promise initialProjectId?: number initialMilestoneId?: number lockProject?: boolean lockMilestone?: boolean + task?: Task | null } -const makeInitialForm = (projectId = 0, milestoneId = 0) => ({ +type FormState = { + title: string + description: string + project_id: number + milestone_id: number + task_type: string + task_subtype: string + priority: string + tags: string + reporter_id: number +} + +const makeInitialForm = (projectId = 0, milestoneId = 0): FormState => ({ title: '', description: '', project_id: projectId, @@ -39,16 +53,19 @@ export default function CreateTaskModal({ isOpen, onClose, onCreated, + onSaved, initialProjectId, initialMilestoneId, lockProject = false, lockMilestone = false, + task, }: Props) { const [projects, setProjects] = useState([]) const [milestones, setMilestones] = useState([]) const [saving, setSaving] = useState(false) - const [form, setForm] = useState(makeInitialForm(initialProjectId, initialMilestoneId)) + const [form, setForm] = useState(makeInitialForm(initialProjectId, initialMilestoneId)) + const isEdit = Boolean(task) const currentType = useMemo( () => TASK_TYPES.find((t) => t.value === form.task_type) || TASK_TYPES[2], [form.task_type] @@ -76,13 +93,26 @@ export default function CreateTaskModal({ const { data } = await api.get('/projects') setProjects(data) - const chosenProjectId = initialProjectId || data[0]?.id || 0 - setForm(makeInitialForm(chosenProjectId, initialMilestoneId || 0)) - await loadMilestones(chosenProjectId, initialMilestoneId) + const chosenProjectId = task?.project_id || initialProjectId || data[0]?.id || 0 + const chosenMilestoneId = task?.milestone_id || initialMilestoneId || 0 + + setForm(task ? { + title: task.title, + description: task.description || '', + project_id: task.project_id, + milestone_id: task.milestone_id || 0, + task_type: task.task_type, + task_subtype: task.task_subtype || '', + priority: task.priority, + tags: task.tags || '', + reporter_id: task.reporter_id, + } : makeInitialForm(chosenProjectId, chosenMilestoneId)) + + await loadMilestones(chosenProjectId, chosenMilestoneId) } init().catch(console.error) - }, [isOpen, initialProjectId, initialMilestoneId]) + }, [isOpen, initialProjectId, initialMilestoneId, task]) const handleProjectChange = async (projectId: number) => { setForm((f) => ({ ...f, project_id: projectId, milestone_id: 0 })) @@ -105,8 +135,11 @@ export default function CreateTaskModal({ setSaving(true) try { - const { data } = await api.post('/tasks', payload) + const { data } = isEdit + ? await api.patch(`/tasks/${task!.id}`, payload) + : await api.post('/tasks', payload) await onCreated?.(data) + await onSaved?.(data) onClose() } finally { setSaving(false) @@ -119,7 +152,7 @@ export default function CreateTaskModal({
e.stopPropagation()}>
-

Create Task

+

{isEdit ? 'Edit Task' : 'Create Task'}

@@ -226,7 +259,7 @@ export default function CreateTaskModal({
diff --git a/src/components/MilestoneFormModal.tsx b/src/components/MilestoneFormModal.tsx new file mode 100644 index 0000000..00e6ecb --- /dev/null +++ b/src/components/MilestoneFormModal.tsx @@ -0,0 +1,154 @@ +import { useEffect, useState } from 'react' +import api from '@/services/api' +import type { Milestone, Project } from '@/types' + +type Props = { + isOpen: boolean + onClose: () => void + onSaved?: (milestone: Milestone) => void | Promise + milestone?: Milestone | null + initialProjectId?: number + lockProject?: boolean +} + +type FormState = { + title: string + description: string + project_id: number + status: string + due_date: string + planned_release_date: string +} + +const emptyForm: FormState = { + title: '', + description: '', + project_id: 0, + status: 'open', + due_date: '', + planned_release_date: '', +} + +const toDateInput = (value?: string | null) => (value ? String(value).slice(0, 10) : '') + +export default function MilestoneFormModal({ isOpen, onClose, onSaved, milestone, initialProjectId, lockProject = false }: Props) { + const [projects, setProjects] = useState([]) + const [saving, setSaving] = useState(false) + const [form, setForm] = useState(emptyForm) + + useEffect(() => { + if (!isOpen) return + + const init = async () => { + const { data } = await api.get('/projects') + setProjects(data) + const defaultProjectId = milestone?.project_id || initialProjectId || data[0]?.id || 0 + setForm({ + title: milestone?.title || '', + description: milestone?.description || '', + project_id: defaultProjectId, + status: milestone?.status || 'open', + due_date: toDateInput(milestone?.due_date), + planned_release_date: toDateInput(milestone?.planned_release_date), + }) + } + + init().catch(console.error) + }, [isOpen, milestone, initialProjectId]) + + const submit = async (e: React.FormEvent) => { + e.preventDefault() + setSaving(true) + try { + const payload = { + title: form.title, + description: form.description || null, + project_id: form.project_id, + status: form.status, + due_date: form.due_date || null, + planned_release_date: form.planned_release_date || null, + } + if (milestone) { + const { data } = await api.patch(`/milestones/${milestone.id}`, payload) + await onSaved?.(data) + } else { + const { data } = await api.post('/milestones', payload) + await onSaved?.(data) + } + onClose() + } finally { + setSaving(false) + } + } + + if (!isOpen) return null + + return ( +
+
e.stopPropagation()}> +
+

{milestone ? 'Edit Milestone' : 'Create Milestone'}

+ +
+ +
+ + +