defmodule ComponentsElixirWeb.StorageLocationsLive do @moduledoc """ LiveView for managing storage locations and QR codes. """ use ComponentsElixirWeb, :live_view alias ComponentsElixir.Inventory alias ComponentsElixir.Inventory.StorageLocation alias ComponentsElixir.QRCode @impl true def mount(_params, _session, socket) do socket = socket |> assign(:storage_locations, list_storage_locations()) |> assign(:form, to_form(%{})) |> assign(:show_form, false) |> assign(:edit_location, nil) |> assign(:qr_scanner_open, false) |> assign(:scanned_codes, []) {:ok, socket} end @impl true def handle_params(params, _url, socket) do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end defp apply_action(socket, :index, _params) do socket |> assign(:page_title, "Storage Locations") |> assign(:storage_location, %StorageLocation{}) end defp apply_action(socket, :new, _params) do socket |> assign(:page_title, "New Storage Location") |> assign(:storage_location, %StorageLocation{}) |> assign(:show_form, true) end defp apply_action(socket, :edit, %{"id" => id}) do location = Inventory.get_storage_location!(id) socket |> assign(:page_title, "Edit Storage Location") |> assign(:storage_location, location) |> assign(:edit_location, location) |> assign(:show_form, true) |> assign(:form, to_form(Inventory.change_storage_location(location))) end @impl true def handle_event("new", _params, socket) do {:noreply, socket |> assign(:show_form, true) |> assign(:storage_location, %StorageLocation{}) |> assign(:edit_location, nil) |> assign(:form, to_form(Inventory.change_storage_location(%StorageLocation{})))} end def handle_event("cancel", _params, socket) do {:noreply, socket |> assign(:show_form, false) |> assign(:edit_location, nil) |> push_patch(to: ~p"/storage_locations")} end def handle_event("validate", %{"storage_location" => params}, socket) do # Normalize parent_id for validation too normalized_params = case Map.get(params, "parent_id") do "" -> Map.put(params, "parent_id", nil) value -> Map.put(params, "parent_id", value) end changeset = case socket.assigns.edit_location do nil -> Inventory.change_storage_location(%StorageLocation{}, normalized_params) location -> Inventory.change_storage_location(location, normalized_params) end {:noreply, assign(socket, :form, to_form(changeset, action: :validate))} end def handle_event("save", %{"storage_location" => params}, socket) do # Normalize parent_id for consistency normalized_params = case Map.get(params, "parent_id") do "" -> Map.put(params, "parent_id", nil) value -> Map.put(params, "parent_id", value) end case socket.assigns.edit_location do nil -> create_storage_location(socket, normalized_params) location -> update_storage_location(socket, location, normalized_params) end end def handle_event("delete", %{"id" => id}, socket) do location = Inventory.get_storage_location!(id) case Inventory.delete_storage_location(location) do {:ok, _} -> {:noreply, socket |> put_flash(:info, "Storage location deleted successfully") |> assign(:storage_locations, list_storage_locations())} {:error, _} -> {:noreply, put_flash(socket, :error, "Unable to delete storage location")} end end def handle_event("open_qr_scanner", _params, socket) do {:noreply, assign(socket, :qr_scanner_open, true)} end def handle_event("close_qr_scanner", _params, socket) do {:noreply, assign(socket, :qr_scanner_open, false)} end def handle_event("qr_scanned", %{"code" => code}, socket) do case QRCode.parse_qr_data(code) do {:ok, parsed} -> case Inventory.get_storage_location_by_qr_code(parsed.code) do nil -> {:noreply, put_flash(socket, :error, "Storage location not found for QR code: #{code}")} location -> scanned_codes = [%{code: code, location: location} | socket.assigns.scanned_codes] {:noreply, socket |> assign(:scanned_codes, scanned_codes) |> put_flash(:info, "Scanned: #{location.path}")} end {:error, reason} -> {:noreply, put_flash(socket, :error, "Invalid QR code: #{reason}")} end end def handle_event("clear_scanned", _params, socket) do {:noreply, assign(socket, :scanned_codes, [])} end defp create_storage_location(socket, params) do case Inventory.create_storage_location(params) do {:ok, _location} -> {:noreply, socket |> put_flash(:info, "Storage location created successfully") |> assign(:show_form, false) |> assign(:storage_locations, list_storage_locations())} {:error, %Ecto.Changeset{} = changeset} -> {:noreply, assign(socket, :form, to_form(changeset))} end end defp update_storage_location(socket, location, params) do case Inventory.update_storage_location(location, params) do {:ok, _location} -> {:noreply, socket |> put_flash(:info, "Storage location updated successfully") |> assign(:show_form, false) |> assign(:edit_location, nil) |> assign(:storage_locations, list_storage_locations()) |> push_patch(to: ~p"/storage_locations")} {:error, %Ecto.Changeset{} = changeset} -> {:noreply, assign(socket, :form, to_form(changeset))} end end defp list_storage_locations do Inventory.list_storage_locations() end defp format_level(level) do case level do 0 -> "Shelf" 1 -> "Drawer" 2 -> "Box" n -> "Level #{n}" end end # Function to get parent options for select dropdown defp parent_options(current_location) do locations = Inventory.list_storage_locations() # Filter out the current location if provided (to prevent self-parent) filtered_locations = case current_location do nil -> locations %{id: current_id} -> Enum.filter(locations, fn loc -> loc.id != current_id end) _ -> locations end filtered_locations |> Enum.map(fn location -> {"#{location.name} (#{location.level})", location.id} end) end end