kaui 4.0.14 → 4.0.16

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.
@@ -63,6 +63,7 @@ module Kaui
63
63
 
64
64
  @overdue = flash[:overdue_deleted] ? nil : wait(fetch_overdue)
65
65
  @overdue_xml = flash[:overdue_deleted] ? nil : wait(fetch_overdue_xml)
66
+ @overdue_config_exists = @overdue_xml.present? || (@overdue&.overdue_states.present? && !@overdue.has_states)
66
67
  @tenant_plugin_config = begin
67
68
  wait(fetch_tenant_plugin_config)
68
69
  rescue StandardError
@@ -288,13 +289,19 @@ module Kaui
288
289
  options = tenant_options_for_client
289
290
  options[:api_key] = @tenant.api_key
290
291
  options[:api_secret] = @tenant.api_secret
292
+ @overdue_config_exists = false
291
293
  begin
292
294
  @overdue = Kaui::Overdue.get_overdue_json(options)
293
295
  @overdue_corrupted = false
296
+ @overdue_config_exists = @overdue.overdue_states.present? && !@overdue.has_states
297
+ rescue KillBillClient::API::NotFound
298
+ @overdue = KillBillClient::Model::Overdue.new.tap { |o| o.overdue_states = [] }
299
+ @overdue_corrupted = false
294
300
  rescue StandardError => e
295
301
  Rails.logger.warn("Failed to load overdue configuration for tenant #{@tenant.id}: #{e.class}: #{e.message}")
296
302
  @overdue = KillBillClient::Model::Overdue.new.tap { |o| o.overdue_states = [] }
297
303
  @overdue_corrupted = true
304
+ @overdue_config_exists = true
298
305
  flash.now[:warning] = 'The existing overdue configuration is corrupted and cannot be loaded. Use the XML upload below to replace it with a valid configuration.'
299
306
  end
300
307
  end
@@ -342,6 +349,24 @@ module Kaui
342
349
  redirect_to admin_tenant_path(current_tenant.id, active_tab: 'OverdueShow'), notice: I18n.t('flashes.notices.overdue_uploaded_successfully')
343
350
  end
344
351
 
352
+ def delete_overdue_config
353
+ current_tenant = safely_find_tenant_by_id(params[:id])
354
+
355
+ options = tenant_options_for_client
356
+ options[:api_key] = current_tenant.api_key
357
+ options[:api_secret] = current_tenant.api_secret
358
+
359
+ begin
360
+ Kaui::AdminTenant.delete_tenant_user_key_value('OVERDUE_CONFIG', options[:username], nil, comment, options)
361
+ rescue StandardError => e
362
+ flash[:error] = "Failed to delete overdue config: #{as_string(e)}"
363
+ redirect_to admin_tenant_new_overdue_config_path(id: current_tenant.id) and return
364
+ end
365
+
366
+ flash[:overdue_deleted] = true
367
+ redirect_to admin_tenant_path(current_tenant.id, active_tab: 'OverdueShow'), notice: I18n.t('flashes.notices.overdue_deleted_successfully')
368
+ end
369
+
345
370
  def upload_invoice_template
346
371
  current_tenant = safely_find_tenant_by_id(params[:id])
347
372
 
@@ -38,7 +38,8 @@ class Kaui::EngineController < ApplicationController
38
38
  end
39
39
 
40
40
  def populate_account_details
41
- @account ||= params[:account_id].present? ? Kaui::Account.find_by_id(params[:account_id], false, false, options_for_klient) : Kaui::Account.new
41
+ account_id = scalar_account_id_param
42
+ @account ||= account_id.present? ? Kaui::Account.find_by_id(account_id, false, false, options_for_klient) : Kaui::Account.new
42
43
  end
43
44
 
44
45
  def retrieve_tenants_for_current_user
@@ -37,8 +37,8 @@ module Kaui
37
37
  end
38
38
 
39
39
  def paginate(searcher, data_extractor, formatter, table_default_columns = [])
40
- search_key = (params[:search] || {})[:value].presence
41
- advance_search_query = params[:advance_search_query].presence
40
+ search_key = normalize_search_key((params[:search] || {})[:value]).presence
41
+ advance_search_query = normalize_search_key(params[:advance_search_query]).presence
42
42
 
43
43
  search_key = advance_search_query if advance_search_query
44
44
  search_key = handle_balance_search(search_key) if search_key.present?
@@ -84,6 +84,30 @@ module Kaui
84
84
  end
85
85
  end
86
86
 
87
+ def advanced_search_query?(search_key)
88
+ search_key.to_s.include?('_q')
89
+ end
90
+
91
+ def scalar_account_id_param
92
+ path_account_id = request.path_parameters[:account_id].presence
93
+ return path_account_id if path_account_id.present?
94
+
95
+ account_id = params[:account_id]
96
+ account_id if account_id.is_a?(String) || account_id.is_a?(Numeric)
97
+ end
98
+
99
+ def normalize_search_key(search_key)
100
+ return if search_key.blank?
101
+
102
+ if search_key.respond_to?(:to_unsafe_h)
103
+ search_key.to_unsafe_h.to_query
104
+ elsif search_key.is_a?(Hash)
105
+ search_key.to_query
106
+ else
107
+ search_key
108
+ end
109
+ end
110
+
87
111
  def promise(&)
88
112
  # Evaluation starts immediately
89
113
  ::Concurrent::Promises.future do
@@ -4,7 +4,7 @@ require 'csv'
4
4
  module Kaui
5
5
  class InvoicesController < Kaui::EngineController
6
6
  def index
7
- @search_query = params[:account_id]
7
+ @search_query = scalar_account_id_param
8
8
  @advance_search_query = params[:q] || request.query_string
9
9
  @ordering = params[:ordering] || 'desc'
10
10
  @offset = params[:offset] || 0
@@ -15,7 +15,7 @@ module Kaui
15
15
  end
16
16
 
17
17
  def download
18
- account_id = params[:account_id]
18
+ account_id = scalar_account_id_param
19
19
  start_date = params[:startDate]
20
20
  end_date = params[:endDate]
21
21
  all_fields_checked = params[:allFieldsChecked] == 'true'
@@ -60,7 +60,7 @@ module Kaui
60
60
 
61
61
  searcher = lambda do |search_key, offset, limit|
62
62
  account = begin
63
- Kaui::Account.find_by_id_or_key(search_key, false, false, cached_options_for_klient)
63
+ Kaui::Account.find_by_id_or_key(search_key, false, false, cached_options_for_klient) unless advanced_search_query?(search_key)
64
64
  rescue StandardError
65
65
  nil
66
66
  end
@@ -5,7 +5,7 @@ require 'csv'
5
5
  module Kaui
6
6
  class PaymentsController < Kaui::EngineController
7
7
  def index
8
- @search_query = params[:q] || params[:account_id]
8
+ @search_query = params[:q] || scalar_account_id_param
9
9
  @advance_search_query = params[:q] || request.query_string
10
10
  @ordering = params[:ordering] || (@search_query.blank? ? 'desc' : 'asc')
11
11
  @offset = params[:offset] || 0
@@ -16,7 +16,7 @@ module Kaui
16
16
  end
17
17
 
18
18
  def download
19
- account_id = params[:account_id]
19
+ account_id = scalar_account_id_param
20
20
  start_date = params[:startDate]
21
21
  end_date = params[:endDate]
22
22
  all_fields_checked = params[:allFieldsChecked] == 'true'
@@ -104,7 +104,7 @@ module Kaui
104
104
  payments = Kaui::Payment.list_or_search(payment_state, offset, limit, options_for_klient)
105
105
  else
106
106
  account = begin
107
- Kaui::Account.find_by_id_or_key(search_key, false, false, options_for_klient)
107
+ Kaui::Account.find_by_id_or_key(search_key, false, false, options_for_klient) unless advanced_search_query?(search_key)
108
108
  rescue StandardError
109
109
  nil
110
110
  end
@@ -1,212 +1,10 @@
1
- <div class="modal fade kaui-accounts-index-filterbar" id="advanceSearchModal" tabindex="-1" role="dialog" aria-labelledby="advanceSearchModalLabel" aria-hidden="true">
2
- <div class="modal-dialog" role="document">
3
- <div class="modal-content">
4
- <div class="modal-header">
5
- <h5 class="modal-title d-flex align-items-center gap-3" id="advanceSearchModalLabel">
6
- <span class="icon-container">
7
- <%= image_tag("kaui/modal/search.svg", width: 20, height: 20) %>
8
- </span>
9
- Advanced Search
10
- </h5>
11
- <button type="button" class="close close-button custom-hover" data-bs-dismiss="modal" aria-label="Close">
12
- <span aria-hidden="true">
13
- <%= image_tag("kaui/modal/close.svg", width: 20, height: 20) %>
14
- </span>
15
- </button>
16
- </div>
17
- <div class="modal-body">
18
- <form id="advanceSearchForm">
19
- <div class="form-group d-flex align-items-center">
20
- <label for="searchFieldSelect" class="mr-2 field-label" style="width: 30%;">Search Field</label>
21
- <select id="searchFieldSelect" class="form-control mr-2">
22
- <% @search_fields.each do |value, title| %>
23
- <option value="<%= value %>"><%= title %></option>
24
- <% end %>
25
- </select>
26
- <button type="button" class="border-button custom-hover" id="addSearchField">
27
- <%= image_tag("kaui/modal/plus.svg", width: 16, height: 16) %>
28
- </button>
29
- </div>
30
- <div id="search-fields-container">
31
- </div>
32
- <div id="save-search-container" style="display: none;">
33
- <hr class="mt-4 mb-3">
34
- <div class="form-group d-flex align-items-center mb-0">
35
- <label for="savedSearchName" class="mr-2 field-label" style="width: 30%;">Save As Name</label>
36
- <input type="text" id="savedSearchName" class="form-control flex-grow-1" placeholder="Enter a name for this search...">
37
- </div>
38
- </div>
39
- </form>
40
- </div>
41
- <div class="modal-footer">
42
- <%= render "kaui/components/button/button", {
43
- label: 'Save Search As...',
44
- variant: "outline-secondary d-inline-flex align-items-center gap-1",
45
- type: "button",
46
- html_class: "kaui-button custom-hover",
47
- html_options: {
48
- id: "saveAdvanceSearch"
49
- }
50
- } %>
51
- <%= render "kaui/components/button/button", {
52
- label: 'Clear Search',
53
- variant: "outline-secondary d-inline-flex align-items-center gap-1",
54
- type: "button",
55
- html_class: "kaui-button custom-hover",
56
- html_options: {
57
- id: "clearAdvanceSearch"
58
- }
59
- } %>
60
- <%= render "kaui/components/button/button", {
61
- label: 'Apply Search',
62
- variant: "outline-secondary d-inline-flex align-items-center gap-1",
63
- type: "button",
64
- html_class: "kaui-dropdown custom-hover",
65
- html_options: {
66
- id: "applyAdvanceSearch"
67
- }
68
- } %>
69
- </div>
70
- </div>
71
- </div>
72
- </div>
73
-
74
- <template id="search-field-template" class="kaui-accounts-index-filterbar">
75
- <div class="form-group row align-items-center justify-content-between search-field">
76
- <label class="col-sm-3 col-form-label search-field-label"></label>
77
- <div class="col-sm-4">
78
- <select class="form-control search-field-filter">
79
- <option value="eq">Equals</option>
80
- <option value="neq">Not Equals</option>
81
- <option value="gt">Greater Than</option>
82
- <option value="gte">Greater Than Or Equal</option>
83
- <option value="lt">Less Than</option>
84
- <option value="lte">Less Than Or Equal</option>
85
- <option value="like">Like</option>
86
- </select>
87
- </div>
88
- <div class="col-sm-4">
89
- <input type="text" class="form-control search-field-value">
90
- </div>
91
- <button type="button" class="button custom-hover" id="remove-search-field">
92
- <%= image_tag("kaui/modal/red-close.svg", width: 16, height: 16) %>
93
- </button>
94
- </div>
95
- </template>
96
-
97
- <%= javascript_tag do %>
98
- $(document).ready(function() {
99
-
100
- populateSearchLabelsFromUrl();
101
- var dateFields = ['Created date', 'Updated date', 'Reference time'];
102
- // Handle the "Add" button click to add new search fields
103
- $('#addSearchField').on('click', function() {
104
- var selectedField = $('#searchFieldSelect option:selected').text();
105
- var template = document.getElementById('search-field-template').content.cloneNode(true);
106
-
107
- // Set the label and input names based on the selected field
108
- template.querySelector('.search-field-label').textContent = selectedField.replace(/([A-Z])/g, ' $1').trim();
109
- template.querySelector('.search-field-filter').name = selectedField + 'Filter';
110
-
111
- // Check if the field should use a date input
112
- if (dateFields.includes(selectedField)) {
113
- template.querySelector('.search-field-value').type = 'date';
114
- } else {
115
- template.querySelector('.search-field-value').type = 'text';
116
- }
117
- template.querySelector('.search-field-value').name = selectedField;
118
-
119
- // Append the new search field to the container
120
- document.getElementById('search-fields-container').appendChild(template);
121
- });
122
-
123
- // Handle the "Apply Search" button click inside the modal
124
- $('#applyAdvanceSearch').on('click', function() {
125
- var searchFields = $('.search-field');
126
- var searchLabelsContainer = $('#search-labels-container');
127
- searchLabelsContainer.empty();
128
-
129
- // Validate that at least one search field has a value
130
- var hasValue = false;
131
- searchFields.each(function() {
132
- var value = $(this).find('.search-field-value').val().trim();
133
- if (value !== '') {
134
- hasValue = true;
135
- return false; // Break the loop
136
- }
137
- });
138
-
139
- // If no search field has a value, show an alert and prevent search
140
- if (!hasValue) {
141
- alert('Please enter a value for at least one search field.');
142
- return;
143
- }
144
-
145
- var table = $('#accounts-table').DataTable();
146
- table.off('preXhr.dt.filter');
147
- table.on('preXhr.dt.filter', function(e, settings, data) {
148
- data.search.value = searchQuery('');
149
- });
150
-
151
- // Update the DataTables ajax URL with the new search parameters
152
- var searchParams = searchQuery('');
153
- var ajaxUrl = "<%= accounts_pagination_path(:ordering => @ordering, :format => :json) %>";
154
- if (searchParams) {
155
- ajaxUrl += (ajaxUrl.includes('?') ? '&' : '?') + searchParams;
156
- }
157
- table.ajax.url(ajaxUrl).load();
158
-
159
- // Update the URL with the search parameters
160
- if (searchParams) {
161
- searchParams = searchParams.replace(/account_id/g, 'ac_id');
162
- var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + searchParams;
163
- window.history.pushState({ path: newUrl }, '', newUrl);
164
- }
165
-
166
- searchFields.each(function() {
167
- var filter = $(this).find('.search-field-filter option:selected').text().trim();
168
- var value = $(this).find('.search-field-value').val().trim();
169
- var columnName = $(this).find('.search-field-filter').attr('name').replace('Filter', '').trim();
170
-
171
- // Create and append the search label
172
- if (value !== '') {
173
- var label = $('<span>', {
174
- class: 'label label-info d-inline-flex align-items-center gap-2',
175
- 'data-field': columnName,
176
- 'data-filter': filter,
177
- 'data-value': value
178
- });
179
- var labelText = $('<span>', { text: columnName + ' [' + filter + '] ' + value });
180
- var closeIcon = $('<span>', {
181
- class: 'filter-close-icon',
182
- style: 'cursor: pointer; margin-left: 5px; display: inline-flex; align-items: center;'
183
- }).html('<svg width="12" height="12" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15.8337 4.1665L4.16699 15.8332M4.16699 4.1665L15.8337 15.8332" stroke="#A4A7AE" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>');
184
- label.append(labelText).append(closeIcon);
185
- searchLabelsContainer.append(label);
186
- }
187
- });
188
-
189
- $('#advanceSearchModal').modal('hide');
190
- });
191
-
192
- // Populate the modal with the current filters when it is shown
193
- $('#advanceSearchModal').on('show.bs.modal', function() {
194
- showAdvanceSearchModal();
195
- $('#search-fields-container .search-field').each(function() {
196
- var input = $(this).find('.search-field-value');
197
- if (dateFields.includes(input.attr('name'))) {
198
- input.attr('type', 'date');
199
- }
200
- });
201
- });
202
-
203
- $('#clearAdvanceSearch').on('click', function() {
204
- clearAdvanceSearch();
205
- });
206
-
207
- // Handle the remove icon click event to remove search fields
208
- $('#search-fields-container').on('click', '#remove-search-field', function() {
209
- $(this).closest('.search-field').remove();
210
- });
211
- });
212
- <% end %>
1
+ <%= render "kaui/shared/advanced_search_filterbar", {
2
+ modal_class: "kaui-accounts-index-filterbar",
3
+ search_fields: @search_fields,
4
+ date_fields: ['Created date', 'Updated date', 'Reference time'],
5
+ number_fields: [],
6
+ table_selector: '#accounts-table',
7
+ pagination_path: accounts_pagination_path(:ordering => @ordering, :format => :json),
8
+ search_query: '',
9
+ balance_notice: false
10
+ } %>
@@ -103,7 +103,13 @@ $(document).ready(function() {
103
103
  "serverSide": true,
104
104
  "search": {"search": "<%= @search_query %>"},
105
105
  "ajax": {
106
- url: "<%= accounts_pagination_path(:ordering => @ordering, :format => :json, :advance_search_query => @advance_search_query) %>",
106
+ type: "POST",
107
+ url: "<%= accounts_pagination_path(:ordering => @ordering, :format => :json) %>",
108
+ headers: {"X-CSRF-Token": $("meta[name='csrf-token']").attr("content")},
109
+ data: function(d) {
110
+ d.advance_search_query = "<%= j(@advance_search_query.to_s) %>";
111
+ return d;
112
+ },
107
113
  dataSrc: function(json) {
108
114
  var colOrder = table.colReorder.order();
109
115
  var reorderedData = json.data.map(function(row) {
@@ -127,9 +133,10 @@ $(document).ready(function() {
127
133
  // DataTable request with the correct filters.
128
134
  if (window.location.search.includes('_q=1') && '<%= j(@advance_search_query.to_s.strip) %>' === '') {
129
135
  var urlSearch = window.location.search.substring(1).replace(/ac_id/g, 'account_id');
130
- var ajaxUrl = "<%= accounts_pagination_path(:ordering => @ordering, :format => :json) %>" + '?' + urlSearch;
136
+ var ajaxUrl = "<%= accounts_pagination_path(:ordering => @ordering, :format => :json) %>";
131
137
  table.on('preXhr.dt.filter', function(e, settings, data) {
132
138
  data.search.value = urlSearch;
139
+ data.advance_search_query = urlSearch;
133
140
  });
134
141
  table.ajax.url(ajaxUrl).load();
135
142
  }
@@ -6,6 +6,17 @@
6
6
  <h2>Existing Overdue Config</h2>
7
7
  </div>
8
8
  <span>
9
+ <% if @overdue_config_exists && can?(:config_upload, Kaui::AdminTenant) %>
10
+ <% overdue_delete_confirmation = t('admin_tenants.delete_overdue_config_confirmation') %>
11
+ <%= link_to kaui_engine.admin_tenant_delete_overdue_config_path(id: @tenant.id), method: :delete, data: { confirm: overdue_delete_confirmation }, class: "text-decoration-none" do %>
12
+ <%= render "kaui/components/button/button", {
13
+ label: "Delete",
14
+ variant: "outline-secondary d-inline-flex align-items-center gap-1",
15
+ type: "button",
16
+ html_class: "kaui-button dots-menu custom-hover",
17
+ } %>
18
+ <% end %>
19
+ <% end %>
9
20
  <%= link_to "javascript:void(0);", onclick: "submit_overdue_xml();", class:"text-decoration-none" do %>
10
21
  <%= render "kaui/components/button/button", {
11
22
  label: "XML View",
@@ -1,4 +1,8 @@
1
- <div class="modal fade" id="advanceSearchModal" tabindex="-1" role="dialog" aria-labelledby="advanceSearchModalLabel" aria-hidden="true">
1
+ <div class="modal fade kaui-advanced-search-modal" id="advanceSearchModal" tabindex="-1" role="dialog" aria-labelledby="advanceSearchModalLabel" aria-hidden="true"
2
+ data-save-search-label="<%= t('advanced_search.actions.save_search_as') %>"
3
+ data-save-label="<%= t('advanced_search.actions.save') %>"
4
+ data-empty-value-message="<%= t('advanced_search.validation.empty_value') %>"
5
+ data-missing-field-message="<%= t('advanced_search.validation.missing_field') %>">
2
6
  <div class="modal-dialog" role="document">
3
7
  <div class="modal-content">
4
8
  <div class="modal-header">
@@ -6,9 +10,9 @@
6
10
  <span class="icon-container">
7
11
  <%= image_tag("kaui/modal/search.svg", width: 20, height: 20) %>
8
12
  </span>
9
- Advanced Search
13
+ <%= t('advanced_search.title') %>
10
14
  </h5>
11
- <button type="button" class="close close-button custom-hover" data-bs-dismiss="modal" aria-label="Close">
15
+ <button type="button" class="close close-button custom-hover" data-bs-dismiss="modal" aria-label="<%= t('advanced_search.actions.close') %>">
12
16
  <span aria-hidden="true">
13
17
  <%= image_tag("kaui/modal/close.svg", width: 20, height: 20) %>
14
18
  </span>
@@ -16,49 +20,54 @@
16
20
  </div>
17
21
  <div class="modal-body">
18
22
  <form id="advanceSearchForm">
19
- <div class="form-group d-flex align-items-center">
20
- <label for="searchFieldSelect" class="mr-2 field-label" style="width: 30%;">Search Field</label>
21
- <select id="searchFieldSelect" class="form-control mr-2">
22
- <% Kaui::Bundle::SEARCH_FIELDS.each do |value, title| %>
23
- <option value="<%= value %>" <%= 'selected' if @search_by == value %>><%= title %></option>
24
- <% end %>
25
- </select>
26
- </div>
27
- <div class="form-group d-flex align-items-center mt-3">
28
- <label for="bundleSearchValue" class="mr-2 field-label" style="width: 30%;">Value</label>
29
- <input type="text" id="bundleSearchValue" class="form-control" placeholder="Enter search value..." value="<%= @search_query %>">
23
+ <div class="advanced-search-builder">
24
+ <div class="advanced-search-group">
25
+ <div class="advanced-search-query-row">
26
+ <select id="searchFieldSelect" class="form-control" aria-label="<%= t('advanced_search.search_field') %>">
27
+ <option value=""><%= t('advanced_search.select_placeholder') %></option>
28
+ <% Kaui::Bundle::SEARCH_FIELDS.each do |value, title| %>
29
+ <option value="<%= value %>" <%= 'selected' if @search_query.present? && @search_by == value %>><%= title %></option>
30
+ <% end %>
31
+ </select>
32
+ <input type="text" id="bundleSearchValue" class="form-control" aria-label="<%= t('advanced_search.search_value') %>" placeholder="<%= t('advanced_search.value_placeholder') %>" value="<%= @search_query %>">
33
+ </div>
34
+ </div>
30
35
  </div>
31
36
  <div id="save-search-container" style="display: none;">
32
37
  <hr class="mt-4 mb-3">
33
38
  <div class="form-group d-flex align-items-center mb-0">
34
- <label for="savedSearchName" class="mr-2 field-label" style="width: 30%;">Save As Name</label>
35
- <input type="text" id="savedSearchName" class="form-control flex-grow-1" placeholder="Enter a name for this search...">
39
+ <label for="savedSearchName" class="mr-2 field-label" style="width: 30%;"><%= t('advanced_search.save_as_name') %></label>
40
+ <input type="text" id="savedSearchName" class="form-control flex-grow-1" placeholder="<%= t('advanced_search.save_name_placeholder') %>">
36
41
  </div>
37
42
  </div>
38
43
  </form>
39
44
  </div>
40
45
  <div class="modal-footer">
41
- <%= render "kaui/components/button/button", {
42
- label: 'Save Search As...',
43
- variant: "outline-secondary d-inline-flex align-items-center gap-1",
44
- type: "button",
45
- html_class: "kaui-button custom-hover",
46
- html_options: { id: "saveAdvanceSearch" }
47
- } %>
48
- <%= render "kaui/components/button/button", {
49
- label: 'Clear Search',
50
- variant: "outline-secondary d-inline-flex align-items-center gap-1",
51
- type: "button",
52
- html_class: "kaui-button custom-hover",
53
- html_options: { id: "clearAdvanceSearch" }
54
- } %>
55
- <%= render "kaui/components/button/button", {
56
- label: 'Apply Search',
57
- variant: "outline-secondary d-inline-flex align-items-center gap-1",
58
- type: "button",
59
- html_class: "kaui-dropdown custom-hover",
60
- html_options: { id: "applyAdvanceSearch" }
61
- } %>
46
+ <div class="advanced-search-footer-left">
47
+ <%= render "kaui/components/button/button", {
48
+ label: t('advanced_search.actions.save_search_as'),
49
+ variant: "advanced-search-button advanced-search-button-secondary d-inline-flex align-items-center gap-1",
50
+ type: "button",
51
+ html_class: "custom-hover",
52
+ html_options: { id: "saveAdvanceSearch" }
53
+ } %>
54
+ </div>
55
+ <div class="advanced-search-footer-right">
56
+ <%= render "kaui/components/button/button", {
57
+ label: t('advanced_search.actions.clear_search'),
58
+ variant: "advanced-search-button advanced-search-button-ghost d-inline-flex align-items-center gap-1",
59
+ type: "button",
60
+ html_class: "custom-hover",
61
+ html_options: { id: "clearAdvanceSearch" }
62
+ } %>
63
+ <%= render "kaui/components/button/button", {
64
+ label: t('advanced_search.actions.apply_search'),
65
+ variant: "advanced-search-button advanced-search-button-primary d-inline-flex align-items-center gap-1",
66
+ type: "button",
67
+ html_class: "custom-hover",
68
+ html_options: { id: "applyAdvanceSearch" }
69
+ } %>
70
+ </div>
62
71
  </div>
63
72
  </div>
64
73
  </div>
@@ -69,7 +78,7 @@ $(document).ready(function() {
69
78
  // Pre-populate modal from current URL params on open
70
79
  $('#advanceSearchModal').on('show.bs.modal', function() {
71
80
  var params = new URLSearchParams(window.location.search);
72
- var searchBy = params.get('search_by') || 'bundle_id';
81
+ var searchBy = params.get('search_by') || '';
73
82
  var q = params.get('q') || '';
74
83
  $('#searchFieldSelect').val(searchBy);
75
84
  $('#bundleSearchValue').val(q);
@@ -79,7 +88,15 @@ $(document).ready(function() {
79
88
  $('#applyAdvanceSearch').on('click', function() {
80
89
  var searchBy = $('#searchFieldSelect').val();
81
90
  var q = $('#bundleSearchValue').val().trim();
82
- if (!q) return;
91
+ if (!searchBy) {
92
+ alert($('#advanceSearchModal').data('missing-field-message'));
93
+ return;
94
+ }
95
+
96
+ if (!q) {
97
+ alert($('#advanceSearchModal').data('empty-value-message'));
98
+ return;
99
+ }
83
100
 
84
101
  var url = new URL(window.location.href);
85
102
  url.searchParams.set('search_by', searchBy);