1
+ 'use client' ;
2
+
3
+ import React , { useState , useEffect } from 'react' ;
4
+ import Link from 'next/link' ;
5
+ import Button from '@/components/Button' ;
6
+ import Spinner from '@/components/Spinner' ;
7
+ import { getPastReadingResultsAction } from '@/actions/readingComprehension' ;
8
+ import type { ReadingSession , ReadingAttempt } from '@/types' ;
9
+
10
+ interface ReadingResult {
11
+ session : ReadingSession ;
12
+ attempt : ReadingAttempt ;
13
+ }
14
+
15
+ export default function ReadingComprehensionResultsPage ( ) {
16
+ const [ results , setResults ] = useState < ReadingResult [ ] > ( [ ] ) ;
17
+ const [ isLoading , setIsLoading ] = useState ( true ) ;
18
+ const [ error , setError ] = useState < string | null > ( null ) ;
19
+
20
+ useEffect ( ( ) => {
21
+ const fetchResults = async ( ) => {
22
+ try {
23
+ const response = await getPastReadingResultsAction ( ) ;
24
+ if ( response . success && response . results ) {
25
+ setResults ( response . results ) ;
26
+ } else {
27
+ setError ( response . message || 'Failed to load results' ) ;
28
+ }
29
+ } catch ( err ) {
30
+ console . error ( 'Failed to fetch results:' , err ) ;
31
+ setError ( 'Failed to load results' ) ;
32
+ } finally {
33
+ setIsLoading ( false ) ;
34
+ }
35
+ } ;
36
+
37
+ fetchResults ( ) ;
38
+ } , [ ] ) ;
39
+
40
+ if ( isLoading ) {
41
+ return (
42
+ < div className = "container mx-auto px-4 py-8 min-h-screen" >
43
+ < div className = "text-center" >
44
+ < Spinner />
45
+ < p className = "mt-4 text-gray-600" > Loading your reading comprehension results...</ p >
46
+ </ div >
47
+ </ div >
48
+ ) ;
49
+ }
50
+
51
+ if ( error ) {
52
+ return (
53
+ < div className = "container mx-auto px-4 py-8 min-h-screen" >
54
+ < div className = "text-center" >
55
+ < h1 className = "text-3xl font-bold text-gray-800 mb-4" > Reading Comprehension Results</ h1 >
56
+ < p className = "text-red-600 mb-6" > { error } </ p >
57
+ < Link href = "/reading-comprehension" >
58
+ < Button variant = "primary" > ← Back to Reading Comprehension</ Button >
59
+ </ Link >
60
+ </ div >
61
+ </ div >
62
+ ) ;
63
+ }
64
+
65
+ const formatReadingTime = ( timeMs : number ) => {
66
+ const seconds = Math . round ( timeMs / 1000 ) ;
67
+ const minutes = Math . floor ( seconds / 60 ) ;
68
+ const remainingSeconds = seconds % 60 ;
69
+
70
+ if ( minutes > 0 ) {
71
+ return `${ minutes } m ${ remainingSeconds } s` ;
72
+ }
73
+ return `${ seconds } s` ;
74
+ } ;
75
+
76
+ const formatDate = ( date : Date ) => {
77
+ return new Date ( date ) . toLocaleDateString ( 'en-US' , {
78
+ year : 'numeric' ,
79
+ month : 'short' ,
80
+ day : 'numeric' ,
81
+ hour : '2-digit' ,
82
+ minute : '2-digit'
83
+ } ) ;
84
+ } ;
85
+
86
+ return (
87
+ < div className = "container mx-auto px-4 py-8 min-h-screen" >
88
+ < div className = "max-w-6xl mx-auto" >
89
+ < div className = "flex items-center justify-between mb-8" >
90
+ < h1 className = "text-3xl font-bold text-gray-800" > Reading Comprehension Results</ h1 >
91
+ < Link href = "/reading-comprehension" >
92
+ < Button variant = "default" > ← Back to Reading Comprehension</ Button >
93
+ </ Link >
94
+ </ div >
95
+
96
+ { results . length === 0 ? (
97
+ < div className = "text-center py-12" >
98
+ < p className = "text-gray-600 mb-6" > No reading comprehension attempts found.</ p >
99
+ < Link href = "/reading-comprehension" >
100
+ < Button variant = "primary" > Start Your First Reading Test</ Button >
101
+ </ Link >
102
+ </ div >
103
+ ) : (
104
+ < div className = "space-y-6" >
105
+ { results . map ( ( result , index ) => (
106
+ < div key = { result . attempt . id } className = "bg-white p-6 rounded-lg shadow-md border border-gray-200" >
107
+ < div className = "grid grid-cols-1 lg:grid-cols-3 gap-6" >
108
+ { /* Left: Basic Info */ }
109
+ < div >
110
+ < h3 className = "text-lg font-semibold text-gray-800 mb-2" >
111
+ { result . session . topic }
112
+ </ h3 >
113
+ < p className = "text-sm text-gray-600 mb-3" >
114
+ Completed: { formatDate ( result . attempt . createdAt ) }
115
+ </ p >
116
+ < div className = "space-y-1 text-sm" >
117
+ < div > Reading Time: < span className = "font-medium" > { formatReadingTime ( result . attempt . reading_time_ms ) } </ span > </ div >
118
+ </ div >
119
+ </ div >
120
+
121
+ { /* Center: Score Metrics */ }
122
+ < div className = "grid grid-cols-3 gap-4" >
123
+ < div className = "text-center p-3 bg-primary/10 rounded-lg" >
124
+ < div className = "text-xl font-bold text-primary" >
125
+ { result . attempt . total_score } /{ result . session . questions . length }
126
+ </ div >
127
+ < div className = "text-xs text-gray-600" > Score</ div >
128
+ </ div >
129
+ < div className = "text-center p-3 bg-green-50 rounded-lg" >
130
+ < div className = "text-xl font-bold text-green-600" >
131
+ { Math . round ( ( result . attempt . total_score / result . session . questions . length ) * 100 ) } %
132
+ </ div >
133
+ < div className = "text-xs text-gray-600" > Accuracy</ div >
134
+ </ div >
135
+ < div className = "text-center p-3 bg-blue-50 rounded-lg" >
136
+ < div className = "text-xl font-bold text-blue-600" >
137
+ { formatReadingTime ( result . attempt . reading_time_ms ) }
138
+ </ div >
139
+ < div className = "text-xs text-gray-600" > Time</ div >
140
+ </ div >
141
+ </ div >
142
+
143
+ { /* Right: Answer Breakdown */ }
144
+ < div >
145
+ < h4 className = "font-medium text-gray-800 mb-2" > Answer Breakdown</ h4 >
146
+ < div className = "space-y-1" >
147
+ { result . attempt . answers . map ( ( answer , answerIndex ) => (
148
+ < div key = { answerIndex } className = "flex items-center text-sm" >
149
+ < span className = "w-8" > Q{ answerIndex + 1 } :</ span >
150
+ < span className = { `px-2 py-0.5 rounded text-xs font-medium ${
151
+ answer . is_correct
152
+ ? 'bg-green-100 text-green-800'
153
+ : 'bg-red-100 text-red-800'
154
+ } `} >
155
+ { answer . is_correct ? '✓' : '✗' }
156
+ </ span >
157
+ { answer . overridden && (
158
+ < span className = "ml-1 text-xs text-orange-600" > (Override)</ span >
159
+ ) }
160
+ </ div >
161
+ ) ) }
162
+ </ div >
163
+ </ div >
164
+ </ div >
165
+
166
+ { /* Expandable Details */ }
167
+ < details className = "mt-4" >
168
+ < summary className = "cursor-pointer text-sm text-primary hover:text-red-700" >
169
+ View Details
170
+ </ summary >
171
+ < div className = "mt-4 pt-4 border-t border-gray-200" >
172
+ < div className = "mb-4" >
173
+ < h5 className = "font-medium text-gray-800 mb-2" > Passage:</ h5 >
174
+ < div className = "text-sm text-gray-600 bg-gray-50 p-3 rounded-md max-h-40 overflow-y-auto" >
175
+ { result . session . passage }
176
+ </ div >
177
+ </ div >
178
+ < div >
179
+ < h5 className = "font-medium text-gray-800 mb-2" > Questions & Answers :</ h5 >
180
+ < div className = "space-y-3" >
181
+ { result . session . questions . map ( ( question , qIndex ) => {
182
+ const userAnswer = result . attempt . answers . find ( a => a . question_index === qIndex ) ;
183
+ return (
184
+ < div key = { qIndex } className = "text-sm border-l-2 border-gray-200 pl-3" >
185
+ < div className = "font-medium text-gray-800 mb-1" >
186
+ Q{ qIndex + 1 } : { question . question_text }
187
+ </ div >
188
+ < div className = "text-gray-600" >
189
+ < div > Your answer: < span className = "font-medium" > { userAnswer ?. user_answer || 'No answer' } </ span > </ div >
190
+ < div > Correct answer: < span className = "font-medium text-green-700" > { question . correct_answer } </ span > </ div >
191
+ </ div >
192
+ </ div >
193
+ ) ;
194
+ } ) }
195
+ </ div >
196
+ </ div >
197
+ </ div >
198
+ </ details >
199
+ </ div >
200
+ ) ) }
201
+ </ div >
202
+ ) }
203
+ </ div >
204
+ </ div >
205
+ ) ;
206
+ }
0 commit comments