From eea37742408367ecac77cacca2589374dedb119f Mon Sep 17 00:00:00 2001
From: Jiang Bohan
Date: Thu, 9 Apr 2026 16:48:43 +0800
Subject: [PATCH] feat(views): refine sub-issue progress UI and add to board
view
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Move progress badge right after issue title (not pushed to far right)
- Increase progress ring size from 11px to 14px for better visibility
- Add sub-issue progress indicator to board card view
- Thread childProgressMap through BoardView → BoardColumn → BoardCard
---
.../views/issues/components/board-card.tsx | 18 ++++++++++++++++--
.../views/issues/components/board-column.tsx | 5 ++++-
.../views/issues/components/board-view.tsx | 8 +++++++-
.../views/issues/components/issues-page.tsx | 1 +
packages/views/issues/components/list-row.tsx | 18 ++++++++++--------
.../my-issues/components/my-issues-page.tsx | 1 +
.../projects/components/project-detail.tsx | 1 +
7 files changed, 40 insertions(+), 12 deletions(-)
diff --git a/packages/views/issues/components/board-card.tsx b/packages/views/issues/components/board-card.tsx
index eb47f97179..9dc9e60790 100644
--- a/packages/views/issues/components/board-card.tsx
+++ b/packages/views/issues/components/board-card.tsx
@@ -15,6 +15,8 @@ import { PriorityPicker, AssigneePicker, DueDatePicker } from "./pickers";
import { PRIORITY_CONFIG } from "@multica/core/issues/config";
import type { CardProperties } from "@multica/core/issues/stores/view-store";
import { useViewStore } from "@multica/core/issues/stores/view-store-context";
+import { ProgressRing } from "./progress-ring";
+import type { ChildProgress } from "./list-row";
function formatDate(date: string): string {
return new Date(date).toLocaleDateString("en-US", {
@@ -39,9 +41,11 @@ function PickerWrapper({ children }: { children: React.ReactNode }) {
export const BoardCardContent = memo(function BoardCardContent({
issue,
editable = false,
+ childProgress,
}: {
issue: Issue;
editable?: boolean;
+ childProgress?: ChildProgress;
}) {
const storeProperties = useViewStore((s) => s.cardProperties);
const priorityCfg = PRIORITY_CONFIG[issue.priority];
@@ -73,6 +77,16 @@ export const BoardCardContent = memo(function BoardCardContent({
{issue.title}
+ {/* Sub-issue progress */}
+ {childProgress && (
+
+
+
+ {childProgress.done}/{childProgress.total}
+
+
+ )}
+
{/* Description */}
{showDescription && (
@@ -173,7 +187,7 @@ const animateLayoutChanges: AnimateLayoutChanges = (args) => {
return defaultAnimateLayoutChanges(args);
};
-export const DraggableBoardCard = memo(function DraggableBoardCard({ issue }: { issue: Issue }) {
+export const DraggableBoardCard = memo(function DraggableBoardCard({ issue, childProgress }: { issue: Issue; childProgress?: ChildProgress }) {
const {
attributes,
listeners,
@@ -204,7 +218,7 @@ export const DraggableBoardCard = memo(function DraggableBoardCard({ issue }: {
href={`/issues/${issue.id}`}
className={`group block transition-colors ${isDragging ? "pointer-events-none" : ""}`}
>
-
+
);
diff --git a/packages/views/issues/components/board-column.tsx b/packages/views/issues/components/board-column.tsx
index 0743ba9128..d5244d60bd 100644
--- a/packages/views/issues/components/board-column.tsx
+++ b/packages/views/issues/components/board-column.tsx
@@ -18,17 +18,20 @@ import { useModalStore } from "@multica/core/modals";
import { useViewStoreApi } from "@multica/core/issues/stores/view-store-context";
import { StatusIcon } from "./status-icon";
import { DraggableBoardCard } from "./board-card";
+import type { ChildProgress } from "./list-row";
export function BoardColumn({
status,
issueIds,
issueMap,
+ childProgressMap,
totalCount,
footer,
}: {
status: IssueStatus;
issueIds: string[];
issueMap: Map;
+ childProgressMap?: Map;
totalCount?: number;
footer?: ReactNode;
}) {
@@ -102,7 +105,7 @@ export function BoardColumn({
>
{resolvedIssues.map((issue) => (
-
+
))}
{issueIds.length === 0 && (
diff --git a/packages/views/issues/components/board-view.tsx b/packages/views/issues/components/board-view.tsx
index f396c75147..34050a93fe 100644
--- a/packages/views/issues/components/board-view.tsx
+++ b/packages/views/issues/components/board-view.tsx
@@ -33,6 +33,7 @@ import { StatusIcon } from "./status-icon";
import { BoardColumn } from "./board-column";
import { BoardCardContent } from "./board-card";
import { InfiniteScrollSentinel } from "./infinite-scroll-sentinel";
+import type { ChildProgress } from "./list-row";
const COLUMN_IDS = new Set(ALL_STATUSES);
@@ -93,12 +94,15 @@ function findColumn(
return null;
}
+const EMPTY_PROGRESS_MAP = new Map();
+
export function BoardView({
issues,
allIssues,
visibleStatuses,
hiddenStatuses,
onMoveIssue,
+ childProgressMap = EMPTY_PROGRESS_MAP,
}: {
issues: Issue[];
allIssues: Issue[];
@@ -109,6 +113,7 @@ export function BoardView({
newStatus: IssueStatus,
newPosition?: number
) => void;
+ childProgressMap?: Map;
}) {
const sortBy = useViewStore((s) => s.sortBy);
const sortDirection = useViewStore((s) => s.sortDirection);
@@ -275,6 +280,7 @@ export function BoardView({
status={status}
issueIds={columns[status] ?? []}
issueMap={issueMapRef.current}
+ childProgressMap={childProgressMap}
totalCount={status === "done" ? doneTotal : undefined}
footer={
status === "done" && hasMore ? (
@@ -295,7 +301,7 @@ export function BoardView({
{activeIssue ? (
-
+
) : null}
diff --git a/packages/views/issues/components/issues-page.tsx b/packages/views/issues/components/issues-page.tsx
index c21ebab2c7..079d4166ba 100644
--- a/packages/views/issues/components/issues-page.tsx
+++ b/packages/views/issues/components/issues-page.tsx
@@ -163,6 +163,7 @@ export function IssuesPage() {
visibleStatuses={visibleStatuses}
hiddenStatuses={hiddenStatuses}
onMoveIssue={handleMoveIssue}
+ childProgressMap={childProgressMap}
/>
) : (
diff --git a/packages/views/issues/components/list-row.tsx b/packages/views/issues/components/list-row.tsx
index 0ff6cdd2ac..a000f94d8b 100644
--- a/packages/views/issues/components/list-row.tsx
+++ b/packages/views/issues/components/list-row.tsx
@@ -57,15 +57,17 @@ export const ListRow = memo(function ListRow({
{issue.identifier}
- {issue.title}
- {childProgress && (
-
-
-
- {childProgress.done}/{childProgress.total}
+
+ {issue.title}
+ {childProgress && (
+
+
+
+ {childProgress.done}/{childProgress.total}
+
-
- )}
+ )}
+
{issue.due_date && (
{formatDate(issue.due_date)}
diff --git a/packages/views/my-issues/components/my-issues-page.tsx b/packages/views/my-issues/components/my-issues-page.tsx
index a1a6c27283..e6b8da0f62 100644
--- a/packages/views/my-issues/components/my-issues-page.tsx
+++ b/packages/views/my-issues/components/my-issues-page.tsx
@@ -203,6 +203,7 @@ export function MyIssuesPage() {
visibleStatuses={visibleStatuses}
hiddenStatuses={hiddenStatuses}
onMoveIssue={handleMoveIssue}
+ childProgressMap={childProgressMap}
/>
) : (
diff --git a/packages/views/projects/components/project-detail.tsx b/packages/views/projects/components/project-detail.tsx
index de492a4b01..0ff8016694 100644
--- a/packages/views/projects/components/project-detail.tsx
+++ b/packages/views/projects/components/project-detail.tsx
@@ -160,6 +160,7 @@ function ProjectIssuesTab({ projectIssues }: { projectIssues: Issue[] }) {
visibleStatuses={visibleStatuses}
hiddenStatuses={hiddenStatuses}
onMoveIssue={handleMoveIssue}
+ childProgressMap={childProgressMap}
/>
) : (