PhoenixKitCatalogue.Web.Components.ItemSelectorModal (PhoenixKitCatalogue v0.19.1)

Copy Markdown View Source

Catalogue item selector modal: the catalogue's analogue of core's MediaSelectorModal. A logged-in user browses the catalogue inside a modal — search, category chips, photo-forward card grid — picks items, sets a quantity per item, reviews the selection in a tray, and confirms.

Usage

# In the parent LiveView's template. Mount it with :if — unmounting
# on close is what gives clean reopen semantics (fresh search, fresh
# scroll, preselects re-read).
<.live_component
  :if={@show_item_selector}
  module={PhoenixKitCatalogue.Web.Components.ItemSelectorModal}
  id="order-item-selector"
  scope={%{catalogue_uuids: [@catalogue.uuid]}}
  selected={@order_lines_by_uuid}
/>

Required host wiring (do not skip — silent failure otherwise)

This is a LiveComponent; it reports through process messages to the host LiveView, exactly like MediaSelectorModal. The host MUST handle both, or a confirmed selection is silently dropped:

  • handle_info({:items_selected, %{id: id, mode: mode, picks: picks}}, socket) — fired on Confirm. Each pick is a map with :uuid, :qty (always a Decimal, integers included — hosts write one clause), :unit, and a display snapshot: :name, :sku, :price, :line_total (price × qty, nil when the item has no price) and :photo_url (a signed URL — it expires, render it, never persist it). The snapshot is for rendering the host's own summary without a re-query; it is NOT an order record — re-read and re-price items server-side when the selection becomes something real.
  • handle_info({:item_selector_closed, %{id: id}}, socket) — fired on cancel/ESC/backdrop, AND after a confirm. Reset the :if assign here.

Scope

scope fixes what the user may browse: any of :catalogue_uuids, :category_uuids, :only, :statuses, :include_descendants (the Catalogue.search_items/2 vocabulary). It is enforced in BrowseState — every fetch re-derives from it, and client events can only narrow within it, so a crafted event cannot browse or select outside what the host allowed. Selection events are additionally accepted only for uuids the component itself has rendered (or that arrived preselected).

Preselection

selected is %{uuid => qty} (string uuids, Decimal or integer quantities). Preselected items are hydrated at mount ignoring scope — the tray must be able to render what the host handed it. A hydrated item that falls OUTSIDE the scope renders in the tray marked unavailable and is excluded from the confirm picks, never silently dropped.

Quantities

qty_precision: 0 (default) is whole numbers; a positive precision turns the same stepper decimal-capable ("2.5" of unit "L"). The input commits on blur/Enter; decimal commas are accepted ("2,5" — ru/et keyboards); all limits re-clamped server-side.