11'use client' ;
22
3+ import { Catalog , Group , ListChecked , Policy } from '@carbon/icons-react' ;
34import { Card , CardContent , CardHeader , CardTitle } from '@comp/ui/card' ;
4- import { Progress } from '@comp/ui/progress' ;
5- import { FrameworkInstance } from '@db' ;
6- import { ComplianceProgressChart } from './ComplianceProgressChart' ;
7- import { PeopleChart } from './PeopleChart' ;
8- import { PoliciesChart } from './PoliciesChart' ;
9- import { TasksChart } from './TasksChart' ;
5+ import { Button } from '@trycompai/design-system' ;
6+ import { ArrowRight } from '@trycompai/design-system/icons' ;
7+ import { useRouter } from 'next/navigation' ;
108
119export function ComplianceOverview ( {
12- frameworks,
10+ organizationId,
11+ overallComplianceScore,
1312 totalPolicies,
1413 publishedPolicies,
1514 totalTasks,
1615 doneTasks,
16+ totalDocuments,
17+ completedDocuments,
1718 totalMembers,
1819 completedMembers,
1920} : {
20- frameworks : FrameworkInstance [ ] ;
21+ organizationId : string ;
22+ overallComplianceScore : number ;
2123 totalPolicies : number ;
2224 publishedPolicies : number ;
2325 totalTasks : number ;
2426 doneTasks : number ;
27+ totalDocuments : number ;
28+ completedDocuments : number ;
2529 totalMembers : number ;
2630 completedMembers : number ;
2731} ) {
28- const compliancePercentage = complianceProgress (
29- publishedPolicies ,
30- doneTasks ,
31- totalPolicies ,
32- totalTasks ,
33- totalMembers ,
34- completedMembers ,
35- ) ;
32+ const router = useRouter ( ) ;
33+
34+ const metrics = [
35+ {
36+ id : 'policies' ,
37+ label : 'Policies' ,
38+ subtitle : `${ publishedPolicies } /${ totalPolicies } policies published` ,
39+ percentage : getPercentage ( publishedPolicies , totalPolicies ) ,
40+ icon : Policy ,
41+ total : totalPolicies ,
42+ href : `/${ organizationId } /policies` ,
43+ } ,
44+ {
45+ id : 'tasks' ,
46+ label : 'Evidence' ,
47+ subtitle : `${ doneTasks } /${ totalTasks } evidence tasks complete` ,
48+ percentage : getPercentage ( doneTasks , totalTasks ) ,
49+ icon : ListChecked ,
50+ total : totalTasks ,
51+ href : `/${ organizationId } /tasks` ,
52+ } ,
53+ {
54+ id : 'documents' ,
55+ label : 'Documents' ,
56+ subtitle : `${ completedDocuments } /${ totalDocuments } documents up to date` ,
57+ percentage : getPercentage ( completedDocuments , totalDocuments ) ,
58+ icon : Catalog ,
59+ total : totalDocuments ,
60+ href : `/${ organizationId } /documents` ,
61+ } ,
62+ {
63+ id : 'people' ,
64+ label : 'People' ,
65+ subtitle : `${ completedMembers } /${ totalMembers } people complete` ,
66+ percentage : getPercentage ( completedMembers , totalMembers ) ,
67+ icon : Group ,
68+ total : totalMembers ,
69+ href : `/${ organizationId } /people/all` ,
70+ } ,
71+ ] as const ;
3672
37- const policiesPercentage = Math . round ( ( publishedPolicies / Math . max ( totalPolicies , 1 ) ) * 100 ) ;
38- const tasksPercentage = Math . round ( ( doneTasks / Math . max ( totalTasks , 1 ) ) * 100 ) ;
39- const peoplePercentage = Math . round ( ( completedMembers / Math . max ( totalMembers , 1 ) ) * 100 ) ;
73+ const compliancePercentage = overallComplianceScore ;
4074
4175 return (
42- < Card className = "flex flex-col overflow-hidden border h-full " >
43- < CardHeader className = "pb-2 " >
76+ < Card className = "flex h-full flex-col overflow-hidden border" >
77+ < CardHeader className = "pb-3 " >
4478 < div className = "flex items-center justify-between" >
45- < CardTitle className = "flex items-center gap-2" > { ' Overall Compliance Progress' } </ CardTitle >
79+ < CardTitle className = "flex items-center gap-2" > Overall Compliance Progress</ CardTitle >
4680 </ div >
47-
4881 < div className = "bg-secondary/50 relative mt-2 h-1 w-full overflow-hidden rounded-full" >
4982 < div
5083 className = "bg-primary h-full transition-all"
@@ -54,107 +87,93 @@ export function ComplianceOverview({
5487 />
5588 </ div >
5689 </ CardHeader >
57- < CardContent className = "flex flex-col flex-1 justify-center" >
58- { /* Progress bars for smaller screens */ }
59- < div className = "space-y-4 lg:hidden mt-4" >
60- { /* Overall Compliance Progress Bar */ }
61- < div className = "space-y-3" >
62- < div className = "flex items-center justify-between" >
63- < div className = "flex items-center gap-2" >
64- < div className = "h-2 w-2 rounded-full bg-primary" > </ div >
65- < span className = "text-sm" > Overall Compliance</ span >
66- </ div >
67- < span className = "font-medium text-sm tabular-nums" > { compliancePercentage } %</ span >
68- </ div >
69- < Progress value = { compliancePercentage } className = "h-1" />
70- </ div >
71-
72- { /* Policies Progress Bar */ }
73- < div className = "space-y-3" >
74- < div className = "flex items-center justify-between" >
75- < div className = "flex items-center gap-2" >
76- < div className = "h-2 w-2 rounded-full bg-blue-500" > </ div >
77- < span className = "text-sm" > Policies Published</ span >
78- </ div >
79- < span className = "font-medium text-sm tabular-nums" > { policiesPercentage } %</ span >
80- </ div >
81- < Progress value = { policiesPercentage } className = "h-1" />
82- </ div >
83-
84- { /* Tasks Progress Bar */ }
85- < div className = "space-y-3" >
86- < div className = "flex items-center justify-between" >
87- < div className = "flex items-center gap-2" >
88- < div className = "h-2 w-2 rounded-full bg-yellow-500" > </ div >
89- < span className = "text-sm" > Tasks Completed</ span >
90- </ div >
91- < span className = "font-medium text-sm tabular-nums" > { tasksPercentage } %</ span >
92- </ div >
93- < Progress value = { tasksPercentage } className = "h-1" />
94- </ div >
95-
96- { /* People Progress Bar */ }
97- < div className = "space-y-3" >
98- < div className = "flex items-center justify-between" >
99- < div className = "flex items-center gap-2" >
100- < div className = "h-2 w-2 rounded-full bg-green-500" > </ div >
101- < span className = "text-sm" > People Score</ span >
90+ < CardContent className = "flex flex-1 flex-col p-0" >
91+ < div className = "divide-y divide-border" >
92+ { metrics . map ( ( metric ) => {
93+ const Icon = metric . icon ;
94+ return (
95+ < div key = { metric . id } className = "flex items-center justify-between px-4 py-3" >
96+ < div className = "flex min-w-0 items-center gap-2 sm:gap-3" >
97+ < div className = "hidden h-9 w-9 place-items-center rounded-md border border-border/70 bg-muted/30 sm:grid" >
98+ < Icon className = "h-4 w-4 text-foreground" />
99+ </ div >
100+ < div className = "min-w-0" >
101+ < p className = "truncate text-sm font-medium text-foreground" > { metric . label } </ p >
102+ < p className = "truncate text-xs text-muted-foreground" > { metric . subtitle } </ p >
103+ { metric . percentage < 100 && (
104+ < div className = "mt-1 md:hidden" >
105+ < Button variant = "link" onClick = { ( ) => router . push ( metric . href ) } >
106+ Continue
107+ </ Button >
108+ </ div >
109+ ) }
110+ </ div >
111+ </ div >
112+ < div className = "flex items-center gap-3" >
113+ { metric . percentage < 100 && (
114+ < div className = "hidden md:block" >
115+ < Button
116+ size = "sm"
117+ variant = "outline"
118+ iconRight = { < ArrowRight size = { 14 } /> }
119+ onClick = { ( ) => router . push ( metric . href ) }
120+ >
121+ Continue
122+ </ Button >
123+ </ div >
124+ ) }
125+ < MiniProgressRing percentage = { metric . percentage } />
126+ </ div >
102127 </ div >
103- < span className = "font-medium text-sm tabular-nums" > { peoplePercentage } %</ span >
104- </ div >
105- < Progress value = { peoplePercentage } className = "h-1" />
106- </ div >
107- </ div >
108-
109- { /* Charts for larger screens */ }
110- < div className = "hidden lg:flex lg:flex-col lg:items-center lg:justify-center lg:gap-4" >
111- < ComplianceProgressChart
112- data = { { score : compliancePercentage , remaining : 100 - compliancePercentage } }
113- />
114- < div className = "flex flex-row items-center justify-center gap-3" >
115- < div className = "flex flex-col items-center justify-center" >
116- < PoliciesChart
117- data = { { published : policiesPercentage , draft : 100 - policiesPercentage } }
118- />
119- </ div >
120- < div className = "flex flex-col items-center justify-center" >
121- < TasksChart data = { { done : tasksPercentage , remaining : 100 - tasksPercentage } } />
122- </ div >
123- < div className = "flex flex-col items-center justify-center" >
124- < PeopleChart
125- data = { { completed : peoplePercentage , remaining : 100 - peoplePercentage } }
126- />
127- </ div >
128- </ div >
128+ ) ;
129+ } ) }
129130 </ div >
130131 </ CardContent >
131132 </ Card >
132133 ) ;
133134}
134135
135- function complianceProgress (
136- publishedPolicies : number ,
137- doneTasks : number ,
138- totalPolicies : number ,
139- totalTasks : number ,
140- totalMembers : number ,
141- completedMembers : number ,
142- ) {
143- // Calculate individual percentages
144- const policiesPercentage = totalPolicies > 0 ? publishedPolicies / totalPolicies : 0 ;
145- const tasksPercentage = totalTasks > 0 ? doneTasks / totalTasks : 0 ;
146- const peoplePercentage = totalMembers > 0 ? completedMembers / totalMembers : 0 ;
147-
148- // Calculate average of the three percentages
149- const totalCategories = [ totalPolicies , totalTasks , totalMembers ] . filter (
150- ( count ) => count > 0 ,
151- ) . length ;
152-
153- if ( totalCategories === 0 ) return 0 ;
136+ function getPercentage ( done : number , total : number ) : number {
137+ if ( total <= 0 ) return 0 ;
138+ return Math . round ( ( done / total ) * 100 ) ;
139+ }
154140
155- const averagePercentage =
156- ( policiesPercentage + tasksPercentage + peoplePercentage ) / totalCategories ;
157- const complianceScore = Math . round ( averagePercentage * 100 ) ;
141+ function MiniProgressRing ( { percentage } : { percentage : number } ) {
142+ const clamped = Math . max ( 0 , Math . min ( 100 , percentage ) ) ;
143+ const radius = 18 ;
144+ const stroke = 4 ;
145+ const normalizedRadius = radius - stroke / 2 ;
146+ const circumference = normalizedRadius * 2 * Math . PI ;
147+ const strokeDashoffset = circumference - ( clamped / 100 ) * circumference ;
158148
159- return complianceScore ;
149+ return (
150+ < div className = "relative h-14 w-14 shrink-0" >
151+ < svg className = "h-14 w-14 -rotate-90" viewBox = "0 0 36 36" >
152+ < circle
153+ cx = "18"
154+ cy = "18"
155+ r = { normalizedRadius }
156+ stroke = "currentColor"
157+ strokeWidth = { stroke }
158+ fill = "transparent"
159+ className = "text-muted/70"
160+ />
161+ < circle
162+ cx = "18"
163+ cy = "18"
164+ r = { normalizedRadius }
165+ stroke = "currentColor"
166+ strokeWidth = { stroke }
167+ fill = "transparent"
168+ strokeDasharray = { circumference }
169+ strokeDashoffset = { strokeDashoffset }
170+ strokeLinecap = "round"
171+ className = "text-primary transition-all duration-500 ease-out"
172+ />
173+ </ svg >
174+ < div className = "absolute inset-0 grid place-items-center text-[11px] font-semibold tabular-nums text-foreground" >
175+ { clamped } %
176+ </ div >
177+ </ div >
178+ ) ;
160179}
0 commit comments