add collected page and move the checkbox
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user