11import { useState , useEffect } from 'react' ;
22import { Link } from 'react-router-dom' ;
3- import { User , Mail , Shield , Award , TrendingUp , Loader2 , AlertTriangle , CheckCircle2 , XCircle , Clock , Zap , Lock , Key , CalendarDays , Code } from 'lucide-react' ;
3+ import { User , Mail , Shield , Award , TrendingUp , Loader2 , AlertTriangle , CheckCircle2 , XCircle , Clock , Zap , Lock , Key , CalendarDays , Code , PieChart } from 'lucide-react' ;
44import { useAuth } from '../context/AuthContext' ;
55import api from '../services/api' ;
66import type { Submission } from '../types' ;
@@ -51,6 +51,7 @@ export default function Profile() {
5151 const [ passwordError , setPasswordError ] = useState < string | null > ( null ) ;
5252 const [ calendarDates , setCalendarDates ] = useState < Set < string > > ( new Set ( ) ) ;
5353 const [ languageStats , setLanguageStats ] = useState < { language : string ; count : number } [ ] > ( [ ] ) ;
54+ const [ submissionStatus , setSubmissionStatus ] = useState < any [ ] > ( [ ] ) ;
5455
5556 useEffect ( ( ) => {
5657 const token = localStorage . getItem ( 'oj_token' ) ;
@@ -67,6 +68,15 @@ export default function Profile() {
6768 setLanguageStats ( data . languages || [ ] ) ;
6869 } )
6970 . catch ( ( ) => { } ) ;
71+ fetch ( '/api/submissions?page=1&limit=1000' , { headers : { Authorization : `Bearer ${ token } ` } } )
72+ . then ( r => r . json ( ) )
73+ . then ( data => {
74+ const submissions = data . submissions || [ ] ;
75+ const statusCount : Record < string , number > = { } ;
76+ submissions . forEach ( ( s : any ) => { statusCount [ s . status ] = ( statusCount [ s . status ] || 0 ) + 1 ; } ) ;
77+ setSubmissionStatus ( Object . entries ( statusCount ) . map ( ( [ status , count ] ) => ( { status, count } ) ) ) ;
78+ } )
79+ . catch ( ( ) => { } ) ;
7080 } , [ ] ) ;
7181
7282 useEffect ( ( ) => {
@@ -239,6 +249,41 @@ export default function Profile() {
239249 </ div >
240250 ) }
241251
252+ { /* Status distribution */ }
253+ { submissionStatus . length > 0 && (
254+ < div className = "card p-6" >
255+ < h3 className = "text-sm font-semibold text-dark-200 mb-3 flex items-center gap-2" >
256+ < PieChart className = "w-4 h-4 text-purple-400" /> 提交状态分布
257+ </ h3 >
258+ < div className = "grid grid-cols-2 gap-3" >
259+ { submissionStatus . map ( ( item : { status : string ; count : number } ) => {
260+ const maxCount = Math . max ( ...submissionStatus . map ( ( s : any ) => s . count ) ) ;
261+ const colorMap : Record < string , string > = {
262+ accepted : 'bg-emerald-500' , wrong_answer : 'bg-red-500' ,
263+ compile_error : 'bg-yellow-500' , time_limit : 'bg-purple-500' ,
264+ runtime_error : 'bg-orange-500' ,
265+ } ;
266+ const labelMap : Record < string , string > = {
267+ accepted : '通过' , wrong_answer : '答案错误' ,
268+ compile_error : '编译错误' , time_limit : '超时' ,
269+ runtime_error : '运行错误' ,
270+ } ;
271+ return (
272+ < div key = { item . status } className = "flex items-center gap-3" >
273+ < div className = { `w-2.5 h-2.5 rounded-full ${ colorMap [ item . status ] || 'bg-dark-500' } ` } />
274+ < span className = "flex-1 text-xs text-dark-400" > { labelMap [ item . status ] || item . status } </ span >
275+ < div className = "flex-1 h-2 bg-dark-700 rounded-full overflow-hidden" >
276+ < div className = { `h-full ${ colorMap [ item . status ] || 'bg-dark-500' } rounded-full` }
277+ style = { { width : `${ ( item . count / maxCount ) * 100 } %` } } />
278+ </ div >
279+ < span className = "text-xs text-dark-400 w-6 text-right" > { item . count } </ span >
280+ </ div >
281+ ) ;
282+ } ) }
283+ </ div >
284+ </ div >
285+ ) }
286+
242287 < GamificationSection />
243288
244289 { /* Solved calendar */ }
0 commit comments