diff --git a/admin/app/components/solidus_admin/shipping/component.html.erb b/admin/app/components/solidus_admin/shipping/component.html.erb new file mode 100644 index 00000000000..16c2324254d --- /dev/null +++ b/admin/app/components/solidus_admin/shipping/component.html.erb @@ -0,0 +1,30 @@ +<%= page do %> + <%= page_header do %> + <%= page_header_title safe_join([ + tag.div(t(".title")), + tag.div(t(".subtitle"), class: "body-small text-gray-500"), + ]) %> + <% end %> + + <%= page_header do %> + <% title = capture do %> + <% tabs.each do |tab_class, href|%> + <%= render component('ui/button').new( + tag: :a, + scheme: :ghost, + href: href, + text: tab_class.model_name.human.pluralize, + "aria-current" => tab_class == @current_class, + ) %> + <% end %> + <% end %> + + <%= page_header_title title %> + + <%= page_header_actions do %> + <%= actions %> + <% end %> + <% end %> + + <%= content %> +<% end %> diff --git a/admin/app/components/solidus_admin/shipping/component.rb b/admin/app/components/solidus_admin/shipping/component.rb new file mode 100644 index 00000000000..a9a68ed13e7 --- /dev/null +++ b/admin/app/components/solidus_admin/shipping/component.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class SolidusAdmin::Shipping::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + renders_one :actions + + def initialize(current_class:) + @current_class = current_class + end + + def tabs + { + Spree::ShippingMethod => solidus_admin.shipping_methods_path, + Spree::ShippingCategory => solidus_admin.shipping_categories_path, + Spree::StockLocation => solidus_admin.stock_locations_path, + } + end +end diff --git a/admin/app/components/solidus_admin/shipping/component.yml b/admin/app/components/solidus_admin/shipping/component.yml new file mode 100644 index 00000000000..5ac6a2fb629 --- /dev/null +++ b/admin/app/components/solidus_admin/shipping/component.yml @@ -0,0 +1,3 @@ +en: + title: "Shipping" + subtitle: "Configure shipping methods, categories, and stock locations for customer orders." diff --git a/admin/app/components/solidus_admin/shipping_categories/index/component.html.erb b/admin/app/components/solidus_admin/shipping_categories/index/component.html.erb new file mode 100644 index 00000000000..9afe630b8ae --- /dev/null +++ b/admin/app/components/solidus_admin/shipping_categories/index/component.html.erb @@ -0,0 +1,32 @@ +<%= render component('shipping').new(current_class: Spree::ShippingCategory) do |layout| %> + <% layout.with_actions do %> + <%= render component("ui/button").new( + tag: :a, + text: t('.add'), + href: spree.new_admin_shipping_category_path, + icon: "add-line", + class: "align-self-end w-full", + ) %> + <% end %> + + <%= render component('ui/table').new( + id: stimulus_id, + data: { + class: Spree::ShippingCategory, + rows: @page.records, + url: ->(shipping_category) { spree.edit_admin_shipping_category_path(shipping_category) }, + prev: prev_page_path, + next: next_page_path, + columns: columns, + batch_actions: batch_actions, + }, + search: { + name: :q, + value: params[:q], + url: solidus_admin.shipping_categories_path, + searchbar_key: :name_or_description_cont, + filters: filters, + scopes: scopes, + }, + ) %> +<% end %> diff --git a/admin/app/components/solidus_admin/shipping_categories/index/component.rb b/admin/app/components/solidus_admin/shipping_categories/index/component.rb new file mode 100644 index 00000000000..74577cae898 --- /dev/null +++ b/admin/app/components/solidus_admin/shipping_categories/index/component.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class SolidusAdmin::ShippingCategories::Index::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + + def initialize(page:) + @page = page + end + + def title + Spree::ShippingCategory.model_name.human.pluralize + end + + def prev_page_path + solidus_admin.url_for(**request.params, page: @page.number - 1, only_path: true) unless @page.first? + end + + def next_page_path + solidus_admin.url_for(**request.params, page: @page.next_param, only_path: true) unless @page.last? + end + + def batch_actions + [ + { + display_name: t('.batch_actions.delete'), + action: solidus_admin.shipping_categories_path, + method: :delete, + icon: 'delete-bin-7-line', + }, + ] + end + + def filters + [] + end + + def scopes + [] + end + + def columns + [ + :name + ] + end +end diff --git a/admin/app/components/solidus_admin/shipping_categories/index/component.yml b/admin/app/components/solidus_admin/shipping_categories/index/component.yml new file mode 100644 index 00000000000..54418702ad3 --- /dev/null +++ b/admin/app/components/solidus_admin/shipping_categories/index/component.yml @@ -0,0 +1,4 @@ +en: + add: 'Add new' + batch_actions: + delete: 'Delete' diff --git a/admin/app/components/solidus_admin/shipping_methods/index/component.html.erb b/admin/app/components/solidus_admin/shipping_methods/index/component.html.erb new file mode 100644 index 00000000000..6bb417a7e87 --- /dev/null +++ b/admin/app/components/solidus_admin/shipping_methods/index/component.html.erb @@ -0,0 +1,32 @@ +<%= render component('shipping').new(current_class: Spree::ShippingMethod) do |layout| %> + <% layout.with_actions do %> + <%= render component("ui/button").new( + tag: :a, + text: t('.add'), + href: spree.new_admin_shipping_method_path, + icon: "add-line", + class: "align-self-end w-full", + ) %> + <% end %> + + <%= render component('ui/table').new( + id: stimulus_id, + data: { + class: Spree::ShippingMethod, + rows: @page.records, + url: ->(shipping_method) { spree.edit_admin_shipping_method_path(shipping_method) }, + prev: prev_page_path, + next: next_page_path, + columns: columns, + batch_actions: batch_actions, + }, + search: { + name: :q, + value: params[:q], + url: solidus_admin.shipping_methods_path, + searchbar_key: :name_or_description_cont, + filters: filters, + scopes: scopes, + }, + ) %> +<% end %> diff --git a/admin/app/components/solidus_admin/shipping_methods/index/component.rb b/admin/app/components/solidus_admin/shipping_methods/index/component.rb new file mode 100644 index 00000000000..ef6fd28f8e9 --- /dev/null +++ b/admin/app/components/solidus_admin/shipping_methods/index/component.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +class SolidusAdmin::ShippingMethods::Index::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + + def initialize(page:) + @page = page + end + + def title + Spree::ShippingMethod.model_name.human.pluralize + end + + def prev_page_path + solidus_admin.url_for(**request.params, page: @page.number - 1, only_path: true) unless @page.first? + end + + def next_page_path + solidus_admin.url_for(**request.params, page: @page.next_param, only_path: true) unless @page.last? + end + + def batch_actions + [ + { + display_name: t('.batch_actions.delete'), + action: solidus_admin.shipping_methods_path, + method: :delete, + icon: 'delete-bin-7-line', + }, + ] + end + + def filters + [] + end + + def scopes + [] + end + + def columns + [ + { + header: :name, + data: -> { [_1.admin_name.presence, _1.name].compact.join(' / ') }, + }, + { + header: :zone, + data: -> { _1.zones.pluck(:name).to_sentence }, + }, + { + header: :calculator, + data: -> { _1.calculator&.description }, + }, + { + header: :available_to_users, + data: -> { _1.available_to_users? ? component('ui/badge').yes : component('ui/badge').no }, + }, + ] + end +end diff --git a/admin/app/components/solidus_admin/shipping_methods/index/component.yml b/admin/app/components/solidus_admin/shipping_methods/index/component.yml new file mode 100644 index 00000000000..54418702ad3 --- /dev/null +++ b/admin/app/components/solidus_admin/shipping_methods/index/component.yml @@ -0,0 +1,4 @@ +en: + add: 'Add new' + batch_actions: + delete: 'Delete' diff --git a/admin/app/components/solidus_admin/stock_locations/index/component.html.erb b/admin/app/components/solidus_admin/stock_locations/index/component.html.erb new file mode 100644 index 00000000000..b127f86ba5c --- /dev/null +++ b/admin/app/components/solidus_admin/stock_locations/index/component.html.erb @@ -0,0 +1,32 @@ +<%= render component('shipping').new(current_class: Spree::StockLocation) do |layout| %> + <% layout.with_actions do %> + <%= render component("ui/button").new( + tag: :a, + text: t('.add'), + href: spree.new_admin_stock_location_path, + icon: "add-line", + class: "align-self-end w-full", + ) %> + <% end %> + + <%= render component('ui/table').new( + id: stimulus_id, + data: { + class: Spree::StockLocation, + rows: @page.records, + url: ->(stock_location) { spree.edit_admin_stock_location_path(stock_location) }, + prev: prev_page_path, + next: next_page_path, + columns: columns, + batch_actions: batch_actions, + }, + search: { + name: :q, + value: params[:q], + url: solidus_admin.stock_locations_path, + searchbar_key: :name_or_description_cont, + filters: filters, + scopes: scopes, + }, + ) %> +<% end %> diff --git a/admin/app/components/solidus_admin/stock_locations/index/component.rb b/admin/app/components/solidus_admin/stock_locations/index/component.rb new file mode 100644 index 00000000000..4ea95405227 --- /dev/null +++ b/admin/app/components/solidus_admin/stock_locations/index/component.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +class SolidusAdmin::StockLocations::Index::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + + def initialize(page:) + @page = page + end + + def title + Spree::StockLocation.model_name.human.pluralize + end + + def prev_page_path + solidus_admin.url_for(**request.params, page: @page.number - 1, only_path: true) unless @page.first? + end + + def next_page_path + solidus_admin.url_for(**request.params, page: @page.next_param, only_path: true) unless @page.last? + end + + def batch_actions + [ + { + display_name: t('.batch_actions.delete'), + action: solidus_admin.stock_locations_path, + method: :delete, + icon: 'delete-bin-7-line', + }, + ] + end + + def filters + [] + end + + def scopes + [] + end + + def columns + [ + :name, + :code, + :admin_name, + { + header: :state, + data: -> { + color = _1.active? ? :green : :graphite_light + text = _1.active? ? t('.active') : t('.inactive') + + component('ui/badge').new(name: text, color: color) + }, + }, + { + header: :backorderable, + data: -> { + _1.backorderable_default ? component('ui/badge').yes : component('ui/badge').no + } + }, + { + header: :default, + data: -> { + _1.default ? component('ui/badge').yes : component('ui/badge').no + } + }, + { + header: :stock_movements, + data: -> { + count = _1.stock_movements.count + + link_to( + "#{count} #{Spree::StockMovement.model_name.human(count: count).downcase}", + spree.admin_stock_location_stock_movements_path(_1), + class: 'body-link' + ) + } + } + ] + end +end diff --git a/admin/app/components/solidus_admin/stock_locations/index/component.yml b/admin/app/components/solidus_admin/stock_locations/index/component.yml new file mode 100644 index 00000000000..72183961309 --- /dev/null +++ b/admin/app/components/solidus_admin/stock_locations/index/component.yml @@ -0,0 +1,6 @@ +en: + active: 'Active' + inactive: 'Inactive' + add: 'Add new' + batch_actions: + delete: 'Delete' diff --git a/admin/app/controllers/solidus_admin/shipping_categories_controller.rb b/admin/app/controllers/solidus_admin/shipping_categories_controller.rb new file mode 100644 index 00000000000..7e77234c58b --- /dev/null +++ b/admin/app/controllers/solidus_admin/shipping_categories_controller.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module SolidusAdmin + class ShippingCategoriesController < SolidusAdmin::BaseController + include SolidusAdmin::ControllerHelpers::Search + + def index + shipping_categories = apply_search_to( + Spree::ShippingCategory.order(id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from(shipping_categories) + + respond_to do |format| + format.html { render component('shipping_categories/index').new(page: @page) } + end + end + + def destroy + @shipping_category = Spree::ShippingCategory.find_by!(id: params[:id]) + + Spree::ShippingCategory.transaction { @shipping_category.destroy } + + flash[:notice] = t('.success') + redirect_back_or_to shipping_categories_path, status: :see_other + end + + private + + def load_shipping_category + @shipping_category = Spree::ShippingCategory.find_by!(id: params[:id]) + authorize! action_name, @shipping_category + end + + def shipping_category_params + params.require(:shipping_category).permit(:shipping_category_id, permitted_shipping_category_attributes) + end + end +end diff --git a/admin/app/controllers/solidus_admin/shipping_methods_controller.rb b/admin/app/controllers/solidus_admin/shipping_methods_controller.rb new file mode 100644 index 00000000000..dcf18294bbd --- /dev/null +++ b/admin/app/controllers/solidus_admin/shipping_methods_controller.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module SolidusAdmin + class ShippingMethodsController < SolidusAdmin::BaseController + include SolidusAdmin::ControllerHelpers::Search + + def index + shipping_methods = apply_search_to( + Spree::ShippingMethod.order(id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from(shipping_methods) + + respond_to do |format| + format.html { render component('shipping_methods/index').new(page: @page) } + end + end + + def destroy + @shipping_methods = Spree::ShippingMethod.where(id: params[:id]) + + Spree::ShippingMethod.transaction { @shipping_methods.destroy_all } + + flash[:notice] = t('.success') + redirect_back_or_to shipping_methods_path, status: :see_other + end + + private + + def load_shipping_method + @shipping_method = Spree::ShippingMethod.find_by!(number: params[:id]) + authorize! action_name, @shipping_method + end + + def shipping_method_params + params.require(:shipping_method).permit(:shipping_method_id, permitted_shipping_method_attributes) + end + end +end diff --git a/admin/app/controllers/solidus_admin/stock_locations_controller.rb b/admin/app/controllers/solidus_admin/stock_locations_controller.rb new file mode 100644 index 00000000000..364bfb12fc3 --- /dev/null +++ b/admin/app/controllers/solidus_admin/stock_locations_controller.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module SolidusAdmin + class StockLocationsController < SolidusAdmin::BaseController + include SolidusAdmin::ControllerHelpers::Search + + def index + stock_locations = apply_search_to( + Spree::StockLocation.order(id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from(stock_locations) + + respond_to do |format| + format.html { render component('stock_locations/index').new(page: @page) } + end + end + + def destroy + @stock_locations = Spree::StockLocation.where(id: params[:id]) + + Spree::StockLocation.transaction { @stock_locations.destroy_all } + + flash[:notice] = t('.success') + redirect_back_or_to stock_locations_path, status: :see_other + end + + private + + def load_stock_location + @stock_location = Spree::StockLocation.find_by!(number: params[:id]) + authorize! action_name, @stock_location + end + + def stock_location_params + params.require(:stock_location).permit(:stock_location_id, permitted_stock_location_attributes) + end + end +end diff --git a/admin/config/locales/shipping_categories.en.yml b/admin/config/locales/shipping_categories.en.yml new file mode 100644 index 00000000000..69ab216dac5 --- /dev/null +++ b/admin/config/locales/shipping_categories.en.yml @@ -0,0 +1,6 @@ +en: + solidus_admin: + shipping_categories: + title: "Shipping Categories" + destroy: + success: "Shipping categories were successfully removed." diff --git a/admin/config/locales/shipping_methods.en.yml b/admin/config/locales/shipping_methods.en.yml new file mode 100644 index 00000000000..d830287e984 --- /dev/null +++ b/admin/config/locales/shipping_methods.en.yml @@ -0,0 +1,6 @@ +en: + solidus_admin: + shipping_methods: + title: "Shipping Methods" + destroy: + success: "Shipping methods were successfully removed." diff --git a/admin/config/locales/stock_locations.en.yml b/admin/config/locales/stock_locations.en.yml new file mode 100644 index 00000000000..5c365e48372 --- /dev/null +++ b/admin/config/locales/stock_locations.en.yml @@ -0,0 +1,6 @@ +en: + solidus_admin: + stock_locations: + title: "Stock Locations" + destroy: + success: "Stock locations were successfully removed." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 3380e1fb21d..fd7dcdaeda5 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -41,4 +41,7 @@ admin_resources :tax_rates, only: [:index, :destroy] admin_resources :payment_methods, only: [:index, :destroy], sortable: true admin_resources :stock_items, only: [:index] + admin_resources :shipping_methods, only: [:index, :destroy] + admin_resources :shipping_categories, only: [:index, :destroy] + admin_resources :stock_locations, only: [:index, :destroy] end diff --git a/admin/spec/features/shipping_categories_spec.rb b/admin/spec/features/shipping_categories_spec.rb new file mode 100644 index 00000000000..e3d02c4a0d9 --- /dev/null +++ b/admin/spec/features/shipping_categories_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe "Shipping Categories", :js, type: :feature do + before { sign_in create(:admin_user, email: 'admin@example.com') } + + it "lists shipping categories and allows deleting them" do + create(:shipping_category, name: "Default-shipping") + + visit "/admin/shipping_categories" + expect(page).to have_content("Default-shipping") + expect(page).to be_axe_clean + + select_row("Default-shipping") + click_on "Delete" + expect(page).to have_content("Shipping categories were successfully removed.") + expect(page).not_to have_content("Default-shipping") + expect(Spree::ShippingCategory.count).to eq(0) + expect(page).to be_axe_clean + end +end diff --git a/admin/spec/features/shipping_methods_spec.rb b/admin/spec/features/shipping_methods_spec.rb new file mode 100644 index 00000000000..3e85df71926 --- /dev/null +++ b/admin/spec/features/shipping_methods_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe "Shipping Methods", :js, type: :feature do + before { sign_in create(:admin_user, email: 'admin@example.com') } + + it "lists tax categories and allows deleting them" do + create(:shipping_method, name: "FAAAST") + + visit "/admin/shipping_methods" + expect(page).to have_content("FAAAST") + expect(page).to be_axe_clean + + select_row("FAAAST") + click_on "Delete" + expect(page).to have_content("Shipping methods were successfully removed.") + expect(page).not_to have_content("FAAAST") + expect(Spree::ShippingMethod.count).to eq(0) + expect(page).to be_axe_clean + end +end diff --git a/admin/spec/features/stock_locations_spec.rb b/admin/spec/features/stock_locations_spec.rb new file mode 100644 index 00000000000..16815215e63 --- /dev/null +++ b/admin/spec/features/stock_locations_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe "Stock Locations", :js, type: :feature do + before { sign_in create(:admin_user, email: 'admin@example.com') } + + it "lists stock locations and allows deleting them" do + create(:stock_location, name: "Default-location") + + visit "/admin/stock_locations" + expect(page).to have_content("Default-location") + expect(page).to be_axe_clean + + select_row("Default-location") + click_on "Delete" + expect(page).to have_content("Stock locations were successfully removed.") + expect(page).not_to have_content("Default-location") + expect(Spree::StockLocation.count).to eq(0) + expect(page).to be_axe_clean + end +end diff --git a/admin/spec/features/tax_categories_spec.rb b/admin/spec/features/tax_categories_spec.rb index 8909ca5e74a..cbc15b436c9 100644 --- a/admin/spec/features/tax_categories_spec.rb +++ b/admin/spec/features/tax_categories_spec.rb @@ -18,7 +18,7 @@ click_on "Delete" expect(page).to have_content("Tax categories were successfully removed.") expect(page).not_to have_content("Clothing") - expect(Spree.user_class.count).to eq(1) + expect(Spree::TaxCategory.count).to eq(1) expect(page).to be_axe_clean end end