spree_admin 5.4.0.rc5 → 5.4.0.rc7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/tailwind/spree/admin/base/_theme.css +2 -1
  3. data/app/assets/tailwind/spree/admin/components/_bulk-panel.css +1 -1
  4. data/app/assets/tailwind/spree/admin/components/_buttons.css +1 -1
  5. data/app/assets/tailwind/spree/admin/components/_cards.css +3 -7
  6. data/app/assets/tailwind/spree/admin/components/_forms.css +1 -1
  7. data/app/assets/tailwind/spree/admin/components/_list-groups.css +1 -1
  8. data/app/assets/tailwind/spree/admin/components/_variants-form.css +7 -0
  9. data/app/controllers/concerns/spree/admin/order_concern.rb +1 -1
  10. data/app/controllers/spree/admin/imports_controller.rb +2 -1
  11. data/app/controllers/spree/admin/orders/user_controller.rb +1 -1
  12. data/app/controllers/spree/admin/product_translations_controller.rb +64 -0
  13. data/app/controllers/spree/admin/products_controller.rb +7 -1
  14. data/app/controllers/spree/admin/stock_movements_controller.rb +37 -0
  15. data/app/helpers/spree/admin/base_helper.rb +3 -1
  16. data/app/helpers/spree/admin/table_helper.rb +1 -1
  17. data/app/javascript/spree/admin/controllers/variants_form_controller.js +9 -0
  18. data/app/models/spree/admin/form_builder.rb +1 -1
  19. data/app/presenters/spree/admin/order_summary_presenter.rb +11 -0
  20. data/app/views/spree/admin/dashboard/show.html.erb +1 -1
  21. data/app/views/spree/admin/exports/_export.html.erb +1 -1
  22. data/app/views/spree/admin/import_rows/_product.html.erb +4 -0
  23. data/app/views/spree/admin/imports/new.html.erb +1 -1
  24. data/app/views/spree/admin/markets/_form.html.erb +1 -1
  25. data/app/views/spree/admin/option_types/_form.html.erb +9 -1
  26. data/app/views/spree/admin/option_types/_option_value_fields.html.erb +15 -1
  27. data/app/views/spree/admin/orders/_shipment.html.erb +1 -1
  28. data/app/views/spree/admin/payments/_payment.html.erb +1 -1
  29. data/app/views/spree/admin/payments/new.html.erb +1 -1
  30. data/app/views/spree/admin/payments/source_forms/_previous_cards.html.erb +1 -1
  31. data/app/views/spree/admin/payments/source_forms/_store_credit.html.erb +1 -1
  32. data/app/views/spree/admin/product_translations/index.html.erb +108 -0
  33. data/app/views/spree/admin/products/form/_variants.html.erb +3 -0
  34. data/app/views/spree/admin/products/form/variants/_variant_template.html.erb +3 -0
  35. data/app/views/spree/admin/shared/_filters_search_bar.html.erb +1 -1
  36. data/app/views/spree/admin/shared/_head.html.erb +1 -1
  37. data/app/views/spree/admin/shared/_media_form.html.erb +1 -1
  38. data/app/views/spree/admin/shipments/edit.html.erb +6 -6
  39. data/app/views/spree/admin/stock_movements/index.html.erb +15 -0
  40. data/app/views/spree/admin/tables/_column_selector.html.erb +1 -1
  41. data/app/views/spree/admin/tables/_query_builder.html.erb +1 -1
  42. data/app/views/spree/admin/tables/_table.html.erb +3 -3
  43. data/app/views/spree/admin/tables/columns/_stock_movement_originator.html.erb +28 -0
  44. data/app/views/spree/admin/translations/edit.html.erb +1 -1
  45. data/app/views/spree/admin/variants/form/_pricing.html.erb +1 -1
  46. data/config/initializers/spree_admin_navigation.rb +18 -3
  47. data/config/initializers/spree_admin_tables.rb +63 -0
  48. data/config/locales/en.yml +12 -4
  49. data/config/routes.rb +3 -0
  50. data/lib/spree/admin/engine.rb +2 -0
  51. metadata +12 -7
  52. data/db/migrate/20250217171018_create_action_text_video_embeds.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e4dcdb0dbb58f3fc9dbded5e7f0869dac57779665a2f38ad0a2c9805e338047
4
- data.tar.gz: fbec9e836b4c310fefd56d82016ba697ede7498be0f68097715f2f606aac353c
3
+ metadata.gz: f6458ffb6c4174e9a472f42a28640be1fdcb253e8bfb49e1aac5f7d62f9e725c
4
+ data.tar.gz: 27987a653ae8d1c11515fc323bcac9e4c6e2da4da241992d0e903bbe3619b117
5
5
  SHA512:
6
- metadata.gz: e0c87915f09a8dda042e8745e876877a0c0087910ae293f2d9ceb8dc2b2cb071a7dc40b475d46b1ad933308f42c80043787a352c0c7d3aadb8a015e81583bd6a
7
- data.tar.gz: 9334252c421fff0751298d07df351ca54b81a5869b5156b3b5da298ffa820d061997ce12c038802b6e95bd41a07fb7140ac280e7f66db5ad9db2edf7dbe2a94a
6
+ metadata.gz: 9352b948c77b39c924911ade674732ec98a9a7ba2bf787fb58fc2b3015396197e9375ec21f7b51ca62d1a088c5533d0efff9a5e10ecb94b6a072c3ac79c48aab
7
+ data.tar.gz: 2a6320babac28aa0948ce2a15b7e48e8797dab9a496d536fc26504da383c5a50bad83c30faef640d021d622795f71bacd1feef104091aa1bcfa676f81de4d617
@@ -14,7 +14,8 @@
14
14
  --color-info: var(--color-blue-900);
15
15
 
16
16
  /* Typography */
17
- --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
17
+ --font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
18
+ --font-mono: "Geist Mono", ui-monospace, monospace;
18
19
 
19
20
  /* Override Tailwind's text-* scale to match Spree design system */
20
21
  --text-2xs: 0.625rem;
@@ -66,7 +66,7 @@
66
66
  }
67
67
 
68
68
  .bulk-panel-container .btn-close {
69
- @apply w-10;
69
+ @apply w-10 rounded-xl;
70
70
  }
71
71
 
72
72
  #bulk-panel {
@@ -11,7 +11,7 @@
11
11
  .btn {
12
12
  @apply inline-flex items-center justify-center text-center align-middle gap-2;
13
13
  @apply cursor-pointer select-none no-underline;
14
- @apply px-3 py-1.5 text-base font-medium leading-normal border border-transparent rounded-xl;
14
+ @apply px-2.5 py-1.5 text-base font-medium leading-normal border border-transparent rounded-xl;
15
15
  @apply transition-colors duration-100 ease-linear;
16
16
  }
17
17
 
@@ -13,10 +13,6 @@
13
13
  @apply p-0 mb-3;
14
14
  }
15
15
 
16
- .card .card {
17
- @apply rounded-xl;
18
- }
19
-
20
16
  /* Card with table */
21
17
  .card > .table,
22
18
  .card .table-responsive > .table {
@@ -74,7 +70,7 @@
74
70
  }
75
71
 
76
72
  .card-body {
77
- @apply flex-auto p-5;
73
+ @apply flex-auto p-3;
78
74
  }
79
75
 
80
76
  .card-body .form-group:last-child {
@@ -87,7 +83,7 @@
87
83
  }
88
84
 
89
85
  .card-header {
90
- @apply flex items-center mb-0 p-5 pr-3 h-14 border-b border-gray-100 rounded-t-[calc(var(--radius-xl)-1px)];
86
+ @apply flex items-center mb-0 px-3 h-12 border-b border-gray-100 rounded-t-[calc(var(--radius-xl)-1px)];
91
87
  }
92
88
 
93
89
  .card-header .card-title {
@@ -107,7 +103,7 @@
107
103
  }
108
104
 
109
105
  .card-footer {
110
- @apply p-5;
106
+ @apply p-3;
111
107
  }
112
108
 
113
109
  .card-footer:last-child {
@@ -23,7 +23,7 @@
23
23
  .select-input,
24
24
  .form-control-sm,
25
25
  .custom-select-sm {
26
- @apply block w-full py-2 px-3 text-base font-normal leading-normal;
26
+ @apply block w-full py-1.5 px-2.5 text-base font-normal leading-normal;
27
27
  @apply text-gray-950 bg-white border border-gray-200 rounded-lg shadow-xs;
28
28
  @apply focus:outline-offset-2 focus:outline-blue-500 focus:border-gray-200 focus:ring-0;
29
29
  @apply transition-all duration-100 ease-in-out;
@@ -8,7 +8,7 @@
8
8
  }
9
9
 
10
10
  .list-group-item {
11
- @apply relative block py-3 px-5 text-gray-950 no-underline border border-gray-200 hover:bg-gray-25;
11
+ @apply relative block py-2.5 px-3 text-gray-950 no-underline border border-gray-200 hover:bg-gray-25;
12
12
  }
13
13
 
14
14
  .list-group-item:first-child {
@@ -72,6 +72,7 @@
72
72
  }
73
73
 
74
74
  .variants-form .variants-table .column-checkbox,
75
+ .variants-form .variants-table .column-thumbnail,
75
76
  .variants-form .variants-table .column-variant,
76
77
  .variants-form .variants-table .column-price,
77
78
  .variants-form .variants-table .column-quantity,
@@ -83,6 +84,11 @@
83
84
  @apply justify-center grow-0 basis-8;
84
85
  }
85
86
 
87
+ .variants-form .variants-table .column-thumbnail {
88
+ @apply grow-0 shrink-0;
89
+ flex-basis: 36px;
90
+ }
91
+
86
92
  .variants-form .variants-table .column-variant {
87
93
  @apply select-none grow-[3] basis-0;
88
94
  }
@@ -136,6 +142,7 @@
136
142
  }
137
143
 
138
144
  .variants-form .variants-table__body__row.nested .column-checkbox,
145
+ .variants-form .variants-table__body__row.nested .column-thumbnail,
139
146
  .variants-form .variants-table__body__row.nested .column-variant {
140
147
  @apply left-5 relative;
141
148
  }
@@ -30,7 +30,7 @@ module Spree
30
30
  end
31
31
 
32
32
  def resource_not_found
33
- flash[:error] = flash_message_for(model_class.new, :not_found)
33
+ flash[:error] = flash_message_for(Spree::Order.new, :not_found)
34
34
  redirect_to spree.admin_orders_path
35
35
  end
36
36
  end
@@ -45,7 +45,8 @@ module Spree
45
45
  end
46
46
 
47
47
  def assign_params
48
- @object.type = available_types.map(&:to_s).find { |type| type == params.dig(:import, :type) } || available_types.first.to_s
48
+ requested_type = params.dig(:import, :type) || params[:type]
49
+ @object.type = available_types.map(&:to_s).find { |type| type == requested_type } || available_types.first.to_s
49
50
  end
50
51
 
51
52
  def available_types
@@ -53,7 +53,7 @@ module Spree
53
53
  end
54
54
 
55
55
  def update
56
- user = Spree.user_class.find_by_prefix_id!(params[:user_id])
56
+ user = Spree.user_class.find_by_param!(params[:user_id])
57
57
  @order.associate_user!(user)
58
58
 
59
59
  if !@order.completed? && @order.line_items.any?
@@ -0,0 +1,64 @@
1
+ module Spree
2
+ module Admin
3
+ class ProductTranslationsController < BaseController
4
+ include Pagy::Method
5
+
6
+ before_action :load_locales
7
+
8
+ def index
9
+ @total_products = store_product_ids.count
10
+ @coverage = build_coverage
11
+ @products = paginated_products
12
+ @translated_locales_map = build_translated_locales_map(@products.map(&:id))
13
+ end
14
+
15
+ private
16
+
17
+ def load_locales
18
+ @default_locale = current_store.default_locale
19
+ @locales = (current_store.supported_locales_list - [@default_locale]).sort
20
+ end
21
+
22
+ def store_product_ids
23
+ @store_product_ids ||= current_store.product_ids
24
+ end
25
+
26
+ def build_coverage
27
+ return [] if @locales.empty?
28
+
29
+ counts = Spree::Product::Translation
30
+ .where(spree_product_id: store_product_ids)
31
+ .where(locale: @locales)
32
+ .where.not(name: [nil, ''])
33
+ .group(:locale)
34
+ .count
35
+
36
+ @locales.map do |locale|
37
+ translated = counts[locale] || 0
38
+ percentage = @total_products.positive? ? (translated * 100.0 / @total_products).round : 0
39
+ { locale: locale, translated: translated, total: @total_products, percentage: percentage }
40
+ end
41
+ end
42
+
43
+ def paginated_products
44
+ scope = current_store.products.order(:name)
45
+ scope = scope.ransack(params[:q]).result if params[:q].present?
46
+ @pagy, products = pagy(scope, limit: params[:per_page] || 25)
47
+ products
48
+ end
49
+
50
+ def build_translated_locales_map(product_ids)
51
+ return {} if product_ids.empty? || @locales.empty?
52
+
53
+ rows = Spree::Product::Translation
54
+ .where(spree_product_id: product_ids, locale: @locales)
55
+ .where.not(name: [nil, ''])
56
+ .pluck(:spree_product_id, :locale)
57
+
58
+ rows.each_with_object({}) do |(product_id, locale), hash|
59
+ (hash[product_id] ||= []) << locale
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -179,10 +179,16 @@ module Spree
179
179
 
180
180
  @product_variant_ids = {}
181
181
  @product_variant_prefix_ids = {}
182
+ @product_variant_images = {}
182
183
 
183
- @product.variants.includes(:option_values).each do |variant|
184
+ @product.variants.includes(:option_values, primary_media: { attachment_attachment: :blob }).each do |variant|
184
185
  @product_variant_ids[variant.human_name] = variant.id.to_s
185
186
  @product_variant_prefix_ids[variant.human_name] = variant.to_param
187
+
188
+ image = variant.primary_media
189
+ if image.present? && image.attached? && image.variable?
190
+ @product_variant_images[variant.human_name] = helpers.spree_image_url(image, variant: :mini)
191
+ end
186
192
  end
187
193
  end
188
194
 
@@ -0,0 +1,37 @@
1
+ module Spree
2
+ module Admin
3
+ class StockMovementsController < ResourceController
4
+ include ProductsBreadcrumbConcern
5
+ include TableConcern
6
+
7
+ before_action :add_breadcrumbs
8
+
9
+ private
10
+
11
+ def collection_default_sort
12
+ 'created_at desc'
13
+ end
14
+
15
+ def scope
16
+ super.joins(stock_item: [:variant, :stock_location]).
17
+ merge(current_store.variants.eligible).
18
+ reorder('')
19
+ end
20
+
21
+ def collection_includes
22
+ {
23
+ stock_item: {
24
+ stock_location: [],
25
+ variant: [option_values: :option_type, product: [variants: [:images], master: [:images]], images: []]
26
+ },
27
+ originator: []
28
+ }
29
+ end
30
+
31
+ def add_breadcrumbs
32
+ add_breadcrumb Spree.t(:stock), spree.admin_stock_items_path
33
+ add_breadcrumb Spree.t(:stock_movements), spree.admin_stock_movements_path
34
+ end
35
+ end
36
+ end
37
+ end
@@ -293,7 +293,9 @@ module Spree
293
293
  max = options[:max] || 100
294
294
  percentage = (value.to_f / max * 100).round
295
295
 
296
- content_tag(:div, class: 'progress') do
296
+ css_class = options[:class] || 'progress'
297
+
298
+ content_tag(:div, class: css_class) do
297
299
  content_tag(:div,
298
300
  { class: 'progress-bar', role: 'progressbar', style: "width: #{percentage}%",
299
301
  aria: { valuenow: value, valuemin: min, valuemax: max } }) do
@@ -359,7 +359,7 @@ module Spree
359
359
  private
360
360
 
361
361
  def sort_dropdown_toggle(current_direction, current_label)
362
- dropdown_toggle(class: 'btn-light btn-sm h-[2.125rem]') do
362
+ dropdown_toggle(class: 'btn-light btn-sm') do
363
363
  safe_join([
364
364
  icon(current_direction == 'asc' ? 'sort-ascending' : 'sort-descending'),
365
365
  content_tag(:span, current_label),
@@ -35,6 +35,7 @@ export default class extends CheckboxSelectAll {
35
35
  currencies: Array,
36
36
  variantIds: Object,
37
37
  variantPrefixIds: Object,
38
+ variantImages: Object,
38
39
  currentStockLocationId: String,
39
40
  stockLocations: Array,
40
41
  optionValuesSelectOptions: Array,
@@ -533,6 +534,14 @@ export default class extends CheckboxSelectAll {
533
534
  }
534
535
  }
535
536
 
537
+ const variantImageUrl = this.variantImagesValue?.[internalName]
538
+ if (variantImageUrl) {
539
+ const thumbnailContainer = variantTarget.querySelector('[data-slot="variantThumbnail"]')
540
+ if (thumbnailContainer) {
541
+ thumbnailContainer.innerHTML = `<div class="overflow-hidden rounded-md border border-gray-300" style="width: 36px; height: 36px;"><img src="${variantImageUrl}" width="36" height="36" alt="" class="object-cover transition-transform duration-300 ease-out hover:scale-120" loading="lazy"></div>`
542
+ }
543
+ }
544
+
536
545
  let previousVariant = null
537
546
 
538
547
  if (i > 0) {
@@ -389,7 +389,7 @@ module Spree
389
389
 
390
390
  @template.content_tag(:div, class: 'input-group') do
391
391
  prepend_html = if prepend.present?
392
- @template.content_tag(:div, prepend, class: 'pl-3 text-gray-400')
392
+ @template.content_tag(:div, prepend, class: 'pl-2 text-gray-400')
393
393
  else
394
394
  ''.html_safe
395
395
  end
@@ -13,6 +13,7 @@ module Spree
13
13
  [
14
14
  *metadata_rows,
15
15
  :separator,
16
+ market_row,
16
17
  locale_row,
17
18
  currency_row,
18
19
  subtotal_row,
@@ -72,6 +73,16 @@ module Spree
72
73
  rows
73
74
  end
74
75
 
76
+ def market_row
77
+ return nil if order.market.blank?
78
+
79
+ {
80
+ label: Spree.t(:market),
81
+ value: order.market.name,
82
+ id: 'market'
83
+ }
84
+ end
85
+
75
86
  def locale_row
76
87
  return nil if order.locale.blank?
77
88
 
@@ -4,7 +4,7 @@
4
4
  <%= Spree.t('admin.dashboard.hi') %> <%= try_spree_current_user.first_name %>!
5
5
  </h2>
6
6
  <p class="mb-4">
7
- <%= Spree.t('admin.dashboard.whats_happening_on', store_name: current_store.name).html_safe %>
7
+ <%= Spree.t('admin.dashboard.whats_happening_on_html', store_name: current_store.name) %>
8
8
  </p>
9
9
  </div>
10
10
  </div>
@@ -2,7 +2,7 @@
2
2
  <td class="w-10"><%= export.number %></td>
3
3
  <td class="text-center w-10">
4
4
  <span class="badge badge-light border">
5
- <%= Spree.t(export.type.demodulize) %>
5
+ <%= Spree.t(export.type.demodulize.underscore) %>
6
6
  </span>
7
7
  </td>
8
8
  <td class="text-center w-10">
@@ -0,0 +1,4 @@
1
+ <%= link_to spree.edit_admin_product_path(item), class: 'flex items-center gap-3 no-underline' do %>
2
+ <%= render 'spree/admin/shared/product_image', object: item %>
3
+ <span class="text-gray-900 font-medium"><%= item.name %></span>
4
+ <% end %>
@@ -1,5 +1,5 @@
1
1
  <%= turbo_frame_tag :drawer do %>
2
- <%= drawer_header(Spree.t(:import) + ' ' + Spree.t(@import.type.demodulize.pluralize.downcase)) %>
2
+ <%= drawer_header(Spree.t(:import) + ' ' + Spree.t(@import.type.demodulize.underscore.pluralize)) %>
3
3
  <%= form_for @import, url: spree.admin_imports_path, data: { controller: 'import-form' } do |f| %>
4
4
  <div class="drawer-body">
5
5
  <%= f.hidden_field :type %>
@@ -8,7 +8,7 @@
8
8
  <div class="card-body">
9
9
  <%= f.spree_text_field :name, required: true %>
10
10
 
11
- <%= f.spree_select :country_ids, @countries.map { |country| ["#{Spree::Country.iso_to_emoji_flag(country.iso)} #{country.name}", country.id] }, { include_blank: false, label: Spree.t(:default_country), autocomplete: true, multiple: true } %>
11
+ <%= f.spree_select :country_ids, @countries.map { |country| ["#{Spree::Country.iso_to_emoji_flag(country.iso)} #{country.name}", country.id] }, { include_blank: false, label: Spree.t(:countries), autocomplete: true, multiple: true } %>
12
12
 
13
13
  <%= f.spree_select :currency, currency_options(f.object.currency), { label: Spree.t(:currency), required: true, autocomplete: true } %>
14
14
 
@@ -12,6 +12,13 @@
12
12
  <%= f.spree_text_field :name, label: Spree.t(:internal_name), data: { slug_form_target: :url }, help_text: raw("This is used internally to identify the option type. <strong>It must be unique.</strong>") %>
13
13
  </div>
14
14
  </div>
15
+ <div class="grid grid-cols-12 gap-6 mb-6">
16
+ <div class="col-span-12 md:col-span-6">
17
+ <%= f.spree_select :kind,
18
+ Spree::OptionType::KINDS.map { |k| [Spree.t("option_type_kinds.#{k}"), k] },
19
+ { label: Spree.t(:kind), help: Spree.t(:option_type_kind_info) } %>
20
+ </div>
21
+ </div>
15
22
  <div class="grid grid-cols-12 gap-6">
16
23
  <div class="col-span-12 md:col-span-6">
17
24
  <%= f.spree_check_box :filterable, help_text: raw(Spree.t('option_type_filterable_info')) %>
@@ -38,6 +45,7 @@
38
45
  <th scope="col"></th>
39
46
  <th scope="col"><%= raw(Spree.t(:presentation) + required_span_tag) %></th>
40
47
  <th scope="col"><%= raw(Spree.t(:internal_name) + required_span_tag) %></th>
48
+ <th scope="col"><%= Spree.t(:color_code) %></th>
41
49
  <th scope="col"></th>
42
50
  </tr>
43
51
  </thead>
@@ -51,7 +59,7 @@
51
59
  <tr data-nested-form-target="target"></tr>
52
60
 
53
61
  <tr class="hover-none">
54
- <td colspan="4" class="text-center p-2">
62
+ <td colspan="5" class="text-center p-2">
55
63
  <button type="button" data-action="nested-form#add" class="btn btn-secondary py-3 w-full" id="add_option_value_button">
56
64
  <%= icon('plus') %>
57
65
  <%= Spree.t(:add_one) %>
@@ -14,12 +14,26 @@
14
14
  <% end %>
15
15
  <%= f.hidden_field :id %>
16
16
  </td>
17
- <td class="w-45 presentation">
17
+ <td class="w-40 presentation">
18
18
  <%= f.text_field :presentation, class: "form-input", data: { slug_form_target: :name, action: 'slug-form#updateUrlFromName' } %>
19
19
  </td>
20
20
  <td class="w-40 name">
21
21
  <%= f.text_field :name, class: "form-input", data: { slug_form_target: :url } %>
22
22
  </td>
23
+ <td class="w-5 color_code">
24
+ <div data-controller="color-picker" data-color-picker-clear-value="true" data-color-picker-default-color-value="#000000">
25
+ <div class="flex items-center gap-2">
26
+ <button type="button"
27
+ data-color-picker-target="picker"
28
+ class="w-8 h-8 rounded border border-gray-300 cursor-pointer shrink-0"
29
+ style="background: <%= f.object.color_code.presence || 'white' %>;">
30
+ </button>
31
+ <span data-color-picker-target="value" class="text-sm text-gray-500"><%= f.object.color_code %></span>
32
+ </div>
33
+ <%= f.hidden_field :color_code, data: { color_picker_target: :input } %>
34
+ <span data-color-picker-target="display" class="hidden"></span>
35
+ </div>
36
+ </td>
23
37
  <td class="w-10 actions">
24
38
  <button type="button" class="btn btn-danger btn-sm remove_option_value_button" data-action="nested-form#remove">
25
39
  <%= icon('trash', class: 'mr-0') %>
@@ -1,5 +1,5 @@
1
1
  <% order ||= shipment.order %>
2
- <%= turbo_frame_tag dom_id(shipment), class: "card rounded-lg" do %>
2
+ <%= turbo_frame_tag dom_id(shipment), class: "card" do %>
3
3
  <div class="card-header items-center justify-between">
4
4
  <div class="flex items-center gap-2">
5
5
  <span class="flex items-center"><%= shipment_state(shipment.state) %></span>
@@ -1,4 +1,4 @@
1
- <div class="card rounded-lg" id="<%= dom_id(payment) %>">
1
+ <div class="card" id="<%= dom_id(payment) %>">
2
2
  <div class="card-header flex items-center gap-2">
3
3
  <span class="badge badge-<%= payment.state %> gap-1">
4
4
  <% if payment.completed? %>
@@ -19,7 +19,7 @@
19
19
  <ul class="list-group mb-6">
20
20
  <% @payment_methods.each do |payment_method| %>
21
21
  <li class="list-group-item p-0 overflow-hidden">
22
- <%= link_to spree.new_admin_order_payment_path(@order, payment_method_id: payment_method.id), class: "form-check-label flex items-center gap-4 px-6 py-6 w-full cursor-pointer hover:bg-gray-25 text-gray-900 no-underline" do %>
22
+ <%= link_to spree.new_admin_order_payment_path(@order, payment_method_id: payment_method.id), class: "form-check-label flex items-center gap-4 px-3 py-3 w-full cursor-pointer hover:bg-gray-25 text-gray-900 no-underline" do %>
23
23
  <div class="form-check flex items-center">
24
24
  <%= f.radio_button :payment_method_id, payment_method.id, class: "form-check-input" %>
25
25
  </div>
@@ -3,7 +3,7 @@
3
3
  <%= f.fields_for :source, @payment.source do |source_form| %>
4
4
  <% previous_cards.each do |card| %>
5
5
  <li class="list-group-item p-0">
6
- <%= source_form.label :id, value: card.id, class: "form-check-label flex items-center gap-4 px-6 py-6 w-full cursor-pointer hover:bg-gray-25" do %>
6
+ <%= source_form.label :id, value: card.id, class: "form-check-label flex items-center gap-4 px-3 py-3 w-full cursor-pointer hover:bg-gray-25" do %>
7
7
  <div class="form-check flex items-center">
8
8
  <%= source_form.radio_button :id, card.id, class: "form-check-input" %>
9
9
  </div>
@@ -3,7 +3,7 @@
3
3
  <%= f.fields_for :source, @payment.source do |source_form| %>
4
4
  <% @store_credits.each do |credit| %>
5
5
  <li class="list-group-item p-0">
6
- <%= source_form.label :id, value: credit.id, class: "form-check-label flex items-center gap-4 px-6 py-6 w-full cursor-pointer hover:bg-gray-25" do %>
6
+ <%= source_form.label :id, value: credit.id, class: "form-check-label flex items-center gap-4 px-3 py-3 w-full cursor-pointer hover:bg-gray-25" do %>
7
7
  <div class="form-check flex items-center">
8
8
  <%= source_form.radio_button :id, credit.id, class: "form-check-input", checked: @payment.source.id == credit.id || @store_credits.first.id == credit.id %>
9
9
  </div>
@@ -0,0 +1,108 @@
1
+ <% content_for :page_title do %>
2
+ <%= Spree.t(:translations) %>
3
+ <% end %>
4
+
5
+ <% content_for :page_actions do %>
6
+ <%= link_to_with_icon 'table-import', Spree.t(:import), spree.new_admin_import_path(type: 'Spree::Imports::ProductTranslations'), class: 'btn btn-light', data: { action: 'drawer#open', turbo_frame: :drawer } if can?(:create, Spree::Import) %>
7
+ <%= link_to_export_modal if can?(:create, Spree::Export) %>
8
+ <% end if @locales.any? %>
9
+
10
+ <% if @locales.empty? %>
11
+ <div class="card-lg">
12
+ <div class="text-center py-16">
13
+ <%= icon 'language', class: 'w-12 h-12 mx-auto mb-4 text-gray-400' %>
14
+ <h3 class="text-lg font-medium text-gray-900 mb-2"><%= Spree.t('admin.product_translations.no_locales_title') %></h3>
15
+ <p class="text-gray-500 mb-4"><%= Spree.t('admin.product_translations.no_locales_description') %></p>
16
+ <%= link_to Spree.t('admin.product_translations.manage_markets'), spree.admin_markets_path, class: 'btn btn-primary' %>
17
+ </div>
18
+ </div>
19
+ <% else %>
20
+ <%= render 'spree/admin/shared/export_modal', export_type: Spree::Exports::ProductTranslations %>
21
+
22
+ <div class="card-lg mb-6">
23
+ <div class="card-header">
24
+ <h3 class="card-title"><%= Spree.t('admin.product_translations.coverage') %></h3>
25
+ </div>
26
+ <div class="card-body p-0">
27
+ <div class="table-responsive">
28
+ <table class="table mb-0">
29
+ <thead>
30
+ <tr>
31
+ <th><%= Spree.t('admin.product_translations.locale') %></th>
32
+ <th class="text-center"><%= Spree.t('admin.product_translations.translated') %></th>
33
+ <th class="text-center"><%= Spree.t('admin.product_translations.total') %></th>
34
+ <th style="width: 40%;"><%= Spree.t('admin.product_translations.progress') %></th>
35
+ </tr>
36
+ </thead>
37
+ <tbody>
38
+ <% @coverage.each do |row| %>
39
+ <tr>
40
+ <td>
41
+ <strong><%= Spree.t('i18n.this_file_language', locale: row[:locale], default: row[:locale]) %></strong>
42
+ <span class="text-gray-400 ml-1"><%= row[:locale] %></span>
43
+ </td>
44
+ <td class="text-center"><%= row[:translated] %></td>
45
+ <td class="text-center"><%= row[:total] %></td>
46
+ <td>
47
+ <div class="flex items-center gap-3">
48
+ <%= progress_bar_component(row[:percentage].to_i, min: 0, max: 100, class: 'progress w-4/5') %>
49
+ <span class="text-sm text-gray-500 whitespace-nowrap"><%= row[:percentage] %>%</span>
50
+ </div>
51
+ </td>
52
+ </tr>
53
+ <% end %>
54
+ </tbody>
55
+ </table>
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ <div class="card-lg">
61
+ <div class="card-header">
62
+ <h3 class="card-title"><%= Spree.t(:products) %></h3>
63
+ </div>
64
+ <div class="card-body p-0">
65
+ <% if @products.any? %>
66
+ <div class="table-responsive">
67
+ <table class="table">
68
+ <thead>
69
+ <tr>
70
+ <th><%= Spree.t(:product) %></th>
71
+ <% @locales.each do |locale| %>
72
+ <th class="text-center"><%= locale %></th>
73
+ <% end %>
74
+ </tr>
75
+ </thead>
76
+ <tbody>
77
+ <% @products.each do |product| %>
78
+ <tr>
79
+ <td>
80
+ <%= link_to spree.edit_admin_translation_path(resource_type: 'Spree::Product', id: product.id),
81
+ data: { action: 'drawer#open', turbo_frame: :drawer }, class: 'flex items-center gap-3 no-underline' do %>
82
+ <%= render 'spree/admin/shared/product_image', object: product %>
83
+ <span class="text-gray-900 font-medium">
84
+ <%= product.name %>
85
+ </span>
86
+ <% end %>
87
+ </td>
88
+ <% @locales.each do |locale| %>
89
+ <td class="text-center">
90
+ <% if @translated_locales_map[product.id]&.include?(locale) %>
91
+ <%= icon 'circle-check-filled', class: 'text-green-500' %>
92
+ <% else %>
93
+ <span class="text-gray-300">&mdash;</span>
94
+ <% end %>
95
+ </td>
96
+ <% end %>
97
+ </tr>
98
+ <% end %>
99
+ </tbody>
100
+ </table>
101
+ <%= render 'spree/admin/shared/index_table_options', collection: @products %>
102
+ </div>
103
+ <% else %>
104
+ <%= render 'spree/admin/shared/no_resource_found', new_object_url: nil %>
105
+ <% end %>
106
+ </div>
107
+ </div>
108
+ <% end %>
@@ -22,6 +22,7 @@
22
22
  <% if @product_prices.present? %> data-variants-form-prices-value="<%= @product_prices.to_json %>" <% end %>
23
23
  <% if @product_variant_ids.present? %> data-variants-form-variant-ids-value="<%= @product_variant_ids.to_json %>" <% end %>
24
24
  <% if @product_variant_prefix_ids.present? %> data-variants-form-variant-prefix-ids-value="<%= @product_variant_prefix_ids.to_json %>" <% end %>
25
+ <% if @product_variant_images.present? %> data-variants-form-variant-images-value="<%= @product_variant_images.to_json %>" <% end %>
25
26
  >
26
27
  <div class="options-creator">
27
28
  <%= render 'spree/admin/products/form/variants/option_template', option_types_for_select: option_types_for_select %>
@@ -145,6 +146,8 @@
145
146
  </div>
146
147
  </div>
147
148
  <% end%>
149
+ <div class="variants-table__header__cell column-thumbnail">
150
+ </div>
148
151
  <div class="variants-table__header__cell column-variant">
149
152
  <%= Spree.t(:variant) %>
150
153
  <button data-variants-form-target="deleteButton" data-action="variants-form#deleteSelected" type="button" class="btn btn-danger btn-sm hidden ml-12">
@@ -12,6 +12,9 @@
12
12
  </div>
13
13
  </div>
14
14
  <% end %>
15
+ <div class="variants-table__body__cell column-thumbnail pr-2" data-slot="variantThumbnail">
16
+ <%= render 'spree/admin/shared/no_image', width: 36, height: 36 %>
17
+ </div>
15
18
  <div class="variants-table__body__cell column-variant" data-slot="variantName">
16
19
  Medium
17
20
  </div>
@@ -2,7 +2,7 @@
2
2
  <% badge_name ||= label %>
3
3
  <% placeholder ||= [Spree.t(:search), controller_name.humanize.downcase].join(' ') %>
4
4
 
5
- <div class="form-input flex items-center py-0 focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-blue-500 grow max-lg:max-w-[300px] mr-auto h-[2.125rem] max-lg:mb-4 transition-all duration-100 ease-in-out" id="filters-search-bar">
5
+ <div class="form-input flex items-center py-0 focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-blue-500 grow max-lg:max-w-[300px] mr-auto max-lg:mb-4 transition-all duration-100 ease-in-out" id="filters-search-bar">
6
6
  <%= icon "search", class: "mr-4 text-gray-600" %>
7
7
  <%= search_field_tag "q[#{param}]", params.dig(:q, param), class: "outline-none border-0 h-full focus:border-0 focus:ring-0 p-0 text-base w-full", placeholder: placeholder, data: { filters_target: :input, badge_name: badge_name } %>
8
8
  </div>
@@ -19,7 +19,7 @@
19
19
 
20
20
  <link rel="preconnect" href="https://fonts.googleapis.com">
21
21
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
22
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
22
+ <link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&display=swap" rel="stylesheet">
23
23
 
24
24
  <%= stylesheet_link_tag 'spree/admin/application', media: :all, data: { turbo_track: "reload" } %>
25
25
  <%= tinymce_assets %>
@@ -24,7 +24,7 @@
24
24
  media_form_target: 'addButton'
25
25
  } do %>
26
26
  <%= icon('upload', class: 'mt-12 text-4xl') %>
27
- <p class="text-sm mt-2 mb-12">
27
+ <p class="text-sm mb-12">
28
28
  <%= Spree.t('admin.upload_new_asset') %>
29
29
  </p>
30
30
  <% end %>
@@ -2,12 +2,12 @@
2
2
  <%= form_with model: @shipment, url: spree.admin_order_shipment_path(@order, @shipment), class: "w-full flex justify-between items-center" do |f| %>
3
3
  <div>
4
4
  <%= f.select :selected_shipping_rate_id, options_for_select(@shipment.shipping_rates.map {|sr| ["#{sr.name} #{sr.display_price}", sr.id] }, @shipment.selected_shipping_rate_id),
5
- {}, class: 'form-select', autofocus: true %>
5
+ {}, class: 'form-select form-select-sm', autofocus: true %>
6
6
  </div>
7
7
  <div class="flex h-full items-center gap-2">
8
8
  <% if can? :update, @shipment %>
9
- <%= turbo_save_button_tag Spree.t('actions.save'), data: { turbo_frame: :_top }, type: :submit, class: 'btn btn-primary' %>
10
- <%= link_to Spree.t('actions.discard'), spree.edit_admin_order_path(@order), class: "btn btn-light" %>
9
+ <%= turbo_save_button_tag Spree.t('actions.save'), data: { turbo_frame: :_top }, type: :submit, class: 'btn btn-sm btn-primary' %>
10
+ <%= link_to Spree.t('actions.discard'), spree.edit_admin_order_path(@order), class: "btn btn-sm btn-light" %>
11
11
  <% end %>
12
12
  </div>
13
13
  <% end %>
@@ -16,12 +16,12 @@
16
16
  <%= form_with model: @shipment, url: spree.admin_order_shipment_path(@order, @shipment), class: "w-full py-1 flex justify-between items-center" do |f| %>
17
17
  <div class="flex items-center text-nowrap gap-x-3 items-center grow mr-3">
18
18
  <%= f.label :tracking, Spree.t(:tracking_number), class: 'mb-0' %>
19
- <%= f.text_field :tracking, class: 'form-input', required: true, autofocus: true %>
19
+ <%= f.text_field :tracking, class: 'form-input form-input-sm', required: true, autofocus: true %>
20
20
  </div>
21
21
  <div class="flex h-full items-center gap-2">
22
22
  <% if can? :update, @shipment %>
23
- <%= turbo_save_button_tag Spree.t('actions.save'), data: { turbo_frame: dom_id(@shipment) }, type: :submit, class: 'btn btn-primary' %>
24
- <%= link_to Spree.t('actions.discard'), spree.edit_admin_order_path(@order), class: "btn btn-light" %>
23
+ <%= turbo_save_button_tag Spree.t('actions.save'), data: { turbo_frame: dom_id(@shipment) }, type: :submit, class: 'btn btn-sm btn-primary' %>
24
+ <%= link_to Spree.t('actions.discard'), spree.edit_admin_order_path(@order), class: "btn btn-sm btn-light" %>
25
25
  <% end %>
26
26
  </div>
27
27
  <% end %>
@@ -0,0 +1,15 @@
1
+ <% content_for :page_title do %>
2
+ <%= Spree.t(:stock) %>
3
+ <% end %>
4
+
5
+ <% content_for :page_tabs do %>
6
+ <%= render "spree/admin/shared/stock_nav" %>
7
+ <% end %>
8
+
9
+ <% content_for :page_actions do %>
10
+ <%= render_admin_partials(:stock_movements_actions_partials) %>
11
+ <% end %>
12
+
13
+ <%= render_admin_partials(:stock_movements_header_partials) %>
14
+
15
+ <%= render_table @collection, :stock_movements, edit_object_url: nil %>
@@ -7,7 +7,7 @@
7
7
  <%= hidden_field_tag :redirect_url, request.fullpath %>
8
8
 
9
9
  <%= dropdown(portal: false) do %>
10
- <%= dropdown_toggle(class: 'btn-light btn-sm h-[2.125rem]') do %>
10
+ <%= dropdown_toggle(class: 'btn-light btn-sm') do %>
11
11
  <%= icon 'columns-3' %>
12
12
  <span><%= Spree.t('admin.tables.columns') %></span>
13
13
  <%= icon 'chevron-down' %>
@@ -6,7 +6,7 @@
6
6
 
7
7
  <div data-controller="dialog" class="h-full">
8
8
  <button type="button"
9
- class="btn btn-light btn-sm h-[2.125rem]"
9
+ class="btn btn-light btn-sm"
10
10
  data-action="dialog#open">
11
11
  <%= icon 'filter' %>
12
12
  <span><%= Spree.t('admin.tables.filters') %></span>
@@ -11,11 +11,11 @@
11
11
  <%= render 'spree/admin/shared/export_modal', export_type: export_type %>
12
12
  <% end %>
13
13
  <div class="<%= local_assigns[:container_class] || 'card-lg' %>" data-controller="table" data-table-url-value="<%= request.path %>">
14
- <div class="p-3 border-b border-gray-200">
14
+ <div class="p-2 border-b border-gray-200">
15
15
  <div class="flex flex-col lg:flex-row gap-3 items-start lg:items-center justify-between">
16
16
  <div class="flex gap-2 items-center flex-wrap">
17
17
  <%= search_form_for @search, url: url_for, html: { data: { turbo_frame: frame_name, turbo_action: 'advance', controller: 'auto-submit search-clear', auto_submit_delay_value: 300 } } do |f| %>
18
- <div class="form-input flex items-center py-0 pr-1 focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-blue-500 lg:w-[300px] mr-auto h-[2.125rem] transition-all duration-100 ease-in-out">
18
+ <div class="form-input flex items-center py-0 pr-1 focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-blue-500 lg:w-[300px] mr-auto h-[2rem] transition-all duration-100 ease-in-out">
19
19
  <%= icon "search", class: "mr-4 text-gray-600 shrink-0" %>
20
20
  <%= search_field_tag "q[#{search_param}]",
21
21
  params.dig(:q, search_param),
@@ -55,7 +55,7 @@
55
55
  date_to_input_name: "q[#{date_range_param}_lt]",
56
56
  date_from_value: params.dig(:q, :"#{date_range_param}_gt"),
57
57
  date_to_value: params.dig(:q, :"#{date_range_param}_lt"),
58
- css_classes: "btn-light btn-sm dropdown-toggle h-[2.125rem]" %>
58
+ css_classes: "btn-light btn-sm" %>
59
59
  <%# Preserve search and filter state when submitting date range %>
60
60
  <% if params.dig(:q, search_param).present? %>
61
61
  <%= hidden_field_tag "q[#{search_param}]", params.dig(:q, search_param) %>
@@ -0,0 +1,28 @@
1
+ <%# locals: (record:, column:, value:) %>
2
+ <% originator = record.originator %>
3
+ <% if originator.is_a?(Spree::StockTransfer) %>
4
+ <%= originator.class.model_name.human %>
5
+ <% if can?(:show, originator) %>
6
+ <%= link_to originator.number, spree.admin_stock_transfer_path(originator), data: { turbo_frame: '_top' } %>
7
+ <% else %>
8
+ <%= originator.number %>
9
+ <% end %>
10
+ <% elsif originator.is_a?(Spree::Shipment) %>
11
+ <%= originator.class.model_name.human %>
12
+ <% if can?(:show, originator.order) %>
13
+ <%= link_to originator.number, spree.edit_admin_order_path(originator.order), data: { turbo_frame: '_top' } %>
14
+ <% else %>
15
+ <%= originator.number %>
16
+ <% end %>
17
+ <% elsif originator.is_a?(Spree::ReturnAuthorization) %>
18
+ <%= originator.class.model_name.human %>
19
+ <% if can?(:show, originator.order) %>
20
+ <%= link_to originator.number, spree.edit_admin_order_path(originator.order), data: { turbo_frame: '_top' } %>
21
+ <% else %>
22
+ <%= originator.number %>
23
+ <% end %>
24
+ <% elsif originator.present? %>
25
+ <%= originator.class.model_name.human %>
26
+ <% else %>
27
+ <span class="text-gray-400"><%= Spree.t(:manual) %></span>
28
+ <% end %>
@@ -32,7 +32,7 @@
32
32
  <p class="text-center mb-3"><%= Spree.t('admin.translations.no_translations_configured') %></p>
33
33
 
34
34
  <% if can?(:edit, current_store) %>
35
- <%= link_to_with_icon 'world', Spree.t('admin.translations.manage_markets'), spree.edit_admin_store_path(section: 'general-settings'), class: 'btn btn-primary', data: { 'turbo-frame': '_top' } %>
35
+ <%= link_to_with_icon 'world', Spree.t('admin.manage_markets'), spree.admin_markets_path, class: 'btn btn-primary', data: { 'turbo-frame': '_top' } %>
36
36
  <% end %>
37
37
  </div>
38
38
  </div>
@@ -5,7 +5,7 @@
5
5
  </h5>
6
6
 
7
7
  <% if can?(:manage, Spree::Store) %>
8
- <%= link_to_with_icon 'adjustments', Spree.t('admin.manage_currencies'), spree.edit_admin_store_path(section: 'general-settings'), class: 'btn btn-sm btn-light' %>
8
+ <%= link_to_with_icon 'world', Spree.t('admin.manage_markets'), spree.admin_markets_path, class: 'btn btn-sm btn-light' %>
9
9
  <% end %>
10
10
  </div>
11
11
  <div class="card-body p-0">
@@ -107,8 +107,16 @@ Rails.application.config.after_initialize do
107
107
  label: :stock,
108
108
  url: :admin_stock_items_path,
109
109
  position: 20,
110
- active: -> { %w[stock_items stock_transfers].include?(controller_name) },
111
- if: -> { can?(:manage, Spree::StockItem) || can?(:manage, Spree::StockTransfer) }
110
+ active: -> { %w[stock_items stock_movements stock_transfers].include?(controller_name) },
111
+ if: -> { can?(:manage, Spree::StockItem) || can?(:manage, Spree::StockMovement) || can?(:manage, Spree::StockTransfer) }
112
+
113
+ # Translations
114
+ products.add :translations,
115
+ label: :translations,
116
+ url: :admin_product_translations_path,
117
+ position: 25,
118
+ active: -> { controller_name == 'product_translations' },
119
+ if: -> { can?(:manage, Spree::Product) && current_store.supported_locales_list.size > 1 }
112
120
 
113
121
  # Taxonomies
114
122
  products.add :taxonomies,
@@ -411,10 +419,17 @@ Rails.application.config.after_initialize do
411
419
  active: -> { controller_name == 'stock_items' },
412
420
  if: -> { can?(:manage, Spree::StockItem) }
413
421
 
422
+ stock_tabs_nav.add :stock_movements,
423
+ label: :stock_movements,
424
+ url: :admin_stock_movements_path,
425
+ position: 20,
426
+ active: -> { controller_name == 'stock_movements' },
427
+ if: -> { can?(:manage, Spree::StockMovement) }
428
+
414
429
  stock_tabs_nav.add :stock_transfers,
415
430
  label: :stock_transfers,
416
431
  url: :admin_stock_transfers_path,
417
- position: 20,
432
+ position: 30,
418
433
  active: -> { controller_name == 'stock_transfers' },
419
434
  if: -> { can?(:manage, Spree::StockTransfer) }
420
435
 
@@ -933,6 +933,69 @@ Rails.application.config.after_initialize do
933
933
  default: false,
934
934
  position: 70
935
935
 
936
+ # Register Stock Movements table
937
+ Spree.admin.tables.register(:stock_movements, model_class: Spree::StockMovement, search_param: :stock_item_variant_product_name_cont, row_actions: false, new_resource: false)
938
+
939
+ Spree.admin.tables.stock_movements.add :variant,
940
+ label: :variant,
941
+ type: :custom,
942
+ sortable: false,
943
+ filterable: true,
944
+ default: true,
945
+ position: 10,
946
+ ransack_attribute: 'stock_item_variant_product_name',
947
+ partial: 'spree/admin/variants/variant',
948
+ partial_locals: ->(record) { { variant: record.variant } }
949
+
950
+ Spree.admin.tables.stock_movements.add :stock_location,
951
+ label: :stock_location,
952
+ type: :custom,
953
+ filter_type: :autocomplete,
954
+ sortable: false,
955
+ filterable: true,
956
+ default: true,
957
+ position: 20,
958
+ ransack_attribute: 'stock_item_stock_location_id',
959
+ operators: %i[eq],
960
+ search_url: ->(view_context) { view_context.spree.admin_stock_locations_select_options_path(format: :json) },
961
+ partial: 'spree/admin/tables/columns/stock_item_location',
962
+ partial_locals: ->(record) { { record: record.stock_item } }
963
+
964
+ Spree.admin.tables.stock_movements.add :quantity,
965
+ label: :quantity,
966
+ type: :number,
967
+ sortable: true,
968
+ filterable: false,
969
+ default: true,
970
+ position: 30
971
+
972
+ Spree.admin.tables.stock_movements.add :originator,
973
+ label: :originator,
974
+ type: :custom,
975
+ filter_type: :select,
976
+ sortable: false,
977
+ filterable: true,
978
+ default: true,
979
+ position: 40,
980
+ ransack_attribute: 'originator_type',
981
+ operators: %i[eq],
982
+ value_options: -> {
983
+ [
984
+ { value: 'Spree::Shipment', label: Spree::Shipment.model_name.human },
985
+ { value: 'Spree::StockTransfer', label: Spree::StockTransfer.model_name.human },
986
+ { value: 'Spree::ReturnAuthorization', label: Spree::ReturnAuthorization.model_name.human }
987
+ ]
988
+ },
989
+ partial: 'spree/admin/tables/columns/stock_movement_originator'
990
+
991
+ Spree.admin.tables.stock_movements.add :created_at,
992
+ label: :created_at,
993
+ type: :datetime,
994
+ sortable: true,
995
+ filterable: true,
996
+ default: true,
997
+ position: 50
998
+
936
999
  # Register Metafield Definitions table
937
1000
  Spree.admin.tables.register(:metafield_definitions, model_class: Spree::MetafieldDefinition, search_param: :search, row_actions: true)
938
1001
 
@@ -99,7 +99,7 @@ en:
99
99
  hi: Hi
100
100
  top_products: Top products
101
101
  view_report: View report
102
- whats_happening_on: Here's what's happening on <strong>%{store_name}</strong> today.
102
+ whats_happening_on_html: Here's what's happening on <strong>%{store_name}</strong> today.
103
103
  digital_shipment_fulfillment_note: This shipment will be marked as fulfilled once the user downloads the digital product
104
104
  display_on_options:
105
105
  back_end: Only on admin panel
@@ -143,7 +143,7 @@ en:
143
143
  page_subtitle: Connect your store to third-party services to enhance customer experience.
144
144
  json_preview:
145
145
  no_api_response: No API response found for this resource
146
- manage_currencies: Manage Currencies
146
+ manage_markets: Manage Markets
147
147
  manage_properties: Manage Properties
148
148
  manage_stock_locations: Manage Stock Locations
149
149
  manage_taxons: Manage Categories
@@ -244,6 +244,15 @@ en:
244
244
  schedule: Schedule
245
245
  scheduled: Price list has been scheduled
246
246
  unschedule: Unschedule
247
+ product_translations:
248
+ coverage: Translation coverage
249
+ locale: Locale
250
+ manage_markets: Manage Markets
251
+ no_locales_description: Set up markets with additional locales to start translating your products.
252
+ no_locales_title: No additional locales configured
253
+ progress: Progress
254
+ total: Total
255
+ translated: Translated
247
256
  products:
248
257
  active: Active
249
258
  all_statuses: All statuses
@@ -366,9 +375,8 @@ en:
366
375
  manual: Manual
367
376
  manual_info: Curate products manually. You can add or remove them in bulk.
368
377
  translations:
369
- manage_markets: Manage Markets
370
378
  no_translations_configured: No translations configured. Please setup Markets to use translations.
371
- upload_new_asset: Upload new asset
379
+ upload_new_asset: Upload new media
372
380
  user:
373
381
  last_order_placed: Last order placed
374
382
  no_store_credit: Customer has no Store Credit available
data/config/routes.rb CHANGED
@@ -27,8 +27,11 @@ Spree::Core::Engine.add_routes do
27
27
  # variant search
28
28
  post 'variants/search'
29
29
  get 'variants/search', defaults: { format: :json }
30
+ # product translations
31
+ resources :product_translations, only: [:index]
30
32
  # stock
31
33
  resources :stock_items, only: [:index, :update, :destroy]
34
+ resources :stock_movements, only: [:index]
32
35
  resources :stock_transfers, except: [:edit, :update]
33
36
  # price lists
34
37
  resources :price_lists do
@@ -96,6 +96,8 @@ module Spree
96
96
  :stock_items_header_partials,
97
97
  :stock_locations_actions_partials,
98
98
  :stock_locations_header_partials,
99
+ :stock_movements_actions_partials,
100
+ :stock_movements_header_partials,
99
101
  :stock_nav_partials,
100
102
  :stock_transfers_actions_partials,
101
103
  :stock_transfers_filters_partials,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.4.0.rc5
4
+ version: 5.4.0.rc7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vendo Connect Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-30 00:00:00.000000000 Z
11
+ date: 2026-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 5.4.0.rc5
19
+ version: 5.4.0.rc7
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 5.4.0.rc5
26
+ version: 5.4.0.rc7
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: active_link_to
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -357,6 +357,7 @@ files:
357
357
  - app/controllers/spree/admin/price_list_products_controller.rb
358
358
  - app/controllers/spree/admin/price_lists_controller.rb
359
359
  - app/controllers/spree/admin/price_rules_controller.rb
360
+ - app/controllers/spree/admin/product_translations_controller.rb
360
361
  - app/controllers/spree/admin/products_controller.rb
361
362
  - app/controllers/spree/admin/profile_controller.rb
362
363
  - app/controllers/spree/admin/promotion_actions_controller.rb
@@ -379,6 +380,7 @@ files:
379
380
  - app/controllers/spree/admin/shipping_methods_controller.rb
380
381
  - app/controllers/spree/admin/stock_items_controller.rb
381
382
  - app/controllers/spree/admin/stock_locations_controller.rb
383
+ - app/controllers/spree/admin/stock_movements_controller.rb
382
384
  - app/controllers/spree/admin/stock_transfers_controller.rb
383
385
  - app/controllers/spree/admin/store_credit_categories_controller.rb
384
386
  - app/controllers/spree/admin/store_credits_controller.rb
@@ -615,6 +617,7 @@ files:
615
617
  - app/views/spree/admin/gift_cards/new.html.erb
616
618
  - app/views/spree/admin/gift_cards/show.html.erb
617
619
  - app/views/spree/admin/import_mappings/update.turbo_stream.erb
620
+ - app/views/spree/admin/import_rows/_product.html.erb
618
621
  - app/views/spree/admin/import_rows/_variant.html.erb
619
622
  - app/views/spree/admin/import_rows/show.html.erb
620
623
  - app/views/spree/admin/imports/_footer.html.erb
@@ -774,6 +777,7 @@ files:
774
777
  - app/views/spree/admin/price_rules/forms/_volume_rule.html.erb
775
778
  - app/views/spree/admin/price_rules/forms/_zone_rule.html.erb
776
779
  - app/views/spree/admin/price_rules/new.html.erb
780
+ - app/views/spree/admin/product_translations/index.html.erb
777
781
  - app/views/spree/admin/products/_edit_page_title.html.erb
778
782
  - app/views/spree/admin/products/_form.html.erb
779
783
  - app/views/spree/admin/products/_import_button.html.erb
@@ -942,6 +946,7 @@ files:
942
946
  - app/views/spree/admin/stock_locations/edit.html.erb
943
947
  - app/views/spree/admin/stock_locations/index.html.erb
944
948
  - app/views/spree/admin/stock_locations/new.html.erb
949
+ - app/views/spree/admin/stock_movements/index.html.erb
945
950
  - app/views/spree/admin/stock_transfers/_destination_movement_form.html.erb
946
951
  - app/views/spree/admin/stock_transfers/_new_variant_modal.html.erb
947
952
  - app/views/spree/admin/stock_transfers/_new_variant_template.html.erb
@@ -993,6 +998,7 @@ files:
993
998
  - app/views/spree/admin/tables/columns/_stock_item_backorderable.html.erb
994
999
  - app/views/spree/admin/tables/columns/_stock_item_count_on_hand.html.erb
995
1000
  - app/views/spree/admin/tables/columns/_stock_item_location.html.erb
1001
+ - app/views/spree/admin/tables/columns/_stock_movement_originator.html.erb
996
1002
  - app/views/spree/admin/tables/columns/_user_location.html.erb
997
1003
  - app/views/spree/admin/tables/columns/_webhook_deliveries_stats.html.erb
998
1004
  - app/views/spree/admin/tables/columns/_webhook_delivery_actions.html.erb
@@ -1095,7 +1101,6 @@ files:
1095
1101
  - config/locales/en.yml
1096
1102
  - config/routes.rb
1097
1103
  - config/tailwind.config.js
1098
- - db/migrate/20250217171018_create_action_text_video_embeds.rb
1099
1104
  - lib/generators/spree/admin/devise/devise_generator.rb
1100
1105
  - lib/generators/spree/admin/install/install_generator.rb
1101
1106
  - lib/generators/spree/admin/install/templates/app/assets/tailwind/spree_admin.css.tt
@@ -1170,9 +1175,9 @@ licenses:
1170
1175
  - BSD-3-Clause
1171
1176
  metadata:
1172
1177
  bug_tracker_uri: https://github.com/spree/spree/issues
1173
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.rc5
1178
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.rc7
1174
1179
  documentation_uri: https://docs.spreecommerce.org/
1175
- source_code_uri: https://github.com/spree/spree/tree/v5.4.0.rc5
1180
+ source_code_uri: https://github.com/spree/spree/tree/v5.4.0.rc7
1176
1181
  post_install_message:
1177
1182
  rdoc_options: []
1178
1183
  require_paths:
@@ -1,11 +0,0 @@
1
- class CreateActionTextVideoEmbeds < ActiveRecord::Migration[6.1]
2
- def change
3
- create_table :action_text_video_embeds, if_not_exists: true do |t|
4
- t.string :url, null: false
5
- t.string :thumbnail_url, null: false
6
- t.text :raw_html, null: false
7
-
8
- t.timestamps
9
- end
10
- end
11
- end