diff --git a/apps/console/src/pages/organizations/frameworks/FrameworkView.tsx b/apps/console/src/pages/organizations/frameworks/FrameworkView.tsx index 9d3e91bb8..4d98f804c 100644 --- a/apps/console/src/pages/organizations/frameworks/FrameworkView.tsx +++ b/apps/console/src/pages/organizations/frameworks/FrameworkView.tsx @@ -18,7 +18,6 @@ import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { Progress } from "@/components/ui/progress"; import type { FrameworkViewQuery as FrameworkViewQueryType } from "./__generated__/FrameworkViewQuery.graphql"; import { PageTemplate } from "@/components/PageTemplate"; import { FrameworkViewSkeleton } from "./FrameworkPage"; @@ -47,7 +46,6 @@ const FrameworkViewQuery = graphql` } `; -// Define control type for better type safety interface Control { id?: string; name?: string; @@ -64,6 +62,8 @@ interface Category { description: string; progress: number; controls: Control[]; + doneCount: number; + totalCount: number; } function FrameworkViewContent({ @@ -94,20 +94,44 @@ function FrameworkViewContent({ } }; + const processedControls = controls.map((control) => ({ + ...control, + status: mapStateToStatus(control.state), + })); + + // Calculate global progress + const implementedCount = processedControls.filter( + (control) => control.status === "complete" + ).length; + const notApplicableCount = processedControls.filter( + (control) => control.status === "not-applicable" + ).length; + const totalControls = processedControls.length; + + // Include not-applicable as effectively "complete" for progress percentage + const effectiveCompletedCount = implementedCount + notApplicableCount; + const globalProgress = totalControls + ? Math.round((effectiveCompletedCount / totalControls) * 100) + : 0; + + // Get global status counts + const globalStatusCounts = processedControls.reduce((acc, control) => { + if (control.status) { + acc[control.status] = (acc[control.status] || 0) + 1; + } + return acc; + }, {} as Record); + // Group controls by category - const controlsByCategory = controls.reduce((acc, control) => { + const controlsByCategory = processedControls.reduce((acc, control) => { if (!control?.category) return acc; if (!acc[control.category]) { acc[control.category] = []; } - acc[control.category].push({ - ...control, - status: mapStateToStatus(control.state), - }); + acc[control.category].push(control); return acc; }, {} as Record); - // Toggle category expansion const toggleCategory = (categoryId: string) => { setExpandedCategories((prev) => prev.includes(categoryId) @@ -116,18 +140,19 @@ function FrameworkViewContent({ ); }; - // Process categories with their controls const categories: Category[] = Object.entries(controlsByCategory) .map(([categoryName, categoryControls]) => { - // Calculate progress - const implementedCount = categoryControls.filter( + const catImplementedCount = categoryControls.filter( (control) => control.status === "complete" ).length; - const applicableCount = categoryControls.filter( - (control) => control.status !== "not-applicable" + const catNotApplicableCount = categoryControls.filter( + (control) => control.status === "not-applicable" ).length; - const progress = applicableCount - ? Math.round((implementedCount / applicableCount) * 100) + // Consider both "complete" and "not-applicable" as done for category progress + const catDoneCount = catImplementedCount + catNotApplicableCount; + const catTotalCount = categoryControls.length; + const progress = catTotalCount + ? Math.round((catDoneCount / catTotalCount) * 100) : 0; return { @@ -136,6 +161,8 @@ function FrameworkViewContent({ description: `Controls related to ${categoryName.toLowerCase()}`, progress: progress, controls: categoryControls, + doneCount: catDoneCount, + totalCount: catTotalCount, }; }) .filter((category) => category.controls.length > 0) @@ -146,27 +173,18 @@ function FrameworkViewContent({ case "complete": return ; case "in-progress": - return ; + return ; case "not-started": - return ; + return ; case "incomplete": return ; case "not-applicable": - return ; + return ; default: return null; } }; - const getStatusCounts = (controls: Control[]) => { - return controls.reduce((acc, control) => { - if (control.status) { - acc[control.status] = (acc[control.status] || 0) + 1; - } - return acc; - }, {} as Record); - }; - return ( } > + {/* Global Progress Summary */} +
+
+

Framework Implementation

+ + {globalProgress}% complete + +
+ + {/* Progress bar container */} +
+ {/* Segmented progress bar */} +
+ {/* Complete segment */} + {globalStatusCounts.complete > 0 && ( +
+ )} + {/* In-progress segment */} + {globalStatusCounts["in-progress"] > 0 && ( +
+ )} + {/* Incomplete segment */} + {globalStatusCounts.incomplete > 0 && ( +
+ )} + {/* Not applicable segment */} + {globalStatusCounts["not-applicable"] > 0 && ( +
+ )} + {/* Not started segment */} + {globalStatusCounts["not-started"] > 0 && ( +
+ )} +
+
+ + {/* Status legend - reorder to match progress bar */} +
+ {globalStatusCounts.complete > 0 && ( +
+
+ Complete ({globalStatusCounts.complete}) +
+ )} + {globalStatusCounts["in-progress"] > 0 && ( +
+
+ In Progress ({globalStatusCounts["in-progress"]}) +
+ )} + {globalStatusCounts.incomplete > 0 && ( +
+
+ Incomplete ({globalStatusCounts.incomplete}) +
+ )} + {globalStatusCounts["not-applicable"] > 0 && ( +
+
+ + Not Applicable ({globalStatusCounts["not-applicable"]}) + +
+ )} + {globalStatusCounts["not-started"] > 0 && ( +
+
+ Not Started ({globalStatusCounts["not-started"]}) +
+ )} +
+
+
{categories.map((category) => { const isExpanded = expandedCategories.includes(category.id); - const statusCounts = getStatusCounts(category.controls); return (
{category.name}
-
- - {statusCounts.complete || 0} Complete - - - {statusCounts["in-progress"] || 0} In Progress - - - {statusCounts.incomplete || 0} Incomplete - +
+ + {category.doneCount} / {category.totalCount} +
-
-
- Implementation progress - {category.progress}% -
- -