Auto — Parts Inventory
A back-counter parts inventory screen for an auto shop, with a searchable table of SKUs, brands, bin locations, prices and on-hand counts. Filter tabs for low and out-of-stock parts stay in sync with live counts, while a slide-in detail drawer adds plus and minus steppers that recompute each part's status pill and row highlight instantly. Reorder actions fire a supplier toast, and summary cards track total units, low stock and estimated stock value.
MCP
Code
:root {
--garage: #141518;
--garage-2: #1f2127;
--steel: #5b6470;
--steel-l: #8a929d;
--orange: #ff6a13;
--orange-d: #e2540a;
--orange-50: #fff0e6;
--ink: #16181c;
--ink-2: #3b4049;
--muted: #737a85;
--bg: #f3f4f6;
--surface: #ffffff;
--line: rgba(20, 21, 24, 0.1);
--line-2: rgba(20, 21, 24, 0.18);
--ok: #2f9e6f;
--warn: #e0962a;
--danger: #d4493e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 18px;
--sh-1: 0 1px 2px rgba(20, 21, 24, 0.06), 0 1px 3px rgba(20, 21, 24, 0.08);
--sh-2: 0 6px 18px rgba(20, 21, 24, 0.1), 0 2px 6px rgba(20, 21, 24, 0.06);
--sh-3: 0 18px 48px rgba(20, 21, 24, 0.22);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.tnum { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }
.sr-only {
position: absolute; width: 1px; height: 1px;
padding: 0; margin: -1px; overflow: hidden;
clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.app { min-height: 100vh; }
/* ---------- Topbar ---------- */
.topbar {
display: flex; align-items: center; justify-content: space-between;
gap: 16px;
padding: 14px 22px;
background: var(--garage);
color: #fff;
border-bottom: 3px solid var(--orange);
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand-mark {
display: grid; place-items: center;
width: 38px; height: 38px;
border-radius: var(--r-sm);
background: linear-gradient(150deg, var(--orange), var(--orange-d));
font-size: 20px; color: #fff;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
}
.brand-text { display: flex; flex-direction: column; line-height: 1.2; }
.brand-text strong { font-weight: 800; letter-spacing: -0.01em; font-size: 16px; }
.brand-text span { font-size: 11.5px; color: var(--steel-l); font-weight: 500; }
.topbar-right { display: flex; align-items: center; gap: 12px; }
.branch-chip {
font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em;
padding: 5px 11px; border-radius: 999px;
background: var(--garage-2); color: var(--steel-l);
border: 1px solid rgba(255, 255, 255, 0.08);
}
/* ---------- Buttons ---------- */
.btn {
font: inherit; font-weight: 600; cursor: pointer;
border-radius: var(--r-sm); border: 1px solid transparent;
padding: 8px 14px; font-size: 13px;
transition: background 0.15s, border-color 0.15s, transform 0.08s, box-shadow 0.15s;
}
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.btn-ghost {
background: rgba(255, 255, 255, 0.06); color: #fff;
border-color: rgba(255, 255, 255, 0.16);
}
.btn-ghost:hover { background: rgba(255, 255, 255, 0.14); }
.btn-primary {
background: linear-gradient(150deg, var(--orange), var(--orange-d));
color: #fff; box-shadow: var(--sh-1);
}
.btn-primary:hover { filter: brightness(1.05); }
.btn-block { width: 100%; padding: 12px; font-size: 14px; }
/* ---------- Layout ---------- */
.layout {
max-width: 1100px;
margin: 0 auto;
padding: 24px 22px 64px;
display: flex; flex-direction: column; gap: 20px;
}
/* ---------- Stat cards ---------- */
.panel-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
}
.stat-card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 16px 16px 14px;
display: flex; flex-direction: column; gap: 3px;
box-shadow: var(--sh-1);
position: relative; overflow: hidden;
}
.stat-card::before {
content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
background: var(--steel);
}
.stat-card.stat-warn::before { background: var(--warn); }
.stat-card.stat-value-money::before { background: var(--orange); }
.stat-label {
font-size: 11px; font-weight: 700; letter-spacing: 0.05em;
text-transform: uppercase; color: var(--muted);
}
.stat-value { font-size: 26px; font-weight: 800; letter-spacing: -0.02em; color: var(--ink); }
.stat-warn .stat-value { color: var(--warn); }
.stat-sub { font-size: 11.5px; color: var(--muted); }
/* ---------- Toolbar ---------- */
.toolbar {
display: flex; align-items: center; justify-content: space-between;
gap: 14px; flex-wrap: wrap;
}
.search {
position: relative; flex: 1 1 280px; min-width: 220px;
}
.search-ico {
position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
color: var(--steel); font-size: 17px; pointer-events: none;
}
.search input {
width: 100%; font: inherit; font-size: 14px;
padding: 11px 14px 11px 38px;
border: 1px solid var(--line-2); border-radius: var(--r-sm);
background: var(--surface); color: var(--ink);
box-shadow: var(--sh-1);
}
.search input::placeholder { color: var(--muted); }
.search input:focus { outline: none; border-color: var(--orange); box-shadow: 0 0 0 3px var(--orange-50); }
.filters { display: flex; gap: 8px; flex-wrap: wrap; }
.chip {
font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
padding: 8px 13px; border-radius: 999px;
border: 1px solid var(--line-2); background: var(--surface); color: var(--ink-2);
display: inline-flex; align-items: center; gap: 7px;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.chip:hover { border-color: var(--steel); }
.chip:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.chip.is-active { background: var(--garage); color: #fff; border-color: var(--garage); }
.chip-n {
font-size: 11px; font-weight: 700;
min-width: 18px; height: 18px; padding: 0 5px;
border-radius: 999px; display: inline-grid; place-items: center;
background: var(--bg); color: var(--ink-2);
}
.chip.is-active .chip-n { background: rgba(255, 255, 255, 0.18); color: #fff; }
/* ---------- Table ---------- */
.table-wrap {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
box-shadow: var(--sh-2);
}
.parts { width: 100%; border-collapse: collapse; }
.parts thead th {
text-align: left; font-size: 11px; font-weight: 700;
letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted);
padding: 13px 14px;
background: #fafbfc; border-bottom: 1px solid var(--line);
white-space: nowrap;
}
.ta-c { text-align: center !important; }
.ta-r { text-align: right !important; }
.parts tbody td {
padding: 12px 14px;
border-bottom: 1px solid var(--line);
font-size: 13.5px; vertical-align: middle;
}
.parts tbody tr { transition: background 0.12s; cursor: pointer; position: relative; }
.parts tbody tr:hover { background: #f9fafb; }
.parts tbody tr:last-child td { border-bottom: none; }
.parts tbody tr.row-low { box-shadow: inset 3px 0 0 var(--warn); background: #fffaf0; }
.parts tbody tr.row-low:hover { background: #fff5e3; }
.parts tbody tr.row-out { box-shadow: inset 3px 0 0 var(--danger); background: #fdf2f1; }
.parts tbody tr.row-out:hover { background: #fbe9e7; }
.cell-sku { font-weight: 600; color: var(--ink); }
.cell-name { font-weight: 600; color: var(--ink); }
.cell-fits { display: block; font-size: 11.5px; color: var(--muted); font-weight: 500; margin-top: 1px; }
.cell-brand { color: var(--ink-2); }
.cell-bin {
font-weight: 600; color: var(--ink-2);
background: var(--bg); border: 1px solid var(--line);
padding: 2px 8px; border-radius: var(--r-sm); font-size: 12px;
}
.cell-qty { font-weight: 700; font-size: 15px; }
.row-out .cell-qty { color: var(--danger); }
.row-low .cell-qty { color: var(--warn); }
.cell-price { font-weight: 600; color: var(--ink); }
.pill {
display: inline-flex; align-items: center; gap: 5px;
font-size: 11.5px; font-weight: 700; letter-spacing: 0.01em;
padding: 3px 9px; border-radius: 999px; white-space: nowrap;
}
.pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.pill-ok { color: var(--ok); background: rgba(47, 158, 111, 0.12); }
.pill-low { color: #a96e16; background: rgba(224, 150, 42, 0.16); }
.pill-out { color: var(--danger); background: rgba(212, 73, 62, 0.13); }
.row-actions { display: flex; gap: 6px; justify-content: flex-end; }
.icon-btn {
font: inherit; cursor: pointer;
border: 1px solid var(--line-2); background: var(--surface);
color: var(--ink-2); border-radius: var(--r-sm);
width: 32px; height: 32px; display: grid; place-items: center;
font-size: 15px; transition: background 0.12s, border-color 0.12s;
}
.icon-btn:hover { background: var(--bg); border-color: var(--steel); }
.icon-btn:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.reorder-btn {
font: inherit; font-size: 12px; font-weight: 700; cursor: pointer;
padding: 6px 11px; border-radius: var(--r-sm);
border: 1px solid var(--orange); color: var(--orange-d);
background: var(--orange-50); white-space: nowrap;
transition: background 0.12s;
}
.reorder-btn:hover { background: #ffe2cf; }
.reorder-btn:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.empty { text-align: center; color: var(--muted); padding: 40px 16px; font-size: 14px; }
/* ---------- Drawer ---------- */
.scrim {
position: fixed; inset: 0; background: rgba(20, 21, 24, 0.42);
backdrop-filter: blur(1px); z-index: 40;
animation: fade 0.18s ease;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.drawer {
position: fixed; top: 0; right: 0; bottom: 0; z-index: 50;
width: min(400px, 92vw);
background: var(--surface);
box-shadow: var(--sh-3);
display: flex; flex-direction: column; gap: 0;
padding: 20px 22px 24px;
transform: translateX(100%);
transition: transform 0.26s cubic-bezier(0.4, 0, 0.2, 1);
overflow-y: auto;
}
.drawer.is-open { transform: translateX(0); }
.drawer-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; }
.drawer-sku {
font-size: 11.5px; font-weight: 700; letter-spacing: 0.03em;
color: var(--orange-d); background: var(--orange-50);
padding: 2px 8px; border-radius: 999px; display: inline-block;
}
.drawer-head h2 { margin: 8px 0 0; font-size: 19px; font-weight: 800; letter-spacing: -0.01em; }
.drawer-photo {
margin: 16px 0; height: 150px; border-radius: var(--r-md);
background:
radial-gradient(120% 90% at 20% 10%, rgba(255, 255, 255, 0.12), transparent 60%),
linear-gradient(140deg, var(--garage-2), var(--garage));
position: relative; overflow: hidden;
border: 1px solid var(--line);
}
.drawer-photo::after {
content: "⛭"; position: absolute; right: -14px; bottom: -22px;
font-size: 120px; color: rgba(255, 106, 19, 0.16); line-height: 1;
}
.spec { margin: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; }
.spec > div { display: flex; flex-direction: column; gap: 2px; }
.spec dt { font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
.spec dd { margin: 0; font-size: 14px; font-weight: 600; color: var(--ink); }
.drawer-qty {
display: flex; align-items: center; justify-content: space-between;
margin: 20px 0 16px; padding: 14px 16px;
background: var(--bg); border-radius: var(--r-md); border: 1px solid var(--line);
}
.dq-label { font-size: 13px; font-weight: 700; color: var(--ink-2); }
.stepper { display: flex; align-items: center; gap: 4px; }
.step {
font: inherit; cursor: pointer; width: 36px; height: 36px;
border: 1px solid var(--line-2); background: var(--surface);
border-radius: var(--r-sm); font-size: 20px; font-weight: 600;
color: var(--ink); display: grid; place-items: center;
transition: background 0.12s, border-color 0.12s;
}
.step:hover { background: var(--garage); color: #fff; border-color: var(--garage); }
.step:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.step-val { min-width: 46px; text-align: center; font-size: 19px; font-weight: 800; }
/* ---------- Toasts ---------- */
.toast-stack {
position: fixed; right: 18px; bottom: 18px; z-index: 60;
display: flex; flex-direction: column; gap: 10px;
max-width: min(340px, 90vw);
}
.toast {
background: var(--garage); color: #fff;
border-radius: var(--r-md); padding: 13px 15px;
box-shadow: var(--sh-3);
border-left: 3px solid var(--orange);
font-size: 13px; font-weight: 500;
display: flex; gap: 10px; align-items: flex-start;
animation: toastIn 0.24s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
.toast.toast-out { animation: toastOut 0.2s ease forwards; }
.toast-ico { font-size: 16px; line-height: 1.3; }
.toast strong { display: block; font-weight: 700; margin-bottom: 1px; }
.toast span { color: var(--steel-l); }
@keyframes toastIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes toastOut { to { opacity: 0; transform: translateY(8px); } }
/* ---------- Responsive ---------- */
@media (max-width: 820px) {
.panel-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
.topbar { padding: 12px 16px; }
.branch-chip { display: none; }
.layout { padding: 16px 14px 56px; }
.panel-grid { gap: 10px; }
.stat-card { padding: 13px; }
.stat-value { font-size: 22px; }
.hide-sm { display: none; }
.parts thead th, .parts tbody td { padding: 10px 10px; }
.cell-bin { display: none; }
}(function () {
"use strict";
/** @type {Array<Object>} */
var PARTS = [
{ sku: "BRK-1042", name: "Ceramic Brake Pads — Front", fits: "Civic / Accord '18–'23", brand: "StopLine", bin: "A-12", price: 48.9, qty: 14, reorder: 6, supplier: "Midstate Auto Supply" },
{ sku: "FLT-0099", name: "Engine Oil Filter (Spin-On)", fits: "Most 4-cyl import", brand: "PureFlow", bin: "B-03", price: 7.25, qty: 3, reorder: 12, supplier: "Pacific Parts Co." },
{ sku: "BAT-7700", name: "Group 35 AGM Battery", fits: "12V · 650 CCA", brand: "VoltCore", bin: "D-21", price: 184.0, qty: 5, reorder: 4, supplier: "Northgate Distributors" },
{ sku: "SPK-3318", name: "Iridium Spark Plug", fits: "Universal · 14mm", brand: "IgnaTek", bin: "A-05", price: 9.6, qty: 0, reorder: 24, supplier: "Pacific Parts Co." },
{ sku: "WPR-2201", name: 'Beam Wiper Blade 24"', fits: "Hook-style arm", brand: "ClearSweep", bin: "C-09", price: 14.75, qty: 22, reorder: 8, supplier: "Midstate Auto Supply" },
{ sku: "ALT-5560", name: "Alternator 130A (Reman)", fits: "F-150 '15–'20", brand: "ReGen", bin: "E-02", price: 219.5, qty: 2, reorder: 2, supplier: "Northgate Distributors" },
{ sku: "TIR-9001", name: "All-Season Tire 215/55R17", fits: "Touring · 94H", brand: "RoadHold", bin: "T-Rack 4", price: 132.0, qty: 16, reorder: 8, supplier: "Summit Tire Wholesale" },
{ sku: "COOL-440", name: "OAT Coolant Concentrate 1gal", fits: "Long-life · orange", brand: "ThermaShield", bin: "F-14", price: 22.4, qty: 9, reorder: 6, supplier: "Pacific Parts Co." },
{ sku: "BLT-1180", name: "Serpentine Belt 6-Rib", fits: "Camry '12–'17", brand: "DriveLink", bin: "B-18", price: 26.9, qty: 1, reorder: 5, supplier: "Midstate Auto Supply" },
{ sku: "ROT-6620", name: "Vented Brake Rotor — Front", fits: "CR-V '17–'22", brand: "StopLine", bin: "A-13", price: 58.0, qty: 8, reorder: 4, supplier: "Midstate Auto Supply" },
{ sku: "CAB-0301", name: "Cabin Air Filter (Carbon)", fits: "Multi-fit · 21×21cm", brand: "PureFlow", bin: "B-06", price: 12.3, qty: 4, reorder: 10, supplier: "Pacific Parts Co." },
{ sku: "SEN-7745", name: "Upstream O2 Sensor", fits: "Sonata '15–'19", brand: "IgnaTek", bin: "E-07", price: 71.0, qty: 0, reorder: 3, supplier: "Northgate Distributors" },
{ sku: "HUB-3340", name: "Wheel Hub Bearing Assembly", fits: "Rogue '14–'20", brand: "AxlePro", bin: "G-01", price: 96.5, qty: 6, reorder: 3, supplier: "Summit Tire Wholesale" },
{ sku: "FUS-0150", name: "Mini Blade Fuse Kit (120pc)", fits: "Assorted amperage", brand: "CircuitMate", bin: "C-22", price: 18.9, qty: 11, reorder: 4, supplier: "Pacific Parts Co." }
];
var state = { filter: "all", query: "" };
var activeSku = null;
var rowsEl = document.getElementById("rows");
var emptyEl = document.getElementById("empty");
var searchEl = document.getElementById("search");
var toastsEl = document.getElementById("toasts");
var fmtMoney = function (n) {
return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
};
var fmtMoney0 = function (n) {
return "$" + Math.round(n).toLocaleString("en-US");
};
var esc = function (s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
});
};
var statusOf = function (p) {
if (p.qty <= 0) return "out";
if (p.qty <= p.reorder) return "low";
return "ok";
};
var pillFor = function (s) {
if (s === "out") return '<span class="pill pill-out">Out of Stock</span>';
if (s === "low") return '<span class="pill pill-low">Low Stock</span>';
return '<span class="pill pill-ok">In Stock</span>';
};
function matches(p) {
var s = statusOf(p);
if (state.filter !== "all" && state.filter !== s) return false;
var q = state.query.trim().toLowerCase();
if (!q) return true;
return (p.sku + " " + p.name + " " + p.brand + " " + p.fits).toLowerCase().indexOf(q) !== -1;
}
function render() {
var visible = PARTS.filter(matches);
rowsEl.innerHTML = visible.map(rowHtml).join("");
emptyEl.hidden = visible.length !== 0;
updateStats();
}
function rowHtml(p) {
var s = statusOf(p);
var rowClass = s === "out" ? "row-out" : s === "low" ? "row-low" : "";
var reorder =
s === "ok"
? ""
: '<button class="reorder-btn" data-reorder="' + p.sku + '" type="button">Reorder</button>';
return (
'<tr class="' + rowClass + '" data-sku="' + p.sku + '" tabindex="0" role="button" aria-label="Open detail for ' + esc(p.name) + '">' +
'<td class="cell-sku tnum">' + esc(p.sku) + "</td>" +
'<td><span class="cell-name">' + esc(p.name) + '</span><span class="cell-fits hide-sm">' + esc(p.fits) + "</span></td>" +
'<td class="cell-brand hide-sm">' + esc(p.brand) + "</td>" +
'<td class="ta-c hide-sm"><span class="cell-bin tnum">' + esc(p.bin) + "</span></td>" +
'<td class="ta-c cell-qty tnum">' + p.qty + "</td>" +
'<td class="ta-r cell-price tnum hide-sm">' + fmtMoney(p.price) + "</td>" +
'<td class="ta-r">' + pillFor(s) + "</td>" +
'<td class="ta-r"><div class="row-actions">' + reorder + '<button class="icon-btn" data-open="' + p.sku + '" type="button" aria-label="Open detail">›</button></div></td>' +
"</tr>"
);
}
function updateStats() {
var total = PARTS.length;
var units = 0, low = 0, value = 0, out = 0, ok = 0;
PARTS.forEach(function (p) {
units += p.qty;
value += p.qty * p.price;
var s = statusOf(p);
if (s === "low") low++;
else if (s === "out") out++;
else ok++;
});
document.getElementById("statTotal").textContent = total;
document.getElementById("statUnits").textContent = units.toLocaleString("en-US");
document.getElementById("statLow").textContent = low + out;
document.getElementById("statValue").textContent = fmtMoney0(value);
document.getElementById("cAll").textContent = total;
document.getElementById("cLow").textContent = low;
document.getElementById("cOut").textContent = out;
document.getElementById("cOk").textContent = ok;
}
/* ---------- Toast ---------- */
function toast(title, msg, icon) {
var el = document.createElement("div");
el.className = "toast";
el.setAttribute("role", "status");
el.innerHTML =
'<span class="toast-ico" aria-hidden="true">' + (icon || "✓") + "</span>" +
"<div><strong>" + esc(title) + "</strong><span>" + esc(msg) + "</span></div>";
toastsEl.appendChild(el);
setTimeout(function () {
el.classList.add("toast-out");
setTimeout(function () { el.remove(); }, 220);
}, 3600);
}
function reorder(sku) {
var p = find(sku);
if (!p) return;
var suggest = Math.max(p.reorder * 2 - p.qty, p.reorder);
toast(
"Reorder placed — " + p.sku,
suggest + " × " + p.brand + " from " + p.supplier,
"⛟"
);
}
function find(sku) {
for (var i = 0; i < PARTS.length; i++) if (PARTS[i].sku === sku) return PARTS[i];
return null;
}
/* ---------- Drawer ---------- */
var drawer = document.getElementById("drawer");
var scrim = document.getElementById("scrim");
var lastFocus = null;
function openDrawer(sku) {
var p = find(sku);
if (!p) return;
activeSku = sku;
lastFocus = document.activeElement;
document.getElementById("dSku").textContent = p.sku;
document.getElementById("dName").textContent = p.name;
document.getElementById("dBrand").textContent = p.brand;
document.getElementById("dBin").textContent = p.bin;
document.getElementById("dFits").textContent = p.fits;
document.getElementById("dPrice").textContent = fmtMoney(p.price);
document.getElementById("dReorder").textContent = p.reorder + " units";
document.getElementById("dSupplier").textContent = p.supplier;
document.getElementById("dQty").textContent = p.qty;
scrim.hidden = false;
drawer.classList.add("is-open");
drawer.setAttribute("aria-hidden", "false");
drawer.focus();
}
function closeDrawer() {
drawer.classList.remove("is-open");
drawer.setAttribute("aria-hidden", "true");
scrim.hidden = true;
activeSku = null;
if (lastFocus && lastFocus.focus) lastFocus.focus();
}
function adjust(sku, delta) {
var p = find(sku);
if (!p) return;
p.qty = Math.max(0, p.qty + delta);
document.getElementById("dQty").textContent = p.qty;
render();
}
/* ---------- Events ---------- */
searchEl.addEventListener("input", function () {
state.query = searchEl.value;
render();
});
document.querySelectorAll(".chip").forEach(function (chip) {
chip.addEventListener("click", function () {
document.querySelectorAll(".chip").forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-selected", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-selected", "true");
state.filter = chip.getAttribute("data-filter");
render();
});
});
rowsEl.addEventListener("click", function (e) {
var reBtn = e.target.closest("[data-reorder]");
if (reBtn) { e.stopPropagation(); reorder(reBtn.getAttribute("data-reorder")); return; }
var openBtn = e.target.closest("[data-open]");
if (openBtn) { e.stopPropagation(); openDrawer(openBtn.getAttribute("data-open")); return; }
var row = e.target.closest("tr[data-sku]");
if (row) openDrawer(row.getAttribute("data-sku"));
});
rowsEl.addEventListener("keydown", function (e) {
if (e.key !== "Enter" && e.key !== " ") return;
var row = e.target.closest("tr[data-sku]");
if (row && e.target === row) {
e.preventDefault();
openDrawer(row.getAttribute("data-sku"));
}
});
document.getElementById("drawerClose").addEventListener("click", closeDrawer);
scrim.addEventListener("click", closeDrawer);
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && activeSku) closeDrawer();
});
drawer.querySelectorAll(".step").forEach(function (btn) {
btn.addEventListener("click", function () {
if (activeSku) adjust(activeSku, parseInt(btn.getAttribute("data-step"), 10));
});
});
document.getElementById("dReorderBtn").addEventListener("click", function () {
if (activeSku) reorder(activeSku);
});
document.getElementById("exportBtn").addEventListener("click", function () {
toast("Inventory exported", PARTS.length + " SKUs written to parts-eastside.csv", "⬇");
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Torque & Tread — Parts Inventory</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="app">
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true">⛭</span>
<div class="brand-text">
<strong>Torque & Tread</strong>
<span>Parts & Inventory · Bay Supply</span>
</div>
</div>
<div class="topbar-right">
<span class="branch-chip">Eastside Branch · #04</span>
<button class="btn btn-ghost" id="exportBtn" type="button">Export CSV</button>
</div>
</header>
<main class="layout">
<section class="panel-grid" aria-label="Inventory summary">
<article class="stat-card">
<span class="stat-label">Total SKUs</span>
<strong class="stat-value tnum" id="statTotal">0</strong>
<span class="stat-sub">across all bins</span>
</article>
<article class="stat-card">
<span class="stat-label">Units On Hand</span>
<strong class="stat-value tnum" id="statUnits">0</strong>
<span class="stat-sub">countable parts</span>
</article>
<article class="stat-card stat-warn">
<span class="stat-label">Low Stock</span>
<strong class="stat-value tnum" id="statLow">0</strong>
<span class="stat-sub">at or below reorder pt</span>
</article>
<article class="stat-card stat-value-money">
<span class="stat-label">Stock Value</span>
<strong class="stat-value tnum" id="statValue">$0</strong>
<span class="stat-sub">retail · est.</span>
</article>
</section>
<section class="toolbar" aria-label="Search and filters">
<div class="search">
<span class="search-ico" aria-hidden="true">⌕</span>
<input
id="search"
type="search"
placeholder="Search SKU, part name or brand…"
aria-label="Search parts by SKU, name or brand"
autocomplete="off"
/>
</div>
<div class="filters" role="tablist" aria-label="Filter parts">
<button class="chip is-active" role="tab" aria-selected="true" data-filter="all" type="button">All <span class="chip-n" id="cAll">0</span></button>
<button class="chip" role="tab" aria-selected="false" data-filter="low" type="button">Low <span class="chip-n" id="cLow">0</span></button>
<button class="chip" role="tab" aria-selected="false" data-filter="out" type="button">Out <span class="chip-n" id="cOut">0</span></button>
<button class="chip" role="tab" aria-selected="false" data-filter="ok" type="button">In Stock <span class="chip-n" id="cOk">0</span></button>
</div>
</section>
<section class="table-wrap" aria-label="Parts inventory">
<table class="parts">
<thead>
<tr>
<th scope="col" class="th-sku">SKU</th>
<th scope="col">Part</th>
<th scope="col" class="hide-sm">Brand</th>
<th scope="col" class="ta-c hide-sm">Bin</th>
<th scope="col" class="ta-c">Qty</th>
<th scope="col" class="ta-r hide-sm">Price</th>
<th scope="col" class="ta-r">Status</th>
<th scope="col" class="ta-r"><span class="sr-only">Actions</span></th>
</tr>
</thead>
<tbody id="rows"></tbody>
</table>
<p class="empty" id="empty" hidden>No parts match your search.</p>
</section>
</main>
</div>
<!-- Part detail drawer -->
<div class="scrim" id="scrim" hidden></div>
<aside class="drawer" id="drawer" aria-label="Part detail" aria-hidden="true" tabindex="-1">
<header class="drawer-head">
<div>
<span class="drawer-sku tnum" id="dSku">—</span>
<h2 id="dName">—</h2>
</div>
<button class="icon-btn" id="drawerClose" type="button" aria-label="Close detail">✕</button>
</header>
<div class="drawer-photo" id="dPhoto" aria-hidden="true"></div>
<dl class="spec">
<div><dt>Brand</dt><dd id="dBrand">—</dd></div>
<div><dt>Bin location</dt><dd class="tnum" id="dBin">—</dd></div>
<div><dt>Fits</dt><dd id="dFits">—</dd></div>
<div><dt>Unit price</dt><dd class="tnum" id="dPrice">—</dd></div>
<div><dt>Reorder point</dt><dd class="tnum" id="dReorder">—</dd></div>
<div><dt>Supplier</dt><dd id="dSupplier">—</dd></div>
</dl>
<div class="drawer-qty">
<span class="dq-label">On hand</span>
<div class="stepper" role="group" aria-label="Adjust quantity">
<button class="step" data-step="-1" type="button" aria-label="Decrease quantity">−</button>
<output class="step-val tnum" id="dQty" aria-live="polite">0</output>
<button class="step" data-step="1" type="button" aria-label="Increase quantity">+</button>
</div>
</div>
<button class="btn btn-primary btn-block" id="dReorderBtn" type="button">Reorder from supplier</button>
</aside>
<div class="toast-stack" id="toasts" aria-live="polite" aria-atomic="false"></div>
<script src="script.js"></script>
</body>
</html>Parts Inventory
A back-counter inventory screen for Torque & Tread, a fictional auto shop. The table lists around a dozen real-feeling parts — ceramic brake pads, iridium plugs, an AGM battery, a reman alternator — each with its SKU, brand, bin location, unit price and on-hand quantity. Every row carries a computed status pill: a part reads Out of Stock at zero, Low Stock once on-hand has fallen to or below its reorder point, and In Stock otherwise. Low and out rows pick up a coloured left accent and a tinted background so they jump out of the table at a glance.
A SKU/name/brand search box narrows the list as you type, and the filter tabs (All / Low / Out / In Stock) carry live counts that stay in sync with the data. Clicking any row opens a slide-in detail drawer with the part’s fitment, supplier and reorder point, plus a + / − stepper that adjusts the on-hand count. Each change recomputes the pill, repaints the row, and updates the summary cards tracking total SKUs, units on hand, low-stock parts and estimated stock value. Reordering a low or out item — from the row button or the drawer — fires a calm confirmation toast naming the supplier and a suggested quantity.
The whole screen is vanilla HTML, CSS and JavaScript with no frameworks or build step. It is
keyboard-friendly (rows open on Enter, Escape closes the drawer), uses aria-live for the changing
count, collapses gracefully to a compact layout down to 360px, and meets AA contrast throughout.
Illustrative UI only — fictional shop/dealership, not a real service system.