Skip to content

Commit

Permalink
Add new users admin items page
Browse files Browse the repository at this point in the history
This migrates the `users/:id/items` page from the legacy
soldius_backend to the new solidus_admin.
  • Loading branch information
MadelineCollier committed Oct 15, 2024
1 parent 9b3c12f commit 9654330
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 1 deletion.
41 changes: 41 additions & 0 deletions admin/app/components/solidus_admin/users/items/component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<%= page do %>
<%= page_header do %>
<%= page_header_back(solidus_admin.users_path) %>
<%= page_header_title(t(".title", email: @user.email)) %>
<%= page_header_actions do %>
<%= render component("ui/button").new(tag: :a, text: t(".create_order_for_user"), href: spree.new_admin_order_path(user_id: @user.id)) %>
<% end %>
<% end %>
<%= page_header do %>
<% tabs.each do |tab| %>
<%= render(component("ui/button").new(tag: :a, scheme: :ghost, text: tab[:text], 'aria-current': tab[:current], href: tab[:href])) %>
<% end %>
<% end %>
<%= page_with_sidebar do %>
<%= page_with_sidebar_main do %>
<%= render component('ui/panel').new(title: t(".items_purchased")) do %>
<% if @items.present? %>
<%= render component('ui/table').new(
id: stimulus_id,
data: {
class: model_class,
rows: rows,
columns: columns,
url: -> { row_url(_1.order) },
},
)%>
<% else %>
<%= t(".no_orders_found") %>
<%= render component("ui/button").new(tag: :a, text: t(".create_one"), href: spree.new_admin_order_path(user_id: @user.id)) %>
<% end %>
<% end %>
<% end %>
<%= page_with_sidebar_aside do %>
<%= render component("users/last_login").new(user: @user) %>
<% end %>
<% end %>
<% end %>
174 changes: 174 additions & 0 deletions admin/app/components/solidus_admin/users/items/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# frozen_string_literal: true

class SolidusAdmin::Users::Items::Component < SolidusAdmin::BaseComponent
include SolidusAdmin::Layout::PageHelpers

def initialize(user:, items:)
@user = user
@items = items
end

def form_id
@form_id ||= "#{stimulus_id}--form-#{@user.id}"

Check warning on line 12 in admin/app/components/solidus_admin/users/items/component.rb

View check run for this annotation

Codecov / codecov/patch

admin/app/components/solidus_admin/users/items/component.rb#L12

Added line #L12 was not covered by tests
end

def tabs
[
{
text: t('.account'),
href: solidus_admin.user_path(@user),
current: false,
},
{
text: t('.addresses'),
href: solidus_admin.addresses_user_path(@user),
current: false,
},
{
text: t('.order_history'),
href: solidus_admin.orders_user_path(@user),
current: false,
},
{
text: t('.items'),
href: solidus_admin.items_user_path(@user),
current: true,
},
{
text: t('.store_credit'),
href: spree.admin_user_store_credits_path(@user),
current: false,
},
]
end

def model_class
Spree::LineItem
end

def row_url(order)
spree.edit_admin_order_path(order)
end

def rows
@items
end

def columns
[
date_column,
image_column,
description_column,
price_column,
quantity_column,
total_column,
state_column,
number_column,
]
end

def date_column
{
col: { class: "w-[8%]" },
header: :date,
data: ->(item) do
content_tag :div, l(item.order.created_at, format: :short), class: "text-sm"
end
}
end

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

render(

Check warning on line 87 in admin/app/components/solidus_admin/users/items/component.rb

View check run for this annotation

Codecov / codecov/patch

admin/app/components/solidus_admin/users/items/component.rb#L87

Added line #L87 was not covered by tests
component('ui/thumbnail').new(
src: image.url(:small),
alt: item.product.name
)
)
end
}
end

def description_column
{
col: { class: "w-[24%]" },
header: t(".description_column_header"),
data: ->(item) { item_name_with_variant_and_sku(item) }
}
end

def price_column
{
col: { class: "w-[10%]" },
header: :price,
data: ->(item) do
content_tag :div, item.single_money.to_html
end
}
end

def quantity_column
{
col: { class: "w-[7%]" },
header: :qty,
data: ->(item) do
content_tag :div, item.quantity
end
}
end

def total_column
{
col: { class: "w-[10%]" },
header: t(".total_column_header"),
data: ->(item) do
content_tag :div, item.money.to_html
end
}
end

def state_column
{
col: { class: "w-[15%]" },
header: :state,
data: ->(item) do
color = {
'complete' => :green,
'returned' => :red,
'canceled' => :blue,
'cart' => :graphite_light,
}[item.order.state] || :yellow
component('ui/badge').new(name: item.order.state.humanize, color: color)
end
}
end

def number_column
{
col: { class: "w-[18%]" },
header: t(".number_column_header"),
data: ->(item) do
content_tag :div, item.order.number, class: "font-semibold text-sm"
end
}
end

private

def item_name_with_variant_and_sku(item)
content = []
content << item.product.name
content << "(#{item.variant.options_text})" if item.variant.option_values.any?
content << "<strong>#{t('spree.sku')}:</strong> #{item.variant.sku}" if item.variant.sku.present?

# The `.html_safe` is required for the description to display as desired.
# rubocop:disable Rails/OutputSafety
safe_join([content_tag(:div, content.join("<br>").html_safe, class: "text-sm")])
# rubocop:enable Rails/OutputSafety
end
end
16 changes: 16 additions & 0 deletions admin/app/components/solidus_admin/users/items/component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
en:
title: "Users / %{email} / Items Purchased"
account: Account
addresses: Addresses
order_history: Order History
items: Items
store_credit: Store Credit
last_active: Last Active
create_order_for_user: Create order for this user
items_purchased: Items Purchased
no_orders_found: No Orders found.
create_one: Create One
back: Back
number_column_header: "Order #"
description_column_header: "Description"
total_column_header: "Total"
17 changes: 16 additions & 1 deletion admin/app/controllers/solidus_admin/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class UsersController < SolidusAdmin::BaseController
include SolidusAdmin::ControllerHelpers::Search
include Spree::Core::ControllerHelpers::StrongParameters

before_action :set_user, only: [:edit, :addresses, :update_addresses, :orders]
before_action :set_user, only: [:edit, :addresses, :update_addresses, :orders, :items]

search_scope(:all, default: true)
search_scope(:customers) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) }
Expand Down Expand Up @@ -58,6 +58,14 @@ def orders
end
end

def items
set_items

respond_to do |format|
format.html { render component('users/items').new(user: @user, items: @items) }
end
end

def edit
respond_to do |format|
format.html { render component('users/edit').new(user: @user) }
Expand Down Expand Up @@ -107,6 +115,13 @@ def set_orders
@orders = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page])
end

def set_items
params[:q] ||= {}
@search = Spree::Order.reverse_chronological.includes(line_items: { variant: [:product, { option_values: :option_type }] }).ransack(params[:q].merge(user_id_eq: @user.id))
@orders = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page])
@items = @orders&.map(&:line_items)&.flatten
end

def authorization_subject
Spree.user_class
end
Expand Down
1 change: 1 addition & 0 deletions admin/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
get :addresses
put :update_addresses
get :orders
get :items
end
end

Expand Down
46 changes: 46 additions & 0 deletions admin/spec/features/users_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,50 @@
end
end
end

context "when viewing a user's purchased items" do
context "when a user has no purchased items" do
before do
create(:user, email: "[email protected]")
visit "/admin/users"
find_row("[email protected]").click
click_on "Items"
end

it "shows the purchased items page" do
expect(page).to have_content("Users / [email protected] / Items Purchased")
expect(page).to have_content("Lifetime Stats")
expect(page).to have_content("Items Purchased")
expect(page).to be_axe_clean
end

it "shows the appropriate content" do
expect(page).to have_content("No Orders found.")
end
end

context "when a user has ordered before" do
before do
create(:order_with_line_items, user: create(:user, email: "[email protected]"))
visit "/admin/users"
find_row("[email protected]").click
click_on "Items"
end

it "shows the purchased items page" do
expect(page).to have_content("Users / [email protected] / Items Purchased")
expect(page).to have_content("Lifetime Stats")
expect(page).to have_content("Items Purchased")
expect(page).to be_axe_clean
end

it "lists the purchased items" do
expect(page).to have_content(/R\d+/) # Matches on any order number.
expect(page).to have_content("Description")
expect(page).to have_content("Qty")
expect(page).to have_content("State")
expect(page).not_to have_content("No Orders found.")
end
end
end
end
10 changes: 10 additions & 0 deletions admin/spec/requests/solidus_admin/users_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@
end
end

describe "GET /items" do
let!(:order) { create(:order_with_line_items, user: user) }

it "renders the items template and displays the user's purchased items" do
get solidus_admin.items_user_path(user)
expect(response).to have_http_status(:ok)
expect(response.body).to include(order.number)
end
end

describe "DELETE /destroy" do
it "deletes the user and redirects to the index page with a 303 See Other status" do
# Ensure the user exists prior to deletion
Expand Down

0 comments on commit 9654330

Please sign in to comment.