Files
Shifted/components/ShopByVehicle.tsx
2026-02-10 01:14:19 +00:00

153 lines
4.5 KiB
TypeScript

"use client";
import { useMemo, useState } from "react";
import catalog from "./data/vehicleCatalog.json";
type Row = { year: number; make: string; model: string; trim: string };
function uniq<T>(arr: T[]) {
return Array.from(new Set(arr));
}
export default function ShopByVehicle({
initial,
onSearch,
vertical = false,
backgroundImage,
}: {
initial?: { year?: string; make?: string; model?: string; trim?: string };
onSearch: (sel: { year: string; make: string; model: string; trim: string }) => void;
vertical?: boolean;
backgroundImage?: string;
}) {
const rows = catalog as Row[];
const [year, setYear] = useState(initial?.year ?? "");
const [make, setMake] = useState(initial?.make ?? "");
const [model, setModel] = useState(initial?.model ?? "");
const [trim, setTrim] = useState(initial?.trim ?? "");
const years = useMemo(() => uniq(rows.map(r => String(r.year))).sort((a, b) => Number(b) - Number(a)), [rows]);
const makes = useMemo(() => {
if (!year) return [];
return uniq(rows.filter(r => String(r.year) === year).map(r => r.make)).sort();
}, [rows, year]);
const models = useMemo(() => {
if (!year || !make) return [];
return uniq(rows.filter(r => String(r.year) === year && r.make === make).map(r => r.model)).sort();
}, [rows, year, make]);
const trims = useMemo(() => {
if (!year || !make || !model) return [];
return uniq(rows.filter(r => String(r.year) === year && r.make === make && r.model === model).map(r => r.trim)).sort();
}, [rows, year, make, model]);
function resetDownstream(from: "year" | "make" | "model") {
if (from === "year") {
setMake("");
setModel("");
setTrim("");
}
if (from === "make") {
setModel("");
setTrim("");
}
if (from === "model") {
setTrim("");
}
}
const wrapStyle = backgroundImage
? { backgroundImage: `url(${backgroundImage})`, backgroundSize: "cover", backgroundPosition: "center" }
: {};
const isActive = Boolean(year && make && model && trim);
return (
<div
className={`card sbv${isActive ? " sbv--active" : ""}`}
style={{ ...wrapStyle, flexDirection: vertical ? "column" : "row" }}
>
<div style={{ width: "100%" }}>
<div className="sbv__controls" style={{ gridTemplateColumns: vertical ? "1fr" : undefined }}>
<select
className="select"
value={year}
onChange={(e) => {
setYear(e.target.value);
resetDownstream("year");
}}
>
<option value="">Year</option>
{years.map((y) => (
<option key={y} value={y}>
{y}
</option>
))}
</select>
<select
className="select"
value={make}
onChange={(e) => {
setMake(e.target.value);
resetDownstream("make");
}}
disabled={!year}
>
<option value="">Make</option>
{makes.map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
<select
className="select"
value={model}
onChange={(e) => {
setModel(e.target.value);
resetDownstream("model");
}}
disabled={!make}
>
<option value="">Model</option>
{models.map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
<select className="select" value={trim} onChange={(e) => setTrim(e.target.value)} disabled={!model}>
<option value="">Trim</option>
{trims.map((t) => (
<option key={t} value={t}>
{t}
</option>
))}
</select>
<button
className="btn"
style={{ gridColumn: vertical ? "auto" : "1 / -1" }}
onClick={() => {
if (!year || !make || !model || !trim) return;
onSearch({ year, make, model, trim });
}}
disabled={!year || !make || !model || !trim}
>
Search
</button>
</div>
<div style={{ marginTop: 10, fontSize: 12, opacity: 0.85 }}>
Note: vehicle options come from data/vehicleCatalog.json (top 12 list for 1970-1991,
full US catalog for 1992-present).
</div>
</div>
</div>
);
}