Commit graph

6 commits

Author SHA1 Message Date
Roland
906705e5b4 ui: thirty-five files off Semantic, leaving fifteen
Phase 2, part 4.  Baseline 50 -> 15.

Migrated: all eight calculators, the rest of inventory and collections, the
Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage,
DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings
and Status, Download, and App.

Also the three shared modules everything imports: api.js and customHooks.js
swap the Semantic toast library for the library's shim (same call shape), and
useForm.js renders the library's Message.  useForm accepts both the old
`{content, header, positive|negative}` shape and the new `{kind, title}`,
because the pages building those objects migrate separately.

Two library gaps closed, both found by call sites needing them:

  ActionInput is a forwardRef now.  Its ref reaches the input rather than the
  wrapper, because callers use it to focus or select the field -- a ref to the
  surrounding div would do neither, which is why one call site had resorted to
  hand-copying the component's markup.

  Header takes a `color` token name.  Three separate call sites had reached for
  an inline style to colour a heading that carries meaning ("3 items to add"
  against "2 to remove"), which is how a hex ends up in markup.

Two files needed repair after being interrupted mid-edit: MapViewer had a
Segment left in its error branch, and VideoPlayer had lost the useContext
import its own code still used.  Neither jest nor tsc noticed; only
`npm run build` did, which is the whole reason that check exists.

Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser,
FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim,
ControllerPage, admin/Downloads.  Theme.tsx is deliberately last -- the
unmigrated files still import its Semantic-backed wrappers, so it can only go
once they have.
2026-07-30 12:54:24 -06:00
Roland
e7c737fe0c
Allow arithmetic expressions in inventory number fields
A user can type a calculation into any numeric inventory field (number,
quantity magnitude, calories) and have it evaluated in place — e.g. weigh a
400 g box of nails, enter '400 - 20', and Tab/Enter swaps in 380.  The result
feeds dependent logic like count-by-weight automatically.

- evaluateExpression() (units.js) reuses the scoped mathjs instance, guarded by
  a strict regex so only bare arithmetic (digits, + - * / ( ) and spaces) is
  evaluated; anything with a letter, a plain number, or unparseable input is
  left untouched.  Float noise is rounded off.
- FieldCell evaluates on blur (Tab/click-away) and on Enter: in a numeric field
  the first Enter resolves a pending expression, a second submits the row.
- NUMERIC_TYPES is exported once from units.js and shared by the table and cell
  editors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 15:14:10 -06:00
Roland
305c73921e
Add "Count by Weight" to estimate item counts from weighing
Counting bulk hardware (screws, nails) by hand is slow.  This adds a workflow
to estimate the count from weight instead: weigh one item, weigh the lot, and
the count is filled in automatically.

- The Fields modal gains a "Count by Weight" button that adds two quantity
  fields, Unit Weight and Total Weight (default grams), and tags the Count
  field as computed.  An existing `count` field is linked in place rather than
  duplicated, so hand-counted items keep their values.
- The link is plain metadata on the Count field
  (`compute: {kind: 'count_by_weight', total, unit}`), so it round-trips
  through the config/API with no backend change (covered by a backend test).
- During item entry, Count = round(Total Weight / Unit Weight), converting
  mismatched units first (g/kg/oz) and refusing to divide when only one side
  has a unit.  It rounds to a whole number (no 403.7 nails).
- When a weight changes, the count always reflects the new reality: it is set
  to the computed value (0 for a sub-unit total) when both weights are present,
  or cleared when they can't produce one (a weight blanked, incompatible
  units) — so a stale count can never persist against edited weights.  A
  hand-typed count is only touched when a weight actually changes.

countByWeight lives in units.js; the field preset, config detection, and the
entry-time apply hook live in a new computeFields.js.  Covered by unit tests
(countByWeight, computeFields), InventoryTable auto-fill / clear / sub-unit on
draft and inline edit, the FieldSchemaEditor preset, and a backend
field-metadata round-trip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:36:56 -06:00
Roland
447e4a40df
Select an item's value when its cell is clicked for editing
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Controller Tests / controller-tests (push) Waiting to run
Jest Tests / jest-tests (push) Waiting to run
Clicking a cell in the Items table now focuses that field's input and selects
its contents, so the user can type a replacement value immediately. FieldCell
gains an `autoFocus` prop (focus + select on mount via a merged ref that still
supports the draft row's first-field ref); InventoryTable tracks which cell was
clicked and flags the matching FieldCell.

Also add a FieldSchemaEditor test covering the select-options comma fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:47:32 -06:00
Roland
947ee01b25
Add CSV/PDF export and a search filter to Inventory
New Export tab with two client-side formats (no new dependencies):
- CSV: the raw items table (RFC-4180 quoting, UTF-8 BOM for Excel), generated
  entirely in the browser.  The download anchor is attached to the DOM and the
  object URL is revoked on the next tick for cross-browser robustness.
- PDF: a printable full-inventory table followed by a group-by summary table,
  via the browser's print dialog ("Save as PDF").  The summary's grouping is
  chosen in the Export tab.

Search filter: a box on its own line under the inventory selector filters the
active inventory across every column's displayed value (case-insensitive;
whitespace-separated terms are AND-ed).  It narrows the whole view — Items
table, Summary aggregation, and Export/PDF — while edits/adds in the table
still operate on the full inventory, so filtering never drops data.

Summary/export grouping:
- The group-by aggregation is extracted into a shared summarize.js (field-role
  detection + count-aware totals via units.js + calories), now the single
  source of truth for the Summary tab, the PDF summary, and the ration
  calculator.  findCaloriesKey/findCountKey moved here from RationCalculator
  (re-exported for back-compat).
- The Sum selector now offers number and calories fields in addition to
  quantity: number/calories are summed as a plain column total, while quantity
  stays count-aware and unit-compacted and the dedicated Calories column stays
  count-aware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:06:38 -06:00
Roland
3d6aead072
Rebuild Inventory as a config-only system with food catalog and calculators
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Controller Tests / controller-tests (push) Has been cancelled
Jest Tests / jest-tests (push) Has been cancelled
Replace the DB-backed inventory module with a config-only design and build out
food-storage tooling around it.

Inventory (config-only, no database)
- Each inventory is a YAML file at config/inventory/<slug>.yaml, loaded once
  into memory and the single source of truth. New reusable MultiFileConfig
  (wrolpi/common.py) manages a directory of per-entity files.
- Inventory types (food/fuel/tool) seed a customizable field schema
  (text/number/quantity/date/select/location/calories). Unit math/aggregation
  moved to the frontend (mathjs); Pint dropped from the backend.
- Minimal API: GET / (all inventories full), POST /, PUT /<slug> (whole-
  inventory save with version-based optimistic concurrency; 404 for missing),
  DELETE. Legacy inventories.yaml / old tables migrate to per-inventory files
  (pound->lb), and an alembic migration drops the tables.

Frontend
- New app/src/components/inventory/ package: spreadsheet-style table with
  Tab/Enter keyboard entry, sortable columns, expired-row highlighting,
  customizable field editor, and pooled cross-inventory location autocomplete.
- A single useInventories store loads everything once and persists whole
  inventories optimistically; list/items/summary/locations derive client-side.
- Summary groups items (default by Category), totals size x count, shows a
  calories column, and embeds a ration estimate (how long the stock lasts).

Food catalog
- Shared, editable catalog (config/inventory/catalog.yaml) seeded from
  catalog_defaults.yaml (~60 generic items, total calories per package) with
  merge-keep-edits + tombstones. GET/PUT /api/inventory/catalog, a management
  modal, and entry-row pre-fill of category/size/calories from a name match.

Calculators
- Generic Food Storage planner (like the Water calculator): household +
  editable per-person/year amounts -> total to store over a chosen duration,
  with women/children weighted by DGA calorie shares and an imperial/metric
  toggle.

Also fixes ConfigFile.save() to accept nested file_names. Backend pytest and
frontend jest suites updated/added throughout; alembic check clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:14:54 -06:00