fix(ui): group plan tasks by User Story or Phase to support Spec Kit format
This commit is contained in:
@@ -1262,39 +1262,75 @@ function TasksPanel({
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<ul style={taskList}>
|
||||
{visible.map((t) => (
|
||||
<li key={t.id}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedId(t.id);
|
||||
setCreating(false);
|
||||
}}
|
||||
style={selectedId === t.id ? taskItemActive : taskItem}
|
||||
>
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", gap: 8 }}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
...taskStatusDot,
|
||||
background: TASK_STATUS_COLOR[t.status],
|
||||
}}
|
||||
/>
|
||||
<span style={taskItemTitle}>{taskTitle(t)}</span>
|
||||
<div>
|
||||
{(() => {
|
||||
const groups: Record<string, typeof visible> = {};
|
||||
for (const t of visible) {
|
||||
const title = taskTitle(t);
|
||||
const match = title.match(/^\[(.*?)\]\s*(.*)$/);
|
||||
const groupName = match ? match[1] : "Uncategorized";
|
||||
const cleanTitle = match ? match[2] : title;
|
||||
|
||||
if (!groups[groupName]) groups[groupName] = [];
|
||||
groups[groupName].push({ ...t, title: cleanTitle });
|
||||
}
|
||||
|
||||
const groupKeys = Object.keys(groups).sort((a, b) => {
|
||||
if (a === "Uncategorized") return 1;
|
||||
if (b === "Uncategorized") return -1;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
return groupKeys.map(groupKey => (
|
||||
<div key={groupKey} style={{ marginBottom: 16 }}>
|
||||
<div style={{
|
||||
fontSize: "0.75rem",
|
||||
fontWeight: 600,
|
||||
color: "var(--fg-mute)",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.04em",
|
||||
marginBottom: 8,
|
||||
marginLeft: 2,
|
||||
}}>
|
||||
{groupKey}
|
||||
</div>
|
||||
<div style={taskItemMeta}>
|
||||
<span>{TASK_STATUS_LABEL[t.status]}</span>
|
||||
<span style={{ color: INK.muted }}>·</span>
|
||||
<span>
|
||||
{relativeTime(t.doneAt ?? t.startedAt ?? t.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul style={taskList}>
|
||||
{groups[groupKey].map((t) => (
|
||||
<li key={t.id}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedId(t.id);
|
||||
setCreating(false);
|
||||
}}
|
||||
style={selectedId === t.id ? taskItemActive : taskItem}
|
||||
>
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", gap: 8 }}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
...taskStatusDot,
|
||||
background: TASK_STATUS_COLOR[t.status],
|
||||
}}
|
||||
/>
|
||||
<span style={taskItemTitle}>{t.title}</span>
|
||||
</div>
|
||||
<div style={taskItemMeta}>
|
||||
<span>{TASK_STATUS_LABEL[t.status]}</span>
|
||||
<span style={{ color: INK.muted }}>·</span>
|
||||
<span>
|
||||
{relativeTime(t.doneAt ?? t.startedAt ?? t.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
));
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user