From 3ce4d206d7b59a915db75c6d539611bff0530a70 Mon Sep 17 00:00:00 2001 From: Syco21 Date: Wed, 25 Mar 2026 19:42:42 +0100 Subject: [PATCH] before optimization --- README.md | 2 ++ src/components/CardRow/CardRow.jsx | 17 ++++++--- src/components/PrintingRow/PrintingRow.jsx | 32 ++++------------- src/components/SearchBar/SearchBar.jsx | 40 ++++++++++++++++++++++ src/index.css | 9 +++-- src/pages/HomePage.jsx | 30 ++++++++++++++-- src/store/CardContext.jsx | 24 +++++++------ 7 files changed, 107 insertions(+), 47 deletions(-) create mode 100644 src/components/SearchBar/SearchBar.jsx diff --git a/README.md b/README.md index a36934d..e806706 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,5 @@ The React Compiler is not enabled on this template because of its impact on dev ## Expanding the ESLint configuration If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. + +## Start Frontend: npm run dev diff --git a/src/components/CardRow/CardRow.jsx b/src/components/CardRow/CardRow.jsx index 005304d..b19e7f3 100644 --- a/src/components/CardRow/CardRow.jsx +++ b/src/components/CardRow/CardRow.jsx @@ -3,19 +3,28 @@ import { CardContext } from '../../store/CardContext'; import PrintingRow from '../PrintingRow/PrintingRow'; function CardRow({ card }) { - const { expandedCardId, setExpandedCardId } = useContext(CardContext); + const { expandedCardId, setExpandedCardId, ownedAmounts } = useContext(CardContext); const isExpanded = expandedCardId === card.id; const toggleExpand = () => setExpandedCardId(isExpanded ? null : card.id); + // Calculate total owned across all printings + const totalOwned = card.printings?.reduce((sum, p) => { + const owned = ownedAmounts[card.id]?.[p.set_id] ?? p.amount_owned ?? 0; + return sum + owned; + }, 0) ?? 0; + return (
{card.name} - {card.type} +
+ {card.type} + Total owned: {totalOwned} +
{isExpanded && card.printings?.length > 0 && ( @@ -33,4 +42,4 @@ function CardRow({ card }) { ); } -export default CardRow; +export default CardRow; \ No newline at end of file diff --git a/src/components/PrintingRow/PrintingRow.jsx b/src/components/PrintingRow/PrintingRow.jsx index 48d44d0..6d5fe68 100644 --- a/src/components/PrintingRow/PrintingRow.jsx +++ b/src/components/PrintingRow/PrintingRow.jsx @@ -1,16 +1,14 @@ -import React, { useContext, useState } from 'react'; +import React, { useContext, useState, memo } from 'react'; import { CardContext } from '../../store/CardContext'; function PrintingRow({ card_id, printing }) { const { ownedAmounts, updateAmount } = useContext(CardContext); const [loading, setLoading] = useState(false); - // Current amount from context, fallback to DB value - const currentAmount = ownedAmounts[card_id]?.[printing.set_id] ?? printing.amount_owned ?? 0; + const currentAmount = ownedAmounts[card_id]?.[printing.set_id]?.[printing.rarity_id] ?? printing.amount_owned ?? 0; const updateBackend = async (newAmount) => { const { set_id, rarity_id } = printing; - if (card_id == null || set_id == null || rarity_id == null) return; setLoading(true); @@ -18,38 +16,22 @@ function PrintingRow({ card_id, printing }) { const response = await fetch('http://localhost:3000/collection/amount', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - card_id, - set_id, - rarity_id, - amount_owned: newAmount - }) + body: JSON.stringify({ card_id, set_id, rarity_id, amount_owned: newAmount }) }); if (response.ok) { - updateAmount(card_id, set_id, newAmount); + updateAmount(card_id, set_id, rarity_id, newAmount); } - } catch (err) { - // silently fail or handle elsewhere } finally { setLoading(false); } }; const increment = () => updateBackend(currentAmount + 1); - const decrement = () => { - if (currentAmount > 0) updateBackend(currentAmount - 1); - }; + const decrement = () => { if (currentAmount > 0) updateBackend(currentAmount - 1); }; return ( -
+
{printing.set_name} {printing.rarity_name}
@@ -60,4 +42,4 @@ function PrintingRow({ card_id, printing }) { ); } -export default PrintingRow; +export default memo(PrintingRow); \ No newline at end of file diff --git a/src/components/SearchBar/SearchBar.jsx b/src/components/SearchBar/SearchBar.jsx new file mode 100644 index 0000000..5838bb0 --- /dev/null +++ b/src/components/SearchBar/SearchBar.jsx @@ -0,0 +1,40 @@ +import React from 'react'; + +function SearchBar({ searchTerm, setSearchTerm }) { + return ( +
+ setSearchTerm(e.target.value)} + style={{ + width: '100%', + padding: '0.5rem 2rem 0.5rem 0.5rem', + borderRadius: '4px', + border: '1px solid #ccc', + boxSizing: 'border-box' + }} + /> + {searchTerm && ( + setSearchTerm('')} + style={{ + position: 'absolute', + right: '0.5rem', + top: '50%', + transform: 'translateY(-50%)', + cursor: 'pointer', + fontSize: '1rem', + color: '#888', + userSelect: 'none' + }} + > + × + + )} +
+ ); +} + +export default SearchBar; \ No newline at end of file diff --git a/src/index.css b/src/index.css index 2c84af0..f42ee28 100644 --- a/src/index.css +++ b/src/index.css @@ -55,17 +55,16 @@ body { } #root { - width: 1126px; - max-width: 100%; - margin: 0 auto; - text-align: center; - border-inline: 1px solid var(--border); + width: 100%; + margin: 0; + text-align: left; /* optional: better for layout apps */ min-height: 100svh; display: flex; flex-direction: column; box-sizing: border-box; } + h1, h2 { font-family: var(--heading); diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index a33a15e..d3eee4c 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -1,14 +1,30 @@ import React, { useEffect, useState, useContext } from 'react'; import CardRow from '../components/CardRow/CardRow'; +import SearchBar from '../components/SearchBar/SearchBar'; import { fetchCards } from '../services/api'; import { CardContext } from '../store/CardContext'; +// Debounce hook +function useDebouncedValue(value, delay = 250) { + const [debounced, setDebounced] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => setDebounced(value), delay); + return () => clearTimeout(handler); + }, [value, delay]); + + return debounced; +} + function HomePage() { const [cards, setCards] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const { expandedCardId, cardImages, setCardImage } = useContext(CardContext); + const debouncedSearchTerm = useDebouncedValue(searchTerm, 250); + useEffect(() => { fetchCards() .then(data => setCards(data)) @@ -33,12 +49,20 @@ function HomePage() { const expandedCard = cards.find(c => c.id === expandedCardId); + // Filter + sort using debounced search + const filteredCards = cards + .filter(card => + card.name.toLowerCase().includes(debouncedSearchTerm.toLowerCase()) + ) + .sort((a, b) => a.name.localeCompare(b.name)); + return (
{/* Left panel: card list */} -
+

Card List

- {cards.map(card => ( + + {filteredCards.map(card => ( ))}
@@ -64,4 +88,4 @@ function HomePage() { ); } -export default HomePage; +export default HomePage; \ No newline at end of file diff --git a/src/store/CardContext.jsx b/src/store/CardContext.jsx index 8309615..b483d35 100644 --- a/src/store/CardContext.jsx +++ b/src/store/CardContext.jsx @@ -3,29 +3,33 @@ import React, { createContext, useState } from 'react'; export const CardContext = createContext(); export function CardProvider({ children }) { - const [expandedCardId, setExpandedCardId] = useState(null); + // ownedAmounts structure: + // { [card_id]: { "[set_id]-[rarity_id]": amount, ... }, ... } const [ownedAmounts, setOwnedAmounts] = useState({}); - const [cardImages, setCardImages] = useState({}); // cache loaded images + const [expandedCardId, setExpandedCardId] = useState(null); + const [cardImages, setCardImages] = useState({}); - // Use set_id as the key to match backend - const updateAmount = (cardId, setId, value) => { + const updateAmount = (card_id, key, amount) => { setOwnedAmounts(prev => ({ ...prev, - [cardId]: { ...(prev[cardId] || {}), [setId]: value } + [card_id]: { + ...prev[card_id], + [key]: amount + } })); }; - const setCardImage = (cardId, imageData) => { - setCardImages(prev => ({ ...prev, [cardId]: imageData })); + const setCardImage = (card_id, blob) => { + setCardImages(prev => ({ ...prev, [card_id]: blob })); }; return ( ); -} +} \ No newline at end of file