From de2b63cd87161ec6b8f02c52fccb54f6efdca1ec Mon Sep 17 00:00:00 2001 From: Corey Blais Date: Thu, 26 Mar 2026 17:12:47 -0400 Subject: [PATCH] adding firearm filter and category summary --- frontend/src/index.css | 34 ++++++++++++++++++++++++ frontend/src/pages/Home.tsx | 53 +++++++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index 56d214b..338d07c 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -412,6 +412,10 @@ label span { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.firearms-stats { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + .mini-stat { border-radius: 22px; padding: 1.2rem 1.35rem; @@ -429,6 +433,28 @@ label span { font-size: 1.65rem; } +.category-count-list { + display: grid; + gap: 0.55rem; +} + +.category-count-item { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + color: #dfe5d3; +} + +.category-count-item span { + margin-bottom: 0; + color: #b7bead; +} + +.category-count-item strong { + font-size: 1rem; +} + .view-grid { grid-template-columns: minmax(0, 1.35fr) minmax(360px, 0.8fr); } @@ -524,6 +550,14 @@ label span { padding: 1rem 0; } +.filter-control { + min-width: 210px; +} + +.filter-control span { + margin-bottom: 0.45rem; +} + .settings-row { padding: 0.95rem 0; border-top: 1px solid rgba(171, 180, 140, 0.08); diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 0809891..0567cb5 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -116,6 +116,7 @@ const profileStorageKey = 'arsenal-iq-profile'; const ammoPageSelectionKey = 'arsenal-iq-ammo-page-calibers'; const ammoPageSelectionMigrationKey = 'arsenal-iq-ammo-page-calibers-v2'; const firearmCategories = ['Handgun', 'Rifle', 'Shotgun', 'PCC', 'Other']; +const allFirearmCategoriesLabel = 'All categories'; const defaultCaliberNames = ['9mm', '.22 LR', '5.56 NATO', '.308 Win', '12 Gauge', '.45 ACP']; const currency = new Intl.NumberFormat('en-US', { @@ -204,6 +205,7 @@ export default function Home() { const [firearmDrafts, setFirearmDrafts] = useState>({}); const [newFirearm, setNewFirearm] = useState(emptyFirearmForm); const [showNewFirearmForm, setShowNewFirearmForm] = useState(false); + const [selectedFirearmCategory, setSelectedFirearmCategory] = useState(allFirearmCategoriesLabel); const [ammoAdjustments, setAmmoAdjustments] = useState({}); const [ammoPageCaliberIds, setAmmoPageCaliberIds] = useState(() => loadStoredAmmoPageSelection()); const [selectedAmmoCaliberId, setSelectedAmmoCaliberId] = useState(''); @@ -268,6 +270,25 @@ export default function Home() { () => enabledCalibers.filter((caliber) => !ammoPageCaliberIds.includes(caliber.id)), [ammoPageCaliberIds, enabledCalibers], ); + const firearmCategoryCounts = useMemo( + () => + firearmCategories.map((category) => ({ + category, + count: data?.firearms.filter((firearm) => firearm.category === category).length ?? 0, + })), + [data], + ); + const filteredFirearms = useMemo(() => { + if (!data) { + return []; + } + + if (selectedFirearmCategory === allFirearmCategoriesLabel) { + return data.firearms; + } + + return data.firearms.filter((firearm) => firearm.category === selectedFirearmCategory); + }, [data, selectedFirearmCategory]); const apiFetch = async (path: string, init: RequestInit = {}, useProfile = true): Promise => { const headers = new Headers(init.headers || {}); @@ -962,7 +983,7 @@ export default function Home() { {activeView !== 'settings' ? ( -
+
{activeView === 'ammo' ? 'Ammo types' : 'Firearms'} {activeView === 'ammo' ? ammoTypesWithRounds : data.summary.totalFirearms} @@ -975,6 +996,19 @@ export default function Home() { : currency.format(data.summary.firearmsInvestment)}
+ {activeView === 'firearms' ? ( +
+ Categories +
+ {firearmCategoryCounts.map((item) => ( +
+ {item.category} + {item.count} +
+ ))} +
+
+ ) : null}
) : null} @@ -989,13 +1023,28 @@ export default function Home() { Registry

Existing firearms

+
{data.firearms.length === 0 ? (

No firearms yet. Add your first record from the panel on the right.

+ ) : filteredFirearms.length === 0 ? ( +

No firearms match the selected category.

) : ( - data.firearms.map((firearm) => { + filteredFirearms.map((firearm) => { const draft = firearmDrafts[firearm.id] ?? buildFirearmForm(firearm); return (