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:
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user