From 717ae437c847b5b974c70896737584f2d27d6ea4 Mon Sep 17 00:00:00 2001 From: Garrett Mills Date: Mon, 26 Oct 2026 20:51:40 -0500 Subject: [PATCH] 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 --- src/views/CardEntry.tsx | 57 ++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/src/views/CardEntry.tsx b/src/views/CardEntry.tsx index 938f0d0..e3e9419 100644 --- a/src/views/CardEntry.tsx +++ b/src/views/CardEntry.tsx @@ -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(); /** 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([]); - const [recent, setRecent] = useState< - { oracleId: string; name: string; qty: number; location: string; deck: string }[] - >([]); + const recentKey = libraryKey("add-recent"); + const [recent, setRecentState] = useState(() => 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() {
    {recent.map((r, i) => (
  • - {r.name} - {r.deck && → {r.deck}} + {r.card.name} + {r.deck && → {r.deck.name}} {r.location && {r.location}} + showDetails(r)} + disabled={!r.card.image_uri} + /> adjust(r.oracleId, -1)} - onIncrement={() => adjust(r.oracleId, 1)} + onDecrement={() => adjust(r.card.oracle_id, -1)} + onIncrement={() => adjust(r.card.oracle_id, 1)} />
  • ))}