22
33import { useState } from 'react' ;
44import Link from 'next/link' ;
5- import { Flag , ChevronDown , ChevronRight , ShieldCheck , ShieldX , RotateCcw } from 'lucide-react' ;
5+ import { Flag , ChevronDown , ChevronRight , ShieldCheck , ShieldX , RotateCcw , XCircle } from 'lucide-react' ;
66import SearchInput from '@/app/components/SearchInput' ;
77import Pagination from '@/app/components/Pagination' ;
88import { toast } from 'sonner' ;
@@ -16,6 +16,8 @@ export default function SubmissionsTable({ participants }: { participants: Parti
1616 const [ expanded , setExpanded ] = useState < Set < string > > ( new Set ( ) ) ;
1717 const [ overriding , setOverriding ] = useState < string | null > ( null ) ;
1818 const [ localOverride , setLocalOverride ] = useState < Record < string , boolean | null > > ( { } ) ;
19+ const [ terminating , setTerminating ] = useState < string | null > ( null ) ;
20+ const [ localClosed , setLocalClosed ] = useState < Set < string > > ( new Set ( ) ) ;
1921 const [ page , setPage ] = useState ( 1 ) ;
2022
2123 async function handleOverride ( sessionId : string , value : boolean | null ) {
@@ -36,6 +38,21 @@ export default function SubmissionsTable({ participants }: { participants: Parti
3638 }
3739 }
3840
41+ async function handleTerminate ( sessionId : string , username : string ) {
42+ if ( ! confirm ( `Terminate ${ username } 's session? This only affects them and cannot be undone.` ) ) return ;
43+ setTerminating ( sessionId ) ;
44+ try {
45+ const res = await fetch ( `/api/instructor/sessions/${ sessionId } ` , { method : 'DELETE' } ) ;
46+ if ( ! res . ok ) throw new Error ( 'Failed' ) ;
47+ setLocalClosed ( ( prev ) => new Set ( prev ) . add ( sessionId ) ) ;
48+ toast . success ( `${ username } 's session terminated` ) ;
49+ } catch {
50+ toast . error ( 'Failed to terminate session' ) ;
51+ } finally {
52+ setTerminating ( null ) ;
53+ }
54+ }
55+
3956 const filtered = participants . filter ( ( p ) => {
4057 const matchSearch = p . username . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ;
4158 const matchFlag = ! filterFlagged || p . is_flagged ;
@@ -98,6 +115,7 @@ export default function SubmissionsTable({ participants }: { participants: Parti
98115 ? localOverride [ p . session_id ]
99116 : p . passed ;
100117 const isOverridden = p . passed_override !== null || p . session_id in localOverride ;
118+ const isClosed = localClosed . has ( p . session_id ) || ! ! p . closed_at ;
101119
102120 return (
103121 < >
@@ -121,6 +139,9 @@ export default function SubmissionsTable({ participants }: { participants: Parti
121139 { p . final_count === p . total_questions && (
122140 < span className = "badge badge-green" style = { { marginLeft : 6 , fontSize : 10 } } > Done</ span >
123141 ) }
142+ { isClosed && p . final_count < p . total_questions && (
143+ < span className = "badge badge-gray" style = { { marginLeft : 6 , fontSize : 10 } } > Closed</ span >
144+ ) }
124145 </ td >
125146 < td >
126147 { p . is_flagged
@@ -175,6 +196,17 @@ export default function SubmissionsTable({ participants }: { participants: Parti
175196 < RotateCcw size = { 11 } />
176197 </ button >
177198 ) }
199+ { ! isClosed && (
200+ < button
201+ className = "btn btn-sm btn-danger"
202+ disabled = { terminating === p . session_id }
203+ onClick = { ( ) => handleTerminate ( p . session_id , p . username ) }
204+ title = "Terminate session"
205+ style = { { fontSize : 11 } }
206+ >
207+ < XCircle size = { 11 } /> End
208+ </ button >
209+ ) }
178210 </ div >
179211 </ td >
180212 </ tr >
0 commit comments