Subtasks UI in TaskModal (Jira-style)
- Parent breadcrumb: 'TE-1 / TE-3' with clickable parent link - Subtasks section: colored status dots, clickable keys, assignee - onOpenTask callback: close current modal, open clicked task - SubtaskBrief interface in API types
This commit is contained in:
parent
aa7c45657b
commit
4770fc62d7
@ -218,6 +218,10 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) {
|
||||
setTasks((prev) => prev.filter((t) => t.id !== id));
|
||||
setSelectedTask(null);
|
||||
}}
|
||||
onOpenTask={(taskId) => {
|
||||
const t = tasks.find(t => t.id === taskId);
|
||||
if (t) setSelectedTask(t);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@ -36,9 +36,10 @@ interface Props {
|
||||
onClose: () => void;
|
||||
onUpdated: (task: Task) => void;
|
||||
onDeleted: (taskId: string) => void;
|
||||
onOpenTask?: (taskId: string) => void;
|
||||
}
|
||||
|
||||
export default function TaskModal({ task, projectId: _projectId, projectSlug: _projectSlug, onClose, onUpdated, onDeleted }: Props) {
|
||||
export default function TaskModal({ task, projectId: _projectId, projectSlug: _projectSlug, onClose, onUpdated, onDeleted, onOpenTask }: Props) {
|
||||
const [title, setTitle] = useState(task.title);
|
||||
const [description, setDescription] = useState(task.description || "");
|
||||
const [status, setStatus] = useState(task.status);
|
||||
@ -161,7 +162,18 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-xs text-[var(--muted)]">{task.key}</div>
|
||||
<div className="text-xs text-[var(--muted)]">
|
||||
{task.parent_key && task.parent_id ? (
|
||||
<span>
|
||||
<button
|
||||
onClick={() => { onClose(); setTimeout(() => onOpenTask?.(task.parent_id!), 100); }}
|
||||
className="text-[var(--accent)] hover:underline"
|
||||
>{task.parent_key}</button>
|
||||
<span className="mx-1">/</span>
|
||||
</span>
|
||||
) : null}
|
||||
{task.key}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row">
|
||||
@ -239,6 +251,34 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Subtasks */}
|
||||
{task.subtasks && task.subtasks.length > 0 && (
|
||||
<div>
|
||||
<div className="text-sm text-[var(--muted)] mb-2">Подзадачи ({task.subtasks.length})</div>
|
||||
<div className="space-y-1">
|
||||
{task.subtasks.map((sub) => (
|
||||
<div key={sub.id} className="flex items-center justify-between p-1.5 bg-[var(--bg)] border border-[var(--border)] rounded text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`w-2 h-2 rounded-full ${
|
||||
sub.status === "done" ? "bg-green-500" :
|
||||
sub.status === "in_progress" ? "bg-blue-500" :
|
||||
sub.status === "in_review" ? "bg-yellow-500" : "bg-gray-500"
|
||||
}`} />
|
||||
<button
|
||||
onClick={() => { onClose(); setTimeout(() => onOpenTask?.(sub.id), 100); }}
|
||||
className="text-[var(--accent)] hover:underline font-mono"
|
||||
>{sub.key}</button>
|
||||
<span>{sub.title}</span>
|
||||
</div>
|
||||
{sub.assignee && (
|
||||
<span className="text-[var(--muted)]">→ {sub.assignee.slug}</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Dependencies */}
|
||||
{(links.length > 0 || showAddLink) && (
|
||||
<div>
|
||||
|
||||
@ -127,10 +127,21 @@ export interface Task {
|
||||
position: number;
|
||||
time_spent: number;
|
||||
steps: Step[];
|
||||
subtasks: SubtaskBrief[];
|
||||
parent_key: string | null;
|
||||
parent_title: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface SubtaskBrief {
|
||||
id: string;
|
||||
key: string;
|
||||
title: string;
|
||||
status: string;
|
||||
assignee: MemberBrief | null;
|
||||
}
|
||||
|
||||
export interface Attachment {
|
||||
id: string;
|
||||
filename: string;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user