PhoenixKitManufacturing.Web.MachineFormLive (PhoenixKitManufacturing v0.3.2)

Copy Markdown View Source

Create/edit form for machines.

Machine fields (name, code, manufacturer…) are plain identifiers, so this form uses core inputs rather than the multilang translatable fields used for machine types. Type links are managed with a click-to-toggle picker held in a MapSet and synced to the join table after the machine saves.

Admin chrome

Self-wraps with LayoutWrapper.app_layout (:self_wrapped_layout on_mount below) instead of relying on PhoenixKit's automatic admin layout, so page_title/page_subtitle render in the global admin header rather than an in-page one — same pattern as PhoenixKitManufacturing.Web.MachinesLive.

Tabs

Once a machine exists, its card is split into in-page tabs (tabs tabs-border, <.link patch={...}>, same recipe as PhoenixKitWarehouse.Web.InternalOrderFormLive): General (passport, location, types, template fields), Operations, Files, Comments. Each tab has its own hidden CRUD route (:operations/:files/:comments in PhoenixKitManufacturing.admin_tabs/0, visible: false — same hidden_crud_tabs convention as the warehouse) so it's directly linkable/bookmarkable and survives a refresh, but never appears in the sidebar. Switching tabs is a patch, not a navigate — the LiveView process stays alive, so handle_params/3 (not mount/3) does the actual per-action loading. mount/3 only sets up tab-independent scaffolding (upload config, locale); handle_params/3 loads the machine and, like InternalOrderFormLive's load_order_into_socket/3, only rebuilds the pending, unsaved edit buffer (changeset, linked types/operations, Attachments scope state) the first time a given uuid is seen — a bare tab switch (same_machine? true) leaves it untouched, so toggling a type, picking a featured image, or setting an operation override survives navigating to another tab before hitting Save.

A :new machine has no uuid yet and stays a single, tab-less page (General only, no tab bar) — Operations/Files/Comments only become reachable once the machine has been saved once, mirroring how InternalOrderFormLive's :new never really renders past its own auto-create redirect.

Save/Cancel live inside the shared <.form>, which wraps every tab except Comments — General, Operations, and Files all hold state that's still pending at save time (passport/types/template are changeset-backed; operation overrides and the featured-image pick are separate socket assigns synced at save, see below); Comments persists immediately through its own component and has nothing to save.

location_uuid/space_uuid are picked via PhoenixKitLocations.Web.Components.PlacePicker, a LiveComponent rendered in its own card outside the main <.form phx-change="validate"> — see the comment on that card in render/1 for why. The picked uuids live in @location_uuid/@space_uuid (updated from the component's {:place_picker_select, ...} message) and are merged into the params in save_machine/3, not read off the form.

Dynamic metadata fields

Machine types can define a field_template — stored in the machine_type entity-data record's metadata["field_template"] (see EntitiesRegistry's "Record shape" moduledoc section for why it lives in metadata rather than data); Machines.merged_field_template/1 merges the templates of every linked, published type into @merged_template, rendered as extra inputs named machine[metadata][KEY] (raw name=, not @form[:atom]metadata here is this machine's freeform value map, keyed by whatever the linked types define, not a fixed changeset field — a different metadata from the type record's own). Recomputed whenever the type selection changes. Each type badge carries a pencil icon-link to Web.MachineTypeTemplateLive, the hidden-route mini-editor for that type's own field_template — the generic entities admin UI has no widget for it (see that module's moduledoc for why).

Operations

Every published operation in the directory (see PhoenixKitManufacturing.EntitiesRegistry) can be linked to this machine, each link optionally overriding the operation's own base_time_norm_seconds for this machine specifically. @operation_overrides is a %{operation_uuid => time_norm_seconds | nil} map — its key set is exactly which operations are linked, the same shape Machines.linked_operation_overrides/1 returns and Machines.sync_machine_operations/3 takes as its desired "after" state, so the form assign doubles as the sync payload with no translation step (mirrors how @linked_type_uuids doubles as sync_machine_types/3's payload). toggle_operation adds/removes a key (nil override — "use the operation's base norm" — until the user types one); set_operation_override (fired on the override input's phx-blur, not the enclosing <.form>'s phx-change — see that handler for why) updates an existing key's value, guarded with Map.replace/3 so a stray blur event for a row the user has since unchecked can't silently re-link it. Both links are synced together in sync_and_redirect/3.

Wired through PhoenixKitManufacturing.Attachments, the same folder-scoped pattern used by PhoenixKitLocations.Attachments (see that module's doc for the general mechanics) and rendered with PhoenixKitManufacturing.Web.Components.FilesCard on the Files tab (see "Tabs" above — only reachable once the machine has a uuid). This form only ever has one Attachments scope — the literal string "machine" — so every Attachments event handler below hardcodes it rather than reading phx-value-scope off the event params. Attachments.maybe_rename_pending_folder_for/2 in save_machine/3's :new clause is a leftover safety net from before the Files tab existed (a :new machine could upload before its first save, landing in a "pending" folder renamed post-save) — now a no-op in practice since :new never renders the Files tab, but left in place rather than torn out, since it's harmless and the fallback stays correct if that ever changes.

One deliberate exception to "folder-scoped": the featured-image picker itself (the MediaSelectorModal in render/1) browses the full media library, not just this machine's folder — see the comment there for why. Everything else (attached-files upload, storage, detach) stays folder-scoped exactly as described above.

Comments

Only rendered on the Comments tab (@active_tab == :comments, which — see "Tabs" above — only exists once the machine has a uuid) and only when PhoenixKitManufacturing.Comments.available?/0 is true. Like the Location card, this is rendered in its own card outside the main <.form>CommentsComponent renders its own internal <.form phx-target={@myself}> for the composer, and nesting that inside this form's <.form> would produce invalid nested <form> elements. use PhoenixKitComments.Embed (below) forwards the rich-text composer's {:leaf_changed, ...} messages to the component — without it, posting a comment silently no-ops (see PhoenixKitComments.Embed moduledoc).

Summary

Functions

on_mount(atom, params, session, socket)