1
1
'use client' ;
2
- import React from 'react' ;
2
+ import { Dropdown } from '@components/molecules' ;
3
+ import { ArrowDownIcon , ArrowDownUpIcon , ArrowUpIcon } from '@public/icons' ;
4
+ import { useState } from 'react' ;
3
5
4
6
interface Props {
5
7
nodes : GraphNode [ ] ;
@@ -8,10 +10,78 @@ interface Props {
8
10
onClick : ( node : GraphNode ) => void ;
9
11
}
10
12
13
+ const FIELDS : { [ key in 'title' | 'year' | 'citationCount' ] : string } = {
14
+ title : 'Title' ,
15
+ year : 'Year' ,
16
+ citationCount : 'Citations' ,
17
+ }
18
+
11
19
export default function PaperList ( { nodes, hoveredNodeId, onHover, onClick } : Props ) {
20
+ const [ sort , setSort ] = useState < {
21
+ open : boolean ,
22
+ field : keyof typeof FIELDS ,
23
+ direction : 'asc' | 'desc'
24
+ }
25
+ > ( { open : false , field : 'title' , direction : 'asc' } )
26
+
27
+ const sortedNodes = [ ...nodes ] . sort ( ( a , b ) => {
28
+ const field = sort . field ;
29
+ const dir = sort . direction === 'asc' ? 1 : - 1 ;
30
+
31
+ const aValue = a [ field ] ?? '' ;
32
+ const bValue = b [ field ] ?? '' ;
33
+
34
+ if ( typeof aValue === 'string' && typeof bValue === 'string' ) {
35
+ return aValue . localeCompare ( bValue ) * dir ;
36
+ }
37
+ if ( typeof aValue === 'number' && typeof bValue === 'number' ) {
38
+ return ( aValue - bValue ) * dir ;
39
+ }
40
+
41
+ return 0 ; // fallback
42
+ } ) ;
43
+
12
44
return (
13
- < div className = 'h-[650px] overflow-auto shadow-gray-500 shadow-sm' >
14
- { nodes . map ( ( node ) => (
45
+ < div className = 'paper-list overflow-auto max-h-[80vh] shadow-gray-500 shadow-sm' >
46
+ < div className = { `paper-list-options flex items-center justify-between` } >
47
+ < span className = 'font-semibold' >
48
+ { nodes . length } documents
49
+ </ span >
50
+ < span className = 'sort-filter flex items-center gap-2 mx-2' >
51
+ < Dropdown >
52
+ < Dropdown . Toggler >
53
+ < ArrowDownUpIcon
54
+ size = { 18 }
55
+ onClick = { ( ) => setSort ( prev => ( { ...prev , open : ! prev . open } ) ) }
56
+ className = 'cursor-pointer'
57
+ />
58
+ </ Dropdown . Toggler >
59
+ < Dropdown . Content >
60
+ < div className = 'flex items-center border-2 bg-white rounded-md' >
61
+ < span className = { `sort-fields mr-10px` } >
62
+ { Object . entries ( FIELDS ) . map ( ( [ key , value ] ) => (
63
+ < span className = { `mr-2 py-[2px] px-[4px] ${ sort . field === key ? 'bg-blue-200' : '' } cursor-pointer` } key = { key } onClick = { ( ) => setSort ( prev => ( {
64
+ ...prev ,
65
+ field : key as 'title' | 'year' | 'citationCount'
66
+ } ) ) } >
67
+ { value }
68
+ </ span >
69
+ ) ) }
70
+ </ span >
71
+ < span className = 'sort-order flex items-center gap-[2px] cursor-pointer' >
72
+ { sort . direction === 'asc'
73
+ ? < ArrowUpIcon size = { 20 } onClick = { ( ) => setSort ( prev => ( { ...prev , direction : 'desc' } ) ) } />
74
+ : < ArrowDownIcon size = { 20 } onClick = { ( ) => setSort ( prev => ( { ...prev , direction : 'asc' } ) ) } />
75
+ }
76
+
77
+ </ span >
78
+
79
+ </ div >
80
+ </ Dropdown . Content >
81
+ </ Dropdown >
82
+ </ span >
83
+ </ div >
84
+ { sortedNodes . map ( ( node ) => (
15
85
< div
16
86
key = { node . id }
17
87
className = { `p-3
@@ -21,10 +91,13 @@ export default function PaperList({ nodes, hoveredNodeId, onHover, onClick }: Pr
21
91
onMouseLeave = { ( ) => onHover ( null ) }
22
92
onClick = { ( ) => onClick ( node ) }
23
93
>
24
- < div className = "text-sm" > { node . title } </ div >
94
+ < div className = "text-sm" >
95
+ { node . title }
96
+ </ div >
25
97
< div >
26
98
{ ( node . tags ?? [ ] ) . map ( ( tag ) =>
27
- < span key = { tag . name } style = { { backgroundColor : tag . color } } className = 'mt-2 mr-1 p-1 text-[10px] rounded-sm text-white opacity-60' >
99
+ < span key = { tag . name } style = { { backgroundColor : tag . color } }
100
+ className = 'mt-2 mr-1 p-1 text-[10px] rounded-sm text-white opacity-60' >
28
101
{ tag . name }
29
102
</ span > ) }
30
103
</ div >
0 commit comments