Fix 18 UX issues: confirmations, undo, drawer nav, empty states, and polish
Build and push image / build (push) Successful in 54s

Comprehensive UX audit covering modals, drawers, dashboard, and inventory.
Key changes: confirmation steps before destructive actions, undo via toast
for consume/gone/checkout, back-navigation across entity drawers, optional
ratings, discrete item count field, audit progress bar, and sortable column
affordance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-08 16:25:41 -04:00
parent 9e31a6ad00
commit 538e5079ab
24 changed files with 519 additions and 116 deletions
+9
View File
@@ -15,6 +15,7 @@ archiveLegacyIfPresent();
archiveV1IfPresent();
migrateAddCheckoutDate();
migrateAddContainerWeight();
migrateAddPrevBinId();
const schema = readFileSync(join(__dirname, "schema.sql"), "utf8");
db.exec(schema);
@@ -35,6 +36,14 @@ function migrateAddContainerWeight(): void {
db.exec(`ALTER TABLE inventory_items ADD COLUMN container_weight REAL`);
}
function migrateAddPrevBinId(): void {
const cols = db
.prepare(`PRAGMA table_info(inventory_items)`)
.all() as { name: string }[];
if (cols.length === 0 || cols.some((c) => c.name === "prev_bin_id")) return;
db.exec(`ALTER TABLE inventory_items ADD COLUMN prev_bin_id TEXT REFERENCES bins(id)`);
}
// One-shot migration: the original schema put per-instance fields (weight,
// bin_id, etc.) directly on `products`. The split schema separates products
// (catalog) from inventory_items (instance). When we detect the old shape,
+2
View File
@@ -34,6 +34,7 @@ type InventoryRow = {
consumed_date: string | null;
gone_date: string | null;
checkout_date: string | null;
prev_bin_id: string | null;
rating: number | null;
notes: string | null;
};
@@ -112,6 +113,7 @@ bootstrapRouter.get("/bootstrap", (_req, res) => {
consumedDate: i.consumed_date,
goneDate: i.gone_date,
checkoutDate: i.checkout_date,
prevBinId: i.prev_bin_id,
rating: i.rating,
notes: i.notes,
audits: (auditsByInventory.get(i.id) ?? []).map((a) => ({
+26 -2
View File
@@ -223,7 +223,7 @@ function doCheckout(id: string, date: string): void {
const result = db
.prepare(
`UPDATE inventory_items
SET status = 'checked-out', checkout_date = ?, bin_id = NULL
SET status = 'checked-out', checkout_date = ?, prev_bin_id = bin_id, bin_id = NULL
WHERE id = ? AND status = 'active'`,
)
.run(date, id);
@@ -249,7 +249,7 @@ function doCheckin(id: string, date: string, binId: string, remainingWeight?: nu
db.prepare(
`UPDATE inventory_items
SET status = 'active', bin_id = ?, checkout_date = NULL
SET status = 'active', bin_id = ?, checkout_date = NULL, prev_bin_id = NULL
WHERE id = ?`,
).run(binId, id);
@@ -355,6 +355,30 @@ inventoryRouter.post("/inventory/:id/gone", (req, res) => {
}
});
inventoryRouter.post("/inventory/:id/reactivate", (req, res) => {
const { binId } = req.body as { binId: string };
try {
const result = db
.prepare(
`UPDATE inventory_items
SET status = 'active',
bin_id = ?,
consumed_date = NULL,
gone_date = NULL,
checkout_date = NULL,
prev_bin_id = NULL
WHERE id = ? AND status IN ('consumed', 'gone', 'checked-out')`,
)
.run(binId, req.params.id);
if (result.changes === 0) {
return res.status(404).json({ error: "not found or already active" });
}
res.json({ ok: true });
} catch (e: any) {
res.status(400).json({ error: e.message });
}
});
inventoryRouter.post("/inventory/:id/audit", (req, res) => {
const { id } = req.params;
const { date, mode, value, confirmedBy } = req.body as {
+1
View File
@@ -74,6 +74,7 @@ CREATE TABLE IF NOT EXISTS inventory_items (
consumed_date TEXT,
gone_date TEXT,
checkout_date TEXT,
prev_bin_id TEXT REFERENCES bins(id),
rating INTEGER,
notes TEXT
);