@@ -5,8 +5,9 @@ import render, { useAppContext } from '../../src/render.jsx';
55import Base from '../../src/components/Base.jsx'
66import DescriptionIcon from '@mui/icons-material/Description' ;
77import BallotIcon from '@mui/icons-material/Ballot' ;
8+ import PersonAddAlt1Icon from '@mui/icons-material/PersonAddAlt1' ;
89import { useState , useEffect } from 'react' ;
9- import { Box , Grid , Button , Dialog , LinearProgress , Typography } from '@mui/material'
10+ import { Box , Grid , Button , Dialog , DialogTitle , DialogContent , DialogActions , TextField , LinearProgress , Typography , Menu , MenuItem , Alert } from '@mui/material'
1011import { useTheme } from '@mui/material/styles' ;
1112import ContentTable from './components/ContentTable.jsx' ;
1213import { PieChart } from '@mui/x-charts/PieChart'
@@ -27,6 +28,12 @@ function Course() {
2728 const [ dialogMaxWidth , setDialogMaxWidth ] = useState ( 'lg' ) ;
2829 const [ enrollmentsCount , setEnrollmentsCount ] = useState ( null ) ;
2930 const [ weeklyStats , setWeeklyStats ] = useState ( null ) ;
31+ const [ enrollMenuAnchorEl , setEnrollMenuAnchorEl ] = useState ( null ) ;
32+ const [ manualEnrollOpen , setManualEnrollOpen ] = useState ( false ) ;
33+ const [ manualEnrollEmail , setManualEnrollEmail ] = useState ( '' ) ;
34+ const [ manualEnrollError , setManualEnrollError ] = useState ( '' ) ;
35+ const [ manualEnrollSubmitting , setManualEnrollSubmitting ] = useState ( false ) ;
36+ const [ pageSuccessMessage , setPageSuccessMessage ] = useState ( '' ) ;
3037 const organizationId = localStorage . getItem ( 'activeOrganizationId' ) ;
3138
3239 const theme = useTheme ( ) ;
@@ -75,6 +82,71 @@ function Course() {
7582 setDialogOpen ( false ) ;
7683 }
7784 }
85+
86+ const isEnrollMenuOpen = Boolean ( enrollMenuAnchorEl ) ;
87+
88+ const openEnrollMenu = ( event ) => {
89+ setEnrollMenuAnchorEl ( event . currentTarget ) ;
90+ }
91+
92+ const closeEnrollMenu = ( ) => {
93+ setEnrollMenuAnchorEl ( null ) ;
94+ }
95+
96+ const openManualEnrollDialog = ( ) => {
97+ closeEnrollMenu ( ) ;
98+ setManualEnrollError ( '' ) ;
99+ setManualEnrollEmail ( '' ) ;
100+ setManualEnrollOpen ( true ) ;
101+ }
102+
103+ const closeManualEnrollDialog = ( ) => {
104+ if ( manualEnrollSubmitting ) {
105+ return ;
106+ }
107+ setManualEnrollOpen ( false ) ;
108+ setManualEnrollError ( '' ) ;
109+ }
110+
111+ const submitManualEnrollment = async ( ) => {
112+ const email = manualEnrollEmail . trim ( ) ;
113+ if ( ! email ) {
114+ setManualEnrollError ( localeMessages [ 'email_required' ] || 'Email is required.' ) ;
115+ return ;
116+ }
117+
118+ setManualEnrollSubmitting ( true ) ;
119+ setManualEnrollError ( '' ) ;
120+
121+ try {
122+ const response = await fetch ( `${ apiBaseUrl } /organizations/${ organizationId } /courses/${ courseId } /enrollments/` , {
123+ method : 'POST' ,
124+ headers : {
125+ 'Content-Type' : 'application/json' ,
126+ 'X-CSRFToken' : getCookie ( 'csrftoken' )
127+ } ,
128+ body : JSON . stringify ( {
129+ learner_email : email ,
130+ } )
131+ } ) ;
132+
133+ const data = await response . json ( ) ;
134+
135+ if ( ! response . ok ) {
136+ setManualEnrollError ( data ?. error || ( localeMessages [ 'enrollment_failed' ] || 'Failed to enroll learner.' ) ) ;
137+ return ;
138+ }
139+
140+ setPageSuccessMessage ( localeMessages [ 'enrollment_success' ] || 'Learner enrolled successfully.' ) ;
141+ setManualEnrollEmail ( '' ) ;
142+ setManualEnrollOpen ( false ) ;
143+ } catch ( error ) {
144+ console . error ( 'Error enrolling learner:' , error ) ;
145+ setManualEnrollError ( localeMessages [ 'enrollment_failed' ] || 'Failed to enroll learner.' ) ;
146+ } finally {
147+ setManualEnrollSubmitting ( false ) ;
148+ }
149+ }
78150 const getContent = async ( contentId , ) => {
79151 console . log ( "Fetching content with ID:" , contentId ) ;
80152 const response = await fetch ( `${ apiBaseUrl } /organizations/${ organizationId } /courses/${ courseId } /contents/${ contentId } /` , {
@@ -200,6 +272,13 @@ function Course() {
200272 ] }
201273 showOrganizationSwitcher = { false }
202274 >
275+ { pageSuccessMessage && (
276+ < Grid size = { { xs : 12 } } px = { 2 } pt = { 2 } >
277+ < Alert severity = "success" onClose = { ( ) => setPageSuccessMessage ( '' ) } >
278+ { pageSuccessMessage }
279+ </ Alert >
280+ </ Grid >
281+ ) }
203282 < Grid size = { { xs : 12 } } py = { 2 } pl = { 2 } >
204283 < Box p = { 2 } sx = { { border : '1px solid' , borderColor : 'border.main' , backgroundColor : 'background.box' , borderRadius : 2 , minHeight : 300 } } >
205284 { userRole !== 'viewer' && < > < Button variant = "contained" startIcon = { < DescriptionIcon sx = { { marginLeft : direction == 'rtl' ? 1 : 0 } } /> } sx = { { marginBottom : 2 } } onClick = { ( ) => {
@@ -214,7 +293,32 @@ function Course() {
214293 cancelCallback = { ( ) => setDialogOpen ( false ) }
215294 successCallback = { resetDialog }
216295 courseId = { courseId } /> </ Suspense > ) ;
217- setDialogOpen ( true ) ; } } > { localeMessages [ "add_quiz" ] } </ Button > </ > }
296+ setDialogOpen ( true ) ; } } > { localeMessages [ "add_quiz" ] } </ Button >
297+ { userRole === 'admin' && < >
298+ < Button
299+ variant = "outlined"
300+ startIcon = { < PersonAddAlt1Icon sx = { { marginLeft : direction == 'rtl' ? 1 : 0 } } /> }
301+ sx = { { marginBottom : 2 , minWidth : 190 } }
302+ onClick = { openEnrollMenu }
303+ >
304+ { localeMessages [ 'enroll_learner' ] || 'Enroll Learner' }
305+ </ Button >
306+ < Menu
307+ anchorEl = { enrollMenuAnchorEl }
308+ open = { isEnrollMenuOpen }
309+ onClose = { closeEnrollMenu }
310+ PaperProps = { {
311+ sx : {
312+ minWidth : 240 ,
313+ }
314+ } }
315+ >
316+ < MenuItem onClick = { openManualEnrollDialog } >
317+ { localeMessages [ 'manual_email' ] || 'Manual email' }
318+ </ MenuItem >
319+ </ Menu >
320+ </ > }
321+ </ > }
218322 < ContentTable courseId = { courseId } loaded = { contentLoaded } eventHandler = { ( event ) => tableEventHandler ( event ) } />
219323 </ Box >
220324 < Grid container spacing = { 2 } >
@@ -267,6 +371,31 @@ function Course() {
267371 < Dialog open = { dialogOpen } onClose = { handleClose } fullWidth maxWidth = { dialogMaxWidth } >
268372 { dialogContent }
269373 </ Dialog >
374+
375+ < Dialog open = { manualEnrollOpen } onClose = { closeManualEnrollDialog } fullWidth maxWidth = "sm" >
376+ < DialogTitle > { localeMessages [ 'enroll_learner' ] || 'Enroll Learner' } </ DialogTitle >
377+ < DialogContent >
378+ < TextField
379+ fullWidth
380+ autoFocus
381+ margin = "dense"
382+ type = "email"
383+ label = { localeMessages [ 'email' ] || 'Email' }
384+ value = { manualEnrollEmail }
385+ onChange = { ( event ) => setManualEnrollEmail ( event . target . value ) }
386+ disabled = { manualEnrollSubmitting }
387+ />
388+ { manualEnrollError && < Alert severity = "error" sx = { { mt : 2 } } > { manualEnrollError } </ Alert > }
389+ </ DialogContent >
390+ < DialogActions >
391+ < Button onClick = { closeManualEnrollDialog } disabled = { manualEnrollSubmitting } >
392+ { localeMessages [ 'cancel' ] || 'Cancel' }
393+ </ Button >
394+ < Button variant = "contained" onClick = { submitManualEnrollment } disabled = { manualEnrollSubmitting } >
395+ { localeMessages [ 'enroll' ] || 'Enroll' }
396+ </ Button >
397+ </ DialogActions >
398+ </ Dialog >
270399 </ Base >
271400 )
272401}
0 commit comments