Commit Graph

11 Commits

Author SHA1 Message Date
josh a3559062db Fix custody view column alignment by giving action buttons a fixed width
Build and push image / build (push) Successful in 1m2s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 20:55:14 -04:00
josh e7fd9af62c Add checkout/custody feature for tracking items in personal possession
Build and push image / build (push) Successful in 1m8s
Items can now be checked out of their bin into "my custody" and later
checked back in or marked consumed. Adds checkout/checkin API endpoints,
a My Custody sidebar page, CheckoutFlow and CheckinFlow modals, and
updates ProductDetail, Inventory, ConsumeFlow, and MarkGoneFlow to
handle the new checked-out status. Bulk items prompt for remaining
weight on check-in.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 20:49:58 -04:00
josh db2af4b79d Fix dashboard date showing yesterday due to UTC parsing
Build and push image / build (push) Successful in 48s
Date-only ISO strings are parsed as UTC midnight, which displays
as the previous day in US timezones. Append T00:00:00 to force
local time interpretation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-04 19:07:35 -04:00
josh a82045d1bd 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>
2026-05-04 18:54:49 -04:00
josh 80034b47c5 User-supplied asset ids; brand on product; strain is the name
Build and push image / build (push) Successful in 48s
Four UX changes after using the rework for a bit:

1. Asset ids are 6-digit numbers from a roll of physical labels — server
   no longer generates them. POST /api/inventory requires assetId; the
   add-inventory form has a digits-only input that auto-focuses on entry.
2. Strain and product name are the same thing. Drop products.name; the
   strain's name supplies the display. Product creation just asks for
   "Name (strain)" and matches/creates a strain by that name.
3. Brand moves from inventory_items to products. SKUs are brand-specific,
   so all instances of a product share the brand. Brand selector lives
   on the product create/edit form, not the per-instance form.
4. Scanning an unknown SKU on the add-inventory step now opens the
   create-product subform with the SKU prefilled — one less click.

Migration: detect prior shape (products.name column present) and rename
products/inventory_items/audits to *_v1 archives, recreate empty.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 18:17:12 -04:00
josh 02dc6e523f Track inventory at the instance level, not by product
Build and push image / build (push) Successful in 46s
The products table conflated catalog ("kind of thing you scan") with
instance ("this jar I bought") — splitting it lets us record every
purchase as its own asset and autofill brand/shop/price/THC from the
last instance when scanning a known SKU.

- products: sku + strain + name + type + kind (catalog only)
- inventory_items: physical jars with short-UUID asset ids, per-batch
  brand/shop/bin/price/cannabinoids/weight, audits, lifecycle
- audits now key on inventory_id; strains lose brand_id and type
- migration: rename existing products/audits/strains to *_legacy on
  first boot so users keep historical reference, fresh start otherwise
- two-step add flow: scan SKU → select/create product → instance
  details (autofilled from last instance) → generated asset id shown
- ScanField matches asset id first, falls back to SKU
- inventory list defaults flat, "By product" toggle groups instances

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 05:59:46 -04:00
josh c7f3bf25d1 Bins: fixed N-column grid per letter group
Build and push image / build (push) Successful in 52s
The bins-by-letter grid used auto-fill with a 380px minimum, so a row
of A1-A6 wrapped once the viewport got tight, and rows with fewer
bins than the auto-fill column count left empty tracks on the right.

Switch to repeat(N, minmax(0, 1fr)) where N is the number of bins in
the group: cards smush to fit so a letter never wraps to a second
visual row, and minmax(0, 1fr) lets columns share whatever width is
available so a sparse row (e.g. C with 2 bins) widens to fill the
line just as a dense row does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:14:45 -04:00
josh d335525073 Group bins by letter, sort by number, drop location
Build and push image / build (push) Successful in 46s
Bins follow an A1/A2/B1 naming convention, so the Bins page now parses
the leading letter prefix as a row group and the trailing number as the
within-row order. Each letter starts a fresh grid section; bins whose
names don't match the pattern fall into a trailing "Other" bucket
sorted alphabetically.

Removes the optional location field from bins end to end: the API
client signatures, server POST/PATCH routes, both product-flow inline
creates, the dropdown labels, the ProductDetail bin row, and the
BinsView header line. The bootstrap query explicitly projects only
id/name/capacity so the dead column doesn't leak through.

The location column stays in the bins table on disk to avoid a
migration on existing deployments — it just isn't read or written.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:07:12 -04:00
josh edb8e2ac92 Bin slot count: discrete products fill by unit, not by row
Build and push image / build (push) Successful in 54s
A 3ct pre-roll was filling one slot of its bin instead of three. Bins
visualise physical capacity, so each unit of a discrete product (rolls,
edibles, vapes) should take its own slot; bulk jars still take one slot
each. The slot tally and fill bar both use the current count
(countLastAudit ?? countOriginal) so the bin frees up as units are
audited away.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:42:39 -04:00
josh 8ef8859c7d Edit and delete brands and shops
Build and push image / build (push) Successful in 49s
Adds PATCH and DELETE endpoints for brands and shops that mirror the
existing bins pattern: deleting a brand or shop nullifies referencing
products (and strains, for brands) inside a transaction so nothing is
lost. Brand renames return 409 when the new name collides with the
UNIQUE constraint, surfaced inline in the edit modal.

The Brands and Shops views now show inline edit/trash icons on each
card; the trash button confirms with a preview of how many products
will be unparented.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:33:42 -04:00
josh 027cf032be Initial commit: Apothecary v0.4.0 2026-05-03 20:19:26 -04:00