spree_admin 5.2.0.rc3 → 5.2.0
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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/spree/admin/application.scss +1 -1
- data/app/assets/stylesheets/spree/admin/components/_dropdowns.scss +2 -0
- data/app/assets/stylesheets/spree/admin/components/_main.scss +2 -233
- data/app/assets/stylesheets/spree/admin/components/_sidebar.scss +693 -0
- data/app/assets/stylesheets/spree/admin/global/_variables.scss +1 -0
- data/app/assets/stylesheets/spree/admin/shared/_base.scss +7 -1
- data/app/assets/stylesheets/spree/admin/views/_dashboard.scss +14 -0
- data/app/controllers/spree/admin/integrations_controller.rb +1 -1
- data/app/controllers/spree/admin/metafields_controller.rb +1 -1
- data/app/controllers/spree/admin/page_blocks_controller.rb +1 -1
- data/app/controllers/spree/admin/payment_methods_controller.rb +1 -1
- data/app/controllers/spree/admin/promotion_actions_controller.rb +1 -1
- data/app/controllers/spree/admin/promotion_rules_controller.rb +1 -1
- data/app/controllers/spree/admin/promotions_controller.rb +1 -1
- data/app/controllers/spree/admin/reports_controller.rb +1 -1
- data/app/controllers/spree/admin/taxons_controller.rb +1 -1
- data/app/controllers/spree/admin/translations_controller.rb +1 -1
- data/app/helpers/spree/admin/base_helper.rb +1 -1
- data/app/helpers/spree/admin/drawer_helper.rb +6 -6
- data/app/helpers/spree/admin/dropdown_helper.rb +10 -2
- data/app/helpers/spree/admin/modal_helper.rb +2 -0
- data/app/helpers/spree/admin/navigation_helper.rb +46 -3
- data/app/helpers/spree/admin/orders_filters_helper.rb +1 -0
- data/app/helpers/spree/admin/promotion_actions_helper.rb +1 -1
- data/app/helpers/spree/admin/promotion_rules_helper.rb +1 -1
- data/app/helpers/spree/admin/translations_helper.rb +1 -1
- data/app/javascript/spree/admin/application.js +2 -1
- data/app/javascript/spree/admin/controllers/dropdown_controller.js +85 -14
- data/app/javascript/spree/admin/controllers/sidebar_controller.js +231 -0
- data/app/javascript/spree/admin/controllers/tooltip_controller.js +84 -31
- data/app/models/spree/admin/form_builder.rb +74 -16
- data/app/models/spree/admin/navigation/builder.rb +82 -0
- data/app/models/spree/admin/navigation/item.rb +177 -0
- data/app/models/spree/admin/navigation.rb +193 -0
- data/app/views/layouts/spree/admin.html.erb +1 -1
- data/app/views/spree/admin/assets/edit.html.erb +1 -1
- data/app/views/spree/admin/custom_domains/_form.html.erb +2 -14
- data/app/views/spree/admin/gift_cards/_filters.html.erb +26 -18
- data/app/views/spree/admin/gift_cards/index.html.erb +1 -1
- data/app/views/spree/admin/orders/_customer.html.erb +2 -2
- data/app/views/spree/admin/orders/_filters.html.erb +34 -25
- data/app/views/spree/admin/orders/_table_filter_dropdown.html.erb +1 -1
- data/app/views/spree/admin/page_blocks/forms/_image.html.erb +2 -5
- data/app/views/spree/admin/page_links/_form.html.erb +4 -13
- data/app/views/spree/admin/page_sections/forms/_header.html.erb +0 -2
- data/app/views/spree/admin/payments/_payment.html.erb +7 -0
- data/app/views/spree/admin/posts/_form.html.erb +1 -4
- data/app/views/spree/admin/posts/filters.html.erb +18 -8
- data/app/views/spree/admin/products/_bulk_operations.html.erb +1 -1
- data/app/views/spree/admin/products/_filters.html.erb +17 -6
- data/app/views/spree/admin/products/_table_filter_dropdown.html.erb +1 -1
- data/app/views/spree/admin/profile/edit.html.erb +9 -61
- data/app/views/spree/admin/promotions/_filters.html.erb +23 -13
- data/app/views/spree/admin/promotions/_table_filter_dropdown.html.erb +1 -1
- data/app/views/spree/admin/promotions/form/_settings.html.erb +2 -13
- data/app/views/spree/admin/refunds/_form.html.erb +1 -9
- data/app/views/spree/admin/return_authorizations/filters.html.erb +1 -1
- data/app/views/spree/admin/shared/_audit_nav.html.erb +2 -0
- data/app/views/spree/admin/shared/_calendar_range_picker.html.erb +2 -2
- data/app/views/spree/admin/shared/_content_header.html.erb +1 -1
- data/app/views/spree/admin/shared/_developers_nav.html.erb +2 -4
- data/app/views/spree/admin/shared/_header.html.erb +5 -7
- data/app/views/spree/admin/shared/_navigation.html.erb +5 -0
- data/app/views/spree/admin/shared/_navigation_item.html.erb +64 -0
- data/app/views/spree/admin/shared/_new_item_dropdown.html.erb +1 -1
- data/app/views/spree/admin/shared/_page_section_image.html.erb +2 -5
- data/app/views/spree/admin/shared/_page_section_logo.html.erb +1 -1
- data/app/views/spree/admin/shared/_returns_and_refunds_nav.html.erb +2 -3
- data/app/views/spree/admin/shared/_shipping_nav.html.erb +3 -2
- data/app/views/spree/admin/shared/_sidebar.html.erb +33 -7
- data/app/views/spree/admin/shared/_stock_nav.html.erb +6 -3
- data/app/views/spree/admin/shared/_tax_nav.html.erb +1 -2
- data/app/views/spree/admin/shared/_team_nav.html.erb +2 -3
- data/app/views/spree/admin/shared/_user_dropdown.html.erb +26 -17
- data/app/views/spree/admin/shared/sidebar/_customers_nav.html.erb +7 -0
- data/app/views/spree/admin/shared/sidebar/_orders_nav.html.erb +22 -2
- data/app/views/spree/admin/shared/sidebar/_products_nav.html.erb +21 -0
- data/app/views/spree/admin/shared/sidebar/_promotions_nav.html.erb +8 -0
- data/app/views/spree/admin/shared/sidebar/_returns_nav.html.erb +12 -0
- data/app/views/spree/admin/shared/sidebar/_store_dropdown.html.erb +3 -1
- data/app/views/spree/admin/shared/sidebar/_store_nav.html.erb +15 -1
- data/app/views/spree/admin/shared/sidebar/_storefront_nav.html.erb +25 -3
- data/app/views/spree/admin/shared/sortable_tree/_taxonomy.html.erb +1 -1
- data/app/views/spree/admin/stock_items/_filters.html.erb +18 -8
- data/app/views/spree/admin/stock_locations/_table_row.html.erb +1 -1
- data/app/views/spree/admin/stock_transfers/_filters.html.erb +19 -9
- data/app/views/spree/admin/storefront/edit.html.erb +2 -14
- data/app/views/spree/admin/stores/form/_basic.html.erb +2 -8
- data/app/views/spree/admin/stores/form/_emails.html.erb +1 -1
- data/app/views/spree/admin/tax_rates/_form.html.erb +1 -10
- data/app/views/spree/admin/taxons/_form.html.erb +2 -9
- data/app/views/spree/admin/themes/_theme.html.erb +1 -1
- data/app/views/spree/admin/translations/translation_rows/_permalink_field_row.html.erb +1 -12
- data/app/views/spree/admin/users/_filters.html.erb +23 -13
- data/config/initializers/spree_admin_navigation.rb +510 -0
- data/config/locales/en.yml +4 -0
- data/lib/generators/spree/admin/scaffold/templates/controller.rb.tt +3 -1
- data/lib/generators/spree/admin/scaffold/templates/views/_table_row.html.erb.tt +8 -6
- data/lib/spree/admin/engine.rb +64 -2
- data/lib/spree/admin/runtime_configuration.rb +1 -0
- data/lib/spree/admin.rb +20 -0
- metadata +17 -15
- data/app/assets/stylesheets/spree/admin/components/_offcanvas.scss +0 -26
- data/app/javascript/spree/admin/helpers/canvas.js +0 -29
- data/app/views/spree/admin/shared/_offcanvas_nav.html.erb +0 -12
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class Navigation
|
|
4
|
+
class Builder
|
|
5
|
+
attr_reader :registry, :parent_item
|
|
6
|
+
|
|
7
|
+
def initialize(registry, parent_item = nil)
|
|
8
|
+
@registry = registry
|
|
9
|
+
@parent_item = parent_item
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Add a navigation item
|
|
13
|
+
# If parent_item is set, the item becomes a child
|
|
14
|
+
def add(key, **options, &block)
|
|
15
|
+
# If we have a parent item, set it in the options
|
|
16
|
+
if parent_item
|
|
17
|
+
options[:parent] = parent_item.key
|
|
18
|
+
# Adjust position to be relative to parent
|
|
19
|
+
options[:position] ||= parent_item.children.size * 10
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
item = registry.add(key, **options, &block)
|
|
23
|
+
item
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Add a section (group of items)
|
|
27
|
+
def section(key, label: nil, &block)
|
|
28
|
+
section_item = add(key, section_label: label || key.to_s.humanize)
|
|
29
|
+
|
|
30
|
+
if block_given?
|
|
31
|
+
builder = self.class.new(registry, section_item)
|
|
32
|
+
# Support both block styles: |nav| nav.add or just add
|
|
33
|
+
if block.arity > 0
|
|
34
|
+
# Block expects parameter: do |nav| nav.add ... end
|
|
35
|
+
block.call(builder)
|
|
36
|
+
else
|
|
37
|
+
# Block uses implicit self: do add ... end
|
|
38
|
+
builder.instance_eval(&block)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
section_item
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Remove an item
|
|
46
|
+
def remove(key)
|
|
47
|
+
registry.remove(key)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Update an item
|
|
51
|
+
def update(key, **options)
|
|
52
|
+
registry.update(key, **options)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Insert before another item
|
|
56
|
+
def insert_before(target_key, new_key, **options)
|
|
57
|
+
registry.insert_before(target_key, new_key, **options)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Insert after another item
|
|
61
|
+
def insert_after(target_key, new_key, **options)
|
|
62
|
+
registry.insert_after(target_key, new_key, **options)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Move an item
|
|
66
|
+
def move(key, **position_options)
|
|
67
|
+
registry.move(key, **position_options)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Replace an item
|
|
71
|
+
def replace(key, **options, &block)
|
|
72
|
+
registry.replace(key, **options, &block)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Reorder items with a block
|
|
76
|
+
def reorder(&block)
|
|
77
|
+
instance_eval(&block) if block_given?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class Navigation
|
|
4
|
+
class Item
|
|
5
|
+
attr_accessor :key, :label, :url, :icon, :position, :parent_key,
|
|
6
|
+
:condition, :badge, :badge_class, :tooltip, :target, :data_attributes, :children, :section_label, :active_condition
|
|
7
|
+
|
|
8
|
+
def initialize(key, **options)
|
|
9
|
+
@key = key.to_sym
|
|
10
|
+
@label = options[:label]
|
|
11
|
+
@url = options[:url]
|
|
12
|
+
@icon = options[:icon]
|
|
13
|
+
@position = options[:position] || 999
|
|
14
|
+
@parent_key = options[:parent]
|
|
15
|
+
@active_condition = options[:active]
|
|
16
|
+
@condition = options.key?(:if) ? options[:if] : options[:condition]
|
|
17
|
+
@badge = options[:badge]
|
|
18
|
+
@badge_class = options[:badge_class]
|
|
19
|
+
@tooltip = options[:tooltip]
|
|
20
|
+
@target = options[:target]
|
|
21
|
+
@data_attributes = options[:data_attributes] || {}
|
|
22
|
+
@section_label = options[:section_label]
|
|
23
|
+
@children = []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Check if this item should be visible for the given user/context
|
|
27
|
+
# @param user_or_context [Object] Either a user object or a view context
|
|
28
|
+
def visible?(user_or_context = nil)
|
|
29
|
+
return true if condition.nil?
|
|
30
|
+
|
|
31
|
+
if condition.respond_to?(:call)
|
|
32
|
+
# If we have a view context with instance_exec, use it to evaluate the condition
|
|
33
|
+
# This allows access to can? and other helper methods
|
|
34
|
+
if user_or_context.respond_to?(:instance_exec)
|
|
35
|
+
user_or_context.instance_exec(&condition)
|
|
36
|
+
else
|
|
37
|
+
# Otherwise, call with the user object
|
|
38
|
+
condition.call(user_or_context)
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
condition
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Check if this item is active based on current path
|
|
46
|
+
# @param current_path [String] The current request path
|
|
47
|
+
# @param context [Object] View context with access to route helpers
|
|
48
|
+
def active?(current_path, context = nil)
|
|
49
|
+
# Use custom active condition if provided (most flexible)
|
|
50
|
+
if active_condition.respond_to?(:call)
|
|
51
|
+
if context&.respond_to?(:instance_exec)
|
|
52
|
+
return context.instance_exec(&active_condition)
|
|
53
|
+
else
|
|
54
|
+
return active_condition.call
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Match exact path
|
|
59
|
+
item_url = resolve_url(context)
|
|
60
|
+
return true if item_url && current_path == item_url
|
|
61
|
+
|
|
62
|
+
# Check if any child item is active
|
|
63
|
+
return true if children.any? { |child| child.active?(current_path, context) }
|
|
64
|
+
|
|
65
|
+
# Default: match if path starts with url (handled by active_link_to)
|
|
66
|
+
if item_url
|
|
67
|
+
current_path.start_with?(item_url)
|
|
68
|
+
else
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Resolve URL (handles symbols, procs, and strings)
|
|
74
|
+
# @param context [Object] View context with access to route helpers
|
|
75
|
+
def resolve_url(context = nil)
|
|
76
|
+
case url
|
|
77
|
+
when Symbol
|
|
78
|
+
# Try to call the route helper on the context (which has spree routes)
|
|
79
|
+
if context&.respond_to?(url)
|
|
80
|
+
context.send(url)
|
|
81
|
+
elsif context&.respond_to?(:spree)
|
|
82
|
+
context.spree.send(url) rescue url.to_s
|
|
83
|
+
else
|
|
84
|
+
url.to_s
|
|
85
|
+
end
|
|
86
|
+
when Proc
|
|
87
|
+
# Evaluate proc in the context where route helpers are available
|
|
88
|
+
if context&.respond_to?(:instance_exec)
|
|
89
|
+
context.instance_exec(&url)
|
|
90
|
+
else
|
|
91
|
+
url.call
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
url
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Resolve label (handles i18n keys)
|
|
99
|
+
def resolve_label
|
|
100
|
+
return label unless label.is_a?(String) || label.is_a?(Symbol)
|
|
101
|
+
|
|
102
|
+
# Use Spree.t for translation which handles the spree namespace
|
|
103
|
+
Spree.t(label, default: label.to_s.humanize)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Compute badge value
|
|
107
|
+
# @param view_context [Object] View context with access to helper methods
|
|
108
|
+
def badge_value(view_context = nil)
|
|
109
|
+
return nil unless badge
|
|
110
|
+
|
|
111
|
+
if badge.respond_to?(:call)
|
|
112
|
+
# Evaluate badge in view context if available (for access to helpers)
|
|
113
|
+
if view_context&.respond_to?(:instance_exec)
|
|
114
|
+
view_context.instance_exec(&badge)
|
|
115
|
+
else
|
|
116
|
+
badge.call
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
badge
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check if this is a section header
|
|
124
|
+
def section?
|
|
125
|
+
section_label.present?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Add a child item
|
|
129
|
+
def add_child(item)
|
|
130
|
+
children << item
|
|
131
|
+
item.parent_key = key
|
|
132
|
+
sort_children!
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Remove a child item
|
|
136
|
+
def remove_child(key)
|
|
137
|
+
children.reject! { |child| child.key == key }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Sort children by position
|
|
141
|
+
def sort_children!
|
|
142
|
+
children.sort_by! { |child| [child.position, child.key.to_s] }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Deep clone for modifications
|
|
146
|
+
def deep_clone
|
|
147
|
+
cloned = self.class.new(key, to_h)
|
|
148
|
+
cloned.children = children.map(&:deep_clone)
|
|
149
|
+
cloned
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Convert to hash
|
|
153
|
+
def to_h
|
|
154
|
+
{
|
|
155
|
+
label: label,
|
|
156
|
+
url: url,
|
|
157
|
+
icon: icon,
|
|
158
|
+
position: position,
|
|
159
|
+
parent: parent_key,
|
|
160
|
+
active: active_condition,
|
|
161
|
+
condition: condition,
|
|
162
|
+
badge: badge,
|
|
163
|
+
badge_class: badge_class,
|
|
164
|
+
tooltip: tooltip,
|
|
165
|
+
target: target,
|
|
166
|
+
data_attributes: data_attributes,
|
|
167
|
+
section_label: section_label
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def inspect
|
|
172
|
+
"#<Spree::Admin::Navigation::Item key=#{key} label=#{label} children=#{children.size}>"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class Navigation
|
|
4
|
+
attr_reader :items, :context
|
|
5
|
+
|
|
6
|
+
def initialize(context)
|
|
7
|
+
@context = context
|
|
8
|
+
@items = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Add a navigation item
|
|
12
|
+
def add(key, **options, &block)
|
|
13
|
+
key = key.to_sym
|
|
14
|
+
item = Item.new(key, **options)
|
|
15
|
+
|
|
16
|
+
@items[key] = item
|
|
17
|
+
|
|
18
|
+
# If block provided, it's for children
|
|
19
|
+
if block_given?
|
|
20
|
+
builder = Builder.new(self, item)
|
|
21
|
+
# Support both block styles: |nav| nav.add or just add
|
|
22
|
+
if block.arity > 0
|
|
23
|
+
# Block expects parameter: do |nav| nav.add ... end
|
|
24
|
+
block.call(builder)
|
|
25
|
+
else
|
|
26
|
+
# Block uses implicit self: do add ... end
|
|
27
|
+
builder.instance_eval(&block)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sort_items!
|
|
32
|
+
item
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Remove a navigation item
|
|
36
|
+
def remove(key)
|
|
37
|
+
key = key.to_sym
|
|
38
|
+
removed = @items.delete(key)
|
|
39
|
+
|
|
40
|
+
# Also remove from any parent's children
|
|
41
|
+
@items.each_value do |item|
|
|
42
|
+
item.remove_child(key)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
removed
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Update an existing navigation item
|
|
49
|
+
def update(key, **options)
|
|
50
|
+
key = key.to_sym
|
|
51
|
+
item = @items[key]
|
|
52
|
+
|
|
53
|
+
return nil unless item
|
|
54
|
+
|
|
55
|
+
options.each do |attr, value|
|
|
56
|
+
item.send("#{attr}=", value) if item.respond_to?("#{attr}=")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
sort_items!
|
|
60
|
+
item
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Find a navigation item
|
|
64
|
+
def find(key)
|
|
65
|
+
@items[key.to_sym]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check if item exists
|
|
69
|
+
def exists?(key)
|
|
70
|
+
@items.key?(key.to_sym)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Insert item before another item
|
|
74
|
+
def insert_before(target_key, new_key, **options)
|
|
75
|
+
target = find(target_key)
|
|
76
|
+
return nil unless target
|
|
77
|
+
|
|
78
|
+
new_position = target.position - 1
|
|
79
|
+
add(new_key, **options.merge(position: new_position))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Insert item after another item
|
|
83
|
+
def insert_after(target_key, new_key, **options)
|
|
84
|
+
target = find(target_key)
|
|
85
|
+
return nil unless target
|
|
86
|
+
|
|
87
|
+
new_position = target.position + 1
|
|
88
|
+
add(new_key, **options.merge(position: new_position))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Move item to a new position
|
|
92
|
+
def move(key, position: nil, before: nil, after: nil)
|
|
93
|
+
item = find(key)
|
|
94
|
+
return nil unless item
|
|
95
|
+
|
|
96
|
+
if before
|
|
97
|
+
target = find(before)
|
|
98
|
+
item.position = target.position - 1 if target
|
|
99
|
+
elsif after
|
|
100
|
+
target = find(after)
|
|
101
|
+
item.position = target.position + 1 if target
|
|
102
|
+
elsif position == :first
|
|
103
|
+
item.position = -999
|
|
104
|
+
elsif position == :last
|
|
105
|
+
item.position = 999
|
|
106
|
+
elsif position.is_a?(Integer)
|
|
107
|
+
item.position = position
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
sort_items!
|
|
111
|
+
item
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Replace an item
|
|
115
|
+
def replace(key, **options, &block)
|
|
116
|
+
remove(key)
|
|
117
|
+
add(key, **options, &block)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Get all root items (items without a parent)
|
|
121
|
+
def root_items
|
|
122
|
+
@items.values.select { |item| item.parent_key.nil? }.sort_by { |item| [item.position, item.key.to_s] }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get all items that are visible to the user
|
|
126
|
+
def visible_items(user = nil, parent_key = nil)
|
|
127
|
+
items_to_filter = if parent_key
|
|
128
|
+
find(parent_key)&.children || []
|
|
129
|
+
else
|
|
130
|
+
root_items
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
items_to_filter.select { |item| item.visible?(user) }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Build tree structure
|
|
137
|
+
def build_tree
|
|
138
|
+
# First, clear all children
|
|
139
|
+
@items.each_value { |item| item.children.clear }
|
|
140
|
+
|
|
141
|
+
# Then rebuild the tree
|
|
142
|
+
@items.each_value do |item|
|
|
143
|
+
if item.parent_key && (parent = @items[item.parent_key])
|
|
144
|
+
parent.add_child(item)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
root_items
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Clear all items
|
|
152
|
+
def clear
|
|
153
|
+
@items.clear
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Get all registered paths (for settings_area? detection)
|
|
157
|
+
def registered_paths(context = nil)
|
|
158
|
+
@items.values.map { |item| item.resolve_url(context) }.compact
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Add a section
|
|
162
|
+
def section(key, label: nil, &block)
|
|
163
|
+
# Create a section header item
|
|
164
|
+
section_item = add(key, section_label: label || key.to_s.humanize, position: @items.size * 100)
|
|
165
|
+
|
|
166
|
+
if block_given?
|
|
167
|
+
builder = Builder.new(self, section_item)
|
|
168
|
+
builder.instance_eval(&block)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
section_item
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Deep clone the registry
|
|
175
|
+
def deep_clone
|
|
176
|
+
cloned = self.class.new(context)
|
|
177
|
+
@items.each do |key, item|
|
|
178
|
+
cloned.items[key] = item.deep_clone
|
|
179
|
+
end
|
|
180
|
+
cloned.build_tree
|
|
181
|
+
cloned
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def sort_items!
|
|
187
|
+
# Sort items by position, then rebuild tree
|
|
188
|
+
@items = @items.sort_by { |_key, item| [item.position, item.key.to_s] }.to_h
|
|
189
|
+
build_tree
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
</head>
|
|
7
7
|
|
|
8
8
|
<body class="admin min-vh-100 <%= controller_name %> <%= action_name %>"
|
|
9
|
-
data-controller="admin"
|
|
9
|
+
data-controller="admin sidebar"
|
|
10
10
|
data-action="keydown.ctrl+s->admin#save keydown.meta+s->admin#save"
|
|
11
11
|
>
|
|
12
12
|
<%= render_admin_partials(:body_start_partials) %>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<%= dialog_header(Spree.t(:edit) + ' ' + Spree.t(:image)) %>
|
|
3
3
|
<%= form_with model: @asset, url: spree.admin_asset_path(@asset), method: :put, scope: :asset do |f| %>
|
|
4
4
|
<div class="dialog-body pb-0" data-turbo-permanent id="asset-<%= @asset.key.parameterize %>">
|
|
5
|
-
<%=
|
|
5
|
+
<%= f.spree_file_field :attachment, width: 200, height: 200, can_delete: false, label: Spree.t(:image) %>
|
|
6
6
|
</div>
|
|
7
7
|
<div class="dialog-body" style="min-height: 200px">
|
|
8
8
|
<%= f.spree_text_area :alt, label: Spree.t(:alt_text), rows: 4 %>
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
<div class="card mb-3">
|
|
2
2
|
<div class="card-body">
|
|
3
|
-
|
|
4
|
-
<%= f.label :url, Spree.t(:domain) %>
|
|
5
|
-
<div class="form-control d-flex align-items-center py-0 focus-shadow focus-border">
|
|
6
|
-
<span class="text-muted">https://</span>
|
|
7
|
-
<%= f.text_field :url, class: 'form-control-plaintext pr-0 pl-1', autofocus: f.object.new_record?, required: true %>
|
|
8
|
-
</div>
|
|
9
|
-
<%= f.error_message_on :url %>
|
|
10
|
-
</div>
|
|
3
|
+
<%= f.spree_text_field :url, label: Spree.t(:domain), prepend: 'https://', autofocus: f.object.new_record?, required: true %>
|
|
11
4
|
|
|
12
|
-
|
|
13
|
-
<%= f.check_box :default, class: 'custom-control-input' %>
|
|
14
|
-
<%= f.label :default, class: 'custom-control-label font-weight-semibold' %>
|
|
15
|
-
<p class="form-text mb-0">We will use this domain in emails to customers.</p>
|
|
16
|
-
<%= f.error_message_on :default %>
|
|
17
|
-
</div>
|
|
5
|
+
<%= f.spree_check_box :default, help: 'We will use this domain in emails to customers.' %>
|
|
18
6
|
</div>
|
|
19
7
|
</div>
|
|
@@ -5,18 +5,23 @@
|
|
|
5
5
|
url: filter_form_url,
|
|
6
6
|
class: "filter-wrap",
|
|
7
7
|
data: {
|
|
8
|
-
controller: "filters
|
|
9
|
-
filters_url_value: request.url
|
|
10
|
-
dropdown_placement_value: "bottom-end"
|
|
8
|
+
controller: "filters dialog",
|
|
9
|
+
filters_url_value: request.url
|
|
11
10
|
} do |f| %>
|
|
12
|
-
|
|
11
|
+
<%= hidden_field_tag :frame_name, frame_name if frame_name.present? %>
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
<div class="d-flex flex-column flex-lg-row gap-2">
|
|
14
|
+
<%= render 'spree/admin/shared/filters_search_bar', param: :code_i_cont, label: Spree.t(:code) %>
|
|
15
|
+
<%= render "table_filter_dropdown" %>
|
|
16
|
+
<%= button_tag type: 'button', class: 'btn btn-light d-flex align-items-center', data: { action: 'dialog#open' } do %>
|
|
17
|
+
<%= icon "adjustments", class: "mr-1" %>
|
|
18
|
+
<%= Spree.t("admin.filters") %>
|
|
19
|
+
<% end %>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<dialog class="drawer" data-dialog-target="dialog" id="gift-card-filters-drawer">
|
|
23
|
+
<%= drawer_header(Spree.t(:filter), 'dialog') %>
|
|
24
|
+
<div class="drawer-body">
|
|
20
25
|
<% if params[:user_id].blank? %>
|
|
21
26
|
<div class="form-group">
|
|
22
27
|
<%= f.label :users_email_eq, Spree.t(:email) %>
|
|
@@ -30,16 +35,19 @@
|
|
|
30
35
|
</div>
|
|
31
36
|
|
|
32
37
|
<%= render_admin_partials(:gift_cards_filters_partials, f: f) %>
|
|
33
|
-
|
|
34
|
-
<div class="form-actions">
|
|
35
|
-
<%= turbo_save_button_tag Spree.t(:filter_results) do %>
|
|
36
|
-
<%= icon("search") %>
|
|
37
|
-
<%= Spree.t(:filter_results) %>
|
|
38
|
-
<% end %>
|
|
39
38
|
</div>
|
|
40
|
-
|
|
39
|
+
<div class="drawer-footer">
|
|
40
|
+
<%= drawer_discard_button('dialog') %>
|
|
41
|
+
<div class="form-actions">
|
|
42
|
+
<%= turbo_save_button_tag Spree.t(:filter_results) do %>
|
|
43
|
+
<%= icon("search") %>
|
|
44
|
+
<%= Spree.t(:filter_results) %>
|
|
45
|
+
<% end %>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</dialog>
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
<%= render "spree/admin/shared/filter_badge_template" %>
|
|
43
51
|
|
|
44
52
|
<div data-filters-target="badgesContainer" class="filter-badges-container"></div>
|
|
45
53
|
<% end %>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<%= icon 'plus' %>
|
|
13
13
|
<%= Spree.t(:new_gift_card) %>
|
|
14
14
|
<% end %>
|
|
15
|
-
<%= dropdown_menu
|
|
15
|
+
<%= dropdown_menu do %>
|
|
16
16
|
<%= link_to spree.new_admin_gift_card_path,
|
|
17
17
|
class: 'text-left dropdown-item' do %>
|
|
18
18
|
<%= icon 'add' %>
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
<%= Spree.t(:customer) %>
|
|
5
5
|
</h5>
|
|
6
6
|
<% if can?(:update_customer, @order) || can?(:update_addresses, @order) %>
|
|
7
|
-
<%= dropdown id: 'customer-edit-dropdown', class: 'ml-auto' do %>
|
|
7
|
+
<%= dropdown id: 'customer-edit-dropdown', class: 'ml-auto', portal: false do %>
|
|
8
8
|
<%= dropdown_toggle class: 'btn-light btn-sm px-1', data: { test_id: 'dropdown-toggle' } do %>
|
|
9
9
|
<%= icon 'dots-vertical', class: 'mr-0' %>
|
|
10
10
|
<% end %>
|
|
11
|
-
<%= dropdown_menu
|
|
11
|
+
<%= dropdown_menu do %>
|
|
12
12
|
<% if can?(:update_customer, @order) && !@order.user.present? %>
|
|
13
13
|
<%= link_to Spree.t('admin.edit_contact_information'), spree.edit_admin_order_contact_information_path(@order), class: 'dropdown-item', data: { turbo_frame: 'dialog', action: 'dialog#open' } %>
|
|
14
14
|
<% end %>
|
|
@@ -14,33 +14,38 @@
|
|
|
14
14
|
url: search_form_path,
|
|
15
15
|
class: "filter-wrap",
|
|
16
16
|
data: {
|
|
17
|
-
controller: "filters
|
|
18
|
-
filters_url_value: request.url
|
|
19
|
-
dropdown_placement_value: "bottom-end"
|
|
17
|
+
controller: "filters dialog",
|
|
18
|
+
filters_url_value: request.url
|
|
20
19
|
} do |f| %>
|
|
21
|
-
|
|
20
|
+
<%= hidden_field_tag :frame_name, frame_name if frame_name.present? %>
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
<div class="d-flex flex-column flex-lg-row gap-2">
|
|
23
|
+
<%= render 'spree/admin/shared/filters_search_bar', param: :multi_search %>
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
<% if controller_name == 'checkouts' %>
|
|
26
|
+
<%= render "spree/admin/shared/calendar_range_picker",
|
|
27
|
+
date_from_input_name: "q[created_at_gt]",
|
|
28
|
+
date_to_input_name: "q[created_at_lt]",
|
|
29
|
+
date_from_value: params.dig(:q, :created_at_gt),
|
|
30
|
+
date_to_value: params.dig(:q, :created_at_lt) %>
|
|
31
|
+
<% else %>
|
|
32
|
+
<%= render "spree/admin/shared/calendar_range_picker",
|
|
33
|
+
date_from_input_name: "q[completed_at_gt]",
|
|
34
|
+
date_to_input_name: "q[completed_at_lt]",
|
|
35
|
+
date_from_value: params.dig(:q, :completed_at_gt),
|
|
36
|
+
date_to_value: params.dig(:q, :completed_at_lt) %>
|
|
37
|
+
<% end %>
|
|
38
|
+
|
|
39
|
+
<%= render "spree/admin/orders/table_filter_dropdown" %>
|
|
40
|
+
<%= button_tag type: 'button', class: 'btn btn-light d-flex align-items-center', data: { action: 'dialog#open' } do %>
|
|
41
|
+
<%= icon "adjustments", class: "mr-1" %>
|
|
42
|
+
<%= Spree.t("admin.filters") %>
|
|
43
|
+
<% end %>
|
|
44
|
+
</div>
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
<%=
|
|
42
|
-
|
|
43
|
-
<div data-dropdown-target="menu" id="table-filter" class="hidden">
|
|
46
|
+
<dialog class="drawer" data-dialog-target="dialog" id="order-filters-drawer">
|
|
47
|
+
<%= drawer_header(Spree.t(:filter), 'dialog') %>
|
|
48
|
+
<div class="drawer-body">
|
|
44
49
|
<%= f.hidden_field :payment_state_not_eq, value: params.dig(:q, :payment_state_not_eq) %>
|
|
45
50
|
<%= f.hidden_field :state_not_eq, value: params.dig(:q, :state_not_eq) %>
|
|
46
51
|
<%= f.hidden_field :user_id_eq, value: @user.try(:id) %>
|
|
@@ -99,8 +104,12 @@
|
|
|
99
104
|
</div>
|
|
100
105
|
|
|
101
106
|
<%= render_admin_partials(:orders_filters_partials, f: f) %>
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
</div>
|
|
108
|
+
<div class="drawer-footer">
|
|
109
|
+
<%= drawer_discard_button('dialog') %>
|
|
110
|
+
<%= render 'spree/admin/shared/filter_submit' %>
|
|
111
|
+
</div>
|
|
112
|
+
</dialog>
|
|
104
113
|
|
|
105
114
|
<%= render "spree/admin/shared/filter_badge_template" %>
|
|
106
115
|
<div data-filters-target="badgesContainer" class="filter-badges-container"></div>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
</span>
|
|
7
7
|
<%= order_filter_dropdown_value %>
|
|
8
8
|
<% end %>
|
|
9
|
-
<%= dropdown_menu
|
|
9
|
+
<%= dropdown_menu do %>
|
|
10
10
|
<%= active_link_to Spree.t('admin.orders.all_orders'), params.to_unsafe_h.deep_merge({q: {shipment_state_eq: '', shipment_state_not_in: '', state_eq: '', state_in: '', refunded: '', partially_refunded: '' }}), class: 'dropdown-item', active: (params[:q] || {}).values_at(*%w[payment_state_not_eq shipment_state_not_in shipment_state_eq state_eq state_in refunded partially_refunded] ).all?(&:blank?) %>
|
|
11
11
|
<%= link_to params.to_unsafe_h.deep_merge({q: {shipment_state_not_in: [:shipped, :canceled], shipment_state_eq: '', state_eq: '', state_in: '', refunded: '', partially_refunded: ''}}),
|
|
12
12
|
class: "dropdown-item d-flex align-items-center justify-content-between #{'active' if params[:q][:shipment_state_not_in] == ['shipped', 'canceled']}" do %>
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
<%=
|
|
2
|
-
|
|
3
|
-
<%= f.label :preferred_image_alt, Spree.t(:alt_text) %>
|
|
4
|
-
<%= f.text_field :preferred_image_alt, data: { action: 'auto-submit#submit' }, class: 'form-control' %>
|
|
5
|
-
</div>
|
|
1
|
+
<%= f.spree_file_field :asset, width: 512, height: 512, auto_submit: true %>
|
|
2
|
+
<%= f.spree_text_field :preferred_image_alt, label: Spree.t(:alt_text), data: { action: 'auto-submit#submit' } %>
|
|
6
3
|
<% content_for(:design_tab) do %>
|
|
7
4
|
<%= render 'spree/admin/page_builder/labeled_range_input',
|
|
8
5
|
f: f, field: :preferred_height, min: 1, max: 1024, unit: 'px', _label: 'Max image height on desktop'
|