11import { useMemo , useState } from 'react' ;
22import { useParams } from 'react-router-dom' ;
33import { useIntl } from '@openedx/frontend-base' ;
4- import { DataTable } from '@openedx/paragon' ;
4+ import { DataTable , IconButton , OverlayTrigger , Popover } from '@openedx/paragon' ;
5+ import { MoreVert } from '@openedx/paragon/icons' ;
56import UsernameFilter from '@src/components/UsernameFilter' ;
6- import messages from '@src/specialExams/messages' ;
77import { useAttempts } from '@src/specialExams/data/apiHook' ;
8- import { DataTableFetchDataProps } from '@src/types' ;
8+ import messages from '@src/specialExams/messages' ;
9+ import { Attempt } from '@src/specialExams/types' ;
10+ import { DataTableFetchDataProps , TableCellValue } from '@src/types' ;
911
1012export const ATTEMPTS_PAGE_SIZE = 25 ;
1113
12- const AttemptsList = ( ) => {
14+ interface AttemptsListProps {
15+ onResume : ( attempt : Attempt ) => void ,
16+ onReset : ( attempt : Attempt ) => void ,
17+ }
18+
19+ const AttemptsList = ( { onResume, onReset } : AttemptsListProps ) => {
1320 const intl = useIntl ( ) ;
1421 const { courseId = '' } = useParams ( ) ;
1522 const [ filters , setFilters ] = useState ( { page : 0 , emailOrUsername : '' , ordering : '' } ) ;
@@ -18,16 +25,107 @@ const AttemptsList = () => {
1825 pageSize : ATTEMPTS_PAGE_SIZE
1926 } ) ;
2027
28+ const ActionCustomCell = ( { row : { original } } : TableCellValue < Attempt > ) => {
29+ const popoverContent = (
30+ < Popover
31+ id = { `popover-${ original . user . username } -${ original . examName } ` }
32+ className = "border-0 shadow-sm"
33+ >
34+ < Popover . Content className = "p-0 border-0" >
35+ < div className = "dropdown-menu show position-static border shadow-sm" >
36+ < button
37+ type = "button"
38+ className = "dropdown-item"
39+ onClick = { ( ) => original . readyToResume ? onResume ( original ) : onReset ( original ) }
40+ >
41+ { original . readyToResume ? intl . formatMessage ( messages . resume ) : intl . formatMessage ( messages . reset ) }
42+ </ button >
43+ </ div >
44+ </ Popover . Content >
45+ </ Popover >
46+ ) ;
47+ return (
48+ < >
49+ < OverlayTrigger
50+ trigger = "click"
51+ placement = "bottom-end"
52+ overlay = { popoverContent }
53+ rootClose
54+ >
55+ < IconButton
56+ alt = { intl . formatMessage ( messages . actions ) }
57+ className = "lead"
58+ iconAs = { MoreVert }
59+ />
60+ </ OverlayTrigger >
61+ </ >
62+ ) ;
63+ } ;
64+
2165 const columns = useMemo ( ( ) => [
2266 { accessor : 'user.username' , Header : intl . formatMessage ( messages . username ) , Filter : UsernameFilter , } ,
2367 { accessor : 'examName' , Header : intl . formatMessage ( messages . examName ) , disableFilters : true , } ,
2468 { accessor : 'allowedTimeLimitMins' , Header : intl . formatMessage ( messages . timeLimit ) , disableFilters : true , } ,
25- { accessor : 'type' , Header : intl . formatMessage ( messages . type ) , disableFilters : true , } ,
26- { accessor : 'startTime' , Header : intl . formatMessage ( messages . startedAt ) , disableFilters : true , } ,
27- { accessor : 'endTime' , Header : intl . formatMessage ( messages . completedAt ) , disableFilters : true , } ,
28- { accessor : 'status' , Header : intl . formatMessage ( messages . status ) , disableFilters : true , } ,
69+ {
70+ accessor : 'examType' ,
71+ Cell : ( { row } : TableCellValue < any > ) => < span className = "text-capitalize" > { row . original . examType } </ span > ,
72+ disableFilters : true ,
73+ Header : intl . formatMessage ( messages . type ) ,
74+ } ,
75+ {
76+ accessor : 'startTime' ,
77+ Cell : ( { row } : TableCellValue < any > ) => (
78+ < span > { row . original . startTime ? `${ intl . formatDate ( new Date ( row . original . startTime ) , {
79+ year : 'numeric' ,
80+ month : '2-digit' ,
81+ day : '2-digit' ,
82+ hour : '2-digit' ,
83+ minute : '2-digit' ,
84+ timeZone : 'UTC' ,
85+ } ) } UTC` : '' }
86+ </ span >
87+ ) ,
88+ disableFilters : true ,
89+ Header : intl . formatMessage ( messages . startedAt ) ,
90+ } ,
91+ {
92+ accessor : 'endTime' ,
93+ Cell : ( { row } : TableCellValue < any > ) => (
94+ < span > { row . original . endTime ? `${ intl . formatDate ( new Date ( row . original . endTime ) , {
95+ year : 'numeric' ,
96+ month : '2-digit' ,
97+ day : '2-digit' ,
98+ hour : '2-digit' ,
99+ minute : '2-digit' ,
100+ timeZone : 'UTC' ,
101+ } ) } UTC` : '' }
102+ </ span >
103+ ) ,
104+ disableFilters : true ,
105+ Header : intl . formatMessage ( messages . completedAt ) ,
106+ } ,
107+ {
108+ accessor : 'status' ,
109+ Cell : ( { row } : TableCellValue < any > ) => < span className = "text-capitalize" > { row . original . status } </ span > ,
110+ disableFilters : true ,
111+ Header : intl . formatMessage ( messages . status ) ,
112+ } ,
113+ {
114+ accessor : 'readyToResume' ,
115+ Cell : ( { row } : TableCellValue < any > ) => (
116+ < span className = "text-capitalize" > { row . original . readyToResume ? intl . formatMessage ( messages . true ) : '' } </ span >
117+ ) ,
118+ disableFilters : true ,
119+ Header : intl . formatMessage ( messages . readyForResume ) ,
120+ }
29121 ] , [ intl ] ) ;
30122
123+ const additionalColumns = [ {
124+ id : 'actions' ,
125+ Header : '' ,
126+ Cell : ActionCustomCell ,
127+ } ] ;
128+
31129 const handleFetchData = ( data : DataTableFetchDataProps ) => {
32130 const emailOrUsernameFilter = data . filters ?. find ( ( f ) => f . id === 'user.username' ) ;
33131 const newEmailOrUsername = emailOrUsernameFilter ? emailOrUsernameFilter . value : '' ;
@@ -44,6 +142,7 @@ const AttemptsList = () => {
44142
45143 return (
46144 < DataTable
145+ additionalColumns = { additionalColumns }
47146 className = "mt-3"
48147 columns = { columns }
49148 data = { data . results }
0 commit comments