add collected page and move the checkbox

This commit is contained in:
2025-08-17 12:32:04 +01:00
parent caee8e27f2
commit b9e7e21919
2 changed files with 55 additions and 15 deletions

View File

@@ -14,6 +14,7 @@ export default function Page() {
const [collected, setCollected] = useState<Set<string>>(new Set()); const [collected, setCollected] = useState<Set<string>>(new Set());
const [setId, setSetId] = useState(''); const [setId, setSetId] = useState('');
const [note, setNote] = useState<string | null>(null); const [note, setNote] = useState<string | null>(null);
const [tab, setTab] = useState<'uncollected' | 'collected'>('uncollected');
useEffect(() => { useEffect(() => {
setCollected(loadChecklist()); setCollected(loadChecklist());
@@ -63,6 +64,15 @@ export default function Page() {
return bySet; return bySet;
}, [cards, query, setId]); }, [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 displayed = useMemo(() => {
return tab === 'collected'
? filtered.filter((c) => collected.has(c.id))
: filtered.filter((c) => !collected.has(c.id));
}, [filtered, collected, tab]);
const setOptions = useMemo(() => { const setOptions = useMemo(() => {
const map = new Map<string, string>(); const map = new Map<string, string>();
for (const c of cards) { for (const c of cards) {
@@ -95,10 +105,40 @@ export default function Page() {
{note} {note}
</div> </div>
) : null} ) : null}
<div className="mb-4 text-sm text-slate-600"> <div className="mb-3 flex items-center gap-2">
Showing {filtered.length} of {cards.length} cards. <button
type="button"
onClick={() => setTab('uncollected')}
className={`px-3 py-1.5 text-sm rounded border transition ${
tab === 'uncollected'
? 'bg-sky-600 text-white border-sky-600'
: 'bg-white text-slate-700 border-slate-300 hover:bg-slate-50'
}`}
aria-pressed={tab === 'uncollected'}
>
Uncollected <span className="ml-1 inline-block rounded bg-white/20 px-1.5 py-0.5 text-xs border border-white/30">
{filteredUncollectedCount}
</span>
</button>
<button
type="button"
onClick={() => setTab('collected')}
className={`px-3 py-1.5 text-sm rounded border transition ${
tab === 'collected'
? 'bg-sky-600 text-white border-sky-600'
: 'bg-white text-slate-700 border-slate-300 hover:bg-slate-50'
}`}
aria-pressed={tab === 'collected'}
>
Collected <span className="ml-1 inline-block rounded bg-white/20 px-1.5 py-0.5 text-xs border border-white/30">
{filteredCollectedCount}
</span>
</button>
</div> </div>
<CardGrid cards={filtered} collected={collected} onToggle={toggleCollected} /> <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} />
</> </>
)} )}
</main> </main>

View File

@@ -7,18 +7,6 @@ import SetBadge from './SetBadge';
export default function CardItem({ card, checked, onToggle }:{ card: TcgCard; checked: boolean; onToggle: (id:string)=>void }) { export default function CardItem({ card, checked, onToggle }:{ card: TcgCard; checked: boolean; onToggle: (id:string)=>void }) {
return ( return (
<div className="group relative overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm hover:shadow-md transition"> <div className="group relative overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm hover:shadow-md transition">
<div className="absolute right-2 top-2 z-10">
<label className="inline-flex items-center gap-2 text-xs bg-white/90 px-2 py-1 rounded shadow border border-slate-200">
<input
type="checkbox"
checked={checked}
onChange={() => onToggle(card.id)}
className="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500"
aria-label={`Mark ${card.name} #${card.number} as collected`}
/>
<span>Collected</span>
</label>
</div>
<div className="relative aspect-[3/4] w-full bg-slate-100"> <div className="relative aspect-[3/4] w-full bg-slate-100">
<Image <Image
src={card.images.small} src={card.images.small}
@@ -34,6 +22,18 @@ export default function CardItem({ card, checked, onToggle }:{ card: TcgCard; ch
{card.rarity ? <span className="text-[0.7rem] rounded bg-slate-100 px-2 py-0.5 text-slate-600">{card.rarity}</span> : null} {card.rarity ? <span className="text-[0.7rem] rounded bg-slate-100 px-2 py-0.5 text-slate-600">{card.rarity}</span> : null}
</div> </div>
<SetBadge set={card.set} /> <SetBadge set={card.set} />
<div className="pt-1">
<label className="inline-flex items-center gap-2 text-xs bg-white px-2 py-1 rounded border border-slate-200">
<input
type="checkbox"
checked={checked}
onChange={() => onToggle(card.id)}
className="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500"
aria-label={`Mark ${card.name} #${card.number} as collected`}
/>
<span>Collected</span>
</label>
</div>
</div> </div>
</div> </div>
); );