diff --git a/src/components/TaskModal.tsx b/src/components/TaskModal.tsx index 1340ca4..3b7d902 100644 --- a/src/components/TaskModal.tsx +++ b/src/components/TaskModal.tsx @@ -1,10 +1,11 @@ import { useEffect, useState } from "react"; -import type { Task, Member, Step, Message } from "@/lib/api"; +import type { Task, Member, Step, Message, TaskLink } from "@/lib/api"; import { updateTask, deleteTask, getMembers, getSteps, createStep, updateStep, deleteStep as _deleteStepApi, getMessages, sendMessage, + getTaskLinks, createTaskLink, deleteTaskLink, } from "@/lib/api"; const STATUSES = [ @@ -45,6 +46,10 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p const [assigneeId, setAssigneeId] = useState(task.assignee_id || ""); const [members, setMembers] = useState([]); const [steps, setSteps] = useState(task.steps || []); + const [links, setLinks] = useState([]); + const [showAddLink, setShowAddLink] = useState(false); + const [linkTargetId, setLinkTargetId] = useState(""); + const [linkType, setLinkType] = useState("depends_on"); const [comments, setComments] = useState([]); const [_saving, setSaving] = useState(false); const [editingDesc, setEditingDesc] = useState(false); @@ -56,6 +61,7 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p useEffect(() => { getMembers().then(setMembers).catch(() => {}); getSteps(task.id).then(setSteps).catch(() => {}); + getTaskLinks(task.id).then(setLinks).catch(() => {}); getMessages({ task_id: task.id }).then(setComments).catch(() => {}); }, [task.id]); @@ -233,6 +239,68 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p + {/* Dependencies */} + {(links.length > 0 || showAddLink) && ( +
+
Зависимости
+
+ {links.map((link) => { + const label = link.link_type === "blocks" ? "🔴 Блокирует" : + link.link_type === "blocked_by" ? "🟡 Заблокировано" : + link.link_type === "depends_on" ? "🟡 Зависит от" : + link.link_type === "required_by" ? "🔵 Требуется для" : + "🔗 Связано с"; + const title = link.target_title || link.source_title || "..."; + return ( +
+ {label}: {title} + +
+ ); + })} +
+ {showAddLink && ( +
+ + setLinkTargetId(e.target.value)} + placeholder="ID задачи..." + className="flex-1 px-2 py-1 bg-[var(--bg)] border border-[var(--border)] rounded text-xs outline-none" + /> + +
+ )} +
+ )} + + + {/* Comments */}
Комментарии
diff --git a/src/lib/api.ts b/src/lib/api.ts index 6dda759..6e4bc86 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -426,4 +426,25 @@ export async function addProjectMember(projectId: string, memberId: string): Pro export async function removeProjectMember(projectId: string, memberId: string): Promise<{ ok: boolean }> { return request(`/api/v1/projects/${projectId}/members/${memberId}`, { method: "DELETE" }); -} \ No newline at end of file +} +// --- Task Links --- +export interface TaskLink { + id: string; + source_id: string; + target_id: string; + link_type: string; + target_title?: string; + source_title?: string; +} + +export async function getTaskLinks(taskId: string): Promise { + return request(`/tasks/${taskId}/links`); +} + +export async function createTaskLink(taskId: string, targetId: string, linkType: string): Promise { + return request(`/tasks/${taskId}/links`, { method: "POST", body: JSON.stringify({ target_id: targetId, link_type: linkType }) }); +} + +export async function deleteTaskLink(taskId: string, linkId: string): Promise { + return request(`/tasks/${taskId}/links/${linkId}`, { method: "DELETE" }); +}