Skip to content

Commit 4c09332

Browse files
hh
1 parent 8a93e35 commit 4c09332

4 files changed

Lines changed: 159 additions & 171 deletions

File tree

components/Yugioh/YugiohCardDataTable.js

Lines changed: 94 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -13,130 +13,112 @@ const YugiohPagination = dynamic(
1313

1414
const 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
);

next.config.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,7 @@
22
const nextConfig = {
33
reactStrictMode: true,
44
productionBrowserSourceMaps: false,
5-
swcMinify: true,
6-
experimental: {
7-
modularizeImports: {
8-
lodash: {
9-
transform: "lodash/{{member}}",
10-
},
11-
},
12-
},
5+
136
async headers() {
147
return [
158
{

0 commit comments

Comments
 (0)