Add filters, stats bar, and fix bugs
ci/woodpecker/push/woodpecker Pipeline was successful

- FilterBar: type chips (Monster/Spell/Trap), owned-only toggle, sort by name/most-owned
- Stats bar: cards in DB, unique owned, total copies
- Card detail panel: type, race, attribute, level/link stars from existing data
- useMemo for filtered+sorted card list (was re-sorting every render)
- Footer: refresh card list after successful full import
- PrintingRow: remove broken custom memo comparator (was comparing static DB field)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 21:06:12 +02:00
parent ebf83aa503
commit a0240499e8
4 changed files with 158 additions and 61 deletions
+45
View File
@@ -0,0 +1,45 @@
import React from 'react';
const TYPES = ['All', 'Monster', 'Spell', 'Trap'];
function FilterBar({ typeFilter, setTypeFilter, ownedOnly, setOwnedOnly, sortBy, setSortBy }) {
return (
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', flexWrap: 'wrap', margin: '0.5rem 0' }}>
<div style={{ display: 'flex', gap: '0.25rem' }}>
{TYPES.map(t => (
<button
key={t}
onClick={() => setTypeFilter(t)}
style={{
padding: '2px 10px',
borderRadius: '12px',
border: '1px solid #ccc',
background: typeFilter === t ? '#444' : 'transparent',
color: typeFilter === t ? '#fff' : '#666',
cursor: 'pointer',
fontSize: '0.8rem',
}}
>
{t}
</button>
))}
</div>
<label style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', cursor: 'pointer', fontSize: '0.85rem', color: '#555' }}>
<input type="checkbox" checked={ownedOnly} onChange={e => setOwnedOnly(e.target.checked)} />
Owned only
</label>
<select
value={sortBy}
onChange={e => setSortBy(e.target.value)}
style={{ fontSize: '0.8rem', padding: '2px 4px', border: '1px solid #ccc', borderRadius: '4px', color: '#555' }}
>
<option value="name">Sort: A Z</option>
<option value="owned">Sort: Most owned</option>
</select>
</div>
);
}
export default FilterBar;
+2 -1
View File
@@ -4,7 +4,7 @@ import { createPortal } from 'react-dom';
import { fetchDatabaseVersion, triggerFullImport } from '../../services/api';
import './Footer.css';
function Footer() {
function Footer({ onImportComplete }) {
const [dbVersion, setDbVersion] = useState(null);
const [importing, setImporting] = useState(false);
const [modalMessage, setModalMessage] = useState('');
@@ -28,6 +28,7 @@ function Footer() {
setDbVersion(data.database_version);
setModalMessage(result.message || 'Import completed');
setShowModal(true);
if (onImportComplete) await onImportComplete();
} catch (err) {
setModalMessage(`Import failed: ${err.message}`);
setShowModal(true);
+1 -15
View File
@@ -59,18 +59,4 @@ function PrintingRow({ card_id, printing }) {
);
}
export default React.memo(
PrintingRow,
(prevProps, nextProps) => {
const prevKey = `${prevProps.printing.set_id}-${prevProps.printing.rarity_id}`;
const nextKey = `${nextProps.printing.set_id}-${nextProps.printing.rarity_id}`;
const prevAmount = prevProps.printing.amount_owned;
const nextAmount = nextProps.printing.amount_owned;
return (
prevProps.card_id === nextProps.card_id &&
prevKey === nextKey &&
prevAmount === nextAmount
);
}
);
export default React.memo(PrintingRow);