support for filtering and updating caliber

This commit is contained in:
2026-01-08 15:26:40 -05:00
parent 72d8f559c9
commit ae86fe6867
16 changed files with 1358 additions and 842 deletions
+47 -4
View File
@@ -8,7 +8,9 @@ export default function Filters(props: {
availableBrands: string[];
brandFilter: string[];
setBrandFilter: (v: string[]) => void;
availableCalibers: string[];
caliberFilter: string[];
setCaliberFilter: (next: string[]) => void;
priceBounds: { min: number | null; max: number | null };
priceRange: { min: number | null; max: number | null };
setPriceRange: (v: { min: number | null; max: number | null }) => void;
@@ -20,6 +22,9 @@ export default function Filters(props: {
availableBrands,
brandFilter,
setBrandFilter,
availableCalibers,
caliberFilter,
setCaliberFilter,
priceBounds,
priceRange,
setPriceRange,
@@ -88,7 +93,10 @@ export default function Filters(props: {
onChange={(e) => {
const value = Number(e.target.value);
setPriceRange({
min: Math.min(value, priceRange.max ?? priceBounds.max ?? value),
min: Math.min(
value,
priceRange.max ?? priceBounds.max ?? value
),
max: priceRange.max ?? priceBounds.max ?? value,
});
}}
@@ -104,7 +112,10 @@ export default function Filters(props: {
const value = Number(e.target.value);
setPriceRange({
min: priceRange.min ?? priceBounds.min ?? value,
max: Math.max(value, priceRange.min ?? priceBounds.min ?? value),
max: Math.max(
value,
priceRange.min ?? priceBounds.min ?? value
),
});
}}
className="w-full"
@@ -168,6 +179,38 @@ export default function Filters(props: {
)}
</div>
{availableCalibers?.length > 0 && (
<div className="mt-4">
<div className="mb-2 text-xs font-semibold tracking-wide text-zinc-300">
Caliber
</div>
<div className="space-y-2">
{availableCalibers.map((c) => {
const checked = caliberFilter.includes(c);
return (
<label
key={c}
className="flex items-center gap-2 text-sm text-zinc-300"
>
<input
type="checkbox"
checked={checked}
onChange={(e) => {
if (e.target.checked)
setCaliberFilter([...caliberFilter, c]);
else
setCaliberFilter(caliberFilter.filter((x) => x !== c));
}}
/>
<span>{c}</span>
</label>
);
})}
</div>
</div>
)}
{/* In-stock toggle */}
<div className="border-t border-zinc-800 pt-3">
<label className="flex cursor-pointer items-center justify-between text-[11px] text-zinc-200">
@@ -187,4 +230,4 @@ export default function Filters(props: {
</div>
</aside>
);
}
}
+18 -1
View File
@@ -112,6 +112,7 @@ export default function PartsBrowseClient(props: {
const [error, setError] = useState<string | null>(null);
const [brandFilter, setBrandFilter] = useState<string[]>([]);
const [caliberFilter, setCaliberFilter] = useState<string[]>([]);
const [sortBy, setSortBy] = useState<SortOption>("relevance");
const [searchQuery, setSearchQuery] = useState("");
const [priceRange, setPriceRange] = useState<{
@@ -151,7 +152,10 @@ export default function PartsBrowseClient(props: {
if (brandFilter.length)
brandFilter.forEach((b) => search.append("brand", b));
// sort mapping
// server search caliber
if (caliberFilter.length)
caliberFilter.forEach((c) => search.append("caliber", c));
// sort mapping (Spring-style: sort=field,dir)
switch (sortBy) {
case "price-asc":
@@ -222,6 +226,7 @@ export default function PartsBrowseClient(props: {
searchQuery,
sortBy,
brandFilter,
caliberFilter,
]);
// Reset pagination on filters
useEffect(() => {
@@ -230,6 +235,7 @@ export default function PartsBrowseClient(props: {
partRole,
effectivePlatform,
brandFilter,
caliberFilter,
sortBy,
searchQuery,
priceRange,
@@ -252,6 +258,14 @@ export default function PartsBrowseClient(props: {
[parts]
);
const availableCalibers = useMemo(
() =>
Array.from(new Set(parts.map((p: any) => p.caliber)))
.filter(Boolean)
.sort((a, b) => String(a).localeCompare(String(b))),
[parts]
);
const priceBounds = useMemo(() => {
const prices = parts
.map((p) => p.price)
@@ -406,6 +420,9 @@ export default function PartsBrowseClient(props: {
availableBrands={availableBrands}
brandFilter={brandFilter}
setBrandFilter={setBrandFilter}
availableCalibers={availableCalibers}
caliberFilter={caliberFilter}
setCaliberFilter={setCaliberFilter}
priceBounds={priceBounds}
priceRange={priceRange}
setPriceRange={setPriceRange}