# `PhoenixKitWarehouse.Web.Components.WarehouseBrowser`
[🔗](https://github.com/BeamLabEU/phoenix_kit_warehouse/blob/0.4.0/lib/phoenix_kit_warehouse/web/components/warehouse_browser.ex#L1)

Warehouse-specific catalogue tree components.

Stateless function components driven by assigns from the parent LiveView:

- `stock_tree/1` — read-only lazy tree annotated with stock quantity and value.
- `count_sheet/1` — editable table of inventory document lines grouped by
  catalogue → category, with conditional price/sum columns.

Adding new positions to a document (internal orders, stocktakes, transfers)
goes through `PhoenixKitCatalogue.Web.Components.ItemSelectorModal` directly
— not through a component here. The picker that used to live in this module
(`add_picker/1`) only ever added lines; it never touched a stock-specific
field, so nothing warehouse-specific was lost by routing that action through
the catalogue's own selector instead.

Tree toggle state (`expanded_catalogues`, `expanded_categories`,
`loaded_categories`, `loaded_items`) lives in the parent LV. These components
are stateless — they only render.

# `count_sheet`

Editable table of inventory document lines grouped by catalogue → category.

Each catalogue group is rendered as a DaisyUI collapsible section (open by
default). Category sub-headers are preserved; each category section ends with
a `<tfoot>` row showing the quantity subtotal. A quantity subtotal badge is
also shown in the catalogue-level collapsible header.

Columns:
- Name / SKU
- Unit
- Current stock (read-only, from `stock_map`)
- Counted (number input, event `set_counted`)
- Unit price (event `set_price`) — only when `track_value`
- Sum (event `set_sum`) — only when `track_value`
- Remove button (event `remove_line`)

The `names` map provides localized display names keyed by UUID:
- item UUID → item name
- catalogue UUID → catalogue display name
- category UUID → category name

Line snapshot names (`line["name"]`) are used as fallback when the UUID
is not in the map.

Price/sum columns are hidden entirely when `track_value` is false.

## Attributes

* `lines` (`:list`) (required)
* `track_value` (`:boolean`) (required)
* `names` (`:map`) (required)
* `stock_map` (`:map`) (required)
* `locale` (`:string`) (required)
* `editable` (`:boolean`) - Defaults to `true`.

# `localized_name`

```elixir
@spec localized_name(map() | struct() | nil, String.t() | nil) :: String.t() | nil
```

Returns the display name of a catalogue / category / item record in the
given UI locale, reading translations from the multilang `data` JSONB via
`PhoenixKitCatalogue.Catalogue.get_translation/2`. Falls back to the
embedded primary translation, then to the denormalized `name` column.
Returns `nil` for `nil` records.

Ported from `Andi.Catalogues.localized_name/2` — that function does not
exist on `PhoenixKitCatalogue.Catalogue` itself (confirmed, matching
Plan 3's identical finding for `Inventories`), so this is real ported
logic, not a call-through. Unlike the original, `locale` is used exactly
as given — the original's `Andi.Locales.entity_locale/1`/`current_locale/0`
normalization is Andi-specific and dropped; callers pass an
already-resolved locale string (see this plan's Global Constraints on
`Andi.Locales.sync_from_phoenix_kit/0`).

# `source_picker`

Stateless modal component for selecting source documents to import into a
warehouse document.

## Attrs

- `id` — explicit modal id (string, required). Pass a stable per-form id to
  avoid collision when multiple modals share the same page.
- `show` — controls visibility (boolean, required).
- `title` — modal header title string (required).
- `on_close` — phx event name sent on cancel / backdrop click (string, required).
- `candidates` — list of `%{uuid, label, label_prefix}` maps to display
  (label_prefix is optional). The parent LiveView builds this list from DB queries in
  `handle_event/3` for `"source_picker_search"`.
- `selected_uuids` — `MapSet` (or list) of already-selected UUIDs; drives
  the checkbox state per row (required).
- `search_query` — current value of the search input, echoed into the field
  on re-render (default `""`).

## Parent events to implement

The component fires these fixed phx event names; the parent LiveView MUST
implement `handle_event/3` for each:

- `"source_picker_search"` — fired on phx-change of the search form.
  Params: `%{"query" => string}`.
  The parent should re-query candidates and assign them.

- `"source_picker_toggle"` — fired on phx-click of a candidate row.
  Params: `%{"uuid" => binary}`.
  The parent should toggle the UUID in/out of `selected_uuids`.

- `"source_picker_select_all"` — fired on phx-click of the "Select all"
  toggle. No extra phx-values. The parent should select every UUID
  currently in `candidates` when not all are selected yet, or clear the
  selection when they already all are.

- `"source_picker_confirm"` — fired on the "Import (N)" primary button.
  No extra phx-values. The parent reads its own `selected_uuids` assign,
  runs the import logic, and then clears the modal.

The `on_close` event is also a parent-side event (cancel / backdrop close).

## Attributes

* `id` (`:string`) (required)
* `show` (`:boolean`) (required)
* `title` (`:string`) (required)
* `on_close` (`:string`) (required)
* `candidates` (`:list`) - Defaults to `[]`.
* `selected_uuids` (`:any`) - Defaults to `[]`.
* `search_query` (`:string`) - Defaults to `""`.

# `stock_sheet`

Read-only stock sheet grouped by catalogue → category.

Accepts a flat list of `%{item, quantity, unit_value}` maps where `item`
has `:catalogue` and `:category` preloaded. Groups are rendered as DaisyUI
collapsible sections (open by default) with a quantity subtotal badge in
the catalogue header and a per-category `<tfoot>` subtotal row.

Columns: Item (name + SKU) / Unit / In stock (qty) / Total value.

Entries may optionally carry a `:below_min?` boolean (§5 — deficit
tracking, set by `Web.StockLive.build_stock_items/1`) — when true, a small
warning icon renders next to the item name. Missing/false is the default
(`Map.get(entry, :below_min?, false)`), so callers that don't track
deficits can keep passing the plain `%{item, quantity, unit_value}` shape.

## Attributes

* `stock_items` (`:list`) (required)
* `locale` (`:string`) (required)

# `stock_tree`

Read-only lazy catalogue tree annotated with stock quantity and total value.

Each item row shows:
- Quantity in stock (with unit label)
- Total value (`quantity * unit_value`) — only when `unit_value` is known;
  otherwise shows a `—` placeholder.

Toggle events (`toggle_catalogue` / `toggle_category`) are sent to the
parent LiveView which updates `expanded_*` and `loaded_*` assigns.

## Attributes

* `catalogue_summaries` (`:list`) (required)
* `expanded_catalogues` (`:any`) (required)
* `expanded_categories` (`:any`) (required)
* `loaded_categories` (`:map`) (required)
* `loaded_items` (`:map`) (required)
* `locale` (`:string`) (required)
* `stock_map` (`:map`) (required)

# `strip_prefix`

```elixir
@spec strip_prefix(String.t() | nil) :: String.t() | nil
```

Strips a configurable catalogue-name prefix (case-insensitive), along with
any single separating space. Returns the original name if the prefix isn't
present, or if stripping would yield an empty string.

Ported from `Andi.Catalogues.strip_prefix/1`. The original reads
`Application.get_env(:andi, :catalogue_prefix, "ANDI")` — Andi-specific
config the package cannot reference. This reads
`Application.get_env(:phoenix_kit_warehouse, :catalogue_prefix, "")`
instead, defaulting to an **empty string** (no-op — every catalogue name
passes through unchanged) rather than a hardcoded brand string, so the
package behaves sensibly out of the box for a fresh host with no
catalogue-naming convention at all. A host that does use a shared naming
prefix opts in via config.

# `unit_label`

```elixir
@spec unit_label(String.t() | nil) :: String.t()
```

Short localized label for a catalogue item unit of measure. Ported from
`Andi.Catalogues.unit_label/1` verbatim.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
