diff --git a/src/app/issues/issue-detail/issue-detail.html b/src/app/issues/issue-detail/issue-detail.html
index 191a19c..467e7ff 100644
--- a/src/app/issues/issue-detail/issue-detail.html
+++ b/src/app/issues/issue-detail/issue-detail.html
@@ -186,7 +186,11 @@
@if (!isEpicIssue) {
diff --git a/src/app/issues/issue-detail/issue-detail.spec.ts b/src/app/issues/issue-detail/issue-detail.spec.ts
index b6d00d6..19588c6 100644
--- a/src/app/issues/issue-detail/issue-detail.spec.ts
+++ b/src/app/issues/issue-detail/issue-detail.spec.ts
@@ -580,6 +580,39 @@ describe('IssueDetail — existing issue', () => {
});
});
+ describe('epicEstimatedTime', () => {
+ beforeEach(() => {
+ (component as any).issue.type = 'Epic';
+ (component as any).issue.name = 'Test Epic';
+ });
+
+ it('returns null when there are no child issues', () => {
+ expect((component as any).epicEstimatedTime).toBeNull();
+ });
+
+ it('returns null when all children have null estimatedTime', () => {
+ store.upsert(makeIssue({ id: 200, epic: 'Test Epic', estimatedTime: null }));
+ expect((component as any).epicEstimatedTime).toBeNull();
+ });
+
+ it('returns the sum of children estimatedTime', () => {
+ store.upsert(makeIssue({ id: 200, epic: 'Test Epic', estimatedTime: 8 }));
+ store.upsert(makeIssue({ id: 201, epic: 'Test Epic', estimatedTime: 4 }));
+ expect((component as any).epicEstimatedTime).toBe(12);
+ });
+
+ it('ignores children with null estimatedTime in the sum', () => {
+ store.upsert(makeIssue({ id: 200, epic: 'Test Epic', estimatedTime: 8 }));
+ store.upsert(makeIssue({ id: 201, epic: 'Test Epic', estimatedTime: null }));
+ expect((component as any).epicEstimatedTime).toBe(8);
+ });
+
+ it('includes children linked via dependsOnIds', () => {
+ store.upsert(makeIssue({ id: 200, dependsOnIds: [1], estimatedTime: 6 }));
+ expect((component as any).epicEstimatedTime).toBe(6);
+ });
+ });
+
describe('create-in-epic flow', () => {
beforeEach(() => {
(component as any).issue.type = 'Epic';
diff --git a/src/app/issues/issue-detail/issue-detail.ts b/src/app/issues/issue-detail/issue-detail.ts
index 1eb35b3..c75737b 100644
--- a/src/app/issues/issue-detail/issue-detail.ts
+++ b/src/app/issues/issue-detail/issue-detail.ts
@@ -150,6 +150,13 @@ export class IssueDetail {
this.issue.type = value;
}
+ protected get epicEstimatedTime(): number | null {
+ const times = this.composedIssues
+ .filter((i): i is IssueEntity & { estimatedTime: number } => i.estimatedTime !== null)
+ .map((i) => i.estimatedTime);
+ return times.length === 0 ? null : times.reduce((a, b) => a + b, 0);
+ }
+
protected get epicIssues(): IssueEntity[] {
return this.issues().filter((issue) => issue.type === 'Epic');
}
diff --git a/src/app/milestones/milestone-detail/milestone-detail.html b/src/app/milestones/milestone-detail/milestone-detail.html
index 9e7169b..9c786d1 100644
--- a/src/app/milestones/milestone-detail/milestone-detail.html
+++ b/src/app/milestones/milestone-detail/milestone-detail.html
@@ -94,6 +94,10 @@
{{ progress }}%
+