11import { useState , useEffect , useCallback } from 'react' ;
2- import { useNavigate } from 'react-router-dom' ;
2+ import { useNavigate , Link } from 'react-router-dom' ;
33import { Plus , Edit , Trash2 , Users , Shield , Code , BarChart3 , AlertTriangle , Loader2 , Activity , Percent , Clock , Download , Upload } from 'lucide-react' ;
44import toast from 'react-hot-toast' ;
55import { useAuth } from '../context/AuthContext' ;
66import api from '../services/api' ;
77import type { Problem , ProblemStats , AdminStats } from '../types' ;
8+ import SubmissionStatus from '../components/SubmissionStatus' ;
89
910export default function Admin ( ) {
1011 const { user } = useAuth ( ) ;
@@ -18,6 +19,8 @@ export default function Admin() {
1819 const [ deletingId , setDeletingId ] = useState < number | null > ( null ) ;
1920 const [ users , setUsers ] = useState < any [ ] > ( [ ] ) ;
2021 const [ usersLoading , setUsersLoading ] = useState ( false ) ;
22+ const [ allSubmissions , setAllSubmissions ] = useState < any [ ] > ( [ ] ) ;
23+ const [ allLoading , setAllLoading ] = useState ( false ) ;
2124
2225 const fetchData = useCallback ( async ( ) => {
2326 setLoading ( true ) ;
@@ -116,6 +119,17 @@ export default function Admin() {
116119 } catch { toast . error ( '删除失败' ) ; }
117120 } ;
118121
122+ const fetchAllSubmissions = async ( ) => {
123+ setAllLoading ( true ) ;
124+ try {
125+ const token = localStorage . getItem ( 'oj_token' ) ;
126+ const res = await fetch ( '/api/submissions/admin/all' , { headers : { Authorization : `Bearer ${ token } ` } } ) ;
127+ const data = await res . json ( ) ;
128+ setAllSubmissions ( data . submissions || [ ] ) ;
129+ } catch { toast . error ( '加载失败' ) ; }
130+ finally { setAllLoading ( false ) ; }
131+ } ;
132+
119133 const TYPE_LABELS : Record < string , string > = {
120134 programming : '编程题' ,
121135 choice : '选择题' ,
@@ -385,8 +399,52 @@ export default function Admin() {
385399 ) ) }
386400 </ tbody >
387401 </ table >
388- </ div >
389- ) }
402+ </ div >
403+ ) }
404+
405+ { /* All Submissions */ }
406+ < div className = "card p-6 mt-6" >
407+ < h2 className = "text-lg font-semibold text-white mb-4 flex items-center gap-2" >
408+ < Activity className = "w-5 h-5 text-cyan-400" /> 所有提交
409+ </ h2 >
410+ { allSubmissions . length === 0 ? (
411+ < button onClick = { fetchAllSubmissions } className = "btn-secondary text-sm inline-flex items-center gap-2" >
412+ { allLoading && < Loader2 className = "w-4 h-4 animate-spin" /> }
413+ 加载提交记录
414+ </ button >
415+ ) : (
416+ < div className = "overflow-x-auto max-h-96 overflow-y-auto" >
417+ < table className = "w-full text-sm" >
418+ < thead >
419+ < tr className = "border-b border-dark-700" >
420+ < th className = "text-left py-2 px-3 text-dark-400" > ID</ th >
421+ < th className = "text-left py-2 px-3 text-dark-400" > 用户</ th >
422+ < th className = "text-left py-2 px-3 text-dark-400" > 题目</ th >
423+ < th className = "text-left py-2 px-3 text-dark-400" > 状态</ th >
424+ < th className = "text-left py-2 px-3 text-dark-400 hidden md:table-cell" > 时间</ th >
425+ </ tr >
426+ </ thead >
427+ < tbody >
428+ { allSubmissions . map ( ( s : any ) => (
429+ < tr key = { s . id } className = "border-b border-dark-800 hover:bg-dark-800/50" >
430+ < td className = "py-2 px-3 text-dark-400" > { s . id } </ td >
431+ < td className = "py-2 px-3 text-white" > { s . username } </ td >
432+ < td className = "py-2 px-3" >
433+ < Link to = { `/problems/${ s . problem_id } ` } className = "text-blue-400 hover:text-blue-300" >
434+ { s . problem_title || `#${ s . problem_id } ` }
435+ </ Link >
436+ </ td >
437+ < td className = "py-2 px-3" >
438+ < SubmissionStatus status = { s . status } score = { s . score } />
439+ </ td >
440+ < td className = "py-2 px-3 text-dark-400 hidden md:table-cell text-xs" > { s . created_at ?. slice ( 0 , 16 ) . replace ( 'T' , ' ' ) } </ td >
441+ </ tr >
442+ ) ) }
443+ </ tbody >
444+ </ table >
445+ </ div >
446+ ) }
447+ </ div >
390448 </ div >
391449 ) ;
392450}
0 commit comments