11import { useState , useEffect } from 'react' ;
22import { history as historyApi } from '../utils/api' ;
3+ import ReviewResults from '../components/ReviewResults' ;
4+ import TextResults from '../components/TextResults' ;
35import {
46 Clock ,
57 Scan ,
@@ -8,6 +10,9 @@ import {
810 Loader2 ,
911 AlertCircle ,
1012 Inbox ,
13+ ChevronRight ,
14+ ArrowLeft ,
15+ Code2 ,
1116} from 'lucide-react' ;
1217
1318function getTypeIcon ( type ) {
@@ -56,13 +61,75 @@ function formatDate(isoString) {
5661 if ( diffHr < 24 ) return `${ diffHr } h ago` ;
5762 if ( diffDay < 7 ) return `${ diffDay } d ago` ;
5863
59- return date . toLocaleDateString ( 'en-US' , { month : 'short' , day : 'numeric' } ) ;
64+ return date . toLocaleDateString ( 'en-US' , { month : 'short' , day : 'numeric' , year : 'numeric' } ) ;
6065}
6166
67+ // ── Detail View ──
68+ function HistoryDetailView ( { detail, onBack } ) {
69+ return (
70+ < div className = "animate-fade-in" >
71+ { /* Back button + header */ }
72+ < button
73+ onClick = { onBack }
74+ className = "btn-ghost mb-4 -ml-2"
75+ >
76+ < ArrowLeft size = { 14 } />
77+ Back to history
78+ </ button >
79+
80+ < div className = "flex items-center gap-3 mb-5" >
81+ { getTypeIcon ( detail . review_type ) }
82+ < div >
83+ < div className = "flex items-center gap-2" >
84+ < span className = { `badge border text-[10px] ${ getTypeBadgeClass ( detail . review_type ) } ` } >
85+ { getTypeLabel ( detail . review_type ) }
86+ </ span >
87+ < span className = "font-mono text-xs text-txt-secondary" > { detail . language } </ span >
88+ < span className = "font-mono text-[10px] text-txt-muted" >
89+ { formatDate ( detail . created_at ) }
90+ </ span >
91+ </ div >
92+ </ div >
93+ </ div >
94+
95+ { /* Original code */ }
96+ < div className = "card p-0 overflow-hidden mb-5" >
97+ < div className = "flex items-center gap-2 px-4 py-2.5 border-b border-surface-5 bg-surface-1/50" >
98+ < Code2 size = { 13 } className = "text-txt-muted" />
99+ < span className = "font-mono text-[10px] text-txt-muted tracking-wide" >
100+ Submitted code · { detail . language }
101+ </ span >
102+ </ div >
103+ < pre className = "p-4 overflow-x-auto" >
104+ < code className = "font-mono text-xs text-txt-secondary leading-relaxed" >
105+ { detail . code }
106+ </ code >
107+ </ pre >
108+ </ div >
109+
110+ { /* Results */ }
111+ { detail . review_type === 'review' && detail . result && (
112+ < ReviewResults result = { detail . result } cached = { false } />
113+ ) }
114+
115+ { detail . review_type === 'explain' && detail . result ?. explanation && (
116+ < TextResults content = { detail . result . explanation } type = "explain" cached = { false } />
117+ ) }
118+
119+ { detail . review_type === 'refactor' && detail . result ?. suggestions && (
120+ < TextResults content = { detail . result . suggestions } type = "refactor" cached = { false } />
121+ ) }
122+ </ div >
123+ ) ;
124+ }
125+
126+ // ── Main History Page ──
62127export default function History ( ) {
63128 const [ items , setItems ] = useState ( [ ] ) ;
64129 const [ loading , setLoading ] = useState ( true ) ;
65130 const [ error , setError ] = useState ( '' ) ;
131+ const [ detail , setDetail ] = useState ( null ) ;
132+ const [ detailLoading , setDetailLoading ] = useState ( false ) ;
66133
67134 useEffect ( ( ) => {
68135 loadHistory ( ) ;
@@ -79,81 +146,118 @@ export default function History() {
79146 }
80147 } ;
81148
149+ const loadDetail = async ( id ) => {
150+ setDetailLoading ( true ) ;
151+ setError ( '' ) ;
152+ try {
153+ const response = await historyApi . getById ( id ) ;
154+ setDetail ( response . data ) ;
155+ } catch ( err ) {
156+ setError ( 'Failed to load review details.' ) ;
157+ } finally {
158+ setDetailLoading ( false ) ;
159+ }
160+ } ;
161+
162+ const handleBack = ( ) => {
163+ setDetail ( null ) ;
164+ } ;
165+
82166 return (
83167 < div className = "max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-6" >
84- < div className = "mb-6" >
85- < h1 className = "font-display font-bold text-xl text-txt-primary" > Review History</ h1 >
86- < p className = "text-sm text-txt-muted mt-1" >
87- Your last 50 code analyses, most recent first.
88- </ p >
89- </ div >
168+ { /* Show detail view if selected */ }
169+ { detail && ! detailLoading && (
170+ < HistoryDetailView detail = { detail } onBack = { handleBack } />
171+ ) }
90172
91- { loading && (
173+ { detailLoading && (
92174 < div className = "card flex items-center justify-center py-20" >
93175 < Loader2 size = { 24 } className = "text-accent-cyan animate-spin" />
94176 </ div >
95177 ) }
96178
97- { error && (
98- < div className = "flex items-center gap-2 bg-accent-red/5 border border-accent-red/20 text-accent-red text-sm px-4 py-3 rounded-lg" >
99- < AlertCircle size = { 14 } />
100- { error }
101- </ div >
102- ) }
179+ { /* Show list if no detail selected */ }
180+ { ! detail && ! detailLoading && (
181+ < >
182+ < div className = "mb-6" >
183+ < h1 className = "font-display font-bold text-xl text-txt-primary" > Review History</ h1 >
184+ < p className = "text-sm text-txt-muted mt-1" >
185+ Your last 50 code analyses, most recent first. Click to view details.
186+ </ p >
187+ </ div >
103188
104- { ! loading && ! error && items . length === 0 && (
105- < div className = "card flex flex-col items-center justify-center py-20 text-center" >
106- < Inbox size = { 28 } className = "text-txt-muted/30 mb-3" />
107- < p className = "text-sm text-txt-muted" > No reviews yet</ p >
108- < p className = "font-mono text-[10px] text-txt-muted/60 mt-1" >
109- Your analysis history will appear here
110- </ p >
111- </ div >
112- ) }
189+ { loading && (
190+ < div className = "card flex items-center justify-center py-20" >
191+ < Loader2 size = { 24 } className = "text-accent-cyan animate-spin" />
192+ </ div >
193+ ) }
194+
195+ { error && (
196+ < div className = "flex items-center gap-2 bg-accent-red/5 border border-accent-red/20 text-accent-red text-sm px-4 py-3 rounded-lg" >
197+ < AlertCircle size = { 14 } />
198+ { error }
199+ </ div >
200+ ) }
201+
202+ { ! loading && ! error && items . length === 0 && (
203+ < div className = "card flex flex-col items-center justify-center py-20 text-center" >
204+ < Inbox size = { 28 } className = "text-txt-muted/30 mb-3" />
205+ < p className = "text-sm text-txt-muted" > No reviews yet</ p >
206+ < p className = "font-mono text-[10px] text-txt-muted/60 mt-1" >
207+ Your analysis history will appear here
208+ </ p >
209+ </ div >
210+ ) }
211+
212+ { ! loading && items . length > 0 && (
213+ < div className = "card p-0 overflow-hidden" >
214+ < div className = "divide-y divide-surface-5" >
215+ { items . map ( ( item ) => (
216+ < button
217+ key = { item . id }
218+ onClick = { ( ) => loadDetail ( item . id ) }
219+ className = "w-full flex items-center gap-4 px-5 py-3.5 hover:bg-surface-3/30 transition-colors text-left"
220+ >
221+ { /* Type icon */ }
222+ < div className = "shrink-0" > { getTypeIcon ( item . review_type ) } </ div >
113223
114- { ! loading && items . length > 0 && (
115- < div className = "card p-0 overflow-hidden" >
116- < div className = "divide-y divide-surface-5" >
117- { items . map ( ( item ) => (
118- < div
119- key = { item . id }
120- className = "flex items-center gap-4 px-5 py-3.5 hover:bg-surface-3/30 transition-colors"
121- >
122- { /* Type icon */ }
123- < div className = "shrink-0" > { getTypeIcon ( item . review_type ) } </ div >
124-
125- { /* Info */ }
126- < div className = "flex-1 min-w-0" >
127- < div className = "flex items-center gap-2 flex-wrap" >
128- < span className = { `badge border text-[10px] ${ getTypeBadgeClass ( item . review_type ) } ` } >
129- { getTypeLabel ( item . review_type ) }
130- </ span >
131- < span className = "font-mono text-xs text-txt-secondary" >
132- { item . language }
133- </ span >
134- </ div >
135- </ div >
136-
137- { /* Score (review only) */ }
138- < div className = "shrink-0 w-12 text-right" >
139- { item . score != null ? (
140- < span className = { `font-display font-bold text-sm ${ getScoreColor ( item . score ) } ` } >
141- { item . score } /10
142- </ span >
143- ) : (
144- < span className = "text-txt-muted/30" > —</ span >
145- ) }
146- </ div >
147-
148- { /* Time */ }
149- < div className = "shrink-0 flex items-center gap-1.5 text-txt-muted" >
150- < Clock size = { 11 } />
151- < span className = "font-mono text-[10px]" > { formatDate ( item . created_at ) } </ span >
152- </ div >
224+ { /* Info */ }
225+ < div className = "flex-1 min-w-0" >
226+ < div className = "flex items-center gap-2 flex-wrap" >
227+ < span className = { `badge border text-[10px] ${ getTypeBadgeClass ( item . review_type ) } ` } >
228+ { getTypeLabel ( item . review_type ) }
229+ </ span >
230+ < span className = "font-mono text-xs text-txt-secondary" >
231+ { item . language }
232+ </ span >
233+ </ div >
234+ </ div >
235+
236+ { /* Score (review only) */ }
237+ < div className = "shrink-0 w-12 text-right" >
238+ { item . score != null ? (
239+ < span className = { `font-display font-bold text-sm ${ getScoreColor ( item . score ) } ` } >
240+ { item . score } /10
241+ </ span >
242+ ) : (
243+ < span className = "text-txt-muted/30" > —</ span >
244+ ) }
245+ </ div >
246+
247+ { /* Time */ }
248+ < div className = "shrink-0 flex items-center gap-1.5 text-txt-muted" >
249+ < Clock size = { 11 } />
250+ < span className = "font-mono text-[10px]" > { formatDate ( item . created_at ) } </ span >
251+ </ div >
252+
253+ { /* Arrow */ }
254+ < ChevronRight size = { 14 } className = "shrink-0 text-txt-muted/30" />
255+ </ button >
256+ ) ) }
153257 </ div >
154- ) ) }
155- </ div >
156- </ div >
258+ </ div >
259+ ) }
260+ </ >
157261 ) }
158262 </ div >
159263 ) ;
0 commit comments