@@ -2,7 +2,7 @@ import { Row as TRow } from "@tanstack/react-table";
2
2
import Table from "components/Table/Table" ;
3
3
import useAPI from "hooks/useAPI" ;
4
4
import { useCallback , useEffect , useMemo , useState } from "react" ;
5
- import { Button , Col , Container , Row } from "react-bootstrap" ;
5
+ import { Button , Col , Container , Row , Tooltip } from "react-bootstrap" ;
6
6
import { RiHealthBookLine } from "react-icons/ri" ;
7
7
import { useDispatch , useSelector } from "react-redux" ;
8
8
import { Outlet , useLocation , useNavigate } from "react-router-dom" ;
@@ -12,17 +12,22 @@ import { ICourseResponse, ROLE } from "../../utils/interfaces";
12
12
import { courseColumns as COURSE_COLUMNS } from "./CourseColumns" ;
13
13
import CopyCourse from "./CourseCopy" ;
14
14
import DeleteCourse from "./CourseDelete" ;
15
- import { formatDate , mergeDataAndNames } from "./CourseUtil" ;
15
+ import { formatDate , mergeDataAndNamesAndInstructors } from "./CourseUtil" ;
16
+ import { OverlayTrigger } from "react-bootstrap" ;
17
+
18
+ import { ICourseResponse as ICourse } from "../../utils/interfaces" ;
16
19
17
20
// Courses Component: Displays and manages courses, including CRUD operations.
18
21
19
22
/**
20
- * @author Atharva Thorve, on December, 2023
21
- * @author Mrityunjay Joshi on December, 2023
23
+ @author Suraj Raghu Kumar, on Oct, 2024
24
+ * @author Yuktasree Muppala on Oct, 2024
25
+ * @author Harvardhan Patil on Oct, 2024
22
26
*/
23
27
const Courses = ( ) => {
24
28
const { error, isLoading, data : CourseResponse , sendRequest : fetchCourses } = useAPI ( ) ;
25
- const { data : InstitutionResponse , sendRequest : fetchInstitutions } = useAPI ( ) ;
29
+ const { data : InstitutionResponse , sendRequest : fetchInstitutions } = useAPI ( ) ;
30
+ const { data : InstructorResponse , sendRequest : fetchInstructors } = useAPI ( ) ;
26
31
const auth = useSelector (
27
32
( state : RootState ) => state . authentication ,
28
33
( prev , next ) => prev . isAuthenticated === next . isAuthenticated
@@ -31,7 +36,19 @@ const Courses = () => {
31
36
const location = useLocation ( ) ;
32
37
const dispatch = useDispatch ( ) ;
33
38
34
- // State for delete and copy confirmation modals
39
+ // show course
40
+ const [ showDetailsModal , setShowDetailsModal ] = useState < boolean > ( false ) ;
41
+ const [ selectedCourse , setSelectedCourse ] = useState < ICourse | null > ( null ) ;
42
+
43
+ // Utility function to manage modals, adhering to Open-closed-principle
44
+ const showModal = ( setModalState : React . Dispatch < React . SetStateAction < boolean > > ,
45
+ setData ?: ( data : ICourse | null ) => void , data ?: ICourse ) => {
46
+ if ( setData ) {
47
+ setData ( data || null ) ;
48
+ }
49
+ setModalState ( true ) ;
50
+ } ;
51
+ const handleShowDetails = ( course : ICourse ) => showModal ( setShowDetailsModal , setSelectedCourse , course ) ;
35
52
const [ showDeleteConfirmation , setShowDeleteConfirmation ] = useState < {
36
53
visible : boolean ;
37
54
data ?: ICourseResponse ;
@@ -44,19 +61,13 @@ const Courses = () => {
44
61
45
62
useEffect ( ( ) => {
46
63
// ToDo: Fix this API in backend so that it the institution name along with the id. Similar to how it is done in users.
47
- if ( ! showDeleteConfirmation . visible || ! showCopyConfirmation . visible ) {
64
+ if ( ! showDeleteConfirmation . visible || ! showCopyConfirmation . visible ) {
48
65
fetchCourses ( { url : `/courses` } ) ;
49
66
// ToDo: Remove this API call later after the above ToDo is completed
50
67
fetchInstitutions ( { url : `/institutions` } ) ;
68
+ fetchInstructors ( { url : `/users` } ) ;
51
69
}
52
- } , [
53
- fetchCourses ,
54
- fetchInstitutions ,
55
- location ,
56
- showDeleteConfirmation . visible ,
57
- auth . user . id ,
58
- showCopyConfirmation . visible ,
59
- ] ) ;
70
+ } , [ fetchCourses , fetchInstitutions , fetchInstructors , location , showDeleteConfirmation . visible , auth . user . id , showCopyConfirmation . visible ] ) ;
60
71
61
72
// Error alert for API errors
62
73
useEffect ( ( ) => {
@@ -66,10 +77,7 @@ const Courses = () => {
66
77
} , [ error , dispatch ] ) ;
67
78
68
79
// Callbacks for handling delete and copy confirmation modals
69
- const onDeleteCourseHandler = useCallback (
70
- ( ) => setShowDeleteConfirmation ( { visible : false } ) ,
71
- [ ]
72
- ) ;
80
+ const onDeleteCourseHandler = useCallback ( ( ) => setShowDeleteConfirmation ( { visible : false } ) , [ ] ) ;
73
81
74
82
const onCopyCourseHandler = useCallback ( ( ) => setShowCopyConfirmation ( { visible : false } ) , [ ] ) ;
75
83
@@ -85,17 +93,17 @@ const Courses = () => {
85
93
) ;
86
94
87
95
const onDeleteHandle = useCallback (
88
- ( row : TRow < ICourseResponse > ) =>
89
- setShowDeleteConfirmation ( { visible : true , data : row . original } ) ,
96
+ ( row : TRow < ICourseResponse > ) => setShowDeleteConfirmation ( { visible : true , data : row . original } ) ,
90
97
[ ]
91
98
) ;
92
99
93
100
const onCopyHandle = useCallback (
94
101
( row : TRow < ICourseResponse > ) => setShowCopyConfirmation ( { visible : true , data : row . original } ) ,
95
102
[ ]
96
103
) ;
97
-
104
+
98
105
const tableColumns = useMemo (
106
+
99
107
( ) => COURSE_COLUMNS ( onEditHandle , onDeleteHandle , onTAHandle , onCopyHandle ) ,
100
108
[ onDeleteHandle , onEditHandle , onTAHandle , onCopyHandle ]
101
109
) ;
@@ -110,58 +118,102 @@ const Courses = () => {
110
118
[ InstitutionResponse ?. data , isLoading ]
111
119
) ;
112
120
113
- tableData = mergeDataAndNames ( tableData , institutionData ) ;
121
+ const instructorData = useMemo (
122
+ ( ) => ( isLoading || ! InstructorResponse ?. data ? [ ] : InstructorResponse . data ) ,
123
+ [ InstructorResponse ?. data , isLoading ]
124
+ ) ;
125
+
126
+ tableData = mergeDataAndNamesAndInstructors ( tableData , institutionData , instructorData ) ;
114
127
115
128
const formattedTableData = tableData . map ( ( item : any ) => ( {
116
129
...item ,
117
130
created_at : formatDate ( item . created_at ) ,
118
131
updated_at : formatDate ( item . updated_at ) ,
119
132
} ) ) ;
120
133
121
- // Render the Courses component
134
+ // `auth.user.id` holds the ID of the logged-in user
135
+ const loggedInUserId = auth . user . id ;
136
+ const loggedInUserRole = auth . user . role ;
137
+
138
+ const visibleCourses = useMemo ( ( ) => {
139
+ // Show all courses to admin and superadmin roles
140
+ if ( loggedInUserRole === ROLE . ADMIN . valueOf ( ) || loggedInUserRole === ROLE . SUPER_ADMIN . valueOf ( ) ) {
141
+ return formattedTableData ;
142
+ }
143
+ // Otherwise, only show courses where the logged-in user is the instructor
144
+ return formattedTableData . filter ( ( CourseResponse : { instructor_id : number ; } ) => CourseResponse . instructor_id === loggedInUserId ) ;
145
+ } , [ formattedTableData , loggedInUserRole ] ) ;
122
146
147
+ // Render the Courses component
148
+
123
149
return (
124
150
< >
125
151
< Outlet />
126
152
< main >
127
153
< Container fluid className = "px-md-4" >
128
- < Row className = "mt-md-2 mb-md-2 " >
154
+ < Row className = "mt-4 mb-4 " >
129
155
< Col className = "text-center" >
130
- < h1 > Manage Courses</ h1 >
156
+ < h1 className = "text-dark" style = { { fontSize : '2rem' , fontWeight : '600' } } >
157
+ { auth . user . role === ROLE . INSTRUCTOR . valueOf ( ) ? (
158
+ < > Instructed by: { auth . user . full_name } </ >
159
+ ) : auth . user . role === ROLE . TA . valueOf ( ) ? (
160
+ < > Assisted by: { auth . user . full_name } </ >
161
+ ) : (
162
+ < > Manage Courses</ >
163
+ ) }
164
+ </ h1 >
131
165
</ Col >
132
166
< hr />
133
167
</ Row >
134
- < Row >
135
- < Col md = { { span : 1 , offset : 11 } } style = { { paddingBottom : "10px" } } >
136
- < Button variant = "outline-success" onClick = { ( ) => navigate ( "new" ) } >
137
- < RiHealthBookLine />
168
+
169
+ < Row className = "mb-4 justify-content-end" >
170
+ < Col xs = "auto" >
171
+ < Button
172
+ variant = "success"
173
+ size = "lg"
174
+ onClick = { ( ) => navigate ( "new" ) }
175
+ aria-label = "Add New Course"
176
+ style = { {
177
+ fontSize : '1rem' ,
178
+ padding : '8px 24px' ,
179
+ borderRadius : '10px' ,
180
+ boxShadow : '0px 4px 10px rgba(0, 0, 0, 0.1)' ,
181
+ display : 'flex' ,
182
+ alignItems : 'center' ,
183
+ } }
184
+ >
185
+ < RiHealthBookLine style = { { marginRight : '8px' , fontSize : '1.4rem' } } />
186
+ Add Course
138
187
</ Button >
139
188
</ Col >
140
- { showDeleteConfirmation . visible && (
141
- < DeleteCourse
142
- courseData = { showDeleteConfirmation . data ! }
143
- onClose = { onDeleteCourseHandler }
144
- />
145
- ) }
146
- { showCopyConfirmation . visible && (
147
- < CopyCourse courseData = { showCopyConfirmation . data ! } onClose = { onCopyCourseHandler } />
148
- ) }
149
189
</ Row >
190
+
191
+ { showDeleteConfirmation . visible && (
192
+ < DeleteCourse courseData = { showDeleteConfirmation . data ! } onClose = { onDeleteCourseHandler } />
193
+ ) }
194
+ { showCopyConfirmation . visible && (
195
+ < CopyCourse courseData = { showCopyConfirmation . data ! } onClose = { onCopyCourseHandler } />
196
+ ) }
197
+
150
198
< Row >
151
199
< Table
152
200
showGlobalFilter = { false }
153
- data = { formattedTableData }
201
+ data = { visibleCourses }
154
202
columns = { tableColumns }
155
203
columnVisibility = { {
156
204
id : false ,
157
205
institution : auth . user . role === ROLE . SUPER_ADMIN . valueOf ( ) ,
206
+ instructor : auth . user . role === ROLE . SUPER_ADMIN . valueOf ( ) ,
158
207
} }
159
208
/>
160
209
</ Row >
161
210
</ Container >
162
211
</ main >
212
+
213
+
163
214
</ >
164
- ) ;
215
+ ) ;
216
+
165
217
} ;
166
218
167
- export default Courses ;
219
+ export default Courses ;
0 commit comments