11'use client' ;
22
33import Link from 'next/link' ;
4+ import { useMemo , useState } from 'react' ;
45import { Badge } from './Badge' ;
56import { EmptyState } from './EmptyState' ;
67import { TransactionLink } from './TransactionLink' ;
78import type { Claim } from '@/types' ;
8- import { formatUSDC , formatDateTime , shortenAddress } from '@/lib/format' ;
9+ import { formatUSDC , formatDateTime } from '@/lib/format' ;
910
1011interface ClaimHistoryTableProps {
1112 claims : Claim [ ] ;
1213 className ?: string ;
1314}
1415
1516export function ClaimHistoryTable ( { claims, className } : ClaimHistoryTableProps ) {
17+ type SortColumn = 'submittedAt' | 'status' | 'payoutAmount' | 'triggerMet' ;
18+ type SortDirection = 'asc' | 'desc' ;
19+
20+ const [ sort , setSort ] = useState < { column : SortColumn ; direction : SortDirection } > ( {
21+ column : 'submittedAt' ,
22+ direction : 'desc' ,
23+ } ) ;
24+
25+ const sortedClaims = useMemo ( ( ) => {
26+ const compareValues = ( a : Claim , b : Claim ) : number => {
27+ switch ( sort . column ) {
28+ case 'submittedAt' :
29+ return a . submittedAt - b . submittedAt ;
30+ case 'status' :
31+ return a . status . localeCompare ( b . status ) ;
32+ case 'payoutAmount' : {
33+ const aValue = a . payoutAmount ? BigInt ( a . payoutAmount ) : null ;
34+ const bValue = b . payoutAmount ? BigInt ( b . payoutAmount ) : null ;
35+ if ( aValue === null && bValue === null ) return 0 ;
36+ if ( aValue === null ) return 1 ;
37+ if ( bValue === null ) return - 1 ;
38+ if ( aValue === bValue ) return 0 ;
39+ return aValue < bValue ? - 1 : 1 ;
40+ }
41+ case 'triggerMet' :
42+ return Number ( a . triggerMet ) - Number ( b . triggerMet ) ;
43+ }
44+
45+ return 0 ;
46+ } ;
47+
48+ const direction = sort . direction === 'asc' ? 1 : - 1 ;
49+
50+ return claims
51+ . map ( ( claim , index ) => ( { claim, index } ) )
52+ . sort ( ( left , right ) => {
53+ if ( sort . column === 'payoutAmount' ) {
54+ const leftValue = left . claim . payoutAmount ? BigInt ( left . claim . payoutAmount ) : null ;
55+ const rightValue = right . claim . payoutAmount ? BigInt ( right . claim . payoutAmount ) : null ;
56+
57+ if ( leftValue === null && rightValue === null ) {
58+ return left . index - right . index ;
59+ }
60+
61+ if ( leftValue === null ) return 1 ;
62+ if ( rightValue === null ) return - 1 ;
63+
64+ if ( leftValue !== rightValue ) {
65+ const payoutDirection = sort . direction === 'asc' ? 1 : - 1 ;
66+ return ( leftValue < rightValue ? - 1 : 1 ) * payoutDirection ;
67+ }
68+ }
69+
70+ const primary = compareValues ( left . claim , right . claim ) ;
71+ if ( primary !== 0 ) {
72+ return primary * direction ;
73+ }
74+ return left . index - right . index ;
75+ } )
76+ . map ( ( { claim } ) => claim ) ;
77+ } , [ claims , sort ] ) ;
78+
79+ const toggleSort = ( column : SortColumn ) => {
80+ setSort ( ( current ) => {
81+ if ( current . column === column ) {
82+ return {
83+ column,
84+ direction : current . direction === 'asc' ? 'desc' : 'asc' ,
85+ } ;
86+ }
87+
88+ return {
89+ column,
90+ direction : column === 'status' ? 'asc' : 'desc' ,
91+ } ;
92+ } ) ;
93+ } ;
94+
95+ const sortIndicator = ( column : SortColumn ) => {
96+ if ( sort . column !== column ) return null ;
97+ return sort . direction === 'asc' ? '↑' : '↓' ;
98+ } ;
99+
16100 if ( ! claims . length ) {
17101 return (
18102 < EmptyState
@@ -31,15 +115,55 @@ export function ClaimHistoryTable({ claims, className }: ClaimHistoryTableProps)
31115 < tr className = "border-b border-white/10 text-left text-xs uppercase tracking-wider text-gray-500" >
32116 < th className = "pb-3 pr-4" > Claim ID</ th >
33117 < th className = "pb-3 pr-4" > Policy</ th >
34- < th className = "pb-3 pr-4" > Trigger</ th >
35- < th className = "pb-3 pr-4" > Payout</ th >
36- < th className = "pb-3 pr-4" > Submitted</ th >
118+ < th className = "pb-3 pr-4" >
119+ < button
120+ type = "button"
121+ onClick = { ( ) => toggleSort ( 'triggerMet' ) }
122+ className = { `inline-flex items-center gap-1 transition-colors hover:text-white ${
123+ sort . column === 'triggerMet' ? 'text-white' : ''
124+ } `}
125+ >
126+ Trigger { sortIndicator ( 'triggerMet' ) }
127+ </ button >
128+ </ th >
129+ < th className = "pb-3 pr-4" >
130+ < button
131+ type = "button"
132+ onClick = { ( ) => toggleSort ( 'payoutAmount' ) }
133+ className = { `inline-flex items-center gap-1 transition-colors hover:text-white ${
134+ sort . column === 'payoutAmount' ? 'text-white' : ''
135+ } `}
136+ >
137+ Payout { sortIndicator ( 'payoutAmount' ) }
138+ </ button >
139+ </ th >
140+ < th className = "pb-3 pr-4" >
141+ < button
142+ type = "button"
143+ onClick = { ( ) => toggleSort ( 'submittedAt' ) }
144+ className = { `inline-flex items-center gap-1 transition-colors hover:text-white ${
145+ sort . column === 'submittedAt' ? 'text-white' : ''
146+ } `}
147+ >
148+ Submitted { sortIndicator ( 'submittedAt' ) }
149+ </ button >
150+ </ th >
37151 < th className = "pb-3 pr-4" > Tx</ th >
38- < th className = "pb-3" > Status</ th >
152+ < th className = "pb-3" >
153+ < button
154+ type = "button"
155+ onClick = { ( ) => toggleSort ( 'status' ) }
156+ className = { `inline-flex items-center gap-1 transition-colors hover:text-white ${
157+ sort . column === 'status' ? 'text-white' : ''
158+ } `}
159+ >
160+ Status { sortIndicator ( 'status' ) }
161+ </ button >
162+ </ th >
39163 </ tr >
40164 </ thead >
41165 < tbody >
42- { claims . map ( ( claim ) => (
166+ { sortedClaims . map ( ( claim ) => (
43167 < tr
44168 key = { claim . id }
45169 className = "border-b border-white/5 transition-colors hover:bg-white/[0.02]"
0 commit comments