add separate checkboxes for variants

This commit is contained in:
2025-08-17 12:37:32 +01:00
parent b9e7e21919
commit e9f132e9dd
5 changed files with 114 additions and 28 deletions

View File

@@ -2,6 +2,7 @@
import React, { useEffect, useMemo, useState } from 'react';
import type { TcgCard } from '@/types/pokemon';
import { loadChecklist, saveChecklist } from '@/lib/checklist';
import type { ChecklistV2, VariantKey } from '@/lib/checklist';
import CardGrid from '@/components/CardGrid';
import Header from '@/components/Header';
import SetFilter from '@/components/SetFilter';
@@ -11,13 +12,13 @@ export default function Page() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [query, setQuery] = useState('');
const [collected, setCollected] = useState<Set<string>>(new Set());
const [checklist, setChecklist] = useState<ChecklistV2>({});
const [setId, setSetId] = useState('');
const [note, setNote] = useState<string | null>(null);
const [tab, setTab] = useState<'uncollected' | 'collected'>('uncollected');
useEffect(() => {
setCollected(loadChecklist());
setChecklist(loadChecklist());
}, []);
useEffect(() => {
@@ -39,15 +40,34 @@ export default function Page() {
load();
}, []);
function toggleCollected(id: string) {
setCollected((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id); else next.add(id);
function toggleCollected(id: string, key: VariantKey) {
setChecklist((prev) => {
const next: ChecklistV2 = { ...prev };
const current = { ...(next[id] || {}) };
current[key] = !current[key];
// Clean up empty records to keep storage tidy
const hasAny = Object.values(current).some(Boolean);
if (hasAny) next[id] = current; else delete next[id];
saveChecklist(next);
return next;
});
}
function availableVariants(card: TcgCard): VariantKey[] {
const list: VariantKey[] = ['base'];
const hasHolo = !!(card.variants?.holofoil || (card as any).tcgplayer?.prices?.holofoil);
const hasReverse = !!(card.variants?.reverseHolofoil || (card as any).tcgplayer?.prices?.reverseHolofoil);
if (hasHolo) list.push('holofoil');
if (hasReverse) list.push('reverseHolofoil');
return list;
}
function isFullyCollected(card: TcgCard): boolean {
const needed = availableVariants(card);
const state = checklist[card.id] || {};
return needed.every((k) => !!state[k]);
}
const filtered = useMemo(() => {
const byQuery = (() => {
if (!query.trim()) return cards;
@@ -64,14 +84,14 @@ export default function Page() {
return bySet;
}, [cards, query, setId]);
const filteredCollectedCount = useMemo(() => filtered.filter((c) => collected.has(c.id)).length, [filtered, collected]);
const filteredUncollectedCount = useMemo(() => filtered.filter((c) => !collected.has(c.id)).length, [filtered, collected]);
const filteredCollectedCount = useMemo(() => filtered.filter((c) => isFullyCollected(c)).length, [filtered, checklist]);
const filteredUncollectedCount = useMemo(() => filtered.length - filteredCollectedCount, [filtered, filteredCollectedCount]);
const displayed = useMemo(() => {
return tab === 'collected'
? filtered.filter((c) => collected.has(c.id))
: filtered.filter((c) => !collected.has(c.id));
}, [filtered, collected, tab]);
? filtered.filter((c) => isFullyCollected(c))
: filtered.filter((c) => !isFullyCollected(c));
}, [filtered, checklist, tab]);
const setOptions = useMemo(() => {
const map = new Map<string, string>();
@@ -87,7 +107,7 @@ export default function Page() {
query={query}
onQueryChange={setQuery}
total={cards.length}
collectedCount={collected.size}
collectedCount={useMemo(() => cards.filter((c) => isFullyCollected(c)).length, [cards, checklist])}
/>
<div className="mb-4 flex flex-wrap items-center gap-3">
@@ -138,7 +158,7 @@ export default function Page() {
<div className="mb-4 text-sm text-slate-600">
Showing {displayed.length} of {filtered.length} cards in this tab.
</div>
<CardGrid cards={displayed} collected={collected} onToggle={toggleCollected} />
<CardGrid cards={displayed} checklist={checklist} onToggle={toggleCollected} />
</>
)}
</main>