@@ -13,130 +13,112 @@ const YugiohPagination = dynamic(
1313
1414const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
1515 const router = useRouter ( ) ;
16- const itemsPerPage = 30 ;
16+ const itemsPerPage = 50 ;
1717 const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
1818 const [ sortConfig , setSortConfig ] = useState ( { key : [ ] , direction : 'ascending' } ) ;
1919 const [ selectedKeys , setSelectedKeys ] = useState ( new Set ( ) ) ;
2020 const [ lastCheckedKey , setLastCheckedKey ] = useState ( null ) ;
2121 const [ selectAllChecked , setSelectAllChecked ] = useState ( false ) ;
2222 const [ notification , setNotification ] = useState ( { show : false , message : '' } ) ;
2323
24- // sort handler unchanged
25- const handleSort = useCallback (
26- ( key ) => {
27- setSortConfig ( ( prev ) => {
28- let direction = 'ascending' ;
29- if ( prev . key === key && prev . direction === 'ascending' ) {
30- direction = 'descending' ;
31- }
32- setMatchedCardData ( ( prevData ) => {
33- const sorted = [ ...prevData ] . sort ( ( a , b ) => {
34- const aValue =
35- key === 'marketPrice' ? a . data ?. marketPrice || 0 : a . card [ key ] ;
36- const bValue =
37- key === 'marketPrice' ? b . data ?. marketPrice || 0 : b . card [ key ] ;
38- if ( aValue < bValue ) return direction === 'ascending' ? - 1 : 1 ;
39- if ( aValue > bValue ) return direction === 'ascending' ? 1 : - 1 ;
40- return 0 ;
41- } ) ;
42- return sorted ;
43- } ) ;
44- return { key, direction } ;
45- } ) ;
46- } ,
47- [ setMatchedCardData ]
48- ) ;
24+ // Build base key from card properties (without index)
25+ const makeKey = ( { card } ) =>
26+ `${ card ?. productName } |${ card ?. setName } |${ card ?. number } |${ card ?. printing } ` ;
4927
50- // sorted & paginated
51- const sortedAndPaginatedData = useMemo ( ( ) => {
52- if ( ! Array . isArray ( matchedCardData ) ) {
53- return { currentItems : [ ] , totalCount : 0 } ;
54- }
55- // full sort
56- const sortedData = [ ...matchedCardData ] . sort ( ( a , b ) => {
57- const aValue =
58- sortConfig . key === 'marketPrice'
59- ? a . data ?. marketPrice || 0
60- : a . card [ sortConfig . key ] ;
61- const bValue =
62- sortConfig . key === 'marketPrice'
63- ? b . data ?. marketPrice || 0
64- : b . card [ sortConfig . key ] ;
28+ // 1. Precompute stable unique IDs including duplicate counts for all items in matchedCardData
29+ const itemUniqueIds = useMemo ( ( ) => {
30+ const counts = { } ;
31+ return matchedCardData . map ( ( item ) => {
32+ const baseKey = makeKey ( item ) ;
33+ counts [ baseKey ] = ( counts [ baseKey ] || 0 ) + 1 ;
34+ return `${ baseKey } |${ counts [ baseKey ] } ` ; // e.g. "card|set|num|print|1", "card|set|num|print|2"
35+ } ) ;
36+ } , [ matchedCardData ] ) ;
37+
38+ // 2. Sort matchedCardData with original index retained for referencing unique IDs
39+ const sortedDataWithIndex = useMemo ( ( ) => {
40+ if ( ! Array . isArray ( matchedCardData ) ) return [ ] ;
41+
42+ const sorted = [ ...matchedCardData ] . map ( ( item , index ) => ( { item, originalIndex : index } ) ) ;
43+ sorted . sort ( ( a , b ) => {
44+ const aValue = sortConfig . key === 'marketPrice'
45+ ? a . item . data ?. marketPrice || 0
46+ : a . item . card [ sortConfig . key ] ;
47+ const bValue = sortConfig . key === 'marketPrice'
48+ ? b . item . data ?. marketPrice || 0
49+ : b . item . card [ sortConfig . key ] ;
6550 if ( aValue < bValue ) return sortConfig . direction === 'ascending' ? - 1 : 1 ;
6651 if ( aValue > bValue ) return sortConfig . direction === 'ascending' ? 1 : - 1 ;
6752 return 0 ;
6853 } ) ;
54+ return sorted ;
55+ } , [ matchedCardData , sortConfig ] ) ;
56+
57+ // 3. Pagination slice on sorted data
58+ const sortedAndPaginatedData = useMemo ( ( ) => {
6959 const indexOfLast = currentPage * itemsPerPage ;
7060 const indexOfFirst = indexOfLast - itemsPerPage ;
71- const currentItems = sortedData . slice ( indexOfFirst , indexOfLast ) ;
72- return { currentItems, totalCount : sortedData . length } ;
73- } , [ matchedCardData , currentPage , sortConfig ] ) ;
61+ const currentItems = sortedDataWithIndex . slice ( indexOfFirst , indexOfLast ) ;
62+ return { currentItems, totalCount : matchedCardData . length } ;
63+ } , [ sortedDataWithIndex , currentPage , matchedCardData . length ] ) ;
7464
75- // build unique rowKey
76- const makeKey = ( { card } ) =>
77- `${ card ?. productName } |${ card ?. setName } |${ card ?. number } |${ card ?. printing } ` ;
65+ // Unique IDs in sorted order (full dataset)
66+ const sortedUniqueIds = useMemo (
67+ ( ) => sortedDataWithIndex . map ( ( { originalIndex } ) => itemUniqueIds [ originalIndex ] ) ,
68+ [ sortedDataWithIndex , itemUniqueIds ]
69+ ) ;
7870
79- // checkbox toggle with Shift+Click
71+ // Checkbox toggle with Shift+Click
8072 const toggleCheckbox = useCallback (
81- ( e , rowKey ) => {
73+ ( e , uniqueId ) => {
8274 const isShift = e . nativeEvent . shiftKey ;
8375 const newSelected = new Set ( selectedKeys ) ;
8476
85- if ( isShift && lastCheckedKey !== null ) {
86- // determine range in the full sorted list
87- const fullKeys = matchedCardData . map ( ( item ) => makeKey ( item ) ) ;
88- const start = fullKeys . indexOf ( lastCheckedKey ) ;
89- const end = fullKeys . indexOf ( rowKey ) ;
90- const [ lo , hi ] = start < end ? [ start , end ] : [ end , start ] ;
91- for ( let i = lo ; i <= hi ; i ++ ) {
92- newSelected . add ( fullKeys [ i ] ) ;
77+ if ( isShift && lastCheckedKey !== null && sortedUniqueIds . length ) {
78+ const start = sortedUniqueIds . indexOf ( lastCheckedKey ) ;
79+ const end = sortedUniqueIds . indexOf ( uniqueId ) ;
80+
81+ if ( start !== - 1 && end !== - 1 ) {
82+ const [ lo , hi ] = start < end ? [ start , end ] : [ end , start ] ;
83+ for ( let i = lo ; i <= hi ; i ++ ) {
84+ newSelected . add ( sortedUniqueIds [ i ] ) ;
85+ }
9386 }
9487 } else {
95- if ( newSelected . has ( rowKey ) ) newSelected . delete ( rowKey ) ;
96- else newSelected . add ( rowKey ) ;
97- setLastCheckedKey ( rowKey ) ;
88+ if ( newSelected . has ( uniqueId ) ) newSelected . delete ( uniqueId ) ;
89+ else newSelected . add ( uniqueId ) ;
90+ setLastCheckedKey ( uniqueId ) ;
9891 }
9992
10093 setSelectedKeys ( newSelected ) ;
10194 setSelectAllChecked ( false ) ;
10295 } ,
103- [ selectedKeys , lastCheckedKey , matchedCardData ]
96+ [ selectedKeys , lastCheckedKey , sortedUniqueIds ]
10497 ) ;
10598
106- // Select All / Deselect All logic
107- const pagedKeys = useMemo (
108- ( ) => sortedAndPaginatedData . currentItems . map ( ( item ) => makeKey ( item ) ) ,
109- [ sortedAndPaginatedData ]
110- ) ;
11199
112- const isAllOnPage = pagedKeys . every ( ( k ) => selectedKeys . has ( k ) ) ;
100+ // Current page unique IDs
101+ const pagedUniqueIds = useMemo (
102+ ( ) => sortedAndPaginatedData . currentItems . map ( ( { originalIndex } ) => itemUniqueIds [ originalIndex ] ) ,
103+ [ sortedAndPaginatedData , itemUniqueIds ]
104+ ) ;
113105
114- const toggleSelectAll = useCallback ( ( ) => {
115- if ( ! selectAllChecked ) {
116- // select all dataset
117- const all = matchedCardData . map ( ( item ) => makeKey ( item ) ) ;
118- setSelectedKeys ( new Set ( all ) ) ;
119- } else {
120- setSelectedKeys ( new Set ( ) ) ;
121- }
122- setSelectAllChecked ( ( f ) => ! f ) ;
123- } , [ selectAllChecked , matchedCardData ] ) ;
106+ const isAllOnPage = pagedUniqueIds . length > 0 && pagedUniqueIds . every ( ( k ) => selectedKeys . has ( k ) ) ;
124107
125108 const handleSelectAllOnPage = ( ) => {
126109 const newSet = new Set ( selectedKeys ) ;
127- pagedKeys . forEach ( ( k ) => newSet . add ( k ) ) ;
110+ pagedUniqueIds . forEach ( ( k ) => newSet . add ( k ) ) ;
128111 setSelectedKeys ( newSet ) ;
129112 } ;
130113
131114 const handleDeselectAllOnPage = ( ) => {
132115 const newSet = new Set ( selectedKeys ) ;
133- pagedKeys . forEach ( ( k ) => newSet . delete ( k ) ) ;
116+ pagedUniqueIds . forEach ( ( k ) => newSet . delete ( k ) ) ;
134117 setSelectedKeys ( newSet ) ;
135118 } ;
136119
137120 const handleSelectAllDataset = ( ) => {
138- const all = matchedCardData . map ( ( item ) => makeKey ( item ) ) ;
139- setSelectedKeys ( new Set ( all ) ) ;
121+ setSelectedKeys ( new Set ( itemUniqueIds ) ) ;
140122 } ;
141123
142124 const handleClear = ( ) => {
@@ -145,7 +127,22 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
145127 setSelectAllChecked ( false ) ;
146128 } ;
147129
148- // Collection + CSV logic unchanged
130+ // Sort handler unchanged
131+ const handleSort = useCallback (
132+ ( key ) => {
133+ setSortConfig ( ( prev ) => {
134+ let direction = 'ascending' ;
135+ if ( prev . key === key && prev . direction === 'ascending' ) {
136+ direction = 'descending' ;
137+ }
138+ return { key, direction } ;
139+ } ) ;
140+ setCurrentPage ( 1 ) ;
141+ } ,
142+ [ ]
143+ ) ;
144+
145+ // Add to collection logic unchanged except use uniqueId filtering
149146 const addToCollection = useCallback ( async ( ) => {
150147 if ( selectedKeys . size === 0 ) {
151148 return setNotification ( { show : true , message : 'No cards were selected to add to the collection!' } ) ;
@@ -155,8 +152,8 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
155152 setNotification ( { show : true , message : "You must be logged in to add cards." } ) ;
156153 return ;
157154 }
158- const selectedData = matchedCardData . filter ( ( _ , idx ) =>
159- selectedKeys . has ( makeKey ( matchedCardData [ idx ] ) )
155+ const selectedData = matchedCardData . filter ( ( _ , index ) =>
156+ selectedKeys . has ( itemUniqueIds [ index ] )
160157 ) ;
161158 const collectionArray = selectedData . map ( ( { card, data } ) => ( {
162159 productName : card ?. productName ,
@@ -183,16 +180,17 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
183180 } catch {
184181 setNotification ( { show : true , message : 'Card(s) failed to save!' } ) ;
185182 }
186- } , [ selectedKeys , matchedCardData ] ) ;
183+ } , [ selectedKeys , matchedCardData , itemUniqueIds ] ) ;
187184
185+ // Download CSV using uniqueIds filtering
188186 const downloadCSV = useCallback ( ( ) => {
189187 if ( selectedKeys . size === 0 ) {
190188 setNotification ( { show : true , message : 'No cards selected to download!' } ) ;
191189 return ;
192190 }
193191 const headers = [ "Name" , "Set" , "Number" , "Printing" , "Rarity" , "Condition" , "Market Price" , "Low Price" ] ;
194192 const rows = matchedCardData
195- . filter ( ( _ , idx ) => selectedKeys . has ( makeKey ( matchedCardData [ idx ] ) ) )
193+ . filter ( ( _ , index ) => selectedKeys . has ( itemUniqueIds [ index ] ) )
196194 . map ( ( { card, data } ) => [
197195 card ?. productName ,
198196 card ?. setName ,
@@ -214,24 +212,22 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
214212 link . click ( ) ;
215213 document . body . removeChild ( link ) ;
216214 URL . revokeObjectURL ( url ) ;
217- } , [ selectedKeys , matchedCardData ] ) ;
215+ } , [ selectedKeys , matchedCardData , itemUniqueIds ] ) ;
218216
219217 const handleGoToCollectionPage = useCallback ( ( ) => {
220218 router . push ( '/yugioh/my-collection' ) ;
221219 } , [ router ] ) ;
222220
223221 return (
224- < div className = "mx-auto w-full mb-10 min-h-max " >
222+ < div className = "mx-auto w-full mb-10 min-h-fit " >
225223 < Notification
226224 show = { notification . show }
227225 setShow = { ( show ) => setNotification ( { ...notification , show } ) }
228226 message = { notification . message }
229227 />
230228
231229 { sortedAndPaginatedData . currentItems . length > 0 && (
232- < >
233-
234-
230+ < div >
235231 { /* Pagination */ }
236232 < div className = "w-full -mt-5" >
237233 < div className = "w-fit mx-auto" >
@@ -253,7 +249,6 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
253249 < button onClick = { handleClear } className = "ml-4 underline float-end" > Clear</ button >
254250 < button onClick = { handleSelectAllDataset } className = "ml-4 underline float-end" > Select All in Dataset</ button >
255251 </ div >
256-
257252 </ div >
258253 ) }
259254
@@ -299,18 +294,18 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
299294 </ thead >
300295
301296 < tbody className = "bg-white divide-y divide-gray-300 text-black px-1 py-1 mx-auto" >
302- { sortedAndPaginatedData . currentItems . map ( ( item , idx ) => {
303- const rowKey = makeKey ( item ) ;
304- const isSelected = selectedKeys . has ( rowKey ) ;
297+ { sortedAndPaginatedData . currentItems . map ( ( { item, originalIndex } ) => {
298+ const uniqueId = itemUniqueIds [ originalIndex ] ;
299+ const isSelected = selectedKeys . has ( uniqueId ) ;
305300 const { card, data } = item ;
306301
307302 return (
308- < tr key = { rowKey } className = "hover:bg-gray-100" >
303+ < tr key = { uniqueId } className = "hover:bg-gray-100" >
309304 < td className = "text-center border border-gray-300" >
310305 < input
311306 type = "checkbox"
312307 checked = { isSelected }
313- onChange = { ( e ) => toggleCheckbox ( e , rowKey ) }
308+ onChange = { ( e ) => toggleCheckbox ( e , uniqueId ) }
314309 />
315310 </ td >
316311 < td className = "p-2 text-center border-t border-gray-100 text-xs lg:text-sm sm:text-left text-black hover:bg-black hover:text-white" >
@@ -339,8 +334,8 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
339334 } ) }
340335 </ tbody >
341336 </ table >
342-
343337 </ div >
338+
344339 < div className = "max-h-fit w-full mt-2 flex justify-center space-x-2" >
345340 < button
346341 type = "button"
@@ -365,7 +360,7 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
365360 </ button >
366361 </ div >
367362 </ div >
368- </ >
363+ </ div >
369364 ) }
370365 </ div >
371366 ) ;
0 commit comments