@@ -69,6 +68,38 @@ function SetCardRow({ card, zebra }) {
);
}
+function CardGroup({ group, isExpanded, onToggle }) {
+ const { ownedAmounts } = useContext(CardContext);
+ const total = group.printings.reduce((sum, p) => {
+ const key = `${p.set_id ?? ''}-${p.rarity_id}`;
+ return sum + (ownedAmounts[group.id]?.[key] ?? p.amount_owned ?? 0);
+ }, 0);
+
+ return (
+ <>
+
+
{group.name}
+
+ {total > 0 && ×{total}}
+ {isExpanded ? '▲' : '▼'}
+
+
+ {isExpanded && group.printings.map((p, i) => (
+
+ ))}
+ >
+ );
+}
+
function SetsPage() {
const [sets, setSets] = useState([]);
const [loading, setLoading] = useState(true);
@@ -76,6 +107,7 @@ function SetsPage() {
const [setCards, setSetCards] = useState([]);
const [cardsLoading, setCardsLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
+ const [expandedCardId, setExpandedCardId] = useState(null);
const isMobile = useMediaQuery('(max-width: 768px)');
useEffect(() => {
@@ -86,6 +118,7 @@ function SetsPage() {
if (selectedSet?.id === set.id) { setSelectedSet(null); setSetCards([]); return; }
setSelectedSet(set);
setSetCards([]);
+ setExpandedCardId(null);
setCardsLoading(true);
fetchSetCards(set.id).then(setSetCards).catch(console.error).finally(() => setCardsLoading(false));
};
@@ -95,6 +128,15 @@ function SetsPage() {
return sets.filter(s => fuzzyMatch(s.set_name, searchTerm) || fuzzyMatch(s.set_code, searchTerm));
}, [sets, searchTerm]);
+ const groupedCards = useMemo(() => {
+ const map = new Map();
+ for (const card of setCards) {
+ if (!map.has(card.id)) map.set(card.id, { id: card.id, name: card.name, printings: [] });
+ map.get(card.id).printings.push(card);
+ }
+ return [...map.values()];
+ }, [setCards]);
+
const setDetail = selectedSet && (
@@ -109,15 +151,20 @@ function SetsPage() {
: (
- CardRarityOwned
+ CardOwned
- {setCards.map((card, i) => (
-
+ {groupedCards.map(group => (
+
setExpandedCardId(expandedCardId === group.id ? null : group.id)}
+ />
))}
)