spree_cm_commissioner 2.3.0.pre.pre16 → 2.3.0.pre.pre17
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/Gemfile.lock +1 -1
- data/README.md +33 -0
- data/app/controllers/blazer/base_controller_decorator.rb +20 -1
- data/app/controllers/spree/api/v2/storefront/intercity_taxi/draft_orders_controller.rb +40 -0
- data/app/controllers/spree/api/v2/storefront/trip_search_controller.rb +47 -9
- data/app/controllers/spree_cm_commissioner/admin/variants_controller_decorator.rb +1 -1
- data/app/factory/spree_cm_commissioner/vendor_telegram_message_factory.rb +65 -0
- data/app/helpers/spree/base_helper_decorator.rb +2 -19
- data/app/interactors/spree_cm_commissioner/vendor_creation_telegram_alert_sender.rb +28 -0
- data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +1 -1
- data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +1 -1
- data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +1 -1
- data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +1 -1
- data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +1 -1
- data/app/jobs/spree_cm_commissioner/vendor_creation_telegram_alert_sender_job.rb +10 -0
- data/app/models/concerns/spree_cm_commissioner/line_item_transitable.rb +51 -0
- data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +7 -0
- data/app/models/concerns/spree_cm_commissioner/option_value_attr_type.rb +25 -0
- data/app/models/concerns/spree_cm_commissioner/route_order_countable.rb +3 -14
- data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +18 -4
- data/app/models/spree_cm_commissioner/block.rb +19 -0
- data/app/models/spree_cm_commissioner/product_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/variant_decorator.rb +0 -5
- data/app/models/spree_cm_commissioner/variant_options.rb +8 -0
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +6 -0
- data/app/overrides/spree/admin/variants/_form/add_permanent_stock.html.erb.deface +2 -1
- data/app/serializers/spree_cm_commissioner/v2/storefront/intercity_taxi_cart_serializer.rb +12 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/intercity_taxi_line_item_serializer.rb +31 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/trip_result_serializer.rb +11 -0
- data/app/services/spree_cm_commissioner/intercity_taxi_order/create.rb +67 -0
- data/app/services/spree_cm_commissioner/intercity_taxi_order/update.rb +79 -0
- data/app/services/spree_cm_commissioner/{transit/base_route_order_metrics_updater.rb → routes/base_update_order_metrics.rb} +2 -2
- data/app/services/spree_cm_commissioner/{transit/route_previous_trip_count_decrementer_service.rb → routes/decrement_previous_trip_count.rb} +3 -3
- data/app/services/spree_cm_commissioner/{transit/route_trip_count_decrementer_service.rb → routes/decrement_trip_count.rb} +2 -2
- data/app/services/spree_cm_commissioner/{transit/route_fulfilled_order_count_incrementer_service.rb → routes/increment_fulfilled_order_count.rb} +3 -3
- data/app/services/spree_cm_commissioner/{transit/route_order_count_incrementer_service.rb → routes/increment_order_count.rb} +3 -3
- data/app/services/spree_cm_commissioner/{transit/route_trip_count_incrementer_service.rb → routes/increment_trip_count.rb} +2 -2
- data/app/services/spree_cm_commissioner/trips/search.rb +65 -0
- data/app/views/blazer/queries/embed/_content.html.erb +51 -2
- data/app/views/spree/admin/option_types/_color_field.html.erb +21 -0
- data/app/views/spree/admin/option_types/_option_value_fields.html.erb +2 -0
- data/app/views/spree/admin/variants/_color_field.html.erb +28 -0
- data/app/views/spree/admin/variants/_date_field.html.erb +11 -1
- data/app/views/spree/admin/variants/_default_field.html.erb +7 -3
- data/app/views/spree/admin/variants/_option_values.html.erb +5 -1
- data/app/views/spree/admin/variants/_time_field.html.erb +7 -1
- data/app/views/spree/order_mailer/_adjustment.html.erb +7 -0
- data/app/views/spree_cm_commissioner/event_transactional_mailer/_event_banner.html.erb +9 -3
- data/config/locales/km.yml +64 -0
- data/config/routes.rb +4 -1
- data/lib/spree_cm_commissioner/test_helper/factories/option_type_factory.rb +18 -0
- data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +30 -0
- data/lib/spree_cm_commissioner/trip_result.rb +15 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +20 -8
|
@@ -68,11 +68,6 @@ module SpreeCmCommissioner
|
|
|
68
68
|
)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
# override
|
|
72
|
-
def options_text
|
|
73
|
-
@options_text ||= Spree::Variants::VisableOptionsPresenter.new(self).to_sentence
|
|
74
|
-
end
|
|
75
|
-
|
|
76
71
|
def selected_option_value_ids
|
|
77
72
|
option_value_variants.pluck(:option_value_id)
|
|
78
73
|
end
|
|
@@ -139,6 +139,14 @@ module SpreeCmCommissioner
|
|
|
139
139
|
@seat_number_layouts ||= option_value_name_for(option_type_name: 'seat-number-layouts')&.split(',')
|
|
140
140
|
end
|
|
141
141
|
|
|
142
|
+
def color
|
|
143
|
+
@color ||= option_value_name_for(option_type_name: 'color')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def ticket_type
|
|
147
|
+
@ticket_type ||= option_value_name_for(option_type_name: 'ticket-type')
|
|
148
|
+
end
|
|
149
|
+
|
|
142
150
|
def seat_type
|
|
143
151
|
@seat_type ||= option_value_name_for(option_type_name: 'seat-type')
|
|
144
152
|
end
|
|
@@ -16,6 +16,8 @@ module SpreeCmCommissioner
|
|
|
16
16
|
|
|
17
17
|
base.before_save :generate_code, if: :code.nil?
|
|
18
18
|
|
|
19
|
+
base.after_create :send_telegram_vendor_creation_alert
|
|
20
|
+
|
|
19
21
|
base.has_many :photos, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::VendorPhoto'
|
|
20
22
|
base.has_many :option_values, through: :products
|
|
21
23
|
base.has_many :vendor_option_types, class_name: 'SpreeCmCommissioner::VendorOptionType'
|
|
@@ -201,6 +203,10 @@ module SpreeCmCommissioner
|
|
|
201
203
|
vendor_kind_option_values.where(option_type_id: rules_option_type.id)
|
|
202
204
|
end
|
|
203
205
|
|
|
206
|
+
def send_telegram_vendor_creation_alert
|
|
207
|
+
VendorCreationTelegramAlertSenderJob.perform_later(id)
|
|
208
|
+
end
|
|
209
|
+
|
|
204
210
|
delegate :present?, to: :tenant, prefix: true
|
|
205
211
|
end
|
|
206
212
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<!-- insert_bottom "[data-hook='admin_variant_form_additional_fields']" -->
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<%# deprecated no longer used, should be removed entirely along with table column, just hide for now. %>
|
|
4
|
+
<div class="form-group d-none">
|
|
4
5
|
<%= f.field_container :permanent_stock do %>
|
|
5
6
|
<%= f.label :permanent_stock, Spree.t(:permanent_stock) %>
|
|
6
7
|
<%= f.number_field :permanent_stock, class: 'form-control' %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module V2
|
|
3
|
+
module Storefront
|
|
4
|
+
class IntercityTaxiCartSerializer < Spree::V2::Storefront::CartSerializer
|
|
5
|
+
has_many :line_items,
|
|
6
|
+
serializer: SpreeCmCommissioner::V2::Storefront::IntercityTaxiLineItemSerializer
|
|
7
|
+
|
|
8
|
+
cache_options store: nil
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
data/app/serializers/spree_cm_commissioner/v2/storefront/intercity_taxi_line_item_serializer.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module V2
|
|
3
|
+
module Storefront
|
|
4
|
+
class IntercityTaxiLineItemSerializer < BaseSerializer
|
|
5
|
+
attributes :pickup_place_name,
|
|
6
|
+
:drop_off_place_name,
|
|
7
|
+
:pickup_lat,
|
|
8
|
+
:pickup_lng,
|
|
9
|
+
:drop_off_lat,
|
|
10
|
+
:drop_off_lng,
|
|
11
|
+
:passenger_count,
|
|
12
|
+
:distance_km,
|
|
13
|
+
:ordered_points,
|
|
14
|
+
:base_km,
|
|
15
|
+
:detour_pickup_km,
|
|
16
|
+
:detour_dropoff_km,
|
|
17
|
+
:extra_pickup_km,
|
|
18
|
+
:extra_dropoff_km,
|
|
19
|
+
:extra_pickup_charge_usd,
|
|
20
|
+
:extra_dropoff_charge_usd,
|
|
21
|
+
:estimated_time_minutes,
|
|
22
|
+
:from_date,
|
|
23
|
+
:to_date,
|
|
24
|
+
:direction,
|
|
25
|
+
:trip_id
|
|
26
|
+
|
|
27
|
+
cache_options store: nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -4,6 +4,17 @@ module SpreeCmCommissioner
|
|
|
4
4
|
class TripResultSerializer < BaseSerializer
|
|
5
5
|
set_type :trip_result
|
|
6
6
|
|
|
7
|
+
# Enable serializer-level caching
|
|
8
|
+
# Longer TTL than controller cache (5min) because:
|
|
9
|
+
# - Individual trips change less frequently
|
|
10
|
+
# - Same trip can be reused across multiple searches
|
|
11
|
+
# - Cache invalidation handled by cache_key_with_version
|
|
12
|
+
cache_options(
|
|
13
|
+
store: Rails.cache,
|
|
14
|
+
namespace: 'trip-result-serializer',
|
|
15
|
+
expires_in: 30.minutes
|
|
16
|
+
)
|
|
17
|
+
|
|
7
18
|
attributes :origin_place, :destination_place, :boarding, :drop_off, :price, :compare_at_amount, :currency, :quantity_available, :max_capacity,
|
|
8
19
|
:allow_seat_selection, :departure_time, :route_type, :distance
|
|
9
20
|
attribute :duration_in_hms, &:duration_in_hms
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module IntercityTaxiOrder
|
|
3
|
+
class Create
|
|
4
|
+
attr_reader :trip_id, :from_date, :to_date, :user_id, :quantity
|
|
5
|
+
|
|
6
|
+
def self.call(trip_id:, from_date:, to_date:, user_id: nil, quantity: 1)
|
|
7
|
+
new(
|
|
8
|
+
trip_id: trip_id,
|
|
9
|
+
from_date: from_date,
|
|
10
|
+
to_date: to_date,
|
|
11
|
+
user_id: user_id,
|
|
12
|
+
quantity: quantity
|
|
13
|
+
).call
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(trip_id:, from_date:, to_date:, user_id:, quantity:)
|
|
17
|
+
@trip_id = trip_id
|
|
18
|
+
@from_date = from_date
|
|
19
|
+
@to_date = to_date
|
|
20
|
+
@user_id = user_id
|
|
21
|
+
@quantity = quantity.to_i <= 0 ? 1 : quantity.to_i
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
validate!
|
|
26
|
+
|
|
27
|
+
ActiveRecord::Base.transaction do
|
|
28
|
+
trip = SpreeCmCommissioner::Trip.find(trip_id)
|
|
29
|
+
variant = trip.product.variants.first
|
|
30
|
+
raise ArgumentError, 'No variant found for trip' if variant.blank?
|
|
31
|
+
|
|
32
|
+
order = Spree::Order.new(state: 'cart', use_billing: true, user_id: user_id)
|
|
33
|
+
|
|
34
|
+
line_item = order.line_items.new(
|
|
35
|
+
variant_id: variant.id,
|
|
36
|
+
quantity: quantity,
|
|
37
|
+
from_date: from_date,
|
|
38
|
+
to_date: to_date,
|
|
39
|
+
private_metadata: {
|
|
40
|
+
trip_id: trip.id
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
build_guests_for!(line_item, quantity)
|
|
45
|
+
|
|
46
|
+
order.save!
|
|
47
|
+
order.update_with_updater!
|
|
48
|
+
|
|
49
|
+
order
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def validate!
|
|
56
|
+
raise ArgumentError, 'trip_id is required' if trip_id.blank?
|
|
57
|
+
raise ArgumentError, 'from_date is required' if from_date.blank?
|
|
58
|
+
raise ArgumentError, 'to_date is required' if to_date.blank?
|
|
59
|
+
raise ArgumentError, 'to_date must be after from_date' if from_date.present? && to_date.present? && to_date < from_date
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_guests_for!(line_item, qty)
|
|
63
|
+
Array.new(qty) { line_item.guests.new }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module IntercityTaxiOrder
|
|
3
|
+
class Update
|
|
4
|
+
attr_reader :order_number, :params
|
|
5
|
+
|
|
6
|
+
def self.call(order_number:, params:)
|
|
7
|
+
new(order_number: order_number, params: params).call
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(order_number:, params:)
|
|
11
|
+
@order_number = order_number
|
|
12
|
+
@params = ensure_parameters(params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
order = Spree::Order.find_by!(number: order_number)
|
|
17
|
+
order.update!(order_params)
|
|
18
|
+
|
|
19
|
+
create_billing_address_if_missing(order)
|
|
20
|
+
|
|
21
|
+
order
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def ensure_parameters(params)
|
|
27
|
+
return params if params.is_a?(::ActionController::Parameters)
|
|
28
|
+
|
|
29
|
+
::ActionController::Parameters.new(params)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create_billing_address_if_missing(order)
|
|
33
|
+
return if order.bill_address.present?
|
|
34
|
+
|
|
35
|
+
SpreeCmCommissioner::BillingAddressCreator.call(order: order)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def order_params
|
|
39
|
+
params.require(:order).permit(
|
|
40
|
+
:intel_phone_number,
|
|
41
|
+
:email,
|
|
42
|
+
line_items_attributes: [
|
|
43
|
+
:id,
|
|
44
|
+
:pickup_place_name,
|
|
45
|
+
:drop_off_place_name,
|
|
46
|
+
:from_date,
|
|
47
|
+
:pickup_lat,
|
|
48
|
+
:pickup_lng,
|
|
49
|
+
:pickup_oob_confirmed,
|
|
50
|
+
:drop_off_lat,
|
|
51
|
+
:drop_off_lng,
|
|
52
|
+
:drop_off_oob_confirmed,
|
|
53
|
+
:distance_km,
|
|
54
|
+
:ordered_points,
|
|
55
|
+
:base_km,
|
|
56
|
+
:detour_pickup_km,
|
|
57
|
+
:detour_dropoff_km,
|
|
58
|
+
:extra_pickup_km,
|
|
59
|
+
:extra_dropoff_km,
|
|
60
|
+
:extra_pickup_charge_usd,
|
|
61
|
+
:extra_dropoff_charge_usd,
|
|
62
|
+
:estimated_time_minutes,
|
|
63
|
+
:remark,
|
|
64
|
+
:passenger_count,
|
|
65
|
+
{ guests_attributes: %i[
|
|
66
|
+
id
|
|
67
|
+
first_name
|
|
68
|
+
last_name
|
|
69
|
+
gender
|
|
70
|
+
nationality_id
|
|
71
|
+
age
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
|
-
module
|
|
3
|
-
class
|
|
2
|
+
module Routes
|
|
3
|
+
class DecrementPreviousTripCount
|
|
4
4
|
prepend ::Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
6
|
def call(previous_route_id:)
|
|
@@ -12,7 +12,7 @@ module SpreeCmCommissioner
|
|
|
12
12
|
# Build a trip-like object that responds to :route and :vendor so the delegated
|
|
13
13
|
# service can locate vendor routes correctly.
|
|
14
14
|
trip_like = Struct.new(:route, :vendor).new(previous_route, previous_route.vendors.first)
|
|
15
|
-
result = SpreeCmCommissioner::
|
|
15
|
+
result = SpreeCmCommissioner::Routes::DecrementTripCount.call(trip: trip_like)
|
|
16
16
|
return result if result.failure?
|
|
17
17
|
else
|
|
18
18
|
previous_route.with_lock do
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
|
-
module
|
|
3
|
-
class
|
|
2
|
+
module Routes
|
|
3
|
+
class IncrementFulfilledOrderCount
|
|
4
4
|
prepend ::Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
6
|
def call(order:)
|
|
7
7
|
return failure(nil, 'Order not found') unless order
|
|
8
8
|
|
|
9
|
-
SpreeCmCommissioner::
|
|
9
|
+
SpreeCmCommissioner::Routes::BaseUpdateOrderMetrics
|
|
10
10
|
.call(order: order, attribute: :fulfilled_order_count)
|
|
11
11
|
|
|
12
12
|
success(order: order)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
|
-
module
|
|
3
|
-
class
|
|
2
|
+
module Routes
|
|
3
|
+
class IncrementOrderCount
|
|
4
4
|
prepend ::Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
6
|
def call(order:)
|
|
7
7
|
return failure(nil, 'Order not found') unless order
|
|
8
8
|
|
|
9
|
-
SpreeCmCommissioner::
|
|
9
|
+
SpreeCmCommissioner::Routes::BaseUpdateOrderMetrics
|
|
10
10
|
.new(order: order, attribute: :order_count)
|
|
11
11
|
.call
|
|
12
12
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Trips
|
|
3
|
+
class Search
|
|
4
|
+
prepend ::Spree::ServiceModule::Base
|
|
5
|
+
|
|
6
|
+
REFRESH_RATE_LIMIT_INTERVAL = 30.seconds
|
|
7
|
+
SEARCH_CACHE_TTL = 5.minutes
|
|
8
|
+
|
|
9
|
+
def self.cache_key_for(params, vendor)
|
|
10
|
+
new.send(:cache_key, params, vendor)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(params:, vendor:, is_intercity_taxi: false)
|
|
14
|
+
return success(results: []) unless valid_search?(params)
|
|
15
|
+
|
|
16
|
+
cache_key = cache_key(params, vendor)
|
|
17
|
+
|
|
18
|
+
results = Rails.cache.fetch(cache_key, expires_in: SEARCH_CACHE_TTL) do
|
|
19
|
+
trips = SpreeCmCommissioner::TripQuery.new(
|
|
20
|
+
origin_id: origin_id(params),
|
|
21
|
+
destination_id: destination_id(params),
|
|
22
|
+
vendor_id: vendor.id,
|
|
23
|
+
number_of_guests: params[:number_of_guests] || 1,
|
|
24
|
+
date: search_date(params).to_date
|
|
25
|
+
).call
|
|
26
|
+
|
|
27
|
+
is_intercity_taxi || taxi?(params) ? filter_intercity(trips) : trips
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
success(results: results)
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
failure(nil, e.message)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def valid_search?(params)
|
|
38
|
+
params[:origin].present? && params[:destination].present? && params[:outbound_date].present?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def filter_intercity(results)
|
|
42
|
+
Array(results).select do |res|
|
|
43
|
+
res.respond_to?(:trips) && res.trips.any? { |t| t.route_type.to_s == 'intercity_taxi' }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def origin_id(params) = outbound?(params) ? params[:origin] : params[:destination]
|
|
48
|
+
def destination_id(params) = outbound?(params) ? params[:destination] : params[:origin]
|
|
49
|
+
def search_date(params) = outbound?(params) ? params[:outbound_date] : params[:inbound_date]
|
|
50
|
+
def outbound?(params) = params[:direction] == 'outbound' || params[:direction].blank?
|
|
51
|
+
def taxi?(params) = params[:service_type] == 'intercity_taxi'
|
|
52
|
+
|
|
53
|
+
def cache_key(params, vendor)
|
|
54
|
+
[
|
|
55
|
+
'search_trips',
|
|
56
|
+
"vendor:#{vendor.id}",
|
|
57
|
+
"dir:#{params[:direction] || 'outbound'}",
|
|
58
|
+
"o:#{origin_id(params)}",
|
|
59
|
+
"d:#{destination_id(params)}",
|
|
60
|
+
"date:#{search_date(params)}"
|
|
61
|
+
].join(':')
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div class="topbar">
|
|
2
2
|
<div class="row" style="padding-top: 13px; margin: 0;">
|
|
3
3
|
<div class="col-sm-9"></div>
|
|
4
|
-
<div class="col-sm-3
|
|
4
|
+
<div class="text-right col-sm-3">
|
|
5
5
|
<%= render partial: 'blazer/queries/embed/download_button' %>
|
|
6
6
|
</div>
|
|
7
7
|
</div>
|
|
@@ -12,9 +12,58 @@
|
|
|
12
12
|
<div id="results" style="font-family: 'Poppins', sans-serif;"></div>
|
|
13
13
|
|
|
14
14
|
<script>
|
|
15
|
+
// Only apply translations for Khmer locale
|
|
16
|
+
<% if I18n.locale == :km %>
|
|
17
|
+
// Column translations from Rails i18n
|
|
18
|
+
const columnTranslations = {
|
|
19
|
+
<% I18n.t('blazer.columns', locale: I18n.locale).each do |key, value| %>
|
|
20
|
+
'<%= key %>': '<%= value %>',
|
|
21
|
+
<% end %>
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Function to translate table headers
|
|
25
|
+
function translateTableHeaders() {
|
|
26
|
+
const tables = document.querySelectorAll('#results table');
|
|
27
|
+
tables.forEach(function(table) {
|
|
28
|
+
const headers = table.querySelectorAll('thead th');
|
|
29
|
+
headers.forEach(function(header) {
|
|
30
|
+
const originalText = header.textContent.trim();
|
|
31
|
+
if (columnTranslations[originalText]) {
|
|
32
|
+
header.textContent = columnTranslations[originalText];
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
<% end %>
|
|
38
|
+
|
|
15
39
|
function showRun(data) {
|
|
16
40
|
$("#results").html(data)
|
|
17
|
-
|
|
41
|
+
|
|
42
|
+
// Apply table functionality
|
|
43
|
+
$("#results table").stupidtable(stupidtableCustomSettings)
|
|
44
|
+
|
|
45
|
+
<% if I18n.locale == :km %>
|
|
46
|
+
// Translate headers first, then apply sticky headers (Khmer only)
|
|
47
|
+
function waitForTable(callback) {
|
|
48
|
+
const checkTable = () => {
|
|
49
|
+
const table = document.querySelector('#results table');
|
|
50
|
+
if (table) {
|
|
51
|
+
callback();
|
|
52
|
+
} else {
|
|
53
|
+
setTimeout(checkTable, 50);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
checkTable();
|
|
57
|
+
}
|
|
58
|
+
waitForTable(function() {
|
|
59
|
+
translateTableHeaders();
|
|
60
|
+
// Apply sticky headers after translation to ensure proper width calculation
|
|
61
|
+
$("#results table").stickyTableHeaders({fixedOffset: 60});
|
|
62
|
+
});
|
|
63
|
+
<% else %>
|
|
64
|
+
// Default behavior for English and other locales
|
|
65
|
+
$("#results table").stickyTableHeaders({fixedOffset: 60});
|
|
66
|
+
<% end %>
|
|
18
67
|
}
|
|
19
68
|
|
|
20
69
|
function showError(message) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<div class="input-group">
|
|
2
|
+
<%= f.text_field :name, class: "form-control color-picker-input", placeholder: "eg. #FF5733", value: f.object.name, required: true, pattern: "^#[0-9A-Fa-f]{6}$", data: { target: "color-preview-#{f.object_name}" } %>
|
|
3
|
+
<span class="input-group-text p-0 color-preview" id="color-preview-<%= f.object_name %>" style="background-color: <%= f.object.name || '#ffffff' %>; border: 1px solid #dee2e6; border-left: none; width: 45px; border-radius: 0 0.375rem 0.375rem 0;"></span>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<script>
|
|
7
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
8
|
+
const colorInput = document.querySelector('[data-target="color-preview-<%= f.object_name %>"]');
|
|
9
|
+
const colorPreview = document.getElementById('color-preview-<%= f.object_name %>');
|
|
10
|
+
|
|
11
|
+
if (colorInput && colorPreview) {
|
|
12
|
+
colorInput.addEventListener('input', function() {
|
|
13
|
+
const color = this.value || '#ffffff';
|
|
14
|
+
// Validate hex color format
|
|
15
|
+
if (/^#[0-9A-Fa-f]{6}$/.test(color)) {
|
|
16
|
+
colorPreview.style.backgroundColor = color;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
<td class="name"><%= f.select :name, ['post-paid', 'pre-paid'], { include_blank: true } , class: "form-control fullwidth", required: true %></td>
|
|
32
32
|
<% when "delivery_option" %>
|
|
33
33
|
<td class="name"><%= f.select :name, [:delivery, :pickup], { include_blank: true } , class: "form-control fullwidth", required: true %></td>
|
|
34
|
+
<% when "color" %>
|
|
35
|
+
<td class="name"><%= render partial: 'color_field', locals: { f: f } %></td>
|
|
34
36
|
<% end %>
|
|
35
37
|
|
|
36
38
|
<td class="presentation"><%= f.text_field :presentation, class: "form-control", required: true %></td>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div class="input-group">
|
|
2
|
+
<div class="input-group-prepend">
|
|
3
|
+
<span class="input-group-text p-0 color-preview" id="color-preview-<%= f.object_name %>" style="background-color: <%= f.object.name || '#ffffff' %>; border: 1px solid #dee2e6; width: 45px;"></span>
|
|
4
|
+
</div>
|
|
5
|
+
<%= f.text_field :name, class: "form-control color-picker-input", placeholder: "eg. #FF5733", value: f.object.name, pattern: "^#[0-9A-Fa-f]{6}$", data: { target: "color-preview-#{f.object_name}" } %>
|
|
6
|
+
<div class="input-group-append">
|
|
7
|
+
<span class="input-group-text bg-light" style="cursor: pointer;" onclick="const input = this.closest('.input-group').querySelector('.color-picker-input'); input.value = ''; this.closest('.input-group').querySelector('.color-preview').style.backgroundColor = '#ffffff';" title="Click to clear">
|
|
8
|
+
<%= svg_icon name: "remove.svg", width: '14', height: '14' %>
|
|
9
|
+
</span>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
15
|
+
const colorInput = document.querySelector('[data-target="color-preview-<%= f.object_name %>"]');
|
|
16
|
+
const colorPreview = document.getElementById('color-preview-<%= f.object_name %>');
|
|
17
|
+
|
|
18
|
+
if (colorInput && colorPreview) {
|
|
19
|
+
colorInput.addEventListener('input', function() {
|
|
20
|
+
const color = this.value || '#ffffff';
|
|
21
|
+
// Validate hex color format
|
|
22
|
+
if (/^#[0-9A-Fa-f]{6}$/.test(color)) {
|
|
23
|
+
colorPreview.style.backgroundColor = color;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
<div class="input-group">
|
|
2
|
+
<div class="input-group-prepend">
|
|
3
|
+
<span class="input-group-text"><%= svg_icon name: "calendar.svg", width: '14', height: '14' %></span>
|
|
4
|
+
</div>
|
|
5
|
+
<%= f.date_field :name, value: f.object.date, class: 'form-control bg-transparent date-input' %>
|
|
6
|
+
<div class="input-group-append">
|
|
7
|
+
<span class="input-group-text bg-light" style="cursor: pointer;" onclick="this.closest('.input-group').querySelector('.date-input').value = '';" title="Click to clear">
|
|
8
|
+
<%= svg_icon name: "remove.svg", width: '14', height: '14' %>
|
|
9
|
+
</span>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<% option_type = f.object.option_type %>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
<% if option_type.ticket_type? %>
|
|
4
|
+
<%= f.text_field :name, class: 'form-control' %>
|
|
5
|
+
<% else %>
|
|
6
|
+
<%= f.collection_select :name, option_type.option_values, :name, :presentation,
|
|
7
|
+
{ include_blank: true },
|
|
8
|
+
{ class: 'select2-clear', id: "option_value_ids-#{option_type.id}" } %>
|
|
9
|
+
<% end %>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
<% sorted_option_values = f.object.option_values.sort_by { |ov| ov.option_type&.position || 0 } %>
|
|
2
|
+
|
|
3
|
+
<%= f.fields_for :option_values, sorted_option_values do |ff| %>
|
|
2
4
|
<% option_type = ff.object.option_type %>
|
|
3
5
|
|
|
4
6
|
<div class="form-group" data-hook="presentation">
|
|
@@ -28,6 +30,8 @@
|
|
|
28
30
|
<%= render partial: 'default_field', locals: { f: ff } %>
|
|
29
31
|
<% when "delivery_option" %>
|
|
30
32
|
<%= render partial: 'default_field', locals: { f: ff } %>
|
|
33
|
+
<% when "color" %>
|
|
34
|
+
<%= render partial: 'color_field', locals: { f: ff } %>
|
|
31
35
|
<% else %>
|
|
32
36
|
<%= render partial: 'default_field', locals: { f: ff } %>
|
|
33
37
|
<% end %>
|
|
@@ -2,5 +2,11 @@
|
|
|
2
2
|
<div class="input-group-prepend">
|
|
3
3
|
<span class="input-group-text"><%= svg_icon name: "clock.svg", width: '14', height: '14' %></span>
|
|
4
4
|
</div>
|
|
5
|
-
|
|
5
|
+
<% time_value = f.object.time.present? ? f.object.time.strftime('%H:%M') : nil %>
|
|
6
|
+
<%= f.time_field :name, value: time_value, class: 'form-control' %>
|
|
7
|
+
<div class="input-group-append">
|
|
8
|
+
<span class="input-group-text bg-light" style="cursor: pointer;" onclick="this.parentElement.parentElement.querySelector('input[type=time]').value = '';" title="Click to clear">
|
|
9
|
+
<%= svg_icon name: "remove.svg", width: '14', height: '14' %>
|
|
10
|
+
</span>
|
|
11
|
+
</div>
|
|
6
12
|
</div>
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<% if @event&.home_banner&.attachment.present? %>
|
|
2
|
+
<div class="banner">
|
|
3
|
+
<%= image_tag(
|
|
4
|
+
main_app.rails_blob_url(@event.home_banner.attachment),
|
|
5
|
+
class: "banner-image",
|
|
6
|
+
alt: t('mail.event_engagement_mailer.organizer.header_title')
|
|
7
|
+
) %>
|
|
8
|
+
</div>
|
|
9
|
+
<% end %>
|