From 0f2f8a245250d89c6be46c8ee564a49977526b9f Mon Sep 17 00:00:00 2001
From: Thomas von Deyen <thomas@vondeyen.com>
Date: Fri, 20 Dec 2024 23:31:38 +0100
Subject: [PATCH] Admin stock items: Load modal with turbo frame

---
 .../stock_items/edit/component.html.erb       | 147 +++++++++---------
 .../stock_items/edit/component.rb             |  11 +-
 .../stock_items/index/component.rb            |  14 +-
 .../solidus_admin/stock_items_controller.rb   |  36 ++---
 4 files changed, 100 insertions(+), 108 deletions(-)

diff --git a/admin/app/components/solidus_admin/stock_items/edit/component.html.erb b/admin/app/components/solidus_admin/stock_items/edit/component.html.erb
index 64a8d7cc594..a0faf887296 100644
--- a/admin/app/components/solidus_admin/stock_items/edit/component.html.erb
+++ b/admin/app/components/solidus_admin/stock_items/edit/component.html.erb
@@ -1,83 +1,84 @@
-<div
-  data-controller="<%= stimulus_id %>"
-  data-<%= stimulus_id %>-initial-count-on-hand-value="<%= @stock_item.count_on_hand_was || @stock_item.count_on_hand %>"
-  data-action="input-><%= stimulus_id %>#updateCountOnHand"
->
-  <%= turbo_frame_tag :edit_stock_item_modal do %>
-    <%= render component("ui/modal").new(title: t(".title")) do |modal| %>
-      <%= form_for @stock_item, url: solidus_admin.stock_item_path(@stock_item), html: { id: form_id } do |f| %>
-        <div class="flex flex-col gap-6 pb-4">
-          <div class="flex gap-4">
-            <%= link_to spree.edit_admin_product_variant_path(
-              @stock_item.variant.product,
-              @stock_item.variant,
-            ), class: 'hover:bg-gray-25 rounded p-1 w-1/2 border border-gray-100' do %>
-              <%= render component("ui/resource_item").new(
-                thumbnail:
-                  (
-                    @stock_item.variant.images.first ||
-                      @stock_item.variant.product.gallery.images.first
-                  )&.url(:small),
-                title: @stock_item.variant.name,
-                subtitle:
-                  "#{@stock_item.variant.sku}#{@stock_item.variant.options_text.presence&.prepend(" - ")}",
-              ) %>
-            <% end %>
-            <%= link_to spree.edit_admin_stock_location_path(@stock_item.stock_location), class: 'hover:bg-gray-25 rounded p-1 w-1/2 border border-gray-100' do %>
-              <%= render component("ui/resource_item").new(
-                title: @stock_item.stock_location.name,
-                subtitle: "#{Spree::StockLocation.model_name.human} #{@stock_item.stock_location.code}",
-              ) %>
-            <% end %>
-          </div>
-
-          <%= render component("ui/forms/field").text_field(
-            f,
-            :count_on_hand,
-            disabled: true,
-            value: @stock_item.count_on_hand_was || @stock_item.count_on_hand,
-            "data-#{stimulus_id}-target": 'countOnHand',
-          ) %>
-          <%= render component("ui/forms/field").new(
-            label: t(".quantity_adjustment"),
-            hint: t(".quantity_adjustment_hint_html"),
-          ) do %>
-            <%= render component("ui/forms/input").new(
-              value: params[:quantity_adjustment] || 0,
-              name: :quantity_adjustment,
-              type: :number,
-              step: 1,
-              "data-#{stimulus_id}-target": 'quantityAdjustment',
+<%= turbo_frame_tag :resource_modal, target: "_top" do %>
+  <%= render component("ui/modal").new(title: t(".title")) do |modal| %>
+    <%= form_for @stock_item, url: form_url, html: { id: form_id } do |f| %>
+      <div
+        class="flex flex-col gap-6 pb-4"
+        data-controller="<%= stimulus_id %>"
+        data-<%= stimulus_id %>-initial-count-on-hand-value="<%= @stock_item.count_on_hand_was || @stock_item.count_on_hand %>"
+        data-action="input-><%= stimulus_id %>#updateCountOnHand"
+      >
+        <div class="flex gap-4">
+          <%= link_to spree.edit_admin_product_variant_path(
+            @stock_item.variant.product,
+            @stock_item.variant,
+          ),
+          data: {turbo_frame: "_top"},
+          class: 'hover:bg-gray-25 rounded p-1 w-1/2 border border-gray-100' do %>
+            <%= render component("ui/resource_item").new(
+              thumbnail:
+                (
+                  @stock_item.variant.images.first ||
+                    @stock_item.variant.product.gallery.images.first
+                )&.url(:small),
+              title: @stock_item.variant.name,
+              subtitle:
+                "#{@stock_item.variant.sku}#{@stock_item.variant.options_text.presence&.prepend(" - ")}",
+            ) %>
+          <% end %>
+          <%= link_to spree.edit_admin_stock_location_path(@stock_item.stock_location),
+            data: {turbo_frame: "_top"},
+            class: 'hover:bg-gray-25 rounded p-1 w-1/2 border border-gray-100' do %>
+            <%= render component("ui/resource_item").new(
+              title: @stock_item.stock_location.name,
+              subtitle: "#{Spree::StockLocation.model_name.human} #{@stock_item.stock_location.code}",
             ) %>
           <% end %>
-
-          <%= render component("ui/forms/switch_field").new(
-            name: "#{f.object_name}[backorderable]",
-            label: Spree::StockItem.human_attribute_name(:backorderable),
-            error: f.object.errors[:backorderable],
-            hint: t(".backorderable_hint_html"),
-            checked: f.object.backorderable?,
-            include_hidden: true,
-          ) %>
         </div>
-      <% end %>
 
-      <% modal.with_actions do %>
-        <form method="dialog">
-          <%= render component("ui/button").new(
-            scheme: :secondary,
-            text: t(".cancel"),
+        <%= render component("ui/forms/field").text_field(
+          f,
+          :count_on_hand,
+          readonly: true,
+          value: @stock_item.count_on_hand_was || @stock_item.count_on_hand,
+          "data-#{stimulus_id}-target": 'countOnHand',
+        ) %>
+        <%= render component("ui/forms/field").new(
+          label: t(".quantity_adjustment"),
+          hint: t(".quantity_adjustment_hint_html"),
+        ) do %>
+          <%= render component("ui/forms/input").new(
+            value: params[:quantity_adjustment] || 0,
+            name: :quantity_adjustment,
+            type: :number,
+            step: 1,
+            "data-#{stimulus_id}-target": 'quantityAdjustment',
           ) %>
-        </form>
+        <% end %>
+
+        <%= render component("ui/forms/switch_field").new(
+          name: "#{f.object_name}[backorderable]",
+          label: Spree::StockItem.human_attribute_name(:backorderable),
+          error: f.object.errors[:backorderable],
+          hint: t(".backorderable_hint_html"),
+          checked: f.object.backorderable?,
+          include_hidden: true,
+        ) %>
+      </div>
+    <% end %>
 
+    <% modal.with_actions do %>
+      <form method="dialog">
         <%= render component("ui/button").new(
-          tag: :button,
-          text: t(".submit"),
-          form: form_id,
+          scheme: :secondary,
+          text: t(".cancel"),
         ) %>
-      <% end %>
+      </form>
+
+      <%= render component("ui/button").new(
+        tag: :button,
+        text: t(".submit"),
+        form: form_id,
+      ) %>
     <% end %>
   <% end %>
-
-  <%= render component("stock_items/index").new(page: @page) %>
-</div>
+<% end %>
diff --git a/admin/app/components/solidus_admin/stock_items/edit/component.rb b/admin/app/components/solidus_admin/stock_items/edit/component.rb
index 3573444d413..63ccf53032e 100644
--- a/admin/app/components/solidus_admin/stock_items/edit/component.rb
+++ b/admin/app/components/solidus_admin/stock_items/edit/component.rb
@@ -1,18 +1,9 @@
 # frozen_string_literal: true
 
-class SolidusAdmin::StockItems::Edit::Component < SolidusAdmin::BaseComponent
-  def initialize(stock_item:, page:)
-    @stock_item = stock_item
-    @page = page
-  end
-
+class SolidusAdmin::StockItems::Edit::Component < SolidusAdmin::Resources::Edit::Component
   def title
     [
       "#{Spree::StockLocation.model_name.human}: #{@stock_item.stock_location.name}",
     ].join(' / ')
   end
-
-  def form_id
-    "#{stimulus_id}-#{dom_id(@stock_item)}"
-  end
 end
diff --git a/admin/app/components/solidus_admin/stock_items/index/component.rb b/admin/app/components/solidus_admin/stock_items/index/component.rb
index fc20bb93f71..4e480aedc6c 100644
--- a/admin/app/components/solidus_admin/stock_items/index/component.rb
+++ b/admin/app/components/solidus_admin/stock_items/index/component.rb
@@ -13,8 +13,8 @@ def search_url
     solidus_admin.stock_items_path
   end
 
-  def row_url(stock_item)
-    solidus_admin.edit_stock_item_path(stock_item, _turbo_frame: :edit_stock_item_modal)
+  def edit_path(stock_item)
+    solidus_admin.edit_stock_item_path(stock_item)
   end
 
   def scopes
@@ -90,7 +90,9 @@ def name_column
     {
       header: :name,
       data: ->(stock_item) do
-        content_tag :div, stock_item.variant.name
+        link_to stock_item.variant.name, edit_path(stock_item),
+          data: { turbo_frame: :resource_modal, turbo_prefetch: false },
+          class: 'body-link'
       end
     }
   end
@@ -99,7 +101,9 @@ def sku_column
     {
       header: :sku,
       data: ->(stock_item) do
-        content_tag :div, stock_item.variant.sku
+        link_to stock_item.variant.sku, edit_path(stock_item),
+          data: { turbo_frame: :resource_modal, turbo_prefetch: false },
+          class: 'body-link'
       end
     }
   end
@@ -168,6 +172,6 @@ def count_on_hand_column
   end
 
   def turbo_frames
-    %w[edit_stock_item_modal]
+    %w[resource_modal]
   end
 end
diff --git a/admin/app/controllers/solidus_admin/stock_items_controller.rb b/admin/app/controllers/solidus_admin/stock_items_controller.rb
index 1333dbe672c..b4be4707d7b 100644
--- a/admin/app/controllers/solidus_admin/stock_items_controller.rb
+++ b/admin/app/controllers/solidus_admin/stock_items_controller.rb
@@ -1,10 +1,9 @@
 # frozen_string_literal: true
 
 module SolidusAdmin
-  class StockItemsController < SolidusAdmin::BaseController
+  class StockItemsController < SolidusAdmin::ResourcesController
     include SolidusAdmin::ControllerHelpers::Search
-    before_action :load_stock_items, only: [:index, :edit, :update]
-    before_action :load_stock_item, only: [:edit, :update]
+    before_action :load_stock_items, only: [:index]
 
     search_scope(:all, default: true) { _1 }
     search_scope(:back_orderable) { _1.where(backorderable: true) }
@@ -18,31 +17,32 @@ def index
       end
     end
 
-    def edit
-      respond_to do |format|
-        format.html { render component('stock_items/edit').new(stock_item: @stock_item, page: @page) }
-      end
-    end
-
     def update
       quantity_adjustment = params[:quantity_adjustment].to_i
-      @stock_item.assign_attributes(stock_item_params)
+      @stock_item.assign_attributes(permitted_resource_params)
       @stock_item.stock_movements.build(quantity: quantity_adjustment, originator: current_solidus_admin_user)
 
       if @stock_item.save
-        respond_to do |format|
-          format.html { redirect_to solidus_admin.stock_items_path, status: :see_other }
-          format.turbo_stream { render turbo_stream: '<turbo-stream action="refresh" />' }
-        end
+        redirect_to after_update_path, status: :see_other
       else
         respond_to do |format|
-          format.html { render component('stock_items/edit').new(stock_item: @stock_item, page: @page), status: :unprocessable_entity }
+          page_component = edit_component.new(@resource)
+
+          format.html do
+            render page_component, status: :unprocessable_entity
+          end
+          format.turbostream do
+            render turbo_stream: turbo_stream.replace(:resource_modal, page_component),
+              status: :unprocessable_entity
+          end
         end
       end
     end
 
     private
 
+    def resource_class = Spree::StockItem
+
     def load_stock_items
       @stock_items = apply_search_to(
         Spree::StockItem.reorder(nil),
@@ -56,11 +56,7 @@ def load_stock_items
       })
     end
 
-    def load_stock_item
-      @stock_item = Spree::StockItem.find(params[:id])
-    end
-
-    def stock_item_params
+    def permitted_resource_params
       params.require(:stock_item).permit(:backorderable)
     end
   end