Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Admin] Add stock_items/edit modal component #5543

Merged
merged 9 commits into from
Dec 12, 2023
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ jobs:
- setup
- test

test_with_coverage:
test_solidus_with_coverage:
parameters:
database:
type: string
Expand All @@ -350,7 +350,7 @@ workflows:
- solidus_installer

# Only test with coverage support with the default versions
- test_with_coverage:
- test_solidus_with_coverage:
post-steps:
- codecov/upload:
file: $COVERAGE_FILE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,11 @@
<% @order.line_items.each do |line_item| %>
<tr class="border-gray-100 border-t">
<td class="px-6 py-4">
<div class="flex gap-2 grow">
<% variant = line_item.variant %>
<%= render component("ui/thumbnail").new(
src: (variant.images.first || variant.product.gallery.images.first)&.url(:small),
alt: variant.name
) %>
<div class="flex-col">
<div class="leading-5 text-black body-small-bold"><%= variant.name %></div>
<div class="leading-5 text-gray-500 body-small">
SKU: <%= variant.sku %>
<%= variant.options_text.presence&.prepend("- ") %>
</div>
</div>
</div>
<%= render component("ui/resource_item").new(
thumbnail: (line_item.variant.images.first || line_item.variant.product.gallery.images.first)&.url(:small),
title: line_item.variant.name,
subtitle: "#{line_item.variant.sku}#{line_item.variant.options_text.presence&.prepend("- ")}",
) %>
</td>
<td class="px-6 py-4">
<%= form_for(line_item, url: solidus_admin.order_line_item_path(@order, line_item), html: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<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"
>
<%= render component("ui/modal").new(title: t(".title"), close_path: solidus_admin.stock_items_path(page: params[:page], q: permitted_query_params)) 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',
) %>
<% 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,
) %>

<% if params[:q] %>
<%= f.hidden_field :q, value: params[:q].to_json, id: false %>
<% end %>

<% if params[:page] %>
<%= f.hidden_field :page, value: params[:page], id: false %>
<% end %>
</div>
<% end %>

<% modal.with_actions do %>
<%= render component("ui/button").new(
tag: :a,
scheme: :secondary,
text: t(".cancel"),
href: solidus_admin.stock_items_path(page: params[:page], q: params[:q]),
) %>

<%= render component("ui/button").new(
tag: :button,
text: t(".submit"),
form: form_id,
) %>
<% end %>
<% end %>

<%= render component("stock_items/index").new(page: @page) %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
static values = {
initialCountOnHand: Number,
}

static targets = ['countOnHand', 'quantityAdjustment']

connect() {
this.updateCountOnHand()
}

updateCountOnHand() {
this.countOnHandTarget.value = parseInt(this.initialCountOnHandValue) + parseInt(this.quantityAdjustmentTarget.value)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

class SolidusAdmin::StockItems::Edit::Component < SolidusAdmin::BaseComponent
def initialize(stock_item:, page:)
@stock_item = stock_item
@page = page
end

def title
[
"#{Spree::StockLocation.model_name.human}: #{@stock_item.stock_location.name}",
].join(' / ')
end

def form_id
"#{stimulus_id}-#{dom_id(@stock_item)}"
end

def permitted_query_params
return params[:q].permit! if params[:q].respond_to?(:permit)
{}
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
en:
submit: "Save"
cancel: "Cancel"
title: "Edit Stock Levels"
quantity_adjustment: "Quantity Adjustment"
quantity_adjustment_hint_html: |
Enter a positive number to increase the stock level, or a negative number to decrease the stock level.
backorderable_hint_html: |
Enable to allow customers to place orders even when the product is out of stock.<br>
When ordering the product customers will know that the product will be delivered at a later date, once it becomes available again.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<%= page_header_title title %>
<% end %>

<%= render component('ui/table').new(
<%=
render component('ui/table').new(
id: stimulus_id,
data: {
class: Spree::StockItem,
Expand All @@ -12,10 +13,11 @@
next: next_page_path,
columns: columns,
batch_actions: batch_actions,
url: -> { solidus_admin.edit_stock_item_path(_1, page: params[:page], q: permitted_query_params) },
},
search: {
name: :q,
value: params[:q],
value: permitted_query_params,
url: solidus_admin.stock_items_path,
searchbar_key: :variant_product_name_or_variant_sku_or_variant_option_values_name_or_variant_option_values_presentation_cont,
filters: filters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@ def columns
stock_location_column,
back_orderable_column,
count_on_hand_column,
stock_movements_column,
]
end

def image_column
{
col: { class: "w-[72px]" },
header: tag.span('aria-label': t('.image'), role: 'text'),
header: tag.span('aria-label': Spree::Image.model_name.human, role: 'text'),
data: ->(stock_item) do
image = stock_item.variant.gallery.images.first or return

Expand Down Expand Up @@ -127,8 +128,29 @@ def variant_column
def stock_location_column
{
header: :stock_location,
data: ->(stock_item) do
link_to stock_item.stock_location.name, spree.admin_stock_location_stock_movements_path(stock_item.stock_location.id, q: { variant_sku_eq: stock_item.variant.sku })
data: ->(stock_item) { stock_item.stock_location.name },
}
end

# Cache the stock movement counts to avoid N+1 queries
def stock_movement_counts
@stock_movement_counts ||= Spree::StockMovement.where(stock_item_id: @page.records.ids).group(:stock_item_id).count
end

def stock_movements_column
{
header: :stock_movements,
data: -> do
count = stock_movement_counts[_1.id] || 0

link_to(
"#{count} #{Spree::StockMovement.model_name.human(count: count).downcase}",
spree.admin_stock_location_stock_movements_path(
_1.stock_location.id,
q: { variant_sku_eq: _1.variant.sku },
),
class: 'body-link'
)
end
}
end
Expand All @@ -150,4 +172,9 @@ def count_on_hand_column
end
}
end

def permitted_query_params
return params[:q].permit! if params[:q].respond_to?(:permit)
{}
end
end
21 changes: 21 additions & 0 deletions admin/app/components/solidus_admin/ui/forms/field/component.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class SolidusAdmin::UI::Forms::Field::Component < SolidusAdmin::BaseComponent
extend SolidusAdmin::ComponentsHelper

def initialize(label:, hint: nil, tip: nil, error: nil, input_attributes: nil, **attributes)
@label = label
@hint = hint
Expand Down Expand Up @@ -70,6 +72,25 @@ def self.text_area(form, method, object: nil, hint: nil, tip: nil, size: :m, **a
)
end

def self.toggle(form, method, object: nil, hint: nil, tip: nil, size: :m, **attributes)
object_name, object, label, errors = extract_form_details(form, object, method)

new(
label: label,
hint: hint,
tip: tip,
error: errors,
).with_content(
component('ui/forms/switch').new(
name: "#{object_name}[#{method}]",
size: size,
checked: object.public_send(method),
include_hidden: true,
**attributes,
)
)
end

def self.extract_form_details(form, object, method)
if form.is_a?(String)
object_name = form
Expand Down
46 changes: 25 additions & 21 deletions admin/app/components/solidus_admin/ui/forms/switch/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,38 @@ class SolidusAdmin::UI::Forms::Switch::Component < SolidusAdmin::BaseComponent
m: 'w-10 h-6 after:w-5 after:h-5 after:checked:translate-x-4',
}.freeze

def initialize(size: :m, **attributes)
def initialize(size: :m, include_hidden: false, **attributes)
@size = size
@attributes = attributes
end
@include_hidden = include_hidden
@attributes[:class] = "
#{SIZES.fetch(@size)}
rounded-full after:rounded-full
appearance-none inline-block relative p-0.5 cursor-pointer

def call
tag.input(
type: 'checkbox',
class: "
#{SIZES.fetch(@size)}
rounded-full after:rounded-full
appearance-none inline-block relative p-0.5 cursor-pointer
outline-none
focus:ring focus:ring-gray-300 focus:ring-0.5 focus:ring-offset-1
active:ring active:ring-gray-300 active:ring-0.5 active:ring-offset-1

outline-none
focus:ring focus:ring-gray-300 focus:ring-0.5 focus:ring-offset-1
active:ring active:ring-gray-300 active:ring-0.5 active:ring-offset-1
disabled:cursor-not-allowed
after:top-0 after:left-0
after:content-[''] after:block
after:transition-all after:duration-300 after:ease-in-out

disabled:cursor-not-allowed
after:top-0 after:left-0
after:content-[''] after:block
after:transition-all after:duration-300 after:ease-in-out
bg-gray-200 after:bg-white
hover:bg-gray-300
checked:bg-gray-500 checked:hover:bg-gray-70
disabled:opacity-40
#{attributes[:class]}
"
end

bg-gray-200 after:bg-white
hover:bg-gray-300
checked:bg-gray-500 checked:hover:bg-gray-70
disabled:opacity-40
",
def call
input = tag.input(
type: 'checkbox',
**@attributes,
)

@include_hidden ? hidden_field_tag(@attributes.fetch(:name), false) + input : input
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<label class="flex flex-wrap items-center gap-2 w-full border border-gray-100 rounded px-4 py-2">
<div class="flex gap-1 items-center grow py-2">
<span class="text-gray-700 body-tiny-bold body-text-bold"><%= @label %></span>

<%= render component("ui/toggletip").new(text: @tip) if @tip.present? %>
</div>

<%= render component("ui/forms/switch").new(**@attributes) %>

<% if @hint.present? || @error.present? %>
<div
class="
w-full body-small [:disabled~&]:text-gray-300 text-gray-500 flex gap-1 flex-col pt-2 pb-4
"
>
<%= tag.span @hint if @hint.present? %>
<%= tag.span safe_join(@error, tag.br), class: "text-red-400" if @error.present? %>
</div>
<% end %>
</label>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class SolidusAdmin::UI::Forms::SwitchField::Component < SolidusAdmin::BaseComponent
def initialize(label:, error:, hint: nil, tip: nil, **attributes)
@label = label
@error = error
@hint = hint
@tip = tip
@attributes = attributes
end
end
Loading