11import React , { useMemo , useEffect } from 'react'
22import { motion } from 'framer-motion'
3+ import { LineChart , Line , XAxis , YAxis , ResponsiveContainer , ReferenceLine } from 'recharts'
34import { AnalyzedGame } from 'src/types'
45import { extractPlayerMistakes } from 'src/lib/analysis'
56
@@ -10,7 +11,6 @@ interface Props {
1011}
1112
1213interface GameSummary {
13- totalMoves : number
1414 whiteMistakes : {
1515 total : number
1616 blunders : number
@@ -21,8 +21,11 @@ interface GameSummary {
2121 blunders : number
2222 inaccuracies : number
2323 }
24- averageDepth : number
25- positionsAnalyzed : number
24+ evaluationData : Array < {
25+ ply : number
26+ evaluation : number
27+ moveNumber : number
28+ } >
2629}
2730
2831export const AnalysisSummaryModal : React . FC < Props > = ( {
@@ -47,87 +50,79 @@ export const AnalysisSummaryModal: React.FC<Props> = ({
4750 const whiteMistakes = extractPlayerMistakes ( game . tree , 'white' )
4851 const blackMistakes = extractPlayerMistakes ( game . tree , 'black' )
4952
50- // Calculate analysis depth statistics
51- let totalDepth = 0
52- let positionsAnalyzed = 0
53-
54- mainLine . forEach ( ( node ) => {
55- if ( node . analysis . stockfish && node . analysis . stockfish . depth > 0 ) {
56- totalDepth += node . analysis . stockfish . depth
57- positionsAnalyzed ++
53+ // Generate evaluation data for the chart
54+ const evaluationData = mainLine . slice ( 1 ) . map ( ( node , index ) => {
55+ const evaluation = node . analysis . stockfish ?. model_optimal_cp || 0
56+ // Convert centipawns to evaluation (cap at +/- 5 for chart readability)
57+ const normalizedEval = Math . max ( - 5 , Math . min ( 5 , evaluation / 100 ) )
58+
59+ return {
60+ ply : index + 1 ,
61+ evaluation : normalizedEval ,
62+ moveNumber : Math . ceil ( ( index + 1 ) / 2 ) ,
5863 }
5964 } )
6065
61- const averageDepth =
62- positionsAnalyzed > 0 ? Math . round ( totalDepth / positionsAnalyzed ) : 0
63-
6466 return {
65- totalMoves : Math . ceil ( ( mainLine . length - 1 ) / 2 ) , // Convert plies to moves
6667 whiteMistakes : {
6768 total : whiteMistakes . length ,
6869 blunders : whiteMistakes . filter ( ( m ) => m . type === 'blunder' ) . length ,
69- inaccuracies : whiteMistakes . filter ( ( m ) => m . type === 'inaccuracy' )
70- . length ,
70+ inaccuracies : whiteMistakes . filter ( ( m ) => m . type === 'inaccuracy' ) . length ,
7171 } ,
7272 blackMistakes : {
7373 total : blackMistakes . length ,
7474 blunders : blackMistakes . filter ( ( m ) => m . type === 'blunder' ) . length ,
75- inaccuracies : blackMistakes . filter ( ( m ) => m . type === 'inaccuracy' )
76- . length ,
75+ inaccuracies : blackMistakes . filter ( ( m ) => m . type === 'inaccuracy' ) . length ,
7776 } ,
78- averageDepth,
79- positionsAnalyzed,
77+ evaluationData,
8078 }
8179 } , [ game . tree ] )
8280
8381 if ( ! isOpen ) return null
8482
85- const MistakeSection = ( {
86- title,
87- color,
88- mistakes,
89- playerName,
90- } : {
83+ const formatYAxisLabel = ( value : number ) => {
84+ if ( value === 0 ) return '0.00'
85+ return value > 0 ? `+${ value . toFixed ( 1 ) } ` : `${ value . toFixed ( 1 ) } `
86+ }
87+
88+ const PlayerPerformanceRow = ( {
89+ title,
90+ color,
91+ mistakes,
92+ playerName
93+ } : {
9194 title : string
9295 color : string
9396 mistakes : { total : number ; blunders : number ; inaccuracies : number }
94- playerName : string
97+ playerName : string
9598 } ) => (
96- < div className = "flex flex-col gap-2 rounded-lg border border-white/10 bg-background-2/40 p-4 " >
97- < div className = "flex items-center gap-2 " >
98- < div
99- className = { `h-3 w-3 rounded-full ${ color === 'white' ? 'bg-white' : 'border border-white bg-black' } ` }
100- / >
101- < h3 className = "font-semibold " > { title } </ h3 >
102- < span className = "text-sm text-secondary" > ( { playerName } ) </ span >
99+ < div className = "flex items-center justify-between rounded border border-white/5 bg-background-2/30 p-3 " >
100+ < div className = "flex items-center gap-3 " >
101+ < div className = { `h-4 w-4 rounded-full ${ color === 'white' ? 'bg-white' : 'border border-white bg-black' } ` } />
102+ < div >
103+ < p className = "text-sm font-medium text-primary" > { playerName } </ p >
104+ < p className = "text-xs text-secondary " > { title } </ p >
105+ </ div >
103106 </ div >
104-
107+
105108 { mistakes . total === 0 ? (
106- < div className = "flex items-center gap-2 text-sm text-green-400" >
107- < span className = "material-symbols-outlined !text-sm" >
108- check_circle
109- </ span >
110- < span > No significant mistakes detected</ span >
109+ < div className = "flex items-center gap-2" >
110+ < span className = "material-symbols-outlined !text-sm text-green-400" > check_circle</ span >
111+ < span className = "text-xs text-green-400" > Clean game</ span >
111112 </ div >
112113 ) : (
113- < div className = "grid grid-cols-3 gap-3 text-sm" >
114- < div className = "flex flex-col items-center gap-1 rounded bg-background-3/50 p-2" >
115- < span className = "text-lg font-bold text-red-400" >
116- { mistakes . blunders }
117- </ span >
118- < span className = "text-xs text-secondary" > Blunders</ span >
114+ < div className = "flex items-center gap-3 text-xs" >
115+ < div className = "flex items-center gap-1" >
116+ < span className = "font-semibold text-red-400" > { mistakes . blunders } </ span >
117+ < span className = "text-secondary" > blunders</ span >
119118 </ div >
120- < div className = "flex flex-col items-center gap-1 rounded bg-background-3/50 p-2" >
121- < span className = "text-lg font-bold text-orange-400" >
122- { mistakes . inaccuracies }
123- </ span >
124- < span className = "text-xs text-secondary" > Inaccuracies</ span >
119+ < div className = "flex items-center gap-1" >
120+ < span className = "font-semibold text-orange-400" > { mistakes . inaccuracies } </ span >
121+ < span className = "text-secondary" > inaccuracies</ span >
125122 </ div >
126- < div className = "flex flex-col items-center gap-1 rounded bg-background-3/50 p-2" >
127- < span className = "text-lg font-bold text-primary" >
128- { mistakes . total }
129- </ span >
130- < span className = "text-xs text-secondary" > Total</ span >
123+ < div className = "flex items-center gap-1" >
124+ < span className = "font-semibold text-primary" > { mistakes . total } </ span >
125+ < span className = "text-secondary" > total</ span >
131126 </ div >
132127 </ div >
133128 ) }
@@ -136,7 +131,7 @@ export const AnalysisSummaryModal: React.FC<Props> = ({
136131
137132 return (
138133 < motion . div
139- className = "absolute left -0 top-0 z-20 flex h-screen w-screen flex-col items-center justify-center bg-black/70 px-4 backdrop-blur-sm md:px-0"
134+ className = "fixed inset -0 z-20 flex items-center justify-center bg-black/70 px-4 backdrop-blur-sm md:px-0"
140135 initial = { { opacity : 0 } }
141136 animate = { { opacity : 1 } }
142137 exit = { { opacity : 0 } }
@@ -146,97 +141,104 @@ export const AnalysisSummaryModal: React.FC<Props> = ({
146141 } }
147142 >
148143 < motion . div
149- className = "flex w-full flex-col gap-5 rounded-md border border-white/10 bg-background-1 p-5 md:w-[min(600px,50vw)] md:p-6 "
144+ className = "flex w-full max-w-4xl flex-col gap-5 rounded-lg border border-white/10 bg-background-1 p-6 shadow-2xl "
150145 initial = { { y : 20 , opacity : 0 } }
151146 animate = { { y : 0 , opacity : 1 } }
152147 exit = { { y : 20 , opacity : 0 } }
153148 transition = { { duration : 0.3 } }
154149 onClick = { ( e ) => e . stopPropagation ( ) }
155150 >
156- < div className = "flex items-center gap-2" >
157- < span className = "material-symbols-outlined text-2xl text-human-3" >
158- analytics
159- </ span >
160- < h2 className = "text-xl font-semibold" > Analysis Summary</ h2 >
161- </ div >
162-
163- < div className = "flex flex-col gap-4" >
164- { /* Game Overview */ }
165- < div className = "flex flex-col gap-2" >
166- < h3 className = "font-semibold text-primary/90" > Game Overview</ h3 >
167- < div className = "grid grid-cols-2 gap-3 text-sm md:grid-cols-4" >
168- < div className = "flex flex-col items-center gap-1 rounded bg-background-2/60 p-3" >
169- < span className = "text-lg font-bold text-human-3" >
170- { summary . totalMoves }
171- </ span >
172- < span className = "text-xs text-secondary" > Total Moves</ span >
173- </ div >
174- < div className = "flex flex-col items-center gap-1 rounded bg-background-2/60 p-3" >
175- < span className = "text-lg font-bold text-human-3" >
176- { summary . positionsAnalyzed }
177- </ span >
178- < span className = "text-xs text-secondary" > Positions</ span >
179- </ div >
180- < div className = "flex flex-col items-center gap-1 rounded bg-background-2/60 p-3" >
181- < span className = "text-lg font-bold text-human-3" >
182- d{ summary . averageDepth }
183- </ span >
184- < span className = "text-xs text-secondary" > Avg Depth</ span >
185- </ div >
186- < div className = "flex flex-col items-center gap-1 rounded bg-background-2/60 p-3" >
187- < span className = "text-lg font-bold text-green-400" > 100%</ span >
188- < span className = "text-xs text-secondary" > Complete</ span >
189- </ div >
190- </ div >
151+ { /* Header */ }
152+ < div className = "flex items-center justify-between border-b border-white/10 pb-4" >
153+ < div className = "flex items-center gap-3" >
154+ < span className = "material-symbols-outlined text-2xl text-human-4" >
155+ analytics
156+ </ span >
157+ < h2 className = "text-xl font-bold text-primary" > Analysis Complete</ h2 >
191158 </ div >
159+ < button
160+ onClick = { onClose }
161+ className = "text-secondary transition-colors hover:text-primary"
162+ >
163+ < span className = "material-symbols-outlined" > close</ span >
164+ </ button >
165+ </ div >
192166
167+ < div className = "flex flex-col gap-5" >
193168 { /* Player Performance */ }
194169 < div className = "flex flex-col gap-3" >
195- < h3 className = "font-semibold text-primary/90" >
196- Player Performance
197- </ h3 >
198-
199- < MistakeSection
200- title = "White"
201- color = "white"
202- mistakes = { summary . whiteMistakes }
203- playerName = { game . whitePlayer . name }
204- />
205-
206- < MistakeSection
207- title = "Black"
208- color = "black"
209- mistakes = { summary . blackMistakes }
210- playerName = { game . blackPlayer . name }
211- />
170+ < h3 className = "text-sm font-semibold text-primary/90" > Player Performance</ h3 >
171+ < div className = "flex flex-col gap-2" >
172+ < PlayerPerformanceRow
173+ title = "White"
174+ color = "white"
175+ mistakes = { summary . whiteMistakes }
176+ playerName = { game . whitePlayer . name }
177+ />
178+ < PlayerPerformanceRow
179+ title = "Black"
180+ color = "black"
181+ mistakes = { summary . blackMistakes }
182+ playerName = { game . blackPlayer . name }
183+ />
184+ </ div >
212185 </ div >
213186
214- { /* Analysis Tips */ }
215- < div className = "flex items-start gap-2 rounded bg-human-4/10 p-3" >
216- < span className = "material-symbols-outlined !text-base text-human-3" >
217- lightbulb
218- </ span >
219- < div className = "flex flex-col gap-1" >
220- < p className = "text-sm font-medium text-human-3" > Next Steps</ p >
221- < p className = "text-xs text-primary/80" >
222- Navigate through the game to review specific positions. Use the
223- "Learn from Mistakes" feature to practice improving
224- the identified errors.
225- </ p >
187+ { /* Evaluation Chart */ }
188+ < div className = "flex flex-col gap-3" >
189+ < h3 className = "text-sm font-semibold text-primary/90" > Game Evaluation</ h3 >
190+ < div className = "rounded border border-white/10 bg-background-2/20 p-4" >
191+ < div className = "h-48" >
192+ < ResponsiveContainer width = "100%" height = "100%" >
193+ < LineChart data = { summary . evaluationData } margin = { { top : 5 , right : 5 , left : 5 , bottom : 5 } } >
194+ < XAxis
195+ dataKey = "moveNumber"
196+ tick = { { fontSize : 12 , fill : '#94a3b8' } }
197+ tickLine = { { stroke : '#334155' } }
198+ axisLine = { { stroke : '#334155' } }
199+ />
200+ < YAxis
201+ domain = { [ - 5 , 5 ] }
202+ tickFormatter = { formatYAxisLabel }
203+ tick = { { fontSize : 12 , fill : '#94a3b8' } }
204+ tickLine = { { stroke : '#334155' } }
205+ axisLine = { { stroke : '#334155' } }
206+ />
207+ < ReferenceLine y = { 0 } stroke = "#475569" strokeDasharray = "2 2" />
208+ < Line
209+ type = "monotone"
210+ dataKey = "evaluation"
211+ stroke = "#3b82f6"
212+ strokeWidth = { 2 }
213+ dot = { false }
214+ activeDot = { { r : 4 , fill : '#3b82f6' } }
215+ />
216+ </ LineChart >
217+ </ ResponsiveContainer >
218+ </ div >
219+ < div className = "mt-2 flex items-center justify-center gap-4 text-xs text-secondary" >
220+ < div className = "flex items-center gap-1" >
221+ < div className = "h-2 w-2 rounded-full bg-blue-500" > </ div >
222+ < span > Stockfish Evaluation</ span >
223+ </ div >
224+ < span > •</ span >
225+ < span > Positive values favor White</ span >
226+ </ div >
226227 </ div >
227228 </ div >
228229 </ div >
229230
230- < div className = "flex justify-end gap-2 pt-2" >
231+ { /* Footer */ }
232+ < div className = "flex justify-end border-t border-white/10 pt-4" >
231233 < button
232234 onClick = { onClose }
233- className = "flex h-9 items-center gap-1 rounded bg-human-4 px-4 text-sm font-medium text-white transition duration-200 hover:bg-human-4/90"
235+ className = "flex items-center gap-2 rounded bg-human-4 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-human-4/90"
234236 >
235- < span className = "material-symbols-outlined text-sm" > check</ span >
236- Got it
237+ < span className = "material-symbols-outlined ! text-sm" > check</ span >
238+ Continue
237239 </ button >
238240 </ div >
239241 </ motion . div >
240242 </ motion . div >
241243 )
242- }
244+ }
0 commit comments