adding firearm filter and category summary
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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<Record<string, FirearmForm>>({});
|
||||
const [newFirearm, setNewFirearm] = useState<FirearmForm>(emptyFirearmForm);
|
||||
const [showNewFirearmForm, setShowNewFirearmForm] = useState(false);
|
||||
const [selectedFirearmCategory, setSelectedFirearmCategory] = useState(allFirearmCategoriesLabel);
|
||||
const [ammoAdjustments, setAmmoAdjustments] = useState<AmmoAdjustments>({});
|
||||
const [ammoPageCaliberIds, setAmmoPageCaliberIds] = useState<string[]>(() => 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 <T,>(path: string, init: RequestInit = {}, useProfile = true): Promise<T> => {
|
||||
const headers = new Headers(init.headers || {});
|
||||
@@ -962,7 +983,7 @@ export default function Home() {
|
||||
</header>
|
||||
|
||||
{activeView !== 'settings' ? (
|
||||
<section className="stage-stats">
|
||||
<section className={activeView === 'firearms' ? 'stage-stats firearms-stats' : 'stage-stats'}>
|
||||
<div className="mini-stat">
|
||||
<span>{activeView === 'ammo' ? 'Ammo types' : 'Firearms'}</span>
|
||||
<strong>{activeView === 'ammo' ? ammoTypesWithRounds : data.summary.totalFirearms}</strong>
|
||||
@@ -975,6 +996,19 @@ export default function Home() {
|
||||
: currency.format(data.summary.firearmsInvestment)}
|
||||
</strong>
|
||||
</div>
|
||||
{activeView === 'firearms' ? (
|
||||
<div className="mini-stat">
|
||||
<span>Categories</span>
|
||||
<div className="category-count-list" aria-label="Firearm counts by category">
|
||||
{firearmCategoryCounts.map((item) => (
|
||||
<div className="category-count-item" key={item.category}>
|
||||
<span>{item.category}</span>
|
||||
<strong>{item.count}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
@@ -989,13 +1023,28 @@ export default function Home() {
|
||||
<span className="panel-kicker">Registry</span>
|
||||
<h3>Existing firearms</h3>
|
||||
</div>
|
||||
<label className="filter-control">
|
||||
<span>Category filter</span>
|
||||
<select value={selectedFirearmCategory} onChange={(event) => setSelectedFirearmCategory(event.target.value)}>
|
||||
<option value={allFirearmCategoriesLabel}>
|
||||
{allFirearmCategoriesLabel} ({data.summary.totalFirearms})
|
||||
</option>
|
||||
{firearmCategoryCounts.map((item) => (
|
||||
<option key={item.category} value={item.category}>
|
||||
{item.category} ({item.count})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="firearm-grid">
|
||||
{data.firearms.length === 0 ? (
|
||||
<p className="placeholder-copy">No firearms yet. Add your first record from the panel on the right.</p>
|
||||
) : filteredFirearms.length === 0 ? (
|
||||
<p className="placeholder-copy">No firearms match the selected category.</p>
|
||||
) : (
|
||||
data.firearms.map((firearm) => {
|
||||
filteredFirearms.map((firearm) => {
|
||||
const draft = firearmDrafts[firearm.id] ?? buildFirearmForm(firearm);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user