18
18
.case-card {
19
19
background : white; margin-bottom : 30px ; border-radius : 8px ;
20
20
box-shadow : 0 2px 4px rgba (0 , 0 , 0 , 0.1 ); overflow : hidden;
21
+ position : relative;
22
+ }
23
+ .case-card .reviewed {
24
+ border-left : 5px solid # 16a34a ;
25
+ }
26
+ .case-card .not-reviewed {
27
+ border-left : 5px solid # dc2626 ;
21
28
}
22
29
.case-header {
23
30
background : # f8f9fa ; padding : 15px 20px ;
26
33
}
27
34
.case-id { font-weight : 600 ; color : # 333 ; }
28
35
.case-meta { font-size : 0.9em ; color : # 666 ; }
36
+ .review-status {
37
+ display : inline-flex; align-items : center; gap : 8px ;
38
+ padding : 4px 12px ; border-radius : 16px ; font-size : 0.8em ; font-weight : 500 ;
39
+ }
40
+ .review-status .reviewed {
41
+ background : # dcfce7 ; color : # 166534 ;
42
+ }
43
+ .review-status .not-reviewed {
44
+ background : # fef2f2 ; color : # dc2626 ;
45
+ }
46
+ .progress-bar {
47
+ background : # f3f4f6 ; border-radius : 8px ; height : 8px ; margin-top : 10px ;
48
+ overflow : hidden;
49
+ }
50
+ .progress-fill {
51
+ background : linear-gradient (90deg , # 16a34a, # 22c55e );
52
+ height : 100% ; transition : width 0.3s ease;
53
+ }
29
54
.case-content { padding : 20px ; }
30
55
.section { margin-bottom : 25px ; }
31
56
.section h4 {
@@ -166,6 +191,13 @@ <h1>📝 Review: {{ data.summary.prompt_version if data.summary else 'Unknown' }
166
191
{{ "%.2f"|format(data.summary.average_metrics.overall_score) }}</ p >
167
192
{% endif %}
168
193
{% endif %}
194
+
195
+ < div id ="review-progress " style ="display: none; ">
196
+ < p > < strong > Review Progress:</ strong > < span id ="progress-text "> Loading...</ span > </ p >
197
+ < div class ="progress-bar ">
198
+ < div class ="progress-fill " id ="progress-fill " style ="width: 0% "> </ div >
199
+ </ div >
200
+ </ div >
169
201
</ div >
170
202
171
203
< div class ="reviewer-info ">
@@ -185,11 +217,16 @@ <h4>👤 Reviewer Information</h4>
185
217
{% if result.success %}
186
218
< div class ="case-card ">
187
219
< div class ="case-header ">
188
- < div class ="case-id "> {{ result.case_id }}</ div >
189
- < div class ="case-meta ">
190
- {{ result.test_case.language }} | {{ result.test_case.compiler }} |
191
- Audience: {{ result.test_case.audience }} |
192
- Type: {{ result.test_case.explanation_type }}
220
+ < div >
221
+ < div class ="case-id "> {{ result.case_id }}</ div >
222
+ < div class ="case-meta ">
223
+ {{ result.test_case.language }} | {{ result.test_case.compiler }} |
224
+ Audience: {{ result.test_case.audience }} |
225
+ Type: {{ result.test_case.explanation_type }}
226
+ </ div >
227
+ </ div >
228
+ < div class ="review-status not-reviewed " id ="status-{{ result.case_id }} ">
229
+ < span > ⚪</ span > Not Reviewed
193
230
</ div >
194
231
</ div >
195
232
@@ -351,6 +388,10 @@ <h4>✏️ Your Review</h4>
351
388
breaks : true
352
389
} ) ;
353
390
391
+ // Global state for existing reviews
392
+ let existingReviews = { } ;
393
+ let reviewProgress = { total : 0 , reviewed : 0 } ;
394
+
354
395
// Initialize page on load
355
396
document . addEventListener ( 'DOMContentLoaded' , function ( ) {
356
397
// Load saved reviewer name from localStorage
@@ -364,6 +405,9 @@ <h4>✏️ Your Review</h4>
364
405
localStorage . setItem ( 'reviewerName' , this . value ) ;
365
406
} ) ;
366
407
408
+ // Load existing reviews for this prompt version
409
+ loadExistingReviews ( ) ;
410
+
367
411
// Render all markdown responses
368
412
{ % for result in data . results % }
369
413
{ % if result . success % }
@@ -376,6 +420,116 @@ <h4>✏️ Your Review</h4>
376
420
{ % endfor % }
377
421
} ) ;
378
422
423
+ async function loadExistingReviews ( ) {
424
+ try {
425
+ const promptVersion = '{{ data.summary.prompt_version if data.summary else "unknown" }}' ;
426
+ const response = await fetch ( `/api/reviews/prompt/${ encodeURIComponent ( promptVersion ) } ` ) ;
427
+ const data = await response . json ( ) ;
428
+
429
+ existingReviews = data . reviews_by_case || { } ;
430
+
431
+ // Update UI for each case
432
+ const caseCards = document . querySelectorAll ( '.case-card' ) ;
433
+ reviewProgress . total = caseCards . length ;
434
+ reviewProgress . reviewed = 0 ;
435
+
436
+ caseCards . forEach ( card => {
437
+ const caseId = extractCaseIdFromCard ( card ) ;
438
+ if ( caseId && existingReviews [ caseId ] ) {
439
+ updateCaseReviewStatus ( caseId , true , existingReviews [ caseId ] ) ;
440
+ reviewProgress . reviewed ++ ;
441
+ } else if ( caseId ) {
442
+ updateCaseReviewStatus ( caseId , false ) ;
443
+ }
444
+ } ) ;
445
+
446
+ updateProgressIndicator ( ) ;
447
+
448
+ } catch ( error ) {
449
+ console . error ( 'Failed to load existing reviews:' , error ) ;
450
+ }
451
+ }
452
+
453
+ function extractCaseIdFromCard ( card ) {
454
+ const statusElement = card . querySelector ( '[id^="status-"]' ) ;
455
+ if ( statusElement ) {
456
+ return statusElement . id . replace ( 'status-' , '' ) ;
457
+ }
458
+ return null ;
459
+ }
460
+
461
+ function updateCaseReviewStatus ( caseId , isReviewed , reviewData = null ) {
462
+ const card = document . querySelector ( `#status-${ caseId } ` ) . closest ( '.case-card' ) ;
463
+ const statusElement = document . getElementById ( `status-${ caseId } ` ) ;
464
+ const saveButton = document . querySelector ( `button[onclick="saveReview('${ caseId } ')"]` ) ;
465
+
466
+ if ( isReviewed ) {
467
+ // Mark as reviewed
468
+ card . classList . remove ( 'not-reviewed' ) ;
469
+ card . classList . add ( 'reviewed' ) ;
470
+ statusElement . classList . remove ( 'not-reviewed' ) ;
471
+ statusElement . classList . add ( 'reviewed' ) ;
472
+ statusElement . innerHTML = '<span>✅</span> Reviewed' ;
473
+
474
+ if ( saveButton ) {
475
+ saveButton . textContent = `Update Review for ${ caseId } ` ;
476
+ }
477
+
478
+ // Pre-populate form if review data available
479
+ if ( reviewData ) {
480
+ populateReviewForm ( caseId , reviewData ) ;
481
+ }
482
+ } else {
483
+ // Mark as not reviewed
484
+ card . classList . remove ( 'reviewed' ) ;
485
+ card . classList . add ( 'not-reviewed' ) ;
486
+ statusElement . classList . remove ( 'reviewed' ) ;
487
+ statusElement . classList . add ( 'not-reviewed' ) ;
488
+ statusElement . innerHTML = '<span>⚪</span> Not Reviewed' ;
489
+
490
+ if ( saveButton ) {
491
+ saveButton . textContent = `Save Review for ${ caseId } ` ;
492
+ }
493
+ }
494
+ }
495
+
496
+ function populateReviewForm ( caseId , reviewData ) {
497
+ // Populate numeric scores
498
+ const fields = [ 'accuracy' , 'relevance' , 'conciseness' , 'insight' , 'appropriateness' ] ;
499
+ fields . forEach ( field => {
500
+ const input = document . querySelector ( `input[name="${ field } "][data-case="${ caseId } "]` ) ;
501
+ if ( input && reviewData [ field ] ) {
502
+ input . value = reviewData [ field ] ;
503
+ }
504
+ } ) ;
505
+
506
+ // Populate text areas
507
+ const textFields = [ 'strengths' , 'weaknesses' , 'suggestions' , 'overall_comments' ] ;
508
+ textFields . forEach ( field => {
509
+ const textarea = document . querySelector ( `textarea[name="${ field } "][data-case="${ caseId } "]` ) ;
510
+ if ( textarea && reviewData [ field ] ) {
511
+ if ( Array . isArray ( reviewData [ field ] ) ) {
512
+ textarea . value = reviewData [ field ] . join ( '\n' ) ;
513
+ } else {
514
+ textarea . value = reviewData [ field ] ;
515
+ }
516
+ }
517
+ } ) ;
518
+ }
519
+
520
+ function updateProgressIndicator ( ) {
521
+ const progressContainer = document . getElementById ( 'review-progress' ) ;
522
+ const progressText = document . getElementById ( 'progress-text' ) ;
523
+ const progressFill = document . getElementById ( 'progress-fill' ) ;
524
+
525
+ if ( reviewProgress . total > 0 ) {
526
+ const percentage = Math . round ( ( reviewProgress . reviewed / reviewProgress . total ) * 100 ) ;
527
+ progressText . textContent = `${ reviewProgress . reviewed } /${ reviewProgress . total } cases reviewed (${ percentage } %)` ;
528
+ progressFill . style . width = `${ percentage } %` ;
529
+ progressContainer . style . display = 'block' ;
530
+ }
531
+ }
532
+
379
533
async function saveReview ( caseId ) {
380
534
const reviewerName = document . getElementById ( 'reviewer-name' ) . value . trim ( ) ;
381
535
if ( ! reviewerName ) {
@@ -429,11 +583,21 @@ <h4>✏️ Your Review</h4>
429
583
const result = await response . json ( ) ;
430
584
431
585
if ( result . success ) {
432
- messageEl . textContent = '✅ Saved!' ;
586
+ const wasUpdate = existingReviews [ caseId ] !== undefined ;
587
+ messageEl . textContent = wasUpdate ? '✅ Updated!' : '✅ Saved!' ;
433
588
messageEl . className = 'success-msg' ;
434
589
435
- const inputs = document . querySelectorAll ( `[data-case="${ caseId } "]` ) ;
436
- inputs . forEach ( input => input . disabled = true ) ;
590
+ // Update the global state
591
+ existingReviews [ caseId ] = formData ;
592
+
593
+ // Update UI to show reviewed status
594
+ if ( ! wasUpdate ) {
595
+ reviewProgress . reviewed ++ ;
596
+ }
597
+ updateCaseReviewStatus ( caseId , true , formData ) ;
598
+ updateProgressIndicator ( ) ;
599
+
600
+ // Don't disable inputs for updates, allow further editing
437
601
} else {
438
602
throw new Error ( result . error || 'Failed to save review' ) ;
439
603
}
@@ -443,7 +607,9 @@ <h4>✏️ Your Review</h4>
443
607
messageEl . className = 'error-msg' ;
444
608
} finally {
445
609
button . disabled = false ;
446
- button . textContent = `Save Review for ${ caseId } ` ;
610
+ // Update button text based on current review status
611
+ const isReviewed = existingReviews [ caseId ] !== undefined ;
612
+ button . textContent = isReviewed ? `Update Review for ${ caseId } ` : `Save Review for ${ caseId } ` ;
447
613
}
448
614
}
449
615
</ script >
0 commit comments