@@ -9,23 +9,27 @@ import {
99} from 'recharts' ;
1010import { useTheme } from '@mui/material/styles' ;
1111import { useTranslation } from 'next-i18next' ;
12+
1213interface ChartDataItem {
1314 name : string ;
1415 value : number ;
1516 color : string ;
1617}
18+
1719interface CourseStatus {
1820 userId : string ;
1921 courseId : string ;
2022 status : 'completed' | 'inprogress' ;
2123}
24+
2225interface CourseCompletionProps {
2326 mandatoryCourses : CourseStatus [ ] ;
2427 nonMandatoryCourses : CourseStatus [ ] ;
2528 userIds ?: string [ ] ;
2629 mandatoryCourseIds ?: string [ ] ;
2730 optionalCourseIds ?: string [ ] ;
2831}
32+
2933const CourseCompletion : React . FC < CourseCompletionProps > = ( {
3034 mandatoryCourses,
3135 nonMandatoryCourses,
@@ -34,137 +38,98 @@ const CourseCompletion: React.FC<CourseCompletionProps> = ({
3438 optionalCourseIds,
3539} ) => {
3640 const { t } = useTranslation ( ) ;
37- console . log ( 'userIds=========>' , userIds ) ;
38- console . log ( 'mandatoryCourseIds=========>' , mandatoryCourseIds ) ;
39- console . log ( 'optionalCourseIds=========>' , optionalCourseIds ) ;
40- console . log ( 'nonMandatoryCoursesCourseCompletion' , nonMandatoryCourses ) ;
4141 const theme = useTheme ( ) ;
4242 const isMobile = useMediaQuery ( theme . breakpoints . down ( 'sm' ) ) ;
43- const prepareMandatoryData = ( ) : ChartDataItem [ ] => {
43+
44+ // ✅ FINAL LOGIC WITH INPROGRESS SUPPORT
45+ const calculateUserCompletion = (
46+ courses : CourseStatus [ ] ,
47+ courseIds ?: string [ ]
48+ ) : ChartDataItem [ ] => {
4449 let completed = 0 ;
4550 let inProgress = 0 ;
4651
47- // Check if we have the required props for validation
48- if ( userIds && mandatoryCourseIds && userIds . length > 0 && mandatoryCourseIds . length > 0 ) {
49- // Group courses by courseId to check completion status per course
50- const coursesByCourseId = new Map < string , Map < string , string > > ( ) ;
51-
52- mandatoryCourses . forEach ( ( course ) => {
53- if ( ! coursesByCourseId . has ( course . courseId ) ) {
54- coursesByCourseId . set ( course . courseId , new Map ( ) ) ;
55- }
56- const userStatusMap = coursesByCourseId . get ( course . courseId ) ;
57- if ( userStatusMap ) {
58- userStatusMap . set ( course . userId , course . status ) ;
59- }
60- } ) ;
61-
62- // Check each mandatory course
63- mandatoryCourseIds . forEach ( ( courseId ) => {
64- const userStatusMap = coursesByCourseId . get ( courseId ) ;
65- // Check if all users have completed this course
66- // A user must have an entry with status 'completed' to be considered completed
67- const allUsersCompleted = userIds . every ( ( userId ) => {
68- const status = userStatusMap ?. get ( userId ) ;
69- return status === 'completed' ;
70- } ) ;
52+ if ( userIds && courseIds && userIds . length > 0 && courseIds . length > 0 ) {
53+ // user → completed courses
54+ const completedMap = new Map < string , Set < string > > ( ) ;
55+ // user → any activity (completed OR inprogress)
56+ const activityMap = new Map < string , boolean > ( ) ;
7157
72- if ( allUsersCompleted ) {
73- completed ++ ;
74- } else {
75- // If not all users completed, count as inProgress
76- // This includes: some users in progress, some not started, or mix of both
77- inProgress ++ ;
58+ courses . forEach ( ( course ) => {
59+ // track activity
60+ if ( ! activityMap . has ( course . userId ) ) {
61+ activityMap . set ( course . userId , false ) ;
7862 }
79- } ) ;
80- } else {
81- // Fallback to original logic if props are not available
82- completed = mandatoryCourses . filter ( course => course . status === 'completed' ) . length ;
83- inProgress = mandatoryCourses . filter ( course => course . status === 'inprogress' ) . length ;
84- }
85-
86- return [
87- {
88- name : t ( 'COMPLETED' ) ,
89- value : completed ,
90- color : '#4CAF50' ,
91- } ,
92- {
93- name : t ( 'IN_PROGRESS' ) ,
94- value : inProgress ,
95- color : '#FFC107' ,
96- } ,
97- ] ;
98- } ;
99- const prepareNonMandatoryData = ( ) : ChartDataItem [ ] => {
100- let completed = 0 ;
101- let inProgress = 0 ;
10263
103- // Check if we have the required props for validation
104- if ( userIds && optionalCourseIds && userIds . length > 0 && optionalCourseIds . length > 0 ) {
105- // Group courses by courseId to check completion status per course
106- const coursesByCourseId = new Map < string , Map < string , string > > ( ) ;
107-
108- nonMandatoryCourses . forEach ( ( course ) => {
109- if ( ! coursesByCourseId . has ( course . courseId ) ) {
110- coursesByCourseId . set ( course . courseId , new Map ( ) ) ;
64+ if ( course . status === 'completed' || course . status === 'inprogress' ) {
65+ activityMap . set ( course . userId , true ) ;
11166 }
112- const userStatusMap = coursesByCourseId . get ( course . courseId ) ;
113- if ( userStatusMap ) {
114- userStatusMap . set ( course . userId , course . status ) ;
67+
68+ // track completed
69+ if ( course . status === 'completed' ) {
70+ if ( ! completedMap . has ( course . userId ) ) {
71+ completedMap . set ( course . userId , new Set ( ) ) ;
72+ }
73+ completedMap . get ( course . userId ) ?. add ( course . courseId ) ;
11574 }
11675 } ) ;
11776
118- // Check each optional course
119- optionalCourseIds . forEach ( ( courseId ) => {
120- const userStatusMap = coursesByCourseId . get ( courseId ) ;
121- // Check if all users have completed this course
122- // A user must have an entry with status 'completed' to be considered completed
123- const allUsersCompleted = userIds . every ( ( userId ) => {
124- const status = userStatusMap ?. get ( userId ) ;
125- return status === 'completed' ;
126- } ) ;
77+ userIds . forEach ( ( userId ) => {
78+ const completedCourses = completedMap . get ( userId ) || new Set ( ) ;
79+ const hasActivity = activityMap . get ( userId ) || false ;
80+
81+ const completedCount = courseIds . filter ( ( courseId ) =>
82+ completedCourses . has ( courseId )
83+ ) . length ;
12784
128- if ( allUsersCompleted ) {
85+ if ( completedCount === courseIds . length ) {
86+ // ✅ all completed
12987 completed ++ ;
130- } else {
131- // If not all users completed, count as inProgress
132- // This includes: some users in progress, some not started, or mix of both
88+ } else if ( hasActivity ) {
89+ // ⏳ has at least one completed OR inprogress
13390 inProgress ++ ;
13491 }
92+ // ❌ no activity → ignore
13593 } ) ;
94+
13695 } else {
137- // Fallback to original logic if props are not available
138- completed = nonMandatoryCourses . filter ( course => course . status === 'completed' ) . length ;
139- inProgress = nonMandatoryCourses . filter ( course => course . status === 'inprogress' ) . length ;
96+ // ✅ FALLBACK LOGIC
97+ completed = courses . filter ( c => c . status === 'completed' ) . length ;
98+ inProgress = courses . filter ( c => c . status === 'inprogress' ) . length ;
14099 }
141100
142101 return [
143- {
144- name : t ( 'COMPLETED' ) ,
145- value : completed ,
146- color : '#4CAF50' ,
147- } ,
148- {
149- name : t ( 'IN_PROGRESS' ) ,
150- value : inProgress ,
151- color : '#FFC107' ,
152- } ,
102+ { name : t ( 'COMPLETED' ) , value : completed , color : '#4CAF50' } ,
103+ { name : t ( 'IN_PROGRESS' ) , value : inProgress , color : '#FFC107' } ,
153104 ] ;
154105 } ;
106+
107+ const prepareMandatoryData = ( ) =>
108+ calculateUserCompletion ( mandatoryCourses , mandatoryCourseIds ) ;
109+
110+ const prepareNonMandatoryData = ( ) =>
111+ calculateUserCompletion ( nonMandatoryCourses , optionalCourseIds ) ;
112+
155113 const renderDonutChart = ( data : ChartDataItem [ ] , title : string ) => {
156114 const backgroundData = [ { value : 100 } ] ;
157- // Responsive radius values: smaller for mobile, original for desktop
158115 const innerRadius = isMobile ? 38 : 48 ;
159116 const outerRadius = isMobile ? 55 : 68 ;
160-
117+
161118 return (
162- < Box sx = { { flex : 1 , display : 'flex' , flexDirection : 'column' , minWidth : 0 , maxWidth : '100%' } } >
163- < Typography variant = "body2" fontWeight = { 500 } color = "text.secondary" gutterBottom sx = { { mb : { xs : 1 , sm : 2 } , fontSize : { xs : '0.875rem' , sm : '0.875rem' } , flexShrink : 0 } } >
119+ < Box sx = { { flex : 1 , display : 'flex' , flexDirection : 'column' , minWidth : 0 } } >
120+ < Typography variant = "body2" fontWeight = { 500 } color = "text.secondary" gutterBottom >
164121 { title }
165122 </ Typography >
166- < Box sx = { { display : 'flex' , flexDirection : { xs : 'column' , sm : 'row' , lg : 'row' } , alignItems : 'center' , gap : { xs : 1.5 , sm : 2 , lg : 1.5 } , width : '100%' , minWidth : 0 , flex : 1 } } >
167- < Box sx = { { position : 'relative' , height : { xs : 120 , sm : 150 , lg : 130 , xl : 140 } , width : { xs : 120 , sm : 150 , lg : 130 , xl : 140 } , flexShrink : 0 } } >
123+
124+ < Box
125+ sx = { {
126+ display : 'flex' ,
127+ flexDirection : { xs : 'column' , sm : 'row' } ,
128+ alignItems : 'center' ,
129+ gap : 2 ,
130+ } }
131+ >
132+ < Box sx = { { position : 'relative' , height : 140 , width : 140 } } >
168133 < ResponsiveContainer width = "100%" height = "100%" >
169134 < PieChart >
170135 < Pie
@@ -174,8 +139,6 @@ const CourseCompletion: React.FC<CourseCompletionProps> = ({
174139 innerRadius = { innerRadius }
175140 outerRadius = { outerRadius }
176141 dataKey = "value"
177- startAngle = { 0 }
178- endAngle = { 360 }
179142 fill = "#E0E0E0"
180143 />
181144 < Pie
@@ -189,54 +152,26 @@ const CourseCompletion: React.FC<CourseCompletionProps> = ({
189152 endAngle = { - 270 }
190153 >
191154 { data . map ( ( entry , index ) => (
192- < Cell key = { `cell- ${ index } ` } fill = { entry . color } />
155+ < Cell key = { index } fill = { entry . color } />
193156 ) ) }
194157 </ Pie >
195158 < Tooltip />
196159 </ PieChart >
197160 </ ResponsiveContainer >
198- < Box
199- sx = { {
200- position : 'absolute' ,
201- top : '50%' ,
202- left : '50%' ,
203- transform : 'translate(-50%, -50%)' ,
204- textAlign : 'center' ,
205- width : { xs : '60px' , sm : '80px' } ,
206- } }
207- >
208- { /* <Typography variant="caption" color="text.secondary" sx={{ fontSize: '9px', textTransform: 'uppercase', lineHeight: 1.2 }}>
209- NO. OF EMPLOYEES
210- </Typography> */ }
211- </ Box >
212161 </ Box >
213- < Stack spacing = { 1 } sx = { { flex : 1 , minWidth : 0 } } >
162+
163+ < Stack spacing = { 1 } >
214164 { data . map ( ( item , index ) => (
215- < Stack
216- key = { index }
217- direction = "row"
218- alignItems = "center"
219- spacing = { 1 }
220- sx = { { minWidth : 0 } }
221- >
165+ < Stack key = { index } direction = "row" alignItems = "center" spacing = { 1 } >
222166 < Box
223167 sx = { {
224168 width : 10 ,
225169 height : 10 ,
226170 borderRadius : '50%' ,
227171 backgroundColor : item . color ,
228- flexShrink : 0 ,
229172 } }
230173 />
231- < Typography
232- variant = "body2"
233- sx = { {
234- color : theme . palette . primary . main ,
235- fontSize : { xs : '12px' , sm : '13px' , lg : '12px' , xl : '13px' } ,
236- minWidth : 0 ,
237- wordBreak : 'break-word'
238- } }
239- >
174+ < Typography variant = "body2" color = "primary" >
240175 { item . name } : { item . value }
241176 </ Typography >
242177 </ Stack >
@@ -246,32 +181,33 @@ const CourseCompletion: React.FC<CourseCompletionProps> = ({
246181 </ Box >
247182 ) ;
248183 } ;
184+
249185 return (
250- < Paper elevation = { 0 } sx = { { p : { xs : 1.5 , sm : 2 } , border : '1px solid #E0E0E0' , borderRadius : 2 , height : '100%' , display : 'flex' , flexDirection : 'column' , overflowX : 'hidden' , overflowY : 'visible' } } >
251- < Typography variant = "subtitle1" fontWeight = { 600 } gutterBottom sx = { { fontSize : { xs : '1rem' , sm : '1.125rem' } , flexShrink : 0 } } >
186+ < Paper
187+ elevation = { 0 }
188+ sx = { {
189+ p : 2 ,
190+ border : '1px solid #E0E0E0' ,
191+ borderRadius : 2 ,
192+ height : '100%' ,
193+ } }
194+ >
195+ < Typography variant = "subtitle1" fontWeight = { 600 } >
252196 { t ( 'COURSE_COMPLETION' ) }
253197 </ Typography >
254- < Stack
255- direction = { { xs : 'column' , sm : 'column' , lg : 'row' , xl : 'row' } }
256- spacing = { { xs : 2 , sm : 2 , lg : 2 , xl : 2.5 } }
257- sx = { {
258- mt : 2 ,
259- flex : 1 ,
260- minHeight : 0 ,
261- width : '100%' ,
262- alignItems : { xs : 'stretch' , sm : 'stretch' , lg : 'flex-start' , xl : 'flex-start' } ,
263- '& > *' : {
264- flex : { lg : '1 1 auto' , xl : '1 1 auto' } ,
265- minWidth : 0 ,
266- maxWidth : { lg : 'calc(50% - 4px)' , xl : 'calc(50% - 5px)' }
267- }
268- } }
269- >
270- { renderDonutChart ( prepareMandatoryData ( ) , t ( 'MANDATORY_COURSES' ) ) }
271- { renderDonutChart ( prepareNonMandatoryData ( ) , t ( 'NON_MANDATORY_COURSES' ) ) }
198+
199+ < Stack direction = { { xs : 'column' , lg : 'row' } } spacing = { 2 } sx = { { mt : 2 } } >
200+ { renderDonutChart (
201+ prepareMandatoryData ( ) ,
202+ t ( 'MANDATORY_COURSES' )
203+ ) }
204+ { renderDonutChart (
205+ prepareNonMandatoryData ( ) ,
206+ t ( 'NON_MANDATORY_COURSES' )
207+ ) }
272208 </ Stack >
273209 </ Paper >
274210 ) ;
275211} ;
276- export default CourseCompletion ;
277212
213+ export default CourseCompletion ;
0 commit comments