/* global React, ReactDOM, I, CarMedia, Nav, Footer, FabWa, useReveal,
INVENTORY, fmtPrice, fmtKm, fuelLabel, transmissionLabel, carStatus, carDrive, fullModel, fullVariant, useInventory */
const { useState, useMemo, useEffect, useRef } = React;
/* INVENTORY now comes from shared.jsx */
/* ─── DROPDOWN FILTER ──────────────────────────────────────── */
function FilterDropdown({ label, count, children }) {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
document.addEventListener("mousedown", onClick);
return () => document.removeEventListener("mousedown", onClick);
}, []);
return (
{open &&
{children}
}
);
}
/* ─── SORT DROPDOWN (custom single-select, themed) ─────────── */
function SortDropdown({ value, onChange, options }) {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
document.addEventListener("mousedown", onClick);
return () => document.removeEventListener("mousedown", onClick);
}, []);
const current = options.find((o) => o.v === value) || options[0];
return (
{open && (
{options.map((o) => (
))}
)}
);
}
/* ─── INDIVIDUAL FILTER PANELS ─────────────────────────────── */
function MultiSelect({ title, options, value, onChange, counts }) {
const toggle = (v) => {
onChange(value.includes(v) ? value.filter((x) => x !== v) : [...value, v]);
};
return (
<>
{title}
{options.map((o) => (
))}
>
);
}
function RangeFilter({ title, min, max, value, onChange, unit }) {
const [lo, hi] = value;
return (
<>
{title}
>
);
}
/* ─── PAGE ─────────────────────────────────────────────────── */
const PAGE_SIZE = 9;
function AanbodPage() {
useReveal();
const { cars: liveCars, loading, fromLive } = useInventory();
const INV = liveCars;
const [q, setQ] = useState("");
const [brands, setBrands] = useState([]);
const [bodies, setBodies] = useState([]);
const [fuels, setFuels] = useState([]);
const [transmissions, setTrans] = useState([]);
const [priceRange, setPriceRange] = useState([null, null]);
const [yearRange, setYearRange] = useState([null, null]);
const [sort, setSort] = useState("recent");
const [view, setView] = useState("grid");
const [page, setPage] = useState(1);
// derived filter option lists + counts
const allBrands = useMemo(() => [...new Set(INV.map(c => c.merk))].sort(), [INV]);
const allBodies = useMemo(() => [...new Set(INV.map(c => c.carrosserie))].sort(), [INV]);
const allFuels = useMemo(() => [...new Set(INV.map(c => c.brandstof))].sort(), [INV]);
const allTrans = useMemo(() => [...new Set(INV.map(c => c.transmissie))].sort(), [INV]);
const brandCounts = useMemo(() => Object.fromEntries(allBrands.map(b => [b, INV.filter(c => c.merk === b).length])), [allBrands, INV]);
const filtered = useMemo(() => {
let r = INV.filter((c) => {
const hay = (c.merk + " " + c.model + " " + (c.type || "") + " " + (c.uitrustingsniveau || "")).toLowerCase();
if (q && !hay.includes(q.toLowerCase())) return false;
if (brands.length && !brands.includes(c.merk)) return false;
if (bodies.length && !bodies.includes(c.carrosserie)) return false;
if (fuels.length && !fuels.includes(c.brandstof)) return false;
if (transmissions.length && !transmissions.includes(c.transmissie)) return false;
if (priceRange[0] != null && c.verkoopprijs_particulier < priceRange[0]) return false;
if (priceRange[1] != null && c.verkoopprijs_particulier > priceRange[1]) return false;
if (yearRange[0] != null && c.bouwjaar < yearRange[0]) return false;
if (yearRange[1] != null && c.bouwjaar > yearRange[1]) return false;
return true;
});
if (sort === "price-asc") r.sort((a, b) => a.verkoopprijs_particulier - b.verkoopprijs_particulier);
else if (sort === "price-desc") r.sort((a, b) => b.verkoopprijs_particulier - a.verkoopprijs_particulier);
else if (sort === "year-desc") r.sort((a, b) => b.bouwjaar - a.bouwjaar);
else if (sort === "km-asc") r.sort((a, b) => a.tellerstand - b.tellerstand);
// 'recent' = source order
return r;
}, [INV, q, brands, bodies, fuels, transmissions, priceRange, yearRange, sort]);
// reset page when filters change
useEffect(() => { setPage(1); }, [q, brands, bodies, fuels, transmissions, priceRange, yearRange, sort]);
const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
const pageItems = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
// active filter chips
const chips = [];
brands.forEach((b) => chips.push({ k: "brand-" + b, label: b, onRm: () => setBrands(brands.filter(x => x !== b)) }));
bodies.forEach((b) => chips.push({ k: "body-" + b, label: b, onRm: () => setBodies(bodies.filter(x => x !== b)) }));
fuels.forEach((f) => chips.push({ k: "fuel-" + f, label: f, onRm: () => setFuels(fuels.filter(x => x !== f)) }));
transmissions.forEach((t) => chips.push({ k: "trans-" + t, label: t, onRm: () => setTrans(transmissions.filter(x => x !== t)) }));
if (priceRange[0] != null || priceRange[1] != null) {
chips.push({ k: "price", label: "Prijs " + (priceRange[0] != null ? "€" + priceRange[0] : "") + "–" + (priceRange[1] != null ? "€" + priceRange[1] : ""), onRm: () => setPriceRange([null, null]) });
}
if (yearRange[0] != null || yearRange[1] != null) {
chips.push({ k: "year", label: "Jaar " + (yearRange[0] != null ? yearRange[0] : "") + "–" + (yearRange[1] != null ? yearRange[1] : ""), onRm: () => setYearRange([null, null]) });
}
if (q) chips.push({ k: "q", label: "\u201C" + q + "\u201D", onRm: () => setQ("") });
const clearAll = () => {
setQ(""); setBrands([]); setBodies([]); setFuels([]); setTrans([]);
setPriceRange([null, null]); setYearRange([null, null]);
};
const activeCount = chips.length;
// pagination renderer
const renderPagi = () => {
if (pageCount <= 1) return null;
const items = [];
items.push();
for (let p = 1; p <= pageCount; p++) {
if (p === 1 || p === pageCount || Math.abs(p - page) <= 1) {
items.push();
} else if (p === 2 || p === pageCount - 1) {
items.push(…);
}
}
items.push();
return {items}
;
};
return (
<>
Het complete aanbod.
Voorraad{fromLive ? " · live" : ""}
{loading ? "…" : INV.length} auto's
{activeCount > 0 && (
{activeCount} filter{activeCount === 1 ? "" : "s"} actief
{chips.map((c) => (
{c.label}
))}
)}
{filtered.length} resultaten {filtered.length !== INV.length && · van {INV.length} totaal}
Pagina {page} / {pageCount}
{filtered.length === 0 ? (
Geen auto's gevonden.
Probeer minder filters of zoek op een ander merk.
) : (
{pageItems.map((c, i) => {
const status = carStatus(c);
const statusClass = status === "sold" ? "car--sold" : status === "reserved" ? "car--reserved" : "";
const badge =
status === "sold" ? { cls: "badge--sold", text: "Verkocht" }
: status === "reserved" ? { cls: "badge--reserved", text: "Gereserveerd" }
: status === "expected" ? { cls: "badge--reserved", text: "Verwacht" }
: c._abba_tag === "featured" ? { cls: "badge--featured", text: "Uitgelicht" }
: c._isNew ? { cls: "badge--new", text: "Nieuw binnen", pulse: true }
: null;
return (
{badge && (
{badge.pulse && }
{badge.text}
)}
{fullModel(c)}
{fullVariant(c)}
KM-stand
{fmtKm(c.tellerstand)}
Brandstof
{fuelLabel(c.brandstof)}
Transmissie
{transmissionLabel(c.transmissie)}
Vanaf{fmtPrice(c.verkoopprijs_particulier)}
Bekijk
);
})}
)}
{renderPagi()}
>
);
}
ReactDOM.createRoot(document.getElementById("root")).render();