Files

234 lines
7.5 KiB
TypeScript

"use client";
import React from "react";
export default function Filters(props: {
partRoleLabel: string;
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;
inStockOnly: boolean;
setInStockOnly: (v: boolean) => void;
}) {
const {
availableBrands,
brandFilter,
setBrandFilter,
availableCalibers,
caliberFilter,
setCaliberFilter,
priceBounds,
priceRange,
setPriceRange,
inStockOnly,
setInStockOnly,
} = props;
return (
<aside className="w-full rounded-md border border-zinc-800 bg-zinc-950/80 p-3 space-y-4">
<div>
<h2 className="text-xs font-semibold uppercase tracking-[0.16em] text-zinc-500">
Filters
</h2>
<p className="mt-1 text-[11px] text-zinc-500">
Narrow down the results.
</p>
</div>
{/* Price range */}
<div>
<div className="mb-1.5 flex items-center justify-between">
<span className="text-[11px] font-medium uppercase tracking-[0.12em] text-zinc-500">
Price
</span>
{(priceRange.min !== null || priceRange.max !== null) && (
<button
type="button"
onClick={() =>
setPriceRange({
min: priceBounds.min,
max: priceBounds.max,
})
}
className="text-[10px] text-zinc-400 hover:text-zinc-200"
>
Reset
</button>
)}
</div>
{priceBounds.min == null || priceBounds.max == null ? (
<p className="text-[11px] text-zinc-500">No price data available.</p>
) : (
<div className="space-y-3">
<div className="flex items-center justify-between text-[10px] text-zinc-400">
<span>
Min:{" "}
<span className="font-semibold text-zinc-200">
${(priceRange.min ?? priceBounds.min).toFixed(0)}
</span>
</span>
<span>
Max:{" "}
<span className="font-semibold text-zinc-200">
${(priceRange.max ?? priceBounds.max).toFixed(0)}
</span>
</span>
</div>
<div className="space-y-2">
<input
type="range"
min={priceBounds.min ?? 0}
max={priceBounds.max ?? 0}
value={priceRange.min ?? priceBounds.min ?? 0}
onChange={(e) => {
const value = Number(e.target.value);
setPriceRange({
min: Math.min(
value,
priceRange.max ?? priceBounds.max ?? value
),
max: priceRange.max ?? priceBounds.max ?? value,
});
}}
className="w-full"
/>
<input
type="range"
min={priceBounds.min ?? 0}
max={priceBounds.max ?? 0}
value={priceRange.max ?? priceBounds.max ?? 0}
onChange={(e) => {
const value = Number(e.target.value);
setPriceRange({
min: priceRange.min ?? priceBounds.min ?? value,
max: Math.max(
value,
priceRange.min ?? priceBounds.min ?? value
),
});
}}
className="w-full"
/>
</div>
</div>
)}
</div>
{/* Brand multi-select */}
<div>
<div className="mb-1.5 flex items-center justify-between">
<span className="text-[11px] font-medium uppercase tracking-[0.12em] text-zinc-500">
Brand
</span>
{brandFilter.length > 0 && (
<button
type="button"
onClick={() => setBrandFilter([])}
className="text-[10px] text-zinc-400 hover:text-zinc-200"
>
Clear
</button>
)}
</div>
{availableBrands.length === 0 ? (
<p className="text-[11px] text-zinc-500">No brands available yet.</p>
) : (
<div className="max-h-56 space-y-1 overflow-y-auto pr-1">
{availableBrands.map((b) => {
const checked = brandFilter.includes(b);
return (
<label
key={b}
className="flex cursor-pointer items-center gap-2 rounded px-1 py-0.5 text-[11px] text-zinc-200 hover:bg-zinc-900/80"
>
<input
type="checkbox"
className="h-3 w-3 rounded border-zinc-600 bg-zinc-900 text-amber-400 focus:ring-amber-400"
checked={checked}
onChange={(e) => {
setBrandFilter(
e.target.checked
? [...brandFilter, b]
: brandFilter.filter((x) => x !== b)
);
}}
/>
<span className="truncate">{b}</span>
</label>
);
})}
</div>
)}
{brandFilter.length === 0 && availableBrands.length > 0 && (
<p className="mt-1 text-[10px] text-zinc-500">
No brands selected showing all.
</p>
)}
</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">
<span className="font-medium uppercase tracking-[0.12em] text-zinc-500">
In stock only
</span>
<input
type="checkbox"
className="h-3 w-3 rounded border-zinc-600 bg-zinc-900 text-amber-400 focus:ring-amber-400"
checked={inStockOnly}
onChange={(e) => setInStockOnly(e.target.checked)}
/>
</label>
<p className="mt-1 text-[10px] text-zinc-500">
Hides items marked out of stock by the backend.
</p>
</div>
</aside>
);
}