<% if show_search_input %>
- <%= render partial: "avo/partials/resource_search", locals: {resource: @resource.route_key, via_reflection: via_reflection} %>
+ <%#= render partial: "avo/partials/resource_search", locals: {resource: @resource.route_key, via_reflection: via_reflection} %>
+
+
<% else %>
<%# Offset for the space-y-2 property when the search is missing %>
diff --git a/app/controllers/avo/base_controller.rb b/app/controllers/avo/base_controller.rb
index efc350ab3..6ac5f05eb 100644
--- a/app/controllers/avo/base_controller.rb
+++ b/app/controllers/avo/base_controller.rb
@@ -36,6 +36,9 @@ def index
@query = @query.includes(*@resource.includes)
end
+ # Apply the search query if configured on the resource
+ safe_call :apply_search
+
# Eager load attachments
if @resource.attachments.present?
@resource.attachments.each do |attachment|
@@ -64,6 +67,38 @@ def index
end
set_component_for __method__
+
+ respond_to do |format|
+ format.html
+ format.turbo_stream do
+ render turbo_stream: [
+ turbo_stream.replace(
+ "#{@resource.model_key}_list",
+ partial: "avo/index/resource_table_component",
+ locals: {
+ resources: @resources,
+ resource: @resource,
+ reflection: @reflection,
+ parent_record: @parent_record,
+ parent_resource: @parent_resource,
+ pagy: @pagy,
+ query: @query,
+ actions: @actions
+ }
+ ),
+ turbo_stream.replace("#{@resource.model_key}_pagination") do
+ Avo::Current.view_context.render Avo::PaginatorComponent.new(
+ pagy: @pagy,
+ turbo_frame: @turbo_frame,
+ index_params: @index_params,
+ resource: @resource,
+ parent_record: @parent_record,
+ parent_resource: @parent_resource
+ )
+ end
+ ]
+ end
+ end
end
def show
@@ -325,17 +360,31 @@ def set_index_params
@index_params = {}
set_pagination_params
+ set_search_params
+ set_sorting_params
+ set_view_type_params
- # Sorting
- @index_params[:sort_by] = params[:sort_by] || @resource.sort_by_param
+ validate_view_type
+ end
+
+ def set_search_params
+ @index_params[:q] = params[:q] if params[:q].present?
+ end
+ def set_sorting_params
+ @index_params[:sort_by] = params[:sort_by] || @resource.sort_by_param
@index_params[:sort_direction] = params[:sort_direction] || @resource.default_sort_direction
+ end
- # View types
+ def set_view_type_params
available_view_types = @resource.available_view_types
@index_params[:available_view_types] = available_view_types
- @index_params[:view_type] = if params[:view_type].present?
+ @index_params[:view_type] = determine_view_type(available_view_types)
+ end
+
+ def determine_view_type(available_view_types)
+ if params[:view_type].present?
params[:view_type]
elsif available_view_types.size == 1
available_view_types.first
@@ -346,8 +395,10 @@ def set_index_params
view: @view
).handle
end
+ end
- if available_view_types.exclude? @index_params[:view_type].to_sym
+ def validate_view_type
+ if @index_params[:available_view_types].exclude? @index_params[:view_type].to_sym
raise "View type '#{@index_params[:view_type]}' is unavailable for #{@resource.class}."
end
end
@@ -660,5 +711,19 @@ def set_pagination_params
def set_query
@query ||= @resource.class.query_scope
end
+
+ def apply_search
+ return if @resource.class.search_query.nil?
+ return if @index_params[:q].nil?
+
+ search_query = @resource.search[:query]
+ return unless search_query.present?
+
+ @query = Avo::ExecutionContext.new(
+ target: @resource.class.search_query,
+ params: params.merge(q: @index_params[:q]),
+ query: @query
+ ).handle
+ end
end
end
diff --git a/app/javascript/js/controllers.js b/app/javascript/js/controllers.js
index 5e07add73..3fc2e70a7 100644
--- a/app/javascript/js/controllers.js
+++ b/app/javascript/js/controllers.js
@@ -36,6 +36,7 @@ import RecordSelectorController from './controllers/record_selector_controller'
import ReloadBelongsToFieldController from './controllers/fields/reload_belongs_to_field_controller'
import ResourceEditController from './controllers/resource_edit_controller'
import ResourceIndexController from './controllers/resource_index_controller'
+import ResourceSearchController from './controllers/resource_search_controller'
import ResourceShowController from './controllers/resource_show_controller'
import SearchController from './controllers/search_controller'
import SelectController from './controllers/select_controller'
@@ -82,6 +83,7 @@ application.register('preview', PreviewController)
application.register('record-selector', RecordSelectorController)
application.register('resource-edit', ResourceEditController)
application.register('resource-index', ResourceIndexController)
+application.register('resource-search', ResourceSearchController)
application.register('resource-show', ResourceShowController)
application.register('search', SearchController)
application.register('select-filter', SelectFilterController)
diff --git a/app/javascript/js/controllers/resource_search_controller.js b/app/javascript/js/controllers/resource_search_controller.js
new file mode 100644
index 000000000..e40c837ac
--- /dev/null
+++ b/app/javascript/js/controllers/resource_search_controller.js
@@ -0,0 +1,68 @@
+import { Controller } from '@hotwired/stimulus'
+import { get } from '@rails/request.js'
+
+export default class extends Controller {
+ static targets = ['input']
+
+ static values = {
+ debounce: { type: Number, default: 300 },
+ }
+
+ connect() {
+ console.log('Resource search controller connected')
+ }
+
+ search() {
+ this.debouncedSearch()
+ }
+
+ debouncedSearch = this.debounce(this.performSearch, this.debounceValue)
+
+ async performSearch() {
+ const query = this.inputTarget.value
+ const currentUrl = new URL(window.location.href)
+
+ // Get existing search params
+ const searchParams = new URLSearchParams(window.location.search)
+
+ // Update the search parameter
+ if (query) {
+ searchParams.set('q', query)
+ } else {
+ searchParams.delete('q')
+ }
+
+ // Reset to first page when searching
+ searchParams.set('page', '1')
+
+ // Construct the new URL with all parameters
+ const newUrl = `${currentUrl.pathname}?${searchParams.toString()}`
+
+ // Replace current URL without affecting browser history
+ window.history.replaceState({}, '', newUrl)
+
+ try {
+ await get(newUrl, {
+ responseKind: 'turbo-stream',
+ headers: {
+ Accept: 'text/vnd.turbo-stream.html',
+ },
+ })
+ } catch (error) {
+ console.error('Error performing search:', error)
+ }
+ }
+
+ debounce(func, wait) {
+ let timeout
+
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout)
+ func.apply(this, args)
+ }
+ clearTimeout(timeout)
+ timeout = setTimeout(later, wait)
+ }
+ }
+}
diff --git a/app/views/avo/index/_resource_table_component.html.erb b/app/views/avo/index/_resource_table_component.html.erb
new file mode 100644
index 000000000..a935fae12
--- /dev/null
+++ b/app/views/avo/index/_resource_table_component.html.erb
@@ -0,0 +1,10 @@
+<%= render Avo::Index::ResourceTableComponent.new(
+ resources: resources,
+ resource: resource,
+ reflection: reflection,
+ parent_record: parent_record,
+ parent_resource: parent_resource,
+ pagy: pagy,
+ query: query,
+ actions: actions
+) %>
diff --git a/package.json b/package.json
index 10ef9549b..76e6cea51 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"@hotwired/stimulus": "^3.2.2",
"@hotwired/turbo-rails": "^8.0.13",
"@rails/activestorage": "^6.1.710",
+ "@rails/request.js": "^0.0.11",
"@stimulus-components/clipboard": "^5.0.0",
"@stimulus-components/password-visibility": "^3.0.0",
"@tailwindcss/container-queries": "^0.1.1",
diff --git a/yarn.lock b/yarn.lock
index 33643750c..1746619ad 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1754,6 +1754,11 @@
dependencies:
spark-md5 "^3.0.0"
+"@rails/request.js@^0.0.11":
+ version "0.0.11"
+ resolved "https://registry.yarnpkg.com/@rails/request.js/-/request.js-0.0.11.tgz#4d9be25a49d97911c64ccd0f00b79d57fca4c3b4"
+ integrity sha512-2U3uYS0kbljt+pAstN+LIlZOl7xmOKig5N6FrvtUWO1wq0zR1Hf90fHfD2SYiyV8yH1nyKpoTmbLqWT0xe1zDg==
+
"@remirror/core-constants@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f"