Add Cards: keep the Just added list across tabs, open card details

The list now lives for the app session (per library) instead of resetting
whenever the tab is left. Each row gets a button opening the card details
modal to set copies' printings, plus the deck's printing and cover when
the card was sent to a deck; owned-count changes there update the row.

Closes #28

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 20:51:40 -05:00
parent b3f659005d
commit 717ae437c8

View File

@@ -1,14 +1,31 @@
import { useEffect, useRef, useState } from "react";
import { api, CardRef, Deck, DeckEntry, DECK_STATUSES, isPhysical, libraryKey } from "../api";
import { CardSearchBox, QtyStepper } from "../components";
import { CardSearchBox, IconButton, Icons, QtyStepper, useClickPreview } from "../components";
interface Added {
card: CardRef;
qty: number;
location: string;
deck: Deck | null;
}
/** "Just added" lists by library key. Module-level so a list outlives the
* tab switches that unmount this view, but not the app session. */
const addedLists = new Map<string, Added[]>();
/** Rapid card entry: fuzzy search-as-you-type, ↑/↓ + Enter (or click) adds one copy. */
export function CardEntry() {
const [query, setQuery] = useState("");
const [results, setResults] = useState<CardRef[]>([]);
const [recent, setRecent] = useState<
{ oracleId: string; name: string; qty: number; location: string; deck: string }[]
>([]);
const recentKey = libraryKey("add-recent");
const [recent, setRecentState] = useState<Added[]>(() => addedLists.get(recentKey) ?? []);
const setRecent = (update: (r: Added[]) => Added[]) =>
setRecentState((r) => {
const next = update(r);
addedLists.set(recentKey, next);
return next;
});
const { open: openPreview } = useClickPreview();
// Persisted so a "box" location sticks while entering a stack of cards.
// Each library has its own boxes.
const [location, setLocation] = useState(() => localStorage.getItem(libraryKey("add-location")) || "");
@@ -93,9 +110,7 @@ export function CardEntry() {
const loc = intoDeck ? "" : location.trim();
if (loc) await api.setLocation(card.oracle_id, loc);
if (deck) await sendToDeck(deck, card.oracle_id, entry);
setRecent((r) =>
[{ oracleId: card.oracle_id, name: card.name, qty, location: loc, deck: deck?.name ?? "" }, ...r].slice(0, 12),
);
setRecent((r) => [{ card, qty, location: loc, deck }, ...r].slice(0, 12));
setQuery("");
setResults([]);
}
@@ -123,7 +138,19 @@ export function CardEntry() {
async function adjust(oracleId: string, delta: number) {
const qty = await api.addOwned(oracleId, delta);
setRecent((r) => r.map((e) => (e.oracleId === oracleId ? { ...e, qty } : e)));
setRecent((r) => r.map((e) => (e.card.oracle_id === oracleId ? { ...e, qty } : e)));
}
/** The click preview, where copies' printings are set — and, for a card
* sent to a deck, which printing the deck shows. */
async function showDetails(added: Added) {
const { card, deck } = added;
const slot = deck ? (await api.deckContents(deck.id)).find((e) => e.oracle_id === card.oracle_id) : undefined;
openPreview(slot ?? { ...card, quantity_owned: added.qty }, {
deckId: slot ? deck!.id : undefined,
// The preview can change how many are owned; keep the row's count in step.
onChanged: () => adjust(card.oracle_id, 0),
});
}
return (
@@ -172,14 +199,20 @@ export function CardEntry() {
<ul className="result-list">
{recent.map((r, i) => (
<li key={i} style={{ cursor: "default" }}>
<span className="card-name">{r.name}</span>
{r.deck && <span className="deck-chip">→ {r.deck}</span>}
<span className="card-name">{r.card.name}</span>
{r.deck && <span className="deck-chip">→ {r.deck.name}</span>}
{r.location && <span className="deck-chip">{r.location}</span>}
<span className="spacer" />
<IconButton
icon={Icons.image}
label="Card details and printings"
onClick={() => showDetails(r)}
disabled={!r.card.image_uri}
/>
<QtyStepper
value={r.qty}
onDecrement={() => adjust(r.oracleId, -1)}
onIncrement={() => adjust(r.oracleId, 1)}
onDecrement={() => adjust(r.card.oracle_id, -1)}
onIncrement={() => adjust(r.card.oracle_id, 1)}
/>
</li>
))}