11import React , { useState , useEffect , useMemo } from "react" ;
2+ import { LineChart } from "@mui/x-charts/LineChart" ;
23import {
34 Box ,
45 Container ,
@@ -78,25 +79,61 @@ function SummaryTab({}) {
7879 const [ activeIndex , setActiveIndex ] = useState ( 0 ) ;
7980 const routeParams = useParams ( ) ;
8081 const [ summaryStats , setSummaryStats ] = useState ( null ) ;
82+ const [ backupData , setBackupData ] = useState ( null ) ;
83+ const [ fileMetadata , setFileMetadata ] = useState ( null ) ;
8184
8285 useEffect ( ( ) => {
8386 fetch (
8487 `/api/summary_statistics/${ routeParams . courseId } /${ routeParams . assignmentId } /${ routeParams . studentId } ` ,
85- {
86- method : "GET" ,
87- } ,
88+ { method : "GET" } ,
8889 )
8990 . then ( ( response ) => {
90- if ( ! response . ok ) {
91+ if ( ! response . ok )
9192 throw new Error ( `HTTP error! Status: ${ response . status } ` ) ;
92- }
9393 return response . json ( ) ;
9494 } )
95- . then ( ( responseData ) => {
96- setSummaryStats ( responseData ) ;
97- } ) ;
95+ . then ( ( data ) => setSummaryStats ( data ) ) ;
9896 } , [ routeParams ] ) ;
9997
98+ useEffect ( ( ) => {
99+ fetch (
100+ `/api/backups/${ routeParams . courseId } /${ routeParams . assignmentId } /${ routeParams . studentId } ` ,
101+ )
102+ . then ( ( res ) => res . json ( ) )
103+ . then ( ( data ) => setBackupData ( data ) ) ;
104+ } , [ routeParams ] ) ;
105+
106+ useEffect ( ( ) => {
107+ fetch (
108+ `/api/backup_file_metadata/${ routeParams . courseId } /${ routeParams . assignmentId } /${ routeParams . studentId } ` ,
109+ )
110+ . then ( ( res ) => res . json ( ) )
111+ . then ( ( data ) => setFileMetadata ( data ) ) ;
112+ } , [ routeParams ] ) ;
113+
114+ const backupTimestamps =
115+ backupData ?. backups . map ( ( b ) => new Date ( b . created ) ) ?? [ ] ;
116+ const xAxis = [ { data : backupTimestamps , scaleType : "time" , label : "Date" } ] ;
117+ const height = 300 ;
118+
119+ const firstFile = fileMetadata
120+ ? Object . keys ( fileMetadata . files_to_metadata ) [ 0 ]
121+ : null ;
122+ const numLines = firstFile
123+ ? fileMetadata . files_to_metadata [ firstFile ] . num_lines
124+ : [ ] ;
125+
126+ const numQuestionsSolved =
127+ backupData ?. backups . map ( ( b ) => b . history . filter ( ( h ) => h . solved ) . length ) ??
128+ [ ] ;
129+ const numQuestionsUnsolved =
130+ backupData ?. backups . map ( ( b ) => b . history . filter ( ( h ) => ! h . solved ) . length ) ??
131+ [ ] ;
132+ const numAttempts =
133+ backupData ?. backups . map ( ( b ) =>
134+ b . history . reduce ( ( sum , h ) => sum + ( h . attempts ?? 0 ) , 0 ) ,
135+ ) ?? [ ] ;
136+
100137 const menuItems = useMemo (
101138 ( ) =>
102139 summaryStats
@@ -195,6 +232,8 @@ function SummaryTab({}) {
195232 [ summaryStats ] ,
196233 ) ;
197234
235+ const chartsReady = backupData && fileMetadata && backupTimestamps . length > 0 ;
236+
198237 return (
199238 < Container maxWidth = "xl" sx = { { py : 4 } } >
200239 < div
@@ -205,72 +244,108 @@ function SummaryTab({}) {
205244 marginBottom : "2rem" ,
206245 } }
207246 >
208- < Typography variant = "h4" > Summary Statistics </ Typography >
209- < InfoTooltip info = "Summary statistics about this student's performance on this assignment, with comparisons to other students " />
247+ < Typography variant = "h4" > Summary</ Typography >
248+ < InfoTooltip info = "Summary statistics and visualizations about this student's performance on this assignment" />
210249 </ div >
211250
212251 { menuItems . length > 0 ? (
213- < >
214- { /* TODO: generalize this left sidebar + main area into a component */ }
215- < Box sx = { { display : "flex" , gap : 3 , minHeight : "80vh" } } >
216- { /* Left Sidebar */ }
217- < Paper
218- elevation = { 2 }
219- sx = { {
220- width : 240 ,
221- flexShrink : 0 ,
222- borderRadius : 2 ,
223- overflow : "hidden" ,
224- } }
225- >
226- < List >
227- { menuItems . map ( ( item , index ) => (
228- < ListItem key = { item . text } disablePadding >
229- < ListItemButton
230- selected = { activeIndex === index }
231- onClick = { ( ) => setActiveIndex ( index ) }
232- >
233- < ListItemIcon > { item . icon } </ ListItemIcon >
234- < ListItemText primary = { item . text } />
252+ < Box sx = { { display : "flex" , gap : 3 , minHeight : "80vh" } } >
253+ { /* Left Sidebar */ }
254+ < Paper
255+ elevation = { 2 }
256+ sx = { {
257+ width : 240 ,
258+ flexShrink : 0 ,
259+ borderRadius : 2 ,
260+ overflow : "hidden" ,
261+ } }
262+ >
263+ < List >
264+ { menuItems . map ( ( item , index ) => (
265+ < ListItem key = { item . text } disablePadding >
266+ < ListItemButton
267+ selected = { activeIndex === index }
268+ onClick = { ( ) => setActiveIndex ( index ) }
269+ >
270+ < ListItemIcon > { item . icon } </ ListItemIcon >
271+ < ListItemText primary = { item . text } />
235272
236- < Box
237- onClick = { ( e ) => e . stopPropagation ( ) }
238- sx = { {
239- ml : "auto" ,
240- display : "flex" ,
241- alignItems : "center" ,
242- } }
243- >
244- { item . tooltip ? (
245- < InfoTooltip info = { item . tooltip } />
246- ) : null }
247- </ Box >
248- </ ListItemButton >
249- </ ListItem >
250- ) ) }
251- </ List >
252- </ Paper >
253-
254- { /* Main Content Area */ }
255- < Paper
256- elevation = { 1 }
257- sx = { {
258- flexGrow : 1 ,
259- p : 4 ,
260- borderRadius : 2 ,
261- backgroundColor : "#fafafa" ,
262- } }
263- >
264- { menuItems [ activeIndex ] . component }
265- </ Paper >
266- </ Box >
273+ < Box
274+ onClick = { ( e ) => e . stopPropagation ( ) }
275+ sx = { { ml : "auto" , display : "flex" , alignItems : "center" } }
276+ >
277+ { item . tooltip ? (
278+ < InfoTooltip info = { item . tooltip } />
279+ ) : null }
280+ </ Box >
281+ </ ListItemButton >
282+ </ ListItem >
283+ ) ) }
284+ </ List >
285+ </ Paper >
267286
268- { /* <ProblemGanttPlot /> */ }
287+ { /* Main Content Area */ }
288+ < Paper
289+ elevation = { 1 }
290+ sx = { {
291+ flexGrow : 1 ,
292+ p : 4 ,
293+ borderRadius : 2 ,
294+ backgroundColor : "#fafafa" ,
295+ } }
296+ >
297+ { menuItems [ activeIndex ] . component }
298+ </ Paper >
299+ </ Box >
300+ ) : (
301+ < CircularProgress />
302+ ) }
269303
270- < BackupCalendarChart />
304+ < BackupCalendarChart />
305+ < BackupGanttPlot />
271306
272- < BackupGanttPlot />
273- </ >
307+ { chartsReady ? (
308+ < div style = { { marginTop : "2rem" } } >
309+ < LineChart
310+ xAxis = { xAxis }
311+ series = { [
312+ {
313+ curve : "linear" ,
314+ data : numLines ,
315+ label : `# of lines in ${ firstFile } ` ,
316+ } ,
317+ ] }
318+ height = { height }
319+ />
320+ < LineChart
321+ margin = { { top : 100 } }
322+ xAxis = { xAxis }
323+ series = { [
324+ {
325+ curve : "linear" ,
326+ data : numQuestionsSolved ,
327+ label : "# of questions solved" ,
328+ } ,
329+ {
330+ curve : "linear" ,
331+ data : numQuestionsUnsolved ,
332+ label : "# of questions unsolved" ,
333+ } ,
334+ ] }
335+ height = { height }
336+ />
337+ < LineChart
338+ xAxis = { xAxis }
339+ series = { [
340+ {
341+ curve : "linear" ,
342+ data : numAttempts ,
343+ label : "# of attempts" ,
344+ } ,
345+ ] }
346+ height = { height }
347+ />
348+ </ div >
274349 ) : (
275350 < CircularProgress />
276351 ) }
0 commit comments