UX overhaul: routing, accessibility, feedback, and polish
Build and push image / build (push) Successful in 50s

Add react-router-dom for URL-based navigation with browser
back/forward, deep links, and bookmarks. Replace window.confirm()
with styled ConfirmDialog. Add toast notifications and success
feedback on consume/audit/gone flows. Add escape-to-close and
focus trapping on modals. Add entrance animations for drawers,
modals, and toasts. Make grids responsive, add sortable inventory
headers, working CSV/JSON export, time-aware greeting, focus-visible
outlines, search clear button, and hover chevrons on inventory rows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 18:54:49 -04:00
parent 80034b47c5
commit a82045d1bd
21 changed files with 640 additions and 145 deletions
+14 -7
View File
@@ -1,3 +1,4 @@
import { useEffect } from "react";
import type { Bootstrap, Item, Product } from "../types.js";
import { TYPES, helpers, TODAY_STR } from "../types.js";
import { fmt, TYPE_GLYPHS } from "../format.js";
@@ -36,6 +37,14 @@ export function ProductDetail({
const isActive = item.status === "active";
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [onClose]);
// Sibling instances of the same product (excluding this one) — useful for
// seeing previous purchases of the same SKU.
const siblings = data.inventoryItems.filter(
@@ -90,6 +99,7 @@ export function ProductDetail({
zIndex: 50,
display: "flex",
justifyContent: "flex-end",
animation: "backdrop-in 200ms ease-out",
}}
onClick={onClose}
>
@@ -98,6 +108,7 @@ export function ProductDetail({
style={{
width: "min(720px, 100vw)",
height: "100%",
animation: "drawer-in 250ms ease-out",
background: "var(--bg)",
borderLeft: "1px solid var(--line)",
overflow: "auto",
@@ -120,7 +131,7 @@ export function ProductDetail({
<div className="smallcaps" style={{ color: "var(--ink-3)" }}>
Inventory · <span className="mono">{item.assetId}</span>
</div>
<div style={{ display: "flex", gap: 6 }}>
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", justifyContent: "flex-end" }}>
{isActive && (
<Btn variant="ghost" icon="check" onClick={() => onAudit(item)}>
Audit
@@ -132,13 +143,9 @@ export function ProductDetail({
</Btn>
)}
{isActive && (
<Btn variant="ghost" icon="bin" onClick={() => onMarkGone(item)}>
Mark gone
</Btn>
<Btn variant="ghost" icon="bin" onClick={() => onMarkGone(item)} />
)}
<Btn variant="ghost" icon="edit" onClick={() => onEdit(item)}>
Edit
</Btn>
<Btn variant="ghost" icon="edit" onClick={() => onEdit(item)} />
<Btn variant="ghost" icon="close" onClick={onClose} />
</div>
</div>