feat(FN-971): add mission status summary with progress bar to mission list

- Add MissionSummary type and getMissionSummary method to MissionStore
- Include mission summary data in GET /missions API endpoint
- Update frontend types and API client for mission summary
- Display mission status summary with progress bar on MissionManager list cards
- Add CSS styles for progress bar and summary display
- Update MissionManager tests for summary display
This commit is contained in:
gsxdsm
2026-04-05 17:19:48 -07:00
parent f6bd237f46
commit 88df7387b5
8 changed files with 189 additions and 8 deletions

View File

@@ -110,7 +110,7 @@ export type {
FeatureLinkedPayload,
} from "./mission-types.js";
export { MissionStore } from "./mission-store.js";
export type { MissionStoreEvents } from "./mission-store.js";
export type { MissionStoreEvents, MissionSummary } from "./mission-store.js";
// ── Central Infrastructure (Multi-Project Support) ───────────────────────────

View File

@@ -31,6 +31,22 @@ import type {
InterviewState,
} from "./mission-types.js";
// ── Mission Summary Type ─────────────────────────────────────────────
/** Status summary for a mission, computed from its hierarchy. */
export interface MissionSummary {
/** Total number of milestones in the mission */
totalMilestones: number;
/** Number of milestones with status "complete" */
completedMilestones: number;
/** Total number of features across all slices */
totalFeatures: number;
/** Number of features with status "done" */
completedFeatures: number;
/** Computed progress percentage (0100), based on features or milestones */
progressPercent: number;
}
// ── Event Types ─────────────────────────────────────────────────────
export interface MissionStoreEvents {
@@ -247,6 +263,51 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
return (rows as any[]).map((row) => this.rowToMission(row));
}
/**
* Get a status summary for a mission, computing milestone and feature counts
* and progress percentage from the hierarchy.
*
* Progress is calculated as:
* - (completedFeatures / totalFeatures) * 100 if there are features
* - (completedMilestones / totalMilestones) * 100 if there are milestones but no features
* - 0 otherwise
*
* @param missionId - Mission ID
* @returns MissionSummary with counts and progress
*/
getMissionSummary(missionId: string): MissionSummary {
const milestones = this.listMilestones(missionId);
const totalMilestones = milestones.length;
const completedMilestones = milestones.filter((m) => m.status === "complete").length;
let totalFeatures = 0;
let completedFeatures = 0;
for (const milestone of milestones) {
const slices = this.listSlices(milestone.id);
for (const slice of slices) {
const features = this.listFeatures(slice.id);
totalFeatures += features.length;
completedFeatures += features.filter((f) => f.status === "done").length;
}
}
let progressPercent = 0;
if (totalFeatures > 0) {
progressPercent = Math.round((completedFeatures / totalFeatures) * 100);
} else if (totalMilestones > 0) {
progressPercent = Math.round((completedMilestones / totalMilestones) * 100);
}
return {
totalMilestones,
completedMilestones,
totalFeatures,
completedFeatures,
progressPercent,
};
}
/**
* Update a mission.
*