153 lines
4.5 KiB
TypeScript
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>
|
|
);
|
|
}
|